diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs index 598e3d85..4ef28428 100755 --- a/eng/generate-website-data.mjs +++ b/eng/generate-website-data.mjs @@ -354,50 +354,6 @@ function generateInstructionsData(gitDates) { }; } -/** - * Categorize a skill based on its name and description - */ -function categorizeSkill(name, description) { - const text = `${name} ${description}`.toLowerCase(); - - if (text.includes("azure") || text.includes("appinsights")) return "Azure"; - if ( - text.includes("github") || - text.includes("gh-cli") || - text.includes("git-commit") || - text.includes("git ") - ) - return "Git & GitHub"; - if (text.includes("vscode") || text.includes("vs code")) return "VS Code"; - if ( - text.includes("test") || - text.includes("qa") || - text.includes("playwright") - ) - return "Testing"; - if ( - text.includes("microsoft") || - text.includes("m365") || - text.includes("workiq") - ) - return "Microsoft"; - if (text.includes("cli") || text.includes("command")) return "CLI Tools"; - if ( - text.includes("diagram") || - text.includes("plantuml") || - text.includes("visual") - ) - return "Diagrams"; - if ( - text.includes("nuget") || - text.includes("dotnet") || - text.includes(".net") - ) - return ".NET"; - - return "Other"; -} - /** * Generate skills metadata */ @@ -405,15 +361,13 @@ function generateSkillsData(gitDates) { const skills = []; if (!fs.existsSync(SKILLS_DIR)) { - return { items: [], filters: { categories: [], hasAssets: ["Yes", "No"] } }; + return { items: [], filters: { hasAssets: ["Yes", "No"] } }; } const folders = fs .readdirSync(SKILLS_DIR) .filter((f) => fs.statSync(path.join(SKILLS_DIR, f)).isDirectory()); - const allCategories = new Set(); - for (const folder of folders) { const skillPath = path.join(SKILLS_DIR, folder); const metadata = parseSkillMetadata(skillPath); @@ -422,8 +376,6 @@ function generateSkillsData(gitDates) { const relativePath = path .relative(ROOT_FOLDER, skillPath) .replace(/\\/g, "/"); - const category = categorizeSkill(metadata.name, metadata.description); - allCategories.add(category); // Get all files in the skill folder recursively const files = getSkillFiles(skillPath, relativePath); @@ -440,7 +392,6 @@ function generateSkillsData(gitDates) { folder, metadata.name, relativePath, - category, ] .join(" ") .toLowerCase(); @@ -453,7 +404,6 @@ function generateSkillsData(gitDates) { assets: metadata.assets, hasAssets: metadata.assets.length > 0, assetCount: metadata.assets.length, - category: category, path: relativePath, skillFile: skillFilePath, files: files, @@ -468,7 +418,6 @@ function generateSkillsData(gitDates) { return { items: sortedSkills, filters: { - categories: Array.from(allCategories).sort(), hasAssets: ["Yes", "No"], }, }; @@ -976,9 +925,7 @@ async function main() { const skillsData = generateSkillsData(gitDates); const skills = skillsData.items; - console.log( - `✓ Generated ${skills.length} skills (${skillsData.filters.categories.length} categories)` - ); + console.log(`✓ Generated ${skills.length} skills`); const pluginsData = generatePluginsData(gitDates); const plugins = pluginsData.items; diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 13e9f92f..87e8be45 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -119,6 +119,7 @@ export default defineConfig({ components: { Head: "./src/components/Head.astro", Footer: "./src/components/Footer.astro", + Search: "./src/components/Search.astro", }, }), sitemap(), diff --git a/website/src/components/Search.astro b/website/src/components/Search.astro new file mode 100644 index 00000000..6a34da31 --- /dev/null +++ b/website/src/components/Search.astro @@ -0,0 +1,516 @@ +--- +import Icon from './Icon.astro'; +import project from 'virtual:starlight/project-context'; + +const pagefindTranslations = { + placeholder: Astro.locals.t('search.label'), + ...Object.fromEntries( + Object.entries(Astro.locals.t.all()) + .filter(([key]) => key.startsWith('pagefind.')) + .map(([key, value]) => [key.replace('pagefind.', ''), value]) + ), +}; + +const dataAttributes: DOMStringMap = { 'data-translations': JSON.stringify(pagefindTranslations) }; +if (project.trailingSlash === 'never') dataAttributes['data-strip-trailing-slash'] = ''; +--- + + + + + +
+ { + /* TODO: Make the layout of this button flexible to accommodate different word lengths. Currently hard-coded for English: “Cancel” */ + } + + { + import.meta.env.DEV ? ( +
+

{Astro.locals.t('search.devWarning')}

+
+ ) : ( +
+ + ) + } +
+
+
+ +{ + /** + * This is intentionally inlined to avoid briefly showing an invalid shortcut. + * Purposely using the deprecated `navigator.platform` property to detect Apple devices, as the + * user agent is spoofed by some browsers when opening the devtools. + */ +} + + + + + + + diff --git a/website/src/integrations/pagefind-resources.ts b/website/src/integrations/pagefind-resources.ts index 95a56335..966f4bfd 100644 --- a/website/src/integrations/pagefind-resources.ts +++ b/website/src/integrations/pagefind-resources.ts @@ -64,6 +64,10 @@ export default function pagefindResources(): AstroIntegration { } const { index } = response; + if (!index) { + throw new Error("Pagefind index is undefined"); + } + // Index all built HTML pages (same as Starlight's default) const indexResult = await index.addDirectory({ path: fileURLToPath(dir), @@ -82,7 +86,9 @@ export default function pagefindResources(): AstroIntegration { try { records = JSON.parse(readFileSync(searchIndexPath, "utf-8")); } catch { - log.warn("Could not read search-index.json, skipping resource indexing."); + log.warn( + "Could not read search-index.json, skipping resource indexing." + ); records = []; } @@ -94,12 +100,15 @@ export default function pagefindResources(): AstroIntegration { const typePage = TYPE_PAGES[record.type]; if (!typePage) continue; - const url = `${base}${typePage.slice(1)}#file=${encodeURIComponent(record.path)}`; + 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}`, + content: + record.searchText || `${record.title} ${record.description}`, language: "en", meta: { title: `${record.title} — ${typeLabel}`, @@ -110,7 +119,8 @@ export default function pagefindResources(): AstroIntegration { }); if (addResult.errors.length > 0) { - for (const err of addResult.errors) log.warn(`Record ${record.id}: ${err}`); + for (const err of addResult.errors) + log.warn(`Record ${record.id}: ${err}`); } else { added++; } @@ -129,7 +139,11 @@ export default function pagefindResources(): AstroIntegration { const elapsed = performance.now() - now; log.info( - `Search index built in ${elapsed < 750 ? `${Math.round(elapsed)}ms` : `${(elapsed / 1000).toFixed(2)}s`}.` + `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 }); diff --git a/website/src/pages/agents.astro b/website/src/pages/agents.astro index bd51fee0..128fc3dd 100644 --- a/website/src/pages/agents.astro +++ b/website/src/pages/agents.astro @@ -18,39 +18,24 @@ const initialItems = sortAgents(agentsData.items, 'title');
- - - -
-
- - -
-
- - -
-
- -
-
- - -
- +
+
{initialItems.length} agents
+
+ Sort +
+
+
+ + +
+
+
+
- -
{initialItems.length} of {initialItems.length} agents
@@ -62,6 +47,7 @@ const initialItems = sortAgents(agentsData.items, 'title'); diff --git a/website/src/pages/hooks.astro b/website/src/pages/hooks.astro index b1d982e8..73733433 100644 --- a/website/src/pages/hooks.astro +++ b/website/src/pages/hooks.astro @@ -18,32 +18,29 @@ const initialItems = sortHooks(hooksData.items, 'title');
- - -
-
- - -
-
- - -
-
- - -
- +
+
{initialItems.length} hooks
+
+ Sort & Filter +
+
+
+ + +
+
+ + +
+ +
+
+
- -
{initialItems.length} of {initialItems.length} hooks
@@ -55,6 +52,7 @@ const initialItems = sortHooks(hooksData.items, 'title'); diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 51cff6ab..f2ffae9a 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -40,49 +40,6 @@ const base = import.meta.env.BASE_URL; Community-contributed agents, instructions, and skills to enhance your GitHub Copilot experience

-
diff --git a/website/src/pages/instructions.astro b/website/src/pages/instructions.astro index 6767edaa..5c017b6a 100644 --- a/website/src/pages/instructions.astro +++ b/website/src/pages/instructions.astro @@ -18,28 +18,29 @@ const initialItems = sortInstructions(instructionsData.items, 'title');
- - -
-
- - -
-
- - -
- +
+
{initialItems.length} instructions
+
+ Sort & Filter +
+
+
+ + +
+
+ + +
+ +
+
+
- -
{initialItems.length} of {initialItems.length} instructions
@@ -51,6 +52,7 @@ const initialItems = sortInstructions(instructionsData.items, 'title'); diff --git a/website/src/pages/plugins.astro b/website/src/pages/plugins.astro index 3f1fd7ed..2d8d7e20 100644 --- a/website/src/pages/plugins.astro +++ b/website/src/pages/plugins.astro @@ -6,9 +6,9 @@ import ContributeCTA from '../components/ContributeCTA.astro'; import EmbeddedPageData from '../components/EmbeddedPageData.astro'; import PageHeader from '../components/PageHeader.astro'; import BackToTop from '../components/BackToTop.astro'; -import { renderPluginsHtml } from '../scripts/pages/plugins-render'; +import { renderPluginsHtml, sortPlugins } from '../scripts/pages/plugins-render'; -const initialItems = pluginsData.items; +const initialItems = sortPlugins(pluginsData.items, 'title'); --- @@ -27,21 +27,29 @@ const initialItems = pluginsData.items;
- - -
-
- - -
- +
+
{initialItems.length} plugins
+
+ Sort & Filter +
+
+
+ + +
+
+ + +
+ +
+
+
- -
{initialItems.length} of {initialItems.length} plugins
@@ -53,6 +61,7 @@ const initialItems = pluginsData.items; diff --git a/website/src/pages/skills.astro b/website/src/pages/skills.astro index 62844ec8..7acdbaa5 100644 --- a/website/src/pages/skills.astro +++ b/website/src/pages/skills.astro @@ -18,34 +18,24 @@ const initialItems = sortSkills(skillsData.items, 'title');
- - -
-
- - -
-
- -
-
- - -
- +
+
{initialItems.length} skills
+
+ Sort +
+
+
+ + +
+
+
+
- -
{initialItems.length} of {initialItems.length} skills
@@ -57,6 +47,7 @@ const initialItems = sortSkills(skillsData.items, 'title'); diff --git a/website/src/pages/tools.astro b/website/src/pages/tools.astro index c8e86e91..c859bb6c 100644 --- a/website/src/pages/tools.astro +++ b/website/src/pages/tools.astro @@ -6,12 +6,15 @@ import ContributeCTA from "../components/ContributeCTA.astro"; import EmbeddedPageData from "../components/EmbeddedPageData.astro"; import PageHeader from "../components/PageHeader.astro"; import BackToTop from '../components/BackToTop.astro'; -import { renderToolsHtml } from "../scripts/pages/tools-render"; +import { renderToolsHtml, sortTools } from "../scripts/pages/tools-render"; -const initialItems = toolsData.items.map((item) => ({ - ...item, - title: item.name, -})); +const initialItems = sortTools( + toolsData.items.map((item) => ({ + ...item, + title: item.name, + })), + "title" +); --- @@ -20,29 +23,34 @@ const initialItems = toolsData.items.map((item) => ({
-
-