mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-27 02:55:15 +00:00
Replace the Astro + Starlight site with the Brand Engineering design prototype, ported component-for-component onto plain Astro + React islands. The prototype is treated as the authority on markup and styling; dynamic data is injected into its components rather than the components being reinterpreted. Framework: - Remove @astrojs/starlight entirely, along with its document shell, search, footer and language selector. BaseLayout.astro now owns the document head, CSP, social meta and analytics. - Add @primer/react-brand and @astrojs/react. Alias the package to its ESM build in vite.resolve, since the default CJS entrypoint breaks named-export detection during SSR, and mark it noExternal so its stylesheet imports resolve. - Promote pagefind to an explicit devDependency; it was previously pulled in transitively by Starlight. Pages: home, the five catalogs, the five detail routes, contributors, Playbook index and articles, and the cookbook are all rendered by ported prototype components inside a shared PageShell. Detail pages share a DetailChassis (hero, breadcrumbs, sticky TOC with scroll-spy, prev/next) while keeping what makes each type distinct: a file switcher over bundled skill assets, an included-items grid and external provenance for plugins, and a hero-scale preview for extensions. i18n: resolve translated Playbook entries via Astro.currentLocale inside the shared article route instead of separate [locale] routes. The explicit routes collided with the i18n fallback routes, so translated articles were being shadowed by their English originals and 900 nonsensical double-locale pages were emitted. Search: TopNavSearch now queries the Pagefind index client-side in addition to the static resource index, merging on href and degrading to the static index in dev, where no index has been built. Also delete the superseded vanilla-TS page renderers and Astro partials, which the ported React components fully replace. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 80686fef-efe3-4cdd-8cd6-bfa61a5d0af6
241 lines
7.0 KiB
TypeScript
241 lines
7.0 KiB
TypeScript
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<number | undefined>(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 (
|
|
<div className={appStyles.installBar}>
|
|
<code className={appStyles.installCommand}>{command}</code>
|
|
<button
|
|
type="button"
|
|
className={appStyles.installCopy}
|
|
onClick={handleCopy}
|
|
aria-label={copied ? "Copied to clipboard" : "Copy install command"}
|
|
>
|
|
{copied ? <CheckIcon size={16} /> : <CopyIcon size={16} />}
|
|
<span>{copied ? "Copied" : "Copy"}</span>
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 ? (
|
|
<Button
|
|
as="a"
|
|
href={installUrl}
|
|
variant="primary"
|
|
hasArrow={false}
|
|
leadingVisual={PlusIcon}
|
|
>
|
|
Open in Copilot app
|
|
</Button>
|
|
) : null}
|
|
{sourceUrl ? (
|
|
<Button
|
|
as="a"
|
|
href={sourceUrl}
|
|
variant={installUrl ? "secondary" : "primary"}
|
|
hasArrow={false}
|
|
leadingVisual={installUrl ? undefined : LinkExternalIcon}
|
|
className={installUrl ? styles.iconButton : undefined}
|
|
aria-label={
|
|
installUrl ? `View ${item.name} on GitHub` : undefined
|
|
}
|
|
>
|
|
{installUrl ? <MarkGithubIcon size={16} /> : "View source repository"}
|
|
</Button>
|
|
) : null}
|
|
</>
|
|
);
|
|
|
|
const heroExtras =
|
|
item.version || item.external ? (
|
|
<div className={styles.metaValues}>
|
|
{item.version ? <Token text={`v${item.version}`} /> : null}
|
|
{item.external ? <Token text="Externally hosted" /> : null}
|
|
</div>
|
|
) : null;
|
|
|
|
const fullToc = hasPreview
|
|
? [{ id: PREVIEW_SECTION_ID, label: "Preview" }, ...toc]
|
|
: toc;
|
|
|
|
return (
|
|
<DetailChassis
|
|
title={item.name}
|
|
description={item.description}
|
|
breadcrumbs={[
|
|
{ label: "Extensions", href: pageHref("extensions") },
|
|
{ label: item.name },
|
|
]}
|
|
install={install}
|
|
heroExtras={heroExtras}
|
|
toc={fullToc}
|
|
sidebar={
|
|
<ResourceMeta
|
|
kicker="Extension details"
|
|
groups={[
|
|
{ label: "Keywords", items: item.keywords ?? [] },
|
|
{
|
|
label: "Source",
|
|
items: item.externalSource ? [item.externalSource] : [],
|
|
},
|
|
]}
|
|
author={item.author?.name ?? null}
|
|
lastUpdated={lastUpdated}
|
|
sourceUrl={sourceUrl ?? undefined}
|
|
/>
|
|
}
|
|
previous={previous}
|
|
next={next}
|
|
currentPage="extensions"
|
|
searchIndex={searchIndex}
|
|
contributorsTotal={contributorsTotal}
|
|
>
|
|
{hasPreview ? (
|
|
<section id={PREVIEW_SECTION_ID} className={styles.articleSection}>
|
|
<h2 className={styles.articleHeading}>Preview</h2>
|
|
{images.map((image) => (
|
|
<figure key={image.url} className={appStyles.videoFigure}>
|
|
<img
|
|
className={appStyles.gif}
|
|
src={image.url}
|
|
alt={image.alt}
|
|
loading="eager"
|
|
decoding="async"
|
|
width={1280}
|
|
height={720}
|
|
/>
|
|
</figure>
|
|
))}
|
|
</section>
|
|
) : null}
|
|
|
|
{installUrl || installCommand ? (
|
|
<section className={styles.articleSection}>
|
|
<h2 className={styles.articleHeading}>Install</h2>
|
|
{installCommand ? <InstallCommand command={installCommand} /> : null}
|
|
{installUrl ? (
|
|
<p>
|
|
Or{" "}
|
|
<a href={installUrl}>open {item.name} directly in the Copilot app</a>
|
|
.
|
|
</p>
|
|
) : null}
|
|
</section>
|
|
) : sourceUrl ? (
|
|
<section className={styles.articleSection}>
|
|
<h2 className={styles.articleHeading}>Install</h2>
|
|
<p>
|
|
{item.name} is hosted outside this repository. Follow the
|
|
installation steps in its{" "}
|
|
<a href={sourceUrl}>source repository</a>.
|
|
</p>
|
|
</section>
|
|
) : null}
|
|
|
|
{markdownHtml ? (
|
|
<section className={styles.articleSection}>
|
|
<div dangerouslySetInnerHTML={{ __html: markdownHtml }} />
|
|
</section>
|
|
) : null}
|
|
</DetailChassis>
|
|
);
|
|
}
|