57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
// Shared by the server and canvas; no storage or network work happens here.
|
|
export type ArchivedImageUrlMap = Record<string, string>;
|
|
|
|
export function isGeneratedImageSource(value: string) {
|
|
return (
|
|
/^https?:\/\//i.test(value) || /^data:image\/[a-z0-9.+-]+;base64,[a-z0-9+/=]+$/i.test(value)
|
|
);
|
|
}
|
|
|
|
export function collectImageArchiveSources(value: unknown): string[] {
|
|
const urls = new Set<string>();
|
|
const visit = (item: unknown) => {
|
|
if (typeof item === "string") {
|
|
if (isGeneratedImageSource(item)) urls.add(item);
|
|
else
|
|
for (const match of item.matchAll(/!\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
if (isGeneratedImageSource(match[1])) urls.add(match[1]);
|
|
}
|
|
} else if (Array.isArray(item)) item.forEach(visit);
|
|
else if (item && typeof item === "object" && !(item instanceof Date))
|
|
Object.values(item).forEach(visit);
|
|
};
|
|
visit(value);
|
|
return [...urls];
|
|
}
|
|
|
|
// Structural sharing preserves unaffected nodes and Date values; no undo step is added.
|
|
export function replaceArchivedImageUrls<T>(value: T, replacements: ArchivedImageUrlMap): T {
|
|
if (typeof value === "string") {
|
|
if (replacements[value]) return replacements[value] as T;
|
|
// URLs inside generated-image Markdown carry both preview and original URLs.
|
|
return value.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match, alt: string, url: string) => {
|
|
const nextAlt = alt
|
|
.split("|||")
|
|
.map((part) => replacements[part] || part)
|
|
.join("|||");
|
|
return ``;
|
|
}) as T;
|
|
}
|
|
if (!value || typeof value !== "object" || value instanceof Date) return value;
|
|
let changed = false;
|
|
if (Array.isArray(value)) {
|
|
const next = value.map((item) => {
|
|
const replaced = replaceArchivedImageUrls(item, replacements);
|
|
if (replaced !== item) changed = true;
|
|
return replaced;
|
|
});
|
|
return (changed ? next : value) as T;
|
|
}
|
|
const next: Record<string, unknown> = {};
|
|
for (const [key, item] of Object.entries(value)) {
|
|
next[key] = replaceArchivedImageUrls(item, replacements);
|
|
if (next[key] !== item) changed = true;
|
|
}
|
|
return (changed ? next : value) as T;
|
|
}
|