mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-12 05:16:19 -07:00
- 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
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
/**
|
|
* 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 '?';
|
|
}
|