smoodly 0.0.12 → 0.0.14
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 +294 -40
- package/dist/admin/editor/CanvasOverlay.d.ts +6 -2
- package/dist/admin/editor/CanvasOverlay.js +9 -11
- package/dist/admin/editor/EditorView.js +135 -28
- package/dist/admin/editor/FloatingToolbar.d.ts +16 -0
- package/dist/admin/editor/FloatingToolbar.js +14 -0
- package/dist/admin/editor/InlineRichtext.d.ts +9 -0
- package/dist/admin/editor/InlineRichtext.js +50 -0
- package/dist/admin/editor/Outline.d.ts +16 -10
- package/dist/admin/editor/Outline.js +13 -10
- package/dist/admin/editor/PageInspector.d.ts +21 -0
- package/dist/admin/editor/PageInspector.js +24 -0
- package/dist/admin/editor/PageSettings.d.ts +1 -3
- package/dist/admin/editor/PageSettings.js +7 -6
- package/dist/admin/editor/SeoSettings.d.ts +7 -0
- package/dist/admin/editor/SeoSettings.js +15 -0
- package/dist/admin/editor/inline-dom.d.ts +27 -0
- package/dist/admin/editor/inline-dom.js +43 -0
- package/dist/admin/editor/inline-session.d.ts +30 -0
- package/dist/admin/editor/inline-session.js +19 -0
- package/dist/admin/editor/protocol.d.ts +22 -1
- package/dist/admin/editor/useCanvasBridge.d.ts +8 -4
- package/dist/admin/editor/useCanvasBridge.js +12 -7
- package/dist/admin/editor/useInlineSession.d.ts +46 -0
- package/dist/admin/editor/useInlineSession.js +244 -0
- package/dist/admin/field-paths.d.ts +13 -0
- package/dist/admin/field-paths.js +49 -0
- package/dist/admin/forms/FieldWidget.d.ts +7 -2
- package/dist/admin/forms/FieldWidget.js +35 -43
- package/dist/admin/forms/ListField.d.ts +10 -0
- package/dist/admin/forms/ListField.js +42 -0
- package/dist/admin/forms/MediaWidget.js +10 -3
- package/dist/admin/forms/RichtextElementCard.d.ts +3 -0
- package/dist/admin/forms/RichtextElementCard.js +37 -0
- package/dist/admin/forms/RichtextImageCard.d.ts +3 -0
- package/dist/admin/forms/RichtextImageCard.js +42 -0
- package/dist/admin/forms/RichtextInsertMenu.d.ts +6 -0
- package/dist/admin/forms/RichtextInsertMenu.js +6 -0
- package/dist/admin/forms/RichtextPopovers.d.ts +7 -0
- package/dist/admin/forms/RichtextPopovers.js +25 -0
- package/dist/admin/forms/RichtextToolbar.d.ts +4 -2
- package/dist/admin/forms/RichtextToolbar.js +4 -2
- package/dist/admin/forms/RichtextWidget.d.ts +4 -1
- package/dist/admin/forms/RichtextWidget.js +23 -120
- package/dist/admin/forms/elements-context.d.ts +7 -0
- package/dist/admin/forms/elements-context.js +13 -0
- package/dist/admin/forms/reveal.d.ts +5 -0
- package/dist/admin/forms/reveal.js +47 -0
- package/dist/admin/forms/richtext-extensions.d.ts +8 -0
- package/dist/admin/forms/richtext-extensions.js +11 -0
- package/dist/admin/forms/richtext-form-context.d.ts +16 -0
- package/dist/admin/forms/richtext-form-context.js +18 -0
- package/dist/admin/forms/richtext-insert.d.ts +38 -0
- package/dist/admin/forms/richtext-insert.js +37 -0
- package/dist/admin/forms/richtext-nodes.d.ts +37 -0
- package/dist/admin/forms/richtext-nodes.js +85 -0
- package/dist/admin/forms/richtext-paste.js +5 -0
- package/dist/admin/forms/tabs.d.ts +3 -0
- package/dist/admin/forms/tabs.js +6 -0
- package/dist/admin/forms/useRichtextEditor.d.ts +51 -0
- package/dist/admin/forms/useRichtextEditor.js +281 -0
- package/dist/admin/next/bridge.js +51 -20
- package/dist/admin/ops-impl.js +85 -19
- package/dist/admin/richtext-doc.d.ts +3 -0
- package/dist/admin/richtext-doc.js +12 -0
- package/dist/admin/serialize.d.ts +11 -0
- package/dist/admin/serialize.js +18 -1
- package/dist/admin/shell/AdminApp.js +2 -1
- package/dist/admin/shell/EntriesList.js +34 -12
- package/dist/admin/shell/EntryForm.js +22 -15
- package/dist/admin/shell/IconRail.js +6 -5
- package/dist/admin/shell/ListView.d.ts +9 -2
- package/dist/admin/shell/ListView.js +7 -6
- package/dist/admin/shell/LoginView.js +2 -2
- package/dist/admin/shell/PagesList.js +53 -24
- package/dist/admin/shell/SharedForm.js +17 -11
- package/dist/admin/shell/SharedList.js +5 -3
- package/dist/admin/shell/entries-list.d.ts +4 -0
- package/dist/admin/shell/entries-list.js +6 -0
- package/dist/admin/shell/pages-tree.d.ts +24 -0
- package/dist/admin/shell/pages-tree.js +59 -1
- package/dist/admin/tree-ops.d.ts +11 -1
- package/dist/admin/tree-ops.js +32 -1
- package/dist/admin/ui/LocaleSwitcher.d.ts +10 -0
- package/dist/admin/ui/LocaleSwitcher.js +28 -8
- package/dist/admin/ui/Modal.js +14 -2
- package/dist/admin/ui/ResizeHandle.d.ts +1 -0
- package/dist/admin/ui/ResizeHandle.js +127 -0
- package/dist/admin/ui/primitives.d.ts +38 -4
- package/dist/admin/ui/primitives.js +35 -10
- package/dist/admin/ui/resize.d.ts +16 -0
- package/dist/admin/ui/resize.js +23 -0
- package/dist/admin/ui/sortable.d.ts +23 -0
- package/dist/admin/ui/sortable.js +57 -0
- package/dist/admin/ui/theme.d.ts +3 -1
- package/dist/admin/ui/theme.js +527 -165
- package/dist/admin/ui/useSortable.d.ts +35 -0
- package/dist/admin/ui/useSortable.js +220 -0
- package/dist/admin/validate.d.ts +6 -1
- package/dist/admin/validate.js +52 -12
- package/dist/collections.d.ts +6 -5
- package/dist/config.d.ts +1 -0
- package/dist/config.js +36 -0
- package/dist/entry-store.d.ts +23 -3
- package/dist/entry-store.js +31 -5
- package/dist/field-defaults.d.ts +8 -0
- package/dist/field-defaults.js +18 -0
- package/dist/fields.d.ts +30 -1
- package/dist/fields.js +33 -3
- package/dist/image/SmoodlyImage.d.ts +4 -1
- package/dist/image/SmoodlyImage.js +2 -2
- package/dist/index.d.ts +16 -7
- package/dist/index.js +10 -4
- package/dist/marks.d.ts +53 -0
- package/dist/marks.js +43 -0
- package/dist/media.d.ts +3 -2
- package/dist/media.js +48 -6
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +1 -0
- package/dist/next/meta.d.ts +7 -3
- package/dist/next/meta.js +18 -9
- package/dist/next/page-renderer.js +1 -1
- package/dist/next/queries.d.ts +36 -0
- package/dist/next/queries.js +87 -0
- package/dist/page-tree.d.ts +3 -0
- package/dist/page-tree.js +6 -0
- package/dist/page.d.ts +11 -3
- package/dist/page.js +26 -0
- package/dist/pairing.d.ts +2 -1
- package/dist/pairing.js +2 -9
- package/dist/refs.d.ts +2 -2
- package/dist/refs.js +33 -12
- package/dist/render.d.ts +14 -7
- package/dist/render.js +4 -3
- package/dist/resolve.d.ts +12 -2
- package/dist/resolve.js +16 -7
- package/dist/revalidate.d.ts +6 -0
- package/dist/revalidate.js +6 -0
- package/dist/richtext/index.d.ts +12 -0
- package/dist/richtext/index.js +9 -0
- package/dist/richtext-render.d.ts +22 -2
- package/dist/richtext-render.js +40 -13
- package/dist/richtext-walk.d.ts +44 -0
- package/dist/richtext-walk.js +46 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +13 -1
- package/dist/section-wrapper.js +2 -1
- package/dist/shared.d.ts +7 -7
- package/dist/site.d.ts +25 -1
- package/dist/site.js +89 -22
- package/dist/store.d.ts +7 -1
- package/dist/store.js +13 -4
- package/dist/supabase-entry-store.d.ts +1 -0
- package/dist/supabase-entry-store.js +34 -11
- package/dist/supabase-store.d.ts +1 -0
- package/dist/supabase-store.js +14 -1
- package/dist/toolset.d.ts +7 -1
- package/dist/toolset.js +13 -4
- package/package.json +6 -1
package/dist/fields.d.ts
CHANGED
|
@@ -4,6 +4,25 @@ export type Descriptor = {
|
|
|
4
4
|
type: string;
|
|
5
5
|
[key: string]: unknown;
|
|
6
6
|
};
|
|
7
|
+
/** A choice: what is stored (`value`) and what the editor reads (`label`). */
|
|
8
|
+
export type Option<V extends string = string> = {
|
|
9
|
+
value: V;
|
|
10
|
+
label: string;
|
|
11
|
+
};
|
|
12
|
+
/** What a schema may write: an option object, or a bare string as shorthand for value = label. */
|
|
13
|
+
export type OptionInput<V extends string = string> = V | Option<V>;
|
|
14
|
+
/** The stored value of one option input. */
|
|
15
|
+
export type OptionValue<T> = T extends string ? T : T extends {
|
|
16
|
+
value: infer V extends string;
|
|
17
|
+
} ? V : never;
|
|
18
|
+
/** Normalizes a schema's option list to objects; refuses an empty list,
|
|
19
|
+
* since a choice with nothing to choose is a schema bug (spec 2026-09-10
|
|
20
|
+
* page fields §1). */
|
|
21
|
+
export declare function normalizeOptions<V extends string>(options: readonly OptionInput<V>[]): Option<V>[];
|
|
22
|
+
/** The options of a descriptor in either shape (objects since spec 2026-09-10; bare strings in older data). */
|
|
23
|
+
export declare function optionsOf(options: unknown): Option[];
|
|
24
|
+
/** The values a stored choice may take. */
|
|
25
|
+
export declare const optionValues: (options: unknown) => string[];
|
|
7
26
|
export type FieldBuilder<Value = unknown, Flags extends BuilderFlags = {}> = {
|
|
8
27
|
readonly descriptor: Descriptor;
|
|
9
28
|
optional(): FieldBuilder<Value, Flags & {
|
|
@@ -42,6 +61,10 @@ type NotLocalized<B> = B extends {
|
|
|
42
61
|
} ? Fl extends {
|
|
43
62
|
localized: true;
|
|
44
63
|
} ? never : B : B;
|
|
64
|
+
export declare const describePath: (path: string) => string;
|
|
65
|
+
/** First path at which `matches` is true of the descriptor, walking into
|
|
66
|
+
* object subfields and a list's item, or null. */
|
|
67
|
+
export declare function findMatch(d: Descriptor, matches: (d: Descriptor) => boolean, path?: string): string | null;
|
|
45
68
|
export declare const f: {
|
|
46
69
|
text: () => FieldBuilder<string, {}>;
|
|
47
70
|
link: () => FieldBuilder<string, {}>;
|
|
@@ -61,7 +84,13 @@ export declare const f: {
|
|
|
61
84
|
toolset?: Toolset;
|
|
62
85
|
}) => FieldBuilder<unknown, {}>;
|
|
63
86
|
dataAttributes: () => FieldBuilder<Record<string, string>, {}>;
|
|
64
|
-
|
|
87
|
+
boolean: () => FieldBuilder<boolean, {}>;
|
|
88
|
+
/** One value from a list, shown as a dropdown. */
|
|
89
|
+
select: <const O extends readonly OptionInput[]>(options: O) => FieldBuilder<OptionValue<O[number]>, {}>;
|
|
90
|
+
/** One value from a list, shown as a radio group. */
|
|
91
|
+
radio: <const O extends readonly OptionInput[]>(options: O) => FieldBuilder<OptionValue<O[number]>, {}>;
|
|
92
|
+
/** Several values from a list, stored as an array, shown as a checkbox list. */
|
|
93
|
+
checkboxes: <const O extends readonly OptionInput[]>(options: O) => FieldBuilder<OptionValue<O[number]>[], {}>;
|
|
65
94
|
object: <S extends Record<string, FieldBuilder<any, any>>>(shape: S & { [K in keyof S]: NotLocalized<S[K]>; }) => FieldBuilder<{ [K in keyof S]: ValueOf<S[K]>; }, {}>;
|
|
66
95
|
/** A repeating item: any builder, but no list at any depth inside the item
|
|
67
96
|
* — a list in an object in a list is still a nested list on screen, and a
|
package/dist/fields.js
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
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
|
+
/** Normalizes a schema's option list to objects; refuses an empty list,
|
|
7
|
+
* since a choice with nothing to choose is a schema bug (spec 2026-09-10
|
|
8
|
+
* page fields §1). */
|
|
9
|
+
export function normalizeOptions(options) {
|
|
10
|
+
if (!Array.isArray(options) || options.length === 0)
|
|
11
|
+
throw new Error("smoodly: a choice field needs at least one option.");
|
|
12
|
+
return options.map((o) => (typeof o === "string" ? { value: o, label: o } : { value: o.value, label: o.label }));
|
|
13
|
+
}
|
|
14
|
+
/** The options of a descriptor in either shape (objects since spec 2026-09-10; bare strings in older data). */
|
|
15
|
+
export function optionsOf(options) {
|
|
16
|
+
if (!Array.isArray(options))
|
|
17
|
+
return [];
|
|
18
|
+
return options.flatMap((o) => {
|
|
19
|
+
if (typeof o === "string")
|
|
20
|
+
return [{ value: o, label: o }];
|
|
21
|
+
if (o && typeof o === "object" && typeof o.value === "string") {
|
|
22
|
+
const opt = o;
|
|
23
|
+
return [{ value: opt.value, label: typeof opt.label === "string" ? opt.label : opt.value }];
|
|
24
|
+
}
|
|
25
|
+
return [];
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** The values a stored choice may take. */
|
|
29
|
+
export const optionValues = (options) => optionsOf(options).map((o) => o.value);
|
|
6
30
|
function builder(descriptor) {
|
|
7
31
|
const chain = (patch) => builder({ ...descriptor, ...patch });
|
|
8
32
|
return {
|
|
@@ -22,10 +46,10 @@ function builder(descriptor) {
|
|
|
22
46
|
tab: (tab) => chain({ tab }),
|
|
23
47
|
};
|
|
24
48
|
}
|
|
25
|
-
const describePath = (path) => (path === "" ? "the item" : `"${path}"`);
|
|
49
|
+
export const describePath = (path) => (path === "" ? "the item" : `"${path}"`);
|
|
26
50
|
/** First path at which `matches` is true of the descriptor, walking into
|
|
27
51
|
* object subfields and a list's item, or null. */
|
|
28
|
-
function findMatch(d, matches, path = "") {
|
|
52
|
+
export function findMatch(d, matches, path = "") {
|
|
29
53
|
if (matches(d))
|
|
30
54
|
return path;
|
|
31
55
|
if (d.type === "object") {
|
|
@@ -74,7 +98,13 @@ export const f = {
|
|
|
74
98
|
file: () => builder({ type: "file" }),
|
|
75
99
|
richtext: (opts) => builder({ type: "richtext", toolset: opts?.toolset ?? DEFAULT_TOOLSET }),
|
|
76
100
|
dataAttributes: () => builder({ type: "dataAttributes" }),
|
|
77
|
-
|
|
101
|
+
boolean: () => builder({ type: "boolean" }),
|
|
102
|
+
/** One value from a list, shown as a dropdown. */
|
|
103
|
+
select: (options) => builder({ type: "select", options: normalizeOptions(options) }),
|
|
104
|
+
/** One value from a list, shown as a radio group. */
|
|
105
|
+
radio: (options) => builder({ type: "radio", options: normalizeOptions(options) }),
|
|
106
|
+
/** Several values from a list, stored as an array, shown as a checkbox list. */
|
|
107
|
+
checkboxes: (options) => builder({ type: "checkboxes", options: normalizeOptions(options) }),
|
|
78
108
|
object: (shape) => {
|
|
79
109
|
for (const [k, v] of Object.entries(shape))
|
|
80
110
|
assertContainerItem("f.object", v.descriptor, k);
|
|
@@ -14,5 +14,8 @@ export type SmoodlyImageProps = {
|
|
|
14
14
|
alt?: string;
|
|
15
15
|
loader?: ImageLoader;
|
|
16
16
|
unoptimized?: boolean;
|
|
17
|
+
} & {
|
|
18
|
+
/** Data attributes land on the wrapper box — a section's field mark (spec 2026-09-10 §1). */
|
|
19
|
+
[attr: `data-${string}`]: string | undefined;
|
|
17
20
|
};
|
|
18
|
-
export declare function SmoodlyImage({ image, aspect, sizes, priority, loading, className, style, alt, loader, unoptimized }: SmoodlyImageProps): ReactElement;
|
|
21
|
+
export declare function SmoodlyImage({ image, aspect, sizes, priority, loading, className, style, alt, loader, unoptimized, ...data }: SmoodlyImageProps): ReactElement;
|
|
@@ -5,11 +5,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
5
5
|
// site imports from the media layer. Server-safe — no "use client".
|
|
6
6
|
import Image from "next/image";
|
|
7
7
|
import { boxAspectOf, frameFor } from "./crop-math.js";
|
|
8
|
-
export function SmoodlyImage({ image, aspect, sizes, priority, loading, className, style, alt, loader, unoptimized }) {
|
|
8
|
+
export function SmoodlyImage({ image, aspect, sizes, priority, loading, className, style, alt, loader, unoptimized, ...data }) {
|
|
9
9
|
const box = boxAspectOf(image, aspect);
|
|
10
10
|
const frame = loader?.handlesCrop ? { left: 0, top: 0, width: 100, height: 100 } : frameFor(image, box);
|
|
11
11
|
const svg = image.mime === "image/svg+xml";
|
|
12
|
-
return (_jsx("span", { className: className, style: { display: "block", position: "relative", overflow: "hidden", aspectRatio: String(box), ...style }, children: _jsx(Image, { src: image.url, alt: alt ?? image.alt, width: image.width, height: image.height, sizes: sizes, priority: priority, loading: loading, unoptimized: unoptimized ?? svg, ...(loader ? { loader: ({ width, quality }) => loader.url(image, { width, quality }) } : {}), style: {
|
|
12
|
+
return (_jsx("span", { className: className, ...data, style: { display: "block", position: "relative", overflow: "hidden", aspectRatio: String(box), ...style }, children: _jsx(Image, { src: image.url, alt: alt ?? image.alt, width: image.width, height: image.height, sizes: sizes, priority: priority, loading: loading, unoptimized: unoptimized ?? svg, ...(loader ? { loader: ({ width, quality }) => loader.url(image, { width, quality }) } : {}), style: {
|
|
13
13
|
position: "absolute", left: `${frame.left}%`, top: `${frame.top}%`,
|
|
14
14
|
width: `${frame.width}%`, height: `${frame.height}%`, maxWidth: "none",
|
|
15
15
|
} }) }));
|
package/dist/index.d.ts
CHANGED
|
@@ -5,13 +5,17 @@ import { Section } from "./section-wrapper.tsx";
|
|
|
5
5
|
import { page } from "./render.tsx";
|
|
6
6
|
import { entryPage } from "./entry-page.tsx";
|
|
7
7
|
import { shared } from "./shared.ts";
|
|
8
|
+
import { marks } from "./marks.ts";
|
|
8
9
|
export { Zone, renderPage, EMPTY_PAGE_CONTEXT } from "./render.tsx";
|
|
9
10
|
export type { PageTree, TreeNode, ZoneContent, PairedPage, Registry, PageContext, PageContextLink, PageLink, PageViewProps } from "./render.tsx";
|
|
10
|
-
export { RichText } from "./richtext-render.tsx";
|
|
11
|
+
export { RichText, defaultRichTextImage } from "./richtext-render.tsx";
|
|
12
|
+
export type { RichTextProps, RichTextElement, RichTextImageProps } from "./richtext-render.tsx";
|
|
11
13
|
export type { PageSchema } from "./page.ts";
|
|
12
14
|
export { fixedSlug } from "./page.ts";
|
|
13
15
|
export type { EntryPageSchema, PairedEntryPage, EntryPageProps } from "./entry-page.tsx";
|
|
14
|
-
export { f } from "./fields.ts";
|
|
16
|
+
export { f, normalizeOptions, optionsOf, optionValues } from "./fields.ts";
|
|
17
|
+
export { defaultsOf, withDefaults } from "./field-defaults.ts";
|
|
18
|
+
export { filterPageTree } from "./page-tree.ts";
|
|
15
19
|
export { ASSET_COLLECTION, DEFAULT_MEDIA, MEDIA_TYPES, FULL_CROP, CENTER_HOTSPOT, parseAspect, cropAspect, clamp01, isMediaValue, collectAssetIds, applyAssets, } from "./media.ts";
|
|
16
20
|
export type { Crop, Hotspot, AssetKind, ImageValue, VideoValue, FileValue, AssetMeta, AssetRecord, ResolvedImage, ResolvedVideo, ResolvedFile, } from "./media.ts";
|
|
17
21
|
export { z } from "./zones.ts";
|
|
@@ -25,15 +29,17 @@ export { SupabasePageStore } from "./supabase-store.ts";
|
|
|
25
29
|
export { SupabaseAssetStore } from "./supabase-asset-store.ts";
|
|
26
30
|
export { collectEntryRefs, collectTreeRefs } from "./refs.ts";
|
|
27
31
|
export type { RefEdge, SectionLike } from "./refs.ts";
|
|
32
|
+
export { richtextBlocks, elementSchemas, IMAGE_NODE, ELEMENT_NODE } from "./richtext-walk.ts";
|
|
33
|
+
export type { BlockNode, ElementSchemas, ImageNodeAttrs, ElementNodeAttrs } from "./richtext-walk.ts";
|
|
28
34
|
export { MemoryRefIndex } from "./ref-index.ts";
|
|
29
35
|
export { MemoryPathIndex } from "./path-index.ts";
|
|
30
36
|
export type { PathIndex } from "./path-index.ts";
|
|
31
37
|
export { MemoryEntryStore } from "./entry-store.ts";
|
|
32
38
|
export { SupabaseEntryStore } from "./supabase-entry-store.ts";
|
|
33
39
|
export { SupabasePathIndex } from "./supabase-path-index.ts";
|
|
34
|
-
export type { EntryStore, EntryRecord, EntryLocale, EntryVersion, EntryUsage, ListOptions, CreateEntryOptions, AddEntryLocaleOptions, EntryStoreOptions, } from "./entry-store.ts";
|
|
35
|
-
export { entryErrors, compareEntries, DEFAULT_ENTRY_LOCALES, localizedKeys, splitFields, entryLocaleRow, entryFieldsIn, entryRefEdges, entryTitleIn, } from "./entry-store.ts";
|
|
36
|
-
export { affectedTargets, pageTag, entryTag, sharedTag } from "./revalidate.ts";
|
|
40
|
+
export type { EntryStore, EntryRecord, EntryLocale, EntryVersion, EntryUsage, ListOptions, CreateEntryOptions, AddEntryLocaleOptions, EntryStoreOptions, Where, WhereValue, } from "./entry-store.ts";
|
|
41
|
+
export { entryErrors, compareEntries, DEFAULT_ENTRY_LOCALES, localizedKeys, splitFields, entryLocaleRow, entryFieldsIn, entryRefEdges, entryTitleIn, matchesWhere, } from "./entry-store.ts";
|
|
42
|
+
export { affectedTargets, pageTag, entryTag, sharedTag, pagesTreeTag, collectionTag } from "./revalidate.ts";
|
|
37
43
|
export type { AffectedTargets } from "./revalidate.ts";
|
|
38
44
|
export { resolveTree, resolveFields, resolveMedia } from "./resolve.ts";
|
|
39
45
|
export type { EntryFetcher, ResolveFieldsOptions, AssetFetcher } from "./resolve.ts";
|
|
@@ -43,7 +49,7 @@ export { fixedPathFor } from "./localize.ts";
|
|
|
43
49
|
export type { PageStore, PageRecord, PageLocale, PageVersion, PageStoreOptions, CreatePageInput, AddLocaleOptions } from "./store.ts";
|
|
44
50
|
export { pathOptions, pageErrors, localeRow, pageRefEdges, DEFAULT_LOCALES } from "./store.ts";
|
|
45
51
|
export type { SmoodlyConfig, ResolvedConfig } from "./config.ts";
|
|
46
|
-
export type { FieldBuilder, Descriptor, ValueOf } from "./fields.ts";
|
|
52
|
+
export type { FieldBuilder, Descriptor, ValueOf, Option, OptionInput, OptionValue } from "./fields.ts";
|
|
47
53
|
export type { SectionSchema, ElementSchema, BuilderMap } from "./schema.ts";
|
|
48
54
|
export type { Props, PairedComponent } from "./pairing.tsx";
|
|
49
55
|
export type { CollectionSchema, EntryOrder } from "./collections.ts";
|
|
@@ -126,9 +132,12 @@ export declare const smoodly: {
|
|
|
126
132
|
toolset: typeof toolset;
|
|
127
133
|
shared: typeof shared;
|
|
128
134
|
Section: typeof Section;
|
|
135
|
+
marks: typeof marks;
|
|
129
136
|
};
|
|
137
|
+
export { NODE_ATTR, ZONE_ATTR, EMPTY_ATTR, FIELD_ATTR, ITEM_ATTR, EDITOR_ATTR, resolveMarkPath } from "./marks.ts";
|
|
138
|
+
export type { MarkNode, MarkPath, Marks, Scope } from "./marks.ts";
|
|
130
139
|
export { resolveSmoodlyEnv, smoodlyEnv } from "./env.ts";
|
|
131
140
|
export type { SmoodlyEnv } from "./env.ts";
|
|
132
141
|
export { clientOptions, createSmoodlyClient } from "./client.ts";
|
|
133
142
|
export { createSmoodlySite, pageContextOf } from "./site.ts";
|
|
134
|
-
export type { SiteOptions, SitePage, SiteEntry, SiteHit, SmoodlySite, LoadOptions, PageTreeItem, SiteEntryItem, EntryTreeItem, EntryQuery, } from "./site.ts";
|
|
143
|
+
export type { SiteOptions, SitePage, SitePageSchema, SiteEntry, SiteHit, SmoodlySite, LoadOptions, PageTreeItem, SiteEntryItem, EntryTreeItem, EntryQuery, } from "./site.ts";
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,13 @@ import { Section } from "./section-wrapper.js";
|
|
|
6
6
|
import { page } from "./render.js";
|
|
7
7
|
import { entryPage } from "./entry-page.js";
|
|
8
8
|
import { shared } from "./shared.js";
|
|
9
|
+
import { marks } from "./marks.js";
|
|
9
10
|
export { Zone, renderPage, EMPTY_PAGE_CONTEXT } from "./render.js";
|
|
10
|
-
export { RichText } from "./richtext-render.js";
|
|
11
|
+
export { RichText, defaultRichTextImage } from "./richtext-render.js";
|
|
11
12
|
export { fixedSlug } from "./page.js";
|
|
12
|
-
export { f } from "./fields.js";
|
|
13
|
+
export { f, normalizeOptions, optionsOf, optionValues } from "./fields.js";
|
|
14
|
+
export { defaultsOf, withDefaults } from "./field-defaults.js";
|
|
15
|
+
export { filterPageTree } from "./page-tree.js";
|
|
13
16
|
export { ASSET_COLLECTION, DEFAULT_MEDIA, MEDIA_TYPES, FULL_CROP, CENTER_HOTSPOT, parseAspect, cropAspect, clamp01, isMediaValue, collectAssetIds, applyAssets, } from "./media.js";
|
|
14
17
|
export { z } from "./zones.js";
|
|
15
18
|
export { defineConfig } from "./config.js";
|
|
@@ -20,13 +23,14 @@ export { imageDimensions } from "./image-dimensions.js";
|
|
|
20
23
|
export { SupabasePageStore } from "./supabase-store.js";
|
|
21
24
|
export { SupabaseAssetStore } from "./supabase-asset-store.js";
|
|
22
25
|
export { collectEntryRefs, collectTreeRefs } from "./refs.js";
|
|
26
|
+
export { richtextBlocks, elementSchemas, IMAGE_NODE, ELEMENT_NODE } from "./richtext-walk.js";
|
|
23
27
|
export { MemoryRefIndex } from "./ref-index.js";
|
|
24
28
|
export { MemoryPathIndex } from "./path-index.js";
|
|
25
29
|
export { MemoryEntryStore } from "./entry-store.js";
|
|
26
30
|
export { SupabaseEntryStore } from "./supabase-entry-store.js";
|
|
27
31
|
export { SupabasePathIndex } from "./supabase-path-index.js";
|
|
28
|
-
export { entryErrors, compareEntries, DEFAULT_ENTRY_LOCALES, localizedKeys, splitFields, entryLocaleRow, entryFieldsIn, entryRefEdges, entryTitleIn, } from "./entry-store.js";
|
|
29
|
-
export { affectedTargets, pageTag, entryTag, sharedTag } from "./revalidate.js";
|
|
32
|
+
export { entryErrors, compareEntries, DEFAULT_ENTRY_LOCALES, localizedKeys, splitFields, entryLocaleRow, entryFieldsIn, entryRefEdges, entryTitleIn, matchesWhere, } from "./entry-store.js";
|
|
33
|
+
export { affectedTargets, pageTag, entryTag, sharedTag, pagesTreeTag, collectionTag } from "./revalidate.js";
|
|
30
34
|
export { resolveTree, resolveFields, resolveMedia } from "./resolve.js";
|
|
31
35
|
export { SEGMENT_RE, isSegment, assertSegment, joinPath, splitPath, segmentFor, isHomeNode, pagePathIn, chainOf, assertDepth, pagePath, pagePathRows, entryPathRows, collectionSegment, buildPageTree, perLocaleSegments, assertSupportedLocale, unsupportedLocale, } from "./paths.js";
|
|
32
36
|
export { fixedPathFor } from "./localize.js";
|
|
@@ -46,7 +50,9 @@ export const smoodly = {
|
|
|
46
50
|
toolset,
|
|
47
51
|
shared,
|
|
48
52
|
Section,
|
|
53
|
+
marks,
|
|
49
54
|
};
|
|
55
|
+
export { NODE_ATTR, ZONE_ATTR, EMPTY_ATTR, FIELD_ATTR, ITEM_ATTR, EDITOR_ATTR, resolveMarkPath } from "./marks.js";
|
|
50
56
|
export { resolveSmoodlyEnv, smoodlyEnv } from "./env.js";
|
|
51
57
|
export { clientOptions, createSmoodlyClient } from "./client.js";
|
|
52
58
|
export { createSmoodlySite, pageContextOf } from "./site.js";
|
package/dist/marks.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { BuilderMap, NamespaceValues } from "./schema.ts";
|
|
2
|
+
/** A section's root, from `<smoodly.Section>` in editor mode. */
|
|
3
|
+
export declare const NODE_ATTR = "data-smd-node";
|
|
4
|
+
/** An empty zone's placeholder box, from `<Zone>` in editor mode. */
|
|
5
|
+
export declare const ZONE_ATTR = "data-smd-zone";
|
|
6
|
+
export declare const EMPTY_ATTR = "data-smd-empty";
|
|
7
|
+
/** The element that renders a field's value verbatim. */
|
|
8
|
+
export declare const FIELD_ATTR = "data-smd-field";
|
|
9
|
+
/** A list item's or object field's root: scopes the field names inside it. */
|
|
10
|
+
export declare const ITEM_ATTR = "data-smd-item";
|
|
11
|
+
/** The root of an inline editing session inside the canvas; the bridge
|
|
12
|
+
* forwards nothing from inside it, and reports no marks under it. */
|
|
13
|
+
export declare const EDITOR_ATTR = "data-smd-editor";
|
|
14
|
+
/** What the resolver needs from an element — a DOM Element satisfies it. */
|
|
15
|
+
export type MarkNode = {
|
|
16
|
+
getAttribute(name: string): string | null;
|
|
17
|
+
parentElement: MarkNode | null;
|
|
18
|
+
};
|
|
19
|
+
export type MarkPath = {
|
|
20
|
+
node: string;
|
|
21
|
+
path: string;
|
|
22
|
+
};
|
|
23
|
+
/** The section node and dotted field path of a marked element: its own
|
|
24
|
+
* field name, prefixed by every item scope from the element itself up to
|
|
25
|
+
* the section root. Null when the element carries no field mark or sits
|
|
26
|
+
* outside a section node. */
|
|
27
|
+
export declare function resolveMarkPath(el: MarkNode): MarkPath | null;
|
|
28
|
+
type Keys<T> = Extract<keyof T, string>;
|
|
29
|
+
type ElementOf<T> = NonNullable<T> extends readonly (infer E)[] ? E : never;
|
|
30
|
+
/** The helpers for one level of a section's values: the section itself,
|
|
31
|
+
* a list item, or an object field. Item attributes are relative to the
|
|
32
|
+
* parent scope — the resolver joins them — so the helpers carry no path. */
|
|
33
|
+
export type Marks<T> = {
|
|
34
|
+
/** Spread on the element that renders `key` verbatim. */
|
|
35
|
+
field<K extends Keys<T>>(key: K): {
|
|
36
|
+
"data-smd-field": K;
|
|
37
|
+
};
|
|
38
|
+
/** List item `index` of `key`: spread `.attrs` on the item's root and mark the fields inside with the returned helpers. */
|
|
39
|
+
item<K extends Keys<T>>(key: K, index: number): Scope<ElementOf<T[K]>>;
|
|
40
|
+
/** The object field `key`: spread `.attrs` on its root and mark the fields inside. */
|
|
41
|
+
object<K extends Keys<T>>(key: K): Scope<NonNullable<T[K]>>;
|
|
42
|
+
};
|
|
43
|
+
export type Scope<T> = Marks<T> & {
|
|
44
|
+
attrs: {
|
|
45
|
+
"data-smd-item": string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/** Typed marks for a section or element schema: `const m = smoodly.marks(heroSchema)`,
|
|
49
|
+
* then `<h1 {...m.field("heading")}>`. The raw attributes stay the contract. */
|
|
50
|
+
export declare function marks<S extends {
|
|
51
|
+
__f?: BuilderMap;
|
|
52
|
+
}>(_schema: S): Marks<NamespaceValues<NonNullable<S["__f"]>>>;
|
|
53
|
+
export {};
|
package/dist/marks.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** A section's root, from `<smoodly.Section>` in editor mode. */
|
|
2
|
+
export const NODE_ATTR = "data-smd-node";
|
|
3
|
+
/** An empty zone's placeholder box, from `<Zone>` in editor mode. */
|
|
4
|
+
export const ZONE_ATTR = "data-smd-zone";
|
|
5
|
+
export const EMPTY_ATTR = "data-smd-empty";
|
|
6
|
+
/** The element that renders a field's value verbatim. */
|
|
7
|
+
export const FIELD_ATTR = "data-smd-field";
|
|
8
|
+
/** A list item's or object field's root: scopes the field names inside it. */
|
|
9
|
+
export const ITEM_ATTR = "data-smd-item";
|
|
10
|
+
/** The root of an inline editing session inside the canvas; the bridge
|
|
11
|
+
* forwards nothing from inside it, and reports no marks under it. */
|
|
12
|
+
export const EDITOR_ATTR = "data-smd-editor";
|
|
13
|
+
/** The section node and dotted field path of a marked element: its own
|
|
14
|
+
* field name, prefixed by every item scope from the element itself up to
|
|
15
|
+
* the section root. Null when the element carries no field mark or sits
|
|
16
|
+
* outside a section node. */
|
|
17
|
+
export function resolveMarkPath(el) {
|
|
18
|
+
const field = el.getAttribute(FIELD_ATTR);
|
|
19
|
+
if (!field)
|
|
20
|
+
return null;
|
|
21
|
+
const segments = [field];
|
|
22
|
+
for (let cur = el; cur; cur = cur.parentElement) {
|
|
23
|
+
const node = cur.getAttribute(NODE_ATTR);
|
|
24
|
+
if (node)
|
|
25
|
+
return { node, path: segments.join(".") };
|
|
26
|
+
const item = cur.getAttribute(ITEM_ATTR);
|
|
27
|
+
if (item)
|
|
28
|
+
segments.unshift(item);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function level() {
|
|
33
|
+
return {
|
|
34
|
+
field: (key) => ({ [FIELD_ATTR]: key }),
|
|
35
|
+
item: (key, index) => ({ ...level(), attrs: { [ITEM_ATTR]: `${key}.${index}` } }),
|
|
36
|
+
object: (key) => ({ ...level(), attrs: { [ITEM_ATTR]: key } }),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Typed marks for a section or element schema: `const m = smoodly.marks(heroSchema)`,
|
|
40
|
+
* then `<h1 {...m.field("heading")}>`. The raw attributes stay the contract. */
|
|
41
|
+
export function marks(_schema) {
|
|
42
|
+
return level();
|
|
43
|
+
}
|
package/dist/media.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Descriptor } from "./fields.ts";
|
|
2
|
+
import { type ElementSchemas } from "./richtext-walk.ts";
|
|
2
3
|
export type Crop = {
|
|
3
4
|
left: number;
|
|
4
5
|
top: number;
|
|
@@ -102,7 +103,7 @@ export declare const isMediaValue: (v: unknown) => v is {
|
|
|
102
103
|
};
|
|
103
104
|
/** Every asset id a value set references, walking lists and objects —
|
|
104
105
|
* unlike refs, media may sit inside a container (spec §8). */
|
|
105
|
-
export declare function collectAssetIds(values: Record<string, unknown> | undefined, descriptors: Record<string, Descriptor>, out: Set<string
|
|
106
|
+
export declare function collectAssetIds(values: Record<string, unknown> | undefined, descriptors: Record<string, Descriptor>, out: Set<string>, elements?: ElementSchemas): void;
|
|
106
107
|
/** Replace every media value in place with its resolved object — or delete
|
|
107
108
|
* it when the asset is missing or pending, so `image?.url` guards hold. */
|
|
108
|
-
export declare function applyAssets(values: Record<string, unknown> | undefined, descriptors: Record<string, Descriptor>, assets: Map<string, AssetRecord
|
|
109
|
+
export declare function applyAssets(values: Record<string, unknown> | undefined, descriptors: Record<string, Descriptor>, assets: Map<string, AssetRecord>, elements?: ElementSchemas): void;
|
package/dist/media.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ELEMENT_NODE, IMAGE_NODE, elementOf, elementSets, richtextBlocks } from "./richtext-walk.js";
|
|
1
2
|
/** The refs target collection of every asset edge — a reserved registered name. */
|
|
2
3
|
export const ASSET_COLLECTION = "asset";
|
|
3
4
|
export const MEDIA_TYPES = new Set(["image", "video", "file"]);
|
|
@@ -30,7 +31,7 @@ export const cropAspect = (crop, imageAspect) => (crop.width * imageAspect) / cr
|
|
|
30
31
|
export const isMediaValue = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && typeof v.asset === "string";
|
|
31
32
|
/** Every asset id a value set references, walking lists and objects —
|
|
32
33
|
* unlike refs, media may sit inside a container (spec §8). */
|
|
33
|
-
export function collectAssetIds(values, descriptors, out) {
|
|
34
|
+
export function collectAssetIds(values, descriptors, out, elements = {}) {
|
|
34
35
|
if (!values)
|
|
35
36
|
return;
|
|
36
37
|
for (const [key, d] of Object.entries(descriptors)) {
|
|
@@ -41,8 +42,24 @@ export function collectAssetIds(values, descriptors, out) {
|
|
|
41
42
|
if (isMediaValue(value))
|
|
42
43
|
out.add(value.asset);
|
|
43
44
|
}
|
|
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 styles.
|
|
47
|
+
for (const node of richtextBlocks(value)) {
|
|
48
|
+
if (node.type === IMAGE_NODE) {
|
|
49
|
+
const image = node.attrs.image;
|
|
50
|
+
if (isMediaValue(image))
|
|
51
|
+
out.add(image.asset);
|
|
52
|
+
}
|
|
53
|
+
else if (node.type === ELEMENT_NODE) {
|
|
54
|
+
const schema = elementOf(node, elements);
|
|
55
|
+
if (schema)
|
|
56
|
+
for (const set of elementSets(node, schema))
|
|
57
|
+
collectAssetIds(set.values, set.descriptors, out, elements);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
44
61
|
else if (d.type === "object" && typeof value === "object" && !Array.isArray(value)) {
|
|
45
|
-
collectAssetIds(value, d.fields, out);
|
|
62
|
+
collectAssetIds(value, d.fields, out, elements);
|
|
46
63
|
}
|
|
47
64
|
else if (d.type === "list" && Array.isArray(value)) {
|
|
48
65
|
const item = d.item;
|
|
@@ -52,7 +69,10 @@ export function collectAssetIds(values, descriptors, out) {
|
|
|
52
69
|
out.add(v.asset);
|
|
53
70
|
}
|
|
54
71
|
else if (item.type === "object" && v && typeof v === "object") {
|
|
55
|
-
collectAssetIds(v, item.fields, out);
|
|
72
|
+
collectAssetIds(v, item.fields, out, elements);
|
|
73
|
+
}
|
|
74
|
+
else if (item.type === "richtext") {
|
|
75
|
+
collectAssetIds({ v }, { v: item }, out, elements);
|
|
56
76
|
}
|
|
57
77
|
}
|
|
58
78
|
}
|
|
@@ -97,7 +117,7 @@ function resolveOne(value, d, assets) {
|
|
|
97
117
|
}
|
|
98
118
|
/** Replace every media value in place with its resolved object — or delete
|
|
99
119
|
* it when the asset is missing or pending, so `image?.url` guards hold. */
|
|
100
|
-
export function applyAssets(values, descriptors, assets) {
|
|
120
|
+
export function applyAssets(values, descriptors, assets, elements = {}) {
|
|
101
121
|
if (!values)
|
|
102
122
|
return;
|
|
103
123
|
for (const [key, d] of Object.entries(descriptors)) {
|
|
@@ -111,8 +131,25 @@ export function applyAssets(values, descriptors, assets) {
|
|
|
111
131
|
else
|
|
112
132
|
values[key] = resolved;
|
|
113
133
|
}
|
|
134
|
+
else if (d.type === "richtext") {
|
|
135
|
+
for (const node of richtextBlocks(value)) {
|
|
136
|
+
if (node.type === IMAGE_NODE) {
|
|
137
|
+
const resolved = resolveOne(node.attrs.image, { type: "image" }, assets);
|
|
138
|
+
if (resolved === undefined)
|
|
139
|
+
delete node.attrs.image;
|
|
140
|
+
else
|
|
141
|
+
node.attrs.image = resolved;
|
|
142
|
+
}
|
|
143
|
+
else if (node.type === ELEMENT_NODE) {
|
|
144
|
+
const schema = elementOf(node, elements);
|
|
145
|
+
if (schema)
|
|
146
|
+
for (const set of elementSets(node, schema))
|
|
147
|
+
applyAssets(set.values, set.descriptors, assets, elements);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
114
151
|
else if (d.type === "object" && typeof value === "object" && !Array.isArray(value)) {
|
|
115
|
-
applyAssets(value, d.fields, assets);
|
|
152
|
+
applyAssets(value, d.fields, assets, elements);
|
|
116
153
|
}
|
|
117
154
|
else if (d.type === "list" && Array.isArray(value)) {
|
|
118
155
|
const item = d.item;
|
|
@@ -122,7 +159,12 @@ export function applyAssets(values, descriptors, assets) {
|
|
|
122
159
|
else if (item.type === "object") {
|
|
123
160
|
for (const v of value)
|
|
124
161
|
if (v && typeof v === "object")
|
|
125
|
-
applyAssets(v, item.fields, assets);
|
|
162
|
+
applyAssets(v, item.fields, assets, elements);
|
|
163
|
+
}
|
|
164
|
+
else if (item.type === "richtext") {
|
|
165
|
+
// Resolves in place inside the document object `v`; the wrapper is throwaway.
|
|
166
|
+
for (const v of value)
|
|
167
|
+
applyAssets({ v }, { v: item }, assets, elements);
|
|
126
168
|
}
|
|
127
169
|
}
|
|
128
170
|
}
|
package/dist/next/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
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
|
+
export { createSmoodlyQueries } from "./queries.ts";
|
|
5
|
+
export type { SmoodlyQueries, QueryOptions, EntriesQuery, TypedEntryItem, SharedValue } from "./queries.ts";
|
package/dist/next/index.js
CHANGED
package/dist/next/meta.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
|
-
/** Maps the page tree's meta
|
|
3
|
-
*
|
|
4
|
-
|
|
2
|
+
/** Maps the page tree's meta onto Next metadata (spec 2026-09-10 page
|
|
3
|
+
* fields §4). The SEO title falls back to the page's own title so every
|
|
4
|
+
* page carries one; absent meta and no title returns {} so the app's
|
|
5
|
+
* layout.tsx defaults apply untouched. */
|
|
6
|
+
export declare function metadataFrom(meta: unknown, page?: {
|
|
7
|
+
title?: string;
|
|
8
|
+
}): Metadata;
|
package/dist/next/meta.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { imageUrl } from "../image/loaders.js";
|
|
2
|
+
const isResolvedImage = (v) => typeof v === "object" && v !== null && typeof v.url === "string";
|
|
3
|
+
/** Maps the page tree's meta onto Next metadata (spec 2026-09-10 page
|
|
4
|
+
* fields §4). The SEO title falls back to the page's own title so every
|
|
5
|
+
* page carries one; absent meta and no title returns {} so the app's
|
|
6
|
+
* layout.tsx defaults apply untouched. */
|
|
7
|
+
export function metadataFrom(meta, page) {
|
|
8
|
+
const m = typeof meta === "object" && meta !== null && !Array.isArray(meta) ? meta : {};
|
|
7
9
|
const str = (key) => (typeof m[key] === "string" && m[key] ? m[key] : undefined);
|
|
8
|
-
const title = str("title");
|
|
10
|
+
const title = str("title") ?? (page?.title ? page.title : undefined);
|
|
9
11
|
const description = str("description");
|
|
10
|
-
const ogImage =
|
|
12
|
+
const ogImage = m.ogImage;
|
|
11
13
|
const out = {};
|
|
12
14
|
if (title)
|
|
13
15
|
out.title = title;
|
|
@@ -18,9 +20,16 @@ export function metadataFrom(meta) {
|
|
|
18
20
|
og.title = title;
|
|
19
21
|
if (description)
|
|
20
22
|
og.description = description;
|
|
21
|
-
if (ogImage)
|
|
23
|
+
if (isResolvedImage(ogImage)) {
|
|
24
|
+
// The delivery seam decides the URL; the cloud's transform loader (OQ 28) can serve a real 1200-wide render.
|
|
25
|
+
og.images = [{ url: imageUrl(ogImage, { width: 1200 }), width: ogImage.width, height: ogImage.height, ...(ogImage.alt ? { alt: ogImage.alt } : {}) }];
|
|
26
|
+
}
|
|
27
|
+
else if (typeof ogImage === "string" && ogImage) {
|
|
22
28
|
og.images = [ogImage];
|
|
29
|
+
}
|
|
23
30
|
if (Object.keys(og).length > 0)
|
|
24
31
|
out.openGraph = og;
|
|
32
|
+
if (m.noindex === true)
|
|
33
|
+
out.robots = { index: false, follow: false };
|
|
25
34
|
return out;
|
|
26
35
|
}
|
|
@@ -73,7 +73,7 @@ export function createPageRenderer(options) {
|
|
|
73
73
|
const hit = await load(locale, path, draft);
|
|
74
74
|
if (!hit || !accept(hit))
|
|
75
75
|
return {};
|
|
76
|
-
return { ...(hit.kind === "page" ? metadataFrom(hit.tree.meta) : {}), ...alternates(hit) };
|
|
76
|
+
return { ...(hit.kind === "page" ? metadataFrom(hit.tree.meta, { title: hit.title }) : {}), ...alternates(hit) };
|
|
77
77
|
}
|
|
78
78
|
const any = () => true;
|
|
79
79
|
/** The escape hatch may only render ITS OWN registration's record. */
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CollectionSchema } from "../collections.ts";
|
|
2
|
+
import type { EntryRecord } from "../entry-store.ts";
|
|
3
|
+
import type { SharedSchema } from "../shared.ts";
|
|
4
|
+
import type { EntryQuery, PageTreeItem, SiteEntryItem, SitePage, SmoodlySite } from "../site.ts";
|
|
5
|
+
export type QueryOptions = {
|
|
6
|
+
locale?: string;
|
|
7
|
+
};
|
|
8
|
+
export type EntriesQuery = Omit<EntryQuery, "locale" | "draft"> & QueryOptions;
|
|
9
|
+
type ByName<T, N> = Extract<T, {
|
|
10
|
+
name: N;
|
|
11
|
+
}>;
|
|
12
|
+
/** Fields typed from the schema; a widened `CollectionSchema[]` array (names are `string`) falls back to the untyped record instead of `never`. */
|
|
13
|
+
type EntryFields<C extends readonly CollectionSchema<any, string>[], N> = string extends C[number]["name"] ? Record<string, unknown> : NonNullable<ByName<C[number], N>["__entry"]>;
|
|
14
|
+
/** A list item whose `entry.fields` are the collection's typed values. */
|
|
15
|
+
export type TypedEntryItem<C extends readonly CollectionSchema<any, string>[], N> = Omit<SiteEntryItem, "entry"> & {
|
|
16
|
+
entry: Omit<EntryRecord, "fields"> & {
|
|
17
|
+
fields: EntryFields<C, N>;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export type SharedValue<S extends readonly SharedSchema<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 SharedSchema<any, string>[]> = {
|
|
22
|
+
/** The published page tree in a locale, every item with its page fields. */
|
|
23
|
+
getPageTree(options?: QueryOptions): Promise<PageTreeItem[]>;
|
|
24
|
+
/** A page by locale-relative path ("/company/about-us") or id. */
|
|
25
|
+
getPage(pathOrId: string, options?: QueryOptions): Promise<SitePage | null>;
|
|
26
|
+
/** Published entries of a collection: `where` (shared fields), `order`, `limit`, `parent` run in the store. */
|
|
27
|
+
getEntries<N extends C[number]["name"]>(collection: N, options?: EntriesQuery): Promise<TypedEntryItem<C, N>[]>;
|
|
28
|
+
getEntry<N extends C[number]["name"]>(collection: N, idOrPath: string, options?: QueryOptions): Promise<TypedEntryItem<C, N> | null>;
|
|
29
|
+
getShared<N extends S[number]["name"]>(item: N, options?: QueryOptions): Promise<SharedValue<S, N> | null>;
|
|
30
|
+
};
|
|
31
|
+
export declare function createSmoodlyQueries<const C extends readonly CollectionSchema<any, string>[], const S extends readonly SharedSchema<any, string>[] = []>(options: {
|
|
32
|
+
site: SmoodlySite;
|
|
33
|
+
collections: C;
|
|
34
|
+
shared?: S;
|
|
35
|
+
}): SmoodlyQueries<C, S>;
|
|
36
|
+
export {};
|