@duffcloudservices/cms 0.3.6 → 0.3.8
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.
|
@@ -25,8 +25,8 @@ var editIconElement = null;
|
|
|
25
25
|
var editIconTarget = null;
|
|
26
26
|
var arrayEditIconElement = null;
|
|
27
27
|
var arrayEditIconTarget = null;
|
|
28
|
-
var
|
|
29
|
-
var
|
|
28
|
+
var imageEditIconElements = [];
|
|
29
|
+
var imageEditIconTargets = [];
|
|
30
30
|
var editingEnabled = true;
|
|
31
31
|
var originalTextValues = /* @__PURE__ */ new Map();
|
|
32
32
|
var sectionResizeObserver = null;
|
|
@@ -200,8 +200,8 @@ function applyEditorToElements() {
|
|
|
200
200
|
el.addEventListener("mouseleave", () => {
|
|
201
201
|
postToParent("dcs:section-hover", { sectionId: null });
|
|
202
202
|
setTimeout(() => {
|
|
203
|
-
if (
|
|
204
|
-
|
|
203
|
+
if (!isAnyImageIconHovered()) {
|
|
204
|
+
removeAllImageEditIcons();
|
|
205
205
|
}
|
|
206
206
|
}, 100);
|
|
207
207
|
});
|
|
@@ -213,14 +213,14 @@ function applyEditorToElements() {
|
|
|
213
213
|
});
|
|
214
214
|
el.addEventListener("mousemove", (e) => {
|
|
215
215
|
if (!editorActive || !editingEnabled || activeEditElement) {
|
|
216
|
-
|
|
216
|
+
removeAllImageEditIcons();
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
|
-
const
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
} else if (
|
|
223
|
-
|
|
219
|
+
const allImages = findAllImagesAtPoint(el, e.clientX, e.clientY);
|
|
220
|
+
if (allImages.length > 0) {
|
|
221
|
+
showImageEditIcons(allImages);
|
|
222
|
+
} else if (imageEditIconTargets.length > 0 && !isAnyImageIconHovered()) {
|
|
223
|
+
removeAllImageEditIcons();
|
|
224
224
|
}
|
|
225
225
|
});
|
|
226
226
|
});
|
|
@@ -236,6 +236,13 @@ function applyEditorToElements() {
|
|
|
236
236
|
e.stopPropagation();
|
|
237
237
|
postToParent("dcs:blog-content-click", { textKey: "blog-content" });
|
|
238
238
|
});
|
|
239
|
+
blogContentEl.addEventListener("dblclick", (e) => {
|
|
240
|
+
const target = e.target;
|
|
241
|
+
if (target.closest(TEXT_KEY_SELECTOR)) return;
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
e.stopPropagation();
|
|
244
|
+
postToParent("dcs:blog-content-click", { textKey: "blog-content" });
|
|
245
|
+
});
|
|
239
246
|
postToParent("dcs:blog-content-ready", { content: blogContentEl.innerHTML });
|
|
240
247
|
}
|
|
241
248
|
}
|
|
@@ -413,67 +420,90 @@ function removeArrayEditIcon() {
|
|
|
413
420
|
}
|
|
414
421
|
arrayEditIconTarget = null;
|
|
415
422
|
}
|
|
416
|
-
function createImageEditIcon() {
|
|
423
|
+
function createImageEditIcon(label) {
|
|
417
424
|
const icon = document.createElement("button");
|
|
418
425
|
icon.className = "dcs-image-edit-icon";
|
|
419
426
|
icon.setAttribute("type", "button");
|
|
420
|
-
icon.setAttribute("title", "Manage image");
|
|
421
|
-
icon.setAttribute("aria-label", "Manage image");
|
|
422
|
-
|
|
423
|
-
icon.
|
|
424
|
-
e.preventDefault();
|
|
425
|
-
e.stopPropagation();
|
|
426
|
-
e.stopImmediatePropagation();
|
|
427
|
-
if (!imageEditIconTarget) return;
|
|
428
|
-
const img = imageEditIconTarget;
|
|
429
|
-
const sectionEl = img.closest("[data-section]");
|
|
430
|
-
postToParent("dcs:image-click", {
|
|
431
|
-
imageUrl: img.currentSrc ?? img.src,
|
|
432
|
-
imageAlt: img.alt ?? null,
|
|
433
|
-
imageWidth: img.naturalWidth,
|
|
434
|
-
imageHeight: img.naturalHeight,
|
|
435
|
-
sectionId: sectionEl?.dataset.section ?? null,
|
|
436
|
-
sectionLabel: sectionEl?.dataset.sectionLabel ?? null
|
|
437
|
-
});
|
|
438
|
-
removeImageEditIcon();
|
|
439
|
-
}, true);
|
|
427
|
+
icon.setAttribute("title", label ? `Replace ${label}` : "Manage image");
|
|
428
|
+
icon.setAttribute("aria-label", label ? `Replace ${label}` : "Manage image");
|
|
429
|
+
const svgHtml = `<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.innerHTML = label ? `${svgHtml}<span class="dcs-image-edit-label">${label}</span>` : svgHtml;
|
|
440
431
|
icon.addEventListener("mouseleave", (e) => {
|
|
441
432
|
const related = e.relatedTarget;
|
|
442
|
-
if (related
|
|
443
|
-
|
|
433
|
+
if (related) {
|
|
434
|
+
for (const target of imageEditIconTargets) {
|
|
435
|
+
if (related === target || target.contains(related)) return;
|
|
436
|
+
}
|
|
437
|
+
if (related.classList?.contains("dcs-image-edit-icon") || related.closest?.(".dcs-image-edit-icon")) return;
|
|
438
|
+
}
|
|
439
|
+
removeAllImageEditIcons();
|
|
444
440
|
});
|
|
445
441
|
return icon;
|
|
446
442
|
}
|
|
447
|
-
function
|
|
443
|
+
function showImageEditIcons(images) {
|
|
448
444
|
if (!editingEnabled) return;
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
445
|
+
if (imageEditIconTargets.length === images.length && images.every((img, i) => imageEditIconTargets[i] === img)) return;
|
|
446
|
+
removeAllImageEditIcons();
|
|
447
|
+
imageEditIconTargets = [...images];
|
|
448
|
+
const refRect = images[0].getBoundingClientRect();
|
|
449
|
+
const hasMultiple = images.length > 1;
|
|
450
|
+
images.forEach((img, index) => {
|
|
451
|
+
let label;
|
|
452
|
+
if (hasMultiple) {
|
|
453
|
+
const alt = img.alt ?? "";
|
|
454
|
+
const dashMatch = alt.match(/\u2014\s*(.+)$/) ?? alt.match(/-\s*(.+)$/);
|
|
455
|
+
label = dashMatch ? dashMatch[1].trim() : `Image ${index + 1}`;
|
|
456
|
+
}
|
|
457
|
+
const iconEl = createImageEditIcon(label);
|
|
458
|
+
iconEl.addEventListener("click", (e) => {
|
|
459
|
+
e.preventDefault();
|
|
460
|
+
e.stopPropagation();
|
|
461
|
+
e.stopImmediatePropagation();
|
|
462
|
+
const sectionEl = img.closest("[data-section]");
|
|
463
|
+
postToParent("dcs:image-click", {
|
|
464
|
+
imageUrl: img.currentSrc ?? img.src,
|
|
465
|
+
imageAlt: img.alt ?? null,
|
|
466
|
+
imageWidth: img.naturalWidth,
|
|
467
|
+
imageHeight: img.naturalHeight,
|
|
468
|
+
sectionId: sectionEl?.dataset.section ?? null,
|
|
469
|
+
sectionLabel: sectionEl?.dataset.sectionLabel ?? null
|
|
470
|
+
});
|
|
471
|
+
removeAllImageEditIcons();
|
|
472
|
+
}, true);
|
|
473
|
+
document.body.appendChild(iconEl);
|
|
474
|
+
iconEl.style.top = `${refRect.top + scrollY + 4 + index * 28}px`;
|
|
475
|
+
iconEl.style.left = hasMultiple ? `${refRect.right + scrollX - iconEl.offsetWidth - 4}px` : `${refRect.right + scrollX - 28}px`;
|
|
476
|
+
iconEl.style.display = "flex";
|
|
477
|
+
if (hasMultiple) {
|
|
478
|
+
requestAnimationFrame(() => {
|
|
479
|
+
iconEl.style.left = `${refRect.right + scrollX - iconEl.offsetWidth - 4}px`;
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
imageEditIconElements.push(iconEl);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function isAnyImageIconHovered() {
|
|
486
|
+
return imageEditIconElements.some((el) => el.matches(":hover"));
|
|
487
|
+
}
|
|
488
|
+
function removeAllImageEditIcons() {
|
|
489
|
+
for (const el of imageEditIconElements) {
|
|
490
|
+
el.remove();
|
|
463
491
|
}
|
|
464
|
-
|
|
492
|
+
imageEditIconElements = [];
|
|
493
|
+
imageEditIconTargets = [];
|
|
465
494
|
}
|
|
466
|
-
function
|
|
495
|
+
function findAllImagesAtPoint(sectionEl, clientX, clientY) {
|
|
496
|
+
const results = [];
|
|
467
497
|
const images = sectionEl.querySelectorAll("img");
|
|
468
498
|
for (const img of images) {
|
|
469
499
|
if (img.naturalWidth > 0 && img.naturalWidth < 50 && img.naturalHeight < 50) continue;
|
|
470
500
|
const rect = img.getBoundingClientRect();
|
|
471
501
|
if (rect.width < 30 || rect.height < 30) continue;
|
|
472
502
|
if (clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom) {
|
|
473
|
-
|
|
503
|
+
results.push(img);
|
|
474
504
|
}
|
|
475
505
|
}
|
|
476
|
-
return
|
|
506
|
+
return results;
|
|
477
507
|
}
|
|
478
508
|
function injectEditorStyles() {
|
|
479
509
|
if (document.getElementById("dcs-editor-styles")) return;
|
|
@@ -581,9 +611,9 @@ function injectEditorStyles() {
|
|
|
581
611
|
display: none;
|
|
582
612
|
align-items: center;
|
|
583
613
|
justify-content: center;
|
|
584
|
-
width: 24px;
|
|
614
|
+
min-width: 24px;
|
|
585
615
|
height: 24px;
|
|
586
|
-
padding: 0;
|
|
616
|
+
padding: 0 4px;
|
|
587
617
|
margin: 0;
|
|
588
618
|
border: 1.5px solid hsl(142 71% 45%);
|
|
589
619
|
border-radius: 4px;
|
|
@@ -594,6 +624,7 @@ function injectEditorStyles() {
|
|
|
594
624
|
pointer-events: auto;
|
|
595
625
|
transition: background 0.15s, transform 0.1s;
|
|
596
626
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
|
|
627
|
+
gap: 2px;
|
|
597
628
|
}
|
|
598
629
|
|
|
599
630
|
.dcs-image-edit-icon:hover {
|
|
@@ -603,6 +634,16 @@ function injectEditorStyles() {
|
|
|
603
634
|
|
|
604
635
|
.dcs-image-edit-icon svg {
|
|
605
636
|
display: block;
|
|
637
|
+
flex-shrink: 0;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/* Label shown inside image edit icon for multi-image scenarios (e.g., before/after) */
|
|
641
|
+
.dcs-image-edit-label {
|
|
642
|
+
font-size: 9px;
|
|
643
|
+
font-weight: 700;
|
|
644
|
+
letter-spacing: 0.03em;
|
|
645
|
+
white-space: nowrap;
|
|
646
|
+
margin-left: 2px;
|
|
606
647
|
}
|
|
607
648
|
|
|
608
649
|
/* Blog content area \u2014 click to open WYSIWYG editor */
|
|
@@ -688,7 +729,7 @@ function handleMessage(event) {
|
|
|
688
729
|
}
|
|
689
730
|
removeEditIcon();
|
|
690
731
|
removeArrayEditIcon();
|
|
691
|
-
|
|
732
|
+
removeAllImageEditIcons();
|
|
692
733
|
document.querySelectorAll(".dcs-editable").forEach((el) => {
|
|
693
734
|
el.classList.remove("dcs-editable");
|
|
694
735
|
});
|
|
@@ -740,44 +781,35 @@ function initEditorBridge() {
|
|
|
740
781
|
const target = e.target;
|
|
741
782
|
const sectionEl = target.closest?.("[data-section]");
|
|
742
783
|
let imgEl = target.tagName === "IMG" ? target : target.closest?.("picture")?.querySelector("img");
|
|
743
|
-
|
|
744
|
-
|
|
784
|
+
let allImages = [];
|
|
785
|
+
if (sectionEl) {
|
|
786
|
+
allImages = findAllImagesAtPoint(sectionEl, e.clientX, e.clientY);
|
|
787
|
+
}
|
|
788
|
+
if (imgEl && !allImages.includes(imgEl)) {
|
|
789
|
+
allImages.push(imgEl);
|
|
745
790
|
}
|
|
791
|
+
const topImage = allImages.length > 0 ? allImages[allImages.length - 1] : null;
|
|
746
792
|
postToParent("dcs:contextmenu", {
|
|
747
793
|
x: e.clientX,
|
|
748
794
|
y: e.clientY,
|
|
749
795
|
sectionId: sectionEl?.dataset.section ?? null,
|
|
750
796
|
sectionLabel: sectionEl?.dataset.sectionLabel ?? null,
|
|
751
|
-
//
|
|
752
|
-
imageUrl:
|
|
753
|
-
imageAlt:
|
|
754
|
-
imageWidth:
|
|
755
|
-
imageHeight:
|
|
797
|
+
// Single image context (backward compatible) — topmost image at click point
|
|
798
|
+
imageUrl: topImage?.currentSrc ?? topImage?.src ?? null,
|
|
799
|
+
imageAlt: topImage?.alt ?? null,
|
|
800
|
+
imageWidth: topImage?.naturalWidth ?? null,
|
|
801
|
+
imageHeight: topImage?.naturalHeight ?? null,
|
|
802
|
+
// All images at click point (for overlapping image scenarios like before/after sliders)
|
|
803
|
+
images: allImages.length > 1 ? allImages.map((img) => ({
|
|
804
|
+
imageUrl: img.currentSrc ?? img.src,
|
|
805
|
+
imageAlt: img.alt ?? null,
|
|
806
|
+
imageWidth: img.naturalWidth,
|
|
807
|
+
imageHeight: img.naturalHeight
|
|
808
|
+
})) : null
|
|
756
809
|
});
|
|
757
810
|
}, true);
|
|
758
|
-
document.addEventListener("click", (
|
|
811
|
+
document.addEventListener("click", () => {
|
|
759
812
|
postToParent("dcs:click", {});
|
|
760
|
-
if (editorActive && editingEnabled) {
|
|
761
|
-
const target = e.target;
|
|
762
|
-
let imgEl = target.tagName === "IMG" ? target : target.closest?.("picture")?.querySelector("img");
|
|
763
|
-
if (!imgEl) {
|
|
764
|
-
const sectionEl = target.closest?.("[data-section]");
|
|
765
|
-
if (sectionEl) {
|
|
766
|
-
imgEl = findImageAtPoint(sectionEl, e.clientX, e.clientY);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
if (imgEl) {
|
|
770
|
-
const sectionEl = target.closest?.("[data-section]");
|
|
771
|
-
postToParent("dcs:image-click", {
|
|
772
|
-
imageUrl: imgEl.currentSrc ?? imgEl.src,
|
|
773
|
-
imageAlt: imgEl.alt ?? null,
|
|
774
|
-
imageWidth: imgEl.naturalWidth,
|
|
775
|
-
imageHeight: imgEl.naturalHeight,
|
|
776
|
-
sectionId: sectionEl?.dataset.section ?? null,
|
|
777
|
-
sectionLabel: sectionEl?.dataset.sectionLabel ?? null
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
813
|
}, true);
|
|
782
814
|
globalThis.self.addEventListener("message", handleMessage);
|
|
783
815
|
if (import.meta.hot) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/editor/editorBridge.ts"],"names":[],"mappings":"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","file":"editorBridge.js","sourcesContent":["/**\r\n * Editor Bridge — injected into customer site iframes by the DCS visual editor.\r\n *\r\n * Responsibilities:\r\n * 1. Report available sections and text keys to the parent portal\r\n * 2. Enable inline contenteditable editing on data-text-key/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 img = findImageAtPoint(el, e.clientX, e.clientY)\r\n if (img) {\r\n showImageEditIcon(img)\r\n } else 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\n// ─── Shared Image Detection ──────────────────────────────────────\r\n\r\n/**\r\n * Find an image element at the given viewport coordinates within a section,\r\n * using bounding-box hit testing. This detects images even when they have\r\n * `pointer-events: none` (e.g., slider images) or are behind overlay divs\r\n * (e.g., gallery hover effects).\r\n */\r\nfunction findImageAtPoint(sectionEl: HTMLElement, clientX: number, clientY: number): HTMLImageElement | null {\r\n const images = sectionEl.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 clientX >= rect.left && clientX <= rect.right &&\r\n clientY >= rect.top && clientY <= rect.bottom\r\n ) {\r\n return img\r\n }\r\n }\r\n return 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 let imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n\r\n // Fallback: position-based detection for images behind overlay divs\r\n // or with pointer-events: none (e.g., before/after sliders, gallery hover overlays)\r\n if (!imgEl && sectionEl) {\r\n imgEl = findImageAtPoint(sectionEl, e.clientX, e.clientY)\r\n }\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 let imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n\r\n // Fallback: position-based detection for images behind overlays\r\n if (!imgEl) {\r\n const sectionEl = target.closest?.('[data-section]') as HTMLElement | null\r\n if (sectionEl) {\r\n imgEl = findImageAtPoint(sectionEl, e.clientX, e.clientY)\r\n }\r\n }\r\n\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 icons for image elements within sections (supports multiple for overlapping images) */\r\nlet imageEditIconElements: HTMLElement[] = []\r\n/** The image elements the image edit icons are currently positioned on */\r\nlet imageEditIconTargets: HTMLImageElement[] = []\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 icons — allows cursor to reach the icons\r\n setTimeout(() => {\r\n if (!isAnyImageIconHovered()) {\r\n removeAllImageEditIcons()\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 // For overlapping images (before/after sliders), show icons for ALL images\r\n // at the cursor position so each one is independently replaceable.\r\n el.addEventListener('mousemove', (e: MouseEvent) => {\r\n if (!editorActive || !editingEnabled || activeEditElement) {\r\n removeAllImageEditIcons()\r\n return\r\n }\r\n\r\n const allImages = findAllImagesAtPoint(el, e.clientX, e.clientY)\r\n if (allImages.length > 0) {\r\n showImageEditIcons(allImages)\r\n } else if (imageEditIconTargets.length > 0 && !isAnyImageIconHovered()) {\r\n removeAllImageEditIcons()\r\n }\r\n })\r\n })\r\n\r\n // Set up blog content area — click or double-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 // Double-click / double-tap also opens the blog content editor\r\n blogContentEl.addEventListener('dblclick', (e) => {\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 a floating image edit icon element for a specific image. This small\r\n * button appears in the top-right corner of a hovered image within a\r\n * [data-section] area, providing a click target to open image management —\r\n * essential for images inside interactive widgets (e.g., before/after sliders)\r\n * where right-click is intercepted by the widget.\r\n *\r\n * When multiple images overlap (e.g., before/after sliders), separate icons\r\n * are created for each image with labels to distinguish them.\r\n */\r\nfunction createImageEditIcon(label?: string): 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', label ? `Replace ${label}` : 'Manage image')\r\n icon.setAttribute('aria-label', label ? `Replace ${label}` : 'Manage image')\r\n // SVG: Image icon — matches lucide \"Image\" icon\r\n const svgHtml = `<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 icon.innerHTML = label ? `${svgHtml}<span class=\"dcs-image-edit-label\">${label}</span>` : svgHtml\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 icons visible if cursor moves back toward any tracked image\r\n if (related) {\r\n for (const target of imageEditIconTargets) {\r\n if (related === target || target.contains(related)) return\r\n }\r\n // Also keep visible if moving to another image icon\r\n if (related.classList?.contains('dcs-image-edit-icon') || related.closest?.('.dcs-image-edit-icon')) return\r\n }\r\n removeAllImageEditIcons()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show image edit icons for all provided images. When only one image is\r\n * present, a single icon appears at the top-right corner. When multiple\r\n * overlapping images are present (e.g., before/after sliders), separate\r\n * labeled icons appear stacked in the top-right corner.\r\n */\r\nfunction showImageEditIcons(images: HTMLImageElement[]) {\r\n if (!editingEnabled) return\r\n\r\n // Check if icons are already showing for these exact images\r\n if (imageEditIconTargets.length === images.length &&\r\n images.every((img, i) => imageEditIconTargets[i] === img)) return\r\n\r\n // Clear existing icons\r\n removeAllImageEditIcons()\r\n\r\n imageEditIconTargets = [...images]\r\n\r\n // Use the bounding box of the first image for positioning (they overlap anyway)\r\n const refRect = images[0].getBoundingClientRect()\r\n const hasMultiple = images.length > 1\r\n\r\n images.forEach((img, index) => {\r\n // Derive a label for multi-image scenarios from alt text\r\n let label: string | undefined\r\n if (hasMultiple) {\r\n const alt = img.alt ?? ''\r\n // Try to extract a meaningful label from alt text (e.g., \"Project — Before\" → \"Before\")\r\n const dashMatch = alt.match(/\\u2014\\s*(.+)$/) ?? alt.match(/-\\s*(.+)$/)\r\n label = dashMatch ? dashMatch[1].trim() : `Image ${index + 1}`\r\n }\r\n\r\n const iconEl = createImageEditIcon(label)\r\n\r\n // Attach click handler with the specific image reference\r\n iconEl.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n const sectionEl = img.closest<HTMLElement>('[data-section]')\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 removeAllImageEditIcons()\r\n }, true)\r\n\r\n document.body.appendChild(iconEl)\r\n\r\n // Stack icons vertically from the top-right corner\r\n iconEl.style.top = `${refRect.top + scrollY + 4 + (index * 28)}px`\r\n iconEl.style.left = hasMultiple\r\n ? `${refRect.right + scrollX - iconEl.offsetWidth - 4}px`\r\n : `${refRect.right + scrollX - 28}px`\r\n iconEl.style.display = 'flex'\r\n\r\n // Re-measure after display for labeled icons with dynamic width\r\n if (hasMultiple) {\r\n requestAnimationFrame(() => {\r\n iconEl.style.left = `${refRect.right + scrollX - iconEl.offsetWidth - 4}px`\r\n })\r\n }\r\n\r\n imageEditIconElements.push(iconEl)\r\n })\r\n}\r\n\r\n/**\r\n * Check if any image edit icon is currently being hovered.\r\n */\r\nfunction isAnyImageIconHovered(): boolean {\r\n return imageEditIconElements.some(el => el.matches(':hover'))\r\n}\r\n\r\n/**\r\n * Hide and remove all image edit icons.\r\n */\r\nfunction removeAllImageEditIcons() {\r\n for (const el of imageEditIconElements) {\r\n el.remove()\r\n }\r\n imageEditIconElements = []\r\n imageEditIconTargets = []\r\n}\r\n\r\n// ─── Shared Image Detection ──────────────────────────────────────\r\n\r\n/**\r\n * Find ALL images at the given viewport coordinates within a section.\r\n * Returns images in DOM order (first = visually behind, last = on top).\r\n * Used for overlapping image scenarios like before/after sliders where\r\n * multiple <img> elements occupy the same bounding box area.\r\n */\r\nfunction findAllImagesAtPoint(sectionEl: HTMLElement, clientX: number, clientY: number): HTMLImageElement[] {\r\n const results: HTMLImageElement[] = []\r\n const images = sectionEl.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 clientX >= rect.left && clientX <= rect.right &&\r\n clientY >= rect.top && clientY <= rect.bottom\r\n ) {\r\n results.push(img)\r\n }\r\n }\r\n return results\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 min-width: 24px;\r\n height: 24px;\r\n padding: 0 4px;\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 gap: 2px;\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 flex-shrink: 0;\r\n }\r\n\r\n /* Label shown inside image edit icon for multi-image scenarios (e.g., before/after) */\r\n .dcs-image-edit-label {\r\n font-size: 9px;\r\n font-weight: 700;\r\n letter-spacing: 0.03em;\r\n white-space: nowrap;\r\n margin-left: 2px;\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 removeAllImageEditIcons()\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 let imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n\r\n // Find ALL images at the click point (handles overlapping images like before/after sliders)\r\n let allImages: HTMLImageElement[] = []\r\n if (sectionEl) {\r\n allImages = findAllImagesAtPoint(sectionEl, e.clientX, e.clientY)\r\n }\r\n\r\n // If direct target detection found an image not in the position-based list, add it\r\n if (imgEl && !allImages.includes(imgEl)) {\r\n allImages.push(imgEl)\r\n }\r\n\r\n // Use the topmost image for backward-compatible single-image fields\r\n const topImage = allImages.length > 0 ? allImages[allImages.length - 1] : 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 // Single image context (backward compatible) — topmost image at click point\r\n imageUrl: topImage?.currentSrc ?? topImage?.src ?? null,\r\n imageAlt: topImage?.alt ?? null,\r\n imageWidth: topImage?.naturalWidth ?? null,\r\n imageHeight: topImage?.naturalHeight ?? null,\r\n // All images at click point (for overlapping image scenarios like before/after sliders)\r\n images: allImages.length > 1\r\n ? allImages.map(img => ({\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 }))\r\n : null,\r\n })\r\n }, true)\r\n\r\n // Forward click events to the portal so it can dismiss context menus\r\n // when the user clicks inside the iframe.\r\n // NOTE: Left-clicking an image does NOT open image management. Users must\r\n // right-click → \"Replace Image\" or use the floating image icon (top-right\r\n // corner on hover). This prevents accidental sheet openings during normal\r\n // page interaction and slider/gallery dragging.\r\n document.addEventListener('click', () => {\r\n postToParent('dcs:click', {})\r\n }, true)\r\n\r\n // Listen for portal commands. Origin is not validated because\r\n // the portal origin varies between environments and all inbound\r\n // messages are type-checked before processing.\r\n // Origin cannot be pre-validated because the portal URL varies by environment.\r\n // All inbound messages are type-checked via the dcs: prefix guard.\r\n globalThis.self.addEventListener('message', handleMessage) // NOSONAR\r\n\r\n // Watch for VitePress / Vite HMR page updates. When the framework\r\n // replaces DOM content, our event listeners and AI buttons are lost.\r\n // Re-discover sections after each update so the portal overlay stays in sync.\r\n if (import.meta.hot) {\r\n import.meta.hot.on('vite:afterUpdate', () => {\r\n // Small delay for the framework to finish DOM updates\r\n setTimeout(rediscoverAndNotify, 300)\r\n })\r\n }\r\n }\r\n\r\n // Report available sections, text keys, and content height\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n lastReportedHeight = contentHeight\r\n\r\n // 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"]}
|