From 3bb799616a5112448879f9ec2fb036411ddbf6f1 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 29 Jan 2026 10:37:55 +1100 Subject: [PATCH] feat: show collections as item list in modal instead of raw YAML - Display collection name, description, and tags - Show clickable list of items in the collection - Each item shows icon, filename, usage hint, and type badge - Clicking an item opens that file in the modal - Hide copy/download buttons for collections (they don't apply) --- website/public/data/manifest.json | 2 +- website/public/styles/global.css | 98 +++++++++++++++++++++ website/src/scripts/modal.ts | 139 ++++++++++++++++++++++++++++-- 3 files changed, 230 insertions(+), 9 deletions(-) diff --git a/website/public/data/manifest.json b/website/public/data/manifest.json index 48d20815..50bab4d1 100644 --- a/website/public/data/manifest.json +++ b/website/public/data/manifest.json @@ -1,5 +1,5 @@ { - "generated": "2026-01-28T23:33:55.118Z", + "generated": "2026-01-28T23:37:43.897Z", "counts": { "agents": 140, "prompts": 134, diff --git a/website/public/styles/global.css b/website/public/styles/global.css index 0ed84953..0486c84b 100644 --- a/website/public/styles/global.css +++ b/website/public/styles/global.css @@ -837,6 +837,104 @@ a:hover { min-height: 200px; } +/* Collection Modal View */ +.collection-view { + padding: 24px; +} + +.collection-description { + font-size: 15px; + color: var(--color-text); + margin-bottom: 16px; + line-height: 1.6; +} + +.collection-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 20px; +} + +.collection-items-header { + padding: 12px 0; + border-bottom: 1px solid var(--color-border); + margin-bottom: 12px; + color: var(--color-text-muted); + font-size: 14px; +} + +.collection-items-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.collection-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: var(--color-bg); + border: 1px solid var(--color-border); + border-radius: var(--border-radius); + cursor: pointer; + transition: all var(--transition); +} + +.collection-item:hover { + background: var(--color-bg-tertiary); + border-color: var(--color-accent); +} + +.collection-item-icon { + font-size: 20px; + flex-shrink: 0; +} + +.collection-item-info { + flex: 1; + min-width: 0; +} + +.collection-item-name { + font-size: 14px; + font-weight: 500; + color: var(--color-text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.collection-item-usage { + font-size: 13px; + color: var(--color-text-muted); + margin-top: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.collection-item-type { + font-size: 12px; + color: var(--color-text-muted); + background: var(--color-bg-tertiary); + padding: 4px 8px; + border-radius: 4px; + flex-shrink: 0; +} + +.collection-loading, +.collection-error { + padding: 40px; + text-align: center; + color: var(--color-text-muted); +} + +.collection-error { + color: var(--color-error); +} + /* Page Layouts */ .page-header { padding: 56px 0 40px; diff --git a/website/src/scripts/modal.ts b/website/src/scripts/modal.ts index 4f2b2409..882c7506 100644 --- a/website/src/scripts/modal.ts +++ b/website/src/scripts/modal.ts @@ -2,13 +2,35 @@ * Modal functionality for file viewing */ -import { fetchFileContent, getVSCodeInstallUrl, copyToClipboard, showToast, downloadFile, shareFile, getResourceType } from './utils'; +import { fetchFileContent, fetchData, getVSCodeInstallUrl, copyToClipboard, showToast, downloadFile, shareFile, getResourceType, escapeHtml, getResourceIcon } from './utils'; // Modal state let currentFilePath: string | null = null; let currentFileContent: string | null = null; let currentFileType: string | null = null; +// Collection data cache +interface CollectionItem { + path: string; + kind: string; + usage?: string | null; +} + +interface Collection { + id: string; + name: string; + description?: string; + path: string; + items: CollectionItem[]; + tags?: string[]; +} + +interface CollectionsData { + items: Collection[]; +} + +let collectionsCache: CollectionsData | null = null; + /** * Setup modal functionality */ @@ -139,13 +161,16 @@ export function setupInstallDropdown(containerId: string): void { export async function openFileModal(filePath: string, type: string, updateUrl = true): Promise { const modal = document.getElementById('file-modal'); const title = document.getElementById('modal-title'); - const contentEl = document.getElementById('modal-content')?.querySelector('code'); + const modalContent = document.getElementById('modal-content'); + const contentEl = modalContent?.querySelector('code'); const installDropdown = document.getElementById('install-dropdown'); const installBtnMain = document.getElementById('install-btn-main') as HTMLAnchorElement | null; const installVscode = document.getElementById('install-vscode') as HTMLAnchorElement | null; const installInsiders = document.getElementById('install-insiders') as HTMLAnchorElement | null; + const copyBtn = document.getElementById('copy-btn'); + const downloadBtn = document.getElementById('download-btn'); - if (!modal || !title || !contentEl) return; + if (!modal || !title || !modalContent) return; currentFilePath = filePath; currentFileType = type; @@ -157,9 +182,29 @@ export async function openFileModal(filePath: string, type: string, updateUrl = // Show modal with loading state title.textContent = filePath.split('/').pop() || filePath; - contentEl.textContent = 'Loading...'; modal.classList.remove('hidden'); + // Handle collections differently - show as item list + if (type === 'collection') { + await openCollectionModal(filePath, title, modalContent, installDropdown, copyBtn, downloadBtn); + return; + } + + // Regular file modal + if (contentEl) { + contentEl.textContent = 'Loading...'; + } + + // Show copy/download buttons for regular files + if (copyBtn) copyBtn.style.display = 'inline-flex'; + if (downloadBtn) downloadBtn.style.display = 'inline-flex'; + + // Restore pre/code structure if it was replaced by collection view + if (!modalContent.querySelector('pre')) { + modalContent.innerHTML = ''; + } + const codeEl = modalContent.querySelector('code'); + // Setup install dropdown const vscodeUrl = getVSCodeInstallUrl(type, filePath, false); const insidersUrl = getVSCodeInstallUrl(type, filePath, true); @@ -178,13 +223,91 @@ export async function openFileModal(filePath: string, type: string, updateUrl = const fileContent = await fetchFileContent(filePath); currentFileContent = fileContent; - if (fileContent) { - contentEl.textContent = fileContent; - } else { - contentEl.textContent = 'Failed to load file content. Click the button below to view on GitHub.'; + if (fileContent && codeEl) { + codeEl.textContent = fileContent; + } else if (codeEl) { + codeEl.textContent = 'Failed to load file content. Click the button below to view on GitHub.'; } } +/** + * Open collection modal with item list + */ +async function openCollectionModal( + filePath: string, + title: HTMLElement, + modalContent: HTMLElement, + installDropdown: HTMLElement | null, + copyBtn: HTMLElement | null, + downloadBtn: HTMLElement | null +): Promise { + // Hide install dropdown and copy/download for collections + if (installDropdown) installDropdown.style.display = 'none'; + if (copyBtn) copyBtn.style.display = 'none'; + if (downloadBtn) downloadBtn.style.display = 'none'; + + // Show loading + modalContent.innerHTML = '
Loading collection...
'; + + // Load collections data if not cached + if (!collectionsCache) { + collectionsCache = await fetchData('collections.json'); + } + + if (!collectionsCache) { + modalContent.innerHTML = '
Failed to load collection data.
'; + return; + } + + // Find the collection + const collection = collectionsCache.items.find(c => c.path === filePath); + if (!collection) { + modalContent.innerHTML = '
Collection not found.
'; + return; + } + + // Update title + title.textContent = collection.name; + + // Render collection view + modalContent.innerHTML = ` +
+
${escapeHtml(collection.description || '')}
+ ${collection.tags && collection.tags.length > 0 ? ` +
+ ${collection.tags.map(t => `${escapeHtml(t)}`).join('')} +
+ ` : ''} +
+ ${collection.items.length} items in this collection +
+
+ ${collection.items.map(item => ` +
+ ${getResourceIcon(item.kind)} +
+
${escapeHtml(item.path.split('/').pop() || item.path)}
+ ${item.usage ? `
${escapeHtml(item.usage)}
` : ''} +
+ ${escapeHtml(item.kind)} +
+ `).join('')} +
+
+ `; + + // Add click handlers to collection items + modalContent.querySelectorAll('.collection-item').forEach(el => { + el.addEventListener('click', () => { + const path = (el as HTMLElement).dataset.path; + const itemType = (el as HTMLElement).dataset.type; + if (path && itemType) { + openFileModal(path, itemType); + } + }); + }); +} + /** * Close modal * @param updateUrl - Whether to update the URL hash (default: true)