@ohhwells/bridge 0.1.155 → 0.1.156

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
@@ -83,6 +83,7 @@ __export(index_exports, {
83
83
  foldAlignIntoTrees: () => foldAlignIntoTrees,
84
84
  getEditModeInitialState: () => getEditModeInitialState,
85
85
  hasMountedContentNodes: () => hasMountedContentNodes,
86
+ indexCollectionItems: () => indexCollectionItems,
86
87
  insertFooterHours: () => insertFooterHours,
87
88
  isEditSessionActive: () => isEditSessionActive,
88
89
  isPlainLeftClick: () => isPlainLeftClick,
@@ -90,6 +91,7 @@ __export(index_exports, {
90
91
  isRenderableTree: () => isRenderableTree,
91
92
  isStickySearchParam: () => isStickySearchParam,
92
93
  isValidUrl: () => isValidUrl,
94
+ matchCollectionItemClick: () => matchCollectionItemClick,
93
95
  nextViableSlide: () => nextViableSlide,
94
96
  parseAiFooterState: () => parseAiFooterState,
95
97
  parseAiSectionsState: () => parseAiSectionsState,
@@ -11695,6 +11697,172 @@ function useLinkHrefGuardian(...deps) {
11695
11697
  }, deps);
11696
11698
  }
11697
11699
 
11700
+ // src/ui/link-modal/linkModal.utils.ts
11701
+ function parseTarget(target) {
11702
+ if (!target) return { pageRoute: "", sectionId: null };
11703
+ const hashIndex = target.indexOf("#");
11704
+ if (hashIndex === -1) return { pageRoute: target, sectionId: null };
11705
+ return {
11706
+ pageRoute: target.slice(0, hashIndex),
11707
+ sectionId: target.slice(hashIndex + 1) || null
11708
+ };
11709
+ }
11710
+ function normalizePath(path) {
11711
+ const trimmed = path.trim();
11712
+ if (!trimmed) return "/";
11713
+ try {
11714
+ if (/^https?:\/\//i.test(trimmed)) {
11715
+ return new URL(trimmed).pathname || "/";
11716
+ }
11717
+ } catch {
11718
+ }
11719
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
11720
+ if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
11721
+ return withSlash;
11722
+ }
11723
+ function findPageByPath(pages, path) {
11724
+ const normalized = normalizePath(path);
11725
+ return pages.find((p) => normalizePath(p.path) === normalized);
11726
+ }
11727
+ function inferPageTitleFromPath(path) {
11728
+ const normalized = normalizePath(path);
11729
+ if (normalized === "/") return "Home";
11730
+ const segment = normalized.split("/").filter(Boolean).pop() ?? "";
11731
+ if (!segment) return "Home";
11732
+ return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
11733
+ }
11734
+ function isInternalPath(route) {
11735
+ const trimmed = route.trim();
11736
+ if (!trimmed) return false;
11737
+ if (/^https?:\/\//i.test(trimmed)) return false;
11738
+ if (trimmed.startsWith("/")) return true;
11739
+ if (trimmed.includes(" ") || trimmed.includes(".")) return false;
11740
+ return true;
11741
+ }
11742
+ function resolvePage(route, pages) {
11743
+ const found = findPageByPath(pages, route);
11744
+ if (found) return { path: found.path, title: found.title };
11745
+ return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
11746
+ }
11747
+ function getSectionsForPath(sectionsByPath, pageRoute) {
11748
+ if (!sectionsByPath) return [];
11749
+ const normalized = normalizePath(pageRoute);
11750
+ return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
11751
+ }
11752
+ function buildTarget(page, section) {
11753
+ if (section) return `${page.path}#${section.id}`;
11754
+ return page.path;
11755
+ }
11756
+ function isValidUrl(urlString) {
11757
+ try {
11758
+ const url = new URL(urlString);
11759
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
11760
+ const hostname = url.hostname;
11761
+ if (!hostname || hostname.length === 0) return false;
11762
+ if (hostname.includes(" ")) return false;
11763
+ if (hostname === "localhost") return true;
11764
+ if (!hostname.includes(".")) return false;
11765
+ if (!/[a-z]/i.test(hostname)) return false;
11766
+ return true;
11767
+ } catch {
11768
+ return false;
11769
+ }
11770
+ }
11771
+ function normalizeUrl(value) {
11772
+ const trimmed = value.trim();
11773
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
11774
+ return `https://${trimmed}`;
11775
+ }
11776
+ function validateUrlInput(value, pages, allPages) {
11777
+ const trimmed = value.trim();
11778
+ if (!trimmed) return "";
11779
+ const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
11780
+ if (exactPage) return "";
11781
+ const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
11782
+ if (hasPrefixMatch) {
11783
+ return "Select a page from the list or enter a valid URL";
11784
+ }
11785
+ const hasScheme = /^https?:\/\//i.test(trimmed);
11786
+ if (!hasScheme && !trimmed.includes(".")) {
11787
+ return "Please enter a valid URL (e.g., https://example.com)";
11788
+ }
11789
+ if (!isValidUrl(normalizeUrl(trimmed))) {
11790
+ return "Please enter a valid URL (e.g., https://example.com)";
11791
+ }
11792
+ return "";
11793
+ }
11794
+ function getEditModeInitialState(target, pages, sections) {
11795
+ const empty = {
11796
+ searchValue: "",
11797
+ selectedPage: null,
11798
+ selectedSection: null,
11799
+ step: "input"
11800
+ };
11801
+ if (!target) return empty;
11802
+ if (isValidUrl(normalizeUrl(target))) {
11803
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
11804
+ }
11805
+ const { pageRoute, sectionId } = parseTarget(target);
11806
+ if (sectionId) {
11807
+ if (isInternalPath(pageRoute)) {
11808
+ const page = resolvePage(pageRoute, pages);
11809
+ const sec = sections.find((s) => s.id === sectionId);
11810
+ return {
11811
+ searchValue: page.title,
11812
+ selectedPage: page,
11813
+ selectedSection: sec ? { id: sec.id, label: sec.label } : null,
11814
+ step: "input"
11815
+ };
11816
+ }
11817
+ return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
11818
+ }
11819
+ if (isInternalPath(pageRoute)) {
11820
+ const page = resolvePage(pageRoute, pages);
11821
+ return {
11822
+ searchValue: page.title,
11823
+ selectedPage: page,
11824
+ selectedSection: null,
11825
+ step: "input"
11826
+ };
11827
+ }
11828
+ return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
11829
+ }
11830
+ function filterAvailablePages(pages, existingTargets) {
11831
+ const taken = new Set(existingTargets);
11832
+ return pages.filter((p) => !taken.has(p.path));
11833
+ }
11834
+
11835
+ // src/lib/collection-item-click.ts
11836
+ function indexCollectionItems(pages) {
11837
+ const index = /* @__PURE__ */ new Map();
11838
+ for (const page of pages) {
11839
+ if (page.kind !== "COLLECTION_ITEM" || !page.collection || !page.path) continue;
11840
+ index.set(normalizePath(page.path), { path: page.path, collection: page.collection });
11841
+ }
11842
+ return index;
11843
+ }
11844
+ function pathOfHref(href) {
11845
+ const trimmed = href.trim();
11846
+ if (!trimmed || trimmed.startsWith("#")) return null;
11847
+ if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) && !/^https?:/i.test(trimmed)) return null;
11848
+ const withoutHash = trimmed.split("#")[0];
11849
+ const withoutQuery = withoutHash.split("?")[0];
11850
+ if (!withoutQuery) return null;
11851
+ return normalizePath(withoutQuery);
11852
+ }
11853
+ function matchCollectionItemClick(target, index) {
11854
+ if (!target || index.size === 0) return null;
11855
+ const text = target.closest('[data-ohw-editable="text"], [data-ohw-editable="plain"]');
11856
+ if (!text) return null;
11857
+ const anchor = text.closest("a");
11858
+ if (!anchor || anchor.closest(CTA_BUTTON_SELECTOR)) return null;
11859
+ const href = anchor.getAttribute("href");
11860
+ if (!href) return null;
11861
+ const path = pathOfHref(href);
11862
+ if (!path) return null;
11863
+ return index.get(path) ?? null;
11864
+ }
11865
+
11698
11866
  // src/lib/sticky-search-params.ts
11699
11867
  var STICKY_EXACT = /* @__PURE__ */ new Set([
11700
11868
  // Site identity. Required in local dev and in the canvas editor's iframe, where there is no
@@ -17044,7 +17212,7 @@ function apiBase() {
17044
17212
  function bridgeVersion() {
17045
17213
  try {
17046
17214
  if (true) {
17047
- return "0.1.155";
17215
+ return "0.1.156";
17048
17216
  }
17049
17217
  } catch {
17050
17218
  }
@@ -17058,7 +17226,7 @@ function stampBridgeVersionOnDocument() {
17058
17226
  return;
17059
17227
  }
17060
17228
  try {
17061
- const v = true ? "0.1.155" : "";
17229
+ const v = true ? "0.1.156" : "";
17062
17230
  if (v) {
17063
17231
  document.documentElement.setAttribute("data-ohw-bridge-version", v);
17064
17232
  window.__OHW_BRIDGE_VERSION__ = v;
@@ -20714,141 +20882,6 @@ function isEditSessionActive() {
20714
20882
  return new URLSearchParams(q).get("mode") === "edit";
20715
20883
  }
20716
20884
 
20717
- // src/ui/link-modal/linkModal.utils.ts
20718
- function parseTarget(target) {
20719
- if (!target) return { pageRoute: "", sectionId: null };
20720
- const hashIndex = target.indexOf("#");
20721
- if (hashIndex === -1) return { pageRoute: target, sectionId: null };
20722
- return {
20723
- pageRoute: target.slice(0, hashIndex),
20724
- sectionId: target.slice(hashIndex + 1) || null
20725
- };
20726
- }
20727
- function normalizePath(path) {
20728
- const trimmed = path.trim();
20729
- if (!trimmed) return "/";
20730
- try {
20731
- if (/^https?:\/\//i.test(trimmed)) {
20732
- return new URL(trimmed).pathname || "/";
20733
- }
20734
- } catch {
20735
- }
20736
- const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
20737
- if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
20738
- return withSlash;
20739
- }
20740
- function findPageByPath(pages, path) {
20741
- const normalized = normalizePath(path);
20742
- return pages.find((p) => normalizePath(p.path) === normalized);
20743
- }
20744
- function inferPageTitleFromPath(path) {
20745
- const normalized = normalizePath(path);
20746
- if (normalized === "/") return "Home";
20747
- const segment = normalized.split("/").filter(Boolean).pop() ?? "";
20748
- if (!segment) return "Home";
20749
- return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
20750
- }
20751
- function isInternalPath(route) {
20752
- const trimmed = route.trim();
20753
- if (!trimmed) return false;
20754
- if (/^https?:\/\//i.test(trimmed)) return false;
20755
- if (trimmed.startsWith("/")) return true;
20756
- if (trimmed.includes(" ") || trimmed.includes(".")) return false;
20757
- return true;
20758
- }
20759
- function resolvePage(route, pages) {
20760
- const found = findPageByPath(pages, route);
20761
- if (found) return { path: found.path, title: found.title };
20762
- return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
20763
- }
20764
- function getSectionsForPath(sectionsByPath, pageRoute) {
20765
- if (!sectionsByPath) return [];
20766
- const normalized = normalizePath(pageRoute);
20767
- return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
20768
- }
20769
- function buildTarget(page, section) {
20770
- if (section) return `${page.path}#${section.id}`;
20771
- return page.path;
20772
- }
20773
- function isValidUrl(urlString) {
20774
- try {
20775
- const url = new URL(urlString);
20776
- if (url.protocol !== "http:" && url.protocol !== "https:") return false;
20777
- const hostname = url.hostname;
20778
- if (!hostname || hostname.length === 0) return false;
20779
- if (hostname.includes(" ")) return false;
20780
- if (hostname === "localhost") return true;
20781
- if (!hostname.includes(".")) return false;
20782
- if (!/[a-z]/i.test(hostname)) return false;
20783
- return true;
20784
- } catch {
20785
- return false;
20786
- }
20787
- }
20788
- function normalizeUrl(value) {
20789
- const trimmed = value.trim();
20790
- if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
20791
- return `https://${trimmed}`;
20792
- }
20793
- function validateUrlInput(value, pages, allPages) {
20794
- const trimmed = value.trim();
20795
- if (!trimmed) return "";
20796
- const exactPage = allPages.find((p) => p.title.toLowerCase() === trimmed.toLowerCase());
20797
- if (exactPage) return "";
20798
- const hasPrefixMatch = pages.some((p) => p.title.toLowerCase().startsWith(trimmed.toLowerCase()));
20799
- if (hasPrefixMatch) {
20800
- return "Select a page from the list or enter a valid URL";
20801
- }
20802
- const hasScheme = /^https?:\/\//i.test(trimmed);
20803
- if (!hasScheme && !trimmed.includes(".")) {
20804
- return "Please enter a valid URL (e.g., https://example.com)";
20805
- }
20806
- if (!isValidUrl(normalizeUrl(trimmed))) {
20807
- return "Please enter a valid URL (e.g., https://example.com)";
20808
- }
20809
- return "";
20810
- }
20811
- function getEditModeInitialState(target, pages, sections) {
20812
- const empty = {
20813
- searchValue: "",
20814
- selectedPage: null,
20815
- selectedSection: null,
20816
- step: "input"
20817
- };
20818
- if (!target) return empty;
20819
- if (isValidUrl(normalizeUrl(target))) {
20820
- return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
20821
- }
20822
- const { pageRoute, sectionId } = parseTarget(target);
20823
- if (sectionId) {
20824
- if (isInternalPath(pageRoute)) {
20825
- const page = resolvePage(pageRoute, pages);
20826
- const sec = sections.find((s) => s.id === sectionId);
20827
- return {
20828
- searchValue: page.title,
20829
- selectedPage: page,
20830
- selectedSection: sec ? { id: sec.id, label: sec.label } : null,
20831
- step: "input"
20832
- };
20833
- }
20834
- return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
20835
- }
20836
- if (isInternalPath(pageRoute)) {
20837
- const page = resolvePage(pageRoute, pages);
20838
- return {
20839
- searchValue: page.title,
20840
- selectedPage: page,
20841
- selectedSection: null,
20842
- step: "input"
20843
- };
20844
- }
20845
- return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
20846
- }
20847
- function filterAvailablePages(pages, existingTargets) {
20848
- const taken = new Set(existingTargets);
20849
- return pages.filter((p) => !taken.has(p.path));
20850
- }
20851
-
20852
20885
  // src/lib/prefetch-sections.ts
20853
20886
  function buildPageFetchUrl(path) {
20854
20887
  const normalized = normalizePath(path);
@@ -26007,6 +26040,7 @@ function OhhwellsBridge() {
26007
26040
  const appliedDrawerRef = (0, import_react21.useRef)(null);
26008
26041
  const pendingDeleteUndoRef = (0, import_react21.useRef)(null);
26009
26042
  const [sitePages, setSitePages] = (0, import_react21.useState)([]);
26043
+ const collectionItemsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
26010
26044
  const [sectionsByPath, setSectionsByPath] = (0, import_react21.useState)({});
26011
26045
  const sectionsPrefetchGenRef = (0, import_react21.useRef)(0);
26012
26046
  const setLinkPopoverRef = (0, import_react21.useRef)(setLinkPopover);
@@ -26150,10 +26184,12 @@ function OhhwellsBridge() {
26150
26184
  const onSitePages = (e) => {
26151
26185
  if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
26152
26186
  if (useFixtures) return;
26153
- const mapped = e.data.pages.map((p) => ({
26187
+ const incoming = e.data.pages;
26188
+ const mapped = incoming.map((p) => ({
26154
26189
  path: p.path,
26155
26190
  title: p.title
26156
26191
  }));
26192
+ collectionItemsRef.current = indexCollectionItems(incoming);
26157
26193
  setSitePages(mapped);
26158
26194
  runSectionsPrefetchRef.current(mapped);
26159
26195
  };
@@ -28573,6 +28609,17 @@ function OhhwellsBridge() {
28573
28609
  }
28574
28610
  if (isInsideFloatingPanel(target)) return;
28575
28611
  if (target.closest("[data-ohw-form-toolbar]")) return;
28612
+ const collectionItem = matchCollectionItemClick(target, collectionItemsRef.current);
28613
+ if (collectionItem) {
28614
+ e.preventDefault();
28615
+ e.stopPropagation();
28616
+ postToParentRef.current({
28617
+ type: "ow:collection-item-selected",
28618
+ path: collectionItem.path,
28619
+ collection: collectionItem.collection
28620
+ });
28621
+ return;
28622
+ }
28576
28623
  if (target.closest(
28577
28624
  '[data-ohw-field-toolbar], [data-ohw-field-type-picker], [data-radix-popper-content-wrapper], [role="menu"], [data-slot="dropdown-menu-content"]'
28578
28625
  )) {
@@ -31993,7 +32040,7 @@ function OhhwellsBridge() {
31993
32040
  postToParent2({
31994
32041
  type: "ow:ready",
31995
32042
  version: "1",
31996
- bridgeVersion: "0.1.155",
32043
+ bridgeVersion: "0.1.156",
31997
32044
  path: pathname,
31998
32045
  nodes: collectEditableNodes(editContentRef.current),
31999
32046
  sections
@@ -33219,6 +33266,7 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33219
33266
  foldAlignIntoTrees,
33220
33267
  getEditModeInitialState,
33221
33268
  hasMountedContentNodes,
33269
+ indexCollectionItems,
33222
33270
  insertFooterHours,
33223
33271
  isEditSessionActive,
33224
33272
  isPlainLeftClick,
@@ -33226,6 +33274,7 @@ function EmptySection({ title, homeHref = "/", eyebrowKey, titleKey, subtitleKey
33226
33274
  isRenderableTree,
33227
33275
  isStickySearchParam,
33228
33276
  isValidUrl,
33277
+ matchCollectionItemClick,
33229
33278
  nextViableSlide,
33230
33279
  parseAiFooterState,
33231
33280
  parseAiSectionsState,