mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-18 05:31:27 +00:00
17b174fb0a
* Add keyword display to extension cards on website - Add .resource-keywords and .keyword-tag CSS styles for rendering keyword badges - Update renderExtensionsHtml() to display keywords below extension description - Keywords now visible on the website extensions page with styled badges - Regenerate website data to include keyword metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Canvas manifest implementation for all extensions Add per-extension canvas manifests with: - Structured canvas metadata (name, description, version, keywords) - Screenshot definitions (icon and gallery with path/type) - Relative paths for images within each extension directory Enhance extension metadata: - Generate meaningful descriptions from source analysis - Extract and assign keywords for discoverability - Store metadata in package.json and extension source files Update website rendering and data generation: - Include keywords in extension cards and search index - Add per-extension canvas.json files for independent evolution - Support screenshot metadata in manifest structure - Generate extensions.json with full canonical paths for website All 9 local canvas extensions now have complete manifests with descriptions, keywords, and screenshot references. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tweaking some descriptions * Fix description priority to prefer package.json over in-source metadata Reverse the priority in canvasDescription so that package.json descriptions (which contain the enhanced, manually-curated descriptions) take precedence over older in-source descriptions extracted from createCanvas(...) calls. This prevents regression when npm run website:data regenerates outputs, ensuring that committed canvas.json files maintain the current descriptions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix canvas validation to skip external.json file The validation script was treating extensions/external.json as if it were a directory, causing false validation failures. Added check to skip files (identified by presence of dot in filename) and only validate actual canvas extension directories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
import { escapeHtml, getGitHubUrl, getLastUpdatedHtml } from "../utils";
|
|
|
|
export interface RenderableExtension {
|
|
id: string;
|
|
canvasId?: string;
|
|
extensionId?: string;
|
|
extensionName?: string;
|
|
name: string;
|
|
path?: string | null;
|
|
ref?: string | null;
|
|
version?: string | null;
|
|
description?: string;
|
|
lastUpdated?: string | null;
|
|
keywords?: string[];
|
|
screenshots?: {
|
|
icon?: {
|
|
path?: string | null;
|
|
type?: string | null;
|
|
} | null;
|
|
gallery?: {
|
|
path?: string | null;
|
|
type?: string | null;
|
|
} | null;
|
|
} | null;
|
|
imageUrl?: string | null;
|
|
assetPath?: string | null;
|
|
installUrl?: string | null;
|
|
sourceUrl?: string | null;
|
|
external?: boolean;
|
|
}
|
|
|
|
export type ExtensionSortOption = "title" | "lastUpdated";
|
|
|
|
export function sortExtensions<T extends RenderableExtension>(
|
|
items: T[],
|
|
sort: ExtensionSortOption
|
|
): 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.name.localeCompare(b.name);
|
|
});
|
|
}
|
|
|
|
export function renderExtensionsHtml(items: RenderableExtension[]): string {
|
|
if (items.length === 0) {
|
|
return `
|
|
<div class="empty-state">
|
|
<h3>No extensions found</h3>
|
|
<p>No canvas extensions are available right now.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return items
|
|
.map((item) => {
|
|
const installUrl =
|
|
item.installUrl ||
|
|
(item.path && item.ref
|
|
? `https://github.com/github/awesome-copilot/tree/${item.ref}/${item.path.replace(
|
|
/\\/g,
|
|
"/"
|
|
)}`
|
|
: "");
|
|
const sourceUrl =
|
|
item.sourceUrl || (item.path ? getGitHubUrl(item.path) : "");
|
|
|
|
return `
|
|
<article class="resource-item" role="listitem">
|
|
<div class="resource-preview">
|
|
${
|
|
item.imageUrl
|
|
? `<button type="button" class="resource-thumbnail-btn" data-preview-url="${escapeHtml(item.imageUrl)}" data-preview-alt="${escapeHtml(item.name)} preview" aria-label="Open ${escapeHtml(item.name)} preview">
|
|
<img class="resource-thumbnail" src="${escapeHtml(item.imageUrl)}" alt="${escapeHtml(item.name)} preview" loading="lazy" />
|
|
</button>`
|
|
: `<div class="resource-thumbnail resource-thumbnail-placeholder" aria-hidden="true">Canvas</div>`
|
|
}
|
|
<div class="resource-info">
|
|
<div class="resource-title">${escapeHtml(item.name)}</div>
|
|
<div class="resource-description">${escapeHtml(
|
|
item.description || "Canvas extension"
|
|
)}</div>
|
|
<div class="resource-keywords">
|
|
${
|
|
item.keywords && item.keywords.length > 0
|
|
? item.keywords
|
|
.map(
|
|
(kw) =>
|
|
`<span class="keyword-tag">${escapeHtml(kw)}</span>`
|
|
)
|
|
.join("")
|
|
: ""
|
|
}
|
|
</div>
|
|
<div class="resource-meta">
|
|
${
|
|
item.external
|
|
? '<span class="resource-tag">External</span>'
|
|
: ""
|
|
}
|
|
${getLastUpdatedHtml(item.lastUpdated)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="resource-actions">
|
|
<button
|
|
class="btn btn-primary btn-small copy-install-url-btn"
|
|
data-install-url="${escapeHtml(installUrl)}"
|
|
title="Copy install URL"
|
|
${installUrl ? "" : "disabled"}
|
|
>
|
|
Install
|
|
</button>
|
|
${
|
|
sourceUrl
|
|
? `<a href="${escapeHtml(
|
|
sourceUrl
|
|
)}" class="btn btn-secondary btn-small" target="_blank" rel="noopener noreferrer" title="View source">Source</a>`
|
|
: ""
|
|
}
|
|
</div>
|
|
</article>
|
|
`;
|
|
})
|
|
.join("");
|
|
}
|