Skip to content

Commit af93033

Browse files
committed
ui: make the showcase post more fancy, add author
1 parent 9d34591 commit af93033

File tree

6 files changed

+378
-6
lines changed

6 files changed

+378
-6
lines changed

content/authors/jacob-coffee.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "Jacob Coffee",
3+
"bio": "PSF Infradwre team. Litestar core developer.",
4+
"github": "JacobCoffee",
5+
"avatar": "https://avatars.githubusercontent.com/u/45884264",
6+
"twitter": "_scriptr",
7+
"bluesky": "scriptr.dev",
8+
"mastodon": "https://fosstodon.org/@Monorepo",
9+
"website": "https://scriptr.dev",
10+
"featured": false
11+
}

content/posts/the-python-insider-blog-has-moved/index.md renamed to content/posts/the-python-insider-blog-has-moved/index.mdx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ published: true
1111
legacyUrl: /2026/03/the-python-insider-blog-has-moved.html
1212
---
1313

14+
import ShowcasePostCards from '../../../src/components/showcase/ShowcasePostCards.astro';
15+
import ShowcaseTagCloud from '../../../src/components/showcase/ShowcaseTagCloud.astro';
16+
import ShowcaseAuthors from '../../../src/components/showcase/ShowcaseAuthors.astro';
17+
import ShowcaseSearch from '../../../src/components/showcase/ShowcaseSearch.astro';
18+
1419
Python Insider now lives at [blog.python.org](https://blog.python.org), backed by a Git repository. All 307 posts from the Blogger era have been migrated over, and old URLs redirect to the new ones automatically. Your RSS readers should pick up the new feed without any action on your part, but if something looks off, the new feed URL is [blog.python.org/rss.xml](https://blog.python.org/rss.xml).
1520

1621
## Why we moved
@@ -32,14 +37,36 @@ The repo README has more detail on frontmatter fields and local development if y
3237

3338
## What's new on the site
3439

35-
Beyond the content itself, the new site has a few features the old Blogger setup never had:
40+
Beyond the content itself, the new site has a few features the old Blogger setup never had. Here's a live look:
41+
42+
### Browse & filter posts
43+
44+
All posts are browsable with pagination, a year filter, and a tag sidebar. Click any tag or year to narrow things down.
45+
46+
<ShowcasePostCards />
47+
48+
### Every author has a page
49+
50+
See who's been writing, how much they've contributed, and browse their posts individually.
51+
52+
<ShowcaseAuthors />
53+
54+
### Tags at a glance
55+
56+
Every tag across the archive, ranked by how often it appears. Great for finding all the release announcements or security updates in one place.
57+
58+
<ShowcaseTagCloud />
59+
60+
### Search everything
61+
62+
Hit <kbd>Ctrl+K</kbd> (or <kbd>Cmd+K</kbd> on Mac) from any page to open the command palette. It searches across all 307+ posts by title, author, tags, and description. There are also keyboard chord shortcuts for quick navigation.
63+
64+
<ShowcaseSearch />
65+
66+
### And more
3667

37-
- **[Browse all posts](/blog/)** with pagination and a tag sidebar for filtering by topic. You can also filter by [year](/blog/year/2025) to find posts from a specific period.
38-
- **[Authors page](/authors/)** listing every contributor and how many posts they've written. Each author has their own page showing all of their posts.
39-
- **[Tags page](/tags/)** with every tag and its post count, so you can quickly find all release announcements, security updates, or community posts.
40-
- **Search** via the command palette — hit `Ctrl+K` (or `Cmd+K` on Mac) from any page to search across all 307+ posts instantly.
4168
- **RSS feed** at [blog.python.org/rss.xml](https://blog.python.org/rss.xml), compatible with the old Blogger feed URL so existing subscribers don't need to change anything.
42-
- **Dark mode** that follows your system preference.
69+
- **Dark mode** that follows your system preference (try the toggle in the header).
4370
- **Open Graph images** generated automatically for every post, so links shared on social media get proper preview cards.
4471

4572
## What's under the hood
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
import { getCollection } from "astro:content";
3+
import { slugify, withBase } from "../../lib/utils";
4+
5+
const allPosts = await getCollection("posts");
6+
const publishedPosts = allPosts.filter((p) => p.data.published);
7+
const allAuthors = await getCollection("authors");
8+
9+
const authorCounts = new Map<string, number>();
10+
for (const post of publishedPosts) {
11+
const slug = slugify(post.data.author);
12+
authorCounts.set(slug, (authorCounts.get(slug) || 0) + 1);
13+
}
14+
15+
const authorsWithCounts = allAuthors
16+
.map((a) => ({
17+
slug: a.id,
18+
name: a.data.name,
19+
github: a.data.github,
20+
count: authorCounts.get(a.id) || 0,
21+
}))
22+
.filter((a) => a.count > 0)
23+
.sort((a, b) => b.count - a.count);
24+
25+
const topAuthors = authorsWithCounts.slice(0, 6);
26+
const maxCount = topAuthors[0]?.count ?? 1;
27+
---
28+
29+
<div class="not-prose my-8 overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800">
30+
<div class="flex items-center justify-between border-b border-zinc-200 bg-zinc-50 px-5 py-3 dark:border-zinc-800 dark:bg-zinc-900/50">
31+
<div class="flex items-center gap-2">
32+
<svg class="h-4 w-4 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
33+
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" stroke-linecap="round" stroke-linejoin="round" />
34+
<circle cx="9" cy="7" r="4" stroke-linecap="round" stroke-linejoin="round" />
35+
<path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75" stroke-linecap="round" stroke-linejoin="round" />
36+
</svg>
37+
<span class="text-sm font-semibold text-zinc-700 dark:text-zinc-300" style="font-family: var(--font-display);">Authors</span>
38+
<span class="text-xs text-zinc-400 dark:text-zinc-500">{authorsWithCounts.length} contributors</span>
39+
</div>
40+
<a href={withBase("/authors")} class="text-xs font-medium text-[#306998] hover:underline dark:text-[#ffd43b]">
41+
View all &rarr;
42+
</a>
43+
</div>
44+
<div class="space-y-0.5 p-3">
45+
{topAuthors.map((author) => {
46+
const pct = Math.round((author.count / maxCount) * 100);
47+
return (
48+
<a
49+
href={withBase(`/authors/${author.slug}`)}
50+
class="group flex items-center gap-3 rounded-lg px-3 py-2 transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800/60"
51+
>
52+
{author.github ? (
53+
<img
54+
src={`https://github.com/${author.github}.png`}
55+
alt=""
56+
class="h-6 w-6 flex-shrink-0 rounded-full"
57+
loading="lazy"
58+
/>
59+
) : (
60+
<div class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-zinc-200 text-xs font-bold text-zinc-500 dark:bg-zinc-700 dark:text-zinc-400">
61+
{author.name.charAt(0)}
62+
</div>
63+
)}
64+
<span class="w-36 flex-shrink-0 truncate text-sm font-semibold text-zinc-700 dark:text-zinc-300" style="font-family: var(--font-display);">
65+
{author.name}
66+
</span>
67+
<div class="h-1.5 flex-1 overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800">
68+
<div
69+
class="h-full rounded-full bg-[#306998] transition-all duration-500 group-hover:opacity-80 dark:bg-[#ffd43b]"
70+
style={`width: ${Math.max(pct, 4)}%;`}
71+
/>
72+
</div>
73+
<span class="w-8 flex-shrink-0 text-right text-xs tabular-nums font-medium text-zinc-400 dark:text-zinc-500">
74+
{author.count}
75+
</span>
76+
</a>
77+
);
78+
})}
79+
</div>
80+
</div>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
import { getCollection } from "astro:content";
3+
import { formatDate, postUrl, slugify, withBase } from "../../lib/utils";
4+
5+
const allPosts = await getCollection("posts");
6+
const recentPosts = allPosts
7+
.filter((p) => p.data.published)
8+
.sort((a, b) => b.data.publishDate.getTime() - a.data.publishDate.getTime())
9+
.slice(0, 3);
10+
11+
// Collect years from all posts for the mini sidebar
12+
const yearSet = new Set<number>();
13+
for (const post of allPosts.filter((p) => p.data.published)) {
14+
yearSet.add(post.data.publishDate.getFullYear());
15+
}
16+
const years = [...yearSet].sort((a, b) => b - a).slice(0, 8);
17+
18+
// Top tags for the mini sidebar
19+
const tagCounts = new Map<string, number>();
20+
for (const post of allPosts.filter((p) => p.data.published)) {
21+
for (const tag of post.data.tags) {
22+
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
23+
}
24+
}
25+
const topTags = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
26+
---
27+
28+
<div class="not-prose my-8 overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800">
29+
<div class="flex items-center justify-between border-b border-zinc-200 bg-zinc-50 px-5 py-3 dark:border-zinc-800 dark:bg-zinc-900/50">
30+
<div class="flex items-center gap-2">
31+
<svg class="h-4 w-4 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
32+
<path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z" stroke-linecap="round" stroke-linejoin="round" />
33+
<path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z" stroke-linecap="round" stroke-linejoin="round" />
34+
</svg>
35+
<span class="text-sm font-semibold text-zinc-700 dark:text-zinc-300" style="font-family: var(--font-display);">Blog</span>
36+
<span class="text-xs text-zinc-400 dark:text-zinc-500">{allPosts.filter((p) => p.data.published).length} posts with filters & pagination</span>
37+
</div>
38+
<a href={withBase("/blog")} class="text-xs font-medium text-[#306998] hover:underline dark:text-[#ffd43b]">
39+
Browse all &rarr;
40+
</a>
41+
</div>
42+
43+
<div class="flex">
44+
<!-- Posts column -->
45+
<div class="min-w-0 flex-1 divide-y divide-zinc-100 dark:divide-zinc-800/60">
46+
{recentPosts.map((post) => (
47+
<a href={withBase(postUrl(post.id, post.data.publishDate.toISOString()))} class="group block px-5 py-3.5 transition-colors hover:bg-zinc-50 dark:hover:bg-zinc-800/30">
48+
<h4 class="text-sm font-semibold leading-snug text-zinc-800 transition-colors group-hover:text-[#306998] dark:text-zinc-200 dark:group-hover:text-[#ffd43b]" style="font-family: var(--font-display);">
49+
{post.data.title}
50+
</h4>
51+
<div class="mt-1 flex items-center gap-2 text-xs text-zinc-400 dark:text-zinc-500">
52+
<span class="font-medium text-zinc-500 dark:text-zinc-400">{post.data.author}</span>
53+
<span class="text-zinc-300 dark:text-zinc-700">&middot;</span>
54+
<time datetime={post.data.publishDate.toISOString()}>{formatDate(post.data.publishDate.toISOString())}</time>
55+
</div>
56+
{post.data.tags.length > 0 && (
57+
<div class="mt-2 flex flex-wrap gap-1">
58+
{post.data.tags.slice(0, 3).map((tag) => (
59+
<span class="rounded-md bg-zinc-100 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
60+
{tag}
61+
</span>
62+
))}
63+
</div>
64+
)}
65+
</a>
66+
))}
67+
</div>
68+
69+
<!-- Mini sidebar -->
70+
<div class="hidden w-44 flex-shrink-0 border-l border-zinc-100 p-4 dark:border-zinc-800/60 sm:block">
71+
<div class="mb-4">
72+
<h5 class="mb-2 text-[10px] font-semibold uppercase tracking-widest text-zinc-400 dark:text-zinc-600">Years</h5>
73+
<div class="flex flex-wrap gap-1">
74+
{years.map((y) => (
75+
<a
76+
href={withBase(`/blog/year/${y}`)}
77+
class="rounded bg-zinc-100 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500 transition-colors hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-700"
78+
>
79+
{y}
80+
</a>
81+
))}
82+
</div>
83+
</div>
84+
<div>
85+
<h5 class="mb-2 text-[10px] font-semibold uppercase tracking-widest text-zinc-400 dark:text-zinc-600">Tags</h5>
86+
<div class="flex flex-wrap gap-1">
87+
{topTags.map(([tag, count]) => (
88+
<a
89+
href={withBase(`/tags/${tag}`)}
90+
class="tag-pill inline-flex items-center gap-0.5 rounded bg-zinc-100 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400"
91+
>
92+
{tag}
93+
<span class="text-[9px] text-zinc-400 dark:text-zinc-600">{count}</span>
94+
</a>
95+
))}
96+
</div>
97+
</div>
98+
</div>
99+
</div>
100+
</div>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
// Static mockup of the command palette — not interactive, just a visual preview
3+
---
4+
5+
<div class="not-prose my-8 overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800">
6+
<div class="flex items-center justify-between border-b border-zinc-200 bg-zinc-50 px-5 py-3 dark:border-zinc-800 dark:bg-zinc-900/50">
7+
<div class="flex items-center gap-2">
8+
<svg class="h-4 w-4 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
9+
<circle cx="11" cy="11" r="8" />
10+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
11+
</svg>
12+
<span class="text-sm font-semibold text-zinc-700 dark:text-zinc-300" style="font-family: var(--font-display);">Command Palette</span>
13+
</div>
14+
<span class="inline-flex items-center gap-1 text-xs text-zinc-400 dark:text-zinc-500">
15+
Press
16+
<kbd class="rounded-md border border-zinc-200 bg-zinc-100 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-400" style="font-family: var(--font-mono);">&#8984;K</kbd>
17+
to try it
18+
</span>
19+
</div>
20+
21+
<!-- Fake command palette mockup -->
22+
<div class="bg-white/80 p-1 backdrop-blur dark:bg-[#0f1117]/80">
23+
<!-- Search input -->
24+
<div class="relative">
25+
<div class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-zinc-300 dark:text-zinc-600">
26+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
27+
<circle cx="11" cy="11" r="8" />
28+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
29+
</svg>
30+
</div>
31+
<div class="border-b border-zinc-100 px-4 py-3 pl-11 text-sm text-zinc-400 dark:border-zinc-800 dark:text-zinc-600">
32+
Search posts, tags, authors...
33+
</div>
34+
</div>
35+
36+
<!-- Mock results -->
37+
<div class="px-2 py-2">
38+
<div class="mb-1 px-3 pt-2 text-[11px] font-semibold uppercase tracking-widest text-zinc-400 dark:text-zinc-600" style="font-family: var(--font-display);">Go to</div>
39+
40+
<div class="flex items-center gap-3 rounded-lg bg-[#306998]/[0.06] px-3 py-2.5 dark:bg-[#ffd43b]/[0.06]">
41+
<svg class="h-4 w-4 flex-shrink-0 text-[#306998] dark:text-[#ffd43b]" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
42+
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
43+
<polyline points="9 22 9 12 15 12 15 22" />
44+
</svg>
45+
<span class="text-sm font-medium text-zinc-800 dark:text-zinc-200">Go to Home</span>
46+
<span class="ml-auto inline-flex items-center gap-1">
47+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">G</kbd>
48+
<span class="text-[10px] italic text-zinc-400 dark:text-zinc-600">then</span>
49+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">H</kbd>
50+
</span>
51+
</div>
52+
53+
<div class="flex items-center gap-3 rounded-lg px-3 py-2.5 transition-colors">
54+
<svg class="h-4 w-4 flex-shrink-0 text-zinc-300 dark:text-zinc-600" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
55+
<path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z" />
56+
<path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z" />
57+
</svg>
58+
<span class="text-sm text-zinc-600 dark:text-zinc-400">Go to Blog</span>
59+
<span class="ml-auto inline-flex items-center gap-1">
60+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">G</kbd>
61+
<span class="text-[10px] italic text-zinc-400 dark:text-zinc-600">then</span>
62+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">B</kbd>
63+
</span>
64+
</div>
65+
66+
<div class="flex items-center gap-3 rounded-lg px-3 py-2.5 transition-colors">
67+
<svg class="h-4 w-4 flex-shrink-0 text-zinc-300 dark:text-zinc-600" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
68+
<path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z" />
69+
<line x1="7" y1="7" x2="7.01" y2="7" />
70+
</svg>
71+
<span class="text-sm text-zinc-600 dark:text-zinc-400">Go to Tags</span>
72+
<span class="ml-auto inline-flex items-center gap-1">
73+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">G</kbd>
74+
<span class="text-[10px] italic text-zinc-400 dark:text-zinc-600">then</span>
75+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">T</kbd>
76+
</span>
77+
</div>
78+
</div>
79+
80+
<!-- Footer -->
81+
<div class="flex items-center gap-4 border-t border-zinc-100 px-4 py-2 dark:border-zinc-800">
82+
<span class="flex items-center gap-1.5 text-[11px] text-zinc-400 dark:text-zinc-600">
83+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">&uarr;&darr;</kbd>
84+
Navigate
85+
</span>
86+
<span class="flex items-center gap-1.5 text-[11px] text-zinc-400 dark:text-zinc-600">
87+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">&crarr;</kbd>
88+
Open
89+
</span>
90+
<span class="flex items-center gap-1.5 text-[11px] text-zinc-400 dark:text-zinc-600">
91+
<kbd class="inline-flex h-[18px] min-w-[20px] items-center justify-center rounded border border-zinc-200 bg-zinc-100 px-1 text-[10px] text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800/80 dark:text-zinc-500" style="font-family: var(--font-mono);">Esc</kbd>
92+
Close
93+
</span>
94+
</div>
95+
</div>
96+
</div>

0 commit comments

Comments
 (0)