@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 +13 -0
- package/package.json +3 -3
- package/src/popups/admin.tsx +49 -4
- package/src/popups/cms-bridges.ts +23 -2
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
|
+
"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.
|
|
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.
|
|
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",
|
package/src/popups/admin.tsx
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
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
|
},
|