import { useEffect, useMemo, useState } from "react"; import Fuse from "fuse.js"; interface IconEntry { slug: string; title: string; aliases: string[]; hex: string; categories: string[]; variants: string[] | Record; } interface RegistryResponse { icons: IconEntry[]; } // NOTE: @main pins live indefinitely. A schema change to icons.json or a // path restructure under public/icons/ would break every installed instance // at once. v1.1 should pin to a release tag (e.g. @v3.0.0) and ship a // migration when the schema bumps. const REGISTRY_URL = "https://cdn.jsdelivr.net/gh/glincker/thesvg@main/src/data/icons.json"; const CDN_BASE = "https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons"; const CACHE_KEY = "registry_cache"; const TTL_MS = 24 * 60 * 60 * 1000; interface CacheEntry { timestamp: number; data: RegistryResponse; } async function loadRegistry(): Promise { const cached = (await chrome.storage.local.get(CACHE_KEY)) as Record< string, CacheEntry | undefined >; const entry = cached[CACHE_KEY]; if (entry && Date.now() - entry.timestamp < TTL_MS) { return entry.data.icons; } const res = await fetch(REGISTRY_URL); if (!res.ok) { throw new Error(`registry fetch failed: ${res.status}`); } const raw = (await res.json()) as IconEntry[] | RegistryResponse; const data: RegistryResponse = Array.isArray(raw) ? { icons: raw } : raw; await chrome.storage.local.set({ [CACHE_KEY]: { timestamp: Date.now(), data }, }); return data.icons; } function variantKeys(entry: IconEntry): string[] { if (Array.isArray(entry.variants)) return entry.variants; return Object.keys(entry.variants); } function cdnUrl(slug: string, variant = "default"): string { return `${CDN_BASE}/${slug}/${variant}.svg`; } async function fetchSvgBody(slug: string, variant = "default"): Promise { const res = await fetch(cdnUrl(slug, variant)); if (!res.ok) { throw new Error(`svg fetch failed: ${res.status}`); } return res.text(); } type Status = "loading" | "ready" | "error"; export default function App() { const [status, setStatus] = useState("loading"); const [icons, setIcons] = useState([]); const [query, setQuery] = useState(""); const [selectedSlug, setSelectedSlug] = useState(null); const [selectedVariant, setSelectedVariant] = useState("default"); const [toast, setToast] = useState(null); useEffect(() => { loadRegistry() .then((list) => { setIcons(list); setStatus("ready"); }) .catch(() => { setStatus("error"); }); }, []); const fuse = useMemo( () => new Fuse(icons, { keys: ["title", "slug", "aliases"], threshold: 0.3, distance: 100, }), [icons] ); const results = useMemo(() => { if (status !== "ready") return []; if (!query.trim()) return icons.slice(0, 20); return fuse.search(query, { limit: 20 }).map((r) => r.item); }, [fuse, icons, query, status]); const selected = useMemo( () => results.find((i) => i.slug === selectedSlug) ?? null, [results, selectedSlug] ); function notify(message: string): void { setToast(message); setTimeout(() => setToast(null), 1400); } async function handleAction( kind: "svg" | "url" | "md", icon: IconEntry, variant: string ): Promise { try { if (kind === "url") { await navigator.clipboard.writeText(cdnUrl(icon.slug, variant)); notify("URL copied"); } else if (kind === "md") { await navigator.clipboard.writeText( `![${icon.title}](${cdnUrl(icon.slug, variant)})` ); notify("Markdown copied"); } else { const body = await fetchSvgBody(icon.slug, variant); await navigator.clipboard.writeText(body); notify("SVG copied"); } } catch { notify("Copy failed"); } } function retry(): void { setStatus("loading"); loadRegistry() .then((list) => { setIcons(list); setStatus("ready"); }) .catch(() => setStatus("error")); } return ( <>
theSVG {icons.length || ""}
{ setQuery(e.target.value); setSelectedSlug(null); setSelectedVariant("default"); }} />
{status === "loading" && (
Loading catalog
)} {status === "error" && (
Could not load registry.
)} {status === "ready" && (
    {results.length === 0 &&
  • No matches
  • } {results.map((icon) => { const isSelected = selected?.slug === icon.slug; const keys = variantKeys(icon); return (
  • { setSelectedSlug(isSelected ? null : icon.slug); // Pick the first available variant. Not every icon has a // "default" key in the registry; some only have // ["color", "mono"] or ["wordmark"]. Defaulting to // whatever the registry actually lists keeps the action // buttons in sync with the chips. const firstKey = variantKeys(icon)[0] ?? "default"; setSelectedVariant(firstKey); }} > {icon.title} {icon.slug} {keys.length > 1 && ( {keys.length} variants )}
  • ); })}
)} {selected && (
{variantKeys(selected).length > 1 && (
{variantKeys(selected).map((v) => ( ))}
)} {selected.title}
)} {toast && (
{toast}
)} ); }