smoodly 0.0.16 → 0.0.18
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 +176 -26
- package/dist/admin/client-ops.js +1 -1
- package/dist/admin/custom-section.d.ts +10 -0
- package/dist/admin/custom-section.js +10 -0
- package/dist/admin/editor/CanvasOverlay.d.ts +4 -4
- package/dist/admin/editor/CanvasOverlay.js +8 -6
- package/dist/admin/editor/EditorView.js +140 -108
- package/dist/admin/editor/GlobalInspector.d.ts +27 -0
- package/dist/admin/editor/GlobalInspector.js +13 -0
- package/dist/admin/editor/InsertPicker.d.ts +6 -5
- package/dist/admin/editor/InsertPicker.js +6 -4
- package/dist/admin/editor/InspectorFields.d.ts +21 -0
- package/dist/admin/editor/InspectorFields.js +22 -0
- package/dist/admin/editor/Outline.d.ts +0 -1
- package/dist/admin/editor/Outline.js +3 -3
- package/dist/admin/editor/{shared-edit.d.ts → global-edit.d.ts} +10 -10
- package/dist/admin/editor/{shared-edit.js → global-edit.js} +4 -4
- package/dist/admin/editor/useGlobalEdit.d.ts +6 -0
- package/dist/admin/editor/{useSharedEdit.js → useGlobalEdit.js} +4 -4
- package/dist/admin/forms/FieldWidget.d.ts +14 -1
- package/dist/admin/forms/FieldWidget.js +38 -11
- package/dist/admin/forms/ListField.d.ts +9 -1
- package/dist/admin/forms/ListField.js +16 -8
- package/dist/admin/forms/RichtextElementCard.js +11 -5
- package/dist/admin/forms/RowsWidget.d.ts +13 -0
- package/dist/admin/forms/RowsWidget.js +26 -0
- package/dist/admin/forms/SpanBar.d.ts +5 -0
- package/dist/admin/forms/SpanBar.js +37 -0
- package/dist/admin/forms/richtext-insert.d.ts +2 -2
- package/dist/admin/forms/richtext-insert.js +3 -3
- package/dist/admin/forms/richtext-nodes.js +1 -1
- package/dist/admin/forms/tabs.d.ts +15 -9
- package/dist/admin/forms/tabs.js +19 -23
- package/dist/admin/forms/useRichtextEditor.js +8 -1
- package/dist/admin/global-items.d.ts +9 -0
- package/dist/admin/{shared-items.js → global-items.js} +17 -16
- package/dist/admin/index.d.ts +3 -3
- package/dist/admin/index.js +1 -1
- package/dist/admin/ops-impl.js +65 -54
- package/dist/admin/ops.d.ts +6 -6
- package/dist/admin/serialize.d.ts +18 -6
- package/dist/admin/serialize.js +27 -4
- package/dist/admin/shell/AdminApp.js +6 -6
- package/dist/admin/shell/GlobalFields.d.ts +24 -0
- package/dist/admin/shell/GlobalFields.js +19 -0
- package/dist/admin/shell/{SharedForm.d.ts → GlobalForm.d.ts} +1 -1
- package/dist/admin/shell/{SharedForm.js → GlobalForm.js} +36 -26
- package/dist/admin/shell/{SharedList.d.ts → GlobalList.d.ts} +1 -1
- package/dist/admin/shell/{SharedList.js → GlobalList.js} +9 -9
- package/dist/admin/shell/IconRail.js +13 -6
- package/dist/admin/shell/PagesList.js +1 -6
- package/dist/admin/shell/{shared-list.d.ts → global-list.d.ts} +3 -3
- package/dist/admin/shell/{shared-list.js → global-list.js} +2 -2
- package/dist/admin/tree-ops.d.ts +10 -8
- package/dist/admin/tree-ops.js +28 -16
- package/dist/admin/ui/ActionMenu.d.ts +3 -1
- package/dist/admin/ui/ActionMenu.js +4 -3
- package/dist/admin/ui/HistoryModal.js +1 -1
- package/dist/admin/ui/PublishControl.d.ts +3 -1
- package/dist/admin/ui/PublishControl.js +2 -2
- package/dist/admin/ui/primitives.d.ts +4 -2
- package/dist/admin/ui/primitives.js +3 -3
- package/dist/admin/ui/theme.js +61 -18
- package/dist/admin/validate.d.ts +27 -2
- package/dist/admin/validate.js +47 -10
- package/dist/collections.d.ts +3 -3
- package/dist/collections.js +1 -1
- package/dist/conditions.d.ts +33 -0
- package/dist/conditions.js +79 -0
- package/dist/config.d.ts +26 -19
- package/dist/config.js +26 -25
- package/dist/css.d.ts +7 -0
- package/dist/css.js +57 -0
- package/dist/custom/CustomSection.d.ts +26 -0
- package/dist/custom/CustomSection.js +20 -0
- package/dist/custom/css.d.ts +3 -0
- package/dist/custom/css.js +28 -0
- package/dist/custom/index.d.ts +26 -0
- package/dist/custom/index.js +16 -0
- package/dist/custom/rows.d.ts +28 -0
- package/dist/custom/rows.js +129 -0
- package/dist/custom/schema.d.ts +32 -0
- package/dist/custom/schema.js +39 -0
- package/dist/entry-page.d.ts +5 -0
- package/dist/entry-page.js +1 -1
- package/dist/entry-store.d.ts +4 -4
- package/dist/entry-store.js +17 -17
- package/dist/fields.d.ts +5 -2
- package/dist/fields.js +4 -2
- package/dist/{shared-id.d.ts → global-id.d.ts} +1 -1
- package/dist/{shared-id.js → global-id.js} +4 -4
- package/dist/global.d.ts +57 -0
- package/dist/global.js +51 -0
- package/dist/index.d.ts +31 -21
- package/dist/index.js +11 -6
- package/dist/media.js +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/page-renderer.d.ts +4 -2
- package/dist/next/page-renderer.js +16 -12
- package/dist/next/queries.d.ts +6 -6
- package/dist/next/queries.js +7 -7
- package/dist/page.js +2 -2
- package/dist/pairing.d.ts +13 -7
- package/dist/pairing.js +4 -4
- package/dist/paths.d.ts +21 -0
- package/dist/paths.js +25 -0
- package/dist/refs.d.ts +2 -2
- package/dist/refs.js +4 -4
- package/dist/render.d.ts +23 -1
- package/dist/render.js +12 -6
- package/dist/resolve.d.ts +11 -6
- package/dist/resolve.js +22 -12
- package/dist/revalidate.d.ts +4 -4
- package/dist/revalidate.js +4 -4
- package/dist/richtext-render.js +2 -2
- package/dist/richtext-walk.d.ts +5 -5
- package/dist/richtext-walk.js +4 -4
- package/dist/schema.d.ts +15 -10
- package/dist/schema.js +19 -7
- package/dist/section-wrapper.d.ts +18 -6
- package/dist/section-wrapper.js +24 -17
- package/dist/site.d.ts +29 -14
- package/dist/site.js +55 -28
- package/dist/sql-space.js +1 -1
- package/dist/sql.js +2 -2
- package/dist/store.d.ts +7 -0
- package/dist/store.js +6 -0
- package/dist/styles.d.ts +27 -0
- package/dist/styles.js +60 -0
- package/dist/supabase-entry-store.js +14 -14
- package/dist/supabase-store.js +2 -1
- package/dist/zones.d.ts +9 -7
- package/dist/zones.js +5 -5
- package/package.json +1 -1
- package/dist/admin/editor/SharedInspector.d.ts +0 -23
- package/dist/admin/editor/SharedInspector.js +0 -13
- package/dist/admin/editor/useSharedEdit.d.ts +0 -6
- package/dist/admin/shared-items.d.ts +0 -9
- package/dist/admin/shell/SharedFields.d.ts +0 -21
- package/dist/admin/shell/SharedFields.js +0 -15
- package/dist/shared.d.ts +0 -53
- package/dist/shared.js +0 -45
package/dist/site.js
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
import { ARRAY_FIELD_TYPES, entryFieldsIn, localizedKeys, schemaOf } from "./entry-store.js";
|
|
2
|
-
import { buildPageTree, chainOf, entryPathRows, entrySegment, pagePath, } from "./paths.js";
|
|
2
|
+
import { buildPageTree, chainOf, entryAlternates, entryPathRows, entrySegment, pageAlternates, pagePath, } from "./paths.js";
|
|
3
3
|
import { resolveFields, resolveMedia, resolveMediaSets, resolveTree } from "./resolve.js";
|
|
4
4
|
import { elementSchemas } from "./richtext-walk.js";
|
|
5
|
-
import {
|
|
5
|
+
import { globalNodeId, SETTINGS_KEY, STYLES_KEY, CSS_KEY } from "./global.js";
|
|
6
|
+
import { SMD_KEY } from "./section-wrapper.js";
|
|
7
|
+
import { resolveStyleValues } from "./styles.js";
|
|
8
|
+
import { resolveCustom } from "./custom/index.js";
|
|
6
9
|
import { withDefaults } from "./field-defaults.js";
|
|
7
|
-
/** The `page` prop for a loaded page. `
|
|
8
|
-
*
|
|
9
|
-
export function pageContextOf(hit,
|
|
10
|
-
const withUrl = (l) => ({ ...l, url:
|
|
10
|
+
/** The `page` prop for a loaded page. `href` is the app's seam from a
|
|
11
|
+
* locale-relative path to a site URL; the default keeps CMS paths as URLs. */
|
|
12
|
+
export function pageContextOf(hit, href = (p) => p) {
|
|
13
|
+
const withUrl = (l) => ({ ...l, url: href(l.path, hit.locale) });
|
|
11
14
|
return {
|
|
12
15
|
id: hit.record.id,
|
|
13
16
|
title: hit.title,
|
|
14
17
|
path: hit.path,
|
|
15
|
-
url:
|
|
18
|
+
url: href(hit.path, hit.locale),
|
|
19
|
+
locale: hit.locale,
|
|
16
20
|
fields: hit.fields,
|
|
17
21
|
ancestors: hit.ancestors.map(withUrl),
|
|
18
22
|
children: hit.children.map(withUrl),
|
|
23
|
+
locales: Object.fromEntries(Object.entries(hit.locales).map(([code, alt]) => [code, alt && { ...alt, url: href(alt.path, code) }])),
|
|
19
24
|
};
|
|
20
25
|
}
|
|
21
26
|
export function createSmoodlySite(options) {
|
|
22
27
|
const locales = options.locales;
|
|
23
28
|
const homePageSlug = options.homePageSlug ?? "home";
|
|
29
|
+
const styles = resolveStyleValues(options.styles);
|
|
30
|
+
const custom = resolveCustom(options.custom);
|
|
24
31
|
const elements = elementSchemas(options.elements);
|
|
25
32
|
const pathOpts = { locales, homePageSlug };
|
|
26
33
|
const mounted = options.collections.filter((c) => c.path !== undefined);
|
|
@@ -33,9 +40,9 @@ export function createSmoodlySite(options) {
|
|
|
33
40
|
const assets = options.stores().assets;
|
|
34
41
|
return assets ? (ids) => assets.getMany(ids) : undefined;
|
|
35
42
|
};
|
|
36
|
-
const
|
|
43
|
+
const sectionSettings = (name) => {
|
|
37
44
|
const s = options.sections.find((x) => ("schema" in x ? x.schema : x).name === name);
|
|
38
|
-
return (s && ("schema" in s ? s.schema : s).
|
|
45
|
+
return (s && ("schema" in s ? s.schema : s).settings) ?? {};
|
|
39
46
|
};
|
|
40
47
|
/** Visible to the public in a locale: present there, and a folder or a
|
|
41
48
|
* published page. Draft mode sees every present node. Presence is the
|
|
@@ -108,11 +115,12 @@ export function createSmoodlySite(options) {
|
|
|
108
115
|
const tree = await resolveTree(raw, {
|
|
109
116
|
sections: options.sections,
|
|
110
117
|
collections: options.collections,
|
|
111
|
-
|
|
118
|
+
globals: options.globals,
|
|
112
119
|
elements: options.elements,
|
|
113
120
|
fetch: fetcher(draft, locale),
|
|
114
121
|
assets: assetFetcher(),
|
|
115
122
|
page: { fields: pageFields(record.template) },
|
|
123
|
+
custom,
|
|
116
124
|
});
|
|
117
125
|
const all = await pages.listPages();
|
|
118
126
|
const index = new Map(all.map((n) => [n.id, n]));
|
|
@@ -122,10 +130,12 @@ export function createSmoodlySite(options) {
|
|
|
122
130
|
const fields = await linkFields([...chain.slice(0, -1), ...kids], locale, draft);
|
|
123
131
|
const ancestors = chain.slice(0, -1).map((n) => link(n, byId, locale, draft, fields));
|
|
124
132
|
const children = kids.map((n) => link(n, byId, locale, draft, fields));
|
|
125
|
-
// Alternates advertise PUBLISHED locales only; the editor (draft) sees every present
|
|
126
|
-
|
|
133
|
+
// Alternates advertise PUBLISHED locales only; the editor (draft) sees every present
|
|
134
|
+
// one. Derived from the tree already listed — no index lookup (spec 2026-09-15 §2).
|
|
135
|
+
const alternates = pageAlternates(record, byId, pathOpts, (l) => draft || record.locales[l]?.status === "published");
|
|
127
136
|
return {
|
|
128
137
|
kind: "page",
|
|
138
|
+
locale,
|
|
129
139
|
record,
|
|
130
140
|
template: record.template,
|
|
131
141
|
tree,
|
|
@@ -134,7 +144,7 @@ export function createSmoodlySite(options) {
|
|
|
134
144
|
fields: withDefaults(tree.fields, pageFields(record.template)),
|
|
135
145
|
ancestors,
|
|
136
146
|
children,
|
|
137
|
-
|
|
147
|
+
locales: alternates,
|
|
138
148
|
};
|
|
139
149
|
}
|
|
140
150
|
async function loadEntry(id, locale, path, draft) {
|
|
@@ -156,8 +166,11 @@ export function createSmoodlySite(options) {
|
|
|
156
166
|
return null;
|
|
157
167
|
const fields = await resolveFields(raw, schema.fields, { collections: options.collections, elements: options.elements, fetch: fetcher(draft, locale), assets: assetFetcher() });
|
|
158
168
|
// Alternates advertise PUBLISHED locales only; the editor (draft) sees every present one.
|
|
159
|
-
|
|
160
|
-
|
|
169
|
+
// One record is in memory (a nested entry's parents are not), so the `paths` index
|
|
170
|
+
// supplies the paths — the same single lookup as before (spec 2026-09-15 §2).
|
|
171
|
+
const known = await entries.pathsOf(schema.name, id);
|
|
172
|
+
const alternates = entryAlternates(record, locales, (l) => draft || record.locales[l]?.status === "published", (l) => known[l] ?? null);
|
|
173
|
+
return { kind: "entry", locale, collection: schema.name, entry: { ...record, fields }, path, locales: alternates };
|
|
161
174
|
}
|
|
162
175
|
return null;
|
|
163
176
|
}
|
|
@@ -165,15 +178,21 @@ export function createSmoodlySite(options) {
|
|
|
165
178
|
/** One locale's published fields and path for entries of one collection;
|
|
166
179
|
* `all` is the whole collection (a draft ancestor still contributes
|
|
167
180
|
* its segment). `fieldsById` is the published read for the locale. */
|
|
168
|
-
function entryItems(schema, wanted, all, locale, fieldsById) {
|
|
181
|
+
function entryItems(schema, wanted, all, locale, draft, fieldsById) {
|
|
169
182
|
const index = new Map(all.map((e) => [e.id, e]));
|
|
170
183
|
const byId = (x) => index.get(x);
|
|
171
184
|
return wanted
|
|
172
185
|
.filter((record) => fieldsById[record.id] !== undefined)
|
|
173
|
-
.map((record) =>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
186
|
+
.map((record) => {
|
|
187
|
+
const rows = entryPathRows([record], byId, schema, locales);
|
|
188
|
+
const pathIn = (l) => rows.find((r) => r.locale === l)?.path ?? null;
|
|
189
|
+
return {
|
|
190
|
+
entry: { ...record, fields: fieldsById[record.id] },
|
|
191
|
+
path: pathIn(locale),
|
|
192
|
+
locale,
|
|
193
|
+
locales: entryAlternates(record, locales, (l) => draft || record.locales[l]?.status === "published", pathIn),
|
|
194
|
+
};
|
|
195
|
+
});
|
|
177
196
|
}
|
|
178
197
|
async function getEntries(collection, o = {}) {
|
|
179
198
|
const schema = schemaOf(options.collections, collection);
|
|
@@ -214,7 +233,7 @@ export function createSmoodlySite(options) {
|
|
|
214
233
|
const fetchAssets = assetFetcher();
|
|
215
234
|
if (fetchAssets)
|
|
216
235
|
await resolveMediaSets(Object.values(fieldsById).map((values) => ({ values, descriptors: schema.fields })), fetchAssets, elements);
|
|
217
|
-
return entryItems(schema, inLocale, all, locale, fieldsById);
|
|
236
|
+
return entryItems(schema, inLocale, all, locale, draft, fieldsById);
|
|
218
237
|
}
|
|
219
238
|
const api = {
|
|
220
239
|
async getPagesTree(o) {
|
|
@@ -251,7 +270,7 @@ export function createSmoodlySite(options) {
|
|
|
251
270
|
const locale = localeOf(o);
|
|
252
271
|
if (idOrPath.startsWith("/")) {
|
|
253
272
|
const hit = await site.resolve(locale, idOrPath, { draft: o?.draft ?? false });
|
|
254
|
-
return hit?.kind === "entry" && hit.collection === collection ? { entry: hit.entry, path: hit.path } : null;
|
|
273
|
+
return hit?.kind === "entry" && hit.collection === collection ? { entry: hit.entry, path: hit.path, locale: hit.locale, locales: hit.locales } : null;
|
|
255
274
|
}
|
|
256
275
|
const items = await getEntries(collection, { locale, draft: o?.draft ?? false });
|
|
257
276
|
const byId = items.find((i) => i.entry.id === idOrPath);
|
|
@@ -267,7 +286,7 @@ export function createSmoodlySite(options) {
|
|
|
267
286
|
const build = (parentId) => items.filter((i) => i.entry.parentId === parentId).map((i) => ({ ...i, children: build(i.entry.id) }));
|
|
268
287
|
return build(null);
|
|
269
288
|
},
|
|
270
|
-
async
|
|
289
|
+
async getGlobal(item, o) {
|
|
271
290
|
const locale = localeOf(o);
|
|
272
291
|
const draft = o?.draft ?? false;
|
|
273
292
|
const { entries } = options.stores();
|
|
@@ -285,18 +304,26 @@ export function createSmoodlySite(options) {
|
|
|
285
304
|
const fields = await resolveFields(raw, item.fields, { collections: options.collections, elements: options.elements, fetch: fetcher(draft, locale), assets: assetFetcher() });
|
|
286
305
|
if (!item.section)
|
|
287
306
|
return fields;
|
|
288
|
-
const { [STYLES_KEY]:
|
|
307
|
+
const { [SETTINGS_KEY]: settings, [STYLES_KEY]: steps, [CSS_KEY]: css, ...rest } = fields;
|
|
289
308
|
const fetch = assetFetcher();
|
|
290
|
-
const own =
|
|
309
|
+
const own = settings !== null && typeof settings === "object" ? settings : {};
|
|
291
310
|
if (fetch)
|
|
292
|
-
await resolveMedia(own,
|
|
293
|
-
// The layout node id
|
|
294
|
-
|
|
311
|
+
await resolveMedia(own, sectionSettings(item.section), fetch, elements);
|
|
312
|
+
// The layout node id, the css and the values ride the styles under the reserved key (spec 2026-09-13 §1, 2026-09-14 §2, §4):
|
|
313
|
+
// the wrapper strips it and emits data-smd-node, so the canvas selects the item and the css reaches it.
|
|
314
|
+
const stepsObject = steps !== null && typeof steps === "object" ? steps : {};
|
|
315
|
+
return {
|
|
316
|
+
...rest,
|
|
317
|
+
settings: own,
|
|
318
|
+
styles: { ...stepsObject, [SMD_KEY]: { node: globalNodeId(item.name), css: typeof css === "string" ? css : undefined, values: styles } },
|
|
319
|
+
};
|
|
295
320
|
},
|
|
296
321
|
};
|
|
297
322
|
const site = {
|
|
298
323
|
locales,
|
|
299
324
|
homePageSlug,
|
|
325
|
+
styles,
|
|
326
|
+
custom,
|
|
300
327
|
locate: (locale, path) => options.stores().pages.resolvePath(locale, path),
|
|
301
328
|
load(target, locale, path, opts = {}) {
|
|
302
329
|
const draft = opts.draft ?? false;
|
package/dist/sql-space.js
CHANGED
|
@@ -31,7 +31,7 @@ $$;`.trim();
|
|
|
31
31
|
}
|
|
32
32
|
/** Which rows a read key (or the anon key, which has no claim) may see.
|
|
33
33
|
* null = write keys only — refs is editor tooling; editors is admission
|
|
34
|
-
* data; nothing a browser needs.
|
|
34
|
+
* data; nothing a browser needs. Global content (spec 2026-09-07) rides
|
|
35
35
|
* the three entry predicates: it is stored as entries. */
|
|
36
36
|
const PUBLISHED_READ = {
|
|
37
37
|
pages: `exists (select 1 from "page_locales" l where l."page_id" = "pages"."id" and l."status" = 'published')`,
|
package/dist/sql.js
CHANGED
|
@@ -31,7 +31,7 @@ function fieldExpr(key, d, source) {
|
|
|
31
31
|
case "date":
|
|
32
32
|
return `(${source}->>'${key}')::timestamptz`;
|
|
33
33
|
default:
|
|
34
|
-
return `${source}->'${key}'`; // image, video, file, object, list, richtext,
|
|
34
|
+
return `${source}->'${key}'`; // image, video, file, object, list, richtext, refList
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
export function collectionSQL(collection) {
|
|
@@ -179,7 +179,7 @@ create index if not exists "entry_locales_status_idx"
|
|
|
179
179
|
-- A version belongs to ONE locale of one entry and stores the MERGED
|
|
180
180
|
-- fields (shared + localized + slug) as they were at that save, so
|
|
181
181
|
-- restoring an old Finnish version restores the shared fields too.
|
|
182
|
-
-- Rows for every collection and every
|
|
182
|
+
-- Rows for every collection and every global item (spec 2026-09-14 §1).
|
|
183
183
|
-- One row per publish plus the live draft (spec 2026-09-13 history §1).
|
|
184
184
|
create table if not exists "entry_versions" (
|
|
185
185
|
"id" uuid primary key default gen_random_uuid(),
|
package/dist/store.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PageTree } from "./render.tsx";
|
|
2
2
|
import type { EntryStore } from "./entry-store.ts";
|
|
3
3
|
import { type RefEdge, type SectionLike } from "./refs.ts";
|
|
4
|
+
import type { Toolset } from "./toolset.ts";
|
|
4
5
|
import type { MemoryRefIndex } from "./ref-index.ts";
|
|
5
6
|
import { type PathIndex } from "./path-index.ts";
|
|
6
7
|
import { type LocaleSet, type PagePathOptions, type PathTarget } from "./paths.ts";
|
|
@@ -107,6 +108,10 @@ export type PageStoreOptions = {
|
|
|
107
108
|
sections?: SectionLike[];
|
|
108
109
|
/** Registered Elements a richtext document may embed (spec 2026-09-08 block nodes §2). */
|
|
109
110
|
elements?: SectionLike[];
|
|
111
|
+
/** The custom section's columns toolset (spec 2026-09-14 §6); with `sections` given, the custom schema always joins the ref walk. */
|
|
112
|
+
custom?: {
|
|
113
|
+
toolset?: Toolset;
|
|
114
|
+
};
|
|
110
115
|
entries?: EntryStore;
|
|
111
116
|
/** The shared URL index (one instance for pages AND entries). */
|
|
112
117
|
paths?: PathIndex;
|
|
@@ -117,6 +122,8 @@ export type PageStoreOptions = {
|
|
|
117
122
|
};
|
|
118
123
|
};
|
|
119
124
|
export declare const DEFAULT_LOCALES: LocaleSet;
|
|
125
|
+
/** The section list every adapter walks refs with: the registered ones plus the custom section's schema (spec 2026-09-14 §6). */
|
|
126
|
+
export declare function refSections(options: PageStoreOptions): SectionLike[] | undefined;
|
|
120
127
|
/** The options every adapter resolves the same way. */
|
|
121
128
|
export declare function pathOptions(options: PageStoreOptions): PagePathOptions & {
|
|
122
129
|
depth: number;
|
package/dist/store.js
CHANGED
|
@@ -11,9 +11,14 @@
|
|
|
11
11
|
// locale — presence IS the row.
|
|
12
12
|
import { ASSET_COLLECTION } from "./media.js";
|
|
13
13
|
import { collectTreeRefs } from "./refs.js";
|
|
14
|
+
import { customToolset, withCustomSchema } from "./custom/index.js";
|
|
14
15
|
import { MemoryPathIndex } from "./path-index.js";
|
|
15
16
|
import { assertDepth, assertSegment, assertSupportedLocale, chainOf, isHomeNode, pagePathRows, unsupportedLocale, } from "./paths.js";
|
|
16
17
|
export const DEFAULT_LOCALES = { default: "en", supported: ["en"] };
|
|
18
|
+
/** The section list every adapter walks refs with: the registered ones plus the custom section's schema (spec 2026-09-14 §6). */
|
|
19
|
+
export function refSections(options) {
|
|
20
|
+
return options.sections ? withCustomSchema(options.sections, customToolset(options.custom?.toolset)) : undefined;
|
|
21
|
+
}
|
|
17
22
|
/** The options every adapter resolves the same way. */
|
|
18
23
|
export function pathOptions(options) {
|
|
19
24
|
return {
|
|
@@ -98,6 +103,7 @@ export class MemoryPageStore {
|
|
|
98
103
|
/** Append-only creation-order tie-break; `size + 1` stays fresh only because entries are never deleted. */
|
|
99
104
|
this.seq = new Map();
|
|
100
105
|
this.byId = (id) => this.pages.get(id);
|
|
106
|
+
this.options = { ...options, sections: refSections(options) };
|
|
101
107
|
this.paths = options.paths ?? new MemoryPathIndex();
|
|
102
108
|
this.opts = pathOptions(options);
|
|
103
109
|
}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Descriptor } from "./fields.ts";
|
|
2
|
+
export type StyleValues = {
|
|
3
|
+
spacing: Record<string, string>;
|
|
4
|
+
width: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
export type StyleValuesInput = {
|
|
7
|
+
spacing?: Record<string, string>;
|
|
8
|
+
width?: Record<string, string>;
|
|
9
|
+
};
|
|
10
|
+
export declare const DEFAULT_STYLE_VALUES: StyleValues;
|
|
11
|
+
/** The "0" step: always present, always `0`, never a config key. */
|
|
12
|
+
export declare const SPACING_NONE = "none";
|
|
13
|
+
export declare const ALIGN_STEPS: readonly ["left", "center", "right"];
|
|
14
|
+
export declare const STYLE_KEYS: readonly ["spacingTop", "spacingBottom", "width", "align"];
|
|
15
|
+
export type StyleKey = (typeof STYLE_KEYS)[number];
|
|
16
|
+
/** What a view receives: steps, not CSS. A config may add steps, so the strings stay open. */
|
|
17
|
+
export type StyleProps = {
|
|
18
|
+
spacingTop?: string;
|
|
19
|
+
spacingBottom?: string;
|
|
20
|
+
width?: string;
|
|
21
|
+
align?: (typeof ALIGN_STEPS)[number];
|
|
22
|
+
};
|
|
23
|
+
export declare function resolveStyleValues(input?: StyleValuesInput): StyleValues;
|
|
24
|
+
/** The set as admin descriptors: optional radios (the empty pill is Default), product labels. */
|
|
25
|
+
export declare function styleDescriptors(values: StyleValues, omit?: readonly string[]): Record<string, Descriptor>;
|
|
26
|
+
/** The inline CSS for a node's steps. Unknown steps and non-strings render nothing. */
|
|
27
|
+
export declare function inlineStyles(steps: Record<string, unknown> | undefined, values: StyleValues): Record<string, string | number>;
|
package/dist/styles.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const DEFAULT_STYLE_VALUES = {
|
|
2
|
+
spacing: { sm: "1.5rem", md: "3.5rem", lg: "5.5rem" },
|
|
3
|
+
width: { narrow: "40rem", wide: "90rem", full: "none" },
|
|
4
|
+
};
|
|
5
|
+
/** The "0" step: always present, always `0`, never a config key. */
|
|
6
|
+
export const SPACING_NONE = "none";
|
|
7
|
+
export const ALIGN_STEPS = ["left", "center", "right"];
|
|
8
|
+
export const STYLE_KEYS = ["spacingTop", "spacingBottom", "width", "align"];
|
|
9
|
+
export function resolveStyleValues(input) {
|
|
10
|
+
const spacing = { ...DEFAULT_STYLE_VALUES.spacing, ...(input?.spacing ?? {}) };
|
|
11
|
+
if (SPACING_NONE in spacing)
|
|
12
|
+
throw new Error(`smoodly: styles.spacing cannot define "${SPACING_NONE}" — the 0 step is always 0.`);
|
|
13
|
+
const width = { ...DEFAULT_STYLE_VALUES.width, ...(input?.width ?? {}) };
|
|
14
|
+
for (const [group, map] of Object.entries({ spacing, width })) {
|
|
15
|
+
for (const [k, v] of Object.entries(map)) {
|
|
16
|
+
if (typeof v !== "string" || v === "")
|
|
17
|
+
throw new Error(`smoodly: styles.${group}.${k} must be a CSS value.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return { spacing, width };
|
|
21
|
+
}
|
|
22
|
+
const SPACING_LABELS = { [SPACING_NONE]: "0", sm: "S", md: "M", lg: "L" };
|
|
23
|
+
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
24
|
+
const radio = (options) => ({ type: "radio", optional: true, options });
|
|
25
|
+
/** The set as admin descriptors: optional radios (the empty pill is Default), product labels. */
|
|
26
|
+
export function styleDescriptors(values, omit = []) {
|
|
27
|
+
const spacingOptions = () => [
|
|
28
|
+
{ value: SPACING_NONE, label: "0" },
|
|
29
|
+
...Object.keys(values.spacing).map((k) => ({ value: k, label: SPACING_LABELS[k] ?? k.toUpperCase() })),
|
|
30
|
+
];
|
|
31
|
+
const all = {
|
|
32
|
+
spacingTop: radio(spacingOptions()),
|
|
33
|
+
spacingBottom: radio(spacingOptions()),
|
|
34
|
+
width: radio(Object.keys(values.width).map((k) => ({ value: k, label: capitalize(k) }))),
|
|
35
|
+
align: radio(ALIGN_STEPS.map((k) => ({ value: k, label: capitalize(k) }))),
|
|
36
|
+
};
|
|
37
|
+
const out = {};
|
|
38
|
+
for (const key of STYLE_KEYS)
|
|
39
|
+
if (!omit.includes(key))
|
|
40
|
+
out[key] = all[key];
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/** The inline CSS for a node's steps. Unknown steps and non-strings render nothing. */
|
|
44
|
+
export function inlineStyles(steps, values) {
|
|
45
|
+
const out = {};
|
|
46
|
+
if (!steps)
|
|
47
|
+
return out;
|
|
48
|
+
const spacing = (v) => v === SPACING_NONE ? 0 : typeof v === "string" && v in values.spacing ? values.spacing[v] : undefined;
|
|
49
|
+
const top = spacing(steps.spacingTop);
|
|
50
|
+
if (top !== undefined)
|
|
51
|
+
out.paddingTop = top;
|
|
52
|
+
const bottom = spacing(steps.spacingBottom);
|
|
53
|
+
if (bottom !== undefined)
|
|
54
|
+
out.paddingBottom = bottom;
|
|
55
|
+
if (typeof steps.width === "string" && steps.width in values.width)
|
|
56
|
+
out["--smd-width"] = values.width[steps.width];
|
|
57
|
+
if (typeof steps.align === "string" && ALIGN_STEPS.includes(steps.align))
|
|
58
|
+
out.textAlign = steps.align;
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// calls the smoodly_reorder SQL function: one statement, updated_at
|
|
8
8
|
// untouched.
|
|
9
9
|
import { collectionDepth, collectionOrder } from "./collections.js";
|
|
10
|
-
import { ARRAY_FIELD_TYPES, DEFAULT_ENTRY_LOCALES, entryErrors, entryFieldsIn, entryLocaleRow, entryRefEdges, entryTitleIn,
|
|
11
|
-
import {
|
|
10
|
+
import { ARRAY_FIELD_TYPES, DEFAULT_ENTRY_LOCALES, entryErrors, entryFieldsIn, entryLocaleRow, entryRefEdges, entryTitleIn, isGlobalSchema, schemaOf, splitFields, } from "./entry-store.js";
|
|
11
|
+
import { globalEntryId } from "./global-id.js";
|
|
12
12
|
import { SupabasePathIndex } from "./supabase-path-index.js";
|
|
13
13
|
import { assertDepth, assertSupportedLocale, entryPathRows, slugOf } from "./paths.js";
|
|
14
14
|
// entries.id is a Postgres uuid column: filtering on a non-UUID string
|
|
@@ -48,7 +48,7 @@ export class SupabaseEntryStore {
|
|
|
48
48
|
this.db = db;
|
|
49
49
|
this.paths = options.paths ?? new SupabasePathIndex(db);
|
|
50
50
|
this.locales = options.locales ?? DEFAULT_ENTRY_LOCALES;
|
|
51
|
-
this.schemas = [...collections, ...(options.
|
|
51
|
+
this.schemas = [...collections, ...(options.globals ?? [])];
|
|
52
52
|
this.elements = options.elements;
|
|
53
53
|
}
|
|
54
54
|
fail(message) {
|
|
@@ -293,7 +293,7 @@ export class SupabaseEntryStore {
|
|
|
293
293
|
const schema = this.schema(collection);
|
|
294
294
|
assertSupportedLocale(options.locale, this.locales);
|
|
295
295
|
const parentId = options.parentId ?? null;
|
|
296
|
-
if (
|
|
296
|
+
if (isGlobalSchema(schema)) {
|
|
297
297
|
const existing = await this.db.from("entries").select("id").eq("collection", collection).limit(1);
|
|
298
298
|
if (existing.error)
|
|
299
299
|
this.fail(existing.error.message);
|
|
@@ -306,17 +306,17 @@ export class SupabaseEntryStore {
|
|
|
306
306
|
this.assertParentHasLocale(parent, options.locale);
|
|
307
307
|
const { shared, localized, slug } = splitFields(fields, schema);
|
|
308
308
|
await this.assertSiblingSlug(collection, parentId, options.locale, slug);
|
|
309
|
-
// A
|
|
309
|
+
// A global item's row id is DERIVED from (space, name), not generated
|
|
310
310
|
// (spec 2026-09-07 §10): the SELECT above is a fast path two racing
|
|
311
311
|
// materializations can both pass, so the primary key is the backstop
|
|
312
312
|
// — a duplicate would be unremovable, since `delete` refuses every
|
|
313
|
-
//
|
|
313
|
+
// global row.
|
|
314
314
|
const row = { collection, parent_id: parentId, fields: shared };
|
|
315
|
-
if (
|
|
316
|
-
row.id = await
|
|
315
|
+
if (isGlobalSchema(schema))
|
|
316
|
+
row.id = await globalEntryId(await this.space(), collection);
|
|
317
317
|
const inserted = await this.db.from("entries").insert(row).select("id").single();
|
|
318
318
|
if (inserted.error) {
|
|
319
|
-
if (
|
|
319
|
+
if (isGlobalSchema(schema) && inserted.error.code === UNIQUE_VIOLATION)
|
|
320
320
|
throw entryErrors.singleton(collection);
|
|
321
321
|
this.fail(inserted.error.message);
|
|
322
322
|
}
|
|
@@ -423,7 +423,7 @@ export class SupabaseEntryStore {
|
|
|
423
423
|
const row = entryLocaleRow(before, locale);
|
|
424
424
|
await this.ensureLiveSnapshot(before, locale);
|
|
425
425
|
const { shared, localized, slug } = splitFields(fields, schema);
|
|
426
|
-
if (
|
|
426
|
+
if (isGlobalSchema(schema) && slug !== null)
|
|
427
427
|
throw entryErrors.noSlug(collection);
|
|
428
428
|
await this.assertSiblingSlug(collection, before.parentId, locale, slug, id);
|
|
429
429
|
await this.updateNode(id, { fields: shared });
|
|
@@ -511,7 +511,7 @@ export class SupabaseEntryStore {
|
|
|
511
511
|
}
|
|
512
512
|
async removeLocale(collection, id, locale) {
|
|
513
513
|
const schema = this.schema(collection);
|
|
514
|
-
if (
|
|
514
|
+
if (isGlobalSchema(schema))
|
|
515
515
|
throw entryErrors.everyLocale(collection);
|
|
516
516
|
const entry = await this.must(collection, id);
|
|
517
517
|
entryLocaleRow(entry, locale);
|
|
@@ -575,7 +575,7 @@ export class SupabaseEntryStore {
|
|
|
575
575
|
return res.data ? toVersion(res.data) : null;
|
|
576
576
|
}
|
|
577
577
|
async move(collection, id, to) {
|
|
578
|
-
if (
|
|
578
|
+
if (isGlobalSchema(this.schema(collection)))
|
|
579
579
|
throw entryErrors.owned(collection, "it has no tree position");
|
|
580
580
|
const entry = await this.must(collection, id);
|
|
581
581
|
const subtree = await this.subtree(entry);
|
|
@@ -622,7 +622,7 @@ export class SupabaseEntryStore {
|
|
|
622
622
|
const entry = await this.fetch(collection, id);
|
|
623
623
|
if (!entry)
|
|
624
624
|
return;
|
|
625
|
-
if (
|
|
625
|
+
if (isGlobalSchema(this.schema(collection)))
|
|
626
626
|
throw entryErrors.owned(collection, "remove the registration to remove it");
|
|
627
627
|
const children = await this.db.from("entries").select("id").eq("parent_id", id).limit(1);
|
|
628
628
|
if (children.error)
|
|
@@ -649,7 +649,7 @@ export class SupabaseEntryStore {
|
|
|
649
649
|
return (await this.fetch(collection, id)) ? this.paths.pathsOf({ kind: "entry", id }) : {};
|
|
650
650
|
}
|
|
651
651
|
async reorder(collection, orderedIds) {
|
|
652
|
-
if (
|
|
652
|
+
if (isGlobalSchema(this.schema(collection)))
|
|
653
653
|
throw entryErrors.owned(collection, "it has no order");
|
|
654
654
|
const res = await this.db.rpc("smoodly_reorder", { p_collection: collection, p_ids: orderedIds });
|
|
655
655
|
if (res.error)
|
package/dist/supabase-store.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// are two indexed lookups. Every read embeds the locale rows
|
|
7
7
|
// (`*, page_locales(*)`) so a record arrives whole. Path rows are
|
|
8
8
|
// rewritten through ONE RPC per write, so a collision rolls back.
|
|
9
|
-
import { assertPublishable, assertSupportedLocale, localeRow, pageErrors, pageRefEdges, pathOptions, } from "./store.js";
|
|
9
|
+
import { assertPublishable, assertSupportedLocale, localeRow, pageErrors, pageRefEdges, pathOptions, refSections, } from "./store.js";
|
|
10
10
|
import { SupabasePathIndex } from "./supabase-path-index.js";
|
|
11
11
|
import { assertDepth, assertSegment, isHomeNode, pagePathRows } from "./paths.js";
|
|
12
12
|
const SELECT = "*, page_locales(*)";
|
|
@@ -40,6 +40,7 @@ export class SupabasePageStore {
|
|
|
40
40
|
constructor(db, options = {}) {
|
|
41
41
|
this.db = db;
|
|
42
42
|
this.options = options;
|
|
43
|
+
this.options = { ...options, sections: refSections(options) };
|
|
43
44
|
this.paths = options.paths ?? new SupabasePathIndex(db);
|
|
44
45
|
this.opts = pathOptions(options);
|
|
45
46
|
}
|
package/dist/zones.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GlobalSchema } from "./global.ts";
|
|
2
2
|
type WithSchema = {
|
|
3
3
|
schema: {
|
|
4
4
|
name: string;
|
|
@@ -8,16 +8,18 @@ export type ZonePolicy = {
|
|
|
8
8
|
kind: "sections";
|
|
9
9
|
allow: string[];
|
|
10
10
|
max?: number;
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
|
+
/** `custom`: whether editors may add a custom section here (spec 2026-09-14 §6). */
|
|
13
|
+
| {
|
|
12
14
|
kind: "freeform";
|
|
13
15
|
allow: string[] | "*";
|
|
14
|
-
|
|
16
|
+
custom: boolean;
|
|
15
17
|
}
|
|
16
|
-
/** `
|
|
18
|
+
/** `global` names the item a bound zone shows (spec 2026-09-07 §4); `component` is its section either way. */
|
|
17
19
|
| {
|
|
18
20
|
kind: "locked";
|
|
19
21
|
component: string;
|
|
20
|
-
|
|
22
|
+
global?: string;
|
|
21
23
|
};
|
|
22
24
|
export declare const z: {
|
|
23
25
|
sections(components: WithSchema[]): {
|
|
@@ -28,8 +30,8 @@ export declare const z: {
|
|
|
28
30
|
};
|
|
29
31
|
freeform(opts: {
|
|
30
32
|
sections: WithSchema[] | "*";
|
|
31
|
-
|
|
33
|
+
custom?: boolean;
|
|
32
34
|
}): ZonePolicy;
|
|
33
|
-
locked(target: WithSchema |
|
|
35
|
+
locked(target: WithSchema | GlobalSchema): ZonePolicy;
|
|
34
36
|
};
|
|
35
37
|
export {};
|
package/dist/zones.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Zone policies — the freedom dial. Policies reference paired components
|
|
2
2
|
// directly; only their registered names are stored (plain data).
|
|
3
|
-
const
|
|
3
|
+
const isGlobal = (target) => "kind" in target && target.kind === "global";
|
|
4
4
|
const names = (components) => components.map((c) => c.schema.name);
|
|
5
5
|
export const z = {
|
|
6
6
|
sections(components) {
|
|
@@ -15,14 +15,14 @@ export const z = {
|
|
|
15
15
|
return {
|
|
16
16
|
kind: "freeform",
|
|
17
17
|
allow: opts.sections === "*" ? "*" : names(opts.sections),
|
|
18
|
-
|
|
18
|
+
custom: opts.custom ?? false,
|
|
19
19
|
};
|
|
20
20
|
},
|
|
21
21
|
locked(target) {
|
|
22
|
-
if (
|
|
22
|
+
if (isGlobal(target)) {
|
|
23
23
|
if (!target.section)
|
|
24
|
-
throw new Error(`smoodly:
|
|
25
|
-
return { kind: "locked", component: target.section,
|
|
24
|
+
throw new Error(`smoodly: global item "${target.name}" has no section — a locked zone needs a visual item.`);
|
|
25
|
+
return { kind: "locked", component: target.section, global: target.name };
|
|
26
26
|
}
|
|
27
27
|
return { kind: "locked", component: target.schema.name };
|
|
28
28
|
},
|
package/package.json
CHANGED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { AdminShared } from "../serialize.ts";
|
|
2
|
-
import type { TabEntry } from "../forms/tabs.ts";
|
|
3
|
-
import type { SharedEditItem } from "./shared-edit.ts";
|
|
4
|
-
import { type RefOption } from "../shell/SharedFields.tsx";
|
|
5
|
-
import { type PublishBusy } from "../ui/PublishControl.tsx";
|
|
6
|
-
export declare function SharedInspector({ item, edit, locale, entries, multilingual, refOptions, inlinePath, onEditHere, onChange, pending, busy, onPublish, onDiscard, onUnpublish, }: {
|
|
7
|
-
item: AdminShared;
|
|
8
|
-
/** null while the item loads. */
|
|
9
|
-
edit: SharedEditItem | null;
|
|
10
|
-
locale: string;
|
|
11
|
-
entries: TabEntry[];
|
|
12
|
-
multilingual: boolean;
|
|
13
|
-
refOptions: Record<string, RefOption[]>;
|
|
14
|
-
inlinePath?: string;
|
|
15
|
-
onEditHere?(): void;
|
|
16
|
-
onChange(ns: "fields" | "styles", key: string, value: unknown): void;
|
|
17
|
-
/** An edit the store has not confirmed (dirty, or the item's autosave not in `saved`). */
|
|
18
|
-
pending: boolean;
|
|
19
|
-
busy: PublishBusy;
|
|
20
|
-
onPublish(): void;
|
|
21
|
-
onDiscard(): void;
|
|
22
|
-
onUnpublish(): void;
|
|
23
|
-
}): import("react").JSX.Element;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { BASE } from "../shell/base.js";
|
|
4
|
-
import { localeStatusOf } from "../shell/pages-tree.js";
|
|
5
|
-
import { SharedFields } from "../shell/SharedFields.js";
|
|
6
|
-
import { PublishControl } from "../ui/PublishControl.js";
|
|
7
|
-
export function SharedInspector({ item, edit, locale, entries, multilingual, refOptions, inlinePath, onEditHere, onChange, pending, busy, onPublish, onDiscard, onUnpublish, }) {
|
|
8
|
-
const row = edit?.record.locales[locale];
|
|
9
|
-
// The same three states the shared list shows; the control decides what applies.
|
|
10
|
-
const status = row ? localeStatusOf(row) : "draft";
|
|
11
|
-
const pages = edit ? [...new Set(edit.usage.filter((u) => u.sourceKind === "page").map((u) => u.sourceId))] : [];
|
|
12
|
-
return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [_jsx("p", { className: "sm-hint", children: "Shared content \u2014 changes every page it is used on." }), edit && (_jsxs("p", { className: "sm-hint", children: [pages.length === 0 ? "Not placed on any page." : `Used on ${pages.length} page${pages.length === 1 ? "" : "s"}: `, pages.map((id, i) => (_jsxs("span", { children: [i > 0 && ", ", _jsx("a", { href: `${BASE}/pages/${encodeURIComponent(id)}?locale=${encodeURIComponent(locale)}`, children: id })] }, id)))] })), _jsx("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: _jsx(PublishControl, { status: row ? status : null, pending: pending, busy: busy, disabled: !edit, onPublish: onPublish, onDiscard: onDiscard, onUnpublish: onUnpublish }) }), edit ? (_jsx(SharedFields, { item: item, entries: entries, fields: edit.fields, errors: edit.errors, refOptions: refOptions, multilingual: multilingual, inlinePath: inlinePath, onEditHere: onEditHere, onChange: onChange })) : (_jsx("p", { style: { color: "var(--text-muted)" }, children: "Loading\u2026" }))] }));
|
|
13
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { AdminOps } from "../ops.ts";
|
|
2
|
-
import { type SharedEdit, type SharedEditState } from "./shared-edit.ts";
|
|
3
|
-
export declare function useSharedEdit({ ops, onSaved }: {
|
|
4
|
-
ops: Pick<AdminOps, "shared">;
|
|
5
|
-
onSaved?: () => void;
|
|
6
|
-
}): SharedEditState & Pick<SharedEdit, "load" | "patch" | "flush" | "setStatus" | "reset">;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ResolvedConfig } from "../config.ts";
|
|
2
|
-
import type { Descriptor } from "../fields.ts";
|
|
3
|
-
import type { EntryRecord, EntryStore } from "../entry-store.ts";
|
|
4
|
-
import { type SharedSchema } from "../shared.ts";
|
|
5
|
-
/** The merged fields a fresh item starts from: sampled field values, and
|
|
6
|
-
* for a visual item `$styles` = the resolved style defaults with the
|
|
7
|
-
* sample's style values on top. Unsampled fields stay absent. */
|
|
8
|
-
export declare function initialSharedFields(item: SharedSchema, resolvedStyles: Record<string, Descriptor>): Record<string, unknown>;
|
|
9
|
-
export declare function ensureSharedItems(entries: EntryStore, config: ResolvedConfig): Promise<Record<string, EntryRecord>>;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { AdminShared } from "../serialize.ts";
|
|
2
|
-
import type { TabEntry } from "../forms/tabs.ts";
|
|
3
|
-
export type RefOption = {
|
|
4
|
-
id: string;
|
|
5
|
-
title: string;
|
|
6
|
-
status: string;
|
|
7
|
-
};
|
|
8
|
-
export declare function SharedFields({ item, entries, fields, errors, refOptions, multilingual, inlinePath, onEditHere, onChange }: {
|
|
9
|
-
item: AdminShared;
|
|
10
|
-
entries: TabEntry[];
|
|
11
|
-
/** The merged draft fields; a visual item's styles under `$styles`. */
|
|
12
|
-
fields: Record<string, unknown>;
|
|
13
|
-
errors?: Record<string, string>;
|
|
14
|
-
/** Loaded ref options by target collection. */
|
|
15
|
-
refOptions: Record<string, RefOption[]>;
|
|
16
|
-
multilingual: boolean;
|
|
17
|
-
/** The content field with an active inline session: its richtext widget yields to a note. */
|
|
18
|
-
inlinePath?: string;
|
|
19
|
-
onEditHere?(): void;
|
|
20
|
-
onChange(ns: "fields" | "styles", key: string, value: unknown): void;
|
|
21
|
-
}): import("react").JSX.Element;
|