@duffcloudservices/cms 0.3.13 → 0.3.15

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/README.md CHANGED
@@ -304,6 +304,29 @@ import { useTextContent } from '@duffcloudservices/cms'
304
304
 
305
305
  The API is the same, so no other code changes are needed.
306
306
 
307
+ ## DCS Visual Editor Bridge
308
+
309
+ For DCS-managed sites, `src/editor/editorBridge.ts` is the shared
310
+ discovery/runtime layer that turns site DOM markers into portal editing
311
+ entry points.
312
+
313
+ Current first-party surfaces:
314
+
315
+ - **Managed forms** — discovers `[data-form-key]`, reports
316
+ `hasManagedForms`/`managedFormIds`, and emits
317
+ `dcs:managed-form-click`
318
+ - **Reviews** — discovers `[data-dcs-reviews]`, reports
319
+ `hasReviews`/`reviewKeys`, emits `dcs:reviews-click`, and accepts
320
+ structured `dcs:update-reviews` data for live preview refresh
321
+
322
+ The bridge owns discovery and affordances; the portal owns the actual
323
+ editing workflow. Keep every entry point converged on one sheet per
324
+ component family instead of creating parallel editors.
325
+
326
+ See [`../FIRST-PARTY-COMPONENTS.md`](../FIRST-PARTY-COMPONENTS.md) for
327
+ the shared contract across runtime markers, bridge events, portal
328
+ workflows, publishing, rollout, and validation.
329
+
307
330
  ## License
308
331
 
309
332
  MIT
@@ -39,6 +39,10 @@ interface EditorReadyPayload {
39
39
  hasBlogContent: boolean;
40
40
  /** Current innerHTML of the blog content element, if present */
41
41
  blogContentHtml: string | null;
42
+ /** Whether the page has a [data-event-content] or [data-rich-content="event"] element */
43
+ hasEventContent: boolean;
44
+ /** Current innerHTML of the event content element, if present */
45
+ eventContentHtml: string | null;
42
46
  /** Current blog post title text from [data-blog-title], if present */
43
47
  blogTitle: string | null;
44
48
  /** Current blog post summary/description text from [data-blog-summary], if present */
@@ -12,15 +12,14 @@ function postToParent(type, data) {
12
12
  }
13
13
  var TEXT_KEY_SELECTOR = "[data-text-key], [data-dcs-text]";
14
14
  var REVIEW_SELECTOR = "[data-dcs-reviews]";
15
+ var BLOG_CONTENT_SELECTOR = "[data-blog-content]";
16
+ var EVENT_CONTENT_SELECTOR = '[data-event-content], [data-rich-content="event"]';
15
17
  function getTextKey(el) {
16
18
  return el.dataset.dcsText ?? el.dataset.textKey ?? "";
17
19
  }
18
20
  function textKeySelector(key) {
19
21
  return `[data-text-key="${key}"], [data-dcs-text="${key}"]`;
20
22
  }
21
- function reviewSelector(key) {
22
- return `[data-dcs-reviews="${key}"]`;
23
- }
24
23
  var editorActive = false;
25
24
  var bridgeInitialized = false;
26
25
  var activeEditElement = null;
@@ -44,6 +43,7 @@ var initializedSections = /* @__PURE__ */ new WeakSet();
44
43
  var initializedReviewElements = /* @__PURE__ */ new WeakSet();
45
44
  var initializedManagedFormElements = /* @__PURE__ */ new WeakSet();
46
45
  var blogContentInitialized = false;
46
+ var eventContentInitialized = false;
47
47
  var blogMetadataInitialized = false;
48
48
  function scheduleRediscovery() {
49
49
  if (rediscoveryTimer) clearTimeout(rediscoveryTimer);
@@ -148,6 +148,7 @@ function checkForNavigation() {
148
148
  removeArrayEditIcon();
149
149
  removeReviewEditIcon();
150
150
  blogContentInitialized = false;
151
+ eventContentInitialized = false;
151
152
  blogMetadataInitialized = false;
152
153
  setTimeout(() => {
153
154
  rediscoverAndNotify();
@@ -167,9 +168,12 @@ function rediscoverAndNotify() {
167
168
  const reviewKeys = discoverReviewKeys();
168
169
  const managedFormIds = discoverManagedFormIds();
169
170
  const contentHeight = measureContentHeight(sections);
170
- const blogEl = document.querySelector("[data-blog-content]");
171
+ const blogEl = document.querySelector(BLOG_CONTENT_SELECTOR);
171
172
  const hasBlogContent = !!blogEl;
172
173
  const blogContentHtml = blogEl ? blogEl.innerHTML : null;
174
+ const eventEl = document.querySelector(EVENT_CONTENT_SELECTOR);
175
+ const hasEventContent = !!eventEl;
176
+ const eventContentHtml = eventEl ? eventEl.innerHTML : null;
173
177
  const blogTitleEl = document.querySelector("[data-blog-title]");
174
178
  const blogSummaryEl = document.querySelector("[data-blog-summary]");
175
179
  const blogTitle = blogTitleEl ? blogTitleEl.textContent?.trim() ?? null : null;
@@ -180,6 +184,8 @@ function rediscoverAndNotify() {
180
184
  contentHeight,
181
185
  hasBlogContent,
182
186
  blogContentHtml,
187
+ hasEventContent,
188
+ eventContentHtml,
183
189
  blogTitle,
184
190
  blogSummary,
185
191
  hasManagedForms: managedFormIds.length > 0,
@@ -304,7 +310,7 @@ function applyEditorToElements() {
304
310
  });
305
311
  });
306
312
  if (!blogContentInitialized) {
307
- const blogContentEl = document.querySelector("[data-blog-content]");
313
+ const blogContentEl = document.querySelector(BLOG_CONTENT_SELECTOR);
308
314
  if (blogContentEl) {
309
315
  blogContentInitialized = true;
310
316
  blogContentEl.classList.add("dcs-blog-content");
@@ -325,6 +331,28 @@ function applyEditorToElements() {
325
331
  postToParent("dcs:blog-content-ready", { content: blogContentEl.innerHTML });
326
332
  }
327
333
  }
334
+ if (!eventContentInitialized) {
335
+ const eventContentEl = document.querySelector(EVENT_CONTENT_SELECTOR);
336
+ if (eventContentEl) {
337
+ eventContentInitialized = true;
338
+ eventContentEl.classList.add("dcs-blog-content");
339
+ eventContentEl.addEventListener("click", (e) => {
340
+ const target = e.target;
341
+ if (target.closest(TEXT_KEY_SELECTOR)) return;
342
+ e.preventDefault();
343
+ e.stopPropagation();
344
+ postToParent("dcs:blog-content-click", { textKey: "event-content" });
345
+ });
346
+ eventContentEl.addEventListener("dblclick", (e) => {
347
+ const target = e.target;
348
+ if (target.closest(TEXT_KEY_SELECTOR)) return;
349
+ e.preventDefault();
350
+ e.stopPropagation();
351
+ postToParent("dcs:blog-content-click", { textKey: "event-content" });
352
+ });
353
+ postToParent("dcs:blog-content-ready", { content: eventContentEl.innerHTML });
354
+ }
355
+ }
328
356
  if (!blogMetadataInitialized) {
329
357
  const titleEl = document.querySelector("[data-blog-title]");
330
358
  const summaryEl = document.querySelector("[data-blog-summary]");
@@ -1027,21 +1055,23 @@ function handleMessage(event) {
1027
1055
  break;
1028
1056
  case "dcs:update-blog-content":
1029
1057
  if (data && typeof data === "object" && "content" in data) {
1030
- const blogEl = document.querySelector("[data-blog-content]");
1031
- if (blogEl) {
1032
- blogEl.innerHTML = data.content;
1058
+ const contentEl = document.querySelector(BLOG_CONTENT_SELECTOR) ?? document.querySelector(EVENT_CONTENT_SELECTOR);
1059
+ if (contentEl) {
1060
+ contentEl.innerHTML = data.content;
1033
1061
  scheduleRediscovery();
1034
1062
  }
1035
1063
  }
1036
1064
  break;
1037
1065
  case "dcs:update-reviews":
1038
- if (data && typeof data === "object" && "key" in data && "html" in data) {
1066
+ if (data && typeof data === "object" && "key" in data) {
1039
1067
  const d = data;
1040
- const reviewEl = document.querySelector(reviewSelector(d.key));
1041
- if (reviewEl) {
1042
- reviewEl.innerHTML = d.html;
1043
- scheduleRediscovery();
1044
- }
1068
+ globalThis.dispatchEvent(new CustomEvent("dcs:reviews-updated", {
1069
+ detail: {
1070
+ key: d.key,
1071
+ reviews: Array.isArray(d.reviews) ? d.reviews : null
1072
+ }
1073
+ }));
1074
+ scheduleRediscovery();
1045
1075
  }
1046
1076
  break;
1047
1077
  }
@@ -1124,7 +1154,8 @@ function initEditorBridge() {
1124
1154
  const reviewKeys = discoverReviewKeys();
1125
1155
  const managedFormIds = discoverManagedFormIds();
1126
1156
  const contentHeight = measureContentHeight(sections);
1127
- const blogEl = document.querySelector("[data-blog-content]");
1157
+ const blogEl = document.querySelector(BLOG_CONTENT_SELECTOR);
1158
+ const eventEl = document.querySelector(EVENT_CONTENT_SELECTOR);
1128
1159
  const blogTitleEl = document.querySelector("[data-blog-title]");
1129
1160
  const blogSummaryEl = document.querySelector("[data-blog-summary]");
1130
1161
  postToParent("dcs:ready", {
@@ -1133,6 +1164,8 @@ function initEditorBridge() {
1133
1164
  contentHeight,
1134
1165
  hasBlogContent: !!blogEl,
1135
1166
  blogContentHtml: blogEl ? blogEl.innerHTML : null,
1167
+ hasEventContent: !!eventEl,
1168
+ eventContentHtml: eventEl ? eventEl.innerHTML : null,
1136
1169
  blogTitle: blogTitleEl ? blogTitleEl.textContent?.trim() ?? null : null,
1137
1170
  blogSummary: blogSummaryEl ? blogSummaryEl.textContent?.trim() ?? null : null,
1138
1171
  hasManagedForms: managedFormIds.length > 0,