@duffcloudservices/cms 0.3.10 → 0.3.12
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.
- package/dist/editor/editorBridge.d.ts +8 -0
- package/dist/editor/editorBridge.js +194 -3
- package/dist/editor/editorBridge.js.map +1 -1
- package/dist/index.d.ts +36 -1
- package/dist/index.js +34 -1
- package/dist/index.js.map +1 -1
- package/package.json +90 -83
- package/src/components/DcsReviewShowcase.vue +326 -0
- package/src/composables/index.ts +6 -0
- package/src/composables/useReviewContent.ts +92 -0
|
@@ -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", {
|
|
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
|
-
|
|
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
|
}
|