@duffcloudservices/cms 0.3.9 → 0.3.11

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.
@@ -43,6 +43,14 @@ interface EditorReadyPayload {
43
43
  blogTitle: string | null;
44
44
  /** Current blog post summary/description text from [data-blog-summary], if present */
45
45
  blogSummary: string | null;
46
+ /** Whether the page has any managed form roots */
47
+ hasManagedForms: boolean;
48
+ /** Form IDs for all detected managed-form roots */
49
+ managedFormIds: string[];
50
+ /** Whether the page has any [data-dcs-reviews] elements */
51
+ hasReviews: boolean;
52
+ /** Keys of all [data-dcs-reviews] elements found on the page */
53
+ reviewKeys: string[];
46
54
  }
47
55
  declare function initEditorBridge(): void;
48
56
 
@@ -11,12 +11,16 @@ function postToParent(type, data) {
11
11
  globalThis.self.parent.postMessage({ type, data }, "*");
12
12
  }
13
13
  var TEXT_KEY_SELECTOR = "[data-text-key], [data-dcs-text]";
14
+ var REVIEW_SELECTOR = "[data-dcs-reviews]";
14
15
  function getTextKey(el) {
15
16
  return el.dataset.dcsText ?? el.dataset.textKey ?? "";
16
17
  }
17
18
  function textKeySelector(key) {
18
19
  return `[data-text-key="${key}"], [data-dcs-text="${key}"]`;
19
20
  }
21
+ function reviewSelector(key) {
22
+ return `[data-dcs-reviews="${key}"]`;
23
+ }
20
24
  var editorActive = false;
21
25
  var bridgeInitialized = false;
22
26
  var activeEditElement = null;
@@ -27,12 +31,16 @@ var arrayEditIconElement = null;
27
31
  var arrayEditIconTarget = null;
28
32
  var imageEditIconElements = [];
29
33
  var imageEditIconTargets = [];
34
+ var reviewEditIconElement = null;
35
+ var reviewEditIconTarget = null;
30
36
  var editingEnabled = true;
31
37
  var originalTextValues = /* @__PURE__ */ new Map();
32
38
  var sectionResizeObserver = null;
33
39
  var rediscoveryTimer = null;
34
40
  var initializedTextElements = /* @__PURE__ */ new WeakSet();
35
41
  var initializedSections = /* @__PURE__ */ new WeakSet();
42
+ var initializedReviewElements = /* @__PURE__ */ new WeakSet();
43
+ var initializedManagedFormElements = /* @__PURE__ */ new WeakSet();
36
44
  var blogContentInitialized = false;
37
45
  var blogMetadataInitialized = false;
38
46
  function scheduleRediscovery() {
@@ -65,6 +73,12 @@ function discoverTextKeys() {
65
73
  (el) => getTextKey(el)
66
74
  );
67
75
  }
76
+ function discoverReviewKeys() {
77
+ return Array.from(document.querySelectorAll(REVIEW_SELECTOR)).map((el) => el.dataset.dcsReviews ?? "").filter(Boolean);
78
+ }
79
+ function discoverManagedFormIds() {
80
+ return Array.from(document.querySelectorAll("[data-form-key]")).map((el) => el.dataset.formKey ?? "").filter(Boolean);
81
+ }
68
82
  function observeSectionLayout() {
69
83
  if (sectionResizeObserver) {
70
84
  sectionResizeObserver.disconnect();
@@ -130,6 +144,9 @@ function checkForNavigation() {
130
144
  }
131
145
  removeEditIcon();
132
146
  removeArrayEditIcon();
147
+ removeReviewEditIcon();
148
+ blogContentInitialized = false;
149
+ blogMetadataInitialized = false;
133
150
  setTimeout(() => {
134
151
  rediscoverAndNotify();
135
152
  }, 500);
@@ -145,6 +162,8 @@ function rediscoverAndNotify() {
145
162
  activeEditElement = null;
146
163
  const sections = discoverSections();
147
164
  const textKeys = discoverTextKeys();
165
+ const reviewKeys = discoverReviewKeys();
166
+ const managedFormIds = discoverManagedFormIds();
148
167
  const contentHeight = measureContentHeight(sections);
149
168
  const blogEl = document.querySelector("[data-blog-content]");
150
169
  const hasBlogContent = !!blogEl;
@@ -153,7 +172,19 @@ function rediscoverAndNotify() {
153
172
  const blogSummaryEl = document.querySelector("[data-blog-summary]");
154
173
  const blogTitle = blogTitleEl ? blogTitleEl.textContent?.trim() ?? null : null;
155
174
  const blogSummary = blogSummaryEl ? blogSummaryEl.textContent?.trim() ?? null : null;
156
- postToParent("dcs:ready", { sections, textKeys, contentHeight, hasBlogContent, blogContentHtml, blogTitle, blogSummary });
175
+ postToParent("dcs:ready", {
176
+ sections,
177
+ textKeys,
178
+ contentHeight,
179
+ hasBlogContent,
180
+ blogContentHtml,
181
+ blogTitle,
182
+ blogSummary,
183
+ hasManagedForms: managedFormIds.length > 0,
184
+ managedFormIds,
185
+ hasReviews: reviewKeys.length > 0,
186
+ reviewKeys
187
+ });
157
188
  if (editorActive) {
158
189
  applyEditorToElements();
159
190
  if (sectionResizeObserver) {
@@ -194,6 +225,20 @@ function applyEditorToElements() {
194
225
  removeArrayEditIcon();
195
226
  });
196
227
  });
228
+ const reviewElements = document.querySelectorAll(REVIEW_SELECTOR);
229
+ reviewElements.forEach((htmlEl) => {
230
+ if (initializedReviewElements.has(htmlEl)) return;
231
+ initializedReviewElements.add(htmlEl);
232
+ htmlEl.addEventListener("mouseenter", () => {
233
+ if (activeEditElement) return;
234
+ showReviewEditIcon(htmlEl);
235
+ });
236
+ htmlEl.addEventListener("mouseleave", (e) => {
237
+ const related = e.relatedTarget;
238
+ if (related && (related.classList?.contains("dcs-review-edit-icon") || related.closest?.(".dcs-review-edit-icon"))) return;
239
+ removeReviewEditIcon();
240
+ });
241
+ });
197
242
  const sections = document.querySelectorAll("[data-section]");
198
243
  sections.forEach((el) => {
199
244
  if (initializedSections.has(el)) return;
@@ -272,6 +317,23 @@ function applyEditorToElements() {
272
317
  summaryEl?.classList.add("dcs-blog-metadata");
273
318
  }
274
319
  }
320
+ const managedFormElements = document.querySelectorAll("[data-form-key]");
321
+ managedFormElements.forEach((formEl) => {
322
+ formEl.classList.add("dcs-managed-form");
323
+ if (initializedManagedFormElements.has(formEl)) return;
324
+ initializedManagedFormElements.add(formEl);
325
+ const openManagedForm = (e) => {
326
+ if (!editingEnabled) return;
327
+ e.preventDefault();
328
+ e.stopPropagation();
329
+ e.stopImmediatePropagation();
330
+ postToParent("dcs:managed-form-click", {
331
+ formId: formEl.dataset.formKey ?? null
332
+ });
333
+ };
334
+ formEl.addEventListener("click", openManagedForm, true);
335
+ formEl.addEventListener("dblclick", openManagedForm, true);
336
+ });
275
337
  }
276
338
  function handleTextKeyDblClick(e) {
277
339
  if (!editingEnabled) return;
@@ -446,6 +508,51 @@ function removeArrayEditIcon() {
446
508
  }
447
509
  arrayEditIconTarget = null;
448
510
  }
511
+ function createReviewEditIcon() {
512
+ const icon = document.createElement("button");
513
+ icon.className = "dcs-review-edit-icon";
514
+ icon.setAttribute("type", "button");
515
+ icon.setAttribute("title", "Edit Reviews");
516
+ icon.setAttribute("aria-label", "Edit reviews in panel");
517
+ icon.textContent = "\u2605";
518
+ icon.addEventListener("click", (e) => {
519
+ e.preventDefault();
520
+ e.stopPropagation();
521
+ e.stopImmediatePropagation();
522
+ if (!reviewEditIconTarget) return;
523
+ const el = reviewEditIconTarget;
524
+ const key = el.dataset.dcsReviews ?? "";
525
+ if (!key) return;
526
+ const sectionParent = el.closest("[data-section]");
527
+ const sectionId = sectionParent?.dataset.section ?? null;
528
+ postToParent("dcs:reviews-click", { key, sectionId });
529
+ removeReviewEditIcon();
530
+ }, true);
531
+ icon.addEventListener("mouseleave", (e) => {
532
+ const related = e.relatedTarget;
533
+ if (related && reviewEditIconTarget && (related === reviewEditIconTarget || reviewEditIconTarget.contains(related))) return;
534
+ removeReviewEditIcon();
535
+ });
536
+ return icon;
537
+ }
538
+ function showReviewEditIcon(el) {
539
+ if (!editingEnabled) return;
540
+ if (!reviewEditIconElement) {
541
+ reviewEditIconElement = createReviewEditIcon();
542
+ document.body.appendChild(reviewEditIconElement);
543
+ }
544
+ reviewEditIconTarget = el;
545
+ const rect = el.getBoundingClientRect();
546
+ reviewEditIconElement.style.top = `${rect.top + scrollY - 4}px`;
547
+ reviewEditIconElement.style.left = `${rect.right + scrollX - 28}px`;
548
+ reviewEditIconElement.style.display = "flex";
549
+ }
550
+ function removeReviewEditIcon() {
551
+ if (reviewEditIconElement) {
552
+ reviewEditIconElement.style.display = "none";
553
+ }
554
+ reviewEditIconTarget = null;
555
+ }
449
556
  function createImageEditIcon(label) {
450
557
  const icon = document.createElement("button");
451
558
  icon.className = "dcs-image-edit-icon";
@@ -630,6 +737,35 @@ function injectEditorStyles() {
630
737
  display: block;
631
738
  }
632
739
 
740
+ /* Floating review edit icon button */
741
+ .dcs-review-edit-icon {
742
+ position: absolute;
743
+ z-index: 99999;
744
+ display: none;
745
+ align-items: center;
746
+ justify-content: center;
747
+ width: 24px;
748
+ height: 24px;
749
+ padding: 0;
750
+ margin: 0;
751
+ border: 1.5px solid #f59e0b;
752
+ border-radius: 4px;
753
+ background: rgb(245 158 11 / 0.1);
754
+ backdrop-filter: blur(4px);
755
+ color: #f59e0b;
756
+ cursor: pointer;
757
+ pointer-events: auto;
758
+ transition: background 0.15s, transform 0.1s;
759
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12);
760
+ font-size: 14px;
761
+ line-height: 1;
762
+ }
763
+
764
+ .dcs-review-edit-icon:hover {
765
+ background: rgb(245 158 11 / 0.25);
766
+ transform: scale(1.1);
767
+ }
768
+
633
769
  /* Floating image edit icon button \u2014 green to distinguish from text (blue) and array (violet) */
634
770
  .dcs-image-edit-icon {
635
771
  position: absolute;
@@ -708,6 +844,42 @@ function injectEditorStyles() {
708
844
  opacity: 1;
709
845
  }
710
846
 
847
+ /* Managed form area \u2014 click to open the portal Form Manager */
848
+ [data-form-key].dcs-managed-form {
849
+ cursor: pointer;
850
+ border-radius: 12px;
851
+ position: relative;
852
+ transition: outline-color 0.2s, background-color 0.2s;
853
+ }
854
+
855
+ [data-form-key].dcs-managed-form:hover {
856
+ outline: 2px solid hsl(271 91% 65% / 0.65) !important;
857
+ outline-offset: 10px;
858
+ background-color: hsl(271 91% 65% / 0.04);
859
+ }
860
+
861
+ [data-form-key].dcs-managed-form::after {
862
+ content: 'Click to manage form';
863
+ position: absolute;
864
+ top: -28px;
865
+ left: 8px;
866
+ font-size: 11px;
867
+ font-weight: 600;
868
+ color: hsl(271 91% 65%);
869
+ background: hsl(271 91% 65% / 0.12);
870
+ border: 1px solid hsl(271 91% 65% / 0.3);
871
+ padding: 2px 8px;
872
+ border-radius: 4px;
873
+ opacity: 0;
874
+ transition: opacity 0.2s;
875
+ pointer-events: none;
876
+ white-space: nowrap;
877
+ }
878
+
879
+ [data-form-key].dcs-managed-form:hover::after {
880
+ opacity: 1;
881
+ }
882
+
711
883
  /* Blog metadata (title / summary) \u2014 click to edit */
712
884
  .dcs-blog-metadata {
713
885
  cursor: pointer;
@@ -767,6 +939,7 @@ function handleMessage(event) {
767
939
  }
768
940
  removeEditIcon();
769
941
  removeArrayEditIcon();
942
+ removeReviewEditIcon();
770
943
  removeAllImageEditIcons();
771
944
  document.querySelectorAll(".dcs-editable").forEach((el) => {
772
945
  el.classList.remove("dcs-editable");
@@ -783,6 +956,16 @@ function handleMessage(event) {
783
956
  }
784
957
  }
785
958
  break;
959
+ case "dcs:update-reviews":
960
+ if (data && typeof data === "object" && "key" in data && "html" in data) {
961
+ const d = data;
962
+ const reviewEl = document.querySelector(reviewSelector(d.key));
963
+ if (reviewEl) {
964
+ reviewEl.innerHTML = d.html;
965
+ scheduleRediscovery();
966
+ }
967
+ }
968
+ break;
786
969
  }
787
970
  }
788
971
  function injectEditorHeightFix() {
@@ -818,7 +1001,8 @@ function initEditorBridge() {
818
1001
  e.preventDefault();
819
1002
  const target = e.target;
820
1003
  const sectionEl = target.closest?.("[data-section]");
821
- let imgEl = target.tagName === "IMG" ? target : target.closest?.("picture")?.querySelector("img");
1004
+ const formEl = target.closest?.("[data-form-key]");
1005
+ const imgEl = target.tagName === "IMG" ? target : target.closest?.("picture")?.querySelector("img");
822
1006
  let allImages = [];
823
1007
  if (sectionEl) {
824
1008
  allImages = findAllImagesAtPoint(sectionEl, e.clientX, e.clientY);
@@ -832,6 +1016,7 @@ function initEditorBridge() {
832
1016
  y: e.clientY,
833
1017
  sectionId: sectionEl?.dataset.section ?? null,
834
1018
  sectionLabel: sectionEl?.dataset.sectionLabel ?? null,
1019
+ formId: formEl?.dataset.formKey ?? null,
835
1020
  // Single image context (backward compatible) — topmost image at click point
836
1021
  imageUrl: topImage?.currentSrc ?? topImage?.src ?? null,
837
1022
  imageAlt: topImage?.alt ?? null,
@@ -858,6 +1043,8 @@ function initEditorBridge() {
858
1043
  }
859
1044
  const sections = discoverSections();
860
1045
  const textKeys = discoverTextKeys();
1046
+ const reviewKeys = discoverReviewKeys();
1047
+ const managedFormIds = discoverManagedFormIds();
861
1048
  const contentHeight = measureContentHeight(sections);
862
1049
  const blogEl = document.querySelector("[data-blog-content]");
863
1050
  const blogTitleEl = document.querySelector("[data-blog-title]");
@@ -869,7 +1056,11 @@ function initEditorBridge() {
869
1056
  hasBlogContent: !!blogEl,
870
1057
  blogContentHtml: blogEl ? blogEl.innerHTML : null,
871
1058
  blogTitle: blogTitleEl ? blogTitleEl.textContent?.trim() ?? null : null,
872
- blogSummary: blogSummaryEl ? blogSummaryEl.textContent?.trim() ?? null : null
1059
+ blogSummary: blogSummaryEl ? blogSummaryEl.textContent?.trim() ?? null : null,
1060
+ hasManagedForms: managedFormIds.length > 0,
1061
+ managedFormIds,
1062
+ hasReviews: reviewKeys.length > 0,
1063
+ reviewKeys
873
1064
  });
874
1065
  observeSectionLayout();
875
1066
  }