@elytracms/studio 0.0.14 → 0.0.16
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/convex/cms.ts +8 -31
- package/convex/content.ts +3 -44
- package/convex/delivery.ts +8 -42
- package/convex/functions.ts +17 -2
- package/convex/members.ts +2 -2
- package/convex/referenceMaintenance.ts +4 -73
- package/convex/references.ts +4 -2
- package/convex/schema.ts +17 -37
- package/convex/sync.ts +10 -42
- package/convex/triggers.ts +57 -0
- package/convex/webhooks.ts +3 -2
- package/dist/components/app-views/default-modules.d.ts +41 -0
- package/dist/components/app-views/default-modules.js +201 -0
- package/dist/components/app-views/default-modules.js.map +1 -0
- package/dist/components/app-views/module.d.ts +93 -0
- package/dist/components/app-views/module.js +75 -0
- package/dist/components/app-views/module.js.map +1 -0
- package/dist/components/app-views/sidebar.d.ts +95 -0
- package/dist/components/app-views/sidebar.js +153 -0
- package/dist/components/app-views/sidebar.js.map +1 -0
- package/dist/components/builder/builder-store.d.ts +92 -40
- package/dist/components/builder/builder-store.js +30 -126
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-breakpoints.d.ts +53 -0
- package/dist/components/builder/canvas-breakpoints.js +55 -0
- package/dist/components/builder/canvas-breakpoints.js.map +1 -0
- package/dist/components/builder/canvas.js +35 -61
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.js +3 -3
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.js +31 -40
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/insert-context.js +1 -1
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/page-tree.js +3 -4
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/property-inspector.js +4 -64
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +23 -5
- package/dist/components/cms/bulk-actions.js +7 -3
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/bulk-bar.d.ts +22 -6
- package/dist/components/cms/bulk-bar.js +46 -9
- package/dist/components/cms/bulk-bar.js.map +1 -1
- package/dist/components/cms/cell.d.ts +0 -4
- package/dist/components/cms/cell.js +1 -5
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-index.d.ts +4 -2
- package/dist/components/cms/cms-index.js +6 -4
- package/dist/components/cms/cms-index.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +7 -6
- package/dist/components/cms/cms-store.js +55 -18
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +8 -4
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/collections-sidebar.d.ts +10 -1
- package/dist/components/cms/collections-sidebar.js +13 -2
- package/dist/components/cms/collections-sidebar.js.map +1 -1
- package/dist/components/cms/detail-editor.js +2 -2
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/doc-state.d.ts +0 -5
- package/dist/components/cms/doc-state.js +1 -7
- package/dist/components/cms/doc-state.js.map +1 -1
- package/dist/components/cms/editable-cell.js +1 -1
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.d.ts +11 -2
- package/dist/components/cms/field-control.js +22 -1
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.d.ts +71 -0
- package/dist/components/cms/field-renderer.js +67 -0
- package/dist/components/cms/field-renderer.js.map +1 -0
- package/dist/components/cms/filter-builder.js +6 -28
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.d.ts +35 -0
- package/dist/components/cms/scalar-value-editor.js +43 -0
- package/dist/components/cms/scalar-value-editor.js.map +1 -0
- package/dist/components/collab/collab-context.d.ts +6 -14
- package/dist/components/collab/collab-context.js +3 -12
- package/dist/components/collab/collab-context.js.map +1 -1
- package/dist/components/collab/collab-seed.d.ts +7 -19
- package/dist/components/collab/collab-seed.js +9 -31
- package/dist/components/collab/collab-seed.js.map +1 -1
- package/dist/components/components/registry-browser.js +6 -12
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/guards.d.ts +0 -9
- package/dist/components/guards.js +0 -12
- package/dist/components/guards.js.map +1 -1
- package/dist/components/project-session.js +8 -3
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/section-nav.d.ts +10 -10
- package/dist/components/section-nav.js +48 -25
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/section-state.d.ts +0 -2
- package/dist/components/section-state.js +0 -9
- package/dist/components/section-state.js.map +1 -1
- package/dist/components/section-surface.d.ts +3 -7
- package/dist/components/section-surface.js +6 -14
- package/dist/components/section-surface.js.map +1 -1
- package/dist/components/sections.d.ts +0 -1
- package/dist/components/sections.js +0 -10
- package/dist/components/sections.js.map +1 -1
- package/dist/components/studio-project-screen.d.ts +3 -4
- package/dist/components/studio-project-screen.js +41 -160
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/components/studio-shell.d.ts +0 -4
- package/dist/components/studio-shell.js +14 -14
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +19 -1
- package/dist/lib/data/collection-content.js +16 -0
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/operations/graph-commands.d.ts +2 -6
- package/dist/lib/operations/graph-commands.js +1 -2
- package/dist/lib/operations/graph-commands.js.map +1 -1
- package/dist/lib/operations/index.d.ts +0 -2
- package/dist/lib/operations/index.js +0 -2
- package/dist/lib/operations/index.js.map +1 -1
- package/dist/lib/operations/session-hydration.d.ts +14 -24
- package/dist/lib/operations/session-hydration.js +15 -34
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +1 -3
- package/dist/lib/persistence/convex-adapter.js +1 -154
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.js +0 -2
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/references/reference-model.d.ts +0 -16
- package/dist/lib/references/reference-model.js +2 -54
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/references/references-store.js +3 -4
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/registry/index.d.ts +0 -16
- package/dist/lib/registry/index.js +0 -15
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/webhooks/tags.js +2 -16
- package/dist/lib/webhooks/tags.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +27 -1
- package/dist/lib/workspace/index.js +34 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/router/match.d.ts +22 -34
- package/dist/router/match.js +22 -40
- package/dist/router/match.js.map +1 -1
- package/package.json +3 -2
- package/convex/graphs.ts +0 -55
- package/convex/publishing.ts +0 -76
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
|
+
import type { CustomizationIssue, Project, SectionAvailability } from '@elytracms/core/studio-core';
|
|
3
|
+
import { type ResolvedAppViews } from './module';
|
|
4
|
+
/**
|
|
5
|
+
* EC-237 — the configurable sidebar, an instance of the EC-236 customization seam.
|
|
6
|
+
*
|
|
7
|
+
* The section rail is a consumer-extendable ARRAY of nav entries — items (each
|
|
8
|
+
* targeting an EC-239 app-view module, optionally deep-linked to a resource),
|
|
9
|
+
* groups of sub-items, and separators. A consuming repo supplies its own array
|
|
10
|
+
* through `config.sidebar`; omitted → the studio derives the default rail from its
|
|
11
|
+
* module registry (every non-hidden module, in order). The supplied array is
|
|
12
|
+
* resolved through the shared {@link resolveOverrides} core: a duplicate id, an
|
|
13
|
+
* item whose target module isn't registered ("unknown view"), or a deep-link that
|
|
14
|
+
* doesn't resolve becomes a visible {@link CustomizationIssue} and the entry is
|
|
15
|
+
* dropped — never a silent failure. Sub-views (a focused "Taxonomy") are EC-239
|
|
16
|
+
* modules; this file decides only how the rail PRESENTS and orders them.
|
|
17
|
+
*/
|
|
18
|
+
/** A nav item targeting an app-view module, optionally deep-linked to a resource. */
|
|
19
|
+
export interface SidebarItemConfig {
|
|
20
|
+
kind: 'item';
|
|
21
|
+
/** Stable id (defaults to `moduleId` via {@link navItem}). */
|
|
22
|
+
id: string;
|
|
23
|
+
/** The app-view module this item mounts — must be in the registry. */
|
|
24
|
+
moduleId: string;
|
|
25
|
+
/** Overrides the module's rail label. */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Overrides the module's rail icon. */
|
|
28
|
+
icon?: LucideIcon;
|
|
29
|
+
/** Trailing path segments targeting a specific resource, e.g. `['category']`. */
|
|
30
|
+
deepLink?: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
export interface SidebarGroupConfig {
|
|
33
|
+
kind: 'group';
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
items: SidebarItemConfig[];
|
|
37
|
+
}
|
|
38
|
+
export interface SidebarSeparatorConfig {
|
|
39
|
+
kind: 'separator';
|
|
40
|
+
id?: string;
|
|
41
|
+
}
|
|
42
|
+
export type SidebarNodeConfig = SidebarItemConfig | SidebarGroupConfig | SidebarSeparatorConfig;
|
|
43
|
+
export interface ResolvedSidebarItem {
|
|
44
|
+
kind: 'item';
|
|
45
|
+
id: string;
|
|
46
|
+
moduleId: string;
|
|
47
|
+
label: string;
|
|
48
|
+
icon: LucideIcon;
|
|
49
|
+
/** The `_splat` to navigate to (moduleId + deep-link segments). */
|
|
50
|
+
path: string;
|
|
51
|
+
availability: SectionAvailability;
|
|
52
|
+
}
|
|
53
|
+
export interface ResolvedSidebarGroup {
|
|
54
|
+
kind: 'group';
|
|
55
|
+
id: string;
|
|
56
|
+
label: string;
|
|
57
|
+
items: ResolvedSidebarItem[];
|
|
58
|
+
}
|
|
59
|
+
export interface ResolvedSidebarSeparator {
|
|
60
|
+
kind: 'separator';
|
|
61
|
+
id: string;
|
|
62
|
+
}
|
|
63
|
+
export type ResolvedSidebarNode = ResolvedSidebarItem | ResolvedSidebarGroup | ResolvedSidebarSeparator;
|
|
64
|
+
export interface ResolvedSidebar {
|
|
65
|
+
nodes: ResolvedSidebarNode[];
|
|
66
|
+
issues: CustomizationIssue[];
|
|
67
|
+
}
|
|
68
|
+
/** A nav item; `id` defaults to `moduleId` when omitted. */
|
|
69
|
+
export declare function navItem(config: Omit<SidebarItemConfig, 'kind' | 'id'> & {
|
|
70
|
+
id?: string;
|
|
71
|
+
}): SidebarItemConfig;
|
|
72
|
+
/** A labelled group of sub-items; `id` defaults to a slug of the label. */
|
|
73
|
+
export declare function navGroup(label: string, items: SidebarItemConfig[], opts?: {
|
|
74
|
+
id?: string;
|
|
75
|
+
}): SidebarGroupConfig;
|
|
76
|
+
/** A divider between rail sections. */
|
|
77
|
+
export declare function navSeparator(id?: string): SidebarSeparatorConfig;
|
|
78
|
+
/** Identity helper that types a consumer's `config.sidebar` array. */
|
|
79
|
+
export declare function defineSidebar(nodes: SidebarNodeConfig[]): SidebarNodeConfig[];
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a consumer sidebar (or the default rail) over the app-view registry
|
|
82
|
+
* through the shared customization core (EC-236). Every node — items, group
|
|
83
|
+
* headers, nested items, separators — is keyed by id and run through
|
|
84
|
+
* {@link resolveOverrides} for dedup + per-node validation, so a duplicate id or
|
|
85
|
+
* an invalid item is a visible issue and is dropped; the surviving nodes are
|
|
86
|
+
* rebuilt into the rail tree (groups keep their surviving sub-items).
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveSidebar(registry: ResolvedAppViews, project: Project, entries?: readonly SidebarNodeConfig[]): ResolvedSidebar;
|
|
89
|
+
/** The active module id parsed from a studio pathname `/<projectId>/<module>[/…]`. */
|
|
90
|
+
export declare function activeModuleFromPath(pathname: string): string | null;
|
|
91
|
+
/**
|
|
92
|
+
* The active project's resolved sidebar — the default rail or `config.sidebar`,
|
|
93
|
+
* resolved over the module registry through the EC-236 seam (static per boot).
|
|
94
|
+
*/
|
|
95
|
+
export declare function useSidebar(project: Project): ResolvedSidebar;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { resolveOverrides } from '@elytracms/core/studio-core';
|
|
3
|
+
import { resolveDeploymentSidebar } from '@elytracms/studio/lib/workspace';
|
|
4
|
+
import { moduleAvailability } from './module';
|
|
5
|
+
import { useAppViews } from './default-modules';
|
|
6
|
+
// --- Builder helpers (the typed consumer API; carry live icons) ---
|
|
7
|
+
/** A nav item; `id` defaults to `moduleId` when omitted. */
|
|
8
|
+
export function navItem(config) {
|
|
9
|
+
return {
|
|
10
|
+
kind: 'item',
|
|
11
|
+
id: config.id ?? config.moduleId,
|
|
12
|
+
moduleId: config.moduleId,
|
|
13
|
+
...(config.label !== undefined ? { label: config.label } : {}),
|
|
14
|
+
...(config.icon !== undefined ? { icon: config.icon } : {}),
|
|
15
|
+
...(config.deepLink !== undefined ? { deepLink: config.deepLink } : {}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** A labelled group of sub-items; `id` defaults to a slug of the label. */
|
|
19
|
+
export function navGroup(label, items, opts = {}) {
|
|
20
|
+
return { kind: 'group', id: opts.id ?? slugify(label), label, items };
|
|
21
|
+
}
|
|
22
|
+
/** A divider between rail sections. */
|
|
23
|
+
export function navSeparator(id) {
|
|
24
|
+
return { kind: 'separator', ...(id !== undefined ? { id } : {}) };
|
|
25
|
+
}
|
|
26
|
+
/** Identity helper that types a consumer's `config.sidebar` array. */
|
|
27
|
+
export function defineSidebar(nodes) {
|
|
28
|
+
return nodes;
|
|
29
|
+
}
|
|
30
|
+
function slugify(label) {
|
|
31
|
+
return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'group';
|
|
32
|
+
}
|
|
33
|
+
// --- Resolution (EC-236 seam) ---
|
|
34
|
+
/** The default rail: every non-hidden module as a plain item, in registry order. */
|
|
35
|
+
function defaultSidebar(modules) {
|
|
36
|
+
return modules
|
|
37
|
+
.filter((module) => !module.hidden)
|
|
38
|
+
.map((module) => ({ kind: 'item', id: module.id, moduleId: module.id }));
|
|
39
|
+
}
|
|
40
|
+
/** The `_splat` path for an item (module id + its deep-link segments). */
|
|
41
|
+
function itemPath(item) {
|
|
42
|
+
return [item.moduleId, ...(item.deepLink ?? [])].join('/');
|
|
43
|
+
}
|
|
44
|
+
/** Validate one item against the registry — unknown module or unresolvable deep-link. */
|
|
45
|
+
function validateItem(registry, item) {
|
|
46
|
+
const module = registry.get(item.moduleId);
|
|
47
|
+
if (!module)
|
|
48
|
+
return `targets module "${item.moduleId}", which is not registered`;
|
|
49
|
+
if (item.deepLink && item.deepLink.length > 0) {
|
|
50
|
+
const parse = module.parsePath;
|
|
51
|
+
if (!parse) {
|
|
52
|
+
return `deep-links into module "${item.moduleId}", which takes no path`;
|
|
53
|
+
}
|
|
54
|
+
if (parse([...item.deepLink]) === null) {
|
|
55
|
+
return `deep-link "${item.deepLink.join('/')}" does not resolve in module "${item.moduleId}"`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a consumer sidebar (or the default rail) over the app-view registry
|
|
62
|
+
* through the shared customization core (EC-236). Every node — items, group
|
|
63
|
+
* headers, nested items, separators — is keyed by id and run through
|
|
64
|
+
* {@link resolveOverrides} for dedup + per-node validation, so a duplicate id or
|
|
65
|
+
* an invalid item is a visible issue and is dropped; the surviving nodes are
|
|
66
|
+
* rebuilt into the rail tree (groups keep their surviving sub-items).
|
|
67
|
+
*/
|
|
68
|
+
export function resolveSidebar(registry, project, entries) {
|
|
69
|
+
const config = entries ?? defaultSidebar(registry.modules);
|
|
70
|
+
const flat = [];
|
|
71
|
+
config.forEach((node, index) => {
|
|
72
|
+
if (node.kind === 'separator') {
|
|
73
|
+
flat.push({ id: node.id ?? `__separator:${index}`, value: { node, isItem: false } });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (node.kind === 'group') {
|
|
77
|
+
flat.push({ id: node.id, value: { node, isItem: false } });
|
|
78
|
+
node.items.forEach((item) => flat.push({ id: item.id, value: { node: item, isItem: true } }));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
flat.push({ id: node.id, value: { node, isItem: true } });
|
|
82
|
+
});
|
|
83
|
+
const resolved = resolveOverrides({
|
|
84
|
+
point: 'sidebar',
|
|
85
|
+
defaults: [],
|
|
86
|
+
overrides: flat,
|
|
87
|
+
allowNew: true,
|
|
88
|
+
validate: (entry) => entry.value.isItem
|
|
89
|
+
? validateItem(registry, entry.value.node)
|
|
90
|
+
: null,
|
|
91
|
+
});
|
|
92
|
+
const survived = (id) => resolved.get(id) !== undefined;
|
|
93
|
+
const toItem = (item) => {
|
|
94
|
+
const module = registry.get(item.moduleId);
|
|
95
|
+
return {
|
|
96
|
+
kind: 'item',
|
|
97
|
+
id: item.id,
|
|
98
|
+
moduleId: item.moduleId,
|
|
99
|
+
label: item.label ?? module.label,
|
|
100
|
+
icon: item.icon ?? module.icon,
|
|
101
|
+
path: itemPath(item),
|
|
102
|
+
availability: moduleAvailability(project, module),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
// Emit each surviving id at most once — `resolveOverrides` keeps the first of a
|
|
106
|
+
// duplicate, so the rebuild must skip later occurrences of that same id too.
|
|
107
|
+
const emitted = new Set();
|
|
108
|
+
const claim = (id) => {
|
|
109
|
+
if (!survived(id) || emitted.has(id))
|
|
110
|
+
return false;
|
|
111
|
+
emitted.add(id);
|
|
112
|
+
return true;
|
|
113
|
+
};
|
|
114
|
+
const nodes = [];
|
|
115
|
+
config.forEach((node, index) => {
|
|
116
|
+
if (node.kind === 'separator') {
|
|
117
|
+
const id = node.id ?? `__separator:${index}`;
|
|
118
|
+
if (claim(id))
|
|
119
|
+
nodes.push({ kind: 'separator', id });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (node.kind === 'group') {
|
|
123
|
+
if (!claim(node.id))
|
|
124
|
+
return;
|
|
125
|
+
const groupItems = node.items.filter((item) => claim(item.id)).map(toItem);
|
|
126
|
+
if (groupItems.length > 0) {
|
|
127
|
+
nodes.push({ kind: 'group', id: node.id, label: node.label, items: groupItems });
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (claim(node.id))
|
|
132
|
+
nodes.push(toItem(node));
|
|
133
|
+
});
|
|
134
|
+
return { nodes, issues: resolved.issues };
|
|
135
|
+
}
|
|
136
|
+
/** The active module id parsed from a studio pathname `/<projectId>/<module>[/…]`. */
|
|
137
|
+
export function activeModuleFromPath(pathname) {
|
|
138
|
+
return pathname.split('/')[2] || null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The active project's resolved sidebar — the default rail or `config.sidebar`,
|
|
142
|
+
* resolved over the module registry through the EC-236 seam (static per boot).
|
|
143
|
+
*/
|
|
144
|
+
export function useSidebar(project) {
|
|
145
|
+
const registry = useAppViews();
|
|
146
|
+
return useMemo(() => {
|
|
147
|
+
// Config carries the sidebar opaquely (`SidebarEntry` = object); the studio
|
|
148
|
+
// owns + validates the rich item/group/separator shape, so cast it back here.
|
|
149
|
+
const entries = resolveDeploymentSidebar();
|
|
150
|
+
return resolveSidebar(registry, project, entries);
|
|
151
|
+
}, [registry, project]);
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=sidebar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../../../src/components/app-views/sidebar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport { resolveOverrides } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue, Project, SectionAvailability } from '@elytracms/core/studio-core'\nimport { resolveDeploymentSidebar } from '@elytracms/studio/lib/workspace'\nimport { moduleAvailability, type AppViewModule, type ResolvedAppViews } from './module'\nimport { useAppViews } from './default-modules'\n\n/**\n * EC-237 — the configurable sidebar, an instance of the EC-236 customization seam.\n *\n * The section rail is a consumer-extendable ARRAY of nav entries — items (each\n * targeting an EC-239 app-view module, optionally deep-linked to a resource),\n * groups of sub-items, and separators. A consuming repo supplies its own array\n * through `config.sidebar`; omitted → the studio derives the default rail from its\n * module registry (every non-hidden module, in order). The supplied array is\n * resolved through the shared {@link resolveOverrides} core: a duplicate id, an\n * item whose target module isn't registered (\"unknown view\"), or a deep-link that\n * doesn't resolve becomes a visible {@link CustomizationIssue} and the entry is\n * dropped — never a silent failure. Sub-views (a focused \"Taxonomy\") are EC-239\n * modules; this file decides only how the rail PRESENTS and orders them.\n */\n\n// --- Consumer config shapes (typed builder helpers below) ---\n\n/** A nav item targeting an app-view module, optionally deep-linked to a resource. */\nexport interface SidebarItemConfig {\n kind: 'item'\n /** Stable id (defaults to `moduleId` via {@link navItem}). */\n id: string\n /** The app-view module this item mounts — must be in the registry. */\n moduleId: string\n /** Overrides the module's rail label. */\n label?: string\n /** Overrides the module's rail icon. */\n icon?: LucideIcon\n /** Trailing path segments targeting a specific resource, e.g. `['category']`. */\n deepLink?: readonly string[]\n}\n\nexport interface SidebarGroupConfig {\n kind: 'group'\n id: string\n label: string\n items: SidebarItemConfig[]\n}\n\nexport interface SidebarSeparatorConfig {\n kind: 'separator'\n id?: string\n}\n\nexport type SidebarNodeConfig = SidebarItemConfig | SidebarGroupConfig | SidebarSeparatorConfig\n\n// --- Resolved (rail-ready) shapes ---\n\nexport interface ResolvedSidebarItem {\n kind: 'item'\n id: string\n moduleId: string\n label: string\n icon: LucideIcon\n /** The `_splat` to navigate to (moduleId + deep-link segments). */\n path: string\n availability: SectionAvailability\n}\nexport interface ResolvedSidebarGroup {\n kind: 'group'\n id: string\n label: string\n items: ResolvedSidebarItem[]\n}\nexport interface ResolvedSidebarSeparator {\n kind: 'separator'\n id: string\n}\nexport type ResolvedSidebarNode =\n | ResolvedSidebarItem\n | ResolvedSidebarGroup\n | ResolvedSidebarSeparator\n\nexport interface ResolvedSidebar {\n nodes: ResolvedSidebarNode[]\n issues: CustomizationIssue[]\n}\n\n// --- Builder helpers (the typed consumer API; carry live icons) ---\n\n/** A nav item; `id` defaults to `moduleId` when omitted. */\nexport function navItem(\n config: Omit<SidebarItemConfig, 'kind' | 'id'> & { id?: string },\n): SidebarItemConfig {\n return {\n kind: 'item',\n id: config.id ?? config.moduleId,\n moduleId: config.moduleId,\n ...(config.label !== undefined ? { label: config.label } : {}),\n ...(config.icon !== undefined ? { icon: config.icon } : {}),\n ...(config.deepLink !== undefined ? { deepLink: config.deepLink } : {}),\n }\n}\n\n/** A labelled group of sub-items; `id` defaults to a slug of the label. */\nexport function navGroup(\n label: string,\n items: SidebarItemConfig[],\n opts: { id?: string } = {},\n): SidebarGroupConfig {\n return { kind: 'group', id: opts.id ?? slugify(label), label, items }\n}\n\n/** A divider between rail sections. */\nexport function navSeparator(id?: string): SidebarSeparatorConfig {\n return { kind: 'separator', ...(id !== undefined ? { id } : {}) }\n}\n\n/** Identity helper that types a consumer's `config.sidebar` array. */\nexport function defineSidebar(nodes: SidebarNodeConfig[]): SidebarNodeConfig[] {\n return nodes\n}\n\nfunction slugify(label: string): string {\n return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'group'\n}\n\n// --- Resolution (EC-236 seam) ---\n\n/** The default rail: every non-hidden module as a plain item, in registry order. */\nfunction defaultSidebar(modules: readonly AppViewModule[]): SidebarItemConfig[] {\n return modules\n .filter((module) => !module.hidden)\n .map((module) => ({ kind: 'item', id: module.id, moduleId: module.id }))\n}\n\n/** The `_splat` path for an item (module id + its deep-link segments). */\nfunction itemPath(item: SidebarItemConfig): string {\n return [item.moduleId, ...(item.deepLink ?? [])].join('/')\n}\n\n/** Validate one item against the registry — unknown module or unresolvable deep-link. */\nfunction validateItem(registry: ResolvedAppViews, item: SidebarItemConfig): string | null {\n const module = registry.get(item.moduleId)\n if (!module) return `targets module \"${item.moduleId}\", which is not registered`\n if (item.deepLink && item.deepLink.length > 0) {\n const parse = module.parsePath\n if (!parse) {\n return `deep-links into module \"${item.moduleId}\", which takes no path`\n }\n if (parse([...item.deepLink]) === null) {\n return `deep-link \"${item.deepLink.join('/')}\" does not resolve in module \"${item.moduleId}\"`\n }\n }\n return null\n}\n\n/**\n * Resolve a consumer sidebar (or the default rail) over the app-view registry\n * through the shared customization core (EC-236). Every node — items, group\n * headers, nested items, separators — is keyed by id and run through\n * {@link resolveOverrides} for dedup + per-node validation, so a duplicate id or\n * an invalid item is a visible issue and is dropped; the surviving nodes are\n * rebuilt into the rail tree (groups keep their surviving sub-items).\n */\nexport function resolveSidebar(\n registry: ResolvedAppViews,\n project: Project,\n entries?: readonly SidebarNodeConfig[],\n): ResolvedSidebar {\n const config = entries ?? defaultSidebar(registry.modules)\n\n // Flatten the tree into a single keyspace so dedup + validation route through\n // the shared core. Separators without an id get a positional one (never a user\n // collision); groups + items keep theirs. `value` carries the node for validate.\n type Flat = { node: SidebarNodeConfig; isItem: boolean }\n const flat: { id: string; value: Flat }[] = []\n config.forEach((node, index) => {\n if (node.kind === 'separator') {\n flat.push({ id: node.id ?? `__separator:${index}`, value: { node, isItem: false } })\n return\n }\n if (node.kind === 'group') {\n flat.push({ id: node.id, value: { node, isItem: false } })\n node.items.forEach((item) => flat.push({ id: item.id, value: { node: item, isItem: true } }))\n return\n }\n flat.push({ id: node.id, value: { node, isItem: true } })\n })\n\n const resolved = resolveOverrides<Flat>({\n point: 'sidebar',\n defaults: [],\n overrides: flat,\n allowNew: true,\n validate: (entry) =>\n entry.value.isItem\n ? validateItem(registry, entry.value.node as SidebarItemConfig)\n : null,\n })\n const survived = (id: string) => resolved.get(id) !== undefined\n\n const toItem = (item: SidebarItemConfig): ResolvedSidebarItem => {\n const module = registry.get(item.moduleId)!\n return {\n kind: 'item',\n id: item.id,\n moduleId: item.moduleId,\n label: item.label ?? module.label,\n icon: item.icon ?? module.icon,\n path: itemPath(item),\n availability: moduleAvailability(project, module),\n }\n }\n\n // Emit each surviving id at most once — `resolveOverrides` keeps the first of a\n // duplicate, so the rebuild must skip later occurrences of that same id too.\n const emitted = new Set<string>()\n const claim = (id: string) => {\n if (!survived(id) || emitted.has(id)) return false\n emitted.add(id)\n return true\n }\n\n const nodes: ResolvedSidebarNode[] = []\n config.forEach((node, index) => {\n if (node.kind === 'separator') {\n const id = node.id ?? `__separator:${index}`\n if (claim(id)) nodes.push({ kind: 'separator', id })\n return\n }\n if (node.kind === 'group') {\n if (!claim(node.id)) return\n const groupItems = node.items.filter((item) => claim(item.id)).map(toItem)\n if (groupItems.length > 0) {\n nodes.push({ kind: 'group', id: node.id, label: node.label, items: groupItems })\n }\n return\n }\n if (claim(node.id)) nodes.push(toItem(node))\n })\n\n return { nodes, issues: resolved.issues }\n}\n\n/** The active module id parsed from a studio pathname `/<projectId>/<module>[/…]`. */\nexport function activeModuleFromPath(pathname: string): string | null {\n return pathname.split('/')[2] || null\n}\n\n/**\n * The active project's resolved sidebar — the default rail or `config.sidebar`,\n * resolved over the module registry through the EC-236 seam (static per boot).\n */\nexport function useSidebar(project: Project): ResolvedSidebar {\n const registry = useAppViews()\n return useMemo(() => {\n // Config carries the sidebar opaquely (`SidebarEntry` = object); the studio\n // owns + validates the rich item/group/separator shape, so cast it back here.\n const entries = resolveDeploymentSidebar() as readonly SidebarNodeConfig[] | undefined\n return resolveSidebar(registry, project, entries)\n }, [registry, project])\n}\n"]}
|
|
@@ -1,13 +1,87 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { ComponentNode,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import type { ComponentNode, InsertTarget, PropValue } from '@elytracms/core/project-graph';
|
|
3
|
+
/**
|
|
4
|
+
* A canvas preview-width key (EC-250): `'full'` (unconstrained) or a configured
|
|
5
|
+
* breakpoint name (`'sm'`, `'md'`, …). Dynamic because the set is sourced from
|
|
6
|
+
* the consuming site's breakpoints, so it is a string, not a fixed union — the
|
|
7
|
+
* switcher resolves the available options via {@link resolveCanvasViewports}.
|
|
8
|
+
*/
|
|
9
|
+
export type Viewport = string;
|
|
10
|
+
/**
|
|
11
|
+
* The builder store's action union. EC-287 retired the DB-resident project graph
|
|
12
|
+
* and its persistence mirror, so these actions are pure in-memory editor state —
|
|
13
|
+
* no action maps to a persisted `graph.*` command anymore. The block-field editor
|
|
14
|
+
* lifts committed edits into the CMS document via `onChange`
|
|
15
|
+
* (`composition-field-editor`); nothing persists a graph.
|
|
16
|
+
*/
|
|
17
|
+
export type BuilderAction = {
|
|
18
|
+
type: 'select';
|
|
19
|
+
id: string | null;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'hover';
|
|
22
|
+
id: string | null;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'viewport';
|
|
25
|
+
viewport: Viewport;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'undo';
|
|
28
|
+
} | {
|
|
29
|
+
type: 'redo';
|
|
30
|
+
} | {
|
|
31
|
+
type: 'insert';
|
|
32
|
+
target: InsertTarget;
|
|
33
|
+
node: ComponentNode;
|
|
34
|
+
select?: boolean;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'remove';
|
|
37
|
+
id: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'duplicate';
|
|
40
|
+
id: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'reorder';
|
|
43
|
+
id: string;
|
|
44
|
+
delta: -1 | 1;
|
|
45
|
+
} | {
|
|
46
|
+
type: 'move';
|
|
47
|
+
id: string;
|
|
48
|
+
target: InsertTarget;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* EC-162 layout gesture: wrap the reference node and the dragged subject in
|
|
52
|
+
* a two-column Grid container as ONE atomic structural edit (one undo step).
|
|
53
|
+
*/
|
|
54
|
+
| {
|
|
55
|
+
type: 'wrap';
|
|
56
|
+
referenceId: string;
|
|
57
|
+
side: 'left' | 'right';
|
|
58
|
+
subject: {
|
|
59
|
+
kind: 'node';
|
|
60
|
+
nodeId: string;
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'component';
|
|
63
|
+
node: ComponentNode;
|
|
64
|
+
};
|
|
65
|
+
} | {
|
|
66
|
+
type: 'setProp';
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
value: PropValue;
|
|
70
|
+
coalesceKey?: string;
|
|
71
|
+
} | {
|
|
72
|
+
type: 'removeProp';
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
};
|
|
6
76
|
export interface BuilderState {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The edited composition: one root-bearing component tree (EC-287). The canvas
|
|
79
|
+
* wraps a page document's `body` blocks into a synthetic root and edits it
|
|
80
|
+
* directly — no page/layout wrapper. Undo/redo snapshot this root.
|
|
81
|
+
*/
|
|
82
|
+
past: ComponentNode[];
|
|
83
|
+
present: ComponentNode;
|
|
84
|
+
future: ComponentNode[];
|
|
11
85
|
selectedId: string | null;
|
|
12
86
|
hoveredId: string | null;
|
|
13
87
|
viewport: Viewport;
|
|
@@ -28,41 +102,19 @@ export interface BuilderStore extends BuilderState {
|
|
|
28
102
|
canRedo: boolean;
|
|
29
103
|
selectedNode: ComponentNode | null;
|
|
30
104
|
dispatch: (action: Action) => void;
|
|
31
|
-
/**
|
|
32
|
-
* Most recent failed/denied persisted mutation (structured, from the
|
|
33
|
-
* operation result). Rendered by the shell — never a silent failure.
|
|
34
|
-
*/
|
|
35
|
-
persistError: OperationError | null;
|
|
36
|
-
clearPersistError: () => void;
|
|
37
|
-
/** Await all queued persisted mutations (publish flows, tests). */
|
|
38
|
-
flushPersistence: () => Promise<void>;
|
|
39
105
|
}
|
|
40
|
-
export declare function initialBuilderState(
|
|
106
|
+
export declare function initialBuilderState(composition: ComponentNode): BuilderState;
|
|
41
107
|
/**
|
|
42
|
-
* Undo/redo design (EC-141): the history stack
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* (`graph.save`, message "Undo"/"Redo"). This is the honest mapping of a
|
|
49
|
-
* linear session history onto an append-only revision log: the session and
|
|
50
|
-
* the persisted latest revision always agree, and the persisted timeline
|
|
51
|
-
* records that an undo happened rather than pretending it didn't.
|
|
52
|
-
*
|
|
53
|
-
* Without a `projectId` (styleguide, isolated tests) the store is a pure
|
|
54
|
-
* in-memory editor and persists nothing.
|
|
108
|
+
* Undo/redo design (EC-141): the history stack is *UI state* — an in-memory
|
|
109
|
+
* stack of composition-root snapshots. EC-287 retired the DB-resident project
|
|
110
|
+
* graph and its append-only revision log, so the store no longer mirrors edits onto a
|
|
111
|
+
* persistence layer: it is a pure in-memory editor. The block-field editor lifts
|
|
112
|
+
* its committed composition into the open CMS document via `onChange`
|
|
113
|
+
* (`composition-field-editor`); the document's own Save persists it as content.
|
|
55
114
|
*/
|
|
56
|
-
export declare function BuilderProvider({
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* A pre-built working graph (the composition-field editor's synthetic page);
|
|
61
|
-
* used as-is when present, overriding `graph`.
|
|
62
|
-
*/
|
|
63
|
-
workingGraph?: WorkingGraph;
|
|
64
|
-
/** Enables persistence through the shared operations client. */
|
|
65
|
-
projectId?: string;
|
|
115
|
+
export declare function BuilderProvider({ composition, children, }: {
|
|
116
|
+
/** The composition root to edit (the composition-field editor's synthetic root). */
|
|
117
|
+
composition: ComponentNode;
|
|
66
118
|
children: ReactNode;
|
|
67
119
|
}): import("react").JSX.Element;
|
|
68
120
|
export declare function useBuilder(): BuilderStore;
|