feat: Add tagging support for blog posts
All checks were successful
Build and Deploy Static Site / build (push) Successful in 7m54s

Introduces tagging system that organizes posts by tags and
generates dedicated tag index pages. Updates templates to
display tags and streamlines tag page rendering.
This commit is contained in:
Kumi 2025-01-01 18:34:58 +01:00
parent 020c916651
commit c037d1d918
Signed by: kumi
GPG key ID: ECBCC9082395383F
3 changed files with 77 additions and 18 deletions

View file

@ -6,6 +6,8 @@ author: Kumi
author_url: https://kumi.website author_url: https://kumi.website
license: CC BY-SA 4.0 license: CC BY-SA 4.0
license_url: https://creativecommons.org/licenses/by-sa/4.0/ license_url: https://creativecommons.org/licenses/by-sa/4.0/
tags: test
excerpt: This is the excerpt of a test post. It is displayed on the index pages.
--- ---
This is a test post. This is a test post.

85
main.py
View file

@ -117,6 +117,8 @@ def generate_blog_html(template_kwargs={}, posts_per_page=5):
blog_dir = pathlib.Path("blog") blog_dir = pathlib.Path("blog")
blog_posts = [] blog_posts = []
blog_tags = {}
for post_dir in blog_dir.iterdir(): for post_dir in blog_dir.iterdir():
if post_dir.is_dir(): if post_dir.is_dir():
md_path = post_dir / "index.md" md_path = post_dir / "index.md"
@ -133,6 +135,7 @@ def generate_blog_html(template_kwargs={}, posts_per_page=5):
else: else:
post_date = front_matter["date"] post_date = front_matter["date"]
front_matter["date"] = post_date.strftime("%Y-%m-%d %H:%M:%S") front_matter["date"] = post_date.strftime("%Y-%m-%d %H:%M:%S")
if post_date > datetime.datetime.now(): if post_date > datetime.datetime.now():
if not args.dev: if not args.dev:
logging.info(f"Skipping future post: {post_dir.name}") logging.info(f"Skipping future post: {post_dir.name}")
@ -143,6 +146,16 @@ def generate_blog_html(template_kwargs={}, posts_per_page=5):
front_matter["content"] = html_content front_matter["content"] = html_content
front_matter["slug"] = post_dir.name front_matter["slug"] = post_dir.name
# Add post to relevant tag lists
if "tags" in front_matter:
for tag in front_matter["tags"].split(","):
tag = tag.strip()
if tag not in blog_tags:
blog_tags[tag] = []
blog_tags[tag].append(front_matter)
# Create excerpt if not present # Create excerpt if not present
if "excerpt" not in front_matter: if "excerpt" not in front_matter:
excerpt = html_content.split("</p>")[0] excerpt = html_content.split("</p>")[0]
@ -171,11 +184,32 @@ def generate_blog_html(template_kwargs={}, posts_per_page=5):
# Sort posts by date, descending # Sort posts by date, descending
blog_posts.sort(key=lambda x: x.get("date", ""), reverse=True) blog_posts.sort(key=lambda x: x.get("date", ""), reverse=True)
# Calculate total pages # Render each individual post
for post in blog_posts:
post.setdefault("license", "CC BY-SA 4.0")
post.setdefault(
"license-url", "https://creativecommons.org/licenses/by-sa/4.0/"
)
post.setdefault("author", "Private.coffee Team")
post.setdefault("author-url", "https://private.coffee")
post["tags"] = [tag.strip() for tag in post.get("tags", "").split(",") if tag]
post_slug = post["slug"]
render_template_to_file(
"blog/post.html",
f"blog/{post_slug}/index.html",
**{**post, "relative_path": calculate_relative_path(2)},
**template_kwargs,
)
# Add tags to template kwargs
template_kwargs["tags"] = blog_tags.keys()
# Generate each index page
total_posts = len(blog_posts) total_posts = len(blog_posts)
total_pages = math.ceil(total_posts / posts_per_page) total_pages = math.ceil(total_posts / posts_per_page)
# Generate each index page
for page in range(total_pages): for page in range(total_pages):
start = page * posts_per_page start = page * posts_per_page
end = start + posts_per_page end = start + posts_per_page
@ -199,22 +233,39 @@ def generate_blog_html(template_kwargs={}, posts_per_page=5):
"blog/index.html", "blog/page/1/index.html", **context "blog/index.html", "blog/page/1/index.html", **context
) )
# Render each individual post # Generate tag pages
for post in blog_posts: for tag, posts in blog_tags.items():
post.setdefault("license", "CC BY-SA 4.0") tag_posts = sorted(posts, key=lambda x: x.get("date", ""), reverse=True)
post.setdefault( total_tag_posts = len(tag_posts)
"license-url", "https://creativecommons.org/licenses/by-sa/4.0/" total_tag_pages = math.ceil(total_tag_posts / posts_per_page)
)
post.setdefault("author", "Private.coffee Team")
post.setdefault("author-url", "https://private.coffee")
post_slug = post["slug"] for page in range(total_tag_pages):
render_template_to_file( start = page * posts_per_page
"blog/post.html", end = start + posts_per_page
f"blog/{post_slug}/index.html", paginated_posts = tag_posts[start:end]
**{**post, "relative_path": calculate_relative_path(2)}, context = {
**template_kwargs, "posts": paginated_posts,
) "current_page": page + 1,
"total_pages": total_tag_pages,
"tag": tag,
"relative_path": calculate_relative_path(3),
**template_kwargs,
}
output_path = (
f"blog/tag/{tag}/index.html"
if page == 0
else f"blog/tag/{tag}/page/{page + 1}/index.html"
)
render_template_to_file("blog/index.html", output_path, **context)
if page == 0:
pathlib.Path(f"build/blog/tag/{tag}/page/1").mkdir(
parents=True, exist_ok=True
)
context["relative_path"] = calculate_relative_path(5)
render_template_to_file(
"blog/index.html", f"blog/tag/{tag}/page/1/index.html", **context
)
logging.info("Blog section generated successfully.") logging.info("Blog section generated successfully.")

View file

@ -4,7 +4,7 @@
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
<div class="container my-5"> <div class="container my-5">
<h1>Blog</h1> <h1>Blog{% if tag %} index for tag {{ tag }}{% endif %}</h1>
<ul class="list-unstyled"> <ul class="list-unstyled">
{% for post in posts %} {% for post in posts %}
<li class="d-flex align-items-center mb-4 p-3 border-bottom"> <li class="d-flex align-items-center mb-4 p-3 border-bottom">
@ -20,6 +20,12 @@
<a href="{{ relative_path }}blog/{{ post.slug }}/index.html">{{ post.title }}</a> <a href="{{ relative_path }}blog/{{ post.slug }}/index.html">{{ post.title }}</a>
</h2> </h2>
<small class="text-muted">by {{ post.author }}, published {{ post.date }}</small> <small class="text-muted">by {{ post.author }}, published {{ post.date }}</small>
{% if post.tags %}
<br>
<small class="text-muted">tags:
{% for tag in post.tags %}<a href="{{ relative_path }}blog/tag/{{ tag }}">{{ tag }}</a>{% endfor %}
</small>
{% endif %}
<p> <p>
{{ post.excerpt }} <a href="{{ relative_path }}blog/{{ post.slug }}/index.html">[read more]</a> {{ post.excerpt }} <a href="{{ relative_path }}blog/{{ post.slug }}/index.html">[read more]</a>
</p> </p>