Skip to content

Commit f1772a1

Browse files
committed
feat: unified chronological feed with pagination (10/page)
Replace separate Writing/Videos/Reading sections with a single chronological feed of blog posts, channel videos, and HN story submissions, sorted newest-first and paginated at 10 items per page. - generate.py: remove _SECTION_DEFS/active_sections; build feed_posts (writing + channel_posts + hn_stories) sorted by date; render page 1 as index.html and subsequent pages under page/{n}/index.html; remove hn_stories from sidebar (they appear in the main feed now) - index.html: unified feed loop with per-source card variants: ✍️ Blog Post — title links to post page, shows labels/excerpt 🎬 Video — thumbnail, links to YouTube 📰 Link Submission — title links to external article, shows HN score/comments Pagination nav (Newer / Page N of M / Older) rendered when >1 page. Sidebar retains HN comments and per-playlist My Watching panels. - style.css: add .post-card__video-thumb and .pagination/* styles
1 parent 9836265 commit f1772a1

3 files changed

Lines changed: 176 additions & 174 deletions

File tree

blog/generate.py

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,10 @@
5555
CONFIG_DIR = SCRIPT_DIR.parent / "config"
5656

5757
# ---------------------------------------------------------------------------
58-
# Section definitions — order controls display order on the site
58+
# Pagination
5959
# ---------------------------------------------------------------------------
6060

61-
_SECTION_DEFS = [
62-
{"key": "writing", "title": "My Writing", "icon": "✍️"},
63-
{"key": "videos", "title": "My Videos", "icon": "🎥"},
64-
{"key": "watching", "title": "My Watching", "icon": "📺"},
65-
{"key": "reading", "title": "My Reading", "icon": "📰"},
66-
]
61+
_PAGE_SIZE = 10
6762

6863
# ---------------------------------------------------------------------------
6964
# Static asset helpers
@@ -186,31 +181,26 @@ def generate_site(
186181
else:
187182
print("HN_USERNAME not configured and none found in GitHub profile — skipping Hacker News ingestor.")
188183

189-
# Build active sections (skip sections that produced no posts)
190-
section_posts = {
191-
"writing": writing_posts,
192-
"videos": channel_posts,
193-
"watching": playlist_posts,
194-
"reading": reading_posts,
195-
}
196-
active_sections = [
197-
{**defn, "posts": section_posts[defn["key"]]}
198-
for defn in _SECTION_DEFS
199-
if section_posts[defn["key"]]
200-
]
201-
202-
# All posts merged and sorted newest-first (for combined feed / post pages)
184+
# All posts merged and sorted newest-first (for individual post page generation)
203185
all_posts = sorted(
204186
writing_posts + channel_posts + playlist_posts + reading_posts,
205187
key=lambda p: p["created_at"],
206188
reverse=True,
207189
)
208190

209191
# --- Sidebar data ---
210-
# Split HN posts into stories vs. comments for separate sidebar panels
192+
# Split HN posts: stories go into the main feed, comments go in the sidebar
211193
_SIDEBAR_LIMIT = 5
212194
hn_stories = [p for p in reading_posts if p.get("metadata", {}).get("hn_type") == "story"]
213195
hn_comments = [p for p in reading_posts if p.get("metadata", {}).get("hn_type") == "comment"]
196+
197+
# Unified main feed: writing + channel videos + HN stories, newest first
198+
feed_posts = sorted(
199+
writing_posts + channel_posts + hn_stories,
200+
key=lambda p: p["created_at"],
201+
reverse=True,
202+
)
203+
total_pages = max(1, (len(feed_posts) + _PAGE_SIZE - 1) // _PAGE_SIZE)
214204
# Build per-username HN profile links (use first effective username if multiple)
215205
_hn_user = (effective_hn_usernames or [None])[0]
216206
hn_submitted_url = (
@@ -240,13 +230,11 @@ def generate_site(
240230
_seen_pids[src_id]["posts"].append(p)
241231

242232
sidebar = {
243-
"hn_stories": hn_stories[:_SIDEBAR_LIMIT],
244-
"hn_comments": hn_comments[:_SIDEBAR_LIMIT],
245-
"hn_submitted_url": hn_submitted_url,
246-
"hn_threads_url": hn_threads_url,
247-
"hn_profile_url": hn_profile_url,
248-
"hn_username": _hn_user,
249-
"playlist_groups": playlist_groups,
233+
"hn_comments": hn_comments[:_SIDEBAR_LIMIT],
234+
"hn_threads_url": hn_threads_url,
235+
"hn_profile_url": hn_profile_url,
236+
"hn_username": _hn_user,
237+
"playlist_groups": playlist_groups,
250238
}
251239

252240
# --- Jinja2 setup ---
@@ -292,11 +280,32 @@ def generate_site(
292280

293281
output_dir.mkdir(parents=True, exist_ok=True)
294282

295-
# Render index page
283+
# Render paginated index pages
296284
index_tmpl = env.get_template("index.html")
297-
index_html = index_tmpl.render(sections=active_sections, sidebar=sidebar)
298-
(output_dir / "index.html").write_text(index_html, encoding="utf-8")
299-
print("Wrote index.html")
285+
for page_num in range(1, total_pages + 1):
286+
start = (page_num - 1) * _PAGE_SIZE
287+
page_posts = feed_posts[start : start + _PAGE_SIZE]
288+
prev_url = (
289+
base_path if page_num == 2
290+
else f"{base_path}page/{page_num - 1}/"
291+
) if page_num > 1 else None
292+
next_url = f"{base_path}page/{page_num + 1}/" if page_num < total_pages else None
293+
page_html = index_tmpl.render(
294+
feed_posts=page_posts,
295+
sidebar=sidebar,
296+
page_num=page_num,
297+
total_pages=total_pages,
298+
prev_url=prev_url,
299+
next_url=next_url,
300+
)
301+
if page_num == 1:
302+
(output_dir / "index.html").write_text(page_html, encoding="utf-8")
303+
print("Wrote index.html")
304+
else:
305+
page_dir = output_dir / "page" / str(page_num)
306+
page_dir.mkdir(parents=True, exist_ok=True)
307+
(page_dir / "index.html").write_text(page_html, encoding="utf-8")
308+
print(f"Wrote page/{page_num}/index.html")
300309

301310
# Render individual post pages
302311
post_tmpl = env.get_template("post.html")

blog/static/style.css

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,51 @@ a.config-detail:hover { color: #0969da; }
793793
margin: 0.4rem 0 0;
794794
}
795795

796+
/* --- Video thumbnail in feed cards ------------------------ */
797+
.post-card__video-thumb {
798+
display: block;
799+
width: 100%;
800+
max-width: 320px;
801+
height: auto;
802+
border-radius: 6px;
803+
margin: 0.5rem 0 0.75rem;
804+
}
805+
806+
/* --- Pagination ------------------------------------------- */
807+
.pagination {
808+
display: flex;
809+
align-items: center;
810+
justify-content: space-between;
811+
gap: 1rem;
812+
margin-top: 2rem;
813+
padding: 1rem 0;
814+
border-top: 1px solid #e2e8f0;
815+
font-size: 0.9rem;
816+
}
817+
818+
.pagination__link {
819+
font-weight: 600;
820+
color: #0969da;
821+
text-decoration: none;
822+
padding: 0.35em 0.85em;
823+
border: 1px solid #e2e8f0;
824+
border-radius: 6px;
825+
background: #fff;
826+
transition: background 0.12s ease;
827+
}
828+
.pagination__link:hover { background: #f1f5f9; text-decoration: none; }
829+
830+
.pagination__link--disabled {
831+
color: #cbd5e1;
832+
cursor: default;
833+
pointer-events: none;
834+
}
835+
836+
.pagination__info {
837+
color: #64748b;
838+
font-size: 0.85rem;
839+
}
840+
796841
/* --- Responsive ------------------------------------------- */
797842
@media (max-width: 900px) {
798843
.page-layout {

0 commit comments

Comments
 (0)