@duffcloudservices/cms 0.1.7 → 0.2.0

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.
@@ -537,16 +537,38 @@ function initEditorBridge() {
537
537
  monitorNavigation();
538
538
  document.addEventListener("contextmenu", (e) => {
539
539
  e.preventDefault();
540
- const sectionEl = e.target.closest?.("[data-section]");
540
+ const target = e.target;
541
+ const sectionEl = target.closest?.("[data-section]");
542
+ const imgEl = target.tagName === "IMG" ? target : target.closest?.("picture")?.querySelector("img");
541
543
  postToParent("dcs:contextmenu", {
542
544
  x: e.clientX,
543
545
  y: e.clientY,
544
546
  sectionId: sectionEl?.dataset.section ?? null,
545
- sectionLabel: sectionEl?.dataset.sectionLabel ?? null
547
+ sectionLabel: sectionEl?.dataset.sectionLabel ?? null,
548
+ // Image context (null when not right-clicking on an image)
549
+ imageUrl: imgEl?.currentSrc ?? imgEl?.src ?? null,
550
+ imageAlt: imgEl?.alt ?? null,
551
+ imageWidth: imgEl?.naturalWidth ?? null,
552
+ imageHeight: imgEl?.naturalHeight ?? null
546
553
  });
547
554
  }, true);
548
- document.addEventListener("click", () => {
555
+ document.addEventListener("click", (e) => {
549
556
  postToParent("dcs:click", {});
557
+ if (editorActive && editingEnabled) {
558
+ const target = e.target;
559
+ const imgEl = target.tagName === "IMG" ? target : target.closest?.("picture")?.querySelector("img");
560
+ if (imgEl) {
561
+ const sectionEl = target.closest?.("[data-section]");
562
+ postToParent("dcs:image-click", {
563
+ imageUrl: imgEl.currentSrc ?? imgEl.src,
564
+ imageAlt: imgEl.alt ?? null,
565
+ imageWidth: imgEl.naturalWidth,
566
+ imageHeight: imgEl.naturalHeight,
567
+ sectionId: sectionEl?.dataset.section ?? null,
568
+ sectionLabel: sectionEl?.dataset.sectionLabel ?? null
569
+ });
570
+ }
571
+ }
550
572
  }, true);
551
573
  globalThis.self.addEventListener("message", handleMessage);
552
574
  if (import.meta.hot) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/editor/editorBridge.ts"],"names":[],"mappings":"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","file":"editorBridge.js","sourcesContent":["/**\r\n * Editor Bridge — injected into customer site iframes by the DCS visual editor.\r\n *\r\n * Responsibilities:\r\n * 1. Report available sections and text keys to the parent portal\r\n * 2. Enable inline contenteditable editing on data-text-key elements (double-click)\r\n * 3. Render section overlay highlights on hover/select from parent\r\n * 4. Communicate all interactions back to the parent via postMessage\r\n * 5. Monitor navigation within the iframe and notify the parent\r\n * 6. Show floating edit icon on hover for text-key elements\r\n *\r\n * NOTE: AI ✨ buttons are rendered by the portal-side SectionOverlayLayer,\r\n * NOT by this bridge. The bridge only reports section/text key data and\r\n * handles inline text editing.\r\n *\r\n * This script runs inside the iframe (customer site). The parent portal\r\n * communicates via postMessage using the `dcs:*` message protocol.\r\n */\r\n\r\n// ─── Types ───────────────────────────────────────────────────────\r\n\r\nexport interface SectionInfo {\r\n id: string\r\n label: string | null\r\n bounds: { x: number; y: number; width: number; height: number }\r\n textKeyCount: number\r\n}\r\n\r\nexport interface EditorReadyPayload {\r\n sections: SectionInfo[]\r\n textKeys: string[]\r\n contentHeight: number\r\n}\r\n\r\n// ─── Message Protocol ────────────────────────────────────────────\r\n\r\ntype InboundMessageType =\r\n | 'dcs:init-editor'\r\n | 'dcs:highlight-section'\r\n | 'dcs:clear-highlight'\r\n | 'dcs:select-text-key'\r\n | 'dcs:update-text'\r\n | 'dcs:set-mode'\r\n | 'dcs:set-editing-enabled'\r\n\r\ntype OutboundMessageType =\r\n | 'dcs:ready'\r\n | 'dcs:text-key-click'\r\n | 'dcs:text-key-changed'\r\n | 'dcs:section-click'\r\n | 'dcs:section-hover'\r\n | 'dcs:text-key-dblclick'\r\n | 'dcs:contextmenu'\r\n | 'dcs:click'\r\n | 'dcs:navigation'\r\n | 'dcs:array-key-click'\r\n\r\nfunction isInIframe(): boolean {\r\n try { return globalThis.self !== globalThis.self.parent } catch { return true }\r\n}\r\n\r\nfunction postToParent(type: OutboundMessageType, data: unknown) {\r\n if (!isInIframe()) return\r\n // Use '*' because the portal origin varies between dev and production\r\n // and this script doesn't know the parent origin at injection time.\r\n // Security: messages are validated by the portal's useIframeBridge.\r\n // Origin validated via dcs: prefix check on all messages\r\n globalThis.self.parent.postMessage({ type, data }, '*') // NOSONAR\r\n}\r\n\r\n// ─── State ───────────────────────────────────────────────────────\r\n\r\nlet editorActive = false\r\nlet bridgeInitialized = false\r\nlet activeEditElement: HTMLElement | null = null\r\n/** The current pathname tracked for navigation detection */\r\nlet lastKnownPathname: string = ''\r\n/** The floating edit icon element shown on hover over text keys */\r\nlet editIconElement: HTMLElement | null = null\r\n/** The element the edit icon is currently attached to */\r\nlet editIconTarget: HTMLElement | null = null\r\n/** The floating edit icon for array/list text keys */\r\nlet arrayEditIconElement: HTMLElement | null = null\r\n/** The element the array edit icon is currently attached to */\r\nlet arrayEditIconTarget: HTMLElement | null = null\r\n/** Whether editing features are enabled (disabled on unmanaged pages) */\r\nlet editingEnabled = true\r\n/** Original text values captured when inline editing starts, for change detection */\r\nconst originalTextValues = new Map<HTMLElement, string>()\r\n/** ResizeObserver for tracking section layout changes */\r\nlet sectionResizeObserver: ResizeObserver | null = null\r\n/** Debounce timer for re-discovery triggered by layout changes */\r\nlet rediscoveryTimer: ReturnType<typeof setTimeout> | null = null\r\n\r\n/** Debounced re-discovery — collapses rapid layout changes into one update */\r\nfunction scheduleRediscovery() {\r\n if (rediscoveryTimer) clearTimeout(rediscoveryTimer)\r\n rediscoveryTimer = setTimeout(() => {\r\n rediscoveryTimer = null\r\n rediscoverAndNotify()\r\n }, 300)\r\n}\r\n\r\n// ─── Section Discovery ───────────────────────────────────────────\r\n\r\nfunction discoverSections(): SectionInfo[] {\r\n const elements = document.querySelectorAll<HTMLElement>('[data-section]')\r\n return Array.from(elements).map((el) => {\r\n const rect = el.getBoundingClientRect()\r\n const textKeyCount = el.querySelectorAll('[data-text-key]').length\r\n return {\r\n id: el.dataset.section!,\r\n label: el.dataset.sectionLabel ?? null,\r\n bounds: {\r\n x: rect.left + scrollX,\r\n y: rect.top + scrollY,\r\n width: rect.width,\r\n height: rect.height,\r\n },\r\n textKeyCount,\r\n }\r\n })\r\n}\r\n\r\nfunction discoverTextKeys(): string[] {\r\n return Array.from(document.querySelectorAll<HTMLElement>('[data-text-key]')).map(\r\n (el) => el.dataset.textKey!,\r\n )\r\n}\r\n\r\n// ─── Layout Change Observation ───────────────────────────────────\r\n\r\n/**\r\n * Watch for layout changes that affect section bounds and overall page height:\r\n * 1. ALL lazy-loaded images on the page — their load events shift subsequent sections\r\n * 2. ResizeObserver on the document body — detects overall page height changes\r\n * 3. ResizeObserver on section elements — detects any size change (font loading,\r\n * dynamic content expansion, CSS transitions, etc.)\r\n *\r\n * When changes are detected, we schedule a debounced re-discovery so the portal\r\n * overlay layer receives updated section bounds and content height that match\r\n * the live layout.\r\n */\r\nfunction observeSectionLayout() {\r\n // Clean up any previous observer (e.g., after SPA navigation)\r\n if (sectionResizeObserver) {\r\n sectionResizeObserver.disconnect()\r\n }\r\n\r\n // 1. Watch ALL lazy images on the page — not just inside sections.\r\n // Images outside sections (e.g., contact form, unmapped content) can still\r\n // push content down and change overall page height and section positions.\r\n const allImages = document.querySelectorAll<HTMLImageElement>('img')\r\n allImages.forEach(img => {\r\n if (!img.complete) {\r\n img.addEventListener('load', scheduleRediscovery, { once: true })\r\n img.addEventListener('error', scheduleRediscovery, { once: true })\r\n }\r\n })\r\n\r\n // 2. ResizeObserver on the document body + each section element.\r\n // Body observation catches overall page height changes from any source.\r\n if (typeof ResizeObserver !== 'undefined') {\r\n sectionResizeObserver = new ResizeObserver(scheduleRediscovery)\r\n // Observe body for overall height changes\r\n sectionResizeObserver.observe(document.body)\r\n // Observe each section for individual bound changes\r\n document.querySelectorAll<HTMLElement>('[data-section]').forEach(el => {\r\n sectionResizeObserver!.observe(el)\r\n })\r\n }\r\n}\r\n\r\n// ─── Section Overlay ─────────────────────────────────────────────\r\n\r\n/**\r\n * Section highlight is now handled entirely by the portal-side\r\n * SectionOverlayLayer rendered on top of the iframe. The bridge\r\n * only reports section bounds and hover events — no visual\r\n * overlays are rendered inside the iframe itself.\r\n */\r\nfunction showSectionHighlight(_sectionId: string) {\r\n // No-op: portal overlay layer handles section highlights\r\n}\r\n\r\nfunction clearSectionHighlight() {\r\n // No-op: portal overlay layer handles section highlights\r\n}\r\n\r\n// ─── Navigation Monitoring ───────────────────────────────────────\r\n\r\n/**\r\n * Monitor navigation within the iframe and notify the parent portal.\r\n * Instead of blocking all navigation, we allow SPA routing and inform\r\n * the portal when the iframe navigates to a different page. The portal\r\n * then updates its own URL and loads editing context for the new page.\r\n *\r\n * Strategy:\r\n * 1. Poll location.pathname on a short interval — this reliably detects\r\n * SPA navigation regardless of which router or framework is used,\r\n * since frameworks may cache History API references before our script loads.\r\n * 2. Block external/cross-origin link clicks (actual page reloads).\r\n * 3. Block form submissions.\r\n * 4. Monitor popstate for back/forward navigation.\r\n * 5. After navigation, re-discover sections and report them to the portal.\r\n */\r\nfunction monitorNavigation() {\r\n lastKnownPathname = globalThis.location.pathname\r\n\r\n // ── Poll for URL changes — reliable with any SPA router ──────\r\n // Frameworks like VitePress/Vue Router may cache history.pushState before\r\n // our bridge loads, so prototype overrides are unreliable. Polling\r\n // location.pathname directly is simple and always works.\r\n setInterval(() => {\r\n checkForNavigation()\r\n }, 250)\r\n\r\n // ── Catch back/forward navigation ────────────────────────────\r\n globalThis.addEventListener('popstate', () => {\r\n checkForNavigation()\r\n })\r\n\r\n // ── Block external link clicks (cross-origin / full page reloads) ──\r\n const handleLinkClick = (e: MouseEvent) => {\r\n const link = (e.target as HTMLElement).closest?.('a[href]') as HTMLAnchorElement | null\r\n if (!link) return\r\n\r\n const href = link.getAttribute('href')\r\n if (!href || href.startsWith('#') || href === 'javascript:void(0)') return\r\n\r\n // Check if this is an external link (different origin)\r\n try {\r\n const resolved = new URL(href, globalThis.location.href)\r\n if (resolved.origin !== globalThis.location.origin) {\r\n // Block external navigation — can't leave the iframe\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n }\r\n // Same-origin links: let the SPA router handle them.\r\n // The History API override above will detect the navigation.\r\n } catch {\r\n // Malformed URL — block it\r\n e.preventDefault()\r\n }\r\n }\r\n\r\n // Register on window first to beat VitePress's capture-phase handler\r\n globalThis.addEventListener('click', handleLinkClick, true)\r\n document.addEventListener('click', handleLinkClick, true)\r\n\r\n // Intercept form submissions\r\n document.addEventListener('submit', (e: Event) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n }, true)\r\n\r\n // Block actual page reloads (location.href = ..., etc.)\r\n window.addEventListener('beforeunload', (e: BeforeUnloadEvent) => {\r\n e.preventDefault()\r\n })\r\n}\r\n\r\n/**\r\n * Check if the iframe pathname has changed and notify the portal.\r\n * Also re-initializes the editor bridge for the new page.\r\n */\r\nfunction checkForNavigation() {\r\n const currentPathname = globalThis.location.pathname\r\n if (currentPathname === lastKnownPathname) return\r\n\r\n lastKnownPathname = currentPathname\r\n\r\n // Notify parent about the navigation\r\n postToParent('dcs:navigation', { pathname: currentPathname })\r\n\r\n // Clean up current edit state\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n\r\n // Wait for the SPA router to finish rendering the new page,\r\n // then re-discover sections and text keys\r\n setTimeout(() => {\r\n rediscoverAndNotify()\r\n }, 500)\r\n}\r\n\r\n// ─── Mode Management ─────────────────────────────────────────────\r\n\r\n/**\r\n * Set the current interaction mode. Both modes allow normal browsing —\r\n * inline editing works via double-click regardless of mode.\r\n * Switching to explore finishes any active inline edit.\r\n */\r\nfunction setMode(mode: 'edit' | 'explore') {\r\n if (mode === 'explore') {\r\n // When explicitly switching to explore, finish any active inline edit\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n }\r\n}\r\n\r\n// ─── Inline Text Editing ─────────────────────────────────────────\r\n\r\n/**\r\n * Re-discover sections and text keys after an HMR update and\r\n * notify the parent portal so the overlay layer updates.\r\n */\r\nfunction rediscoverAndNotify() {\r\n activeEditElement = null\r\n\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n\r\n // Skip re-report if height hasn't meaningfully changed (< 2px drift)\r\n // AND sections/textKeys are the same count — avoids portal re-render churn.\r\n if (Math.abs(contentHeight - lastReportedHeight) <= 2) {\r\n // Height is stable — still report in case sections/textKeys changed\r\n }\r\n lastReportedHeight = contentHeight\r\n\r\n // Always report — sections/textKeys may have changed even if height is stable\r\n postToParent('dcs:ready', { sections, textKeys, contentHeight } satisfies EditorReadyPayload)\r\n\r\n // Re-enable editor mode on the new elements\r\n if (editorActive) {\r\n editorActive = false // allow re-initialization\r\n enableEditorMode()\r\n }\r\n\r\n // Re-observe layout changes on the new DOM\r\n observeSectionLayout()\r\n}\r\n\r\nfunction enableEditorMode() {\r\n if (editorActive) return\r\n editorActive = true\r\n\r\n // Inject editor styles\r\n injectEditorStyles()\r\n\r\n // Set up all text key elements — double-click to start editing\r\n const textElements = document.querySelectorAll<HTMLElement>('[data-text-key]')\r\n textElements.forEach((htmlEl) => {\r\n htmlEl.classList.add('dcs-editable')\r\n\r\n // Double-click to start inline editing (signals parent to switch to edit mode)\r\n htmlEl.addEventListener('dblclick', handleTextKeyDblClick)\r\n // Blur to finish editing\r\n htmlEl.addEventListener('blur', handleTextKeyBlur)\r\n // Keyboard: Enter to finish, Escape to cancel\r\n htmlEl.addEventListener('keydown', handleTextKeyKeydown)\r\n\r\n // Show/hide the floating edit icon on hover (T icon for regular text, List icon for array keys)\r\n htmlEl.addEventListener('mouseenter', () => {\r\n // Don't show icon while actively editing this or another element\r\n if (activeEditElement) return\r\n const key = htmlEl.dataset.textKey!\r\n if (isArrayTextKey(key)) {\r\n showArrayEditIcon(htmlEl)\r\n } else {\r\n showEditIcon(htmlEl)\r\n }\r\n })\r\n htmlEl.addEventListener('mouseleave', (e: MouseEvent) => {\r\n // Don't hide if moving to the edit icon or array edit icon\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && (\r\n related.classList?.contains('dcs-edit-icon') || related.closest?.('.dcs-edit-icon') ||\r\n related.classList?.contains('dcs-array-edit-icon') || related.closest?.('.dcs-array-edit-icon')\r\n )) return\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n })\r\n })\r\n\r\n // Set up section hover reporting (portal overlay handles the visual treatment)\r\n const sections = document.querySelectorAll<HTMLElement>('[data-section]')\r\n sections.forEach((el) => {\r\n const sectionId = el.dataset.section!\r\n\r\n el.addEventListener('mouseenter', () => {\r\n postToParent('dcs:section-hover', { sectionId })\r\n })\r\n el.addEventListener('mouseleave', () => {\r\n postToParent('dcs:section-hover', { sectionId: null })\r\n })\r\n el.addEventListener('click', (e) => {\r\n // Only fire section click if not clicking on a text-key element\r\n const target = e.target as HTMLElement\r\n if (!target.closest('[data-text-key]')) {\r\n postToParent('dcs:section-click', { sectionId })\r\n }\r\n })\r\n })\r\n}\r\n\r\n/**\r\n * Handle double-click on a text key element.\r\n * Notifies the parent portal to switch to edit mode and begins inline editing.\r\n */\r\nfunction handleTextKeyDblClick(e: Event) {\r\n if (!editingEnabled) return\r\n\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n const el = e.currentTarget as HTMLElement\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Always notify the parent to switch to edit mode when starting inline editing.\r\n // In explore mode this triggers the mode switch; in edit mode it's a no-op on the parent side.\r\n postToParent('dcs:text-key-dblclick', { key, sectionId })\r\n\r\n // Start inline editing\r\n startInlineEdit(el, key, sectionId)\r\n}\r\n\r\n/**\r\n * Start inline contenteditable editing on a text key element.\r\n * Called by double-click or when the parent sends dcs:select-text-key.\r\n */\r\nfunction startInlineEdit(el: HTMLElement, key: string, sectionId: string | null) {\r\n // Finish any active edit first\r\n if (activeEditElement && activeEditElement !== el) {\r\n finishEdit(activeEditElement)\r\n }\r\n\r\n // Store original text for change detection on finish\r\n originalTextValues.set(el, el.innerText.trim())\r\n\r\n // Enable contenteditable\r\n el.setAttribute('contenteditable', 'true')\r\n el.classList.add('dcs-editing')\r\n el.focus()\r\n activeEditElement = el\r\n\r\n // Select all text for easy replacement\r\n const selection = getSelection()\r\n const range = document.createRange()\r\n range.selectNodeContents(el)\r\n selection?.removeAllRanges()\r\n selection?.addRange(range)\r\n\r\n postToParent('dcs:text-key-click', { key, sectionId })\r\n}\r\n\r\nfunction handleTextKeyBlur(e: Event) {\r\n const el = e.currentTarget as HTMLElement\r\n finishEdit(el)\r\n}\r\n\r\nfunction handleTextKeyKeydown(e: KeyboardEvent) {\r\n if (e.key === 'Enter' && !e.shiftKey) {\r\n e.preventDefault()\r\n ;(e.currentTarget as HTMLElement).blur()\r\n }\r\n if (e.key === 'Escape') {\r\n e.preventDefault()\r\n ;(e.currentTarget as HTMLElement).blur()\r\n }\r\n}\r\n\r\nfunction finishEdit(el: HTMLElement) {\r\n if (!el.hasAttribute('contenteditable')) return\r\n\r\n el.removeAttribute('contenteditable')\r\n el.classList.remove('dcs-editing')\r\n\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n const newValue = el.innerText.trim()\r\n const originalValue = originalTextValues.get(el) ?? ''\r\n\r\n if (activeEditElement === el) {\r\n activeEditElement = null\r\n }\r\n\r\n // Only report to the portal when text was actually modified\r\n if (newValue !== originalValue) {\r\n postToParent('dcs:text-key-changed', { key, value: newValue, sectionId })\r\n }\r\n\r\n originalTextValues.delete(el)\r\n}\r\n\r\nfunction focusTextKey(key: string) {\r\n const el = document.querySelector<HTMLElement>(`[data-text-key=\"${key}\"]`)\r\n if (!el) return\r\n el.scrollIntoView({ behavior: 'smooth', block: 'center' })\r\n\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Start inline editing directly\r\n startInlineEdit(el, key, sectionId)\r\n}\r\n\r\nfunction updateTextInPlace(key: string, value: string) {\r\n const el = document.querySelector<HTMLElement>(`[data-text-key=\"${key}\"]`)\r\n if (!el) return\r\n // Don't update if we're currently editing this element\r\n if (el === activeEditElement) return\r\n el.innerText = value\r\n}\r\n\r\n// ─── Edit Icon (Floating Text Cursor Button) ────────────────────\r\n\r\n/**\r\n * Create the floating edit icon element. This small button appears\r\n * in the top-left corner of a hovered [data-text-key] element,\r\n * providing a click target to start inline editing without triggering\r\n * navigation (important for text inside buttons/links).\r\n */\r\nfunction createEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Edit text')\r\n icon.setAttribute('aria-label', 'Edit text inline')\r\n // SVG: Type icon (text cursor) — matches lucide \"Type\" icon\r\n icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"/><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"/></svg>`\r\n\r\n icon.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n if (!editIconTarget) return\r\n const el = editIconTarget\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Signal parent to switch to edit mode\r\n postToParent('dcs:text-key-dblclick', { key, sectionId })\r\n // Start inline editing\r\n startInlineEdit(el, key, sectionId)\r\n // Hide the icon while editing\r\n removeEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the target element)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && editIconTarget && (related === editIconTarget || editIconTarget.contains(related))) return\r\n removeEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the edit icon positioned at the top-left corner of the given element.\r\n */\r\nfunction showEditIcon(el: HTMLElement) {\r\n if (!editingEnabled) return\r\n\r\n if (!editIconElement) {\r\n editIconElement = createEditIcon()\r\n document.body.appendChild(editIconElement)\r\n }\r\n\r\n editIconTarget = el\r\n\r\n // Position relative to the element's bounding box\r\n const rect = el.getBoundingClientRect()\r\n editIconElement.style.top = `${rect.top + scrollY - 4}px`\r\n editIconElement.style.left = `${rect.left + scrollX - 4}px`\r\n editIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the edit icon.\r\n */\r\nfunction removeEditIcon() {\r\n if (editIconElement) {\r\n editIconElement.style.display = 'none'\r\n }\r\n editIconTarget = null\r\n}\r\n\r\n// ─── Array Key Detection ─────────────────────────────────────────\r\n\r\n/**\r\n * Check if a text key is part of an array pattern (e.g., \"conditions.0.title\").\r\n * Array text keys contain an indexed segment — either:\r\n * - Pure numeric: `baseKey.N.field` (e.g., `conditions.0.title`)\r\n * - Hyphenated index: `baseKey.prefix-N.field` (e.g., `steps.step-0.title`)\r\n */\r\nfunction isArrayTextKey(key: string): boolean {\r\n // Match pure numeric segment (.0.) or hyphenated index (.prefix-0.)\r\n return /\\.\\d+\\./.test(key) || /\\.\\w+-\\d+\\./.test(key)\r\n}\r\n\r\n/**\r\n * Extract the base array key from an array text key.\r\n * - `conditions.0.title` → `conditions`\r\n * - `steps.step-0.title` → `steps.step`\r\n */\r\nfunction getArrayBaseKey(key: string): string | null {\r\n // Try hyphenated pattern first (more specific)\r\n const hyphenMatch = key.match(/^(.+?)-\\d+\\./)\r\n if (hyphenMatch) return hyphenMatch[1]\r\n // Fall back to pure numeric pattern\r\n const numMatch = key.match(/^(.+?)\\.\\d+\\./)\r\n return numMatch ? numMatch[1] : null\r\n}\r\n\r\n// ─── Array Edit Icon (Floating List Button) ──────────────────────\r\n\r\n/**\r\n * Create the floating array edit icon element. This small button appears\r\n * in the top-left corner of a hovered [data-text-key] element whose key\r\n * matches an array pattern. Clicking it sends dcs:array-key-click to the\r\n * portal to open the array editor sheet.\r\n */\r\nfunction createArrayEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-array-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Edit list items')\r\n icon.setAttribute('aria-label', 'Edit list items in panel')\r\n // SVG: List icon — matches lucide \"List\" icon\r\n icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>`\r\n\r\n icon.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n if (!arrayEditIconTarget) return\r\n const el = arrayEditIconTarget\r\n const key = el.dataset.textKey!\r\n const arrayKey = getArrayBaseKey(key)\r\n if (!arrayKey) return\r\n\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Signal parent to open array editor sheet\r\n postToParent('dcs:array-key-click', { arrayKey, sectionId })\r\n // Hide the icon\r\n removeArrayEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the target element)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && arrayEditIconTarget && (related === arrayEditIconTarget || arrayEditIconTarget.contains(related))) return\r\n removeArrayEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the array edit icon positioned at the top-left corner of the given element.\r\n */\r\nfunction showArrayEditIcon(el: HTMLElement) {\r\n if (!editingEnabled) return\r\n\r\n if (!arrayEditIconElement) {\r\n arrayEditIconElement = createArrayEditIcon()\r\n document.body.appendChild(arrayEditIconElement)\r\n }\r\n\r\n arrayEditIconTarget = el\r\n\r\n // Position relative to the element's bounding box\r\n const rect = el.getBoundingClientRect()\r\n arrayEditIconElement.style.top = `${rect.top + scrollY - 4}px`\r\n arrayEditIconElement.style.left = `${rect.left + scrollX - 4}px`\r\n arrayEditIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the array edit icon.\r\n */\r\nfunction removeArrayEditIcon() {\r\n if (arrayEditIconElement) {\r\n arrayEditIconElement.style.display = 'none'\r\n }\r\n arrayEditIconTarget = null\r\n}\r\n\r\nfunction injectEditorStyles() {\r\n if (document.getElementById('dcs-editor-styles')) return\r\n\r\n const style = document.createElement('style')\r\n style.id = 'dcs-editor-styles'\r\n style.textContent = `\r\n [data-text-key].dcs-editable {\r\n cursor: text !important;\r\n border-radius: 4px;\r\n position: relative;\r\n }\r\n\r\n [data-text-key].dcs-editable:hover {\r\n outline: 2px dashed hsl(221.2 83.2% 53.3% / 0.4) !important;\r\n outline-offset: 4px;\r\n background-color: hsl(221.2 83.2% 53.3% / 0.03);\r\n }\r\n\r\n [data-text-key].dcs-editing {\r\n outline: 2px solid hsl(221.2 83.2% 53.3%) !important;\r\n outline-offset: 4px;\r\n background-color: hsl(221.2 83.2% 53.3% / 0.06);\r\n min-height: 1em;\r\n }\r\n\r\n [data-text-key].dcs-editing:focus {\r\n outline: 2px solid hsl(221.2 83.2% 53.3%) !important;\r\n outline-offset: 4px;\r\n }\r\n\r\n .dcs-section-highlight {\r\n pointer-events: none;\r\n }\r\n\r\n /* Floating edit icon button */\r\n .dcs-edit-icon {\r\n position: absolute;\r\n z-index: 99999;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 0;\r\n margin: 0;\r\n border: 1.5px solid hsl(221.2 83.2% 53.3%);\r\n border-radius: 4px;\r\n background: hsl(221.2 83.2% 53.3% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(221.2 83.2% 53.3%);\r\n cursor: pointer;\r\n pointer-events: auto;\r\n transition: background 0.15s, transform 0.1s;\r\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n }\r\n\r\n .dcs-edit-icon:hover {\r\n background: hsl(221.2 83.2% 53.3% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-edit-icon svg {\r\n display: block;\r\n }\r\n\r\n /* Floating array edit icon button */\r\n .dcs-array-edit-icon {\r\n position: absolute;\r\n z-index: 99999;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 0;\r\n margin: 0;\r\n border: 1.5px solid hsl(271 91% 65%);\r\n border-radius: 4px;\r\n background: hsl(271 91% 65% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(271 91% 65%);\r\n cursor: pointer;\r\n pointer-events: auto;\r\n transition: background 0.15s, transform 0.1s;\r\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n }\r\n\r\n .dcs-array-edit-icon:hover {\r\n background: hsl(271 91% 65% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-array-edit-icon svg {\r\n display: block;\r\n }\r\n `\r\n document.head.appendChild(style)\r\n}\r\n\r\n// ─── Inbound Message Handler ─────────────────────────────────────\r\n\r\nfunction handleMessage(event: MessageEvent) {\r\n // Validate message structure (origin is not checked because\r\n // the parent portal origin varies between dev and production)\r\n const msg = event.data\r\n if (!msg || typeof msg !== 'object' || typeof msg.type !== 'string') return\r\n if (!msg.type.startsWith('dcs:')) return\r\n\r\n const { type, data } = msg as { type: InboundMessageType; data: unknown }\r\n\r\n switch (type) {\r\n case 'dcs:init-editor':\r\n enableEditorMode()\r\n break\r\n\r\n case 'dcs:highlight-section':\r\n if (data && typeof data === 'object' && 'sectionId' in data) {\r\n showSectionHighlight((data as { sectionId: string }).sectionId)\r\n }\r\n break\r\n\r\n case 'dcs:clear-highlight':\r\n clearSectionHighlight()\r\n break\r\n\r\n case 'dcs:select-text-key':\r\n if (data && typeof data === 'object' && 'key' in data) {\r\n focusTextKey((data as { key: string }).key)\r\n }\r\n break\r\n\r\n case 'dcs:update-text':\r\n if (data && typeof data === 'object' && 'key' in data && 'value' in data) {\r\n const d = data as { key: string; value: string }\r\n updateTextInPlace(d.key, d.value)\r\n }\r\n break\r\n\r\n case 'dcs:set-mode':\r\n if (data && typeof data === 'object' && 'mode' in data) {\r\n setMode((data as { mode: 'edit' | 'explore' }).mode)\r\n }\r\n break\r\n\r\n case 'dcs:set-editing-enabled':\r\n if (data && typeof data === 'object' && 'enabled' in data) {\r\n editingEnabled = (data as { enabled: boolean }).enabled\r\n if (editingEnabled) {\r\n // Re-add dcs-editable class to text key elements\r\n document.querySelectorAll<HTMLElement>('[data-text-key]').forEach(el => {\r\n el.classList.add('dcs-editable')\r\n })\r\n } else {\r\n // Clean up: finish any active edit, remove edit icon, remove hover styles\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n // Remove dcs-editable class to disable hover outlines\r\n document.querySelectorAll<HTMLElement>('.dcs-editable').forEach(el => {\r\n el.classList.remove('dcs-editable')\r\n })\r\n }\r\n }\r\n break\r\n }\r\n}\r\n\r\n// ─── Bootstrap ───────────────────────────────────────────────────\r\n\r\n/**\r\n * Inject a stylesheet that neutralizes viewport-relative min-heights.\r\n * In the visual editor iframe, the portal controls height from body.scrollHeight.\r\n * CSS rules like `min-height: 100vh` create a feedback loop:\r\n * iframe height → vh grows → body.scrollHeight grows → iframe height grows → …\r\n * Neutralizing these rules lets the content determine its natural height.\r\n */\r\nfunction injectEditorHeightFix() {\r\n if (document.getElementById('dcs-editor-height-fix')) return\r\n const style = document.createElement('style')\r\n style.id = 'dcs-editor-height-fix'\r\n style.textContent = `\r\n /* DCS Editor Bridge: Neutralize viewport-relative min-heights */\r\n .min-h-screen, .min-h-dvh, .min-h-svh,\r\n .min-h-\\\\[100vh\\\\], .min-h-\\\\[100dvh\\\\], .min-h-\\\\[100svh\\\\] {\r\n min-height: 0 !important;\r\n }\r\n `\r\n document.head.appendChild(style)\r\n}\r\n\r\n/** Last reported content height — used to avoid re-sending when stable */\r\nlet lastReportedHeight = 0\r\n\r\n/**\r\n * Measure the true content height, avoiding inflation from viewport-relative\r\n * CSS rules. Uses section bounds as the primary signal (immune to vh feedback),\r\n * falling back to body.scrollHeight (which is reliable once the editor height\r\n * fix stylesheet has been injected).\r\n */\r\nfunction measureContentHeight(sections: SectionInfo[]): number {\r\n // Primary: compute from section bounds — these reflect actual element positions\r\n // after the height-fix stylesheet has neutralized viewport-relative min-heights.\r\n let maxBottom = 0\r\n for (const s of sections) {\r\n const bottom = s.bounds.y + s.bounds.height\r\n if (bottom > maxBottom) maxBottom = bottom\r\n }\r\n const sectionHeight = maxBottom > 0 ? Math.ceil(maxBottom) : 0\r\n\r\n // Fallback: body.scrollHeight (reliable now that min-height: 100vh is neutralized)\r\n const bodyHeight = document.body.scrollHeight\r\n\r\n // Use whichever is larger — sections might miss elements without data-section,\r\n // but bodyHeight should be accurate with the injected CSS fix.\r\n return Math.max(sectionHeight, bodyHeight)\r\n}\r\n\r\nexport function initEditorBridge() {\r\n if (!isInIframe()) return // Not in iframe\r\n\r\n // Inject height fix CSS before any measurement\r\n injectEditorHeightFix()\r\n\r\n // Prevent duplicate listener registration on HMR re-execution\r\n if (!bridgeInitialized) {\r\n bridgeInitialized = true\r\n\r\n // Monitor navigation within the iframe — notify the portal of page changes\r\n monitorNavigation()\r\n\r\n // Forward right-click events to the portal so the context menu\r\n // works when right-clicking inside the iframe (cross-origin boundary\r\n // prevents the parent from receiving native contextmenu events).\r\n document.addEventListener('contextmenu', (e: MouseEvent) => {\r\n e.preventDefault()\r\n const sectionEl = (e.target as HTMLElement).closest?.('[data-section]') as HTMLElement | null\r\n postToParent('dcs:contextmenu', {\r\n x: e.clientX,\r\n y: e.clientY,\r\n sectionId: sectionEl?.dataset.section ?? null,\r\n sectionLabel: sectionEl?.dataset.sectionLabel ?? null,\r\n })\r\n }, true)\r\n\r\n // Forward click events to the portal so it can dismiss context menus\r\n // when the user clicks inside the iframe.\r\n document.addEventListener('click', () => {\r\n postToParent('dcs:click', {})\r\n }, true)\r\n\r\n // Listen for portal commands. Origin is not validated because\r\n // the portal origin varies between environments and all inbound\r\n // messages are type-checked before processing.\r\n // Origin cannot be pre-validated because the portal URL varies by environment.\r\n // All inbound messages are type-checked via the dcs: prefix guard.\r\n globalThis.self.addEventListener('message', handleMessage) // NOSONAR\r\n\r\n // Watch for VitePress / Vite HMR page updates. When the framework\r\n // replaces DOM content, our event listeners and AI buttons are lost.\r\n // Re-discover sections after each update so the portal overlay stays in sync.\r\n if (import.meta.hot) {\r\n import.meta.hot.on('vite:afterUpdate', () => {\r\n // Small delay for the framework to finish DOM updates\r\n setTimeout(rediscoverAndNotify, 300)\r\n })\r\n }\r\n }\r\n\r\n // Report available sections, text keys, and content height\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n lastReportedHeight = contentHeight\r\n\r\n postToParent('dcs:ready', {\r\n sections,\r\n textKeys,\r\n contentHeight,\r\n } satisfies EditorReadyPayload)\r\n\r\n // Observe layout changes (lazy images, dynamic content) and re-report bounds\r\n observeSectionLayout()\r\n}\r\n\r\n// Auto-initialize when the script is loaded (e.g., injected by Vite plugin)\r\n// Delay slightly to ensure the page is fully rendered\r\nif (typeof globalThis.self !== 'undefined' && isInIframe()) {\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', () => {\r\n // Small delay for Vue/framework hydration\r\n setTimeout(initEditorBridge, 200)\r\n })\r\n } else {\r\n setTimeout(initEditorBridge, 200)\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/editor/editorBridge.ts"],"names":[],"mappings":"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","file":"editorBridge.js","sourcesContent":["/**\r\n * Editor Bridge — injected into customer site iframes by the DCS visual editor.\r\n *\r\n * Responsibilities:\r\n * 1. Report available sections and text keys to the parent portal\r\n * 2. Enable inline contenteditable editing on data-text-key elements (double-click)\r\n * 3. Render section overlay highlights on hover/select from parent\r\n * 4. Communicate all interactions back to the parent via postMessage\r\n * 5. Monitor navigation within the iframe and notify the parent\r\n * 6. Show floating edit icon on hover for text-key elements\r\n *\r\n * NOTE: AI ✨ buttons are rendered by the portal-side SectionOverlayLayer,\r\n * NOT by this bridge. The bridge only reports section/text key data and\r\n * handles inline text editing.\r\n *\r\n * This script runs inside the iframe (customer site). The parent portal\r\n * communicates via postMessage using the `dcs:*` message protocol.\r\n */\r\n\r\n// ─── Types ───────────────────────────────────────────────────────\r\n\r\nexport interface SectionInfo {\r\n id: string\r\n label: string | null\r\n bounds: { x: number; y: number; width: number; height: number }\r\n textKeyCount: number\r\n}\r\n\r\nexport interface EditorReadyPayload {\r\n sections: SectionInfo[]\r\n textKeys: string[]\r\n contentHeight: number\r\n}\r\n\r\n// ─── Message Protocol ────────────────────────────────────────────\r\n\r\ntype InboundMessageType =\r\n | 'dcs:init-editor'\r\n | 'dcs:highlight-section'\r\n | 'dcs:clear-highlight'\r\n | 'dcs:select-text-key'\r\n | 'dcs:update-text'\r\n | 'dcs:set-mode'\r\n | 'dcs:set-editing-enabled'\r\n\r\ntype OutboundMessageType =\r\n | 'dcs:ready'\r\n | 'dcs:text-key-click'\r\n | 'dcs:text-key-changed'\r\n | 'dcs:section-click'\r\n | 'dcs:section-hover'\r\n | 'dcs:text-key-dblclick'\r\n | 'dcs:contextmenu'\r\n | 'dcs:click'\r\n | 'dcs:navigation'\r\n | 'dcs:array-key-click'\r\n | 'dcs:image-click'\r\n\r\nfunction isInIframe(): boolean {\r\n try { return globalThis.self !== globalThis.self.parent } catch { return true }\r\n}\r\n\r\nfunction postToParent(type: OutboundMessageType, data: unknown) {\r\n if (!isInIframe()) return\r\n // Use '*' because the portal origin varies between dev and production\r\n // and this script doesn't know the parent origin at injection time.\r\n // Security: messages are validated by the portal's useIframeBridge.\r\n // Origin validated via dcs: prefix check on all messages\r\n globalThis.self.parent.postMessage({ type, data }, '*') // NOSONAR\r\n}\r\n\r\n// ─── State ───────────────────────────────────────────────────────\r\n\r\nlet editorActive = false\r\nlet bridgeInitialized = false\r\nlet activeEditElement: HTMLElement | null = null\r\n/** The current pathname tracked for navigation detection */\r\nlet lastKnownPathname: string = ''\r\n/** The floating edit icon element shown on hover over text keys */\r\nlet editIconElement: HTMLElement | null = null\r\n/** The element the edit icon is currently attached to */\r\nlet editIconTarget: HTMLElement | null = null\r\n/** The floating edit icon for array/list text keys */\r\nlet arrayEditIconElement: HTMLElement | null = null\r\n/** The element the array edit icon is currently attached to */\r\nlet arrayEditIconTarget: HTMLElement | null = null\r\n/** Whether editing features are enabled (disabled on unmanaged pages) */\r\nlet editingEnabled = true\r\n/** Original text values captured when inline editing starts, for change detection */\r\nconst originalTextValues = new Map<HTMLElement, string>()\r\n/** ResizeObserver for tracking section layout changes */\r\nlet sectionResizeObserver: ResizeObserver | null = null\r\n/** Debounce timer for re-discovery triggered by layout changes */\r\nlet rediscoveryTimer: ReturnType<typeof setTimeout> | null = null\r\n\r\n/** Debounced re-discovery — collapses rapid layout changes into one update */\r\nfunction scheduleRediscovery() {\r\n if (rediscoveryTimer) clearTimeout(rediscoveryTimer)\r\n rediscoveryTimer = setTimeout(() => {\r\n rediscoveryTimer = null\r\n rediscoverAndNotify()\r\n }, 300)\r\n}\r\n\r\n// ─── Section Discovery ───────────────────────────────────────────\r\n\r\nfunction discoverSections(): SectionInfo[] {\r\n const elements = document.querySelectorAll<HTMLElement>('[data-section]')\r\n return Array.from(elements).map((el) => {\r\n const rect = el.getBoundingClientRect()\r\n const textKeyCount = el.querySelectorAll('[data-text-key]').length\r\n return {\r\n id: el.dataset.section!,\r\n label: el.dataset.sectionLabel ?? null,\r\n bounds: {\r\n x: rect.left + scrollX,\r\n y: rect.top + scrollY,\r\n width: rect.width,\r\n height: rect.height,\r\n },\r\n textKeyCount,\r\n }\r\n })\r\n}\r\n\r\nfunction discoverTextKeys(): string[] {\r\n return Array.from(document.querySelectorAll<HTMLElement>('[data-text-key]')).map(\r\n (el) => el.dataset.textKey!,\r\n )\r\n}\r\n\r\n// ─── Layout Change Observation ───────────────────────────────────\r\n\r\n/**\r\n * Watch for layout changes that affect section bounds and overall page height:\r\n * 1. ALL lazy-loaded images on the page — their load events shift subsequent sections\r\n * 2. ResizeObserver on the document body — detects overall page height changes\r\n * 3. ResizeObserver on section elements — detects any size change (font loading,\r\n * dynamic content expansion, CSS transitions, etc.)\r\n *\r\n * When changes are detected, we schedule a debounced re-discovery so the portal\r\n * overlay layer receives updated section bounds and content height that match\r\n * the live layout.\r\n */\r\nfunction observeSectionLayout() {\r\n // Clean up any previous observer (e.g., after SPA navigation)\r\n if (sectionResizeObserver) {\r\n sectionResizeObserver.disconnect()\r\n }\r\n\r\n // 1. Watch ALL lazy images on the page — not just inside sections.\r\n // Images outside sections (e.g., contact form, unmapped content) can still\r\n // push content down and change overall page height and section positions.\r\n const allImages = document.querySelectorAll<HTMLImageElement>('img')\r\n allImages.forEach(img => {\r\n if (!img.complete) {\r\n img.addEventListener('load', scheduleRediscovery, { once: true })\r\n img.addEventListener('error', scheduleRediscovery, { once: true })\r\n }\r\n })\r\n\r\n // 2. ResizeObserver on the document body + each section element.\r\n // Body observation catches overall page height changes from any source.\r\n if (typeof ResizeObserver !== 'undefined') {\r\n sectionResizeObserver = new ResizeObserver(scheduleRediscovery)\r\n // Observe body for overall height changes\r\n sectionResizeObserver.observe(document.body)\r\n // Observe each section for individual bound changes\r\n document.querySelectorAll<HTMLElement>('[data-section]').forEach(el => {\r\n sectionResizeObserver!.observe(el)\r\n })\r\n }\r\n}\r\n\r\n// ─── Section Overlay ─────────────────────────────────────────────\r\n\r\n/**\r\n * Section highlight is now handled entirely by the portal-side\r\n * SectionOverlayLayer rendered on top of the iframe. The bridge\r\n * only reports section bounds and hover events — no visual\r\n * overlays are rendered inside the iframe itself.\r\n */\r\nfunction showSectionHighlight(_sectionId: string) {\r\n // No-op: portal overlay layer handles section highlights\r\n}\r\n\r\nfunction clearSectionHighlight() {\r\n // No-op: portal overlay layer handles section highlights\r\n}\r\n\r\n// ─── Navigation Monitoring ───────────────────────────────────────\r\n\r\n/**\r\n * Monitor navigation within the iframe and notify the parent portal.\r\n * Instead of blocking all navigation, we allow SPA routing and inform\r\n * the portal when the iframe navigates to a different page. The portal\r\n * then updates its own URL and loads editing context for the new page.\r\n *\r\n * Strategy:\r\n * 1. Poll location.pathname on a short interval — this reliably detects\r\n * SPA navigation regardless of which router or framework is used,\r\n * since frameworks may cache History API references before our script loads.\r\n * 2. Block external/cross-origin link clicks (actual page reloads).\r\n * 3. Block form submissions.\r\n * 4. Monitor popstate for back/forward navigation.\r\n * 5. After navigation, re-discover sections and report them to the portal.\r\n */\r\nfunction monitorNavigation() {\r\n lastKnownPathname = globalThis.location.pathname\r\n\r\n // ── Poll for URL changes — reliable with any SPA router ──────\r\n // Frameworks like VitePress/Vue Router may cache history.pushState before\r\n // our bridge loads, so prototype overrides are unreliable. Polling\r\n // location.pathname directly is simple and always works.\r\n setInterval(() => {\r\n checkForNavigation()\r\n }, 250)\r\n\r\n // ── Catch back/forward navigation ────────────────────────────\r\n globalThis.addEventListener('popstate', () => {\r\n checkForNavigation()\r\n })\r\n\r\n // ── Block external link clicks (cross-origin / full page reloads) ──\r\n const handleLinkClick = (e: MouseEvent) => {\r\n const link = (e.target as HTMLElement).closest?.('a[href]') as HTMLAnchorElement | null\r\n if (!link) return\r\n\r\n const href = link.getAttribute('href')\r\n if (!href || href.startsWith('#') || href === 'javascript:void(0)') return\r\n\r\n // Check if this is an external link (different origin)\r\n try {\r\n const resolved = new URL(href, globalThis.location.href)\r\n if (resolved.origin !== globalThis.location.origin) {\r\n // Block external navigation — can't leave the iframe\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n }\r\n // Same-origin links: let the SPA router handle them.\r\n // The History API override above will detect the navigation.\r\n } catch {\r\n // Malformed URL — block it\r\n e.preventDefault()\r\n }\r\n }\r\n\r\n // Register on window first to beat VitePress's capture-phase handler\r\n globalThis.addEventListener('click', handleLinkClick, true)\r\n document.addEventListener('click', handleLinkClick, true)\r\n\r\n // Intercept form submissions\r\n document.addEventListener('submit', (e: Event) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n }, true)\r\n\r\n // Block actual page reloads (location.href = ..., etc.)\r\n window.addEventListener('beforeunload', (e: BeforeUnloadEvent) => {\r\n e.preventDefault()\r\n })\r\n}\r\n\r\n/**\r\n * Check if the iframe pathname has changed and notify the portal.\r\n * Also re-initializes the editor bridge for the new page.\r\n */\r\nfunction checkForNavigation() {\r\n const currentPathname = globalThis.location.pathname\r\n if (currentPathname === lastKnownPathname) return\r\n\r\n lastKnownPathname = currentPathname\r\n\r\n // Notify parent about the navigation\r\n postToParent('dcs:navigation', { pathname: currentPathname })\r\n\r\n // Clean up current edit state\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n\r\n // Wait for the SPA router to finish rendering the new page,\r\n // then re-discover sections and text keys\r\n setTimeout(() => {\r\n rediscoverAndNotify()\r\n }, 500)\r\n}\r\n\r\n// ─── Mode Management ─────────────────────────────────────────────\r\n\r\n/**\r\n * Set the current interaction mode. Both modes allow normal browsing —\r\n * inline editing works via double-click regardless of mode.\r\n * Switching to explore finishes any active inline edit.\r\n */\r\nfunction setMode(mode: 'edit' | 'explore') {\r\n if (mode === 'explore') {\r\n // When explicitly switching to explore, finish any active inline edit\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n }\r\n}\r\n\r\n// ─── Inline Text Editing ─────────────────────────────────────────\r\n\r\n/**\r\n * Re-discover sections and text keys after an HMR update and\r\n * notify the parent portal so the overlay layer updates.\r\n */\r\nfunction rediscoverAndNotify() {\r\n activeEditElement = null\r\n\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n\r\n // Skip re-report if height hasn't meaningfully changed (< 2px drift)\r\n // AND sections/textKeys are the same count — avoids portal re-render churn.\r\n if (Math.abs(contentHeight - lastReportedHeight) <= 2) {\r\n // Height is stable — still report in case sections/textKeys changed\r\n }\r\n lastReportedHeight = contentHeight\r\n\r\n // Always report — sections/textKeys may have changed even if height is stable\r\n postToParent('dcs:ready', { sections, textKeys, contentHeight } satisfies EditorReadyPayload)\r\n\r\n // Re-enable editor mode on the new elements\r\n if (editorActive) {\r\n editorActive = false // allow re-initialization\r\n enableEditorMode()\r\n }\r\n\r\n // Re-observe layout changes on the new DOM\r\n observeSectionLayout()\r\n}\r\n\r\nfunction enableEditorMode() {\r\n if (editorActive) return\r\n editorActive = true\r\n\r\n // Inject editor styles\r\n injectEditorStyles()\r\n\r\n // Set up all text key elements — double-click to start editing\r\n const textElements = document.querySelectorAll<HTMLElement>('[data-text-key]')\r\n textElements.forEach((htmlEl) => {\r\n htmlEl.classList.add('dcs-editable')\r\n\r\n // Double-click to start inline editing (signals parent to switch to edit mode)\r\n htmlEl.addEventListener('dblclick', handleTextKeyDblClick)\r\n // Blur to finish editing\r\n htmlEl.addEventListener('blur', handleTextKeyBlur)\r\n // Keyboard: Enter to finish, Escape to cancel\r\n htmlEl.addEventListener('keydown', handleTextKeyKeydown)\r\n\r\n // Show/hide the floating edit icon on hover (T icon for regular text, List icon for array keys)\r\n htmlEl.addEventListener('mouseenter', () => {\r\n // Don't show icon while actively editing this or another element\r\n if (activeEditElement) return\r\n const key = htmlEl.dataset.textKey!\r\n if (isArrayTextKey(key)) {\r\n showArrayEditIcon(htmlEl)\r\n } else {\r\n showEditIcon(htmlEl)\r\n }\r\n })\r\n htmlEl.addEventListener('mouseleave', (e: MouseEvent) => {\r\n // Don't hide if moving to the edit icon or array edit icon\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && (\r\n related.classList?.contains('dcs-edit-icon') || related.closest?.('.dcs-edit-icon') ||\r\n related.classList?.contains('dcs-array-edit-icon') || related.closest?.('.dcs-array-edit-icon')\r\n )) return\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n })\r\n })\r\n\r\n // Set up section hover reporting (portal overlay handles the visual treatment)\r\n const sections = document.querySelectorAll<HTMLElement>('[data-section]')\r\n sections.forEach((el) => {\r\n const sectionId = el.dataset.section!\r\n\r\n el.addEventListener('mouseenter', () => {\r\n postToParent('dcs:section-hover', { sectionId })\r\n })\r\n el.addEventListener('mouseleave', () => {\r\n postToParent('dcs:section-hover', { sectionId: null })\r\n })\r\n el.addEventListener('click', (e) => {\r\n // Only fire section click if not clicking on a text-key element\r\n const target = e.target as HTMLElement\r\n if (!target.closest('[data-text-key]')) {\r\n postToParent('dcs:section-click', { sectionId })\r\n }\r\n })\r\n })\r\n}\r\n\r\n/**\r\n * Handle double-click on a text key element.\r\n * Notifies the parent portal to switch to edit mode and begins inline editing.\r\n */\r\nfunction handleTextKeyDblClick(e: Event) {\r\n if (!editingEnabled) return\r\n\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n const el = e.currentTarget as HTMLElement\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Always notify the parent to switch to edit mode when starting inline editing.\r\n // In explore mode this triggers the mode switch; in edit mode it's a no-op on the parent side.\r\n postToParent('dcs:text-key-dblclick', { key, sectionId })\r\n\r\n // Start inline editing\r\n startInlineEdit(el, key, sectionId)\r\n}\r\n\r\n/**\r\n * Start inline contenteditable editing on a text key element.\r\n * Called by double-click or when the parent sends dcs:select-text-key.\r\n */\r\nfunction startInlineEdit(el: HTMLElement, key: string, sectionId: string | null) {\r\n // Finish any active edit first\r\n if (activeEditElement && activeEditElement !== el) {\r\n finishEdit(activeEditElement)\r\n }\r\n\r\n // Store original text for change detection on finish\r\n originalTextValues.set(el, el.innerText.trim())\r\n\r\n // Enable contenteditable\r\n el.setAttribute('contenteditable', 'true')\r\n el.classList.add('dcs-editing')\r\n el.focus()\r\n activeEditElement = el\r\n\r\n // Select all text for easy replacement\r\n const selection = getSelection()\r\n const range = document.createRange()\r\n range.selectNodeContents(el)\r\n selection?.removeAllRanges()\r\n selection?.addRange(range)\r\n\r\n postToParent('dcs:text-key-click', { key, sectionId })\r\n}\r\n\r\nfunction handleTextKeyBlur(e: Event) {\r\n const el = e.currentTarget as HTMLElement\r\n finishEdit(el)\r\n}\r\n\r\nfunction handleTextKeyKeydown(e: KeyboardEvent) {\r\n if (e.key === 'Enter' && !e.shiftKey) {\r\n e.preventDefault()\r\n ;(e.currentTarget as HTMLElement).blur()\r\n }\r\n if (e.key === 'Escape') {\r\n e.preventDefault()\r\n ;(e.currentTarget as HTMLElement).blur()\r\n }\r\n}\r\n\r\nfunction finishEdit(el: HTMLElement) {\r\n if (!el.hasAttribute('contenteditable')) return\r\n\r\n el.removeAttribute('contenteditable')\r\n el.classList.remove('dcs-editing')\r\n\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n const newValue = el.innerText.trim()\r\n const originalValue = originalTextValues.get(el) ?? ''\r\n\r\n if (activeEditElement === el) {\r\n activeEditElement = null\r\n }\r\n\r\n // Only report to the portal when text was actually modified\r\n if (newValue !== originalValue) {\r\n postToParent('dcs:text-key-changed', { key, value: newValue, sectionId })\r\n }\r\n\r\n originalTextValues.delete(el)\r\n}\r\n\r\nfunction focusTextKey(key: string) {\r\n const el = document.querySelector<HTMLElement>(`[data-text-key=\"${key}\"]`)\r\n if (!el) return\r\n el.scrollIntoView({ behavior: 'smooth', block: 'center' })\r\n\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Start inline editing directly\r\n startInlineEdit(el, key, sectionId)\r\n}\r\n\r\nfunction updateTextInPlace(key: string, value: string) {\r\n const el = document.querySelector<HTMLElement>(`[data-text-key=\"${key}\"]`)\r\n if (!el) return\r\n // Don't update if we're currently editing this element\r\n if (el === activeEditElement) return\r\n el.innerText = value\r\n}\r\n\r\n// ─── Edit Icon (Floating Text Cursor Button) ────────────────────\r\n\r\n/**\r\n * Create the floating edit icon element. This small button appears\r\n * in the top-left corner of a hovered [data-text-key] element,\r\n * providing a click target to start inline editing without triggering\r\n * navigation (important for text inside buttons/links).\r\n */\r\nfunction createEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Edit text')\r\n icon.setAttribute('aria-label', 'Edit text inline')\r\n // SVG: Type icon (text cursor) — matches lucide \"Type\" icon\r\n icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"/><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"/></svg>`\r\n\r\n icon.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n if (!editIconTarget) return\r\n const el = editIconTarget\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Signal parent to switch to edit mode\r\n postToParent('dcs:text-key-dblclick', { key, sectionId })\r\n // Start inline editing\r\n startInlineEdit(el, key, sectionId)\r\n // Hide the icon while editing\r\n removeEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the target element)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && editIconTarget && (related === editIconTarget || editIconTarget.contains(related))) return\r\n removeEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the edit icon positioned at the top-left corner of the given element.\r\n */\r\nfunction showEditIcon(el: HTMLElement) {\r\n if (!editingEnabled) return\r\n\r\n if (!editIconElement) {\r\n editIconElement = createEditIcon()\r\n document.body.appendChild(editIconElement)\r\n }\r\n\r\n editIconTarget = el\r\n\r\n // Position relative to the element's bounding box\r\n const rect = el.getBoundingClientRect()\r\n editIconElement.style.top = `${rect.top + scrollY - 4}px`\r\n editIconElement.style.left = `${rect.left + scrollX - 4}px`\r\n editIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the edit icon.\r\n */\r\nfunction removeEditIcon() {\r\n if (editIconElement) {\r\n editIconElement.style.display = 'none'\r\n }\r\n editIconTarget = null\r\n}\r\n\r\n// ─── Array Key Detection ─────────────────────────────────────────\r\n\r\n/**\r\n * Check if a text key is part of an array pattern (e.g., \"conditions.0.title\").\r\n * Array text keys contain an indexed segment — either:\r\n * - Pure numeric: `baseKey.N.field` (e.g., `conditions.0.title`)\r\n * - Hyphenated index: `baseKey.prefix-N.field` (e.g., `steps.step-0.title`)\r\n */\r\nfunction isArrayTextKey(key: string): boolean {\r\n // Match pure numeric segment (.0.) or hyphenated index (.prefix-0.)\r\n return /\\.\\d+\\./.test(key) || /\\.\\w+-\\d+\\./.test(key)\r\n}\r\n\r\n/**\r\n * Extract the base array key from an array text key.\r\n * - `conditions.0.title` → `conditions`\r\n * - `steps.step-0.title` → `steps.step`\r\n */\r\nfunction getArrayBaseKey(key: string): string | null {\r\n // Try hyphenated pattern first (more specific)\r\n const hyphenMatch = key.match(/^(.+?)-\\d+\\./)\r\n if (hyphenMatch) return hyphenMatch[1]\r\n // Fall back to pure numeric pattern\r\n const numMatch = key.match(/^(.+?)\\.\\d+\\./)\r\n return numMatch ? numMatch[1] : null\r\n}\r\n\r\n// ─── Array Edit Icon (Floating List Button) ──────────────────────\r\n\r\n/**\r\n * Create the floating array edit icon element. This small button appears\r\n * in the top-left corner of a hovered [data-text-key] element whose key\r\n * matches an array pattern. Clicking it sends dcs:array-key-click to the\r\n * portal to open the array editor sheet.\r\n */\r\nfunction createArrayEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-array-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Edit list items')\r\n icon.setAttribute('aria-label', 'Edit list items in panel')\r\n // SVG: List icon — matches lucide \"List\" icon\r\n icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>`\r\n\r\n icon.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n if (!arrayEditIconTarget) return\r\n const el = arrayEditIconTarget\r\n const key = el.dataset.textKey!\r\n const arrayKey = getArrayBaseKey(key)\r\n if (!arrayKey) return\r\n\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Signal parent to open array editor sheet\r\n postToParent('dcs:array-key-click', { arrayKey, sectionId })\r\n // Hide the icon\r\n removeArrayEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the target element)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && arrayEditIconTarget && (related === arrayEditIconTarget || arrayEditIconTarget.contains(related))) return\r\n removeArrayEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the array edit icon positioned at the top-left corner of the given element.\r\n */\r\nfunction showArrayEditIcon(el: HTMLElement) {\r\n if (!editingEnabled) return\r\n\r\n if (!arrayEditIconElement) {\r\n arrayEditIconElement = createArrayEditIcon()\r\n document.body.appendChild(arrayEditIconElement)\r\n }\r\n\r\n arrayEditIconTarget = el\r\n\r\n // Position relative to the element's bounding box\r\n const rect = el.getBoundingClientRect()\r\n arrayEditIconElement.style.top = `${rect.top + scrollY - 4}px`\r\n arrayEditIconElement.style.left = `${rect.left + scrollX - 4}px`\r\n arrayEditIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the array edit icon.\r\n */\r\nfunction removeArrayEditIcon() {\r\n if (arrayEditIconElement) {\r\n arrayEditIconElement.style.display = 'none'\r\n }\r\n arrayEditIconTarget = null\r\n}\r\n\r\nfunction injectEditorStyles() {\r\n if (document.getElementById('dcs-editor-styles')) return\r\n\r\n const style = document.createElement('style')\r\n style.id = 'dcs-editor-styles'\r\n style.textContent = `\r\n [data-text-key].dcs-editable {\r\n cursor: text !important;\r\n border-radius: 4px;\r\n position: relative;\r\n }\r\n\r\n [data-text-key].dcs-editable:hover {\r\n outline: 2px dashed hsl(221.2 83.2% 53.3% / 0.4) !important;\r\n outline-offset: 4px;\r\n background-color: hsl(221.2 83.2% 53.3% / 0.03);\r\n }\r\n\r\n [data-text-key].dcs-editing {\r\n outline: 2px solid hsl(221.2 83.2% 53.3%) !important;\r\n outline-offset: 4px;\r\n background-color: hsl(221.2 83.2% 53.3% / 0.06);\r\n min-height: 1em;\r\n }\r\n\r\n [data-text-key].dcs-editing:focus {\r\n outline: 2px solid hsl(221.2 83.2% 53.3%) !important;\r\n outline-offset: 4px;\r\n }\r\n\r\n .dcs-section-highlight {\r\n pointer-events: none;\r\n }\r\n\r\n /* Floating edit icon button */\r\n .dcs-edit-icon {\r\n position: absolute;\r\n z-index: 99999;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 0;\r\n margin: 0;\r\n border: 1.5px solid hsl(221.2 83.2% 53.3%);\r\n border-radius: 4px;\r\n background: hsl(221.2 83.2% 53.3% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(221.2 83.2% 53.3%);\r\n cursor: pointer;\r\n pointer-events: auto;\r\n transition: background 0.15s, transform 0.1s;\r\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n }\r\n\r\n .dcs-edit-icon:hover {\r\n background: hsl(221.2 83.2% 53.3% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-edit-icon svg {\r\n display: block;\r\n }\r\n\r\n /* Floating array edit icon button */\r\n .dcs-array-edit-icon {\r\n position: absolute;\r\n z-index: 99999;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 0;\r\n margin: 0;\r\n border: 1.5px solid hsl(271 91% 65%);\r\n border-radius: 4px;\r\n background: hsl(271 91% 65% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(271 91% 65%);\r\n cursor: pointer;\r\n pointer-events: auto;\r\n transition: background 0.15s, transform 0.1s;\r\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n }\r\n\r\n .dcs-array-edit-icon:hover {\r\n background: hsl(271 91% 65% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-array-edit-icon svg {\r\n display: block;\r\n }\r\n `\r\n document.head.appendChild(style)\r\n}\r\n\r\n// ─── Inbound Message Handler ─────────────────────────────────────\r\n\r\nfunction handleMessage(event: MessageEvent) {\r\n // Validate message structure (origin is not checked because\r\n // the parent portal origin varies between dev and production)\r\n const msg = event.data\r\n if (!msg || typeof msg !== 'object' || typeof msg.type !== 'string') return\r\n if (!msg.type.startsWith('dcs:')) return\r\n\r\n const { type, data } = msg as { type: InboundMessageType; data: unknown }\r\n\r\n switch (type) {\r\n case 'dcs:init-editor':\r\n enableEditorMode()\r\n break\r\n\r\n case 'dcs:highlight-section':\r\n if (data && typeof data === 'object' && 'sectionId' in data) {\r\n showSectionHighlight((data as { sectionId: string }).sectionId)\r\n }\r\n break\r\n\r\n case 'dcs:clear-highlight':\r\n clearSectionHighlight()\r\n break\r\n\r\n case 'dcs:select-text-key':\r\n if (data && typeof data === 'object' && 'key' in data) {\r\n focusTextKey((data as { key: string }).key)\r\n }\r\n break\r\n\r\n case 'dcs:update-text':\r\n if (data && typeof data === 'object' && 'key' in data && 'value' in data) {\r\n const d = data as { key: string; value: string }\r\n updateTextInPlace(d.key, d.value)\r\n }\r\n break\r\n\r\n case 'dcs:set-mode':\r\n if (data && typeof data === 'object' && 'mode' in data) {\r\n setMode((data as { mode: 'edit' | 'explore' }).mode)\r\n }\r\n break\r\n\r\n case 'dcs:set-editing-enabled':\r\n if (data && typeof data === 'object' && 'enabled' in data) {\r\n editingEnabled = (data as { enabled: boolean }).enabled\r\n if (editingEnabled) {\r\n // Re-add dcs-editable class to text key elements\r\n document.querySelectorAll<HTMLElement>('[data-text-key]').forEach(el => {\r\n el.classList.add('dcs-editable')\r\n })\r\n } else {\r\n // Clean up: finish any active edit, remove edit icon, remove hover styles\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n // Remove dcs-editable class to disable hover outlines\r\n document.querySelectorAll<HTMLElement>('.dcs-editable').forEach(el => {\r\n el.classList.remove('dcs-editable')\r\n })\r\n }\r\n }\r\n break\r\n }\r\n}\r\n\r\n// ─── Bootstrap ───────────────────────────────────────────────────\r\n\r\n/**\r\n * Inject a stylesheet that neutralizes viewport-relative min-heights.\r\n * In the visual editor iframe, the portal controls height from body.scrollHeight.\r\n * CSS rules like `min-height: 100vh` create a feedback loop:\r\n * iframe height → vh grows → body.scrollHeight grows → iframe height grows → …\r\n * Neutralizing these rules lets the content determine its natural height.\r\n */\r\nfunction injectEditorHeightFix() {\r\n if (document.getElementById('dcs-editor-height-fix')) return\r\n const style = document.createElement('style')\r\n style.id = 'dcs-editor-height-fix'\r\n style.textContent = `\r\n /* DCS Editor Bridge: Neutralize viewport-relative min-heights */\r\n .min-h-screen, .min-h-dvh, .min-h-svh,\r\n .min-h-\\\\[100vh\\\\], .min-h-\\\\[100dvh\\\\], .min-h-\\\\[100svh\\\\] {\r\n min-height: 0 !important;\r\n }\r\n `\r\n document.head.appendChild(style)\r\n}\r\n\r\n/** Last reported content height — used to avoid re-sending when stable */\r\nlet lastReportedHeight = 0\r\n\r\n/**\r\n * Measure the true content height, avoiding inflation from viewport-relative\r\n * CSS rules. Uses section bounds as the primary signal (immune to vh feedback),\r\n * falling back to body.scrollHeight (which is reliable once the editor height\r\n * fix stylesheet has been injected).\r\n */\r\nfunction measureContentHeight(sections: SectionInfo[]): number {\r\n // Primary: compute from section bounds — these reflect actual element positions\r\n // after the height-fix stylesheet has neutralized viewport-relative min-heights.\r\n let maxBottom = 0\r\n for (const s of sections) {\r\n const bottom = s.bounds.y + s.bounds.height\r\n if (bottom > maxBottom) maxBottom = bottom\r\n }\r\n const sectionHeight = maxBottom > 0 ? Math.ceil(maxBottom) : 0\r\n\r\n // Fallback: body.scrollHeight (reliable now that min-height: 100vh is neutralized)\r\n const bodyHeight = document.body.scrollHeight\r\n\r\n // Use whichever is larger — sections might miss elements without data-section,\r\n // but bodyHeight should be accurate with the injected CSS fix.\r\n return Math.max(sectionHeight, bodyHeight)\r\n}\r\n\r\nexport function initEditorBridge() {\r\n if (!isInIframe()) return // Not in iframe\r\n\r\n // Inject height fix CSS before any measurement\r\n injectEditorHeightFix()\r\n\r\n // Prevent duplicate listener registration on HMR re-execution\r\n if (!bridgeInitialized) {\r\n bridgeInitialized = true\r\n\r\n // Monitor navigation within the iframe — notify the portal of page changes\r\n monitorNavigation()\r\n\r\n // Forward right-click events to the portal so the context menu\r\n // works when right-clicking inside the iframe (cross-origin boundary\r\n // prevents the parent from receiving native contextmenu events).\r\n document.addEventListener('contextmenu', (e: MouseEvent) => {\r\n e.preventDefault()\r\n const target = e.target as HTMLElement\r\n const sectionEl = target.closest?.('[data-section]') as HTMLElement | null\r\n\r\n // Detect if the right-click target is an image or inside a <picture> element\r\n const imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n\r\n postToParent('dcs:contextmenu', {\r\n x: e.clientX,\r\n y: e.clientY,\r\n sectionId: sectionEl?.dataset.section ?? null,\r\n sectionLabel: sectionEl?.dataset.sectionLabel ?? null,\r\n // Image context (null when not right-clicking on an image)\r\n imageUrl: imgEl?.currentSrc ?? imgEl?.src ?? null,\r\n imageAlt: imgEl?.alt ?? null,\r\n imageWidth: imgEl?.naturalWidth ?? null,\r\n imageHeight: imgEl?.naturalHeight ?? null,\r\n })\r\n }, true)\r\n\r\n // Forward click events to the portal so it can dismiss context menus\r\n // when the user clicks inside the iframe. Also detect image clicks\r\n // in edit mode to enable direct image management.\r\n document.addEventListener('click', (e: MouseEvent) => {\r\n postToParent('dcs:click', {})\r\n\r\n // In edit mode, detect image clicks for image management\r\n if (editorActive && editingEnabled) {\r\n const target = e.target as HTMLElement\r\n const imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n if (imgEl) {\r\n const sectionEl = target.closest?.('[data-section]') as HTMLElement | null\r\n postToParent('dcs:image-click', {\r\n imageUrl: imgEl.currentSrc ?? imgEl.src,\r\n imageAlt: imgEl.alt ?? null,\r\n imageWidth: imgEl.naturalWidth,\r\n imageHeight: imgEl.naturalHeight,\r\n sectionId: sectionEl?.dataset.section ?? null,\r\n sectionLabel: sectionEl?.dataset.sectionLabel ?? null,\r\n })\r\n }\r\n }\r\n }, true)\r\n\r\n // Listen for portal commands. Origin is not validated because\r\n // the portal origin varies between environments and all inbound\r\n // messages are type-checked before processing.\r\n // Origin cannot be pre-validated because the portal URL varies by environment.\r\n // All inbound messages are type-checked via the dcs: prefix guard.\r\n globalThis.self.addEventListener('message', handleMessage) // NOSONAR\r\n\r\n // Watch for VitePress / Vite HMR page updates. When the framework\r\n // replaces DOM content, our event listeners and AI buttons are lost.\r\n // Re-discover sections after each update so the portal overlay stays in sync.\r\n if (import.meta.hot) {\r\n import.meta.hot.on('vite:afterUpdate', () => {\r\n // Small delay for the framework to finish DOM updates\r\n setTimeout(rediscoverAndNotify, 300)\r\n })\r\n }\r\n }\r\n\r\n // Report available sections, text keys, and content height\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n lastReportedHeight = contentHeight\r\n\r\n postToParent('dcs:ready', {\r\n sections,\r\n textKeys,\r\n contentHeight,\r\n } satisfies EditorReadyPayload)\r\n\r\n // Observe layout changes (lazy images, dynamic content) and re-report bounds\r\n observeSectionLayout()\r\n}\r\n\r\n// Auto-initialize when the script is loaded (e.g., injected by Vite plugin)\r\n// Delay slightly to ensure the page is fully rendered\r\nif (typeof globalThis.self !== 'undefined' && isInIframe()) {\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', () => {\r\n // Small delay for Vue/framework hydration\r\n setTimeout(initEditorBridge, 200)\r\n })\r\n } else {\r\n setTimeout(initEditorBridge, 200)\r\n }\r\n}\r\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as vue from 'vue';
2
- import { ComputedRef } from 'vue';
2
+ import { ComputedRef, MaybeRefOrGetter } from 'vue';
3
+ import { ImageContext, ResponsiveImageResult } from '@duffcloudservices/cms-core';
4
+ export { ImageContext, ResponsiveImageOptions, ResponsiveImageResult, ResponsiveSource, isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
3
5
 
4
6
  /**
5
7
  * Types for .dcs/content.yaml structure
@@ -582,6 +584,12 @@ interface MediaCarouselItem {
582
584
  type: 'image' | 'video' | 'youtube' | 'instagram';
583
585
  /** Accessibility alt text */
584
586
  alt?: string;
587
+ /**
588
+ * Whether this image has responsive CDN variants available.
589
+ * Automatically set to `true` when the URL matches the DCS CDN asset pattern.
590
+ * Components rendering the carousel should use `<ResponsiveImage>` when this is `true`.
591
+ */
592
+ responsive?: boolean;
585
593
  }
586
594
  /**
587
595
  * Configuration for useMediaCarousel composable
@@ -622,4 +630,59 @@ interface UseMediaCarouselReturn {
622
630
  */
623
631
  declare function useMediaCarousel(config: UseMediaCarouselConfig): UseMediaCarouselReturn;
624
632
 
625
- export { type DcsContentFile, type GlobalSeoConfig, type HeadOverrides, type MediaCarouselItem, type PageSeoConfig, type ReleaseNote, type ReleaseNotesReturn, type ResolvedPageSeo, type SeoAlternateConfig, type SeoAuthorConfig, type SeoConfiguration, type SeoImagesConfig, type SeoOpenGraphConfig, type SeoSchemaConfig, type SeoSocialConfig, type SeoTwitterConfig, type SeoVerificationConfig, type SiteVersionReturn, type TextContentConfig, type TextContentReturn, type UseMediaCarouselConfig, type UseMediaCarouselReturn, type UseSeoConfig, type UseSeoReturn, createSiteSEO, useMediaCarousel, useReleaseNotes, useSEO, useSiteVersion, useTextContent };
633
+ /**
634
+ * Vue 3 composable that resolves responsive image variants for DCS CDN-hosted assets.
635
+ *
636
+ * Wraps the framework-agnostic `resolveResponsiveImage` from `@duffcloudservices/cms-core`
637
+ * with reactive Vue refs so it can be used directly in `<script setup>` blocks.
638
+ *
639
+ * @example
640
+ * ```vue
641
+ * <script setup lang="ts">
642
+ * import { useResponsiveImage } from '@duffcloudservices/cms'
643
+ *
644
+ * const hero = useResponsiveImage({
645
+ * src: 'https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg',
646
+ * alt: 'Hero image',
647
+ * context: 'hero',
648
+ * })
649
+ * </script>
650
+ *
651
+ * <template>
652
+ * <picture v-if="hero.hasVariants">
653
+ * <source
654
+ * v-for="source in hero.sources"
655
+ * :key="source.type"
656
+ * :srcset="source.srcset"
657
+ * :type="source.type"
658
+ * :sizes="source.sizes"
659
+ * />
660
+ * <img v-bind="hero.imgProps" class="w-full h-full object-cover" />
661
+ * </picture>
662
+ * <img v-else v-bind="hero.imgProps" class="w-full h-full object-cover" />
663
+ * </template>
664
+ * ```
665
+ */
666
+
667
+ interface UseResponsiveImageOptions {
668
+ /** Source URL — can be a reactive ref, getter, or plain string. */
669
+ src: MaybeRefOrGetter<string>;
670
+ /** Alt text — can be a reactive ref, getter, or plain string. */
671
+ alt: MaybeRefOrGetter<string>;
672
+ /** Sizing context — determines which variants to include. */
673
+ context?: MaybeRefOrGetter<ImageContext | undefined>;
674
+ /** Optional `sizes` attribute override. */
675
+ sizes?: MaybeRefOrGetter<string | undefined>;
676
+ /** Skip variant resolution and use the original URL only. */
677
+ original?: MaybeRefOrGetter<boolean | undefined>;
678
+ }
679
+ /**
680
+ * Reactively resolves responsive image metadata for a DCS CDN URL.
681
+ *
682
+ * The returned object is a computed ref that recomputes whenever any
683
+ * of the input refs change. Spread `imgProps` onto an `<img>` or
684
+ * combine with `sources` inside a `<picture>` element.
685
+ */
686
+ declare function useResponsiveImage(options: UseResponsiveImageOptions): ResponsiveImageResult;
687
+
688
+ export { type DcsContentFile, type GlobalSeoConfig, type HeadOverrides, type MediaCarouselItem, type PageSeoConfig, type ReleaseNote, type ReleaseNotesReturn, type ResolvedPageSeo, type SeoAlternateConfig, type SeoAuthorConfig, type SeoConfiguration, type SeoImagesConfig, type SeoOpenGraphConfig, type SeoSchemaConfig, type SeoSocialConfig, type SeoTwitterConfig, type SeoVerificationConfig, type SiteVersionReturn, type TextContentConfig, type TextContentReturn, type UseMediaCarouselConfig, type UseMediaCarouselReturn, type UseSeoConfig, type UseSeoReturn, createSiteSEO, useMediaCarousel, useReleaseNotes, useResponsiveImage, useSEO, useSiteVersion, useTextContent };
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
- import { ref, computed, onMounted, readonly } from 'vue';
1
+ import { ref, computed, onMounted, readonly, toValue } from 'vue';
2
2
  import { useHead } from '@unhead/vue';
3
+ import { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
4
+ export { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
3
5
 
4
6
  // src/composables/useTextContent.ts
5
7
  var fetchCache = /* @__PURE__ */ new Map();
@@ -529,7 +531,10 @@ function useMediaCarousel(config) {
529
531
  result.push({
530
532
  url,
531
533
  type,
532
- alt: alt && alt !== altKey ? alt : void 0
534
+ alt: alt && alt !== altKey ? alt : void 0,
535
+ // Flag CDN-hosted images as responsive so carousel components
536
+ // can render them with <ResponsiveImage> automatically
537
+ responsive: type === "image" && isCdnAssetUrl(url)
533
538
  });
534
539
  }
535
540
  if (result.length === 0) {
@@ -545,7 +550,29 @@ function useMediaCarousel(config) {
545
550
  count
546
551
  };
547
552
  }
553
+ function useResponsiveImage(options) {
554
+ const result = computed(
555
+ () => resolveResponsiveImage({
556
+ src: toValue(options.src),
557
+ alt: toValue(options.alt),
558
+ context: toValue(options.context),
559
+ sizes: toValue(options.sizes),
560
+ original: toValue(options.original) ?? void 0
561
+ })
562
+ );
563
+ return {
564
+ get imgProps() {
565
+ return result.value.imgProps;
566
+ },
567
+ get sources() {
568
+ return result.value.sources;
569
+ },
570
+ get hasVariants() {
571
+ return result.value.hasVariants;
572
+ }
573
+ };
574
+ }
548
575
 
549
- export { createSiteSEO, useMediaCarousel, useReleaseNotes, useSEO, useSiteVersion, useTextContent };
576
+ export { createSiteSEO, useMediaCarousel, useReleaseNotes, useResponsiveImage, useSEO, useSiteVersion, useTextContent };
550
577
  //# sourceMappingURL=index.js.map
551
578
  //# sourceMappingURL=index.js.map