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'] = '';
+---
+
+
+
+
+
+
+
+{
+ /**
+ * 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');
@@ -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');
@@ -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
-
-
-
- Type at least two characters to show matching resources, then press
- the Down Arrow key to move into the results.
-
-
-
-
-
-
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');
@@ -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;
@@ -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');
@@ -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) => ({
-
-
-
-
+
@@ -64,53 +72,6 @@ const initialItems = toolsData.items.map((item) => ({
diff --git a/website/src/pages/workflows.astro b/website/src/pages/workflows.astro
index 1b742727..98058948 100644
--- a/website/src/pages/workflows.astro
+++ b/website/src/pages/workflows.astro
@@ -18,28 +18,29 @@ const initialItems = sortWorkflows(workflowsData.items, 'title');
@@ -51,6 +52,7 @@ const initialItems = sortWorkflows(workflowsData.items, 'title');
diff --git a/website/src/scripts/listing-flyouts.ts b/website/src/scripts/listing-flyouts.ts
new file mode 100644
index 00000000..4d0ecb7b
--- /dev/null
+++ b/website/src/scripts/listing-flyouts.ts
@@ -0,0 +1,64 @@
+declare global {
+ interface Window {
+ __awesomeCopilotListingFlyoutsInitialized?: boolean;
+ }
+}
+
+const FLYOUT_SELECTOR = '.listing-controls';
+
+function closeFlyouts(except?: HTMLDetailsElement): void {
+ document.querySelectorAll
(FLYOUT_SELECTOR).forEach((flyout) => {
+ if (flyout !== except) {
+ flyout.open = false;
+ }
+ });
+}
+
+export function initListingFlyouts(): void {
+ if (window.__awesomeCopilotListingFlyoutsInitialized) return;
+
+ document.addEventListener(
+ 'toggle',
+ (event) => {
+ const flyout = event.target;
+ if (!(flyout instanceof HTMLDetailsElement) || !flyout.matches(FLYOUT_SELECTOR) || !flyout.open) {
+ return;
+ }
+
+ closeFlyouts(flyout);
+ },
+ true
+ );
+
+ document.addEventListener('click', (event) => {
+ const target = event.target;
+ if (target instanceof Element && target.closest(FLYOUT_SELECTOR)) {
+ return;
+ }
+
+ closeFlyouts();
+ });
+
+ document.addEventListener('keydown', (event) => {
+ if (event.key !== 'Escape') return;
+
+ const activeFlyout = document.activeElement instanceof Element
+ ? (document.activeElement.closest(FLYOUT_SELECTOR) as HTMLDetailsElement | null)
+ : null;
+
+ closeFlyouts();
+
+ const summary = activeFlyout?.querySelector('summary');
+ if (summary instanceof HTMLElement) {
+ summary.focus();
+ }
+ });
+
+ window.__awesomeCopilotListingFlyoutsInitialized = true;
+}
+
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initListingFlyouts, { once: true });
+} else {
+ initListingFlyouts();
+}
diff --git a/website/src/scripts/pages/agents-render.ts b/website/src/scripts/pages/agents-render.ts
index 068ec99c..64696e72 100644
--- a/website/src/scripts/pages/agents-render.ts
+++ b/website/src/scripts/pages/agents-render.ts
@@ -35,36 +35,23 @@ export function sortAgents(
});
}
-export function renderAgentsHtml(
- items: RenderableAgent[],
- options: {
- query?: string;
- highlightTitle?: (title: string, query: string) => string;
- } = {}
-): string {
- const { query = "", highlightTitle } = options;
-
+export function renderAgentsHtml(items: RenderableAgent[]): string {
if (items.length === 0) {
return `
No agents found
-
Try a different search term or adjust filters
+
No agents are available right now.
`;
}
return items
.map((item) => {
- const titleHtml =
- query && highlightTitle
- ? highlightTitle(item.title, query)
- : escapeHtml(item.title);
-
return `