@ohhwells/bridge 0.1.124 → 0.1.126

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/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  DropdownMenuItem: () => DropdownMenuItem,
47
47
  DropdownMenuSeparator: () => DropdownMenuSeparator,
48
48
  DropdownMenuTrigger: () => DropdownMenuTrigger,
49
+ EmptySection: () => EmptySection,
49
50
  ItemActionToolbar: () => ItemActionToolbar,
50
51
  ItemInteractionLayer: () => ItemInteractionLayer,
51
52
  LinkEditorPanel: () => LinkEditorPanel,
@@ -295,10 +296,14 @@ function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE
295
296
  var BRAND_NAME_KEY = "__ohw_brand_name";
296
297
  var BRAND_TITLE_KEY = "__ohw_site_title";
297
298
  var BRAND_FAVICON_LETTER_KEY = "__ohw_favicon_letter";
299
+ var SITE_DESCRIPTION_KEY = "__ohw_site_description";
300
+ var SITE_OG_IMAGE_KEY = "__ohw_og_image";
298
301
  var BRAND_CHROME_KEYS = /* @__PURE__ */ new Set([
299
302
  BRAND_NAME_KEY,
300
303
  BRAND_TITLE_KEY,
301
- BRAND_FAVICON_LETTER_KEY
304
+ BRAND_FAVICON_LETTER_KEY,
305
+ SITE_DESCRIPTION_KEY,
306
+ SITE_OG_IMAGE_KEY
302
307
  ]);
303
308
  function upsertMeta(selector, attr, token, value) {
304
309
  let el = document.head.querySelector(selector);
@@ -309,6 +314,9 @@ function upsertMeta(selector, attr, token, value) {
309
314
  }
310
315
  if (el.getAttribute("content") !== value) el.setAttribute("content", value);
311
316
  }
317
+ function removeMeta(selector) {
318
+ document.head.querySelector(selector)?.remove();
319
+ }
312
320
  function escapeXml(value) {
313
321
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
314
322
  }
@@ -324,6 +332,22 @@ function applyLetterFavicon(letter) {
324
332
  link.type = "image/svg+xml";
325
333
  if (link.href !== href) link.href = href;
326
334
  }
335
+ function applyDescription(description) {
336
+ if (!description) return;
337
+ upsertMeta('meta[name="description"]', "name", "description", description);
338
+ upsertMeta('meta[property="og:description"]', "property", "og:description", description);
339
+ upsertMeta('meta[name="twitter:description"]', "name", "twitter:description", description);
340
+ }
341
+ function applyOgImage(url) {
342
+ if (url) {
343
+ upsertMeta('meta[property="og:image"]', "property", "og:image", url);
344
+ upsertMeta('meta[name="twitter:image"]', "name", "twitter:image", url);
345
+ upsertMeta('meta[name="twitter:card"]', "name", "twitter:card", "summary_large_image");
346
+ return;
347
+ }
348
+ removeMeta('meta[property="og:image"]');
349
+ removeMeta('meta[name="twitter:image"]');
350
+ }
327
351
  function applyBrandChrome(content) {
328
352
  const name = content[BRAND_NAME_KEY];
329
353
  if (typeof name === "string" && name.length > 0) {
@@ -342,6 +366,13 @@ function applyBrandChrome(content) {
342
366
  if (typeof letter === "string" && letter.length > 0) {
343
367
  applyLetterFavicon(letter);
344
368
  }
369
+ const description = content[SITE_DESCRIPTION_KEY];
370
+ if (typeof description === "string" && description.length > 0) {
371
+ applyDescription(description);
372
+ }
373
+ if (Object.prototype.hasOwnProperty.call(content, SITE_OG_IMAGE_KEY)) {
374
+ applyOgImage(String(content[SITE_OG_IMAGE_KEY] ?? "").trim());
375
+ }
345
376
  }
346
377
 
347
378
  // src/lib/brand-kit.ts
@@ -19618,21 +19649,10 @@ function parseSchedulingInsertAfter(insertAfter) {
19618
19649
  insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
19619
19650
  };
19620
19651
  }
19621
- function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
19622
- const parsed = parseSchedulingInsertAfter(insertAfter);
19623
- const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
19624
- const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
19625
- return { effectiveInsertAfter, insertBefore };
19626
- }
19627
- function getSchedulingMountPoint(insertAfter) {
19628
- const { anchor } = parseSchedulingInsertAfter(insertAfter);
19629
- let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
19630
- if (!anchorEl && anchor === "scheduling") {
19631
- const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
19632
- anchorEl = widgets.at(-1) ?? null;
19633
- }
19634
- if (!anchorEl) return null;
19635
- return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
19652
+ function resolveEntryAnchor(entry) {
19653
+ if (entry.anchorId !== void 0) return { anchorId: entry.anchorId, beforeId: entry.beforeId ?? null };
19654
+ const parsed = parseSchedulingInsertAfter(entry.insertAfter);
19655
+ return { anchorId: parsed.anchor, beforeId: parsed.insertBefore };
19636
19656
  }
19637
19657
  function schedulingMountDepth(insertAfter) {
19638
19658
  if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
@@ -19649,8 +19669,7 @@ function getPageSchedulingEntries(raw) {
19649
19669
  }
19650
19670
  }
19651
19671
  function isSchedulingWidgetMissing(entry) {
19652
- const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
19653
- return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
19672
+ return !document.querySelector(`[data-ohw-section="${schedulingSectionId(entry.insertAfter)}"]`);
19654
19673
  }
19655
19674
  function hasMissingSchedulingWidgets(entries) {
19656
19675
  return entries.some(isSchedulingWidgetMissing);
@@ -19688,17 +19707,18 @@ function initSectionsFromContent(content, removeExisting = false, currentPath =
19688
19707
  } catch {
19689
19708
  }
19690
19709
  }
19691
- function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
19692
- const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
19693
- const sectionId = schedulingSectionId(effectiveInsertAfter);
19710
+ function mountSchedulingWidget(anchorId, notifyOnConnect = false, scheduleId, beforeId, existingWidgetId) {
19711
+ const widgetId = existingWidgetId ?? (beforeId ? `${anchorId}${INSERT_BEFORE_MARKER}${beforeId}` : anchorId);
19712
+ const sectionId = schedulingSectionId(widgetId);
19694
19713
  if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
19695
- const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
19696
- if (!mountPoint) return false;
19714
+ const anchorEl = document.querySelector(`[data-ohw-section="${anchorId}"]`);
19715
+ if (!anchorEl) return false;
19716
+ const mountPoint = anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
19697
19717
  const container = document.createElement("div");
19698
19718
  container.dataset.ohwSectionContainer = "scheduling";
19699
19719
  container.dataset.ohwInstance = sectionId;
19700
- if (insertBefore) {
19701
- const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
19720
+ if (beforeId) {
19721
+ const beforeAnchor = document.querySelector(`[data-ohw-section="${beforeId}"]`);
19702
19722
  const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
19703
19723
  if (!beforePoint) return false;
19704
19724
  beforePoint.insertAdjacentElement("beforebegin", container);
@@ -19709,20 +19729,26 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
19709
19729
  }
19710
19730
  tail.insertAdjacentElement("afterend", container);
19711
19731
  }
19712
- const root = (0, import_client2.createRoot)(container);
19713
- schedulingRoots.set(container, root);
19714
- (0, import_react_dom4.flushSync)(() => {
19715
- root.render(
19716
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19717
- SchedulingWidget,
19718
- {
19719
- notifyOnConnect,
19720
- initialScheduleId: scheduleId,
19721
- insertAfter: effectiveInsertAfter
19722
- }
19723
- )
19724
- );
19725
- });
19732
+ try {
19733
+ const root = (0, import_client2.createRoot)(container);
19734
+ schedulingRoots.set(container, root);
19735
+ (0, import_react_dom4.flushSync)(() => {
19736
+ root.render(
19737
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
19738
+ SchedulingWidget,
19739
+ {
19740
+ notifyOnConnect,
19741
+ initialScheduleId: scheduleId,
19742
+ insertAfter: widgetId
19743
+ }
19744
+ )
19745
+ );
19746
+ });
19747
+ } catch (err) {
19748
+ console.error("[ow:scheduling] render threw", err);
19749
+ container.remove();
19750
+ return false;
19751
+ }
19726
19752
  const tracker = getSectionsTracker();
19727
19753
  let sections = [];
19728
19754
  try {
@@ -19730,10 +19756,12 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
19730
19756
  } catch {
19731
19757
  }
19732
19758
  const inEditor = typeof window !== "undefined" && window.self !== window.top;
19733
- if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
19759
+ if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === widgetId)) {
19734
19760
  sections.push({
19735
19761
  type: "scheduling",
19736
- insertAfter: effectiveInsertAfter,
19762
+ insertAfter: widgetId,
19763
+ anchorId,
19764
+ beforeId: beforeId ?? null,
19737
19765
  pagePath: window.location.pathname,
19738
19766
  ...scheduleId ? { scheduleId } : {}
19739
19767
  });
@@ -19747,7 +19775,8 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
19747
19775
  for (let i = pending.length - 1; i >= 0; i--) {
19748
19776
  const entry = pending[i];
19749
19777
  const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
19750
- if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId ?? null)) {
19778
+ const { anchorId, beforeId } = resolveEntryAnchor(entry);
19779
+ if (mountSchedulingWidget(anchorId, shouldNotify, entry.scheduleId ?? null, beforeId, entry.insertAfter)) {
19751
19780
  pending.splice(i, 1);
19752
19781
  }
19753
19782
  }
@@ -20810,6 +20839,7 @@ function StateToggle({
20810
20839
  );
20811
20840
  }
20812
20841
  var contentCache = /* @__PURE__ */ new Map();
20842
+ var fetchedContentPaths = /* @__PURE__ */ new Set();
20813
20843
  var brandingCache = /* @__PURE__ */ new Map();
20814
20844
  var OHW_LOADER_STYLE = {
20815
20845
  position: "fixed",
@@ -21016,6 +21046,7 @@ function OhhwellsBridge() {
21016
21046
  if (!subdomainFromQuery || typeof window === "undefined") return;
21017
21047
  const url = new URL(window.location.href);
21018
21048
  if (!url.searchParams.has("subdomain")) return;
21049
+ if (!resolveSubdomain(null)) return;
21019
21050
  url.searchParams.delete("subdomain");
21020
21051
  window.history.replaceState(null, "", url.pathname + url.search + url.hash);
21021
21052
  }, [subdomainFromQuery]);
@@ -23062,7 +23093,9 @@ function OhhwellsBridge() {
23062
23093
  let cancelled = false;
23063
23094
  setFetchState("loading");
23064
23095
  const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
23065
- fetch(`${apiUrl}/api/public/sites/${subdomain}/content`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
23096
+ const initialPath = pathname;
23097
+ fetchedContentPaths.add(`${subdomain}::${initialPath}`);
23098
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(initialPath)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
23066
23099
  if (cancelled) return;
23067
23100
  const content = data?.content ?? {};
23068
23101
  const branding = Boolean(data?.showBranding);
@@ -23259,6 +23292,17 @@ function OhhwellsBridge() {
23259
23292
  debounceTimer = setTimeout(applyFromCache, 150);
23260
23293
  };
23261
23294
  applyFromCache();
23295
+ const pathCacheKey = `${subdomain}::${pathname}`;
23296
+ if (!fetchedContentPaths.has(pathCacheKey)) {
23297
+ fetchedContentPaths.add(pathCacheKey);
23298
+ const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
23299
+ fetch(`${apiUrl}/api/public/sites/${subdomain}/content?path=${encodeURIComponent(pathname)}`, { cache: "no-store" }).then((r2) => r2.ok ? r2.json() : null).then((data) => {
23300
+ if (!data?.content) return;
23301
+ contentCache.set(subdomain, data.content);
23302
+ applyFromCache();
23303
+ }).catch(() => {
23304
+ });
23305
+ }
23262
23306
  observer = new MutationObserver(scheduleApply);
23263
23307
  observer.observe(document.body, { childList: true, subtree: true });
23264
23308
  return () => {
@@ -26123,6 +26167,21 @@ function OhhwellsBridge() {
26123
26167
  type: "text",
26124
26168
  text: editContentRef.current[CUSTOM_CODE_AFTER_HEAD_KEY] ?? ""
26125
26169
  });
26170
+ nodes.push({
26171
+ key: BRAND_TITLE_KEY,
26172
+ type: "text",
26173
+ text: editContentRef.current[BRAND_TITLE_KEY] ?? ""
26174
+ });
26175
+ nodes.push({
26176
+ key: SITE_DESCRIPTION_KEY,
26177
+ type: "text",
26178
+ text: editContentRef.current[SITE_DESCRIPTION_KEY] ?? ""
26179
+ });
26180
+ nodes.push({
26181
+ key: SITE_OG_IMAGE_KEY,
26182
+ type: "text",
26183
+ text: editContentRef.current[SITE_OG_IMAGE_KEY] ?? ""
26184
+ });
26126
26185
  document.querySelectorAll('[data-ohw-editable="form"]').forEach((form) => {
26127
26186
  const formKey = formKeyOf(form);
26128
26187
  if (!formKey) return;
@@ -26145,8 +26204,12 @@ function OhhwellsBridge() {
26145
26204
  if (inserted) {
26146
26205
  const tracker = getSectionsTracker();
26147
26206
  postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
26148
- const h = document.body.scrollHeight;
26149
- if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
26207
+ const reportHeight = () => {
26208
+ const h = document.body.scrollHeight;
26209
+ if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
26210
+ };
26211
+ reportHeight();
26212
+ setTimeout(reportHeight, 500);
26150
26213
  }
26151
26214
  };
26152
26215
  const handleSwitchSchedule = (e) => {
@@ -26872,7 +26935,7 @@ function OhhwellsBridge() {
26872
26935
  postToParent2({
26873
26936
  type: "ow:ready",
26874
26937
  version: "1",
26875
- bridgeVersion: "0.1.123",
26938
+ bridgeVersion: "0.1.125",
26876
26939
  path: pathname,
26877
26940
  nodes: collectEditableNodes(editContentRef.current),
26878
26941
  sections
@@ -27959,6 +28022,59 @@ function OhhwellsBridge() {
27959
28022
  ) : null
27960
28023
  ] });
27961
28024
  }
28025
+
28026
+ // src/ui/EmptySection.tsx
28027
+ var import_link = __toESM(require("next/link"), 1);
28028
+ var import_jsx_runtime37 = require("react/jsx-runtime");
28029
+ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey }) {
28030
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
28031
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
28032
+ "p",
28033
+ {
28034
+ style: {
28035
+ fontFamily: "var(--brand-font-body)",
28036
+ fontSize: "0.75rem",
28037
+ fontWeight: 500,
28038
+ letterSpacing: "0.15em",
28039
+ textTransform: "uppercase",
28040
+ color: "var(--brand-accent)",
28041
+ marginBottom: "1.5rem"
28042
+ },
28043
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_link.default, { href: homeHref, style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { ...eyebrowKey ? { "data-ohw-editable": "text", "data-ohw-key": eyebrowKey } : {}, children: "Home" }) })
28044
+ }
28045
+ ),
28046
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
28047
+ "h1",
28048
+ {
28049
+ style: {
28050
+ fontFamily: "var(--brand-font-heading)",
28051
+ fontSize: "clamp(2rem, 3.5vw, 2.75rem)",
28052
+ lineHeight: 1.1,
28053
+ letterSpacing: "-0.025em",
28054
+ color: "var(--brand-text)",
28055
+ marginBottom: "1rem"
28056
+ },
28057
+ ...titleKey ? { "data-ohw-editable": "text", "data-ohw-key": titleKey, "data-ohw-max-length": 80 } : {},
28058
+ children: title
28059
+ }
28060
+ ),
28061
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
28062
+ "p",
28063
+ {
28064
+ style: {
28065
+ fontFamily: "var(--brand-font-body)",
28066
+ fontSize: "1rem",
28067
+ lineHeight: 1.7,
28068
+ fontWeight: 300,
28069
+ color: "var(--brand-text-muted)",
28070
+ maxWidth: "340px"
28071
+ },
28072
+ ...subtitleKey ? { "data-ohw-editable": "text", "data-ohw-key": subtitleKey, "data-ohw-max-length": 160 } : {},
28073
+ children: "This page doesn't have any content yet."
28074
+ }
28075
+ )
28076
+ ] });
28077
+ }
27962
28078
  // Annotate the CommonJS export names for ESM import in node:
27963
28079
  0 && (module.exports = {
27964
28080
  AI_DEFAULT_BRAND,
@@ -27976,6 +28092,7 @@ function OhhwellsBridge() {
27976
28092
  DropdownMenuItem,
27977
28093
  DropdownMenuSeparator,
27978
28094
  DropdownMenuTrigger,
28095
+ EmptySection,
27979
28096
  ItemActionToolbar,
27980
28097
  ItemInteractionLayer,
27981
28098
  LinkEditorPanel,