const BASE_URL = "https://thesvg.org"; export interface Preferences { defaultVariant: string; } export interface IconEntry { slug: string; title: string; aliases: string[]; categories: string[]; hex: string; url: string | null; variants: string[]; } export interface IconDetail { slug: string; title: string; aliases: string[]; categories: string[]; hex: string; url: string | null; variants: Record; cdn: { jsdelivr: string; direct: string; }; } interface RegistryResponse { total: number; icons: IconEntry[]; } export interface SearchResult { total: number; icons: IconEntry[]; } export interface Category { name: string; count: number; } let cachedRegistry: RegistryResponse | null = null; async function fetchRegistry(): Promise { if (cachedRegistry) return cachedRegistry; const res = await fetch(`${BASE_URL}/api/registry.json`); if (!res.ok) throw new Error(`API error: ${res.status}`); cachedRegistry = (await res.json()) as RegistryResponse; return cachedRegistry; } export async function searchIcons( query?: string, category?: string, limit = 50, ): Promise { const data = await fetchRegistry(); let filtered = data.icons; if (query) { const q = query.toLowerCase(); filtered = filtered.filter( (icon) => icon.title.toLowerCase().includes(q) || icon.slug.toLowerCase().includes(q) || icon.aliases.some((a) => a.toLowerCase().includes(q)) || icon.categories.some((c) => c.toLowerCase().includes(q)), ); } if (category && category !== "all") { const cat = category.toLowerCase(); filtered = filtered.filter((icon) => icon.categories.some((c) => c.toLowerCase() === cat), ); } return { total: data.total, icons: filtered.slice(0, limit), }; } async function fetchSvg(url: string): Promise { const res = await fetch(url); if (!res.ok) return ""; return res.text(); } export async function getIcon(slug: string): Promise { const data = await fetchRegistry(); const entry = data.icons.find((i) => i.slug === slug); if (!entry) throw new Error(`Icon not found: ${slug}`); // Fetch SVG content for all variants in parallel const variantEntries = await Promise.all( entry.variants.map(async (v) => { const url = getIconUrl(slug, v); const svg = await fetchSvg(url); return [v, { url, svg }] as const; }), ); return { slug: entry.slug, title: entry.title, aliases: entry.aliases, categories: entry.categories, hex: entry.hex, url: entry.url, variants: Object.fromEntries(variantEntries), cdn: { jsdelivr: getCdnUrl(slug), direct: getIconUrl(slug), }, }; } export async function getCategories(): Promise { const res = await fetch(`${BASE_URL}/api/categories.json`); if (!res.ok) throw new Error(`API error: ${res.status}`); const data: { categories: Category[] } = await res.json(); return data.categories; } export function getIconUrl(slug: string, variant = "default"): string { return `${BASE_URL}/icons/${slug}/${variant}.svg`; } export function getIconPageUrl(slug: string): string { return `${BASE_URL}/icon/${slug}`; } export function getCdnUrl(slug: string, variant = "default"): string { return `https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/${slug}/${variant}.svg`; } // --- Copy format helpers --- function toPascalCase(str: string): string { return str .replace(/[^a-zA-Z0-9]+(.)/g, (_, c: string) => c.toUpperCase()) .replace(/^(.)/, (_, c: string) => c.toUpperCase()); } function svgToJsxAttrs(svg: string): string { return svg .replace(/\bclass="/g, 'className="') .replace(/\bxmlns="[^"]*"/g, "") .replace( /\b([a-z]+)-([a-z])/g, (_, a: string, b: string) => `${a}${b.toUpperCase()}`, ); } export function toJsx(svg: string, componentName: string): string { const name = toPascalCase(componentName); const jsxSvg = svgToJsxAttrs(svg); return `export function ${name}Icon(props) {\n return (\n ${jsxSvg.replace(/`; } export function toDataUri(svg: string): string { const encoded = encodeURIComponent(svg) .replace(/'/g, "%27") .replace(/"/g, "%22"); return `data:image/svg+xml,${encoded}`; }