@pramen/cms 0.0.61 → 0.0.64
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 +34 -0
- package/dist/blockkit.d.ts +85 -13
- package/dist/blockkit.js +140 -11
- package/dist/index.d.ts +202 -12
- package/dist/index.js +415 -10
- package/dist/panel.d.ts +53 -0
- package/dist/panel.js +74 -0
- package/package.json +2 -2
- package/src/blockkit.ts +195 -25
- package/src/index.ts +445 -25
- package/src/panel.ts +105 -0
package/src/panel.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// A custom admin PANEL — a project's own React screen, rendered inside the editor's chrome
|
|
2
|
+
// at a real route with a real nav entry.
|
|
3
|
+
//
|
|
4
|
+
// WHY A SECOND KIND, WHEN `adminPage()` EXISTS
|
|
5
|
+
//
|
|
6
|
+
// Block Kit (`adminPage()`) is a server-driven vocabulary: the handler returns JSON, the
|
|
7
|
+
// editor renders it, and the whole page comes back on every interaction. That is exactly
|
|
8
|
+
// right for a list-and-form screen, and it is the reason no project JavaScript runs in the
|
|
9
|
+
// admin. But the properties that make it safe are the same ones that cap it — an input
|
|
10
|
+
// cannot fire an interaction, every control is disabled for the round trip (so focus and
|
|
11
|
+
// caret are lost on each keystroke that matters), a table row cannot expand, and there is
|
|
12
|
+
// no link, no redirect, no dialog, no autofocus and no date input. Those are not gaps to
|
|
13
|
+
// patch one element at a time; a screen that needs local interaction needs local code.
|
|
14
|
+
//
|
|
15
|
+
// The alternative a project reaches for when Block Kit runs out is what this replaces: a
|
|
16
|
+
// standalone React SPA served next to the editor with the chrome rebuilt by hand. It goes
|
|
17
|
+
// out of the application, it does not have the same layout, and every chrome fix has to be
|
|
18
|
+
// made twice.
|
|
19
|
+
//
|
|
20
|
+
// So a panel is the SAME registry entry as a Block Kit page with the render moved to the
|
|
21
|
+
// browser. Same slug space, same `/apps/:slug` route, same "Apps" band in the nav, and —
|
|
22
|
+
// the part that matters — the same server-side role filter: a panel the caller may not open
|
|
23
|
+
// is absent from `listAdminPages`, so there is no nav entry to click and no route to reach.
|
|
24
|
+
//
|
|
25
|
+
// WHAT LIVES WHERE. The server owns everything a nav entry is made of (slug, label, icon,
|
|
26
|
+
// position, roles); the browser bundle owns only the component. That split is deliberate:
|
|
27
|
+
// if the bundle declared the label and the position, a client that failed to load would
|
|
28
|
+
// take the nav entry with it, and a client that loaded would be declaring its own placement
|
|
29
|
+
// with nothing to check it against. A panel whose bundle never registers is a listed entry
|
|
30
|
+
// that renders a diagnostic — which is a legible failure — rather than a section that
|
|
31
|
+
// silently ceases to exist.
|
|
32
|
+
//
|
|
33
|
+
// WHAT IS GIVEN UP. Block Kit's headline property is that no project JavaScript ever runs in
|
|
34
|
+
// the admin (#33). A panel gives that up, deliberately and only where a deployment asks for
|
|
35
|
+
// it: the bundle runs in the editor's own page with the editor's own session in scope, so it
|
|
36
|
+
// can read the stored token and call anything the caller can. `roles` below and the ACL still
|
|
37
|
+
// bound what the SERVER will do, and `PanelApi` is a small surface to write against, but
|
|
38
|
+
// neither is a sandbox — a panel is part of the admin, not a guest in it. That is the reason
|
|
39
|
+
// `adminPage()` remains the first thing to reach for and this the second.
|
|
40
|
+
//
|
|
41
|
+
// The same reconciliation is written from the other side at the top of `blockkit.ts` — why
|
|
42
|
+
// "do not ship the component tree" and this are one position rather than two, and which of
|
|
43
|
+
// the pair to reach for first. Read together; changing one without the other leaves the
|
|
44
|
+
// framework arguing with itself.
|
|
45
|
+
//
|
|
46
|
+
// A LEAF module, like `href.ts` and `nav.ts`: `blockkit.ts` imports it to widen the
|
|
47
|
+
// registry, and it imports nothing back.
|
|
48
|
+
|
|
49
|
+
/** One custom admin panel: a nav entry the browser bundle fills in.
|
|
50
|
+
*
|
|
51
|
+
* There is no `render` here and there is not meant to be one — the rendering half is a
|
|
52
|
+
* React component the deployment's panel bundle registers under the same `slug`. Everything
|
|
53
|
+
* that decides whether the entry EXISTS is here, on the server, where it can be enforced.
|
|
54
|
+
*/
|
|
55
|
+
export interface AdminPanelDef {
|
|
56
|
+
/** Discriminates a panel from an `AdminPageDef` in the one registry they share. Set by
|
|
57
|
+
* {@link adminPanel}; it is a required field rather than an inferred one so a hand-built
|
|
58
|
+
* object literal cannot be a half-declared panel. */
|
|
59
|
+
readonly kind: "panel";
|
|
60
|
+
/** URL + registry key: served at `/apps/:slug` in the editor, and the id the browser
|
|
61
|
+
* bundle registers its component under. */
|
|
62
|
+
readonly slug: string;
|
|
63
|
+
/** Nav label. */
|
|
64
|
+
readonly label: string;
|
|
65
|
+
/** Optional nav icon (emoji or short string). */
|
|
66
|
+
readonly icon?: string;
|
|
67
|
+
/** Where it sits in the nav — see `NAV_ORDER`. Defaults to `NAV_ORDER.adminPages`. */
|
|
68
|
+
readonly navOrder?: number;
|
|
69
|
+
/** Roles that may open it. Defaults to the deployment's `editorRoles`, exactly as a Block
|
|
70
|
+
* Kit page's does — one registry, one gate.
|
|
71
|
+
*
|
|
72
|
+
* This is the ONLY authorization a panel gets for free. A panel's own code runs in the
|
|
73
|
+
* browser, so every call it makes is an ordinary RPC under the caller's own identity and
|
|
74
|
+
* ACL; this list decides who is shown the screen, not what the screen may do. */
|
|
75
|
+
readonly roles?: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Declare a custom admin panel. Spread the result into `createAdminPageHandlers` alongside
|
|
80
|
+
* any `adminPage()`s:
|
|
81
|
+
*
|
|
82
|
+
* const curation = adminPanel("curation", {
|
|
83
|
+
* label: "Curation",
|
|
84
|
+
* icon: "🎛",
|
|
85
|
+
* navOrder: NAV_ORDER.media + 10,
|
|
86
|
+
* roles: ["editor", "admin"],
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* handlers = { ...createAdminPageHandlers([desk, curation], { editorRoles }) };
|
|
90
|
+
*
|
|
91
|
+
* The matching component is registered by the deployment's panel bundle — see the
|
|
92
|
+
* "Custom admin panels" section of the CMS docs.
|
|
93
|
+
*/
|
|
94
|
+
export function adminPanel(slug: string, opts: Omit<AdminPanelDef, "slug" | "kind">): AdminPanelDef {
|
|
95
|
+
return { ...opts, kind: "panel", slug };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Whether a registry entry is a panel (and so has no server-side render).
|
|
99
|
+
*
|
|
100
|
+
* Reads the discriminant rather than testing for the ABSENCE of `render`: "no render" is
|
|
101
|
+
* also what a malformed page looks like, and `validateAdminPages` has to be able to tell a
|
|
102
|
+
* panel from a page someone forgot to finish. */
|
|
103
|
+
export function isAdminPanel(def: { readonly kind?: string }): def is AdminPanelDef {
|
|
104
|
+
return def.kind === "panel";
|
|
105
|
+
}
|