@velastack/cms 0.4.0 → 0.5.1
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 +140 -28
- package/dist/backend/index.d.ts +1 -1
- package/dist/backend/routes/seed.d.ts +6 -0
- package/dist/backend/routes/seed.js +96 -0
- package/dist/backend/routes/table.js +3 -0
- package/dist/backend/store/queries.d.ts +6 -1
- package/dist/backend/store/queries.js +89 -0
- package/dist/cms.js +13 -16
- package/dist/components/admin-bar/admin-bar-internal.svelte +4 -4
- package/dist/components/admin-bar/admin-bar.css +1 -1
- package/dist/components/admin-bar/field-input.svelte +239 -0
- package/dist/components/admin-bar/field-input.svelte.d.ts +30 -0
- package/dist/components/admin-bar/fields-form.svelte +218 -0
- package/dist/components/admin-bar/fields-form.svelte.d.ts +16 -0
- package/dist/components/admin-bar/icons/pencil.svelte +19 -0
- package/dist/components/admin-bar/icons/pencil.svelte.d.ts +3 -0
- package/dist/components/admin-bar/keyboard-shortcuts-dialog.svelte +1 -1
- package/dist/components/admin-bar/link-input.svelte +102 -0
- package/dist/components/admin-bar/link-input.svelte.d.ts +9 -0
- package/dist/components/admin-bar/list-field.svelte +93 -0
- package/dist/components/admin-bar/list-field.svelte.d.ts +20 -0
- package/dist/components/admin-bar/locales-panel.svelte +42 -1
- package/dist/components/admin-bar/page-config.d.ts +8 -1
- package/dist/components/admin-bar/page-config.js +15 -2
- package/dist/components/admin-bar/page-options.d.ts +24 -0
- package/dist/components/admin-bar/page-options.js +53 -0
- package/dist/components/admin-bar/reorder-buttons.svelte +52 -0
- package/dist/components/admin-bar/reorder-buttons.svelte.d.ts +10 -0
- package/dist/components/admin-bar/rich-text-input.svelte +92 -0
- package/dist/components/admin-bar/rich-text-input.svelte.d.ts +9 -0
- package/dist/components/admin-bar/seo-panel.svelte +144 -189
- package/dist/components/admin-bar/site-options-panel.svelte +115 -0
- package/dist/components/admin-bar/site-options-panel.svelte.d.ts +7 -0
- package/dist/components/admin-bar/structured-editor-popover.svelte +259 -0
- package/dist/components/admin-bar/structured-editor-popover.svelte.d.ts +48 -0
- package/dist/components/admin-bar/ui/checkbox/checkbox.svelte +36 -0
- package/dist/components/admin-bar/ui/checkbox/checkbox.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/checkbox/index.d.ts +2 -0
- package/dist/components/admin-bar/ui/checkbox/index.js +2 -0
- package/dist/components/admin-bar/ui/popover/index.d.ts +5 -0
- package/dist/components/admin-bar/ui/popover/index.js +7 -0
- package/dist/components/admin-bar/ui/popover/popover-close.svelte +7 -0
- package/dist/components/admin-bar/ui/popover/popover-close.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/popover/popover-content.svelte +35 -0
- package/dist/components/admin-bar/ui/popover/popover-content.svelte.d.ts +10 -0
- package/dist/components/admin-bar/ui/popover/popover-trigger.svelte +7 -0
- package/dist/components/admin-bar/ui/popover/popover-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/popover/popover.svelte +7 -0
- package/dist/components/admin-bar/ui/popover/popover.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/sheet/index.d.ts +10 -0
- package/dist/components/admin-bar/ui/sheet/index.js +12 -0
- package/dist/components/admin-bar/ui/sheet/sheet-close.svelte +7 -0
- package/dist/components/admin-bar/ui/sheet/sheet-close.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet-content.svelte +64 -0
- package/dist/components/admin-bar/ui/sheet/sheet-content.svelte.d.ts +17 -0
- package/dist/components/admin-bar/ui/sheet/sheet-description.svelte +17 -0
- package/dist/components/admin-bar/ui/sheet/sheet-description.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet-footer.svelte +23 -0
- package/dist/components/admin-bar/ui/sheet/sheet-footer.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/sheet/sheet-header.svelte +20 -0
- package/dist/components/admin-bar/ui/sheet/sheet-header.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/sheet/sheet-overlay.svelte +20 -0
- package/dist/components/admin-bar/ui/sheet/sheet-overlay.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet-title.svelte +17 -0
- package/dist/components/admin-bar/ui/sheet/sheet-title.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/components/admin-bar/ui/sheet/sheet-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/sheet/sheet.svelte +7 -0
- package/dist/components/admin-bar/ui/sheet/sheet.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/switch/index.d.ts +2 -0
- package/dist/components/admin-bar/ui/switch/index.js +2 -0
- package/dist/components/admin-bar/ui/switch/switch.svelte +27 -0
- package/dist/components/admin-bar/ui/switch/switch.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/tabs/index.d.ts +5 -0
- package/dist/components/admin-bar/ui/tabs/index.js +7 -0
- package/dist/components/admin-bar/ui/tabs/tabs-content.svelte +17 -0
- package/dist/components/admin-bar/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/tabs/tabs-list.svelte +16 -0
- package/dist/components/admin-bar/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/tabs/tabs-trigger.svelte +20 -0
- package/dist/components/admin-bar/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/tabs/tabs.svelte +19 -0
- package/dist/components/admin-bar/ui/tabs/tabs.svelte.d.ts +4 -0
- package/dist/components/cms/cms-boolean.svelte +9 -18
- package/dist/components/cms/cms-boolean.svelte.d.ts +1 -0
- package/dist/components/cms/cms-collection.svelte +46 -0
- package/dist/components/cms/cms-collection.svelte.d.ts +25 -0
- package/dist/components/cms/cms-contact.svelte +26 -0
- package/dist/components/cms/cms-contact.svelte.d.ts +14 -0
- package/dist/components/cms/cms-date-time-editable.svelte +3 -3
- package/dist/components/cms/cms-date-time.svelte +17 -17
- package/dist/components/cms/cms-date-time.svelte.d.ts +2 -1
- package/dist/components/cms/cms-faq.svelte +22 -0
- package/dist/components/cms/cms-faq.svelte.d.ts +15 -0
- package/dist/components/cms/cms-gallery.svelte +31 -0
- package/dist/components/cms/cms-gallery.svelte.d.ts +16 -0
- package/dist/components/cms/cms-hours-editable.svelte +160 -0
- package/dist/components/cms/cms-hours-editable.svelte.d.ts +17 -0
- package/dist/components/cms/cms-hours.svelte +42 -0
- package/dist/components/cms/cms-hours.svelte.d.ts +19 -0
- package/dist/components/cms/cms-image-editable.svelte +13 -4
- package/dist/components/cms/cms-image.svelte +19 -34
- package/dist/components/cms/cms-image.svelte.d.ts +7 -8
- package/dist/components/cms/cms-link-editable.svelte +4 -60
- package/dist/components/cms/cms-link.svelte +15 -28
- package/dist/components/cms/cms-link.svelte.d.ts +4 -9
- package/dist/components/cms/cms-logos.svelte +29 -0
- package/dist/components/cms/cms-logos.svelte.d.ts +16 -0
- package/dist/components/cms/cms-markdown.svelte +15 -16
- package/dist/components/cms/cms-markdown.svelte.d.ts +1 -0
- package/dist/components/cms/cms-nav.svelte +22 -0
- package/dist/components/cms/cms-nav.svelte.d.ts +15 -0
- package/dist/components/cms/cms-number-editable.svelte +4 -2
- package/dist/components/cms/cms-number.svelte +18 -17
- package/dist/components/cms/cms-number.svelte.d.ts +2 -1
- package/dist/components/cms/cms-pricing.svelte +36 -0
- package/dist/components/cms/cms-pricing.svelte.d.ts +14 -0
- package/dist/components/cms/cms-rich-text.svelte +17 -22
- package/dist/components/cms/cms-rich-text.svelte.d.ts +1 -0
- package/dist/components/cms/cms-schedule.svelte +35 -0
- package/dist/components/cms/cms-schedule.svelte.d.ts +16 -0
- package/dist/components/cms/cms-social-links.svelte +35 -0
- package/dist/components/cms/cms-social-links.svelte.d.ts +15 -0
- package/dist/components/cms/cms-stats.svelte +31 -0
- package/dist/components/cms/cms-stats.svelte.d.ts +16 -0
- package/dist/components/cms/cms-steps.svelte +31 -0
- package/dist/components/cms/cms-steps.svelte.d.ts +16 -0
- package/dist/components/cms/cms-store.svelte.d.ts +14 -0
- package/dist/components/cms/cms-store.svelte.js +39 -2
- package/dist/components/cms/cms-structured-editable.svelte +29 -0
- package/dist/components/cms/{cms-repeater-editable.svelte.d.ts → cms-structured-editable.svelte.d.ts} +12 -8
- package/dist/components/cms/cms-structured.svelte +50 -0
- package/dist/components/cms/cms-structured.svelte.d.ts +49 -0
- package/dist/components/cms/cms-team.svelte +22 -0
- package/dist/components/cms/cms-team.svelte.d.ts +15 -0
- package/dist/components/cms/cms-testimonials.svelte +36 -0
- package/dist/components/cms/cms-testimonials.svelte.d.ts +15 -0
- package/dist/components/cms/cms-text.svelte +23 -30
- package/dist/components/cms/cms-text.svelte.d.ts +3 -1
- package/dist/components/cms/cms-timeline.svelte +35 -0
- package/dist/components/cms/cms-timeline.svelte.d.ts +16 -0
- package/dist/components/cms/index.d.ts +15 -1
- package/dist/components/cms/index.js +15 -1
- package/dist/components/cms/scope.d.ts +25 -5
- package/dist/components/cms/use-cms-field.svelte.d.ts +54 -0
- package/dist/components/cms/use-cms-field.svelte.js +83 -0
- package/dist/core/metadata.d.ts +44 -0
- package/dist/core/metadata.js +59 -0
- package/dist/core/shapes/collection.d.ts +60 -0
- package/dist/core/shapes/collection.js +194 -0
- package/dist/core/shapes/contact.d.ts +70 -0
- package/dist/core/shapes/contact.js +214 -0
- package/dist/core/shapes/faq.d.ts +12 -0
- package/dist/core/shapes/faq.js +48 -0
- package/dist/core/shapes/form.d.ts +46 -0
- package/dist/core/shapes/form.js +7 -0
- package/dist/core/shapes/hours.d.ts +91 -0
- package/dist/core/shapes/hours.js +325 -0
- package/dist/core/shapes/image.d.ts +15 -0
- package/dist/core/shapes/image.js +27 -0
- package/dist/core/shapes/index.d.ts +31 -0
- package/dist/core/shapes/index.js +13 -0
- package/dist/core/shapes/link.d.ts +15 -0
- package/dist/core/shapes/link.js +42 -0
- package/dist/core/shapes/nav.d.ts +21 -0
- package/dist/core/shapes/nav.js +71 -0
- package/dist/core/shapes/presets.d.ts +65 -0
- package/dist/core/shapes/presets.js +152 -0
- package/dist/core/shapes/pricing.d.ts +40 -0
- package/dist/core/shapes/pricing.js +115 -0
- package/dist/core/shapes/social-links.d.ts +19 -0
- package/dist/core/shapes/social-links.js +83 -0
- package/dist/core/shapes/team.d.ts +22 -0
- package/dist/core/shapes/team.js +70 -0
- package/dist/core/shapes/testimonials.d.ts +24 -0
- package/dist/core/shapes/testimonials.js +86 -0
- package/dist/core/structured-registry.d.ts +10 -0
- package/dist/core/structured-registry.js +6 -0
- package/dist/core/structured.d.ts +131 -0
- package/dist/core/structured.js +235 -0
- package/dist/core/wire.d.ts +31 -1
- package/dist/editor.d.ts +26 -0
- package/dist/editor.js +25 -0
- package/dist/index.d.ts +26 -4
- package/dist/index.js +31 -2
- package/dist/server/load-cms.js +1 -0
- package/package.json +12 -3
- package/plugin/README.md +22 -2
- package/plugin/dist/build.d.ts +74 -0
- package/plugin/dist/build.js +210 -0
- package/plugin/dist/build.js.map +1 -0
- package/plugin/dist/index.d.ts +10 -1
- package/plugin/dist/index.js +63 -4
- package/plugin/dist/index.js.map +1 -1
- package/plugin/dist/manifest.d.ts +31 -0
- package/plugin/dist/manifest.js +146 -41
- package/plugin/dist/manifest.js.map +1 -1
- package/plugin/dist/parse-svelte.d.ts +15 -0
- package/plugin/dist/parse-svelte.js +26 -16
- package/plugin/dist/parse-svelte.js.map +1 -1
- package/dist/components/admin-bar/site-settings-panel.svelte +0 -225
- package/dist/components/admin-bar/site-settings-panel.svelte.d.ts +0 -7
- package/dist/components/cms/cms-repeater-editable.svelte +0 -137
- package/dist/components/cms/cms-repeater.svelte +0 -55
- package/dist/components/cms/cms-repeater.svelte.d.ts +0 -30
|
@@ -5,6 +5,16 @@ export type CmsScope = {
|
|
|
5
5
|
routeId: string;
|
|
6
6
|
params: Record<string, string>;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* One CMS component usage as recorded by the Vite plugin: which value it
|
|
10
|
+
* reads and which component type reads it.
|
|
11
|
+
*/
|
|
12
|
+
export type CmsManifestUsage = {
|
|
13
|
+
name: string;
|
|
14
|
+
/** Canonical exported component name (`CmsText`, `CmsHours`, …). */
|
|
15
|
+
component: string;
|
|
16
|
+
preset?: string;
|
|
17
|
+
};
|
|
8
18
|
export type CmsScopeEntry = {
|
|
9
19
|
scopeId: string;
|
|
10
20
|
kind: 'layout' | 'page';
|
|
@@ -15,23 +25,31 @@ export type CmsScopeEntry = {
|
|
|
15
25
|
* `metadata` branch shows up here too (e.g. `'metadata.title'`).
|
|
16
26
|
*/
|
|
17
27
|
fields: string[];
|
|
28
|
+
/** The usages behind `fields`, with component types. Absent on hand-built manifests. */
|
|
29
|
+
usages?: CmsManifestUsage[];
|
|
18
30
|
};
|
|
19
31
|
/**
|
|
20
|
-
* Schema for the project-wide site scope
|
|
21
|
-
* Top-level keys are sections rendered as
|
|
22
|
-
* each section's `fields` keys become path segments in the stored
|
|
23
|
-
* (`
|
|
32
|
+
* Schema for the project-wide site scope: the **Site Options** panel.
|
|
33
|
+
* Authored on `createCms({ site })`. Top-level keys are sections rendered as
|
|
34
|
+
* groups; each section's `fields` keys become path segments in the stored
|
|
35
|
+
* tree (`seo.schemaType`, `seo.shareImage`).
|
|
36
|
+
*
|
|
37
|
+
* Site options are for values that never render on the page — the schema.org
|
|
38
|
+
* type, the default share image, enabled locales. Anything a visitor sees
|
|
39
|
+
* belongs in the root layout scope, edited where it renders.
|
|
24
40
|
*
|
|
25
41
|
* `type` selects the input widget. Storage is unconstrained — the schema is
|
|
26
42
|
* a UI hint, not an enforced type for the underlying tree (to keep the data
|
|
27
43
|
* model uniform with pages/layouts and avoid destructive prunes when fields
|
|
28
44
|
* are added/removed).
|
|
29
45
|
*/
|
|
30
|
-
export type SiteFieldType = 'text' | 'markdown' | 'number' | 'datetime' | 'url' | 'color' | 'image';
|
|
46
|
+
export type SiteFieldType = 'text' | 'markdown' | 'number' | 'datetime' | 'url' | 'color' | 'image' | 'enum' | 'boolean';
|
|
31
47
|
export type SiteFieldSchema = {
|
|
32
48
|
type: SiteFieldType;
|
|
33
49
|
label: string;
|
|
34
50
|
placeholder?: string;
|
|
51
|
+
/** Options for `enum` fields. */
|
|
52
|
+
values?: readonly string[];
|
|
35
53
|
};
|
|
36
54
|
export type SiteSectionSchema = {
|
|
37
55
|
label: string;
|
|
@@ -104,6 +122,8 @@ export type CmsManifestScope = {
|
|
|
104
122
|
* `metadata` branch shows up here too (e.g. `'metadata.title'`).
|
|
105
123
|
*/
|
|
106
124
|
fields: string[];
|
|
125
|
+
/** The usages behind `fields`, with component types. Absent on hand-built manifests. */
|
|
126
|
+
usages?: CmsManifestUsage[];
|
|
107
127
|
};
|
|
108
128
|
export type CmsManifestRoute = {
|
|
109
129
|
scopes: CmsManifestScope[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one piece of plumbing every display component shares: find the scope,
|
|
3
|
+
* read the value, decide whether it is editable, write it back.
|
|
4
|
+
*
|
|
5
|
+
* Scope resolution (`scope` prop):
|
|
6
|
+
* - absent → the enclosing route scope from context, installed by the plugin
|
|
7
|
+
* into every `+layout.svelte` / `+page.svelte`.
|
|
8
|
+
* - `'root'` → the root layout scope `layout:/`, so a page can read and write
|
|
9
|
+
* a value that lives site-wide (hours, contact, navigation, branding). The
|
|
10
|
+
* payload already carries every enclosing layout tree, and the root layout
|
|
11
|
+
* encloses everything.
|
|
12
|
+
* - any other string → that scope id verbatim (`layout:/(public)`), for a
|
|
13
|
+
* value owned by an intermediate layout. Only param-less scopes can be
|
|
14
|
+
* addressed this way.
|
|
15
|
+
*
|
|
16
|
+
* Read rule (see `core/structured.ts`): `undefined` means nothing is stored
|
|
17
|
+
* and the component shows its fallback; `null` means the editor cleared the
|
|
18
|
+
* field, and the component shows it empty. Never conflate the two — that is
|
|
19
|
+
* the bug where a cleared field reverts to the template's demo copy.
|
|
20
|
+
*/
|
|
21
|
+
import { type CmsScope } from './scope.js';
|
|
22
|
+
import { type CmsScopeRef } from './cms-store.svelte.js';
|
|
23
|
+
export declare const ROOT_SCOPE_ID = "layout:/";
|
|
24
|
+
export type CmsFieldInput = {
|
|
25
|
+
name: string;
|
|
26
|
+
scope?: string;
|
|
27
|
+
/** Per-item override: disables editing and scope lookup. */
|
|
28
|
+
value?: unknown;
|
|
29
|
+
};
|
|
30
|
+
export declare const resolveScopeRef: (scope: string | undefined, context: CmsScope | undefined) => CmsScopeRef | null;
|
|
31
|
+
export type CmsField<T> = {
|
|
32
|
+
/** Resolved scope, or `null` outside any route scope. */
|
|
33
|
+
readonly ref: CmsScopeRef | null;
|
|
34
|
+
/** Raw stored value: `undefined` (nothing), `null` (cleared) or the value. */
|
|
35
|
+
readonly raw: unknown;
|
|
36
|
+
/** What to render, per the component's `read`. */
|
|
37
|
+
readonly current: T;
|
|
38
|
+
readonly editable: boolean;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
/** Write the whole value into the draft. Pass `null` to clear. `locale`
|
|
41
|
+
* defaults to the locale being edited; structured editors pass the
|
|
42
|
+
* default locale for structure and another locale for its `$t` strings. */
|
|
43
|
+
set: (next: unknown, locale?: string) => void;
|
|
44
|
+
/** Write a value at a sub-path of this field, e.g. a `$t` string. */
|
|
45
|
+
setAt: (subPath: string, next: unknown, locale?: string) => void;
|
|
46
|
+
/** Read this field (or a sub-path of it) in a specific locale. */
|
|
47
|
+
getIn: (locale: string, subPath?: string) => unknown;
|
|
48
|
+
clear: () => void;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Call once at component init. `input` is an accessor so prop changes flow
|
|
52
|
+
* through; `read` turns the raw stored value into what the component renders.
|
|
53
|
+
*/
|
|
54
|
+
export declare const useCmsField: <T>(input: () => CmsFieldInput, read: (raw: unknown) => T) => CmsField<T>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one piece of plumbing every display component shares: find the scope,
|
|
3
|
+
* read the value, decide whether it is editable, write it back.
|
|
4
|
+
*
|
|
5
|
+
* Scope resolution (`scope` prop):
|
|
6
|
+
* - absent → the enclosing route scope from context, installed by the plugin
|
|
7
|
+
* into every `+layout.svelte` / `+page.svelte`.
|
|
8
|
+
* - `'root'` → the root layout scope `layout:/`, so a page can read and write
|
|
9
|
+
* a value that lives site-wide (hours, contact, navigation, branding). The
|
|
10
|
+
* payload already carries every enclosing layout tree, and the root layout
|
|
11
|
+
* encloses everything.
|
|
12
|
+
* - any other string → that scope id verbatim (`layout:/(public)`), for a
|
|
13
|
+
* value owned by an intermediate layout. Only param-less scopes can be
|
|
14
|
+
* addressed this way.
|
|
15
|
+
*
|
|
16
|
+
* Read rule (see `core/structured.ts`): `undefined` means nothing is stored
|
|
17
|
+
* and the component shows its fallback; `null` means the editor cleared the
|
|
18
|
+
* field, and the component shows it empty. Never conflate the two — that is
|
|
19
|
+
* the bug where a cleared field reverts to the template's demo copy.
|
|
20
|
+
*/
|
|
21
|
+
import { getCmsScope } from './scope.js';
|
|
22
|
+
import { cmsStore } from './cms-store.svelte.js';
|
|
23
|
+
export const ROOT_SCOPE_ID = 'layout:/';
|
|
24
|
+
export const resolveScopeRef = (scope, context) => {
|
|
25
|
+
if (scope === 'root')
|
|
26
|
+
return { scopeId: ROOT_SCOPE_ID, routeId: '/', params: {} };
|
|
27
|
+
if (scope) {
|
|
28
|
+
const colon = scope.indexOf(':');
|
|
29
|
+
const routeId = colon === -1 ? scope : scope.slice(colon + 1);
|
|
30
|
+
return { scopeId: colon === -1 ? `layout:${scope}` : scope, routeId, params: {} };
|
|
31
|
+
}
|
|
32
|
+
if (!context)
|
|
33
|
+
return null;
|
|
34
|
+
return { scopeId: context.scopeId, routeId: context.routeId, params: context.params };
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Call once at component init. `input` is an accessor so prop changes flow
|
|
38
|
+
* through; `read` turns the raw stored value into what the component renders.
|
|
39
|
+
*/
|
|
40
|
+
export const useCmsField = (input, read) => {
|
|
41
|
+
const context = getCmsScope();
|
|
42
|
+
const ref = $derived(resolveScopeRef(input().scope, context));
|
|
43
|
+
const raw = $derived.by(() => {
|
|
44
|
+
const { name, value } = input();
|
|
45
|
+
if (value !== undefined)
|
|
46
|
+
return value;
|
|
47
|
+
return ref ? cmsStore.getValue(ref, name) : undefined;
|
|
48
|
+
});
|
|
49
|
+
const current = $derived(read(raw));
|
|
50
|
+
const editable = $derived(cmsStore.isEditing && input().value === undefined && !!ref);
|
|
51
|
+
const set = (next, locale) => {
|
|
52
|
+
if (ref)
|
|
53
|
+
cmsStore.setValue(ref, input().name, next, locale);
|
|
54
|
+
};
|
|
55
|
+
const setAt = (subPath, next, locale) => {
|
|
56
|
+
if (ref)
|
|
57
|
+
cmsStore.setValue(ref, `${input().name}.${subPath}`, next, locale);
|
|
58
|
+
};
|
|
59
|
+
const getIn = (locale, subPath) => ref
|
|
60
|
+
? cmsStore.getValue(ref, subPath ? `${input().name}.${subPath}` : input().name, locale)
|
|
61
|
+
: undefined;
|
|
62
|
+
return {
|
|
63
|
+
get ref() {
|
|
64
|
+
return ref;
|
|
65
|
+
},
|
|
66
|
+
get raw() {
|
|
67
|
+
return raw;
|
|
68
|
+
},
|
|
69
|
+
get current() {
|
|
70
|
+
return current;
|
|
71
|
+
},
|
|
72
|
+
get editable() {
|
|
73
|
+
return editable;
|
|
74
|
+
},
|
|
75
|
+
get name() {
|
|
76
|
+
return input().name;
|
|
77
|
+
},
|
|
78
|
+
set,
|
|
79
|
+
setAt,
|
|
80
|
+
getIn,
|
|
81
|
+
clear: () => set(null)
|
|
82
|
+
};
|
|
83
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page metadata → `svelte-meta-tags` props.
|
|
3
|
+
*
|
|
4
|
+
* The page scope's `metadata` branch holds what the SEO panel edits (see
|
|
5
|
+
* `DEFAULT_METADATA_SCHEMA`). This maps it onto the object
|
|
6
|
+
* `definePageMetaTags` / `<MetaTags>` accept, so a template's root layout is
|
|
7
|
+
* one line: `deepMerge(baseMetaTags, toMetaTags(cms.metadata, { siteName }))`.
|
|
8
|
+
* Share fields fall back to the search fields, and the site name — read from
|
|
9
|
+
* the root layout's `branding.name` — supplies the title template and the
|
|
10
|
+
* Open Graph site name.
|
|
11
|
+
*/
|
|
12
|
+
export type MetaTagsInput = Record<string, unknown>;
|
|
13
|
+
export type MetaTagsOptions = {
|
|
14
|
+
/** The root layout's `branding.name`; drives `titleTemplate` and `openGraph.siteName`. */
|
|
15
|
+
siteName?: string;
|
|
16
|
+
/** Title template when `siteName` is set. `%s` is the page title. Default `'%s · <siteName>'`. */
|
|
17
|
+
titleTemplate?: string;
|
|
18
|
+
/** Absolute URL of the page, for `openGraph.url` when no canonical is set. */
|
|
19
|
+
url?: string;
|
|
20
|
+
};
|
|
21
|
+
export type MetaTagsOutput = {
|
|
22
|
+
title?: string;
|
|
23
|
+
titleTemplate?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
canonical?: string;
|
|
26
|
+
robots?: string;
|
|
27
|
+
openGraph?: {
|
|
28
|
+
title?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
siteName?: string;
|
|
32
|
+
images?: Array<{
|
|
33
|
+
url: string;
|
|
34
|
+
alt?: string;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
37
|
+
twitter?: {
|
|
38
|
+
cardType?: 'summary' | 'summary_large_image';
|
|
39
|
+
title?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
image?: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export declare const toMetaTags: (metadata: MetaTagsInput, options?: MetaTagsOptions) => MetaTagsOutput;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const str = (v) => (typeof v === 'string' && v !== '' ? v : undefined);
|
|
2
|
+
const imageUrl = (v) => {
|
|
3
|
+
if (typeof v === 'string')
|
|
4
|
+
return v ? { url: v } : undefined;
|
|
5
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
6
|
+
const r = v;
|
|
7
|
+
if (typeof r.url === 'string' && r.url) {
|
|
8
|
+
return typeof r.alt === 'string' && r.alt ? { url: r.url, alt: r.alt } : { url: r.url };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
};
|
|
13
|
+
export const toMetaTags = (metadata, options = {}) => {
|
|
14
|
+
const title = str(metadata.title);
|
|
15
|
+
const description = str(metadata.description);
|
|
16
|
+
const ogTitle = str(metadata.ogTitle) ?? title;
|
|
17
|
+
const ogDescription = str(metadata.ogDescription) ?? description;
|
|
18
|
+
const image = imageUrl(metadata.ogImage);
|
|
19
|
+
const canonical = str(metadata.canonical);
|
|
20
|
+
const card = str(metadata.twitterCard);
|
|
21
|
+
const out = {};
|
|
22
|
+
if (title)
|
|
23
|
+
out.title = title;
|
|
24
|
+
if (options.siteName)
|
|
25
|
+
out.titleTemplate = options.titleTemplate ?? `%s · ${options.siteName}`;
|
|
26
|
+
if (description)
|
|
27
|
+
out.description = description;
|
|
28
|
+
if (canonical)
|
|
29
|
+
out.canonical = canonical;
|
|
30
|
+
if (metadata.noindex === true)
|
|
31
|
+
out.robots = 'noindex, nofollow';
|
|
32
|
+
const og = {};
|
|
33
|
+
if (ogTitle)
|
|
34
|
+
og.title = ogTitle;
|
|
35
|
+
if (ogDescription)
|
|
36
|
+
og.description = ogDescription;
|
|
37
|
+
if (canonical ?? options.url)
|
|
38
|
+
og.url = canonical ?? options.url;
|
|
39
|
+
if (options.siteName)
|
|
40
|
+
og.siteName = options.siteName;
|
|
41
|
+
if (image)
|
|
42
|
+
og.images = [image];
|
|
43
|
+
if (Object.keys(og).length > 0)
|
|
44
|
+
out.openGraph = og;
|
|
45
|
+
const tw = {};
|
|
46
|
+
if (card === 'summary' || card === 'summary_large_image')
|
|
47
|
+
tw.cardType = card;
|
|
48
|
+
else if (image)
|
|
49
|
+
tw.cardType = 'summary_large_image';
|
|
50
|
+
if (ogTitle)
|
|
51
|
+
tw.title = ogTitle;
|
|
52
|
+
if (ogDescription)
|
|
53
|
+
tw.description = ogDescription;
|
|
54
|
+
if (image)
|
|
55
|
+
tw.image = image.url;
|
|
56
|
+
if (Object.keys(tw).length > 0)
|
|
57
|
+
out.twitter = tw;
|
|
58
|
+
return out;
|
|
59
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `CmsCollection`: services, rooms, programmes, tours, projects, case
|
|
3
|
+
* studies. One fixed core plus a `details` key-value list for kind-specific
|
|
4
|
+
* facts. Lists only: an item's `link` covers "read more".
|
|
5
|
+
*/
|
|
6
|
+
import type { Tree } from '../path.js';
|
|
7
|
+
import { type ListValue } from './form.js';
|
|
8
|
+
import { type CmsImageValue } from './image.js';
|
|
9
|
+
import { type CmsLinkValue } from './link.js';
|
|
10
|
+
export type CollectionDetail = {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
value: string;
|
|
14
|
+
};
|
|
15
|
+
export type CollectionPrice = {
|
|
16
|
+
amount: number | null;
|
|
17
|
+
/** ISO 4217, e.g. `EUR`. */
|
|
18
|
+
currency: string;
|
|
19
|
+
/** `night`, `person`, `hour`… shown after the amount. */
|
|
20
|
+
period: string;
|
|
21
|
+
/** `from`, shown before the amount. */
|
|
22
|
+
prefix: string;
|
|
23
|
+
};
|
|
24
|
+
export type CollectionItem = {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
summary: string;
|
|
28
|
+
body: string;
|
|
29
|
+
image: CmsImageValue | null;
|
|
30
|
+
gallery: CmsImageValue[];
|
|
31
|
+
price: CollectionPrice;
|
|
32
|
+
duration: string;
|
|
33
|
+
link: CmsLinkValue | null;
|
|
34
|
+
tags: string[];
|
|
35
|
+
features: string[];
|
|
36
|
+
featured: boolean;
|
|
37
|
+
hidden: boolean;
|
|
38
|
+
details: CollectionDetail[];
|
|
39
|
+
};
|
|
40
|
+
export type CmsCollectionValue = ListValue<CollectionItem>;
|
|
41
|
+
export type CollectionItemView = CollectionItem & {
|
|
42
|
+
href: string | null;
|
|
43
|
+
};
|
|
44
|
+
export declare const cmsCollection: import("./form.js").FormShape<CmsCollectionValue>;
|
|
45
|
+
export type CollectionSlice = {
|
|
46
|
+
/** At most this many items. */
|
|
47
|
+
limit?: number;
|
|
48
|
+
/** Only featured items. */
|
|
49
|
+
featured?: boolean;
|
|
50
|
+
/** Only items carrying this tag. */
|
|
51
|
+
tag?: string;
|
|
52
|
+
};
|
|
53
|
+
/** Visible items (not hidden, with a title), optionally sliced. */
|
|
54
|
+
export declare const collectionView: (value: CmsCollectionValue, slice?: CollectionSlice) => CollectionItemView[];
|
|
55
|
+
/** Every tag in use, in first-seen order. */
|
|
56
|
+
export declare const collectionTags: (value: CmsCollectionValue) => string[];
|
|
57
|
+
/** `from €120 / night`, or empty when there is no amount. */
|
|
58
|
+
export declare const formatCollectionPrice: (price: CollectionPrice, locale: string) => string;
|
|
59
|
+
/** A schema.org `ItemList` of the visible items. */
|
|
60
|
+
export declare const toItemList: (value: CmsCollectionValue, baseUrl?: string) => Tree;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { asBoolean, asItems, asNumber, asString, asStrings, isPlainObject } from '../structured.js';
|
|
2
|
+
import { defineForm } from './form.js';
|
|
3
|
+
import { asImage, asImages } from './image.js';
|
|
4
|
+
import { asLink, linkHref } from './link.js';
|
|
5
|
+
export const cmsCollection = defineForm({
|
|
6
|
+
component: 'CmsCollection',
|
|
7
|
+
label: 'Collection',
|
|
8
|
+
version: 1,
|
|
9
|
+
translatable: [],
|
|
10
|
+
items: {
|
|
11
|
+
key: 'items',
|
|
12
|
+
translatable: [
|
|
13
|
+
'title',
|
|
14
|
+
'summary',
|
|
15
|
+
'body',
|
|
16
|
+
'duration',
|
|
17
|
+
'tags',
|
|
18
|
+
'features',
|
|
19
|
+
'price.period',
|
|
20
|
+
'price.prefix'
|
|
21
|
+
],
|
|
22
|
+
items: { key: 'details', translatable: ['label', 'value'] }
|
|
23
|
+
},
|
|
24
|
+
normalize: (raw) => {
|
|
25
|
+
const items = Array.isArray(raw) ? raw : isPlainObject(raw) ? raw.items : [];
|
|
26
|
+
return {
|
|
27
|
+
v: 1,
|
|
28
|
+
items: asItems(items, (r) => {
|
|
29
|
+
const p = isPlainObject(r.price) ? r.price : {};
|
|
30
|
+
return {
|
|
31
|
+
title: asString(r.title),
|
|
32
|
+
summary: asString(r.summary),
|
|
33
|
+
body: asString(r.body),
|
|
34
|
+
image: asImage(r.image),
|
|
35
|
+
gallery: asImages(r.gallery),
|
|
36
|
+
price: {
|
|
37
|
+
amount: asNumber(p.amount),
|
|
38
|
+
currency: asString(p.currency),
|
|
39
|
+
period: asString(p.period),
|
|
40
|
+
prefix: asString(p.prefix)
|
|
41
|
+
},
|
|
42
|
+
duration: asString(r.duration),
|
|
43
|
+
link: asLink(r.link),
|
|
44
|
+
tags: asStrings(r.tags).filter((t) => t !== ''),
|
|
45
|
+
features: asStrings(r.features).filter((f) => f !== ''),
|
|
46
|
+
featured: asBoolean(r.featured),
|
|
47
|
+
hidden: asBoolean(r.hidden),
|
|
48
|
+
details: asItems(r.details, (d) => ({
|
|
49
|
+
label: asString(d.label),
|
|
50
|
+
value: asString(d.value)
|
|
51
|
+
}))
|
|
52
|
+
};
|
|
53
|
+
})
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
empty: () => ({ v: 1, items: [] }),
|
|
57
|
+
fields: [
|
|
58
|
+
{
|
|
59
|
+
key: 'items',
|
|
60
|
+
label: 'Items',
|
|
61
|
+
type: 'list',
|
|
62
|
+
itemLabel: 'item',
|
|
63
|
+
titleKey: 'title',
|
|
64
|
+
blank: () => ({
|
|
65
|
+
title: '',
|
|
66
|
+
summary: '',
|
|
67
|
+
body: '',
|
|
68
|
+
image: null,
|
|
69
|
+
gallery: [],
|
|
70
|
+
price: { amount: null, currency: '', period: '', prefix: '' },
|
|
71
|
+
duration: '',
|
|
72
|
+
link: null,
|
|
73
|
+
tags: [],
|
|
74
|
+
features: [],
|
|
75
|
+
featured: false,
|
|
76
|
+
hidden: false,
|
|
77
|
+
details: []
|
|
78
|
+
}),
|
|
79
|
+
fields: [
|
|
80
|
+
{ key: 'title', label: 'Title', type: 'text', group: 'Details' },
|
|
81
|
+
{ key: 'summary', label: 'Summary', type: 'long-string', group: 'Details' },
|
|
82
|
+
{ key: 'body', label: 'Body', type: 'html', group: 'Details' },
|
|
83
|
+
{
|
|
84
|
+
key: 'duration',
|
|
85
|
+
label: 'Duration',
|
|
86
|
+
type: 'text',
|
|
87
|
+
half: true,
|
|
88
|
+
placeholder: '3 hours',
|
|
89
|
+
group: 'Details'
|
|
90
|
+
},
|
|
91
|
+
{ key: 'link', label: 'Link', type: 'link', group: 'Details' },
|
|
92
|
+
{
|
|
93
|
+
key: 'tags',
|
|
94
|
+
label: 'Tags',
|
|
95
|
+
type: 'strings',
|
|
96
|
+
placeholder: 'One per line',
|
|
97
|
+
group: 'Details'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: 'features',
|
|
101
|
+
label: 'Features',
|
|
102
|
+
type: 'strings',
|
|
103
|
+
placeholder: 'One per line',
|
|
104
|
+
group: 'Details'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: 'details',
|
|
108
|
+
label: 'Details',
|
|
109
|
+
type: 'list',
|
|
110
|
+
itemLabel: 'detail',
|
|
111
|
+
titleKey: 'label',
|
|
112
|
+
group: 'Details',
|
|
113
|
+
blank: () => ({ label: '', value: '' }),
|
|
114
|
+
fields: [
|
|
115
|
+
{ key: 'label', label: 'Label', type: 'text', half: true, placeholder: 'Sleeps' },
|
|
116
|
+
{ key: 'value', label: 'Value', type: 'text', placeholder: '4' }
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{ key: 'featured', label: 'Featured', type: 'boolean', group: 'Details' },
|
|
120
|
+
{ key: 'hidden', label: 'Hidden', type: 'boolean', group: 'Details' },
|
|
121
|
+
{ key: 'image', label: 'Image', type: 'image', group: 'Media' },
|
|
122
|
+
{ key: 'gallery', label: 'Gallery', type: 'images', group: 'Media' },
|
|
123
|
+
{
|
|
124
|
+
key: 'price.prefix',
|
|
125
|
+
label: 'Prefix',
|
|
126
|
+
type: 'text',
|
|
127
|
+
half: true,
|
|
128
|
+
placeholder: 'from',
|
|
129
|
+
group: 'Pricing'
|
|
130
|
+
},
|
|
131
|
+
{ key: 'price.amount', label: 'Amount', type: 'number', group: 'Pricing' },
|
|
132
|
+
{
|
|
133
|
+
key: 'price.currency',
|
|
134
|
+
label: 'Currency',
|
|
135
|
+
type: 'text',
|
|
136
|
+
half: true,
|
|
137
|
+
placeholder: 'EUR',
|
|
138
|
+
group: 'Pricing'
|
|
139
|
+
},
|
|
140
|
+
{ key: 'price.period', label: 'Per', type: 'text', placeholder: 'night', group: 'Pricing' }
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
});
|
|
145
|
+
/** Visible items (not hidden, with a title), optionally sliced. */
|
|
146
|
+
export const collectionView = (value, slice = {}) => {
|
|
147
|
+
let items = value.items.filter((i) => !i.hidden && i.title !== '');
|
|
148
|
+
if (slice.featured)
|
|
149
|
+
items = items.filter((i) => i.featured);
|
|
150
|
+
if (slice.tag)
|
|
151
|
+
items = items.filter((i) => i.tags.includes(slice.tag));
|
|
152
|
+
if (slice.limit !== undefined && slice.limit >= 0)
|
|
153
|
+
items = items.slice(0, slice.limit);
|
|
154
|
+
return items.map((i) => ({ ...i, href: i.link ? linkHref(i.link) || null : null }));
|
|
155
|
+
};
|
|
156
|
+
/** Every tag in use, in first-seen order. */
|
|
157
|
+
export const collectionTags = (value) => {
|
|
158
|
+
const out = [];
|
|
159
|
+
for (const item of value.items)
|
|
160
|
+
for (const t of item.tags)
|
|
161
|
+
if (!out.includes(t))
|
|
162
|
+
out.push(t);
|
|
163
|
+
return out;
|
|
164
|
+
};
|
|
165
|
+
/** `from €120 / night`, or empty when there is no amount. */
|
|
166
|
+
export const formatCollectionPrice = (price, locale) => {
|
|
167
|
+
if (price.amount === null)
|
|
168
|
+
return '';
|
|
169
|
+
let amount;
|
|
170
|
+
try {
|
|
171
|
+
amount = price.currency
|
|
172
|
+
? new Intl.NumberFormat(locale, {
|
|
173
|
+
style: 'currency',
|
|
174
|
+
currency: price.currency,
|
|
175
|
+
maximumFractionDigits: Number.isInteger(price.amount) ? 0 : 2
|
|
176
|
+
}).format(price.amount)
|
|
177
|
+
: new Intl.NumberFormat(locale).format(price.amount);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
amount = `${price.currency} ${price.amount}`.trim();
|
|
181
|
+
}
|
|
182
|
+
return [price.prefix, amount, price.period ? `/ ${price.period}` : ''].filter(Boolean).join(' ');
|
|
183
|
+
};
|
|
184
|
+
/** A schema.org `ItemList` of the visible items. */
|
|
185
|
+
export const toItemList = (value, baseUrl = '') => ({
|
|
186
|
+
'@context': 'https://schema.org',
|
|
187
|
+
'@type': 'ItemList',
|
|
188
|
+
itemListElement: collectionView(value).map((i, n) => {
|
|
189
|
+
const item = { '@type': 'ListItem', position: n + 1, name: i.title };
|
|
190
|
+
if (i.href)
|
|
191
|
+
item.url = baseUrl + i.href;
|
|
192
|
+
return item;
|
|
193
|
+
})
|
|
194
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `CmsContact`: where and how to reach the business. Labels translate;
|
|
3
|
+
* addresses and numbers do not. `contactView` adds the hrefs and the
|
|
4
|
+
* formatted address a template renders.
|
|
5
|
+
*/
|
|
6
|
+
import type { Tree } from '../path.js';
|
|
7
|
+
export type ContactPhone = {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
number: string;
|
|
11
|
+
};
|
|
12
|
+
export type ContactEmail = {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
address: string;
|
|
16
|
+
};
|
|
17
|
+
export type ContactAddress = {
|
|
18
|
+
lines: string[];
|
|
19
|
+
locality: string;
|
|
20
|
+
region: string;
|
|
21
|
+
postcode: string;
|
|
22
|
+
country: string;
|
|
23
|
+
};
|
|
24
|
+
export type ContactLabels = {
|
|
25
|
+
address: string;
|
|
26
|
+
phone: string;
|
|
27
|
+
email: string;
|
|
28
|
+
whatsapp: string;
|
|
29
|
+
directions: string;
|
|
30
|
+
};
|
|
31
|
+
export type CmsContactValue = {
|
|
32
|
+
v: 1;
|
|
33
|
+
name: string;
|
|
34
|
+
address: ContactAddress;
|
|
35
|
+
phones: ContactPhone[];
|
|
36
|
+
emails: ContactEmail[];
|
|
37
|
+
whatsapp: string;
|
|
38
|
+
directionsUrl: string;
|
|
39
|
+
geo: {
|
|
40
|
+
lat: number;
|
|
41
|
+
lng: number;
|
|
42
|
+
} | null;
|
|
43
|
+
labels: ContactLabels;
|
|
44
|
+
};
|
|
45
|
+
export declare const DEFAULT_CONTACT_LABELS: ContactLabels;
|
|
46
|
+
export declare const cmsContact: import("./form.js").FormShape<CmsContactValue>;
|
|
47
|
+
export declare const telHref: (number: string) => string;
|
|
48
|
+
export declare const whatsappHref: (number: string) => string;
|
|
49
|
+
/** Address lines in the country's conventional order, empty parts dropped. */
|
|
50
|
+
export declare const formatAddress: (address: ContactAddress) => string[];
|
|
51
|
+
export type ContactView = Omit<CmsContactValue, 'phones' | 'emails'> & {
|
|
52
|
+
phones: Array<ContactPhone & {
|
|
53
|
+
href: string;
|
|
54
|
+
}>;
|
|
55
|
+
emails: Array<ContactEmail & {
|
|
56
|
+
href: string;
|
|
57
|
+
}>;
|
|
58
|
+
/** First phone's `tel:` href, or `null`. */
|
|
59
|
+
telHref: string | null;
|
|
60
|
+
/** First email's `mailto:`, or `null`. */
|
|
61
|
+
mailto: string | null;
|
|
62
|
+
whatsappHref: string | null;
|
|
63
|
+
/** `directionsUrl`, else a Google Maps link from `geo` or the address. */
|
|
64
|
+
mapsHref: string | null;
|
|
65
|
+
formattedAddress: string[];
|
|
66
|
+
hasAddress: boolean;
|
|
67
|
+
};
|
|
68
|
+
export declare const contactView: (value: CmsContactValue) => ContactView;
|
|
69
|
+
/** A schema.org `PostalAddress` node, or `null` without an address. */
|
|
70
|
+
export declare const toPostalAddress: (value: CmsContactValue) => Tree | null;
|