@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
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six list presets: fixed item shapes behind exported components, so a
|
|
3
|
+
* template never invents a schema of its own. A new shape is a new preset.
|
|
4
|
+
*/
|
|
5
|
+
import { asItems, asString, isPlainObject } from '../structured.js';
|
|
6
|
+
import { defineForm } from './form.js';
|
|
7
|
+
import { asImage } from './image.js';
|
|
8
|
+
import { asLink, linkHref } from './link.js';
|
|
9
|
+
const definePreset = (o) => defineForm({
|
|
10
|
+
component: o.component,
|
|
11
|
+
label: o.label,
|
|
12
|
+
version: 1,
|
|
13
|
+
translatable: [],
|
|
14
|
+
items: { key: 'items', translatable: o.translatable },
|
|
15
|
+
normalize: (raw) => {
|
|
16
|
+
const items = Array.isArray(raw) ? raw : isPlainObject(raw) ? raw.items : [];
|
|
17
|
+
return { v: 1, items: asItems(items, o.normalizeItem) };
|
|
18
|
+
},
|
|
19
|
+
empty: () => ({ v: 1, items: [] }),
|
|
20
|
+
fields: [
|
|
21
|
+
{
|
|
22
|
+
key: 'items',
|
|
23
|
+
label: o.label,
|
|
24
|
+
type: 'list',
|
|
25
|
+
itemLabel: o.itemLabel,
|
|
26
|
+
titleKey: o.titleKey,
|
|
27
|
+
blank: o.blank,
|
|
28
|
+
fields: o.fields
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
export const cmsStats = definePreset({
|
|
33
|
+
component: 'CmsStats',
|
|
34
|
+
label: 'Stats',
|
|
35
|
+
itemLabel: 'stat',
|
|
36
|
+
titleKey: 'label',
|
|
37
|
+
translatable: ['value', 'label', 'note'],
|
|
38
|
+
blank: () => ({ value: '', label: '', note: '' }),
|
|
39
|
+
fields: [
|
|
40
|
+
{ key: 'value', label: 'Value', type: 'text', half: true, placeholder: '12k' },
|
|
41
|
+
{ key: 'label', label: 'Label', type: 'text', placeholder: 'Guests a year' },
|
|
42
|
+
{ key: 'note', label: 'Note', type: 'text' }
|
|
43
|
+
],
|
|
44
|
+
normalizeItem: (r) => ({
|
|
45
|
+
value: asString(r.value),
|
|
46
|
+
label: asString(r.label),
|
|
47
|
+
note: asString(r.note)
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
export const cmsSteps = definePreset({
|
|
51
|
+
component: 'CmsSteps',
|
|
52
|
+
label: 'Steps',
|
|
53
|
+
itemLabel: 'step',
|
|
54
|
+
titleKey: 'title',
|
|
55
|
+
translatable: ['title', 'body'],
|
|
56
|
+
blank: () => ({ title: '', body: '', image: null }),
|
|
57
|
+
fields: [
|
|
58
|
+
{ key: 'title', label: 'Title', type: 'text' },
|
|
59
|
+
{ key: 'body', label: 'Body', type: 'html' },
|
|
60
|
+
{ key: 'image', label: 'Image', type: 'image' }
|
|
61
|
+
],
|
|
62
|
+
normalizeItem: (r) => ({
|
|
63
|
+
title: asString(r.title),
|
|
64
|
+
body: asString(r.body),
|
|
65
|
+
image: asImage(r.image)
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
export const cmsTimeline = definePreset({
|
|
69
|
+
component: 'CmsTimeline',
|
|
70
|
+
label: 'Timeline',
|
|
71
|
+
itemLabel: 'entry',
|
|
72
|
+
titleKey: 'title',
|
|
73
|
+
translatable: ['date', 'title', 'body'],
|
|
74
|
+
blank: () => ({ date: '', title: '', body: '', image: null }),
|
|
75
|
+
fields: [
|
|
76
|
+
{ key: 'date', label: 'Date', type: 'text', half: true, placeholder: '2019' },
|
|
77
|
+
{ key: 'title', label: 'Title', type: 'text' },
|
|
78
|
+
{ key: 'body', label: 'Body', type: 'html' },
|
|
79
|
+
{ key: 'image', label: 'Image', type: 'image' }
|
|
80
|
+
],
|
|
81
|
+
normalizeItem: (r) => ({
|
|
82
|
+
date: asString(r.date),
|
|
83
|
+
title: asString(r.title),
|
|
84
|
+
body: asString(r.body),
|
|
85
|
+
image: asImage(r.image)
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
export const cmsGallery = definePreset({
|
|
89
|
+
component: 'CmsGallery',
|
|
90
|
+
label: 'Gallery',
|
|
91
|
+
itemLabel: 'photo',
|
|
92
|
+
titleKey: 'caption',
|
|
93
|
+
translatable: ['caption'],
|
|
94
|
+
blank: () => ({ image: null, caption: '' }),
|
|
95
|
+
fields: [
|
|
96
|
+
{ key: 'image', label: 'Photo', type: 'image' },
|
|
97
|
+
{ key: 'caption', label: 'Caption', type: 'text' }
|
|
98
|
+
],
|
|
99
|
+
normalizeItem: (r) => ({ image: asImage(r.image ?? r.src), caption: asString(r.caption) })
|
|
100
|
+
});
|
|
101
|
+
export const cmsLogos = definePreset({
|
|
102
|
+
component: 'CmsLogos',
|
|
103
|
+
label: 'Logos',
|
|
104
|
+
itemLabel: 'logo',
|
|
105
|
+
titleKey: 'name',
|
|
106
|
+
translatable: [],
|
|
107
|
+
blank: () => ({ image: null, name: '', link: null }),
|
|
108
|
+
fields: [
|
|
109
|
+
{ key: 'name', label: 'Name', type: 'text' },
|
|
110
|
+
{ key: 'image', label: 'Logo', type: 'image' },
|
|
111
|
+
{ key: 'link', label: 'Link', type: 'link' }
|
|
112
|
+
],
|
|
113
|
+
normalizeItem: (r) => ({ image: asImage(r.image), name: asString(r.name), link: asLink(r.link) })
|
|
114
|
+
});
|
|
115
|
+
export const logosView = (value) => value.items
|
|
116
|
+
.filter((l) => l.image !== null)
|
|
117
|
+
.map((l) => ({ ...l, href: l.link ? linkHref(l.link) || null : null }));
|
|
118
|
+
export const cmsSchedule = definePreset({
|
|
119
|
+
component: 'CmsSchedule',
|
|
120
|
+
label: 'Schedule',
|
|
121
|
+
itemLabel: 'session',
|
|
122
|
+
titleKey: 'title',
|
|
123
|
+
translatable: ['day', 'time', 'title', 'body', 'location'],
|
|
124
|
+
blank: () => ({ day: '', time: '', title: '', body: '', location: '' }),
|
|
125
|
+
fields: [
|
|
126
|
+
{ key: 'day', label: 'Day', type: 'text', half: true, placeholder: 'Monday' },
|
|
127
|
+
{ key: 'time', label: 'Time', type: 'text', placeholder: '10:00 – 11:00' },
|
|
128
|
+
{ key: 'title', label: 'Title', type: 'text' },
|
|
129
|
+
{ key: 'location', label: 'Location', type: 'text' },
|
|
130
|
+
{ key: 'body', label: 'Details', type: 'html' }
|
|
131
|
+
],
|
|
132
|
+
normalizeItem: (r) => ({
|
|
133
|
+
day: asString(r.day),
|
|
134
|
+
time: asString(r.time),
|
|
135
|
+
title: asString(r.title),
|
|
136
|
+
body: asString(r.body),
|
|
137
|
+
location: asString(r.location)
|
|
138
|
+
})
|
|
139
|
+
});
|
|
140
|
+
/** Sessions grouped by `day`, in first-seen order. */
|
|
141
|
+
export const scheduleByDay = (value) => {
|
|
142
|
+
const out = [];
|
|
143
|
+
for (const item of value.items) {
|
|
144
|
+
if (item.title === '')
|
|
145
|
+
continue;
|
|
146
|
+
let group = out.find((g) => g.day === item.day);
|
|
147
|
+
if (!group)
|
|
148
|
+
out.push((group = { day: item.day, items: [] }));
|
|
149
|
+
group.items.push(item);
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type CmsLinkValue } from './link.js';
|
|
2
|
+
export type PricingFeature = {
|
|
3
|
+
id: string;
|
|
4
|
+
text: string;
|
|
5
|
+
included: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type PricingTier = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
price: {
|
|
11
|
+
amount: number | null;
|
|
12
|
+
/** A quote, not a number: the template shows `labels.custom`. */
|
|
13
|
+
custom: boolean;
|
|
14
|
+
/** `month`, `person`… shown after the amount. */
|
|
15
|
+
period: string;
|
|
16
|
+
};
|
|
17
|
+
description: string;
|
|
18
|
+
features: PricingFeature[];
|
|
19
|
+
cta: CmsLinkValue | null;
|
|
20
|
+
highlighted: boolean;
|
|
21
|
+
badge: string;
|
|
22
|
+
};
|
|
23
|
+
export type CmsPricingValue = {
|
|
24
|
+
v: 1;
|
|
25
|
+
/** ISO 4217, e.g. `EUR`. */
|
|
26
|
+
currency: string;
|
|
27
|
+
labels: {
|
|
28
|
+
custom: string;
|
|
29
|
+
};
|
|
30
|
+
items: PricingTier[];
|
|
31
|
+
};
|
|
32
|
+
export type PricingTierView = PricingTier & {
|
|
33
|
+
priceText: string;
|
|
34
|
+
href: string | null;
|
|
35
|
+
};
|
|
36
|
+
export declare const cmsPricing: import("./form.js").FormShape<CmsPricingValue>;
|
|
37
|
+
/** `€29 / month`, the custom label, or empty without an amount. */
|
|
38
|
+
export declare const formatPrice: (tier: PricingTier, value: Pick<CmsPricingValue, "currency" | "labels">, locale: string) => string;
|
|
39
|
+
/** Tiers with a name, each with its formatted price and CTA href. */
|
|
40
|
+
export declare const pricingView: (value: CmsPricingValue, locale: string) => PricingTierView[];
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** `CmsPricing`: tiers with a feature checklist and a call to action. */
|
|
2
|
+
import { asBoolean, asItems, asNumber, asString, isPlainObject } from '../structured.js';
|
|
3
|
+
import { defineForm } from './form.js';
|
|
4
|
+
import { asLink, linkHref } from './link.js';
|
|
5
|
+
export const cmsPricing = defineForm({
|
|
6
|
+
component: 'CmsPricing',
|
|
7
|
+
label: 'Pricing',
|
|
8
|
+
version: 1,
|
|
9
|
+
translatable: ['labels.custom'],
|
|
10
|
+
items: {
|
|
11
|
+
key: 'items',
|
|
12
|
+
translatable: ['name', 'description', 'badge', 'price.period'],
|
|
13
|
+
items: { key: 'features', translatable: ['text'] }
|
|
14
|
+
},
|
|
15
|
+
normalize: (raw) => {
|
|
16
|
+
const r = isPlainObject(raw) ? raw : {};
|
|
17
|
+
const labels = isPlainObject(r.labels) ? r.labels : {};
|
|
18
|
+
return {
|
|
19
|
+
v: 1,
|
|
20
|
+
currency: asString(r.currency),
|
|
21
|
+
labels: { custom: asString(labels.custom, 'Custom') },
|
|
22
|
+
items: asItems(r.items, (t) => {
|
|
23
|
+
const p = isPlainObject(t.price) ? t.price : {};
|
|
24
|
+
return {
|
|
25
|
+
name: asString(t.name),
|
|
26
|
+
price: {
|
|
27
|
+
amount: asNumber(p.amount),
|
|
28
|
+
custom: asBoolean(p.custom, p.amount === 'custom'),
|
|
29
|
+
period: asString(p.period)
|
|
30
|
+
},
|
|
31
|
+
description: asString(t.description),
|
|
32
|
+
features: asItems(t.features, (f) => ({
|
|
33
|
+
text: asString(f.text),
|
|
34
|
+
included: asBoolean(f.included, true)
|
|
35
|
+
})),
|
|
36
|
+
cta: asLink(t.cta),
|
|
37
|
+
highlighted: asBoolean(t.highlighted),
|
|
38
|
+
badge: asString(t.badge)
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
empty: () => ({ v: 1, currency: '', labels: { custom: 'Custom' }, items: [] }),
|
|
44
|
+
fields: [
|
|
45
|
+
{ key: 'currency', label: 'Currency', type: 'text', half: true, placeholder: 'EUR' },
|
|
46
|
+
{ key: 'labels.custom', label: '“Custom” price label', type: 'text' },
|
|
47
|
+
{
|
|
48
|
+
key: 'items',
|
|
49
|
+
label: 'Tiers',
|
|
50
|
+
type: 'list',
|
|
51
|
+
itemLabel: 'tier',
|
|
52
|
+
titleKey: 'name',
|
|
53
|
+
blank: () => ({
|
|
54
|
+
name: '',
|
|
55
|
+
price: { amount: null, custom: false, period: '' },
|
|
56
|
+
description: '',
|
|
57
|
+
features: [],
|
|
58
|
+
cta: null,
|
|
59
|
+
highlighted: false,
|
|
60
|
+
badge: ''
|
|
61
|
+
}),
|
|
62
|
+
fields: [
|
|
63
|
+
{ key: 'name', label: 'Name', type: 'text', half: true },
|
|
64
|
+
{ key: 'badge', label: 'Badge', type: 'text', placeholder: 'Most popular' },
|
|
65
|
+
{ key: 'price.amount', label: 'Price', type: 'number', half: true },
|
|
66
|
+
{ key: 'price.period', label: 'Per', type: 'text', placeholder: 'month' },
|
|
67
|
+
{ key: 'price.custom', label: 'Custom price (quote)', type: 'boolean' },
|
|
68
|
+
{ key: 'description', label: 'Description', type: 'long-string' },
|
|
69
|
+
{
|
|
70
|
+
key: 'features',
|
|
71
|
+
label: 'Features',
|
|
72
|
+
type: 'list',
|
|
73
|
+
itemLabel: 'feature',
|
|
74
|
+
titleKey: 'text',
|
|
75
|
+
blank: () => ({ text: '', included: true }),
|
|
76
|
+
fields: [
|
|
77
|
+
{ key: 'text', label: 'Feature', type: 'text' },
|
|
78
|
+
{ key: 'included', label: 'Included', type: 'boolean' }
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{ key: 'cta', label: 'Call to action', type: 'link' },
|
|
82
|
+
{ key: 'highlighted', label: 'Highlighted', type: 'boolean' }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
/** `€29 / month`, the custom label, or empty without an amount. */
|
|
88
|
+
export const formatPrice = (tier, value, locale) => {
|
|
89
|
+
if (tier.price.custom)
|
|
90
|
+
return value.labels.custom;
|
|
91
|
+
if (tier.price.amount === null)
|
|
92
|
+
return '';
|
|
93
|
+
let amount;
|
|
94
|
+
try {
|
|
95
|
+
amount = value.currency
|
|
96
|
+
? new Intl.NumberFormat(locale, {
|
|
97
|
+
style: 'currency',
|
|
98
|
+
currency: value.currency,
|
|
99
|
+
maximumFractionDigits: Number.isInteger(tier.price.amount) ? 0 : 2
|
|
100
|
+
}).format(tier.price.amount)
|
|
101
|
+
: new Intl.NumberFormat(locale).format(tier.price.amount);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
amount = `${value.currency} ${tier.price.amount}`.trim();
|
|
105
|
+
}
|
|
106
|
+
return tier.price.period ? `${amount} / ${tier.price.period}` : amount;
|
|
107
|
+
};
|
|
108
|
+
/** Tiers with a name, each with its formatted price and CTA href. */
|
|
109
|
+
export const pricingView = (value, locale) => value.items
|
|
110
|
+
.filter((t) => t.name !== '')
|
|
111
|
+
.map((t) => ({
|
|
112
|
+
...t,
|
|
113
|
+
priceText: formatPrice(t, value, locale),
|
|
114
|
+
href: t.cta ? linkHref(t.cta) || null : null
|
|
115
|
+
}));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ListValue } from './form.js';
|
|
2
|
+
export declare const SOCIAL_PLATFORMS: readonly ["instagram", "facebook", "tiktok", "youtube", "linkedin", "x", "threads", "pinterest", "whatsapp", "tripadvisor", "google", "yelp", "other"];
|
|
3
|
+
export type SocialPlatform = (typeof SOCIAL_PLATFORMS)[number];
|
|
4
|
+
export declare const SOCIAL_PLATFORM_NAMES: Readonly<Record<SocialPlatform, string>>;
|
|
5
|
+
export type SocialLink = {
|
|
6
|
+
id: string;
|
|
7
|
+
platform: SocialPlatform;
|
|
8
|
+
url: string;
|
|
9
|
+
label: string;
|
|
10
|
+
};
|
|
11
|
+
export type CmsSocialLinksValue = ListValue<SocialLink>;
|
|
12
|
+
export type SocialLinkView = SocialLink & {
|
|
13
|
+
name: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const cmsSocialLinks: import("./form.js").FormShape<CmsSocialLinksValue>;
|
|
16
|
+
/** Profiles with a URL, each with a display `name` (the label, else the platform). */
|
|
17
|
+
export declare const socialLinksView: (value: CmsSocialLinksValue) => SocialLinkView[];
|
|
18
|
+
/** The `sameAs` list for an Organization or LocalBusiness JSON-LD node. */
|
|
19
|
+
export declare const toSameAs: (value: CmsSocialLinksValue) => string[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `CmsSocialLinks`: an ordered list of profiles. The template supplies the
|
|
3
|
+
* icon for `platform`; `label` defaults to the platform's name.
|
|
4
|
+
*/
|
|
5
|
+
import { asEnum, asItems, asString, isPlainObject } from '../structured.js';
|
|
6
|
+
import { defineForm } from './form.js';
|
|
7
|
+
export const SOCIAL_PLATFORMS = [
|
|
8
|
+
'instagram',
|
|
9
|
+
'facebook',
|
|
10
|
+
'tiktok',
|
|
11
|
+
'youtube',
|
|
12
|
+
'linkedin',
|
|
13
|
+
'x',
|
|
14
|
+
'threads',
|
|
15
|
+
'pinterest',
|
|
16
|
+
'whatsapp',
|
|
17
|
+
'tripadvisor',
|
|
18
|
+
'google',
|
|
19
|
+
'yelp',
|
|
20
|
+
'other'
|
|
21
|
+
];
|
|
22
|
+
export const SOCIAL_PLATFORM_NAMES = {
|
|
23
|
+
instagram: 'Instagram',
|
|
24
|
+
facebook: 'Facebook',
|
|
25
|
+
tiktok: 'TikTok',
|
|
26
|
+
youtube: 'YouTube',
|
|
27
|
+
linkedin: 'LinkedIn',
|
|
28
|
+
x: 'X',
|
|
29
|
+
threads: 'Threads',
|
|
30
|
+
pinterest: 'Pinterest',
|
|
31
|
+
whatsapp: 'WhatsApp',
|
|
32
|
+
tripadvisor: 'Tripadvisor',
|
|
33
|
+
google: 'Google',
|
|
34
|
+
yelp: 'Yelp',
|
|
35
|
+
other: 'Website'
|
|
36
|
+
};
|
|
37
|
+
export const cmsSocialLinks = defineForm({
|
|
38
|
+
component: 'CmsSocialLinks',
|
|
39
|
+
label: 'Social links',
|
|
40
|
+
version: 1,
|
|
41
|
+
translatable: [],
|
|
42
|
+
items: { key: 'items', translatable: ['label'] },
|
|
43
|
+
normalize: (raw) => {
|
|
44
|
+
const items = Array.isArray(raw) ? raw : isPlainObject(raw) ? raw.items : [];
|
|
45
|
+
return {
|
|
46
|
+
v: 1,
|
|
47
|
+
items: asItems(items, (r) => ({
|
|
48
|
+
platform: asEnum(r.platform, SOCIAL_PLATFORMS, 'other'),
|
|
49
|
+
url: asString(r.url),
|
|
50
|
+
label: asString(r.label)
|
|
51
|
+
}))
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
empty: () => ({ v: 1, items: [] }),
|
|
55
|
+
fields: [
|
|
56
|
+
{
|
|
57
|
+
key: 'items',
|
|
58
|
+
label: 'Profiles',
|
|
59
|
+
type: 'list',
|
|
60
|
+
itemLabel: 'profile',
|
|
61
|
+
titleKey: 'platform',
|
|
62
|
+
blank: () => ({ platform: 'instagram', url: '', label: '' }),
|
|
63
|
+
fields: [
|
|
64
|
+
{
|
|
65
|
+
key: 'platform',
|
|
66
|
+
label: 'Platform',
|
|
67
|
+
type: 'enum',
|
|
68
|
+
values: SOCIAL_PLATFORMS,
|
|
69
|
+
names: SOCIAL_PLATFORM_NAMES,
|
|
70
|
+
half: true
|
|
71
|
+
},
|
|
72
|
+
{ key: 'url', label: 'URL', type: 'url', placeholder: 'https://…' },
|
|
73
|
+
{ key: 'label', label: 'Label', type: 'text', placeholder: 'Defaults to the platform name' }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
});
|
|
78
|
+
/** Profiles with a URL, each with a display `name` (the label, else the platform). */
|
|
79
|
+
export const socialLinksView = (value) => value.items
|
|
80
|
+
.filter((i) => i.url !== '')
|
|
81
|
+
.map((i) => ({ ...i, name: i.label || SOCIAL_PLATFORM_NAMES[i.platform] }));
|
|
82
|
+
/** The `sameAs` list for an Organization or LocalBusiness JSON-LD node. */
|
|
83
|
+
export const toSameAs = (value) => value.items.map((i) => i.url).filter((u) => u !== '');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ListValue } from './form.js';
|
|
2
|
+
import { type CmsImageValue } from './image.js';
|
|
3
|
+
import { type SocialPlatform } from './social-links.js';
|
|
4
|
+
export type TeamLink = {
|
|
5
|
+
id: string;
|
|
6
|
+
platform: SocialPlatform;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
export type TeamMember = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
role: string;
|
|
13
|
+
bio: string;
|
|
14
|
+
photo: CmsImageValue | null;
|
|
15
|
+
email: string;
|
|
16
|
+
phone: string;
|
|
17
|
+
links: TeamLink[];
|
|
18
|
+
};
|
|
19
|
+
export type CmsTeamValue = ListValue<TeamMember>;
|
|
20
|
+
export declare const cmsTeam: import("./form.js").FormShape<CmsTeamValue>;
|
|
21
|
+
/** Members with a name. */
|
|
22
|
+
export declare const teamView: (value: CmsTeamValue) => TeamMember[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** `CmsTeam`: people with a portrait, a role and a rich bio. */
|
|
2
|
+
import { asEnum, asItems, asString, isPlainObject } from '../structured.js';
|
|
3
|
+
import { defineForm } from './form.js';
|
|
4
|
+
import { asImage } from './image.js';
|
|
5
|
+
import { SOCIAL_PLATFORMS, SOCIAL_PLATFORM_NAMES } from './social-links.js';
|
|
6
|
+
export const cmsTeam = defineForm({
|
|
7
|
+
component: 'CmsTeam',
|
|
8
|
+
label: 'Team',
|
|
9
|
+
version: 1,
|
|
10
|
+
translatable: [],
|
|
11
|
+
items: { key: 'items', translatable: ['role', 'bio'] },
|
|
12
|
+
normalize: (raw) => {
|
|
13
|
+
const items = Array.isArray(raw) ? raw : isPlainObject(raw) ? raw.items : [];
|
|
14
|
+
return {
|
|
15
|
+
v: 1,
|
|
16
|
+
items: asItems(items, (r) => ({
|
|
17
|
+
name: asString(r.name),
|
|
18
|
+
role: asString(r.role),
|
|
19
|
+
bio: asString(r.bio),
|
|
20
|
+
photo: asImage(r.photo),
|
|
21
|
+
email: asString(r.email),
|
|
22
|
+
phone: asString(r.phone),
|
|
23
|
+
links: asItems(r.links, (l) => ({
|
|
24
|
+
platform: asEnum(l.platform, SOCIAL_PLATFORMS, 'other'),
|
|
25
|
+
url: asString(l.url)
|
|
26
|
+
}))
|
|
27
|
+
}))
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
empty: () => ({ v: 1, items: [] }),
|
|
31
|
+
fields: [
|
|
32
|
+
{
|
|
33
|
+
key: 'items',
|
|
34
|
+
label: 'People',
|
|
35
|
+
type: 'list',
|
|
36
|
+
itemLabel: 'person',
|
|
37
|
+
titleKey: 'name',
|
|
38
|
+
blank: () => ({ name: '', role: '', bio: '', photo: null, email: '', phone: '', links: [] }),
|
|
39
|
+
fields: [
|
|
40
|
+
{ key: 'name', label: 'Name', type: 'text', half: true },
|
|
41
|
+
{ key: 'role', label: 'Role', type: 'text' },
|
|
42
|
+
{ key: 'photo', label: 'Portrait', type: 'image' },
|
|
43
|
+
{ key: 'bio', label: 'Bio', type: 'html' },
|
|
44
|
+
{ key: 'email', label: 'Email', type: 'text', half: true },
|
|
45
|
+
{ key: 'phone', label: 'Phone', type: 'text' },
|
|
46
|
+
{
|
|
47
|
+
key: 'links',
|
|
48
|
+
label: 'Profiles',
|
|
49
|
+
type: 'list',
|
|
50
|
+
itemLabel: 'profile',
|
|
51
|
+
titleKey: 'platform',
|
|
52
|
+
blank: () => ({ platform: 'linkedin', url: '' }),
|
|
53
|
+
fields: [
|
|
54
|
+
{
|
|
55
|
+
key: 'platform',
|
|
56
|
+
label: 'Platform',
|
|
57
|
+
type: 'enum',
|
|
58
|
+
values: SOCIAL_PLATFORMS,
|
|
59
|
+
names: SOCIAL_PLATFORM_NAMES,
|
|
60
|
+
half: true
|
|
61
|
+
},
|
|
62
|
+
{ key: 'url', label: 'URL', type: 'url' }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
/** Members with a name. */
|
|
70
|
+
export const teamView = (value) => value.items.filter((m) => m.name !== '');
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ListValue } from './form.js';
|
|
2
|
+
import { type CmsImageValue } from './image.js';
|
|
3
|
+
export type Testimonial = {
|
|
4
|
+
id: string;
|
|
5
|
+
quote: string;
|
|
6
|
+
author: string;
|
|
7
|
+
role: string;
|
|
8
|
+
company: string;
|
|
9
|
+
photo: CmsImageValue | null;
|
|
10
|
+
/** 1 to 5, or `null` when not rated. */
|
|
11
|
+
rating: number | null;
|
|
12
|
+
source: {
|
|
13
|
+
label: string;
|
|
14
|
+
url: string;
|
|
15
|
+
};
|
|
16
|
+
/** ISO date (`YYYY-MM-DD`), or empty. */
|
|
17
|
+
date: string;
|
|
18
|
+
};
|
|
19
|
+
export type CmsTestimonialsValue = ListValue<Testimonial>;
|
|
20
|
+
export declare const cmsTestimonials: import("./form.js").FormShape<CmsTestimonialsValue>;
|
|
21
|
+
/** Testimonials that have a quote. */
|
|
22
|
+
export declare const testimonialsView: (value: CmsTestimonialsValue) => Testimonial[];
|
|
23
|
+
/** A schema.org `Review` node. `itemReviewed` is the business or product node. */
|
|
24
|
+
export declare const toReview: (item: Testimonial, itemReviewed?: Record<string, unknown>) => Record<string, unknown>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** `CmsTestimonials`: quotes with an author, a rating and a source, plus Review JSON-LD. */
|
|
2
|
+
import { asItems, asNumber, asString, isPlainObject } from '../structured.js';
|
|
3
|
+
import { defineForm } from './form.js';
|
|
4
|
+
import { asImage } from './image.js';
|
|
5
|
+
const asRating = (v) => {
|
|
6
|
+
const n = asNumber(typeof v === 'string' ? Number(v) : v);
|
|
7
|
+
return n !== null && n >= 1 && n <= 5 ? Math.round(n) : null;
|
|
8
|
+
};
|
|
9
|
+
export const cmsTestimonials = defineForm({
|
|
10
|
+
component: 'CmsTestimonials',
|
|
11
|
+
label: 'Testimonials',
|
|
12
|
+
version: 1,
|
|
13
|
+
translatable: [],
|
|
14
|
+
items: { key: 'items', translatable: ['quote', 'role'] },
|
|
15
|
+
normalize: (raw) => {
|
|
16
|
+
const items = Array.isArray(raw) ? raw : isPlainObject(raw) ? raw.items : [];
|
|
17
|
+
return {
|
|
18
|
+
v: 1,
|
|
19
|
+
items: asItems(items, (r) => {
|
|
20
|
+
const source = isPlainObject(r.source) ? r.source : {};
|
|
21
|
+
return {
|
|
22
|
+
quote: asString(r.quote),
|
|
23
|
+
author: asString(r.author),
|
|
24
|
+
role: asString(r.role),
|
|
25
|
+
company: asString(r.company),
|
|
26
|
+
photo: asImage(r.photo),
|
|
27
|
+
rating: asRating(r.rating),
|
|
28
|
+
source: { label: asString(source.label), url: asString(source.url) },
|
|
29
|
+
date: asString(r.date)
|
|
30
|
+
};
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
empty: () => ({ v: 1, items: [] }),
|
|
35
|
+
fields: [
|
|
36
|
+
{
|
|
37
|
+
key: 'items',
|
|
38
|
+
label: 'Quotes',
|
|
39
|
+
type: 'list',
|
|
40
|
+
itemLabel: 'testimonial',
|
|
41
|
+
titleKey: 'author',
|
|
42
|
+
blank: () => ({
|
|
43
|
+
quote: '',
|
|
44
|
+
author: '',
|
|
45
|
+
role: '',
|
|
46
|
+
company: '',
|
|
47
|
+
photo: null,
|
|
48
|
+
rating: null,
|
|
49
|
+
source: { label: '', url: '' },
|
|
50
|
+
date: ''
|
|
51
|
+
}),
|
|
52
|
+
fields: [
|
|
53
|
+
{ key: 'quote', label: 'Quote', type: 'html' },
|
|
54
|
+
{ key: 'author', label: 'Author', type: 'text', half: true },
|
|
55
|
+
{ key: 'role', label: 'Role', type: 'text' },
|
|
56
|
+
{ key: 'company', label: 'Company', type: 'text', half: true },
|
|
57
|
+
{ key: 'date', label: 'Date', type: 'date' },
|
|
58
|
+
{ key: 'rating', label: 'Rating', type: 'rating' },
|
|
59
|
+
{ key: 'photo', label: 'Photo', type: 'image' },
|
|
60
|
+
{ key: 'source.label', label: 'Source', type: 'text', half: true, placeholder: 'Google' },
|
|
61
|
+
{ key: 'source.url', label: 'Source URL', type: 'url' }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
});
|
|
66
|
+
const stripTags = (html) => html.replace(/<[^>]*>/g, '').trim();
|
|
67
|
+
/** Testimonials that have a quote. */
|
|
68
|
+
export const testimonialsView = (value) => value.items.filter((t) => stripTags(t.quote) !== '');
|
|
69
|
+
/** A schema.org `Review` node. `itemReviewed` is the business or product node. */
|
|
70
|
+
export const toReview = (item, itemReviewed) => {
|
|
71
|
+
const out = {
|
|
72
|
+
'@type': 'Review',
|
|
73
|
+
reviewBody: stripTags(item.quote),
|
|
74
|
+
author: { '@type': 'Person', name: item.author }
|
|
75
|
+
};
|
|
76
|
+
if (item.rating !== null) {
|
|
77
|
+
out.reviewRating = { '@type': 'Rating', ratingValue: item.rating, bestRating: 5 };
|
|
78
|
+
}
|
|
79
|
+
if (item.date)
|
|
80
|
+
out.datePublished = item.date;
|
|
81
|
+
if (item.source.label)
|
|
82
|
+
out.publisher = { '@type': 'Organization', name: item.source.label };
|
|
83
|
+
if (itemReviewed)
|
|
84
|
+
out.itemReviewed = itemReviewed;
|
|
85
|
+
return out;
|
|
86
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which exported component reads which structured shape. Structured
|
|
3
|
+
* components register their schema at module load, so the Locales panel can
|
|
4
|
+
* count translatable strings for every usage the manifest recorded on the
|
|
5
|
+
* current route without importing the components themselves.
|
|
6
|
+
*/
|
|
7
|
+
import type { Structured } from './structured.js';
|
|
8
|
+
import type { Tree } from './path.js';
|
|
9
|
+
export declare const registerStructured: <T extends Tree>(schema: Structured<T>) => Structured<T>;
|
|
10
|
+
export declare const getStructured: (component: string) => Structured<Tree> | undefined;
|