@upstart.gg/vite-plugins 0.1.68 → 0.1.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/upstart-editor-api.d.ts +2 -0
- package/dist/upstart-editor-api.d.ts.map +1 -1
- package/dist/upstart-editor-api.js +67 -9
- package/dist/upstart-editor-api.js.map +1 -1
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-attrs.js +18 -1
- package/dist/vite-plugin-upstart-attrs.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/array-controls.js +7 -7
- package/dist/vite-plugin-upstart-editor/runtime/array-controls.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +30 -16
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/dom-guard.js +45 -0
- package/dist/vite-plugin-upstart-editor/runtime/dom-guard.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.d.ts +7 -1
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.js +42 -2
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/form-guard.js +2 -2
- package/dist/vite-plugin-upstart-editor/runtime/form-guard.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/image-pick.js +32 -0
- package/dist/vite-plugin-upstart-editor/runtime/image-pick.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js +9 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/labels.d.ts +32 -0
- package/dist/vite-plugin-upstart-editor/runtime/labels.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/labels.js +45 -0
- package/dist/vite-plugin-upstart-editor/runtime/labels.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +26 -14
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +14 -1
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/tests/dom-guard.test.ts +100 -0
- package/src/tests/editor-labels.test.ts +40 -0
- package/src/tests/image-pick.test.ts +50 -0
- package/src/tests/upstart-editor-api.test.ts +75 -0
- package/src/tests/vite-plugin-upstart-attrs.test.ts +21 -0
- package/src/upstart-editor-api.ts +74 -8
- package/src/vite-plugin-upstart-attrs.ts +44 -1
- package/src/vite-plugin-upstart-editor/runtime/array-controls.ts +7 -7
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +29 -19
- package/src/vite-plugin-upstart-editor/runtime/dom-guard.ts +67 -0
- package/src/vite-plugin-upstart-editor/runtime/error-handler.ts +48 -1
- package/src/vite-plugin-upstart-editor/runtime/form-guard.ts +3 -2
- package/src/vite-plugin-upstart-editor/runtime/image-pick.ts +40 -0
- package/src/vite-plugin-upstart-editor/runtime/index.ts +11 -0
- package/src/vite-plugin-upstart-editor/runtime/labels.ts +77 -0
- package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +39 -15
- package/src/vite-plugin-upstart-editor/runtime/types.ts +12 -1
|
@@ -2,7 +2,9 @@ import { clearSelection, getCurrentMode, getSelection, removeSelection, setCurre
|
|
|
2
2
|
import { sendToParent } from "./utils.js";
|
|
3
3
|
import { initClickHandler } from "./click-handler.js";
|
|
4
4
|
import { hideOverlays, initHoverOverlay } from "./hover-overlay.js";
|
|
5
|
+
import { setLabels } from "./labels.js";
|
|
5
6
|
import { initErrorHandler } from "./error-handler.js";
|
|
7
|
+
import { installDomGuard } from "./dom-guard.js";
|
|
6
8
|
import { activateAllEditors, destroyAllActiveEditors, initTextEditor } from "./text-editor.js";
|
|
7
9
|
import { hideArrayControls, initArrayControls, refreshArrayControls } from "./array-controls.js";
|
|
8
10
|
import { initFormGuard } from "./form-guard.js";
|
|
@@ -120,6 +122,11 @@ function initUpstartEditor() {
|
|
|
120
122
|
};
|
|
121
123
|
const i18next = globalThis.__i18next;
|
|
122
124
|
const getRawI18nTemplate = i18next ? (namespace, key) => i18next.getResource(i18next.language, namespace, key) : void 0;
|
|
125
|
+
document.documentElement.setAttribute("data-upstart-editor", "");
|
|
126
|
+
installDomGuard(Node.prototype, () => sendToParent({
|
|
127
|
+
type: "editor-desync",
|
|
128
|
+
fatal: false
|
|
129
|
+
}));
|
|
123
130
|
initTextEditor(getRawI18nTemplate ? { getRawI18nTemplate } : {});
|
|
124
131
|
initClickHandler();
|
|
125
132
|
initHoverOverlay();
|
|
@@ -163,6 +170,7 @@ function handleParentMessage(event) {
|
|
|
163
170
|
} else if (message.type === "preview-classname") {
|
|
164
171
|
const el = document.querySelector(`[data-upstart-classname-id="${message.classNameId}"]`);
|
|
165
172
|
if (el) el.className = message.className;
|
|
173
|
+
if (message.previewCSS === void 0) return;
|
|
166
174
|
const STYLE_ID = "upstart-preview-style";
|
|
167
175
|
let styleEl = document.getElementById(STYLE_ID);
|
|
168
176
|
if (message.previewCSS) {
|
|
@@ -210,7 +218,7 @@ function handleParentMessage(event) {
|
|
|
210
218
|
type: "selection-changed",
|
|
211
219
|
selection: getSelection()
|
|
212
220
|
});
|
|
213
|
-
}
|
|
221
|
+
} else if (message.type === "set-labels") setLabels(message.labels);
|
|
214
222
|
}
|
|
215
223
|
/**
|
|
216
224
|
* Drop the whole "select" mode selection: clear the in-memory set and remove the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/index.ts"],"sourcesContent":["import { initClickHandler } from \"./click-handler.js\";\nimport { initHoverOverlay, hideOverlays } from \"./hover-overlay.js\";\nimport { initErrorHandler } from \"./error-handler.js\";\nimport { initTextEditor, activateAllEditors, destroyAllActiveEditors } from \"./text-editor.js\";\nimport { initArrayControls, refreshArrayControls, hideArrayControls } from \"./array-controls.js\";\nimport { initFormGuard } from \"./form-guard.js\";\nimport { sendToParent } from \"./utils.js\";\nimport { getCurrentMode, setCurrentMode, clearSelection, removeSelection, getSelection } from \"./state.js\";\nimport type { EditorMode, UpstartParentMessage } from \"./types.js\";\n\nlet isInitialized = false;\n\nexport { getCurrentMode };\n\n/**\n * Set the current editor mode.\n */\nexport function setMode(mode: EditorMode): void {\n setCurrentMode(mode);\n console.log(`[Upstart Editor] Setting mode to: ${mode}`);\n if (mode === \"edit\") {\n disableSelectMode();\n enableEditMode();\n } else if (mode === \"select\") {\n disableEditMode();\n enableSelectMode();\n } else {\n disableEditMode();\n disableSelectMode();\n }\n}\n\nexport function waitForHydration(callback: () => void): void {\n // Remix/React 18 wraps hydrateRoot in startTransition, making hydration a\n // concurrent (low-priority) operation that can span many frames after the\n // load event. Instead of guessing a delay, we wait for the DOM to stabilise:\n // once 200 ms pass without any childList mutations, hydration is done.\n const STABILITY_MS = 200;\n\n const onReady = () => {\n let timer: number | null = null;\n\n const settle = () => {\n if (timer) clearTimeout(timer);\n timer = window.setTimeout(() => {\n observer.disconnect();\n callback();\n }, STABILITY_MS);\n };\n\n const observer = new MutationObserver(settle);\n observer.observe(document.documentElement, { childList: true, subtree: true });\n\n // Kick off the first timer (covers case where no mutations occur after load)\n settle();\n };\n\n if (document.readyState === \"complete\") {\n onReady();\n } else {\n window.addEventListener(\"load\", onReady, { once: true });\n }\n}\n\n/**\n * Report the react-router id of the rendered route to the parent editor, so it can edit\n * that route's `meta` export (page title, description…).\n *\n * The id is read from react-router's own data router rather than from `history.pushState`:\n * the router pushes the new URL BEFORE it commits the new matches, so a route id read at\n * pushState time would still be the previous page's. Subscribing gives us the id once the\n * navigation is complete. The router global only appears during hydration, hence the retry.\n */\nfunction initRouteReporter(): void {\n let lastReported: string | undefined;\n\n const report = () => {\n // biome-ignore lint/suspicious/noExplicitAny: react-router does not type its window globals\n const matches = (window as any).__reactRouterDataRouter?.state?.matches;\n const routeId = Array.isArray(matches) ? matches[matches.length - 1]?.route?.id : undefined;\n if (typeof routeId !== \"string\" || routeId === lastReported) return;\n lastReported = routeId;\n sendToParent({ type: \"editor-route\", routeId });\n };\n\n const attach = (): boolean => {\n // biome-ignore lint/suspicious/noExplicitAny: react-router does not type its window globals\n const router = (window as any).__reactRouterDataRouter;\n if (typeof router?.subscribe !== \"function\") return false;\n router.subscribe(() => report());\n report();\n return true;\n };\n\n if (attach()) return;\n let attempts = 0;\n const timer = setInterval(() => {\n if (attach() || ++attempts > 25) clearInterval(timer);\n }, 200);\n}\n\n/**\n * Initialize the Upstart editor runtime.\n */\nexport function initUpstartEditor(): void {\n if (isInitialized) {\n console.log(\"[Upstart Editor] Editor is already initialized\");\n return;\n }\n\n try {\n console.log(\"[Upstart Editor] Initializing...\");\n\n isInitialized = true;\n\n window.addEventListener(\"message\", handleParentMessage);\n\n // Current path (pathname + search), reported to the parent so it can reload\n // the iframe on the page being viewed instead of always the home page.\n const currentPath = () => location.pathname + location.search;\n\n // Notify parent on SPA navigation so it can resend the current editMode\n // and track the current page for the next reload.\n window.addEventListener(\"popstate\", () => {\n resetSelection();\n sendToParent({ type: \"editor-navigated\", path: currentPath() });\n });\n const originalPushState = history.pushState.bind(history);\n history.pushState = (...args) => {\n originalPushState(...args);\n resetSelection();\n sendToParent({ type: \"editor-navigated\", path: currentPath() });\n };\n const originalReplaceState = history.replaceState.bind(history);\n history.replaceState = (...args) => {\n originalReplaceState(...args);\n resetSelection();\n sendToParent({ type: \"editor-navigated\", path: currentPath() });\n };\n\n // i18next integration: if the app exposes it on window.__i18next (set before hydration),\n // wire it up so template variables (e.g. {{year}}) render as non-editable atoms.\n const i18next = (globalThis as any).__i18next;\n const getRawI18nTemplate = i18next\n ? (namespace: string, key: string) =>\n i18next.getResource(i18next.language, namespace, key) as string | undefined\n : undefined;\n\n initTextEditor(getRawI18nTemplate ? { getRawI18nTemplate } : {});\n initClickHandler();\n initHoverOverlay();\n initArrayControls();\n initFormGuard();\n initErrorHandler();\n initRouteReporter();\n\n sendToParent({ type: \"editor-ready\", path: currentPath() });\n } catch (error) {\n console.error(\"[Upstart Editor] Initialization failed:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\nconst ALLOWED_ORIGINS = [\"http://localhost:8080\", /upstart.gg$/];\n\nconst matchAllowedOrigins = (origin: string) => {\n return ALLOWED_ORIGINS.some((allowedOrigin) => {\n if (typeof allowedOrigin === \"string\") {\n return origin === allowedOrigin;\n } else if (allowedOrigin instanceof RegExp) {\n return allowedOrigin.test(origin);\n }\n return false;\n });\n};\n\nfunction handleParentMessage(event: MessageEvent): void {\n const message = event.data as UpstartParentMessage | undefined;\n\n console.log(\"[Upstart Editor] Received message from parent:\", { event, message });\n\n if (!message || !matchAllowedOrigins(event.origin)) {\n console.warn(\"[Upstart Editor] Ignoring message from unknown source:\", event.origin);\n return;\n }\n\n if (message.type === \"set-mode\") {\n console.log(\"Setting editor mode to:\", message.mode);\n setMode(message.mode);\n } else if (message.type === \"preview-classname\") {\n const el = document.querySelector<HTMLElement>(`[data-upstart-classname-id=\"${message.classNameId}\"]`);\n if (el) {\n el.className = message.className;\n }\n const STYLE_ID = \"upstart-preview-style\";\n let styleEl = document.getElementById(STYLE_ID) as HTMLStyleElement | null;\n if (message.previewCSS) {\n if (!styleEl) {\n styleEl = document.createElement(\"style\");\n styleEl.id = STYLE_ID;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = message.previewCSS;\n } else if (styleEl) {\n styleEl.textContent = \"\";\n }\n } else if (message.type === \"preview-image\") {\n const el = document.querySelector<HTMLImageElement>(`[data-upstart-image-id=\"${message.imageId}\"]`);\n if (el) {\n el.src = message.src;\n }\n } else if (message.type === \"preview-image-style\") {\n // One <style> per image so previewing an image and its container at the same\n // time never overwrite each other (the className preview uses its own element).\n const styleId = `upstart-preview-image-style-${String(message.imageId).replace(/[^a-zA-Z0-9_-]/g, \"_\")}`;\n let styleEl = document.getElementById(styleId) as HTMLStyleElement | null;\n const css = typeof message.css === \"string\" ? message.css : \"\";\n if (css) {\n if (!styleEl) {\n styleEl = document.createElement(\"style\");\n styleEl.id = styleId;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = css;\n } else if (styleEl) {\n styleEl.remove();\n }\n } else if (message.type === \"request-scroll-position\") {\n sendToParent({ type: \"scroll-position\", x: window.scrollX, y: window.scrollY });\n } else if (message.type === \"restore-scroll-position\") {\n window.scrollTo({ left: message.x, top: message.y, behavior: \"auto\" });\n } else if (message.type === \"clear-selection\") {\n resetSelection();\n // Echo the now-empty selection so the parent store (chips / counter) updates.\n sendToParent({ type: \"selection-changed\", selection: getSelection() });\n } else if (message.type === \"deselect\") {\n const el = removeSelection(message.key);\n el?.removeAttribute(\"data-upstart-selected\");\n sendToParent({ type: \"selection-changed\", selection: getSelection() });\n }\n}\n\n/**\n * Drop the whole \"select\" mode selection: clear the in-memory set and remove the\n * visual marker from any element still carrying it.\n */\nfunction resetSelection(): void {\n clearSelection();\n for (const el of document.querySelectorAll(\"[data-upstart-selected]\")) {\n el.removeAttribute(\"data-upstart-selected\");\n }\n}\n\nfunction enableEditMode(): void {\n console.log(\"[Upstart Editor] Edit mode enabled\");\n document.documentElement.setAttribute(\"data-upstart-edit-mode\", \"\");\n activateAllEditors();\n refreshArrayControls();\n}\n\nfunction disableEditMode(): void {\n console.log(\"[Upstart Editor] Preview mode enabled\");\n document.documentElement.removeAttribute(\"data-upstart-edit-mode\");\n destroyAllActiveEditors();\n hideOverlays();\n hideArrayControls();\n}\n\nfunction enableSelectMode(): void {\n console.log(\"[Upstart Editor] Select mode enabled\");\n // Selectable hover (hover-overlay) and the persistent selected outline are\n // gated on this attribute. The selection Map / data-upstart-selected attrs are\n // kept across mode switches, so toggling back to select restores the outlines.\n document.documentElement.setAttribute(\"data-upstart-select-mode\", \"\");\n}\n\nfunction disableSelectMode(): void {\n document.documentElement.removeAttribute(\"data-upstart-select-mode\");\n hideOverlays();\n}\n\nexport { initTextEditor } from \"./text-editor.js\";\nexport { initClickHandler } from \"./click-handler.js\";\nexport { initErrorHandler } from \"./error-handler.js\";\nexport { initHoverOverlay } from \"./hover-overlay.js\";\nexport { sendToParent } from \"./utils.js\";\nexport type { EditorMessage, UpstartEditorMessage } from \"./types.js\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/index.ts"],"sourcesContent":["import { initClickHandler } from \"./click-handler.js\";\nimport { initHoverOverlay, hideOverlays } from \"./hover-overlay.js\";\nimport { initErrorHandler } from \"./error-handler.js\";\nimport { installDomGuard } from \"./dom-guard.js\";\nimport { initTextEditor, activateAllEditors, destroyAllActiveEditors } from \"./text-editor.js\";\nimport { initArrayControls, refreshArrayControls, hideArrayControls } from \"./array-controls.js\";\nimport { initFormGuard } from \"./form-guard.js\";\nimport { sendToParent } from \"./utils.js\";\nimport { getCurrentMode, setCurrentMode, clearSelection, removeSelection, getSelection } from \"./state.js\";\nimport { setLabels } from \"./labels.js\";\nimport type { EditorMode, UpstartParentMessage } from \"./types.js\";\n\nlet isInitialized = false;\n\nexport { getCurrentMode };\n\n/**\n * Set the current editor mode.\n */\nexport function setMode(mode: EditorMode): void {\n setCurrentMode(mode);\n console.log(`[Upstart Editor] Setting mode to: ${mode}`);\n if (mode === \"edit\") {\n disableSelectMode();\n enableEditMode();\n } else if (mode === \"select\") {\n disableEditMode();\n enableSelectMode();\n } else {\n disableEditMode();\n disableSelectMode();\n }\n}\n\nexport function waitForHydration(callback: () => void): void {\n // Remix/React 18 wraps hydrateRoot in startTransition, making hydration a\n // concurrent (low-priority) operation that can span many frames after the\n // load event. Instead of guessing a delay, we wait for the DOM to stabilise:\n // once 200 ms pass without any childList mutations, hydration is done.\n const STABILITY_MS = 200;\n\n const onReady = () => {\n let timer: number | null = null;\n\n const settle = () => {\n if (timer) clearTimeout(timer);\n timer = window.setTimeout(() => {\n observer.disconnect();\n callback();\n }, STABILITY_MS);\n };\n\n const observer = new MutationObserver(settle);\n observer.observe(document.documentElement, { childList: true, subtree: true });\n\n // Kick off the first timer (covers case where no mutations occur after load)\n settle();\n };\n\n if (document.readyState === \"complete\") {\n onReady();\n } else {\n window.addEventListener(\"load\", onReady, { once: true });\n }\n}\n\n/**\n * Report the react-router id of the rendered route to the parent editor, so it can edit\n * that route's `meta` export (page title, description…).\n *\n * The id is read from react-router's own data router rather than from `history.pushState`:\n * the router pushes the new URL BEFORE it commits the new matches, so a route id read at\n * pushState time would still be the previous page's. Subscribing gives us the id once the\n * navigation is complete. The router global only appears during hydration, hence the retry.\n */\nfunction initRouteReporter(): void {\n let lastReported: string | undefined;\n\n const report = () => {\n // biome-ignore lint/suspicious/noExplicitAny: react-router does not type its window globals\n const matches = (window as any).__reactRouterDataRouter?.state?.matches;\n const routeId = Array.isArray(matches) ? matches[matches.length - 1]?.route?.id : undefined;\n if (typeof routeId !== \"string\" || routeId === lastReported) return;\n lastReported = routeId;\n sendToParent({ type: \"editor-route\", routeId });\n };\n\n const attach = (): boolean => {\n // biome-ignore lint/suspicious/noExplicitAny: react-router does not type its window globals\n const router = (window as any).__reactRouterDataRouter;\n if (typeof router?.subscribe !== \"function\") return false;\n router.subscribe(() => report());\n report();\n return true;\n };\n\n if (attach()) return;\n let attempts = 0;\n const timer = setInterval(() => {\n if (attach() || ++attempts > 25) clearInterval(timer);\n }, 200);\n}\n\n/**\n * Initialize the Upstart editor runtime.\n */\nexport function initUpstartEditor(): void {\n if (isInitialized) {\n console.log(\"[Upstart Editor] Editor is already initialized\");\n return;\n }\n\n try {\n console.log(\"[Upstart Editor] Initializing...\");\n\n isInitialized = true;\n\n window.addEventListener(\"message\", handleParentMessage);\n\n // Current path (pathname + search), reported to the parent so it can reload\n // the iframe on the page being viewed instead of always the home page.\n const currentPath = () => location.pathname + location.search;\n\n // Notify parent on SPA navigation so it can resend the current editMode\n // and track the current page for the next reload.\n window.addEventListener(\"popstate\", () => {\n resetSelection();\n sendToParent({ type: \"editor-navigated\", path: currentPath() });\n });\n const originalPushState = history.pushState.bind(history);\n history.pushState = (...args) => {\n originalPushState(...args);\n resetSelection();\n sendToParent({ type: \"editor-navigated\", path: currentPath() });\n };\n const originalReplaceState = history.replaceState.bind(history);\n history.replaceState = (...args) => {\n originalReplaceState(...args);\n resetSelection();\n sendToParent({ type: \"editor-navigated\", path: currentPath() });\n };\n\n // i18next integration: if the app exposes it on window.__i18next (set before hydration),\n // wire it up so template variables (e.g. {{year}}) render as non-editable atoms.\n const i18next = (globalThis as any).__i18next;\n const getRawI18nTemplate = i18next\n ? (namespace: string, key: string) =>\n i18next.getResource(i18next.language, namespace, key) as string | undefined\n : undefined;\n\n // Marks the document as running inside the editor preview (the site's error boundary\n // uses it to offer a refresh instead of a raw error).\n document.documentElement.setAttribute(\"data-upstart-editor\", \"\");\n installDomGuard(Node.prototype, () => sendToParent({ type: \"editor-desync\", fatal: false }));\n initTextEditor(getRawI18nTemplate ? { getRawI18nTemplate } : {});\n initClickHandler();\n initHoverOverlay();\n initArrayControls();\n initFormGuard();\n initErrorHandler();\n initRouteReporter();\n\n sendToParent({ type: \"editor-ready\", path: currentPath() });\n } catch (error) {\n console.error(\"[Upstart Editor] Initialization failed:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\nconst ALLOWED_ORIGINS = [\"http://localhost:8080\", /upstart.gg$/];\n\nconst matchAllowedOrigins = (origin: string) => {\n return ALLOWED_ORIGINS.some((allowedOrigin) => {\n if (typeof allowedOrigin === \"string\") {\n return origin === allowedOrigin;\n } else if (allowedOrigin instanceof RegExp) {\n return allowedOrigin.test(origin);\n }\n return false;\n });\n};\n\nfunction handleParentMessage(event: MessageEvent): void {\n const message = event.data as UpstartParentMessage | undefined;\n\n console.log(\"[Upstart Editor] Received message from parent:\", { event, message });\n\n if (!message || !matchAllowedOrigins(event.origin)) {\n console.warn(\"[Upstart Editor] Ignoring message from unknown source:\", event.origin);\n return;\n }\n\n if (message.type === \"set-mode\") {\n console.log(\"Setting editor mode to:\", message.mode);\n setMode(message.mode);\n } else if (message.type === \"preview-classname\") {\n const el = document.querySelector<HTMLElement>(`[data-upstart-classname-id=\"${message.classNameId}\"]`);\n if (el) {\n el.className = message.className;\n }\n // No `previewCSS` at all means \"patch the className only\": the editor uses it after a save\n // to sync the DOM with the file without touching the current CSS preview.\n if (message.previewCSS === undefined) return;\n const STYLE_ID = \"upstart-preview-style\";\n let styleEl = document.getElementById(STYLE_ID) as HTMLStyleElement | null;\n if (message.previewCSS) {\n if (!styleEl) {\n styleEl = document.createElement(\"style\");\n styleEl.id = STYLE_ID;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = message.previewCSS;\n } else if (styleEl) {\n styleEl.textContent = \"\";\n }\n } else if (message.type === \"preview-image\") {\n const el = document.querySelector<HTMLImageElement>(`[data-upstart-image-id=\"${message.imageId}\"]`);\n if (el) {\n el.src = message.src;\n }\n } else if (message.type === \"preview-image-style\") {\n // One <style> per image so previewing an image and its container at the same\n // time never overwrite each other (the className preview uses its own element).\n const styleId = `upstart-preview-image-style-${String(message.imageId).replace(/[^a-zA-Z0-9_-]/g, \"_\")}`;\n let styleEl = document.getElementById(styleId) as HTMLStyleElement | null;\n const css = typeof message.css === \"string\" ? message.css : \"\";\n if (css) {\n if (!styleEl) {\n styleEl = document.createElement(\"style\");\n styleEl.id = styleId;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = css;\n } else if (styleEl) {\n styleEl.remove();\n }\n } else if (message.type === \"request-scroll-position\") {\n sendToParent({ type: \"scroll-position\", x: window.scrollX, y: window.scrollY });\n } else if (message.type === \"restore-scroll-position\") {\n window.scrollTo({ left: message.x, top: message.y, behavior: \"auto\" });\n } else if (message.type === \"clear-selection\") {\n resetSelection();\n // Echo the now-empty selection so the parent store (chips / counter) updates.\n sendToParent({ type: \"selection-changed\", selection: getSelection() });\n } else if (message.type === \"deselect\") {\n const el = removeSelection(message.key);\n el?.removeAttribute(\"data-upstart-selected\");\n sendToParent({ type: \"selection-changed\", selection: getSelection() });\n } else if (message.type === \"set-labels\") {\n setLabels(message.labels);\n }\n}\n\n/**\n * Drop the whole \"select\" mode selection: clear the in-memory set and remove the\n * visual marker from any element still carrying it.\n */\nfunction resetSelection(): void {\n clearSelection();\n for (const el of document.querySelectorAll(\"[data-upstart-selected]\")) {\n el.removeAttribute(\"data-upstart-selected\");\n }\n}\n\nfunction enableEditMode(): void {\n console.log(\"[Upstart Editor] Edit mode enabled\");\n document.documentElement.setAttribute(\"data-upstart-edit-mode\", \"\");\n activateAllEditors();\n refreshArrayControls();\n}\n\nfunction disableEditMode(): void {\n console.log(\"[Upstart Editor] Preview mode enabled\");\n document.documentElement.removeAttribute(\"data-upstart-edit-mode\");\n destroyAllActiveEditors();\n hideOverlays();\n hideArrayControls();\n}\n\nfunction enableSelectMode(): void {\n console.log(\"[Upstart Editor] Select mode enabled\");\n // Selectable hover (hover-overlay) and the persistent selected outline are\n // gated on this attribute. The selection Map / data-upstart-selected attrs are\n // kept across mode switches, so toggling back to select restores the outlines.\n document.documentElement.setAttribute(\"data-upstart-select-mode\", \"\");\n}\n\nfunction disableSelectMode(): void {\n document.documentElement.removeAttribute(\"data-upstart-select-mode\");\n hideOverlays();\n}\n\nexport { initTextEditor } from \"./text-editor.js\";\nexport { initClickHandler } from \"./click-handler.js\";\nexport { initErrorHandler } from \"./error-handler.js\";\nexport { initHoverOverlay } from \"./hover-overlay.js\";\nexport { sendToParent } from \"./utils.js\";\nexport type { EditorMessage, UpstartEditorMessage } from \"./types.js\";\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/vite-plugin-upstart-editor/runtime/labels.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* User-facing labels of the editor runtime. The runtime runs inside the previewed site,
|
|
4
|
+
* which has no access to the dashboard's i18n catalogs: the editor sends the translated
|
|
5
|
+
* labels through a `set-labels` message once the runtime is ready, and everything the
|
|
6
|
+
* runtime renders reads them lazily via {@link getLabels}. English is the fallback.
|
|
7
|
+
*/
|
|
8
|
+
interface RuntimeLabels {
|
|
9
|
+
newItem: string;
|
|
10
|
+
keepItem: string;
|
|
11
|
+
removeItem: string;
|
|
12
|
+
addItem: string;
|
|
13
|
+
applyChanges: string;
|
|
14
|
+
dragToReorder: string;
|
|
15
|
+
clickToEdit: string;
|
|
16
|
+
startTyping: string;
|
|
17
|
+
recoveryTitle: string;
|
|
18
|
+
recoveryText: string;
|
|
19
|
+
refresh: string;
|
|
20
|
+
formGuard: string;
|
|
21
|
+
bold: string;
|
|
22
|
+
italic: string;
|
|
23
|
+
strikethrough: string;
|
|
24
|
+
inlineCode: string;
|
|
25
|
+
heading: string;
|
|
26
|
+
blockquote: string;
|
|
27
|
+
bulletList: string;
|
|
28
|
+
orderedList: string;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { RuntimeLabels };
|
|
32
|
+
//# sourceMappingURL=labels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labels.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/labels.ts"],"mappings":";;AAMA;;;;;UAAiB,aAAA;EACf,OAAA;EACA,QAAA;EACA,UAAA;EACA,OAAA;EACA,YAAA;EACA,aAAA;EACA,WAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,OAAA;EACA,SAAA;EACA,IAAA;EACA,MAAA;EACA,aAAA;EACA,UAAA;EACA,OAAA;EACA,UAAA;EACA,UAAA;EACA,WAAA;AAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/vite-plugin-upstart-editor/runtime/labels.ts
|
|
2
|
+
const DEFAULT_LABELS = Object.freeze({
|
|
3
|
+
newItem: "New item",
|
|
4
|
+
keepItem: "Keep item",
|
|
5
|
+
removeItem: "Remove item",
|
|
6
|
+
addItem: "Add item",
|
|
7
|
+
applyChanges: "Apply changes",
|
|
8
|
+
dragToReorder: "Drag to reorder",
|
|
9
|
+
clickToEdit: "Click to edit...",
|
|
10
|
+
startTyping: "Start typing...",
|
|
11
|
+
recoveryTitle: "The preview needs a refresh",
|
|
12
|
+
recoveryText: "Your changes are saved. Refresh the preview to keep editing.",
|
|
13
|
+
refresh: "Refresh",
|
|
14
|
+
formGuard: "This action isn’t available in the editor preview",
|
|
15
|
+
bold: "Bold",
|
|
16
|
+
italic: "Italic",
|
|
17
|
+
strikethrough: "Strikethrough",
|
|
18
|
+
inlineCode: "Inline Code",
|
|
19
|
+
heading: "Heading",
|
|
20
|
+
blockquote: "Blockquote",
|
|
21
|
+
bulletList: "Bullet List",
|
|
22
|
+
orderedList: "Ordered List"
|
|
23
|
+
});
|
|
24
|
+
let labels = { ...DEFAULT_LABELS };
|
|
25
|
+
/** Current labels (translated once the editor sent them, English before that). */
|
|
26
|
+
function getLabels() {
|
|
27
|
+
return labels;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Override labels with the ones received from the editor. Unknown keys and non-string
|
|
31
|
+
* values are ignored so a malformed message can never blank a control.
|
|
32
|
+
*/
|
|
33
|
+
function setLabels(input) {
|
|
34
|
+
if (typeof input !== "object" || input === null) return;
|
|
35
|
+
const next = { ...labels };
|
|
36
|
+
for (const key of Object.keys(DEFAULT_LABELS)) {
|
|
37
|
+
const value = input[key];
|
|
38
|
+
if (typeof value === "string" && value.length > 0) next[key] = value;
|
|
39
|
+
}
|
|
40
|
+
labels = next;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { getLabels, setLabels };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labels.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/labels.ts"],"sourcesContent":["/**\n * User-facing labels of the editor runtime. The runtime runs inside the previewed site,\n * which has no access to the dashboard's i18n catalogs: the editor sends the translated\n * labels through a `set-labels` message once the runtime is ready, and everything the\n * runtime renders reads them lazily via {@link getLabels}. English is the fallback.\n */\nexport interface RuntimeLabels {\n newItem: string;\n keepItem: string;\n removeItem: string;\n addItem: string;\n applyChanges: string;\n dragToReorder: string;\n clickToEdit: string;\n startTyping: string;\n recoveryTitle: string;\n recoveryText: string;\n refresh: string;\n formGuard: string;\n bold: string;\n italic: string;\n strikethrough: string;\n inlineCode: string;\n heading: string;\n blockquote: string;\n bulletList: string;\n orderedList: string;\n}\n\nexport const DEFAULT_LABELS: Readonly<RuntimeLabels> = Object.freeze({\n newItem: \"New item\",\n keepItem: \"Keep item\",\n removeItem: \"Remove item\",\n addItem: \"Add item\",\n applyChanges: \"Apply changes\",\n dragToReorder: \"Drag to reorder\",\n clickToEdit: \"Click to edit...\",\n startTyping: \"Start typing...\",\n recoveryTitle: \"The preview needs a refresh\",\n recoveryText: \"Your changes are saved. Refresh the preview to keep editing.\",\n refresh: \"Refresh\",\n formGuard: \"This action isn’t available in the editor preview\",\n bold: \"Bold\",\n italic: \"Italic\",\n strikethrough: \"Strikethrough\",\n inlineCode: \"Inline Code\",\n heading: \"Heading\",\n blockquote: \"Blockquote\",\n bulletList: \"Bullet List\",\n orderedList: \"Ordered List\",\n});\n\nlet labels: RuntimeLabels = { ...DEFAULT_LABELS };\n\n/** Current labels (translated once the editor sent them, English before that). */\nexport function getLabels(): Readonly<RuntimeLabels> {\n return labels;\n}\n\n/**\n * Override labels with the ones received from the editor. Unknown keys and non-string\n * values are ignored so a malformed message can never blank a control.\n */\nexport function setLabels(input: unknown): void {\n if (typeof input !== \"object\" || input === null) return;\n const next: RuntimeLabels = { ...labels };\n for (const key of Object.keys(DEFAULT_LABELS) as (keyof RuntimeLabels)[]) {\n const value = (input as Record<string, unknown>)[key];\n if (typeof value === \"string\" && value.length > 0) next[key] = value;\n }\n labels = next;\n}\n\n/** Back to the English defaults (used by tests). */\nexport function resetLabels(): void {\n labels = { ...DEFAULT_LABELS };\n}\n"],"mappings":";AA6BA,MAAa,iBAA0C,OAAO,OAAO;CACnE,SAAS;CACT,UAAU;CACV,YAAY;CACZ,SAAS;CACT,cAAc;CACd,eAAe;CACf,aAAa;CACb,aAAa;CACb,eAAe;CACf,cAAc;CACd,SAAS;CACT,WAAW;CACX,MAAM;CACN,QAAQ;CACR,eAAe;CACf,YAAY;CACZ,SAAS;CACT,YAAY;CACZ,YAAY;CACZ,aAAa;CACd,CAAC;AAEF,IAAI,SAAwB,EAAE,GAAG,gBAAgB;;AAGjD,SAAgB,YAAqC;CACnD,OAAO;;;;;;AAOT,SAAgB,UAAU,OAAsB;CAC9C,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;CACjD,MAAM,OAAsB,EAAE,GAAG,QAAQ;CACzC,KAAK,MAAM,OAAO,OAAO,KAAK,eAAe,EAA6B;EACxE,MAAM,QAAS,MAAkC;EACjD,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG,KAAK,OAAO;;CAEjE,SAAS"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-editor.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/text-editor.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"text-editor.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/text-editor.ts"],"mappings":";;;;;AAuQA;;iBAAgB,cAAA,CAAe,OAAA,GAAS,oBAAA;;;AAyDxC;iBAAgB,kBAAA,CAAA;;;;iBAgBA,uBAAA,CAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sendToParent } from "./utils.js";
|
|
2
|
+
import { getLabels } from "./labels.js";
|
|
2
3
|
import { Editor, Extension, Node } from "@tiptap/core";
|
|
3
4
|
import { BubbleMenu } from "@tiptap/extension-bubble-menu";
|
|
4
5
|
import Placeholder from "@tiptap/extension-placeholder";
|
|
@@ -250,6 +251,10 @@ const activeEditors = /* @__PURE__ */ new Map();
|
|
|
250
251
|
let i18nSyncInProgress = false;
|
|
251
252
|
const styleCache = /* @__PURE__ */ new WeakMap();
|
|
252
253
|
let resolvedOptions = DEFAULT_OPTIONS;
|
|
254
|
+
/** The site may override the placeholder; the default one follows the editor's language. */
|
|
255
|
+
function placeholderFor(options) {
|
|
256
|
+
return options.placeholder === DEFAULT_OPTIONS.placeholder ? getLabels().startTyping : options.placeholder;
|
|
257
|
+
}
|
|
253
258
|
let shortcutsRegistered = false;
|
|
254
259
|
let domObserver = null;
|
|
255
260
|
let reactivateTimer = null;
|
|
@@ -323,9 +328,15 @@ function destroyAllActiveEditors() {
|
|
|
323
328
|
stopDomObserver();
|
|
324
329
|
for (const element of activeEditors.keys()) destroyEditor(element);
|
|
325
330
|
}
|
|
331
|
+
/** Descendants the plain editor cannot reproduce: taking over the element would destroy them. */
|
|
332
|
+
const PLAIN_EDITOR_FOREIGN_CHILDREN = "svg, img, picture, video, iframe, canvas";
|
|
326
333
|
function setupEditableElement(element, options) {
|
|
327
334
|
const hash = getEditableHash(element);
|
|
328
335
|
if (!hash || activeEditors.has(element)) return;
|
|
336
|
+
if (getEditorMode(element, options) === "plain" && element.querySelector(PLAIN_EDITOR_FOREIGN_CHILDREN)) {
|
|
337
|
+
console.warn("[Upstart Editor] Skipping text editor on an element that also holds media/icons:", element.dataset.upstartHash, element);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
329
340
|
cacheStyles(element);
|
|
330
341
|
activateEditor(element, hash, options);
|
|
331
342
|
}
|
|
@@ -399,7 +410,7 @@ function createPlainTextEditor(element, options) {
|
|
|
399
410
|
codeBlock: false,
|
|
400
411
|
horizontalRule: false
|
|
401
412
|
}),
|
|
402
|
-
Placeholder.configure({ placeholder:
|
|
413
|
+
Placeholder.configure({ placeholder: getLabels().clickToEdit })
|
|
403
414
|
],
|
|
404
415
|
content,
|
|
405
416
|
editorProps: { attributes: { class: "upstart-editor-active" } },
|
|
@@ -420,7 +431,7 @@ function createRichTextEditor(element, options) {
|
|
|
420
431
|
element.innerHTML = "";
|
|
421
432
|
let hasChanged = false;
|
|
422
433
|
const bubbleMenuElement = createBubbleMenuElement();
|
|
423
|
-
const extensions = [StarterKit, Placeholder.configure({ placeholder: options
|
|
434
|
+
const extensions = [StarterKit, Placeholder.configure({ placeholder: placeholderFor(options) })];
|
|
424
435
|
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
425
436
|
const editor = new Editor({
|
|
426
437
|
element,
|
|
@@ -459,7 +470,7 @@ function createInlineRichTextEditor(element, options) {
|
|
|
459
470
|
codeBlock: false,
|
|
460
471
|
horizontalRule: false
|
|
461
472
|
}),
|
|
462
|
-
Placeholder.configure({ placeholder:
|
|
473
|
+
Placeholder.configure({ placeholder: getLabels().clickToEdit })
|
|
463
474
|
];
|
|
464
475
|
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
465
476
|
const editor = new Editor({
|
|
@@ -497,7 +508,7 @@ function createDirectEditor(element, options) {
|
|
|
497
508
|
codeBlock: false,
|
|
498
509
|
horizontalRule: false
|
|
499
510
|
}),
|
|
500
|
-
Placeholder.configure({ placeholder:
|
|
511
|
+
Placeholder.configure({ placeholder: getLabels().clickToEdit })
|
|
501
512
|
];
|
|
502
513
|
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
503
514
|
const editor = new Editor({
|
|
@@ -522,7 +533,7 @@ function createRichPanelEditor(element, options) {
|
|
|
522
533
|
element.innerHTML = "";
|
|
523
534
|
let hasChanged = false;
|
|
524
535
|
const bubbleMenuElement = createBubbleMenuElement("inline");
|
|
525
|
-
const extensions = [StarterKit, Placeholder.configure({ placeholder: options
|
|
536
|
+
const extensions = [StarterKit, Placeholder.configure({ placeholder: placeholderFor(options) })];
|
|
526
537
|
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
527
538
|
const editor = new Editor({
|
|
528
539
|
element,
|
|
@@ -739,41 +750,41 @@ const BUBBLE_MENU_GROUPS = [
|
|
|
739
750
|
{
|
|
740
751
|
command: "bold",
|
|
741
752
|
icon: "format-bold",
|
|
742
|
-
|
|
753
|
+
label: "bold"
|
|
743
754
|
},
|
|
744
755
|
{
|
|
745
756
|
command: "italic",
|
|
746
757
|
icon: "format-italic",
|
|
747
|
-
|
|
758
|
+
label: "italic"
|
|
748
759
|
},
|
|
749
760
|
{
|
|
750
761
|
command: "strike",
|
|
751
762
|
icon: "format-strikethrough",
|
|
752
|
-
|
|
763
|
+
label: "strikethrough"
|
|
753
764
|
},
|
|
754
765
|
{
|
|
755
766
|
command: "code",
|
|
756
767
|
icon: "code-tags",
|
|
757
|
-
|
|
768
|
+
label: "inlineCode"
|
|
758
769
|
}
|
|
759
770
|
],
|
|
760
771
|
[{
|
|
761
772
|
command: "heading",
|
|
762
773
|
icon: "format-header-1",
|
|
763
|
-
|
|
774
|
+
label: "heading"
|
|
764
775
|
}, {
|
|
765
776
|
command: "blockquote",
|
|
766
777
|
icon: "format-quote-close",
|
|
767
|
-
|
|
778
|
+
label: "blockquote"
|
|
768
779
|
}],
|
|
769
780
|
[{
|
|
770
781
|
command: "bulletList",
|
|
771
782
|
icon: "format-list-bulleted",
|
|
772
|
-
|
|
783
|
+
label: "bulletList"
|
|
773
784
|
}, {
|
|
774
785
|
command: "orderedList",
|
|
775
786
|
icon: "format-list-numbered",
|
|
776
|
-
|
|
787
|
+
label: "orderedList"
|
|
777
788
|
}]
|
|
778
789
|
];
|
|
779
790
|
function getIconifyUrl(iconName) {
|
|
@@ -790,7 +801,8 @@ function createBubbleMenuElement(mode = "block") {
|
|
|
790
801
|
separator.style.cssText = "width: 1px; height: 16px; background: rgba(255,255,255,0.2); margin: 0 4px; flex-shrink: 0;";
|
|
791
802
|
menu.appendChild(separator);
|
|
792
803
|
}
|
|
793
|
-
for (const { command, icon,
|
|
804
|
+
for (const { command, icon, label } of group) {
|
|
805
|
+
const title = getLabels()[label];
|
|
794
806
|
const button = document.createElement("button");
|
|
795
807
|
button.type = "button";
|
|
796
808
|
button.dataset.command = command;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-editor.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/text-editor.ts"],"sourcesContent":["import { Editor, Extension, Node } from \"@tiptap/core\";\nimport { BubbleMenu } from \"@tiptap/extension-bubble-menu\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { sendToParent } from \"./utils.js\";\nimport type { EditorInstance, TextEditorMode, UpstartEditorOptions } from \"./types.js\";\n\n/**\n * Custom Document node for inline elements (e.g. <a>, <span>, headings).\n * Uses `inline*` content instead of the default `block+` to prevent\n * TipTap from wrapping text in <p> tags inside inline elements.\n */\nconst InlineDocument = Node.create({\n name: \"doc\",\n topNode: true,\n content: \"inline*\",\n});\n\n/**\n * An atomic inline node that represents an i18n template variable like {{year}}.\n * It is displayed as a grayed non-editable chip showing the resolved value.\n * renderText() returns {{varName}} so getText() serialises back to the raw template.\n */\nconst TemplateVariable = Node.create({\n name: \"templateVariable\",\n group: \"inline\",\n inline: true,\n atom: true,\n\n addAttributes() {\n return {\n varName: { default: \"\" },\n value: { default: \"\" },\n };\n },\n\n parseHTML() {\n return [{ tag: \"span[data-tpl-var]\" }];\n },\n\n renderHTML({ node }) {\n return [\n \"span\",\n {\n \"data-tpl-var\": node.attrs.varName,\n contenteditable: \"false\",\n style:\n \"opacity:0.5;background:rgba(0,0,0,0.08);border-radius:3px;padding:0 3px;\" +\n \"font-family:monospace;font-size:0.875em;cursor:default;user-select:none;\",\n },\n node.attrs.value || `{{${node.attrs.varName}}}`,\n ];\n },\n\n renderText({ node }) {\n return `{{${node.attrs.varName}}}`;\n },\n});\n\n/**\n * An atomic inline node holding static text that surrounds an i18n node in the\n * source (e.g. the \" *\" in `<label><Trans i18nKey=\"…\" /> *</label>`).\n * It stays visible and non-editable, and renderText() returns \"\" so getText()\n * yields only the translation — the static part never leaks into the saved value.\n */\nconst StaticAffix = Node.create({\n name: \"staticAffix\",\n group: \"inline\",\n inline: true,\n atom: true,\n selectable: false,\n\n addAttributes() {\n return {\n text: { default: \"\" },\n };\n },\n\n parseHTML() {\n return [{ tag: \"span[data-static-affix]\" }];\n },\n\n renderHTML({ node }) {\n return [\n \"span\",\n {\n \"data-static-affix\": \"\",\n contenteditable: \"false\",\n style: \"cursor:default;user-select:none;white-space:pre;\",\n },\n node.attrs.text,\n ];\n },\n\n renderText() {\n return \"\";\n },\n});\n\n/**\n * Remaps Enter to insert a <br> (hard break) instead of creating a new paragraph.\n * Used in inline-rich mode where block nodes are not allowed.\n */\nconst EnterHardBreak = Extension.create({\n name: \"enterHardBreak\",\n addKeyboardShortcuts() {\n return {\n Enter: () => this.editor.commands.setHardBreak(),\n };\n },\n});\n\nconst DEFAULT_OPTIONS: Required<UpstartEditorOptions> = {\n richTextElements: [\"p\", \"div\", \"article\", \"section\"],\n inlineRichTextElements: [\"a\", \"span\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"],\n plainTextElements: [\"button\", \"label\"],\n bubbleMenu: true,\n placeholder: \"Start typing...\",\n autoSaveDelay: 1000,\n getRawI18nTemplate: () => undefined,\n};\n\n/**\n * Parse a `data-upstart-i18n` value into its namespace and key.\n *\n * The build-time plugin guarantees a single \"namespace:key\" per element (a ternary\n * between two translations is emitted as a runtime-resolved expression), but a stale\n * build may still carry a comma-separated list of keys. Such a value is unresolvable —\n * we cannot tell which translation the edited text belongs to — so it is rejected\n * instead of being saved to a mangled key.\n */\nfunction parseI18nAttr(value: string | undefined): { namespace: string; key: string } | null {\n if (!value) return null;\n if (value.includes(\",\")) {\n console.warn(\"[Upstart Editor] Ambiguous i18n key, refusing to save:\", value);\n return null;\n }\n const colonIdx = value.indexOf(\":\");\n return colonIdx >= 0\n ? { namespace: value.slice(0, colonIdx), key: value.slice(colonIdx + 1) }\n : { namespace: \"translation\", key: value };\n}\n\n/**\n * Static text emitted by the build-time plugin around a single i18n node\n * (`data-upstart-i18n-prefix` / `data-upstart-i18n-suffix`).\n */\nfunction getAffixes(element: HTMLElement): { prefix: string; suffix: string } {\n return {\n prefix: element.dataset.upstartI18nPrefix ?? \"\",\n suffix: element.dataset.upstartI18nSuffix ?? \"\",\n };\n}\n\n/** Remove the static affixes from a rendered text to get the translation alone. */\nfunction stripAffixes(element: HTMLElement, text: string): string {\n const { prefix, suffix } = getAffixes(element);\n let out = text;\n if (prefix && out.startsWith(prefix)) out = out.slice(prefix.length);\n if (suffix && out.endsWith(suffix)) out = out.slice(0, out.length - suffix.length);\n return out;\n}\n\n/** Re-attach the static affixes to a translation for display purposes. */\nfunction applyAffixes(element: HTMLElement, text: string): string {\n const { prefix, suffix } = getAffixes(element);\n return `${prefix}${text}${suffix}`;\n}\n\n/**\n * Wrap the editable inline nodes with non-editable StaticAffix nodes so the static\n * parts stay visible while remaining outside of the edited (and saved) content.\n */\nfunction buildAffixDocument(element: HTMLElement, inlineNodes: object[]): object {\n const { prefix, suffix } = getAffixes(element);\n const content: object[] = [...inlineNodes];\n if (prefix) content.unshift({ type: \"staticAffix\", attrs: { text: prefix } });\n if (suffix) content.push({ type: \"staticAffix\", attrs: { text: suffix } });\n return { type: \"doc\", content: [{ type: \"paragraph\", content }] };\n}\n\n/** Extract the inline nodes of a single-paragraph document produced above. */\nfunction getInlineNodes(content: string | object): object[] {\n if (typeof content === \"string\") {\n return content ? [{ type: \"text\", text: content }] : [];\n }\n const paragraph = (content as { content?: { content?: object[] }[] }).content?.[0];\n return paragraph?.content ?? [];\n}\n\n/**\n * Parse a raw i18n template (e.g. \"Copyright {{year}}\") together with the\n * already-rendered text (e.g. \"Copyright 2026\") and produce a TipTap JSON\n * document where variable tokens become TemplateVariable nodes.\n */\nfunction buildI18nContent(rawTemplate: string, renderedText: string): object {\n const VAR_RE = /\\{\\{(\\w+)\\}\\}/g;\n type Segment = { type: \"text\"; text: string } | { type: \"var\"; varName: string };\n const segments: Segment[] = [];\n\n let lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = VAR_RE.exec(rawTemplate)) !== null) {\n if (m.index > lastIndex) segments.push({ type: \"text\", text: rawTemplate.slice(lastIndex, m.index) });\n segments.push({ type: \"var\", varName: m[1] });\n lastIndex = m.index + m[0].length;\n }\n if (lastIndex < rawTemplate.length) segments.push({ type: \"text\", text: rawTemplate.slice(lastIndex) });\n\n // Build a regex over the rendered text to capture variable values\n const escRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regexParts = segments.map((s) => (s.type === \"text\" ? escRe(s.text) : \"(.+?)\"));\n const fullRegex = new RegExp(\"^\" + regexParts.join(\"\") + \"$\");\n const varSegments = segments.filter((s): s is Extract<Segment, { type: \"var\" }> => s.type === \"var\");\n\n const varValues: Record<string, string> = {};\n const match = renderedText.match(fullRegex);\n if (match) {\n varSegments.forEach((seg, i) => {\n varValues[seg.varName] = match[i + 1] ?? `{{${seg.varName}}}`;\n });\n } else {\n varSegments.forEach((seg) => {\n varValues[seg.varName] = `{{${seg.varName}}}`;\n });\n }\n\n const inlineNodes = segments\n .map((s) =>\n s.type === \"text\"\n ? { type: \"text\", text: s.text }\n : { type: \"templateVariable\", attrs: { varName: s.varName, value: varValues[s.varName] } },\n )\n .filter((n) => n.type !== \"text\" || (n as any).text !== \"\");\n\n return { type: \"doc\", content: [{ type: \"paragraph\", content: inlineNodes }] };\n}\n\n// Keyed by the host DOM element, NOT by data-upstart-hash: a component rendered\n// multiple times (e.g. a TimelineItem reused 4×) emits the same source-derived\n// hash on every instance, so keying by hash would let only the first instance\n// become editable. The element is the only per-instance unique identity.\nconst activeEditors = new Map<HTMLElement, EditorInstance>();\nlet i18nSyncInProgress = false;\nconst styleCache = new WeakMap<\n HTMLElement,\n { outline: string; outlineOffset: string; cursor: string; transition: string }\n>();\nlet resolvedOptions: Required<UpstartEditorOptions> = DEFAULT_OPTIONS;\nlet shortcutsRegistered = false;\nlet domObserver: MutationObserver | null = null;\nlet reactivateTimer: number | null = null;\n\n/**\n * Initialize TipTap text editing for elements marked as editable.\n * Activation is deferred to avoid conflicting with React hydration.\n */\nexport function initTextEditor(options: UpstartEditorOptions = {}): void {\n resolvedOptions = { ...DEFAULT_OPTIONS, ...options };\n registerKeyboardShortcuts();\n\n // Defer activation to let React finish hydrating the server-rendered HTML.\n injectEditorStyles();\n // activateAllEditors();\n startDomObserver();\n}\n\n/**\n * Inject CSS rules that visually hide original content when an editor is active.\n * This avoids moving DOM nodes (which breaks React hydration).\n *\n * - `font-size: 0` + `color: transparent` hides direct text nodes\n * - `> *:not(.ProseMirror)` hides child elements\n * - ProseMirror gets explicit inline styles to restore text rendering\n */\nfunction injectEditorStyles(): void {\n if (document.getElementById(\"upstart-editor-styles\")) return;\n\n const style = document.createElement(\"style\");\n style.id = \"upstart-editor-styles\";\n style.textContent = [\n \"[data-upstart-editor-active] {\",\n \" cursor: text;\",\n \"}\",\n // \"[data-upstart-editor-active] > *:not(.ProseMirror) {\",\n // \" display: none !important;\",\n // \"}\",\n \"[data-upstart-editor-active] .ProseMirror {\",\n \" outline: none !important;\",\n \" font-size: inherit !important;\",\n \" line-height: inherit !important;\",\n \" color: inherit !important;\",\n \" letter-spacing: inherit !important;\",\n \" font-weight: inherit !important;\",\n \" white-space: inherit !important;\",\n \"}\",\n \"[data-upstart-editor-active] .ProseMirror:focus {\",\n \" outline: none !important;\",\n \"}\",\n // Single-line editors (plain/direct, e.g. array-item pills) render their text\n // inside a ProseMirror <p>, which would otherwise pick up the browser's default\n // paragraph margin and change the element's height when edit mode activates.\n \"[data-upstart-editor-active][data-upstart-editable-text-mode='plain'] .ProseMirror p,\",\n \"[data-upstart-editor-active][data-upstart-editable-text-mode='direct'] .ProseMirror p {\",\n \" margin: 0 !important;\",\n \" padding: 0 !important;\",\n \"}\",\n ].join(\"\\n\");\n document.head.appendChild(style);\n}\n\n/**\n * Activate editors on all editable elements. Safe to call multiple times.\n */\nexport function activateAllEditors(): void {\n cleanupOrphanedEditors();\n const editables = document.querySelectorAll<HTMLElement>('[data-upstart-editable-text=\"true\"]');\n editables.forEach((element) => {\n try {\n setupEditableElement(element, resolvedOptions);\n } catch (error) {\n console.error(\"[Upstart Editor] Failed to activate element:\", element.dataset.upstartHash, error);\n }\n });\n console.log(\"[Upstart Editor] Text editors activated\");\n}\n\n/**\n * Destroy all active editors.\n */\nexport function destroyAllActiveEditors(): void {\n stopDomObserver();\n for (const element of activeEditors.keys()) {\n destroyEditor(element);\n }\n}\n\nfunction setupEditableElement(element: HTMLElement, options: Required<UpstartEditorOptions>): void {\n const hash = getEditableHash(element);\n if (!hash || activeEditors.has(element)) {\n return;\n }\n\n cacheStyles(element);\n activateEditor(element, hash, options);\n}\n\nfunction activateEditor(element: HTMLElement, hash: string, options: Required<UpstartEditorOptions>): void {\n const mode = getEditorMode(element, options);\n\n let editor: Editor;\n\n switch (mode) {\n case \"direct\":\n editor = createDirectEditor(element, options);\n break;\n case \"rich-panel\":\n editor = createRichPanelEditor(element, options);\n break;\n case \"block-rich\":\n editor = createRichTextEditor(element, options);\n break;\n case \"inline-rich\":\n editor = createInlineRichTextEditor(element, options);\n break;\n case \"plain\":\n editor = createPlainTextEditor(element, options);\n break;\n }\n\n // Mark element — triggers CSS rules that hide original content\n element.dataset.upstartEditorActive = \"true\";\n\n applyActiveStyles(element);\n activeEditors.set(element, { editor, element, hash, mode });\n}\n\nfunction createPlainTextEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n // A label like `<Trans i18nKey=\"…\" /> *` renders the static \" *\" alongside the\n // translation: strip it so only the translation becomes editable.\n const renderedText = stripAffixes(element, element.textContent ?? \"\");\n\n let content: string | object = renderedText;\n const extraExtensions: ReturnType<typeof Node.create>[] = [];\n\n // Mixed-text: JSXText + expression chips (e.g. © {year} Some text)\n const mixedTemplate = element.dataset.upstartMixedTemplate;\n if (mixedTemplate) {\n content = buildI18nContent(mixedTemplate, renderedText);\n extraExtensions.push(TemplateVariable);\n } else {\n // Check for i18n template variables (e.g. data-i18n-values=\"year,month\")\n const i18nValueKeys = element.dataset.i18nValues?.split(\",\").filter(Boolean) ?? [];\n if (i18nValueKeys.length > 0) {\n const parsed = parseI18nAttr(element.dataset.upstartI18n);\n if (parsed) {\n const rawTemplate = options.getRawI18nTemplate(parsed.namespace, parsed.key);\n if (rawTemplate) {\n content = buildI18nContent(rawTemplate, renderedText);\n extraExtensions.push(TemplateVariable);\n }\n }\n }\n }\n\n const { prefix, suffix } = getAffixes(element);\n if (prefix || suffix) {\n extraExtensions.push(StaticAffix);\n content = buildAffixDocument(element, getInlineNodes(content));\n }\n\n element.textContent = \"\";\n let hasChanged = false;\n\n const editor = new Editor({\n element,\n extensions: [\n ...extraExtensions,\n StarterKit.configure({\n heading: false,\n bold: false,\n italic: false,\n strike: false,\n blockquote: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n codeBlock: false,\n horizontalRule: false,\n }),\n Placeholder.configure({\n placeholder: \"Click to edit...\",\n }),\n ],\n content,\n editorProps: {\n attributes: {\n class: \"upstart-editor-active\",\n },\n },\n onUpdate: () => {\n if (i18nSyncInProgress) return;\n hasChanged = true;\n syncI18nSiblings(element);\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n saveText(element, e.getText());\n },\n });\n\n return editor;\n}\n\nfunction createRichTextEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n const content = element.innerHTML;\n element.innerHTML = \"\"; // Clear the element first!\n let hasChanged = false;\n\n const bubbleMenuElement = createBubbleMenuElement();\n const extensions: Extension[] = [\n StarterKit,\n Placeholder.configure({\n placeholder: options.placeholder,\n }),\n ];\n\n if (options.bubbleMenu) {\n extensions.push(\n BubbleMenu.configure({\n element: bubbleMenuElement,\n }),\n );\n }\n\n const editor = new Editor({\n element,\n extensions,\n content,\n editorProps: {\n attributes: {\n class: \"upstart-editor-active\",\n },\n },\n onUpdate: () => {\n if (i18nSyncInProgress) return;\n hasChanged = true;\n syncI18nSiblings(element);\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n saveText(element, e.getHTML());\n },\n });\n\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, editor);\n }\n\n return editor;\n}\n\nfunction createInlineRichTextEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n const content = element.innerHTML;\n element.innerHTML = \"\"; // Clear the element first!\n let hasChanged = false;\n\n const bubbleMenuElement = createBubbleMenuElement(\"inline\");\n const extensions: (Extension | ReturnType<typeof Node.create>)[] = [\n InlineDocument,\n EnterHardBreak,\n StarterKit.configure({\n document: false,\n heading: false,\n blockquote: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n codeBlock: false,\n horizontalRule: false,\n }),\n Placeholder.configure({\n placeholder: \"Click to edit...\",\n }),\n ];\n\n if (options.bubbleMenu) {\n extensions.push(\n BubbleMenu.configure({\n element: bubbleMenuElement,\n }),\n );\n }\n\n const editor = new Editor({\n element,\n extensions,\n content,\n editorProps: {\n attributes: {\n class: \"upstart-editor-active\",\n },\n },\n onUpdate: () => {\n if (i18nSyncInProgress) return;\n hasChanged = true;\n syncI18nSiblings(element);\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n saveText(element, e.getHTML());\n },\n });\n\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, editor);\n }\n\n return editor;\n}\n\nfunction createDirectEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n const content = element.innerHTML;\n element.innerHTML = \"\";\n let hasChanged = false;\n\n const bubbleMenuElement = createBubbleMenuElement(\"inline\");\n const extensions: (Extension | ReturnType<typeof Node.create>)[] = [\n EnterHardBreak,\n StarterKit.configure({\n heading: false,\n blockquote: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n codeBlock: false,\n horizontalRule: false,\n }),\n Placeholder.configure({ placeholder: \"Click to edit...\" }),\n ];\n\n if (options.bubbleMenu) {\n extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));\n }\n\n const editor = new Editor({\n element,\n extensions,\n content,\n editorProps: { attributes: { class: \"upstart-editor-active\" } },\n onUpdate: () => {\n hasChanged = true;\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n // Strip the outer <p> that TipTap adds when using default document schema\n saveText(element, e.getHTML().replace(/^<p>([\\s\\S]*)<\\/p>$/, \"$1\"));\n },\n });\n\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, editor);\n }\n\n return editor;\n}\n\nfunction createRichPanelEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n const content = element.innerHTML;\n element.innerHTML = \"\";\n let hasChanged = false;\n\n const bubbleMenuElement = createBubbleMenuElement(\"inline\");\n const extensions: Extension[] = [StarterKit, Placeholder.configure({ placeholder: options.placeholder })];\n\n if (options.bubbleMenu) {\n extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));\n }\n\n const editor = new Editor({\n element,\n extensions,\n content,\n editorProps: { attributes: { class: \"upstart-editor-active\" } },\n onUpdate: () => {\n hasChanged = true;\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n saveText(element, e.getHTML());\n },\n });\n\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, editor);\n }\n\n return editor;\n}\n\nfunction getEditorMode(element: HTMLElement, options: Required<UpstartEditorOptions>): TextEditorMode {\n const modeOverride = element.dataset.upstartEditableTextMode as TextEditorMode | undefined;\n if (\n modeOverride === \"plain\" ||\n modeOverride === \"inline-rich\" ||\n modeOverride === \"block-rich\" ||\n modeOverride === \"direct\" ||\n modeOverride === \"rich-panel\"\n ) {\n return modeOverride;\n }\n\n const tagName = element.tagName.toLowerCase();\n\n if (options.plainTextElements.includes(tagName)) {\n return \"plain\";\n }\n\n if (options.inlineRichTextElements.includes(tagName)) {\n return \"inline-rich\";\n }\n\n if (options.richTextElements.includes(tagName)) {\n return \"block-rich\";\n }\n\n return \"block-rich\";\n}\n\nfunction syncI18nSiblings(sourceElement: HTMLElement): void {\n const instance = activeEditors.get(sourceElement);\n console.log(\"[Upstart Editor] Syncing i18n siblings for\", instance?.hash);\n if (!instance) {\n console.warn(\"[Upstart Editor] No editor instance found for element:\", sourceElement);\n return;\n }\n\n const i18nKey = instance.element.dataset.upstartI18n;\n if (!i18nKey) {\n console.warn(\"[Upstart Editor] No sibling i18n key found for element:\", instance.element);\n return;\n }\n\n const siblings = document.querySelectorAll<HTMLElement>(`[data-upstart-i18n=\"${CSS.escape(i18nKey)}\"]`);\n console.log(`[Upstart Editor] Found ${siblings.length} sibling(s) with i18n key \"${i18nKey}\"`);\n\n // Always use plain text so each sibling's schema can wrap it correctly\n // (e.g. InlineDocument vs block Document have incompatible content rules).\n const plainContent = instance.editor.getText();\n\n i18nSyncInProgress = true;\n try {\n for (const sibling of siblings) {\n if (sibling === instance.element) continue;\n\n const siblingInstance = activeEditors.get(sibling);\n\n const { prefix, suffix } = getAffixes(sibling);\n const hasAffixes = Boolean(prefix || suffix);\n\n if (siblingInstance) {\n console.log(\n `[Upstart Editor] Updating sibling editor (hash: ${siblingInstance.hash}) with new content`,\n siblingInstance,\n );\n if (hasAffixes) {\n // setContent rebuilds the StaticAffix nodes, which insertContent would drop\n siblingInstance.editor.commands.setContent(\n buildAffixDocument(sibling, plainContent ? [{ type: \"text\", text: plainContent }] : []),\n );\n } else {\n sibling.innerText = plainContent;\n siblingInstance.editor.chain().selectAll().insertContent(plainContent).run();\n }\n } else {\n sibling.textContent = applyAffixes(sibling, plainContent);\n }\n }\n } finally {\n i18nSyncInProgress = false;\n }\n}\n\nfunction saveText(element: HTMLElement, newText: string): void {\n try {\n const instance = activeEditors.get(element);\n const dataset = instance?.element.dataset ?? {};\n\n // direct/rich-panel always go via editTextDirect; plain goes via editTextDirect when a\n // registry id is present (mixed-text), and via editText (i18n) otherwise.\n if (instance?.mode === \"direct\" || instance?.mode === \"rich-panel\" || dataset.upstartId) {\n sendToParent({\n type: \"text-edit\",\n payload: { action: \"editTextDirect\", id: dataset.upstartId!, content: newText },\n });\n } else {\n const parsed = parseI18nAttr(dataset.upstartI18n);\n if (!parsed) {\n console.warn(\"[Upstart Editor] No resolvable i18n key on element, edit not saved:\", element);\n return;\n }\n sendToParent({\n type: \"text-edit\",\n payload: {\n action: \"editText\",\n content: newText,\n namespace: parsed.namespace,\n key: parsed.key,\n language: document.documentElement.lang,\n },\n });\n }\n\n console.log(\"[Upstart Editor] Text save message sent:\", instance?.hash);\n } catch (error) {\n console.error(\"[Upstart Editor] Failed to send save message:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\nfunction destroyEditor(element: HTMLElement): void {\n const instance = activeEditors.get(element);\n if (!instance) {\n return;\n }\n\n const isPlainMode = instance.mode === \"plain\";\n const finalContent = isPlainMode ? instance.editor.getText() : instance.editor.getHTML();\n\n instance.editor.destroy();\n\n // Remove ProseMirror DOM\n const editorDom = instance.element.querySelector(\".ProseMirror\");\n if (editorDom) editorDom.remove();\n\n // Remove CSS-hiding marker — original content becomes visible again\n delete instance.element.dataset.upstartEditorActive;\n\n // Update element content with the final edited text\n if (isPlainMode) {\n instance.element.textContent = applyAffixes(instance.element, finalContent);\n } else {\n instance.element.innerHTML = finalContent;\n }\n\n restoreStyles(instance.element);\n activeEditors.delete(element);\n}\n\n// ---------------------------------------------------------------------------\n// MutationObserver — re-activates editors after React re-renders\n// ---------------------------------------------------------------------------\n\nfunction startDomObserver(): void {\n if (domObserver) return;\n\n domObserver = new MutationObserver((mutations) => {\n let needsReactivation = false;\n\n for (const mutation of mutations) {\n if (mutation.type !== \"childList\") continue;\n\n for (const node of mutation.addedNodes) {\n if (\n node instanceof HTMLElement &&\n (node.matches?.('[data-upstart-editable-text=\"true\"]') ||\n node.querySelector?.('[data-upstart-editable-text=\"true\"]'))\n ) {\n needsReactivation = true;\n break;\n }\n }\n\n if (!needsReactivation) {\n for (const node of mutation.removedNodes) {\n if (\n node instanceof HTMLElement &&\n (node.matches?.('[data-upstart-editable-text=\"true\"]') ||\n node.querySelector?.('[data-upstart-editable-text=\"true\"]'))\n ) {\n needsReactivation = true;\n break;\n }\n }\n }\n\n if (needsReactivation) break;\n }\n\n if (needsReactivation) {\n scheduleReactivation();\n }\n });\n\n domObserver.observe(document.body, { childList: true, subtree: true });\n}\n\nfunction stopDomObserver(): void {\n if (domObserver) {\n domObserver.disconnect();\n domObserver = null;\n }\n if (reactivateTimer) {\n clearTimeout(reactivateTimer);\n reactivateTimer = null;\n }\n}\n\nfunction scheduleReactivation(): void {\n if (reactivateTimer) clearTimeout(reactivateTimer);\n reactivateTimer = window.setTimeout(() => {\n reactivateTimer = null;\n activateAllEditors();\n }, 50);\n}\n\n/**\n * Remove editors whose host element has been detached from the document\n * (e.g. React replaced the subtree during a re-render).\n */\nfunction cleanupOrphanedEditors(): void {\n for (const [element, instance] of activeEditors) {\n if (!document.contains(instance.element)) {\n instance.editor.destroy();\n activeEditors.delete(element);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Keyboard shortcuts\n// ---------------------------------------------------------------------------\n\nfunction registerKeyboardShortcuts(): void {\n if (shortcutsRegistered) {\n return;\n }\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") {\n blurAllEditors();\n }\n\n if ((event.metaKey || event.ctrlKey) && event.key === \"Enter\") {\n blurAllEditors();\n }\n });\n\n shortcutsRegistered = true;\n}\n\nfunction blurAllEditors(): void {\n activeEditors.forEach((instance) => {\n instance.editor.commands.blur();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getEditableHash(element: HTMLElement): string | null {\n return element.dataset.upstartHash ?? null;\n}\n\nfunction cacheStyles(element: HTMLElement): void {\n if (styleCache.has(element)) {\n return;\n }\n\n styleCache.set(element, {\n outline: element.style.outline || \"\",\n outlineOffset: element.style.outlineOffset || \"\",\n cursor: element.style.cursor || \"\",\n transition: element.style.transition || \"\",\n });\n}\n\nfunction applyActiveStyles(element: HTMLElement): void {\n cacheStyles(element);\n}\n\nfunction restoreStyles(element: HTMLElement): void {\n const cached = styleCache.get(element);\n if (!cached) {\n element.style.outline = \"\";\n element.style.outlineOffset = \"\";\n element.style.cursor = \"\";\n element.style.transition = \"\";\n return;\n }\n\n element.style.outline = cached.outline;\n element.style.outlineOffset = cached.outlineOffset;\n element.style.cursor = cached.cursor;\n element.style.transition = cached.transition;\n}\n\nconst BUBBLE_MENU_GROUPS: { command: string; icon: string; title: string }[][] = [\n [\n { command: \"bold\", icon: \"format-bold\", title: \"Bold\" },\n { command: \"italic\", icon: \"format-italic\", title: \"Italic\" },\n { command: \"strike\", icon: \"format-strikethrough\", title: \"Strikethrough\" },\n { command: \"code\", icon: \"code-tags\", title: \"Inline Code\" },\n ],\n [\n { command: \"heading\", icon: \"format-header-1\", title: \"Heading\" },\n { command: \"blockquote\", icon: \"format-quote-close\", title: \"Blockquote\" },\n ],\n [\n { command: \"bulletList\", icon: \"format-list-bulleted\", title: \"Bullet List\" },\n { command: \"orderedList\", icon: \"format-list-numbered\", title: \"Ordered List\" },\n ],\n];\n\nfunction getIconifyUrl(iconName: string): string {\n return `https://api.iconify.design/mdi/${iconName}.svg?height=none&color=%23fff`;\n}\n\nfunction createBubbleMenuElement(mode: \"block\" | \"inline\" = \"block\"): HTMLDivElement {\n const groups = mode === \"inline\" ? [BUBBLE_MENU_GROUPS[0]] : BUBBLE_MENU_GROUPS;\n const menu = document.createElement(\"div\");\n menu.className = \"upstart-editor-bubble-menu\";\n menu.style.cssText =\n \"display: flex; align-items: center; gap: 2px; padding: 4px; \" +\n \"background: #111827; color: #ffffff; border-radius: 8px; \" +\n \"font-size: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.25);\";\n\n groups.forEach((group, groupIndex) => {\n if (groupIndex > 0) {\n const separator = document.createElement(\"div\");\n separator.style.cssText =\n \"width: 1px; height: 16px; background: rgba(255,255,255,0.2); \" + \"margin: 0 4px; flex-shrink: 0;\";\n menu.appendChild(separator);\n }\n\n for (const { command, icon, title } of group) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.dataset.command = command;\n button.title = title;\n button.style.cssText =\n \"background: transparent; border: none; color: inherit; cursor: pointer; \" +\n \"display: flex; align-items: center; justify-content: center; \" +\n \"width: 28px; height: 28px; padding: 4px; border-radius: 4px; \" +\n \"transition: background 0.15s ease;\";\n\n const img = document.createElement(\"img\");\n img.src = getIconifyUrl(icon);\n img.alt = title;\n img.style.cssText = \"width: 18px; height: 18px; display: block; pointer-events: none;\";\n button.appendChild(img);\n\n button.addEventListener(\"mouseenter\", () => {\n if (!button.dataset.active) {\n button.style.background = \"rgba(255,255,255,0.12)\";\n }\n });\n button.addEventListener(\"mouseleave\", () => {\n button.style.background = button.dataset.active ? \"rgba(255,255,255,0.2)\" : \"transparent\";\n });\n\n menu.appendChild(button);\n }\n });\n\n return menu;\n}\n\nfunction wireBubbleMenu(menu: HTMLDivElement, editor: Editor): void {\n menu.addEventListener(\"mousedown\", (event) => {\n event.preventDefault();\n });\n\n menu.addEventListener(\n \"click\",\n (event) => {\n console.log(\"menu button cliked\");\n event.stopPropagation();\n const target = event.target as HTMLElement | null;\n const command = target?.dataset.command;\n if (!command) return;\n\n const chain = editor.chain().focus();\n\n switch (command) {\n case \"bold\":\n chain.toggleBold().run();\n break;\n case \"italic\":\n chain.toggleItalic().run();\n break;\n case \"strike\":\n chain.toggleStrike().run();\n break;\n case \"code\":\n chain.toggleCode().run();\n break;\n case \"heading\":\n chain.toggleHeading({ level: 1 }).run();\n break;\n case \"blockquote\":\n chain.toggleBlockquote().run();\n break;\n case \"bulletList\":\n chain.toggleBulletList().run();\n break;\n case \"orderedList\":\n chain.toggleOrderedList().run();\n break;\n }\n },\n { capture: true },\n );\n\n const updateActiveStates = (): void => {\n const buttons = menu.querySelectorAll<HTMLButtonElement>(\"button[data-command]\");\n for (const button of buttons) {\n const command = button.dataset.command!;\n const isActive =\n command === \"heading\" ? editor.isActive(\"heading\", { level: 1 }) : editor.isActive(command);\n\n if (isActive) {\n button.dataset.active = \"true\";\n button.style.background = \"rgba(255,255,255,0.2)\";\n } else {\n delete button.dataset.active;\n button.style.background = \"transparent\";\n }\n }\n };\n\n editor.on(\"transaction\", updateActiveStates);\n updateActiveStates();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"text-editor.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/text-editor.ts"],"sourcesContent":["import { Editor, Extension, Node } from \"@tiptap/core\";\nimport { BubbleMenu } from \"@tiptap/extension-bubble-menu\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { sendToParent } from \"./utils.js\";\nimport type { EditorInstance, TextEditorMode, UpstartEditorOptions } from \"./types.js\";\nimport { getLabels, type RuntimeLabels } from \"./labels.js\";\n\n/**\n * Custom Document node for inline elements (e.g. <a>, <span>, headings).\n * Uses `inline*` content instead of the default `block+` to prevent\n * TipTap from wrapping text in <p> tags inside inline elements.\n */\nconst InlineDocument = Node.create({\n name: \"doc\",\n topNode: true,\n content: \"inline*\",\n});\n\n/**\n * An atomic inline node that represents an i18n template variable like {{year}}.\n * It is displayed as a grayed non-editable chip showing the resolved value.\n * renderText() returns {{varName}} so getText() serialises back to the raw template.\n */\nconst TemplateVariable = Node.create({\n name: \"templateVariable\",\n group: \"inline\",\n inline: true,\n atom: true,\n\n addAttributes() {\n return {\n varName: { default: \"\" },\n value: { default: \"\" },\n };\n },\n\n parseHTML() {\n return [{ tag: \"span[data-tpl-var]\" }];\n },\n\n renderHTML({ node }) {\n return [\n \"span\",\n {\n \"data-tpl-var\": node.attrs.varName,\n contenteditable: \"false\",\n style:\n \"opacity:0.5;background:rgba(0,0,0,0.08);border-radius:3px;padding:0 3px;\" +\n \"font-family:monospace;font-size:0.875em;cursor:default;user-select:none;\",\n },\n node.attrs.value || `{{${node.attrs.varName}}}`,\n ];\n },\n\n renderText({ node }) {\n return `{{${node.attrs.varName}}}`;\n },\n});\n\n/**\n * An atomic inline node holding static text that surrounds an i18n node in the\n * source (e.g. the \" *\" in `<label><Trans i18nKey=\"…\" /> *</label>`).\n * It stays visible and non-editable, and renderText() returns \"\" so getText()\n * yields only the translation — the static part never leaks into the saved value.\n */\nconst StaticAffix = Node.create({\n name: \"staticAffix\",\n group: \"inline\",\n inline: true,\n atom: true,\n selectable: false,\n\n addAttributes() {\n return {\n text: { default: \"\" },\n };\n },\n\n parseHTML() {\n return [{ tag: \"span[data-static-affix]\" }];\n },\n\n renderHTML({ node }) {\n return [\n \"span\",\n {\n \"data-static-affix\": \"\",\n contenteditable: \"false\",\n style: \"cursor:default;user-select:none;white-space:pre;\",\n },\n node.attrs.text,\n ];\n },\n\n renderText() {\n return \"\";\n },\n});\n\n/**\n * Remaps Enter to insert a <br> (hard break) instead of creating a new paragraph.\n * Used in inline-rich mode where block nodes are not allowed.\n */\nconst EnterHardBreak = Extension.create({\n name: \"enterHardBreak\",\n addKeyboardShortcuts() {\n return {\n Enter: () => this.editor.commands.setHardBreak(),\n };\n },\n});\n\nconst DEFAULT_OPTIONS: Required<UpstartEditorOptions> = {\n richTextElements: [\"p\", \"div\", \"article\", \"section\"],\n inlineRichTextElements: [\"a\", \"span\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"],\n plainTextElements: [\"button\", \"label\"],\n bubbleMenu: true,\n placeholder: \"Start typing...\",\n autoSaveDelay: 1000,\n getRawI18nTemplate: () => undefined,\n};\n\n/**\n * Parse a `data-upstart-i18n` value into its namespace and key.\n *\n * The build-time plugin guarantees a single \"namespace:key\" per element (a ternary\n * between two translations is emitted as a runtime-resolved expression), but a stale\n * build may still carry a comma-separated list of keys. Such a value is unresolvable —\n * we cannot tell which translation the edited text belongs to — so it is rejected\n * instead of being saved to a mangled key.\n */\nfunction parseI18nAttr(value: string | undefined): { namespace: string; key: string } | null {\n if (!value) return null;\n if (value.includes(\",\")) {\n console.warn(\"[Upstart Editor] Ambiguous i18n key, refusing to save:\", value);\n return null;\n }\n const colonIdx = value.indexOf(\":\");\n return colonIdx >= 0\n ? { namespace: value.slice(0, colonIdx), key: value.slice(colonIdx + 1) }\n : { namespace: \"translation\", key: value };\n}\n\n/**\n * Static text emitted by the build-time plugin around a single i18n node\n * (`data-upstart-i18n-prefix` / `data-upstart-i18n-suffix`).\n */\nfunction getAffixes(element: HTMLElement): { prefix: string; suffix: string } {\n return {\n prefix: element.dataset.upstartI18nPrefix ?? \"\",\n suffix: element.dataset.upstartI18nSuffix ?? \"\",\n };\n}\n\n/** Remove the static affixes from a rendered text to get the translation alone. */\nfunction stripAffixes(element: HTMLElement, text: string): string {\n const { prefix, suffix } = getAffixes(element);\n let out = text;\n if (prefix && out.startsWith(prefix)) out = out.slice(prefix.length);\n if (suffix && out.endsWith(suffix)) out = out.slice(0, out.length - suffix.length);\n return out;\n}\n\n/** Re-attach the static affixes to a translation for display purposes. */\nfunction applyAffixes(element: HTMLElement, text: string): string {\n const { prefix, suffix } = getAffixes(element);\n return `${prefix}${text}${suffix}`;\n}\n\n/**\n * Wrap the editable inline nodes with non-editable StaticAffix nodes so the static\n * parts stay visible while remaining outside of the edited (and saved) content.\n */\nfunction buildAffixDocument(element: HTMLElement, inlineNodes: object[]): object {\n const { prefix, suffix } = getAffixes(element);\n const content: object[] = [...inlineNodes];\n if (prefix) content.unshift({ type: \"staticAffix\", attrs: { text: prefix } });\n if (suffix) content.push({ type: \"staticAffix\", attrs: { text: suffix } });\n return { type: \"doc\", content: [{ type: \"paragraph\", content }] };\n}\n\n/** Extract the inline nodes of a single-paragraph document produced above. */\nfunction getInlineNodes(content: string | object): object[] {\n if (typeof content === \"string\") {\n return content ? [{ type: \"text\", text: content }] : [];\n }\n const paragraph = (content as { content?: { content?: object[] }[] }).content?.[0];\n return paragraph?.content ?? [];\n}\n\n/**\n * Parse a raw i18n template (e.g. \"Copyright {{year}}\") together with the\n * already-rendered text (e.g. \"Copyright 2026\") and produce a TipTap JSON\n * document where variable tokens become TemplateVariable nodes.\n */\nfunction buildI18nContent(rawTemplate: string, renderedText: string): object {\n const VAR_RE = /\\{\\{(\\w+)\\}\\}/g;\n type Segment = { type: \"text\"; text: string } | { type: \"var\"; varName: string };\n const segments: Segment[] = [];\n\n let lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = VAR_RE.exec(rawTemplate)) !== null) {\n if (m.index > lastIndex) segments.push({ type: \"text\", text: rawTemplate.slice(lastIndex, m.index) });\n segments.push({ type: \"var\", varName: m[1] });\n lastIndex = m.index + m[0].length;\n }\n if (lastIndex < rawTemplate.length) segments.push({ type: \"text\", text: rawTemplate.slice(lastIndex) });\n\n // Build a regex over the rendered text to capture variable values\n const escRe = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regexParts = segments.map((s) => (s.type === \"text\" ? escRe(s.text) : \"(.+?)\"));\n const fullRegex = new RegExp(\"^\" + regexParts.join(\"\") + \"$\");\n const varSegments = segments.filter((s): s is Extract<Segment, { type: \"var\" }> => s.type === \"var\");\n\n const varValues: Record<string, string> = {};\n const match = renderedText.match(fullRegex);\n if (match) {\n varSegments.forEach((seg, i) => {\n varValues[seg.varName] = match[i + 1] ?? `{{${seg.varName}}}`;\n });\n } else {\n varSegments.forEach((seg) => {\n varValues[seg.varName] = `{{${seg.varName}}}`;\n });\n }\n\n const inlineNodes = segments\n .map((s) =>\n s.type === \"text\"\n ? { type: \"text\", text: s.text }\n : { type: \"templateVariable\", attrs: { varName: s.varName, value: varValues[s.varName] } },\n )\n .filter((n) => n.type !== \"text\" || (n as any).text !== \"\");\n\n return { type: \"doc\", content: [{ type: \"paragraph\", content: inlineNodes }] };\n}\n\n// Keyed by the host DOM element, NOT by data-upstart-hash: a component rendered\n// multiple times (e.g. a TimelineItem reused 4×) emits the same source-derived\n// hash on every instance, so keying by hash would let only the first instance\n// become editable. The element is the only per-instance unique identity.\nconst activeEditors = new Map<HTMLElement, EditorInstance>();\nlet i18nSyncInProgress = false;\nconst styleCache = new WeakMap<\n HTMLElement,\n { outline: string; outlineOffset: string; cursor: string; transition: string }\n>();\nlet resolvedOptions: Required<UpstartEditorOptions> = DEFAULT_OPTIONS;\n\n/** The site may override the placeholder; the default one follows the editor's language. */\nfunction placeholderFor(options: Required<UpstartEditorOptions>): string {\n return options.placeholder === DEFAULT_OPTIONS.placeholder ? getLabels().startTyping : options.placeholder;\n}\nlet shortcutsRegistered = false;\nlet domObserver: MutationObserver | null = null;\nlet reactivateTimer: number | null = null;\n\n/**\n * Initialize TipTap text editing for elements marked as editable.\n * Activation is deferred to avoid conflicting with React hydration.\n */\nexport function initTextEditor(options: UpstartEditorOptions = {}): void {\n resolvedOptions = { ...DEFAULT_OPTIONS, ...options };\n registerKeyboardShortcuts();\n\n // Defer activation to let React finish hydrating the server-rendered HTML.\n injectEditorStyles();\n // activateAllEditors();\n startDomObserver();\n}\n\n/**\n * Inject CSS rules that visually hide original content when an editor is active.\n * This avoids moving DOM nodes (which breaks React hydration).\n *\n * - `font-size: 0` + `color: transparent` hides direct text nodes\n * - `> *:not(.ProseMirror)` hides child elements\n * - ProseMirror gets explicit inline styles to restore text rendering\n */\nfunction injectEditorStyles(): void {\n if (document.getElementById(\"upstart-editor-styles\")) return;\n\n const style = document.createElement(\"style\");\n style.id = \"upstart-editor-styles\";\n style.textContent = [\n \"[data-upstart-editor-active] {\",\n \" cursor: text;\",\n \"}\",\n // \"[data-upstart-editor-active] > *:not(.ProseMirror) {\",\n // \" display: none !important;\",\n // \"}\",\n \"[data-upstart-editor-active] .ProseMirror {\",\n \" outline: none !important;\",\n \" font-size: inherit !important;\",\n \" line-height: inherit !important;\",\n \" color: inherit !important;\",\n \" letter-spacing: inherit !important;\",\n \" font-weight: inherit !important;\",\n \" white-space: inherit !important;\",\n \"}\",\n \"[data-upstart-editor-active] .ProseMirror:focus {\",\n \" outline: none !important;\",\n \"}\",\n // Single-line editors (plain/direct, e.g. array-item pills) render their text\n // inside a ProseMirror <p>, which would otherwise pick up the browser's default\n // paragraph margin and change the element's height when edit mode activates.\n \"[data-upstart-editor-active][data-upstart-editable-text-mode='plain'] .ProseMirror p,\",\n \"[data-upstart-editor-active][data-upstart-editable-text-mode='direct'] .ProseMirror p {\",\n \" margin: 0 !important;\",\n \" padding: 0 !important;\",\n \"}\",\n ].join(\"\\n\");\n document.head.appendChild(style);\n}\n\n/**\n * Activate editors on all editable elements. Safe to call multiple times.\n */\nexport function activateAllEditors(): void {\n cleanupOrphanedEditors();\n const editables = document.querySelectorAll<HTMLElement>('[data-upstart-editable-text=\"true\"]');\n editables.forEach((element) => {\n try {\n setupEditableElement(element, resolvedOptions);\n } catch (error) {\n console.error(\"[Upstart Editor] Failed to activate element:\", element.dataset.upstartHash, error);\n }\n });\n console.log(\"[Upstart Editor] Text editors activated\");\n}\n\n/**\n * Destroy all active editors.\n */\nexport function destroyAllActiveEditors(): void {\n stopDomObserver();\n for (const element of activeEditors.keys()) {\n destroyEditor(element);\n }\n}\n\n/** Descendants the plain editor cannot reproduce: taking over the element would destroy them. */\nconst PLAIN_EDITOR_FOREIGN_CHILDREN = \"svg, img, picture, video, iframe, canvas\";\n\nfunction setupEditableElement(element: HTMLElement, options: Required<UpstartEditorOptions>): void {\n const hash = getEditableHash(element);\n if (!hash || activeEditors.has(element)) {\n return;\n }\n // Older builds may still mark a container holding an icon next to its text as editable:\n // the plain editor would wipe the icon and React would later fight over it. Leave such\n // elements alone (the current plugin wraps the text in its own <span> instead).\n if (getEditorMode(element, options) === \"plain\" && element.querySelector(PLAIN_EDITOR_FOREIGN_CHILDREN)) {\n console.warn(\n \"[Upstart Editor] Skipping text editor on an element that also holds media/icons:\",\n element.dataset.upstartHash,\n element,\n );\n return;\n }\n\n cacheStyles(element);\n activateEditor(element, hash, options);\n}\n\nfunction activateEditor(element: HTMLElement, hash: string, options: Required<UpstartEditorOptions>): void {\n const mode = getEditorMode(element, options);\n\n let editor: Editor;\n\n switch (mode) {\n case \"direct\":\n editor = createDirectEditor(element, options);\n break;\n case \"rich-panel\":\n editor = createRichPanelEditor(element, options);\n break;\n case \"block-rich\":\n editor = createRichTextEditor(element, options);\n break;\n case \"inline-rich\":\n editor = createInlineRichTextEditor(element, options);\n break;\n case \"plain\":\n editor = createPlainTextEditor(element, options);\n break;\n }\n\n // Mark element — triggers CSS rules that hide original content\n element.dataset.upstartEditorActive = \"true\";\n\n applyActiveStyles(element);\n activeEditors.set(element, { editor, element, hash, mode });\n}\n\nfunction createPlainTextEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n // A label like `<Trans i18nKey=\"…\" /> *` renders the static \" *\" alongside the\n // translation: strip it so only the translation becomes editable.\n const renderedText = stripAffixes(element, element.textContent ?? \"\");\n\n let content: string | object = renderedText;\n const extraExtensions: ReturnType<typeof Node.create>[] = [];\n\n // Mixed-text: JSXText + expression chips (e.g. © {year} Some text)\n const mixedTemplate = element.dataset.upstartMixedTemplate;\n if (mixedTemplate) {\n content = buildI18nContent(mixedTemplate, renderedText);\n extraExtensions.push(TemplateVariable);\n } else {\n // Check for i18n template variables (e.g. data-i18n-values=\"year,month\")\n const i18nValueKeys = element.dataset.i18nValues?.split(\",\").filter(Boolean) ?? [];\n if (i18nValueKeys.length > 0) {\n const parsed = parseI18nAttr(element.dataset.upstartI18n);\n if (parsed) {\n const rawTemplate = options.getRawI18nTemplate(parsed.namespace, parsed.key);\n if (rawTemplate) {\n content = buildI18nContent(rawTemplate, renderedText);\n extraExtensions.push(TemplateVariable);\n }\n }\n }\n }\n\n const { prefix, suffix } = getAffixes(element);\n if (prefix || suffix) {\n extraExtensions.push(StaticAffix);\n content = buildAffixDocument(element, getInlineNodes(content));\n }\n\n element.textContent = \"\";\n let hasChanged = false;\n\n const editor = new Editor({\n element,\n extensions: [\n ...extraExtensions,\n StarterKit.configure({\n heading: false,\n bold: false,\n italic: false,\n strike: false,\n blockquote: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n codeBlock: false,\n horizontalRule: false,\n }),\n Placeholder.configure({\n placeholder: getLabels().clickToEdit,\n }),\n ],\n content,\n editorProps: {\n attributes: {\n class: \"upstart-editor-active\",\n },\n },\n onUpdate: () => {\n if (i18nSyncInProgress) return;\n hasChanged = true;\n syncI18nSiblings(element);\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n saveText(element, e.getText());\n },\n });\n\n return editor;\n}\n\nfunction createRichTextEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n const content = element.innerHTML;\n element.innerHTML = \"\"; // Clear the element first!\n let hasChanged = false;\n\n const bubbleMenuElement = createBubbleMenuElement();\n const extensions: Extension[] = [\n StarterKit,\n Placeholder.configure({\n placeholder: placeholderFor(options),\n }),\n ];\n\n if (options.bubbleMenu) {\n extensions.push(\n BubbleMenu.configure({\n element: bubbleMenuElement,\n }),\n );\n }\n\n const editor = new Editor({\n element,\n extensions,\n content,\n editorProps: {\n attributes: {\n class: \"upstart-editor-active\",\n },\n },\n onUpdate: () => {\n if (i18nSyncInProgress) return;\n hasChanged = true;\n syncI18nSiblings(element);\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n saveText(element, e.getHTML());\n },\n });\n\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, editor);\n }\n\n return editor;\n}\n\nfunction createInlineRichTextEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n const content = element.innerHTML;\n element.innerHTML = \"\"; // Clear the element first!\n let hasChanged = false;\n\n const bubbleMenuElement = createBubbleMenuElement(\"inline\");\n const extensions: (Extension | ReturnType<typeof Node.create>)[] = [\n InlineDocument,\n EnterHardBreak,\n StarterKit.configure({\n document: false,\n heading: false,\n blockquote: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n codeBlock: false,\n horizontalRule: false,\n }),\n Placeholder.configure({\n placeholder: getLabels().clickToEdit,\n }),\n ];\n\n if (options.bubbleMenu) {\n extensions.push(\n BubbleMenu.configure({\n element: bubbleMenuElement,\n }),\n );\n }\n\n const editor = new Editor({\n element,\n extensions,\n content,\n editorProps: {\n attributes: {\n class: \"upstart-editor-active\",\n },\n },\n onUpdate: () => {\n if (i18nSyncInProgress) return;\n hasChanged = true;\n syncI18nSiblings(element);\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n saveText(element, e.getHTML());\n },\n });\n\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, editor);\n }\n\n return editor;\n}\n\nfunction createDirectEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n const content = element.innerHTML;\n element.innerHTML = \"\";\n let hasChanged = false;\n\n const bubbleMenuElement = createBubbleMenuElement(\"inline\");\n const extensions: (Extension | ReturnType<typeof Node.create>)[] = [\n EnterHardBreak,\n StarterKit.configure({\n heading: false,\n blockquote: false,\n bulletList: false,\n orderedList: false,\n listItem: false,\n codeBlock: false,\n horizontalRule: false,\n }),\n Placeholder.configure({ placeholder: getLabels().clickToEdit }),\n ];\n\n if (options.bubbleMenu) {\n extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));\n }\n\n const editor = new Editor({\n element,\n extensions,\n content,\n editorProps: { attributes: { class: \"upstart-editor-active\" } },\n onUpdate: () => {\n hasChanged = true;\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n // Strip the outer <p> that TipTap adds when using default document schema\n saveText(element, e.getHTML().replace(/^<p>([\\s\\S]*)<\\/p>$/, \"$1\"));\n },\n });\n\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, editor);\n }\n\n return editor;\n}\n\nfunction createRichPanelEditor(element: HTMLElement, options: Required<UpstartEditorOptions>): Editor {\n const content = element.innerHTML;\n element.innerHTML = \"\";\n let hasChanged = false;\n\n const bubbleMenuElement = createBubbleMenuElement(\"inline\");\n const extensions: Extension[] = [\n StarterKit,\n Placeholder.configure({ placeholder: placeholderFor(options) }),\n ];\n\n if (options.bubbleMenu) {\n extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));\n }\n\n const editor = new Editor({\n element,\n extensions,\n content,\n editorProps: { attributes: { class: \"upstart-editor-active\" } },\n onUpdate: () => {\n hasChanged = true;\n },\n onBlur: ({ editor: e }) => {\n if (!hasChanged) return;\n hasChanged = false;\n saveText(element, e.getHTML());\n },\n });\n\n if (options.bubbleMenu) {\n wireBubbleMenu(bubbleMenuElement, editor);\n }\n\n return editor;\n}\n\nfunction getEditorMode(element: HTMLElement, options: Required<UpstartEditorOptions>): TextEditorMode {\n const modeOverride = element.dataset.upstartEditableTextMode as TextEditorMode | undefined;\n if (\n modeOverride === \"plain\" ||\n modeOverride === \"inline-rich\" ||\n modeOverride === \"block-rich\" ||\n modeOverride === \"direct\" ||\n modeOverride === \"rich-panel\"\n ) {\n return modeOverride;\n }\n\n const tagName = element.tagName.toLowerCase();\n\n if (options.plainTextElements.includes(tagName)) {\n return \"plain\";\n }\n\n if (options.inlineRichTextElements.includes(tagName)) {\n return \"inline-rich\";\n }\n\n if (options.richTextElements.includes(tagName)) {\n return \"block-rich\";\n }\n\n return \"block-rich\";\n}\n\nfunction syncI18nSiblings(sourceElement: HTMLElement): void {\n const instance = activeEditors.get(sourceElement);\n console.log(\"[Upstart Editor] Syncing i18n siblings for\", instance?.hash);\n if (!instance) {\n console.warn(\"[Upstart Editor] No editor instance found for element:\", sourceElement);\n return;\n }\n\n const i18nKey = instance.element.dataset.upstartI18n;\n if (!i18nKey) {\n console.warn(\"[Upstart Editor] No sibling i18n key found for element:\", instance.element);\n return;\n }\n\n const siblings = document.querySelectorAll<HTMLElement>(`[data-upstart-i18n=\"${CSS.escape(i18nKey)}\"]`);\n console.log(`[Upstart Editor] Found ${siblings.length} sibling(s) with i18n key \"${i18nKey}\"`);\n\n // Always use plain text so each sibling's schema can wrap it correctly\n // (e.g. InlineDocument vs block Document have incompatible content rules).\n const plainContent = instance.editor.getText();\n\n i18nSyncInProgress = true;\n try {\n for (const sibling of siblings) {\n if (sibling === instance.element) continue;\n\n const siblingInstance = activeEditors.get(sibling);\n\n const { prefix, suffix } = getAffixes(sibling);\n const hasAffixes = Boolean(prefix || suffix);\n\n if (siblingInstance) {\n console.log(\n `[Upstart Editor] Updating sibling editor (hash: ${siblingInstance.hash}) with new content`,\n siblingInstance,\n );\n if (hasAffixes) {\n // setContent rebuilds the StaticAffix nodes, which insertContent would drop\n siblingInstance.editor.commands.setContent(\n buildAffixDocument(sibling, plainContent ? [{ type: \"text\", text: plainContent }] : []),\n );\n } else {\n sibling.innerText = plainContent;\n siblingInstance.editor.chain().selectAll().insertContent(plainContent).run();\n }\n } else {\n sibling.textContent = applyAffixes(sibling, plainContent);\n }\n }\n } finally {\n i18nSyncInProgress = false;\n }\n}\n\nfunction saveText(element: HTMLElement, newText: string): void {\n try {\n const instance = activeEditors.get(element);\n const dataset = instance?.element.dataset ?? {};\n\n // direct/rich-panel always go via editTextDirect; plain goes via editTextDirect when a\n // registry id is present (mixed-text), and via editText (i18n) otherwise.\n if (instance?.mode === \"direct\" || instance?.mode === \"rich-panel\" || dataset.upstartId) {\n sendToParent({\n type: \"text-edit\",\n payload: { action: \"editTextDirect\", id: dataset.upstartId!, content: newText },\n });\n } else {\n const parsed = parseI18nAttr(dataset.upstartI18n);\n if (!parsed) {\n console.warn(\"[Upstart Editor] No resolvable i18n key on element, edit not saved:\", element);\n return;\n }\n sendToParent({\n type: \"text-edit\",\n payload: {\n action: \"editText\",\n content: newText,\n namespace: parsed.namespace,\n key: parsed.key,\n language: document.documentElement.lang,\n },\n });\n }\n\n console.log(\"[Upstart Editor] Text save message sent:\", instance?.hash);\n } catch (error) {\n console.error(\"[Upstart Editor] Failed to send save message:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\nfunction destroyEditor(element: HTMLElement): void {\n const instance = activeEditors.get(element);\n if (!instance) {\n return;\n }\n\n const isPlainMode = instance.mode === \"plain\";\n const finalContent = isPlainMode ? instance.editor.getText() : instance.editor.getHTML();\n\n instance.editor.destroy();\n\n // Remove ProseMirror DOM\n const editorDom = instance.element.querySelector(\".ProseMirror\");\n if (editorDom) editorDom.remove();\n\n // Remove CSS-hiding marker — original content becomes visible again\n delete instance.element.dataset.upstartEditorActive;\n\n // Update element content with the final edited text\n if (isPlainMode) {\n instance.element.textContent = applyAffixes(instance.element, finalContent);\n } else {\n instance.element.innerHTML = finalContent;\n }\n\n restoreStyles(instance.element);\n activeEditors.delete(element);\n}\n\n// ---------------------------------------------------------------------------\n// MutationObserver — re-activates editors after React re-renders\n// ---------------------------------------------------------------------------\n\nfunction startDomObserver(): void {\n if (domObserver) return;\n\n domObserver = new MutationObserver((mutations) => {\n let needsReactivation = false;\n\n for (const mutation of mutations) {\n if (mutation.type !== \"childList\") continue;\n\n for (const node of mutation.addedNodes) {\n if (\n node instanceof HTMLElement &&\n (node.matches?.('[data-upstart-editable-text=\"true\"]') ||\n node.querySelector?.('[data-upstart-editable-text=\"true\"]'))\n ) {\n needsReactivation = true;\n break;\n }\n }\n\n if (!needsReactivation) {\n for (const node of mutation.removedNodes) {\n if (\n node instanceof HTMLElement &&\n (node.matches?.('[data-upstart-editable-text=\"true\"]') ||\n node.querySelector?.('[data-upstart-editable-text=\"true\"]'))\n ) {\n needsReactivation = true;\n break;\n }\n }\n }\n\n if (needsReactivation) break;\n }\n\n if (needsReactivation) {\n scheduleReactivation();\n }\n });\n\n domObserver.observe(document.body, { childList: true, subtree: true });\n}\n\nfunction stopDomObserver(): void {\n if (domObserver) {\n domObserver.disconnect();\n domObserver = null;\n }\n if (reactivateTimer) {\n clearTimeout(reactivateTimer);\n reactivateTimer = null;\n }\n}\n\nfunction scheduleReactivation(): void {\n if (reactivateTimer) clearTimeout(reactivateTimer);\n reactivateTimer = window.setTimeout(() => {\n reactivateTimer = null;\n activateAllEditors();\n }, 50);\n}\n\n/**\n * Remove editors whose host element has been detached from the document\n * (e.g. React replaced the subtree during a re-render).\n */\nfunction cleanupOrphanedEditors(): void {\n for (const [element, instance] of activeEditors) {\n if (!document.contains(instance.element)) {\n instance.editor.destroy();\n activeEditors.delete(element);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Keyboard shortcuts\n// ---------------------------------------------------------------------------\n\nfunction registerKeyboardShortcuts(): void {\n if (shortcutsRegistered) {\n return;\n }\n\n document.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") {\n blurAllEditors();\n }\n\n if ((event.metaKey || event.ctrlKey) && event.key === \"Enter\") {\n blurAllEditors();\n }\n });\n\n shortcutsRegistered = true;\n}\n\nfunction blurAllEditors(): void {\n activeEditors.forEach((instance) => {\n instance.editor.commands.blur();\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getEditableHash(element: HTMLElement): string | null {\n return element.dataset.upstartHash ?? null;\n}\n\nfunction cacheStyles(element: HTMLElement): void {\n if (styleCache.has(element)) {\n return;\n }\n\n styleCache.set(element, {\n outline: element.style.outline || \"\",\n outlineOffset: element.style.outlineOffset || \"\",\n cursor: element.style.cursor || \"\",\n transition: element.style.transition || \"\",\n });\n}\n\nfunction applyActiveStyles(element: HTMLElement): void {\n cacheStyles(element);\n}\n\nfunction restoreStyles(element: HTMLElement): void {\n const cached = styleCache.get(element);\n if (!cached) {\n element.style.outline = \"\";\n element.style.outlineOffset = \"\";\n element.style.cursor = \"\";\n element.style.transition = \"\";\n return;\n }\n\n element.style.outline = cached.outline;\n element.style.outlineOffset = cached.outlineOffset;\n element.style.cursor = cached.cursor;\n element.style.transition = cached.transition;\n}\n\nconst BUBBLE_MENU_GROUPS: { command: string; icon: string; label: keyof RuntimeLabels }[][] = [\n [\n { command: \"bold\", icon: \"format-bold\", label: \"bold\" },\n { command: \"italic\", icon: \"format-italic\", label: \"italic\" },\n { command: \"strike\", icon: \"format-strikethrough\", label: \"strikethrough\" },\n { command: \"code\", icon: \"code-tags\", label: \"inlineCode\" },\n ],\n [\n { command: \"heading\", icon: \"format-header-1\", label: \"heading\" },\n { command: \"blockquote\", icon: \"format-quote-close\", label: \"blockquote\" },\n ],\n [\n { command: \"bulletList\", icon: \"format-list-bulleted\", label: \"bulletList\" },\n { command: \"orderedList\", icon: \"format-list-numbered\", label: \"orderedList\" },\n ],\n];\n\nfunction getIconifyUrl(iconName: string): string {\n return `https://api.iconify.design/mdi/${iconName}.svg?height=none&color=%23fff`;\n}\n\nfunction createBubbleMenuElement(mode: \"block\" | \"inline\" = \"block\"): HTMLDivElement {\n const groups = mode === \"inline\" ? [BUBBLE_MENU_GROUPS[0]] : BUBBLE_MENU_GROUPS;\n const menu = document.createElement(\"div\");\n menu.className = \"upstart-editor-bubble-menu\";\n menu.style.cssText =\n \"display: flex; align-items: center; gap: 2px; padding: 4px; \" +\n \"background: #111827; color: #ffffff; border-radius: 8px; \" +\n \"font-size: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.25);\";\n\n groups.forEach((group, groupIndex) => {\n if (groupIndex > 0) {\n const separator = document.createElement(\"div\");\n separator.style.cssText =\n \"width: 1px; height: 16px; background: rgba(255,255,255,0.2); \" + \"margin: 0 4px; flex-shrink: 0;\";\n menu.appendChild(separator);\n }\n\n for (const { command, icon, label } of group) {\n const title = getLabels()[label];\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.dataset.command = command;\n button.title = title;\n button.style.cssText =\n \"background: transparent; border: none; color: inherit; cursor: pointer; \" +\n \"display: flex; align-items: center; justify-content: center; \" +\n \"width: 28px; height: 28px; padding: 4px; border-radius: 4px; \" +\n \"transition: background 0.15s ease;\";\n\n const img = document.createElement(\"img\");\n img.src = getIconifyUrl(icon);\n img.alt = title;\n img.style.cssText = \"width: 18px; height: 18px; display: block; pointer-events: none;\";\n button.appendChild(img);\n\n button.addEventListener(\"mouseenter\", () => {\n if (!button.dataset.active) {\n button.style.background = \"rgba(255,255,255,0.12)\";\n }\n });\n button.addEventListener(\"mouseleave\", () => {\n button.style.background = button.dataset.active ? \"rgba(255,255,255,0.2)\" : \"transparent\";\n });\n\n menu.appendChild(button);\n }\n });\n\n return menu;\n}\n\nfunction wireBubbleMenu(menu: HTMLDivElement, editor: Editor): void {\n menu.addEventListener(\"mousedown\", (event) => {\n event.preventDefault();\n });\n\n menu.addEventListener(\n \"click\",\n (event) => {\n console.log(\"menu button cliked\");\n event.stopPropagation();\n const target = event.target as HTMLElement | null;\n const command = target?.dataset.command;\n if (!command) return;\n\n const chain = editor.chain().focus();\n\n switch (command) {\n case \"bold\":\n chain.toggleBold().run();\n break;\n case \"italic\":\n chain.toggleItalic().run();\n break;\n case \"strike\":\n chain.toggleStrike().run();\n break;\n case \"code\":\n chain.toggleCode().run();\n break;\n case \"heading\":\n chain.toggleHeading({ level: 1 }).run();\n break;\n case \"blockquote\":\n chain.toggleBlockquote().run();\n break;\n case \"bulletList\":\n chain.toggleBulletList().run();\n break;\n case \"orderedList\":\n chain.toggleOrderedList().run();\n break;\n }\n },\n { capture: true },\n );\n\n const updateActiveStates = (): void => {\n const buttons = menu.querySelectorAll<HTMLButtonElement>(\"button[data-command]\");\n for (const button of buttons) {\n const command = button.dataset.command!;\n const isActive =\n command === \"heading\" ? editor.isActive(\"heading\", { level: 1 }) : editor.isActive(command);\n\n if (isActive) {\n button.dataset.active = \"true\";\n button.style.background = \"rgba(255,255,255,0.2)\";\n } else {\n delete button.dataset.active;\n button.style.background = \"transparent\";\n }\n }\n };\n\n editor.on(\"transaction\", updateActiveStates);\n updateActiveStates();\n}\n"],"mappings":"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"}
|