@upstart.gg/vite-plugins 0.1.29 → 0.1.31

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.
Files changed (50) hide show
  1. package/dist/upstart-editor-api.d.ts +13 -1
  2. package/dist/upstart-editor-api.d.ts.map +1 -1
  3. package/dist/upstart-editor-api.js +59 -1
  4. package/dist/upstart-editor-api.js.map +1 -1
  5. package/dist/vite-plugin-upstart-attrs.d.ts +12 -7
  6. package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
  7. package/dist/vite-plugin-upstart-attrs.js +195 -3
  8. package/dist/vite-plugin-upstart-attrs.js.map +1 -1
  9. package/dist/vite-plugin-upstart-branding/plugin.d.ts +3 -3
  10. package/dist/vite-plugin-upstart-branding/plugin.d.ts.map +1 -1
  11. package/dist/vite-plugin-upstart-branding/plugin.js.map +1 -1
  12. package/dist/vite-plugin-upstart-branding/runtime.js.map +1 -1
  13. package/dist/vite-plugin-upstart-editor/plugin.d.ts +3 -3
  14. package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -1
  15. package/dist/vite-plugin-upstart-editor/plugin.js +4 -1
  16. package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -1
  17. package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +27 -16
  18. package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -1
  19. package/dist/vite-plugin-upstart-editor/runtime/error-handler.js.map +1 -1
  20. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +1 -1
  21. package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -1
  22. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +1 -1
  23. package/dist/vite-plugin-upstart-editor/runtime/index.js +14 -2
  24. package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
  25. package/dist/{src/vite-plugin-upstart-editor → vite-plugin-upstart-editor}/runtime/state.d.ts +1 -1
  26. package/dist/vite-plugin-upstart-editor/runtime/state.d.ts.map +1 -0
  27. package/dist/vite-plugin-upstart-editor/runtime/state.js.map +1 -0
  28. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -1
  29. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +212 -28
  30. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
  31. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +16 -3
  32. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
  33. package/dist/vite-plugin-upstart-editor/runtime/utils.js.map +1 -1
  34. package/dist/vite-plugin-upstart-theme.d.ts +3 -3
  35. package/dist/vite-plugin-upstart-theme.d.ts.map +1 -1
  36. package/dist/vite-plugin-upstart-theme.js +2 -2
  37. package/dist/vite-plugin-upstart-theme.js.map +1 -1
  38. package/package.json +7 -7
  39. package/src/tests/vite-plugin-upstart-attrs.test.ts +298 -37
  40. package/src/upstart-editor-api.ts +71 -0
  41. package/src/vite-plugin-upstart-attrs.ts +293 -5
  42. package/src/vite-plugin-upstart-editor/plugin.ts +11 -1
  43. package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +35 -21
  44. package/src/vite-plugin-upstart-editor/runtime/index.ts +21 -1
  45. package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +260 -41
  46. package/src/vite-plugin-upstart-editor/runtime/types.ts +17 -4
  47. package/src/vite-plugin-upstart-theme.ts +4 -1
  48. package/dist/src/vite-plugin-upstart-editor/runtime/state.d.ts.map +0 -1
  49. package/dist/src/vite-plugin-upstart-editor/runtime/state.js.map +0 -1
  50. /package/dist/{src/vite-plugin-upstart-editor → vite-plugin-upstart-editor}/runtime/state.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"hover-overlay.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/hover-overlay.ts"],"sourcesContent":["import { getCurrentMode } from \"./state.js\";\nimport { sendToParent } from \"./utils.js\";\n\nlet overlay: HTMLDivElement | null = null;\nlet currentTarget: HTMLElement | null = null;\nlet hoveredEl: HTMLElement | null = null;\nlet isInitialized = false;\nlet rafId: number | null = null;\n\n/**\n * Initialize hover overlay.\n */\nexport function initHoverOverlay(): void {\n if (typeof document === \"undefined\") {\n return;\n }\n\n if (isInitialized) {\n return;\n }\n\n console.log(\"[Upstart Editor] Initializing hover overlay...\");\n\n const style = document.createElement(\"style\");\n style.id = \"upstart-hover-edit-styles\";\n style.textContent = [\n \":root[data-upstart-edit-mode] [data-upstart-classname-id]:not([data-upstart-editable-text='true']),\",\n \":root[data-upstart-edit-mode] [data-upstart-datasource][data-upstart-record-id]:not([data-upstart-editable-text='true']),\",\n \":root[data-upstart-edit-mode] [data-upstart-editor-active] {\",\n \" transition: outline 150ms, outline-offset 150ms;\",\n \"}\",\n \":root[data-upstart-edit-mode] [data-upstart-hovered] {\",\n \" outline: 2px solid rgba(114, 112, 198, 0.6);\",\n \" outline-offset: 4px;\",\n \" cursor: pointer;\",\n \"}\",\n \":root[data-upstart-edit-mode] [data-upstart-editor-active][data-upstart-hovered] {\",\n \" outline: 2px solid rgba(114, 112, 198, 0.6);\",\n \" outline-offset: 4px;\",\n \" cursor: text;\",\n \"}\",\n \":root[data-upstart-edit-mode] [data-upstart-hovered-ancestor] {\",\n \" outline: 1px dashed rgba(114, 112, 198, 0.6);\",\n \" cursor: pointer;\",\n \"}\",\n ].join(\"\\n\");\n document.head.appendChild(style);\n\n document.addEventListener(\"mouseover\", handleMouseOver);\n document.addEventListener(\"mouseout\", handleMouseOut);\n document.addEventListener(\"keydown\", handleKeyChange);\n document.addEventListener(\"keyup\", handleKeyChange);\n window.addEventListener(\"scroll\", scheduleOverlayUpdate, { passive: true });\n window.addEventListener(\"resize\", scheduleOverlayUpdate, { passive: true });\n\n isInitialized = true;\n}\n\n/**\n * Hide all overlays.\n */\nexport function hideOverlays(): void {\n if (overlay) {\n overlay.style.display = \"none\";\n currentTarget = null;\n }\n clearEditableHover();\n}\n\nfunction isEligible(el: HTMLElement): boolean {\n if (el.dataset.upstartEditableText === \"true\") return false;\n return !!(el.dataset.upstartClassnameId || (el.dataset.upstartDatasource && el.dataset.upstartRecordId));\n}\n\nfunction clearEditableHover(): void {\n for (const el of document.querySelectorAll(\"[data-upstart-hovered],[data-upstart-hovered-ancestor]\")) {\n (el as HTMLElement).removeAttribute(\"data-upstart-hovered\");\n (el as HTMLElement).removeAttribute(\"data-upstart-hovered-ancestor\");\n }\n hoveredEl = null;\n}\n\nfunction applyTextHover(el: HTMLElement): void {\n clearEditableHover();\n el.setAttribute(\"data-upstart-hovered\", \"\");\n hoveredEl = el;\n}\n\nfunction applyEditableHover(target: HTMLElement, withAncestors: boolean): void {\n clearEditableHover();\n\n // Find closest eligible element\n let el: HTMLElement | null = target;\n while (el && el !== document.documentElement) {\n if (isEligible(el)) break;\n el = el.parentElement;\n }\n if (!el || el === document.documentElement) return;\n\n el.setAttribute(\"data-upstart-hovered\", \"\");\n hoveredEl = el;\n\n if (withAncestors) {\n let ancestor = el.parentElement;\n while (ancestor && ancestor !== document.documentElement) {\n if (isEligible(ancestor)) {\n ancestor.setAttribute(\"data-upstart-hovered-ancestor\", \"\");\n }\n ancestor = ancestor.parentElement;\n }\n }\n}\n\nfunction handleMouseOver(event: MouseEvent): void {\n if (getCurrentMode() !== \"edit\") {\n return;\n }\n\n const target = event.target as HTMLElement | null;\n if (!target) {\n return;\n }\n\n // If hovering inside an active text editor, highlight only that element\n // and suppress the component overlay box so parents don't get hover effects.\n const textEl = target.closest<HTMLElement>(\"[data-upstart-editor-active]\");\n if (textEl) {\n applyTextHover(textEl);\n if (overlay) overlay.style.display = \"none\";\n currentTarget = null;\n return;\n }\n\n applyEditableHover(target, event.metaKey || event.ctrlKey);\n\n const component = target.closest<HTMLElement>(\"[data-upstart-component]\");\n if (!component || component.dataset.upstartEditorActive) {\n return;\n }\n\n if (!overlay) {\n createOverlay();\n }\n\n currentTarget = component;\n positionOverlay(component);\n}\n\nfunction handleMouseOut(event: MouseEvent): void {\n const target = event.target as HTMLElement | null;\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n if (!target) {\n return;\n }\n\n const component = target.closest<HTMLElement>(\"[data-upstart-component]\");\n if (!component) {\n return;\n }\n\n if (relatedTarget && component.contains(relatedTarget)) {\n return;\n }\n\n hideOverlays();\n}\n\nfunction handleKeyChange(event: KeyboardEvent): void {\n if (getCurrentMode() !== \"edit\" || !hoveredEl) return;\n if (event.key !== \"Meta\" && event.key !== \"Control\") return;\n applyEditableHover(hoveredEl, event.type === \"keydown\" && (event.metaKey || event.ctrlKey));\n}\n\nfunction createOverlay(): void {\n overlay = document.createElement(\"div\");\n overlay.id = \"upstart-hover-overlay\";\n overlay.style.cssText =\n \"position: absolute; pointer-events: none; border: 2px solid rgba(114, 112, 198, 0.6); \" +\n \"background: rgba(114, 112, 198, 0.05); border-radius: 4px; z-index: 9999; \" +\n \"transition: all 0.1s ease; display: none;\";\n document.body.appendChild(overlay);\n}\n\nfunction positionOverlay(element: HTMLElement): void {\n if (!overlay) {\n return;\n }\n\n const rect = element.getBoundingClientRect();\n overlay.style.top = `${rect.top + window.scrollY}px`;\n overlay.style.left = `${rect.left + window.scrollX}px`;\n overlay.style.width = `${rect.width}px`;\n overlay.style.height = `${rect.height}px`;\n overlay.style.display = \"block\";\n}\n\nfunction scheduleOverlayUpdate(): void {\n if (rafId !== null) {\n return;\n }\n\n rafId = requestAnimationFrame(() => {\n rafId = null;\n if (currentTarget && overlay && overlay.style.display === \"block\") {\n positionOverlay(currentTarget);\n }\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hover-overlay.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/hover-overlay.ts"],"sourcesContent":["import { getCurrentMode } from \"./state.js\";\nimport { sendToParent } from \"./utils.js\";\n\nlet overlay: HTMLDivElement | null = null;\nlet currentTarget: HTMLElement | null = null;\nlet hoveredEl: HTMLElement | null = null;\nlet isInitialized = false;\nlet rafId: number | null = null;\n\n/**\n * Initialize hover overlay.\n */\nexport function initHoverOverlay(): void {\n if (typeof document === \"undefined\") {\n return;\n }\n\n if (isInitialized) {\n return;\n }\n\n console.log(\"[Upstart Editor] Initializing hover overlay...\");\n\n const style = document.createElement(\"style\");\n style.id = \"upstart-hover-edit-styles\";\n style.textContent = [\n \":root[data-upstart-edit-mode] [data-upstart-classname-id]:not([data-upstart-editable-text='true']),\",\n \":root[data-upstart-edit-mode] [data-upstart-datasource][data-upstart-record-id]:not([data-upstart-editable-text='true']),\",\n \":root[data-upstart-edit-mode] [data-upstart-editor-active] {\",\n \" transition: outline 150ms, outline-offset 150ms;\",\n \"}\",\n \":root[data-upstart-edit-mode] [data-upstart-hovered] {\",\n \" outline: 2px solid rgba(114, 112, 198, 0.6);\",\n \" outline-offset: 4px;\",\n \" cursor: pointer;\",\n \"}\",\n \":root[data-upstart-edit-mode] [data-upstart-editor-active][data-upstart-hovered] {\",\n \" outline: 2px solid rgba(114, 112, 198, 0.6);\",\n \" outline-offset: 4px;\",\n \" cursor: text;\",\n \"}\",\n \":root[data-upstart-edit-mode] [data-upstart-hovered-ancestor] {\",\n \" outline: 1px dashed rgba(114, 112, 198, 0.6);\",\n \" cursor: pointer;\",\n \"}\",\n ].join(\"\\n\");\n document.head.appendChild(style);\n\n document.addEventListener(\"mouseover\", handleMouseOver);\n document.addEventListener(\"mouseout\", handleMouseOut);\n document.addEventListener(\"keydown\", handleKeyChange);\n document.addEventListener(\"keyup\", handleKeyChange);\n window.addEventListener(\"scroll\", scheduleOverlayUpdate, { passive: true });\n window.addEventListener(\"resize\", scheduleOverlayUpdate, { passive: true });\n\n isInitialized = true;\n}\n\n/**\n * Hide all overlays.\n */\nexport function hideOverlays(): void {\n if (overlay) {\n overlay.style.display = \"none\";\n currentTarget = null;\n }\n clearEditableHover();\n}\n\nfunction isEligible(el: HTMLElement): boolean {\n if (el.dataset.upstartEditableText === \"true\") return false;\n return !!(el.dataset.upstartClassnameId || (el.dataset.upstartDatasource && el.dataset.upstartRecordId));\n}\n\nfunction clearEditableHover(): void {\n for (const el of document.querySelectorAll(\"[data-upstart-hovered],[data-upstart-hovered-ancestor]\")) {\n (el as HTMLElement).removeAttribute(\"data-upstart-hovered\");\n (el as HTMLElement).removeAttribute(\"data-upstart-hovered-ancestor\");\n }\n hoveredEl = null;\n}\n\nfunction applyTextHover(el: HTMLElement): void {\n clearEditableHover();\n el.setAttribute(\"data-upstart-hovered\", \"\");\n hoveredEl = el;\n}\n\nfunction applyEditableHover(target: HTMLElement, withAncestors: boolean): void {\n clearEditableHover();\n\n // Find closest eligible element\n let el: HTMLElement | null = target;\n while (el && el !== document.documentElement) {\n if (isEligible(el)) break;\n el = el.parentElement;\n }\n if (!el || el === document.documentElement) return;\n\n el.setAttribute(\"data-upstart-hovered\", \"\");\n hoveredEl = el;\n\n if (withAncestors) {\n let ancestor = el.parentElement;\n while (ancestor && ancestor !== document.documentElement) {\n if (isEligible(ancestor)) {\n ancestor.setAttribute(\"data-upstart-hovered-ancestor\", \"\");\n }\n ancestor = ancestor.parentElement;\n }\n }\n}\n\nfunction handleMouseOver(event: MouseEvent): void {\n if (getCurrentMode() !== \"edit\") {\n return;\n }\n\n const target = event.target as HTMLElement | null;\n if (!target) {\n return;\n }\n\n // If hovering inside an active text editor, highlight only that element\n // and suppress the component overlay box so parents don't get hover effects.\n const textEl = target.closest<HTMLElement>(\"[data-upstart-editor-active]\");\n if (textEl) {\n applyTextHover(textEl);\n if (overlay) overlay.style.display = \"none\";\n currentTarget = null;\n return;\n }\n\n applyEditableHover(target, event.metaKey || event.ctrlKey);\n\n const component = target.closest<HTMLElement>(\"[data-upstart-component]\");\n if (!component || component.dataset.upstartEditorActive) {\n return;\n }\n\n if (!overlay) {\n createOverlay();\n }\n\n currentTarget = component;\n positionOverlay(component);\n}\n\nfunction handleMouseOut(event: MouseEvent): void {\n const target = event.target as HTMLElement | null;\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n if (!target) {\n return;\n }\n\n const component = target.closest<HTMLElement>(\"[data-upstart-component]\");\n if (!component) {\n return;\n }\n\n if (relatedTarget && component.contains(relatedTarget)) {\n return;\n }\n\n hideOverlays();\n}\n\nfunction handleKeyChange(event: KeyboardEvent): void {\n if (getCurrentMode() !== \"edit\" || !hoveredEl) return;\n if (event.key !== \"Meta\" && event.key !== \"Control\") return;\n applyEditableHover(hoveredEl, event.type === \"keydown\" && (event.metaKey || event.ctrlKey));\n}\n\nfunction createOverlay(): void {\n overlay = document.createElement(\"div\");\n overlay.id = \"upstart-hover-overlay\";\n overlay.style.cssText =\n \"position: absolute; pointer-events: none; border: 2px solid rgba(114, 112, 198, 0.6); \" +\n \"background: rgba(114, 112, 198, 0.05); border-radius: 4px; z-index: 9999; \" +\n \"transition: all 0.1s ease; display: none;\";\n document.body.appendChild(overlay);\n}\n\nfunction positionOverlay(element: HTMLElement): void {\n if (!overlay) {\n return;\n }\n\n const rect = element.getBoundingClientRect();\n overlay.style.top = `${rect.top + window.scrollY}px`;\n overlay.style.left = `${rect.left + window.scrollX}px`;\n overlay.style.width = `${rect.width}px`;\n overlay.style.height = `${rect.height}px`;\n overlay.style.display = \"block\";\n}\n\nfunction scheduleOverlayUpdate(): void {\n if (rafId !== null) {\n return;\n }\n\n rafId = requestAnimationFrame(() => {\n rafId = null;\n if (currentTarget && overlay && overlay.style.display === \"block\") {\n positionOverlay(currentTarget);\n }\n });\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { EditorMessage, EditorMode, UpstartEditorMessage } from "./types.js";
2
2
  import { initClickHandler } from "./click-handler.js";
3
3
  import { initErrorHandler } from "./error-handler.js";
4
4
  import { initHoverOverlay } from "./hover-overlay.js";
5
- import { getCurrentMode } from "../../src/vite-plugin-upstart-editor/runtime/state.js";
5
+ import { getCurrentMode } from "./state.js";
6
6
  import { initTextEditor } from "./text-editor.js";
7
7
  import { sendToParent } from "./utils.js";
8
8
 
@@ -1,4 +1,4 @@
1
- import { getCurrentMode, setCurrentMode } from "../../src/vite-plugin-upstart-editor/runtime/state.js";
1
+ import { getCurrentMode, setCurrentMode } from "./state.js";
2
2
  import { sendToParent } from "./utils.js";
3
3
  import { initClickHandler } from "./click-handler.js";
4
4
  import { hideOverlays, initHoverOverlay } from "./hover-overlay.js";
@@ -61,7 +61,9 @@ function initUpstartEditor() {
61
61
  originalReplaceState(...args);
62
62
  sendToParent({ type: "editor-navigated" });
63
63
  };
64
- initTextEditor();
64
+ const i18next = globalThis.__i18next;
65
+ const getRawI18nTemplate = i18next ? (namespace, key) => i18next.getResource(i18next.language, namespace, key) : void 0;
66
+ initTextEditor(getRawI18nTemplate ? { getRawI18nTemplate } : {});
65
67
  initClickHandler();
66
68
  initHoverOverlay();
67
69
  initErrorHandler();
@@ -98,6 +100,16 @@ function handleParentMessage(event) {
98
100
  } else if (message.type === "preview-classname") {
99
101
  const el = document.querySelector(`[data-upstart-classname-id="${message.classNameId}"]`);
100
102
  if (el) el.className = message.className;
103
+ const STYLE_ID = "upstart-preview-style";
104
+ let styleEl = document.getElementById(STYLE_ID);
105
+ if (message.previewCSS) {
106
+ if (!styleEl) {
107
+ styleEl = document.createElement("style");
108
+ styleEl.id = STYLE_ID;
109
+ document.head.appendChild(styleEl);
110
+ }
111
+ styleEl.textContent = message.previewCSS;
112
+ } else if (styleEl) styleEl.textContent = "";
101
113
  } else if (message.type === "request-scroll-position") sendToParent({
102
114
  type: "scroll-position",
103
115
  x: window.scrollX,
@@ -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 { sendToParent } from \"./utils.js\";\nimport { getCurrentMode, setCurrentMode } 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 enableEditMode();\n } else {\n disableEditMode();\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 * 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 // Notify parent on SPA navigation so it can resend the current editMode\n window.addEventListener(\"popstate\", () => {\n sendToParent({ type: \"editor-navigated\" });\n });\n const originalPushState = history.pushState.bind(history);\n history.pushState = (...args) => {\n originalPushState(...args);\n sendToParent({ type: \"editor-navigated\" });\n };\n const originalReplaceState = history.replaceState.bind(history);\n history.replaceState = (...args) => {\n originalReplaceState(...args);\n sendToParent({ type: \"editor-navigated\" });\n };\n\n initTextEditor();\n initClickHandler();\n initHoverOverlay();\n initErrorHandler();\n\n sendToParent({ type: \"editor-ready\" });\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 } 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 }\n}\n\nfunction enableEditMode(): void {\n console.log(\"[Upstart Editor] Edit mode enabled\");\n document.documentElement.setAttribute(\"data-upstart-edit-mode\", \"\");\n activateAllEditors();\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}\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 { initTextEditor, activateAllEditors, destroyAllActiveEditors } from \"./text-editor.js\";\nimport { sendToParent } from \"./utils.js\";\nimport { getCurrentMode, setCurrentMode } 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 enableEditMode();\n } else {\n disableEditMode();\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 * 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 // Notify parent on SPA navigation so it can resend the current editMode\n window.addEventListener(\"popstate\", () => {\n sendToParent({ type: \"editor-navigated\" });\n });\n const originalPushState = history.pushState.bind(history);\n history.pushState = (...args) => {\n originalPushState(...args);\n sendToParent({ type: \"editor-navigated\" });\n };\n const originalReplaceState = history.replaceState.bind(history);\n history.replaceState = (...args) => {\n originalReplaceState(...args);\n sendToParent({ type: \"editor-navigated\" });\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 initErrorHandler();\n\n sendToParent({ type: \"editor-ready\" });\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 === \"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 }\n}\n\nfunction enableEditMode(): void {\n console.log(\"[Upstart Editor] Edit mode enabled\");\n document.documentElement.setAttribute(\"data-upstart-edit-mode\", \"\");\n activateAllEditors();\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}\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,4 +1,4 @@
1
- import { EditorMode } from "../../../vite-plugin-upstart-editor/runtime/types.js";
1
+ import { EditorMode } from "./types.js";
2
2
 
3
3
  //#region src/vite-plugin-upstart-editor/runtime/state.d.ts
4
4
  /**
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/state.ts"],"mappings":";;;;;AAOA;iBAAgB,cAAA,CAAA,GAAkB,UAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/state.ts"],"sourcesContent":["import type { EditorMode } from \"./types.js\";\n\nlet currentMode: EditorMode = \"preview\";\n\n/**\n * Get the current editor mode.\n */\nexport function getCurrentMode(): EditorMode {\n return currentMode;\n}\n\n/**\n * Set the current editor mode (internal use only).\n */\nexport function setCurrentMode(mode: EditorMode): void {\n currentMode = mode;\n}\n"],"mappings":";AAEA,IAAI,cAA0B;;;;AAK9B,SAAgB,iBAA6B;CAC3C,OAAO;;;;;AAMT,SAAgB,eAAe,MAAwB;CACrD,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"text-editor.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/text-editor.ts"],"mappings":";;;;;AAuDA;;iBAAgB,cAAA,CAAe,OAAA,GAAS,oBAAA;;;AAiDxC;iBAAgB,kBAAA,CAAA;;;;iBAkBA,uBAAA,CAAA"}
1
+ {"version":3,"file":"text-editor.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/text-editor.ts"],"mappings":";;;;;AAiJA;;iBAAgB,cAAA,CAAe,OAAA,GAAS,oBAAA;;;AAiDxC;iBAAgB,kBAAA,CAAA;;;;iBAgBA,uBAAA,CAAA"}
@@ -15,6 +15,40 @@ const InlineDocument = Node.create({
15
15
  content: "inline*"
16
16
  });
17
17
  /**
18
+ * An atomic inline node that represents an i18n template variable like {{year}}.
19
+ * It is displayed as a grayed non-editable chip showing the resolved value.
20
+ * renderText() returns {{varName}} so getText() serialises back to the raw template.
21
+ */
22
+ const TemplateVariable = Node.create({
23
+ name: "templateVariable",
24
+ group: "inline",
25
+ inline: true,
26
+ atom: true,
27
+ addAttributes() {
28
+ return {
29
+ varName: { default: "" },
30
+ value: { default: "" }
31
+ };
32
+ },
33
+ parseHTML() {
34
+ return [{ tag: "span[data-tpl-var]" }];
35
+ },
36
+ renderHTML({ node }) {
37
+ return [
38
+ "span",
39
+ {
40
+ "data-tpl-var": node.attrs.varName,
41
+ contenteditable: "false",
42
+ style: "opacity:0.5;background:rgba(0,0,0,0.08);border-radius:3px;padding:0 3px;font-family:monospace;font-size:0.875em;cursor:default;user-select:none;"
43
+ },
44
+ node.attrs.value || `{{${node.attrs.varName}}}`
45
+ ];
46
+ },
47
+ renderText({ node }) {
48
+ return `{{${node.attrs.varName}}}`;
49
+ }
50
+ });
51
+ /**
18
52
  * Remaps Enter to insert a <br> (hard break) instead of creating a new paragraph.
19
53
  * Used in inline-rich mode where block nodes are not allowed.
20
54
  */
@@ -44,8 +78,63 @@ const DEFAULT_OPTIONS = {
44
78
  plainTextElements: ["button", "label"],
45
79
  bubbleMenu: true,
46
80
  placeholder: "Start typing...",
47
- autoSaveDelay: 1e3
81
+ autoSaveDelay: 1e3,
82
+ getRawI18nTemplate: () => void 0
48
83
  };
84
+ /**
85
+ * Parse a raw i18n template (e.g. "Copyright {{year}}") together with the
86
+ * already-rendered text (e.g. "Copyright 2026") and produce a TipTap JSON
87
+ * document where variable tokens become TemplateVariable nodes.
88
+ */
89
+ function buildI18nContent(rawTemplate, renderedText) {
90
+ const VAR_RE = /\{\{(\w+)\}\}/g;
91
+ const segments = [];
92
+ let lastIndex = 0;
93
+ let m;
94
+ while ((m = VAR_RE.exec(rawTemplate)) !== null) {
95
+ if (m.index > lastIndex) segments.push({
96
+ type: "text",
97
+ text: rawTemplate.slice(lastIndex, m.index)
98
+ });
99
+ segments.push({
100
+ type: "var",
101
+ varName: m[1]
102
+ });
103
+ lastIndex = m.index + m[0].length;
104
+ }
105
+ if (lastIndex < rawTemplate.length) segments.push({
106
+ type: "text",
107
+ text: rawTemplate.slice(lastIndex)
108
+ });
109
+ const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
110
+ const regexParts = segments.map((s) => s.type === "text" ? escRe(s.text) : "(.+?)");
111
+ const fullRegex = new RegExp("^" + regexParts.join("") + "$");
112
+ const varSegments = segments.filter((s) => s.type === "var");
113
+ const varValues = {};
114
+ const match = renderedText.match(fullRegex);
115
+ if (match) varSegments.forEach((seg, i) => {
116
+ varValues[seg.varName] = match[i + 1] ?? `{{${seg.varName}}}`;
117
+ });
118
+ else varSegments.forEach((seg) => {
119
+ varValues[seg.varName] = `{{${seg.varName}}}`;
120
+ });
121
+ return {
122
+ type: "doc",
123
+ content: [{
124
+ type: "paragraph",
125
+ content: segments.map((s) => s.type === "text" ? {
126
+ type: "text",
127
+ text: s.text
128
+ } : {
129
+ type: "templateVariable",
130
+ attrs: {
131
+ varName: s.varName,
132
+ value: varValues[s.varName]
133
+ }
134
+ }).filter((n) => n.type !== "text" || n.text !== "")
135
+ }]
136
+ };
137
+ }
49
138
  const activeEditors = /* @__PURE__ */ new Map();
50
139
  let i18nSyncInProgress = false;
51
140
  const styleCache = /* @__PURE__ */ new WeakMap();
@@ -101,17 +190,15 @@ function injectEditorStyles() {
101
190
  * Activate editors on all editable elements. Safe to call multiple times.
102
191
  */
103
192
  function activateAllEditors() {
104
- try {
105
- cleanupOrphanedEditors();
106
- document.querySelectorAll("[data-upstart-editable-text=\"true\"]").forEach((element) => setupEditableElement(element, resolvedOptions));
107
- console.log("[Upstart Editor] Text editors activated");
108
- } catch (error) {
109
- console.error("[Upstart Editor] Failed to activate text editors:", error);
110
- sendToParent({
111
- type: "editor-error",
112
- error: error instanceof Error ? error.message : "Unknown error"
113
- });
114
- }
193
+ cleanupOrphanedEditors();
194
+ document.querySelectorAll("[data-upstart-editable-text=\"true\"]").forEach((element) => {
195
+ try {
196
+ setupEditableElement(element, resolvedOptions);
197
+ } catch (error) {
198
+ console.error("[Upstart Editor] Failed to activate element:", element.dataset.upstartHash, error);
199
+ }
200
+ });
201
+ console.log("[Upstart Editor] Text editors activated");
115
202
  }
116
203
  /**
117
204
  * Destroy all active editors.
@@ -130,6 +217,12 @@ function activateEditor(element, hash, options) {
130
217
  const mode = getEditorMode(element, options);
131
218
  let editor;
132
219
  switch (mode) {
220
+ case "direct":
221
+ editor = createDirectEditor(element, hash, options);
222
+ break;
223
+ case "rich-panel":
224
+ editor = createRichPanelEditor(element, hash, options);
225
+ break;
133
226
  case "block-rich":
134
227
  editor = createRichTextEditor(element, hash, options);
135
228
  break;
@@ -150,15 +243,33 @@ function activateEditor(element, hash, options) {
150
243
  });
151
244
  }
152
245
  function createPlainTextEditor(element, hash, options) {
153
- const content = element.textContent ?? "";
246
+ const renderedText = element.textContent ?? "";
247
+ let content = renderedText;
248
+ const extraExtensions = [];
249
+ const mixedTemplate = element.dataset.upstartMixedTemplate;
250
+ if (mixedTemplate) {
251
+ content = buildI18nContent(mixedTemplate, renderedText);
252
+ extraExtensions.push(TemplateVariable);
253
+ } else if ((element.dataset.i18nValues?.split(",").filter(Boolean) ?? []).length > 0) {
254
+ const i18nAttr = element.dataset.upstartI18n;
255
+ if (i18nAttr) {
256
+ const colonIdx = i18nAttr.indexOf(":");
257
+ const namespace = colonIdx >= 0 ? i18nAttr.slice(0, colonIdx) : "translation";
258
+ const key = colonIdx >= 0 ? i18nAttr.slice(colonIdx + 1) : i18nAttr;
259
+ const rawTemplate = options.getRawI18nTemplate(namespace, key);
260
+ if (rawTemplate) {
261
+ content = buildI18nContent(rawTemplate, renderedText);
262
+ extraExtensions.push(TemplateVariable);
263
+ }
264
+ }
265
+ }
154
266
  element.textContent = "";
155
267
  let hasChanged = false;
156
268
  return new Editor({
157
269
  element,
158
270
  extensions: [
159
- InlineDocument,
271
+ ...extraExtensions,
160
272
  StarterKit.configure({
161
- document: false,
162
273
  heading: false,
163
274
  bold: false,
164
275
  italic: false,
@@ -174,7 +285,7 @@ function createPlainTextEditor(element, hash, options) {
174
285
  ],
175
286
  content,
176
287
  editorProps: { attributes: { class: "upstart-editor-active" } },
177
- onUpdate: ({ editor: e }) => {
288
+ onUpdate: () => {
178
289
  if (i18nSyncInProgress) return;
179
290
  hasChanged = true;
180
291
  syncI18nSiblings(hash);
@@ -198,7 +309,7 @@ function createRichTextEditor(element, hash, options) {
198
309
  extensions,
199
310
  content,
200
311
  editorProps: { attributes: { class: "upstart-editor-active" } },
201
- onUpdate: ({ editor: e }) => {
312
+ onUpdate: () => {
202
313
  if (i18nSyncInProgress) return;
203
314
  hasChanged = true;
204
315
  syncI18nSiblings(hash);
@@ -238,7 +349,7 @@ function createInlineRichTextEditor(element, hash, options) {
238
349
  extensions,
239
350
  content,
240
351
  editorProps: { attributes: { class: "upstart-editor-active" } },
241
- onUpdate: ({ editor: e }) => {
352
+ onUpdate: () => {
242
353
  if (i18nSyncInProgress) return;
243
354
  hasChanged = true;
244
355
  syncI18nSiblings(hash);
@@ -252,9 +363,69 @@ function createInlineRichTextEditor(element, hash, options) {
252
363
  if (options.bubbleMenu) wireBubbleMenu(bubbleMenuElement, editor);
253
364
  return editor;
254
365
  }
366
+ function createDirectEditor(element, hash, options) {
367
+ const content = element.innerHTML;
368
+ element.innerHTML = "";
369
+ let hasChanged = false;
370
+ const bubbleMenuElement = createBubbleMenuElement("inline");
371
+ const extensions = [
372
+ EnterHardBreak,
373
+ StarterKit.configure({
374
+ heading: false,
375
+ blockquote: false,
376
+ bulletList: false,
377
+ orderedList: false,
378
+ listItem: false,
379
+ codeBlock: false,
380
+ horizontalRule: false
381
+ }),
382
+ Placeholder.configure({ placeholder: "Click to edit..." })
383
+ ];
384
+ if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
385
+ const editor = new Editor({
386
+ element,
387
+ extensions,
388
+ content,
389
+ editorProps: { attributes: { class: "upstart-editor-active" } },
390
+ onUpdate: () => {
391
+ hasChanged = true;
392
+ },
393
+ onBlur: ({ editor: e }) => {
394
+ if (!hasChanged) return;
395
+ hasChanged = false;
396
+ saveText(hash, e.getHTML().replace(/^<p>([\s\S]*)<\/p>$/, "$1"));
397
+ }
398
+ });
399
+ if (options.bubbleMenu) wireBubbleMenu(bubbleMenuElement, editor);
400
+ return editor;
401
+ }
402
+ function createRichPanelEditor(element, hash, options) {
403
+ const content = element.innerHTML;
404
+ element.innerHTML = "";
405
+ let hasChanged = false;
406
+ const bubbleMenuElement = createBubbleMenuElement("inline");
407
+ const extensions = [StarterKit, Placeholder.configure({ placeholder: options.placeholder })];
408
+ if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
409
+ const editor = new Editor({
410
+ element,
411
+ extensions,
412
+ content,
413
+ editorProps: { attributes: { class: "upstart-editor-active" } },
414
+ onUpdate: () => {
415
+ hasChanged = true;
416
+ },
417
+ onBlur: ({ editor: e }) => {
418
+ if (!hasChanged) return;
419
+ hasChanged = false;
420
+ saveText(hash, e.getHTML());
421
+ }
422
+ });
423
+ if (options.bubbleMenu) wireBubbleMenu(bubbleMenuElement, editor);
424
+ return editor;
425
+ }
255
426
  function getEditorMode(element, options) {
256
427
  const modeOverride = element.dataset.upstartEditableTextMode;
257
- if (modeOverride === "plain" || modeOverride === "inline-rich" || modeOverride === "block-rich") return modeOverride;
428
+ if (modeOverride === "plain" || modeOverride === "inline-rich" || modeOverride === "block-rich" || modeOverride === "direct" || modeOverride === "rich-panel") return modeOverride;
258
429
  const tagName = element.tagName.toLowerCase();
259
430
  if (options.plainTextElements.includes(tagName)) return "plain";
260
431
  if (options.inlineRichTextElements.includes(tagName)) return "inline-rich";
@@ -294,17 +465,29 @@ function syncI18nSiblings(sourceHash) {
294
465
  }
295
466
  function saveText(hash, newText) {
296
467
  try {
297
- const [namespace, key] = (activeEditors.get(hash)?.element.dataset ?? {}).upstartI18n?.split(":") ?? [];
298
- sendToParent({
468
+ const instance = activeEditors.get(hash);
469
+ const dataset = instance?.element.dataset ?? {};
470
+ if (instance?.mode === "direct" || instance?.mode === "rich-panel" || dataset.upstartId) sendToParent({
299
471
  type: "text-edit",
300
472
  payload: {
301
- action: "editText",
302
- content: newText,
303
- namespace,
304
- key,
305
- language: document.documentElement.lang
473
+ action: "editTextDirect",
474
+ id: dataset.upstartId,
475
+ content: newText
306
476
  }
307
477
  });
478
+ else {
479
+ const [namespace, key] = dataset.upstartI18n?.split(":") ?? [];
480
+ sendToParent({
481
+ type: "text-edit",
482
+ payload: {
483
+ action: "editText",
484
+ content: newText,
485
+ namespace,
486
+ key,
487
+ language: document.documentElement.lang
488
+ }
489
+ });
490
+ }
308
491
  console.log("[Upstart Editor] Text save message sent:", hash);
309
492
  } catch (error) {
310
493
  console.error("[Upstart Editor] Failed to send save message:", error);
@@ -317,12 +500,13 @@ function saveText(hash, newText) {
317
500
  function destroyEditor(hash) {
318
501
  const instance = activeEditors.get(hash);
319
502
  if (!instance) return;
320
- const finalContent = instance.mode === "plain" ? instance.editor.getText() : instance.editor.getHTML();
503
+ const isPlainMode = instance.mode === "plain";
504
+ const finalContent = isPlainMode ? instance.editor.getText() : instance.editor.getHTML();
321
505
  instance.editor.destroy();
322
506
  const editorDom = instance.element.querySelector(".ProseMirror");
323
507
  if (editorDom) editorDom.remove();
324
508
  delete instance.element.dataset.upstartEditorActive;
325
- if (instance.mode === "plain") instance.element.textContent = finalContent;
509
+ if (isPlainMode) instance.element.textContent = finalContent;
326
510
  else instance.element.innerHTML = finalContent;
327
511
  restoreStyles(instance.element);
328
512
  activeEditors.delete(hash);