From e5ab942036ce1944d8679e23349b5279cf79fb5d Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Tue, 14 Jul 2026 12:45:43 +1000 Subject: [PATCH] Removing the pages and their references --- eng/generate-website-data.mjs | 265 +---------------- website/astro.config.mjs | 3 - .../docs/learning-hub/agentic-workflows.md | 2 - .../learning-hub/automating-with-hooks.md | 1 - .../using-copilot-coding-agent.md | 2 +- website/src/pages/hook/[id].astro | 159 ---------- website/src/pages/hooks.astro | 56 ---- website/src/pages/tools.astro | 277 ------------------ website/src/pages/workflow/[id].astro | 131 --------- website/src/pages/workflows.astro | 56 ---- website/src/scripts/pages/hooks-render.ts | 104 ------- website/src/scripts/pages/hooks.ts | 199 ------------- website/src/scripts/pages/tools-render.ts | 203 ------------- website/src/scripts/pages/tools.ts | 211 ------------- website/src/scripts/pages/workflows-render.ts | 75 ----- website/src/scripts/pages/workflows.ts | 139 --------- 16 files changed, 3 insertions(+), 1880 deletions(-) delete mode 100644 website/src/pages/hook/[id].astro delete mode 100644 website/src/pages/hooks.astro delete mode 100644 website/src/pages/tools.astro delete mode 100644 website/src/pages/workflow/[id].astro delete mode 100644 website/src/pages/workflows.astro delete mode 100644 website/src/scripts/pages/hooks-render.ts delete mode 100644 website/src/scripts/pages/hooks.ts delete mode 100644 website/src/scripts/pages/tools-render.ts delete mode 100644 website/src/scripts/pages/tools.ts delete mode 100644 website/src/scripts/pages/workflows-render.ts delete mode 100644 website/src/scripts/pages/workflows.ts diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs index 2e646443..fb138806 100755 --- a/eng/generate-website-data.mjs +++ b/eng/generate-website-data.mjs @@ -2,7 +2,7 @@ /** * Generate JSON metadata files for the GitHub Pages website. - * This script extracts metadata from agents, instructions, skills, hooks, and plugins + * This script extracts metadata from agents, instructions, skills, and plugins * and writes them to website/data/ for client-side search and display. */ @@ -14,19 +14,15 @@ import { AGENTS_DIR, COOKBOOK_DIR, EXTENSIONS_DIR, - HOOKS_DIR, INSTRUCTIONS_DIR, PLUGINS_DIR, ROOT_FOLDER, SKILLS_DIR, - WORKFLOWS_DIR, } from "./constants.mjs"; import { getGitFileDates } from "./utils/git-dates.mjs"; import { parseFrontmatter, - parseHookMetadata, parseSkillMetadata, - parseWorkflowMetadata, parseYamlFile, } from "./yaml-parser.mjs"; @@ -211,136 +207,6 @@ function generateAgentsData(gitDates) { }; } -/** - * Generate hooks metadata - */ -/** - * Generate hooks metadata (similar to skills - folder-based) - */ -function generateHooksData(gitDates) { - const hooks = []; - - // Check if hooks directory exists - if (!fs.existsSync(HOOKS_DIR)) { - return { - items: hooks, - filters: { - hooks: [], - tags: [], - }, - }; - } - - // Get all hook folders (directories) - const hookFolders = fs.readdirSync(HOOKS_DIR).filter((file) => { - const filePath = path.join(HOOKS_DIR, file); - return fs.statSync(filePath).isDirectory(); - }); - - // Track all unique values for filters - const allHookTypes = new Set(); - const allTags = new Set(); - - for (const folder of hookFolders) { - const hookPath = path.join(HOOKS_DIR, folder); - const metadata = parseHookMetadata(hookPath); - if (!metadata) continue; - - const relativePath = path - .relative(ROOT_FOLDER, hookPath) - .replace(/\\/g, "/"); - const readmeRelativePath = `${relativePath}/README.md`; - - // Get all files in the hook folder recursively (for the file browser and - // ZIP download on the detail page). - const files = getFolderFiles(hookPath, relativePath); - - // Track unique values - (metadata.hooks || []).forEach((h) => allHookTypes.add(h)); - (metadata.tags || []).forEach((t) => allTags.add(t)); - - hooks.push({ - id: folder, - title: metadata.name, - description: metadata.description, - hooks: metadata.hooks || [], - tags: metadata.tags || [], - assets: metadata.assets || [], - files, - path: relativePath, - readmeFile: readmeRelativePath, - readmeFileName: "README.md", - lastUpdated: gitDates.get(readmeRelativePath) || null, - }); - } - - // Sort and return with filter metadata - const sortedHooks = hooks.sort((a, b) => a.title.localeCompare(b.title)); - - return { - items: sortedHooks, - filters: { - hooks: Array.from(allHookTypes).sort(), - tags: Array.from(allTags).sort(), - }, - }; -} - -/** - * Generate workflows metadata (flat .md files) - */ -function generateWorkflowsData(gitDates) { - const workflows = []; - - if (!fs.existsSync(WORKFLOWS_DIR)) { - return { - items: workflows, - filters: { - triggers: [], - }, - }; - } - - const workflowFiles = fs.readdirSync(WORKFLOWS_DIR).filter((file) => { - return file.endsWith(".md") && file !== ".gitkeep"; - }); - - const allTriggers = new Set(); - - for (const file of workflowFiles) { - const filePath = path.join(WORKFLOWS_DIR, file); - const metadata = parseWorkflowMetadata(filePath); - if (!metadata) continue; - - const relativePath = path - .relative(ROOT_FOLDER, filePath) - .replace(/\\/g, "/"); - - (metadata.triggers || []).forEach((t) => allTriggers.add(t)); - - const id = path.basename(file, ".md"); - workflows.push({ - id, - title: metadata.name, - description: metadata.description, - triggers: metadata.triggers || [], - path: relativePath, - lastUpdated: gitDates.get(relativePath) || null, - }); - } - - const sortedWorkflows = workflows.sort((a, b) => - a.title.localeCompare(b.title) - ); - - return { - items: sortedWorkflows, - filters: { - triggers: Array.from(allTriggers).sort(), - }, - }; -} - /** * Parse applyTo field into an array of patterns */ @@ -565,7 +431,7 @@ function getAgentFiles(agentDir, pluginRootPath) { * Build a lookup index of resource id -> { title, url } for the kinds that have * dedicated detail pages, so plugin items can deep-link to them. */ -function buildResourceIndex({ agents, skills, instructions, hooks, extensions }) { +function buildResourceIndex({ agents, skills, instructions, extensions }) { const toMap = (items, urlPrefix) => { const map = new Map(); for (const item of items || []) { @@ -602,7 +468,6 @@ function buildResourceIndex({ agents, skills, instructions, hooks, extensions }) agent: toMap(agents, "agent"), skill: toMap(skills, "skill"), instruction: toMap(instructions, "instruction"), - hook: toMap(hooks, "hook"), extension: extensionMap, }; } @@ -1459,71 +1324,12 @@ function generateExtensionsData(extensionManifestData) { return { items, filters }; } -/** - * Generate tools metadata from website/data/tools.yml - */ -function generateToolsData() { - const toolsFile = path.join(WEBSITE_SOURCE_DATA_DIR, "tools.yml"); - - if (!fs.existsSync(toolsFile)) { - console.warn("No tools.yml file found at", toolsFile); - return { items: [], filters: { categories: [], tags: [] } }; - } - - const data = parseYamlFile(toolsFile); - - if (!data || !data.tools) { - return { items: [], filters: { categories: [], tags: [] } }; - } - - const allCategories = new Set(); - const allTags = new Set(); - - const tools = data.tools.map((tool) => { - const category = tool.category || "Other"; - allCategories.add(category); - - const tags = tool.tags || []; - tags.forEach((t) => allTags.add(t)); - - return { - id: tool.id, - name: tool.name, - description: tool.description || "", - category: category, - featured: tool.featured || false, - requirements: tool.requirements || [], - features: tool.features || [], - links: tool.links || {}, - configuration: tool.configuration || null, - tags: tags, - }; - }); - - // Sort with featured first, then alphabetically - const sortedTools = tools.sort((a, b) => { - if (a.featured && !b.featured) return -1; - if (!a.featured && b.featured) return 1; - return a.name.localeCompare(b.name); - }); - - return { - items: sortedTools, - filters: { - categories: Array.from(allCategories).sort(), - tags: Array.from(allTags).sort(), - }, - }; -} - /** * Generate a combined index for search */ function generateSearchIndex( agents, instructions, - hooks, - workflows, skills, plugins ) { @@ -1556,33 +1362,6 @@ function generateSearchIndex( }); } - for (const hook of hooks) { - index.push({ - type: "hook", - id: hook.id, - title: hook.title, - description: hook.description, - path: hook.readmeFile, - lastUpdated: hook.lastUpdated, - searchText: `${hook.title} ${hook.description} ${hook.hooks.join( - " " - )} ${hook.tags.join(" ")}`.toLowerCase(), - }); - } - - for (const workflow of workflows) { - index.push({ - type: "workflow", - id: workflow.id, - title: workflow.title, - description: workflow.description, - path: workflow.path, - lastUpdated: workflow.lastUpdated, - searchText: `${workflow.title} ${workflow.description - } ${workflow.triggers.join(" ")}`.toLowerCase(), - }); - } - for (const skill of skills) { index.push({ type: "skill", @@ -1752,8 +1531,6 @@ async function main() { [ "agents/", "instructions/", - "hooks/", - "workflows/", "skills/", "extensions/", "plugins/", @@ -1771,18 +1548,6 @@ async function main() { `✓ Generated ${agents.length} agents (${agentsData.filters.models.length} models, ${agentsData.filters.tools.length} tools)` ); - const hooksData = generateHooksData(gitDates); - const hooks = hooksData.items; - console.log( - `✓ Generated ${hooks.length} hooks (${hooksData.filters.hooks.length} hook types, ${hooksData.filters.tags.length} tags)` - ); - - const workflowsData = generateWorkflowsData(gitDates); - const workflows = workflowsData.items; - console.log( - `✓ Generated ${workflows.length} workflows (${workflowsData.filters.triggers.length} triggers)` - ); - const instructionsData = generateInstructionsData(gitDates); const instructions = instructionsData.items; console.log( @@ -1804,7 +1569,6 @@ async function main() { agents, skills, instructions, - hooks, extensions, }); const pluginsData = generatePluginsData(gitDates, resourceIndex); @@ -1813,12 +1577,6 @@ async function main() { `✓ Generated ${plugins.length} plugins (${pluginsData.filters.tags.length} tags)` ); - const toolsData = generateToolsData(); - const tools = toolsData.items; - console.log( - `✓ Generated ${tools.length} tools (${toolsData.filters.categories.length} categories)` - ); - const samplesData = generateSamplesData(); console.log( `✓ Generated ${samplesData.totalRecipes} recipes in ${samplesData.totalCookbooks} cookbooks (${samplesData.filters.languages.length} languages, ${samplesData.filters.tags.length} tags)` @@ -1833,8 +1591,6 @@ async function main() { const searchIndex = generateSearchIndex( agents, instructions, - hooks, - workflows, skills, plugins ); @@ -1846,16 +1602,6 @@ async function main() { JSON.stringify(agentsData, null, 2) ); - fs.writeFileSync( - path.join(WEBSITE_DATA_DIR, "hooks.json"), - JSON.stringify(hooksData, null, 2) - ); - - fs.writeFileSync( - path.join(WEBSITE_DATA_DIR, "workflows.json"), - JSON.stringify(workflowsData, null, 2) - ); - fs.writeFileSync( path.join(WEBSITE_DATA_DIR, "instructions.json"), JSON.stringify(instructionsData, null, 2) @@ -1876,10 +1622,6 @@ async function main() { JSON.stringify(extensionsData, null, 2) ); - fs.writeFileSync( - path.join(WEBSITE_DATA_DIR, "tools.json"), - JSON.stringify(toolsData, null, 2) - ); fs.writeFileSync( path.join(WEBSITE_DATA_DIR, "samples.json"), @@ -1898,11 +1640,8 @@ async function main() { agents: agents.length, instructions: instructions.length, skills: skills.length, - hooks: hooks.length, - workflows: workflows.length, plugins: plugins.length, extensions: extensions.length, - tools: tools.length, contributors: contributorCount, samples: samplesData.totalRecipes, total: searchIndex.length, diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 1df5c300..0ef62bc5 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -128,11 +128,8 @@ export default defineConfig({ { label: "Agents", link: "/agents/" }, { label: "Instructions", link: "/instructions/" }, { label: "Skills", link: "/skills/" }, - { label: "Hooks", link: "/hooks/" }, - { label: "Workflows", link: "/workflows/" }, { label: "Canvas Extensions", link: "/extensions/" }, { label: "Plugins", link: "/plugins/" }, - { label: "Tools", link: "/tools/" }, { label: "Contributors", link: "/contributors/" }, ], }, diff --git a/website/src/content/docs/learning-hub/agentic-workflows.md b/website/src/content/docs/learning-hub/agentic-workflows.md index 70987839..7b876aec 100644 --- a/website/src/content/docs/learning-hub/agentic-workflows.md +++ b/website/src/content/docs/learning-hub/agentic-workflows.md @@ -211,8 +211,6 @@ gh aw compile --validate --no-emit workflows/my-new-workflow.md ## Learn More - **Official documentation**: [GitHub Agentic Workflows](https://gh.io/gh-aw) — full specification and reference -- **Browse workflows**: [Awesome Copilot Workflows](../../workflows/) — community-contributed collection -- **Contributing guide**: [CONTRIBUTING.md](https://github.com/github/awesome-copilot/blob/main/CONTRIBUTING.md#adding-agentic-workflows) — detailed contribution guidelines - **Related**: [Automating with Hooks](../automating-with-hooks/) — deterministic automation for Copilot agent sessions - **Related**: [Using the Copilot Coding Agent](../using-copilot-coding-agent/) — the agent that powers agentic workflows diff --git a/website/src/content/docs/learning-hub/automating-with-hooks.md b/website/src/content/docs/learning-hub/automating-with-hooks.md index 56ab02fa..8864598f 100644 --- a/website/src/content/docs/learning-hub/automating-with-hooks.md +++ b/website/src/content/docs/learning-hub/automating-with-hooks.md @@ -681,7 +681,6 @@ A: Yes. Hooks are especially valuable with the coding agent because they provide ## Next Steps -- **Explore Examples**: Browse the [Hooks Directory](../../hooks/) for ready-to-use hook configurations - **Build Agents**: [Building Custom Agents](../building-custom-agents/) — Create agents that complement hooks - **Automate Further**: [Using the Copilot Coding Agent](../using-copilot-coding-agent/) — Run hooks in autonomous agent sessions diff --git a/website/src/content/docs/learning-hub/using-copilot-coding-agent.md b/website/src/content/docs/learning-hub/using-copilot-coding-agent.md index 62b40f4a..b0e8a8c8 100644 --- a/website/src/content/docs/learning-hub/using-copilot-coding-agent.md +++ b/website/src/content/docs/learning-hub/using-copilot-coding-agent.md @@ -451,6 +451,6 @@ A: Yes. You can specify which agent to use when assigning work — the coding ag - **Add Guardrails**: [Automating with Hooks](../automating-with-hooks/) — Ensure code quality in autonomous sessions - **Build Custom Agents**: [Building Custom Agents](../building-custom-agents/) — Create specialized agents for the coding agent to use - **Explore Configuration**: [Copilot Configuration Basics](../copilot-configuration-basics/) — Set up repository-level customizations -- **Browse Community Resources**: Explore the [Agents](../../agents/), [Skills](../../skills/), and [Hooks](../../hooks/) directories for ready-to-use resources +- **Browse Community Resources**: Explore the [Agents](../../agents/), [Skills](../../skills/), and [Plugins](../../plugins/) directories for ready-to-use resources --- diff --git a/website/src/pages/hook/[id].astro b/website/src/pages/hook/[id].astro deleted file mode 100644 index b4d95a37..00000000 --- a/website/src/pages/hook/[id].astro +++ /dev/null @@ -1,159 +0,0 @@ ---- -import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; -import BackToTop from "../../components/BackToTop.astro"; -import hooksData from "../../../public/data/hooks.json"; -import RawMarkdown from "../../components/pages/RawMarkdown.astro"; -import type { HeaderItem } from "../../components/pages/Header.astro"; -import DetailHeader from "../../components/pages/Header.astro"; -import Breadcrumb from "../../components/pages/Breadcrumb.astro"; -import Sidebar from "../../components/pages/Sidebar.astro"; -import FileBrowser from "../../components/pages/FileBrowser.astro"; -import { readResourceMarkdown, formatLastUpdated } from "../../lib/detail-page"; - -const GITHUB_BASE = "https://github.com/github/awesome-copilot/blob/main"; -const GITHUB_TREE_BASE = "https://github.com/github/awesome-copilot/tree/main"; - -interface HookFile { - name: string; - path: string; - size?: number; -} - -interface HookItem extends HeaderItem { - id: string; - title: string; - description?: string; - path: string; - readmeFile: string; - readmeFileName?: string; - files: HookFile[]; - hooks: string[]; - tags: string[]; - assets: string[]; - lastUpdated?: string | null; -} - -export function getStaticPaths() { - return (hooksData.items as HookItem[]).map((item) => ({ - params: { id: item.id }, - props: { item }, - })); -} - -const { item } = Astro.props as { item: HookItem }; - -const { markdownHtml, frontmatterText, rawMarkdown } = readResourceMarkdown( - item.readmeFile -); -const lastUpdated = formatLastUpdated(item.lastUpdated); -const githubUrl = `${GITHUB_TREE_BASE}/${item.path}`; -const readmeFileName = item.readmeFileName ?? "README.md"; - -const fileCount = item.files.length; -const chips: { - title: string; - items: string[]; - filterBase?: string; - filterParam?: string; -}[] = []; -if (item.hooks.length > 0) { - chips.push({ title: "Hook events", items: item.hooks }); -} -chips.push({ - title: "Files", - items: [`${fileCount} file${fileCount === 1 ? "" : "s"}`], -}); -if (item.tags.length > 0) { - chips.push({ - title: "Tags", - items: item.tags, - filterBase: "/hooks/", - filterParam: "tag", - }); -} ---- - - -
- -
- - - - -
-
- -
- - -
-

Install this hook

-

- Hooks are multi-file bundles with no CLI installer. Download the - ZIP and copy the files into your project's hooks directory. -

- -
-
-
-
-
- - - - -
diff --git a/website/src/pages/hooks.astro b/website/src/pages/hooks.astro deleted file mode 100644 index e21f7d96..00000000 --- a/website/src/pages/hooks.astro +++ /dev/null @@ -1,56 +0,0 @@ ---- -import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; -import ContributeCTA from '../components/ContributeCTA.astro'; -import PageHeader from '../components/PageHeader.astro'; -import EmbeddedPageData from '../components/EmbeddedPageData.astro'; -import BackToTop from '../components/BackToTop.astro'; -import hooksData from '../../public/data/hooks.json'; -import { renderHooksHtml, sortHooks } from '../scripts/pages/hooks-render'; - -const initialItems = sortHooks(hooksData.items, 'title'); ---- - - -
- - -
-
-
-
-
{initialItems.length} hooks
-
- Sort & Filter -
-
-
- - -
-
- - -
- -
-
-
-
-
-
- -
-
-
- - - - - -
diff --git a/website/src/pages/tools.astro b/website/src/pages/tools.astro deleted file mode 100644 index 69b56814..00000000 --- a/website/src/pages/tools.astro +++ /dev/null @@ -1,277 +0,0 @@ ---- -import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; -import toolsData from '../../public/data/tools.json'; -import Modal from '../components/Modal.astro'; -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, sortTools } from "../scripts/pages/tools-render"; - -const initialItems = sortTools( - toolsData.items.map((item) => ({ - ...item, - title: item.name, - })), - "title" -); ---- - - -
- - -
-
-
-
-
{initialItems.length} tools
-
- Sort & Filter -
-
-
- - -
-
- - -
- -
-
-
-
-
- -
- -
-

More Tools Coming Soon

-

- We're working on additional tools to enhance your GitHub Copilot - experience. Check back soon! -

-
- -
-
-
- - - - - - - - -
diff --git a/website/src/pages/workflow/[id].astro b/website/src/pages/workflow/[id].astro deleted file mode 100644 index ab41f88b..00000000 --- a/website/src/pages/workflow/[id].astro +++ /dev/null @@ -1,131 +0,0 @@ ---- -import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; -import BackToTop from "../../components/BackToTop.astro"; -import workflowsData from "../../../public/data/workflows.json"; -import RawMarkdown from "../../components/pages/RawMarkdown.astro"; -import type { HeaderItem } from "../../components/pages/Header.astro"; -import DetailHeader from "../../components/pages/Header.astro"; -import Breadcrumb from "../../components/pages/Breadcrumb.astro"; -import Main from "../../components/pages/Main.astro"; -import Sidebar from "../../components/pages/Sidebar.astro"; -import { readResourceMarkdown, formatLastUpdated } from "../../lib/detail-page"; - -const GITHUB_BASE = "https://github.com/github/awesome-copilot/blob/main"; -const workflowGuideHref = "/learning-hub/agentic-workflows/"; - -interface WorkflowItem extends HeaderItem { - id: string; - title: string; - description?: string; - path: string; - triggers: string[]; - lastUpdated?: string | null; -} - -export function getStaticPaths() { - return (workflowsData.items as WorkflowItem[]).map((item) => ({ - params: { id: item.id }, - props: { item }, - })); -} - -const { item } = Astro.props as { item: WorkflowItem }; - -const { markdownHtml, frontmatterText, rawMarkdown } = readResourceMarkdown( - item.path -); -const lastUpdated = formatLastUpdated(item.lastUpdated); -const githubUrl = `${GITHUB_BASE}/${item.path}`; - -const chips: { title: string; items: string[] }[] = []; -if (item.triggers.length > 0) { - chips.push({ title: "Triggers", items: item.triggers }); -} ---- - - -
- -
- - - - -
-
- - -
-

Install this workflow

-

- Install the gh aw CLI{" "} - (gh extension install github/gh-aw), then copy this - file into .github/workflows/ and run{" "} - gh aw compile to generate the lock file. See the{" "} - Agentic Workflows guide{" "} - for full setup steps. -

- - -
-
-
-
-
- - - - -
diff --git a/website/src/pages/workflows.astro b/website/src/pages/workflows.astro deleted file mode 100644 index 4f3eba36..00000000 --- a/website/src/pages/workflows.astro +++ /dev/null @@ -1,56 +0,0 @@ ---- -import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; -import workflowsData from '../../public/data/workflows.json'; -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 { renderWorkflowsHtml, sortWorkflows } from '../scripts/pages/workflows-render'; - -const initialItems = sortWorkflows(workflowsData.items, 'title'); ---- - - -
- - -
-
-
-
-
{initialItems.length} workflows
-
- Sort & Filter -
-
-
- - -
-
- - -
- -
-
-
-
-
-
- -
-
-
- - - - - -
diff --git a/website/src/scripts/pages/hooks-render.ts b/website/src/scripts/pages/hooks-render.ts deleted file mode 100644 index 0617cc6d..00000000 --- a/website/src/scripts/pages/hooks-render.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - escapeHtml, - getGitHubUrl, - getLastUpdatedHtml, -} from "../utils"; -import { renderEmptyStateHtml, renderSharedCardHtml } from "./card-render"; - -export interface RenderableHookFile { - name: string; - path: string; -} - -export interface RenderableHook { - id: string; - title: string; - description?: string; - path: string; - readmeFile: string; - hooks: string[]; - tags: string[]; - assets: string[]; - files: RenderableHookFile[]; - lastUpdated?: string | null; -} - -export type HookSortOption = "title" | "lastUpdated"; - -/** - * Build the URL of a hook's dedicated detail page. - */ -export function getHookDetailUrl(id: string): string { - return `/hook/${id}/`; -} - -export function sortHooks( - items: T[], - sort: HookSortOption -): T[] { - return [...items].sort((a, b) => { - if (sort === "lastUpdated") { - const dateA = a.lastUpdated ? new Date(a.lastUpdated).getTime() : 0; - const dateB = b.lastUpdated ? new Date(b.lastUpdated).getTime() : 0; - return dateB - dateA; - } - - return a.title.localeCompare(b.title); - }); -} - -export function renderHooksHtml(items: RenderableHook[]): string { - if (items.length === 0) { - return renderEmptyStateHtml("No hooks found", "Try adjusting the selected filters."); - } - - return items - .map((item) => { - const metaHtml = ` - ${item.hooks - .map( - (hook) => `${escapeHtml(hook)}` - ) - .join("")} - ${item.tags - .map((tag) => `${escapeHtml(tag)}`) - .join("")} - ${ - item.assets.length > 0 - ? `${item.assets.length} asset${ - item.assets.length === 1 ? "" : "s" - }` - : "" - } - ${getLastUpdatedHtml(item.lastUpdated)} - `; - - const actionsHtml = ` - - GitHub - `; - - return renderSharedCardHtml({ - title: item.title, - description: item.description || "No description", - href: getHookDetailUrl(item.id), - articleAttributes: { - "data-path": item.readmeFile, - "data-hook-id": item.id, - }, - metaHtml, - actionsHtml, - }); - }) - .join(""); -} diff --git a/website/src/scripts/pages/hooks.ts b/website/src/scripts/pages/hooks.ts deleted file mode 100644 index 3a15d473..00000000 --- a/website/src/scripts/pages/hooks.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Hooks page functionality - */ -import { - fetchData, - getQueryParam, - getQueryParamValues, - showToast, - downloadZipBundle, - updateQueryParams, -} from '../utils'; -import { clearSelectValues, getSelectValues, setSelectValues } from './select-utils'; -import { - renderHooksHtml, - sortHooks, - type HookSortOption, - type RenderableHook, -} from './hooks-render'; - -interface Hook extends RenderableHook {} - -interface HooksData { - items: Hook[]; - filters: { - tags: string[]; - }; -} - -let allItems: Hook[] = []; -let tagSelectEl: HTMLSelectElement | null = null; -let currentFilters = { - tags: [] as string[], -}; -let currentSort: HookSortOption = 'title'; -let resourceListHandlersReady = false; - -function sortItems(items: Hook[]): Hook[] { - return sortHooks(items, currentSort); -} - -function applyFiltersAndRender(): void { - const countEl = document.getElementById('results-count'); - let results = [...allItems]; - - if (currentFilters.tags.length > 0) { - results = results.filter((item) => item.tags.some((tag) => currentFilters.tags.includes(tag))); - } - - results = sortItems(results); - - renderItems(results); - let countText = `${results.length} hook${results.length === 1 ? '' : 's'}`; - if (currentFilters.tags.length > 0) { - countText = `${results.length} of ${allItems.length} hooks (filtered by ${currentFilters.tags.length} tag${currentFilters.tags.length > 1 ? 's' : ''})`; - } - if (countEl) countEl.textContent = countText; -} - -function renderItems(items: Hook[]): void { - const list = document.getElementById('resource-list'); - if (!list) return; - - list.innerHTML = renderHooksHtml(items); -} - -async function downloadHook(hookId: string, btn: HTMLButtonElement): Promise { - const hook = allItems.find((item) => item.id === hookId); - if (!hook) { - showToast('Hook not found.', 'error'); - return; - } - - const files = [ - { name: 'README.md', path: hook.readmeFile }, - ...hook.assets.map((asset) => ({ - name: asset, - path: `${hook.path}/${asset}`, - })), - ]; - - if (files.length === 0) { - showToast('No files found for this hook.', 'error'); - return; - } - - const originalContent = btn.innerHTML; - btn.disabled = true; - btn.innerHTML = ' Preparing...'; - - try { - await downloadZipBundle(hook.id, files); - - btn.innerHTML = ' Downloaded!'; - setTimeout(() => { - btn.disabled = false; - btn.innerHTML = originalContent; - }, 2000); - } catch (error) { - const message = error instanceof Error ? error.message : 'Download failed.'; - showToast(message, 'error'); - btn.innerHTML = ' Failed'; - setTimeout(() => { - btn.disabled = false; - btn.innerHTML = originalContent; - }, 2000); - } -} - -function setupResourceListHandlers(list: HTMLElement | null): void { - if (!list || resourceListHandlersReady) return; - - list.addEventListener('click', (event) => { - const target = event.target as HTMLElement; - const downloadButton = target.closest('.download-hook-btn') as HTMLButtonElement | null; - if (downloadButton) { - event.preventDefault(); - event.stopPropagation(); - const hookId = downloadButton.dataset.hookId; - if (hookId) downloadHook(hookId, downloadButton); - return; - } - }); - - resourceListHandlersReady = true; -} - -function syncUrlState(): void { - updateQueryParams({ - q: '', - hook: [], - tag: currentFilters.tags, - sort: currentSort === 'title' ? '' : currentSort, - }); -} - -export async function initHooksPage(): Promise { - const list = document.getElementById('resource-list'); - const clearFiltersBtn = document.getElementById('clear-filters'); - const sortSelect = document.getElementById('sort-select') as HTMLSelectElement | null; - - setupResourceListHandlers(list as HTMLElement | null); - - const data = await fetchData('hooks.json'); - if (!data || !data.items) { - if (list) list.innerHTML = '

Failed to load data

'; - return; - } - - allItems = data.items; - - tagSelectEl = document.getElementById('filter-tag') as HTMLSelectElement | null; - if (tagSelectEl) { - tagSelectEl.innerHTML = ''; - data.filters.tags.forEach((tag) => { - const option = document.createElement('option'); - option.value = tag; - option.textContent = tag; - tagSelectEl?.appendChild(option); - }); - } - - const initialTags = getQueryParamValues('tag').filter((tag) => data.filters.tags.includes(tag)); - const initialSort = getQueryParam('sort'); - - if (initialTags.length > 0) { - currentFilters.tags = initialTags; - setSelectValues(tagSelectEl, initialTags); - } - if (initialSort === 'lastUpdated') { - currentSort = initialSort; - if (sortSelect) sortSelect.value = initialSort; - } - - tagSelectEl?.addEventListener('change', () => { - currentFilters.tags = getSelectValues(tagSelectEl); - applyFiltersAndRender(); - syncUrlState(); - }); - - sortSelect?.addEventListener('change', () => { - currentSort = sortSelect.value as HookSortOption; - applyFiltersAndRender(); - syncUrlState(); - }); - - clearFiltersBtn?.addEventListener('click', () => { - currentFilters = { tags: [] }; - currentSort = 'title'; - clearSelectValues(tagSelectEl); - if (sortSelect) sortSelect.value = 'title'; - applyFiltersAndRender(); - syncUrlState(); - }); - - applyFiltersAndRender(); -} - -// Auto-initialize when DOM is ready -document.addEventListener('DOMContentLoaded', initHooksPage); diff --git a/website/src/scripts/pages/tools-render.ts b/website/src/scripts/pages/tools-render.ts deleted file mode 100644 index 30b46533..00000000 --- a/website/src/scripts/pages/tools-render.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { escapeHtml } from "../utils"; - -export interface RenderableTool { - id: string; - name: string; - title: string; - description: string; - category: string; - featured: boolean; - requirements: string[]; - features: string[]; - links: { - blog?: string; - vscode?: string; - "vscode-insiders"?: string; - "visual-studio"?: string; - github?: string; - documentation?: string; - marketplace?: string; - npm?: string; - pypi?: string; - }; - configuration?: { - type: string; - content: string; - } | null; - tags: string[]; -} - -export type ToolSortOption = "featured" | "title"; - -export function sortTools( - tools: T[], - sort: ToolSortOption -): T[] { - return [...tools].sort((a, b) => { - if (sort === "featured") { - if (a.featured && !b.featured) return -1; - if (!a.featured && b.featured) return 1; - } - - return a.title.localeCompare(b.title); - }); -} - -function formatMultilineText(text: string): string { - return escapeHtml(text).replace(/\r?\n/g, "
"); -} - -function sanitizeToolUrl(url: string): string { - try { - const protocol = new URL(url).protocol; - if ( - protocol === "http:" || - protocol === "https:" || - protocol === "vscode:" || - protocol === "vscode-insiders:" - ) { - return escapeHtml(url); - } - } catch { - return "#"; - } - - return "#"; -} - -function getToolActionLink( - href: string | undefined, - label: string, - className: string -): string { - if (!href) return ""; - return `${label}`; -} - -export function renderToolsHtml( - tools: RenderableTool[] -): string { - if (tools.length === 0) { - return ` -
-

No tools found

-

Try a different category or clear the current filters

-
- `; - } - - return tools - .map((tool) => { - const badges: string[] = []; - if (tool.featured) { - badges.push('Featured'); - } - badges.push( - `${escapeHtml(tool.category)}` - ); - - const features = - tool.features && tool.features.length > 0 - ? `
-

Features

-
    ${tool.features - .map((feature) => `
  • ${escapeHtml(feature)}
  • `) - .join("")}
-
` - : ""; - - const requirements = - tool.requirements && tool.requirements.length > 0 - ? `
-

Requirements

-
    ${tool.requirements - .map((requirement) => `
  • ${escapeHtml(requirement)}
  • `) - .join("")}
-
` - : ""; - - const tags = - tool.tags && tool.tags.length > 0 - ? `
- ${tool.tags - .map((tag) => `${escapeHtml(tag)}`) - .join("")} -
` - : ""; - - const config = tool.configuration - ? `
-

Configuration

-
-
${escapeHtml(tool.configuration.content)}
-
- -
` - : ""; - - const actions = [ - getToolActionLink(tool.links.blog, "📖 Blog", "btn btn-secondary"), - getToolActionLink( - tool.links.marketplace, - "🏪 Marketplace", - "btn btn-secondary" - ), - getToolActionLink(tool.links.npm, "📦 npm", "btn btn-secondary"), - getToolActionLink(tool.links.pypi, "🐍 PyPI", "btn btn-secondary"), - getToolActionLink( - tool.links.documentation, - "📚 Docs", - "btn btn-secondary" - ), - getToolActionLink(tool.links.github, "GitHub", "btn btn-secondary"), - getToolActionLink( - tool.links.vscode, - "Install in VS Code", - "btn btn-primary" - ), - getToolActionLink( - tool.links["vscode-insiders"], - "VS Code Insiders", - "btn btn-outline" - ), - getToolActionLink( - tool.links["visual-studio"], - "Visual Studio", - "btn btn-outline" - ), - ].filter(Boolean); - - const actionsHtml = - actions.length > 0 - ? `
${actions.join("")}
` - : ""; - - return ` -
-
-

${escapeHtml(tool.name)}

-
- ${badges.join("")} -
-
-

${formatMultilineText(tool.description)}

- ${features} - ${requirements} - ${config} - ${tags} - ${actionsHtml} -
- `; - }) - .join(""); -} diff --git a/website/src/scripts/pages/tools.ts b/website/src/scripts/pages/tools.ts deleted file mode 100644 index 8519e8c8..00000000 --- a/website/src/scripts/pages/tools.ts +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Tools page functionality - */ -import { - fetchData, - getQueryParam, - updateQueryParams, -} from "../utils"; -import { - renderToolsHtml, - sortTools, - type ToolSortOption, -} from "./tools-render"; - -export interface Tool { - id: string; - name: string; - title: string; - description: string; - category: string; - featured: boolean; - requirements: string[]; - features: string[]; - links: { - blog?: string; - vscode?: string; - "vscode-insiders"?: string; - "visual-studio"?: string; - github?: string; - documentation?: string; - marketplace?: string; - npm?: string; - pypi?: string; - }; - configuration?: { - type: string; - content: string; - }; - tags: string[]; -} - -interface ToolsData { - items: Tool[]; - filters: { - categories: string[]; - tags: string[]; - }; -} - -let allItems: Tool[] = []; -let currentFilters = { - categories: [] as string[], -}; -let currentSort: ToolSortOption = "featured"; -let copyHandlersReady = false; -let initialized = false; - -function sortItems(items: Tool[]): Tool[] { - return sortTools(items, currentSort); -} - -function getCountText(resultsCount: number): string { - if (currentFilters.categories.length === 0) { - return `${resultsCount} tool${resultsCount === 1 ? "" : "s"}`; - } - - return `${resultsCount} of ${allItems.length} tools (filtered by ${currentFilters.categories.length} categor${currentFilters.categories.length === 1 ? "y" : "ies"})`; -} - -function applyFiltersAndRender(): void { - const countEl = document.getElementById("results-count"); - let results = [...allItems]; - - if (currentFilters.categories.length > 0) { - results = results.filter((item) => - currentFilters.categories.includes(item.category) - ); - } - - results = sortItems(results); - - renderTools(results); - if (countEl) countEl.textContent = getCountText(results.length); -} - -function renderTools(tools: Tool[]): void { - const container = document.getElementById("tools-list"); - if (!container) return; - container.innerHTML = renderToolsHtml(tools); -} - -function syncUrlState(): void { - updateQueryParams({ - q: "", - category: currentFilters.categories, - sort: currentSort === "featured" ? "" : currentSort, - }); -} - -function setupCopyConfigHandlers(): void { - if (copyHandlersReady) return; - - document.addEventListener("click", async (event) => { - const button = (event.target as HTMLElement).closest( - ".copy-config-btn" - ) as HTMLButtonElement | null; - if (!button) return; - - event.stopPropagation(); - const config = decodeURIComponent(button.dataset.config || ""); - try { - await navigator.clipboard.writeText(config); - button.classList.add("copied"); - const originalHtml = button.innerHTML; - button.innerHTML = ` - - - - Copied! - `; - setTimeout(() => { - button.classList.remove("copied"); - button.innerHTML = originalHtml; - }, 2000); - } catch (err) { - console.error("Failed to copy:", err); - } - }); - - copyHandlersReady = true; -} - -export async function initToolsPage(): Promise { - if (initialized) return; - initialized = true; - - const categoryFilter = document.getElementById( - "filter-category" - ) as HTMLSelectElement; - const clearFiltersBtn = document.getElementById("clear-filters"); - const sortSelect = document.getElementById("sort-select") as HTMLSelectElement; - - const data = await fetchData("tools.json"); - if (!data || !data.items) { - const container = document.getElementById("tools-list"); - if (container) - container.innerHTML = - '

Failed to load tools

'; - return; - } - - // Map items to include title for FuzzySearch - allItems = data.items.map((item) => ({ - ...item, - title: item.name, // FuzzySearch uses title - })); - - // Populate category filter - if (categoryFilter && data.filters.categories) { - categoryFilter.innerHTML = - '' + - data.filters.categories - .map( - (c) => `` - ) - .join(""); - - const initialCategory = getQueryParam("category"); - if (initialCategory && data.filters.categories.includes(initialCategory)) { - currentFilters.categories = [initialCategory]; - categoryFilter.value = initialCategory; - } - - categoryFilter.addEventListener("change", () => { - currentFilters.categories = categoryFilter.value - ? [categoryFilter.value] - : []; - applyFiltersAndRender(); - syncUrlState(); - }); - } - - const initialSort = getQueryParam("sort"); - if (initialSort === "title") { - currentSort = initialSort; - if (sortSelect) sortSelect.value = initialSort; - } - sortSelect?.addEventListener("change", () => { - currentSort = sortSelect.value as ToolSortOption; - applyFiltersAndRender(); - syncUrlState(); - }); - - applyFiltersAndRender(); - syncUrlState(); - - // Clear filters - clearFiltersBtn?.addEventListener("click", () => { - currentFilters = { categories: [] }; - currentSort = "featured"; - if (categoryFilter) categoryFilter.value = ""; - if (sortSelect) sortSelect.value = "featured"; - applyFiltersAndRender(); - syncUrlState(); - }); - - setupCopyConfigHandlers(); -} - -// Auto-initialize when DOM is ready -document.addEventListener("DOMContentLoaded", initToolsPage); diff --git a/website/src/scripts/pages/workflows-render.ts b/website/src/scripts/pages/workflows-render.ts deleted file mode 100644 index b2b60bcc..00000000 --- a/website/src/scripts/pages/workflows-render.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - escapeHtml, - getActionButtonsHtml, - getGitHubUrl, - getLastUpdatedHtml, -} from '../utils'; -import { renderEmptyStateHtml, renderSharedCardHtml } from './card-render'; - -export interface RenderableWorkflow { - id: string; - title: string; - description?: string; - path: string; - triggers: string[]; - lastUpdated?: string | null; -} - -export type WorkflowSortOption = 'title' | 'lastUpdated'; - -/** - * Build the URL of a workflow's dedicated detail page. - */ -export function getWorkflowDetailUrl(id: string): string { - return `/workflow/${id}/`; -} - -export function sortWorkflows( - items: T[], - sort: WorkflowSortOption -): T[] { - return [...items].sort((a, b) => { - if (sort === 'lastUpdated') { - const dateA = a.lastUpdated ? new Date(a.lastUpdated).getTime() : 0; - const dateB = b.lastUpdated ? new Date(b.lastUpdated).getTime() : 0; - return dateB - dateA; - } - - return a.title.localeCompare(b.title); - }); -} - -export function renderWorkflowsHtml( - items: RenderableWorkflow[] -): string { - if (items.length === 0) { - return renderEmptyStateHtml('No workflows found', 'Try adjusting the selected filters.'); - } - - return items - .map((item) => { - const metaHtml = ` - ${item.triggers - .map((trigger) => `${escapeHtml(trigger)}`) - .join('')} - ${getLastUpdatedHtml(item.lastUpdated)} - `; - - const actionsHtml = ` - ${getActionButtonsHtml(item.path)} - GitHub - `; - - return renderSharedCardHtml({ - title: item.title, - description: item.description || 'No description', - href: getWorkflowDetailUrl(item.id), - articleAttributes: { - 'data-path': item.path, - }, - metaHtml, - actionsHtml, - }); - }) - .join(''); -} diff --git a/website/src/scripts/pages/workflows.ts b/website/src/scripts/pages/workflows.ts deleted file mode 100644 index a74c522d..00000000 --- a/website/src/scripts/pages/workflows.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Workflows page functionality - */ -import { - fetchData, - getQueryParam, - getQueryParamValues, - setupActionHandlers, - updateQueryParams, -} from '../utils'; -import { clearSelectValues, getSelectValues, setSelectValues } from './select-utils'; -import { - renderWorkflowsHtml, - sortWorkflows, - type RenderableWorkflow, - type WorkflowSortOption, -} from './workflows-render'; - -interface Workflow extends RenderableWorkflow { - id: string; - path: string; - triggers: string[]; - lastUpdated?: string | null; -} - -interface WorkflowsData { - items: Workflow[]; - filters: { - triggers: string[]; - }; -} - -let allItems: Workflow[] = []; -let triggerSelectEl: HTMLSelectElement | null = null; -let currentFilters = { - triggers: [] as string[], -}; -let currentSort: WorkflowSortOption = 'title'; - -function sortItems(items: Workflow[]): Workflow[] { - return sortWorkflows(items, currentSort); -} - -function applyFiltersAndRender(): void { - const countEl = document.getElementById('results-count'); - let results = [...allItems]; - - if (currentFilters.triggers.length > 0) { - results = results.filter((item) => item.triggers.some((trigger) => currentFilters.triggers.includes(trigger))); - } - - results = sortItems(results); - - renderItems(results); - let countText = `${results.length} workflow${results.length === 1 ? '' : 's'}`; - if (currentFilters.triggers.length > 0) { - countText = `${results.length} of ${allItems.length} workflows (filtered by ${currentFilters.triggers.length} trigger${currentFilters.triggers.length > 1 ? 's' : ''})`; - } - if (countEl) countEl.textContent = countText; -} - -function renderItems(items: Workflow[]): void { - const list = document.getElementById('resource-list'); - if (!list) return; - - list.innerHTML = renderWorkflowsHtml(items); -} - -function syncUrlState(): void { - updateQueryParams({ - q: '', - trigger: currentFilters.triggers, - sort: currentSort === 'title' ? '' : currentSort, - }); -} - -export async function initWorkflowsPage(): Promise { - const list = document.getElementById('resource-list'); - const clearFiltersBtn = document.getElementById('clear-filters'); - const sortSelect = document.getElementById('sort-select') as HTMLSelectElement | null; - - const data = await fetchData('workflows.json'); - if (!data || !data.items) { - if (list) list.innerHTML = '

Failed to load data

'; - return; - } - - allItems = data.items; - - triggerSelectEl = document.getElementById('filter-trigger') as HTMLSelectElement | null; - if (triggerSelectEl) { - triggerSelectEl.innerHTML = ''; - data.filters.triggers.forEach((trigger) => { - const option = document.createElement('option'); - option.value = trigger; - option.textContent = trigger; - triggerSelectEl?.appendChild(option); - }); - } - - const initialTriggers = getQueryParamValues('trigger').filter((trigger) => data.filters.triggers.includes(trigger)); - const initialSort = getQueryParam('sort'); - - if (initialTriggers.length > 0) { - currentFilters.triggers = initialTriggers; - setSelectValues(triggerSelectEl, initialTriggers); - } - if (initialSort === 'lastUpdated') { - currentSort = initialSort; - if (sortSelect) sortSelect.value = initialSort; - } - - triggerSelectEl?.addEventListener('change', () => { - currentFilters.triggers = getSelectValues(triggerSelectEl); - applyFiltersAndRender(); - syncUrlState(); - }); - - sortSelect?.addEventListener('change', () => { - currentSort = sortSelect.value as WorkflowSortOption; - applyFiltersAndRender(); - syncUrlState(); - }); - - clearFiltersBtn?.addEventListener('click', () => { - currentFilters = { triggers: [] }; - currentSort = 'title'; - clearSelectValues(triggerSelectEl); - if (sortSelect) sortSelect.value = 'title'; - applyFiltersAndRender(); - syncUrlState(); - }); - - applyFiltersAndRender(); - setupActionHandlers(); -} - -// Auto-initialize when DOM is ready -document.addEventListener('DOMContentLoaded', initWorkflowsPage);