--- import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; import BackToTop from "../../components/BackToTop.astro"; import extensionsData from "../../../public/data/extensions.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 PluginInstall from "../../components/pages/PluginInstall.astro"; import { readResourceMarkdown, formatLastUpdated } from "../../lib/detail-page"; import { sanitizeHttpUrl } from "../../lib/external-source"; const GITHUB_TREE = "https://github.com/github/awesome-copilot/tree/main"; interface ExtensionScreenshot { path?: string | null; type?: string | null; } interface ExtensionAuthor { name: string; url?: string; } interface ExtensionEntry extends HeaderItem { id: string; canvasId?: string; extensionId?: string; extensionName?: string; pluginName?: string | null; name: string; title?: string; version?: string | null; readmeFile?: string | null; description?: string; path?: string | null; ref?: string | null; lastUpdated?: string | null; screenshots?: { icon?: ExtensionScreenshot | null; gallery?: ExtensionScreenshot | ExtensionScreenshot[] | null; } | null; imageUrl?: string | null; assetPath?: string | null; installUrl?: string | null; installCommand?: string | null; sourceUrl?: string | null; externalSource?: string | null; external?: boolean; author?: ExtensionAuthor | null; keywords?: string[]; } export function getStaticPaths() { return (extensionsData.items as ExtensionEntry[]).map((item) => ({ params: { id: item.id }, props: { item }, })); } const { item } = Astro.props as { item: ExtensionEntry }; const headerItem = { ...item, title: item.title ?? item.name }; const isExternal = item.external === true; // Allow only http(s) URLs from external/generated data in href/src contexts; // unsafe values collapse to "" so downstream truthiness guards still hold. const safeUrl = (value?: string | null): string => { const sanitized = sanitizeHttpUrl(value); return sanitized === "#" ? "" : sanitized; }; const readmePath = item.readmeFile ?? null; const { markdownHtml, frontmatterText, rawMarkdown } = readmePath ? readResourceMarkdown(readmePath) : { markdownHtml: "", frontmatterText: "", rawMarkdown: "" }; const lastUpdated = formatLastUpdated(item.lastUpdated ?? null); // Resolve preview images (icon + gallery), converting repo paths to raw URLs. function toRawAssetUrl(assetPath?: string | null): string { if (!assetPath) return ""; if (/^https?:\/\//i.test(assetPath)) return assetPath; if (!item.ref) return ""; return `https://raw.githubusercontent.com/github/awesome-copilot/${item.ref}/${assetPath.replace( /\\/g, "/" )}`; } function normalizeScreenshotEntries( value: ExtensionScreenshot | ExtensionScreenshot[] | null | undefined ): ExtensionScreenshot[] { if (!value) return []; return Array.isArray(value) ? value.filter(Boolean) : [value]; } const galleryImages: string[] = (() => { const images: string[] = []; if (item.imageUrl) images.push(item.imageUrl); const iconUrl = toRawAssetUrl(item.screenshots?.icon?.path); if (iconUrl) images.push(iconUrl); for (const entry of normalizeScreenshotEntries(item.screenshots?.gallery)) { const url = toRawAssetUrl(entry.path); if (url) images.push(url); } return Array.from(new Set(images.map(safeUrl).filter(Boolean))); })(); const installUrl = safeUrl( item.installUrl || (item.path && item.ref ? `https://github.com/github/awesome-copilot/tree/${item.ref}/${item.path.replace( /\\/g, "/" )}` : "") ); const sourceUrl = safeUrl(item.sourceUrl); const externalSource = (item.externalSource || "").trim(); const installCommand = item.installCommand || (item.pluginName ? `copilot plugin install ${item.pluginName}@awesome-copilot` : ""); const githubUrl = isExternal ? sourceUrl || GITHUB_TREE : item.path ? `${GITHUB_TREE}/${item.path}` : installUrl || GITHUB_TREE; // Sidebar "Source" shows item.path; external extensions have no repo path. const sidebarItem = isExternal && sourceUrl ? { ...item, path: sourceUrl } : item; const shortHash = (value: string) => /^[0-9a-f]{40}$/i.test(value) ? value.slice(0, 7) : value; const chips: { title: string; items: string[]; filterBase?: string; filterParam?: string; }[] = []; if (item.version) { chips.push({ title: "Version", items: [item.version] }); } if (item.canvasId) { chips.push({ title: "Canvas ID", items: [item.canvasId] }); } if (item.keywords && item.keywords.length > 0) { chips.push({ title: "Keywords", items: item.keywords, filterBase: "/extensions/", filterParam: "keyword", }); } if (item.author?.name) { chips.push({ title: "Author", items: [item.author.name] }); } if (!isExternal && item.ref) { chips.push({ title: "Commit", items: [shortHash(item.ref)] }); } ---
{ galleryImages.length > 0 && ( ) } { markdownHtml ? (

Documentation

) : (

About

{item.description}

{isExternal ? "This is a third-party canvas extension hosted outside this repository." : "No README is bundled with this extension."}{" "} View it{" "} on GitHub .

) }
{ isExternal ? ( ) : ( ) }