mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 02:15:12 +00:00
Add multi-select filters, light/dark theme, and skill ZIP downloads
- Add multi-select dropdown component for all filter fields - Implement light/dark theme toggle with system preference detection - Add client-side ZIP download for skills using JSZip - Include file lists in skills metadata for download feature - Add title tooltips to multi-select options for long values - Update all pages with consistent theme toggle in header
This commit is contained in:
@@ -80,17 +80,34 @@ function generateAgentsData() {
|
||||
.readdirSync(AGENTS_DIR)
|
||||
.filter((f) => f.endsWith(".agent.md"));
|
||||
|
||||
// Track all unique values for filters
|
||||
const allModels = new Set();
|
||||
const allTools = new Set();
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(AGENTS_DIR, file);
|
||||
const frontmatter = parseFrontmatter(filePath);
|
||||
const relativePath = path.relative(ROOT_FOLDER, filePath).replace(/\\/g, "/");
|
||||
|
||||
const model = frontmatter?.model || null;
|
||||
const tools = frontmatter?.tools || [];
|
||||
const handoffs = frontmatter?.handoffs || [];
|
||||
|
||||
// Track unique values
|
||||
if (model) allModels.add(model);
|
||||
tools.forEach((t) => allTools.add(t));
|
||||
|
||||
agents.push({
|
||||
id: file.replace(".agent.md", ""),
|
||||
title: extractTitle(filePath, frontmatter),
|
||||
description: frontmatter?.description || "",
|
||||
model: frontmatter?.model || null,
|
||||
tools: frontmatter?.tools || [],
|
||||
model: model,
|
||||
tools: tools,
|
||||
hasHandoffs: handoffs.length > 0,
|
||||
handoffs: handoffs.map((h) => ({
|
||||
label: h.label || "",
|
||||
agent: h.agent || "",
|
||||
})),
|
||||
mcpServers: frontmatter?.["mcp-servers"]
|
||||
? Object.keys(frontmatter["mcp-servers"])
|
||||
: [],
|
||||
@@ -99,7 +116,16 @@ function generateAgentsData() {
|
||||
});
|
||||
}
|
||||
|
||||
return agents.sort((a, b) => a.title.localeCompare(b.title));
|
||||
// Sort and return with filter metadata
|
||||
const sortedAgents = agents.sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
return {
|
||||
items: sortedAgents,
|
||||
filters: {
|
||||
models: ["(none)", ...Array.from(allModels).sort()],
|
||||
tools: Array.from(allTools).sort(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,24 +137,73 @@ function generatePromptsData() {
|
||||
.readdirSync(PROMPTS_DIR)
|
||||
.filter((f) => f.endsWith(".prompt.md"));
|
||||
|
||||
// Track all unique tools for filters
|
||||
const allTools = new Set();
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(PROMPTS_DIR, file);
|
||||
const frontmatter = parseFrontmatter(filePath);
|
||||
const relativePath = path.relative(ROOT_FOLDER, filePath).replace(/\\/g, "/");
|
||||
|
||||
const tools = frontmatter?.tools || [];
|
||||
tools.forEach((t) => allTools.add(t));
|
||||
|
||||
prompts.push({
|
||||
id: file.replace(".prompt.md", ""),
|
||||
title: extractTitle(filePath, frontmatter),
|
||||
description: frontmatter?.description || "",
|
||||
agent: frontmatter?.agent || null,
|
||||
model: frontmatter?.model || null,
|
||||
tools: frontmatter?.tools || [],
|
||||
tools: tools,
|
||||
path: relativePath,
|
||||
filename: file,
|
||||
});
|
||||
}
|
||||
|
||||
return prompts.sort((a, b) => a.title.localeCompare(b.title));
|
||||
const sortedPrompts = prompts.sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
return {
|
||||
items: sortedPrompts,
|
||||
filters: {
|
||||
tools: Array.from(allTools).sort(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse applyTo field into an array of patterns
|
||||
*/
|
||||
function parseApplyToPatterns(applyTo) {
|
||||
if (!applyTo) return [];
|
||||
|
||||
// Handle array format
|
||||
if (Array.isArray(applyTo)) {
|
||||
return applyTo.map(p => p.trim()).filter(p => p.length > 0);
|
||||
}
|
||||
|
||||
// Handle string format (comma-separated)
|
||||
if (typeof applyTo === 'string') {
|
||||
return applyTo.split(',').map(p => p.trim()).filter(p => p.length > 0);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract file extension from a glob pattern
|
||||
*/
|
||||
function extractExtensionFromPattern(pattern) {
|
||||
// Match patterns like **.ts, **/*.js, *.py, etc.
|
||||
const match = pattern.match(/\*\.(\w+)$/);
|
||||
if (match) return `.${match[1]}`;
|
||||
|
||||
// Match patterns like **/*.{ts,tsx}
|
||||
const braceMatch = pattern.match(/\*\.\{([^}]+)\}$/);
|
||||
if (braceMatch) {
|
||||
return braceMatch[1].split(',').map(ext => `.${ext.trim()}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,22 +215,75 @@ function generateInstructionsData() {
|
||||
.readdirSync(INSTRUCTIONS_DIR)
|
||||
.filter((f) => f.endsWith(".instructions.md"));
|
||||
|
||||
// Track all unique patterns and extensions for filters
|
||||
const allPatterns = new Set();
|
||||
const allExtensions = new Set();
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(INSTRUCTIONS_DIR, file);
|
||||
const frontmatter = parseFrontmatter(filePath);
|
||||
const relativePath = path.relative(ROOT_FOLDER, filePath).replace(/\\/g, "/");
|
||||
|
||||
const applyToRaw = frontmatter?.applyTo || null;
|
||||
const applyToPatterns = parseApplyToPatterns(applyToRaw);
|
||||
|
||||
// Extract extensions from patterns
|
||||
const extensions = [];
|
||||
for (const pattern of applyToPatterns) {
|
||||
allPatterns.add(pattern);
|
||||
const ext = extractExtensionFromPattern(pattern);
|
||||
if (ext) {
|
||||
if (Array.isArray(ext)) {
|
||||
ext.forEach(e => {
|
||||
extensions.push(e);
|
||||
allExtensions.add(e);
|
||||
});
|
||||
} else {
|
||||
extensions.push(ext);
|
||||
allExtensions.add(ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instructions.push({
|
||||
id: file.replace(".instructions.md", ""),
|
||||
title: extractTitle(filePath, frontmatter),
|
||||
description: frontmatter?.description || "",
|
||||
applyTo: frontmatter?.applyTo || null,
|
||||
applyTo: applyToRaw,
|
||||
applyToPatterns: applyToPatterns,
|
||||
extensions: [...new Set(extensions)],
|
||||
path: relativePath,
|
||||
filename: file,
|
||||
});
|
||||
}
|
||||
|
||||
return instructions.sort((a, b) => a.title.localeCompare(b.title));
|
||||
const sortedInstructions = instructions.sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
return {
|
||||
items: sortedInstructions,
|
||||
filters: {
|
||||
patterns: Array.from(allPatterns).sort(),
|
||||
extensions: ["(none)", ...Array.from(allExtensions).sort()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize a skill based on its name and description
|
||||
*/
|
||||
function categorizeSkill(name, description) {
|
||||
const text = `${name} ${description}`.toLowerCase();
|
||||
|
||||
if (text.includes('azure') || text.includes('appinsights')) return 'Azure';
|
||||
if (text.includes('github') || text.includes('gh-cli') || text.includes('git-commit') || text.includes('git ')) return 'Git & GitHub';
|
||||
if (text.includes('vscode') || text.includes('vs code')) return 'VS Code';
|
||||
if (text.includes('test') || text.includes('qa') || text.includes('playwright')) return 'Testing';
|
||||
if (text.includes('microsoft') || text.includes('m365') || text.includes('workiq')) return 'Microsoft';
|
||||
if (text.includes('cli') || text.includes('command')) return 'CLI Tools';
|
||||
if (text.includes('diagram') || text.includes('plantuml') || text.includes('visual')) return 'Diagrams';
|
||||
if (text.includes('nuget') || text.includes('dotnet') || text.includes('.net')) return '.NET';
|
||||
|
||||
return 'Other';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,19 +293,26 @@ function generateSkillsData() {
|
||||
const skills = [];
|
||||
|
||||
if (!fs.existsSync(SKILLS_DIR)) {
|
||||
return skills;
|
||||
return { items: [], filters: { categories: [], hasAssets: ['Yes', 'No'] } };
|
||||
}
|
||||
|
||||
const folders = fs
|
||||
.readdirSync(SKILLS_DIR)
|
||||
.filter((f) => fs.statSync(path.join(SKILLS_DIR, f)).isDirectory());
|
||||
|
||||
const allCategories = new Set();
|
||||
|
||||
for (const folder of folders) {
|
||||
const skillPath = path.join(SKILLS_DIR, folder);
|
||||
const metadata = parseSkillMetadata(skillPath);
|
||||
|
||||
if (metadata) {
|
||||
const relativePath = path.relative(ROOT_FOLDER, skillPath).replace(/\\/g, "/");
|
||||
const category = categorizeSkill(metadata.name, metadata.description);
|
||||
allCategories.add(category);
|
||||
|
||||
// Get all files in the skill folder recursively
|
||||
const files = getSkillFiles(skillPath, relativePath);
|
||||
|
||||
skills.push({
|
||||
id: folder,
|
||||
@@ -188,13 +323,55 @@ function generateSkillsData() {
|
||||
.join(" "),
|
||||
description: metadata.description,
|
||||
assets: metadata.assets,
|
||||
hasAssets: metadata.assets.length > 0,
|
||||
assetCount: metadata.assets.length,
|
||||
category: category,
|
||||
path: relativePath,
|
||||
skillFile: `${relativePath}/SKILL.md`,
|
||||
files: files,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return skills.sort((a, b) => a.title.localeCompare(b.title));
|
||||
const sortedSkills = skills.sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
return {
|
||||
items: sortedSkills,
|
||||
filters: {
|
||||
categories: Array.from(allCategories).sort(),
|
||||
hasAssets: ['Yes', 'No'],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all files in a skill folder recursively
|
||||
*/
|
||||
function getSkillFiles(skillPath, relativePath) {
|
||||
const files = [];
|
||||
|
||||
function walkDir(dir, relDir) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
walkDir(fullPath, relPath);
|
||||
} else {
|
||||
// Get file size
|
||||
const stats = fs.statSync(fullPath);
|
||||
files.push({
|
||||
path: `${relativePath}/${relPath}`,
|
||||
name: relPath,
|
||||
size: stats.size,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walkDir(skillPath, '');
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,17 +388,23 @@ function generateCollectionsData() {
|
||||
.readdirSync(COLLECTIONS_DIR)
|
||||
.filter((f) => f.endsWith(".collection.yml"));
|
||||
|
||||
// Track all unique tags
|
||||
const allTags = new Set();
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(COLLECTIONS_DIR, file);
|
||||
const data = parseCollectionYaml(filePath);
|
||||
const relativePath = path.relative(ROOT_FOLDER, filePath).replace(/\\/g, "/");
|
||||
|
||||
if (data) {
|
||||
const tags = data.tags || [];
|
||||
tags.forEach((t) => allTags.add(t));
|
||||
|
||||
collections.push({
|
||||
id: file.replace(".collection.yml", ""),
|
||||
name: data.name || file.replace(".collection.yml", ""),
|
||||
description: data.description || "",
|
||||
tags: data.tags || [],
|
||||
tags: tags,
|
||||
featured: data.featured || false,
|
||||
items: (data.items || []).map((item) => ({
|
||||
path: item.path,
|
||||
@@ -235,11 +418,18 @@ function generateCollectionsData() {
|
||||
}
|
||||
|
||||
// Sort with featured first, then alphabetically
|
||||
return collections.sort((a, b) => {
|
||||
const sortedCollections = collections.sort((a, b) => {
|
||||
if (a.featured && !b.featured) return -1;
|
||||
if (!a.featured && b.featured) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return {
|
||||
items: sortedCollections,
|
||||
filters: {
|
||||
tags: Array.from(allTags).sort(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,20 +506,25 @@ async function main() {
|
||||
ensureDataDir();
|
||||
|
||||
// Generate all data
|
||||
const agents = generateAgentsData();
|
||||
console.log(`✓ Generated ${agents.length} agents`);
|
||||
const agentsData = generateAgentsData();
|
||||
const agents = agentsData.items;
|
||||
console.log(`✓ Generated ${agents.length} agents (${agentsData.filters.models.length} models, ${agentsData.filters.tools.length} tools)`);
|
||||
|
||||
const prompts = generatePromptsData();
|
||||
console.log(`✓ Generated ${prompts.length} prompts`);
|
||||
const promptsData = generatePromptsData();
|
||||
const prompts = promptsData.items;
|
||||
console.log(`✓ Generated ${prompts.length} prompts (${promptsData.filters.tools.length} tools)`);
|
||||
|
||||
const instructions = generateInstructionsData();
|
||||
console.log(`✓ Generated ${instructions.length} instructions`);
|
||||
const instructionsData = generateInstructionsData();
|
||||
const instructions = instructionsData.items;
|
||||
console.log(`✓ Generated ${instructions.length} instructions (${instructionsData.filters.extensions.length} extensions)`);
|
||||
|
||||
const skills = generateSkillsData();
|
||||
console.log(`✓ Generated ${skills.length} skills`);
|
||||
const skillsData = generateSkillsData();
|
||||
const skills = skillsData.items;
|
||||
console.log(`✓ Generated ${skills.length} skills (${skillsData.filters.categories.length} categories)`);
|
||||
|
||||
const collections = generateCollectionsData();
|
||||
console.log(`✓ Generated ${collections.length} collections`);
|
||||
const collectionsData = generateCollectionsData();
|
||||
const collections = collectionsData.items;
|
||||
console.log(`✓ Generated ${collections.length} collections (${collectionsData.filters.tags.length} tags)`);
|
||||
|
||||
const searchIndex = generateSearchIndex(agents, prompts, instructions, skills, collections);
|
||||
console.log(`✓ Generated search index with ${searchIndex.length} items`);
|
||||
@@ -337,27 +532,27 @@ async function main() {
|
||||
// Write JSON files
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "agents.json"),
|
||||
JSON.stringify(agents, null, 2)
|
||||
JSON.stringify(agentsData, null, 2)
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "prompts.json"),
|
||||
JSON.stringify(prompts, null, 2)
|
||||
JSON.stringify(promptsData, null, 2)
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "instructions.json"),
|
||||
JSON.stringify(instructions, null, 2)
|
||||
JSON.stringify(instructionsData, null, 2)
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "skills.json"),
|
||||
JSON.stringify(skills, null, 2)
|
||||
JSON.stringify(skillsData, null, 2)
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "collections.json"),
|
||||
JSON.stringify(collections, null, 2)
|
||||
JSON.stringify(collectionsData, null, 2)
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* CSS Variables and Base Styles */
|
||||
:root {
|
||||
/* Dark theme (default) */
|
||||
--color-bg: #0d1117;
|
||||
--color-bg-secondary: #161b22;
|
||||
--color-bg-tertiary: #21262d;
|
||||
@@ -25,9 +26,26 @@
|
||||
--header-height: 64px;
|
||||
}
|
||||
|
||||
/* Light mode support */
|
||||
/* Light theme */
|
||||
[data-theme="light"] {
|
||||
--color-bg: #ffffff;
|
||||
--color-bg-secondary: #f6f8fa;
|
||||
--color-bg-tertiary: #f0f3f6;
|
||||
--color-border: #d0d7de;
|
||||
--color-text: #24292f;
|
||||
--color-text-muted: #57606a;
|
||||
--color-text-emphasis: #1f2328;
|
||||
--color-link: #0969da;
|
||||
--color-link-hover: #0550ae;
|
||||
--color-card-bg: #ffffff;
|
||||
--color-card-hover: #f6f8fa;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 8px 24px rgba(0,0,0,0.08);
|
||||
--shadow-lg: 0 8px 24px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* Auto theme based on system preference */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
:root:not([data-theme="dark"]) {
|
||||
--color-bg: #ffffff;
|
||||
--color-bg-secondary: #f6f8fa;
|
||||
--color-bg-tertiary: #f0f3f6;
|
||||
@@ -39,6 +57,8 @@
|
||||
--color-link-hover: #0550ae;
|
||||
--color-card-bg: #ffffff;
|
||||
--color-card-hover: #f6f8fa;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 8px 24px rgba(0,0,0,0.08);
|
||||
--shadow-lg: 0 8px 24px rgba(0,0,0,0.15);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +160,69 @@ a:hover {
|
||||
color: var(--color-text-emphasis);
|
||||
}
|
||||
|
||||
/* Theme Toggle */
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-emphasis);
|
||||
}
|
||||
|
||||
.theme-toggle svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.theme-toggle .icon-sun,
|
||||
.theme-toggle .icon-moon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show sun icon in dark mode (click to switch to light) */
|
||||
:root:not([data-theme="light"]) .theme-toggle .icon-sun {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:root:not([data-theme="light"]) .theme-toggle .icon-moon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show moon icon in light mode (click to switch to dark) */
|
||||
[data-theme="light"] .theme-toggle .icon-sun {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-theme="light"] .theme-toggle .icon-moon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Handle auto mode with prefers-color-scheme */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme="dark"]):not([data-theme="light"]) .theme-toggle .icon-sun {
|
||||
display: none;
|
||||
}
|
||||
:root:not([data-theme="dark"]):not([data-theme="light"]) .theme-toggle .icon-moon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
background: linear-gradient(180deg, var(--color-bg-secondary) 0%, var(--color-bg) 100%);
|
||||
@@ -452,6 +535,26 @@ a:hover {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* Spinner animation */
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* Button states */
|
||||
.btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
position: fixed;
|
||||
@@ -570,6 +673,308 @@ a:hover {
|
||||
border-color: var(--color-link);
|
||||
}
|
||||
|
||||
/* Filters Bar */
|
||||
.filters-bar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-group label {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-group select {
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text);
|
||||
min-width: 150px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-group select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-link);
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Multi-Select Component */
|
||||
.multi-select {
|
||||
position: relative;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.multi-select-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.multi-select-trigger:hover {
|
||||
border-color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.multi-select.is-open .multi-select-trigger {
|
||||
border-color: var(--color-link);
|
||||
}
|
||||
|
||||
.multi-select-display {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.multi-select-display.has-value {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.multi-select-arrow {
|
||||
flex-shrink: 0;
|
||||
transition: transform var(--transition);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.multi-select.is-open .multi-select-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.multi-select-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: 4px;
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 100;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
max-height: 320px;
|
||||
}
|
||||
|
||||
.multi-select.is-open .multi-select-dropdown {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.multi-select-search-wrapper {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.multi-select-search {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.multi-select-search:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-link);
|
||||
}
|
||||
|
||||
.multi-select-options {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.multi-select-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition);
|
||||
}
|
||||
|
||||
.multi-select-option:hover {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.multi-select-option input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.multi-select-checkbox {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 3px;
|
||||
background-color: var(--color-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.multi-select-option input[type="checkbox"]:checked + .multi-select-checkbox {
|
||||
background-color: var(--color-link);
|
||||
border-color: var(--color-link);
|
||||
}
|
||||
|
||||
.multi-select-option input[type="checkbox"]:checked + .multi-select-checkbox::after {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'/%3E%3C/svg%3E");
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.multi-select-label {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: var(--color-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.multi-select-empty {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.multi-select-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.multi-select-actions button {
|
||||
flex: 1;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.multi-select-clear {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.multi-select-clear:hover {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.multi-select-done {
|
||||
background-color: var(--color-link);
|
||||
border: 1px solid var(--color-link);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.multi-select-done:hover {
|
||||
background-color: var(--color-link-hover);
|
||||
border-color: var(--color-link-hover);
|
||||
}
|
||||
|
||||
/* Tag variants */
|
||||
.tag-model {
|
||||
background-color: rgba(88, 166, 255, 0.15);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.tag-none {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tag-handoffs {
|
||||
background-color: rgba(210, 153, 34, 0.15);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.tag-extension {
|
||||
background-color: rgba(35, 134, 54, 0.15);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.tag-category {
|
||||
background-color: rgba(130, 80, 223, 0.15);
|
||||
color: #a371f7;
|
||||
}
|
||||
|
||||
.tag-assets {
|
||||
background-color: rgba(88, 166, 255, 0.15);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.tag-collection {
|
||||
background-color: rgba(210, 153, 34, 0.15);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.tag-featured {
|
||||
background-color: rgba(210, 153, 34, 0.2);
|
||||
color: var(--color-warning);
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.results-count {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-muted);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-01-28T02:42:05.621Z",
|
||||
"generated": "2026-01-28T03:53:29.513Z",
|
||||
"counts": {
|
||||
"agents": 140,
|
||||
"prompts": 134,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@
|
||||
<meta name="description" content="Community-contributed instructions, prompts, agents, and skills for GitHub Copilot">
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🤖</text></svg>">
|
||||
<script src="js/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -25,11 +26,21 @@
|
||||
<a href="pages/tools.html">Tools</a>
|
||||
<a href="pages/samples.html">Samples</a>
|
||||
</nav>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
13
website/js/jszip.min.js
vendored
Normal file
13
website/js/jszip.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
209
website/js/multi-select.js
Normal file
209
website/js/multi-select.js
Normal file
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* Multi-select dropdown component
|
||||
* Creates a dropdown with checkboxes for multiple selections
|
||||
*/
|
||||
class MultiSelect {
|
||||
constructor(container, options = {}) {
|
||||
this.container = typeof container === 'string' ? document.querySelector(container) : container;
|
||||
this.options = {
|
||||
placeholder: options.placeholder || 'Select...',
|
||||
searchable: options.searchable !== false,
|
||||
onChange: options.onChange || (() => {}),
|
||||
maxDisplay: options.maxDisplay || 2,
|
||||
};
|
||||
this.items = [];
|
||||
this.selected = new Set();
|
||||
this.isOpen = false;
|
||||
this.searchQuery = '';
|
||||
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.container.classList.add('multi-select');
|
||||
this.container.innerHTML = `
|
||||
<button type="button" class="multi-select-trigger" aria-haspopup="listbox" aria-expanded="false">
|
||||
<span class="multi-select-display">${this.options.placeholder}</span>
|
||||
<svg class="multi-select-arrow" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<path d="M4.427 7.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 7H4.604a.25.25 0 00-.177.427z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="multi-select-dropdown" role="listbox" aria-multiselectable="true">
|
||||
${this.options.searchable ? `
|
||||
<div class="multi-select-search-wrapper">
|
||||
<input type="text" class="multi-select-search" placeholder="Search..." autocomplete="off">
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="multi-select-options"></div>
|
||||
<div class="multi-select-actions">
|
||||
<button type="button" class="multi-select-clear">Clear</button>
|
||||
<button type="button" class="multi-select-done">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.trigger = this.container.querySelector('.multi-select-trigger');
|
||||
this.display = this.container.querySelector('.multi-select-display');
|
||||
this.dropdown = this.container.querySelector('.multi-select-dropdown');
|
||||
this.optionsContainer = this.container.querySelector('.multi-select-options');
|
||||
this.searchInput = this.container.querySelector('.multi-select-search');
|
||||
this.clearBtn = this.container.querySelector('.multi-select-clear');
|
||||
this.doneBtn = this.container.querySelector('.multi-select-done');
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Toggle dropdown
|
||||
this.trigger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.toggle();
|
||||
});
|
||||
|
||||
// Search
|
||||
if (this.searchInput) {
|
||||
this.searchInput.addEventListener('input', () => {
|
||||
this.searchQuery = this.searchInput.value.toLowerCase();
|
||||
this.renderOptions();
|
||||
});
|
||||
this.searchInput.addEventListener('click', (e) => e.stopPropagation());
|
||||
}
|
||||
|
||||
// Clear selection
|
||||
this.clearBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.clearSelection();
|
||||
});
|
||||
|
||||
// Done button
|
||||
this.doneBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.close();
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!this.container.contains(e.target)) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
this.container.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setItems(items) {
|
||||
this.items = items.map(item => {
|
||||
if (typeof item === 'string') {
|
||||
return { value: item, label: item };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.renderOptions();
|
||||
}
|
||||
|
||||
renderOptions() {
|
||||
const filteredItems = this.items.filter(item => {
|
||||
if (!this.searchQuery) return true;
|
||||
return item.label.toLowerCase().includes(this.searchQuery);
|
||||
});
|
||||
|
||||
if (filteredItems.length === 0) {
|
||||
this.optionsContainer.innerHTML = '<div class="multi-select-empty">No options found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
this.optionsContainer.innerHTML = filteredItems.map(item => `
|
||||
<label class="multi-select-option" data-value="${this.escapeHtml(item.value)}" title="${this.escapeHtml(item.label)}">
|
||||
<input type="checkbox" ${this.selected.has(item.value) ? 'checked' : ''}>
|
||||
<span class="multi-select-checkbox"></span>
|
||||
<span class="multi-select-label">${this.escapeHtml(item.label)}</span>
|
||||
</label>
|
||||
`).join('');
|
||||
|
||||
// Add change listeners to checkboxes
|
||||
this.optionsContainer.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', (e) => {
|
||||
const value = e.target.closest('.multi-select-option').dataset.value;
|
||||
if (e.target.checked) {
|
||||
this.selected.add(value);
|
||||
} else {
|
||||
this.selected.delete(value);
|
||||
}
|
||||
this.updateDisplay();
|
||||
this.options.onChange(this.getSelected());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
const selected = this.getSelected();
|
||||
if (selected.length === 0) {
|
||||
this.display.textContent = this.options.placeholder;
|
||||
this.display.classList.remove('has-value');
|
||||
} else if (selected.length <= this.options.maxDisplay) {
|
||||
this.display.textContent = selected.join(', ');
|
||||
this.display.classList.add('has-value');
|
||||
} else {
|
||||
this.display.textContent = `${selected.length} selected`;
|
||||
this.display.classList.add('has-value');
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this.isOpen = true;
|
||||
this.container.classList.add('is-open');
|
||||
this.trigger.setAttribute('aria-expanded', 'true');
|
||||
if (this.searchInput) {
|
||||
this.searchInput.value = '';
|
||||
this.searchQuery = '';
|
||||
this.renderOptions();
|
||||
setTimeout(() => this.searchInput.focus(), 10);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
this.container.classList.remove('is-open');
|
||||
this.trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
getSelected() {
|
||||
return Array.from(this.selected);
|
||||
}
|
||||
|
||||
setSelected(values) {
|
||||
this.selected = new Set(values);
|
||||
this.renderOptions();
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
this.selected.clear();
|
||||
this.renderOptions();
|
||||
this.updateDisplay();
|
||||
this.options.onChange(this.getSelected());
|
||||
}
|
||||
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = MultiSelect;
|
||||
}
|
||||
74
website/js/theme.js
Normal file
74
website/js/theme.js
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Theme management for the Awesome Copilot website
|
||||
* Supports light/dark mode with user preference storage
|
||||
*/
|
||||
|
||||
const THEME_KEY = 'awesome-copilot-theme';
|
||||
|
||||
/**
|
||||
* Get the current theme preference
|
||||
* Priority: localStorage > system preference > dark (default)
|
||||
*/
|
||||
function getThemePreference() {
|
||||
const stored = localStorage.getItem(THEME_KEY);
|
||||
if (stored === 'light' || stored === 'dark') {
|
||||
return stored;
|
||||
}
|
||||
// Check system preference
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
|
||||
return 'light';
|
||||
}
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply theme to the document
|
||||
*/
|
||||
function applyTheme(theme) {
|
||||
if (theme === 'light') {
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle between light and dark theme
|
||||
*/
|
||||
function toggleTheme() {
|
||||
const current = document.documentElement.getAttribute('data-theme');
|
||||
const newTheme = current === 'light' ? 'dark' : 'light';
|
||||
applyTheme(newTheme);
|
||||
localStorage.setItem(THEME_KEY, newTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize theme on page load
|
||||
*/
|
||||
function initTheme() {
|
||||
// Apply theme immediately to prevent flash
|
||||
const theme = getThemePreference();
|
||||
applyTheme(theme);
|
||||
|
||||
// Listen for system theme changes
|
||||
if (window.matchMedia) {
|
||||
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
|
||||
// Only auto-switch if user hasn't set a preference
|
||||
const stored = localStorage.getItem(THEME_KEY);
|
||||
if (!stored) {
|
||||
applyTheme(e.matches ? 'light' : 'dark');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize theme immediately (before DOM ready to prevent flash)
|
||||
initTheme();
|
||||
|
||||
// Setup toggle button after DOM ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const toggleBtn = document.getElementById('theme-toggle');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', toggleTheme);
|
||||
}
|
||||
});
|
||||
@@ -78,6 +78,13 @@ function getGitHubUrl(filePath) {
|
||||
return `${REPO_GITHUB_URL}/${filePath}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw GitHub URL for a file (for fetching content)
|
||||
*/
|
||||
function getRawGitHubUrl(filePath) {
|
||||
return `${REPO_BASE_URL}/${filePath}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a toast notification
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="description" content="Custom agents for specialized GitHub Copilot experiences">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🤖</text></svg>">
|
||||
<script src="../js/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -25,11 +26,21 @@
|
||||
<a href="tools.html">Tools</a>
|
||||
<a href="samples.html">Samples</a>
|
||||
</nav>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -47,6 +58,26 @@
|
||||
<div class="search-bar">
|
||||
<input type="text" id="search-input" placeholder="Search agents..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label>Model:</label>
|
||||
<div id="filter-model" class="multi-select-container"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>Tool:</label>
|
||||
<div id="filter-tool" class="multi-select-container"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="filter-handoffs">
|
||||
Has Handoffs
|
||||
</label>
|
||||
</div>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading agents...</div>
|
||||
@@ -100,45 +131,131 @@
|
||||
|
||||
<script src="../js/utils.js"></script>
|
||||
<script src="../js/search.js"></script>
|
||||
<script src="../js/multi-select.js"></script>
|
||||
<script src="../js/app.js"></script>
|
||||
<script>
|
||||
// Page-specific initialization
|
||||
const resourceType = 'agent';
|
||||
const dataFile = 'agents.json';
|
||||
let allItems = [];
|
||||
let filters = { models: [], tools: [] };
|
||||
let search = new FuzzySearch();
|
||||
let modelSelect, toolSelect;
|
||||
|
||||
// Current filter state
|
||||
let currentFilters = {
|
||||
models: [],
|
||||
tools: [],
|
||||
hasHandoffs: false,
|
||||
};
|
||||
|
||||
async function initPage() {
|
||||
const list = document.getElementById('resource-list');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const handoffsCheckbox = document.getElementById('filter-handoffs');
|
||||
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||
|
||||
// Load data
|
||||
const data = await fetchData(dataFile);
|
||||
if (!data) {
|
||||
if (!data || !data.items) {
|
||||
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
allItems = data;
|
||||
allItems = data.items;
|
||||
filters = data.filters;
|
||||
search.setItems(allItems);
|
||||
|
||||
// Initialize multi-select filters
|
||||
modelSelect = new MultiSelect('#filter-model', {
|
||||
placeholder: 'All Models',
|
||||
onChange: (selected) => {
|
||||
currentFilters.models = selected;
|
||||
applyFiltersAndRender();
|
||||
}
|
||||
});
|
||||
modelSelect.setItems(filters.models);
|
||||
|
||||
toolSelect = new MultiSelect('#filter-tool', {
|
||||
placeholder: 'All Tools',
|
||||
onChange: (selected) => {
|
||||
currentFilters.tools = selected;
|
||||
applyFiltersAndRender();
|
||||
}
|
||||
});
|
||||
toolSelect.setItems(filters.tools);
|
||||
|
||||
// Render all items
|
||||
renderItems(allItems);
|
||||
countEl.textContent = `${allItems.length} agents`;
|
||||
applyFiltersAndRender();
|
||||
|
||||
// Setup search
|
||||
searchInput.addEventListener('input', debounce((e) => {
|
||||
const query = e.target.value;
|
||||
const results = query ? search.search(query) : allItems;
|
||||
renderItems(results, query);
|
||||
countEl.textContent = `${results.length} of ${allItems.length} agents`;
|
||||
}, 200));
|
||||
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
|
||||
|
||||
// Setup filter listeners
|
||||
handoffsCheckbox.addEventListener('change', () => {
|
||||
currentFilters.hasHandoffs = handoffsCheckbox.checked;
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
clearFiltersBtn.addEventListener('click', () => {
|
||||
currentFilters = { models: [], tools: [], hasHandoffs: false };
|
||||
modelSelect.clearSelection();
|
||||
toolSelect.clearSelection();
|
||||
handoffsCheckbox.checked = false;
|
||||
searchInput.value = '';
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
// Setup modal
|
||||
setupModal();
|
||||
}
|
||||
|
||||
function applyFiltersAndRender() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const query = searchInput.value;
|
||||
|
||||
// Start with all items or search results
|
||||
let results = query ? search.search(query) : [...allItems];
|
||||
|
||||
// Apply model filter (OR logic - match any selected model)
|
||||
if (currentFilters.models.length > 0) {
|
||||
results = results.filter(item => {
|
||||
if (currentFilters.models.includes('(none)') && !item.model) {
|
||||
return true;
|
||||
}
|
||||
return currentFilters.models.includes(item.model);
|
||||
});
|
||||
}
|
||||
|
||||
// Apply tool filter (OR logic - match any selected tool)
|
||||
if (currentFilters.tools.length > 0) {
|
||||
results = results.filter(item =>
|
||||
item.tools?.some(tool => currentFilters.tools.includes(tool))
|
||||
);
|
||||
}
|
||||
|
||||
// Apply handoffs filter
|
||||
if (currentFilters.hasHandoffs) {
|
||||
results = results.filter(item => item.hasHandoffs);
|
||||
}
|
||||
|
||||
renderItems(results, query);
|
||||
|
||||
// Update count with filter info
|
||||
const activeFilters = [];
|
||||
if (currentFilters.models.length > 0) activeFilters.push(`models: ${currentFilters.models.length}`);
|
||||
if (currentFilters.tools.length > 0) activeFilters.push(`tools: ${currentFilters.tools.length}`);
|
||||
if (currentFilters.hasHandoffs) activeFilters.push('has handoffs');
|
||||
|
||||
let countText = `${results.length} of ${allItems.length} agents`;
|
||||
if (activeFilters.length > 0) {
|
||||
countText += ` (filtered by ${activeFilters.join(', ')})`;
|
||||
}
|
||||
countEl.textContent = countText;
|
||||
}
|
||||
|
||||
function renderItems(items, query = '') {
|
||||
const list = document.getElementById('resource-list');
|
||||
|
||||
@@ -146,7 +263,7 @@
|
||||
list.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<h3>No agents found</h3>
|
||||
<p>Try a different search term</p>
|
||||
<p>Try a different search term or adjust filters</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -157,12 +274,12 @@
|
||||
<div class="resource-info">
|
||||
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
|
||||
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
|
||||
${item.tools?.length || item.mcpServers?.length ? `
|
||||
<div class="resource-meta">
|
||||
${item.model ? `<span class="resource-tag">Model: ${escapeHtml(item.model)}</span>` : ''}
|
||||
${item.mcpServers?.slice(0, 3).map(s => `<span class="resource-tag">MCP: ${escapeHtml(s)}</span>`).join('') || ''}
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="resource-meta">
|
||||
${item.model ? `<span class="resource-tag tag-model">${escapeHtml(item.model)}</span>` : '<span class="resource-tag tag-none">No model</span>'}
|
||||
${item.hasHandoffs ? '<span class="resource-tag tag-handoffs">Has Handoffs</span>' : ''}
|
||||
${item.tools?.length ? `<span class="resource-tag">${item.tools.length} tools</span>` : ''}
|
||||
${item.mcpServers?.length ? `<span class="resource-tag">MCP: ${item.mcpServers.length}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-actions">
|
||||
<a href="${getVSCodeInstallUrl(resourceType, item.path)}" class="btn btn-primary" onclick="event.stopPropagation()">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="description" content="Curated collections of prompts, instructions, and agents for specific workflows">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📦</text></svg>">
|
||||
<script src="../js/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -25,11 +26,21 @@
|
||||
<a href="tools.html">Tools</a>
|
||||
<a href="samples.html">Samples</a>
|
||||
</nav>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -47,6 +58,22 @@
|
||||
<div class="search-bar">
|
||||
<input type="text" id="search-input" placeholder="Search collections..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label>Tag:</label>
|
||||
<div id="filter-tag" class="multi-select-container"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="filter-featured">
|
||||
Featured Only
|
||||
</label>
|
||||
</div>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading collections...</div>
|
||||
@@ -96,43 +123,109 @@
|
||||
|
||||
<script src="../js/utils.js"></script>
|
||||
<script src="../js/search.js"></script>
|
||||
<script src="../js/multi-select.js"></script>
|
||||
<script src="../js/app.js"></script>
|
||||
<script>
|
||||
const resourceType = 'collection';
|
||||
const dataFile = 'collections.json';
|
||||
let allItems = [];
|
||||
let filters = { tags: [] };
|
||||
let search = new FuzzySearch();
|
||||
let tagSelect;
|
||||
|
||||
// Current filter state
|
||||
let currentFilters = {
|
||||
tags: [],
|
||||
featured: false,
|
||||
};
|
||||
|
||||
async function initPage() {
|
||||
const list = document.getElementById('resource-list');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const featuredCheckbox = document.getElementById('filter-featured');
|
||||
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||
|
||||
const data = await fetchData(dataFile);
|
||||
if (!data) {
|
||||
if (!data || !data.items) {
|
||||
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
allItems = data;
|
||||
allItems = data.items;
|
||||
filters = data.filters;
|
||||
search.setItems(allItems.map(item => ({
|
||||
...item,
|
||||
title: item.name,
|
||||
searchText: `${item.name} ${item.description} ${item.tags?.join(' ') || ''}`.toLowerCase()
|
||||
})));
|
||||
renderItems(allItems);
|
||||
countEl.textContent = `${allItems.length} collections`;
|
||||
|
||||
searchInput.addEventListener('input', debounce((e) => {
|
||||
const query = e.target.value;
|
||||
const results = query ? search.search(query) : allItems;
|
||||
renderItems(results, query);
|
||||
countEl.textContent = `${results.length} of ${allItems.length} collections`;
|
||||
}, 200));
|
||||
// Initialize multi-select filter
|
||||
tagSelect = new MultiSelect('#filter-tag', {
|
||||
placeholder: 'All Tags',
|
||||
onChange: (selected) => {
|
||||
currentFilters.tags = selected;
|
||||
applyFiltersAndRender();
|
||||
}
|
||||
});
|
||||
tagSelect.setItems(filters.tags);
|
||||
|
||||
// Render all items
|
||||
applyFiltersAndRender();
|
||||
|
||||
// Setup search
|
||||
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
|
||||
|
||||
featuredCheckbox.addEventListener('change', () => {
|
||||
currentFilters.featured = featuredCheckbox.checked;
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
clearFiltersBtn.addEventListener('click', () => {
|
||||
currentFilters = { tags: [], featured: false };
|
||||
tagSelect.clearSelection();
|
||||
featuredCheckbox.checked = false;
|
||||
searchInput.value = '';
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
setupModal();
|
||||
}
|
||||
|
||||
function applyFiltersAndRender() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const query = searchInput.value;
|
||||
|
||||
// Start with all items or search results
|
||||
let results = query ? search.search(query) : [...allItems];
|
||||
|
||||
// Apply tag filter (OR logic)
|
||||
if (currentFilters.tags.length > 0) {
|
||||
results = results.filter(item =>
|
||||
item.tags?.some(tag => currentFilters.tags.includes(tag))
|
||||
);
|
||||
}
|
||||
|
||||
// Apply featured filter
|
||||
if (currentFilters.featured) {
|
||||
results = results.filter(item => item.featured);
|
||||
}
|
||||
|
||||
renderItems(results, query);
|
||||
|
||||
// Update count with filter info
|
||||
const activeFilters = [];
|
||||
if (currentFilters.tags.length > 0) activeFilters.push(`${currentFilters.tags.length} tag${currentFilters.tags.length > 1 ? 's' : ''}`);
|
||||
if (currentFilters.featured) activeFilters.push('featured');
|
||||
|
||||
let countText = `${results.length} of ${allItems.length} collections`;
|
||||
if (activeFilters.length > 0) {
|
||||
countText += ` (filtered by ${activeFilters.join(', ')})`;
|
||||
}
|
||||
countEl.textContent = countText;
|
||||
}
|
||||
|
||||
function renderItems(items, query = '') {
|
||||
const list = document.getElementById('resource-list');
|
||||
|
||||
@@ -140,7 +233,7 @@
|
||||
list.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<h3>No collections found</h3>
|
||||
<p>Try a different search term</p>
|
||||
<p>Try a different search term or adjust filters</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -150,11 +243,11 @@
|
||||
<div class="resource-item" onclick="openFileModal('${item.path}', '${resourceType}')">
|
||||
<div class="resource-info">
|
||||
<div class="resource-title">
|
||||
${item.featured ? '⭐ ' : ''}${query ? search.highlight(item.name, query) : escapeHtml(item.name)}
|
||||
${item.featured ? '<span class="tag-featured">⭐ Featured</span> ' : ''}${query ? search.highlight(item.name, query) : escapeHtml(item.name)}
|
||||
</div>
|
||||
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
|
||||
<div class="resource-meta">
|
||||
${item.tags?.map(tag => `<span class="resource-tag">${escapeHtml(tag)}</span>`).join('') || ''}
|
||||
${item.tags?.map(tag => `<span class="resource-tag tag-collection">${escapeHtml(tag)}</span>`).join('') || ''}
|
||||
<span class="resource-tag">${item.items?.length || 0} items</span>
|
||||
</div>
|
||||
${item.items?.length ? `
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="description" content="Coding standards and best practices for GitHub Copilot">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📋</text></svg>">
|
||||
<script src="../js/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -25,11 +26,21 @@
|
||||
<a href="tools.html">Tools</a>
|
||||
<a href="samples.html">Samples</a>
|
||||
</nav>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -47,6 +58,16 @@
|
||||
<div class="search-bar">
|
||||
<input type="text" id="search-input" placeholder="Search instructions..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label>File Extension:</label>
|
||||
<div id="filter-extension" class="multi-select-container"></div>
|
||||
</div>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading instructions...</div>
|
||||
@@ -100,39 +121,91 @@
|
||||
|
||||
<script src="../js/utils.js"></script>
|
||||
<script src="../js/search.js"></script>
|
||||
<script src="../js/multi-select.js"></script>
|
||||
<script src="../js/app.js"></script>
|
||||
<script>
|
||||
const resourceType = 'instruction';
|
||||
const dataFile = 'instructions.json';
|
||||
let allItems = [];
|
||||
let filters = { extensions: [], patterns: [] };
|
||||
let search = new FuzzySearch();
|
||||
let extensionSelect;
|
||||
|
||||
// Current filter state
|
||||
let currentFilters = {
|
||||
extensions: [],
|
||||
};
|
||||
|
||||
async function initPage() {
|
||||
const list = document.getElementById('resource-list');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||
|
||||
const data = await fetchData(dataFile);
|
||||
if (!data) {
|
||||
if (!data || !data.items) {
|
||||
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
allItems = data;
|
||||
allItems = data.items;
|
||||
filters = data.filters;
|
||||
search.setItems(allItems);
|
||||
renderItems(allItems);
|
||||
countEl.textContent = `${allItems.length} instructions`;
|
||||
|
||||
searchInput.addEventListener('input', debounce((e) => {
|
||||
const query = e.target.value;
|
||||
const results = query ? search.search(query) : allItems;
|
||||
renderItems(results, query);
|
||||
countEl.textContent = `${results.length} of ${allItems.length} instructions`;
|
||||
}, 200));
|
||||
// Initialize multi-select filter
|
||||
extensionSelect = new MultiSelect('#filter-extension', {
|
||||
placeholder: 'All Extensions',
|
||||
onChange: (selected) => {
|
||||
currentFilters.extensions = selected;
|
||||
applyFiltersAndRender();
|
||||
}
|
||||
});
|
||||
extensionSelect.setItems(filters.extensions);
|
||||
|
||||
// Render all items
|
||||
applyFiltersAndRender();
|
||||
|
||||
// Setup search
|
||||
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
|
||||
|
||||
clearFiltersBtn.addEventListener('click', () => {
|
||||
currentFilters = { extensions: [] };
|
||||
extensionSelect.clearSelection();
|
||||
searchInput.value = '';
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
setupModal();
|
||||
}
|
||||
|
||||
function applyFiltersAndRender() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const query = searchInput.value;
|
||||
|
||||
// Start with all items or search results
|
||||
let results = query ? search.search(query) : [...allItems];
|
||||
|
||||
// Apply extension filter (OR logic)
|
||||
if (currentFilters.extensions.length > 0) {
|
||||
results = results.filter(item => {
|
||||
if (currentFilters.extensions.includes('(none)') && (!item.extensions || item.extensions.length === 0)) {
|
||||
return true;
|
||||
}
|
||||
return item.extensions?.some(ext => currentFilters.extensions.includes(ext));
|
||||
});
|
||||
}
|
||||
|
||||
renderItems(results, query);
|
||||
|
||||
// Update count with filter info
|
||||
let countText = `${results.length} of ${allItems.length} instructions`;
|
||||
if (currentFilters.extensions.length > 0) {
|
||||
countText += ` (filtered by ${currentFilters.extensions.length} extension${currentFilters.extensions.length > 1 ? 's' : ''})`;
|
||||
}
|
||||
countEl.textContent = countText;
|
||||
}
|
||||
|
||||
function renderItems(items, query = '') {
|
||||
const list = document.getElementById('resource-list');
|
||||
|
||||
@@ -140,7 +213,7 @@
|
||||
list.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<h3>No instructions found</h3>
|
||||
<p>Try a different search term</p>
|
||||
<p>Try a different search term or adjust filters</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -151,11 +224,9 @@
|
||||
<div class="resource-info">
|
||||
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
|
||||
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
|
||||
${item.applyTo ? `
|
||||
<div class="resource-meta">
|
||||
<span class="resource-tag">Applies to: ${escapeHtml(item.applyTo)}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="resource-meta">
|
||||
${item.extensions?.length ? item.extensions.map(ext => `<span class="resource-tag tag-extension">${escapeHtml(ext)}</span>`).join('') : '<span class="resource-tag tag-none">All files</span>'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-actions">
|
||||
<a href="${getVSCodeInstallUrl('instructions', item.path)}" class="btn btn-primary" onclick="event.stopPropagation()">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="description" content="Ready-to-use prompt templates for development tasks with GitHub Copilot">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎯</text></svg>">
|
||||
<script src="../js/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -25,11 +26,21 @@
|
||||
<a href="tools.html">Tools</a>
|
||||
<a href="samples.html">Samples</a>
|
||||
</nav>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -47,6 +58,16 @@
|
||||
<div class="search-bar">
|
||||
<input type="text" id="search-input" placeholder="Search prompts..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label>Tool:</label>
|
||||
<div id="filter-tool" class="multi-select-container"></div>
|
||||
</div>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading prompts...</div>
|
||||
@@ -100,39 +121,88 @@
|
||||
|
||||
<script src="../js/utils.js"></script>
|
||||
<script src="../js/search.js"></script>
|
||||
<script src="../js/multi-select.js"></script>
|
||||
<script src="../js/app.js"></script>
|
||||
<script>
|
||||
const resourceType = 'prompt';
|
||||
const dataFile = 'prompts.json';
|
||||
let allItems = [];
|
||||
let filters = { tools: [] };
|
||||
let search = new FuzzySearch();
|
||||
let toolSelect;
|
||||
|
||||
// Current filter state
|
||||
let currentFilters = {
|
||||
tools: [],
|
||||
};
|
||||
|
||||
async function initPage() {
|
||||
const list = document.getElementById('resource-list');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||
|
||||
const data = await fetchData(dataFile);
|
||||
if (!data) {
|
||||
if (!data || !data.items) {
|
||||
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
allItems = data;
|
||||
allItems = data.items;
|
||||
filters = data.filters;
|
||||
search.setItems(allItems);
|
||||
renderItems(allItems);
|
||||
countEl.textContent = `${allItems.length} prompts`;
|
||||
|
||||
searchInput.addEventListener('input', debounce((e) => {
|
||||
const query = e.target.value;
|
||||
const results = query ? search.search(query) : allItems;
|
||||
renderItems(results, query);
|
||||
countEl.textContent = `${results.length} of ${allItems.length} prompts`;
|
||||
}, 200));
|
||||
// Initialize multi-select filter
|
||||
toolSelect = new MultiSelect('#filter-tool', {
|
||||
placeholder: 'All Tools',
|
||||
onChange: (selected) => {
|
||||
currentFilters.tools = selected;
|
||||
applyFiltersAndRender();
|
||||
}
|
||||
});
|
||||
toolSelect.setItems(filters.tools);
|
||||
|
||||
// Render all items
|
||||
applyFiltersAndRender();
|
||||
|
||||
// Setup search
|
||||
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
|
||||
|
||||
clearFiltersBtn.addEventListener('click', () => {
|
||||
currentFilters = { tools: [] };
|
||||
toolSelect.clearSelection();
|
||||
searchInput.value = '';
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
setupModal();
|
||||
}
|
||||
|
||||
function applyFiltersAndRender() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const query = searchInput.value;
|
||||
|
||||
// Start with all items or search results
|
||||
let results = query ? search.search(query) : [...allItems];
|
||||
|
||||
// Apply tool filter (OR logic)
|
||||
if (currentFilters.tools.length > 0) {
|
||||
results = results.filter(item =>
|
||||
item.tools?.some(tool => currentFilters.tools.includes(tool))
|
||||
);
|
||||
}
|
||||
|
||||
renderItems(results, query);
|
||||
|
||||
// Update count with filter info
|
||||
let countText = `${results.length} of ${allItems.length} prompts`;
|
||||
if (currentFilters.tools.length > 0) {
|
||||
countText += ` (filtered by ${currentFilters.tools.length} tool${currentFilters.tools.length > 1 ? 's' : ''})`;
|
||||
}
|
||||
countEl.textContent = countText;
|
||||
}
|
||||
|
||||
function renderItems(items, query = '') {
|
||||
const list = document.getElementById('resource-list');
|
||||
|
||||
@@ -140,7 +210,7 @@
|
||||
list.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<h3>No prompts found</h3>
|
||||
<p>Try a different search term</p>
|
||||
<p>Try a different search term or adjust filters</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -152,8 +222,8 @@
|
||||
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
|
||||
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
|
||||
<div class="resource-meta">
|
||||
${item.model ? `<span class="resource-tag">Model: ${escapeHtml(item.model)}</span>` : ''}
|
||||
${item.agent ? `<span class="resource-tag">Agent: ${escapeHtml(item.agent)}</span>` : ''}
|
||||
${item.model ? `<span class="resource-tag tag-model">${escapeHtml(item.model)}</span>` : ''}
|
||||
${item.tools?.length ? `<span class="resource-tag">${item.tools.length} tools</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-actions">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="description" content="Code samples and examples for building with GitHub Copilot">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📚</text></svg>">
|
||||
<script src="../js/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -25,11 +26,21 @@
|
||||
<a href="tools.html">Tools</a>
|
||||
<a href="samples.html" class="active">Samples</a>
|
||||
</nav>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="description" content="Self-contained agent skills with instructions and bundled resources">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<script src="../js/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -25,11 +26,21 @@
|
||||
<a href="tools.html">Tools</a>
|
||||
<a href="samples.html">Samples</a>
|
||||
</nav>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -47,6 +58,22 @@
|
||||
<div class="search-bar">
|
||||
<input type="text" id="search-input" placeholder="Search skills..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label>Category:</label>
|
||||
<div id="filter-category" class="multi-select-container"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="filter-has-assets">
|
||||
Has Bundled Assets
|
||||
</label>
|
||||
</div>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
|
||||
<div class="results-count" id="results-count"></div>
|
||||
<div class="resource-list" id="resource-list">
|
||||
<div class="loading">Loading skills...</div>
|
||||
@@ -99,39 +126,104 @@
|
||||
|
||||
<script src="../js/utils.js"></script>
|
||||
<script src="../js/search.js"></script>
|
||||
<script src="../js/multi-select.js"></script>
|
||||
<script src="../js/jszip.min.js"></script>
|
||||
<script src="../js/app.js"></script>
|
||||
<script>
|
||||
const resourceType = 'skill';
|
||||
const dataFile = 'skills.json';
|
||||
let allItems = [];
|
||||
let filters = { categories: [], hasAssets: [] };
|
||||
let search = new FuzzySearch();
|
||||
let categorySelect;
|
||||
|
||||
// Current filter state
|
||||
let currentFilters = {
|
||||
categories: [],
|
||||
hasAssets: false,
|
||||
};
|
||||
|
||||
async function initPage() {
|
||||
const list = document.getElementById('resource-list');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const hasAssetsCheckbox = document.getElementById('filter-has-assets');
|
||||
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||
|
||||
const data = await fetchData(dataFile);
|
||||
if (!data) {
|
||||
if (!data || !data.items) {
|
||||
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
allItems = data;
|
||||
allItems = data.items;
|
||||
filters = data.filters;
|
||||
search.setItems(allItems);
|
||||
renderItems(allItems);
|
||||
countEl.textContent = `${allItems.length} skills`;
|
||||
|
||||
searchInput.addEventListener('input', debounce((e) => {
|
||||
const query = e.target.value;
|
||||
const results = query ? search.search(query) : allItems;
|
||||
renderItems(results, query);
|
||||
countEl.textContent = `${results.length} of ${allItems.length} skills`;
|
||||
}, 200));
|
||||
// Initialize multi-select filter
|
||||
categorySelect = new MultiSelect('#filter-category', {
|
||||
placeholder: 'All Categories',
|
||||
onChange: (selected) => {
|
||||
currentFilters.categories = selected;
|
||||
applyFiltersAndRender();
|
||||
}
|
||||
});
|
||||
categorySelect.setItems(filters.categories);
|
||||
|
||||
// Render all items
|
||||
applyFiltersAndRender();
|
||||
|
||||
// Setup search
|
||||
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
|
||||
|
||||
hasAssetsCheckbox.addEventListener('change', () => {
|
||||
currentFilters.hasAssets = hasAssetsCheckbox.checked;
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
clearFiltersBtn.addEventListener('click', () => {
|
||||
currentFilters = { categories: [], hasAssets: false };
|
||||
categorySelect.clearSelection();
|
||||
hasAssetsCheckbox.checked = false;
|
||||
searchInput.value = '';
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
setupModal();
|
||||
}
|
||||
|
||||
function applyFiltersAndRender() {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const countEl = document.getElementById('results-count');
|
||||
const query = searchInput.value;
|
||||
|
||||
// Start with all items or search results
|
||||
let results = query ? search.search(query) : [...allItems];
|
||||
|
||||
// Apply category filter (OR logic)
|
||||
if (currentFilters.categories.length > 0) {
|
||||
results = results.filter(item => currentFilters.categories.includes(item.category));
|
||||
}
|
||||
|
||||
// Apply has assets filter
|
||||
if (currentFilters.hasAssets) {
|
||||
results = results.filter(item => item.hasAssets);
|
||||
}
|
||||
|
||||
renderItems(results, query);
|
||||
|
||||
// Update count with filter info
|
||||
const activeFilters = [];
|
||||
if (currentFilters.categories.length > 0) activeFilters.push(`${currentFilters.categories.length} categor${currentFilters.categories.length > 1 ? 'ies' : 'y'}`);
|
||||
if (currentFilters.hasAssets) activeFilters.push('has assets');
|
||||
|
||||
let countText = `${results.length} of ${allItems.length} skills`;
|
||||
if (activeFilters.length > 0) {
|
||||
countText += ` (filtered by ${activeFilters.join(', ')})`;
|
||||
}
|
||||
countEl.textContent = countText;
|
||||
}
|
||||
|
||||
function renderItems(items, query = '') {
|
||||
const list = document.getElementById('resource-list');
|
||||
|
||||
@@ -139,7 +231,7 @@
|
||||
list.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<h3>No skills found</h3>
|
||||
<p>Try a different search term</p>
|
||||
<p>Try a different search term or adjust filters</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -150,13 +242,20 @@
|
||||
<div class="resource-info">
|
||||
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
|
||||
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
|
||||
${item.assets?.length ? `
|
||||
<div class="resource-meta">
|
||||
<span class="resource-tag">${item.assets.length} bundled asset${item.assets.length === 1 ? '' : 's'}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="resource-meta">
|
||||
<span class="resource-tag tag-category">${escapeHtml(item.category)}</span>
|
||||
${item.hasAssets ? `<span class="resource-tag tag-assets">${item.assetCount} asset${item.assetCount === 1 ? '' : 's'}</span>` : ''}
|
||||
<span class="resource-tag">${item.files.length} file${item.files.length === 1 ? '' : 's'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-actions">
|
||||
<button class="btn btn-primary" onclick="event.stopPropagation(); downloadSkill('${item.id}')" title="Download as ZIP">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"/>
|
||||
<path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"/>
|
||||
</svg>
|
||||
Download
|
||||
</button>
|
||||
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary" target="_blank" onclick="event.stopPropagation()">
|
||||
View Folder
|
||||
</a>
|
||||
@@ -165,6 +264,103 @@
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Download skill as ZIP file
|
||||
async function downloadSkill(skillId) {
|
||||
const skill = allItems.find(item => item.id === skillId);
|
||||
if (!skill || !skill.files || skill.files.length === 0) {
|
||||
alert('No files found for this skill');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
const btn = event.target.closest('button');
|
||||
const originalContent = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = `
|
||||
<svg class="spinner" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<path d="M8 0a8 8 0 1 0 8 8h-1.5A6.5 6.5 0 1 1 8 1.5V0z"/>
|
||||
</svg>
|
||||
Preparing...
|
||||
`;
|
||||
|
||||
try {
|
||||
const zip = new JSZip();
|
||||
const folder = zip.folder(skill.id);
|
||||
|
||||
// Fetch all files in parallel
|
||||
const fetchPromises = skill.files.map(async (file) => {
|
||||
const url = getRawGitHubUrl(file.path);
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
console.warn(`Failed to fetch ${file.path}: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
const content = await response.text();
|
||||
return { name: file.name, content };
|
||||
} catch (err) {
|
||||
console.warn(`Error fetching ${file.path}:`, err);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(fetchPromises);
|
||||
|
||||
// Add successfully fetched files to zip
|
||||
let addedFiles = 0;
|
||||
for (const result of results) {
|
||||
if (result) {
|
||||
folder.file(result.name, result.content);
|
||||
addedFiles++;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedFiles === 0) {
|
||||
throw new Error('Failed to fetch any files');
|
||||
}
|
||||
|
||||
// Generate and download zip
|
||||
const blob = await zip.generateAsync({ type: 'blob' });
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.download = `${skill.id}.zip`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
|
||||
// Show success
|
||||
btn.innerHTML = `
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0z"/>
|
||||
</svg>
|
||||
Downloaded!
|
||||
`;
|
||||
|
||||
setTimeout(() => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalContent;
|
||||
}, 2000);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Download failed:', err);
|
||||
btn.innerHTML = `
|
||||
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 0 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06z"/>
|
||||
</svg>
|
||||
Failed
|
||||
`;
|
||||
|
||||
setTimeout(() => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalContent;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initPage);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="description" content="MCP servers and developer tools for GitHub Copilot">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔧</text></svg>">
|
||||
<script src="../js/theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
@@ -25,11 +26,21 @@
|
||||
<a href="tools.html" class="active">Tools</a>
|
||||
<a href="samples.html">Samples</a>
|
||||
</nav>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
|
||||
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user