Files
Genarrative/src/routing/routeImageReadyGateUtils.ts
T
kdletters 071faa482c 统一 Rust 与 TypeScript 格式化门禁
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口

完成项目 TypeScript/Prettier 与 Rust 全量格式化

修复 Pingora expected executable 门禁的空白敏感误报

同步开发运维文档与 AGC skill pack 格式化忽略规则
2026-09-01 16:28:34 +08:00

113 lines
2.9 KiB
TypeScript

const CSS_IMAGE_URL_PATTERN =
/url\(\s*(?:"([^"]+)"|'([^']+)'|([^'")]+))\s*\)/gu;
export type ImagePreloadResult = {
url: string;
status: 'loaded' | 'failed' | 'timeout';
};
export function extractCssImageUrls(value: string) {
const urls: string[] = [];
CSS_IMAGE_URL_PATTERN.lastIndex = 0;
let match = CSS_IMAGE_URL_PATTERN.exec(value);
while (match) {
const rawUrl = match[1] ?? match[2] ?? match[3] ?? '';
const normalizedRawUrl = rawUrl.trim();
if (normalizedRawUrl) {
urls.push(normalizedRawUrl);
}
match = CSS_IMAGE_URL_PATTERN.exec(value);
}
return urls;
}
export function normalizePreloadImageUrl(rawUrl: string) {
const trimmedUrl = rawUrl.trim();
if (!trimmedUrl || trimmedUrl === 'none' || trimmedUrl.startsWith('#')) {
return null;
}
if (
trimmedUrl.startsWith('data:') ||
trimmedUrl.startsWith('blob:') ||
trimmedUrl.startsWith('http://') ||
trimmedUrl.startsWith('https://')
) {
return trimmedUrl;
}
const baseUrl =
typeof document === 'undefined' ? 'http://localhost/' : document.baseURI;
try {
return new URL(trimmedUrl, baseUrl).href;
} catch {
return null;
}
}
function addNormalizedImageUrl(urls: Set<string>, rawUrl: string | null) {
if (!rawUrl) {
return;
}
const normalizedUrl = normalizePreloadImageUrl(rawUrl);
if (normalizedUrl) {
urls.add(normalizedUrl);
}
}
export function hasCssImageUrlChange(mutation: MutationRecord) {
if (!(mutation.target instanceof HTMLElement)) {
return false;
}
const previousUrls = mutation.oldValue
? extractCssImageUrls(mutation.oldValue)
: [];
const currentUrls = [
...extractCssImageUrls(mutation.target.style.backgroundImage),
...extractCssImageUrls(mutation.target.style.borderImageSource),
...extractCssImageUrls(mutation.target.style.listStyleImage),
];
if (previousUrls.length !== currentUrls.length) {
return true;
}
return currentUrls.some((url, index) => url !== previousUrls[index]);
}
export function collectRouteImageUrls(root: HTMLElement) {
const urls = new Set<string>();
const elements = [
root,
...Array.from(root.querySelectorAll<HTMLElement>('*')),
];
root.querySelectorAll<HTMLImageElement>('img').forEach((image) => {
addNormalizedImageUrl(urls, image.currentSrc);
addNormalizedImageUrl(urls, image.getAttribute('src'));
});
elements.forEach((element) => {
const computedStyle = window.getComputedStyle(element);
[
element.style.backgroundImage,
element.style.borderImageSource,
element.style.listStyleImage,
computedStyle.backgroundImage,
computedStyle.borderImageSource,
computedStyle.listStyleImage,
].forEach((cssImageValue) => {
extractCssImageUrls(cssImageValue).forEach((url) => {
addNormalizedImageUrl(urls, url);
});
});
});
return Array.from(urls);
}