From cf3586eadde0c721bb7a249e49c023b0525dddc4 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 13 Feb 2026 00:13:25 +0100 Subject: [PATCH] feat: data-driven blog sidebars and fix recentPosts glob MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix recentPosts collection glob (content/posts/ → content/) so the widget actually finds posts. Extract blog-sidebar widgets into reusable partials. Make both sidebar.njk (listing pages) and blog-sidebar.njk (post pages) configurable via homepageConfig.blogListingSidebar and homepageConfig.blogPostSidebar, with fallback to current hardcoded widgets for backward compatibility. Co-Authored-By: Claude Opus 4.6 --- _includes/components/blog-sidebar.njk | 349 ++++-------------- _includes/components/sidebar.njk | 91 ++++- .../widgets/author-card-compact.njk | 28 ++ .../components/widgets/post-categories.njk | 19 + .../components/widgets/post-navigation.njk | 60 +++ .../components/widgets/recent-posts-blog.njk | 83 +++++ _includes/components/widgets/share.njk | 24 ++ _includes/components/widgets/subscribe.njk | 18 + _includes/components/widgets/toc.njk | 17 + _includes/components/widgets/webmentions.njk | 32 ++ eleventy.config.js | 2 +- 11 files changed, 420 insertions(+), 303 deletions(-) create mode 100644 _includes/components/widgets/author-card-compact.njk create mode 100644 _includes/components/widgets/post-categories.njk create mode 100644 _includes/components/widgets/post-navigation.njk create mode 100644 _includes/components/widgets/recent-posts-blog.njk create mode 100644 _includes/components/widgets/share.njk create mode 100644 _includes/components/widgets/subscribe.njk create mode 100644 _includes/components/widgets/toc.njk create mode 100644 _includes/components/widgets/webmentions.njk diff --git a/_includes/components/blog-sidebar.njk b/_includes/components/blog-sidebar.njk index 4f609ac..45ba0bd 100644 --- a/_includes/components/blog-sidebar.njk +++ b/_includes/components/blog-sidebar.njk @@ -1,290 +1,73 @@ {# Blog Sidebar - Shown on individual post pages #} -{# Contains: Author compact card, Related posts, Categories, Recent posts #} +{# Data-driven when homepageConfig.blogPostSidebar is configured, otherwise falls back to default widgets #} -{# Author Compact Card - h-card microformat (compact version) #} -
-
- {# Hidden u-photo for reliable microformat parsing #} - - -
- - {{ site.author.name }} - -

{{ site.author.title }}

- {% if site.author.locality %} -

{{ site.author.locality }}{% if site.author.country %}, {{ site.author.country }}{% endif %}

- {% endif %} -
-
- {# Hidden but present for microformat completeness #} - - {% if site.author.email %}{% endif %} - {% if site.author.org %}{% endif %} -
- -{# Post Navigation Widget - Previous/Next #} -{% if previousPost or nextPost %} -
-

More Posts

-
- {% if previousPost %} -
- Previous - {% set _likedUrl = previousPost.data.likeOf or previousPost.data.like_of %} - {% set _bookmarkedUrl = previousPost.data.bookmarkOf or previousPost.data.bookmark_of %} - {% set _repostedUrl = previousPost.data.repostOf or previousPost.data.repost_of %} - {% set _replyToUrl = previousPost.data.inReplyTo or previousPost.data.in_reply_to %} - - {% if _likedUrl %} - - Liked {{ _likedUrl | replace("https://", "") | truncate(35) }} - {% elif _bookmarkedUrl %} - - {{ previousPost.data.title or ("Bookmarked " + (_bookmarkedUrl | replace("https://", "") | truncate(30))) }} - {% elif _repostedUrl %} - - Reposted {{ _repostedUrl | replace("https://", "") | truncate(35) }} - {% elif _replyToUrl %} - - Reply to {{ _replyToUrl | replace("https://", "") | truncate(35) }} - {% else %} - {{ previousPost.data.title or previousPost.data.name or (previousPost.templateContent | striptags | truncate(50)) or "Note" }} +{% if homepageConfig and homepageConfig.blogPostSidebar and homepageConfig.blogPostSidebar.length %} + {# === Data-driven mode: render configured widgets === #} + {% for widget in homepageConfig.blogPostSidebar %} + {% if widget.type == "author-card-compact" %} + {% include "components/widgets/author-card-compact.njk" %} + {% elif widget.type == "author-card" %} + {% include "components/widgets/author-card.njk" %} + {% elif widget.type == "post-navigation" %} + {% include "components/widgets/post-navigation.njk" %} + {% elif widget.type == "toc" %} + {% include "components/widgets/toc.njk" %} + {% elif widget.type == "post-categories" %} + {% include "components/widgets/post-categories.njk" %} + {% elif widget.type == "recent-posts" %} + {% include "components/widgets/recent-posts-blog.njk" %} + {% elif widget.type == "webmentions" %} + {% include "components/widgets/webmentions.njk" %} + {% elif widget.type == "share" %} + {% include "components/widgets/share.njk" %} + {% elif widget.type == "subscribe" %} + {% include "components/widgets/subscribe.njk" %} + {% elif widget.type == "social-activity" %} + {% include "components/widgets/social-activity.njk" %} + {% elif widget.type == "github-repos" %} + {% include "components/widgets/github-repos.njk" %} + {% elif widget.type == "funkwhale" %} + {% include "components/widgets/funkwhale.njk" %} + {% elif widget.type == "blogroll" %} + {% include "components/widgets/blogroll.njk" %} + {% elif widget.type == "categories" %} + {% include "components/widgets/categories.njk" %} + {% elif widget.type == "search" %} + + {% elif widget.type == "custom-html" %} + {% set wConfig = widget.config or {} %} + - {% endif %} - {% if nextPost %} -
- Next - {% set _likedUrl = nextPost.data.likeOf or nextPost.data.like_of %} - {% set _bookmarkedUrl = nextPost.data.bookmarkOf or nextPost.data.bookmark_of %} - {% set _repostedUrl = nextPost.data.repostOf or nextPost.data.repost_of %} - {% set _replyToUrl = nextPost.data.inReplyTo or nextPost.data.in_reply_to %} - - {% if _likedUrl %} - - Liked {{ _likedUrl | replace("https://", "") | truncate(35) }} - {% elif _bookmarkedUrl %} - - {{ nextPost.data.title or ("Bookmarked " + (_bookmarkedUrl | replace("https://", "") | truncate(30))) }} - {% elif _repostedUrl %} - - Reposted {{ _repostedUrl | replace("https://", "") | truncate(35) }} - {% elif _replyToUrl %} - - Reply to {{ _replyToUrl | replace("https://", "") | truncate(35) }} - {% else %} - {{ nextPost.data.title or nextPost.data.name or (nextPost.templateContent | striptags | truncate(50)) or "Note" }} + {% if wConfig.content %} +
+ {{ wConfig.content | safe }} +
{% endif %} -
-
- {% endif %} -
-
-{% endif %} - -{# Table of Contents Widget (for articles with headings) #} -{% if toc and toc.length %} -
-

Contents

- -
-{% endif %} - -{# Categories for This Post #} -{% if category %} -
-

Categories

-
- {% if category is string %} - - {{ category }} - +
{% else %} - {% for cat in category %} - - {{ cat }} - - {% endfor %} + {% endif %} -
-
+ {% endfor %} +{% else %} + {# === Fallback: default blog post sidebar (backward compatibility) === #} + {% include "components/widgets/author-card-compact.njk" %} + {% include "components/widgets/post-navigation.njk" %} + {% include "components/widgets/toc.njk" %} + {% include "components/widgets/post-categories.njk" %} + {% include "components/widgets/recent-posts-blog.njk" %} + {% include "components/widgets/webmentions.njk" %} + {% include "components/widgets/share.njk" %} + {% include "components/widgets/subscribe.njk" %} {% endif %} - -{# Recent Posts Widget — type-aware #} -{% if collections.posts %} -
-

Recent Posts

- - - View all posts - -
-{% endif %} - -{# Webmentions Widget (if this post has any) #} -{% if webmentions and webmentions.length %} -
-

- - - - Interactions -

-
- {% set likes = webmentions | filter("like-of") %} - {% set reposts = webmentions | filter("repost-of") %} - {% set replies = webmentions | filter("in-reply-to") %} - - {% if likes.length %} -

- {{ likes.length }} likes -

- {% endif %} - {% if reposts.length %} -

- {{ reposts.length }} reposts -

- {% endif %} - {% if replies.length %} -

- {{ replies.length }} replies -

- {% endif %} -
-
-{% endif %} - -{# Share Widget #} -
-

Share

- -
- -{# Subscribe Widget #} - diff --git a/_includes/components/sidebar.njk b/_includes/components/sidebar.njk index 0c503ea..6641139 100644 --- a/_includes/components/sidebar.njk +++ b/_includes/components/sidebar.njk @@ -1,26 +1,79 @@ -{# Sidebar — composed from individual widget partials #} -{# Each widget handles its own conditional display internally, #} -{# except API-only widgets which need a data-source guard here. #} +{# Sidebar — for blog listing pages (/blog/, /notes/, /articles/...) #} +{# Data-driven when homepageConfig.blogListingSidebar is configured, otherwise falls back to default widgets #} -{# Author Card (h-card) — always shown #} -{% include "components/widgets/author-card.njk" %} +{% if homepageConfig and homepageConfig.blogListingSidebar and homepageConfig.blogListingSidebar.length %} + {# === Data-driven mode: render configured widgets === #} + {% for widget in homepageConfig.blogListingSidebar %} + {% if widget.type == "author-card" %} + {% include "components/widgets/author-card.njk" %} + {% elif widget.type == "author-card-compact" %} + {% include "components/widgets/author-card-compact.njk" %} + {% elif widget.type == "social-activity" %} + {% include "components/widgets/social-activity.njk" %} + {% elif widget.type == "github-repos" %} + {% include "components/widgets/github-repos.njk" %} + {% elif widget.type == "funkwhale" %} + {% include "components/widgets/funkwhale.njk" %} + {% elif widget.type == "recent-posts" %} + {% include "components/widgets/recent-posts.njk" %} + {% elif widget.type == "blogroll" %} + {% if blogrollStatus and blogrollStatus.source == "indiekit" %} + {% include "components/widgets/blogroll.njk" %} + {% endif %} + {% elif widget.type == "categories" %} + {% include "components/widgets/categories.njk" %} + {% elif widget.type == "subscribe" %} + {% include "components/widgets/subscribe.njk" %} + {% elif widget.type == "search" %} + + {% elif widget.type == "custom-html" %} + {% set wConfig = widget.config or {} %} + + {% else %} + + {% endif %} + {% endfor %} +{% else %} + {# === Fallback: current hardcoded sidebar (backward compatibility) === #} + {# Author Card (h-card) — always shown #} + {% include "components/widgets/author-card.njk" %} -{# Social Activity — Bluesky/Mastodon feeds #} -{% include "components/widgets/social-activity.njk" %} + {# Social Activity — Bluesky/Mastodon feeds #} + {% include "components/widgets/social-activity.njk" %} -{# GitHub Repos #} -{% include "components/widgets/github-repos.njk" %} + {# GitHub Repos #} + {% include "components/widgets/github-repos.njk" %} -{# Funkwhale — Now Playing / Listening Stats #} -{% include "components/widgets/funkwhale.njk" %} + {# Funkwhale — Now Playing / Listening Stats #} + {% include "components/widgets/funkwhale.njk" %} -{# Recent Posts (for non-blog pages) #} -{% include "components/widgets/recent-posts.njk" %} + {# Recent Posts (for non-blog pages) #} + {% include "components/widgets/recent-posts.njk" %} -{# Blogroll — only when backend is available #} -{% if blogrollStatus and blogrollStatus.source == "indiekit" %} -{% include "components/widgets/blogroll.njk" %} + {# Blogroll — only when backend is available #} + {% if blogrollStatus and blogrollStatus.source == "indiekit" %} + {% include "components/widgets/blogroll.njk" %} + {% endif %} + + {# Categories/Tags #} + {% include "components/widgets/categories.njk" %} {% endif %} - -{# Categories/Tags #} -{% include "components/widgets/categories.njk" %} diff --git a/_includes/components/widgets/author-card-compact.njk b/_includes/components/widgets/author-card-compact.njk new file mode 100644 index 0000000..95e87dc --- /dev/null +++ b/_includes/components/widgets/author-card-compact.njk @@ -0,0 +1,28 @@ +{# Author Compact Card - h-card microformat (compact version for blog sidebars) #} +
+
+ {# Hidden u-photo for reliable microformat parsing #} + + +
+ + {{ site.author.name }} + +

{{ site.author.title }}

+ {% if site.author.locality %} +

{{ site.author.locality }}{% if site.author.country %}, {{ site.author.country }}{% endif %}

+ {% endif %} +
+
+ {# Hidden but present for microformat completeness #} + + {% if site.author.email %}{% endif %} + {% if site.author.org %}{% endif %} +
diff --git a/_includes/components/widgets/post-categories.njk b/_includes/components/widgets/post-categories.njk new file mode 100644 index 0000000..22b4417 --- /dev/null +++ b/_includes/components/widgets/post-categories.njk @@ -0,0 +1,19 @@ +{# Categories for This Post #} +{% if category %} +
+

Categories

+
+ {% if category is string %} + + {{ category }} + + {% else %} + {% for cat in category %} + + {{ cat }} + + {% endfor %} + {% endif %} +
+
+{% endif %} diff --git a/_includes/components/widgets/post-navigation.njk b/_includes/components/widgets/post-navigation.njk new file mode 100644 index 0000000..3bd08a8 --- /dev/null +++ b/_includes/components/widgets/post-navigation.njk @@ -0,0 +1,60 @@ +{# Post Navigation Widget - Previous/Next #} +{% if previousPost or nextPost %} +
+

More Posts

+
+ {% if previousPost %} + + {% endif %} + {% if nextPost %} +
+ Next + {% set _likedUrl = nextPost.data.likeOf or nextPost.data.like_of %} + {% set _bookmarkedUrl = nextPost.data.bookmarkOf or nextPost.data.bookmark_of %} + {% set _repostedUrl = nextPost.data.repostOf or nextPost.data.repost_of %} + {% set _replyToUrl = nextPost.data.inReplyTo or nextPost.data.in_reply_to %} + + {% if _likedUrl %} + + Liked {{ _likedUrl | replace("https://", "") | truncate(35) }} + {% elif _bookmarkedUrl %} + + {{ nextPost.data.title or ("Bookmarked " + (_bookmarkedUrl | replace("https://", "") | truncate(30))) }} + {% elif _repostedUrl %} + + Reposted {{ _repostedUrl | replace("https://", "") | truncate(35) }} + {% elif _replyToUrl %} + + Reply to {{ _replyToUrl | replace("https://", "") | truncate(35) }} + {% else %} + {{ nextPost.data.title or nextPost.data.name or (nextPost.templateContent | striptags | truncate(50)) or "Note" }} + {% endif %} + +
+ {% endif %} +
+
+{% endif %} diff --git a/_includes/components/widgets/recent-posts-blog.njk b/_includes/components/widgets/recent-posts-blog.njk new file mode 100644 index 0000000..b2b96f4 --- /dev/null +++ b/_includes/components/widgets/recent-posts-blog.njk @@ -0,0 +1,83 @@ +{# Recent Posts Widget — type-aware, for blog/post sidebars #} +{# Uses collections.posts directly (all post types, not just recentPosts collection) #} +{% if collections.posts %} +
+

Recent Posts

+ + + View all posts + +
+{% endif %} diff --git a/_includes/components/widgets/share.njk b/_includes/components/widgets/share.njk new file mode 100644 index 0000000..878d810 --- /dev/null +++ b/_includes/components/widgets/share.njk @@ -0,0 +1,24 @@ +{# Share Widget #} +
+

Share

+ +
diff --git a/_includes/components/widgets/subscribe.njk b/_includes/components/widgets/subscribe.njk new file mode 100644 index 0000000..984c589 --- /dev/null +++ b/_includes/components/widgets/subscribe.njk @@ -0,0 +1,18 @@ +{# Subscribe Widget #} + diff --git a/_includes/components/widgets/toc.njk b/_includes/components/widgets/toc.njk new file mode 100644 index 0000000..3b06235 --- /dev/null +++ b/_includes/components/widgets/toc.njk @@ -0,0 +1,17 @@ +{# Table of Contents Widget (for articles with headings) #} +{% if toc and toc.length %} +
+

Contents

+ +
+{% endif %} diff --git a/_includes/components/widgets/webmentions.njk b/_includes/components/widgets/webmentions.njk new file mode 100644 index 0000000..674d42f --- /dev/null +++ b/_includes/components/widgets/webmentions.njk @@ -0,0 +1,32 @@ +{# Webmentions Widget (if this post has any) #} +{% if webmentions and webmentions.length %} +
+

+ + + + Interactions +

+
+ {% set likes = webmentions | filter("like-of") %} + {% set reposts = webmentions | filter("repost-of") %} + {% set replies = webmentions | filter("in-reply-to") %} + + {% if likes.length %} +

+ {{ likes.length }} likes +

+ {% endif %} + {% if reposts.length %} +

+ {{ reposts.length }} reposts +

+ {% endif %} + {% if replies.length %} +

+ {{ replies.length }} replies +

+ {% endif %} +
+
+{% endif %} diff --git a/eleventy.config.js b/eleventy.config.js index df4ee89..6c8b15d 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -418,7 +418,7 @@ export default function (eleventyConfig) { // Recent posts for sidebar eleventyConfig.addCollection("recentPosts", function (collectionApi) { return collectionApi - .getFilteredByGlob("content/posts/**/*.md") + .getFilteredByGlob("content/**/*.md") .sort((a, b) => b.date - a.date) .slice(0, 5); });