diff --git a/README.md b/README.md index 1c00d7e..549d0e8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![picopaper logo](images/logo.png) + # picopaper A minimal static site generator for blogs built with Python 3 and Jinja2 @@ -24,6 +26,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/config.py b/config.py index b7976d8..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" @@ -10,6 +10,12 @@ 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/'} ] +# 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 0000000..5f00575 Binary files /dev/null and b/images/logo.png differ 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)**! diff --git a/picopaper.py b/picopaper.py index 7576a38..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,10 +20,21 @@ 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)) + # 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 +129,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 +145,11 @@ 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, + hide_logo=self.hide_logo, + hide_title=self.hide_title, + logo_path=self.logo_path ) output_path.parent.mkdir(parents=True, exist_ok=True) @@ -143,7 +158,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 +179,11 @@ 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 [], + hide_logo=self.hide_logo, + hide_title=self.hide_title, + logo_path=self.logo_path ) output_path.parent.mkdir(parents=True, exist_ok=True) @@ -173,7 +192,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 +201,11 @@ 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 [], + hide_logo=self.hide_logo, + hide_title=self.hide_title, + logo_path=self.logo_path ) # Create directory for the post slug @@ -246,7 +269,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 +279,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/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 }}