mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-04-02 16:44:56 +02:00
feat: add source tabs and recent sort to blogroll widget
Sidebar widget now fetches blogs sorted by recently updated and groups them into tabs by source type (Microsub/FeedLand). Tabs only appear when multiple source types exist. Each tab shows up to 8 blogs.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{# Blogroll Widget - Dynamic loading from API #}
|
||||
{# Blogroll Widget - Dynamic loading from API with source tabs #}
|
||||
<div class="widget" x-data="blogrollWidget()" x-init="init()">
|
||||
<h3 class="widget-title flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -7,8 +7,22 @@
|
||||
<a href="/blogroll/" class="hover:text-primary-600 dark:hover:text-primary-400">Blogroll</a>
|
||||
</h3>
|
||||
|
||||
<ul x-show="blogs.length > 0" class="space-y-2 mt-3">
|
||||
<template x-for="blog in blogs.slice(0, 8)" :key="blog.id">
|
||||
{# Source tabs - only shown when multiple sources exist #}
|
||||
<div x-show="tabs.length > 1" class="flex gap-1 mt-3 mb-2 border-b border-surface-200 dark:border-surface-700">
|
||||
<template x-for="tab in tabs" :key="tab.key">
|
||||
<button
|
||||
@click="activeTab = tab.key"
|
||||
:class="activeTab === tab.key
|
||||
? 'border-b-2 border-primary-600 text-primary-600 dark:text-primary-400 dark:border-primary-400'
|
||||
: 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
|
||||
class="px-2 py-1 text-xs font-medium transition-colors -mb-px"
|
||||
x-text="tab.label + ' (' + tab.count + ')'"
|
||||
></button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<ul x-show="filteredBlogs.length > 0" class="space-y-2" :class="tabs.length > 1 ? '' : 'mt-3'">
|
||||
<template x-for="blog in filteredBlogs.slice(0, 8)" :key="blog.id">
|
||||
<li>
|
||||
<a
|
||||
:href="blog.siteUrl || blog.feedUrl"
|
||||
@@ -25,12 +39,12 @@
|
||||
</template>
|
||||
</ul>
|
||||
|
||||
<div x-show="blogs.length === 0 && !loading" class="text-sm text-surface-500 py-2">
|
||||
<div x-show="filteredBlogs.length === 0 && !loading" class="text-sm text-surface-500 py-2">
|
||||
No blogs loaded yet.
|
||||
</div>
|
||||
|
||||
<a x-show="blogs.length > 0" href="/blogroll/" class="text-sm text-primary-600 dark:text-primary-400 hover:underline mt-3 inline-flex items-center gap-1">
|
||||
View all <span x-text="blogs.length"></span> blogs
|
||||
<a x-show="allBlogs.length > 0" href="/blogroll/" class="text-sm text-primary-600 dark:text-primary-400 hover:underline mt-3 inline-flex items-center gap-1">
|
||||
View all <span x-text="allBlogs.length"></span> blogs
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
@@ -38,18 +52,56 @@
|
||||
<script>
|
||||
function blogrollWidget() {
|
||||
return {
|
||||
blogs: [],
|
||||
allBlogs: [],
|
||||
activeTab: 'all',
|
||||
tabs: [],
|
||||
loading: true,
|
||||
|
||||
get filteredBlogs() {
|
||||
if (this.activeTab === 'all') return this.allBlogs;
|
||||
return this.allBlogs.filter(b => (b.source || 'other') === this.activeTab);
|
||||
},
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const res = await fetch('/blogrollapi/api/blogs?limit=10').then(r => r.json());
|
||||
this.blogs = res.items || [];
|
||||
const res = await fetch('/blogrollapi/api/blogs?sort=recent&limit=200').then(r => r.json());
|
||||
this.allBlogs = res.items || [];
|
||||
this.buildTabs();
|
||||
} catch (err) {
|
||||
console.error('Blogroll widget error:', err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
buildTabs() {
|
||||
const counts = {};
|
||||
for (const blog of this.allBlogs) {
|
||||
const src = blog.source || 'other';
|
||||
counts[src] = (counts[src] || 0) + 1;
|
||||
}
|
||||
|
||||
const labels = {
|
||||
microsub: 'Microsub',
|
||||
feedland: 'FeedLand',
|
||||
other: 'Other',
|
||||
};
|
||||
|
||||
const sources = Object.keys(counts);
|
||||
if (sources.length <= 1) {
|
||||
this.tabs = [];
|
||||
this.activeTab = 'all';
|
||||
return;
|
||||
}
|
||||
|
||||
this.tabs = sources.map(key => ({
|
||||
key,
|
||||
label: labels[key] || key,
|
||||
count: counts[key],
|
||||
}));
|
||||
|
||||
// Default to the first tab
|
||||
this.activeTab = this.tabs[0].key;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user