mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-23 03:45:13 +00:00
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
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generated": "2026-01-28T22:33:37.643Z",
|
"generated": "2026-01-28T22:44:20.356Z",
|
||||||
"counts": {
|
"counts": {
|
||||||
"agents": 140,
|
"agents": 140,
|
||||||
"prompts": 134,
|
"prompts": 134,
|
||||||
|
|||||||
@@ -14,6 +14,18 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Copy
|
Copy
|
||||||
</button>
|
</button>
|
||||||
|
<button id="download-btn" class="btn btn-secondary" title="Download file">
|
||||||
|
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||||
|
<path d="M7.47 10.78a.75.75 0 0 0 1.06 0l3.75-3.75a.75.75 0 0 0-1.06-1.06L8.75 8.44V1.75a.75.75 0 0 0-1.5 0v6.69L4.78 5.97a.75.75 0 0 0-1.06 1.06l3.75 3.75ZM3.75 13a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z"/>
|
||||||
|
</svg>
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
<button id="share-btn" class="btn btn-secondary" title="Copy link to clipboard">
|
||||||
|
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||||
|
<path d="M7.775 3.275a.75.75 0 0 0 1.06 1.06l1.25-1.25a2 2 0 1 1 2.83 2.83l-2.5 2.5a2 2 0 0 1-2.83 0 .75.75 0 0 0-1.06 1.06 3.5 3.5 0 0 0 4.95 0l2.5-2.5a3.5 3.5 0 0 0-4.95-4.95l-1.25 1.25zm-.025 5.45a.75.75 0 0 0-1.06-1.06l-1.25 1.25a2 2 0 1 1-2.83-2.83l2.5-2.5a2 2 0 0 1 2.83 0 .75.75 0 0 0 1.06-1.06 3.5 3.5 0 0 0-4.95 0l-2.5 2.5a3.5 3.5 0 0 0 4.95 4.95l1.25-1.25z"/>
|
||||||
|
</svg>
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
<div id="install-dropdown" class="install-dropdown" style="display: none;">
|
<div id="install-dropdown" class="install-dropdown" style="display: none;">
|
||||||
<a id="install-btn-main" class="btn btn-primary install-btn-main" target="_blank" rel="noopener">
|
<a id="install-btn-main" class="btn btn-primary install-btn-main" target="_blank" rel="noopener">
|
||||||
Install
|
Install
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Modal functionality for file viewing
|
* Modal functionality for file viewing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fetchFileContent, getVSCodeInstallUrl, copyToClipboard, showToast } from './utils';
|
import { fetchFileContent, getVSCodeInstallUrl, copyToClipboard, showToast, downloadFile, shareFile } from './utils';
|
||||||
|
|
||||||
// Modal state
|
// Modal state
|
||||||
let currentFilePath: string | null = null;
|
let currentFilePath: string | null = null;
|
||||||
@@ -16,6 +16,8 @@ export function setupModal(): void {
|
|||||||
const modal = document.getElementById('file-modal');
|
const modal = document.getElementById('file-modal');
|
||||||
const closeBtn = document.getElementById('close-modal');
|
const closeBtn = document.getElementById('close-modal');
|
||||||
const copyBtn = document.getElementById('copy-btn');
|
const copyBtn = document.getElementById('copy-btn');
|
||||||
|
const downloadBtn = document.getElementById('download-btn');
|
||||||
|
const shareBtn = document.getElementById('share-btn');
|
||||||
|
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
|
|
||||||
@@ -38,6 +40,20 @@ export function setupModal(): void {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
downloadBtn?.addEventListener('click', async () => {
|
||||||
|
if (currentFilePath) {
|
||||||
|
const success = await downloadFile(currentFilePath);
|
||||||
|
showToast(success ? 'Download started!' : 'Download failed', success ? 'success' : 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
shareBtn?.addEventListener('click', async () => {
|
||||||
|
if (currentFilePath) {
|
||||||
|
const success = await shareFile(currentFilePath);
|
||||||
|
showToast(success ? 'Link copied to clipboard!' : 'Failed to copy link', success ? 'success' : 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Setup install dropdown toggle
|
// Setup install dropdown toggle
|
||||||
setupInstallDropdown('install-dropdown');
|
setupInstallDropdown('install-dropdown');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
||||||
import { FuzzySearch } from '../search';
|
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';
|
import { setupModal, openFileModal } from '../modal';
|
||||||
|
|
||||||
interface Agent {
|
interface Agent {
|
||||||
@@ -103,6 +103,7 @@ function renderItems(items: Agent[], query = ''): void {
|
|||||||
</div>
|
</div>
|
||||||
<div class="resource-actions">
|
<div class="resource-actions">
|
||||||
${getInstallDropdownHtml(resourceType, item.path, true)}
|
${getInstallDropdownHtml(resourceType, item.path, true)}
|
||||||
|
${getActionButtonsHtml(item.path, true)}
|
||||||
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary btn-small" target="_blank" onclick="event.stopPropagation()">
|
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary btn-small" target="_blank" onclick="event.stopPropagation()">
|
||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
@@ -170,6 +171,7 @@ export async function initAgentsPage(): Promise<void> {
|
|||||||
|
|
||||||
setupModal();
|
setupModal();
|
||||||
setupDropdownCloseHandlers();
|
setupDropdownCloseHandlers();
|
||||||
|
setupActionHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-initialize when DOM is ready
|
// Auto-initialize when DOM is ready
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
||||||
import { FuzzySearch } from '../search';
|
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';
|
import { setupModal, openFileModal } from '../modal';
|
||||||
|
|
||||||
interface Instruction {
|
interface Instruction {
|
||||||
@@ -73,6 +73,7 @@ function renderItems(items: Instruction[], query = ''): void {
|
|||||||
</div>
|
</div>
|
||||||
<div class="resource-actions">
|
<div class="resource-actions">
|
||||||
${getInstallDropdownHtml('instructions', item.path, true)}
|
${getInstallDropdownHtml('instructions', item.path, true)}
|
||||||
|
${getActionButtonsHtml(item.path, true)}
|
||||||
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary btn-small" target="_blank" onclick="event.stopPropagation()">
|
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary btn-small" target="_blank" onclick="event.stopPropagation()">
|
||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
@@ -122,6 +123,7 @@ export async function initInstructionsPage(): Promise<void> {
|
|||||||
|
|
||||||
setupModal();
|
setupModal();
|
||||||
setupDropdownCloseHandlers();
|
setupDropdownCloseHandlers();
|
||||||
|
setupActionHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-initialize when DOM is ready
|
// Auto-initialize when DOM is ready
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
import { createChoices, getChoicesValues, type Choices } from '../choices';
|
||||||
import { FuzzySearch } from '../search';
|
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';
|
import { setupModal, openFileModal } from '../modal';
|
||||||
|
|
||||||
interface Prompt {
|
interface Prompt {
|
||||||
@@ -68,6 +68,7 @@ function renderItems(items: Prompt[], query = ''): void {
|
|||||||
</div>
|
</div>
|
||||||
<div class="resource-actions">
|
<div class="resource-actions">
|
||||||
${getInstallDropdownHtml(resourceType, item.path, true)}
|
${getInstallDropdownHtml(resourceType, item.path, true)}
|
||||||
|
${getActionButtonsHtml(item.path, true)}
|
||||||
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary btn-small" target="_blank" onclick="event.stopPropagation()">
|
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary btn-small" target="_blank" onclick="event.stopPropagation()">
|
||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
@@ -117,6 +118,7 @@ export async function initPromptsPage(): Promise<void> {
|
|||||||
|
|
||||||
setupModal();
|
setupModal();
|
||||||
setupDropdownCloseHandlers();
|
setupDropdownCloseHandlers();
|
||||||
|
setupActionHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-initialize when DOM is ready
|
// Auto-initialize when DOM is ready
|
||||||
|
|||||||
@@ -114,6 +114,43 @@ export function getRawGitHubUrl(filePath: string): string {
|
|||||||
return `${REPO_BASE_URL}/${filePath}`;
|
return `${REPO_BASE_URL}/${filePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a file from its path
|
||||||
|
*/
|
||||||
|
export async function downloadFile(filePath: string): Promise<boolean> {
|
||||||
|
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<boolean> {
|
||||||
|
const url = getGitHubUrl(filePath);
|
||||||
|
return copyToClipboard(url);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a toast notification
|
* 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 `
|
||||||
|
<button class="btn btn-secondary ${btnClass} action-download" data-path="${escapeHtml(filePath)}" onclick="event.stopPropagation(); window.__downloadFile && window.__downloadFile('${escapedPath}')" title="Download file">
|
||||||
|
<svg viewBox="0 0 16 16" width="${iconSize}" height="${iconSize}" fill="currentColor">
|
||||||
|
<path d="M7.47 10.78a.75.75 0 0 0 1.06 0l3.75-3.75a.75.75 0 0 0-1.06-1.06L8.75 8.44V1.75a.75.75 0 0 0-1.5 0v6.69L4.78 5.97a.75.75 0 0 0-1.06 1.06l3.75 3.75ZM3.75 13a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary ${btnClass} action-share" data-path="${escapeHtml(filePath)}" onclick="event.stopPropagation(); window.__shareFile && window.__shareFile('${escapedPath}')" title="Copy link">
|
||||||
|
<svg viewBox="0 0 16 16" width="${iconSize}" height="${iconSize}" fill="currentColor">
|
||||||
|
<path d="M7.775 3.275a.75.75 0 0 0 1.06 1.06l1.25-1.25a2 2 0 1 1 2.83 2.83l-2.5 2.5a2 2 0 0 1-2.83 0 .75.75 0 0 0-1.06 1.06 3.5 3.5 0 0 0 4.95 0l2.5-2.5a3.5 3.5 0 0 0-4.95-4.95l-1.25 1.25zm-.025 5.45a.75.75 0 0 0-1.06-1.06l-1.25 1.25a2 2 0 1 1-2.83-2.83l2.5-2.5a2 2 0 0 1 2.83 0 .75.75 0 0 0 1.06-1.06 3.5 3.5 0 0 0-4.95 0l-2.5 2.5a3.5 3.5 0 0 0 4.95 4.95l1.25-1.25z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user