@bygghammar/cms-platform 0.90.0-m7.3 → 0.90.0-m7.4

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/CHANGELOG.md CHANGED
@@ -8,6 +8,19 @@ matches `package.json` exactly. See
8
8
  Versions 0.1.0 through 0.78.0 predate this repository and were reconstructed
9
9
  from the published npm tarballs.
10
10
 
11
+ ## 0.90.0-m7.4
12
+
13
+ - `cmsPageDirectory` takes the site's routing engine and asks it for each
14
+ page's public path, instead of assuming the page key is the path. The home
15
+ page is why: its key is `home` and its address is `/`, so the popup page
16
+ picker would have offered an address that matches nothing.
17
+ - The Popups screen supplies that resolver from the `usePageUrl` hook the
18
+ backoffice already registers — no new host field, no second pages query. The
19
+ hook is called from its own component so it stays unconditional while the
20
+ page group remains optional.
21
+ - Requires `@bygghammar/lightweight-popup@>=0.2.0-m7.4`, which is where the
22
+ page picker itself lives.
23
+
11
24
  ## 0.90.0-m7.3
12
25
 
13
26
  - Popup exclusions are derived from the configured `backofficePath`, so a site
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bygghammar/cms-platform",
3
- "version": "0.90.0-m7.3",
3
+ "version": "0.90.0-m7.4",
4
4
  "description": "Shared CMS platform: blocks, block types, i18n core, UI primitives and design-token contract used by every Bygghammar/Voyara site.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -263,7 +263,7 @@
263
263
  "@dnd-kit/sortable": ">=3",
264
264
  "@dnd-kit/utilities": ">=3",
265
265
  "zod": ">=3",
266
- "@bygghammar/lightweight-popup": ">=0.2.0-m7.3"
266
+ "@bygghammar/lightweight-popup": ">=0.2.0-m7.4"
267
267
  },
268
268
  "peerDependenciesMeta": {
269
269
  "@supabase/supabase-js": {
@@ -328,7 +328,7 @@
328
328
  "check": "bun run typecheck && bun run test && bun run verify:pack"
329
329
  },
330
330
  "devDependencies": {
331
- "@bygghammar/lightweight-popup": "file:./vendor/bygghammar-lightweight-popup-0.2.0-m7.3.tgz",
331
+ "@bygghammar/lightweight-popup": "file:./vendor/bygghammar-lightweight-popup-0.2.0-m7.4.tgz",
332
332
  "@dnd-kit/core": "^6.3.1",
333
333
  "@dnd-kit/sortable": "^10.0.0",
334
334
  "@dnd-kit/utilities": "^3.2.2",
@@ -11,7 +11,7 @@
11
11
  * CMS half: the site's forms and pages, discovered through the platform's own
12
12
  * host contract rather than reimplemented per site.
13
13
  */
14
- import { useMemo } from "react";
14
+ import { useCallback, useMemo } from "react";
15
15
  import type { ReactNode } from "react";
16
16
  import { PopupAdminApp } from "@bygghammar/lightweight-popup/admin";
17
17
  // The backoffice styling ships with the popup package but must be imported by
@@ -21,6 +21,9 @@ import { PopupAdminApp } from "@bygghammar/lightweight-popup/admin";
21
21
  import "@bygghammar/lightweight-popup/admin.css";
22
22
  import type { AdminAdapters } from "@bygghammar/lightweight-popup/adapters";
23
23
 
24
+ import { backofficeHost } from "../backoffice/routes/host";
25
+ import type { BackofficeHost } from "../backoffice/routes/host";
26
+ import { siteConfig } from "../config/site-config";
24
27
  import { useFeatureGate } from "../backoffice/shell/use-resolved-config";
25
28
  import { cmsPopupNavigation } from "./cms-bridges";
26
29
  import { cmsMediaPicker } from "./media-bridge";
@@ -32,7 +35,21 @@ import {
32
35
  } from "./cms-bridges";
33
36
  import { popupAdminHost } from "./host";
34
37
 
35
- function PopupsRouteInner(): ReactNode {
38
+ /** The page group, or nothing — a site can run popups without the page builder. */
39
+ type PagesHost = NonNullable<BackofficeHost["pages"]>;
40
+
41
+ /**
42
+ * The screen, once it knows how to turn a page key into an address.
43
+ *
44
+ * Split out for one reason: `usePageUrl` is a hook and the page group is
45
+ * optional, so it cannot be called behind a condition. A separate component
46
+ * makes the call unconditional in the only place it happens.
47
+ */
48
+ function PopupsScreen({
49
+ resolvePath,
50
+ }: {
51
+ resolvePath?: (pageKey: string) => string;
52
+ }): ReactNode {
36
53
  const host = popupAdminHost();
37
54
 
38
55
  const adapters = useMemo<AdminAdapters>(
@@ -42,7 +59,7 @@ function PopupsRouteInner(): ReactNode {
42
59
  // The CMS knows its own forms and pages; a host only overrides these when
43
60
  // its content model differs from the platform's.
44
61
  formDirectory: host.formDirectory ?? cmsFormDirectory(),
45
- pages: host.pages ?? cmsPageDirectory(),
62
+ pages: host.pages ?? cmsPageDirectory(resolvePath),
46
63
  // Only offered when the site actually has destinations: the popup editor
47
64
  // shows no picker at all otherwise, rather than an empty control.
48
65
  optInDestinations: cmsOptInDestinations(),
@@ -57,12 +74,40 @@ function PopupsRouteInner(): ReactNode {
57
74
  renderForm: ({ formKey }) => <ManagedForm formKey={formKey} />,
58
75
  },
59
76
  }),
60
- [host],
77
+ [host, resolvePath],
61
78
  );
62
79
 
63
80
  return <PopupAdminApp adapters={adapters} />;
64
81
  }
65
82
 
83
+ /**
84
+ * The same screen, with the site's routing engine behind the page addresses.
85
+ *
86
+ * The default locale's path, because a popup's targeting is one list of paths
87
+ * for the whole site rather than one per language. On a site that gives each
88
+ * locale its own address, the other locales are a rule away in Advanced.
89
+ */
90
+ function PopupsScreenWithPageUrls({ pages }: { pages: PagesHost }): ReactNode {
91
+ const pageUrl = pages.usePageUrl();
92
+ const locale = siteConfig().defaultLocale;
93
+ const resolvePath = useCallback(
94
+ (pageKey: string) => pageUrl(pageKey, locale).path,
95
+ [pageUrl, locale],
96
+ );
97
+ return <PopupsScreen resolvePath={resolvePath} />;
98
+ }
99
+
100
+ function PopupsRouteInner(): ReactNode {
101
+ const pages = backofficeHost().pages;
102
+ // Branching on which component renders, not on which hooks run: each of the
103
+ // two calls its own hooks unconditionally, which is the rule.
104
+ return pages === undefined ? (
105
+ <PopupsScreen />
106
+ ) : (
107
+ <PopupsScreenWithPageUrls pages={pages} />
108
+ );
109
+ }
110
+
66
111
  /** PopupsRoute guarded by the "popups" module switch. */
67
112
  export function PopupsRoute(): ReactNode {
68
113
  const gate = useFeatureGate("popups", "Popups");
@@ -138,11 +138,26 @@ export function cmsFormDirectory(): FormDirectoryPort {
138
138
  /**
139
139
  * The site's pages, for targeting.
140
140
  *
141
+ * The popup builder shows an editor their pages by name and stores the
142
+ * address, so both halves have to be right. The name is the CMS's own page
143
+ * name. The address is the one a visitor would type — which is not the page's
144
+ * key: this platform's routing turns a key into a path through the site's slug
145
+ * table, and the home page is the case that proves it, since its key is
146
+ * `home` and its address is `/`. Targeting `/home` would match nothing, and
147
+ * nothing would say why.
148
+ *
149
+ * So a caller passes `resolvePath` — the routing engine the backoffice already
150
+ * uses for "view this page" links. Without one, the key is the best guess
151
+ * available, which is what this did before and is right for a host whose keys
152
+ * are paths.
153
+ *
141
154
  * Returns an empty list rather than throwing when the host has no page module
142
155
  * registered: a missing picker is a smaller problem than a backoffice screen
143
156
  * that will not render.
144
157
  */
145
- export function cmsPageDirectory(): PageDirectoryPort {
158
+ export function cmsPageDirectory(
159
+ resolvePath?: (pageKey: string) => string,
160
+ ): PageDirectoryPort {
146
161
  return {
147
162
  listPages: async (): Promise<readonly HostPage[]> => {
148
163
  // Read through an index rather than a named property: the host contract
@@ -158,7 +173,13 @@ export function cmsPageDirectory(): PageDirectoryPort {
158
173
  return rows.flatMap((row) => {
159
174
  const key = text(row, "path", "slug", "key");
160
175
  if (key === undefined) return [];
161
- const path = key.startsWith("/") ? key : `/${key}`;
176
+ const resolved = resolvePath === undefined ? undefined : resolvePath(key);
177
+ const path =
178
+ resolved !== undefined && resolved.startsWith("/")
179
+ ? resolved
180
+ : key.startsWith("/")
181
+ ? key
182
+ : `/${key}`;
162
183
  return [{ path, title: text(row, "title", "name") ?? path }];
163
184
  });
164
185
  },