mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-12 17:16:20 -07:00
feat(anime): show extension and repo icons in extensions panel
- Add extension-icons module (buildIconIndex, isSafeIconUrl, repoFaviconUrl, iconMonogram) with tests - Render each row's icon with a monogram fallback while loading or missing - Installed extensions borrow their icon from the catalogue; repo rows use the index host's favicon - Restrict icon loads to https URLs - Thread iconUrl through AvailableExtension and the runtime, document the feature
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Icons for the extension rows.
|
||||
*
|
||||
* An Aniyomi repository publishes each extension's icon — the streaming site's
|
||||
* own favicon — next to its APK, so the repository index is the only place an
|
||||
* icon can come from. Installed extensions are listed from disk and carry no
|
||||
* icon of their own; they borrow the one their package has in the catalogue,
|
||||
* which means an extension whose repository was removed, or one dropped in by
|
||||
* hand, falls back to a monogram.
|
||||
*/
|
||||
|
||||
interface IconSource {
|
||||
pkg: string;
|
||||
iconUrl: string;
|
||||
}
|
||||
|
||||
/** Package name to icon URL, for looking up an installed extension's icon. */
|
||||
export function buildIconIndex(extensions: IconSource[]): Map<string, string> {
|
||||
const index = new Map<string, string>();
|
||||
for (const extension of extensions) {
|
||||
if (typeof extension.iconUrl === 'string' && extension.iconUrl.length > 0) {
|
||||
index.set(extension.pkg, extension.iconUrl);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/** Only https icons are loaded; a repo index is content the user pointed us at. */
|
||||
export function isSafeIconUrl(url: string | null | undefined): url is string {
|
||||
return typeof url === 'string' && /^https:\/\/\S+$/.test(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* The favicon of the host serving a repository index.
|
||||
*
|
||||
* A repository publishes no icon for itself, so the host's own favicon stands
|
||||
* in. Nothing depends on it: a host that serves none simply leaves the row on
|
||||
* its monogram.
|
||||
*/
|
||||
export function repoFaviconUrl(indexUrl: string): string | null {
|
||||
try {
|
||||
const url = new URL(indexUrl.trim());
|
||||
if (url.protocol !== 'https:') return null;
|
||||
return `https://${url.host}/favicon.ico`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The letter shown while an icon loads, or in place of one that never does.
|
||||
*
|
||||
* Leading punctuation and whitespace are skipped, and a name that carries no
|
||||
* letter or digit at all still gets a placeholder rather than an empty box.
|
||||
*/
|
||||
export function iconMonogram(name: string): string {
|
||||
for (const char of name.trim()) {
|
||||
if (/[\p{L}\p{N}]/u.test(char)) return char.toUpperCase();
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
Reference in New Issue
Block a user