From 1b8f77cb4b9179837f465e33acdfd72316c32abc Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Thu, 23 Oct 2025 22:41:36 +0200 Subject: [PATCH 1/6] ADD feature 5 random posts at the end of a post #17 --- picopaper.py | 31 +++++++++++++++-------- theme/default/templates/post.tmpl | 2 ++ theme/default/templates/random_posts.tmpl | 23 +++++++++++++++++ 3 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 theme/default/templates/random_posts.tmpl diff --git a/picopaper.py b/picopaper.py index 7576a38..f9184a1 100644 --- a/picopaper.py +++ b/picopaper.py @@ -24,6 +24,14 @@ class SSGGGenerator: # Setup Jinja2 self.env = Environment(loader=FileSystemLoader(self.templates_dir)) + # Add custom filter for random sampling + def random_sample(items, count): + import random + items_list = list(items) + return random.sample(items_list, min(count, len(items_list))) + + self.env.filters['random_sample'] = random_sample + # Setup markdown with toc extension for header anchors self.md = markdown.Markdown(extensions=['extra', 'toc']) @@ -118,7 +126,7 @@ class SSGGGenerator: return posts - def generate_index(self, posts, feed_name=None): + def generate_index(self, posts, feed_name=None, all_posts=None): """Generate index.html with all posts (or feed-specific index)""" template = self.env.get_template('index.tmpl') @@ -134,7 +142,8 @@ class SSGGGenerator: blog_title=self.blog_title, blog_description=self.blog_description, navbar_items=self.navbar_items, - posts=posts + posts=posts, + all_posts=all_posts or posts ) output_path.parent.mkdir(parents=True, exist_ok=True) @@ -143,7 +152,7 @@ class SSGGGenerator: print(f"✓ Generated {output_path}") - def generate_feeds_overview(self, feeds): + def generate_feeds_overview(self, feeds, all_posts=None): """Generate /feed/index.html with list of all non-excluded feeds""" template = self.env.get_template('feeds.tmpl') @@ -164,7 +173,8 @@ class SSGGGenerator: blog_title=self.blog_title, blog_description=self.blog_description, navbar_items=self.navbar_items, - feeds=feed_list + feeds=feed_list, + all_posts=all_posts or [] ) output_path.parent.mkdir(parents=True, exist_ok=True) @@ -173,7 +183,7 @@ class SSGGGenerator: print(f"✓ Generated {output_path}") - def generate_post_page(self, post): + def generate_post_page(self, post, all_posts=None): """Generate individual post page for 'long' posts""" template = self.env.get_template('post.tmpl') @@ -182,7 +192,8 @@ class SSGGGenerator: blog_title=self.blog_title, blog_description=self.blog_description, navbar_items=self.navbar_items, - post=post + post=post, + all_posts=all_posts or [] ) # Create directory for the post slug @@ -246,7 +257,7 @@ class SSGGGenerator: and p['feed'] not in self.exclude_feeds] # Generate main index with filtered feed posts - self.generate_index(feed_posts) + self.generate_index(feed_posts, all_posts=feed_posts) # Group posts by feed (include all posts, not just those in main feed) feeds = {} @@ -256,16 +267,16 @@ class SSGGGenerator: # Generate feed-specific pages for feed_name, posts in feeds.items(): - self.generate_index(posts, feed_name) + self.generate_index(posts, feed_name, all_posts=feed_posts) # Generate feeds overview page if feeds: - self.generate_feeds_overview(feeds) + self.generate_feeds_overview(feeds, all_posts=feed_posts) # Generate individual pages for long posts, short posts, and pages for post in all_posts: if post['type'] in ['long', 'short', 'page']: - self.generate_post_page(post) + self.generate_post_page(post, all_posts=feed_posts) # Copy assets self.copy_assets() diff --git a/theme/default/templates/post.tmpl b/theme/default/templates/post.tmpl index d1573bd..9191071 100644 --- a/theme/default/templates/post.tmpl +++ b/theme/default/templates/post.tmpl @@ -14,6 +14,8 @@ {{ post.content | safe }} + + {% include 'random_posts.tmpl' %} {% include 'footer.tmpl' %} diff --git a/theme/default/templates/random_posts.tmpl b/theme/default/templates/random_posts.tmpl new file mode 100644 index 0000000..f75e2f0 --- /dev/null +++ b/theme/default/templates/random_posts.tmpl @@ -0,0 +1,23 @@ +{% if all_posts %} +{# Filter out pages and current post, then randomly select 5 #} +{% set available_posts = all_posts | selectattr('type', 'ne', 'page') | list %} +{% if post %} + {% set available_posts = available_posts | rejectattr('slug', 'equalto', post.slug) | list %} +{% endif %} +{% if available_posts %} + {% set random_posts = available_posts | random_sample(5) %} + {% if random_posts %} +
+

More Posts

+ +
+ {% endif %} +{% endif %} +{% endif %} From eb0f4cf5a5e19e7e574d451c0b927a8987491ca1 Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Thu, 23 Oct 2025 22:44:13 +0200 Subject: [PATCH 2/6] ADD documentation of #17 --- README.md | 1 + items/2025-10-09_short_features.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 1c00d7e..e72c317 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Show cases: - separate feeds (used for categories, tagging, etc) `/feed/{tag}` - exclusion of feeds from main feed (drafts or system notes) - HTML anchors for headers +- list random posts at the bottom **Ideas**: - RSS diff --git a/items/2025-10-09_short_features.md b/items/2025-10-09_short_features.md index e521d99..78dffec 100644 --- a/items/2025-10-09_short_features.md +++ b/items/2025-10-09_short_features.md @@ -7,6 +7,7 @@ - excluding feeds in config file like [/feed/draft/](/feed/draft/) - static files like [/LICENSE](/LICENSE) - html anchors for headers like [#second-header](/your-first-article/#second-header) +- show random posts at the end of the page Everything is **[open-source](https://git.uphillsecurity.com/cf7/picopaper)**! From cc1cc4639cd4d1fd45afe7deb4245f62422f044b Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Thu, 23 Oct 2025 22:46:58 +0200 Subject: [PATCH 3/6] ADD /feed/ to navbar #16 --- config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config.py b/config.py index b7976d8..3f23cdb 100644 --- a/config.py +++ b/config.py @@ -10,6 +10,7 @@ EXCLUDE_FEEDS_FROM_MAIN = ['draft','private'] # e.g., ['python', 'drafts'] # Navigation bar items - list of dictionaries with 'text' and 'url' keys NAVBAR_ITEMS = [ {'text': 'Home', 'url': '/'}, + {'text': 'Feeds', 'url': '/feed/'}, {'text': 'About', 'url': '/about/'} ] From b623e17c5a5057858b614215a6cd3ae97d6423c8 Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Thu, 23 Oct 2025 23:44:24 +0200 Subject: [PATCH 4/6] ADD Logo configuration for the header #14 --- config.py | 7 ++++++- images/logo.png | Bin 0 -> 4505 bytes picopaper.py | 20 ++++++++++++++++---- theme/default/assets/style.css | 13 ++++++++++++- theme/default/templates/header.tmpl | 11 ++++++++++- 5 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 images/logo.png diff --git a/config.py b/config.py index 3f23cdb..2f193d3 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,6 @@ """Configuration file for picopaper blog""" -BLOG_TITLE = "picopaper" +BLOG_TITLE = "PicoPaper.com" BLOG_DESCRIPTION = "we like simple." THEME = "default" @@ -14,3 +14,8 @@ NAVBAR_ITEMS = [ {'text': 'About', 'url': '/about/'} ] +# Logo settings +HIDE_LOGO = False +HIDE_TITLE = True +LOGO_PATH = "/images/logo.png" + diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5f0057581acbe1155cc3f9f5bae1a3a34704fc5e GIT binary patch literal 4505 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 literal 0 HcmV?d00001 diff --git a/picopaper.py b/picopaper.py index f9184a1..700bc67 100644 --- a/picopaper.py +++ b/picopaper.py @@ -6,7 +6,7 @@ from datetime import datetime from pathlib import Path from jinja2 import Environment, FileSystemLoader import markdown -from config import BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_FEEDS_FROM_MAIN, NAVBAR_ITEMS +from config import BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_FEEDS_FROM_MAIN, NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH class SSGGGenerator: def __init__(self, items_dir='items', output_dir='output', theme=None, blog_title=None, blog_description=None): @@ -20,6 +20,9 @@ class SSGGGenerator: self.blog_description = blog_description or BLOG_DESCRIPTION self.exclude_feeds = EXCLUDE_FEEDS_FROM_MAIN self.navbar_items = NAVBAR_ITEMS + self.hide_logo = HIDE_LOGO + self.hide_title = HIDE_TITLE + self.logo_path = LOGO_PATH # Setup Jinja2 self.env = Environment(loader=FileSystemLoader(self.templates_dir)) @@ -143,7 +146,10 @@ class SSGGGenerator: blog_description=self.blog_description, navbar_items=self.navbar_items, posts=posts, - all_posts=all_posts or posts + all_posts=all_posts or posts, + hide_logo=self.hide_logo, + hide_title=self.hide_title, + logo_path=self.logo_path ) output_path.parent.mkdir(parents=True, exist_ok=True) @@ -174,7 +180,10 @@ class SSGGGenerator: blog_description=self.blog_description, navbar_items=self.navbar_items, feeds=feed_list, - all_posts=all_posts or [] + all_posts=all_posts or [], + hide_logo=self.hide_logo, + hide_title=self.hide_title, + logo_path=self.logo_path ) output_path.parent.mkdir(parents=True, exist_ok=True) @@ -193,7 +202,10 @@ class SSGGGenerator: blog_description=self.blog_description, navbar_items=self.navbar_items, post=post, - all_posts=all_posts or [] + all_posts=all_posts or [], + hide_logo=self.hide_logo, + hide_title=self.hide_title, + logo_path=self.logo_path ) # Create directory for the post slug diff --git a/theme/default/assets/style.css b/theme/default/assets/style.css index 1663aa4..9b276fc 100644 --- a/theme/default/assets/style.css +++ b/theme/default/assets/style.css @@ -12,6 +12,7 @@ header { padding: 20px; margin-bottom: 40px; border-radius: 10px; + text-align: center; } img { @@ -22,7 +23,13 @@ hr { border: 1px solid #efefef; } -h1 { margin: 0; } +h1 { + margin: 0; +} + +.header-logo { + vertical-align: middle; +} .blog-description { margin: 10px 0 0 0; @@ -33,6 +40,10 @@ h1 { margin: 0; } .main-nav { margin-top: 15px; font-weight: bold; + display: flex; + justify-content: center; + gap: 10px; + flex-wrap: wrap; } .nav-item { diff --git a/theme/default/templates/header.tmpl b/theme/default/templates/header.tmpl index 22b854c..e92c6f6 100644 --- a/theme/default/templates/header.tmpl +++ b/theme/default/templates/header.tmpl @@ -1,5 +1,14 @@
-

{{ blog_title }}

+

+ + {% if not hide_logo %} + + {% endif %} + {% if not hide_title %} + {{ blog_title }} + {% endif %} + +

{{ blog_description }}