@duffcloudservices/cms 0.3.3 → 0.3.5

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.
@@ -8,6 +8,7 @@
8
8
  * 4. Communicate all interactions back to the parent via postMessage
9
9
  * 5. Monitor navigation within the iframe and notify the parent
10
10
  * 6. Show floating edit icon on hover for text-key elements
11
+ * 7. Show floating image icon on hover for images within sections
11
12
  *
12
13
  * Supports both legacy `data-text-key` and modern `data-dcs-text` attributes
13
14
  * for backward compatibility during the migration period.
@@ -25,6 +25,8 @@ var editIconElement = null;
25
25
  var editIconTarget = null;
26
26
  var arrayEditIconElement = null;
27
27
  var arrayEditIconTarget = null;
28
+ var imageEditIconElement = null;
29
+ var imageEditIconTarget = null;
28
30
  var editingEnabled = true;
29
31
  var originalTextValues = /* @__PURE__ */ new Map();
30
32
  var sectionResizeObserver = null;
@@ -197,6 +199,11 @@ function applyEditorToElements() {
197
199
  });
198
200
  el.addEventListener("mouseleave", () => {
199
201
  postToParent("dcs:section-hover", { sectionId: null });
202
+ setTimeout(() => {
203
+ if (imageEditIconElement && !imageEditIconElement.matches(":hover")) {
204
+ removeImageEditIcon();
205
+ }
206
+ }, 100);
200
207
  });
201
208
  el.addEventListener("click", (e) => {
202
209
  const target = e.target;
@@ -204,6 +211,25 @@ function applyEditorToElements() {
204
211
  postToParent("dcs:section-click", { sectionId });
205
212
  }
206
213
  });
214
+ el.addEventListener("mousemove", (e) => {
215
+ if (!editorActive || !editingEnabled || activeEditElement) {
216
+ removeImageEditIcon();
217
+ return;
218
+ }
219
+ const images = el.querySelectorAll("img");
220
+ for (const img of images) {
221
+ if (img.naturalWidth > 0 && img.naturalWidth < 50 && img.naturalHeight < 50) continue;
222
+ const rect = img.getBoundingClientRect();
223
+ if (rect.width < 30 || rect.height < 30) continue;
224
+ if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) {
225
+ showImageEditIcon(img);
226
+ return;
227
+ }
228
+ }
229
+ if (imageEditIconTarget && !imageEditIconElement?.matches(":hover")) {
230
+ removeImageEditIcon();
231
+ }
232
+ });
207
233
  });
208
234
  if (!blogContentInitialized) {
209
235
  const blogContentEl = document.querySelector("[data-blog-content]");
@@ -394,6 +420,56 @@ function removeArrayEditIcon() {
394
420
  }
395
421
  arrayEditIconTarget = null;
396
422
  }
423
+ function createImageEditIcon() {
424
+ const icon = document.createElement("button");
425
+ icon.className = "dcs-image-edit-icon";
426
+ icon.setAttribute("type", "button");
427
+ icon.setAttribute("title", "Manage image");
428
+ icon.setAttribute("aria-label", "Manage image");
429
+ 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"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`;
430
+ icon.addEventListener("click", (e) => {
431
+ e.preventDefault();
432
+ e.stopPropagation();
433
+ e.stopImmediatePropagation();
434
+ if (!imageEditIconTarget) return;
435
+ const img = imageEditIconTarget;
436
+ const sectionEl = img.closest("[data-section]");
437
+ postToParent("dcs:image-click", {
438
+ imageUrl: img.currentSrc ?? img.src,
439
+ imageAlt: img.alt ?? null,
440
+ imageWidth: img.naturalWidth,
441
+ imageHeight: img.naturalHeight,
442
+ sectionId: sectionEl?.dataset.section ?? null,
443
+ sectionLabel: sectionEl?.dataset.sectionLabel ?? null
444
+ });
445
+ removeImageEditIcon();
446
+ }, true);
447
+ icon.addEventListener("mouseleave", (e) => {
448
+ const related = e.relatedTarget;
449
+ if (related && imageEditIconTarget && (related === imageEditIconTarget || imageEditIconTarget.contains(related))) return;
450
+ removeImageEditIcon();
451
+ });
452
+ return icon;
453
+ }
454
+ function showImageEditIcon(img) {
455
+ if (!editingEnabled) return;
456
+ if (imageEditIconTarget === img && imageEditIconElement?.style.display === "flex") return;
457
+ if (!imageEditIconElement) {
458
+ imageEditIconElement = createImageEditIcon();
459
+ document.body.appendChild(imageEditIconElement);
460
+ }
461
+ imageEditIconTarget = img;
462
+ const rect = img.getBoundingClientRect();
463
+ imageEditIconElement.style.top = `${rect.top + scrollY + 4}px`;
464
+ imageEditIconElement.style.left = `${rect.right + scrollX - 28}px`;
465
+ imageEditIconElement.style.display = "flex";
466
+ }
467
+ function removeImageEditIcon() {
468
+ if (imageEditIconElement) {
469
+ imageEditIconElement.style.display = "none";
470
+ }
471
+ imageEditIconTarget = null;
472
+ }
397
473
  function injectEditorStyles() {
398
474
  if (document.getElementById("dcs-editor-styles")) return;
399
475
  const style = document.createElement("style");
@@ -493,6 +569,37 @@ function injectEditorStyles() {
493
569
  display: block;
494
570
  }
495
571
 
572
+ /* Floating image edit icon button \u2014 green to distinguish from text (blue) and array (violet) */
573
+ .dcs-image-edit-icon {
574
+ position: absolute;
575
+ z-index: 99999;
576
+ display: none;
577
+ align-items: center;
578
+ justify-content: center;
579
+ width: 24px;
580
+ height: 24px;
581
+ padding: 0;
582
+ margin: 0;
583
+ border: 1.5px solid hsl(142 71% 45%);
584
+ border-radius: 4px;
585
+ background: hsl(142 71% 45% / 0.1);
586
+ backdrop-filter: blur(4px);
587
+ color: hsl(142 71% 45%);
588
+ cursor: pointer;
589
+ pointer-events: auto;
590
+ transition: background 0.15s, transform 0.1s;
591
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12);
592
+ }
593
+
594
+ .dcs-image-edit-icon:hover {
595
+ background: hsl(142 71% 45% / 0.25);
596
+ transform: scale(1.1);
597
+ }
598
+
599
+ .dcs-image-edit-icon svg {
600
+ display: block;
601
+ }
602
+
496
603
  /* Blog content area \u2014 click to open WYSIWYG editor */
497
604
  [data-blog-content].dcs-blog-content {
498
605
  cursor: pointer;
@@ -576,6 +683,7 @@ function handleMessage(event) {
576
683
  }
577
684
  removeEditIcon();
578
685
  removeArrayEditIcon();
686
+ removeImageEditIcon();
579
687
  document.querySelectorAll(".dcs-editable").forEach((el) => {
580
688
  el.classList.remove("dcs-editable");
581
689
  });
@@ -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/data-dcs-text 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 * Supports both legacy `data-text-key` and modern `data-dcs-text` attributes\r\n * for backward compatibility during the migration period.\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 /** Whether the page has a [data-blog-content] element (blog post page) */\r\n hasBlogContent: boolean\r\n /** Current innerHTML of the blog content element, if present */\r\n blogContentHtml: string | null\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 | 'dcs:update-blog-content'\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 | 'dcs:blog-content-click'\r\n | 'dcs:blog-content-ready'\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// ─── Attribute Compatibility ─────────────────────────────────────\r\n\r\n/**\r\n * CSS selector that matches elements with either the legacy `data-text-key`\r\n * attribute or the modern `data-dcs-text` attribute. This ensures the editor\r\n * bridge works with both pre-migration and post-migration sites.\r\n */\r\nconst TEXT_KEY_SELECTOR = '[data-text-key], [data-dcs-text]'\r\n\r\n/**\r\n * Get the text key value from an element, checking the modern `data-dcs-text`\r\n * attribute first, then falling back to the legacy `data-text-key`.\r\n */\r\nfunction getTextKey(el: HTMLElement): string {\r\n return el.dataset.dcsText ?? el.dataset.textKey ?? ''\r\n}\r\n\r\n/**\r\n * Build a CSS selector that matches a specific text key value\r\n * across both legacy and modern attribute names.\r\n */\r\nfunction textKeySelector(key: string): string {\r\n return `[data-text-key=\"${key}\"], [data-dcs-text=\"${key}\"]`\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/** Track elements that have had editor event listeners attached (prevents duplicate listeners) */\r\nconst initializedTextElements = new WeakSet<HTMLElement>()\r\nconst initializedSections = new WeakSet<HTMLElement>()\r\nlet blogContentInitialized = false\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(TEXT_KEY_SELECTOR).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>(TEXT_KEY_SELECTOR)).map(\r\n (el) => getTextKey(el),\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 // Detect blog content element\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n const hasBlogContent = !!blogEl\r\n const blogContentHtml = blogEl ? blogEl.innerHTML : null\r\n\r\n // Always report — sections/textKeys may have changed even if height is stable\r\n postToParent('dcs:ready', { sections, textKeys, contentHeight, hasBlogContent, blogContentHtml } satisfies EditorReadyPayload)\r\n\r\n // Apply editor features to any new elements (e.g., after HMR or navigation)\r\n // without re-creating the ResizeObserver (which would cause an infinite loop)\r\n if (editorActive) {\r\n applyEditorToElements()\r\n\r\n // Observe any new section elements that appeared after navigation/HMR.\r\n // ResizeObserver.observe() is idempotent — safe to call on already-observed elements.\r\n if (sectionResizeObserver) {\r\n document.querySelectorAll<HTMLElement>('[data-section]').forEach(el => {\r\n sectionResizeObserver!.observe(el)\r\n })\r\n }\r\n }\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 // Apply editor features (classes + listeners) to all current elements\r\n applyEditorToElements()\r\n}\r\n\r\n/**\r\n * Apply editor features (classes + event listeners) to elements on the page.\r\n * Uses WeakSets to track which elements have already been initialized,\r\n * preventing duplicate event listeners on repeated calls (e.g., after HMR\r\n * updates or layout-triggered re-discovery).\r\n */\r\nfunction applyEditorToElements() {\r\n // Set up all text key elements — double-click to start editing\r\n const textElements = document.querySelectorAll<HTMLElement>(TEXT_KEY_SELECTOR)\r\n textElements.forEach((htmlEl) => {\r\n // Always ensure the class is present (may have been removed by set-editing-enabled)\r\n htmlEl.classList.add('dcs-editable')\r\n\r\n // Only add event listeners once per element\r\n if (initializedTextElements.has(htmlEl)) return\r\n initializedTextElements.add(htmlEl)\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 = getTextKey(htmlEl)\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 if (initializedSections.has(el)) return\r\n initializedSections.add(el)\r\n\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(TEXT_KEY_SELECTOR)) {\r\n postToParent('dcs:section-click', { sectionId })\r\n }\r\n })\r\n })\r\n\r\n // Set up blog content area — single click to open blog content editor\r\n if (!blogContentInitialized) {\r\n const blogContentEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n if (blogContentEl) {\r\n blogContentInitialized = true\r\n blogContentEl.classList.add('dcs-blog-content')\r\n blogContentEl.addEventListener('click', (e) => {\r\n // Don't trigger blog content click if clicking on a text-key element\r\n const target = e.target as HTMLElement\r\n if (target.closest(TEXT_KEY_SELECTOR)) return\r\n e.preventDefault()\r\n e.stopPropagation()\r\n postToParent('dcs:blog-content-click', { textKey: 'blog-content' })\r\n })\r\n // Also send the current content to the parent for initial load\r\n postToParent('dcs:blog-content-ready', { content: blogContentEl.innerHTML })\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 = getTextKey(el)\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 = getTextKey(el)\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>(textKeySelector(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>(textKeySelector(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] or [data-dcs-text] 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 = getTextKey(el)\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] or [data-dcs-text] 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 = getTextKey(el)\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 [data-dcs-text].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 [data-dcs-text].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 [data-dcs-text].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 [data-dcs-text].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 /* Blog content area — click to open WYSIWYG editor */\r\n [data-blog-content].dcs-blog-content {\r\n cursor: pointer;\r\n border-radius: 8px;\r\n position: relative;\r\n transition: outline-color 0.2s, background-color 0.2s;\r\n }\r\n\r\n [data-blog-content].dcs-blog-content:hover {\r\n outline: 2px dashed hsl(142 71% 45% / 0.5) !important;\r\n outline-offset: 8px;\r\n background-color: hsl(142 71% 45% / 0.03);\r\n }\r\n\r\n [data-blog-content].dcs-blog-content::after {\r\n content: 'Click to edit blog content';\r\n position: absolute;\r\n top: -28px;\r\n left: 8px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n color: hsl(142 71% 45%);\r\n background: hsl(142 71% 45% / 0.1);\r\n border: 1px solid hsl(142 71% 45% / 0.3);\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n opacity: 0;\r\n transition: opacity 0.2s;\r\n pointer-events: none;\r\n white-space: nowrap;\r\n }\r\n\r\n [data-blog-content].dcs-blog-content:hover::after {\r\n opacity: 1;\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>(TEXT_KEY_SELECTOR).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 case 'dcs:update-blog-content':\r\n if (data && typeof data === 'object' && 'content' in data) {\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n if (blogEl) {\r\n blogEl.innerHTML = (data as { content: string }).content\r\n // Trigger re-discovery so the portal overlay updates for any height changes\r\n scheduleRediscovery()\r\n }\r\n }\r\n break\r\n }\r\n}\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 // Detect blog content element for the initial ready report\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n\r\n postToParent('dcs:ready', {\r\n sections,\r\n textKeys,\r\n contentHeight,\r\n hasBlogContent: !!blogEl,\r\n blogContentHtml: blogEl ? blogEl.innerHTML : null,\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// NOTE: Auto-initialization is NOT done here. The Vite plugin's virtual module\r\n// (dcsEditorPlugin) handles initialization by importing and calling initEditorBridge().\r\n// Having auto-init as a side effect of the import caused double-initialization\r\n// (two dcs:ready messages on startup).\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/data-dcs-text 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 * 7. Show floating image icon on hover for images within sections\r\n *\r\n * Supports both legacy `data-text-key` and modern `data-dcs-text` attributes\r\n * for backward compatibility during the migration period.\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 /** Whether the page has a [data-blog-content] element (blog post page) */\r\n hasBlogContent: boolean\r\n /** Current innerHTML of the blog content element, if present */\r\n blogContentHtml: string | null\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 | 'dcs:update-blog-content'\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 | 'dcs:blog-content-click'\r\n | 'dcs:blog-content-ready'\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// ─── Attribute Compatibility ─────────────────────────────────────\r\n\r\n/**\r\n * CSS selector that matches elements with either the legacy `data-text-key`\r\n * attribute or the modern `data-dcs-text` attribute. This ensures the editor\r\n * bridge works with both pre-migration and post-migration sites.\r\n */\r\nconst TEXT_KEY_SELECTOR = '[data-text-key], [data-dcs-text]'\r\n\r\n/**\r\n * Get the text key value from an element, checking the modern `data-dcs-text`\r\n * attribute first, then falling back to the legacy `data-text-key`.\r\n */\r\nfunction getTextKey(el: HTMLElement): string {\r\n return el.dataset.dcsText ?? el.dataset.textKey ?? ''\r\n}\r\n\r\n/**\r\n * Build a CSS selector that matches a specific text key value\r\n * across both legacy and modern attribute names.\r\n */\r\nfunction textKeySelector(key: string): string {\r\n return `[data-text-key=\"${key}\"], [data-dcs-text=\"${key}\"]`\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/** The floating edit icon for image elements within sections */\r\nlet imageEditIconElement: HTMLElement | null = null\r\n/** The image element the image edit icon is currently positioned on */\r\nlet imageEditIconTarget: HTMLImageElement | 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/** Track elements that have had editor event listeners attached (prevents duplicate listeners) */\r\nconst initializedTextElements = new WeakSet<HTMLElement>()\r\nconst initializedSections = new WeakSet<HTMLElement>()\r\nlet blogContentInitialized = false\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(TEXT_KEY_SELECTOR).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>(TEXT_KEY_SELECTOR)).map(\r\n (el) => getTextKey(el),\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 // Detect blog content element\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n const hasBlogContent = !!blogEl\r\n const blogContentHtml = blogEl ? blogEl.innerHTML : null\r\n\r\n // Always report — sections/textKeys may have changed even if height is stable\r\n postToParent('dcs:ready', { sections, textKeys, contentHeight, hasBlogContent, blogContentHtml } satisfies EditorReadyPayload)\r\n\r\n // Apply editor features to any new elements (e.g., after HMR or navigation)\r\n // without re-creating the ResizeObserver (which would cause an infinite loop)\r\n if (editorActive) {\r\n applyEditorToElements()\r\n\r\n // Observe any new section elements that appeared after navigation/HMR.\r\n // ResizeObserver.observe() is idempotent — safe to call on already-observed elements.\r\n if (sectionResizeObserver) {\r\n document.querySelectorAll<HTMLElement>('[data-section]').forEach(el => {\r\n sectionResizeObserver!.observe(el)\r\n })\r\n }\r\n }\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 // Apply editor features (classes + listeners) to all current elements\r\n applyEditorToElements()\r\n}\r\n\r\n/**\r\n * Apply editor features (classes + event listeners) to elements on the page.\r\n * Uses WeakSets to track which elements have already been initialized,\r\n * preventing duplicate event listeners on repeated calls (e.g., after HMR\r\n * updates or layout-triggered re-discovery).\r\n */\r\nfunction applyEditorToElements() {\r\n // Set up all text key elements — double-click to start editing\r\n const textElements = document.querySelectorAll<HTMLElement>(TEXT_KEY_SELECTOR)\r\n textElements.forEach((htmlEl) => {\r\n // Always ensure the class is present (may have been removed by set-editing-enabled)\r\n htmlEl.classList.add('dcs-editable')\r\n\r\n // Only add event listeners once per element\r\n if (initializedTextElements.has(htmlEl)) return\r\n initializedTextElements.add(htmlEl)\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 = getTextKey(htmlEl)\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 if (initializedSections.has(el)) return\r\n initializedSections.add(el)\r\n\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 // Delayed cleanup of image icon — allows cursor to reach the icon\r\n setTimeout(() => {\r\n if (imageEditIconElement && !imageEditIconElement.matches(':hover')) {\r\n removeImageEditIcon()\r\n }\r\n }, 100)\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(TEXT_KEY_SELECTOR)) {\r\n postToParent('dcs:section-click', { sectionId })\r\n }\r\n })\r\n\r\n // Image hover detection — detect when cursor is over an <img> inside a section.\r\n // Uses mousemove on the section because some images (e.g., inside sliders)\r\n // have pointer-events: none and cannot receive mouseenter directly.\r\n el.addEventListener('mousemove', (e: MouseEvent) => {\r\n if (!editorActive || !editingEnabled || activeEditElement) {\r\n removeImageEditIcon()\r\n return\r\n }\r\n\r\n const images = el.querySelectorAll<HTMLImageElement>('img')\r\n for (const img of images) {\r\n // Skip tiny images (icons, spacers, SVG fallbacks)\r\n if (img.naturalWidth > 0 && img.naturalWidth < 50 && img.naturalHeight < 50) continue\r\n const rect = img.getBoundingClientRect()\r\n if (rect.width < 30 || rect.height < 30) continue\r\n\r\n if (\r\n e.clientX >= rect.left && e.clientX <= rect.right &&\r\n e.clientY >= rect.top && e.clientY <= rect.bottom\r\n ) {\r\n showImageEditIcon(img)\r\n return\r\n }\r\n }\r\n // Mouse is in section but not over any image\r\n if (imageEditIconTarget && !imageEditIconElement?.matches(':hover')) {\r\n removeImageEditIcon()\r\n }\r\n })\r\n })\r\n\r\n // Set up blog content area — single click to open blog content editor\r\n if (!blogContentInitialized) {\r\n const blogContentEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n if (blogContentEl) {\r\n blogContentInitialized = true\r\n blogContentEl.classList.add('dcs-blog-content')\r\n blogContentEl.addEventListener('click', (e) => {\r\n // Don't trigger blog content click if clicking on a text-key element\r\n const target = e.target as HTMLElement\r\n if (target.closest(TEXT_KEY_SELECTOR)) return\r\n e.preventDefault()\r\n e.stopPropagation()\r\n postToParent('dcs:blog-content-click', { textKey: 'blog-content' })\r\n })\r\n // Also send the current content to the parent for initial load\r\n postToParent('dcs:blog-content-ready', { content: blogContentEl.innerHTML })\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 = getTextKey(el)\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 = getTextKey(el)\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>(textKeySelector(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>(textKeySelector(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] or [data-dcs-text] 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 = getTextKey(el)\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] or [data-dcs-text] 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 = getTextKey(el)\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\n// ─── Image Edit Icon (Floating Camera Button) ───────────────────\r\n\r\n/**\r\n * Create the floating image edit icon element. This small button appears\r\n * in the top-right corner of a hovered image within a [data-section] area,\r\n * providing a click target to open image management — essential for images\r\n * inside interactive widgets (e.g., before/after sliders) where right-click\r\n * is intercepted by the widget.\r\n */\r\nfunction createImageEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-image-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Manage image')\r\n icon.setAttribute('aria-label', 'Manage image')\r\n // SVG: Image icon — matches lucide \"Image\" 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\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></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 (!imageEditIconTarget) return\r\n const img = imageEditIconTarget\r\n const sectionEl = img.closest<HTMLElement>('[data-section]')\r\n\r\n postToParent('dcs:image-click', {\r\n imageUrl: img.currentSrc ?? img.src,\r\n imageAlt: img.alt ?? null,\r\n imageWidth: img.naturalWidth,\r\n imageHeight: img.naturalHeight,\r\n sectionId: sectionEl?.dataset.section ?? null,\r\n sectionLabel: sectionEl?.dataset.sectionLabel ?? null,\r\n })\r\n removeImageEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the image)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n // Keep icon visible if cursor moves back toward the image area\r\n if (related && imageEditIconTarget && (\r\n related === imageEditIconTarget || imageEditIconTarget.contains(related)\r\n )) return\r\n removeImageEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the image edit icon positioned at the top-right corner of the given image.\r\n */\r\nfunction showImageEditIcon(img: HTMLImageElement) {\r\n if (!editingEnabled) return\r\n // Don't re-position if already showing for this exact image\r\n if (imageEditIconTarget === img && imageEditIconElement?.style.display === 'flex') return\r\n\r\n if (!imageEditIconElement) {\r\n imageEditIconElement = createImageEditIcon()\r\n document.body.appendChild(imageEditIconElement)\r\n }\r\n\r\n imageEditIconTarget = img\r\n\r\n const rect = img.getBoundingClientRect()\r\n // Position at the top-right corner of the image\r\n imageEditIconElement.style.top = `${rect.top + scrollY + 4}px`\r\n imageEditIconElement.style.left = `${rect.right + scrollX - 28}px`\r\n imageEditIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the image edit icon.\r\n */\r\nfunction removeImageEditIcon() {\r\n if (imageEditIconElement) {\r\n imageEditIconElement.style.display = 'none'\r\n }\r\n imageEditIconTarget = 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 [data-dcs-text].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 [data-dcs-text].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 [data-dcs-text].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 [data-dcs-text].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 /* Floating image edit icon button — green to distinguish from text (blue) and array (violet) */\r\n .dcs-image-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(142 71% 45%);\r\n border-radius: 4px;\r\n background: hsl(142 71% 45% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(142 71% 45%);\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-image-edit-icon:hover {\r\n background: hsl(142 71% 45% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-image-edit-icon svg {\r\n display: block;\r\n }\r\n\r\n /* Blog content area — click to open WYSIWYG editor */\r\n [data-blog-content].dcs-blog-content {\r\n cursor: pointer;\r\n border-radius: 8px;\r\n position: relative;\r\n transition: outline-color 0.2s, background-color 0.2s;\r\n }\r\n\r\n [data-blog-content].dcs-blog-content:hover {\r\n outline: 2px dashed hsl(142 71% 45% / 0.5) !important;\r\n outline-offset: 8px;\r\n background-color: hsl(142 71% 45% / 0.03);\r\n }\r\n\r\n [data-blog-content].dcs-blog-content::after {\r\n content: 'Click to edit blog content';\r\n position: absolute;\r\n top: -28px;\r\n left: 8px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n color: hsl(142 71% 45%);\r\n background: hsl(142 71% 45% / 0.1);\r\n border: 1px solid hsl(142 71% 45% / 0.3);\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n opacity: 0;\r\n transition: opacity 0.2s;\r\n pointer-events: none;\r\n white-space: nowrap;\r\n }\r\n\r\n [data-blog-content].dcs-blog-content:hover::after {\r\n opacity: 1;\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>(TEXT_KEY_SELECTOR).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 removeImageEditIcon()\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 case 'dcs:update-blog-content':\r\n if (data && typeof data === 'object' && 'content' in data) {\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n if (blogEl) {\r\n blogEl.innerHTML = (data as { content: string }).content\r\n // Trigger re-discovery so the portal overlay updates for any height changes\r\n scheduleRediscovery()\r\n }\r\n }\r\n break\r\n }\r\n}\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 // Detect blog content element for the initial ready report\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n\r\n postToParent('dcs:ready', {\r\n sections,\r\n textKeys,\r\n contentHeight,\r\n hasBlogContent: !!blogEl,\r\n blogContentHtml: blogEl ? blogEl.innerHTML : null,\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// NOTE: Auto-initialization is NOT done here. The Vite plugin's virtual module\r\n// (dcsEditorPlugin) handles initialization by importing and calling initEditorBridge().\r\n// Having auto-init as a side effect of the import caused double-initialization\r\n// (two dcs:ready messages on startup).\r\n"]}
package/package.json CHANGED
@@ -1,84 +1,83 @@
1
- {
2
- "name": "@duffcloudservices/cms",
3
- "version": "0.3.3",
4
- "description": "Vue 3 composables and Vite plugins for DCS CMS integration",
5
- "type": "module",
6
- "exports": {
7
- ".": {
8
- "types": "./dist/index.d.ts",
9
- "import": "./dist/index.js"
10
- },
11
- "./plugins": {
12
- "types": "./dist/plugins/index.d.ts",
13
- "import": "./dist/plugins/index.js"
14
- },
15
- "./editor": {
16
- "types": "./dist/editor/editorBridge.d.ts",
17
- "import": "./dist/editor/editorBridge.js"
18
- },
19
- "./components": {
20
- "import": "./src/components/PreviewRibbon.vue"
21
- },
22
- "./responsive-image": {
23
- "import": "./src/components/ResponsiveImage.vue"
24
- }
25
- },
26
- "main": "./dist/index.js",
27
- "types": "./dist/index.d.ts",
28
- "files": [
29
- "dist",
30
- "src/components",
31
- "src/composables"
32
- ],
33
- "scripts": {
34
- "build": "tsup",
35
- "dev": "tsup --watch",
36
- "test": "vitest run",
37
- "test:watch": "vitest",
38
- "type-check": "tsc --noEmit",
39
- "lint": "eslint src --ext .ts",
40
- "prepublishOnly": "pnpm run build"
41
- },
42
- "peerDependencies": {
43
- "vue": "^3.4.0",
44
- "@unhead/vue": "^1.9.0"
45
- },
46
- "dependencies": {
47
- "@duffcloudservices/cms-core": "workspace:*",
48
- "js-yaml": "^4.1.0"
49
- },
50
- "devDependencies": {
51
- "@types/js-yaml": "^4.0.9",
52
- "@types/markdown-it": "^14.1.0",
53
- "@types/node": "^20.11.0",
54
- "@vue/test-utils": "^2.4.0",
55
- "markdown-it": "^14.0.0",
56
- "tsup": "^8.0.0",
57
- "typescript": "~5.6.3",
58
- "vite": "^6.3.5",
59
- "vitest": "^3.2.3",
60
- "vue": "^3.5.16",
61
- "@unhead/vue": "^2.0.5"
62
- },
63
- "keywords": [
64
- "vue",
65
- "vitepress",
66
- "cms",
67
- "dcs",
68
- "composables",
69
- "duff-cloud-services"
70
- ],
71
- "author": "Duff Cloud Services",
72
- "license": "MIT",
73
- "repository": {
74
- "type": "git",
75
- "url": "https://github.com/duffn/dcs"
76
- },
77
- "homepage": "https://portal.duffcloudservices.com",
78
- "bugs": {
79
- "url": "https://github.com/duffn/dcs/issues"
80
- },
81
- "engines": {
82
- "node": ">=18.0.0"
83
- }
84
- }
1
+ {
2
+ "name": "@duffcloudservices/cms",
3
+ "version": "0.3.5",
4
+ "description": "Vue 3 composables and Vite plugins for DCS CMS integration",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./plugins": {
12
+ "types": "./dist/plugins/index.d.ts",
13
+ "import": "./dist/plugins/index.js"
14
+ },
15
+ "./editor": {
16
+ "types": "./dist/editor/editorBridge.d.ts",
17
+ "import": "./dist/editor/editorBridge.js"
18
+ },
19
+ "./components": {
20
+ "import": "./src/components/PreviewRibbon.vue"
21
+ },
22
+ "./responsive-image": {
23
+ "import": "./src/components/ResponsiveImage.vue"
24
+ }
25
+ },
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "files": [
29
+ "dist",
30
+ "src/components",
31
+ "src/composables"
32
+ ],
33
+ "peerDependencies": {
34
+ "vue": "^3.4.0",
35
+ "@unhead/vue": "^1.9.0"
36
+ },
37
+ "dependencies": {
38
+ "js-yaml": "^4.1.0",
39
+ "@duffcloudservices/cms-core": "0.4.2"
40
+ },
41
+ "devDependencies": {
42
+ "@types/js-yaml": "^4.0.9",
43
+ "@types/markdown-it": "^14.1.0",
44
+ "@types/node": "^20.11.0",
45
+ "@vue/test-utils": "^2.4.0",
46
+ "markdown-it": "^14.0.0",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "~5.6.3",
49
+ "vite": "^6.3.5",
50
+ "vitest": "^3.2.3",
51
+ "vue": "^3.5.16",
52
+ "@unhead/vue": "^2.0.5"
53
+ },
54
+ "keywords": [
55
+ "vue",
56
+ "vitepress",
57
+ "cms",
58
+ "dcs",
59
+ "composables",
60
+ "duff-cloud-services"
61
+ ],
62
+ "author": "Duff Cloud Services",
63
+ "license": "MIT",
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "https://github.com/duffn/dcs"
67
+ },
68
+ "homepage": "https://portal.duffcloudservices.com",
69
+ "bugs": {
70
+ "url": "https://github.com/duffn/dcs/issues"
71
+ },
72
+ "engines": {
73
+ "node": ">=18.0.0"
74
+ },
75
+ "scripts": {
76
+ "build": "tsup",
77
+ "dev": "tsup --watch",
78
+ "test": "vitest run",
79
+ "test:watch": "vitest",
80
+ "type-check": "tsc --noEmit",
81
+ "lint": "eslint src --ext .ts"
82
+ }
83
+ }