Add canvas previews, external extension links, and release notes showcase (#1987)

* Add extension thumbnails and preview assets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add clickable extension image preview modal

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update website/src/styles/global.css

* Add preview assets for canvas extensions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update canvas extension preview images

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Delete extensions/backlog-swipe-triage/assets/swipe-canvas-triage.png

* Support external canvas extensions and add Coffilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add homepage link to GitHub repository

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add release notes showcase canvas extension

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Enhance release notes canvas sourcing and layout

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Aaron Powell <me@aaron-powell.com>
This commit is contained in:
James Montemagno
2026-06-16 23:18:49 +00:00
committed by GitHub
parent a34c98bfbf
commit ea5d3f4acb
25 changed files with 5482 additions and 26 deletions
+54 -23
View File
@@ -3,9 +3,15 @@ import { escapeHtml, getGitHubUrl, getLastUpdatedHtml } from "../utils";
export interface RenderableExtension {
id: string;
name: string;
path: string;
ref: string;
path?: string | null;
ref?: string | null;
description?: string;
lastUpdated?: string | null;
imageUrl?: string | null;
assetPath?: string | null;
installUrl?: string | null;
sourceUrl?: string | null;
external?: boolean;
}
export type ExtensionSortOption = "title" | "lastUpdated";
@@ -36,37 +42,62 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
}
return items
.map(
(item) => `
.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">
<div class="resource-info">
<div class="resource-title">${escapeHtml(item.name)}</div>
<div class="resource-description">Canvas extension</div>
<div class="resource-meta">
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
${
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-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(
`https://github.com/github/awesome-copilot/tree/${item.ref}/${item.path.replace(
/\\/g,
"/"
)}`
)}"
data-install-url="${escapeHtml(installUrl)}"
title="Copy install URL"
${installUrl ? "" : "disabled"}
>
Install
</button>
<a href="${getGitHubUrl(
item.path
)}" class="btn btn-secondary btn-small" target="_blank" rel="noopener noreferrer" title="View on GitHub">GitHub</a>
${
sourceUrl
? `<a href="${escapeHtml(
sourceUrl
)}" class="btn btn-secondary btn-small" target="_blank" rel="noopener noreferrer" title="View source">Source</a>`
: ""
}
</div>
</article>
`
)
`;
})
.join("");
}
+63
View File
@@ -27,6 +27,30 @@ let allItems: Extension[] = [];
let currentSort: ExtensionSortOption = "title";
let actionHandlersReady = false;
function openPreviewModal(url: string, alt: string): void {
const modal = document.getElementById("extension-preview-modal");
const image = document.getElementById("extension-preview-image") as HTMLImageElement | null;
const title = document.getElementById("extension-preview-title");
if (!modal || !image || !title) return;
image.src = url;
image.alt = alt;
title.textContent = alt.replace(/ preview$/i, "");
modal.classList.remove("hidden");
modal.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
}
function closePreviewModal(): void {
const modal = document.getElementById("extension-preview-modal");
if (!modal) return;
modal.classList.add("hidden");
modal.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
}
function applySortAndRender(): void {
const countEl = document.getElementById("results-count");
const results = sortExtensions(allItems, currentSort);
@@ -49,6 +73,20 @@ function setupActionHandlers(list: HTMLElement | null): void {
list.addEventListener("click", async (event) => {
const target = event.target as HTMLElement;
const thumbnailButton = target.closest(
".resource-thumbnail-btn"
) as HTMLButtonElement | null;
if (thumbnailButton) {
event.preventDefault();
event.stopPropagation();
openPreviewModal(
thumbnailButton.dataset.previewUrl || "",
thumbnailButton.dataset.previewAlt || "Extension preview"
);
return;
}
const installButton = target.closest(
".copy-install-url-btn"
) as HTMLButtonElement | null;
@@ -57,6 +95,10 @@ function setupActionHandlers(list: HTMLElement | null): void {
event.stopPropagation();
const installUrl = installButton.dataset.installUrl || "";
if (!installUrl) {
showToast("No install URL available for this extension", "error");
return;
}
const success = await copyToClipboard(installUrl);
showToast(
success ? "Install URL copied!" : "Failed to copy install URL",
@@ -64,6 +106,27 @@ function setupActionHandlers(list: HTMLElement | null): void {
);
});
const modal = document.getElementById("extension-preview-modal");
const closeButton = document.getElementById("extension-preview-close");
if (modal) {
modal.addEventListener("click", (event) => {
if (event.target === modal) {
closePreviewModal();
}
});
}
if (closeButton) {
closeButton.addEventListener("click", closePreviewModal);
}
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closePreviewModal();
}
});
actionHandlersReady = true;
}