ultisuite-client/lib/drive/docs-graphic-assets.ts
R3D347HR4Y 2a7c153748
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
wrap page
2026-06-10 12:48:27 +02:00

90 lines
2.7 KiB
TypeScript

import type { TipTapJSON } from "@/lib/drive/richtext-import"
const BASE64_PREFIX = "data:"
/** Collect base64 image src values from TipTap JSON. */
export function collectBase64ImageSrcs(content: TipTapJSON): string[] {
const srcs: string[] = []
const walk = (node: unknown) => {
if (!node || typeof node !== "object") return
const record = node as TipTapJSON
const attrs = record.attrs as Record<string, unknown> | undefined
if (
(record.type === "image" ||
record.type === "docsGraphic" ||
record.type === "docsInlineGraphic") &&
typeof attrs?.src === "string" &&
attrs.src.startsWith(BASE64_PREFIX)
) {
srcs.push(attrs.src)
}
if (Array.isArray(record.content)) record.content.forEach(walk)
}
walk(content)
return srcs
}
export type UploadedAsset = {
assetId: string
url: string
}
/** Upload a base64 image and return asset reference. Phase 2 blob migration. */
export async function uploadGraphicAsset(
src: string,
uploadFn: (body: { dataUrl: string }) => Promise<UploadedAsset>
): Promise<UploadedAsset | null> {
if (!src.startsWith(BASE64_PREFIX)) return null
try {
return await uploadFn({ dataUrl: src })
} catch {
return null
}
}
/** Replace base64 src with asset URL in TipTap JSON (lazy migration). */
export function applyAssetToContent(
content: TipTapJSON,
src: string,
asset: UploadedAsset
): TipTapJSON {
const walk = (node: unknown): unknown => {
if (!node || typeof node !== "object") return node
if (Array.isArray(node)) return node.map(walk)
const record = node as TipTapJSON
const attrs = record.attrs as Record<string, unknown> | undefined
if (
attrs &&
attrs.src === src &&
(record.type === "image" ||
record.type === "docsGraphic" ||
record.type === "docsInlineGraphic")
) {
return {
...record,
attrs: { ...attrs, src: asset.url, assetId: asset.assetId },
}
}
if (Array.isArray(record.content)) {
return { ...record, content: record.content.map(walk) }
}
return record
}
const next = walk(content)
return (next && typeof next === "object" ? next : content) as TipTapJSON
}
/** Migrate all base64 images in document to backend assets when uploadFn provided. */
export async function migrateBase64ImagesInContent(
content: TipTapJSON,
uploadFn: (body: { dataUrl: string }) => Promise<UploadedAsset>
): Promise<TipTapJSON> {
let result = content
const srcs = [...new Set(collectBase64ImageSrcs(content))]
for (const src of srcs) {
const asset = await uploadGraphicAsset(src, uploadFn)
if (asset) result = applyAssetToContent(result, src, asset)
}
return result
}