smoodly 0.0.16 → 0.0.17
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 +156 -21
- 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 +5 -5
- package/dist/admin/editor/EditorView.js +140 -108
- package/dist/admin/editor/GlobalInspector.d.ts +24 -0
- package/dist/admin/editor/{SharedInspector.js → GlobalInspector.js} +4 -4
- 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/HistoryModal.js +1 -1
- package/dist/admin/ui/primitives.d.ts +3 -1
- package/dist/admin/ui/primitives.js +2 -2
- package/dist/admin/ui/theme.js +54 -14
- 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-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 +28 -18
- package/dist/index.js +10 -5
- 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 +9 -9
- 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/refs.d.ts +2 -2
- package/dist/refs.js +4 -4
- package/dist/render.d.ts +13 -1
- package/dist/render.js +10 -5
- 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 +15 -5
- package/dist/site.js +24 -10
- 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/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/entry-store.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CollectionSchema, EntryOrder } from "./collections.ts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { GlobalSchema } from "./global.ts";
|
|
3
3
|
import { type RefEdge, type SectionLike } from "./refs.ts";
|
|
4
4
|
import { MemoryRefIndex } from "./ref-index.ts";
|
|
5
5
|
import { type PathIndex } from "./path-index.ts";
|
|
@@ -82,7 +82,7 @@ export type AddEntryLocaleOptions = {
|
|
|
82
82
|
export type EntryStoreOptions = {
|
|
83
83
|
paths?: PathIndex;
|
|
84
84
|
locales?: LocaleSet;
|
|
85
|
-
|
|
85
|
+
globals?: GlobalSchema[];
|
|
86
86
|
/** Registered Elements a richtext document may embed (spec 2026-09-08 block nodes §2). */
|
|
87
87
|
elements?: SectionLike[];
|
|
88
88
|
};
|
|
@@ -154,8 +154,8 @@ export interface EntryStore {
|
|
|
154
154
|
pathsOf(collection: string, id: string): Promise<Record<string, string>>;
|
|
155
155
|
}
|
|
156
156
|
export declare function schemaOf(collections: CollectionSchema<any>[], name: string): CollectionSchema<any>;
|
|
157
|
-
/** A
|
|
158
|
-
export declare const
|
|
157
|
+
/** A global item is a collection with exactly one entry (spec 2026-09-07). */
|
|
158
|
+
export declare const isGlobalSchema: (schema: CollectionSchema<any>) => boolean;
|
|
159
159
|
export declare const DEFAULT_ENTRY_LOCALES: LocaleSet;
|
|
160
160
|
export declare const entryErrors: {
|
|
161
161
|
notFound: (collection: string, id: string) => Error;
|
package/dist/entry-store.js
CHANGED
|
@@ -13,7 +13,7 @@ import { collectionDepth, collectionOrder } from "./collections.js";
|
|
|
13
13
|
import { collectEntryRefs } from "./refs.js";
|
|
14
14
|
import { MemoryRefIndex } from "./ref-index.js";
|
|
15
15
|
import { MemoryPathIndex } from "./path-index.js";
|
|
16
|
-
import {
|
|
16
|
+
import { globalEntryId } from "./global-id.js";
|
|
17
17
|
import { DEFAULT_SPACE_ID } from "./sql-space.js";
|
|
18
18
|
import { assertDepth, assertSupportedLocale, chainOf, entryPathRows, slugOf, unsupportedLocale, } from "./paths.js";
|
|
19
19
|
/** The field types whose value is an array: a scalar `where` on one is
|
|
@@ -40,8 +40,8 @@ export function schemaOf(collections, name) {
|
|
|
40
40
|
throw new Error(`smoodly: no collection named "${name}".`);
|
|
41
41
|
return schema;
|
|
42
42
|
}
|
|
43
|
-
/** A
|
|
44
|
-
export const
|
|
43
|
+
/** A global item is a collection with exactly one entry (spec 2026-09-07). */
|
|
44
|
+
export const isGlobalSchema = (schema) => schema.kind === "global";
|
|
45
45
|
let counter = 0;
|
|
46
46
|
const uid = (prefix) => `${prefix}-${++counter}-${Date.now().toString(36)}`;
|
|
47
47
|
export const DEFAULT_ENTRY_LOCALES = { default: "en", supported: ["en"] };
|
|
@@ -62,11 +62,11 @@ export const entryErrors = {
|
|
|
62
62
|
lastLocale: (id) => new Error(`smoodly: entry "${id}" must exist in at least one locale — delete the entry instead.`),
|
|
63
63
|
localeInUse: (locale, titles) => new Error(`smoodly: child entries still exist in locale "${locale}" — remove it from ${titles.map((t) => `"${t}"`).join(", ")} first.`),
|
|
64
64
|
moveLocales: (locale) => new Error(`smoodly: the destination parent does not exist in locale "${locale}" — the entry cannot move under it.`),
|
|
65
|
-
//
|
|
66
|
-
singleton: (name) => new Error(`smoodly:
|
|
67
|
-
owned: (name, verb) => new Error(`smoodly:
|
|
68
|
-
noSlug: (name) => new Error(`smoodly:
|
|
69
|
-
everyLocale: (name) => new Error(`smoodly:
|
|
65
|
+
// Global items (spec 2026-09-07 §3): one row, no URL, every locale, code-owned.
|
|
66
|
+
singleton: (name) => new Error(`smoodly: global item "${name}" already exists — it is a singleton.`),
|
|
67
|
+
owned: (name, verb) => new Error(`smoodly: global item "${name}" is owned by code — ${verb}.`),
|
|
68
|
+
noSlug: (name) => new Error(`smoodly: global item "${name}" has no URL — it cannot take a slug.`),
|
|
69
|
+
everyLocale: (name) => new Error(`smoodly: global item "${name}" exists in every locale — it cannot be removed from one.`),
|
|
70
70
|
foreignVersion: (collection, id, locale, versionId) => new Error(`smoodly: "${versionId}" is not a version of entry "${id}" in "${collection}", locale "${locale}".`),
|
|
71
71
|
};
|
|
72
72
|
/** The keys a locale row owns: every field with .localized(), except
|
|
@@ -167,7 +167,7 @@ export class MemoryEntryStore {
|
|
|
167
167
|
this.byId = (id) => this.entries.get(id);
|
|
168
168
|
this.paths = options.paths ?? new MemoryPathIndex();
|
|
169
169
|
this.locales = options.locales ?? DEFAULT_ENTRY_LOCALES;
|
|
170
|
-
this.schemas = [...collections, ...(options.
|
|
170
|
+
this.schemas = [...collections, ...(options.globals ?? [])];
|
|
171
171
|
this.elements = options.elements;
|
|
172
172
|
}
|
|
173
173
|
schema(collection) {
|
|
@@ -304,7 +304,7 @@ export class MemoryEntryStore {
|
|
|
304
304
|
const schema = this.schema(collection);
|
|
305
305
|
assertSupportedLocale(options.locale, this.locales);
|
|
306
306
|
const parentId = options.parentId ?? null;
|
|
307
|
-
if (
|
|
307
|
+
if (isGlobalSchema(schema)) {
|
|
308
308
|
if ([...this.entries.values()].some((e) => e.collection === collection))
|
|
309
309
|
throw entryErrors.singleton(collection);
|
|
310
310
|
if (slugOf(fields) !== null)
|
|
@@ -316,10 +316,10 @@ export class MemoryEntryStore {
|
|
|
316
316
|
this.assertSiblingSlug(collection, parentId, options.locale, slug);
|
|
317
317
|
const now = Date.now();
|
|
318
318
|
const entry = {
|
|
319
|
-
// A
|
|
319
|
+
// A global item's id is derived, not minted (spec 2026-09-07 §10):
|
|
320
320
|
// the same uuid v5 the Supabase adapter computes, so self-host and
|
|
321
321
|
// cloud agree on the row a materialization would create.
|
|
322
|
-
id:
|
|
322
|
+
id: isGlobalSchema(schema) ? await globalEntryId(DEFAULT_SPACE_ID, collection) : uid("entry"),
|
|
323
323
|
collection, parentId, fields: shared,
|
|
324
324
|
locales: {
|
|
325
325
|
[options.locale]: {
|
|
@@ -393,7 +393,7 @@ export class MemoryEntryStore {
|
|
|
393
393
|
const row = entryLocaleRow(entry, locale);
|
|
394
394
|
this.ensureLiveSnapshot(entry, locale);
|
|
395
395
|
const { shared, localized, slug } = splitFields(fields, schema);
|
|
396
|
-
if (
|
|
396
|
+
if (isGlobalSchema(schema) && slug !== null)
|
|
397
397
|
throw entryErrors.noSlug(collection);
|
|
398
398
|
this.assertSiblingSlug(collection, entry.parentId, locale, slug, id);
|
|
399
399
|
const before = { fields: entry.fields, row: { ...row } };
|
|
@@ -479,7 +479,7 @@ export class MemoryEntryStore {
|
|
|
479
479
|
}
|
|
480
480
|
async removeLocale(collection, id, locale) {
|
|
481
481
|
const schema = this.schema(collection);
|
|
482
|
-
if (
|
|
482
|
+
if (isGlobalSchema(schema))
|
|
483
483
|
throw entryErrors.everyLocale(collection);
|
|
484
484
|
const entry = this.must(collection, id);
|
|
485
485
|
const row = entryLocaleRow(entry, locale);
|
|
@@ -533,7 +533,7 @@ export class MemoryEntryStore {
|
|
|
533
533
|
return v ? structuredClone(v) : null;
|
|
534
534
|
}
|
|
535
535
|
async move(collection, id, to) {
|
|
536
|
-
if (
|
|
536
|
+
if (isGlobalSchema(this.schema(collection)))
|
|
537
537
|
throw entryErrors.owned(collection, "it has no tree position");
|
|
538
538
|
const entry = this.must(collection, id);
|
|
539
539
|
const subtree = this.subtree(entry);
|
|
@@ -572,7 +572,7 @@ export class MemoryEntryStore {
|
|
|
572
572
|
const entry = this.row(collection, id);
|
|
573
573
|
if (!entry)
|
|
574
574
|
return;
|
|
575
|
-
if (
|
|
575
|
+
if (isGlobalSchema(this.schema(collection)))
|
|
576
576
|
throw entryErrors.owned(collection, "remove the registration to remove it");
|
|
577
577
|
if ([...this.entries.values()].some((e) => e.parentId === id))
|
|
578
578
|
throw entryErrors.hasChildren(collection, id);
|
|
@@ -587,7 +587,7 @@ export class MemoryEntryStore {
|
|
|
587
587
|
this.refs.remove({ sourceKind: "entry", sourceId: id });
|
|
588
588
|
}
|
|
589
589
|
async reorder(collection, orderedIds) {
|
|
590
|
-
if (
|
|
590
|
+
if (isGlobalSchema(this.schema(collection)))
|
|
591
591
|
throw entryErrors.owned(collection, "it has no order");
|
|
592
592
|
orderedIds.forEach((id, i) => {
|
|
593
593
|
const entry = this.row(collection, id);
|
package/dist/fields.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Toolset } from "./toolset.ts";
|
|
2
2
|
import { type ResolvedFile, type ResolvedImage, type ResolvedVideo } from "./media.ts";
|
|
3
|
+
import { type Condition, when } from "./conditions.ts";
|
|
3
4
|
export type Descriptor = {
|
|
4
5
|
type: string;
|
|
5
6
|
[key: string]: unknown;
|
|
@@ -43,7 +44,8 @@ export type FieldBuilder<Value = unknown, Flags extends BuilderFlags = {}> = {
|
|
|
43
44
|
* - no call — hydrate the entry itself; its refs stay ids
|
|
44
45
|
*/
|
|
45
46
|
resolve(...projection: (string | Record<string, unknown>)[]): FieldBuilder<Value, Flags>;
|
|
46
|
-
|
|
47
|
+
/** Hide the field while the condition holds (spec 2026-09-14 §12): it leaves the form and validation skips it; the stored value is kept. */
|
|
48
|
+
hidden(condition: Condition): FieldBuilder<Value, Flags>;
|
|
47
49
|
/** phantom, type-level only */
|
|
48
50
|
readonly __value?: Value;
|
|
49
51
|
readonly __flags?: Flags;
|
|
@@ -83,7 +85,8 @@ export declare const f: {
|
|
|
83
85
|
richtext: (opts?: {
|
|
84
86
|
toolset?: Toolset;
|
|
85
87
|
}) => FieldBuilder<unknown, {}>;
|
|
86
|
-
|
|
88
|
+
/** A condition for `.hidden()`: `f.when("settings.mediaType").is("video")`. */
|
|
89
|
+
when: typeof when;
|
|
87
90
|
boolean: () => FieldBuilder<boolean, {}>;
|
|
88
91
|
/** One value from a list, shown as a dropdown. */
|
|
89
92
|
select: <const O extends readonly OptionInput[]>(options: O) => FieldBuilder<OptionValue<O[number]>, {}>;
|
package/dist/fields.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// keyed by descriptor type. Chaining is immutable.
|
|
4
4
|
import { DEFAULT_TOOLSET } from "./toolset.js";
|
|
5
5
|
import { parseAspect } from "./media.js";
|
|
6
|
+
import { when } from "./conditions.js";
|
|
6
7
|
/** Normalizes a schema's option list to objects; refuses an empty list,
|
|
7
8
|
* since a choice with nothing to choose is a schema bug (spec 2026-09-10
|
|
8
9
|
* page fields §1). */
|
|
@@ -43,7 +44,7 @@ function builder(descriptor) {
|
|
|
43
44
|
return chain({ resolve: projection[0] });
|
|
44
45
|
return chain({ resolve: Object.fromEntries(projection.map((k) => [k, true])) });
|
|
45
46
|
},
|
|
46
|
-
|
|
47
|
+
hidden: (condition) => chain({ hidden: condition }),
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
export const describePath = (path) => (path === "" ? "the item" : `"${path}"`);
|
|
@@ -97,7 +98,8 @@ export const f = {
|
|
|
97
98
|
video: () => builder({ type: "video" }),
|
|
98
99
|
file: () => builder({ type: "file" }),
|
|
99
100
|
richtext: (opts) => builder({ type: "richtext", toolset: opts?.toolset ?? DEFAULT_TOOLSET }),
|
|
100
|
-
|
|
101
|
+
/** A condition for `.hidden()`: `f.when("settings.mediaType").is("video")`. */
|
|
102
|
+
when,
|
|
101
103
|
boolean: () => builder({ type: "boolean" }),
|
|
102
104
|
/** One value from a list, shown as a dropdown. */
|
|
103
105
|
select: (options) => builder({ type: "select", options: normalizeOptions(options) }),
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** RFC 4122 §4.3 uuid v5: sha1(namespace bytes ‖ name bytes), version 5,
|
|
2
2
|
* variant 10xx, first 16 bytes of the digest. */
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function globalEntryId(space: string, name: string): Promise<string>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// The row id of a
|
|
1
|
+
// The row id of a global item (spec 2026-09-07 §10 "§3 row id"): uuid v5
|
|
2
2
|
// of (space id as the namespace, item name as the name). Deterministic in
|
|
3
3
|
// both adapters, so a second concurrent materialization of the same item
|
|
4
4
|
// hits the `entries` primary key instead of adding an unremovable
|
|
5
|
-
// duplicate — `delete` refuses every
|
|
5
|
+
// duplicate — `delete` refuses every global row. The space is part of it
|
|
6
6
|
// because the primary key is `id` alone and, in the cloud, many spaces
|
|
7
7
|
// share one table: a name-only id would make space B's footer collide
|
|
8
8
|
// with space A's.
|
|
@@ -16,7 +16,7 @@ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
|
16
16
|
/** The namespace's 16 raw bytes — RFC 4122 hashes the bytes, not the text. */
|
|
17
17
|
function uuidBytes(uuid) {
|
|
18
18
|
if (!UUID_RE.test(uuid))
|
|
19
|
-
throw new Error(`smoodly: "${uuid}" is not a uuid — a
|
|
19
|
+
throw new Error(`smoodly: "${uuid}" is not a uuid — a global item's namespace is the space id.`);
|
|
20
20
|
const hex = uuid.replace(/-/g, "");
|
|
21
21
|
const bytes = new Uint8Array(16);
|
|
22
22
|
for (let i = 0; i < 16; i++)
|
|
@@ -25,7 +25,7 @@ function uuidBytes(uuid) {
|
|
|
25
25
|
}
|
|
26
26
|
/** RFC 4122 §4.3 uuid v5: sha1(namespace bytes ‖ name bytes), version 5,
|
|
27
27
|
* variant 10xx, first 16 bytes of the digest. */
|
|
28
|
-
export async function
|
|
28
|
+
export async function globalEntryId(space, name) {
|
|
29
29
|
const input = new Uint8Array([...uuidBytes(space), ...new TextEncoder().encode(name)]);
|
|
30
30
|
const digest = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-1", input));
|
|
31
31
|
const bytes = digest.subarray(0, 16);
|
package/dist/global.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CollectionSchema } from "./collections.ts";
|
|
2
|
+
import type { Descriptor } from "./fields.ts";
|
|
3
|
+
import type { PairedComponent } from "./pairing.tsx";
|
|
4
|
+
import type { BuilderMap, NamespaceValues, SectionSchema } from "./schema.ts";
|
|
5
|
+
import type { StyleProps } from "./styles.ts";
|
|
6
|
+
/** The reserved tree node type of a placement. */
|
|
7
|
+
export declare const GLOBAL_NODE_TYPE = "global";
|
|
8
|
+
/** The reserved keys a visual item's settings, styles steps and css live under in its row (spec 2026-09-14 §8). */
|
|
9
|
+
export declare const SETTINGS_KEY = "$settings";
|
|
10
|
+
export declare const STYLES_KEY = "$styles";
|
|
11
|
+
export declare const CSS_KEY = "$css";
|
|
12
|
+
/** The node id a layout-rendered visual item carries (spec 2026-09-13 §1):
|
|
13
|
+
* `getGlobal` stamps it into the styles' `__smd`, the section wrapper emits it,
|
|
14
|
+
* the canvas selects by it. A placement keeps its own tree node id
|
|
15
|
+
* (`global-xxxxxxxx`, no colon). */
|
|
16
|
+
export declare const GLOBAL_NODE_PREFIX = "global:";
|
|
17
|
+
export declare const globalNodeId: (name: string) => string;
|
|
18
|
+
/** The item a layout node id names, or null for any other id. */
|
|
19
|
+
export declare function globalNameOf(id: string): string | null;
|
|
20
|
+
export type GlobalSchema<V = Record<string, unknown>, N extends string = string> = CollectionSchema<any, N> & {
|
|
21
|
+
kind: "global";
|
|
22
|
+
/** The registered section a visual item renders through; absent = an object item. */
|
|
23
|
+
section?: string;
|
|
24
|
+
/** The section's settings descriptors (empty for an object item). */
|
|
25
|
+
settings: Record<string, Descriptor>;
|
|
26
|
+
sample?: Record<string, unknown>;
|
|
27
|
+
/** phantom: what getGlobal returns */
|
|
28
|
+
readonly __value?: V;
|
|
29
|
+
};
|
|
30
|
+
export type GlobalLink = {
|
|
31
|
+
item: string;
|
|
32
|
+
ref: string;
|
|
33
|
+
};
|
|
34
|
+
/** The placement a tree node is, or null for any other node. Accepts a
|
|
35
|
+
* bare `{ type, fields }` shape or a full tree node — `id` (and any
|
|
36
|
+
* other tree-node property) is ignored. */
|
|
37
|
+
export declare function globalLink(node: {
|
|
38
|
+
id?: string;
|
|
39
|
+
type: string;
|
|
40
|
+
fields?: Record<string, unknown>;
|
|
41
|
+
}): GlobalLink | null;
|
|
42
|
+
type GlobalInput<N extends string = string> = {
|
|
43
|
+
name: N;
|
|
44
|
+
title: string;
|
|
45
|
+
};
|
|
46
|
+
export declare function globalItem<F extends BuilderMap, const N extends string = string>(input: GlobalInput<N> & {
|
|
47
|
+
fields: F;
|
|
48
|
+
section?: undefined;
|
|
49
|
+
}): GlobalSchema<NamespaceValues<F>, N>;
|
|
50
|
+
export declare function globalItem<F extends BuilderMap, St extends BuilderMap, const N extends string = string>(input: GlobalInput<N> & {
|
|
51
|
+
section: PairedComponent<SectionSchema<F, St>, F, St>;
|
|
52
|
+
fields?: undefined;
|
|
53
|
+
}): GlobalSchema<NamespaceValues<F> & {
|
|
54
|
+
settings: NamespaceValues<St>;
|
|
55
|
+
styles: StyleProps;
|
|
56
|
+
}, N>;
|
|
57
|
+
export {};
|
package/dist/global.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** The reserved tree node type of a placement. */
|
|
2
|
+
export const GLOBAL_NODE_TYPE = "global";
|
|
3
|
+
/** The reserved keys a visual item's settings, styles steps and css live under in its row (spec 2026-09-14 §8). */
|
|
4
|
+
export const SETTINGS_KEY = "$settings";
|
|
5
|
+
export const STYLES_KEY = "$styles";
|
|
6
|
+
export const CSS_KEY = "$css";
|
|
7
|
+
const RESERVED_KEYS = [SETTINGS_KEY, STYLES_KEY, CSS_KEY];
|
|
8
|
+
/** The node id a layout-rendered visual item carries (spec 2026-09-13 §1):
|
|
9
|
+
* `getGlobal` stamps it into the styles' `__smd`, the section wrapper emits it,
|
|
10
|
+
* the canvas selects by it. A placement keeps its own tree node id
|
|
11
|
+
* (`global-xxxxxxxx`, no colon). */
|
|
12
|
+
export const GLOBAL_NODE_PREFIX = "global:";
|
|
13
|
+
export const globalNodeId = (name) => `${GLOBAL_NODE_PREFIX}${name}`;
|
|
14
|
+
/** The item a layout node id names, or null for any other id. */
|
|
15
|
+
export function globalNameOf(id) {
|
|
16
|
+
return id.startsWith(GLOBAL_NODE_PREFIX) && id.length > GLOBAL_NODE_PREFIX.length ? id.slice(GLOBAL_NODE_PREFIX.length) : null;
|
|
17
|
+
}
|
|
18
|
+
/** The placement a tree node is, or null for any other node. Accepts a
|
|
19
|
+
* bare `{ type, fields }` shape or a full tree node — `id` (and any
|
|
20
|
+
* other tree-node property) is ignored. */
|
|
21
|
+
export function globalLink(node) {
|
|
22
|
+
if (node.type !== GLOBAL_NODE_TYPE)
|
|
23
|
+
return null;
|
|
24
|
+
const { item, ref } = node.fields ?? {};
|
|
25
|
+
return typeof item === "string" && typeof ref === "string" ? { item, ref } : null;
|
|
26
|
+
}
|
|
27
|
+
/** `smoodly.global()`: named `globalItem` here so nothing shadows the `global` idiom. */
|
|
28
|
+
export function globalItem(input) {
|
|
29
|
+
if (!input?.name)
|
|
30
|
+
throw new Error("smoodly: a global item requires a name.");
|
|
31
|
+
if ((input.fields === undefined) === (input.section === undefined)) {
|
|
32
|
+
throw new Error(`smoodly: global item "${input.name}" must declare exactly one of \`section\` or \`fields\`.`);
|
|
33
|
+
}
|
|
34
|
+
const fields = input.section
|
|
35
|
+
? input.section.schema.fields
|
|
36
|
+
: Object.fromEntries(Object.entries(input.fields).map(([k, b]) => [k, b.descriptor]));
|
|
37
|
+
for (const key of RESERVED_KEYS) {
|
|
38
|
+
if (Object.prototype.hasOwnProperty.call(fields, key)) {
|
|
39
|
+
throw new Error(`smoodly: global item "${input.name}" has a field named "${key}" — that key is reserved for a visual item's ${key === SETTINGS_KEY ? "settings" : key === STYLES_KEY ? "styles" : "css"}.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
kind: "global",
|
|
44
|
+
name: input.name,
|
|
45
|
+
title: input.title,
|
|
46
|
+
fields,
|
|
47
|
+
settings: input.section ? input.section.schema.settings : {},
|
|
48
|
+
...(input.section ? { section: input.section.schema.name } : {}),
|
|
49
|
+
...(input.section?.schema.sample ? { sample: input.section.schema.sample } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { toolset } from "./toolset.ts";
|
|
|
4
4
|
import { Section } from "./section-wrapper.tsx";
|
|
5
5
|
import { page } from "./render.tsx";
|
|
6
6
|
import { entryPage } from "./entry-page.tsx";
|
|
7
|
-
import {
|
|
7
|
+
import { globalItem } from "./global.ts";
|
|
8
8
|
import { marks } from "./marks.ts";
|
|
9
9
|
export { Zone, renderPage, EMPTY_PAGE_CONTEXT } from "./render.tsx";
|
|
10
10
|
export type { PageTree, TreeNode, ZoneContent, PairedPage, Registry, PageContext, PageContextLink, PageLink, PageViewProps } from "./render.tsx";
|
|
@@ -14,6 +14,11 @@ export type { PageSchema } from "./page.ts";
|
|
|
14
14
|
export { fixedSlug } from "./page.ts";
|
|
15
15
|
export type { EntryPageSchema, PairedEntryPage, EntryPageProps } from "./entry-page.tsx";
|
|
16
16
|
export { f, normalizeOptions, optionsOf, optionValues } from "./fields.ts";
|
|
17
|
+
export { when, evaluate, isHidden, assertConditions, splitConditionPath } from "./conditions.ts";
|
|
18
|
+
export { DEFAULT_STYLE_VALUES, STYLE_KEYS, SPACING_NONE, resolveStyleValues, styleDescriptors, inlineStyles } from "./styles.ts";
|
|
19
|
+
export type { StyleValues, StyleValuesInput, StyleProps, StyleKey } from "./styles.ts";
|
|
20
|
+
export * from "./custom/index.ts";
|
|
21
|
+
export type { Condition, ConditionOp, ConditionScope } from "./conditions.ts";
|
|
17
22
|
export { defaultsOf, withDefaults } from "./field-defaults.ts";
|
|
18
23
|
export { filterPageTree } from "./page-tree.ts";
|
|
19
24
|
export { ASSET_COLLECTION, DEFAULT_MEDIA, MEDIA_TYPES, FULL_CROP, CENTER_HOTSPOT, parseAspect, cropAspect, clamp01, isMediaValue, collectAssetIds, applyAssets, } from "./media.ts";
|
|
@@ -39,7 +44,7 @@ export { SupabaseEntryStore } from "./supabase-entry-store.ts";
|
|
|
39
44
|
export { SupabasePathIndex } from "./supabase-path-index.ts";
|
|
40
45
|
export type { EntryStore, EntryRecord, EntryLocale, EntryVersion, EntryUsage, ListOptions, CreateEntryOptions, AddEntryLocaleOptions, EntryStoreOptions, Where, WhereValue, } from "./entry-store.ts";
|
|
41
46
|
export { entryErrors, compareEntries, DEFAULT_ENTRY_LOCALES, localizedKeys, splitFields, entryLocaleRow, entryFieldsIn, entryRefEdges, entryTitleIn, matchesWhere, } from "./entry-store.ts";
|
|
42
|
-
export { affectedTargets, pageTag, entryTag,
|
|
47
|
+
export { affectedTargets, pageTag, entryTag, globalTag, pagesTreeTag, collectionTag } from "./revalidate.ts";
|
|
43
48
|
export type { AffectedTargets } from "./revalidate.ts";
|
|
44
49
|
export { resolveTree, resolveFields, resolveMedia } from "./resolve.ts";
|
|
45
50
|
export type { EntryFetcher, ResolveFieldsOptions, AssetFetcher } from "./resolve.ts";
|
|
@@ -56,21 +61,28 @@ export type { CollectionSchema, EntryOrder } from "./collections.ts";
|
|
|
56
61
|
export { DEFAULT_TOOLSET, type Toolset, type ToolsetMark, type ToolsetNode } from "./toolset.ts";
|
|
57
62
|
export { collectionOrder, collectionDepth, DEFAULT_ORDER } from "./collections.ts";
|
|
58
63
|
export type { ZonePolicy } from "./zones.ts";
|
|
59
|
-
export {
|
|
60
|
-
export {
|
|
64
|
+
export { GLOBAL_NODE_TYPE, SETTINGS_KEY, STYLES_KEY, CSS_KEY, globalLink } from "./global.ts";
|
|
65
|
+
export { SMD_KEY } from "./section-wrapper.tsx";
|
|
66
|
+
export type { SmdInternal } from "./section-wrapper.tsx";
|
|
67
|
+
export { CSS_MAX, cssProblem, safeCss, scopedCss } from "./css.ts";
|
|
68
|
+
export { globalEntryId } from "./global-id.ts";
|
|
61
69
|
export { DEFAULT_SPACE_ID } from "./sql-space.ts";
|
|
62
|
-
export type {
|
|
70
|
+
export type { GlobalSchema, GlobalLink } from "./global.ts";
|
|
63
71
|
export declare const smoodly: {
|
|
64
72
|
schema: {
|
|
65
73
|
page: typeof import("./page.ts").pageSchema;
|
|
66
|
-
section<F extends import("./schema.ts").BuilderMap = {}, S extends import("./schema.ts").BuilderMap = {}
|
|
74
|
+
section<F extends import("./schema.ts").BuilderMap = {}, S extends import("./schema.ts").BuilderMap = {}>(input: {
|
|
67
75
|
name: string;
|
|
68
76
|
title: string;
|
|
69
77
|
icon?: string;
|
|
70
78
|
description?: string;
|
|
71
79
|
fields?: F | undefined;
|
|
72
|
-
|
|
73
|
-
meta?:
|
|
80
|
+
settings?: S | undefined;
|
|
81
|
+
meta?: never;
|
|
82
|
+
styles?: false | {
|
|
83
|
+
omit: string[];
|
|
84
|
+
};
|
|
85
|
+
css?: false;
|
|
74
86
|
sample?: Partial<{ [K_1 in Exclude<keyof F, { [K in keyof F]: F[K] extends {
|
|
75
87
|
__flags?: infer Fl;
|
|
76
88
|
} ? Fl extends {
|
|
@@ -88,18 +100,19 @@ export declare const smoodly: {
|
|
|
88
100
|
} ? Fl extends {
|
|
89
101
|
optional: true;
|
|
90
102
|
} ? K_3 : never : never; }[keyof S]]?: import("./fields.ts").ValueOf<S[K_5]> | undefined; }> | undefined;
|
|
91
|
-
styleDefaults?: false | {
|
|
92
|
-
omit: string[];
|
|
93
|
-
};
|
|
94
103
|
}): import("./schema.ts").SectionSchema<F, S>;
|
|
95
|
-
element<F extends import("./schema.ts").BuilderMap = {}, S extends import("./schema.ts").BuilderMap = {}
|
|
104
|
+
element<F extends import("./schema.ts").BuilderMap = {}, S extends import("./schema.ts").BuilderMap = {}>(input: {
|
|
96
105
|
name: string;
|
|
97
106
|
title: string;
|
|
98
107
|
icon?: string;
|
|
99
108
|
description?: string;
|
|
100
109
|
fields?: F | undefined;
|
|
101
|
-
|
|
102
|
-
meta?:
|
|
110
|
+
settings?: S | undefined;
|
|
111
|
+
meta?: never;
|
|
112
|
+
styles?: false | {
|
|
113
|
+
omit: string[];
|
|
114
|
+
};
|
|
115
|
+
css?: false;
|
|
103
116
|
sample?: Partial<{ [K_1 in Exclude<keyof F, { [K in keyof F]: F[K] extends {
|
|
104
117
|
__flags?: infer Fl;
|
|
105
118
|
} ? Fl extends {
|
|
@@ -117,9 +130,6 @@ export declare const smoodly: {
|
|
|
117
130
|
} ? Fl extends {
|
|
118
131
|
optional: true;
|
|
119
132
|
} ? K_3 : never : never; }[keyof S]]?: import("./fields.ts").ValueOf<S[K_5]> | undefined; }> | undefined;
|
|
120
|
-
styleDefaults?: false | {
|
|
121
|
-
omit: string[];
|
|
122
|
-
};
|
|
123
133
|
} & {
|
|
124
134
|
contexts: ("richtext" | "column")[];
|
|
125
135
|
}): import("./schema.ts").ElementSchema<F, S>;
|
|
@@ -130,7 +140,7 @@ export declare const smoodly: {
|
|
|
130
140
|
entryPage: typeof entryPage;
|
|
131
141
|
collection: typeof collection;
|
|
132
142
|
toolset: typeof toolset;
|
|
133
|
-
|
|
143
|
+
global: typeof globalItem;
|
|
134
144
|
Section: typeof Section;
|
|
135
145
|
marks: typeof marks;
|
|
136
146
|
};
|
package/dist/index.js
CHANGED
|
@@ -5,12 +5,15 @@ import { toolset } from "./toolset.js";
|
|
|
5
5
|
import { Section } from "./section-wrapper.js";
|
|
6
6
|
import { page } from "./render.js";
|
|
7
7
|
import { entryPage } from "./entry-page.js";
|
|
8
|
-
import {
|
|
8
|
+
import { globalItem } from "./global.js";
|
|
9
9
|
import { marks } from "./marks.js";
|
|
10
10
|
export { Zone, renderPage, EMPTY_PAGE_CONTEXT } from "./render.js";
|
|
11
11
|
export { RichText, defaultRichTextImage } from "./richtext-render.js";
|
|
12
12
|
export { fixedSlug } from "./page.js";
|
|
13
13
|
export { f, normalizeOptions, optionsOf, optionValues } from "./fields.js";
|
|
14
|
+
export { when, evaluate, isHidden, assertConditions, splitConditionPath } from "./conditions.js";
|
|
15
|
+
export { DEFAULT_STYLE_VALUES, STYLE_KEYS, SPACING_NONE, resolveStyleValues, styleDescriptors, inlineStyles } from "./styles.js";
|
|
16
|
+
export * from "./custom/index.js";
|
|
14
17
|
export { defaultsOf, withDefaults } from "./field-defaults.js";
|
|
15
18
|
export { filterPageTree } from "./page-tree.js";
|
|
16
19
|
export { ASSET_COLLECTION, DEFAULT_MEDIA, MEDIA_TYPES, FULL_CROP, CENTER_HOTSPOT, parseAspect, cropAspect, clamp01, isMediaValue, collectAssetIds, applyAssets, } from "./media.js";
|
|
@@ -30,15 +33,17 @@ export { MemoryEntryStore } from "./entry-store.js";
|
|
|
30
33
|
export { SupabaseEntryStore } from "./supabase-entry-store.js";
|
|
31
34
|
export { SupabasePathIndex } from "./supabase-path-index.js";
|
|
32
35
|
export { entryErrors, compareEntries, DEFAULT_ENTRY_LOCALES, localizedKeys, splitFields, entryLocaleRow, entryFieldsIn, entryRefEdges, entryTitleIn, matchesWhere, } from "./entry-store.js";
|
|
33
|
-
export { affectedTargets, pageTag, entryTag,
|
|
36
|
+
export { affectedTargets, pageTag, entryTag, globalTag, pagesTreeTag, collectionTag } from "./revalidate.js";
|
|
34
37
|
export { resolveTree, resolveFields, resolveMedia } from "./resolve.js";
|
|
35
38
|
export { SEGMENT_RE, isSegment, assertSegment, joinPath, splitPath, segmentFor, isHomeNode, pagePathIn, chainOf, assertDepth, pagePath, pagePathRows, entryPathRows, collectionSegment, buildPageTree, perLocaleSegments, assertSupportedLocale, unsupportedLocale, } from "./paths.js";
|
|
36
39
|
export { fixedPathFor } from "./localize.js";
|
|
37
40
|
export { pathOptions, pageErrors, localeRow, pageRefEdges, DEFAULT_LOCALES } from "./store.js";
|
|
38
41
|
export { DEFAULT_TOOLSET } from "./toolset.js";
|
|
39
42
|
export { collectionOrder, collectionDepth, DEFAULT_ORDER } from "./collections.js";
|
|
40
|
-
export {
|
|
41
|
-
export {
|
|
43
|
+
export { GLOBAL_NODE_TYPE, SETTINGS_KEY, STYLES_KEY, CSS_KEY, globalLink } from "./global.js";
|
|
44
|
+
export { SMD_KEY } from "./section-wrapper.js";
|
|
45
|
+
export { CSS_MAX, cssProblem, safeCss, scopedCss } from "./css.js";
|
|
46
|
+
export { globalEntryId } from "./global-id.js";
|
|
42
47
|
export { DEFAULT_SPACE_ID } from "./sql-space.js";
|
|
43
48
|
export const smoodly = {
|
|
44
49
|
schema,
|
|
@@ -48,7 +53,7 @@ export const smoodly = {
|
|
|
48
53
|
entryPage,
|
|
49
54
|
collection,
|
|
50
55
|
toolset,
|
|
51
|
-
|
|
56
|
+
global: globalItem,
|
|
52
57
|
Section,
|
|
53
58
|
marks,
|
|
54
59
|
};
|
package/dist/media.js
CHANGED
|
@@ -43,7 +43,7 @@ export function collectAssetIds(values, descriptors, out, elements = {}) {
|
|
|
43
43
|
out.add(value.asset);
|
|
44
44
|
}
|
|
45
45
|
else if (d.type === "richtext") {
|
|
46
|
-
// Block nodes (spec 2026-09-08 block nodes §2): image nodes, and media inside a known element's fields and
|
|
46
|
+
// Block nodes (spec 2026-09-08 block nodes §2): image nodes, and media inside a known element's fields and settings.
|
|
47
47
|
for (const node of richtextBlocks(value)) {
|
|
48
48
|
if (node.type === IMAGE_NODE) {
|
|
49
49
|
const image = node.attrs.image;
|
package/dist/next/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { createPageRenderer } from "./page-renderer.tsx";
|
|
|
2
2
|
export type { PageRendererOptions, PathRenderOptions, PageRegistration } from "./page-renderer.tsx";
|
|
3
3
|
export { metadataFrom } from "./meta.ts";
|
|
4
4
|
export { createSmoodlyQueries } from "./queries.ts";
|
|
5
|
-
export type { SmoodlyQueries, QueryOptions, EntriesQuery, TypedEntryItem,
|
|
5
|
+
export type { SmoodlyQueries, QueryOptions, EntriesQuery, TypedEntryItem, GlobalValue } from "./queries.ts";
|
|
@@ -2,7 +2,7 @@ import type { ComponentType, ReactElement } from "react";
|
|
|
2
2
|
import type { Metadata } from "next";
|
|
3
3
|
import type { Descriptor } from "../fields.ts";
|
|
4
4
|
import { type PairedPage } from "../render.tsx";
|
|
5
|
-
import type {
|
|
5
|
+
import type { GlobalSchema } from "../global.ts";
|
|
6
6
|
import { type SmoodlySite } from "../site.ts";
|
|
7
7
|
/** A registered section, as renderPage's registry needs it. */
|
|
8
8
|
type SectionEntry = ComponentType<any> & {
|
|
@@ -22,6 +22,8 @@ export type PageRegistration = ((props: any) => ReactElement) & {
|
|
|
22
22
|
export type PageRendererOptions = {
|
|
23
23
|
site: SmoodlySite;
|
|
24
24
|
sections: SectionEntry[];
|
|
25
|
+
/** Registered Elements: what the custom section's default richtext renderer needs when no RichText is bound (spec 2026-09-14 §6). */
|
|
26
|
+
elements?: SectionEntry[];
|
|
25
27
|
pages: ReadonlyArray<PageRegistration>;
|
|
26
28
|
/** Locale-relative CMS path → site URL. Default: the path itself. With
|
|
27
29
|
* next-intl: `(path, locale) => getPathname({ locale, href: path })`. */
|
|
@@ -32,7 +34,7 @@ export type PathRenderOptions = {
|
|
|
32
34
|
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
|
33
35
|
};
|
|
34
36
|
export declare function createPageRenderer(options: PageRendererOptions): {
|
|
35
|
-
|
|
37
|
+
getSmoodlyGlobal: <V>(item: GlobalSchema<V>, opts?: {
|
|
36
38
|
locale?: string;
|
|
37
39
|
}) => Promise<V | null>;
|
|
38
40
|
renderSmoodlyPath: (segments: string[], opts?: PathRenderOptions) => Promise<ReactElement<unknown, string | import("react").JSXElementConstructor<any>>>;
|
|
@@ -7,7 +7,7 @@ import { EditorBridge } from "../admin/next/bridge.js";
|
|
|
7
7
|
import { fixedPathFor } from "../localize.js";
|
|
8
8
|
import { joinPath } from "../paths.js";
|
|
9
9
|
import { renderPage } from "../render.js";
|
|
10
|
-
import { entryTag, pageTag,
|
|
10
|
+
import { entryTag, pageTag, globalTag } from "../revalidate.js";
|
|
11
11
|
import { pageContextOf } from "../site.js";
|
|
12
12
|
import { metadataFrom } from "./meta.js";
|
|
13
13
|
export function createPageRenderer(options) {
|
|
@@ -35,7 +35,7 @@ export function createPageRenderer(options) {
|
|
|
35
35
|
notFound();
|
|
36
36
|
}
|
|
37
37
|
const page = pageContextOf(hit, (path) => href(path, locale));
|
|
38
|
-
return renderPage(Page, hit.tree, { sections: options.sections }, { editor, page });
|
|
38
|
+
return renderPage(Page, hit.tree, { sections: options.sections, elements: options.elements, styles: site.styles, custom: site.custom }, { editor, page });
|
|
39
39
|
}
|
|
40
40
|
const View = entryPages.get(hit.collection);
|
|
41
41
|
if (!View) {
|
|
@@ -86,20 +86,20 @@ export function createPageRenderer(options) {
|
|
|
86
86
|
* error surfaces as a rejected promise like every other failure. */
|
|
87
87
|
const fixedPath = (Page) => (locale) => fixedPathFor(Page.schema, locale, site.locales, site.homePageSlug);
|
|
88
88
|
const literal = (segments) => () => joinPath(segments);
|
|
89
|
-
/** A
|
|
90
|
-
*
|
|
91
|
-
async function
|
|
89
|
+
/** A global item for a layout: cached per (item, locale) under the
|
|
90
|
+
* global tag outside draft mode, the live draft inside it. */
|
|
91
|
+
async function getSmoodlyGlobal(item, opts) {
|
|
92
92
|
await connection();
|
|
93
93
|
const locale = opts?.locale ?? site.locales.default;
|
|
94
94
|
const { isEnabled: draft } = await draftMode();
|
|
95
95
|
if (draft)
|
|
96
|
-
return site.
|
|
97
|
-
return unstable_cache(() => site.
|
|
98
|
-
tags: [
|
|
96
|
+
return site.getGlobal(item, { locale, draft: true });
|
|
97
|
+
return unstable_cache(() => site.getGlobal(item, { locale }), ["smoodly-global", item.name, locale], {
|
|
98
|
+
tags: [globalTag(item.name)],
|
|
99
99
|
})();
|
|
100
100
|
}
|
|
101
101
|
return {
|
|
102
|
-
|
|
102
|
+
getSmoodlyGlobal,
|
|
103
103
|
renderSmoodlyPath: (segments, opts) => page(literal(segments), opts, any),
|
|
104
104
|
smoodlyMetadata: (segments, opts) => metadata(literal(segments), opts, any),
|
|
105
105
|
/** A fixed registration rendered from the developer's own route file. */
|
package/dist/next/queries.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CollectionSchema } from "../collections.ts";
|
|
2
2
|
import type { EntryRecord } from "../entry-store.ts";
|
|
3
|
-
import type {
|
|
3
|
+
import type { GlobalSchema } from "../global.ts";
|
|
4
4
|
import type { EntryQuery, PageTreeItem, SiteEntryItem, SitePage, SmoodlySite } from "../site.ts";
|
|
5
5
|
export type QueryOptions = {
|
|
6
6
|
locale?: string;
|
|
@@ -17,8 +17,8 @@ export type TypedEntryItem<C extends readonly CollectionSchema<any, string>[], N
|
|
|
17
17
|
fields: EntryFields<C, N>;
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
|
-
export type
|
|
21
|
-
export type SmoodlyQueries<C extends readonly CollectionSchema<any, string>[], S extends readonly
|
|
20
|
+
export type GlobalValue<S extends readonly GlobalSchema<any, string>[], N> = string extends S[number]["name"] ? Record<string, unknown> : NonNullable<ByName<S[number], N>["__value"]>;
|
|
21
|
+
export type SmoodlyQueries<C extends readonly CollectionSchema<any, string>[], S extends readonly GlobalSchema<any, string>[]> = {
|
|
22
22
|
/** The published page tree in a locale, every item with its page fields. */
|
|
23
23
|
getPageTree(options?: QueryOptions): Promise<PageTreeItem[]>;
|
|
24
24
|
/** A page by locale-relative path ("/company/about-us") or id. */
|
|
@@ -26,11 +26,11 @@ export type SmoodlyQueries<C extends readonly CollectionSchema<any, string>[], S
|
|
|
26
26
|
/** Published entries of a collection: `where` (shared fields), `order`, `limit`, `parent` run in the store. */
|
|
27
27
|
getEntries<N extends C[number]["name"]>(collection: N, options?: EntriesQuery): Promise<TypedEntryItem<C, N>[]>;
|
|
28
28
|
getEntry<N extends C[number]["name"]>(collection: N, idOrPath: string, options?: QueryOptions): Promise<TypedEntryItem<C, N> | null>;
|
|
29
|
-
|
|
29
|
+
getGlobal<N extends S[number]["name"]>(item: N, options?: QueryOptions): Promise<GlobalValue<S, N> | null>;
|
|
30
30
|
};
|
|
31
|
-
export declare function createSmoodlyQueries<const C extends readonly CollectionSchema<any, string>[], const S extends readonly
|
|
31
|
+
export declare function createSmoodlyQueries<const C extends readonly CollectionSchema<any, string>[], const S extends readonly GlobalSchema<any, string>[] = []>(options: {
|
|
32
32
|
site: SmoodlySite;
|
|
33
33
|
collections: C;
|
|
34
|
-
|
|
34
|
+
globals?: S;
|
|
35
35
|
}): SmoodlyQueries<C, S>;
|
|
36
36
|
export {};
|