{"version":3,"file":"classic-editor.es.mjs","names":[],"sources":["../src/classic-editor/isClassicEditorPage.ts","../src/classic-editor/classicPreviewCss.ts","../src/classic-editor/writeWidgetAttributes.ts","../src/classic-editor/commitWidgetAttributes.ts","../src/classic-editor/readWidgetAttributes.ts","../src/classic-editor/openClassicConfigModal.ts","../src/classic-editor/LimitedEditorBanner.ts","../src/classic-editor/renderClassicPreview.ts","../src/classic-editor/createClassicPreviewElementClass.ts","../src/classic-editor/isTinyMceEditorIframe.ts","../src/classic-editor/registerWidgetInIframe.ts","../src/classic-editor/installClassicEditorSupport.ts"],"sourcesContent":["/**\n * Detects Staffbase's Classic Editor URL gate (?isClassic=true). Classic-editor\n * support runs ONLY here: the modern Studio editor and the on-page editor define\n * the widget themselves, so running there would double-define the element and\n * could break save.\n * @returns {boolean} True when the current page is the Classic Editor.\n */\nexport const isClassicEditorPage = (): boolean => {\n  try {\n    return (\n      new URLSearchParams(window.location.search).get('isClassic') === 'true'\n    )\n  } catch {\n    return false\n  }\n}\n","/**\n * Extra CSS injected into the classic-editor preview shadow root (on top of the\n * widget's own styles). Makes the preview click-through so double-clicks reach\n * the host element (which opens the config dialog), shows a pointer cursor, and\n * adds a bottom margin so the preview does not collide with following content.\n */\nexport const classicPreviewCss = `\n:host { cursor: pointer; display: block; margin-bottom: 12px; }\n:host > * { pointer-events: none; user-select: none; -webkit-user-select: none; }\n`\n","/**\n * Writes an edited attribute map back onto a placed widget element as kebab-case\n * attributes, removing the legacy snake_case twin of each so the saved tag stays\n * canonical and tinymce serializes a single clean form.\n * @param {HTMLElement} element - The widget element to update.\n * @param {Record<string, string>} values - The edited attribute map (kebab keys).\n * @returns {void}\n */\nexport const writeWidgetAttributes = (\n  element: HTMLElement,\n  values: Record<string, string>,\n): void => {\n  for (const [name, value] of Object.entries(values)) {\n    element.removeAttribute(name.replace(/-/g, '_'))\n    element.setAttribute(name, value)\n  }\n}\n","import type { MinimalTinyClassicEditor } from './types/MinimalTinyClassicEditor'\nimport { writeWidgetAttributes } from './writeWidgetAttributes'\n\n/**\n * Applies an edited attribute map to a placed widget so it persists on\n * \"Guardar\". The write runs inside a tinymce undo transaction (a tracked,\n * undoable edit), then Staffbase's richTextField._registerChange re-diffs the\n * content and flips its per-locale dirty flag — without that flag the classic\n * editor treats a programmatic attribute change as no change and saves nothing.\n * The tinymce global lives on the top window where the bundle runs; falls back\n * to a plain write if the editor APIs are unavailable.\n * @param {HTMLElement} element - The widget element to update.\n * @param {Record<string, string>} values - The edited attribute map.\n * @returns {void}\n */\nexport const commitWidgetAttributes = (\n  element: HTMLElement,\n  values: Record<string, string>,\n): void => {\n  const editor = (\n    window as unknown as {\n      tinymce?: { activeEditor?: MinimalTinyClassicEditor }\n    }\n  ).tinymce?.activeEditor\n\n  /**\n   *\n   */\n  const write = (): void => {\n    writeWidgetAttributes(element, values)\n  }\n\n  if (editor?.undoManager?.transact) editor.undoManager.transact(write)\n  else write()\n\n  editor?.settings?.richTextField?._registerChange?.()\n  editor?.setDirty?.(true)\n  editor?.nodeChanged?.()\n}\n","/**\n * Reads the widget's configurable attributes off a placed element into a plain\n * map. Each name is read kebab-first with a snake_case fallback so already-saved\n * widgets (which may carry either form) read correctly. Absent attributes are\n * omitted.\n * @param {HTMLElement} element - The placed widget element.\n * @param {string[]} attributes - The attribute names to read (kebab-case).\n * @returns {Record<string, string>} The current attribute map.\n */\nexport const readWidgetAttributes = (\n  element: HTMLElement,\n  attributes: string[],\n): Record<string, string> => {\n  const values: Record<string, string> = {}\n  for (const name of attributes) {\n    const value =\n      element.getAttribute(name) ??\n      element.getAttribute(name.replace(/-/g, '_'))\n    if (value !== null) values[name] = value\n  }\n  return values\n}\n","import { createRoot } from 'react-dom/client'\n\nimport { commitWidgetAttributes } from './commitWidgetAttributes'\nimport { readWidgetAttributes } from './readWidgetAttributes'\nimport type { ClassicEditorSupportConfig } from './types/ClassicEditorSupportConfig'\n\n/**\n * Opens the widget's configuration form for a placed element. Mounts the form\n * (provided by the widget via config.renderConfigForm) into the top document via\n * the bundle's own React; on submit, writes the edited attributes back onto the\n * element and persists them. A fixed host id guards against stacking dialogs.\n * @param {HTMLElement} element - The placed widget element to configure.\n * @param {ClassicEditorSupportConfig} config - The widget's support config.\n * @returns {void}\n */\nexport const openClassicConfigModal = (\n  element: HTMLElement,\n  config: ClassicEditorSupportConfig,\n): void => {\n  const hostId = `sbu-classic-config-${config.element}`\n  if (window.document.getElementById(hostId)) return\n\n  const host = window.document.createElement('div')\n  host.id = hostId\n  window.document.body.appendChild(host)\n  const root = createRoot(host)\n\n  /**\n   *\n   */\n  const close = (): void => {\n    root.unmount()\n    host.remove()\n  }\n\n  root.render(\n    config.renderConfigForm({\n      initial: readWidgetAttributes(element, config.attributes),\n      onCancel: close,\n      /**\n       *\n       * @param values\n       */\n      onSubmit: (values) => {\n        commitWidgetAttributes(element, values)\n        close()\n      },\n    }),\n  )\n}\n","import { createElement, type CSSProperties, type ReactElement } from 'react'\n\n/**\n * Warning shown above the classic-editor preview. The classic editor only shows\n * a read-only preview for externally-hosted widgets, so this tells editors the\n * surface is limited and how to edit (double-click for the config dialog). Built\n * with createElement (no JSX) to avoid a JSX-runtime build coupling.\n * @returns {ReactElement} The warning banner.\n */\nexport const LimitedEditorBanner = (): ReactElement => {\n  const bannerStyle: CSSProperties = {\n    display: 'flex',\n    alignItems: 'center',\n    gap: '6px',\n    margin: '0 0 4px',\n    padding: '4px 8px',\n    background: '#fff7e6',\n    border: '1px solid #ffe1a8',\n    borderRadius: '3px',\n    color: '#8a6d3b',\n    font: '600 11px/1.3 Helvetica, Arial, sans-serif',\n  }\n\n  return createElement(\n    'div',\n    { role: 'note', style: bannerStyle },\n    createElement('span', { 'aria-hidden': 'true' }, '⚠'),\n    createElement(\n      'span',\n      null,\n      'Limited editor — double-click to configure (full preview in Studio / live page)',\n    ),\n  )\n}\n","import { createElement, Fragment } from 'react'\nimport type { Root } from 'react-dom/client'\n\nimport type { ShadowMount } from '../types/shadow/ShadowMount'\nimport { LimitedEditorBanner } from './LimitedEditorBanner'\nimport type { ClassicEditorSupportConfig } from './types/ClassicEditorSupportConfig'\n\n/**\n * Renders the limited-editor warning plus the widget's own editor-preview view\n * into the placed element's shadow mount, using the bundle's React root. The\n * widget's preview lives in the shadow root and is never serialized on save.\n * Built with createElement (no JSX) to avoid a JSX-runtime build coupling.\n * @param {Root} root - The bundle's React root for this element.\n * @param {ShadowMount} mount - The element's shadow mount.\n * @param {HTMLElement} element - The placed widget element.\n * @param {ClassicEditorSupportConfig} config - The widget's support config.\n * @returns {void}\n */\nexport const renderClassicPreview = (\n  root: Root,\n  mount: ShadowMount,\n  element: HTMLElement,\n  config: ClassicEditorSupportConfig,\n): void => {\n  root.render(\n    createElement(\n      Fragment,\n      null,\n      createElement(LimitedEditorBanner),\n      config.renderEditorPreview({\n        /**\n         *\n         * @param name\n         */\n        getAttribute: (name) => element.getAttribute(name),\n        mount,\n      }),\n    ),\n  )\n}\n","import { createRoot, type Root } from 'react-dom/client'\n\nimport { ensureShadowMount } from '../shadow/react/ensureShadowMount'\nimport type { ShadowMount } from '../types/shadow/ShadowMount'\nimport { classicPreviewCss } from './classicPreviewCss'\nimport { openClassicConfigModal } from './openClassicConfigModal'\nimport { renderClassicPreview } from './renderClassicPreview'\nimport type { ClassicEditorSupportConfig } from './types/ClassicEditorSupportConfig'\n\n/**\n * Builds the preview custom-element class scoped to one iframe's realm. Custom\n * elements must extend the HTMLElement of the realm whose registry defines them,\n * so the iframe's own HTMLElement is used as the base. The element renders the\n * widget's preview (in a shadow mount) and opens the config dialog on\n * double-click.\n * @param {Window} win - The iframe window whose realm the class belongs to.\n * @param {ClassicEditorSupportConfig} config - The widget's support config.\n * @returns {CustomElementConstructor} The realm-bound preview element class.\n */\nexport const createClassicPreviewElementClass = (\n  win: Window,\n  config: ClassicEditorSupportConfig,\n): CustomElementConstructor => {\n  const RealmHTMLElement = (win as Window & typeof globalThis).HTMLElement\n\n  return class ClassicPreviewElement extends RealmHTMLElement {\n    private mount: ShadowMount | undefined\n    private root: Root | undefined\n\n    /**\n     *\n     */\n    public static get observedAttributes(): string[] {\n      return config.attributes\n    }\n\n    /**\n     *\n     */\n    public connectedCallback(): void {\n      this.mount ??= ensureShadowMount(this, {\n        cacheKey: `${config.element}-classic`,\n        cssText: `${config.widgetCss}\\n${classicPreviewCss}`,\n      })\n      this.root ??= createRoot(this.mount.reactMountEl)\n      this.renderPreview()\n      this.addEventListener('dblclick', this.handleEdit)\n    }\n\n    /**\n     *\n     */\n    public attributeChangedCallback(): void {\n      this.renderPreview()\n    }\n\n    /**\n     *\n     */\n    public disconnectedCallback(): void {\n      this.root?.unmount()\n      this.root = undefined\n      this.mount = undefined\n      this.removeEventListener('dblclick', this.handleEdit)\n    }\n\n    /**\n     *\n     */\n    private readonly renderPreview = (): void => {\n      if (this.root && this.mount) {\n        renderClassicPreview(this.root, this.mount, this, config)\n      }\n    }\n\n    /**\n     *\n     */\n    private readonly handleEdit = (): void => {\n      openClassicConfigModal(this, config)\n    }\n  }\n}\n","/**\n * Detects iframes hosting the classic editor's tinymce canvas. Support must be\n * registered ONLY there: every other same-origin iframe (the page-preview\n * iframe, Studio's device simulator) loads the real bundle and must keep its\n * registry free, or the host's defineBlock loses the race and the live widget\n * cannot mount (which can also corrupt content on save).\n *\n * tinymce marks its canvas iframe with the `tox-edit-area__iframe` class; the\n * `mce_<uid>_ifr` id pattern is the fallback across tinymce versions.\n * @param {HTMLIFrameElement} iframe - The iframe to test.\n * @returns {boolean} True when the iframe is a tinymce editor canvas.\n */\nexport const isTinyMceEditorIframe = (iframe: HTMLIFrameElement): boolean =>\n  iframe.classList.contains('tox-edit-area__iframe') ||\n  /^mce_[a-z0-9]+_ifr$/i.test(iframe.id)\n","import { createClassicPreviewElementClass } from './createClassicPreviewElementClass'\nimport { isTinyMceEditorIframe } from './isTinyMceEditorIframe'\nimport type { ClassicEditorSupportConfig } from './types/ClassicEditorSupportConfig'\n\n/**\n * Registers the preview element in one same-origin tinymce editor iframe's\n * customElements registry. Idempotent and silent on cross-origin iframes.\n * @param {HTMLIFrameElement} iframe - The candidate iframe.\n * @param {ClassicEditorSupportConfig} config - The widget's support config.\n * @returns {void}\n */\nexport const registerWidgetInIframe = (\n  iframe: HTMLIFrameElement,\n  config: ClassicEditorSupportConfig,\n): void => {\n  if (!isTinyMceEditorIframe(iframe)) return\n  const win = iframe.contentWindow\n  if (!win || win === window) return\n  try {\n    // Touch the document to surface a cross-origin SecurityError here.\n    void win.document\n  } catch {\n    return\n  }\n  const registry = win.customElements\n  if (!registry || registry.get(config.element)) return\n  try {\n    registry.define(\n      config.element,\n      createClassicPreviewElementClass(win, config),\n    )\n  } catch {\n    // A concurrent define by the host, or a torn-down iframe: nothing to do.\n  }\n}\n","import { isClassicEditorPage } from './isClassicEditorPage'\nimport { registerWidgetInIframe } from './registerWidgetInIframe'\nimport type { ClassicEditorSupportConfig } from './types/ClassicEditorSupportConfig'\n\n/**\n * Makes an externally-hosted Staffbase widget usable in the classic page editor.\n *\n * The classic editor runs the bundle in the top window but never defines the\n * custom element inside its tinymce canvas iframe, so placed elements stay inert\n * there and have no edit affordance. Call this once from the bundle entry (after\n * defineBlock). It watches for tinymce canvas iframes and, in each, defines a\n * preview element that renders the widget's editor view (with a limited-editor\n * warning) and opens the widget's config form on double-click, persisting edits\n * via Staffbase's classic-editor save model.\n *\n * No-op outside the classic editor (?isClassic=true) and on cross-origin iframes.\n * @param {ClassicEditorSupportConfig} config - The widget's support config.\n * @returns {void}\n */\nexport const installClassicEditorSupport = (\n  config: ClassicEditorSupportConfig,\n): void => {\n  if (!isClassicEditorPage()) return\n\n  /**\n   *\n   * @param iframe\n   */\n  const register = (iframe: HTMLIFrameElement): void => {\n    registerWidgetInIframe(iframe, config)\n  }\n\n  document.querySelectorAll('iframe').forEach(register)\n\n  const observer = new MutationObserver((records) => {\n    for (const record of records) {\n      for (const node of record.addedNodes) {\n        if (node instanceof HTMLIFrameElement) {\n          register(node)\n          node.addEventListener('load', () => register(node), { once: true })\n        } else if (node instanceof Element) {\n          node.querySelectorAll('iframe').forEach(register)\n        }\n      }\n    }\n  })\n  observer.observe(document.documentElement, { childList: true, subtree: true })\n}\n"],"mappings":"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"}