From d450d7d3d5b1197063972554d0f83d422b7dfe11 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 29 Jan 2026 09:44:31 +1100 Subject: [PATCH] feat: add download and share buttons to list view and modal - Add Download button to download file as .md file - Add Share button to copy GitHub link to clipboard - Both buttons appear in modal header and list view actions - Use icon-only buttons in list view for cleaner UI --- website/public/data/manifest.json | 2 +- website/src/components/Modal.astro | 12 ++++ website/src/scripts/modal.ts | 18 +++++- website/src/scripts/pages/agents.ts | 4 +- website/src/scripts/pages/instructions.ts | 4 +- website/src/scripts/pages/prompts.ts | 4 +- website/src/scripts/utils.ts | 75 +++++++++++++++++++++++ 7 files changed, 114 insertions(+), 5 deletions(-) diff --git a/website/public/data/manifest.json b/website/public/data/manifest.json index 6d9bc0bb..1da92aff 100644 --- a/website/public/data/manifest.json +++ b/website/public/data/manifest.json @@ -1,5 +1,5 @@ { - "generated": "2026-01-28T22:33:37.643Z", + "generated": "2026-01-28T22:44:20.356Z", "counts": { "agents": 140, "prompts": 134, diff --git a/website/src/components/Modal.astro b/website/src/components/Modal.astro index 2f69cfb8..cd0761d1 100644 --- a/website/src/components/Modal.astro +++ b/website/src/components/Modal.astro @@ -14,6 +14,18 @@ Copy + +
${getInstallDropdownHtml(resourceType, item.path, true)} + ${getActionButtonsHtml(item.path, true)} GitHub @@ -170,6 +171,7 @@ export async function initAgentsPage(): Promise { setupModal(); setupDropdownCloseHandlers(); + setupActionHandlers(); } // Auto-initialize when DOM is ready diff --git a/website/src/scripts/pages/instructions.ts b/website/src/scripts/pages/instructions.ts index 019503f3..7eba6393 100644 --- a/website/src/scripts/pages/instructions.ts +++ b/website/src/scripts/pages/instructions.ts @@ -3,7 +3,7 @@ */ import { createChoices, getChoicesValues, type Choices } from '../choices'; import { FuzzySearch } from '../search'; -import { fetchData, debounce, escapeHtml, getGitHubUrl, getInstallDropdownHtml, setupDropdownCloseHandlers } from '../utils'; +import { fetchData, debounce, escapeHtml, getGitHubUrl, getInstallDropdownHtml, setupDropdownCloseHandlers, getActionButtonsHtml, setupActionHandlers } from '../utils'; import { setupModal, openFileModal } from '../modal'; interface Instruction { @@ -73,6 +73,7 @@ function renderItems(items: Instruction[], query = ''): void {
${getInstallDropdownHtml('instructions', item.path, true)} + ${getActionButtonsHtml(item.path, true)} GitHub @@ -122,6 +123,7 @@ export async function initInstructionsPage(): Promise { setupModal(); setupDropdownCloseHandlers(); + setupActionHandlers(); } // Auto-initialize when DOM is ready diff --git a/website/src/scripts/pages/prompts.ts b/website/src/scripts/pages/prompts.ts index bdb05b0b..d03edf3d 100644 --- a/website/src/scripts/pages/prompts.ts +++ b/website/src/scripts/pages/prompts.ts @@ -3,7 +3,7 @@ */ import { createChoices, getChoicesValues, type Choices } from '../choices'; import { FuzzySearch } from '../search'; -import { fetchData, debounce, escapeHtml, getGitHubUrl, getInstallDropdownHtml, setupDropdownCloseHandlers } from '../utils'; +import { fetchData, debounce, escapeHtml, getGitHubUrl, getInstallDropdownHtml, setupDropdownCloseHandlers, getActionButtonsHtml, setupActionHandlers } from '../utils'; import { setupModal, openFileModal } from '../modal'; interface Prompt { @@ -68,6 +68,7 @@ function renderItems(items: Prompt[], query = ''): void {
${getInstallDropdownHtml(resourceType, item.path, true)} + ${getActionButtonsHtml(item.path, true)} GitHub @@ -117,6 +118,7 @@ export async function initPromptsPage(): Promise { setupModal(); setupDropdownCloseHandlers(); + setupActionHandlers(); } // Auto-initialize when DOM is ready diff --git a/website/src/scripts/utils.ts b/website/src/scripts/utils.ts index 1d885154..418d3c13 100644 --- a/website/src/scripts/utils.ts +++ b/website/src/scripts/utils.ts @@ -114,6 +114,43 @@ export function getRawGitHubUrl(filePath: string): string { return `${REPO_BASE_URL}/${filePath}`; } +/** + * Download a file from its path + */ +export async function downloadFile(filePath: string): Promise { + try { + const response = await fetch(`${REPO_BASE_URL}/${filePath}`); + if (!response.ok) throw new Error('Failed to fetch file'); + + const content = await response.text(); + const filename = filePath.split('/').pop() || 'file.md'; + + const blob = new Blob([content], { type: 'text/markdown' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + return true; + } catch (error) { + console.error('Download failed:', error); + return false; + } +} + +/** + * Share/copy link to clipboard + */ +export async function shareFile(filePath: string): Promise { + const url = getGitHubUrl(filePath); + return copyToClipboard(url); +} + /** * Show a toast notification */ @@ -254,3 +291,41 @@ export function setupDropdownCloseHandlers(): void { } }); } + +/** + * Generate HTML for action buttons (download, share) in list view + */ +export function getActionButtonsHtml(filePath: string, small = false): string { + const btnClass = small ? 'btn-small' : ''; + const iconSize = small ? 14 : 16; + const escapedPath = filePath.replace(/'/g, "\\'"); + + return ` + + + `; +} + +/** + * Setup global action handlers for download and share buttons + */ +export function setupActionHandlers(): void { + // Expose functions globally for inline onclick handlers + (window as Window & { __downloadFile?: (path: string) => void; __shareFile?: (path: string) => void }).__downloadFile = async (path: string) => { + const success = await downloadFile(path); + showToast(success ? 'Download started!' : 'Download failed', success ? 'success' : 'error'); + }; + + (window as Window & { __downloadFile?: (path: string) => void; __shareFile?: (path: string) => void }).__shareFile = async (path: string) => { + const success = await shareFile(path); + showToast(success ? 'Link copied!' : 'Failed to copy link', success ? 'success' : 'error'); + }; +}