/** * Bridge theme CSS and decorative images into PAGE JavaScript context. */ import patchScript from "@/seqta/utils/themeImagePagePatch.js?script&iife"; import { extensionPageScriptUrl } from "@/lib/extensionPageScriptUrl"; import { blobToBase64Data } from "@/plugins/built-in/themes/themeImageUrl"; const PAGE_PATCH_LOADER_ID = "bsplus-theme-image-page-patch-loader"; const BRIDGE_ID = "bsplus-theme-image-bridge"; const PAYLOAD_ID = "bsplus-theme-image-payload"; export type ThemePageSyncInput = { images?: Array<{ variableName: string; blob: Blob }>; customCss?: string; previewCss?: string; clear?: boolean; clearPreview?: boolean; }; export function installThemeImagePagePatch(): void { if (document.getElementById(PAGE_PATCH_LOADER_ID)) return; const script = document.createElement("script"); script.id = PAGE_PATCH_LOADER_ID; script.src = extensionPageScriptUrl(patchScript); script.addEventListener("load", () => script.remove()); (document.documentElement || document.head).appendChild(script); } function ensureBridgeElements(): void { if (!document.getElementById(PAYLOAD_ID)) { const payload = document.createElement("textarea"); payload.id = PAYLOAD_ID; payload.hidden = true; payload.setAttribute("aria-hidden", "true"); payload.tabIndex = -1; document.documentElement.appendChild(payload); } if (!document.getElementById(BRIDGE_ID)) { const bridge = document.createElement("div"); bridge.id = BRIDGE_ID; bridge.hidden = true; bridge.setAttribute("aria-hidden", "true"); document.documentElement.appendChild(bridge); } } function sendPayload(payload: Record): void { installThemeImagePagePatch(); ensureBridgeElements(); const payloadEl = document.getElementById(PAYLOAD_ID) as HTMLTextAreaElement; payloadEl.value = JSON.stringify(payload); const bridge = document.getElementById(BRIDGE_ID)!; bridge.setAttribute("data-rev", String(Number(bridge.getAttribute("data-rev") || "0") + 1)); } export async function syncThemeToPage(input: ThemePageSyncInput): Promise { if (input.clear) { sendPayload({ clear: true }); return; } const payload: Record = {}; if (input.clearPreview) payload.clearPreview = true; if (input.customCss !== undefined) payload.customCss = input.customCss; if (input.previewCss !== undefined) payload.previewCss = input.previewCss; if (input.images !== undefined) { payload.images = await Promise.all( input.images.map(async (image) => ({ variableName: image.variableName, data: await blobToBase64Data(image.blob), mime: image.blob.type || "image/png", })), ); } if (Object.keys(payload).length === 0) return; sendPayload(payload); } export function clearThemeInPage(): void { sendPayload({ clear: true }); }