@duffcloudservices/cms 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +244 -8
  2. package/dist/chunk-A5F4C72F.js +500 -0
  3. package/dist/chunk-A5F4C72F.js.map +1 -0
  4. package/dist/{chunk-F3EIWEZD.js → chunk-HVSF23P7.js} +971 -73
  5. package/dist/chunk-HVSF23P7.js.map +1 -0
  6. package/dist/editor/editorBridge.d.ts +53 -1
  7. package/dist/editor/editorBridge.js +141 -5
  8. package/dist/editor/editorBridge.js.map +1 -1
  9. package/dist/headHonesty-OzxvLuwd.d.ts +222 -0
  10. package/dist/index.d.ts +369 -22
  11. package/dist/index.js +424 -22
  12. package/dist/index.js.map +1 -1
  13. package/dist/installSeoHead-kWQwObez.d.ts +627 -0
  14. package/dist/plugins/index.d.ts +173 -6
  15. package/dist/plugins/index.js +628 -54
  16. package/dist/plugins/index.js.map +1 -1
  17. package/dist/seo/index.d.ts +763 -4
  18. package/dist/seo/index.js +2 -2
  19. package/dist/{vitepressTransform-DfmABXmK.d.ts → vitepressTransform-JG_zlaux.d.ts} +99 -6
  20. package/package.json +26 -16
  21. package/src/components/DcsCallButton.test.ts +58 -0
  22. package/src/components/DcsCallButton.vue +19 -4
  23. package/src/components/DcsReviewShowcase.vue +5 -1
  24. package/src/components/LiteMediaEmbed.vue +3 -3
  25. package/src/components/ManagedImage.test.ts +94 -0
  26. package/src/components/ManagedImage.vue +58 -6
  27. package/src/components/PreviewRibbon.vue +4 -1
  28. package/src/composables/useConversionTracking.test.ts +492 -0
  29. package/src/composables/useConversionTracking.ts +770 -0
  30. package/src/composables/useReleaseNotes.ts +7 -1
  31. package/src/composables/useResponsiveImage.ts +6 -0
  32. package/src/composables/useSEO.applyHead.test.ts +150 -0
  33. package/src/composables/useSEO.ts +63 -17
  34. package/src/composables/useSiteVersion.ts +4 -1
  35. package/src/composables/useSiteVisitorSession.test.ts +56 -0
  36. package/src/composables/useSiteVisitorSession.ts +39 -3
  37. package/src/composables/useTextContent.ts +9 -1
  38. package/dist/chunk-DAYLLSEE.js +0 -3
  39. package/dist/chunk-DAYLLSEE.js.map +0 -1
  40. package/dist/chunk-F3EIWEZD.js.map +0 -1
  41. package/dist/spliceHeadHtml-CsBEucGy.d.ts +0 -254
@@ -56,6 +56,58 @@ interface EditorReadyPayload {
56
56
  /** Keys of all [data-dcs-reviews] elements found on the page */
57
57
  reviewKeys: string[];
58
58
  }
59
+ /**
60
+ * Swap the live `<img>`(s) carrying a managed image key to a new source.
61
+ *
62
+ * Mirrors the self-heal invariant: a `<picture>` does NOT re-resolve after its
63
+ * `<source>` set changes, so the stale `<source>` siblings are removed and the
64
+ * `srcset` on the `<img>` cleared — otherwise the browser keeps serving the old
65
+ * variant. `data-dcs-image-url`/`-alt` are updated too so a subsequent
66
+ * right-click reports the new image, not the replaced one.
67
+ *
68
+ * @returns the number of elements updated (0 when the key isn't on the page).
69
+ */
70
+ declare function applyImageOverride(key: string, src: string, alt?: string | null): number;
71
+ /**
72
+ * Re-apply all pending image overrides. Called after a re-render (HMR / SPA nav)
73
+ * so a hot reload doesn't revert a not-yet-saved swap.
74
+ */
75
+ declare function applyPendingImageOverrides(): void;
76
+ /**
77
+ * Record a pending image override (test/utility seam for the replay path).
78
+ */
79
+ declare function recordPendingImageOverride(key: string, src: string, alt?: string | null): void;
80
+ /**
81
+ * Handle a `dcs:update-text` that actually targets a MANAGED IMAGE key.
82
+ *
83
+ * The portal's discard/undo paths (discardTextChanges / discardSingleTextChange
84
+ * in visualEditor.ts) revert EVERY staged key — including image keys — via the
85
+ * text revert callback, which sends `dcs:update-text`. `updateTextInPlace` only
86
+ * touches `data-text-key` elements, a no-op for image keys (which live on
87
+ * `data-dcs-image-key`), while `pendingImageOverrides` would keep re-applying
88
+ * the discarded src on every re-render — the discarded swap stayed sticky until
89
+ * a manual refresh. So: if the key has a pending image override (or a managed
90
+ * image element on the page), drop the sticky override and swap the live
91
+ * `<img>` back to the reverted URL. Covers discard-all, discard-single, undo,
92
+ * and reload-drops-staged-swap in one place.
93
+ *
94
+ * @returns true when the key was handled as an image revert (skip text handling).
95
+ */
96
+ declare function revertImageOverrideIfImageKey(key: string, value: string): boolean;
97
+ /**
98
+ * Handle a `dcs:update-text` that targets a MANAGED IMAGE **ALT** key
99
+ * (`data-dcs-image-alt-key`). The portal stages an alt change alongside the
100
+ * src change on "Replace without AI"; on discard/undo it reverts each staged
101
+ * key via `dcs:update-text`. Alt keys are attributes, not text nodes, so
102
+ * `updateTextInPlace` would be a silent no-op — instead restore the `<img>`
103
+ * alt and refresh any pending image override (keyed by the SRC key) so a
104
+ * re-render replay does not resurrect the discarded alt.
105
+ *
106
+ * @returns true when the key was handled as an image-alt revert.
107
+ */
108
+ declare function revertImageAltOverrideIfAltKey(key: string, value: string): boolean;
109
+ /** Clear all pending image overrides (test isolation). */
110
+ declare function clearPendingImageOverrides(): void;
59
111
  declare function initEditorBridge(): void;
60
112
 
61
- export { type EditorReadyPayload, type SectionInfo, initEditorBridge };
113
+ export { type EditorReadyPayload, type SectionInfo, applyImageOverride, applyPendingImageOverrides, clearPendingImageOverrides, initEditorBridge, recordPendingImageOverride, revertImageAltOverrideIfAltKey, revertImageOverrideIfImageKey };
@@ -193,6 +193,7 @@ function rediscoverAndNotify() {
193
193
  hasReviews: reviewKeys.length > 0,
194
194
  reviewKeys
195
195
  });
196
+ applyPendingImageOverrides();
196
197
  if (editorActive) {
197
198
  applyEditorToElements();
198
199
  if (sectionResizeObserver) {
@@ -217,6 +218,7 @@ function applyEditorToElements() {
217
218
  htmlEl.addEventListener("dblclick", handleTextKeyDblClick);
218
219
  htmlEl.addEventListener("blur", handleTextKeyBlur);
219
220
  htmlEl.addEventListener("keydown", handleTextKeyKeydown);
221
+ htmlEl.addEventListener("paste", handleTextKeyPaste);
220
222
  htmlEl.addEventListener("mouseenter", () => {
221
223
  if (activeEditElement) return;
222
224
  const key = getTextKey(htmlEl);
@@ -387,12 +389,21 @@ function handleTextKeyDblClick(e) {
387
389
  postToParent("dcs:text-key-dblclick", { key, sectionId });
388
390
  startInlineEdit(el, key, sectionId);
389
391
  }
392
+ function supportsPlaintextOnly() {
393
+ try {
394
+ const probe = document.createElement("div");
395
+ probe.contentEditable = "plaintext-only";
396
+ return probe.getAttribute("contenteditable") === "plaintext-only";
397
+ } catch {
398
+ return false;
399
+ }
400
+ }
390
401
  function startInlineEdit(el, key, sectionId) {
391
402
  if (activeEditElement && activeEditElement !== el) {
392
403
  finishEdit(activeEditElement);
393
404
  }
394
405
  originalTextValues.set(el, el.innerText.trim());
395
- el.setAttribute("contenteditable", "true");
406
+ el.setAttribute("contenteditable", supportsPlaintextOnly() ? "plaintext-only" : "true");
396
407
  el.classList.add("dcs-editing");
397
408
  el.focus();
398
409
  activeEditElement = el;
@@ -414,9 +425,44 @@ function handleTextKeyKeydown(e) {
414
425
  }
415
426
  if (e.key === "Escape") {
416
427
  e.preventDefault();
417
- e.currentTarget.blur();
428
+ cancelInlineEdit(e.currentTarget);
418
429
  }
419
430
  }
431
+ function handleTextKeyPaste(e) {
432
+ const el = e.currentTarget;
433
+ if (el !== activeEditElement) return;
434
+ if (el.getAttribute("contenteditable") === "plaintext-only") return;
435
+ const text = e.clipboardData?.getData("text/plain") ?? "";
436
+ e.preventDefault();
437
+ insertPlainTextAtCaret(el, text);
438
+ }
439
+ function insertPlainTextAtCaret(el, text) {
440
+ const selection = getSelection();
441
+ if (!selection || selection.rangeCount === 0) {
442
+ el.appendChild(document.createTextNode(text));
443
+ return;
444
+ }
445
+ const range = selection.getRangeAt(0);
446
+ if (!el.contains(range.commonAncestorContainer)) return;
447
+ range.deleteContents();
448
+ if (text) {
449
+ const node = document.createTextNode(text);
450
+ range.insertNode(node);
451
+ range.setStartAfter(node);
452
+ }
453
+ range.collapse(true);
454
+ selection.removeAllRanges();
455
+ selection.addRange(range);
456
+ }
457
+ function cancelInlineEdit(el) {
458
+ if (!el.hasAttribute("contenteditable")) return;
459
+ const original = originalTextValues.get(el);
460
+ if (original !== void 0) {
461
+ el.innerText = original;
462
+ }
463
+ el.blur();
464
+ finishEdit(el);
465
+ }
420
466
  function finishEdit(el) {
421
467
  if (!el.hasAttribute("contenteditable")) return;
422
468
  el.removeAttribute("contenteditable");
@@ -663,6 +709,83 @@ function createImageEditIcon(label) {
663
709
  function getManagedImageKey(img) {
664
710
  return img.dataset.dcsImageKey || img.closest("[data-dcs-image-key]")?.dataset.dcsImageKey || null;
665
711
  }
712
+ function getManagedImageAltKey(img) {
713
+ return img.dataset.dcsImageAltKey || img.closest("[data-dcs-image-alt-key]")?.dataset.dcsImageAltKey || null;
714
+ }
715
+ var pendingImageOverrides = /* @__PURE__ */ new Map();
716
+ function cssEscapeAttributeValue(value) {
717
+ const cssGlobal = globalThis.CSS;
718
+ if (typeof cssGlobal?.escape === "function") return cssGlobal.escape(value);
719
+ return value.replace(/[\\"]/g, "\\$&");
720
+ }
721
+ function applyImageOverride(key, src, alt) {
722
+ if (!key) return 0;
723
+ const escapedKey = cssEscapeAttributeValue(key);
724
+ const targets = /* @__PURE__ */ new Set();
725
+ document.querySelectorAll(`img[data-dcs-image-key="${escapedKey}"]`).forEach((img) => targets.add(img));
726
+ document.querySelectorAll(`[data-dcs-image-key="${escapedKey}"]`).forEach((el) => {
727
+ if (el instanceof HTMLImageElement) {
728
+ targets.add(el);
729
+ } else {
730
+ el.querySelectorAll("img").forEach((img) => targets.add(img));
731
+ }
732
+ });
733
+ targets.forEach((img) => {
734
+ img.closest("picture")?.querySelectorAll("source").forEach((s) => s.remove());
735
+ img.removeAttribute("srcset");
736
+ img.src = src;
737
+ img.dataset.dcsImageUrl = src;
738
+ if (alt != null) {
739
+ img.alt = alt;
740
+ img.dataset.dcsImageAlt = alt;
741
+ }
742
+ });
743
+ return targets.size;
744
+ }
745
+ function setImageOverride(key, src, alt) {
746
+ pendingImageOverrides.set(key, { src, alt });
747
+ applyImageOverride(key, src, alt);
748
+ scheduleRediscovery();
749
+ }
750
+ function applyPendingImageOverrides() {
751
+ for (const [key, override] of pendingImageOverrides) {
752
+ applyImageOverride(key, override.src, override.alt);
753
+ }
754
+ }
755
+ function recordPendingImageOverride(key, src, alt) {
756
+ pendingImageOverrides.set(key, { src, alt });
757
+ }
758
+ function revertImageOverrideIfImageKey(key, value) {
759
+ if (!key) return false;
760
+ const isPending = pendingImageOverrides.has(key);
761
+ const isImageElement = !isPending && !!document.querySelector(`[data-dcs-image-key="${cssEscapeAttributeValue(key)}"]`);
762
+ if (!isPending && !isImageElement) return false;
763
+ pendingImageOverrides.delete(key);
764
+ if (value) applyImageOverride(key, value);
765
+ return true;
766
+ }
767
+ function revertImageAltOverrideIfAltKey(key, value) {
768
+ if (!key) return false;
769
+ const escapedKey = cssEscapeAttributeValue(key);
770
+ const hosts = document.querySelectorAll(`[data-dcs-image-alt-key="${escapedKey}"]`);
771
+ if (hosts.length === 0) return false;
772
+ hosts.forEach((host) => {
773
+ const imgs = host instanceof HTMLImageElement ? [host] : Array.from(host.querySelectorAll("img"));
774
+ imgs.forEach((img) => {
775
+ img.alt = value;
776
+ img.dataset.dcsImageAlt = value;
777
+ });
778
+ const srcKey = host.dataset.dcsImageKey || host.closest("[data-dcs-image-key]")?.dataset.dcsImageKey;
779
+ if (srcKey) {
780
+ const pending = pendingImageOverrides.get(srcKey);
781
+ if (pending) pendingImageOverrides.set(srcKey, { ...pending, alt: value });
782
+ }
783
+ });
784
+ return true;
785
+ }
786
+ function clearPendingImageOverrides() {
787
+ pendingImageOverrides.clear();
788
+ }
666
789
  function showImageEditIcons(images) {
667
790
  if (!editingEnabled) return;
668
791
  if (imageEditIconTargets.length === images.length && images.every((img, i) => imageEditIconTargets[i] === img)) return;
@@ -689,6 +812,7 @@ function showImageEditIcons(images) {
689
812
  imageWidth: img.naturalWidth,
690
813
  imageHeight: img.naturalHeight,
691
814
  imageKey: getManagedImageKey(img),
815
+ imageAltKey: getManagedImageAltKey(img),
692
816
  sectionId: sectionEl?.dataset.section ?? null,
693
817
  sectionLabel: sectionEl?.dataset.sectionLabel ?? null
694
818
  });
@@ -1027,7 +1151,9 @@ function handleMessage(event) {
1027
1151
  case "dcs:update-text":
1028
1152
  if (data && typeof data === "object" && "key" in data && "value" in data) {
1029
1153
  const d = data;
1030
- updateTextInPlace(d.key, d.value);
1154
+ if (!revertImageOverrideIfImageKey(d.key, d.value) && !revertImageAltOverrideIfAltKey(d.key, d.value)) {
1155
+ updateTextInPlace(d.key, d.value);
1156
+ }
1031
1157
  }
1032
1158
  break;
1033
1159
  case "dcs:set-mode":
@@ -1066,6 +1192,14 @@ function handleMessage(event) {
1066
1192
  }
1067
1193
  }
1068
1194
  break;
1195
+ case "dcs:image-set":
1196
+ if (data && typeof data === "object" && "key" in data && "src" in data) {
1197
+ const d = data;
1198
+ if (typeof d.key === "string" && typeof d.src === "string") {
1199
+ setImageOverride(d.key, d.src, d.alt ?? null);
1200
+ }
1201
+ }
1202
+ break;
1069
1203
  case "dcs:update-reviews":
1070
1204
  if (data && typeof data === "object" && "key" in data) {
1071
1205
  const d = data;
@@ -1135,13 +1269,15 @@ function initEditorBridge() {
1135
1269
  imageWidth: topImage?.naturalWidth ?? null,
1136
1270
  imageHeight: topImage?.naturalHeight ?? null,
1137
1271
  imageKey: topImage ? getManagedImageKey(topImage) : null,
1272
+ imageAltKey: topImage ? getManagedImageAltKey(topImage) : null,
1138
1273
  // All images at click point (for overlapping image scenarios like before/after sliders)
1139
1274
  images: allImages.length > 1 ? allImages.map((img) => ({
1140
1275
  imageUrl: img.currentSrc ?? img.src,
1141
1276
  imageAlt: img.alt ?? null,
1142
1277
  imageWidth: img.naturalWidth,
1143
1278
  imageHeight: img.naturalHeight,
1144
- imageKey: getManagedImageKey(img)
1279
+ imageKey: getManagedImageKey(img),
1280
+ imageAltKey: getManagedImageAltKey(img)
1145
1281
  })) : null
1146
1282
  });
1147
1283
  }, true);
@@ -1182,6 +1318,6 @@ function initEditorBridge() {
1182
1318
  observeSectionLayout();
1183
1319
  }
1184
1320
 
1185
- export { initEditorBridge };
1321
+ export { applyImageOverride, applyPendingImageOverrides, clearPendingImageOverrides, initEditorBridge, recordPendingImageOverride, revertImageAltOverrideIfAltKey, revertImageOverrideIfImageKey };
1186
1322
  //# sourceMappingURL=editorBridge.js.map
1187
1323
  //# sourceMappingURL=editorBridge.js.map