@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.
Files changed (28) hide show
  1. package/dist/vite-plugin-upstart-editor/runtime/array-controls.js +7 -7
  2. package/dist/vite-plugin-upstart-editor/runtime/array-controls.js.map +1 -1
  3. package/dist/vite-plugin-upstart-editor/runtime/error-handler.d.ts.map +1 -1
  4. package/dist/vite-plugin-upstart-editor/runtime/error-handler.js +4 -3
  5. package/dist/vite-plugin-upstart-editor/runtime/error-handler.js.map +1 -1
  6. package/dist/vite-plugin-upstart-editor/runtime/form-guard.js +2 -2
  7. package/dist/vite-plugin-upstart-editor/runtime/form-guard.js.map +1 -1
  8. package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -1
  9. package/dist/vite-plugin-upstart-editor/runtime/index.js +2 -1
  10. package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
  11. package/dist/vite-plugin-upstart-editor/runtime/labels.d.ts +32 -0
  12. package/dist/vite-plugin-upstart-editor/runtime/labels.d.ts.map +1 -0
  13. package/dist/vite-plugin-upstart-editor/runtime/labels.js +45 -0
  14. package/dist/vite-plugin-upstart-editor/runtime/labels.js.map +1 -0
  15. package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -1
  16. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +20 -14
  17. package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
  18. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +5 -1
  19. package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
  20. package/package.json +3 -3
  21. package/src/tests/editor-labels.test.ts +40 -0
  22. package/src/vite-plugin-upstart-editor/runtime/array-controls.ts +7 -7
  23. package/src/vite-plugin-upstart-editor/runtime/error-handler.ts +4 -3
  24. package/src/vite-plugin-upstart-editor/runtime/form-guard.ts +3 -2
  25. package/src/vite-plugin-upstart-editor/runtime/index.ts +3 -0
  26. package/src/vite-plugin-upstart-editor/runtime/labels.ts +77 -0
  27. package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +25 -15
  28. 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: "Click to edit..." })
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.placeholder })];
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: "Click to edit..." })
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: "Click to edit..." })
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.placeholder })];
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
- title: "Bold"
753
+ label: "bold"
749
754
  },
750
755
  {
751
756
  command: "italic",
752
757
  icon: "format-italic",
753
- title: "Italic"
758
+ label: "italic"
754
759
  },
755
760
  {
756
761
  command: "strike",
757
762
  icon: "format-strikethrough",
758
- title: "Strikethrough"
763
+ label: "strikethrough"
759
764
  },
760
765
  {
761
766
  command: "code",
762
767
  icon: "code-tags",
763
- title: "Inline Code"
768
+ label: "inlineCode"
764
769
  }
765
770
  ],
766
771
  [{
767
772
  command: "heading",
768
773
  icon: "format-header-1",
769
- title: "Heading"
774
+ label: "heading"
770
775
  }, {
771
776
  command: "blockquote",
772
777
  icon: "format-quote-close",
773
- title: "Blockquote"
778
+ label: "blockquote"
774
779
  }],
775
780
  [{
776
781
  command: "bulletList",
777
782
  icon: "format-list-bulleted",
778
- title: "Bullet List"
783
+ label: "bulletList"
779
784
  }, {
780
785
  command: "orderedList",
781
786
  icon: "format-list-numbered",
782
- title: "Ordered List"
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, title } of group) {
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":";;;;;;AAeA;;;;KAAY,UAAA;AAOZ;;;;;AAAA,UAAiB,YAAA;EACf,GAAA;EACA,IAAA;EACA,QAAA;EACA,aAAA;EACA,QAAA;EACA,IAAA;EAMS;AAWX;;;;EAXE,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;EAKA;EAHA,WAAA;EACA,gBAAA;EAKA;EAHA,KAAA;EACA,MAAA;EACA,YAAA;EACA,aAAA;EAKe;EAHf,OAAA;EASuB;EAPvB,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,WAQM;EANN,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;AAcxB;;;AAAA,UATiB,oBAAA;EACf,MAAA;EACA,IAAA,EAAM,aAAA;EAAA,CACL,GAAA;AAAA;;;AAeH;UATiB,oBAAA;EACf,MAAA;EACA,IAAA,EAAM,aAAA;EAAA,CACL,GAAA;AAAA;;;;UAMc,oBAAA;EA2Cf;;;;EAtCA,gBAAA;EA4Ce;;;;;EArCf,sBAAA;;;;;EAMA,iBAAA;;;;;EAMA,UAAA;;;;;EAMA,WAAA;;;;;EAMA,aAAA;;;;;;EAOA,kBAAA,IAAsB,SAAA,UAAmB,GAAA;AAAA;;;;UAM1B,0BAAA;;;;;EAKf,OAAA;;;;;EAMA,UAAA;AAAA"}
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.69",
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.69"
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.69"
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 ? "Keep item" : "Remove item";
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 = "Remove item";
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 = "Add item";
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 = "Apply changes";
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 = "Drag to reorder";
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 = DEFAULT_NEW_ITEM;
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 = "The preview needs a refresh";
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 = "Your changes are saved. Refresh the preview to keep editing.";
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 = "Refresh";
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 = TOOLTIP_MESSAGE;
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