smoodly 0.0.45 → 0.0.47
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 +3 -3
- package/dist/addresses.d.ts +6 -7
- package/dist/addresses.js +6 -4
- package/dist/admin/PublicSans-LICENSE.txt +93 -0
- package/dist/admin/auth-config.d.ts +6 -0
- package/dist/admin/auth-config.js +1 -0
- package/dist/admin/browser/fetch-op.d.ts +10 -0
- package/dist/admin/browser/fetch-op.js +44 -0
- package/dist/admin/browser/main.d.ts +1 -0
- package/dist/admin/browser/main.js +21 -0
- package/dist/admin/custom-section.d.ts +1 -1
- package/dist/admin/drift.d.ts +1 -1
- package/dist/admin/editor/EditorView.js +14 -14
- package/dist/admin/editor/autosave.js +1 -1
- package/dist/admin/editor/canvas-address.d.ts +2 -0
- package/dist/admin/editor/canvas-address.js +8 -0
- package/dist/admin/editor/overlay-geometry.d.ts +1 -1
- package/dist/admin/editor/protocol.d.ts +18 -4
- package/dist/admin/editor/protocol.js +15 -1
- package/dist/admin/editor/useCanvasBridge.d.ts +1 -1
- package/dist/admin/editor/useCanvasBridge.js +5 -6
- package/dist/admin/editor/useInlineSession.js +4 -2
- package/dist/admin/files.d.ts +11 -0
- package/dist/admin/files.generated.js +2 -0
- package/dist/admin/files.js +8 -0
- package/dist/admin/forms/useRefOptions.d.ts +2 -2
- package/dist/admin/forms/useRefOptions.js +2 -2
- package/dist/admin/forms/useRichtextEditor.js +4 -4
- package/dist/admin/handler.d.ts +40 -0
- package/dist/admin/handler.js +151 -0
- package/dist/admin/health.d.ts +4 -0
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/ops-impl.d.ts +7 -1
- package/dist/admin/ops-impl.js +112 -35
- package/dist/admin/ops.d.ts +3 -2
- package/dist/admin/page-data.d.ts +21 -0
- package/dist/admin/page-data.js +4 -0
- package/dist/admin/page.d.ts +6 -0
- package/dist/admin/page.js +35 -0
- package/dist/admin/serialize.js +7 -7
- package/dist/admin/shell/AdminApp.js +2 -2
- package/dist/admin/shell/ContentHealthPage.js +3 -1
- package/dist/admin/shell/EntryForm.js +1 -1
- package/dist/admin/shell/router.d.ts +3 -16
- package/dist/admin/shell/router.js +3 -39
- package/dist/admin/shell/session.d.ts +2 -5
- package/dist/admin/supabase-auth.js +1 -1
- package/dist/admin/tree-ops.d.ts +1 -1
- package/dist/admin/ui/fonts.d.ts +12 -0
- package/dist/admin/ui/fonts.js +22 -0
- package/dist/admin/ui/theme.d.ts +13 -4
- package/dist/admin/ui/theme.js +28 -157
- package/dist/assemble.d.ts +60 -0
- package/dist/assemble.js +359 -0
- package/dist/astro/cache.d.ts +34 -0
- package/dist/astro/cache.js +69 -0
- package/dist/astro/components/Custom.astro +21 -0
- package/dist/astro/components/Guarded.astro +24 -0
- package/dist/astro/components/Head.astro +26 -0
- package/dist/astro/components/Image.astro +20 -0
- package/dist/astro/components/RichText.astro +23 -0
- package/dist/astro/components/Section.astro +20 -0
- package/dist/astro/components/Video.astro +5 -0
- package/dist/astro/components/Zone.astro +19 -0
- package/dist/astro/components.d.ts +6 -0
- package/dist/astro/components.js +7 -0
- package/dist/astro/content-loaders.d.ts +40 -0
- package/dist/astro/content-loaders.js +100 -0
- package/dist/astro/content.d.ts +9 -0
- package/dist/astro/content.js +17 -0
- package/dist/astro/custom.d.ts +1 -0
- package/dist/astro/custom.js +3 -0
- package/dist/astro/defaults.d.ts +20 -0
- package/dist/astro/defaults.js +21 -0
- package/dist/astro/env-files.d.ts +1 -0
- package/dist/astro/env-files.js +21 -0
- package/dist/astro/head.d.ts +13 -0
- package/dist/astro/head.js +19 -0
- package/dist/astro/image.d.ts +27 -0
- package/dist/astro/image.js +29 -0
- package/dist/astro/index.d.ts +43 -0
- package/dist/astro/index.js +111 -0
- package/dist/astro/locale-paths.d.ts +30 -0
- package/dist/astro/locale-paths.js +38 -0
- package/dist/astro/middleware.d.ts +1 -0
- package/dist/astro/middleware.js +83 -0
- package/dist/astro/preview-document.d.ts +1 -0
- package/dist/astro/preview-document.js +18 -0
- package/dist/astro/preview.d.ts +10 -0
- package/dist/astro/preview.js +72 -0
- package/dist/astro/richtext.d.ts +15 -0
- package/dist/astro/richtext.js +78 -0
- package/dist/astro/routes/admin.d.ts +3 -0
- package/dist/astro/routes/admin.js +41 -0
- package/dist/astro/routes/page.astro +17 -0
- package/dist/astro/routes/preview.astro +11 -0
- package/dist/astro/runtime.d.ts +32 -0
- package/dist/astro/runtime.js +46 -0
- package/dist/astro/scope.d.ts +45 -0
- package/dist/astro/scope.js +103 -0
- package/dist/astro/server.d.ts +14 -0
- package/dist/astro/server.js +45 -0
- package/dist/astro/shell.d.ts +67 -0
- package/dist/astro/shell.js +63 -0
- package/dist/astro/style.d.ts +1 -0
- package/dist/astro/style.js +11 -0
- package/dist/astro/views-check.d.ts +3 -0
- package/dist/astro/views-check.js +19 -0
- package/dist/astro/zone.d.ts +39 -0
- package/dist/astro/zone.js +33 -0
- package/dist/bind.d.ts +19 -0
- package/dist/bind.js +34 -0
- package/dist/bridge/bridge.d.ts +9 -0
- package/dist/bridge/bridge.js +107 -0
- package/dist/bridge/entry.d.ts +1 -0
- package/dist/bridge/entry.js +5 -0
- package/dist/bridge/preview-pill.d.ts +18 -0
- package/dist/bridge/preview-pill.js +115 -0
- package/dist/cli/import.d.ts +3 -0
- package/dist/cli/import.js +10 -1
- package/dist/cli/load-collections.js +4 -3
- package/dist/cli/load-config.d.ts +4 -0
- package/dist/cli/load-config.js +26 -0
- package/dist/cli/main.d.ts +5 -1
- package/dist/cli/main.js +24 -6
- package/dist/cli/migrate.d.ts +13 -0
- package/dist/cli/migrate.js +30 -2
- package/dist/cli/rebuild.d.ts +23 -0
- package/dist/cli/rebuild.js +22 -0
- package/dist/cli/space-io.d.ts +15 -1
- package/dist/cli/space-io.js +14 -0
- package/dist/client.js +5 -0
- package/dist/config.d.ts +28 -20
- package/dist/config.js +71 -75
- package/dist/content/content-api.d.ts +1 -1
- package/dist/content/node.d.ts +2 -3
- package/dist/content/node.js +2 -1
- package/dist/crash-message.d.ts +1 -0
- package/dist/crash-message.js +8 -0
- package/dist/css.d.ts +4 -0
- package/dist/css.js +5 -2
- package/dist/custom/CustomSection.d.ts +1 -1
- package/dist/custom/CustomSection.js +4 -8
- package/dist/custom/css.js +2 -2
- package/dist/custom/index.d.ts +6 -10
- package/dist/custom/index.js +1 -2
- package/dist/custom/layout.d.ts +5 -0
- package/dist/custom/layout.js +14 -0
- package/dist/document-stamp.d.ts +7 -0
- package/dist/document-stamp.js +48 -0
- package/dist/entry-page.d.ts +3 -18
- package/dist/env.js +1 -1
- package/dist/global-id.js +2 -2
- package/dist/global.d.ts +5 -2
- package/dist/global.js +8 -5
- package/dist/image/next-config.d.ts +3 -0
- package/dist/image/next-config.js +12 -7
- package/dist/image/remote-pattern.d.ts +1 -0
- package/dist/image/remote-pattern.js +5 -0
- package/dist/index.d.ts +15 -17
- package/dist/index.js +10 -19
- package/dist/marks.d.ts +7 -0
- package/dist/marks.js +7 -0
- package/dist/next/admin-route.d.ts +7 -0
- package/dist/next/admin-route.js +28 -0
- package/dist/next/bridge.d.ts +1 -0
- package/dist/next/bridge.js +26 -0
- package/dist/next/canvas-boundary.d.ts +1 -1
- package/dist/next/canvas-slot.d.ts +1 -0
- package/dist/next/canvas-slot.js +4 -3
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +1 -0
- package/dist/next/page-renderer.d.ts +5 -1
- package/dist/next/page-renderer.js +19 -33
- package/dist/next/preview-pill-define.d.ts +1 -0
- package/dist/next/preview-pill-define.js +10 -0
- package/dist/next/preview-pill.d.ts +0 -3
- package/dist/next/preview-pill.js +7 -50
- package/dist/next/queries.d.ts +3 -28
- package/dist/next/queries.js +3 -90
- package/dist/next/reads.d.ts +2 -0
- package/dist/next/reads.js +18 -0
- package/dist/page-context.d.ts +89 -0
- package/dist/page-context.js +8 -0
- package/dist/pairing.d.ts +2 -22
- package/dist/project.d.ts +85 -0
- package/dist/project.js +228 -0
- package/dist/published.d.ts +115 -0
- package/dist/published.js +136 -0
- package/dist/react.d.ts +61 -0
- package/dist/react.js +17 -0
- package/dist/read-shapes.d.ts +1 -1
- package/dist/reads.d.ts +80 -0
- package/dist/reads.js +142 -0
- package/dist/refs.d.ts +1 -1
- package/dist/registration.d.ts +8 -0
- package/dist/registration.js +17 -0
- package/dist/render.d.ts +5 -83
- package/dist/render.js +5 -10
- package/dist/resolve.d.ts +1 -1
- package/dist/richtext/index.d.ts +6 -2
- package/dist/richtext/index.js +3 -2
- package/dist/richtext-href.d.ts +4 -0
- package/dist/richtext-href.js +17 -0
- package/dist/richtext-render.js +1 -14
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +1 -1
- package/dist/section-crash.d.ts +2 -2
- package/dist/section-crash.js +2 -7
- package/dist/section-wrapper.d.ts +3 -10
- package/dist/section-wrapper.js +3 -2
- package/dist/site.d.ts +40 -14
- package/dist/site.js +199 -284
- package/dist/smd-key.d.ts +9 -0
- package/dist/smd-key.js +1 -0
- package/dist/sql-space.d.ts +5 -0
- package/dist/sql-space.js +11 -5
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +127 -1
- package/dist/store.d.ts +1 -1
- package/dist/supabase-published-store.d.ts +45 -0
- package/dist/supabase-published-store.js +247 -0
- package/dist/supabase-store.d.ts +1 -1
- package/dist/toolset.d.ts +2 -4
- package/dist/toolset.js +6 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/view-props.d.ts +40 -0
- package/dist/view-props.js +1 -0
- package/dist/views.d.ts +36 -0
- package/dist/views.js +52 -0
- package/dist/zones.d.ts +7 -7
- package/dist/zones.js +7 -6
- package/migrations/20261007120000_smoodly_v8.sql +154 -0
- package/migrations/20261007200000_smoodly_v9.sql +30 -0
- package/migrations/20261008120000_smoodly_v10.sql +47 -0
- package/package.json +46 -19
- package/dist/admin/next/bridge.d.ts +0 -2
- package/dist/admin/next/bridge.js +0 -129
- package/dist/admin/next/create-admin.d.ts +0 -19
- package/dist/admin/next/create-admin.js +0 -44
- package/dist/admin/next/index.d.ts +0 -8
- package/dist/admin/next/index.js +0 -8
- package/dist/admin/next/op-handler.d.ts +0 -18
- package/dist/admin/next/op-handler.js +0 -31
package/dist/config.js
CHANGED
|
@@ -5,51 +5,49 @@ import { DEFAULT_ADMIN_PATH, assertAdminPath, isAdminSlug } from "./admin-path.j
|
|
|
5
5
|
import { resolveStyleValues, styleDescriptors } from "./styles.js";
|
|
6
6
|
import { resolveCustom } from "./custom/index.js";
|
|
7
7
|
import { isValidZone } from "./time-zone.js";
|
|
8
|
+
import { schemaOnly } from "./registration.js";
|
|
8
9
|
/** `global`: the placement node type and an admin route; `asset`: the refs
|
|
9
10
|
* target of every media edge; `media`: the admin's Media screen route. */
|
|
10
11
|
export const RESERVED_NAMES = ["global", "asset", "media"];
|
|
11
12
|
const isReserved = (name) => RESERVED_NAMES.includes(name);
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
...sectionNameList,
|
|
18
|
-
...(config.registry.elements ?? []).map((e) => e.schema.name),
|
|
19
|
-
...(config.registry.collections ?? []).map((c) => c.name),
|
|
20
|
-
...(config.registry.pages ?? []).filter((p) => p.schema.kind !== "entryPage").map((p) => p.schema.name),
|
|
21
|
-
];
|
|
22
|
-
for (const name of all) {
|
|
23
|
-
if (isReserved(name))
|
|
24
|
-
throw new Error(`smoodly: "${name}" is a reserved name.`);
|
|
25
|
-
if (seen.has(name))
|
|
26
|
-
throw new Error(`smoodly: duplicate registered name "${name}".`);
|
|
27
|
-
seen.add(name);
|
|
28
|
-
}
|
|
29
|
-
// Global items get their own pass, after every other kind is settled: a
|
|
30
|
-
// global item's name must be unique among global items, and must not
|
|
31
|
-
// collide with any collection/element/page/section name above — EXCEPT a
|
|
32
|
-
// visual item may reuse the name of the registered section it renders
|
|
33
|
-
// (spec 2026-09-07 §2's example pairs a "footer" section with a "footer"
|
|
34
|
-
// item). That exemption is narrow: it only excuses a match against the
|
|
35
|
-
// item's own, actually-registered section — never against a collection,
|
|
36
|
-
// element, or page that happens to share the string, and never against a
|
|
37
|
-
// second global item of the same name.
|
|
38
|
-
const globalSeen = new Set();
|
|
39
|
-
for (const item of config.registry.globals ?? []) {
|
|
40
|
-
const ownSection = item.name === item.section && sectionNames.has(item.name);
|
|
41
|
-
if (isReserved(item.name))
|
|
42
|
-
throw new Error(`smoodly: "${item.name}" is a reserved name.`);
|
|
43
|
-
if (globalSeen.has(item.name) || (seen.has(item.name) && !ownSection)) {
|
|
44
|
-
throw new Error(`smoodly: duplicate registered name "${item.name}".`);
|
|
13
|
+
function normalizeRegistry(config) {
|
|
14
|
+
const collections = config.registry.collections ?? [];
|
|
15
|
+
for (const c of collections) {
|
|
16
|
+
if (c.kind === "global") {
|
|
17
|
+
throw new Error(`smoodly: "${c.name}" is a global item — list it under registry.globals, not registry.collections.`);
|
|
45
18
|
}
|
|
46
|
-
globalSeen.add(item.name);
|
|
47
19
|
}
|
|
20
|
+
return {
|
|
21
|
+
sections: (config.registry.sections ?? []).map((s) => schemaOnly(s)),
|
|
22
|
+
elements: (config.registry.elements ?? []).map((e) => schemaOnly(e)),
|
|
23
|
+
collections,
|
|
24
|
+
globals: config.registry.globals ?? [],
|
|
25
|
+
// Page types only: an entry page is a view, registered with defineViews, and schemaOnly refuses it here.
|
|
26
|
+
pages: (config.registry.pages ?? []).map((p) => schemaOnly(p)),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Names are unique per kind (plan 2026-10-02 registration split): a section and a collection may share one. */
|
|
30
|
+
function assertUniqueNames(registry) {
|
|
31
|
+
const check = (names, what) => {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
for (const name of names) {
|
|
34
|
+
if (isReserved(name))
|
|
35
|
+
throw new Error(`smoodly: "${name}" is a reserved name.`);
|
|
36
|
+
if (seen.has(name))
|
|
37
|
+
throw new Error(`smoodly: duplicate ${what} name "${name}".`);
|
|
38
|
+
seen.add(name);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// One component map renders sections and elements, so they share a namespace.
|
|
42
|
+
check([...registry.sections, ...registry.elements].map((s) => s.name), "section or element");
|
|
43
|
+
// A global item is stored as an entry of a collection named after it, so the two share one.
|
|
44
|
+
check([...registry.collections, ...registry.globals].map((c) => c.name), "collection or global item");
|
|
45
|
+
check(registry.pages.map((p) => p.name), "page type");
|
|
48
46
|
}
|
|
49
47
|
/** A visual item renders through a registered section. */
|
|
50
|
-
function assertGlobalSections(
|
|
51
|
-
const sections = new Set(
|
|
52
|
-
for (const item of
|
|
48
|
+
function assertGlobalSections(registry) {
|
|
49
|
+
const sections = new Set(registry.sections.map((s) => s.name));
|
|
50
|
+
for (const item of registry.globals) {
|
|
53
51
|
if (item.section !== undefined && !sections.has(item.section)) {
|
|
54
52
|
throw new Error(`smoodly: global item "${item.name}" renders section "${item.section}", which is not registered.`);
|
|
55
53
|
}
|
|
@@ -62,39 +60,35 @@ function assertGlobalSections(config) {
|
|
|
62
60
|
* path, which is the collection's index page — and nothing may sit
|
|
63
61
|
* inside a collection's address. An address starting at the admin's
|
|
64
62
|
* segment is refused outright (spec 2026-09-17 slug guard §5). */
|
|
65
|
-
function assertRootSegments(
|
|
66
|
-
const { default: def } =
|
|
63
|
+
function assertRootSegments(registry, locales, adminPath) {
|
|
64
|
+
const { default: def } = locales;
|
|
67
65
|
// Fixed slugs and collection paths both fill an omitted locale from the
|
|
68
66
|
// default one — where the materializer puts the record and the entry
|
|
69
67
|
// derivation puts the entries — so the checks see every clash at boot.
|
|
70
|
-
const pageSegments = (v) => fixedPageSegments(v,
|
|
71
|
-
const pages =
|
|
68
|
+
const pageSegments = (v) => fixedPageSegments(v, locales);
|
|
69
|
+
const pages = registry.pages.filter((p) => p.slug !== undefined);
|
|
72
70
|
const owners = new Map(); // "<locale> <segment>" → page name
|
|
73
71
|
for (const p of pages) {
|
|
74
|
-
const slug = p.
|
|
72
|
+
const slug = p.slug;
|
|
75
73
|
if (typeof slug !== "string" && slug[def] === undefined) {
|
|
76
|
-
throw new Error(`smoodly: page "${p.
|
|
74
|
+
throw new Error(`smoodly: page "${p.name}" declares a per-locale slug without the default locale "${def}".`);
|
|
77
75
|
}
|
|
78
76
|
for (const [locale, segment] of Object.entries(pageSegments(slug))) {
|
|
79
|
-
assertAddress(segment, `page "${p.
|
|
77
|
+
assertAddress(segment, `page "${p.name}" fixed slug`);
|
|
80
78
|
if (isAdminSlug(segment.split("/")[0], adminPath)) {
|
|
81
|
-
throw new Error(`smoodly: page "${p.
|
|
79
|
+
throw new Error(`smoodly: page "${p.name}" has the fixed slug "${segment}", where the admin lives — change its slug or admin.path.`);
|
|
82
80
|
}
|
|
83
81
|
const owner = owners.get(`${locale} ${segment}`);
|
|
84
82
|
if (owner) {
|
|
85
|
-
throw new Error(`smoodly: page registrations "${owner}" and "${p.
|
|
83
|
+
throw new Error(`smoodly: page registrations "${owner}" and "${p.name}" both claim the fixed slug "${segment}" (${locale}).`);
|
|
86
84
|
}
|
|
87
|
-
owners.set(`${locale} ${segment}`, p.
|
|
85
|
+
owners.set(`${locale} ${segment}`, p.name);
|
|
88
86
|
}
|
|
89
87
|
}
|
|
90
|
-
const entryPages = new Set((config.registry.pages ?? []).filter((p) => p.schema.kind === "entryPage").map((p) => p.schema.collection));
|
|
91
88
|
const collectionOwners = new Map();
|
|
92
|
-
for (const c of
|
|
89
|
+
for (const c of registry.collections) {
|
|
93
90
|
if (c.path === undefined)
|
|
94
91
|
continue;
|
|
95
|
-
if (!entryPages.has(c.name)) {
|
|
96
|
-
throw new Error(`smoodly: collection "${c.name}" declares a path but no entry page — register smoodly.entryPage(${c.name}, View).`);
|
|
97
|
-
}
|
|
98
92
|
if (typeof c.path !== "string" && c.path[def] === undefined) {
|
|
99
93
|
throw new Error(`smoodly: collection "${c.name}" declares a per-locale path without the default locale "${def}".`);
|
|
100
94
|
}
|
|
@@ -115,8 +109,8 @@ function assertRootSegments(config, adminPath) {
|
|
|
115
109
|
const pageName = owners.get(`${locale} ${segment}`);
|
|
116
110
|
if (!pageName)
|
|
117
111
|
continue;
|
|
118
|
-
const page = pages.find((p) => p.
|
|
119
|
-
const everywhere = Object.entries(segments).every(([l, s]) => pageSegments(page.
|
|
112
|
+
const page = pages.find((p) => p.name === pageName);
|
|
113
|
+
const everywhere = Object.entries(segments).every(([l, s]) => pageSegments(page.slug)[l] === s);
|
|
120
114
|
if (!everywhere) {
|
|
121
115
|
throw new Error(`smoodly: collection "${c.name}" and page registration "${pageName}" both claim the address "${segment}" (${locale}).`);
|
|
122
116
|
}
|
|
@@ -125,15 +119,15 @@ function assertRootSegments(config, adminPath) {
|
|
|
125
119
|
// A collection's entries live below its address: nothing else may sit there (spec 2026-09-29 nested paths §2).
|
|
126
120
|
const claims = [];
|
|
127
121
|
for (const p of pages)
|
|
128
|
-
for (const [locale, address] of Object.entries(pageSegments(p.
|
|
129
|
-
claims.push({ what: `page "${p.
|
|
130
|
-
for (const c of
|
|
122
|
+
for (const [locale, address] of Object.entries(pageSegments(p.slug)))
|
|
123
|
+
claims.push({ what: `page "${p.name}"`, locale, address });
|
|
124
|
+
for (const c of registry.collections) {
|
|
131
125
|
if (c.path === undefined)
|
|
132
126
|
continue;
|
|
133
127
|
for (const [locale, address] of Object.entries(pageSegments(c.path)))
|
|
134
128
|
claims.push({ what: `collection "${c.name}"`, locale, address });
|
|
135
129
|
}
|
|
136
|
-
for (const c of
|
|
130
|
+
for (const c of registry.collections) {
|
|
137
131
|
if (c.path === undefined)
|
|
138
132
|
continue;
|
|
139
133
|
for (const [locale, base] of Object.entries(pageSegments(c.path))) {
|
|
@@ -184,18 +178,18 @@ function toolsetElementNames(descriptors, out) {
|
|
|
184
178
|
}
|
|
185
179
|
}
|
|
186
180
|
/** A field can never offer an element the admin cannot serialize (spec 2026-09-08 block nodes §1). */
|
|
187
|
-
function assertToolsetElements(
|
|
188
|
-
const registered = new Set(
|
|
181
|
+
function assertToolsetElements(registry, custom) {
|
|
182
|
+
const registered = new Set(registry.elements.map((e) => e.name));
|
|
189
183
|
const wanted = new Set();
|
|
190
|
-
for (const s of
|
|
191
|
-
toolsetElementNames(s.
|
|
192
|
-
for (const e of
|
|
193
|
-
toolsetElementNames(e.
|
|
194
|
-
for (const c of
|
|
184
|
+
for (const s of registry.sections)
|
|
185
|
+
toolsetElementNames(s.fields, wanted);
|
|
186
|
+
for (const e of registry.elements)
|
|
187
|
+
toolsetElementNames(e.fields, wanted);
|
|
188
|
+
for (const c of registry.collections)
|
|
195
189
|
toolsetElementNames(c.fields, wanted);
|
|
196
|
-
for (const s of
|
|
190
|
+
for (const s of registry.globals)
|
|
197
191
|
toolsetElementNames(s.fields, wanted);
|
|
198
|
-
for (const n of
|
|
192
|
+
for (const n of custom?.toolset?.elements ?? [])
|
|
199
193
|
wanted.add(n);
|
|
200
194
|
for (const name of wanted) {
|
|
201
195
|
if (!registered.has(name))
|
|
@@ -205,12 +199,13 @@ function assertToolsetElements(config) {
|
|
|
205
199
|
export function defineConfig(config) {
|
|
206
200
|
assertLocales(config);
|
|
207
201
|
assertTimeZone(config);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
const registry = normalizeRegistry(config);
|
|
203
|
+
assertUniqueNames(registry);
|
|
204
|
+
assertGlobalSections(registry);
|
|
205
|
+
assertToolsetElements(registry, config.custom);
|
|
211
206
|
const adminPath = config.admin?.path ?? DEFAULT_ADMIN_PATH;
|
|
212
207
|
assertAdminPath(adminPath);
|
|
213
|
-
assertRootSegments(config, adminPath);
|
|
208
|
+
assertRootSegments(registry, config.locales, adminPath);
|
|
214
209
|
const depth = config.pages?.tree?.depth ?? 1;
|
|
215
210
|
if (!Number.isInteger(depth) || depth < 1)
|
|
216
211
|
throw new Error("smoodly: pages.tree.depth must be a positive integer.");
|
|
@@ -218,7 +213,7 @@ export function defineConfig(config) {
|
|
|
218
213
|
// collection's mount: that node is a folder or an index page, never "/".
|
|
219
214
|
const homePageSlug = config.homePageSlug ?? "home";
|
|
220
215
|
assertSegment(homePageSlug, "homePageSlug");
|
|
221
|
-
for (const c of
|
|
216
|
+
for (const c of registry.collections) {
|
|
222
217
|
if (c.path === undefined)
|
|
223
218
|
continue;
|
|
224
219
|
const segment = typeof c.path === "string" ? c.path : c.path[config.locales.default];
|
|
@@ -229,7 +224,7 @@ export function defineConfig(config) {
|
|
|
229
224
|
// A code-owned record keeps its address's last part as its slug, and the
|
|
230
225
|
// root record slugged homePageSlug IS the home page: a nested address
|
|
231
226
|
// ending in it would serve "/" (spec 2026-09-29 nested paths §3).
|
|
232
|
-
for (const a of codeAddresses({ locales: config.locales, pages:
|
|
227
|
+
for (const a of codeAddresses({ locales: config.locales, pages: registry.pages, collections: registry.collections })) {
|
|
233
228
|
for (const [locale, address] of Object.entries(a.segments)) {
|
|
234
229
|
if (address.includes("/") && lastPart(address) === homePageSlug) {
|
|
235
230
|
throw new Error(`smoodly: the address "${address}" (${locale}) ends in homePageSlug "${homePageSlug}" — its record would be the home page; end it in another segment.`);
|
|
@@ -238,7 +233,7 @@ export function defineConfig(config) {
|
|
|
238
233
|
}
|
|
239
234
|
const styles = resolveStyleValues(config.styles);
|
|
240
235
|
const custom = resolveCustom(config.custom);
|
|
241
|
-
const byName = new Map(
|
|
236
|
+
const byName = new Map(registry.sections.map((s) => [s.name, s]));
|
|
242
237
|
const media = {
|
|
243
238
|
maxSize: config.media?.maxSize ?? DEFAULT_MEDIA.maxSize,
|
|
244
239
|
accept: config.media?.accept ? [...config.media.accept] : [...DEFAULT_MEDIA.accept],
|
|
@@ -246,9 +241,10 @@ export function defineConfig(config) {
|
|
|
246
241
|
if (!Number.isInteger(media.maxSize) || media.maxSize <= 0) {
|
|
247
242
|
throw new Error("smoodly: media.maxSize must be a positive integer of bytes.");
|
|
248
243
|
}
|
|
249
|
-
const addresses = addressMap(codeAddresses({ locales: config.locales, pages:
|
|
244
|
+
const addresses = addressMap(codeAddresses({ locales: config.locales, pages: registry.pages, collections: registry.collections }));
|
|
250
245
|
return {
|
|
251
246
|
...config,
|
|
247
|
+
registry,
|
|
252
248
|
homePageSlug,
|
|
253
249
|
pageDepth: depth,
|
|
254
250
|
media,
|
|
@@ -3,7 +3,7 @@ import type { EntryCreateInput, OpError, PageCreateInput } from "../admin/ops.ts
|
|
|
3
3
|
import type { FieldError } from "../admin/validate.ts";
|
|
4
4
|
import type { EntryRecord } from "../entry-store.ts";
|
|
5
5
|
import type { PageRecord } from "../store.ts";
|
|
6
|
-
import type { PageTree } from "../
|
|
6
|
+
import type { PageTree } from "../page-context.ts";
|
|
7
7
|
import type { ImageValue } from "../media.ts";
|
|
8
8
|
import type { MemberStore } from "../member-store.ts";
|
|
9
9
|
export type UploadMeta = {
|
package/dist/content/node.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { BuilderMap, SectionSchema, StoredValues } from "../schema.ts";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
export declare function node<F extends BuilderMap, St extends BuilderMap>(section: PairedComponent<SectionSchema<F, St>, F, St>, fields: Partial<StoredValues<F>>, options?: {
|
|
2
|
+
import type { TreeNode } from "../page-context.ts";
|
|
3
|
+
export declare function node<F extends BuilderMap, St extends BuilderMap>(section: SectionSchema<F, St>, fields: Partial<StoredValues<F>>, options?: {
|
|
5
4
|
settings?: Partial<StoredValues<St>>;
|
|
6
5
|
id?: string;
|
|
7
6
|
}): TreeNode;
|
package/dist/content/node.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { newNodeId } from "../admin/tree-ops.js";
|
|
2
|
+
import { schemaOnly } from "../registration.js";
|
|
2
3
|
export function node(section, fields, options = {}) {
|
|
3
|
-
const type = section.
|
|
4
|
+
const type = schemaOnly(section).name;
|
|
4
5
|
return {
|
|
5
6
|
id: options.id ?? newNodeId(type),
|
|
6
7
|
type,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function crashMessage(error: unknown): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Why a section crashed, for its placeholder (spec 2026-09-27 section guard §3): the message, or the digest
|
|
2
|
+
// when a production build stripped the message. Shared by the React placeholder and Astro's guard.
|
|
3
|
+
export function crashMessage(error) {
|
|
4
|
+
const digest = typeof error === "object" && error !== null ? error.digest : undefined;
|
|
5
|
+
if (typeof digest === "string")
|
|
6
|
+
return `the server log has the error (digest ${digest})`;
|
|
7
|
+
return error instanceof Error ? error.message : String(error);
|
|
8
|
+
}
|
package/dist/css.d.ts
CHANGED
|
@@ -3,5 +3,9 @@ export declare const CSS_MAX = 4000;
|
|
|
3
3
|
export declare function cssProblem(css: string): string | null;
|
|
4
4
|
/** The snippet when it is a clean string, else "". */
|
|
5
5
|
export declare function safeCss(css: unknown): string;
|
|
6
|
+
/** A node id as the inside of a CSS attribute-selector string. "<" as its CSS escape: the rule is written inside
|
|
7
|
+
* a <style> element, and an id is whatever a request stored, so it must never be able to close that element
|
|
8
|
+
* (Astro writes the text raw; React escapes it). */
|
|
9
|
+
export declare const escapeNodeId: (id: string) => string;
|
|
6
10
|
/** Nested under the node's selector — every rule inside reaches the section only. */
|
|
7
11
|
export declare function scopedCss(nodeId: string, css: string): string;
|
package/dist/css.js
CHANGED
|
@@ -50,8 +50,11 @@ export function cssProblem(css) {
|
|
|
50
50
|
export function safeCss(css) {
|
|
51
51
|
return typeof css === "string" && css !== "" && cssProblem(css) === null ? css : "";
|
|
52
52
|
}
|
|
53
|
+
/** A node id as the inside of a CSS attribute-selector string. "<" as its CSS escape: the rule is written inside
|
|
54
|
+
* a <style> element, and an id is whatever a request stored, so it must never be able to close that element
|
|
55
|
+
* (Astro writes the text raw; React escapes it). */
|
|
56
|
+
export const escapeNodeId = (id) => id.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/</g, "\\3c ");
|
|
53
57
|
/** Nested under the node's selector — every rule inside reaches the section only. */
|
|
54
58
|
export function scopedCss(nodeId, css) {
|
|
55
|
-
|
|
56
|
-
return `[data-smd-node="${id}"]{${css}}`;
|
|
59
|
+
return `[data-smd-node="${escapeNodeId(nodeId)}"]{${css}}`;
|
|
57
60
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentType, ReactElement } from "react";
|
|
2
|
-
import { SMD_KEY, type SmdInternal } from "../
|
|
2
|
+
import { SMD_KEY, type SmdInternal } from "../smd-key.ts";
|
|
3
3
|
import type { StyleProps } from "../styles.ts";
|
|
4
4
|
import { type RichTextElement } from "../richtext-render.tsx";
|
|
5
5
|
export type CustomOptions = {
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Section
|
|
2
|
+
import { Section } from "../section-wrapper.js";
|
|
3
|
+
import { SMD_KEY } from "../smd-key.js";
|
|
3
4
|
import { RichText } from "../richtext-render.js";
|
|
4
5
|
import { FIELD_ATTR, ITEM_ATTR } from "../marks.js";
|
|
5
6
|
import { CUSTOM_NODE_TYPE, DEFAULT_BREAKPOINT } from "./schema.js";
|
|
6
|
-
import {
|
|
7
|
+
import { customLayout } from "./layout.js";
|
|
7
8
|
import { RENDER_VIEW } from "../section-guard.js";
|
|
8
|
-
const isRow = (r) => typeof r === "object" && r !== null && Array.isArray(r.columns);
|
|
9
9
|
export function createCustomSection({ RichText: Bound, breakpoint = DEFAULT_BREAKPOINT, elements = [] }) {
|
|
10
10
|
const Body = Bound ?? (({ doc }) => _jsx(RichText, { doc: doc, elements: elements }));
|
|
11
11
|
function CustomSection({ rows, styles }) {
|
|
12
|
-
const list =
|
|
13
|
-
const smd = styles?.[SMD_KEY];
|
|
14
|
-
const withLayout = smd?.node && list.length > 0
|
|
15
|
-
? { ...styles, [SMD_KEY]: { ...smd, layoutCss: customCss(smd.node, list, breakpoint) } }
|
|
16
|
-
: styles;
|
|
12
|
+
const { list, styles: withLayout } = customLayout(rows, styles ?? {}, breakpoint);
|
|
17
13
|
return (_jsx(Section, { styles: withLayout, "data-custom": "", children: list.map((row, i) => (_jsx("div", { className: row.reverse ? "smd-row smd-row--reverse" : "smd-row", "data-row": i, [ITEM_ATTR]: `rows.${i}`, children: row.columns.map((doc, j) => (_jsx("div", { className: "smd-col", [FIELD_ATTR]: `columns.${j}`, children: _jsx(Body, { doc: doc }) }, j))) }, i))) }));
|
|
18
14
|
}
|
|
19
15
|
CustomSection.schema = { name: CUSTOM_NODE_TYPE, title: "Custom section" };
|
package/dist/custom/css.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
// reaches it. One rule per row by data-row (nth-child would count the
|
|
5
5
|
// <style> children), one breakpoint that stacks every row.
|
|
6
6
|
import { ALIGN_VALUES, GAP_VALUES, SPAN_TOTAL } from "./schema.js";
|
|
7
|
+
import { escapeNodeId } from "../css.js";
|
|
7
8
|
import { renormalize } from "./rows.js";
|
|
8
9
|
const LENGTH_RE = /^\d+(?:\.\d+)?(?:px|rem|em)$/;
|
|
9
10
|
export const isCssLength = (s) => LENGTH_RE.test(s);
|
|
10
|
-
const escapeId = (id) => id.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
11
11
|
export function customCss(nodeId, rows, breakpoint) {
|
|
12
|
-
const s = `[data-smd-node="${
|
|
12
|
+
const s = `[data-smd-node="${escapeNodeId(nodeId)}"]`;
|
|
13
13
|
let css = `${s} .smd-row{display:grid;max-width:var(--smd-width,var(--container,none));margin-inline:auto;padding-inline:var(--smd-gutter,1rem);box-sizing:border-box}` +
|
|
14
14
|
`${s} .smd-row+.smd-row{margin-top:2rem}`;
|
|
15
15
|
rows.forEach((row, i) => {
|
package/dist/custom/index.d.ts
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
import type { ComponentType } from "react";
|
|
2
1
|
import type { Toolset } from "../toolset.ts";
|
|
3
2
|
import { customSchema } from "./schema.ts";
|
|
4
3
|
export * from "./schema.ts";
|
|
5
4
|
export * from "./rows.ts";
|
|
6
5
|
export * from "./css.ts";
|
|
7
|
-
|
|
8
|
-
export type { CustomOptions, CustomSectionProps, CustomSectionComponent } from "./CustomSection.tsx";
|
|
9
|
-
/** `config.custom` / `site.custom` as written (spec 2026-09-14 §6). */
|
|
6
|
+
/** `config.custom` / `site.custom` as written (spec 2026-09-14 §6). The bound RichText is a view: it goes to defineViews and reaches the renderer as an option. */
|
|
10
7
|
export type CustomConfigInput = {
|
|
11
8
|
breakpoint?: string;
|
|
12
9
|
toolset?: Toolset;
|
|
13
|
-
RichText?: ComponentType<{
|
|
14
|
-
doc: unknown;
|
|
15
|
-
}>;
|
|
16
10
|
};
|
|
11
|
+
/** The bound RichText as the config sees it: a framework's component over a document (React: `ComponentType<{ doc }>`). */
|
|
12
|
+
export type RichTextView = (props: {
|
|
13
|
+
doc: unknown;
|
|
14
|
+
}) => unknown;
|
|
17
15
|
export type ResolvedCustom = {
|
|
18
16
|
breakpoint: string;
|
|
19
17
|
toolset: Toolset;
|
|
20
|
-
RichText?:
|
|
21
|
-
doc: unknown;
|
|
22
|
-
}>;
|
|
18
|
+
RichText?: RichTextView;
|
|
23
19
|
};
|
|
24
20
|
/** Every section map the walks build gets the custom schema too (spec 2026-09-14 §6), so a column's images resolve and count as usage. */
|
|
25
21
|
export declare function withCustomSchema<T>(sections: readonly T[], toolset: Toolset): (T | ReturnType<typeof customSchema>)[];
|
package/dist/custom/index.js
CHANGED
|
@@ -3,7 +3,6 @@ import { isCssLength } from "./css.js";
|
|
|
3
3
|
export * from "./schema.js";
|
|
4
4
|
export * from "./rows.js";
|
|
5
5
|
export * from "./css.js";
|
|
6
|
-
export { createCustomSection } from "./CustomSection.js";
|
|
7
6
|
/** Every section map the walks build gets the custom schema too (spec 2026-09-14 §6), so a column's images resolve and count as usage. */
|
|
8
7
|
export function withCustomSchema(sections, toolset) {
|
|
9
8
|
return [...sections, customSchema(toolset)];
|
|
@@ -12,5 +11,5 @@ export function resolveCustom(input) {
|
|
|
12
11
|
const breakpoint = input?.breakpoint ?? DEFAULT_BREAKPOINT;
|
|
13
12
|
if (!isCssLength(breakpoint))
|
|
14
13
|
throw new Error(`smoodly: custom.breakpoint "${breakpoint}" must be a length like "48rem" or "768px".`);
|
|
15
|
-
return { breakpoint, toolset: customToolset(input?.toolset)
|
|
14
|
+
return { breakpoint, toolset: customToolset(input?.toolset) };
|
|
16
15
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The custom section's rows and layout css, decided once for both renderers (CustomSection.tsx and
|
|
2
|
+
// astro/custom.ts): the rows a stored value yields (fail-soft: a malformed value is no rows), and the layout
|
|
3
|
+
// CSS — Smoodly's own, computed from the stored numbers — riding the styles' reserved key as `layoutCss`,
|
|
4
|
+
// which the wrapper emits before the editor's own css so the box still wins. No framework import.
|
|
5
|
+
import { SMD_KEY } from "../smd-key.js";
|
|
6
|
+
import { customCss } from "./css.js";
|
|
7
|
+
const isRow = (r) => typeof r === "object" && r !== null && Array.isArray(r.columns);
|
|
8
|
+
export function customLayout(rows, styles, breakpoint) {
|
|
9
|
+
const list = Array.isArray(rows) ? rows.filter(isRow) : [];
|
|
10
|
+
const smd = styles[SMD_KEY];
|
|
11
|
+
if (!smd?.node || list.length === 0)
|
|
12
|
+
return { list, styles };
|
|
13
|
+
return { list, styles: { ...styles, [SMD_KEY]: { ...smd, layoutCss: customCss(smd.node, list, breakpoint) } } };
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SiteOptions } from "./site.ts";
|
|
2
|
+
/** Bumped by hand when the assembler's output changes shape; the upgrading guide names each bump. */
|
|
3
|
+
export declare const DOCUMENT_FORMAT = 1;
|
|
4
|
+
/** The parts of the registry that reach a document: what `cacheKey` hashes, minus the presentation. */
|
|
5
|
+
export declare function documentModel(options: SiteOptions): unknown;
|
|
6
|
+
/** `<format>:<model hash>` — what a delivery row's `model` column holds. */
|
|
7
|
+
export declare function documentStamp(options: SiteOptions): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The stamp a delivery row carries (DESIGN §3, the document stamp, 2026-10-08): which document format and
|
|
2
|
+
// which content model built it. Not the cache key: the package version is in that one, not here, because a
|
|
3
|
+
// release that leaves documents alone must leave them alone.
|
|
4
|
+
import { modelKey } from "./model-key.js";
|
|
5
|
+
import { schemaOf } from "./registration.js";
|
|
6
|
+
/** Bumped by hand when the assembler's output changes shape; the upgrading guide names each bump. */
|
|
7
|
+
export const DOCUMENT_FORMAT = 1;
|
|
8
|
+
/** Schema keys that reach the admin only: a change to one leaves every document as it was. */
|
|
9
|
+
const PRESENTATION = new Set(["title", "description", "sample", "icon", "preview", "group", "admin"]);
|
|
10
|
+
/** A field descriptor without the parts that reach the admin only (a hidden condition decides what the editor shows). */
|
|
11
|
+
function fieldModel(field) {
|
|
12
|
+
if (!field || typeof field !== "object")
|
|
13
|
+
return field;
|
|
14
|
+
const { hidden: _hidden, ...rest } = field;
|
|
15
|
+
return Object.fromEntries(Object.entries(rest).map(([k, v]) => [k, k === "fields" ? fieldsModel(v) : k === "of" ? fieldModel(v) : v]));
|
|
16
|
+
}
|
|
17
|
+
function fieldsModel(fields) {
|
|
18
|
+
if (!fields || typeof fields !== "object")
|
|
19
|
+
return fields;
|
|
20
|
+
return Object.fromEntries(Object.entries(fields).map(([k, v]) => [k, fieldModel(v)]));
|
|
21
|
+
}
|
|
22
|
+
/** A schema without its presentation. */
|
|
23
|
+
function schemaModel(schema) {
|
|
24
|
+
if (!schema || typeof schema !== "object")
|
|
25
|
+
return schema;
|
|
26
|
+
return Object.fromEntries(Object.entries(schema)
|
|
27
|
+
.filter(([k]) => !PRESENTATION.has(k))
|
|
28
|
+
.map(([k, v]) => [k, k === "fields" || k === "settings" ? fieldsModel(v) : v]));
|
|
29
|
+
}
|
|
30
|
+
/** The parts of the registry that reach a document: what `cacheKey` hashes, minus the presentation. */
|
|
31
|
+
export function documentModel(options) {
|
|
32
|
+
return {
|
|
33
|
+
collections: options.collections.map(schemaModel),
|
|
34
|
+
// A global whole, its presentation dropped: its settings' defaults reach a visual item's document.
|
|
35
|
+
globals: (options.globals ?? []).map(schemaModel),
|
|
36
|
+
sections: options.sections.map((x) => schemaModel(schemaOf(x))),
|
|
37
|
+
elements: (options.elements ?? []).map((x) => schemaModel(schemaOf(x))),
|
|
38
|
+
pages: options.pages.map((x) => schemaModel(schemaOf(x))),
|
|
39
|
+
locales: options.locales,
|
|
40
|
+
homePageSlug: options.homePageSlug,
|
|
41
|
+
styles: options.styles,
|
|
42
|
+
custom: options.custom,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** `<format>:<model hash>` — what a delivery row's `model` column holds. */
|
|
46
|
+
export function documentStamp(options) {
|
|
47
|
+
return `${DOCUMENT_FORMAT}:${modelKey(documentModel(options))}`;
|
|
48
|
+
}
|
package/dist/entry-page.d.ts
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
import type { ComponentType, ReactElement } from "react";
|
|
2
2
|
import type { CollectionSchema } from "./collections.ts";
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
export type EntryPageSchema = {
|
|
6
|
-
kind: "entryPage";
|
|
7
|
-
name: string;
|
|
8
|
-
collection: string;
|
|
9
|
-
};
|
|
10
|
-
/** What an entry page renders: the entry as a read returns it — its path, locale and
|
|
11
|
-
* locale alternates ride on it (spec 2026-09-29 content API contract §3). */
|
|
12
|
-
export type EntryPageProps<F = Record<string, unknown>> = {
|
|
13
|
-
entry: EntryItem<F>;
|
|
14
|
-
};
|
|
3
|
+
import type { EntryFields, EntryPageProps, EntryPageSchema } from "./view-props.ts";
|
|
4
|
+
export type { EntryPageSchema, EntryPageProps };
|
|
15
5
|
export type PairedEntryPage = ((props: EntryPageProps) => ReactElement) & {
|
|
16
6
|
schema: EntryPageSchema;
|
|
17
7
|
};
|
|
18
|
-
|
|
19
|
-
type Materialized<C extends CollectionSchema<any>> = {
|
|
20
|
-
[K in keyof NonNullable<C["__entry"]>]: Materialize<NonNullable<C["__entry"]>[K]>;
|
|
21
|
-
};
|
|
22
|
-
export declare function entryPage<C extends CollectionSchema<any>>(collection: C, View: ComponentType<EntryPageProps<Materialized<C>>>): PairedEntryPage;
|
|
23
|
-
export {};
|
|
8
|
+
export declare function entryPage<C extends CollectionSchema<any>>(collection: C, View: ComponentType<EntryPageProps<EntryFields<C>>>): PairedEntryPage;
|
package/dist/env.js
CHANGED
|
@@ -25,7 +25,7 @@ function jwtRole(v) {
|
|
|
25
25
|
export function resolveSmoodlyEnv(env, need = "site") {
|
|
26
26
|
const missing = (need === "admin" ? ADMIN_NEEDS : SITE_NEEDS).filter((name) => !env[name]);
|
|
27
27
|
if (missing.length > 0)
|
|
28
|
-
throw new Error(`smoodly: set ${missing.join(", ")} in .env.local, or in your host's environment variables (Deploying: ${DEPLOYING}).`);
|
|
28
|
+
throw new Error(`smoodly: set ${missing.join(", ")} in .env (Astro) or .env.local (Next.js), or in your host's environment variables (Deploying: ${DEPLOYING}).`);
|
|
29
29
|
const secretKey = env.SMOODLY_SECRET_KEY;
|
|
30
30
|
const publishableKey = env.SMOODLY_PUBLISHABLE_KEY ?? "";
|
|
31
31
|
if (secretKey.startsWith("sb_publishable_") || jwtRole(secretKey) === "anon") {
|
package/dist/global-id.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// No imports — Web Crypto (`globalThis.crypto.subtle`) exists in Node
|
|
11
11
|
// >= 20 and in every browser, so this module carries no node builtin
|
|
12
12
|
// into the package root's graph. It matters because every scaffolded
|
|
13
|
-
//
|
|
14
|
-
// section's browser build would fail on `node:crypto`.
|
|
13
|
+
// component does `import { smoodly } from "smoodly/react"`, which re-exports
|
|
14
|
+
// the root, and a "use client" section's browser build would fail on `node:crypto`.
|
|
15
15
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16
16
|
/** The namespace's 16 raw bytes — RFC 4122 hashes the bytes, not the text. */
|
|
17
17
|
function uuidBytes(uuid) {
|
package/dist/global.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { CollectionSchema } from "./collections.ts";
|
|
2
2
|
import type { Descriptor } from "./fields.ts";
|
|
3
|
-
import type { PairedComponent } from "./pairing.tsx";
|
|
4
3
|
import type { BuilderMap, SectionSchema, Values } from "./schema.ts";
|
|
5
4
|
import type { GlobalItem, VisualGlobalItem } from "./read-shapes.ts";
|
|
6
5
|
/** The reserved tree node type of a placement. */
|
|
@@ -24,6 +23,9 @@ export type GlobalSchema<V = Record<string, unknown>, N extends string = string>
|
|
|
24
23
|
/** The section's settings descriptors (empty for an object item). */
|
|
25
24
|
settings: Record<string, Descriptor>;
|
|
26
25
|
sample?: Record<string, unknown>;
|
|
26
|
+
/** Every page renders it (the header, the footer): the page's lookup brings it along, so the layout's read costs
|
|
27
|
+
* no call (decided 2026-10-08). Absent: placed in zones or read by one view, fetched only where it is read. */
|
|
28
|
+
layout?: true;
|
|
27
29
|
/** phantom: what getGlobal returns */
|
|
28
30
|
readonly __value?: V;
|
|
29
31
|
};
|
|
@@ -42,13 +44,14 @@ export declare function globalLink(node: {
|
|
|
42
44
|
type GlobalInput<N extends string = string> = {
|
|
43
45
|
name: N;
|
|
44
46
|
title: string;
|
|
47
|
+
layout?: boolean;
|
|
45
48
|
};
|
|
46
49
|
export declare function globalItem<F extends BuilderMap, const N extends string = string>(input: GlobalInput<N> & {
|
|
47
50
|
fields: F;
|
|
48
51
|
section?: undefined;
|
|
49
52
|
}): GlobalSchema<GlobalItem<Values<F>>, N>;
|
|
50
53
|
export declare function globalItem<F extends BuilderMap, St extends BuilderMap, const N extends string = string>(input: GlobalInput<N> & {
|
|
51
|
-
section:
|
|
54
|
+
section: SectionSchema<F, St>;
|
|
52
55
|
fields?: undefined;
|
|
53
56
|
}): GlobalSchema<VisualGlobalItem<Values<F>, Values<St>>, N>;
|
|
54
57
|
export {};
|
package/dist/global.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { schemaOnly } from "./registration.js";
|
|
1
2
|
/** The reserved tree node type of a placement. */
|
|
2
3
|
export const GLOBAL_NODE_TYPE = "global";
|
|
3
4
|
/** The reserved keys a visual item's settings, styles steps and css live under in its row (spec 2026-09-14 §8). */
|
|
@@ -31,8 +32,9 @@ export function globalItem(input) {
|
|
|
31
32
|
if ((input.fields === undefined) === (input.section === undefined)) {
|
|
32
33
|
throw new Error(`smoodly: global item "${input.name}" must declare exactly one of \`section\` or \`fields\`.`);
|
|
33
34
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const section = input.section ? schemaOnly(input.section) : undefined;
|
|
36
|
+
const fields = section
|
|
37
|
+
? section.fields
|
|
36
38
|
: Object.fromEntries(Object.entries(input.fields).map(([k, b]) => [k, b.descriptor]));
|
|
37
39
|
for (const key of RESERVED_KEYS) {
|
|
38
40
|
if (Object.prototype.hasOwnProperty.call(fields, key)) {
|
|
@@ -44,8 +46,9 @@ export function globalItem(input) {
|
|
|
44
46
|
name: input.name,
|
|
45
47
|
title: input.title,
|
|
46
48
|
fields,
|
|
47
|
-
settings:
|
|
48
|
-
...(
|
|
49
|
-
...(
|
|
49
|
+
settings: section ? section.settings : {},
|
|
50
|
+
...(section ? { section: section.name } : {}),
|
|
51
|
+
...(section?.sample ? { sample: section.sample } : {}),
|
|
52
|
+
...(input.layout ? { layout: true } : {}),
|
|
50
53
|
};
|
|
51
54
|
}
|
|
@@ -12,4 +12,7 @@ export type SmoodlyImagesConfig = {
|
|
|
12
12
|
* Supabase stack answers on. A public hostname that happens to resolve
|
|
13
13
|
* privately (a Docker alias) is not detected; set the flag by hand then. */
|
|
14
14
|
export declare function isPrivateHost(hostname: string): boolean;
|
|
15
|
+
/** The Supabase host's public media path as a remote pattern. Defined here, not in remote-pattern.ts: this
|
|
16
|
+
* entry loads in plain Node and imports nothing; remote-pattern.ts re-exports it for the Astro side. */
|
|
17
|
+
export declare function mediaRemotePattern(url: string): RemotePattern;
|
|
15
18
|
export declare function smoodlyImages(url?: string | undefined): SmoodlyImagesConfig;
|