import { CheckIcon, CopyIcon, LinkExternalIcon, MarkGithubIcon, PlusIcon, } from "@primer/octicons-react"; import React from "react"; import { Button, Token } from "@primer/react-brand"; import { DetailChassis, type DetailSibling, type DetailTocItem, } from "./DetailChassis"; import { ResourceMeta } from "./ResourceMeta"; import { pageHref } from "./pageHref"; import type { SearchItem } from "./searchIndex"; import styles from "./styles/dotnet-upgrade.module.css"; // The framed media block and the install command bar are prototype treatments // that live in the Copilot app page's stylesheet; reused here verbatim. import appStyles from "./styles/github-copilot-app.module.css"; export type ExtensionDetailImage = { url: string; alt: string }; export type ExtensionDetailItem = { id: string; name: string; description?: string; version?: string | null; pluginName?: string | null; external?: boolean; externalSource?: string | null; keywords?: string[]; author?: { name?: string; url?: string } | null; }; export type ExtensionDetailProps = { item: ExtensionDetailItem; /** Preview screenshots, largest/primary first. */ images: ExtensionDetailImage[]; /** Rendered, sanitized README with heading ids already stamped. */ markdownHtml: string; toc: DetailTocItem[]; /** `ghapp://` deep link — absent for external extensions. */ installUrl?: string | null; /** Copilot CLI command — absent for external extensions. */ installCommand?: string | null; /** Repository the extension is published from. */ sourceUrl?: string | null; lastUpdated?: string | null; previous?: DetailSibling; next?: DetailSibling; searchIndex?: SearchItem[]; contributorsTotal?: number; }; const PREVIEW_SECTION_ID = "preview"; /** Copyable `copilot plugin install …` line, matching the prototype install bar. */ function InstallCommand({ command }: { command: string }) { const [copied, setCopied] = React.useState(false); const timer = React.useRef(undefined); React.useEffect(() => () => window.clearTimeout(timer.current), []); const handleCopy = () => { navigator.clipboard?.writeText(command).catch(() => undefined); setCopied(true); window.clearTimeout(timer.current); timer.current = window.setTimeout(() => setCopied(false), 2000); }; return (
{command}
); } /** * Detail route for a canvas extension. * * Two things set it apart from the other resource details: the preview * screenshot leads the content column at hero scale (a gallery when an * extension ships more than one), and installation has two paths — the * `ghapp://` deep link and the Copilot CLI command. Externally hosted * extensions carry neither, so they fall back to a link to their source repo. */ export function ExtensionDetail({ item, images, markdownHtml, toc, installUrl, installCommand, sourceUrl, lastUpdated, previous, next, searchIndex, contributorsTotal, }: ExtensionDetailProps) { const hasPreview = images.length > 0; const install = ( <> {installUrl ? ( ) : null} {sourceUrl ? ( ) : null} ); const heroExtras = item.version || item.external ? (
{item.version ? : null} {item.external ? : null}
) : null; const fullToc = hasPreview ? [{ id: PREVIEW_SECTION_ID, label: "Preview" }, ...toc] : toc; return ( } previous={previous} next={next} currentPage="extensions" searchIndex={searchIndex} contributorsTotal={contributorsTotal} > {hasPreview ? (

Preview

{images.map((image) => (
{image.alt}
))}
) : null} {installUrl || installCommand ? (

Install

{installCommand ? : null} {installUrl ? (

Or{" "} open {item.name} directly in the Copilot app .

) : null}
) : sourceUrl ? (

Install

{item.name} is hosted outside this repository. Follow the installation steps in its{" "} source repository.

) : null} {markdownHtml ? (
) : null}
); }