mirror of
https://github.com/github/awesome-copilot.git
synced 2026-03-12 12:15:12 +00:00
Migrate website to Starlight with full-text resource search (#883)
* Add search functionality to Learning Hub index page Add a client-side search bar that filters articles by title, description, and tags. Sections with no matching results are hidden automatically. Uses the existing .search-bar CSS pattern from the cookbook page. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: remove deprecated layouts, theme script, and learning-hub config Phase 5 cleanup of Starlight migration: - Delete BaseLayout.astro (replaced by StarlightPage) - Delete ArticleLayout.astro (replaced by Starlight docs rendering) - Delete theme.ts (Starlight has built-in theme toggle) - Delete src/config/learning-hub.ts (sidebar order now in astro.config.mjs) - Replace learning-hub glob collection with Starlight docs collection in content.config.ts - Keep search.ts (still used by homepage and all resource page scripts) Build verified: 23 pages, no errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate website to Starlight with full-text resource search - Replace bespoke Astro layouts with Starlight integration - Homepage and resource pages use StarlightPage wrapper - Learning Hub articles rendered via Starlight docs collection - Starlight provides search, theme toggle, sidebar, ToC, a11y - Add custom Pagefind integration for resource search - All 614 agents/skills/instructions/hooks/workflows/plugins indexed as custom records with deep-link URLs - Type filter pills (horizontal pill toggles) above results - Search results link directly to resource modals via #file= hash - Move global.css to src/styles/ for Vite processing - Scope CSS reset to #main-content to avoid Starlight conflicts - Full-width page gradient via body:has(#main-content) - Light/dark theme support with Starlight gray scale inversion - Delete old layouts (BaseLayout, ArticleLayout), theme.ts, config Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review feedback - Fix pagefind-resources.ts header comment (pagefind:true not false) - Remove unused base variable in cookbook/index.astro - Replace hardcoded /awesome-copilot/ paths with relative links in index.md - Delete stale public/styles/global.css (source of truth is src/styles/) - Replace fragile getBasePath() with Astro config base in pagefind integration - Document pagefind:true reasoning in astro.config.mjs - Use proper visually-hidden pattern + :focus-visible ring for filter pills - Remove dead header/nav/theme CSS from global.css (~160 lines) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
142
website/src/integrations/pagefind-resources.ts
Normal file
142
website/src/integrations/pagefind-resources.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Custom Pagefind integration that extends Starlight's search index
|
||||
* with resource records (agents, skills, instructions, hooks, workflows, plugins).
|
||||
*
|
||||
* Starlight's pagefind is enabled (pagefind: true) so the search UI renders,
|
||||
* but this integration runs AFTER Starlight's and overwrites the index with
|
||||
* HTML pages + custom resource records combined.
|
||||
*/
|
||||
import type { AstroIntegration } from "astro";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import * as pagefind from "pagefind";
|
||||
|
||||
interface SearchRecord {
|
||||
type: string;
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
path: string;
|
||||
tags?: string[];
|
||||
searchText: string;
|
||||
}
|
||||
|
||||
const TYPE_LABELS: Record<string, string> = {
|
||||
agent: "Agent",
|
||||
instruction: "Instruction",
|
||||
skill: "Skill",
|
||||
hook: "Hook",
|
||||
workflow: "Workflow",
|
||||
plugin: "Plugin",
|
||||
tool: "Tool",
|
||||
};
|
||||
|
||||
const TYPE_PAGES: Record<string, string> = {
|
||||
agent: "/agents/",
|
||||
instruction: "/instructions/",
|
||||
skill: "/skills/",
|
||||
hook: "/hooks/",
|
||||
workflow: "/workflows/",
|
||||
plugin: "/plugins/",
|
||||
tool: "/tools/",
|
||||
};
|
||||
|
||||
export default function pagefindResources(): AstroIntegration {
|
||||
let siteBase = "/";
|
||||
|
||||
return {
|
||||
name: "pagefind-resources",
|
||||
hooks: {
|
||||
"astro:config:done": ({ config }) => {
|
||||
siteBase = config.base;
|
||||
},
|
||||
"astro:build:done": async ({ dir, logger }) => {
|
||||
const log = logger.fork("pagefind-resources");
|
||||
const now = performance.now();
|
||||
|
||||
try {
|
||||
log.info("Building search index with Pagefind + resource records...");
|
||||
|
||||
const response = await pagefind.createIndex();
|
||||
if (response.errors.length > 0) {
|
||||
for (const err of response.errors) log.error(err);
|
||||
throw new Error("Failed to create Pagefind index");
|
||||
}
|
||||
const { index } = response;
|
||||
|
||||
// Index all built HTML pages (same as Starlight's default)
|
||||
const indexResult = await index.addDirectory({
|
||||
path: fileURLToPath(dir),
|
||||
});
|
||||
if (indexResult.errors.length > 0) {
|
||||
for (const err of indexResult.errors) log.error(err);
|
||||
throw new Error("Failed to index HTML directory");
|
||||
}
|
||||
log.info(`Indexed ${indexResult.page_count} HTML pages.`);
|
||||
|
||||
// Read and index resource records from search-index.json
|
||||
const searchIndexPath = fileURLToPath(
|
||||
new URL("./data/search-index.json", dir)
|
||||
);
|
||||
let records: SearchRecord[];
|
||||
try {
|
||||
records = JSON.parse(readFileSync(searchIndexPath, "utf-8"));
|
||||
} catch {
|
||||
log.warn("Could not read search-index.json, skipping resource indexing.");
|
||||
records = [];
|
||||
}
|
||||
|
||||
// Use the base path from Astro config (e.g. "/awesome-copilot/")
|
||||
const base = siteBase.endsWith("/") ? siteBase : `${siteBase}/`;
|
||||
|
||||
let added = 0;
|
||||
for (const record of records) {
|
||||
const typePage = TYPE_PAGES[record.type];
|
||||
if (!typePage) continue;
|
||||
|
||||
const url = `${base}${typePage.slice(1)}#file=${encodeURIComponent(record.path)}`;
|
||||
const typeLabel = TYPE_LABELS[record.type] || record.type;
|
||||
|
||||
const addResult = await index.addCustomRecord({
|
||||
url,
|
||||
content: record.searchText || `${record.title} ${record.description}`,
|
||||
language: "en",
|
||||
meta: {
|
||||
title: `${record.title} — ${typeLabel}`,
|
||||
},
|
||||
filters: {
|
||||
type: [record.type],
|
||||
},
|
||||
});
|
||||
|
||||
if (addResult.errors.length > 0) {
|
||||
for (const err of addResult.errors) log.warn(`Record ${record.id}: ${err}`);
|
||||
} else {
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`Added ${added} resource records.`);
|
||||
|
||||
// Write the combined index
|
||||
const writeResult = await index.writeFiles({
|
||||
outputPath: fileURLToPath(new URL("./pagefind/", dir)),
|
||||
});
|
||||
if (writeResult.errors.length > 0) {
|
||||
for (const err of writeResult.errors) log.error(err);
|
||||
throw new Error("Failed to write Pagefind files");
|
||||
}
|
||||
|
||||
const elapsed = performance.now() - now;
|
||||
log.info(
|
||||
`Search index built in ${elapsed < 750 ? `${Math.round(elapsed)}ms` : `${(elapsed / 1000).toFixed(2)}s`}.`
|
||||
);
|
||||
} catch (cause) {
|
||||
throw new Error("Failed to build Pagefind search index.", { cause });
|
||||
} finally {
|
||||
await pagefind.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user