/** * URL 工具链 - 处理阿里系 CDN 图片 URL * 从 docs/extension/plan.md §6.3 移植(来自生产代码) */ const IMG_EXT = /\.(jpg|jpeg|png|gif|bmp|heic|webp|avif)$/i; /** * 缩略图 URL → 原图 URL * 阿里 CDN 尺寸后缀在扩展名后:xxx.jpg_400x400.jpg → xxx.jpg */ export function toOriginalUrl(url: string): string { const m = url.match(/^(.+?\.(jpg|jpeg|png|gif|bmp|heic|webp|avif))_/i); return m ? m[1] : url; } /** url("https://...") → https://... */ export function urlInBrackets(s: string): string { if (!s?.trim()) return ''; return s.match(/\((.*?)\)/)?.[1]?.replace(/['"]/g, '') ?? ''; } export function isDataUrl(u: string): boolean { return /^data:image/.test(u); } /** 协议相对 // / 根相对 / / 相对路径 → 绝对 URL */ export function toAbsoluteUrl(u: string): string { if (!u) return u; if (isDataUrl(u) || u.startsWith('blob:')) return u; const proto = u.startsWith('http:') ? 'http' : 'https'; if (/^\/\//.test(u)) return `${proto}:${u}`; if (/^\//.test(u)) return `${location.origin}${u}`; if (!/^(.*):/.test(u)) return `${location.origin}/${u}`; return u; } /** 去重用的归一化 key:剥掉尺寸后缀和 query */ export function dedupeKey(url: string): string { const base = toOriginalUrl(url); try { const u = new URL(base); u.search = ''; u.hash = ''; return u.toString(); } catch { return base; } } export function looksLikeImageUrl(u: string): boolean { if (isDataUrl(u)) return true; try { return IMG_EXT.test(new URL(u).pathname); } catch { return IMG_EXT.test(u); } } /** 清洗文件名非法字符(Windows 兼容) */ export function cleanFilename(name: string): string { return name .replace(/[<>:"/\\|?*]/g, '_') .replace(/\s+/g, '_') .substring(0, 80); }