feat: add /github/starred/ page with Pagefind search and live updates
- New starred.njk page rendering all ~5k starred repos as searchable cards - Separate Pagefind index (pagefind-starred) for starred-only search - Alpine.js live updates section for stars added since last build - Load More pagination (50 at a time, all in DOM) - githubStarred.js data file fetching from plugin API (1d cache) - Link from /github/ to /github/starred/ - Exclude starred cards from main site Pagefind index Confab-Link: http://localhost:8080/sessions/b130e9e5-4723-435d-8d5a-fc38113381c9
This commit is contained in:
44
_data/githubStarred.js
Normal file
44
_data/githubStarred.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* GitHub Starred Repos Data
|
||||
* Fetches all cached starred repos from Indiekit's GitHub endpoint
|
||||
* Uses EleventyFetch with 1-day cache (plugin handles freshness)
|
||||
*/
|
||||
|
||||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
|
||||
|
||||
export default async function () {
|
||||
const buildDate = new Date().toISOString();
|
||||
|
||||
try {
|
||||
const url = `${INDIEKIT_URL}/githubapi/api/starred/all`;
|
||||
console.log(`[githubStarred] Fetching from: ${url}`);
|
||||
|
||||
const data = await EleventyFetch(url, {
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[githubStarred] Loaded ${data.stars?.length || 0} starred repos (total: ${data.totalCount})`,
|
||||
);
|
||||
|
||||
return {
|
||||
stars: data.stars || [],
|
||||
totalCount: data.totalCount || 0,
|
||||
lastSync: data.lastSync || null,
|
||||
buildDate,
|
||||
source: "indiekit",
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(`[githubStarred] API unavailable: ${error.message}`);
|
||||
return {
|
||||
stars: [],
|
||||
totalCount: 0,
|
||||
lastSync: null,
|
||||
buildDate,
|
||||
source: "error",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@ export default function (eleventyConfig) {
|
||||
// Ignore Pagefind output directory
|
||||
eleventyConfig.ignores.add("pagefind");
|
||||
eleventyConfig.ignores.add("pagefind/**");
|
||||
eleventyConfig.ignores.add("pagefind-starred");
|
||||
eleventyConfig.ignores.add("pagefind-starred/**");
|
||||
|
||||
// Ignore interactive assets (served via passthrough copy, not processed as templates)
|
||||
eleventyConfig.ignores.add("interactive");
|
||||
@@ -48,6 +50,8 @@ export default function (eleventyConfig) {
|
||||
eleventyConfig.watchIgnores.add("/app/data/site/**");
|
||||
eleventyConfig.watchIgnores.add("pagefind");
|
||||
eleventyConfig.watchIgnores.add("pagefind/**");
|
||||
eleventyConfig.watchIgnores.add("pagefind-starred");
|
||||
eleventyConfig.watchIgnores.add("pagefind-starred/**");
|
||||
eleventyConfig.watchIgnores.add(".cache/og");
|
||||
eleventyConfig.watchIgnores.add(".cache/og/**");
|
||||
eleventyConfig.watchIgnores.add(".cache/unfurl");
|
||||
@@ -1039,7 +1043,7 @@ export default function (eleventyConfig) {
|
||||
const outputDir = directories?.output || dir.output;
|
||||
try {
|
||||
console.log(`[pagefind] Indexing ${outputDir} (${runMode})...`);
|
||||
execFileSync("npx", ["pagefind", "--site", outputDir, "--output-subdir", "pagefind", "--glob", "**/*.html"], {
|
||||
execFileSync("npx", ["pagefind", "--site", outputDir, "--output-subdir", "pagefind", "--glob", "**/*.html", "--exclude-selectors", ".starred-card"], {
|
||||
stdio: "inherit",
|
||||
timeout: 120000,
|
||||
});
|
||||
@@ -1047,6 +1051,23 @@ export default function (eleventyConfig) {
|
||||
} catch (err) {
|
||||
console.error("[pagefind] Indexing failed:", err.message);
|
||||
}
|
||||
|
||||
// Starred repos Pagefind index — separate from main site search
|
||||
try {
|
||||
console.log("[pagefind-starred] Indexing starred repos...");
|
||||
execFileSync("npx", [
|
||||
"pagefind",
|
||||
"--site", outputDir,
|
||||
"--output-subdir", "pagefind-starred",
|
||||
"--glob", "github/starred/index.html",
|
||||
], {
|
||||
stdio: "inherit",
|
||||
timeout: 120000,
|
||||
});
|
||||
console.log("[pagefind-starred] Indexing complete");
|
||||
} catch (err) {
|
||||
console.error("[pagefind-starred] Indexing failed:", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// WebSub hub notification — skip on incremental rebuilds
|
||||
|
||||
@@ -259,6 +259,10 @@ withSidebar: true
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<a href="/github/starred/" class="inline-block mt-4 text-primary-600 dark:text-primary-400 hover:underline">
|
||||
View all {{ githubStarred.totalCount | default(githubActivity.stars | length) }} starred repos →
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="text-surface-600 dark:text-surface-400">No starred repositories found.</p>
|
||||
{% endif %}
|
||||
|
||||
191
starred.njk
Normal file
191
starred.njk
Normal file
@@ -0,0 +1,191 @@
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
title: Starred Repositories
|
||||
permalink: /github/starred/
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
|
||||
<div class="starred-page">
|
||||
<header class="mb-6 sm:mb-8">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<a href="/github/" class="text-sm text-primary-600 dark:text-primary-400 hover:underline">← GitHub Activity</a>
|
||||
</div>
|
||||
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100 mb-2 flex items-center gap-3">
|
||||
<svg class="w-8 h-8 text-yellow-500" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.828 1.48 8.279-7.416-3.967-7.417 3.967 1.481-8.279-6.064-5.828 8.332-1.151z"/>
|
||||
</svg>
|
||||
Starred Repositories
|
||||
</h1>
|
||||
<p class="text-surface-600 dark:text-surface-400">
|
||||
{{ githubStarred.totalCount | default("0") }} repos starred on GitHub.
|
||||
{% if githubStarred.lastSync %}
|
||||
Last synced {{ githubStarred.lastSync | date("PPp") }}.
|
||||
{% endif %}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{# Search — separate Pagefind index for starred repos #}
|
||||
<div class="mb-8">
|
||||
<div id="starred-search"></div>
|
||||
<script>
|
||||
(function() {
|
||||
if (typeof _pfStarredQueue === "undefined") window._pfStarredQueue = [];
|
||||
_pfStarredQueue.push(["#starred-search", { showSubResults: false, showImages: false }]);
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{# Live updates — new stars since last build #}
|
||||
<div x-data="starredLive" x-show="newStars.length > 0" x-cloak class="mb-8">
|
||||
<h2 class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
|
||||
Recently Starred
|
||||
<span class="text-sm font-normal text-surface-500" x-text="newStars.length + ' new since last build'"></span>
|
||||
</h2>
|
||||
<div class="grid gap-3 sm:gap-4 md:grid-cols-2 mb-4">
|
||||
<template x-for="repo in newStars" :key="repo.fullName">
|
||||
<article class="p-4 bg-gradient-to-br from-green-50 to-white dark:from-green-900/20 dark:to-surface-800 rounded-lg border-2 border-green-200 dark:border-green-800">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<img :src="repo.ownerAvatar" :alt="repo.ownerLogin" class="w-5 h-5 rounded-full" loading="lazy">
|
||||
<h3 class="font-semibold text-surface-900 dark:text-surface-100 truncate">
|
||||
<a :href="repo.url" class="hover:text-primary-600 dark:hover:text-primary-400" target="_blank" rel="noopener" x-text="repo.fullName"></a>
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-sm text-surface-600 dark:text-surface-400 mb-2 line-clamp-2" x-text="repo.description"></p>
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs text-surface-500">
|
||||
<span x-show="repo.language" class="flex items-center gap-1">
|
||||
<span class="w-2.5 h-2.5 rounded-full bg-primary-500"></span>
|
||||
<span x-text="repo.language"></span>
|
||||
</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.828 1.48 8.279-7.416-3.967-7.417 3.967 1.481-8.279-6.064-5.828 8.332-1.151z"/></svg>
|
||||
<span x-text="repo.stars"></span>
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# All starred repos — Pagefind-indexed #}
|
||||
{% if githubStarred.stars.length %}
|
||||
<div x-data="{ visibleCount: 50 }">
|
||||
<h2 class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-4">
|
||||
All Starred
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-3 sm:gap-4 md:grid-cols-2" id="starred-grid">
|
||||
{% for repo in githubStarred.stars %}
|
||||
<article
|
||||
class="starred-card p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-600 transition-colors"
|
||||
data-pagefind-body
|
||||
data-index="{{ loop.index0 }}"
|
||||
{% if loop.index0 >= 50 %}x-show="visibleCount > {{ loop.index0 }}" x-cloak{% endif %}
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
{% if repo.ownerAvatar %}
|
||||
<img src="{{ repo.ownerAvatar }}" alt="{{ repo.ownerLogin }}" class="w-5 h-5 rounded-full" loading="lazy" data-pagefind-ignore>
|
||||
{% endif %}
|
||||
<h3 class="font-semibold text-surface-900 dark:text-surface-100 truncate" data-pagefind-meta="title">
|
||||
<a href="{{ repo.url }}" class="hover:text-primary-600 dark:hover:text-primary-400" target="_blank" rel="noopener">
|
||||
{{ repo.fullName }}
|
||||
</a>
|
||||
</h3>
|
||||
{% if repo.archived %}
|
||||
<span class="text-xs px-1.5 py-0.5 bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 rounded" data-pagefind-ignore>Archived</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if repo.description %}
|
||||
<p class="text-sm text-surface-600 dark:text-surface-400 mb-2 line-clamp-2">{{ repo.description }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if repo.topics.length %}
|
||||
<div class="flex flex-wrap gap-1.5 mb-2">
|
||||
{% for topic in repo.topics %}
|
||||
<span class="text-xs px-2 py-0.5 bg-primary-100 dark:bg-primary-900 text-primary-800 dark:text-primary-200 rounded" data-pagefind-filter="topic">{{ topic }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3 text-xs text-surface-500">
|
||||
{% if repo.language %}
|
||||
<span class="flex items-center gap-1" data-pagefind-filter="language">
|
||||
<span class="w-2.5 h-2.5 rounded-full bg-primary-500" data-pagefind-ignore></span>
|
||||
{{ repo.language }}
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.828 1.48 8.279-7.416-3.967-7.417 3.967 1.481-8.279-6.064-5.828 8.332-1.151z"/></svg>
|
||||
{{ repo.stars }}
|
||||
</span>
|
||||
{% if repo.forks > 0 %}
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"/></svg>
|
||||
{{ repo.forks }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if repo.license %}
|
||||
<span>{{ repo.license }}</span>
|
||||
{% endif %}
|
||||
{% if repo.starredAt %}
|
||||
<span data-pagefind-ignore>Starred {{ repo.starredAt | date("MMM d, yyyy") }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Load More button #}
|
||||
{% if githubStarred.stars.length > 50 %}
|
||||
<div class="mt-6 text-center" x-show="visibleCount < {{ githubStarred.stars.length }}">
|
||||
<button
|
||||
@click="visibleCount = Math.min(visibleCount + 50, {{ githubStarred.stars.length }})"
|
||||
class="px-6 py-2.5 bg-primary-600 hover:bg-primary-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Load More
|
||||
<span class="text-primary-200" x-text="'(' + ({{ githubStarred.stars.length }} - visibleCount) + ' remaining)'"></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-surface-600 dark:text-surface-400">No starred repositories found. The cache may still be syncing.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Alpine.js component for live starred updates #}
|
||||
<script>
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("starredLive", () => ({
|
||||
newStars: [],
|
||||
async init() {
|
||||
const buildDate = "{{ githubStarred.buildDate }}";
|
||||
if (!buildDate || buildDate === "") return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/githubapi/api/starred/recent?since=${encodeURIComponent(buildDate)}`);
|
||||
if (!response.ok) return;
|
||||
const data = await response.json();
|
||||
this.newStars = data.stars || [];
|
||||
} catch {
|
||||
// Silently fail — live updates are a nice-to-have
|
||||
}
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
{# Separate Pagefind instance for starred repos #}
|
||||
<link rel="stylesheet" href="/pagefind-starred/pagefind-ui.css" media="print" onload="this.media='all'">
|
||||
<noscript><link rel="stylesheet" href="/pagefind-starred/pagefind-ui.css"></noscript>
|
||||
<script src="/pagefind-starred/pagefind-ui.js" defer></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (typeof PagefindUI === "undefined") return;
|
||||
if (typeof _pfStarredQueue === "undefined") return;
|
||||
for (const [sel, opts] of _pfStarredQueue) {
|
||||
new PagefindUI(Object.assign({ element: sel, showSubResults: false, showImages: false, resetStyles: false }, opts));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user