mirror of
https://github.com/github/awesome-copilot.git
synced 2026-03-12 12:15:12 +00:00
feat: show external plugins on the website (#937)
* feat: show external plugins on the website Read plugins/external.json during website data generation and include external plugins alongside local ones in plugins.json. External plugins are flagged with external:true and carry metadata (author, repository, homepage, license, source). On the website: - Plugin cards show a '🔗 External' badge and author attribution - The 'Repository' button links to the source path within the repo - The modal shows metadata (author, repo, homepage, license) and a 'View Repository' CTA instead of an items list - External plugins are searchable and filterable by tags Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR #937 security and UX review comments - Add sanitizeUrl() function to validate URLs and prevent XSS via javascript:/data: schemes - Add rel="noopener noreferrer" to all target="_blank" links to prevent reverse-tabnabbing - Change external plugin path from external/<name> to plugins/<name> for proper deep-linking - Track actual count of external plugins added (after filtering/deduplication) in build logs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -544,6 +544,63 @@ function generatePluginsData(gitDates) {
|
||||
}
|
||||
}
|
||||
|
||||
// Load external plugins from plugins/external.json
|
||||
const externalJsonPath = path.join(PLUGINS_DIR, "external.json");
|
||||
if (fs.existsSync(externalJsonPath)) {
|
||||
try {
|
||||
const externalPlugins = JSON.parse(
|
||||
fs.readFileSync(externalJsonPath, "utf-8")
|
||||
);
|
||||
if (Array.isArray(externalPlugins)) {
|
||||
let addedCount = 0;
|
||||
for (const ext of externalPlugins) {
|
||||
if (!ext.name || !ext.description) {
|
||||
console.warn(
|
||||
`Skipping external plugin with missing name/description`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if a local plugin with the same name already exists
|
||||
if (plugins.some((p) => p.id === ext.name)) {
|
||||
console.warn(
|
||||
`Skipping external plugin "${ext.name}" — local plugin with same name exists`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const tags = ext.keywords || ext.tags || [];
|
||||
|
||||
plugins.push({
|
||||
id: ext.name,
|
||||
name: ext.name,
|
||||
description: ext.description || "",
|
||||
path: `plugins/${ext.name}`,
|
||||
tags: tags,
|
||||
itemCount: 0,
|
||||
items: [],
|
||||
external: true,
|
||||
repository: ext.repository || null,
|
||||
homepage: ext.homepage || null,
|
||||
author: ext.author || null,
|
||||
license: ext.license || null,
|
||||
source: ext.source || null,
|
||||
lastUpdated: null,
|
||||
searchText: `${ext.name} ${ext.description || ""} ${tags.join(
|
||||
" "
|
||||
)} ${ext.author?.name || ""} ${ext.repository || ""}`.toLowerCase(),
|
||||
});
|
||||
addedCount++;
|
||||
}
|
||||
console.log(
|
||||
` ✓ Loaded ${addedCount} external plugin(s)`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`Failed to parse external plugins: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all unique tags
|
||||
const allTags = [...new Set(plugins.flatMap((p) => p.tags))].sort();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user