@duffcloudservices/cms 0.2.0 → 0.3.1

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.
@@ -3,12 +3,15 @@
3
3
  *
4
4
  * Responsibilities:
5
5
  * 1. Report available sections and text keys to the parent portal
6
- * 2. Enable inline contenteditable editing on data-text-key elements (double-click)
6
+ * 2. Enable inline contenteditable editing on data-text-key/data-dcs-text elements (double-click)
7
7
  * 3. Render section overlay highlights on hover/select from parent
8
8
  * 4. Communicate all interactions back to the parent via postMessage
9
9
  * 5. Monitor navigation within the iframe and notify the parent
10
10
  * 6. Show floating edit icon on hover for text-key elements
11
11
  *
12
+ * Supports both legacy `data-text-key` and modern `data-dcs-text` attributes
13
+ * for backward compatibility during the migration period.
14
+ *
12
15
  * NOTE: AI ✨ buttons are rendered by the portal-side SectionOverlayLayer,
13
16
  * NOT by this bridge. The bridge only reports section/text key data and
14
17
  * handles inline text editing.
@@ -31,6 +34,10 @@ interface EditorReadyPayload {
31
34
  sections: SectionInfo[];
32
35
  textKeys: string[];
33
36
  contentHeight: number;
37
+ /** Whether the page has a [data-blog-content] element (blog post page) */
38
+ hasBlogContent: boolean;
39
+ /** Current innerHTML of the blog content element, if present */
40
+ blogContentHtml: string | null;
34
41
  }
35
42
  declare function initEditorBridge(): void;
36
43
 
@@ -10,6 +10,13 @@ function postToParent(type, data) {
10
10
  if (!isInIframe()) return;
11
11
  globalThis.self.parent.postMessage({ type, data }, "*");
12
12
  }
13
+ var TEXT_KEY_SELECTOR = "[data-text-key], [data-dcs-text]";
14
+ function getTextKey(el) {
15
+ return el.dataset.dcsText ?? el.dataset.textKey ?? "";
16
+ }
17
+ function textKeySelector(key) {
18
+ return `[data-text-key="${key}"], [data-dcs-text="${key}"]`;
19
+ }
13
20
  var editorActive = false;
14
21
  var bridgeInitialized = false;
15
22
  var activeEditElement = null;
@@ -22,6 +29,9 @@ var editingEnabled = true;
22
29
  var originalTextValues = /* @__PURE__ */ new Map();
23
30
  var sectionResizeObserver = null;
24
31
  var rediscoveryTimer = null;
32
+ var initializedTextElements = /* @__PURE__ */ new WeakSet();
33
+ var initializedSections = /* @__PURE__ */ new WeakSet();
34
+ var blogContentInitialized = false;
25
35
  function scheduleRediscovery() {
26
36
  if (rediscoveryTimer) clearTimeout(rediscoveryTimer);
27
37
  rediscoveryTimer = setTimeout(() => {
@@ -33,7 +43,7 @@ function discoverSections() {
33
43
  const elements = document.querySelectorAll("[data-section]");
34
44
  return Array.from(elements).map((el) => {
35
45
  const rect = el.getBoundingClientRect();
36
- const textKeyCount = el.querySelectorAll("[data-text-key]").length;
46
+ const textKeyCount = el.querySelectorAll(TEXT_KEY_SELECTOR).length;
37
47
  return {
38
48
  id: el.dataset.section,
39
49
  label: el.dataset.sectionLabel ?? null,
@@ -48,8 +58,8 @@ function discoverSections() {
48
58
  });
49
59
  }
50
60
  function discoverTextKeys() {
51
- return Array.from(document.querySelectorAll("[data-text-key]")).map(
52
- (el) => el.dataset.textKey
61
+ return Array.from(document.querySelectorAll(TEXT_KEY_SELECTOR)).map(
62
+ (el) => getTextKey(el)
53
63
  );
54
64
  }
55
65
  function observeSectionLayout() {
@@ -133,26 +143,37 @@ function rediscoverAndNotify() {
133
143
  const sections = discoverSections();
134
144
  const textKeys = discoverTextKeys();
135
145
  const contentHeight = measureContentHeight(sections);
136
- postToParent("dcs:ready", { sections, textKeys, contentHeight });
146
+ const blogEl = document.querySelector("[data-blog-content]");
147
+ const hasBlogContent = !!blogEl;
148
+ const blogContentHtml = blogEl ? blogEl.innerHTML : null;
149
+ postToParent("dcs:ready", { sections, textKeys, contentHeight, hasBlogContent, blogContentHtml });
137
150
  if (editorActive) {
138
- editorActive = false;
139
- enableEditorMode();
151
+ applyEditorToElements();
152
+ if (sectionResizeObserver) {
153
+ document.querySelectorAll("[data-section]").forEach((el) => {
154
+ sectionResizeObserver.observe(el);
155
+ });
156
+ }
140
157
  }
141
- observeSectionLayout();
142
158
  }
143
159
  function enableEditorMode() {
144
160
  if (editorActive) return;
145
161
  editorActive = true;
146
162
  injectEditorStyles();
147
- const textElements = document.querySelectorAll("[data-text-key]");
163
+ applyEditorToElements();
164
+ }
165
+ function applyEditorToElements() {
166
+ const textElements = document.querySelectorAll(TEXT_KEY_SELECTOR);
148
167
  textElements.forEach((htmlEl) => {
149
168
  htmlEl.classList.add("dcs-editable");
169
+ if (initializedTextElements.has(htmlEl)) return;
170
+ initializedTextElements.add(htmlEl);
150
171
  htmlEl.addEventListener("dblclick", handleTextKeyDblClick);
151
172
  htmlEl.addEventListener("blur", handleTextKeyBlur);
152
173
  htmlEl.addEventListener("keydown", handleTextKeyKeydown);
153
174
  htmlEl.addEventListener("mouseenter", () => {
154
175
  if (activeEditElement) return;
155
- const key = htmlEl.dataset.textKey;
176
+ const key = getTextKey(htmlEl);
156
177
  if (isArrayTextKey(key)) {
157
178
  showArrayEditIcon(htmlEl);
158
179
  } else {
@@ -168,6 +189,8 @@ function enableEditorMode() {
168
189
  });
169
190
  const sections = document.querySelectorAll("[data-section]");
170
191
  sections.forEach((el) => {
192
+ if (initializedSections.has(el)) return;
193
+ initializedSections.add(el);
171
194
  const sectionId = el.dataset.section;
172
195
  el.addEventListener("mouseenter", () => {
173
196
  postToParent("dcs:section-hover", { sectionId });
@@ -177,11 +200,26 @@ function enableEditorMode() {
177
200
  });
178
201
  el.addEventListener("click", (e) => {
179
202
  const target = e.target;
180
- if (!target.closest("[data-text-key]")) {
203
+ if (!target.closest(TEXT_KEY_SELECTOR)) {
181
204
  postToParent("dcs:section-click", { sectionId });
182
205
  }
183
206
  });
184
207
  });
208
+ if (!blogContentInitialized) {
209
+ const blogContentEl = document.querySelector("[data-blog-content]");
210
+ if (blogContentEl) {
211
+ blogContentInitialized = true;
212
+ blogContentEl.classList.add("dcs-blog-content");
213
+ blogContentEl.addEventListener("click", (e) => {
214
+ const target = e.target;
215
+ if (target.closest(TEXT_KEY_SELECTOR)) return;
216
+ e.preventDefault();
217
+ e.stopPropagation();
218
+ postToParent("dcs:blog-content-click", { textKey: "blog-content" });
219
+ });
220
+ postToParent("dcs:blog-content-ready", { content: blogContentEl.innerHTML });
221
+ }
222
+ }
185
223
  }
186
224
  function handleTextKeyDblClick(e) {
187
225
  if (!editingEnabled) return;
@@ -189,7 +227,7 @@ function handleTextKeyDblClick(e) {
189
227
  e.stopPropagation();
190
228
  e.stopImmediatePropagation();
191
229
  const el = e.currentTarget;
192
- const key = el.dataset.textKey;
230
+ const key = getTextKey(el);
193
231
  const sectionParent = el.closest("[data-section]");
194
232
  const sectionId = sectionParent?.dataset.section ?? null;
195
233
  postToParent("dcs:text-key-dblclick", { key, sectionId });
@@ -229,7 +267,7 @@ function finishEdit(el) {
229
267
  if (!el.hasAttribute("contenteditable")) return;
230
268
  el.removeAttribute("contenteditable");
231
269
  el.classList.remove("dcs-editing");
232
- const key = el.dataset.textKey;
270
+ const key = getTextKey(el);
233
271
  const sectionParent = el.closest("[data-section]");
234
272
  const sectionId = sectionParent?.dataset.section ?? null;
235
273
  const newValue = el.innerText.trim();
@@ -243,7 +281,7 @@ function finishEdit(el) {
243
281
  originalTextValues.delete(el);
244
282
  }
245
283
  function focusTextKey(key) {
246
- const el = document.querySelector(`[data-text-key="${key}"]`);
284
+ const el = document.querySelector(textKeySelector(key));
247
285
  if (!el) return;
248
286
  el.scrollIntoView({ behavior: "smooth", block: "center" });
249
287
  const sectionParent = el.closest("[data-section]");
@@ -251,7 +289,7 @@ function focusTextKey(key) {
251
289
  startInlineEdit(el, key, sectionId);
252
290
  }
253
291
  function updateTextInPlace(key, value) {
254
- const el = document.querySelector(`[data-text-key="${key}"]`);
292
+ const el = document.querySelector(textKeySelector(key));
255
293
  if (!el) return;
256
294
  if (el === activeEditElement) return;
257
295
  el.innerText = value;
@@ -269,7 +307,7 @@ function createEditIcon() {
269
307
  e.stopImmediatePropagation();
270
308
  if (!editIconTarget) return;
271
309
  const el = editIconTarget;
272
- const key = el.dataset.textKey;
310
+ const key = getTextKey(el);
273
311
  const sectionParent = el.closest("[data-section]");
274
312
  const sectionId = sectionParent?.dataset.section ?? null;
275
313
  postToParent("dcs:text-key-dblclick", { key, sectionId });
@@ -323,7 +361,7 @@ function createArrayEditIcon() {
323
361
  e.stopImmediatePropagation();
324
362
  if (!arrayEditIconTarget) return;
325
363
  const el = arrayEditIconTarget;
326
- const key = el.dataset.textKey;
364
+ const key = getTextKey(el);
327
365
  const arrayKey = getArrayBaseKey(key);
328
366
  if (!arrayKey) return;
329
367
  const sectionParent = el.closest("[data-section]");
@@ -361,26 +399,30 @@ function injectEditorStyles() {
361
399
  const style = document.createElement("style");
362
400
  style.id = "dcs-editor-styles";
363
401
  style.textContent = `
364
- [data-text-key].dcs-editable {
402
+ [data-text-key].dcs-editable,
403
+ [data-dcs-text].dcs-editable {
365
404
  cursor: text !important;
366
405
  border-radius: 4px;
367
406
  position: relative;
368
407
  }
369
408
 
370
- [data-text-key].dcs-editable:hover {
409
+ [data-text-key].dcs-editable:hover,
410
+ [data-dcs-text].dcs-editable:hover {
371
411
  outline: 2px dashed hsl(221.2 83.2% 53.3% / 0.4) !important;
372
412
  outline-offset: 4px;
373
413
  background-color: hsl(221.2 83.2% 53.3% / 0.03);
374
414
  }
375
415
 
376
- [data-text-key].dcs-editing {
416
+ [data-text-key].dcs-editing,
417
+ [data-dcs-text].dcs-editing {
377
418
  outline: 2px solid hsl(221.2 83.2% 53.3%) !important;
378
419
  outline-offset: 4px;
379
420
  background-color: hsl(221.2 83.2% 53.3% / 0.06);
380
421
  min-height: 1em;
381
422
  }
382
423
 
383
- [data-text-key].dcs-editing:focus {
424
+ [data-text-key].dcs-editing:focus,
425
+ [data-dcs-text].dcs-editing:focus {
384
426
  outline: 2px solid hsl(221.2 83.2% 53.3%) !important;
385
427
  outline-offset: 4px;
386
428
  }
@@ -450,6 +492,42 @@ function injectEditorStyles() {
450
492
  .dcs-array-edit-icon svg {
451
493
  display: block;
452
494
  }
495
+
496
+ /* Blog content area \u2014 click to open WYSIWYG editor */
497
+ [data-blog-content].dcs-blog-content {
498
+ cursor: pointer;
499
+ border-radius: 8px;
500
+ position: relative;
501
+ transition: outline-color 0.2s, background-color 0.2s;
502
+ }
503
+
504
+ [data-blog-content].dcs-blog-content:hover {
505
+ outline: 2px dashed hsl(142 71% 45% / 0.5) !important;
506
+ outline-offset: 8px;
507
+ background-color: hsl(142 71% 45% / 0.03);
508
+ }
509
+
510
+ [data-blog-content].dcs-blog-content::after {
511
+ content: 'Click to edit blog content';
512
+ position: absolute;
513
+ top: -28px;
514
+ left: 8px;
515
+ font-size: 11px;
516
+ font-weight: 600;
517
+ color: hsl(142 71% 45%);
518
+ background: hsl(142 71% 45% / 0.1);
519
+ border: 1px solid hsl(142 71% 45% / 0.3);
520
+ padding: 2px 8px;
521
+ border-radius: 4px;
522
+ opacity: 0;
523
+ transition: opacity 0.2s;
524
+ pointer-events: none;
525
+ white-space: nowrap;
526
+ }
527
+
528
+ [data-blog-content].dcs-blog-content:hover::after {
529
+ opacity: 1;
530
+ }
453
531
  `;
454
532
  document.head.appendChild(style);
455
533
  }
@@ -489,7 +567,7 @@ function handleMessage(event) {
489
567
  if (data && typeof data === "object" && "enabled" in data) {
490
568
  editingEnabled = data.enabled;
491
569
  if (editingEnabled) {
492
- document.querySelectorAll("[data-text-key]").forEach((el) => {
570
+ document.querySelectorAll(TEXT_KEY_SELECTOR).forEach((el) => {
493
571
  el.classList.add("dcs-editable");
494
572
  });
495
573
  } else {
@@ -504,6 +582,15 @@ function handleMessage(event) {
504
582
  }
505
583
  }
506
584
  break;
585
+ case "dcs:update-blog-content":
586
+ if (data && typeof data === "object" && "content" in data) {
587
+ const blogEl = document.querySelector("[data-blog-content]");
588
+ if (blogEl) {
589
+ blogEl.innerHTML = data.content;
590
+ scheduleRediscovery();
591
+ }
592
+ }
593
+ break;
507
594
  }
508
595
  }
509
596
  function injectEditorHeightFix() {
@@ -580,22 +667,16 @@ function initEditorBridge() {
580
667
  const sections = discoverSections();
581
668
  const textKeys = discoverTextKeys();
582
669
  const contentHeight = measureContentHeight(sections);
670
+ const blogEl = document.querySelector("[data-blog-content]");
583
671
  postToParent("dcs:ready", {
584
672
  sections,
585
673
  textKeys,
586
- contentHeight
674
+ contentHeight,
675
+ hasBlogContent: !!blogEl,
676
+ blogContentHtml: blogEl ? blogEl.innerHTML : null
587
677
  });
588
678
  observeSectionLayout();
589
679
  }
590
- if (typeof globalThis.self !== "undefined" && isInIframe()) {
591
- if (document.readyState === "loading") {
592
- document.addEventListener("DOMContentLoaded", () => {
593
- setTimeout(initEditorBridge, 200);
594
- });
595
- } else {
596
- setTimeout(initEditorBridge, 200);
597
- }
598
- }
599
680
 
600
681
  export { initEditorBridge };
601
682
  //# sourceMappingURL=editorBridge.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/editor/editorBridge.ts"],"names":[],"mappings":"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","file":"editorBridge.js","sourcesContent":["/**\r\n * Editor Bridge — injected into customer site iframes by the DCS visual editor.\r\n *\r\n * Responsibilities:\r\n * 1. Report available sections and text keys to the parent portal\r\n * 2. Enable inline contenteditable editing on data-text-key elements (double-click)\r\n * 3. Render section overlay highlights on hover/select from parent\r\n * 4. Communicate all interactions back to the parent via postMessage\r\n * 5. Monitor navigation within the iframe and notify the parent\r\n * 6. Show floating edit icon on hover for text-key elements\r\n *\r\n * NOTE: AI ✨ buttons are rendered by the portal-side SectionOverlayLayer,\r\n * NOT by this bridge. The bridge only reports section/text key data and\r\n * handles inline text editing.\r\n *\r\n * This script runs inside the iframe (customer site). The parent portal\r\n * communicates via postMessage using the `dcs:*` message protocol.\r\n */\r\n\r\n// ─── Types ───────────────────────────────────────────────────────\r\n\r\nexport interface SectionInfo {\r\n id: string\r\n label: string | null\r\n bounds: { x: number; y: number; width: number; height: number }\r\n textKeyCount: number\r\n}\r\n\r\nexport interface EditorReadyPayload {\r\n sections: SectionInfo[]\r\n textKeys: string[]\r\n contentHeight: number\r\n}\r\n\r\n// ─── Message Protocol ────────────────────────────────────────────\r\n\r\ntype InboundMessageType =\r\n | 'dcs:init-editor'\r\n | 'dcs:highlight-section'\r\n | 'dcs:clear-highlight'\r\n | 'dcs:select-text-key'\r\n | 'dcs:update-text'\r\n | 'dcs:set-mode'\r\n | 'dcs:set-editing-enabled'\r\n\r\ntype OutboundMessageType =\r\n | 'dcs:ready'\r\n | 'dcs:text-key-click'\r\n | 'dcs:text-key-changed'\r\n | 'dcs:section-click'\r\n | 'dcs:section-hover'\r\n | 'dcs:text-key-dblclick'\r\n | 'dcs:contextmenu'\r\n | 'dcs:click'\r\n | 'dcs:navigation'\r\n | 'dcs:array-key-click'\r\n | 'dcs:image-click'\r\n\r\nfunction isInIframe(): boolean {\r\n try { return globalThis.self !== globalThis.self.parent } catch { return true }\r\n}\r\n\r\nfunction postToParent(type: OutboundMessageType, data: unknown) {\r\n if (!isInIframe()) return\r\n // Use '*' because the portal origin varies between dev and production\r\n // and this script doesn't know the parent origin at injection time.\r\n // Security: messages are validated by the portal's useIframeBridge.\r\n // Origin validated via dcs: prefix check on all messages\r\n globalThis.self.parent.postMessage({ type, data }, '*') // NOSONAR\r\n}\r\n\r\n// ─── State ───────────────────────────────────────────────────────\r\n\r\nlet editorActive = false\r\nlet bridgeInitialized = false\r\nlet activeEditElement: HTMLElement | null = null\r\n/** The current pathname tracked for navigation detection */\r\nlet lastKnownPathname: string = ''\r\n/** The floating edit icon element shown on hover over text keys */\r\nlet editIconElement: HTMLElement | null = null\r\n/** The element the edit icon is currently attached to */\r\nlet editIconTarget: HTMLElement | null = null\r\n/** The floating edit icon for array/list text keys */\r\nlet arrayEditIconElement: HTMLElement | null = null\r\n/** The element the array edit icon is currently attached to */\r\nlet arrayEditIconTarget: HTMLElement | null = null\r\n/** Whether editing features are enabled (disabled on unmanaged pages) */\r\nlet editingEnabled = true\r\n/** Original text values captured when inline editing starts, for change detection */\r\nconst originalTextValues = new Map<HTMLElement, string>()\r\n/** ResizeObserver for tracking section layout changes */\r\nlet sectionResizeObserver: ResizeObserver | null = null\r\n/** Debounce timer for re-discovery triggered by layout changes */\r\nlet rediscoveryTimer: ReturnType<typeof setTimeout> | null = null\r\n\r\n/** Debounced re-discovery — collapses rapid layout changes into one update */\r\nfunction scheduleRediscovery() {\r\n if (rediscoveryTimer) clearTimeout(rediscoveryTimer)\r\n rediscoveryTimer = setTimeout(() => {\r\n rediscoveryTimer = null\r\n rediscoverAndNotify()\r\n }, 300)\r\n}\r\n\r\n// ─── Section Discovery ───────────────────────────────────────────\r\n\r\nfunction discoverSections(): SectionInfo[] {\r\n const elements = document.querySelectorAll<HTMLElement>('[data-section]')\r\n return Array.from(elements).map((el) => {\r\n const rect = el.getBoundingClientRect()\r\n const textKeyCount = el.querySelectorAll('[data-text-key]').length\r\n return {\r\n id: el.dataset.section!,\r\n label: el.dataset.sectionLabel ?? null,\r\n bounds: {\r\n x: rect.left + scrollX,\r\n y: rect.top + scrollY,\r\n width: rect.width,\r\n height: rect.height,\r\n },\r\n textKeyCount,\r\n }\r\n })\r\n}\r\n\r\nfunction discoverTextKeys(): string[] {\r\n return Array.from(document.querySelectorAll<HTMLElement>('[data-text-key]')).map(\r\n (el) => el.dataset.textKey!,\r\n )\r\n}\r\n\r\n// ─── Layout Change Observation ───────────────────────────────────\r\n\r\n/**\r\n * Watch for layout changes that affect section bounds and overall page height:\r\n * 1. ALL lazy-loaded images on the page — their load events shift subsequent sections\r\n * 2. ResizeObserver on the document body — detects overall page height changes\r\n * 3. ResizeObserver on section elements — detects any size change (font loading,\r\n * dynamic content expansion, CSS transitions, etc.)\r\n *\r\n * When changes are detected, we schedule a debounced re-discovery so the portal\r\n * overlay layer receives updated section bounds and content height that match\r\n * the live layout.\r\n */\r\nfunction observeSectionLayout() {\r\n // Clean up any previous observer (e.g., after SPA navigation)\r\n if (sectionResizeObserver) {\r\n sectionResizeObserver.disconnect()\r\n }\r\n\r\n // 1. Watch ALL lazy images on the page — not just inside sections.\r\n // Images outside sections (e.g., contact form, unmapped content) can still\r\n // push content down and change overall page height and section positions.\r\n const allImages = document.querySelectorAll<HTMLImageElement>('img')\r\n allImages.forEach(img => {\r\n if (!img.complete) {\r\n img.addEventListener('load', scheduleRediscovery, { once: true })\r\n img.addEventListener('error', scheduleRediscovery, { once: true })\r\n }\r\n })\r\n\r\n // 2. ResizeObserver on the document body + each section element.\r\n // Body observation catches overall page height changes from any source.\r\n if (typeof ResizeObserver !== 'undefined') {\r\n sectionResizeObserver = new ResizeObserver(scheduleRediscovery)\r\n // Observe body for overall height changes\r\n sectionResizeObserver.observe(document.body)\r\n // Observe each section for individual bound changes\r\n document.querySelectorAll<HTMLElement>('[data-section]').forEach(el => {\r\n sectionResizeObserver!.observe(el)\r\n })\r\n }\r\n}\r\n\r\n// ─── Section Overlay ─────────────────────────────────────────────\r\n\r\n/**\r\n * Section highlight is now handled entirely by the portal-side\r\n * SectionOverlayLayer rendered on top of the iframe. The bridge\r\n * only reports section bounds and hover events — no visual\r\n * overlays are rendered inside the iframe itself.\r\n */\r\nfunction showSectionHighlight(_sectionId: string) {\r\n // No-op: portal overlay layer handles section highlights\r\n}\r\n\r\nfunction clearSectionHighlight() {\r\n // No-op: portal overlay layer handles section highlights\r\n}\r\n\r\n// ─── Navigation Monitoring ───────────────────────────────────────\r\n\r\n/**\r\n * Monitor navigation within the iframe and notify the parent portal.\r\n * Instead of blocking all navigation, we allow SPA routing and inform\r\n * the portal when the iframe navigates to a different page. The portal\r\n * then updates its own URL and loads editing context for the new page.\r\n *\r\n * Strategy:\r\n * 1. Poll location.pathname on a short interval — this reliably detects\r\n * SPA navigation regardless of which router or framework is used,\r\n * since frameworks may cache History API references before our script loads.\r\n * 2. Block external/cross-origin link clicks (actual page reloads).\r\n * 3. Block form submissions.\r\n * 4. Monitor popstate for back/forward navigation.\r\n * 5. After navigation, re-discover sections and report them to the portal.\r\n */\r\nfunction monitorNavigation() {\r\n lastKnownPathname = globalThis.location.pathname\r\n\r\n // ── Poll for URL changes — reliable with any SPA router ──────\r\n // Frameworks like VitePress/Vue Router may cache history.pushState before\r\n // our bridge loads, so prototype overrides are unreliable. Polling\r\n // location.pathname directly is simple and always works.\r\n setInterval(() => {\r\n checkForNavigation()\r\n }, 250)\r\n\r\n // ── Catch back/forward navigation ────────────────────────────\r\n globalThis.addEventListener('popstate', () => {\r\n checkForNavigation()\r\n })\r\n\r\n // ── Block external link clicks (cross-origin / full page reloads) ──\r\n const handleLinkClick = (e: MouseEvent) => {\r\n const link = (e.target as HTMLElement).closest?.('a[href]') as HTMLAnchorElement | null\r\n if (!link) return\r\n\r\n const href = link.getAttribute('href')\r\n if (!href || href.startsWith('#') || href === 'javascript:void(0)') return\r\n\r\n // Check if this is an external link (different origin)\r\n try {\r\n const resolved = new URL(href, globalThis.location.href)\r\n if (resolved.origin !== globalThis.location.origin) {\r\n // Block external navigation — can't leave the iframe\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n }\r\n // Same-origin links: let the SPA router handle them.\r\n // The History API override above will detect the navigation.\r\n } catch {\r\n // Malformed URL — block it\r\n e.preventDefault()\r\n }\r\n }\r\n\r\n // Register on window first to beat VitePress's capture-phase handler\r\n globalThis.addEventListener('click', handleLinkClick, true)\r\n document.addEventListener('click', handleLinkClick, true)\r\n\r\n // Intercept form submissions\r\n document.addEventListener('submit', (e: Event) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n }, true)\r\n\r\n // Block actual page reloads (location.href = ..., etc.)\r\n window.addEventListener('beforeunload', (e: BeforeUnloadEvent) => {\r\n e.preventDefault()\r\n })\r\n}\r\n\r\n/**\r\n * Check if the iframe pathname has changed and notify the portal.\r\n * Also re-initializes the editor bridge for the new page.\r\n */\r\nfunction checkForNavigation() {\r\n const currentPathname = globalThis.location.pathname\r\n if (currentPathname === lastKnownPathname) return\r\n\r\n lastKnownPathname = currentPathname\r\n\r\n // Notify parent about the navigation\r\n postToParent('dcs:navigation', { pathname: currentPathname })\r\n\r\n // Clean up current edit state\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n\r\n // Wait for the SPA router to finish rendering the new page,\r\n // then re-discover sections and text keys\r\n setTimeout(() => {\r\n rediscoverAndNotify()\r\n }, 500)\r\n}\r\n\r\n// ─── Mode Management ─────────────────────────────────────────────\r\n\r\n/**\r\n * Set the current interaction mode. Both modes allow normal browsing —\r\n * inline editing works via double-click regardless of mode.\r\n * Switching to explore finishes any active inline edit.\r\n */\r\nfunction setMode(mode: 'edit' | 'explore') {\r\n if (mode === 'explore') {\r\n // When explicitly switching to explore, finish any active inline edit\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n }\r\n}\r\n\r\n// ─── Inline Text Editing ─────────────────────────────────────────\r\n\r\n/**\r\n * Re-discover sections and text keys after an HMR update and\r\n * notify the parent portal so the overlay layer updates.\r\n */\r\nfunction rediscoverAndNotify() {\r\n activeEditElement = null\r\n\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n\r\n // Skip re-report if height hasn't meaningfully changed (< 2px drift)\r\n // AND sections/textKeys are the same count — avoids portal re-render churn.\r\n if (Math.abs(contentHeight - lastReportedHeight) <= 2) {\r\n // Height is stable — still report in case sections/textKeys changed\r\n }\r\n lastReportedHeight = contentHeight\r\n\r\n // Always report — sections/textKeys may have changed even if height is stable\r\n postToParent('dcs:ready', { sections, textKeys, contentHeight } satisfies EditorReadyPayload)\r\n\r\n // Re-enable editor mode on the new elements\r\n if (editorActive) {\r\n editorActive = false // allow re-initialization\r\n enableEditorMode()\r\n }\r\n\r\n // Re-observe layout changes on the new DOM\r\n observeSectionLayout()\r\n}\r\n\r\nfunction enableEditorMode() {\r\n if (editorActive) return\r\n editorActive = true\r\n\r\n // Inject editor styles\r\n injectEditorStyles()\r\n\r\n // Set up all text key elements — double-click to start editing\r\n const textElements = document.querySelectorAll<HTMLElement>('[data-text-key]')\r\n textElements.forEach((htmlEl) => {\r\n htmlEl.classList.add('dcs-editable')\r\n\r\n // Double-click to start inline editing (signals parent to switch to edit mode)\r\n htmlEl.addEventListener('dblclick', handleTextKeyDblClick)\r\n // Blur to finish editing\r\n htmlEl.addEventListener('blur', handleTextKeyBlur)\r\n // Keyboard: Enter to finish, Escape to cancel\r\n htmlEl.addEventListener('keydown', handleTextKeyKeydown)\r\n\r\n // Show/hide the floating edit icon on hover (T icon for regular text, List icon for array keys)\r\n htmlEl.addEventListener('mouseenter', () => {\r\n // Don't show icon while actively editing this or another element\r\n if (activeEditElement) return\r\n const key = htmlEl.dataset.textKey!\r\n if (isArrayTextKey(key)) {\r\n showArrayEditIcon(htmlEl)\r\n } else {\r\n showEditIcon(htmlEl)\r\n }\r\n })\r\n htmlEl.addEventListener('mouseleave', (e: MouseEvent) => {\r\n // Don't hide if moving to the edit icon or array edit icon\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && (\r\n related.classList?.contains('dcs-edit-icon') || related.closest?.('.dcs-edit-icon') ||\r\n related.classList?.contains('dcs-array-edit-icon') || related.closest?.('.dcs-array-edit-icon')\r\n )) return\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n })\r\n })\r\n\r\n // Set up section hover reporting (portal overlay handles the visual treatment)\r\n const sections = document.querySelectorAll<HTMLElement>('[data-section]')\r\n sections.forEach((el) => {\r\n const sectionId = el.dataset.section!\r\n\r\n el.addEventListener('mouseenter', () => {\r\n postToParent('dcs:section-hover', { sectionId })\r\n })\r\n el.addEventListener('mouseleave', () => {\r\n postToParent('dcs:section-hover', { sectionId: null })\r\n })\r\n el.addEventListener('click', (e) => {\r\n // Only fire section click if not clicking on a text-key element\r\n const target = e.target as HTMLElement\r\n if (!target.closest('[data-text-key]')) {\r\n postToParent('dcs:section-click', { sectionId })\r\n }\r\n })\r\n })\r\n}\r\n\r\n/**\r\n * Handle double-click on a text key element.\r\n * Notifies the parent portal to switch to edit mode and begins inline editing.\r\n */\r\nfunction handleTextKeyDblClick(e: Event) {\r\n if (!editingEnabled) return\r\n\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n const el = e.currentTarget as HTMLElement\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Always notify the parent to switch to edit mode when starting inline editing.\r\n // In explore mode this triggers the mode switch; in edit mode it's a no-op on the parent side.\r\n postToParent('dcs:text-key-dblclick', { key, sectionId })\r\n\r\n // Start inline editing\r\n startInlineEdit(el, key, sectionId)\r\n}\r\n\r\n/**\r\n * Start inline contenteditable editing on a text key element.\r\n * Called by double-click or when the parent sends dcs:select-text-key.\r\n */\r\nfunction startInlineEdit(el: HTMLElement, key: string, sectionId: string | null) {\r\n // Finish any active edit first\r\n if (activeEditElement && activeEditElement !== el) {\r\n finishEdit(activeEditElement)\r\n }\r\n\r\n // Store original text for change detection on finish\r\n originalTextValues.set(el, el.innerText.trim())\r\n\r\n // Enable contenteditable\r\n el.setAttribute('contenteditable', 'true')\r\n el.classList.add('dcs-editing')\r\n el.focus()\r\n activeEditElement = el\r\n\r\n // Select all text for easy replacement\r\n const selection = getSelection()\r\n const range = document.createRange()\r\n range.selectNodeContents(el)\r\n selection?.removeAllRanges()\r\n selection?.addRange(range)\r\n\r\n postToParent('dcs:text-key-click', { key, sectionId })\r\n}\r\n\r\nfunction handleTextKeyBlur(e: Event) {\r\n const el = e.currentTarget as HTMLElement\r\n finishEdit(el)\r\n}\r\n\r\nfunction handleTextKeyKeydown(e: KeyboardEvent) {\r\n if (e.key === 'Enter' && !e.shiftKey) {\r\n e.preventDefault()\r\n ;(e.currentTarget as HTMLElement).blur()\r\n }\r\n if (e.key === 'Escape') {\r\n e.preventDefault()\r\n ;(e.currentTarget as HTMLElement).blur()\r\n }\r\n}\r\n\r\nfunction finishEdit(el: HTMLElement) {\r\n if (!el.hasAttribute('contenteditable')) return\r\n\r\n el.removeAttribute('contenteditable')\r\n el.classList.remove('dcs-editing')\r\n\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n const newValue = el.innerText.trim()\r\n const originalValue = originalTextValues.get(el) ?? ''\r\n\r\n if (activeEditElement === el) {\r\n activeEditElement = null\r\n }\r\n\r\n // Only report to the portal when text was actually modified\r\n if (newValue !== originalValue) {\r\n postToParent('dcs:text-key-changed', { key, value: newValue, sectionId })\r\n }\r\n\r\n originalTextValues.delete(el)\r\n}\r\n\r\nfunction focusTextKey(key: string) {\r\n const el = document.querySelector<HTMLElement>(`[data-text-key=\"${key}\"]`)\r\n if (!el) return\r\n el.scrollIntoView({ behavior: 'smooth', block: 'center' })\r\n\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Start inline editing directly\r\n startInlineEdit(el, key, sectionId)\r\n}\r\n\r\nfunction updateTextInPlace(key: string, value: string) {\r\n const el = document.querySelector<HTMLElement>(`[data-text-key=\"${key}\"]`)\r\n if (!el) return\r\n // Don't update if we're currently editing this element\r\n if (el === activeEditElement) return\r\n el.innerText = value\r\n}\r\n\r\n// ─── Edit Icon (Floating Text Cursor Button) ────────────────────\r\n\r\n/**\r\n * Create the floating edit icon element. This small button appears\r\n * in the top-left corner of a hovered [data-text-key] element,\r\n * providing a click target to start inline editing without triggering\r\n * navigation (important for text inside buttons/links).\r\n */\r\nfunction createEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Edit text')\r\n icon.setAttribute('aria-label', 'Edit text inline')\r\n // SVG: Type icon (text cursor) — matches lucide \"Type\" icon\r\n icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"/><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"/></svg>`\r\n\r\n icon.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n if (!editIconTarget) return\r\n const el = editIconTarget\r\n const key = el.dataset.textKey!\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Signal parent to switch to edit mode\r\n postToParent('dcs:text-key-dblclick', { key, sectionId })\r\n // Start inline editing\r\n startInlineEdit(el, key, sectionId)\r\n // Hide the icon while editing\r\n removeEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the target element)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && editIconTarget && (related === editIconTarget || editIconTarget.contains(related))) return\r\n removeEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the edit icon positioned at the top-left corner of the given element.\r\n */\r\nfunction showEditIcon(el: HTMLElement) {\r\n if (!editingEnabled) return\r\n\r\n if (!editIconElement) {\r\n editIconElement = createEditIcon()\r\n document.body.appendChild(editIconElement)\r\n }\r\n\r\n editIconTarget = el\r\n\r\n // Position relative to the element's bounding box\r\n const rect = el.getBoundingClientRect()\r\n editIconElement.style.top = `${rect.top + scrollY - 4}px`\r\n editIconElement.style.left = `${rect.left + scrollX - 4}px`\r\n editIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the edit icon.\r\n */\r\nfunction removeEditIcon() {\r\n if (editIconElement) {\r\n editIconElement.style.display = 'none'\r\n }\r\n editIconTarget = null\r\n}\r\n\r\n// ─── Array Key Detection ─────────────────────────────────────────\r\n\r\n/**\r\n * Check if a text key is part of an array pattern (e.g., \"conditions.0.title\").\r\n * Array text keys contain an indexed segment — either:\r\n * - Pure numeric: `baseKey.N.field` (e.g., `conditions.0.title`)\r\n * - Hyphenated index: `baseKey.prefix-N.field` (e.g., `steps.step-0.title`)\r\n */\r\nfunction isArrayTextKey(key: string): boolean {\r\n // Match pure numeric segment (.0.) or hyphenated index (.prefix-0.)\r\n return /\\.\\d+\\./.test(key) || /\\.\\w+-\\d+\\./.test(key)\r\n}\r\n\r\n/**\r\n * Extract the base array key from an array text key.\r\n * - `conditions.0.title` → `conditions`\r\n * - `steps.step-0.title` → `steps.step`\r\n */\r\nfunction getArrayBaseKey(key: string): string | null {\r\n // Try hyphenated pattern first (more specific)\r\n const hyphenMatch = key.match(/^(.+?)-\\d+\\./)\r\n if (hyphenMatch) return hyphenMatch[1]\r\n // Fall back to pure numeric pattern\r\n const numMatch = key.match(/^(.+?)\\.\\d+\\./)\r\n return numMatch ? numMatch[1] : null\r\n}\r\n\r\n// ─── Array Edit Icon (Floating List Button) ──────────────────────\r\n\r\n/**\r\n * Create the floating array edit icon element. This small button appears\r\n * in the top-left corner of a hovered [data-text-key] element whose key\r\n * matches an array pattern. Clicking it sends dcs:array-key-click to the\r\n * portal to open the array editor sheet.\r\n */\r\nfunction createArrayEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-array-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Edit list items')\r\n icon.setAttribute('aria-label', 'Edit list items in panel')\r\n // SVG: List icon — matches lucide \"List\" icon\r\n icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>`\r\n\r\n icon.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n if (!arrayEditIconTarget) return\r\n const el = arrayEditIconTarget\r\n const key = el.dataset.textKey!\r\n const arrayKey = getArrayBaseKey(key)\r\n if (!arrayKey) return\r\n\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Signal parent to open array editor sheet\r\n postToParent('dcs:array-key-click', { arrayKey, sectionId })\r\n // Hide the icon\r\n removeArrayEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the target element)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && arrayEditIconTarget && (related === arrayEditIconTarget || arrayEditIconTarget.contains(related))) return\r\n removeArrayEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the array edit icon positioned at the top-left corner of the given element.\r\n */\r\nfunction showArrayEditIcon(el: HTMLElement) {\r\n if (!editingEnabled) return\r\n\r\n if (!arrayEditIconElement) {\r\n arrayEditIconElement = createArrayEditIcon()\r\n document.body.appendChild(arrayEditIconElement)\r\n }\r\n\r\n arrayEditIconTarget = el\r\n\r\n // Position relative to the element's bounding box\r\n const rect = el.getBoundingClientRect()\r\n arrayEditIconElement.style.top = `${rect.top + scrollY - 4}px`\r\n arrayEditIconElement.style.left = `${rect.left + scrollX - 4}px`\r\n arrayEditIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the array edit icon.\r\n */\r\nfunction removeArrayEditIcon() {\r\n if (arrayEditIconElement) {\r\n arrayEditIconElement.style.display = 'none'\r\n }\r\n arrayEditIconTarget = null\r\n}\r\n\r\nfunction injectEditorStyles() {\r\n if (document.getElementById('dcs-editor-styles')) return\r\n\r\n const style = document.createElement('style')\r\n style.id = 'dcs-editor-styles'\r\n style.textContent = `\r\n [data-text-key].dcs-editable {\r\n cursor: text !important;\r\n border-radius: 4px;\r\n position: relative;\r\n }\r\n\r\n [data-text-key].dcs-editable:hover {\r\n outline: 2px dashed hsl(221.2 83.2% 53.3% / 0.4) !important;\r\n outline-offset: 4px;\r\n background-color: hsl(221.2 83.2% 53.3% / 0.03);\r\n }\r\n\r\n [data-text-key].dcs-editing {\r\n outline: 2px solid hsl(221.2 83.2% 53.3%) !important;\r\n outline-offset: 4px;\r\n background-color: hsl(221.2 83.2% 53.3% / 0.06);\r\n min-height: 1em;\r\n }\r\n\r\n [data-text-key].dcs-editing:focus {\r\n outline: 2px solid hsl(221.2 83.2% 53.3%) !important;\r\n outline-offset: 4px;\r\n }\r\n\r\n .dcs-section-highlight {\r\n pointer-events: none;\r\n }\r\n\r\n /* Floating edit icon button */\r\n .dcs-edit-icon {\r\n position: absolute;\r\n z-index: 99999;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 0;\r\n margin: 0;\r\n border: 1.5px solid hsl(221.2 83.2% 53.3%);\r\n border-radius: 4px;\r\n background: hsl(221.2 83.2% 53.3% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(221.2 83.2% 53.3%);\r\n cursor: pointer;\r\n pointer-events: auto;\r\n transition: background 0.15s, transform 0.1s;\r\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n }\r\n\r\n .dcs-edit-icon:hover {\r\n background: hsl(221.2 83.2% 53.3% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-edit-icon svg {\r\n display: block;\r\n }\r\n\r\n /* Floating array edit icon button */\r\n .dcs-array-edit-icon {\r\n position: absolute;\r\n z-index: 99999;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 0;\r\n margin: 0;\r\n border: 1.5px solid hsl(271 91% 65%);\r\n border-radius: 4px;\r\n background: hsl(271 91% 65% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(271 91% 65%);\r\n cursor: pointer;\r\n pointer-events: auto;\r\n transition: background 0.15s, transform 0.1s;\r\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n }\r\n\r\n .dcs-array-edit-icon:hover {\r\n background: hsl(271 91% 65% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-array-edit-icon svg {\r\n display: block;\r\n }\r\n `\r\n document.head.appendChild(style)\r\n}\r\n\r\n// ─── Inbound Message Handler ─────────────────────────────────────\r\n\r\nfunction handleMessage(event: MessageEvent) {\r\n // Validate message structure (origin is not checked because\r\n // the parent portal origin varies between dev and production)\r\n const msg = event.data\r\n if (!msg || typeof msg !== 'object' || typeof msg.type !== 'string') return\r\n if (!msg.type.startsWith('dcs:')) return\r\n\r\n const { type, data } = msg as { type: InboundMessageType; data: unknown }\r\n\r\n switch (type) {\r\n case 'dcs:init-editor':\r\n enableEditorMode()\r\n break\r\n\r\n case 'dcs:highlight-section':\r\n if (data && typeof data === 'object' && 'sectionId' in data) {\r\n showSectionHighlight((data as { sectionId: string }).sectionId)\r\n }\r\n break\r\n\r\n case 'dcs:clear-highlight':\r\n clearSectionHighlight()\r\n break\r\n\r\n case 'dcs:select-text-key':\r\n if (data && typeof data === 'object' && 'key' in data) {\r\n focusTextKey((data as { key: string }).key)\r\n }\r\n break\r\n\r\n case 'dcs:update-text':\r\n if (data && typeof data === 'object' && 'key' in data && 'value' in data) {\r\n const d = data as { key: string; value: string }\r\n updateTextInPlace(d.key, d.value)\r\n }\r\n break\r\n\r\n case 'dcs:set-mode':\r\n if (data && typeof data === 'object' && 'mode' in data) {\r\n setMode((data as { mode: 'edit' | 'explore' }).mode)\r\n }\r\n break\r\n\r\n case 'dcs:set-editing-enabled':\r\n if (data && typeof data === 'object' && 'enabled' in data) {\r\n editingEnabled = (data as { enabled: boolean }).enabled\r\n if (editingEnabled) {\r\n // Re-add dcs-editable class to text key elements\r\n document.querySelectorAll<HTMLElement>('[data-text-key]').forEach(el => {\r\n el.classList.add('dcs-editable')\r\n })\r\n } else {\r\n // Clean up: finish any active edit, remove edit icon, remove hover styles\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n // Remove dcs-editable class to disable hover outlines\r\n document.querySelectorAll<HTMLElement>('.dcs-editable').forEach(el => {\r\n el.classList.remove('dcs-editable')\r\n })\r\n }\r\n }\r\n break\r\n }\r\n}\r\n\r\n// ─── Bootstrap ───────────────────────────────────────────────────\r\n\r\n/**\r\n * Inject a stylesheet that neutralizes viewport-relative min-heights.\r\n * In the visual editor iframe, the portal controls height from body.scrollHeight.\r\n * CSS rules like `min-height: 100vh` create a feedback loop:\r\n * iframe height → vh grows → body.scrollHeight grows → iframe height grows → …\r\n * Neutralizing these rules lets the content determine its natural height.\r\n */\r\nfunction injectEditorHeightFix() {\r\n if (document.getElementById('dcs-editor-height-fix')) return\r\n const style = document.createElement('style')\r\n style.id = 'dcs-editor-height-fix'\r\n style.textContent = `\r\n /* DCS Editor Bridge: Neutralize viewport-relative min-heights */\r\n .min-h-screen, .min-h-dvh, .min-h-svh,\r\n .min-h-\\\\[100vh\\\\], .min-h-\\\\[100dvh\\\\], .min-h-\\\\[100svh\\\\] {\r\n min-height: 0 !important;\r\n }\r\n `\r\n document.head.appendChild(style)\r\n}\r\n\r\n/** Last reported content height — used to avoid re-sending when stable */\r\nlet lastReportedHeight = 0\r\n\r\n/**\r\n * Measure the true content height, avoiding inflation from viewport-relative\r\n * CSS rules. Uses section bounds as the primary signal (immune to vh feedback),\r\n * falling back to body.scrollHeight (which is reliable once the editor height\r\n * fix stylesheet has been injected).\r\n */\r\nfunction measureContentHeight(sections: SectionInfo[]): number {\r\n // Primary: compute from section bounds — these reflect actual element positions\r\n // after the height-fix stylesheet has neutralized viewport-relative min-heights.\r\n let maxBottom = 0\r\n for (const s of sections) {\r\n const bottom = s.bounds.y + s.bounds.height\r\n if (bottom > maxBottom) maxBottom = bottom\r\n }\r\n const sectionHeight = maxBottom > 0 ? Math.ceil(maxBottom) : 0\r\n\r\n // Fallback: body.scrollHeight (reliable now that min-height: 100vh is neutralized)\r\n const bodyHeight = document.body.scrollHeight\r\n\r\n // Use whichever is larger — sections might miss elements without data-section,\r\n // but bodyHeight should be accurate with the injected CSS fix.\r\n return Math.max(sectionHeight, bodyHeight)\r\n}\r\n\r\nexport function initEditorBridge() {\r\n if (!isInIframe()) return // Not in iframe\r\n\r\n // Inject height fix CSS before any measurement\r\n injectEditorHeightFix()\r\n\r\n // Prevent duplicate listener registration on HMR re-execution\r\n if (!bridgeInitialized) {\r\n bridgeInitialized = true\r\n\r\n // Monitor navigation within the iframe — notify the portal of page changes\r\n monitorNavigation()\r\n\r\n // Forward right-click events to the portal so the context menu\r\n // works when right-clicking inside the iframe (cross-origin boundary\r\n // prevents the parent from receiving native contextmenu events).\r\n document.addEventListener('contextmenu', (e: MouseEvent) => {\r\n e.preventDefault()\r\n const target = e.target as HTMLElement\r\n const sectionEl = target.closest?.('[data-section]') as HTMLElement | null\r\n\r\n // Detect if the right-click target is an image or inside a <picture> element\r\n const imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n\r\n postToParent('dcs:contextmenu', {\r\n x: e.clientX,\r\n y: e.clientY,\r\n sectionId: sectionEl?.dataset.section ?? null,\r\n sectionLabel: sectionEl?.dataset.sectionLabel ?? null,\r\n // Image context (null when not right-clicking on an image)\r\n imageUrl: imgEl?.currentSrc ?? imgEl?.src ?? null,\r\n imageAlt: imgEl?.alt ?? null,\r\n imageWidth: imgEl?.naturalWidth ?? null,\r\n imageHeight: imgEl?.naturalHeight ?? null,\r\n })\r\n }, true)\r\n\r\n // Forward click events to the portal so it can dismiss context menus\r\n // when the user clicks inside the iframe. Also detect image clicks\r\n // in edit mode to enable direct image management.\r\n document.addEventListener('click', (e: MouseEvent) => {\r\n postToParent('dcs:click', {})\r\n\r\n // In edit mode, detect image clicks for image management\r\n if (editorActive && editingEnabled) {\r\n const target = e.target as HTMLElement\r\n const imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n if (imgEl) {\r\n const sectionEl = target.closest?.('[data-section]') as HTMLElement | null\r\n postToParent('dcs:image-click', {\r\n imageUrl: imgEl.currentSrc ?? imgEl.src,\r\n imageAlt: imgEl.alt ?? null,\r\n imageWidth: imgEl.naturalWidth,\r\n imageHeight: imgEl.naturalHeight,\r\n sectionId: sectionEl?.dataset.section ?? null,\r\n sectionLabel: sectionEl?.dataset.sectionLabel ?? null,\r\n })\r\n }\r\n }\r\n }, true)\r\n\r\n // Listen for portal commands. Origin is not validated because\r\n // the portal origin varies between environments and all inbound\r\n // messages are type-checked before processing.\r\n // Origin cannot be pre-validated because the portal URL varies by environment.\r\n // All inbound messages are type-checked via the dcs: prefix guard.\r\n globalThis.self.addEventListener('message', handleMessage) // NOSONAR\r\n\r\n // Watch for VitePress / Vite HMR page updates. When the framework\r\n // replaces DOM content, our event listeners and AI buttons are lost.\r\n // Re-discover sections after each update so the portal overlay stays in sync.\r\n if (import.meta.hot) {\r\n import.meta.hot.on('vite:afterUpdate', () => {\r\n // Small delay for the framework to finish DOM updates\r\n setTimeout(rediscoverAndNotify, 300)\r\n })\r\n }\r\n }\r\n\r\n // Report available sections, text keys, and content height\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n lastReportedHeight = contentHeight\r\n\r\n postToParent('dcs:ready', {\r\n sections,\r\n textKeys,\r\n contentHeight,\r\n } satisfies EditorReadyPayload)\r\n\r\n // Observe layout changes (lazy images, dynamic content) and re-report bounds\r\n observeSectionLayout()\r\n}\r\n\r\n// Auto-initialize when the script is loaded (e.g., injected by Vite plugin)\r\n// Delay slightly to ensure the page is fully rendered\r\nif (typeof globalThis.self !== 'undefined' && isInIframe()) {\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', () => {\r\n // Small delay for Vue/framework hydration\r\n setTimeout(initEditorBridge, 200)\r\n })\r\n } else {\r\n setTimeout(initEditorBridge, 200)\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/editor/editorBridge.ts"],"names":[],"mappings":"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","file":"editorBridge.js","sourcesContent":["/**\r\n * Editor Bridge — injected into customer site iframes by the DCS visual editor.\r\n *\r\n * Responsibilities:\r\n * 1. Report available sections and text keys to the parent portal\r\n * 2. Enable inline contenteditable editing on data-text-key/data-dcs-text elements (double-click)\r\n * 3. Render section overlay highlights on hover/select from parent\r\n * 4. Communicate all interactions back to the parent via postMessage\r\n * 5. Monitor navigation within the iframe and notify the parent\r\n * 6. Show floating edit icon on hover for text-key elements\r\n *\r\n * Supports both legacy `data-text-key` and modern `data-dcs-text` attributes\r\n * for backward compatibility during the migration period.\r\n *\r\n * NOTE: AI ✨ buttons are rendered by the portal-side SectionOverlayLayer,\r\n * NOT by this bridge. The bridge only reports section/text key data and\r\n * handles inline text editing.\r\n *\r\n * This script runs inside the iframe (customer site). The parent portal\r\n * communicates via postMessage using the `dcs:*` message protocol.\r\n */\r\n\r\n// ─── Types ───────────────────────────────────────────────────────\r\n\r\nexport interface SectionInfo {\r\n id: string\r\n label: string | null\r\n bounds: { x: number; y: number; width: number; height: number }\r\n textKeyCount: number\r\n}\r\n\r\nexport interface EditorReadyPayload {\r\n sections: SectionInfo[]\r\n textKeys: string[]\r\n contentHeight: number\r\n /** Whether the page has a [data-blog-content] element (blog post page) */\r\n hasBlogContent: boolean\r\n /** Current innerHTML of the blog content element, if present */\r\n blogContentHtml: string | null\r\n}\r\n\r\n// ─── Message Protocol ────────────────────────────────────────────\r\n\r\ntype InboundMessageType =\r\n | 'dcs:init-editor'\r\n | 'dcs:highlight-section'\r\n | 'dcs:clear-highlight'\r\n | 'dcs:select-text-key'\r\n | 'dcs:update-text'\r\n | 'dcs:set-mode'\r\n | 'dcs:set-editing-enabled'\r\n | 'dcs:update-blog-content'\r\n\r\ntype OutboundMessageType =\r\n | 'dcs:ready'\r\n | 'dcs:text-key-click'\r\n | 'dcs:text-key-changed'\r\n | 'dcs:section-click'\r\n | 'dcs:section-hover'\r\n | 'dcs:text-key-dblclick'\r\n | 'dcs:contextmenu'\r\n | 'dcs:click'\r\n | 'dcs:navigation'\r\n | 'dcs:array-key-click'\r\n | 'dcs:image-click'\r\n | 'dcs:blog-content-click'\r\n | 'dcs:blog-content-ready'\r\n\r\nfunction isInIframe(): boolean {\r\n try { return globalThis.self !== globalThis.self.parent } catch { return true }\r\n}\r\n\r\nfunction postToParent(type: OutboundMessageType, data: unknown) {\r\n if (!isInIframe()) return\r\n // Use '*' because the portal origin varies between dev and production\r\n // and this script doesn't know the parent origin at injection time.\r\n // Security: messages are validated by the portal's useIframeBridge.\r\n // Origin validated via dcs: prefix check on all messages\r\n globalThis.self.parent.postMessage({ type, data }, '*') // NOSONAR\r\n}\r\n\r\n// ─── Attribute Compatibility ─────────────────────────────────────\r\n\r\n/**\r\n * CSS selector that matches elements with either the legacy `data-text-key`\r\n * attribute or the modern `data-dcs-text` attribute. This ensures the editor\r\n * bridge works with both pre-migration and post-migration sites.\r\n */\r\nconst TEXT_KEY_SELECTOR = '[data-text-key], [data-dcs-text]'\r\n\r\n/**\r\n * Get the text key value from an element, checking the modern `data-dcs-text`\r\n * attribute first, then falling back to the legacy `data-text-key`.\r\n */\r\nfunction getTextKey(el: HTMLElement): string {\r\n return el.dataset.dcsText ?? el.dataset.textKey ?? ''\r\n}\r\n\r\n/**\r\n * Build a CSS selector that matches a specific text key value\r\n * across both legacy and modern attribute names.\r\n */\r\nfunction textKeySelector(key: string): string {\r\n return `[data-text-key=\"${key}\"], [data-dcs-text=\"${key}\"]`\r\n}\r\n\r\n// ─── State ───────────────────────────────────────────────────────\r\n\r\nlet editorActive = false\r\nlet bridgeInitialized = false\r\nlet activeEditElement: HTMLElement | null = null\r\n/** The current pathname tracked for navigation detection */\r\nlet lastKnownPathname: string = ''\r\n/** The floating edit icon element shown on hover over text keys */\r\nlet editIconElement: HTMLElement | null = null\r\n/** The element the edit icon is currently attached to */\r\nlet editIconTarget: HTMLElement | null = null\r\n/** The floating edit icon for array/list text keys */\r\nlet arrayEditIconElement: HTMLElement | null = null\r\n/** The element the array edit icon is currently attached to */\r\nlet arrayEditIconTarget: HTMLElement | null = null\r\n/** Whether editing features are enabled (disabled on unmanaged pages) */\r\nlet editingEnabled = true\r\n/** Original text values captured when inline editing starts, for change detection */\r\nconst originalTextValues = new Map<HTMLElement, string>()\r\n/** ResizeObserver for tracking section layout changes */\r\nlet sectionResizeObserver: ResizeObserver | null = null\r\n/** Debounce timer for re-discovery triggered by layout changes */\r\nlet rediscoveryTimer: ReturnType<typeof setTimeout> | null = null\r\n/** Track elements that have had editor event listeners attached (prevents duplicate listeners) */\r\nconst initializedTextElements = new WeakSet<HTMLElement>()\r\nconst initializedSections = new WeakSet<HTMLElement>()\r\nlet blogContentInitialized = false\r\n\r\n/** Debounced re-discovery — collapses rapid layout changes into one update */\r\nfunction scheduleRediscovery() {\r\n if (rediscoveryTimer) clearTimeout(rediscoveryTimer)\r\n rediscoveryTimer = setTimeout(() => {\r\n rediscoveryTimer = null\r\n rediscoverAndNotify()\r\n }, 300)\r\n}\r\n\r\n// ─── Section Discovery ───────────────────────────────────────────\r\n\r\nfunction discoverSections(): SectionInfo[] {\r\n const elements = document.querySelectorAll<HTMLElement>('[data-section]')\r\n return Array.from(elements).map((el) => {\r\n const rect = el.getBoundingClientRect()\r\n const textKeyCount = el.querySelectorAll(TEXT_KEY_SELECTOR).length\r\n return {\r\n id: el.dataset.section!,\r\n label: el.dataset.sectionLabel ?? null,\r\n bounds: {\r\n x: rect.left + scrollX,\r\n y: rect.top + scrollY,\r\n width: rect.width,\r\n height: rect.height,\r\n },\r\n textKeyCount,\r\n }\r\n })\r\n}\r\n\r\nfunction discoverTextKeys(): string[] {\r\n return Array.from(document.querySelectorAll<HTMLElement>(TEXT_KEY_SELECTOR)).map(\r\n (el) => getTextKey(el),\r\n )\r\n}\r\n\r\n// ─── Layout Change Observation ───────────────────────────────────\r\n\r\n/**\r\n * Watch for layout changes that affect section bounds and overall page height:\r\n * 1. ALL lazy-loaded images on the page — their load events shift subsequent sections\r\n * 2. ResizeObserver on the document body — detects overall page height changes\r\n * 3. ResizeObserver on section elements — detects any size change (font loading,\r\n * dynamic content expansion, CSS transitions, etc.)\r\n *\r\n * When changes are detected, we schedule a debounced re-discovery so the portal\r\n * overlay layer receives updated section bounds and content height that match\r\n * the live layout.\r\n */\r\nfunction observeSectionLayout() {\r\n // Clean up any previous observer (e.g., after SPA navigation)\r\n if (sectionResizeObserver) {\r\n sectionResizeObserver.disconnect()\r\n }\r\n\r\n // 1. Watch ALL lazy images on the page — not just inside sections.\r\n // Images outside sections (e.g., contact form, unmapped content) can still\r\n // push content down and change overall page height and section positions.\r\n const allImages = document.querySelectorAll<HTMLImageElement>('img')\r\n allImages.forEach(img => {\r\n if (!img.complete) {\r\n img.addEventListener('load', scheduleRediscovery, { once: true })\r\n img.addEventListener('error', scheduleRediscovery, { once: true })\r\n }\r\n })\r\n\r\n // 2. ResizeObserver on the document body + each section element.\r\n // Body observation catches overall page height changes from any source.\r\n if (typeof ResizeObserver !== 'undefined') {\r\n sectionResizeObserver = new ResizeObserver(scheduleRediscovery)\r\n // Observe body for overall height changes\r\n sectionResizeObserver.observe(document.body)\r\n // Observe each section for individual bound changes\r\n document.querySelectorAll<HTMLElement>('[data-section]').forEach(el => {\r\n sectionResizeObserver!.observe(el)\r\n })\r\n }\r\n}\r\n\r\n// ─── Section Overlay ─────────────────────────────────────────────\r\n\r\n/**\r\n * Section highlight is now handled entirely by the portal-side\r\n * SectionOverlayLayer rendered on top of the iframe. The bridge\r\n * only reports section bounds and hover events — no visual\r\n * overlays are rendered inside the iframe itself.\r\n */\r\nfunction showSectionHighlight(_sectionId: string) {\r\n // No-op: portal overlay layer handles section highlights\r\n}\r\n\r\nfunction clearSectionHighlight() {\r\n // No-op: portal overlay layer handles section highlights\r\n}\r\n\r\n// ─── Navigation Monitoring ───────────────────────────────────────\r\n\r\n/**\r\n * Monitor navigation within the iframe and notify the parent portal.\r\n * Instead of blocking all navigation, we allow SPA routing and inform\r\n * the portal when the iframe navigates to a different page. The portal\r\n * then updates its own URL and loads editing context for the new page.\r\n *\r\n * Strategy:\r\n * 1. Poll location.pathname on a short interval — this reliably detects\r\n * SPA navigation regardless of which router or framework is used,\r\n * since frameworks may cache History API references before our script loads.\r\n * 2. Block external/cross-origin link clicks (actual page reloads).\r\n * 3. Block form submissions.\r\n * 4. Monitor popstate for back/forward navigation.\r\n * 5. After navigation, re-discover sections and report them to the portal.\r\n */\r\nfunction monitorNavigation() {\r\n lastKnownPathname = globalThis.location.pathname\r\n\r\n // ── Poll for URL changes — reliable with any SPA router ──────\r\n // Frameworks like VitePress/Vue Router may cache history.pushState before\r\n // our bridge loads, so prototype overrides are unreliable. Polling\r\n // location.pathname directly is simple and always works.\r\n setInterval(() => {\r\n checkForNavigation()\r\n }, 250)\r\n\r\n // ── Catch back/forward navigation ────────────────────────────\r\n globalThis.addEventListener('popstate', () => {\r\n checkForNavigation()\r\n })\r\n\r\n // ── Block external link clicks (cross-origin / full page reloads) ──\r\n const handleLinkClick = (e: MouseEvent) => {\r\n const link = (e.target as HTMLElement).closest?.('a[href]') as HTMLAnchorElement | null\r\n if (!link) return\r\n\r\n const href = link.getAttribute('href')\r\n if (!href || href.startsWith('#') || href === 'javascript:void(0)') return\r\n\r\n // Check if this is an external link (different origin)\r\n try {\r\n const resolved = new URL(href, globalThis.location.href)\r\n if (resolved.origin !== globalThis.location.origin) {\r\n // Block external navigation — can't leave the iframe\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n }\r\n // Same-origin links: let the SPA router handle them.\r\n // The History API override above will detect the navigation.\r\n } catch {\r\n // Malformed URL — block it\r\n e.preventDefault()\r\n }\r\n }\r\n\r\n // Register on window first to beat VitePress's capture-phase handler\r\n globalThis.addEventListener('click', handleLinkClick, true)\r\n document.addEventListener('click', handleLinkClick, true)\r\n\r\n // Intercept form submissions\r\n document.addEventListener('submit', (e: Event) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n }, true)\r\n\r\n // Block actual page reloads (location.href = ..., etc.)\r\n window.addEventListener('beforeunload', (e: BeforeUnloadEvent) => {\r\n e.preventDefault()\r\n })\r\n}\r\n\r\n/**\r\n * Check if the iframe pathname has changed and notify the portal.\r\n * Also re-initializes the editor bridge for the new page.\r\n */\r\nfunction checkForNavigation() {\r\n const currentPathname = globalThis.location.pathname\r\n if (currentPathname === lastKnownPathname) return\r\n\r\n lastKnownPathname = currentPathname\r\n\r\n // Notify parent about the navigation\r\n postToParent('dcs:navigation', { pathname: currentPathname })\r\n\r\n // Clean up current edit state\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n\r\n // Wait for the SPA router to finish rendering the new page,\r\n // then re-discover sections and text keys\r\n setTimeout(() => {\r\n rediscoverAndNotify()\r\n }, 500)\r\n}\r\n\r\n// ─── Mode Management ─────────────────────────────────────────────\r\n\r\n/**\r\n * Set the current interaction mode. Both modes allow normal browsing —\r\n * inline editing works via double-click regardless of mode.\r\n * Switching to explore finishes any active inline edit.\r\n */\r\nfunction setMode(mode: 'edit' | 'explore') {\r\n if (mode === 'explore') {\r\n // When explicitly switching to explore, finish any active inline edit\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n }\r\n}\r\n\r\n// ─── Inline Text Editing ─────────────────────────────────────────\r\n\r\n/**\r\n * Re-discover sections and text keys after an HMR update and\r\n * notify the parent portal so the overlay layer updates.\r\n */\r\nfunction rediscoverAndNotify() {\r\n activeEditElement = null\r\n\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n\r\n // Skip re-report if height hasn't meaningfully changed (< 2px drift)\r\n // AND sections/textKeys are the same count — avoids portal re-render churn.\r\n if (Math.abs(contentHeight - lastReportedHeight) <= 2) {\r\n // Height is stable — still report in case sections/textKeys changed\r\n }\r\n lastReportedHeight = contentHeight\r\n\r\n // Detect blog content element\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n const hasBlogContent = !!blogEl\r\n const blogContentHtml = blogEl ? blogEl.innerHTML : null\r\n\r\n // Always report — sections/textKeys may have changed even if height is stable\r\n postToParent('dcs:ready', { sections, textKeys, contentHeight, hasBlogContent, blogContentHtml } satisfies EditorReadyPayload)\r\n\r\n // Apply editor features to any new elements (e.g., after HMR or navigation)\r\n // without re-creating the ResizeObserver (which would cause an infinite loop)\r\n if (editorActive) {\r\n applyEditorToElements()\r\n\r\n // Observe any new section elements that appeared after navigation/HMR.\r\n // ResizeObserver.observe() is idempotent — safe to call on already-observed elements.\r\n if (sectionResizeObserver) {\r\n document.querySelectorAll<HTMLElement>('[data-section]').forEach(el => {\r\n sectionResizeObserver!.observe(el)\r\n })\r\n }\r\n }\r\n}\r\n\r\nfunction enableEditorMode() {\r\n if (editorActive) return\r\n editorActive = true\r\n\r\n // Inject editor styles\r\n injectEditorStyles()\r\n\r\n // Apply editor features (classes + listeners) to all current elements\r\n applyEditorToElements()\r\n}\r\n\r\n/**\r\n * Apply editor features (classes + event listeners) to elements on the page.\r\n * Uses WeakSets to track which elements have already been initialized,\r\n * preventing duplicate event listeners on repeated calls (e.g., after HMR\r\n * updates or layout-triggered re-discovery).\r\n */\r\nfunction applyEditorToElements() {\r\n // Set up all text key elements — double-click to start editing\r\n const textElements = document.querySelectorAll<HTMLElement>(TEXT_KEY_SELECTOR)\r\n textElements.forEach((htmlEl) => {\r\n // Always ensure the class is present (may have been removed by set-editing-enabled)\r\n htmlEl.classList.add('dcs-editable')\r\n\r\n // Only add event listeners once per element\r\n if (initializedTextElements.has(htmlEl)) return\r\n initializedTextElements.add(htmlEl)\r\n\r\n // Double-click to start inline editing (signals parent to switch to edit mode)\r\n htmlEl.addEventListener('dblclick', handleTextKeyDblClick)\r\n // Blur to finish editing\r\n htmlEl.addEventListener('blur', handleTextKeyBlur)\r\n // Keyboard: Enter to finish, Escape to cancel\r\n htmlEl.addEventListener('keydown', handleTextKeyKeydown)\r\n\r\n // Show/hide the floating edit icon on hover (T icon for regular text, List icon for array keys)\r\n htmlEl.addEventListener('mouseenter', () => {\r\n // Don't show icon while actively editing this or another element\r\n if (activeEditElement) return\r\n const key = getTextKey(htmlEl)\r\n if (isArrayTextKey(key)) {\r\n showArrayEditIcon(htmlEl)\r\n } else {\r\n showEditIcon(htmlEl)\r\n }\r\n })\r\n htmlEl.addEventListener('mouseleave', (e: MouseEvent) => {\r\n // Don't hide if moving to the edit icon or array edit icon\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && (\r\n related.classList?.contains('dcs-edit-icon') || related.closest?.('.dcs-edit-icon') ||\r\n related.classList?.contains('dcs-array-edit-icon') || related.closest?.('.dcs-array-edit-icon')\r\n )) return\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n })\r\n })\r\n\r\n // Set up section hover reporting (portal overlay handles the visual treatment)\r\n const sections = document.querySelectorAll<HTMLElement>('[data-section]')\r\n sections.forEach((el) => {\r\n if (initializedSections.has(el)) return\r\n initializedSections.add(el)\r\n\r\n const sectionId = el.dataset.section!\r\n\r\n el.addEventListener('mouseenter', () => {\r\n postToParent('dcs:section-hover', { sectionId })\r\n })\r\n el.addEventListener('mouseleave', () => {\r\n postToParent('dcs:section-hover', { sectionId: null })\r\n })\r\n el.addEventListener('click', (e) => {\r\n // Only fire section click if not clicking on a text-key element\r\n const target = e.target as HTMLElement\r\n if (!target.closest(TEXT_KEY_SELECTOR)) {\r\n postToParent('dcs:section-click', { sectionId })\r\n }\r\n })\r\n })\r\n\r\n // Set up blog content area — single click to open blog content editor\r\n if (!blogContentInitialized) {\r\n const blogContentEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n if (blogContentEl) {\r\n blogContentInitialized = true\r\n blogContentEl.classList.add('dcs-blog-content')\r\n blogContentEl.addEventListener('click', (e) => {\r\n // Don't trigger blog content click if clicking on a text-key element\r\n const target = e.target as HTMLElement\r\n if (target.closest(TEXT_KEY_SELECTOR)) return\r\n e.preventDefault()\r\n e.stopPropagation()\r\n postToParent('dcs:blog-content-click', { textKey: 'blog-content' })\r\n })\r\n // Also send the current content to the parent for initial load\r\n postToParent('dcs:blog-content-ready', { content: blogContentEl.innerHTML })\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Handle double-click on a text key element.\r\n * Notifies the parent portal to switch to edit mode and begins inline editing.\r\n */\r\nfunction handleTextKeyDblClick(e: Event) {\r\n if (!editingEnabled) return\r\n\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n const el = e.currentTarget as HTMLElement\r\n const key = getTextKey(el)\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Always notify the parent to switch to edit mode when starting inline editing.\r\n // In explore mode this triggers the mode switch; in edit mode it's a no-op on the parent side.\r\n postToParent('dcs:text-key-dblclick', { key, sectionId })\r\n\r\n // Start inline editing\r\n startInlineEdit(el, key, sectionId)\r\n}\r\n\r\n/**\r\n * Start inline contenteditable editing on a text key element.\r\n * Called by double-click or when the parent sends dcs:select-text-key.\r\n */\r\nfunction startInlineEdit(el: HTMLElement, key: string, sectionId: string | null) {\r\n // Finish any active edit first\r\n if (activeEditElement && activeEditElement !== el) {\r\n finishEdit(activeEditElement)\r\n }\r\n\r\n // Store original text for change detection on finish\r\n originalTextValues.set(el, el.innerText.trim())\r\n\r\n // Enable contenteditable\r\n el.setAttribute('contenteditable', 'true')\r\n el.classList.add('dcs-editing')\r\n el.focus()\r\n activeEditElement = el\r\n\r\n // Select all text for easy replacement\r\n const selection = getSelection()\r\n const range = document.createRange()\r\n range.selectNodeContents(el)\r\n selection?.removeAllRanges()\r\n selection?.addRange(range)\r\n\r\n postToParent('dcs:text-key-click', { key, sectionId })\r\n}\r\n\r\nfunction handleTextKeyBlur(e: Event) {\r\n const el = e.currentTarget as HTMLElement\r\n finishEdit(el)\r\n}\r\n\r\nfunction handleTextKeyKeydown(e: KeyboardEvent) {\r\n if (e.key === 'Enter' && !e.shiftKey) {\r\n e.preventDefault()\r\n ;(e.currentTarget as HTMLElement).blur()\r\n }\r\n if (e.key === 'Escape') {\r\n e.preventDefault()\r\n ;(e.currentTarget as HTMLElement).blur()\r\n }\r\n}\r\n\r\nfunction finishEdit(el: HTMLElement) {\r\n if (!el.hasAttribute('contenteditable')) return\r\n\r\n el.removeAttribute('contenteditable')\r\n el.classList.remove('dcs-editing')\r\n\r\n const key = getTextKey(el)\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n const newValue = el.innerText.trim()\r\n const originalValue = originalTextValues.get(el) ?? ''\r\n\r\n if (activeEditElement === el) {\r\n activeEditElement = null\r\n }\r\n\r\n // Only report to the portal when text was actually modified\r\n if (newValue !== originalValue) {\r\n postToParent('dcs:text-key-changed', { key, value: newValue, sectionId })\r\n }\r\n\r\n originalTextValues.delete(el)\r\n}\r\n\r\nfunction focusTextKey(key: string) {\r\n const el = document.querySelector<HTMLElement>(textKeySelector(key))\r\n if (!el) return\r\n el.scrollIntoView({ behavior: 'smooth', block: 'center' })\r\n\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Start inline editing directly\r\n startInlineEdit(el, key, sectionId)\r\n}\r\n\r\nfunction updateTextInPlace(key: string, value: string) {\r\n const el = document.querySelector<HTMLElement>(textKeySelector(key))\r\n if (!el) return\r\n // Don't update if we're currently editing this element\r\n if (el === activeEditElement) return\r\n el.innerText = value\r\n}\r\n\r\n// ─── Edit Icon (Floating Text Cursor Button) ────────────────────\r\n\r\n/**\r\n * Create the floating edit icon element. This small button appears\r\n * in the top-left corner of a hovered [data-text-key] or [data-dcs-text] element,\r\n * providing a click target to start inline editing without triggering\r\n * navigation (important for text inside buttons/links).\r\n */\r\nfunction createEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Edit text')\r\n icon.setAttribute('aria-label', 'Edit text inline')\r\n // SVG: Type icon (text cursor) — matches lucide \"Type\" icon\r\n icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"/><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"/></svg>`\r\n\r\n icon.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n if (!editIconTarget) return\r\n const el = editIconTarget\r\n const key = getTextKey(el)\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Signal parent to switch to edit mode\r\n postToParent('dcs:text-key-dblclick', { key, sectionId })\r\n // Start inline editing\r\n startInlineEdit(el, key, sectionId)\r\n // Hide the icon while editing\r\n removeEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the target element)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && editIconTarget && (related === editIconTarget || editIconTarget.contains(related))) return\r\n removeEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the edit icon positioned at the top-left corner of the given element.\r\n */\r\nfunction showEditIcon(el: HTMLElement) {\r\n if (!editingEnabled) return\r\n\r\n if (!editIconElement) {\r\n editIconElement = createEditIcon()\r\n document.body.appendChild(editIconElement)\r\n }\r\n\r\n editIconTarget = el\r\n\r\n // Position relative to the element's bounding box\r\n const rect = el.getBoundingClientRect()\r\n editIconElement.style.top = `${rect.top + scrollY - 4}px`\r\n editIconElement.style.left = `${rect.left + scrollX - 4}px`\r\n editIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the edit icon.\r\n */\r\nfunction removeEditIcon() {\r\n if (editIconElement) {\r\n editIconElement.style.display = 'none'\r\n }\r\n editIconTarget = null\r\n}\r\n\r\n// ─── Array Key Detection ─────────────────────────────────────────\r\n\r\n/**\r\n * Check if a text key is part of an array pattern (e.g., \"conditions.0.title\").\r\n * Array text keys contain an indexed segment — either:\r\n * - Pure numeric: `baseKey.N.field` (e.g., `conditions.0.title`)\r\n * - Hyphenated index: `baseKey.prefix-N.field` (e.g., `steps.step-0.title`)\r\n */\r\nfunction isArrayTextKey(key: string): boolean {\r\n // Match pure numeric segment (.0.) or hyphenated index (.prefix-0.)\r\n return /\\.\\d+\\./.test(key) || /\\.\\w+-\\d+\\./.test(key)\r\n}\r\n\r\n/**\r\n * Extract the base array key from an array text key.\r\n * - `conditions.0.title` → `conditions`\r\n * - `steps.step-0.title` → `steps.step`\r\n */\r\nfunction getArrayBaseKey(key: string): string | null {\r\n // Try hyphenated pattern first (more specific)\r\n const hyphenMatch = key.match(/^(.+?)-\\d+\\./)\r\n if (hyphenMatch) return hyphenMatch[1]\r\n // Fall back to pure numeric pattern\r\n const numMatch = key.match(/^(.+?)\\.\\d+\\./)\r\n return numMatch ? numMatch[1] : null\r\n}\r\n\r\n// ─── Array Edit Icon (Floating List Button) ──────────────────────\r\n\r\n/**\r\n * Create the floating array edit icon element. This small button appears\r\n * in the top-left corner of a hovered [data-text-key] or [data-dcs-text] element whose key\r\n * matches an array pattern. Clicking it sends dcs:array-key-click to the\r\n * portal to open the array editor sheet.\r\n */\r\nfunction createArrayEditIcon(): HTMLElement {\r\n const icon = document.createElement('button')\r\n icon.className = 'dcs-array-edit-icon'\r\n icon.setAttribute('type', 'button')\r\n icon.setAttribute('title', 'Edit list items')\r\n icon.setAttribute('aria-label', 'Edit list items in panel')\r\n // SVG: List icon — matches lucide \"List\" icon\r\n icon.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>`\r\n\r\n icon.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n e.stopImmediatePropagation()\r\n\r\n if (!arrayEditIconTarget) return\r\n const el = arrayEditIconTarget\r\n const key = getTextKey(el)\r\n const arrayKey = getArrayBaseKey(key)\r\n if (!arrayKey) return\r\n\r\n const sectionParent = el.closest<HTMLElement>('[data-section]')\r\n const sectionId = sectionParent?.dataset.section ?? null\r\n\r\n // Signal parent to open array editor sheet\r\n postToParent('dcs:array-key-click', { arrayKey, sectionId })\r\n // Hide the icon\r\n removeArrayEditIcon()\r\n }, true)\r\n\r\n // Hide icon when mouse leaves it (and isn't going to the target element)\r\n icon.addEventListener('mouseleave', (e: MouseEvent) => {\r\n const related = e.relatedTarget as HTMLElement | null\r\n if (related && arrayEditIconTarget && (related === arrayEditIconTarget || arrayEditIconTarget.contains(related))) return\r\n removeArrayEditIcon()\r\n })\r\n\r\n return icon\r\n}\r\n\r\n/**\r\n * Show the array edit icon positioned at the top-left corner of the given element.\r\n */\r\nfunction showArrayEditIcon(el: HTMLElement) {\r\n if (!editingEnabled) return\r\n\r\n if (!arrayEditIconElement) {\r\n arrayEditIconElement = createArrayEditIcon()\r\n document.body.appendChild(arrayEditIconElement)\r\n }\r\n\r\n arrayEditIconTarget = el\r\n\r\n // Position relative to the element's bounding box\r\n const rect = el.getBoundingClientRect()\r\n arrayEditIconElement.style.top = `${rect.top + scrollY - 4}px`\r\n arrayEditIconElement.style.left = `${rect.left + scrollX - 4}px`\r\n arrayEditIconElement.style.display = 'flex'\r\n}\r\n\r\n/**\r\n * Hide and detach the array edit icon.\r\n */\r\nfunction removeArrayEditIcon() {\r\n if (arrayEditIconElement) {\r\n arrayEditIconElement.style.display = 'none'\r\n }\r\n arrayEditIconTarget = null\r\n}\r\n\r\nfunction injectEditorStyles() {\r\n if (document.getElementById('dcs-editor-styles')) return\r\n\r\n const style = document.createElement('style')\r\n style.id = 'dcs-editor-styles'\r\n style.textContent = `\r\n [data-text-key].dcs-editable,\r\n [data-dcs-text].dcs-editable {\r\n cursor: text !important;\r\n border-radius: 4px;\r\n position: relative;\r\n }\r\n\r\n [data-text-key].dcs-editable:hover,\r\n [data-dcs-text].dcs-editable:hover {\r\n outline: 2px dashed hsl(221.2 83.2% 53.3% / 0.4) !important;\r\n outline-offset: 4px;\r\n background-color: hsl(221.2 83.2% 53.3% / 0.03);\r\n }\r\n\r\n [data-text-key].dcs-editing,\r\n [data-dcs-text].dcs-editing {\r\n outline: 2px solid hsl(221.2 83.2% 53.3%) !important;\r\n outline-offset: 4px;\r\n background-color: hsl(221.2 83.2% 53.3% / 0.06);\r\n min-height: 1em;\r\n }\r\n\r\n [data-text-key].dcs-editing:focus,\r\n [data-dcs-text].dcs-editing:focus {\r\n outline: 2px solid hsl(221.2 83.2% 53.3%) !important;\r\n outline-offset: 4px;\r\n }\r\n\r\n .dcs-section-highlight {\r\n pointer-events: none;\r\n }\r\n\r\n /* Floating edit icon button */\r\n .dcs-edit-icon {\r\n position: absolute;\r\n z-index: 99999;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 0;\r\n margin: 0;\r\n border: 1.5px solid hsl(221.2 83.2% 53.3%);\r\n border-radius: 4px;\r\n background: hsl(221.2 83.2% 53.3% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(221.2 83.2% 53.3%);\r\n cursor: pointer;\r\n pointer-events: auto;\r\n transition: background 0.15s, transform 0.1s;\r\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n }\r\n\r\n .dcs-edit-icon:hover {\r\n background: hsl(221.2 83.2% 53.3% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-edit-icon svg {\r\n display: block;\r\n }\r\n\r\n /* Floating array edit icon button */\r\n .dcs-array-edit-icon {\r\n position: absolute;\r\n z-index: 99999;\r\n display: none;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 0;\r\n margin: 0;\r\n border: 1.5px solid hsl(271 91% 65%);\r\n border-radius: 4px;\r\n background: hsl(271 91% 65% / 0.1);\r\n backdrop-filter: blur(4px);\r\n color: hsl(271 91% 65%);\r\n cursor: pointer;\r\n pointer-events: auto;\r\n transition: background 0.15s, transform 0.1s;\r\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n }\r\n\r\n .dcs-array-edit-icon:hover {\r\n background: hsl(271 91% 65% / 0.25);\r\n transform: scale(1.1);\r\n }\r\n\r\n .dcs-array-edit-icon svg {\r\n display: block;\r\n }\r\n\r\n /* Blog content area — click to open WYSIWYG editor */\r\n [data-blog-content].dcs-blog-content {\r\n cursor: pointer;\r\n border-radius: 8px;\r\n position: relative;\r\n transition: outline-color 0.2s, background-color 0.2s;\r\n }\r\n\r\n [data-blog-content].dcs-blog-content:hover {\r\n outline: 2px dashed hsl(142 71% 45% / 0.5) !important;\r\n outline-offset: 8px;\r\n background-color: hsl(142 71% 45% / 0.03);\r\n }\r\n\r\n [data-blog-content].dcs-blog-content::after {\r\n content: 'Click to edit blog content';\r\n position: absolute;\r\n top: -28px;\r\n left: 8px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n color: hsl(142 71% 45%);\r\n background: hsl(142 71% 45% / 0.1);\r\n border: 1px solid hsl(142 71% 45% / 0.3);\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n opacity: 0;\r\n transition: opacity 0.2s;\r\n pointer-events: none;\r\n white-space: nowrap;\r\n }\r\n\r\n [data-blog-content].dcs-blog-content:hover::after {\r\n opacity: 1;\r\n }\r\n `\r\n document.head.appendChild(style)\r\n}\r\n\r\n// ─── Inbound Message Handler ─────────────────────────────────────\r\n\r\nfunction handleMessage(event: MessageEvent) {\r\n // Validate message structure (origin is not checked because\r\n // the parent portal origin varies between dev and production)\r\n const msg = event.data\r\n if (!msg || typeof msg !== 'object' || typeof msg.type !== 'string') return\r\n if (!msg.type.startsWith('dcs:')) return\r\n\r\n const { type, data } = msg as { type: InboundMessageType; data: unknown }\r\n\r\n switch (type) {\r\n case 'dcs:init-editor':\r\n enableEditorMode()\r\n break\r\n\r\n case 'dcs:highlight-section':\r\n if (data && typeof data === 'object' && 'sectionId' in data) {\r\n showSectionHighlight((data as { sectionId: string }).sectionId)\r\n }\r\n break\r\n\r\n case 'dcs:clear-highlight':\r\n clearSectionHighlight()\r\n break\r\n\r\n case 'dcs:select-text-key':\r\n if (data && typeof data === 'object' && 'key' in data) {\r\n focusTextKey((data as { key: string }).key)\r\n }\r\n break\r\n\r\n case 'dcs:update-text':\r\n if (data && typeof data === 'object' && 'key' in data && 'value' in data) {\r\n const d = data as { key: string; value: string }\r\n updateTextInPlace(d.key, d.value)\r\n }\r\n break\r\n\r\n case 'dcs:set-mode':\r\n if (data && typeof data === 'object' && 'mode' in data) {\r\n setMode((data as { mode: 'edit' | 'explore' }).mode)\r\n }\r\n break\r\n\r\n case 'dcs:set-editing-enabled':\r\n if (data && typeof data === 'object' && 'enabled' in data) {\r\n editingEnabled = (data as { enabled: boolean }).enabled\r\n if (editingEnabled) {\r\n // Re-add dcs-editable class to text key elements\r\n document.querySelectorAll<HTMLElement>(TEXT_KEY_SELECTOR).forEach(el => {\r\n el.classList.add('dcs-editable')\r\n })\r\n } else {\r\n // Clean up: finish any active edit, remove edit icon, remove hover styles\r\n if (activeEditElement) {\r\n finishEdit(activeEditElement)\r\n }\r\n removeEditIcon()\r\n removeArrayEditIcon()\r\n // Remove dcs-editable class to disable hover outlines\r\n document.querySelectorAll<HTMLElement>('.dcs-editable').forEach(el => {\r\n el.classList.remove('dcs-editable')\r\n })\r\n }\r\n }\r\n break\r\n\r\n case 'dcs:update-blog-content':\r\n if (data && typeof data === 'object' && 'content' in data) {\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n if (blogEl) {\r\n blogEl.innerHTML = (data as { content: string }).content\r\n // Trigger re-discovery so the portal overlay updates for any height changes\r\n scheduleRediscovery()\r\n }\r\n }\r\n break\r\n }\r\n}\r\n\r\n/**\r\n * Inject a stylesheet that neutralizes viewport-relative min-heights.\r\n * In the visual editor iframe, the portal controls height from body.scrollHeight.\r\n * CSS rules like `min-height: 100vh` create a feedback loop:\r\n * iframe height → vh grows → body.scrollHeight grows → iframe height grows → …\r\n * Neutralizing these rules lets the content determine its natural height.\r\n */\r\nfunction injectEditorHeightFix() {\r\n if (document.getElementById('dcs-editor-height-fix')) return\r\n const style = document.createElement('style')\r\n style.id = 'dcs-editor-height-fix'\r\n style.textContent = `\r\n /* DCS Editor Bridge: Neutralize viewport-relative min-heights */\r\n .min-h-screen, .min-h-dvh, .min-h-svh,\r\n .min-h-\\\\[100vh\\\\], .min-h-\\\\[100dvh\\\\], .min-h-\\\\[100svh\\\\] {\r\n min-height: 0 !important;\r\n }\r\n `\r\n document.head.appendChild(style)\r\n}\r\n\r\n/** Last reported content height — used to avoid re-sending when stable */\r\nlet lastReportedHeight = 0\r\n\r\n/**\r\n * Measure the true content height, avoiding inflation from viewport-relative\r\n * CSS rules. Uses section bounds as the primary signal (immune to vh feedback),\r\n * falling back to body.scrollHeight (which is reliable once the editor height\r\n * fix stylesheet has been injected).\r\n */\r\nfunction measureContentHeight(sections: SectionInfo[]): number {\r\n // Primary: compute from section bounds — these reflect actual element positions\r\n // after the height-fix stylesheet has neutralized viewport-relative min-heights.\r\n let maxBottom = 0\r\n for (const s of sections) {\r\n const bottom = s.bounds.y + s.bounds.height\r\n if (bottom > maxBottom) maxBottom = bottom\r\n }\r\n const sectionHeight = maxBottom > 0 ? Math.ceil(maxBottom) : 0\r\n\r\n // Fallback: body.scrollHeight (reliable now that min-height: 100vh is neutralized)\r\n const bodyHeight = document.body.scrollHeight\r\n\r\n // Use whichever is larger — sections might miss elements without data-section,\r\n // but bodyHeight should be accurate with the injected CSS fix.\r\n return Math.max(sectionHeight, bodyHeight)\r\n}\r\n\r\nexport function initEditorBridge() {\r\n if (!isInIframe()) return // Not in iframe\r\n\r\n // Inject height fix CSS before any measurement\r\n injectEditorHeightFix()\r\n\r\n // Prevent duplicate listener registration on HMR re-execution\r\n if (!bridgeInitialized) {\r\n bridgeInitialized = true\r\n\r\n // Monitor navigation within the iframe — notify the portal of page changes\r\n monitorNavigation()\r\n\r\n // Forward right-click events to the portal so the context menu\r\n // works when right-clicking inside the iframe (cross-origin boundary\r\n // prevents the parent from receiving native contextmenu events).\r\n document.addEventListener('contextmenu', (e: MouseEvent) => {\r\n e.preventDefault()\r\n const target = e.target as HTMLElement\r\n const sectionEl = target.closest?.('[data-section]') as HTMLElement | null\r\n\r\n // Detect if the right-click target is an image or inside a <picture> element\r\n const imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n\r\n postToParent('dcs:contextmenu', {\r\n x: e.clientX,\r\n y: e.clientY,\r\n sectionId: sectionEl?.dataset.section ?? null,\r\n sectionLabel: sectionEl?.dataset.sectionLabel ?? null,\r\n // Image context (null when not right-clicking on an image)\r\n imageUrl: imgEl?.currentSrc ?? imgEl?.src ?? null,\r\n imageAlt: imgEl?.alt ?? null,\r\n imageWidth: imgEl?.naturalWidth ?? null,\r\n imageHeight: imgEl?.naturalHeight ?? null,\r\n })\r\n }, true)\r\n\r\n // Forward click events to the portal so it can dismiss context menus\r\n // when the user clicks inside the iframe. Also detect image clicks\r\n // in edit mode to enable direct image management.\r\n document.addEventListener('click', (e: MouseEvent) => {\r\n postToParent('dcs:click', {})\r\n\r\n // In edit mode, detect image clicks for image management\r\n if (editorActive && editingEnabled) {\r\n const target = e.target as HTMLElement\r\n const imgEl = target.tagName === 'IMG'\r\n ? target as HTMLImageElement\r\n : (target.closest?.('picture')?.querySelector('img') as HTMLImageElement | null)\r\n if (imgEl) {\r\n const sectionEl = target.closest?.('[data-section]') as HTMLElement | null\r\n postToParent('dcs:image-click', {\r\n imageUrl: imgEl.currentSrc ?? imgEl.src,\r\n imageAlt: imgEl.alt ?? null,\r\n imageWidth: imgEl.naturalWidth,\r\n imageHeight: imgEl.naturalHeight,\r\n sectionId: sectionEl?.dataset.section ?? null,\r\n sectionLabel: sectionEl?.dataset.sectionLabel ?? null,\r\n })\r\n }\r\n }\r\n }, true)\r\n\r\n // Listen for portal commands. Origin is not validated because\r\n // the portal origin varies between environments and all inbound\r\n // messages are type-checked before processing.\r\n // Origin cannot be pre-validated because the portal URL varies by environment.\r\n // All inbound messages are type-checked via the dcs: prefix guard.\r\n globalThis.self.addEventListener('message', handleMessage) // NOSONAR\r\n\r\n // Watch for VitePress / Vite HMR page updates. When the framework\r\n // replaces DOM content, our event listeners and AI buttons are lost.\r\n // Re-discover sections after each update so the portal overlay stays in sync.\r\n if (import.meta.hot) {\r\n import.meta.hot.on('vite:afterUpdate', () => {\r\n // Small delay for the framework to finish DOM updates\r\n setTimeout(rediscoverAndNotify, 300)\r\n })\r\n }\r\n }\r\n\r\n // Report available sections, text keys, and content height\r\n const sections = discoverSections()\r\n const textKeys = discoverTextKeys()\r\n const contentHeight = measureContentHeight(sections)\r\n lastReportedHeight = contentHeight\r\n\r\n // Detect blog content element for the initial ready report\r\n const blogEl = document.querySelector<HTMLElement>('[data-blog-content]')\r\n\r\n postToParent('dcs:ready', {\r\n sections,\r\n textKeys,\r\n contentHeight,\r\n hasBlogContent: !!blogEl,\r\n blogContentHtml: blogEl ? blogEl.innerHTML : null,\r\n } satisfies EditorReadyPayload)\r\n\r\n // Observe layout changes (lazy images, dynamic content) and re-report bounds\r\n observeSectionLayout()\r\n}\r\n\r\n// NOTE: Auto-initialization is NOT done here. The Vite plugin's virtual module\r\n// (dcsEditorPlugin) handles initialization by importing and calling initEditorBridge().\r\n// Having auto-init as a side effect of the import caused double-initialization\r\n// (two dcs:ready messages on startup).\r\n"]}
package/dist/index.d.ts CHANGED
@@ -356,6 +356,8 @@ interface HeadOverrides {
356
356
  title?: string;
357
357
  /** Override description */
358
358
  description?: string;
359
+ /** Override keywords meta tag */
360
+ keywords?: string;
359
361
  /** Additional or replacement schemas */
360
362
  schemas?: object[];
361
363
  /** Additional meta tags */
@@ -170,8 +170,14 @@ function dcsEditorPlugin(options = {}) {
170
170
  const { debug = false } = options;
171
171
  const VIRTUAL_PATH = "/__dcs-editor-bridge.js";
172
172
  const RIBBON_VIRTUAL_PATH = "/__dcs-preview-ribbon.js";
173
+ let isDev = false;
174
+ let basePath = "/";
173
175
  return {
174
176
  name: "dcs-editor-bridge",
177
+ configResolved(config) {
178
+ isDev = config.command === "serve";
179
+ basePath = config.base ?? "/";
180
+ },
175
181
  resolveId(id) {
176
182
  if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {
177
183
  return id;
@@ -214,15 +220,34 @@ if (document.readyState === 'loading') {
214
220
  `;
215
221
  }
216
222
  },
217
- transformIndexHtml(html) {
218
- if (debug) {
219
- console.log("[dcs-editor] Injecting editor bridge + preview ribbon script tags");
220
- }
221
- const editorTag = `<script type="module" src="${VIRTUAL_PATH}"></script>`;
222
- const ribbonTag = `<script type="module" src="${RIBBON_VIRTUAL_PATH}"></script>`;
223
- return html.replace("</body>", `${editorTag}
223
+ /**
224
+ * Only inject the editor bridge and ribbon scripts when:
225
+ * 1. Running in dev mode (Vite dev server), AND
226
+ * 2. The app is served at the root path (base === '/')
227
+ *
228
+ * These are virtual modules resolved via absolute paths (e.g.
229
+ * `/__dcs-editor-bridge.js`). When base !== '/', the app is served at a
230
+ * sub-path (e.g. /handyman-bryan/) and the absolute virtual paths are NOT
231
+ * routed to the Vite dev server by the reverse proxy — they hit the main
232
+ * DCS server instead and get redirected to portal.duffcloudservices.com
233
+ * (no CORS headers), causing browser console errors.
234
+ *
235
+ * In production builds, App.vue imports and mounts PreviewRibbon
236
+ * directly, so virtual script injection is always redundant anyway.
237
+ */
238
+ transformIndexHtml: {
239
+ order: "pre",
240
+ handler(html) {
241
+ if (!isDev || basePath !== "/") return html;
242
+ if (debug) {
243
+ console.log("[dcs-editor] Injecting editor bridge + preview ribbon script tags");
244
+ }
245
+ const editorTag = `<script type="module" src="${VIRTUAL_PATH}"></script>`;
246
+ const ribbonTag = `<script type="module" src="${RIBBON_VIRTUAL_PATH}"></script>`;
247
+ return html.replace("</body>", `${editorTag}
224
248
  ${ribbonTag}
225
249
  </body>`);
250
+ }
226
251
  }
227
252
  };
228
253
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/dcsContentPlugin.ts","../../src/plugins/dcsSeoPlugin.ts","../../src/plugins/dcsEditorPlugin.ts","../../src/plugins/dcsPreviewPlugin.ts","../../src/plugins/dcsCdnImagePlugin.ts","../../src/plugins/responsiveImagePlugin.ts"],"names":["fs","path","yaml","escapeHtml"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * DCS Content Plugin for Vite\r\n *\r\n * Reads `.dcs/content.yaml` at build time and injects content\r\n * as `__DCS_CONTENT__` global variable for use by useTextContent.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsContentPlugin({ debug: true })\r\n * ]\r\n * })\r\n * ```\r\n *\r\n * For VitePress:\r\n * ```typescript\r\n * // .vitepress/config.ts\r\n * import { defineConfig } from 'vitepress'\r\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsContentPlugin()\r\n * ]\r\n * }\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport yaml from 'js-yaml'\r\nimport type { Plugin, ResolvedConfig } from 'vite'\r\nimport type { DcsContentFile } from '../types/content'\r\n\r\nexport interface DcsContentPluginOptions {\r\n /** Path to content.yaml relative to project root (default: '.dcs/content.yaml') */\r\n contentPath?: string\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects .dcs/content.yaml at build time.\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function dcsContentPlugin(options: DcsContentPluginOptions = {}): Plugin {\r\n const { contentPath = '.dcs/content.yaml', debug = false } = options\r\n\r\n let resolvedConfig: ResolvedConfig\r\n\r\n return {\r\n name: 'dcs-content',\r\n\r\n configResolved(config) {\r\n resolvedConfig = config\r\n },\r\n\r\n config(config) {\r\n const projectRoot = config.root || process.cwd()\r\n\r\n // Try to find content.yaml in multiple locations\r\n // VitePress projects have the docs folder as root, so check parent too\r\n const possiblePaths = [\r\n path.resolve(projectRoot, contentPath),\r\n path.resolve(projectRoot, '..', contentPath),\r\n path.resolve(process.cwd(), contentPath),\r\n ]\r\n\r\n let foundPath: string | undefined\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n foundPath = testPath\r\n break\r\n }\r\n }\r\n\r\n if (!foundPath) {\r\n if (debug) {\r\n console.log('[dcs-content] No content.yaml found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n console.log('[dcs-content] Using defaults only')\r\n }\r\n return {\r\n define: {\r\n __DCS_CONTENT__: 'undefined',\r\n },\r\n }\r\n }\r\n\r\n try {\r\n const fileContent = fs.readFileSync(foundPath, 'utf8')\r\n const content = yaml.load(fileContent) as DcsContentFile\r\n\r\n if (debug) {\r\n console.log(`[dcs-content] Loaded ${foundPath}`)\r\n console.log(`[dcs-content] Version: ${content.version}`)\r\n console.log(`[dcs-content] Pages: ${Object.keys(content.pages ?? {}).join(', ') || '(none)'}`)\r\n console.log(`[dcs-content] Global keys: ${Object.keys(content.global ?? {}).length}`)\r\n }\r\n\r\n return {\r\n define: {\r\n __DCS_CONTENT__: JSON.stringify(content),\r\n },\r\n }\r\n } catch (error) {\r\n console.warn('[dcs-content] Failed to parse content.yaml:', error)\r\n return {\r\n define: {\r\n __DCS_CONTENT__: 'undefined',\r\n },\r\n }\r\n }\r\n },\r\n\r\n // Watch for changes in development\r\n configureServer(server) {\r\n const projectRoot = resolvedConfig?.root || process.cwd()\r\n\r\n const watchPaths = [\r\n path.resolve(projectRoot, contentPath),\r\n path.resolve(projectRoot, '..', contentPath),\r\n ]\r\n\r\n watchPaths.forEach((watchPath) => {\r\n if (fs.existsSync(watchPath)) {\r\n server.watcher.add(watchPath)\r\n\r\n server.watcher.on('change', (changedPath) => {\r\n if (changedPath === watchPath) {\r\n if (debug) {\r\n console.log('[dcs-content] content.yaml changed, triggering reload')\r\n }\r\n server.restart()\r\n }\r\n })\r\n }\r\n })\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS SEO Plugin for Vite\r\n *\r\n * Reads `.dcs/seo.yaml` at build time and injects content\r\n * as `__DCS_SEO__` global variable for use by useSEO.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsSeoPlugin({ debug: true })\r\n * ]\r\n * })\r\n * ```\r\n *\r\n * For VitePress:\r\n * ```typescript\r\n * // .vitepress/config.ts\r\n * import { defineConfig } from 'vitepress'\r\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsSeoPlugin()\r\n * ]\r\n * }\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport yaml from 'js-yaml'\r\nimport type { Plugin, ResolvedConfig } from 'vite'\r\nimport type { SeoConfiguration } from '../types/seo'\r\n\r\nexport interface DcsSeoPluginOptions {\r\n /** Path to seo.yaml relative to project root (default: '.dcs/seo.yaml') */\r\n seoPath?: string\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects .dcs/seo.yaml at build time.\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function dcsSeoPlugin(options: DcsSeoPluginOptions = {}): Plugin {\r\n const { seoPath = '.dcs/seo.yaml', debug = false } = options\r\n\r\n let resolvedConfig: ResolvedConfig\r\n\r\n return {\r\n name: 'dcs-seo',\r\n\r\n configResolved(config) {\r\n resolvedConfig = config\r\n },\r\n\r\n config(config) {\r\n const projectRoot = config.root || process.cwd()\r\n\r\n // Try to find seo.yaml in multiple locations\r\n // VitePress projects have the docs folder as root, so check parent too\r\n const possiblePaths = [\r\n path.resolve(projectRoot, seoPath),\r\n path.resolve(projectRoot, '..', seoPath),\r\n path.resolve(process.cwd(), seoPath),\r\n ]\r\n\r\n let foundPath: string | undefined\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n foundPath = testPath\r\n break\r\n }\r\n }\r\n\r\n if (!foundPath) {\r\n if (debug) {\r\n console.log('[dcs-seo] No seo.yaml found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n console.log('[dcs-seo] Using defaults only')\r\n }\r\n return {\r\n define: {\r\n __DCS_SEO__: 'undefined',\r\n },\r\n }\r\n }\r\n\r\n try {\r\n const fileContent = fs.readFileSync(foundPath, 'utf8')\r\n const seoConfig = yaml.load(fileContent) as SeoConfiguration\r\n\r\n if (debug) {\r\n console.log(`[dcs-seo] Loaded ${foundPath}`)\r\n console.log(`[dcs-seo] Version: ${seoConfig.version}`)\r\n console.log(`[dcs-seo] Site Name: ${seoConfig.global?.siteName || '(not set)'}`)\r\n console.log(`[dcs-seo] Pages: ${Object.keys(seoConfig.pages ?? {}).join(', ') || '(none)'}`)\r\n }\r\n\r\n return {\r\n define: {\r\n __DCS_SEO__: JSON.stringify(seoConfig),\r\n },\r\n }\r\n } catch (error) {\r\n console.warn('[dcs-seo] Failed to parse seo.yaml:', error)\r\n return {\r\n define: {\r\n __DCS_SEO__: 'undefined',\r\n },\r\n }\r\n }\r\n },\r\n\r\n // Watch for changes in development\r\n configureServer(server) {\r\n const projectRoot = resolvedConfig?.root || process.cwd()\r\n\r\n const watchPaths = [\r\n path.resolve(projectRoot, seoPath),\r\n path.resolve(projectRoot, '..', seoPath),\r\n ]\r\n\r\n watchPaths.forEach((watchPath) => {\r\n if (fs.existsSync(watchPath)) {\r\n server.watcher.add(watchPath)\r\n\r\n server.watcher.on('change', (changedPath) => {\r\n if (changedPath === watchPath) {\r\n if (debug) {\r\n console.log('[dcs-seo] seo.yaml changed, triggering reload')\r\n }\r\n server.restart()\r\n }\r\n })\r\n }\r\n })\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS Editor Plugin for Vite\r\n *\r\n * Injects the editor bridge script into customer sites when running\r\n * in portal preview mode (inside the visual editor iframe).\r\n *\r\n * The bridge enables:\r\n * - Inline text editing via contenteditable\r\n * - Section hover highlights and AI ✨ buttons\r\n * - postMessage communication with the portal\r\n *\r\n * This plugin should only be active in development/preview mode.\r\n * It's safe to include in production builds — it does nothing unless\r\n * the page detects it's inside an iframe.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsEditorPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsEditorPlugin()\r\n * ]\r\n * })\r\n * ```\r\n */\r\n\r\nimport type { Plugin } from 'vite'\r\n\r\nexport interface DcsEditorPluginOptions {\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects the editor bridge script for portal preview integration.\r\n *\r\n * The bridge auto-initializes only when the page detects it's running inside an iframe,\r\n * so it's safe to include in all builds — it's a no-op in standalone browsing.\r\n *\r\n * Uses a virtual module (`/__dcs-editor-bridge.js`) served through Vite's dev server\r\n * so that bare module specifiers (like `@duffcloudservices/cms/editor`) are properly\r\n * resolved through Vite's module graph rather than hitting the browser's native ESM\r\n * resolver, which can't handle bare specifiers.\r\n */\r\nexport function dcsEditorPlugin(options: DcsEditorPluginOptions = {}): Plugin {\r\n const { debug = false } = options\r\n const VIRTUAL_PATH = '/__dcs-editor-bridge.js'\r\n const RIBBON_VIRTUAL_PATH = '/__dcs-preview-ribbon.js'\r\n\r\n return {\r\n name: 'dcs-editor-bridge',\r\n\r\n resolveId(id) {\r\n if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {\r\n return id\r\n }\r\n },\r\n\r\n load(id) {\r\n if (id === VIRTUAL_PATH) {\r\n return `\r\nimport { initEditorBridge } from '@duffcloudservices/cms/editor'\r\n// The bridge only activates inside an iframe (portal preview)\r\nif (window.parent !== window) {\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', () => setTimeout(initEditorBridge, 200))\r\n } else {\r\n setTimeout(initEditorBridge, 200)\r\n }\r\n}\r\n`\r\n }\r\n\r\n if (id === RIBBON_VIRTUAL_PATH) {\r\n return `\r\nimport { createApp } from 'vue'\r\nimport PreviewRibbon from '@duffcloudservices/cms/components'\r\n\r\nfunction mountRibbon() {\r\n // Create a detached mount point\r\n const el = document.createElement('div')\r\n el.id = 'dcs-preview-ribbon-root'\r\n document.body.appendChild(el)\r\n\r\n const app = createApp(PreviewRibbon)\r\n app.mount(el)\r\n}\r\n\r\nif (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', mountRibbon)\r\n} else {\r\n mountRibbon()\r\n}\r\n`\r\n }\r\n },\r\n\r\n transformIndexHtml(html) {\r\n if (debug) {\r\n console.log('[dcs-editor] Injecting editor bridge + preview ribbon script tags')\r\n }\r\n\r\n // Inject both the editor bridge and preview ribbon scripts\r\n const editorTag = `<script type=\"module\" src=\"${VIRTUAL_PATH}\"></script>`\r\n const ribbonTag = `<script type=\"module\" src=\"${RIBBON_VIRTUAL_PATH}\"></script>`\r\n\r\n // Insert before closing </body> tag\r\n return html.replace('</body>', `${editorTag}\\n${ribbonTag}\\n</body>`)\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS Preview Plugin for Vue\r\n *\r\n * Registers a supplied ribbon component as a global `DcsPreviewRibbon`\r\n * component. The ribbon handles its own visibility — it only renders on\r\n * `preview.duffcloudservices.com` and hides everywhere else (localhost,\r\n * production domains, and inside the visual page editor iframe).\r\n *\r\n * Why does the caller pass the component in? Because this file is compiled\r\n * by tsup (esbuild) which has no `.vue` SFC loader. Keeping the raw `.vue`\r\n * import out of the compiled plugins bundle avoids the build error while\r\n * still letting consumer code (which *does* run through Vite) resolve the\r\n * SFC at dev/build time.\r\n *\r\n * @example VitePress theme/index.ts\r\n * ```typescript\r\n * import { dcsPreviewPlugin } from '@duffcloudservices/cms/plugins'\r\n * import PreviewRibbon from '@duffcloudservices/cms/components'\r\n *\r\n * export default {\r\n * Layout,\r\n * enhanceApp({ app }) {\r\n * app.use(dcsPreviewPlugin(PreviewRibbon))\r\n * }\r\n * }\r\n * ```\r\n */\r\n\r\nimport { defineComponent, h, type App, type Component, type Plugin } from 'vue'\r\n\r\nexport interface DcsPreviewPluginOptions {\r\n /**\r\n * Override the version string displayed on the ribbon.\r\n * If omitted, the ribbon auto-detects from VITE_SITE_VERSION or the API.\r\n */\r\n version?: string | null\r\n}\r\n\r\n/**\r\n * Creates and returns the DCS Preview plugin.\r\n *\r\n * When installed, it registers the supplied ribbon component as a global\r\n * `DcsPreviewRibbon` component. Add `<DcsPreviewRibbon />` to your root\r\n * Layout, or use the `dcsEditorPlugin` Vite plugin which injects it via\r\n * `transformIndexHtml`.\r\n *\r\n * @param ribbonComponent - The PreviewRibbon SFC (imported by the consumer)\r\n * @param options - Optional configuration\r\n */\r\nexport function dcsPreviewPlugin(\r\n ribbonComponent: Component,\r\n options: DcsPreviewPluginOptions = {},\r\n): Plugin {\r\n const Wrapper = defineComponent({\r\n name: 'DcsPreviewRibbon',\r\n setup() {\r\n return () => h(ribbonComponent, { version: options.version ?? null })\r\n },\r\n })\r\n\r\n return {\r\n install(app: App) {\r\n app.component('DcsPreviewRibbon', Wrapper)\r\n\r\n if (options.version !== undefined) {\r\n app.provide('__dcs_preview_version__', options.version)\r\n }\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS CDN Image Plugin for Vite\r\n *\r\n * Rewrites local static image references (e.g. `/images/staff/photo.jpg`)\r\n * to CDN URLs at build time using the `.dcs/cdn-image-map.json` mapping file\r\n * generated by the `image-migrate adopt` CLI command.\r\n *\r\n * For raster images with WebP variants, `<img>` elements are transformed into\r\n * responsive `<picture>` elements with `srcset` for optimised delivery.\r\n * SVGs receive a simple URL swap with no variant handling.\r\n *\r\n * **In development mode this plugin is a no-op** — local `/images/` paths\r\n * continue to work via Vite's static asset serving so hot-reload is unaffected.\r\n *\r\n * The plugin handles two in-pipeline replacement vectors:\r\n *\r\n * 1. **Module transform** (`transform` hook) — rewrites `<img>` tags and\r\n * string literals in Vue SFCs, TS, JS, CSS, MD, and HTML modules.\r\n * 2. **Chunk rendering** (`renderChunk` hook) — rewrites image paths in\r\n * final rendered JS/CSS chunks AFTER Vite's `define` substitution.\r\n * This catches data injected by `dcsContentPlugin` via `__DCS_CONTENT__`\r\n * (from `.dcs/content.yaml`) which bypasses the `transform` hook.\r\n *\r\n * A third vector — **post-build file processing** — is handled by the\r\n * companion `dcsCdnBuildEnd` hook, which must be registered separately in\r\n * VitePress config. VitePress generates HTML *after* both Vite builds\r\n * complete, so Vite plugin hooks (`closeBundle`, `transformIndexHtml`)\r\n * cannot catch SSR-rendered `<head>` tags or static `public/` files like\r\n * `service-worker.js`.\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { dcsCdnImagePlugin, dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsCdnImagePlugin()\r\n * ]\r\n * },\r\n * buildEnd: dcsCdnBuildEnd()\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport type { Plugin } from 'vite'\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Types\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nexport interface CdnImageMapVariant {\r\n suffix: string\r\n blobPath: string\r\n cdnUrl: string\r\n width: number\r\n height: number\r\n size: number\r\n}\r\n\r\nexport interface CdnImageMapEntry {\r\n localPath: string\r\n blobPath: string\r\n cdnUrl: string\r\n fileName: string\r\n contentType: string\r\n originalSize: number\r\n width?: number\r\n height?: number\r\n hash: string\r\n variants?: CdnImageMapVariant[]\r\n}\r\n\r\nexport interface CdnImageMap {\r\n generated: string\r\n site: string\r\n cdnBase: string\r\n images: CdnImageMapEntry[]\r\n}\r\n\r\nexport interface DcsCdnImagePluginOptions {\r\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\r\n mapPath?: string\r\n\r\n /**\r\n * Patterns to match for replacement. Each must be a **leading-slash path prefix**\r\n * that appears in source code (e.g. `/images/`). The `localPath` field in the\r\n * mapping file is compared *without* a leading slash.\r\n *\r\n * Default: `['/images/']`\r\n */\r\n pathPrefixes?: string[]\r\n\r\n /**\r\n * Default `sizes` attribute for responsive `<picture>` elements.\r\n * Override per-context via the `data-sizes` attribute on the original `<img>`.\r\n *\r\n * Default: `'(max-width: 1024px) 100vw, 1024px'`\r\n */\r\n defaultSizes?: string\r\n\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Helpers\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n}\r\n\r\n/**\r\n * Build a responsive `<picture>` element for a raster image that has\r\n * WebP variants.\r\n */\r\nfunction buildPictureElement(\r\n entry: CdnImageMapEntry,\r\n alt: string,\r\n extraAttrs: string,\r\n sizes: string\r\n): string {\r\n const variants = entry.variants ?? []\r\n\r\n // Collect only the display-relevant variants (not thumb, not og)\r\n const displayVariants = variants.filter(\r\n (v) => v.suffix === '-sm' || v.suffix === '-md' || v.suffix === '-lg'\r\n )\r\n\r\n if (displayVariants.length === 0) {\r\n // No display variants — fall back to original CDN URL as plain <img>\r\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\r\n }\r\n\r\n const srcset = displayVariants\r\n .map((v) => `${v.cdnUrl} ${v.width}w`)\r\n .join(', ')\r\n\r\n // Pick the -md variant as the default src, or fall through to original\r\n const mdVariant = variants.find((v) => v.suffix === '-md')\r\n const fallbackSrc = mdVariant ? mdVariant.cdnUrl : entry.cdnUrl\r\n\r\n return [\r\n '<picture>',\r\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"${escapeHtml(sizes)}\" />`,\r\n ` <img src=\"${escapeHtml(fallbackSrc)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`,\r\n '</picture>',\r\n ].join('\\n')\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Shared helpers\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Try to find and load the CDN image map from common locations.\r\n * VitePress nests docs under a subfolder, so we check parent dirs too.\r\n */\r\nfunction loadCdnImageMap(\r\n projectRoot: string,\r\n relativeMapPath: string,\r\n debug: boolean\r\n): Map<string, CdnImageMapEntry> | null {\r\n const possiblePaths = [\r\n path.resolve(projectRoot, relativeMapPath),\r\n path.resolve(projectRoot, '..', relativeMapPath),\r\n path.resolve(process.cwd(), relativeMapPath),\r\n ]\r\n\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n try {\r\n const raw = fs.readFileSync(testPath, 'utf8')\r\n const data: CdnImageMap = JSON.parse(raw)\r\n\r\n const map = new Map<string, CdnImageMapEntry>()\r\n for (const entry of data.images) {\r\n // Store both with and without leading slash for flexible matching\r\n map.set(entry.localPath, entry)\r\n if (!entry.localPath.startsWith('/')) {\r\n map.set('/' + entry.localPath, entry)\r\n }\r\n }\r\n\r\n if (debug) {\r\n console.log(`[dcs-cdn-image] Loaded ${data.images.length} entries from ${testPath}`)\r\n }\r\n return map\r\n } catch (error) {\r\n console.warn(`[dcs-cdn-image] Failed to parse ${testPath}:`, error)\r\n }\r\n }\r\n }\r\n\r\n if (debug) {\r\n console.log('[dcs-cdn-image] No cdn-image-map.json found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n }\r\n return null\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Vite Plugin\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Vite plugin that rewrites static `/images/` paths to CDN URLs at build time.\r\n */\r\nexport function dcsCdnImagePlugin(options: DcsCdnImagePluginOptions = {}): Plugin {\r\n const {\r\n mapPath = '.dcs/cdn-image-map.json',\r\n pathPrefixes = ['/images/'],\r\n defaultSizes = '(max-width: 1024px) 100vw, 1024px',\r\n debug = false,\r\n } = options\r\n\r\n // Lookup map built from the JSON file: key = localPath (no leading slash)\r\n let imageMap: Map<string, CdnImageMapEntry> | null = null\r\n let isProduction = false\r\n\r\n /**\r\n * Check whether a source code string contains any of the path prefixes\r\n * we care about. Quick bail-out for files that don't reference images.\r\n */\r\n function hasImageReferences(code: string): boolean {\r\n return pathPrefixes.some((prefix) => code.includes(prefix))\r\n }\r\n\r\n /**\r\n * Replace image path references in any string value with CDN URLs.\r\n * Used for code transforms and renderChunk post-processing.\r\n */\r\n function replaceImagePaths(str: string): string {\r\n if (!imageMap || imageMap.size === 0) return str\r\n let result = str\r\n for (const prefix of pathPrefixes) {\r\n // Global replace: matches prefix followed by a relative path bounded\r\n // by quotes, parens, or whitespace. Allows spaces in filenames.\r\n const regex = new RegExp(\r\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\r\n 'g'\r\n )\r\n result = result.replace(regex, (match, quote, relPath) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n return `${quote}${entry.cdnUrl}`\r\n })\r\n }\r\n return result\r\n }\r\n\r\n /**\r\n * Replace standalone URL references in JS/TS string literals and Vue template\r\n * attributes. This handles patterns like:\r\n * src=\"/images/foo.jpg\"\r\n * { src: '/images/foo.jpg' }\r\n * url(/images/foo.svg)\r\n *\r\n * For `<img>` tags with raster images that have variants, the entire tag is\r\n * replaced with a `<picture>` element.\r\n */\r\n function transformCode(code: string): string {\r\n if (!imageMap || imageMap.size === 0) return code\r\n\r\n // ── Pass 1: Replace <img> tags that reference mapped raster images ──────\r\n // Matches: <img ... src=\"/images/foo.jpg\" ... /> or <img ... src=\"/images/foo.jpg\" ... >\r\n const imgTagRegex = /<img\\b([^>]*?)src=[\"'](\\/(images\\/[^\"']+))[\"']([^>]*?)\\/?>/gi\r\n code = code.replace(imgTagRegex, (_match, beforeSrc, _srcWithSlash, localPath, afterSrc) => {\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return _match\r\n\r\n // Extract alt from the existing attributes\r\n const altMatch = (beforeSrc + afterSrc).match(/alt=[\"']([^\"']*)[\"']/)\r\n const alt = altMatch ? altMatch[1] : ''\r\n\r\n // Collect other attributes (not src, alt, loading, decoding — we set those)\r\n const otherAttrs = (beforeSrc + afterSrc)\r\n .replace(/\\balt=[\"'][^\"']*[\"']/gi, '')\r\n .replace(/\\bloading=[\"'][^\"']*[\"']/gi, '')\r\n .replace(/\\bdecoding=[\"'][^\"']*[\"']/gi, '')\r\n .trim()\r\n\r\n const extraAttrs = otherAttrs ? ' ' + otherAttrs : ''\r\n\r\n // Extract custom sizes if specified\r\n const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=[\"']([^\"']*)[\"']/)\r\n const sizes = sizesMatch ? sizesMatch[1] : defaultSizes\r\n\r\n // SVGs and images without variants → simple URL swap <img>\r\n if (!entry.variants || entry.variants.length === 0) {\r\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\r\n }\r\n\r\n // Raster with variants → <picture>\r\n return buildPictureElement(entry, alt, extraAttrs, sizes)\r\n })\r\n\r\n // ── Pass 2: Replace remaining string literal / CSS url() references ─────\r\n code = replaceImagePaths(code)\r\n\r\n return code\r\n }\r\n\r\n return {\r\n name: 'dcs-cdn-image',\r\n enforce: 'pre',\r\n\r\n configResolved(config) {\r\n isProduction = config.command === 'build'\r\n if (!isProduction) {\r\n if (debug) {\r\n console.log('[dcs-cdn-image] Dev mode — plugin disabled (local images served by Vite)')\r\n }\r\n return\r\n }\r\n imageMap = loadCdnImageMap(config.root, mapPath, debug)\r\n },\r\n\r\n transform(code, id) {\r\n // Only run in production builds\r\n if (!isProduction || !imageMap) return\r\n\r\n // Only process relevant file types\r\n if (!/\\.(vue|ts|tsx|js|jsx|css|scss|md|html)(\\?.*)?$/.test(id)) return\r\n\r\n // Quick bail — skip files with no image path references\r\n if (!hasImageReferences(code)) return\r\n\r\n const transformed = transformCode(code)\r\n if (transformed !== code) {\r\n return { code: transformed, map: null }\r\n }\r\n },\r\n\r\n /**\r\n * Replace image paths in final rendered JS/CSS chunks.\r\n *\r\n * This fires AFTER Vite's `define` substitution, catching paths that were\r\n * injected via `define` values (e.g. `__DCS_CONTENT__` set by\r\n * `dcsContentPlugin` from `.dcs/content.yaml`). Those paths bypass the\r\n * `transform` hook because `define` replacement happens at bundle time.\r\n */\r\n renderChunk(code, _chunk) {\r\n if (!isProduction || !imageMap) return null\r\n if (!hasImageReferences(code)) return null\r\n\r\n const replaced = replaceImagePaths(code)\r\n if (replaced !== code) {\r\n if (debug) {\r\n console.log(`[dcs-cdn-image] renderChunk: rewrote image paths in ${_chunk.fileName}`)\r\n }\r\n return { code: replaced, map: null }\r\n }\r\n return null\r\n },\r\n\r\n /**\r\n * Transform HTML output to catch references injected outside the module\r\n * pipeline — e.g. VitePress `head` config tags (favicons, OG images).\r\n *\r\n * Note: In VitePress, this hook fires for the `index.html` template but\r\n * NOT for SSR-rendered per-page HTML. Use `dcsCdnBuildEnd` for full\r\n * coverage of generated HTML files.\r\n */\r\n transformIndexHtml(html) {\r\n if (!isProduction || !imageMap) return html\r\n if (!hasImageReferences(html)) return html\r\n\r\n // Replace href/src/content attributes that reference mapped images\r\n for (const prefix of pathPrefixes) {\r\n const attrRegex = new RegExp(\r\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\r\n 'gi'\r\n )\r\n html = html.replace(attrRegex, (match, pre, relPath, post) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n return `${pre}${entry.cdnUrl}${post}`\r\n })\r\n }\r\n\r\n return html\r\n },\r\n }\r\n}\r\n\r\n/**\r\n * Escape a string for use in a RegExp.\r\n */\r\nfunction escapeRegex(str: string): string {\r\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// VitePress buildEnd hook\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nexport interface DcsCdnBuildEndOptions {\r\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\r\n mapPath?: string\r\n\r\n /**\r\n * Patterns to match for replacement.\r\n * Default: `['/images/']`\r\n */\r\n pathPrefixes?: string[]\r\n\r\n /** Enable debug logging */\r\n debug?: boolean\r\n\r\n /**\r\n * File extensions to process in the output directory.\r\n * Default: `['.html', '.js']`\r\n */\r\n extensions?: string[]\r\n}\r\n\r\n/**\r\n * VitePress `buildEnd` hook factory that post-processes generated HTML and\r\n * static files in the output directory to replace remaining `/images/` paths\r\n * with CDN URLs.\r\n *\r\n * VitePress generates HTML **after** both Vite builds complete, so Vite\r\n * plugin hooks (`closeBundle`, `transformIndexHtml`) cannot catch\r\n * SSR-rendered `<head>` tags (favicons, OG images). This hook runs after all\r\n * HTML files are written to disk.\r\n *\r\n * Also rewrites static files (e.g. `service-worker.js`) copied from\r\n * `public/` that are not part of Vite's module pipeline.\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * buildEnd: dcsCdnBuildEnd({ debug: true })\r\n * })\r\n * ```\r\n */\r\nexport function dcsCdnBuildEnd(options: DcsCdnBuildEndOptions = {}) {\r\n const {\r\n mapPath = '.dcs/cdn-image-map.json',\r\n pathPrefixes = ['/images/'],\r\n debug = false,\r\n extensions = ['.html', '.js'],\r\n } = options\r\n\r\n return async (siteConfig: { root: string; outDir: string }) => {\r\n const imageMap = loadCdnImageMap(siteConfig.root, mapPath, debug)\r\n if (!imageMap) {\r\n if (debug) {\r\n console.log('[dcs-cdn-image] buildEnd: no image map found')\r\n }\r\n return\r\n }\r\n\r\n const outDir = siteConfig.outDir\r\n if (!fs.existsSync(outDir)) return\r\n\r\n let filesProcessed = 0\r\n let refsReplaced = 0\r\n\r\n function walkDir(dir: string) {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true })\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name)\r\n if (entry.isDirectory()) {\r\n walkDir(fullPath)\r\n } else if (extensions.some((ext) => entry.name.endsWith(ext))) {\r\n processFile(fullPath)\r\n }\r\n }\r\n }\r\n\r\n function processFile(filePath: string) {\r\n let content = fs.readFileSync(filePath, 'utf8')\r\n if (!pathPrefixes.some((p) => content.includes(p))) return\r\n\r\n let changed = false\r\n\r\n if (filePath.endsWith('.html')) {\r\n // HTML: replace href/src/content attribute values\r\n for (const prefix of pathPrefixes) {\r\n const attrRegex = new RegExp(\r\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\r\n 'gi'\r\n )\r\n content = content.replace(attrRegex, (match, pre, relPath, post) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const mapEntry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!mapEntry) return match\r\n changed = true\r\n refsReplaced++\r\n return `${pre}${mapEntry.cdnUrl}${post}`\r\n })\r\n }\r\n } else {\r\n // JS/other: replace quoted string paths\r\n for (const prefix of pathPrefixes) {\r\n const regex = new RegExp(\r\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\r\n 'g'\r\n )\r\n content = content.replace(regex, (match, quote, relPath) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n changed = true\r\n refsReplaced++\r\n return `${quote}${entry.cdnUrl}`\r\n })\r\n }\r\n }\r\n\r\n if (changed) {\r\n fs.writeFileSync(filePath, content, 'utf8')\r\n filesProcessed++\r\n }\r\n }\r\n\r\n walkDir(outDir)\r\n\r\n if (debug) {\r\n console.log(\r\n `[dcs-cdn-image] buildEnd: post-processed ${filesProcessed} files, replaced ${refsReplaced} references`\r\n )\r\n }\r\n }\r\n}","/**\r\n * markdown-it plugin that transforms standard `![alt](url)` image syntax\r\n * into responsive `<picture>` elements when the URL matches the DCS CDN\r\n * asset pattern.\r\n *\r\n * Non-CDN images are rendered with the default image renderer (plain `<img>`).\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { responsiveImagePlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * markdown: {\r\n * config: (md) => {\r\n * md.use(responsiveImagePlugin)\r\n * },\r\n * },\r\n * })\r\n * ```\r\n *\r\n * Input markdown:\r\n * ```md\r\n * ![Physical therapy session](https://files.duffcloudservices.com/kept/assets/blog/abc-123.jpg)\r\n * ```\r\n *\r\n * Rendered HTML:\r\n * ```html\r\n * <picture>\r\n * <source srcset=\"...abc-123-sm.webp 640w, ...abc-123-md.webp 1024w, ...abc-123-lg.webp 1920w\"\r\n * type=\"image/webp\"\r\n * sizes=\"(max-width: 1024px) 100vw, 1024px\" />\r\n * <img src=\"...abc-123-md.webp\" alt=\"Physical therapy session\" loading=\"lazy\" decoding=\"async\" />\r\n * </picture>\r\n * ```\r\n */\r\n\r\nimport type MarkdownIt from 'markdown-it'\r\n\r\n/** Matches DCS CDN asset URLs: base path / UUID . extension */\r\nconst CDN_PATTERN = /^(https?:\\/\\/files\\.[^/]+\\/[^/]+\\/assets\\/(?:[^/]+\\/)*)([a-f0-9-]+)\\.(\\w+)$/\r\n\r\n/**\r\n * Escapes HTML special characters for safe attribute embedding.\r\n * Falls back to a simple replace chain when `md.utils.escapeHtml` is unavailable.\r\n */\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n}\r\n\r\nexport function responsiveImagePlugin(md: MarkdownIt): void {\r\n const defaultImageRenderer = md.renderer.rules.image\r\n\r\n md.renderer.rules.image = (tokens, idx, options, env, self) => {\r\n const token = tokens[idx]\r\n const src = token.attrGet('src') ?? ''\r\n const alt = token.content ?? ''\r\n\r\n const match = src.match(CDN_PATTERN)\r\n if (!match) {\r\n // Not a CDN image — render normally\r\n if (defaultImageRenderer) {\r\n return defaultImageRenderer(tokens, idx, options, env, self)\r\n }\r\n return self.renderToken(tokens, idx, options)\r\n }\r\n\r\n const [, basePath, uuid] = match\r\n\r\n const srcset = [\r\n `${basePath}${uuid}-sm.webp 640w`,\r\n `${basePath}${uuid}-md.webp 1024w`,\r\n `${basePath}${uuid}-lg.webp 1920w`,\r\n ].join(', ')\r\n\r\n const escapedAlt = escapeHtml(alt)\r\n\r\n return [\r\n '<picture>',\r\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"(max-width: 1024px) 100vw, 1024px\" />`,\r\n ` <img src=\"${basePath}${uuid}-md.webp\" alt=\"${escapedAlt}\" loading=\"lazy\" decoding=\"async\" />`,\r\n '</picture>',\r\n ].join('\\n')\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/plugins/dcsContentPlugin.ts","../../src/plugins/dcsSeoPlugin.ts","../../src/plugins/dcsEditorPlugin.ts","../../src/plugins/dcsPreviewPlugin.ts","../../src/plugins/dcsCdnImagePlugin.ts","../../src/plugins/responsiveImagePlugin.ts"],"names":["fs","path","yaml","escapeHtml"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * DCS Content Plugin for Vite\r\n *\r\n * Reads `.dcs/content.yaml` at build time and injects content\r\n * as `__DCS_CONTENT__` global variable for use by useTextContent.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsContentPlugin({ debug: true })\r\n * ]\r\n * })\r\n * ```\r\n *\r\n * For VitePress:\r\n * ```typescript\r\n * // .vitepress/config.ts\r\n * import { defineConfig } from 'vitepress'\r\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsContentPlugin()\r\n * ]\r\n * }\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport yaml from 'js-yaml'\r\nimport type { Plugin, ResolvedConfig } from 'vite'\r\nimport type { DcsContentFile } from '../types/content'\r\n\r\nexport interface DcsContentPluginOptions {\r\n /** Path to content.yaml relative to project root (default: '.dcs/content.yaml') */\r\n contentPath?: string\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects .dcs/content.yaml at build time.\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function dcsContentPlugin(options: DcsContentPluginOptions = {}): Plugin {\r\n const { contentPath = '.dcs/content.yaml', debug = false } = options\r\n\r\n let resolvedConfig: ResolvedConfig\r\n\r\n return {\r\n name: 'dcs-content',\r\n\r\n configResolved(config) {\r\n resolvedConfig = config\r\n },\r\n\r\n config(config) {\r\n const projectRoot = config.root || process.cwd()\r\n\r\n // Try to find content.yaml in multiple locations\r\n // VitePress projects have the docs folder as root, so check parent too\r\n const possiblePaths = [\r\n path.resolve(projectRoot, contentPath),\r\n path.resolve(projectRoot, '..', contentPath),\r\n path.resolve(process.cwd(), contentPath),\r\n ]\r\n\r\n let foundPath: string | undefined\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n foundPath = testPath\r\n break\r\n }\r\n }\r\n\r\n if (!foundPath) {\r\n if (debug) {\r\n console.log('[dcs-content] No content.yaml found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n console.log('[dcs-content] Using defaults only')\r\n }\r\n return {\r\n define: {\r\n __DCS_CONTENT__: 'undefined',\r\n },\r\n }\r\n }\r\n\r\n try {\r\n const fileContent = fs.readFileSync(foundPath, 'utf8')\r\n const content = yaml.load(fileContent) as DcsContentFile\r\n\r\n if (debug) {\r\n console.log(`[dcs-content] Loaded ${foundPath}`)\r\n console.log(`[dcs-content] Version: ${content.version}`)\r\n console.log(`[dcs-content] Pages: ${Object.keys(content.pages ?? {}).join(', ') || '(none)'}`)\r\n console.log(`[dcs-content] Global keys: ${Object.keys(content.global ?? {}).length}`)\r\n }\r\n\r\n return {\r\n define: {\r\n __DCS_CONTENT__: JSON.stringify(content),\r\n },\r\n }\r\n } catch (error) {\r\n console.warn('[dcs-content] Failed to parse content.yaml:', error)\r\n return {\r\n define: {\r\n __DCS_CONTENT__: 'undefined',\r\n },\r\n }\r\n }\r\n },\r\n\r\n // Watch for changes in development\r\n configureServer(server) {\r\n const projectRoot = resolvedConfig?.root || process.cwd()\r\n\r\n const watchPaths = [\r\n path.resolve(projectRoot, contentPath),\r\n path.resolve(projectRoot, '..', contentPath),\r\n ]\r\n\r\n watchPaths.forEach((watchPath) => {\r\n if (fs.existsSync(watchPath)) {\r\n server.watcher.add(watchPath)\r\n\r\n server.watcher.on('change', (changedPath) => {\r\n if (changedPath === watchPath) {\r\n if (debug) {\r\n console.log('[dcs-content] content.yaml changed, triggering reload')\r\n }\r\n server.restart()\r\n }\r\n })\r\n }\r\n })\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS SEO Plugin for Vite\r\n *\r\n * Reads `.dcs/seo.yaml` at build time and injects content\r\n * as `__DCS_SEO__` global variable for use by useSEO.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsSeoPlugin({ debug: true })\r\n * ]\r\n * })\r\n * ```\r\n *\r\n * For VitePress:\r\n * ```typescript\r\n * // .vitepress/config.ts\r\n * import { defineConfig } from 'vitepress'\r\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsSeoPlugin()\r\n * ]\r\n * }\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport yaml from 'js-yaml'\r\nimport type { Plugin, ResolvedConfig } from 'vite'\r\nimport type { SeoConfiguration } from '../types/seo'\r\n\r\nexport interface DcsSeoPluginOptions {\r\n /** Path to seo.yaml relative to project root (default: '.dcs/seo.yaml') */\r\n seoPath?: string\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects .dcs/seo.yaml at build time.\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function dcsSeoPlugin(options: DcsSeoPluginOptions = {}): Plugin {\r\n const { seoPath = '.dcs/seo.yaml', debug = false } = options\r\n\r\n let resolvedConfig: ResolvedConfig\r\n\r\n return {\r\n name: 'dcs-seo',\r\n\r\n configResolved(config) {\r\n resolvedConfig = config\r\n },\r\n\r\n config(config) {\r\n const projectRoot = config.root || process.cwd()\r\n\r\n // Try to find seo.yaml in multiple locations\r\n // VitePress projects have the docs folder as root, so check parent too\r\n const possiblePaths = [\r\n path.resolve(projectRoot, seoPath),\r\n path.resolve(projectRoot, '..', seoPath),\r\n path.resolve(process.cwd(), seoPath),\r\n ]\r\n\r\n let foundPath: string | undefined\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n foundPath = testPath\r\n break\r\n }\r\n }\r\n\r\n if (!foundPath) {\r\n if (debug) {\r\n console.log('[dcs-seo] No seo.yaml found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n console.log('[dcs-seo] Using defaults only')\r\n }\r\n return {\r\n define: {\r\n __DCS_SEO__: 'undefined',\r\n },\r\n }\r\n }\r\n\r\n try {\r\n const fileContent = fs.readFileSync(foundPath, 'utf8')\r\n const seoConfig = yaml.load(fileContent) as SeoConfiguration\r\n\r\n if (debug) {\r\n console.log(`[dcs-seo] Loaded ${foundPath}`)\r\n console.log(`[dcs-seo] Version: ${seoConfig.version}`)\r\n console.log(`[dcs-seo] Site Name: ${seoConfig.global?.siteName || '(not set)'}`)\r\n console.log(`[dcs-seo] Pages: ${Object.keys(seoConfig.pages ?? {}).join(', ') || '(none)'}`)\r\n }\r\n\r\n return {\r\n define: {\r\n __DCS_SEO__: JSON.stringify(seoConfig),\r\n },\r\n }\r\n } catch (error) {\r\n console.warn('[dcs-seo] Failed to parse seo.yaml:', error)\r\n return {\r\n define: {\r\n __DCS_SEO__: 'undefined',\r\n },\r\n }\r\n }\r\n },\r\n\r\n // Watch for changes in development\r\n configureServer(server) {\r\n const projectRoot = resolvedConfig?.root || process.cwd()\r\n\r\n const watchPaths = [\r\n path.resolve(projectRoot, seoPath),\r\n path.resolve(projectRoot, '..', seoPath),\r\n ]\r\n\r\n watchPaths.forEach((watchPath) => {\r\n if (fs.existsSync(watchPath)) {\r\n server.watcher.add(watchPath)\r\n\r\n server.watcher.on('change', (changedPath) => {\r\n if (changedPath === watchPath) {\r\n if (debug) {\r\n console.log('[dcs-seo] seo.yaml changed, triggering reload')\r\n }\r\n server.restart()\r\n }\r\n })\r\n }\r\n })\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS Editor Plugin for Vite\r\n *\r\n * Injects the editor bridge script into customer sites when running\r\n * in portal preview mode (inside the visual editor iframe).\r\n *\r\n * The bridge enables:\r\n * - Inline text editing via contenteditable\r\n * - Section hover highlights and AI ✨ buttons\r\n * - postMessage communication with the portal\r\n *\r\n * This plugin should only be active in development/preview mode.\r\n * It's safe to include in production builds — it does nothing unless\r\n * the page detects it's inside an iframe.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsEditorPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsEditorPlugin()\r\n * ]\r\n * })\r\n * ```\r\n */\r\n\r\nimport type { Plugin } from 'vite'\r\n\r\nexport interface DcsEditorPluginOptions {\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects the editor bridge script for portal preview integration.\r\n *\r\n * The bridge auto-initializes only when the page detects it's running inside an iframe,\r\n * so it's safe to include in all builds — it's a no-op in standalone browsing.\r\n *\r\n * Uses a virtual module (`/__dcs-editor-bridge.js`) served through Vite's dev server\r\n * so that bare module specifiers (like `@duffcloudservices/cms/editor`) are properly\r\n * resolved through Vite's module graph rather than hitting the browser's native ESM\r\n * resolver, which can't handle bare specifiers.\r\n */\r\nexport function dcsEditorPlugin(options: DcsEditorPluginOptions = {}): Plugin {\r\n const { debug = false } = options\r\n const VIRTUAL_PATH = '/__dcs-editor-bridge.js'\r\n const RIBBON_VIRTUAL_PATH = '/__dcs-preview-ribbon.js'\r\n\r\n // Captured during configResolved — used to gate HTML injection.\r\n let isDev = false\r\n let basePath = '/'\r\n\r\n return {\r\n name: 'dcs-editor-bridge',\r\n\r\n configResolved(config) {\r\n isDev = config.command === 'serve'\r\n basePath = config.base ?? '/'\r\n },\r\n\r\n resolveId(id) {\r\n if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {\r\n return id\r\n }\r\n },\r\n\r\n load(id) {\r\n if (id === VIRTUAL_PATH) {\r\n return `\r\nimport { initEditorBridge } from '@duffcloudservices/cms/editor'\r\n// The bridge only activates inside an iframe (portal preview)\r\nif (window.parent !== window) {\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', () => setTimeout(initEditorBridge, 200))\r\n } else {\r\n setTimeout(initEditorBridge, 200)\r\n }\r\n}\r\n`\r\n }\r\n\r\n if (id === RIBBON_VIRTUAL_PATH) {\r\n return `\r\nimport { createApp } from 'vue'\r\nimport PreviewRibbon from '@duffcloudservices/cms/components'\r\n\r\nfunction mountRibbon() {\r\n // Create a detached mount point\r\n const el = document.createElement('div')\r\n el.id = 'dcs-preview-ribbon-root'\r\n document.body.appendChild(el)\r\n\r\n const app = createApp(PreviewRibbon)\r\n app.mount(el)\r\n}\r\n\r\nif (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', mountRibbon)\r\n} else {\r\n mountRibbon()\r\n}\r\n`\r\n }\r\n },\r\n\r\n /**\r\n * Only inject the editor bridge and ribbon scripts when:\r\n * 1. Running in dev mode (Vite dev server), AND\r\n * 2. The app is served at the root path (base === '/')\r\n *\r\n * These are virtual modules resolved via absolute paths (e.g.\r\n * `/__dcs-editor-bridge.js`). When base !== '/', the app is served at a\r\n * sub-path (e.g. /handyman-bryan/) and the absolute virtual paths are NOT\r\n * routed to the Vite dev server by the reverse proxy — they hit the main\r\n * DCS server instead and get redirected to portal.duffcloudservices.com\r\n * (no CORS headers), causing browser console errors.\r\n *\r\n * In production builds, App.vue imports and mounts PreviewRibbon\r\n * directly, so virtual script injection is always redundant anyway.\r\n */\r\n transformIndexHtml: {\r\n order: 'pre',\r\n handler(html) {\r\n if (!isDev || basePath !== '/') return html\r\n\r\n if (debug) {\r\n console.log('[dcs-editor] Injecting editor bridge + preview ribbon script tags')\r\n }\r\n\r\n // Inject both the editor bridge and preview ribbon scripts\r\n const editorTag = `<script type=\"module\" src=\"${VIRTUAL_PATH}\"></script>`\r\n const ribbonTag = `<script type=\"module\" src=\"${RIBBON_VIRTUAL_PATH}\"></script>`\r\n\r\n // Insert before closing </body> tag\r\n return html.replace('</body>', `${editorTag}\\n${ribbonTag}\\n</body>`)\r\n },\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS Preview Plugin for Vue\r\n *\r\n * Registers a supplied ribbon component as a global `DcsPreviewRibbon`\r\n * component. The ribbon handles its own visibility — it only renders on\r\n * `preview.duffcloudservices.com` and hides everywhere else (localhost,\r\n * production domains, and inside the visual page editor iframe).\r\n *\r\n * Why does the caller pass the component in? Because this file is compiled\r\n * by tsup (esbuild) which has no `.vue` SFC loader. Keeping the raw `.vue`\r\n * import out of the compiled plugins bundle avoids the build error while\r\n * still letting consumer code (which *does* run through Vite) resolve the\r\n * SFC at dev/build time.\r\n *\r\n * @example VitePress theme/index.ts\r\n * ```typescript\r\n * import { dcsPreviewPlugin } from '@duffcloudservices/cms/plugins'\r\n * import PreviewRibbon from '@duffcloudservices/cms/components'\r\n *\r\n * export default {\r\n * Layout,\r\n * enhanceApp({ app }) {\r\n * app.use(dcsPreviewPlugin(PreviewRibbon))\r\n * }\r\n * }\r\n * ```\r\n */\r\n\r\nimport { defineComponent, h, type App, type Component, type Plugin } from 'vue'\r\n\r\nexport interface DcsPreviewPluginOptions {\r\n /**\r\n * Override the version string displayed on the ribbon.\r\n * If omitted, the ribbon auto-detects from VITE_SITE_VERSION or the API.\r\n */\r\n version?: string | null\r\n}\r\n\r\n/**\r\n * Creates and returns the DCS Preview plugin.\r\n *\r\n * When installed, it registers the supplied ribbon component as a global\r\n * `DcsPreviewRibbon` component. Add `<DcsPreviewRibbon />` to your root\r\n * Layout, or use the `dcsEditorPlugin` Vite plugin which injects it via\r\n * `transformIndexHtml`.\r\n *\r\n * @param ribbonComponent - The PreviewRibbon SFC (imported by the consumer)\r\n * @param options - Optional configuration\r\n */\r\nexport function dcsPreviewPlugin(\r\n ribbonComponent: Component,\r\n options: DcsPreviewPluginOptions = {},\r\n): Plugin {\r\n const Wrapper = defineComponent({\r\n name: 'DcsPreviewRibbon',\r\n setup() {\r\n return () => h(ribbonComponent, { version: options.version ?? null })\r\n },\r\n })\r\n\r\n return {\r\n install(app: App) {\r\n app.component('DcsPreviewRibbon', Wrapper)\r\n\r\n if (options.version !== undefined) {\r\n app.provide('__dcs_preview_version__', options.version)\r\n }\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS CDN Image Plugin for Vite\r\n *\r\n * Rewrites local static image references (e.g. `/images/staff/photo.jpg`)\r\n * to CDN URLs at build time using the `.dcs/cdn-image-map.json` mapping file\r\n * generated by the `image-migrate adopt` CLI command.\r\n *\r\n * For raster images with WebP variants, `<img>` elements are transformed into\r\n * responsive `<picture>` elements with `srcset` for optimised delivery.\r\n * SVGs receive a simple URL swap with no variant handling.\r\n *\r\n * **In development mode this plugin is a no-op** — local `/images/` paths\r\n * continue to work via Vite's static asset serving so hot-reload is unaffected.\r\n *\r\n * The plugin handles two in-pipeline replacement vectors:\r\n *\r\n * 1. **Module transform** (`transform` hook) — rewrites `<img>` tags and\r\n * string literals in Vue SFCs, TS, JS, CSS, MD, and HTML modules.\r\n * 2. **Chunk rendering** (`renderChunk` hook) — rewrites image paths in\r\n * final rendered JS/CSS chunks AFTER Vite's `define` substitution.\r\n * This catches data injected by `dcsContentPlugin` via `__DCS_CONTENT__`\r\n * (from `.dcs/content.yaml`) which bypasses the `transform` hook.\r\n *\r\n * A third vector — **post-build file processing** — is handled by the\r\n * companion `dcsCdnBuildEnd` hook, which must be registered separately in\r\n * VitePress config. VitePress generates HTML *after* both Vite builds\r\n * complete, so Vite plugin hooks (`closeBundle`, `transformIndexHtml`)\r\n * cannot catch SSR-rendered `<head>` tags or static `public/` files like\r\n * `service-worker.js`.\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { dcsCdnImagePlugin, dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsCdnImagePlugin()\r\n * ]\r\n * },\r\n * buildEnd: dcsCdnBuildEnd()\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport type { Plugin } from 'vite'\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Types\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nexport interface CdnImageMapVariant {\r\n suffix: string\r\n blobPath: string\r\n cdnUrl: string\r\n width: number\r\n height: number\r\n size: number\r\n}\r\n\r\nexport interface CdnImageMapEntry {\r\n localPath: string\r\n blobPath: string\r\n cdnUrl: string\r\n fileName: string\r\n contentType: string\r\n originalSize: number\r\n width?: number\r\n height?: number\r\n hash: string\r\n variants?: CdnImageMapVariant[]\r\n}\r\n\r\nexport interface CdnImageMap {\r\n generated: string\r\n site: string\r\n cdnBase: string\r\n images: CdnImageMapEntry[]\r\n}\r\n\r\nexport interface DcsCdnImagePluginOptions {\r\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\r\n mapPath?: string\r\n\r\n /**\r\n * Patterns to match for replacement. Each must be a **leading-slash path prefix**\r\n * that appears in source code (e.g. `/images/`). The `localPath` field in the\r\n * mapping file is compared *without* a leading slash.\r\n *\r\n * Default: `['/images/']`\r\n */\r\n pathPrefixes?: string[]\r\n\r\n /**\r\n * Default `sizes` attribute for responsive `<picture>` elements.\r\n * Override per-context via the `data-sizes` attribute on the original `<img>`.\r\n *\r\n * Default: `'(max-width: 1024px) 100vw, 1024px'`\r\n */\r\n defaultSizes?: string\r\n\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Helpers\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n}\r\n\r\n/**\r\n * Build a responsive `<picture>` element for a raster image that has\r\n * WebP variants.\r\n */\r\nfunction buildPictureElement(\r\n entry: CdnImageMapEntry,\r\n alt: string,\r\n extraAttrs: string,\r\n sizes: string\r\n): string {\r\n const variants = entry.variants ?? []\r\n\r\n // Collect only the display-relevant variants (not thumb, not og)\r\n const displayVariants = variants.filter(\r\n (v) => v.suffix === '-sm' || v.suffix === '-md' || v.suffix === '-lg'\r\n )\r\n\r\n if (displayVariants.length === 0) {\r\n // No display variants — fall back to original CDN URL as plain <img>\r\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\r\n }\r\n\r\n const srcset = displayVariants\r\n .map((v) => `${v.cdnUrl} ${v.width}w`)\r\n .join(', ')\r\n\r\n // Pick the -md variant as the default src, or fall through to original\r\n const mdVariant = variants.find((v) => v.suffix === '-md')\r\n const fallbackSrc = mdVariant ? mdVariant.cdnUrl : entry.cdnUrl\r\n\r\n return [\r\n '<picture>',\r\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"${escapeHtml(sizes)}\" />`,\r\n ` <img src=\"${escapeHtml(fallbackSrc)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`,\r\n '</picture>',\r\n ].join('\\n')\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Shared helpers\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Try to find and load the CDN image map from common locations.\r\n * VitePress nests docs under a subfolder, so we check parent dirs too.\r\n */\r\nfunction loadCdnImageMap(\r\n projectRoot: string,\r\n relativeMapPath: string,\r\n debug: boolean\r\n): Map<string, CdnImageMapEntry> | null {\r\n const possiblePaths = [\r\n path.resolve(projectRoot, relativeMapPath),\r\n path.resolve(projectRoot, '..', relativeMapPath),\r\n path.resolve(process.cwd(), relativeMapPath),\r\n ]\r\n\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n try {\r\n const raw = fs.readFileSync(testPath, 'utf8')\r\n const data: CdnImageMap = JSON.parse(raw)\r\n\r\n const map = new Map<string, CdnImageMapEntry>()\r\n for (const entry of data.images) {\r\n // Store both with and without leading slash for flexible matching\r\n map.set(entry.localPath, entry)\r\n if (!entry.localPath.startsWith('/')) {\r\n map.set('/' + entry.localPath, entry)\r\n }\r\n }\r\n\r\n if (debug) {\r\n console.log(`[dcs-cdn-image] Loaded ${data.images.length} entries from ${testPath}`)\r\n }\r\n return map\r\n } catch (error) {\r\n console.warn(`[dcs-cdn-image] Failed to parse ${testPath}:`, error)\r\n }\r\n }\r\n }\r\n\r\n if (debug) {\r\n console.log('[dcs-cdn-image] No cdn-image-map.json found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n }\r\n return null\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Vite Plugin\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Vite plugin that rewrites static `/images/` paths to CDN URLs at build time.\r\n */\r\nexport function dcsCdnImagePlugin(options: DcsCdnImagePluginOptions = {}): Plugin {\r\n const {\r\n mapPath = '.dcs/cdn-image-map.json',\r\n pathPrefixes = ['/images/'],\r\n defaultSizes = '(max-width: 1024px) 100vw, 1024px',\r\n debug = false,\r\n } = options\r\n\r\n // Lookup map built from the JSON file: key = localPath (no leading slash)\r\n let imageMap: Map<string, CdnImageMapEntry> | null = null\r\n let isProduction = false\r\n\r\n /**\r\n * Check whether a source code string contains any of the path prefixes\r\n * we care about. Quick bail-out for files that don't reference images.\r\n */\r\n function hasImageReferences(code: string): boolean {\r\n return pathPrefixes.some((prefix) => code.includes(prefix))\r\n }\r\n\r\n /**\r\n * Replace image path references in any string value with CDN URLs.\r\n * Used for code transforms and renderChunk post-processing.\r\n */\r\n function replaceImagePaths(str: string): string {\r\n if (!imageMap || imageMap.size === 0) return str\r\n let result = str\r\n for (const prefix of pathPrefixes) {\r\n // Global replace: matches prefix followed by a relative path bounded\r\n // by quotes, parens, or whitespace. Allows spaces in filenames.\r\n const regex = new RegExp(\r\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\r\n 'g'\r\n )\r\n result = result.replace(regex, (match, quote, relPath) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n return `${quote}${entry.cdnUrl}`\r\n })\r\n }\r\n return result\r\n }\r\n\r\n /**\r\n * Replace standalone URL references in JS/TS string literals and Vue template\r\n * attributes. This handles patterns like:\r\n * src=\"/images/foo.jpg\"\r\n * { src: '/images/foo.jpg' }\r\n * url(/images/foo.svg)\r\n *\r\n * For `<img>` tags with raster images that have variants, the entire tag is\r\n * replaced with a `<picture>` element.\r\n */\r\n function transformCode(code: string): string {\r\n if (!imageMap || imageMap.size === 0) return code\r\n\r\n // ── Pass 1: Replace <img> tags that reference mapped raster images ──────\r\n // Matches: <img ... src=\"/images/foo.jpg\" ... /> or <img ... src=\"/images/foo.jpg\" ... >\r\n const imgTagRegex = /<img\\b([^>]*?)src=[\"'](\\/(images\\/[^\"']+))[\"']([^>]*?)\\/?>/gi\r\n code = code.replace(imgTagRegex, (_match, beforeSrc, _srcWithSlash, localPath, afterSrc) => {\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return _match\r\n\r\n // Extract alt from the existing attributes\r\n const altMatch = (beforeSrc + afterSrc).match(/alt=[\"']([^\"']*)[\"']/)\r\n const alt = altMatch ? altMatch[1] : ''\r\n\r\n // Collect other attributes (not src, alt, loading, decoding — we set those)\r\n const otherAttrs = (beforeSrc + afterSrc)\r\n .replace(/\\balt=[\"'][^\"']*[\"']/gi, '')\r\n .replace(/\\bloading=[\"'][^\"']*[\"']/gi, '')\r\n .replace(/\\bdecoding=[\"'][^\"']*[\"']/gi, '')\r\n .trim()\r\n\r\n const extraAttrs = otherAttrs ? ' ' + otherAttrs : ''\r\n\r\n // Extract custom sizes if specified\r\n const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=[\"']([^\"']*)[\"']/)\r\n const sizes = sizesMatch ? sizesMatch[1] : defaultSizes\r\n\r\n // SVGs and images without variants → simple URL swap <img>\r\n if (!entry.variants || entry.variants.length === 0) {\r\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\r\n }\r\n\r\n // Raster with variants → <picture>\r\n return buildPictureElement(entry, alt, extraAttrs, sizes)\r\n })\r\n\r\n // ── Pass 2: Replace remaining string literal / CSS url() references ─────\r\n code = replaceImagePaths(code)\r\n\r\n return code\r\n }\r\n\r\n return {\r\n name: 'dcs-cdn-image',\r\n enforce: 'pre',\r\n\r\n configResolved(config) {\r\n isProduction = config.command === 'build'\r\n if (!isProduction) {\r\n if (debug) {\r\n console.log('[dcs-cdn-image] Dev mode — plugin disabled (local images served by Vite)')\r\n }\r\n return\r\n }\r\n imageMap = loadCdnImageMap(config.root, mapPath, debug)\r\n },\r\n\r\n transform(code, id) {\r\n // Only run in production builds\r\n if (!isProduction || !imageMap) return\r\n\r\n // Only process relevant file types\r\n if (!/\\.(vue|ts|tsx|js|jsx|css|scss|md|html)(\\?.*)?$/.test(id)) return\r\n\r\n // Quick bail — skip files with no image path references\r\n if (!hasImageReferences(code)) return\r\n\r\n const transformed = transformCode(code)\r\n if (transformed !== code) {\r\n return { code: transformed, map: null }\r\n }\r\n },\r\n\r\n /**\r\n * Replace image paths in final rendered JS/CSS chunks.\r\n *\r\n * This fires AFTER Vite's `define` substitution, catching paths that were\r\n * injected via `define` values (e.g. `__DCS_CONTENT__` set by\r\n * `dcsContentPlugin` from `.dcs/content.yaml`). Those paths bypass the\r\n * `transform` hook because `define` replacement happens at bundle time.\r\n */\r\n renderChunk(code, _chunk) {\r\n if (!isProduction || !imageMap) return null\r\n if (!hasImageReferences(code)) return null\r\n\r\n const replaced = replaceImagePaths(code)\r\n if (replaced !== code) {\r\n if (debug) {\r\n console.log(`[dcs-cdn-image] renderChunk: rewrote image paths in ${_chunk.fileName}`)\r\n }\r\n return { code: replaced, map: null }\r\n }\r\n return null\r\n },\r\n\r\n /**\r\n * Transform HTML output to catch references injected outside the module\r\n * pipeline — e.g. VitePress `head` config tags (favicons, OG images).\r\n *\r\n * Note: In VitePress, this hook fires for the `index.html` template but\r\n * NOT for SSR-rendered per-page HTML. Use `dcsCdnBuildEnd` for full\r\n * coverage of generated HTML files.\r\n */\r\n transformIndexHtml(html) {\r\n if (!isProduction || !imageMap) return html\r\n if (!hasImageReferences(html)) return html\r\n\r\n // Replace href/src/content attributes that reference mapped images\r\n for (const prefix of pathPrefixes) {\r\n const attrRegex = new RegExp(\r\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\r\n 'gi'\r\n )\r\n html = html.replace(attrRegex, (match, pre, relPath, post) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n return `${pre}${entry.cdnUrl}${post}`\r\n })\r\n }\r\n\r\n return html\r\n },\r\n }\r\n}\r\n\r\n/**\r\n * Escape a string for use in a RegExp.\r\n */\r\nfunction escapeRegex(str: string): string {\r\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// VitePress buildEnd hook\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nexport interface DcsCdnBuildEndOptions {\r\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\r\n mapPath?: string\r\n\r\n /**\r\n * Patterns to match for replacement.\r\n * Default: `['/images/']`\r\n */\r\n pathPrefixes?: string[]\r\n\r\n /** Enable debug logging */\r\n debug?: boolean\r\n\r\n /**\r\n * File extensions to process in the output directory.\r\n * Default: `['.html', '.js']`\r\n */\r\n extensions?: string[]\r\n}\r\n\r\n/**\r\n * VitePress `buildEnd` hook factory that post-processes generated HTML and\r\n * static files in the output directory to replace remaining `/images/` paths\r\n * with CDN URLs.\r\n *\r\n * VitePress generates HTML **after** both Vite builds complete, so Vite\r\n * plugin hooks (`closeBundle`, `transformIndexHtml`) cannot catch\r\n * SSR-rendered `<head>` tags (favicons, OG images). This hook runs after all\r\n * HTML files are written to disk.\r\n *\r\n * Also rewrites static files (e.g. `service-worker.js`) copied from\r\n * `public/` that are not part of Vite's module pipeline.\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * buildEnd: dcsCdnBuildEnd({ debug: true })\r\n * })\r\n * ```\r\n */\r\nexport function dcsCdnBuildEnd(options: DcsCdnBuildEndOptions = {}) {\r\n const {\r\n mapPath = '.dcs/cdn-image-map.json',\r\n pathPrefixes = ['/images/'],\r\n debug = false,\r\n extensions = ['.html', '.js'],\r\n } = options\r\n\r\n return async (siteConfig: { root: string; outDir: string }) => {\r\n const imageMap = loadCdnImageMap(siteConfig.root, mapPath, debug)\r\n if (!imageMap) {\r\n if (debug) {\r\n console.log('[dcs-cdn-image] buildEnd: no image map found')\r\n }\r\n return\r\n }\r\n\r\n const outDir = siteConfig.outDir\r\n if (!fs.existsSync(outDir)) return\r\n\r\n let filesProcessed = 0\r\n let refsReplaced = 0\r\n\r\n function walkDir(dir: string) {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true })\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name)\r\n if (entry.isDirectory()) {\r\n walkDir(fullPath)\r\n } else if (extensions.some((ext) => entry.name.endsWith(ext))) {\r\n processFile(fullPath)\r\n }\r\n }\r\n }\r\n\r\n function processFile(filePath: string) {\r\n let content = fs.readFileSync(filePath, 'utf8')\r\n if (!pathPrefixes.some((p) => content.includes(p))) return\r\n\r\n let changed = false\r\n\r\n if (filePath.endsWith('.html')) {\r\n // HTML: replace href/src/content attribute values\r\n for (const prefix of pathPrefixes) {\r\n const attrRegex = new RegExp(\r\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\r\n 'gi'\r\n )\r\n content = content.replace(attrRegex, (match, pre, relPath, post) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const mapEntry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!mapEntry) return match\r\n changed = true\r\n refsReplaced++\r\n return `${pre}${mapEntry.cdnUrl}${post}`\r\n })\r\n }\r\n } else {\r\n // JS/other: replace quoted string paths\r\n for (const prefix of pathPrefixes) {\r\n const regex = new RegExp(\r\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\r\n 'g'\r\n )\r\n content = content.replace(regex, (match, quote, relPath) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n changed = true\r\n refsReplaced++\r\n return `${quote}${entry.cdnUrl}`\r\n })\r\n }\r\n }\r\n\r\n if (changed) {\r\n fs.writeFileSync(filePath, content, 'utf8')\r\n filesProcessed++\r\n }\r\n }\r\n\r\n walkDir(outDir)\r\n\r\n if (debug) {\r\n console.log(\r\n `[dcs-cdn-image] buildEnd: post-processed ${filesProcessed} files, replaced ${refsReplaced} references`\r\n )\r\n }\r\n }\r\n}","/**\r\n * markdown-it plugin that transforms standard `![alt](url)` image syntax\r\n * into responsive `<picture>` elements when the URL matches the DCS CDN\r\n * asset pattern.\r\n *\r\n * Non-CDN images are rendered with the default image renderer (plain `<img>`).\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { responsiveImagePlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * markdown: {\r\n * config: (md) => {\r\n * md.use(responsiveImagePlugin)\r\n * },\r\n * },\r\n * })\r\n * ```\r\n *\r\n * Input markdown:\r\n * ```md\r\n * ![Physical therapy session](https://files.duffcloudservices.com/kept/assets/blog/abc-123.jpg)\r\n * ```\r\n *\r\n * Rendered HTML:\r\n * ```html\r\n * <picture>\r\n * <source srcset=\"...abc-123-sm.webp 640w, ...abc-123-md.webp 1024w, ...abc-123-lg.webp 1920w\"\r\n * type=\"image/webp\"\r\n * sizes=\"(max-width: 1024px) 100vw, 1024px\" />\r\n * <img src=\"...abc-123-md.webp\" alt=\"Physical therapy session\" loading=\"lazy\" decoding=\"async\" />\r\n * </picture>\r\n * ```\r\n */\r\n\r\nimport type MarkdownIt from 'markdown-it'\r\n\r\n/** Matches DCS CDN asset URLs: base path / UUID . extension */\r\nconst CDN_PATTERN = /^(https?:\\/\\/files\\.[^/]+\\/[^/]+\\/assets\\/(?:[^/]+\\/)*)([a-f0-9-]+)\\.(\\w+)$/\r\n\r\n/**\r\n * Escapes HTML special characters for safe attribute embedding.\r\n * Falls back to a simple replace chain when `md.utils.escapeHtml` is unavailable.\r\n */\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n}\r\n\r\nexport function responsiveImagePlugin(md: MarkdownIt): void {\r\n const defaultImageRenderer = md.renderer.rules.image\r\n\r\n md.renderer.rules.image = (tokens, idx, options, env, self) => {\r\n const token = tokens[idx]\r\n const src = token.attrGet('src') ?? ''\r\n const alt = token.content ?? ''\r\n\r\n const match = src.match(CDN_PATTERN)\r\n if (!match) {\r\n // Not a CDN image — render normally\r\n if (defaultImageRenderer) {\r\n return defaultImageRenderer(tokens, idx, options, env, self)\r\n }\r\n return self.renderToken(tokens, idx, options)\r\n }\r\n\r\n const [, basePath, uuid] = match\r\n\r\n const srcset = [\r\n `${basePath}${uuid}-sm.webp 640w`,\r\n `${basePath}${uuid}-md.webp 1024w`,\r\n `${basePath}${uuid}-lg.webp 1920w`,\r\n ].join(', ')\r\n\r\n const escapedAlt = escapeHtml(alt)\r\n\r\n return [\r\n '<picture>',\r\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"(max-width: 1024px) 100vw, 1024px\" />`,\r\n ` <img src=\"${basePath}${uuid}-md.webp\" alt=\"${escapedAlt}\" loading=\"lazy\" decoding=\"async\" />`,\r\n '</picture>',\r\n ].join('\\n')\r\n }\r\n}\r\n"]}
package/package.json CHANGED
@@ -1,82 +1,83 @@
1
- {
2
- "name": "@duffcloudservices/cms",
3
- "version": "0.2.0",
4
- "description": "Vue 3 composables and Vite plugins for DCS CMS integration",
5
- "type": "module",
6
- "exports": {
7
- ".": {
8
- "types": "./dist/index.d.ts",
9
- "import": "./dist/index.js"
10
- },
11
- "./plugins": {
12
- "types": "./dist/plugins/index.d.ts",
13
- "import": "./dist/plugins/index.js"
14
- },
15
- "./editor": {
16
- "types": "./dist/editor/editorBridge.d.ts",
17
- "import": "./dist/editor/editorBridge.js"
18
- },
19
- "./components": {
20
- "import": "./src/components/PreviewRibbon.vue"
21
- },
22
- "./responsive-image": {
23
- "import": "./src/components/ResponsiveImage.vue"
24
- }
25
- },
26
- "main": "./dist/index.js",
27
- "types": "./dist/index.d.ts",
28
- "files": [
29
- "dist",
30
- "src/components"
31
- ],
32
- "peerDependencies": {
33
- "vue": "^3.4.0",
34
- "@unhead/vue": "^1.9.0"
35
- },
36
- "dependencies": {
37
- "js-yaml": "^4.1.0",
38
- "@duffcloudservices/cms-core": "0.2.0"
39
- },
40
- "devDependencies": {
41
- "@types/js-yaml": "^4.0.9",
42
- "@types/markdown-it": "^14.1.0",
43
- "@types/node": "^20.11.0",
44
- "@vue/test-utils": "^2.4.0",
45
- "markdown-it": "^14.0.0",
46
- "tsup": "^8.0.0",
47
- "typescript": "~5.6.3",
48
- "vite": "^6.3.5",
49
- "vitest": "^3.2.3",
50
- "vue": "^3.5.16",
51
- "@unhead/vue": "^2.0.5"
52
- },
53
- "keywords": [
54
- "vue",
55
- "vitepress",
56
- "cms",
57
- "dcs",
58
- "composables",
59
- "duff-cloud-services"
60
- ],
61
- "author": "Duff Cloud Services",
62
- "license": "MIT",
63
- "repository": {
64
- "type": "git",
65
- "url": "https://github.com/duffn/dcs"
66
- },
67
- "homepage": "https://portal.duffcloudservices.com",
68
- "bugs": {
69
- "url": "https://github.com/duffn/dcs/issues"
70
- },
71
- "engines": {
72
- "node": ">=18.0.0"
73
- },
74
- "scripts": {
75
- "build": "tsup",
76
- "dev": "tsup --watch",
77
- "test": "vitest run",
78
- "test:watch": "vitest",
79
- "type-check": "tsc --noEmit",
80
- "lint": "eslint src --ext .ts"
81
- }
82
- }
1
+ {
2
+ "name": "@duffcloudservices/cms",
3
+ "version": "0.3.1",
4
+ "description": "Vue 3 composables and Vite plugins for DCS CMS integration",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./plugins": {
12
+ "types": "./dist/plugins/index.d.ts",
13
+ "import": "./dist/plugins/index.js"
14
+ },
15
+ "./editor": {
16
+ "types": "./dist/editor/editorBridge.d.ts",
17
+ "import": "./dist/editor/editorBridge.js"
18
+ },
19
+ "./components": {
20
+ "import": "./src/components/PreviewRibbon.vue"
21
+ },
22
+ "./responsive-image": {
23
+ "import": "./src/components/ResponsiveImage.vue"
24
+ }
25
+ },
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "files": [
29
+ "dist",
30
+ "src/components"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsup",
34
+ "dev": "tsup --watch",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "type-check": "tsc --noEmit",
38
+ "lint": "eslint src --ext .ts",
39
+ "prepublishOnly": "pnpm run build"
40
+ },
41
+ "peerDependencies": {
42
+ "vue": "^3.4.0",
43
+ "@unhead/vue": "^1.9.0"
44
+ },
45
+ "dependencies": {
46
+ "@duffcloudservices/cms-core": "workspace:*",
47
+ "js-yaml": "^4.1.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/js-yaml": "^4.0.9",
51
+ "@types/markdown-it": "^14.1.0",
52
+ "@types/node": "^20.11.0",
53
+ "@vue/test-utils": "^2.4.0",
54
+ "markdown-it": "^14.0.0",
55
+ "tsup": "^8.0.0",
56
+ "typescript": "~5.6.3",
57
+ "vite": "^6.3.5",
58
+ "vitest": "^3.2.3",
59
+ "vue": "^3.5.16",
60
+ "@unhead/vue": "^2.0.5"
61
+ },
62
+ "keywords": [
63
+ "vue",
64
+ "vitepress",
65
+ "cms",
66
+ "dcs",
67
+ "composables",
68
+ "duff-cloud-services"
69
+ ],
70
+ "author": "Duff Cloud Services",
71
+ "license": "MIT",
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "https://github.com/duffn/dcs"
75
+ },
76
+ "homepage": "https://portal.duffcloudservices.com",
77
+ "bugs": {
78
+ "url": "https://github.com/duffn/dcs/issues"
79
+ },
80
+ "engines": {
81
+ "node": ">=18.0.0"
82
+ }
83
+ }