mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-04-02 16:44:56 +02:00
feat: harmonize blog views with homepage UI/UX
Add color-coded left borders to post cards on all blog listing and category pages, and make sidebar widgets collapsible with localStorage persistence on both listing and single-post sidebars.
This commit is contained in:
@@ -1,9 +1,68 @@
|
||||
{# Blog Sidebar - Shown on individual post pages #}
|
||||
{# Data-driven when homepageConfig.blogPostSidebar is configured, otherwise falls back to default widgets #}
|
||||
{# Each widget is wrapped in a collapsible container with localStorage persistence #}
|
||||
|
||||
{% if homepageConfig and homepageConfig.blogPostSidebar and homepageConfig.blogPostSidebar.length %}
|
||||
{# === Data-driven mode: render configured widgets === #}
|
||||
{% for widget in homepageConfig.blogPostSidebar %}
|
||||
|
||||
{# Resolve widget title #}
|
||||
{% if widget.type == "search" %}{% set widgetTitle = "Search" %}
|
||||
{% elif widget.type == "social-activity" %}{% set widgetTitle = "Social Activity" %}
|
||||
{% elif widget.type == "github-repos" %}{% set widgetTitle = "GitHub" %}
|
||||
{% elif widget.type == "funkwhale" %}{% set widgetTitle = "Listening" %}
|
||||
{% elif widget.type == "recent-posts" %}{% set widgetTitle = "Recent Posts" %}
|
||||
{% elif widget.type == "blogroll" %}{% set widgetTitle = "Blogroll" %}
|
||||
{% elif widget.type == "feedland" %}{% set widgetTitle = "FeedLand" %}
|
||||
{% elif widget.type == "categories" %}{% set widgetTitle = "Categories" %}
|
||||
{% elif widget.type == "webmentions" %}{% set widgetTitle = "Webmentions" %}
|
||||
{% elif widget.type == "recent-comments" %}{% set widgetTitle = "Recent Comments" %}
|
||||
{% elif widget.type == "fediverse-follow" %}{% set widgetTitle = "Fediverse" %}
|
||||
{% elif widget.type == "author-card" %}{% set widgetTitle = "Author" %}
|
||||
{% elif widget.type == "author-card-compact" %}{% set widgetTitle = "Author" %}
|
||||
{% elif widget.type == "subscribe" %}{% set widgetTitle = "Subscribe" %}
|
||||
{% elif widget.type == "toc" %}{% set widgetTitle = "Table of Contents" %}
|
||||
{% elif widget.type == "post-categories" %}{% set widgetTitle = "Categories" %}
|
||||
{% elif widget.type == "share" %}{% set widgetTitle = "Share" %}
|
||||
{% elif widget.type == "custom-html" %}{% set widgetTitle = (widget.config.title if widget.config and widget.config.title) or "Custom" %}
|
||||
{% else %}{% set widgetTitle = widget.type %}
|
||||
{% endif %}
|
||||
|
||||
{% set widgetKey = "post-widget-" + widget.type + "-" + loop.index0 %}
|
||||
{% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
|
||||
|
||||
{# Collapsible wrapper — Alpine.js handles toggle, localStorage persists state #}
|
||||
<div
|
||||
class="widget-collapsible mb-4"
|
||||
x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : {{ defaultOpen }} }"
|
||||
>
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button
|
||||
class="widget-header w-full p-4"
|
||||
@click="open = !open; localStorage.setItem('{{ widgetKey }}', open)"
|
||||
:aria-expanded="open ? 'true' : 'false'"
|
||||
>
|
||||
<h3 class="widget-title font-bold text-lg">{{ widgetTitle }}</h3>
|
||||
<svg
|
||||
class="widget-chevron"
|
||||
:class="open && 'rotate-180'"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
x-show="open"
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
x-cloak
|
||||
>
|
||||
{# Widget content — inner .widget provides padding, inner title hidden by CSS #}
|
||||
{% if widget.type == "author-card-compact" %}
|
||||
{% include "components/widgets/author-card-compact.njk" %}
|
||||
{% elif widget.type == "author-card" %}
|
||||
@@ -37,7 +96,6 @@
|
||||
{% elif widget.type == "search" %}
|
||||
<is-land on:visible>
|
||||
<div class="widget">
|
||||
<h3 class="widget-title">Search</h3>
|
||||
<div id="blog-sidebar-search"></div>
|
||||
<script>initPagefind("#blog-sidebar-search");</script>
|
||||
</div>
|
||||
@@ -48,9 +106,6 @@
|
||||
{% set wConfig = widget.config or {} %}
|
||||
<is-land on:visible>
|
||||
<div class="widget">
|
||||
{% if wConfig.title %}
|
||||
<h3 class="widget-title">{{ wConfig.title }}</h3>
|
||||
{% endif %}
|
||||
{% if wConfig.content %}
|
||||
<div class="prose dark:prose-invert prose-sm max-w-none">
|
||||
{{ wConfig.content | safe }}
|
||||
@@ -61,15 +116,124 @@
|
||||
{% else %}
|
||||
<!-- Unknown widget type: {{ widget.type }} -->
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# === Fallback: default blog post sidebar (backward compatibility) === #}
|
||||
{# Each widget wrapped in collapsible container #}
|
||||
|
||||
{# Author Card Compact #}
|
||||
{% set widgetKey = "post-fb-author-card-compact" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Author</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/author-card-compact.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Table of Contents #}
|
||||
{% set widgetKey = "post-fb-toc" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Table of Contents</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/toc.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Post Categories #}
|
||||
{% set widgetKey = "post-fb-post-categories" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Categories</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/post-categories.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Recent Posts #}
|
||||
{% set widgetKey = "post-fb-recent-posts" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Recent Posts</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/recent-posts-blog.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Webmentions #}
|
||||
{% set widgetKey = "post-fb-webmentions" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Webmentions</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/webmentions.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Share #}
|
||||
{% set widgetKey = "post-fb-share" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Share</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/share.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Subscribe #}
|
||||
{% set widgetKey = "post-fb-subscribe" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Subscribe</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/subscribe.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Recent Comments #}
|
||||
{% set widgetKey = "post-fb-recent-comments" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Recent Comments</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/recent-comments.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,9 +1,65 @@
|
||||
{# Sidebar — for blog listing pages (/blog/, /notes/, /articles/...) #}
|
||||
{# Data-driven when homepageConfig.blogListingSidebar is configured, otherwise falls back to default widgets #}
|
||||
{# Each widget is wrapped in a collapsible container with localStorage persistence #}
|
||||
|
||||
{% if homepageConfig and homepageConfig.blogListingSidebar and homepageConfig.blogListingSidebar.length %}
|
||||
{# === Data-driven mode: render configured widgets === #}
|
||||
{% for widget in homepageConfig.blogListingSidebar %}
|
||||
|
||||
{# Resolve widget title #}
|
||||
{% if widget.type == "search" %}{% set widgetTitle = "Search" %}
|
||||
{% elif widget.type == "social-activity" %}{% set widgetTitle = "Social Activity" %}
|
||||
{% elif widget.type == "github-repos" %}{% set widgetTitle = "GitHub" %}
|
||||
{% elif widget.type == "funkwhale" %}{% set widgetTitle = "Listening" %}
|
||||
{% elif widget.type == "recent-posts" %}{% set widgetTitle = "Recent Posts" %}
|
||||
{% elif widget.type == "blogroll" %}{% set widgetTitle = "Blogroll" %}
|
||||
{% elif widget.type == "feedland" %}{% set widgetTitle = "FeedLand" %}
|
||||
{% elif widget.type == "categories" %}{% set widgetTitle = "Categories" %}
|
||||
{% elif widget.type == "webmentions" %}{% set widgetTitle = "Webmentions" %}
|
||||
{% elif widget.type == "recent-comments" %}{% set widgetTitle = "Recent Comments" %}
|
||||
{% elif widget.type == "fediverse-follow" %}{% set widgetTitle = "Fediverse" %}
|
||||
{% elif widget.type == "author-card" %}{% set widgetTitle = "Author" %}
|
||||
{% elif widget.type == "author-card-compact" %}{% set widgetTitle = "Author" %}
|
||||
{% elif widget.type == "subscribe" %}{% set widgetTitle = "Subscribe" %}
|
||||
{% elif widget.type == "custom-html" %}{% set widgetTitle = (widget.config.title if widget.config and widget.config.title) or "Custom" %}
|
||||
{% else %}{% set widgetTitle = widget.type %}
|
||||
{% endif %}
|
||||
|
||||
{% set widgetKey = "listing-widget-" + widget.type + "-" + loop.index0 %}
|
||||
{% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
|
||||
|
||||
{# Collapsible wrapper — Alpine.js handles toggle, localStorage persists state #}
|
||||
<div
|
||||
class="widget-collapsible mb-4"
|
||||
x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : {{ defaultOpen }} }"
|
||||
>
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button
|
||||
class="widget-header w-full p-4"
|
||||
@click="open = !open; localStorage.setItem('{{ widgetKey }}', open)"
|
||||
:aria-expanded="open ? 'true' : 'false'"
|
||||
>
|
||||
<h3 class="widget-title font-bold text-lg">{{ widgetTitle }}</h3>
|
||||
<svg
|
||||
class="widget-chevron"
|
||||
:class="open && 'rotate-180'"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
x-show="open"
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
x-cloak
|
||||
>
|
||||
{# Widget content — inner .widget provides padding, inner title hidden by CSS #}
|
||||
{% if widget.type == "author-card" %}
|
||||
{% include "components/widgets/author-card.njk" %}
|
||||
{% elif widget.type == "author-card-compact" %}
|
||||
@@ -31,7 +87,6 @@
|
||||
{% elif widget.type == "search" %}
|
||||
<is-land on:visible>
|
||||
<div class="widget">
|
||||
<h3 class="widget-title">Search</h3>
|
||||
<div id="listing-sidebar-search"></div>
|
||||
<script>initPagefind("#listing-sidebar-search");</script>
|
||||
</div>
|
||||
@@ -44,9 +99,6 @@
|
||||
{% set wConfig = widget.config or {} %}
|
||||
<is-land on:visible>
|
||||
<div class="widget">
|
||||
{% if wConfig.title %}
|
||||
<h3 class="widget-title">{{ wConfig.title }}</h3>
|
||||
{% endif %}
|
||||
{% if wConfig.content %}
|
||||
<div class="prose dark:prose-invert prose-sm max-w-none">
|
||||
{{ wConfig.content | safe }}
|
||||
@@ -57,37 +109,142 @@
|
||||
{% else %}
|
||||
<!-- Unknown widget type: {{ widget.type }} -->
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# === Fallback: current hardcoded sidebar (backward compatibility) === #}
|
||||
{# Each widget wrapped in collapsible container #}
|
||||
|
||||
{# Author Card (h-card) — always shown #}
|
||||
{% set widgetKey = "listing-fb-author-card" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Author</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/author-card.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Social Activity — Bluesky/Mastodon feeds #}
|
||||
{% set widgetKey = "listing-fb-social-activity" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Social Activity</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/social-activity.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# GitHub Repos #}
|
||||
{% set widgetKey = "listing-fb-github-repos" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">GitHub</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/github-repos.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Funkwhale — Now Playing / Listening Stats #}
|
||||
{% set widgetKey = "listing-fb-funkwhale" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Listening</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/funkwhale.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Recent Posts (for non-blog pages) #}
|
||||
{# Recent Posts #}
|
||||
{% set widgetKey = "listing-fb-recent-posts" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Recent Posts</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/recent-posts.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Blogroll — only when backend is available #}
|
||||
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}
|
||||
{% set widgetKey = "listing-fb-blogroll" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Blogroll</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/blogroll.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# FeedLand — only when backend is available #}
|
||||
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}
|
||||
{% set widgetKey = "listing-fb-feedland" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">FeedLand</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/feedland.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Recent Comments #}
|
||||
{% set widgetKey = "listing-fb-recent-comments" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Recent Comments</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/recent-comments.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Categories/Tags #}
|
||||
{% set widgetKey = "listing-fb-categories" %}
|
||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
||||
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
|
||||
<h3 class="widget-title font-bold text-lg">Categories</h3>
|
||||
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
|
||||
{% include "components/widgets/categories.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -25,7 +25,7 @@ permalink: "articles/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
|
||||
{% if paginatedArticles.length > 0 %}
|
||||
<ul class="post-list">
|
||||
{% for post in paginatedArticles %}
|
||||
<li class="h-entry post-card">
|
||||
<li class="h-entry post-card border-l-[3px] border-l-surface-300 dark:border-l-surface-600">
|
||||
<div class="post-header">
|
||||
<h2 class="text-xl font-semibold mb-1 flex-1">
|
||||
<a class="p-name u-url text-surface-900 dark:text-surface-100 hover:text-primary-600 dark:hover:text-primary-400" href="{{ post.url }}">
|
||||
|
||||
16
blog.njk
16
blog.njk
@@ -45,7 +45,21 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
{% set replyToUrl = post.data.inReplyTo or post.data.in_reply_to %}
|
||||
{% set hasPhotos = post.data.photo and post.data.photo.length %}
|
||||
{% set _postType %}{% if likedUrl %}like{% elif bookmarkedUrl %}bookmark{% elif repostedUrl %}repost{% elif replyToUrl %}reply{% elif hasPhotos %}photo{% elif post.data.title %}article{% else %}note{% endif %}{% endset %}
|
||||
<li class="h-entry post-card" data-filter-type="{{ _postType | trim }}">
|
||||
{% set borderClass = "" %}
|
||||
{% if likedUrl %}
|
||||
{% set borderClass = "border-l-[3px] border-l-red-400 dark:border-l-red-500" %}
|
||||
{% elif bookmarkedUrl %}
|
||||
{% set borderClass = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
|
||||
{% elif repostedUrl %}
|
||||
{% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
|
||||
{% elif replyToUrl %}
|
||||
{% set borderClass = "border-l-[3px] border-l-primary-400 dark:border-l-primary-500" %}
|
||||
{% elif hasPhotos %}
|
||||
{% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
||||
{% else %}
|
||||
{% set borderClass = "border-l-[3px] border-l-surface-300 dark:border-l-surface-600" %}
|
||||
{% endif %}
|
||||
<li class="h-entry post-card {{ borderClass }}" data-filter-type="{{ _postType | trim }}">
|
||||
|
||||
{% if likedUrl %}
|
||||
{# ── Like card ── #}
|
||||
|
||||
@@ -25,7 +25,7 @@ permalink: "bookmarks/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageN
|
||||
{% if paginatedBookmarks.length > 0 %}
|
||||
<ul class="post-list">
|
||||
{% for post in paginatedBookmarks %}
|
||||
<li class="h-entry post-card">
|
||||
<li class="h-entry post-card border-l-[3px] border-l-amber-400 dark:border-l-amber-500">
|
||||
<div class="post-header">
|
||||
{% if post.data.title %}
|
||||
<h2 class="text-xl font-semibold mb-1 flex-1">
|
||||
|
||||
@@ -34,7 +34,23 @@ eleventyComputed:
|
||||
<p class="text-sm text-surface-500 dark:text-surface-400 mb-4">{{ categoryPosts.length }} post{% if categoryPosts.length != 1 %}s{% endif %}</p>
|
||||
<ul class="post-list">
|
||||
{% for post in categoryPosts %}
|
||||
<li class="h-entry post-card">
|
||||
{% set postType = post.inputPath | replace("./content/", "") %}
|
||||
{% set postType = postType.split("/")[0] %}
|
||||
{% set borderClass = "" %}
|
||||
{% if postType == "likes" %}
|
||||
{% set borderClass = "border-l-[3px] border-l-red-400 dark:border-l-red-500" %}
|
||||
{% elif postType == "bookmarks" %}
|
||||
{% set borderClass = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
|
||||
{% elif postType == "reposts" %}
|
||||
{% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
|
||||
{% elif postType == "replies" %}
|
||||
{% set borderClass = "border-l-[3px] border-l-primary-400 dark:border-l-primary-500" %}
|
||||
{% elif postType == "photos" %}
|
||||
{% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
||||
{% else %}
|
||||
{% set borderClass = "border-l-[3px] border-l-surface-300 dark:border-l-surface-600" %}
|
||||
{% endif %}
|
||||
<li class="h-entry post-card {{ borderClass }}">
|
||||
<div class="post-header">
|
||||
<h2 class="text-xl font-semibold mb-1 flex-1">
|
||||
<a class="p-name u-url text-surface-900 dark:text-surface-100 hover:text-primary-600 dark:hover:text-primary-400" href="{{ post.url }}">
|
||||
@@ -46,8 +62,6 @@ eleventyComputed:
|
||||
<time class="dt-published" datetime="{{ post.date | isoDate }}">
|
||||
{{ post.date | dateDisplay }}
|
||||
</time>
|
||||
{% set postType = post.inputPath | replace("./content/", "") %}
|
||||
{% set postType = postType.split("/")[0] %}
|
||||
<span class="post-type">{{ postType }}</span>
|
||||
</div>
|
||||
<p class="p-summary text-surface-700 dark:text-surface-300 mt-3">
|
||||
|
||||
@@ -25,7 +25,7 @@ permalink: "likes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
|
||||
{% if paginatedLikes.length > 0 %}
|
||||
<ul class="post-list">
|
||||
{% for post in paginatedLikes %}
|
||||
<li class="h-entry post-card">
|
||||
<li class="h-entry post-card border-l-[3px] border-l-red-400 dark:border-l-red-500">
|
||||
<div class="post-header flex items-start gap-3">
|
||||
<div class="flex-shrink-0 mt-1">
|
||||
<svg class="w-5 h-5 text-red-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
|
||||
@@ -25,7 +25,7 @@ permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
|
||||
{% if paginatedNotes.length > 0 %}
|
||||
<ul class="post-list">
|
||||
{% for post in paginatedNotes %}
|
||||
<li class="h-entry post-card">
|
||||
<li class="h-entry post-card border-l-[3px] border-l-surface-300 dark:border-l-surface-600">
|
||||
<div class="post-header">
|
||||
<a class="u-url" href="{{ post.url }}">
|
||||
<time class="dt-published text-sm text-primary-600 dark:text-primary-400 font-medium" datetime="{{ post.date | isoDate }}">
|
||||
|
||||
@@ -25,7 +25,7 @@ permalink: "photos/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumb
|
||||
{% if paginatedPhotos.length > 0 %}
|
||||
<ul class="post-list photo-list">
|
||||
{% for post in paginatedPhotos %}
|
||||
<li class="h-entry post-card">
|
||||
<li class="h-entry post-card border-l-[3px] border-l-purple-400 dark:border-l-purple-500">
|
||||
<div class="post-meta">
|
||||
<time class="dt-published" datetime="{{ post.date | isoDate }}">
|
||||
{{ post.date | dateDisplay }}
|
||||
|
||||
@@ -25,7 +25,7 @@ permalink: "replies/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
|
||||
{% if paginatedReplies.length > 0 %}
|
||||
<ul class="post-list">
|
||||
{% for post in paginatedReplies %}
|
||||
<li class="h-entry post-card">
|
||||
<li class="h-entry post-card border-l-[3px] border-l-primary-400 dark:border-l-primary-500">
|
||||
<div class="post-header flex items-start gap-3">
|
||||
<div class="flex-shrink-0 mt-1">
|
||||
<svg class="w-5 h-5 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
|
||||
@@ -25,7 +25,7 @@ permalink: "reposts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
|
||||
{% if paginatedReposts.length > 0 %}
|
||||
<ul class="post-list">
|
||||
{% for post in paginatedReposts %}
|
||||
<li class="h-entry post-card">
|
||||
<li class="h-entry post-card border-l-[3px] border-l-green-400 dark:border-l-green-500">
|
||||
<div class="post-header flex items-start gap-3">
|
||||
<div class="flex-shrink-0 mt-1">
|
||||
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
|
||||
Reference in New Issue
Block a user