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:
Aaron Powell
2026-03-10 10:13:16 +11:00
committed by GitHub
parent b91369d1b5
commit 3efc4f3a5b
5 changed files with 312 additions and 8 deletions

View File

@@ -214,6 +214,24 @@ export function escapeHtml(text: string): string {
return div.innerHTML;
}
/**
* Validate and sanitize URLs to prevent XSS attacks
* Only allows http/https protocols, returns '#' for invalid URLs
*/
export function sanitizeUrl(url: string | null | undefined): string {
if (!url) return '#';
try {
const parsed = new URL(url);
// Only allow http and https protocols
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
return url;
}
} catch {
// Invalid URL
}
return '#';
}
/**
* Truncate text with ellipsis
*/