smoodly 0.0.55 → 0.0.57
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/dist/admin/editor/PageSettings.js +3 -2
- package/dist/admin/editor/global-edit.js +5 -2
- package/dist/admin/files.generated.js +1 -1
- package/dist/admin/forms/FieldWidget.js +4 -1
- package/dist/admin/shell/IconRail.js +13 -8
- package/dist/admin/shell/console-links.d.ts +3 -0
- package/dist/admin/shell/console-links.js +8 -3
- package/dist/admin/tree-ops.js +6 -2
- package/dist/admin/ui/theme.js +25 -12
- package/dist/admin/validate.d.ts +4 -0
- package/dist/admin/validate.js +9 -0
- package/dist/astro/components/RichText.astro +2 -1
- package/dist/astro/preview.d.ts +6 -3
- package/dist/astro/preview.js +25 -12
- package/dist/astro/richtext.js +2 -2
- package/dist/astro/routes/admin.js +2 -2
- package/dist/config.js +6 -3
- package/dist/css.js +4 -0
- package/dist/custom/index.js +3 -0
- package/dist/entry-store.d.ts +3 -0
- package/dist/image/markup.js +19 -3
- package/dist/marks.d.ts +4 -2
- package/dist/marks.js +4 -2
- package/dist/read-shapes.d.ts +1 -0
- package/dist/read-shapes.js +4 -1
- package/dist/registration.d.ts +3 -2
- package/dist/registration.js +7 -2
- package/dist/richtext/index.js +9 -0
- package/dist/richtext-href.d.ts +2 -0
- package/dist/richtext-href.js +2 -0
- package/dist/richtext-render.js +5 -4
- package/dist/store.d.ts +3 -0
- package/dist/store.js +6 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/views.js +27 -0
- package/package.json +5 -1
package/dist/richtext/index.js
CHANGED
|
@@ -2,6 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { RichText } from "../richtext-render.js";
|
|
3
3
|
import { SmoodlyImage } from "../image/SmoodlyImage.js";
|
|
4
4
|
export function createRichText({ elements = [], sizes } = {}) {
|
|
5
|
+
// A schema where a paired element belongs would fail at the first render on `undefined.name`; name it here (register 2026-10-03).
|
|
6
|
+
for (const e of elements) {
|
|
7
|
+
if (e && typeof e.schema === "object")
|
|
8
|
+
continue;
|
|
9
|
+
const name = e?.name;
|
|
10
|
+
throw new Error(typeof name === "string"
|
|
11
|
+
? `smoodly: createRichText({ elements }) got the schema "${name}" where a component belongs — pair it first: smoodly.element(${name}Schema, View).`
|
|
12
|
+
: `smoodly: createRichText({ elements }) got ${e === null ? "null" : typeof e} where a paired element belongs.`);
|
|
13
|
+
}
|
|
5
14
|
const image = ({ image, caption }) => (_jsxs("figure", { children: [_jsx(SmoodlyImage, { image: image, sizes: sizes }), caption !== "" && _jsx("figcaption", { children: caption })] }));
|
|
6
15
|
function Bound({ doc }) {
|
|
7
16
|
return _jsx(RichText, { doc: doc, elements: elements, image: image });
|
package/dist/richtext-href.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ResolvedImage } from "./media.ts";
|
|
2
2
|
export declare function safeHref(raw: unknown): string | null;
|
|
3
|
+
/** A text node's text as both renderers show it: a string or a number; anything else is nothing, never a throw. */
|
|
4
|
+
export declare const textOf: (v: unknown) => string;
|
|
3
5
|
export declare function escapeHtml(text: string): string;
|
|
4
6
|
export declare const isResolvedImage: (v: unknown) => v is ResolvedImage;
|
package/dist/richtext-href.js
CHANGED
|
@@ -9,6 +9,8 @@ export function safeHref(raw) {
|
|
|
9
9
|
const href = raw.replace(/[\t\n\r]/g, "").trim();
|
|
10
10
|
return href !== "" && SAFE_HREF.test(href) ? href : null;
|
|
11
11
|
}
|
|
12
|
+
/** A text node's text as both renderers show it: a string or a number; anything else is nothing, never a throw. */
|
|
13
|
+
export const textOf = (v) => (typeof v === "string" ? v : typeof v === "number" ? String(v) : "");
|
|
12
14
|
const ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
13
15
|
export function escapeHtml(text) {
|
|
14
16
|
return text.replace(/[&<>"']/g, (c) => ESCAPES[c]);
|
package/dist/richtext-render.js
CHANGED
|
@@ -9,7 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
9
9
|
// (default: a figure over a plain <img>); an `element` node renders the
|
|
10
10
|
// paired component named in `elements`, nothing when unknown.
|
|
11
11
|
import { Fragment, createElement } from "react";
|
|
12
|
-
import { isResolvedImage, safeHref } from "./richtext-href.js";
|
|
12
|
+
import { isResolvedImage, safeHref, textOf } from "./richtext-href.js";
|
|
13
13
|
import { ELEMENT_NODE, IMAGE_NODE } from "./richtext-walk.js";
|
|
14
14
|
/** Lazy by default: body images sit below the fold, and a lazy <img> is
|
|
15
15
|
* the one React does not hoist a preload hint for on the server. */
|
|
@@ -31,7 +31,7 @@ function renderNode(node, r) {
|
|
|
31
31
|
return null;
|
|
32
32
|
const children = Array.isArray(node.content) ? renderNodes(node.content, r) : null;
|
|
33
33
|
switch (node.type) {
|
|
34
|
-
case "text": return withMarks(node.text
|
|
34
|
+
case "text": return withMarks(textOf(node.text), node.marks ?? []);
|
|
35
35
|
case "paragraph": return _jsx("p", { children: children });
|
|
36
36
|
case "heading": {
|
|
37
37
|
const level = Math.min(6, Math.max(1, Math.round(Number(node.attrs?.level ?? 1) || 1)));
|
|
@@ -53,8 +53,9 @@ function renderNode(node, r) {
|
|
|
53
53
|
const Component = typeof a.name === "string" ? r.elements.get(a.name) : undefined;
|
|
54
54
|
if (!Component)
|
|
55
55
|
return null;
|
|
56
|
-
|
|
57
|
-
const
|
|
56
|
+
// A record only, as the Astro renderer takes it: an array spread as props is nonsense.
|
|
57
|
+
const fields = a.fields && typeof a.fields === "object" && !Array.isArray(a.fields) ? a.fields : {};
|
|
58
|
+
const settings = a.settings && typeof a.settings === "object" && !Array.isArray(a.settings) ? a.settings : {};
|
|
58
59
|
return _jsx(Component, { ...fields, settings: settings });
|
|
59
60
|
}
|
|
60
61
|
default: return children;
|
package/dist/store.d.ts
CHANGED
|
@@ -195,3 +195,6 @@ export declare function pageRefEdges(trees: PageTree[], sections: SectionLike[],
|
|
|
195
195
|
* THERE — every referenced entry must exist, have a row in that locale,
|
|
196
196
|
* and be published in it. Throws naming the offenders. */
|
|
197
197
|
export declare function assertPublishable(label: string, tree: PageTree, sections: SectionLike[], entries: EntryStore, locale: string): Promise<void>;
|
|
198
|
+
/** The same rule for any set of reference edges: an entry's or a global item's fields publish only when every
|
|
199
|
+
* entry they reference is published in that locale (register 2026-09-14: pages were gated, entries were not). */
|
|
200
|
+
export declare function assertRefsPublished(label: string, edges: RefEdge[], entries: EntryStore, locale: string): Promise<void>;
|
package/dist/store.js
CHANGED
|
@@ -83,8 +83,13 @@ export function pageRefEdges(trees, sections, elements) {
|
|
|
83
83
|
* THERE — every referenced entry must exist, have a row in that locale,
|
|
84
84
|
* and be published in it. Throws naming the offenders. */
|
|
85
85
|
export async function assertPublishable(label, tree, sections, entries, locale) {
|
|
86
|
+
await assertRefsPublished(label, collectTreeRefs(tree, sections), entries, locale);
|
|
87
|
+
}
|
|
88
|
+
/** The same rule for any set of reference edges: an entry's or a global item's fields publish only when every
|
|
89
|
+
* entry they reference is published in that locale (register 2026-09-14: pages were gated, entries were not). */
|
|
90
|
+
export async function assertRefsPublished(label, edges, entries, locale) {
|
|
86
91
|
const targets = new Map();
|
|
87
|
-
for (const edge of
|
|
92
|
+
for (const edge of edges) {
|
|
88
93
|
if (edge.targetCollection === ASSET_COLLECTION)
|
|
89
94
|
continue; // assets are immediate, never draft-gated
|
|
90
95
|
if (!targets.has(edge.targetId))
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SMOODLY_VERSION = "0.0.
|
|
1
|
+
export declare const SMOODLY_VERSION = "0.0.57";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/write-version.mjs from package.json — do not edit.
|
|
2
|
-
export const SMOODLY_VERSION = "0.0.
|
|
2
|
+
export const SMOODLY_VERSION = "0.0.57";
|
package/dist/views.js
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/** Every entry of a defineViews list is a paired component; a bare schema or a plain function is named here, where
|
|
2
|
+
* `undefined.name` would have been the word (register 2026-10-03). */
|
|
3
|
+
function assertPaired(key, builder, views) {
|
|
4
|
+
for (const v of views) {
|
|
5
|
+
const schema = v?.schema;
|
|
6
|
+
if (schema && typeof schema === "object")
|
|
7
|
+
continue;
|
|
8
|
+
const name = v?.name;
|
|
9
|
+
if (typeof v === "function") {
|
|
10
|
+
throw new Error(`smoodly: defineViews({ ${key} }) got a plain function "${String(name)}" where a paired component belongs — pair it first: smoodly.${builder}(schema, View).`);
|
|
11
|
+
}
|
|
12
|
+
if (v && typeof v === "object" && typeof name === "string") {
|
|
13
|
+
throw new Error(`smoodly: defineViews({ ${key} }) got the schema "${name}" where a component belongs — pair it first: smoodly.${builder}(${name}Schema, View).`);
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`smoodly: defineViews({ ${key} }) got ${v === null ? "null" : typeof v} where a paired component belongs.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
1
18
|
function match(what, key, schemas, views) {
|
|
2
19
|
const registered = new Map(schemas.map((s) => [s.name, s]));
|
|
3
20
|
const viewed = new Set();
|
|
@@ -8,6 +25,8 @@ function match(what, key, schemas, views) {
|
|
|
8
25
|
if (schema !== v.schema) {
|
|
9
26
|
throw new Error(`smoodly: the view for ${what} "${v.schema.name}" is paired with a different schema object than the one in the config — pair it with the registered schema.`);
|
|
10
27
|
}
|
|
28
|
+
if (viewed.has(v.schema.name))
|
|
29
|
+
throw new Error(`smoodly: two views are paired with ${what} "${v.schema.name}" — keep one in defineViews({ ${key} }).`);
|
|
11
30
|
viewed.add(v.schema.name);
|
|
12
31
|
}
|
|
13
32
|
for (const s of schemas) {
|
|
@@ -17,6 +36,14 @@ function match(what, key, schemas, views) {
|
|
|
17
36
|
}
|
|
18
37
|
export function defineViews(config, views) {
|
|
19
38
|
const elements = views.elements ?? [];
|
|
39
|
+
assertPaired("sections", "section", views.sections);
|
|
40
|
+
assertPaired("elements", "element", elements);
|
|
41
|
+
assertPaired("pages", "page", views.pages);
|
|
42
|
+
for (const p of views.pages) {
|
|
43
|
+
if (p.schema.kind !== "page" && p.schema.kind !== "entryPage") {
|
|
44
|
+
throw new Error(`smoodly: defineViews({ pages }) got the ${p.schema.kind} "${p.schema.name}" — pages holds page types and entry pages; ${p.schema.kind}s go to defineViews({ ${p.schema.kind}s }).`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
20
47
|
match("section", "sections", config.registry.sections, views.sections);
|
|
21
48
|
match("element", "elements", config.registry.elements, elements);
|
|
22
49
|
match("page type", "pages", config.registry.pages, views.pages.filter((p) => p.schema.kind === "page"));
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smoodly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.57",
|
|
4
4
|
"description": "Open-source, code-first CMS for Astro and Next.js: define a component once, it becomes a typed, editable building block.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.12"
|
|
8
|
+
},
|
|
6
9
|
"repository": {
|
|
7
10
|
"type": "git",
|
|
8
11
|
"url": "git+https://github.com/smoodly-io/smoodly.git",
|
|
@@ -51,6 +54,7 @@
|
|
|
51
54
|
"types": "./dist/richtext/index.d.ts",
|
|
52
55
|
"default": "./dist/richtext/index.js"
|
|
53
56
|
},
|
|
57
|
+
"./package.json": "./package.json",
|
|
54
58
|
"./admin": {
|
|
55
59
|
"types": "./dist/admin/index.d.ts",
|
|
56
60
|
"default": "./dist/admin/index.js"
|