@upstart.gg/vite-plugins 0.1.69 → 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/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/error-handler.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.js +4 -3
- 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/index.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js +2 -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 +20 -14
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +5 -1
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/tests/editor-labels.test.ts +40 -0
- package/src/vite-plugin-upstart-editor/runtime/array-controls.ts +7 -7
- package/src/vite-plugin-upstart-editor/runtime/error-handler.ts +4 -3
- package/src/vite-plugin-upstart-editor/runtime/form-guard.ts +3 -2
- package/src/vite-plugin-upstart-editor/runtime/index.ts +3 -0
- package/src/vite-plugin-upstart-editor/runtime/labels.ts +77 -0
- package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +25 -15
- package/src/vite-plugin-upstart-editor/runtime/types.ts +6 -1
|
@@ -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;
|
|
@@ -405,7 +410,7 @@ function createPlainTextEditor(element, options) {
|
|
|
405
410
|
codeBlock: false,
|
|
406
411
|
horizontalRule: false
|
|
407
412
|
}),
|
|
408
|
-
Placeholder.configure({ placeholder:
|
|
413
|
+
Placeholder.configure({ placeholder: getLabels().clickToEdit })
|
|
409
414
|
],
|
|
410
415
|
content,
|
|
411
416
|
editorProps: { attributes: { class: "upstart-editor-active" } },
|
|
@@ -426,7 +431,7 @@ function createRichTextEditor(element, options) {
|
|
|
426
431
|
element.innerHTML = "";
|
|
427
432
|
let hasChanged = false;
|
|
428
433
|
const bubbleMenuElement = createBubbleMenuElement();
|
|
429
|
-
const extensions = [StarterKit, Placeholder.configure({ placeholder: options
|
|
434
|
+
const extensions = [StarterKit, Placeholder.configure({ placeholder: placeholderFor(options) })];
|
|
430
435
|
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
431
436
|
const editor = new Editor({
|
|
432
437
|
element,
|
|
@@ -465,7 +470,7 @@ function createInlineRichTextEditor(element, options) {
|
|
|
465
470
|
codeBlock: false,
|
|
466
471
|
horizontalRule: false
|
|
467
472
|
}),
|
|
468
|
-
Placeholder.configure({ placeholder:
|
|
473
|
+
Placeholder.configure({ placeholder: getLabels().clickToEdit })
|
|
469
474
|
];
|
|
470
475
|
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
471
476
|
const editor = new Editor({
|
|
@@ -503,7 +508,7 @@ function createDirectEditor(element, options) {
|
|
|
503
508
|
codeBlock: false,
|
|
504
509
|
horizontalRule: false
|
|
505
510
|
}),
|
|
506
|
-
Placeholder.configure({ placeholder:
|
|
511
|
+
Placeholder.configure({ placeholder: getLabels().clickToEdit })
|
|
507
512
|
];
|
|
508
513
|
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
509
514
|
const editor = new Editor({
|
|
@@ -528,7 +533,7 @@ function createRichPanelEditor(element, options) {
|
|
|
528
533
|
element.innerHTML = "";
|
|
529
534
|
let hasChanged = false;
|
|
530
535
|
const bubbleMenuElement = createBubbleMenuElement("inline");
|
|
531
|
-
const extensions = [StarterKit, Placeholder.configure({ placeholder: options
|
|
536
|
+
const extensions = [StarterKit, Placeholder.configure({ placeholder: placeholderFor(options) })];
|
|
532
537
|
if (options.bubbleMenu) extensions.push(BubbleMenu.configure({ element: bubbleMenuElement }));
|
|
533
538
|
const editor = new Editor({
|
|
534
539
|
element,
|
|
@@ -745,41 +750,41 @@ const BUBBLE_MENU_GROUPS = [
|
|
|
745
750
|
{
|
|
746
751
|
command: "bold",
|
|
747
752
|
icon: "format-bold",
|
|
748
|
-
|
|
753
|
+
label: "bold"
|
|
749
754
|
},
|
|
750
755
|
{
|
|
751
756
|
command: "italic",
|
|
752
757
|
icon: "format-italic",
|
|
753
|
-
|
|
758
|
+
label: "italic"
|
|
754
759
|
},
|
|
755
760
|
{
|
|
756
761
|
command: "strike",
|
|
757
762
|
icon: "format-strikethrough",
|
|
758
|
-
|
|
763
|
+
label: "strikethrough"
|
|
759
764
|
},
|
|
760
765
|
{
|
|
761
766
|
command: "code",
|
|
762
767
|
icon: "code-tags",
|
|
763
|
-
|
|
768
|
+
label: "inlineCode"
|
|
764
769
|
}
|
|
765
770
|
],
|
|
766
771
|
[{
|
|
767
772
|
command: "heading",
|
|
768
773
|
icon: "format-header-1",
|
|
769
|
-
|
|
774
|
+
label: "heading"
|
|
770
775
|
}, {
|
|
771
776
|
command: "blockquote",
|
|
772
777
|
icon: "format-quote-close",
|
|
773
|
-
|
|
778
|
+
label: "blockquote"
|
|
774
779
|
}],
|
|
775
780
|
[{
|
|
776
781
|
command: "bulletList",
|
|
777
782
|
icon: "format-list-bulleted",
|
|
778
|
-
|
|
783
|
+
label: "bulletList"
|
|
779
784
|
}, {
|
|
780
785
|
command: "orderedList",
|
|
781
786
|
icon: "format-list-numbered",
|
|
782
|
-
|
|
787
|
+
label: "orderedList"
|
|
783
788
|
}]
|
|
784
789
|
];
|
|
785
790
|
function getIconifyUrl(iconName) {
|
|
@@ -796,7 +801,8 @@ function createBubbleMenuElement(mode = "block") {
|
|
|
796
801
|
separator.style.cssText = "width: 1px; height: 16px; background: rgba(255,255,255,0.2); margin: 0 4px; flex-shrink: 0;";
|
|
797
802
|
menu.appendChild(separator);
|
|
798
803
|
}
|
|
799
|
-
for (const { command, icon,
|
|
804
|
+
for (const { command, icon, label } of group) {
|
|
805
|
+
const title = getLabels()[label];
|
|
800
806
|
const button = document.createElement("button");
|
|
801
807
|
button.type = "button";
|
|
802
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\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: \"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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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PayloadArrayItemAdd, PayloadArrayItemDelete, PayloadArraySet, PayloadEditText, PayloadEditTextDirect } from "../../upstart-editor-api.js";
|
|
2
|
+
import { RuntimeLabels } from "./labels.js";
|
|
2
3
|
import { Editor } from "@tiptap/core";
|
|
3
4
|
|
|
4
5
|
//#region src/vite-plugin-upstart-editor/runtime/types.d.ts
|
|
@@ -158,6 +159,9 @@ type ParentMessage = {
|
|
|
158
159
|
} | {
|
|
159
160
|
type: "deselect";
|
|
160
161
|
key: string;
|
|
162
|
+
} /** Translated labels for the controls the runtime renders (see `runtime/labels.ts`). */ | {
|
|
163
|
+
type: "set-labels";
|
|
164
|
+
labels: Partial<RuntimeLabels>;
|
|
161
165
|
};
|
|
162
166
|
/**
|
|
163
167
|
* Message wrapper sent via postMessage from iframe.
|
|
@@ -233,5 +237,5 @@ interface UpstartEditorPluginOptions {
|
|
|
233
237
|
autoInject?: boolean;
|
|
234
238
|
}
|
|
235
239
|
//#endregion
|
|
236
|
-
export { EditableImageRef, EditorInstance, EditorMessage, EditorMode, ElementBounds, ParentMessage, SelectionRef, TextEditorMode, UpstartEditorMessage, UpstartEditorOptions, UpstartEditorPluginOptions, UpstartParentMessage };
|
|
240
|
+
export { EditableImageRef, EditorInstance, EditorMessage, EditorMode, ElementBounds, ParentMessage, type RuntimeLabels, SelectionRef, TextEditorMode, UpstartEditorMessage, UpstartEditorOptions, UpstartEditorPluginOptions, UpstartParentMessage };
|
|
237
241
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/types.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/types.ts"],"mappings":";;;;;;AAkBA;;;;;KAAY,UAAA;;;;;;UAOK,YAAA;EACf,GAAA;EACA,IAAA;EACA,QAAA;EACA,aAAA;EACA,QAAA;EACA,IAAA;EAiBU;;;;;EAXV,SAAA;AAAA;;;;;;;;;KAWU,cAAA;;AAiBZ;;UAZiB,aAAA;EACf,GAAA;EACA,IAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;EACA,MAAA;AAAA;;;;UAMe,cAAA;EACf,MAAA,EAAQ,MAAA;EACR,OAAA,EAAS,WAAA;EACT,IAAA;EACA,IAAA,EAAM,cAAA;AAAA;;;;;UAOS,gBAAA;EACf,EAAA;EACA,GAAA;EACA,GAAA;EAMA;EAJA,WAAA;EACA,gBAAA;EAOA;EALA,KAAA;EACA,MAAA;EACA,YAAA;EACA,aAAA;EAWU;EATV,OAAA;;EAEA,iBAAA;EACA,eAAA;AAAA;;;;KAMU,aAAA;EAEN,IAAA;EACA,OAAA,EACI,eAAA,GACA,qBAAA,GACA,mBAAA,GACA,sBAAA,GACA,eAAA;AAAA;EAEJ,IAAA;EAAsB,IAAA;AAAA;EACtB,IAAA;EAA0B,IAAA;AAAA;EAE1B,IAAA;EAAsB,OAAA;AAAA;EACtB,IAAA;EAAyB,CAAA;EAAW,CAAA;AAAA;EACpC,IAAA;EAAsB,KAAA;AAAA;;;;;;;EAMtB,IAAA;EAAuB,KAAA;AAAA;EACvB,IAAA;EAA2B,SAAA,EAAW,YAAA;AAAA;EAEtC,IAAA;EACA,IAAA;EACA,aAAA;EACA,QAAA;EACA,gBAAA;EACA,WAAA;EACA,YAAA;EACA,QAAA;EACA,WAAA,GAAc,MAAA;EACd,MAAA,EAAQ,aAAA;EACR,aAAA,WAIyB;EAFzB,OAAA,WAQmB;EANnB,MAAA,GAAS,gBAAA;AAAA;;;;KAMH,aAAA;EACN,IAAA;EAAkB,IAAA,EAAM,UAAA;AAAA;EACxB,IAAA;EAA2B,WAAA;EAAqB,SAAA;EAAmB,UAAA;AAAA;EACnE,IAAA;EAAuB,OAAA;EAAiB,GAAA;AAAA;EAExC,IAAA;EAA6B,OAAA;EAAiB,GAAA;AAAA;EAC9C,IAAA;AAAA;EACA,IAAA;EAAiC,CAAA;EAAW,CAAA;AAAA;EAC5C,IAAA;AAAA;EACA,IAAA;EAAkB,GAAA;AAAA;EAElB,IAAA;EAAoB,MAAA,EAAQ,OAAA,CAAQ,aAAA;AAAA;;;AAc1C;UATiB,oBAAA;EACf,MAAA;EACA,IAAA,EAAM,aAAA;EAAA,CACL,GAAA;AAAA;;;;UAMc,oBAAA;EACf,MAAA;EACA,IAAA,EAAM,aAAA;EAAA,CACL,GAAA;AAAA;;;;UAMc,oBAAA;EA8Bf;;;;EAzBA,gBAAA;EAsCoD;;AAMtD;;;EArCE,sBAAA;EAgDU;;;;EA1CV,iBAAA;;;;;EAMA,UAAA;;;;;EAMA,WAAA;;;;;EAMA,aAAA;;;;;;EAOA,kBAAA,IAAsB,SAAA,UAAmB,GAAA;AAAA;;;;UAM1B,0BAAA;;;;;EAKf,OAAA;;;;;EAMA,UAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upstart.gg/vite-plugins",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.70",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"oxc-parser": "0.101.0",
|
|
24
24
|
"unplugin": "^2.3.11",
|
|
25
25
|
"zimmerframe": "^1.1.4",
|
|
26
|
-
"@upstart.gg/sdk": "^0.1.
|
|
26
|
+
"@upstart.gg/sdk": "^0.1.70"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@rolldown/binding-linux-arm64-gnu": "1.0.0",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
"peerDependencies": {
|
|
120
120
|
"zod": "4.3.6",
|
|
121
|
-
"@upstart.gg/sdk": "^0.1.
|
|
121
|
+
"@upstart.gg/sdk": "^0.1.70"
|
|
122
122
|
},
|
|
123
123
|
"author": "Upstart",
|
|
124
124
|
"publishConfig": {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_LABELS,
|
|
4
|
+
getLabels,
|
|
5
|
+
resetLabels,
|
|
6
|
+
setLabels,
|
|
7
|
+
} from "../vite-plugin-upstart-editor/runtime/labels";
|
|
8
|
+
|
|
9
|
+
describe("editor runtime labels", () => {
|
|
10
|
+
afterEach(() => resetLabels());
|
|
11
|
+
|
|
12
|
+
test("starts with the English defaults", () => {
|
|
13
|
+
expect(getLabels()).toEqual(DEFAULT_LABELS);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("overrides only the provided keys", () => {
|
|
17
|
+
setLabels({ addItem: "Ajouter un élément", clickToEdit: "Cliquez pour modifier..." });
|
|
18
|
+
expect(getLabels().addItem).toBe("Ajouter un élément");
|
|
19
|
+
expect(getLabels().clickToEdit).toBe("Cliquez pour modifier...");
|
|
20
|
+
expect(getLabels().removeItem).toBe(DEFAULT_LABELS.removeItem);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("ignores unknown keys, empty strings and non-string values", () => {
|
|
24
|
+
setLabels({ addItem: "", removeItem: 42, unknown: "x" });
|
|
25
|
+
expect(getLabels()).toEqual(DEFAULT_LABELS);
|
|
26
|
+
expect("unknown" in getLabels()).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("ignores malformed payloads", () => {
|
|
30
|
+
setLabels(null);
|
|
31
|
+
setLabels("labels");
|
|
32
|
+
expect(getLabels()).toEqual(DEFAULT_LABELS);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("resetLabels restores the defaults", () => {
|
|
36
|
+
setLabels({ refresh: "Rafraîchir" });
|
|
37
|
+
resetLabels();
|
|
38
|
+
expect(getLabels().refresh).toBe(DEFAULT_LABELS.refresh);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getCurrentMode } from "./state.js";
|
|
2
2
|
import { sendToParent } from "./utils.js";
|
|
3
|
+
import { getLabels } from "./labels.js";
|
|
3
4
|
|
|
4
5
|
// Editor-only controls for inline-array .map() lists. Add/delete are staged LOCALLY
|
|
5
6
|
// (no server round-trip per action): "×" marks an item for removal, "+" inserts an
|
|
@@ -7,7 +8,6 @@ import { sendToParent } from "./utils.js";
|
|
|
7
8
|
// via a single `arraySet` edit + rebuild. Per-item text editing stays live; arraySet
|
|
8
9
|
// reads the current item texts from the DOM so live edits are preserved.
|
|
9
10
|
|
|
10
|
-
const DEFAULT_NEW_ITEM = "New item";
|
|
11
11
|
const DRAFT_ATTR = "data-upstart-draft-item";
|
|
12
12
|
|
|
13
13
|
// Layout of the "+"/"✓" stack placed to the right of a list.
|
|
@@ -299,7 +299,7 @@ function makeItemDeleteButton(
|
|
|
299
299
|
): HTMLButtonElement {
|
|
300
300
|
const btn = baseButton(rect.top + window.scrollY - 8, rect.right + window.scrollX - 8, 16, "#ef4444");
|
|
301
301
|
setIcon(btn, marked ? "restore" : "cross", 10);
|
|
302
|
-
btn.title = marked ?
|
|
302
|
+
btn.title = marked ? getLabels().keepItem : getLabels().removeItem;
|
|
303
303
|
btn.addEventListener("click", (e) => {
|
|
304
304
|
e.preventDefault();
|
|
305
305
|
e.stopPropagation();
|
|
@@ -316,7 +316,7 @@ function makeItemDeleteButton(
|
|
|
316
316
|
function makeDraftRemoveButton(pill: HTMLElement, rect: DOMRect): HTMLButtonElement {
|
|
317
317
|
const btn = baseButton(rect.top + window.scrollY - 8, rect.right + window.scrollX - 8, 16, "#ef4444");
|
|
318
318
|
setIcon(btn, "cross", 10);
|
|
319
|
-
btn.title =
|
|
319
|
+
btn.title = getLabels().removeItem;
|
|
320
320
|
btn.addEventListener("click", (e) => {
|
|
321
321
|
e.preventDefault();
|
|
322
322
|
e.stopPropagation();
|
|
@@ -329,7 +329,7 @@ function makeDraftRemoveButton(pill: HTMLElement, rect: DOMRect): HTMLButtonElem
|
|
|
329
329
|
function makeAddButton(arrayId: string, top: number, left: number): HTMLButtonElement {
|
|
330
330
|
const btn = baseButton(top, left, 22, "#7270c6");
|
|
331
331
|
setIcon(btn, "plus", 14);
|
|
332
|
-
btn.title =
|
|
332
|
+
btn.title = getLabels().addItem;
|
|
333
333
|
btn.addEventListener("click", (e) => {
|
|
334
334
|
e.preventDefault();
|
|
335
335
|
e.stopPropagation();
|
|
@@ -341,7 +341,7 @@ function makeAddButton(arrayId: string, top: number, left: number): HTMLButtonEl
|
|
|
341
341
|
function makeApplyButton(arrayId: string, top: number, left: number): HTMLButtonElement {
|
|
342
342
|
const btn = baseButton(top, left, 22, "#16a34a");
|
|
343
343
|
setIcon(btn, "check", 13);
|
|
344
|
-
btn.title =
|
|
344
|
+
btn.title = getLabels().applyChanges;
|
|
345
345
|
btn.addEventListener("click", (e) => {
|
|
346
346
|
e.preventDefault();
|
|
347
347
|
e.stopPropagation();
|
|
@@ -357,7 +357,7 @@ function makeDragHandle(arrayId: string, node: HTMLElement, rect: DOMRect): HTML
|
|
|
357
357
|
const left = rect.left + window.scrollX + rect.width / 2 - 8;
|
|
358
358
|
const btn = baseButton(top, left, 16, "#6b7280");
|
|
359
359
|
setIcon(btn, "grip", 12);
|
|
360
|
-
btn.title =
|
|
360
|
+
btn.title = getLabels().dragToReorder;
|
|
361
361
|
btn.style.cursor = "grab";
|
|
362
362
|
btn.addEventListener("pointerdown", (e) => {
|
|
363
363
|
e.preventDefault();
|
|
@@ -430,7 +430,7 @@ function addDraftPill(arrayId: string): void {
|
|
|
430
430
|
pill.removeAttribute("style");
|
|
431
431
|
pill.setAttribute(DRAFT_ATTR, arrayId);
|
|
432
432
|
pill.setAttribute("contenteditable", "true");
|
|
433
|
-
pill.textContent =
|
|
433
|
+
pill.textContent = getLabels().newItem;
|
|
434
434
|
|
|
435
435
|
// Insert after the current last pill of this array (drafts go at the end).
|
|
436
436
|
const last = draftPillsFor(arrayId).pop() ?? originalItemsFor(arrayId).pop();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sendToParent } from "./utils.js";
|
|
2
|
+
import { getLabels } from "./labels.js";
|
|
2
3
|
|
|
3
4
|
/** Errors thrown by React's DOM reconciliation once the editor mutated its nodes. */
|
|
4
5
|
const RECONCILIATION_ERROR_RE =
|
|
@@ -24,14 +25,14 @@ export function showRecoveryOverlay(): void {
|
|
|
24
25
|
"max-width: 24rem; margin: 1rem; padding: 1.5rem; border-radius: 0.75rem; background: #fff; " +
|
|
25
26
|
"box-shadow: 0 20px 50px rgba(0,0,0,0.3); text-align: center;";
|
|
26
27
|
const title = document.createElement("p");
|
|
27
|
-
title.textContent =
|
|
28
|
+
title.textContent = getLabels().recoveryTitle;
|
|
28
29
|
title.style.cssText = "margin: 0 0 0.5rem; font-size: 1.1rem; font-weight: 600;";
|
|
29
30
|
const text = document.createElement("p");
|
|
30
|
-
text.textContent =
|
|
31
|
+
text.textContent = getLabels().recoveryText;
|
|
31
32
|
text.style.cssText = "margin: 0 0 1.25rem; font-size: 0.9rem; color: #555;";
|
|
32
33
|
const button = document.createElement("button");
|
|
33
34
|
button.type = "button";
|
|
34
|
-
button.textContent =
|
|
35
|
+
button.textContent = getLabels().refresh;
|
|
35
36
|
button.style.cssText =
|
|
36
37
|
"padding: 0.6rem 1.4rem; border: 0; border-radius: 9999px; background: #7270c6; color: #fff; " +
|
|
37
38
|
"font-weight: 600; font-size: 0.95rem; cursor: pointer;";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { getLabels } from "./labels.js";
|
|
2
|
+
|
|
1
3
|
let isInitialized = false;
|
|
2
4
|
|
|
3
5
|
// Methods that don't mutate data and must keep working: GET forms (search /
|
|
4
6
|
// navigation) and method="dialog" (e.g. a modal's close/backdrop buttons).
|
|
5
7
|
const SAFE_METHODS = new Set(["get", "dialog"]);
|
|
6
8
|
|
|
7
|
-
const TOOLTIP_MESSAGE = "This action isn’t available in the editor preview";
|
|
8
9
|
const TOOLTIP_DURATION_MS = 2500;
|
|
9
10
|
|
|
10
11
|
// Popover API renders in the browser top layer, so the tooltip sits above modal
|
|
@@ -99,7 +100,7 @@ function ensureTooltip(): HTMLDivElement {
|
|
|
99
100
|
if (tooltip) return tooltip;
|
|
100
101
|
tooltip = document.createElement("div");
|
|
101
102
|
tooltip.id = "upstart-form-guard-tooltip";
|
|
102
|
-
tooltip.textContent =
|
|
103
|
+
tooltip.textContent = getLabels().formGuard;
|
|
103
104
|
|
|
104
105
|
let css =
|
|
105
106
|
"background: #111827; color: #fff; font-size: 12px; line-height: 1.3; " +
|
|
@@ -7,6 +7,7 @@ import { initArrayControls, refreshArrayControls, hideArrayControls } from "./ar
|
|
|
7
7
|
import { initFormGuard } from "./form-guard.js";
|
|
8
8
|
import { sendToParent } from "./utils.js";
|
|
9
9
|
import { getCurrentMode, setCurrentMode, clearSelection, removeSelection, getSelection } from "./state.js";
|
|
10
|
+
import { setLabels } from "./labels.js";
|
|
10
11
|
import type { EditorMode, UpstartParentMessage } from "./types.js";
|
|
11
12
|
|
|
12
13
|
let isInitialized = false;
|
|
@@ -248,6 +249,8 @@ function handleParentMessage(event: MessageEvent): void {
|
|
|
248
249
|
const el = removeSelection(message.key);
|
|
249
250
|
el?.removeAttribute("data-upstart-selected");
|
|
250
251
|
sendToParent({ type: "selection-changed", selection: getSelection() });
|
|
252
|
+
} else if (message.type === "set-labels") {
|
|
253
|
+
setLabels(message.labels);
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
|