Skip to content

Commit 701f400

Browse files
JacobCoffeeclaude
andcommitted
add post references panel and move legacy notice to top of post
Remark plugin now collects PEP, docs, PyPI, GitHub refs into remarkPluginFrontmatter. References display as grouped, color-coded chips above the post body alongside the Blogger legacy notice. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5cd13b3 commit 701f400

File tree

4 files changed

+160
-7
lines changed

4 files changed

+160
-7
lines changed

src/assets/styles/global.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,3 +1415,21 @@ body {
14151415
background-color: rgba(160, 160, 170, 0.14);
14161416
border-color: rgba(160, 160, 170, 0.32);
14171417
}
1418+
1419+
/* ── Post footer references panel ── */
1420+
1421+
.post-refs-header {
1422+
background: linear-gradient(135deg, rgba(48, 105, 152, 0.04) 0%, rgba(255, 212, 59, 0.04) 100%);
1423+
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
1424+
}
1425+
.dark .post-refs-header {
1426+
background: linear-gradient(135deg, rgba(48, 105, 152, 0.08) 0%, rgba(255, 212, 59, 0.05) 100%);
1427+
border-bottom-color: rgba(255, 255, 255, 0.06);
1428+
}
1429+
1430+
.post-refs-panel {
1431+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
1432+
}
1433+
.dark .post-refs-panel {
1434+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
1435+
}

src/layouts/BlogPostLayout.astro

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
import BaseLayout from "./BaseLayout.astro";
33
import { formatDate, slugify, withBase } from "../lib/utils";
44
5+
interface Reference {
6+
type: string;
7+
label: string;
8+
url: string;
9+
}
10+
511
interface Props {
612
title: string;
713
publishDate: string;
@@ -10,10 +16,31 @@ interface Props {
1016
description?: string | null;
1117
tags?: readonly string[];
1218
slug?: string;
19+
legacyUrl?: string | null;
20+
references?: Reference[];
1321
}
1422
1523
const isDev = import.meta.env.DEV;
16-
const { title, publishDate, updatedDate, author, description, tags, slug } = Astro.props;
24+
const { title, publishDate, updatedDate, author, description, tags, slug, legacyUrl, references = [] } = Astro.props;
25+
const bloggerUrl = legacyUrl ? `https://pythoninsider.blogspot.com${legacyUrl}` : null;
26+
27+
// Group references by type
28+
const refGroups = new Map<string, Reference[]>();
29+
for (const ref of references) {
30+
if (!refGroups.has(ref.type)) refGroups.set(ref.type, []);
31+
refGroups.get(ref.type)!.push(ref);
32+
}
33+
34+
const groupMeta: Record<string, { label: string; order: number }> = {
35+
"pep": { label: "PEPs", order: 0 },
36+
"docs": { label: "Documentation", order: 1 },
37+
"gh-repo": { label: "Repositories", order: 2 },
38+
"gh-user": { label: "People", order: 3 },
39+
"pypi": { label: "Packages", order: 4 },
40+
};
41+
42+
const sortedGroups = [...refGroups.entries()]
43+
.sort((a, b) => (groupMeta[a[0]]?.order ?? 99) - (groupMeta[b[0]]?.order ?? 99));
1744
---
1845

1946
<BaseLayout title={`${title} | Python Insider`} description={description ?? undefined}>
@@ -66,12 +93,100 @@ const { title, publishDate, updatedDate, author, description, tags, slug } = Ast
6693
<div class="mt-8 h-px" style="background: linear-gradient(to right, rgba(48, 105, 152, 0.3), rgba(255, 212, 59, 0.2), transparent);"></div>
6794
</header>
6895

96+
<!-- Blogger legacy notice -->
97+
{bloggerUrl && (
98+
<div class="not-prose mb-6 animate-fade-up stagger-1">
99+
<a
100+
href={bloggerUrl}
101+
target="_blank"
102+
rel="noopener noreferrer"
103+
class="flex items-center gap-3 rounded-lg border border-orange-200/60 bg-gradient-to-r from-orange-50 to-amber-50/50 px-4 py-3 transition-colors hover:border-orange-300/80 hover:from-orange-50 hover:to-amber-50 dark:border-orange-800/30 dark:from-orange-950/20 dark:to-amber-950/10 dark:hover:border-orange-700/40 dark:hover:from-orange-950/30 dark:hover:to-amber-950/20"
104+
>
105+
<div class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-orange-100 dark:bg-orange-900/30">
106+
<svg class="h-4 w-4 text-orange-500 dark:text-orange-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
107+
<path d="M12 8v4l3 3" />
108+
<circle cx="12" cy="12" r="10" />
109+
</svg>
110+
</div>
111+
<div class="min-w-0 flex-1">
112+
<p class="text-xs font-medium text-orange-800 dark:text-orange-300">Originally published on Blogger</p>
113+
<p class="mt-0.5 truncate text-[11px] text-orange-600/70 dark:text-orange-400/50">{bloggerUrl}</p>
114+
</div>
115+
<svg class="h-4 w-4 flex-shrink-0 text-orange-400 dark:text-orange-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
116+
<path stroke-linecap="round" stroke-linejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
117+
</svg>
118+
</a>
119+
</div>
120+
)}
121+
122+
<!-- References panel -->
123+
{sortedGroups.length > 0 && (
124+
<div class="not-prose mb-8 animate-fade-up stagger-1">
125+
<div class="post-refs-panel overflow-hidden rounded-xl border border-zinc-200/80 dark:border-zinc-700/60">
126+
<div class="post-refs-header flex items-center gap-2.5 px-5 py-3">
127+
<svg class="h-4 w-4 text-[#306998] dark:text-[#ffd43b]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
128+
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" />
129+
</svg>
130+
<span class="text-xs font-bold uppercase tracking-[0.12em] text-zinc-500 dark:text-zinc-400" style="font-family: var(--font-display);">
131+
Referenced in this post
132+
</span>
133+
<span class="ml-auto text-[10px] tabular-nums font-medium text-zinc-400 dark:text-zinc-500">
134+
{references.length}
135+
</span>
136+
</div>
137+
138+
<div class="space-y-px bg-zinc-100/50 dark:bg-zinc-800/30">
139+
{sortedGroups.map(([type, refs]) => (
140+
<div class="bg-white px-5 py-3.5 dark:bg-[#0f1117]">
141+
<div class="mb-2.5 text-[10px] font-bold uppercase tracking-[0.15em] text-zinc-400 dark:text-zinc-500">
142+
{groupMeta[type]?.label ?? type}
143+
</div>
144+
<div class="flex flex-wrap gap-1.5">
145+
{refs.map((ref) => (
146+
<a
147+
href={ref.url}
148+
class:list={[
149+
"ref-footer-chip group inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-all",
150+
type === "pep" && "bg-amber-50 text-amber-800 hover:bg-amber-100 dark:bg-amber-900/20 dark:text-amber-300 dark:hover:bg-amber-900/40",
151+
type === "docs" && "bg-blue-50 text-blue-700 hover:bg-blue-100 dark:bg-blue-900/20 dark:text-blue-300 dark:hover:bg-blue-900/40",
152+
type === "gh-repo" && "bg-zinc-100 text-zinc-700 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700",
153+
type === "gh-user" && "bg-zinc-100 text-zinc-700 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700",
154+
type === "pypi" && "bg-emerald-50 text-emerald-700 hover:bg-emerald-100 dark:bg-emerald-900/20 dark:text-emerald-300 dark:hover:bg-emerald-900/40",
155+
]}
156+
target="_blank"
157+
rel="noopener noreferrer"
158+
>
159+
{type === "pep" && (
160+
<svg class="h-3 w-3 opacity-60" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>
161+
)}
162+
{type === "docs" && (
163+
<svg class="h-3 w-3 opacity-60" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
164+
)}
165+
{(type === "gh-repo" || type === "gh-user") && (
166+
<svg class="h-3 w-3 opacity-60" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
167+
)}
168+
{type === "pypi" && (
169+
<svg class="h-3 w-3 opacity-60" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.29 7 12 12 20.71 7"/><line x1="12" x2="12" y1="22" y2="12"/></svg>
170+
)}
171+
<span class="truncate max-w-[200px]">{ref.label}</span>
172+
<svg class="h-2.5 w-2.5 opacity-0 transition-opacity group-hover:opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path d="M7 7h10v10"/><path d="M7 17 17 7"/></svg>
173+
</a>
174+
))}
175+
</div>
176+
</div>
177+
))}
178+
</div>
179+
</div>
180+
</div>
181+
)}
182+
69183
<div class="animate-fade-up stagger-2">
70184
<slot />
71185
</div>
72186

73-
<footer class="not-prose mt-14 border-t border-zinc-200 pt-8 dark:border-zinc-700">
74-
<div class="flex items-center justify-between">
187+
<footer class="not-prose mt-16">
188+
<!-- Navigation -->
189+
<div class="flex items-center justify-between border-t border-zinc-200 pt-6 dark:border-zinc-700">
75190
<a
76191
href={withBase("/blog")}
77192
class="group inline-flex items-center gap-2 text-sm font-medium text-[#306998] transition-colors hover:text-[#254f73] dark:text-[#ffd43b] dark:hover:text-[#f0c419]"

src/pages/blog/[slug].astro

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ export const getStaticPaths: GetStaticPaths = async () => {
1616
}));
1717
};
1818
19-
interface Props { post: CollectionEntry<"posts"> }
20-
const { post } = Astro.props;
21-
const { Content } = await render(post);
19+
const { post } = Astro.props as { post: CollectionEntry<"posts"> };
20+
const { Content, remarkPluginFrontmatter } = await render(post);
21+
const references = (remarkPluginFrontmatter?.references ?? []) as Array<{ type: string; label: string; url: string }>;
2222
---
2323

2424
<BlogPostLayout
@@ -29,6 +29,8 @@ const { Content } = await render(post);
2929
description={post.data.description}
3030
tags={post.data.tags}
3131
slug={post.id}
32+
legacyUrl={post.data.legacyUrl}
33+
references={references}
3234
>
3335
<Content />
3436
</BlogPostLayout>

src/plugins/remark-python-refs.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,19 @@ function splitMarkdocTags(value: string): Array<{ type: "text"; value: string }
246246
// ── Plugin ──────────────────────────────────────────────
247247

248248
export default function remarkPythonRefs() {
249-
return (tree: Root) => {
249+
return (tree: Root, file: any) => {
250+
// Collect unique references for the post footer
251+
const seen = new Set<string>();
252+
const refs: Array<{ type: RefType; label: string; url: string }> = [];
253+
254+
function collectRef(type: RefType, label: string, url: string) {
255+
const key = `${type}:${url}`;
256+
if (!seen.has(key)) {
257+
seen.add(key);
258+
refs.push({ type, label, url });
259+
}
260+
}
261+
250262
// Pass 1: Transform matching links → badges
251263
visit(tree, "link", (node: Link, index, parent) => {
252264
if (index == null || !parent) return;
@@ -255,6 +267,7 @@ export default function remarkPythonRefs() {
255267
const info = classify(node.url, label);
256268
if (!info) return;
257269

270+
collectRef(info.type, label, node.url);
258271
const html = buildBadgeHtml({ ...info, label, url: node.url });
259272

260273
// Replace the link node with a raw HTML node
@@ -288,5 +301,10 @@ export default function remarkPythonRefs() {
288301
node.children = newChildren;
289302
}
290303
});
304+
305+
// Expose collected references via remarkPluginFrontmatter
306+
if (!file.data.astro) file.data.astro = {};
307+
if (!file.data.astro.frontmatter) file.data.astro.frontmatter = {};
308+
file.data.astro.frontmatter.references = refs;
291309
};
292310
}

0 commit comments

Comments
 (0)