mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-26 01:10:20 +00:00
Consolidate listing pages with unified grid cards and modal system (#2101)
* Prototype extension details modal - Add detail popup modal for extension cards with full metadata and gallery - Implement image gallery with thumbnail strip and main image selection - Add modal styling and positioning in global.css - Connect card click handlers to open modal with extension data Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix accessibility issues with modal focus restoration - Add missing listing-cards-page class to agents.astro page root - Pass focusable button element to openCardDetailsModal instead of article - Fixes focus restoration for keyboard users when closing modal - Applied fix across all listing pages (agents, instructions, hooks, plugins, skills, workflows) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address remaining PR review feedback - Fix extension modal ARIA state by setting aria-current to "true" and removing it when inactive - Use focusable .resource-preview as modal trigger for extension thumbnail/click/keyboard paths - Extract shared multi-select helpers into pages/select-utils.ts and reuse across instructions/hooks/plugins/workflows - Remove unused card-model.ts to avoid dead/overlapping type definitions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
* Modal functionality for file viewing
|
||||
*/
|
||||
|
||||
import { marked } from "marked";
|
||||
import {
|
||||
fetchFileContent,
|
||||
fetchData,
|
||||
@@ -18,7 +17,6 @@ import {
|
||||
sanitizeUrl,
|
||||
REPO_IDENTIFIER,
|
||||
} from "./utils";
|
||||
import fm from "front-matter";
|
||||
|
||||
type ModalViewMode = "rendered" | "raw";
|
||||
|
||||
@@ -352,7 +350,11 @@ async function renderCurrentFileContent(): Promise<void> {
|
||||
const container = ensureDivContent("modal-rendered-content");
|
||||
if (!container) return;
|
||||
|
||||
const { body: markdownBody } = fm(currentFileContent);
|
||||
const [{ marked }, { default: fm }] = await Promise.all([
|
||||
import("marked"),
|
||||
import("front-matter"),
|
||||
]);
|
||||
const { body: markdownBody } = fm<string>(currentFileContent);
|
||||
container.innerHTML = marked(markdownBody, { async: false });
|
||||
} else {
|
||||
await renderHighlightedCode(currentFileContent, currentFilePath);
|
||||
@@ -452,6 +454,19 @@ interface PluginsData {
|
||||
|
||||
let pluginsCache: PluginsData | null = null;
|
||||
|
||||
interface OpenCardDetailsRequest {
|
||||
title: string;
|
||||
description: string;
|
||||
previewIcon?: string;
|
||||
previewText?: string;
|
||||
metaHtml?: string;
|
||||
tagsHtml?: string;
|
||||
actionsHtml?: string;
|
||||
detailsHtml?: string;
|
||||
contentClassName?: string;
|
||||
trigger?: HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all focusable elements within a container
|
||||
*/
|
||||
@@ -731,6 +746,19 @@ export function setupModal(): void {
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("click", async (event) => {
|
||||
const target = event.target as HTMLElement;
|
||||
const openFileButton = target.closest<HTMLElement>("[data-open-file-path]");
|
||||
if (!openFileButton) return;
|
||||
|
||||
const filePath = openFileButton.dataset.openFilePath;
|
||||
if (!filePath) return;
|
||||
|
||||
event.preventDefault();
|
||||
const fileType = openFileButton.dataset.openFileType || getResourceType(filePath);
|
||||
await openFileModal(filePath, fileType, true, openFileButton);
|
||||
});
|
||||
|
||||
// Check for deep link on initial load
|
||||
handleHashChange();
|
||||
}
|
||||
@@ -894,6 +922,8 @@ export async function openFileModal(
|
||||
const closeBtn = document.getElementById("close-modal");
|
||||
if (!modal || !title) return;
|
||||
|
||||
modal.classList.remove("details-mode");
|
||||
|
||||
currentFilePath = filePath;
|
||||
currentFileType = type;
|
||||
currentViewMode = "raw";
|
||||
@@ -989,6 +1019,85 @@ export async function openFileModal(
|
||||
await renderCurrentFileContent();
|
||||
}
|
||||
|
||||
export function openCardDetailsModal({
|
||||
title,
|
||||
description,
|
||||
previewIcon = "📄",
|
||||
previewText = "",
|
||||
metaHtml = "",
|
||||
tagsHtml = "",
|
||||
actionsHtml = "",
|
||||
detailsHtml = "",
|
||||
contentClassName = "modal-card-details",
|
||||
trigger,
|
||||
}: OpenCardDetailsRequest): void {
|
||||
const modal = document.getElementById("file-modal");
|
||||
const modalTitle = document.getElementById("modal-title");
|
||||
const closeBtn = document.getElementById("close-modal");
|
||||
const modalBody = getModalBody();
|
||||
|
||||
if (!modal || !modalTitle || !modalBody) return;
|
||||
|
||||
triggerElement = trigger || (document.activeElement as HTMLElement);
|
||||
if (!originalDocumentTitle) {
|
||||
originalDocumentTitle = document.title;
|
||||
}
|
||||
|
||||
currentFilePath = null;
|
||||
currentFileContent = null;
|
||||
currentFileType = "details";
|
||||
currentViewMode = "raw";
|
||||
hideSkillFileSwitcher();
|
||||
|
||||
modal.classList.add("details-mode");
|
||||
modalTitle.textContent = title;
|
||||
document.title = `${title} | Awesome GitHub Copilot`;
|
||||
|
||||
const content = ensureDivContent(contentClassName);
|
||||
if (!content) return;
|
||||
|
||||
content.innerHTML =
|
||||
detailsHtml ||
|
||||
`
|
||||
<div class="resource-details-body modal-card-details-body">
|
||||
<div class="resource-details-preview">
|
||||
<div class="resource-details-preview-icon" aria-hidden="true">${escapeHtml(previewIcon)}</div>
|
||||
${
|
||||
previewText
|
||||
? `<p class="resource-details-preview-text">${escapeHtml(previewText)}</p>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="resource-details-content">
|
||||
<p class="resource-details-description">${escapeHtml(description)}</p>
|
||||
${
|
||||
metaHtml
|
||||
? `<div class="resource-meta resource-details-meta">${metaHtml}</div>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
tagsHtml
|
||||
? `<div class="resource-keywords resource-details-tags">${tagsHtml}</div>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
actionsHtml
|
||||
? `<div class="resource-actions resource-details-actions">${actionsHtml}</div>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modalBody.scrollTop = 0;
|
||||
modal.classList.remove("hidden");
|
||||
modal.classList.add("visible");
|
||||
|
||||
setTimeout(() => {
|
||||
closeBtn?.focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open plugin modal with item list
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user