5,137 starred repos in Nunjucks template + Pagefind indexing exceeded the 2048MB Eleventy heap limit during build. Switched to Alpine.js client-side rendering: - _data/githubStarred.js: returns only buildDate (no API fetch) - starred.njk: fetches /githubapi/api/starred/all via Alpine.js - Added client-side text search (replaces separate Pagefind index) - Removed pagefind-starred build step and --exclude-selectors flag Confab-Link: http://localhost:8080/sessions/b130e9e5-4723-435d-8d5a-fc38113381c9
208 lines
9.1 KiB
Plaintext
208 lines
9.1 KiB
Plaintext
---
|
|
layout: layouts/base.njk
|
|
title: Starred Repositories
|
|
permalink: /github/starred/
|
|
eleventyExcludeFromCollections: true
|
|
---
|
|
|
|
<div class="starred-page" x-data="starredPage" x-cloak>
|
|
<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">
|
|
<template x-if="loading">
|
|
<span>Loading starred repos…</span>
|
|
</template>
|
|
<template x-if="!loading && totalCount > 0">
|
|
<span>
|
|
<span x-text="totalCount"></span> repos starred on GitHub.
|
|
<template x-if="lastSync">
|
|
<span>Last synced <span x-text="formatDate(lastSync)"></span>.</span>
|
|
</template>
|
|
</span>
|
|
</template>
|
|
<template x-if="!loading && totalCount === 0">
|
|
<span>No starred repositories found. The cache may still be syncing.</span>
|
|
</template>
|
|
</p>
|
|
</header>
|
|
|
|
{# Search box #}
|
|
<template x-if="!loading && allStars.length > 0">
|
|
<div class="mb-6">
|
|
<div class="relative">
|
|
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
</svg>
|
|
<input
|
|
type="text"
|
|
x-model="searchQuery"
|
|
placeholder="Search starred repos by name, description, topic, or language..."
|
|
class="w-full pl-10 pr-4 py-2.5 rounded-lg border border-surface-300 dark:border-surface-600 bg-white dark:bg-surface-800 text-surface-900 dark:text-surface-100 placeholder-surface-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
>
|
|
<template x-if="searchQuery">
|
|
<button @click="searchQuery = ''" class="absolute right-3 top-1/2 -translate-y-1/2 text-surface-400 hover:text-surface-600">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</template>
|
|
</div>
|
|
<template x-if="searchQuery">
|
|
<p class="mt-2 text-sm text-surface-500">
|
|
<span x-text="filteredStars.length"></span> results for “<span x-text="searchQuery"></span>”
|
|
</p>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
{# Loading state #}
|
|
<template x-if="loading">
|
|
<div class="text-center py-12">
|
|
<div class="inline-block w-8 h-8 border-4 border-primary-200 border-t-primary-600 rounded-full animate-spin"></div>
|
|
<p class="mt-4 text-surface-500">Loading starred repositories…</p>
|
|
</div>
|
|
</template>
|
|
|
|
{# Error state #}
|
|
<template x-if="error">
|
|
<p class="text-surface-600 dark:text-surface-400" x-text="error"></p>
|
|
</template>
|
|
|
|
{# All starred repos — client-side rendered #}
|
|
<template x-if="!loading && allStars.length > 0">
|
|
<div>
|
|
<div class="grid gap-3 sm:gap-4 md:grid-cols-2" id="starred-grid">
|
|
<template x-for="repo in visibleStars" :key="repo.fullName">
|
|
<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">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<template x-if="repo.ownerAvatar">
|
|
<img :src="repo.ownerAvatar" :alt="repo.ownerLogin" class="w-5 h-5 rounded-full" loading="lazy">
|
|
</template>
|
|
<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>
|
|
<template x-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">Archived</span>
|
|
</template>
|
|
</div>
|
|
|
|
<template x-if="repo.description">
|
|
<p class="text-sm text-surface-600 dark:text-surface-400 mb-2 line-clamp-2" x-text="repo.description"></p>
|
|
</template>
|
|
|
|
<template x-if="repo.topics && repo.topics.length">
|
|
<div class="flex flex-wrap gap-1.5 mb-2">
|
|
<template x-for="topic in repo.topics" :key="topic">
|
|
<span class="text-xs px-2 py-0.5 bg-primary-100 dark:bg-primary-900 text-primary-800 dark:text-primary-200 rounded" x-text="topic"></span>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="flex flex-wrap items-center gap-3 text-xs text-surface-500">
|
|
<template x-if="repo.language">
|
|
<span 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>
|
|
</template>
|
|
<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>
|
|
<template x-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>
|
|
<span x-text="repo.forks"></span>
|
|
</span>
|
|
</template>
|
|
<template x-if="repo.license">
|
|
<span x-text="repo.license"></span>
|
|
</template>
|
|
<template x-if="repo.starredAt">
|
|
<span x-text="'Starred ' + formatDate(repo.starredAt)"></span>
|
|
</template>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
</div>
|
|
|
|
{# Load More button #}
|
|
<template x-if="!searchQuery && visibleCount < allStars.length">
|
|
<div class="mt-6 text-center">
|
|
<button
|
|
@click="visibleCount = Math.min(visibleCount + 50, allStars.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="'(' + (allStars.length - visibleCount) + ' remaining)'"></span>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
{# Alpine.js component #}
|
|
<script>
|
|
document.addEventListener("alpine:init", () => {
|
|
Alpine.data("starredPage", () => ({
|
|
allStars: [],
|
|
totalCount: 0,
|
|
lastSync: null,
|
|
loading: true,
|
|
error: null,
|
|
searchQuery: "",
|
|
visibleCount: 50,
|
|
|
|
get filteredStars() {
|
|
if (!this.searchQuery) return this.allStars;
|
|
const q = this.searchQuery.toLowerCase();
|
|
return this.allStars.filter(r =>
|
|
(r.fullName && r.fullName.toLowerCase().includes(q)) ||
|
|
(r.description && r.description.toLowerCase().includes(q)) ||
|
|
(r.language && r.language.toLowerCase().includes(q)) ||
|
|
(r.topics && r.topics.some(t => t.toLowerCase().includes(q)))
|
|
);
|
|
},
|
|
|
|
get visibleStars() {
|
|
if (this.searchQuery) return this.filteredStars;
|
|
return this.allStars.slice(0, this.visibleCount);
|
|
},
|
|
|
|
formatDate(iso) {
|
|
if (!iso) return "";
|
|
try {
|
|
return new Date(iso).toLocaleDateString("en-US", {
|
|
year: "numeric", month: "short", day: "numeric"
|
|
});
|
|
} catch { return iso; }
|
|
},
|
|
|
|
async init() {
|
|
try {
|
|
const response = await fetch("/githubapi/api/starred/all");
|
|
if (!response.ok) throw new Error("API returned " + response.status);
|
|
const data = await response.json();
|
|
this.allStars = data.stars || [];
|
|
this.totalCount = data.totalCount || 0;
|
|
this.lastSync = data.lastSync || null;
|
|
} catch (err) {
|
|
this.error = "Could not load starred repositories. Try refreshing the page.";
|
|
console.error("[starred]", err);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
}));
|
|
});
|
|
</script>
|