ux: CHANGE change 'feeds' to 'sections' - BREAKING - short guide in release notes

This commit is contained in:
Caffeine Fueled 2026-03-22 20:30:05 +01:00
parent 98ab0ab419
commit a99d1db1cb
Signed by: cf7
GPG key ID: CA295D643074C68C
4 changed files with 59 additions and 59 deletions

View file

@ -23,8 +23,8 @@ Show cases:
- long- and short form content - long- and short form content
- pages - pages
- static files - static files
- separate feeds (used for categories, tagging, etc) `/feed/{tag}` - separate sections (used for categories, tagging, etc) `/section/{tag}`
- exclusion of feeds from main feed (drafts or system notes) - exclusion of sections from main index (drafts or system notes)
- HTML anchors for headers - HTML anchors for headers
- list random posts at the bottom - list random posts at the bottom
- optional RSS feeds - optional RSS feeds
@ -47,33 +47,33 @@ Put markdown file into `items` dir. **Important naming convention**:
2025-10-05_short_quick-update_draft.md 2025-10-05_short_quick-update_draft.md
``` ```
Format: `YYYY-MM-DD_type_slug[_feed].md` Format: `YYYY-MM-DD_type_slug[_section].md`
- `2025-10-03` - date of the article - `2025-10-03` - date of the article
- `_long_` - type of content: `long`, `short`, or `page` - `_long_` - type of content: `long`, `short`, or `page`
- `building-a-static-site-generator` - slug/path for the URL - `building-a-static-site-generator` - slug/path for the URL
- `_draft` (optional) - feed tag for categorization - `_draft` (optional) - section tag for categorization
The first `#` header is the title of the article - no frontmatter needed. The first `#` header is the title of the article - no frontmatter needed.
**Types of content**: **Types of content**:
- `long` - only title with link to articles will be displayed in feed - `long` - only title with link to articles will be displayed in the main index
- `short` - title and all content will be displayed - `short` - title and all content will be displayed
- `page` - won't be displayed in feed at all - `page` - won't be displayed in the main index at all
### Feeds ### Sections
Posts can be tagged with an optional feed category (e.g., `_python`, `_webdev`). Posts with feed tags: Posts can be tagged with an optional section (e.g., `_python`, `_webdev`). Posts with section tags:
- Appear on the main page (unless excluded in config) - Appear on the main page (unless excluded in config)
- Have their own feed page at `/feed/{tag}/` - Have their own section page at `/section/{tag}/`
**Configuration in `config.py`:** **Configuration in `config.py`:**
```python ```python
# Exclude specific feeds from main page (they'll still have /feed/name/ pages) # Exclude specific sections from main page (they'll still have /section/name/ pages)
EXCLUDE_FEEDS_FROM_MAIN = ['draft', 'private'] EXCLUDE_SECTIONS_FROM_MAIN = ['draft', 'private']
``` ```
This is useful for draft posts or topic-specific content you want separated from the main feed. This is useful for draft posts or topic-specific content you want separated from the main index.
--- ---

View file

@ -8,13 +8,13 @@ AUTHOR_NAME = "PicoPaper"
AUTHOR_EMAIL = "hello@picopaper.com" # Optional AUTHOR_EMAIL = "hello@picopaper.com" # Optional
THEME = "default" THEME = "default"
# Exclude specific feeds from the main page (they'll still have their own /feed/name/ pages) # Exclude specific sections from the main page (they'll still have their own /section/name/ pages)
EXCLUDE_FEEDS_FROM_MAIN = ['draft','private'] # e.g., ['python', 'drafts'] EXCLUDE_SECTIONS_FROM_MAIN = ['draft','private'] # e.g., ['python', 'drafts']
# Navigation bar items - list of dictionaries with 'text' and 'url' keys # Navigation bar items - list of dictionaries with 'text' and 'url' keys
NAVBAR_ITEMS = [ NAVBAR_ITEMS = [
{'text': 'Home', 'url': '/'}, {'text': 'Home', 'url': '/'},
{'text': 'Feeds', 'url': '/feed/'}, {'text': 'Sections', 'url': '/section/'},
{'text': 'About', 'url': '/about/'}, {'text': 'About', 'url': '/about/'},
{'text': 'RSS', 'url': '/rss.xml'} {'text': 'RSS', 'url': '/rss.xml'}
] ]

View file

@ -6,7 +6,7 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
import markdown import markdown
from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_FEEDS_FROM_MAIN, from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_SECTIONS_FROM_MAIN,
NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH, NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH,
ENABLE_RSS_FEED, RSS_FEED_PATH, ENABLE_RSS_FEED, RSS_FEED_PATH,
BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS) BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS)
@ -21,7 +21,7 @@ class SSGGGenerator:
self.assets_dir = self.theme_dir / 'assets' self.assets_dir = self.theme_dir / 'assets'
self.blog_title = blog_title or BLOG_TITLE self.blog_title = blog_title or BLOG_TITLE
self.blog_description = blog_description or BLOG_DESCRIPTION self.blog_description = blog_description or BLOG_DESCRIPTION
self.exclude_feeds = EXCLUDE_FEEDS_FROM_MAIN self.exclude_sections = EXCLUDE_SECTIONS_FROM_MAIN
self.navbar_items = NAVBAR_ITEMS self.navbar_items = NAVBAR_ITEMS
self.hide_logo = HIDE_LOGO self.hide_logo = HIDE_LOGO
self.hide_title = HIDE_TITLE self.hide_title = HIDE_TITLE
@ -42,7 +42,7 @@ class SSGGGenerator:
self.md = markdown.Markdown(extensions=['extra', 'toc']) self.md = markdown.Markdown(extensions=['extra', 'toc'])
def parse_filename(self, filename, subpath=''): def parse_filename(self, filename, subpath=''):
"""Parse filename format: YYYY-MM-DD_type_name[_feed].md """Parse filename format: YYYY-MM-DD_type_name[_section].md
Args: Args:
filename: The markdown filename filename: The markdown filename
@ -54,7 +54,7 @@ class SSGGGenerator:
if not match: if not match:
return None return None
date_str, post_type, name, feed = match.groups() date_str, post_type, name, section = match.groups()
date = datetime.strptime(date_str, '%Y-%m-%d') date = datetime.strptime(date_str, '%Y-%m-%d')
return { return {
@ -62,7 +62,7 @@ class SSGGGenerator:
'date_str': date.strftime('%Y-%m-%d'), 'date_str': date.strftime('%Y-%m-%d'),
'type': post_type, 'type': post_type,
'name': name, 'name': name,
'feed': feed, 'section': section,
'filename': filename, 'filename': filename,
'subpath': subpath 'subpath': subpath
} }
@ -140,7 +140,7 @@ class SSGGGenerator:
'content': content, 'content': content,
'slug': slug, 'slug': slug,
'url': url, 'url': url,
'feed': parsed['feed'], 'section': parsed['section'],
'source': str(relative_path), 'source': str(relative_path),
'subpath': parsed['subpath'] 'subpath': parsed['subpath']
} }
@ -152,13 +152,13 @@ class SSGGGenerator:
return posts return posts
def generate_index(self, posts, feed_name=None, all_posts=None): def generate_index(self, posts, section_name=None, all_posts=None):
"""Generate index.html with all posts (or feed-specific index)""" """Generate index.html with all posts (or section-specific index)"""
template = self.env.get_template('index.tmpl') template = self.env.get_template('index.tmpl')
if feed_name: if section_name:
title = f"{feed_name} - {self.blog_title}" title = f"{section_name} - {self.blog_title}"
output_path = self.output_dir / 'feed' / feed_name / 'index.html' output_path = self.output_dir / 'section' / section_name / 'index.html'
else: else:
title = self.blog_title title = self.blog_title
output_path = self.output_dir / 'index.html' output_path = self.output_dir / 'index.html'
@ -183,28 +183,28 @@ class SSGGGenerator:
print(f"✓ Generated {output_path}") print(f"✓ Generated {output_path}")
def generate_feeds_overview(self, feeds, all_posts=None): def generate_sections_overview(self, sections, all_posts=None):
"""Generate /feed/index.html with list of all non-excluded feeds""" """Generate /section/index.html with list of all non-excluded sections"""
template = self.env.get_template('feeds.tmpl') template = self.env.get_template('sections.tmpl')
# Prepare feed data with counts, excluding feeds in EXCLUDE_FEEDS_FROM_MAIN # Prepare section data with counts, excluding sections in EXCLUDE_SECTIONS_FROM_MAIN
feed_list = [] section_list = []
for feed_name, posts in sorted(feeds.items()): for section_name, posts in sorted(sections.items()):
if feed_name not in self.exclude_feeds: if section_name not in self.exclude_sections:
feed_list.append({ section_list.append({
'name': feed_name, 'name': section_name,
'count': len(posts) 'count': len(posts)
}) })
title = f"Feeds - {self.blog_title}" title = f"Sections - {self.blog_title}"
output_path = self.output_dir / 'feed' / 'index.html' output_path = self.output_dir / 'section' / 'index.html'
html = template.render( html = template.render(
title=title, title=title,
blog_title=self.blog_title, blog_title=self.blog_title,
blog_description=self.blog_description, blog_description=self.blog_description,
navbar_items=self.navbar_items, navbar_items=self.navbar_items,
feeds=feed_list, sections=section_list,
all_posts=all_posts or [], all_posts=all_posts or [],
hide_logo=self.hide_logo, hide_logo=self.hide_logo,
hide_title=self.hide_title, hide_title=self.hide_title,
@ -397,27 +397,27 @@ class SSGGGenerator:
all_posts = self.collect_posts() all_posts = self.collect_posts()
print(f"Found {len(all_posts)} posts") print(f"Found {len(all_posts)} posts")
# Filter out pages and excluded feeds from main feed # Filter out pages and excluded sections from main index
feed_posts = [p for p in all_posts main_posts = [p for p in all_posts
if p['type'] != 'page' if p['type'] != 'page'
and p['feed'] not in self.exclude_feeds] and p['section'] not in self.exclude_sections]
# Generate main index with filtered feed posts # Generate main index with filtered posts
self.generate_index(feed_posts, all_posts=feed_posts) self.generate_index(main_posts, all_posts=main_posts)
# Group posts by feed (include all posts, not just those in main feed) # Group posts by section (include all posts, not just those in main index)
feeds = {} sections = {}
for post in all_posts: for post in all_posts:
if post['feed'] and post['type'] != 'page': if post['section'] and post['type'] != 'page':
feeds.setdefault(post['feed'], []).append(post) sections.setdefault(post['section'], []).append(post)
# Generate feed-specific pages # Generate section-specific pages
for feed_name, posts in feeds.items(): for section_name, posts in sections.items():
self.generate_index(posts, feed_name, all_posts=feed_posts) self.generate_index(posts, section_name, all_posts=main_posts)
# Generate feeds overview page # Generate sections overview page
if feeds: if sections:
self.generate_feeds_overview(feeds, all_posts=feed_posts) self.generate_sections_overview(sections, all_posts=main_posts)
# Group posts by subdirectory # Group posts by subdirectory
subdirs = {} subdirs = {}
@ -427,16 +427,16 @@ class SSGGGenerator:
# Generate subdirectory index pages (e.g., /projects/) # Generate subdirectory index pages (e.g., /projects/)
for subpath, subdir_posts in subdirs.items(): for subpath, subdir_posts in subdirs.items():
self.generate_subdir_index(subpath, subdir_posts, all_posts=feed_posts) self.generate_subdir_index(subpath, subdir_posts, all_posts=main_posts)
# Generate individual pages for long posts, short posts, and pages # Generate individual pages for long posts, short posts, and pages
for post in all_posts: for post in all_posts:
if post['type'] in ['long', 'short', 'page']: if post['type'] in ['long', 'short', 'page']:
self.generate_post_page(post, all_posts=feed_posts) self.generate_post_page(post, all_posts=main_posts)
# Generate RSS feed # Generate RSS feed
if ENABLE_RSS_FEED: if ENABLE_RSS_FEED:
self.generate_rss_feed(feed_posts) self.generate_rss_feed(main_posts)
# Copy assets # Copy assets
self.copy_assets() self.copy_assets()

View file

@ -7,10 +7,10 @@
{% include 'header.tmpl' %} {% include 'header.tmpl' %}
<main> <main>
<h2>Feeds</h2> <h2>Sections</h2>
<ul> <ul>
{% for feed in feeds %} {% for section in sections %}
<li><a href="/feed/{{ feed.name }}/">{{ feed.name }}</a> ({{ feed.count }} posts)</li> <li><a href="/section/{{ section.name }}/">{{ section.name }}</a> ({{ section.count }} posts)</li>
{% endfor %} {% endfor %}
</ul> </ul>
</main> </main>