import * as Y from "yjs"; /** * Collaborative store for HTML embed content, backed by Y.Map("embeds"). * * Each key is a filename (e.g. "d3-scaling-chart.html") and the value is * the raw HTML fragment (not a full document - buildDoc wraps it for display). * * The ProseMirror htmlEmbed node only stores the `src` attribute as a * reference key. The actual HTML lives here so multiple collaborators * can edit chart content concurrently. */ export function createEmbedStore(ydoc: Y.Doc) { const ymap = ydoc.getMap("embeds"); function get(src: string): string { return ymap.get(src) ?? ""; } function set(src: string, html: string) { ymap.set(src, html); } function has(src: string): boolean { return ymap.has(src); } function remove(src: string) { ymap.delete(src); } function rename(oldSrc: string, newSrc: string) { const html = ymap.get(oldSrc); if (html !== undefined) { ydoc.transact(() => { ymap.set(newSrc, html); ymap.delete(oldSrc); }); } } /** Apply a search/replace patch to an existing embed. */ function patch(src: string, search: string, replace: string): boolean { const html = ymap.get(src); if (!html || !html.includes(search)) return false; ymap.set(src, html.replace(search, replace)); return true; } function keys(): string[] { return Array.from(ymap.keys()); } function getAll(): Record { const result: Record = {}; ymap.forEach((val, key) => { result[key] = val; }); return result; } /** * Observe changes to any embed. * Returns an unsubscribe function. */ function observe(callback: () => void) { ymap.observe(callback); return () => ymap.unobserve(callback); } /** * Observe changes to a specific embed key. * Returns an unsubscribe function. */ function observeKey(src: string, callback: (html: string) => void) { const handler = (event: Y.YMapEvent) => { if (event.keysChanged.has(src)) { callback(ymap.get(src) ?? ""); } }; ymap.observe(handler); return () => ymap.unobserve(handler); } return { get, set, has, remove, rename, patch, keys, getAll, observe, observeKey, }; } export type EmbedStore = ReturnType;