@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.
- package/dist/upstart-editor-api.d.ts +13 -1
- package/dist/upstart-editor-api.d.ts.map +1 -1
- package/dist/upstart-editor-api.js +59 -1
- package/dist/upstart-editor-api.js.map +1 -1
- package/dist/vite-plugin-upstart-attrs.d.ts +12 -7
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-attrs.js +195 -3
- package/dist/vite-plugin-upstart-attrs.js.map +1 -1
- package/dist/vite-plugin-upstart-branding/plugin.d.ts +3 -3
- package/dist/vite-plugin-upstart-branding/plugin.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-branding/plugin.js.map +1 -1
- package/dist/vite-plugin-upstart-branding/runtime.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/plugin.d.ts +3 -3
- package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/plugin.js +4 -1
- package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +27 -16
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js +14 -2
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
- package/dist/{src/vite-plugin-upstart-editor → vite-plugin-upstart-editor}/runtime/state.d.ts +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/state.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/state.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 +212 -28
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +16 -3
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/utils.js.map +1 -1
- package/dist/vite-plugin-upstart-theme.d.ts +3 -3
- package/dist/vite-plugin-upstart-theme.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-theme.js +2 -2
- package/dist/vite-plugin-upstart-theme.js.map +1 -1
- package/package.json +7 -7
- package/src/tests/vite-plugin-upstart-attrs.test.ts +298 -37
- package/src/upstart-editor-api.ts +71 -0
- package/src/vite-plugin-upstart-attrs.ts +293 -5
- package/src/vite-plugin-upstart-editor/plugin.ts +11 -1
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +35 -21
- package/src/vite-plugin-upstart-editor/runtime/index.ts +21 -1
- package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +260 -41
- package/src/vite-plugin-upstart-editor/runtime/types.ts +17 -4
- package/src/vite-plugin-upstart-theme.ts +4 -1
- package/dist/src/vite-plugin-upstart-editor/runtime/state.d.ts.map +0 -1
- package/dist/src/vite-plugin-upstart-editor/runtime/state.js.map +0 -1
- /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":";;AAGA,IAAI,UAAiC;AACrC,IAAI,gBAAoC;AACxC,IAAI,YAAgC;AACpC,IAAI,gBAAgB;AACpB,IAAI,QAAuB;;;;AAK3B,SAAgB,mBAAyB;
|
|
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 "
|
|
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 "
|
|
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
|
-
|
|
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":";;;;;;;AAQA,IAAI,gBAAgB;;;;AAOpB,SAAgB,QAAQ,MAAwB;
|
|
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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"}
|
|
@@ -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":";;;;;
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
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
|
-
|
|
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: (
|
|
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: (
|
|
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: (
|
|
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
|
|
298
|
-
|
|
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: "
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
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 (
|
|
509
|
+
if (isPlainMode) instance.element.textContent = finalContent;
|
|
326
510
|
else instance.element.innerHTML = finalContent;
|
|
327
511
|
restoreStyles(instance.element);
|
|
328
512
|
activeEditors.delete(hash);
|