@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,325 @@
|
|
|
1
|
+
import { asBoolean, asItems, asString, isPlainObject } from '../structured.js';
|
|
2
|
+
import { defineForm } from './form.js';
|
|
3
|
+
export const DAY_KEYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
|
|
4
|
+
const TIME = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
5
|
+
const asTime = (v, fallback) => typeof v === 'string' && TIME.test(v) ? v : fallback;
|
|
6
|
+
const asRanges = (v) => {
|
|
7
|
+
if (!Array.isArray(v))
|
|
8
|
+
return [];
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const r of v) {
|
|
11
|
+
if (!isPlainObject(r))
|
|
12
|
+
continue;
|
|
13
|
+
out.push({ open: asTime(r.open, '09:00'), close: asTime(r.close, '17:00') });
|
|
14
|
+
}
|
|
15
|
+
return out.slice(0, 2);
|
|
16
|
+
};
|
|
17
|
+
const asDay = (v) => {
|
|
18
|
+
if (!isPlainObject(v))
|
|
19
|
+
return { closed: true, ranges: [] };
|
|
20
|
+
const ranges = asRanges(v.ranges);
|
|
21
|
+
return { closed: asBoolean(v.closed, ranges.length === 0), ranges };
|
|
22
|
+
};
|
|
23
|
+
export const DEFAULT_HOURS_LABELS = {
|
|
24
|
+
closed: 'Closed',
|
|
25
|
+
today: 'Today',
|
|
26
|
+
openNow: 'Open now',
|
|
27
|
+
closedNow: 'Closed now'
|
|
28
|
+
};
|
|
29
|
+
const emptyDays = () => {
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const k of DAY_KEYS)
|
|
32
|
+
out[k] = { closed: true, ranges: [] };
|
|
33
|
+
return out;
|
|
34
|
+
};
|
|
35
|
+
export const cmsHours = defineForm({
|
|
36
|
+
component: 'CmsHours',
|
|
37
|
+
label: 'Opening hours',
|
|
38
|
+
version: 1,
|
|
39
|
+
translatable: ['note', 'labels.closed', 'labels.today', 'labels.openNow', 'labels.closedNow'],
|
|
40
|
+
items: { key: 'exceptions', translatable: ['label'] },
|
|
41
|
+
normalize: (raw) => {
|
|
42
|
+
const r = isPlainObject(raw) ? raw : {};
|
|
43
|
+
const days = isPlainObject(r.days) ? r.days : {};
|
|
44
|
+
const labels = isPlainObject(r.labels) ? r.labels : {};
|
|
45
|
+
const out = {
|
|
46
|
+
v: 1,
|
|
47
|
+
timezone: asString(r.timezone),
|
|
48
|
+
days: emptyDays(),
|
|
49
|
+
note: asString(r.note),
|
|
50
|
+
exceptions: asItems(r.exceptions, (e) => {
|
|
51
|
+
const ranges = asRanges(e.ranges);
|
|
52
|
+
return {
|
|
53
|
+
date: asString(e.date),
|
|
54
|
+
label: asString(e.label),
|
|
55
|
+
closed: asBoolean(e.closed, ranges.length === 0),
|
|
56
|
+
ranges
|
|
57
|
+
};
|
|
58
|
+
}),
|
|
59
|
+
labels: {
|
|
60
|
+
closed: asString(labels.closed, DEFAULT_HOURS_LABELS.closed),
|
|
61
|
+
today: asString(labels.today, DEFAULT_HOURS_LABELS.today),
|
|
62
|
+
openNow: asString(labels.openNow, DEFAULT_HOURS_LABELS.openNow),
|
|
63
|
+
closedNow: asString(labels.closedNow, DEFAULT_HOURS_LABELS.closedNow)
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
for (const k of DAY_KEYS)
|
|
67
|
+
if (k in days)
|
|
68
|
+
out.days[k] = asDay(days[k]);
|
|
69
|
+
return out;
|
|
70
|
+
},
|
|
71
|
+
empty: () => ({
|
|
72
|
+
v: 1,
|
|
73
|
+
timezone: '',
|
|
74
|
+
days: emptyDays(),
|
|
75
|
+
note: '',
|
|
76
|
+
exceptions: [],
|
|
77
|
+
labels: { ...DEFAULT_HOURS_LABELS }
|
|
78
|
+
}),
|
|
79
|
+
// The week grid is a bespoke editor (cms-hours-editable); these are the
|
|
80
|
+
// fields it renders below the grid through the generic form.
|
|
81
|
+
fields: [
|
|
82
|
+
{ key: 'note', label: 'Note', type: 'text', placeholder: 'Closed on public holidays' },
|
|
83
|
+
{
|
|
84
|
+
key: 'exceptions',
|
|
85
|
+
label: 'Exceptions',
|
|
86
|
+
type: 'list',
|
|
87
|
+
itemLabel: 'exception',
|
|
88
|
+
titleKey: 'label',
|
|
89
|
+
blank: () => ({ date: '', label: '', closed: true, ranges: [] }),
|
|
90
|
+
fields: [
|
|
91
|
+
{ key: 'date', label: 'Date', type: 'date', half: true },
|
|
92
|
+
{ key: 'label', label: 'Label', type: 'text', placeholder: 'Christmas Day' },
|
|
93
|
+
{ key: 'closed', label: 'Closed all day', type: 'boolean' }
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: 'timezone',
|
|
98
|
+
label: 'Timezone',
|
|
99
|
+
type: 'text',
|
|
100
|
+
placeholder: 'Europe/Madrid',
|
|
101
|
+
group: 'Labels'
|
|
102
|
+
},
|
|
103
|
+
{ key: 'labels.closed', label: '“Closed”', type: 'text', half: true, group: 'Labels' },
|
|
104
|
+
{ key: 'labels.today', label: '“Today”', type: 'text', group: 'Labels' },
|
|
105
|
+
{ key: 'labels.openNow', label: '“Open now”', type: 'text', half: true, group: 'Labels' },
|
|
106
|
+
{ key: 'labels.closedNow', label: '“Closed now”', type: 'text', group: 'Labels' }
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Derived views
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
const minutesOf = (hhmm) => {
|
|
113
|
+
const [h, m] = hhmm.split(':').map(Number);
|
|
114
|
+
return h * 60 + m;
|
|
115
|
+
};
|
|
116
|
+
/** `09:00` → `9:00 AM` (en) / `9:00` (es). */
|
|
117
|
+
export const formatTime = (hhmm, locale) => {
|
|
118
|
+
if (!TIME.test(hhmm))
|
|
119
|
+
return hhmm;
|
|
120
|
+
const [h, m] = hhmm.split(':').map(Number);
|
|
121
|
+
try {
|
|
122
|
+
return new Intl.DateTimeFormat(locale, {
|
|
123
|
+
hour: 'numeric',
|
|
124
|
+
minute: '2-digit',
|
|
125
|
+
timeZone: 'UTC'
|
|
126
|
+
}).format(new Date(Date.UTC(2024, 0, 1, h, m)));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return hhmm;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/** `9:00 AM – 5:00 PM, 6:00 – 10:00 PM`, or the closed label. */
|
|
133
|
+
export const formatRanges = (ranges, locale, closedLabel) => ranges.length === 0
|
|
134
|
+
? closedLabel
|
|
135
|
+
: ranges
|
|
136
|
+
.map((r) => `${formatTime(r.open, locale)} – ${formatTime(r.close, locale)}`)
|
|
137
|
+
.join(', ');
|
|
138
|
+
/** Short weekday names in the visitor's locale, Monday first. */
|
|
139
|
+
export const dayNames = (locale, weekday = 'short') => {
|
|
140
|
+
const out = {};
|
|
141
|
+
let fmt = null;
|
|
142
|
+
try {
|
|
143
|
+
fmt = new Intl.DateTimeFormat(locale, { weekday, timeZone: 'UTC' });
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
fmt = null;
|
|
147
|
+
}
|
|
148
|
+
DAY_KEYS.forEach((k, i) => {
|
|
149
|
+
// 2024-01-01 is a Monday.
|
|
150
|
+
out[k] = fmt ? fmt.format(new Date(Date.UTC(2024, 0, 1 + i))) : k;
|
|
151
|
+
});
|
|
152
|
+
return out;
|
|
153
|
+
};
|
|
154
|
+
/** The current weekday, date and minute in `timezone` (the visitor's zone when empty or invalid). */
|
|
155
|
+
export const zonedNow = (timezone, now = new Date()) => {
|
|
156
|
+
const keyOf = (weekday) => {
|
|
157
|
+
const k = weekday.slice(0, 3).toLowerCase();
|
|
158
|
+
return DAY_KEYS.includes(k) ? k : 'mon';
|
|
159
|
+
};
|
|
160
|
+
if (timezone) {
|
|
161
|
+
try {
|
|
162
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
163
|
+
timeZone: timezone,
|
|
164
|
+
weekday: 'short',
|
|
165
|
+
year: 'numeric',
|
|
166
|
+
month: '2-digit',
|
|
167
|
+
day: '2-digit',
|
|
168
|
+
hour: '2-digit',
|
|
169
|
+
minute: '2-digit',
|
|
170
|
+
hourCycle: 'h23'
|
|
171
|
+
}).formatToParts(now);
|
|
172
|
+
const p = (t) => parts.find((x) => x.type === t)?.value ?? '';
|
|
173
|
+
return {
|
|
174
|
+
key: keyOf(p('weekday')),
|
|
175
|
+
date: `${p('year')}-${p('month')}-${p('day')}`,
|
|
176
|
+
minutes: Number(p('hour')) * 60 + Number(p('minute'))
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
/* fall through to the visitor's zone */
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
184
|
+
const day = (now.getDay() + 6) % 7; // Sunday → 6
|
|
185
|
+
return {
|
|
186
|
+
key: DAY_KEYS[day],
|
|
187
|
+
date: `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`,
|
|
188
|
+
minutes: now.getHours() * 60 + now.getMinutes()
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
const sameHours = (a, b) => a.closed === b.closed &&
|
|
192
|
+
a.ranges.length === b.ranges.length &&
|
|
193
|
+
a.ranges.every((r, i) => r.open === b.ranges[i].open && r.close === b.ranges[i].close);
|
|
194
|
+
const effective = (d) => d.closed ? [] : d.ranges;
|
|
195
|
+
const isOpenAt = (ranges, minutes) => ranges.some((r) => {
|
|
196
|
+
const open = minutesOf(r.open);
|
|
197
|
+
const close = minutesOf(r.close);
|
|
198
|
+
if (close <= open)
|
|
199
|
+
return minutes >= open || minutes < close; // overnight
|
|
200
|
+
return minutes >= open && minutes < close;
|
|
201
|
+
});
|
|
202
|
+
export const formatDate = (iso, locale) => {
|
|
203
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
204
|
+
if (!m)
|
|
205
|
+
return iso;
|
|
206
|
+
try {
|
|
207
|
+
return new Intl.DateTimeFormat(locale, {
|
|
208
|
+
day: 'numeric',
|
|
209
|
+
month: 'short',
|
|
210
|
+
timeZone: 'UTC'
|
|
211
|
+
}).format(new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]))));
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return iso;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
export const hoursView = (value, locale, now = new Date()) => {
|
|
218
|
+
const names = dayNames(locale);
|
|
219
|
+
const closedLabel = value.labels.closed;
|
|
220
|
+
const rows = [];
|
|
221
|
+
for (const key of DAY_KEYS) {
|
|
222
|
+
const day = value.days[key];
|
|
223
|
+
const last = rows[rows.length - 1];
|
|
224
|
+
if (last && sameHours(value.days[last.keys[last.keys.length - 1]], day)) {
|
|
225
|
+
last.keys.push(key);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
rows.push({
|
|
229
|
+
id: key,
|
|
230
|
+
keys: [key],
|
|
231
|
+
days: '',
|
|
232
|
+
text: formatRanges(effective(day), locale, closedLabel),
|
|
233
|
+
closed: day.closed
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
for (const row of rows) {
|
|
237
|
+
const first = names[row.keys[0]];
|
|
238
|
+
const lastName = names[row.keys[row.keys.length - 1]];
|
|
239
|
+
row.days =
|
|
240
|
+
row.keys.length === 1
|
|
241
|
+
? first
|
|
242
|
+
: row.keys.length === 2
|
|
243
|
+
? `${first}, ${lastName}`
|
|
244
|
+
: `${first} – ${lastName}`;
|
|
245
|
+
}
|
|
246
|
+
const z = zonedNow(value.timezone, now);
|
|
247
|
+
const exception = value.exceptions.find((e) => e.date === z.date) ?? null;
|
|
248
|
+
const todayRanges = effective(exception ?? value.days[z.key]);
|
|
249
|
+
const today = {
|
|
250
|
+
key: z.key,
|
|
251
|
+
date: z.date,
|
|
252
|
+
label: value.labels.today,
|
|
253
|
+
day: names[z.key],
|
|
254
|
+
text: formatRanges(todayRanges, locale, closedLabel),
|
|
255
|
+
closed: todayRanges.length === 0,
|
|
256
|
+
exception
|
|
257
|
+
};
|
|
258
|
+
const isOpenNow = isOpenAt(todayRanges, z.minutes);
|
|
259
|
+
const exceptionRows = value.exceptions
|
|
260
|
+
.filter((e) => e.date !== '')
|
|
261
|
+
.map((e) => ({
|
|
262
|
+
...e,
|
|
263
|
+
dateText: formatDate(e.date, locale),
|
|
264
|
+
text: formatRanges(effective(e), locale, closedLabel)
|
|
265
|
+
}));
|
|
266
|
+
return {
|
|
267
|
+
...value,
|
|
268
|
+
rows,
|
|
269
|
+
today,
|
|
270
|
+
isOpenNow,
|
|
271
|
+
status: isOpenNow ? value.labels.openNow : value.labels.closedNow,
|
|
272
|
+
exceptionRows
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
const SCHEMA_DAYS = {
|
|
276
|
+
mon: 'Monday',
|
|
277
|
+
tue: 'Tuesday',
|
|
278
|
+
wed: 'Wednesday',
|
|
279
|
+
thu: 'Thursday',
|
|
280
|
+
fri: 'Friday',
|
|
281
|
+
sat: 'Saturday',
|
|
282
|
+
sun: 'Sunday'
|
|
283
|
+
};
|
|
284
|
+
/** schema.org `openingHoursSpecification` entries, one per distinct range. */
|
|
285
|
+
export const toOpeningHoursSpecification = (value) => {
|
|
286
|
+
const byRange = new Map();
|
|
287
|
+
for (const key of DAY_KEYS) {
|
|
288
|
+
for (const r of effective(value.days[key])) {
|
|
289
|
+
const k = `${r.open}-${r.close}`;
|
|
290
|
+
const entry = byRange.get(k) ?? { opens: r.open, closes: r.close, days: [] };
|
|
291
|
+
entry.days.push(SCHEMA_DAYS[key]);
|
|
292
|
+
byRange.set(k, entry);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const out = [...byRange.values()].map((e) => ({
|
|
296
|
+
'@type': 'OpeningHoursSpecification',
|
|
297
|
+
dayOfWeek: e.days,
|
|
298
|
+
opens: e.opens,
|
|
299
|
+
closes: e.closes
|
|
300
|
+
}));
|
|
301
|
+
for (const e of value.exceptions) {
|
|
302
|
+
if (!e.date)
|
|
303
|
+
continue;
|
|
304
|
+
if (e.closed || e.ranges.length === 0) {
|
|
305
|
+
out.push({
|
|
306
|
+
'@type': 'OpeningHoursSpecification',
|
|
307
|
+
validFrom: e.date,
|
|
308
|
+
validThrough: e.date,
|
|
309
|
+
opens: '00:00',
|
|
310
|
+
closes: '00:00'
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
for (const r of e.ranges) {
|
|
315
|
+
out.push({
|
|
316
|
+
'@type': 'OpeningHoursSpecification',
|
|
317
|
+
validFrom: e.date,
|
|
318
|
+
validThrough: e.date,
|
|
319
|
+
opens: r.open,
|
|
320
|
+
closes: r.close
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stored shape of an image slot. Seeds and fallbacks may pass a bare URL
|
|
3
|
+
* string; it is normalized to `{ url }`. A cleared slot stores `url: null`.
|
|
4
|
+
*/
|
|
5
|
+
export type CmsImageValue = {
|
|
6
|
+
url?: string | null;
|
|
7
|
+
alt?: string | null;
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const normalizeImage: (raw: unknown) => CmsImageValue;
|
|
12
|
+
/** An image inside a structured value: `null` when there is no URL. */
|
|
13
|
+
export declare const asImage: (raw: unknown) => CmsImageValue | null;
|
|
14
|
+
/** A list of images, dropping entries without a URL. */
|
|
15
|
+
export declare const asImages: (raw: unknown) => CmsImageValue[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const normalizeImage = (raw) => {
|
|
2
|
+
if (raw == null)
|
|
3
|
+
return {};
|
|
4
|
+
if (typeof raw === 'string')
|
|
5
|
+
return { url: raw };
|
|
6
|
+
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
7
|
+
const r = raw;
|
|
8
|
+
const out = {};
|
|
9
|
+
if (typeof r.url === 'string')
|
|
10
|
+
out.url = r.url;
|
|
11
|
+
if (typeof r.alt === 'string')
|
|
12
|
+
out.alt = r.alt;
|
|
13
|
+
if (typeof r.width === 'number')
|
|
14
|
+
out.width = r.width;
|
|
15
|
+
if (typeof r.height === 'number')
|
|
16
|
+
out.height = r.height;
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
};
|
|
21
|
+
/** An image inside a structured value: `null` when there is no URL. */
|
|
22
|
+
export const asImage = (raw) => {
|
|
23
|
+
const img = normalizeImage(raw);
|
|
24
|
+
return img.url ? img : null;
|
|
25
|
+
};
|
|
26
|
+
/** A list of images, dropping entries without a URL. */
|
|
27
|
+
export const asImages = (raw) => Array.isArray(raw) ? raw.map(asImage).filter((i) => i !== null) : [];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The structured value shapes behind the editor components. Importing this
|
|
3
|
+
* module registers every schema, so the Locales panel can count translatable
|
|
4
|
+
* strings for any usage the manifest recorded.
|
|
5
|
+
*/
|
|
6
|
+
export type { FormField, FormFieldType, FormShape, ListValue } from './form.js';
|
|
7
|
+
export { defineForm } from './form.js';
|
|
8
|
+
export { normalizeLink, asLink, linkHref } from './link.js';
|
|
9
|
+
export type { CmsLinkValue } from './link.js';
|
|
10
|
+
export { normalizeImage, asImage, asImages } from './image.js';
|
|
11
|
+
export type { CmsImageValue } from './image.js';
|
|
12
|
+
export { cmsNav, navView, isActive } from './nav.js';
|
|
13
|
+
export type { CmsNavValue, NavItem, NavItemView } from './nav.js';
|
|
14
|
+
export { cmsHours, hoursView, formatTime, formatRanges, formatDate, dayNames, zonedNow, toOpeningHoursSpecification, DAY_KEYS, DEFAULT_HOURS_LABELS } from './hours.js';
|
|
15
|
+
export type { CmsHoursValue, DayKey, DayHours, HoursRange, HoursException, HoursLabels, HoursRow, HoursToday, HoursExceptionRow, HoursView } from './hours.js';
|
|
16
|
+
export { cmsContact, contactView, formatAddress, telHref, whatsappHref, toPostalAddress, DEFAULT_CONTACT_LABELS } from './contact.js';
|
|
17
|
+
export type { CmsContactValue, ContactAddress, ContactEmail, ContactLabels, ContactPhone, ContactView } from './contact.js';
|
|
18
|
+
export { cmsSocialLinks, socialLinksView, toSameAs, SOCIAL_PLATFORMS, SOCIAL_PLATFORM_NAMES } from './social-links.js';
|
|
19
|
+
export type { CmsSocialLinksValue, SocialLink, SocialLinkView, SocialPlatform } from './social-links.js';
|
|
20
|
+
export { cmsTeam, teamView } from './team.js';
|
|
21
|
+
export type { CmsTeamValue, TeamLink, TeamMember } from './team.js';
|
|
22
|
+
export { cmsTestimonials, testimonialsView, toReview } from './testimonials.js';
|
|
23
|
+
export type { CmsTestimonialsValue, Testimonial } from './testimonials.js';
|
|
24
|
+
export { cmsCollection, collectionView, collectionTags, formatCollectionPrice, toItemList } from './collection.js';
|
|
25
|
+
export type { CmsCollectionValue, CollectionDetail, CollectionItem, CollectionItemView, CollectionPrice, CollectionSlice } from './collection.js';
|
|
26
|
+
export { cmsPricing, pricingView, formatPrice } from './pricing.js';
|
|
27
|
+
export type { CmsPricingValue, PricingFeature, PricingTier, PricingTierView } from './pricing.js';
|
|
28
|
+
export { cmsFaq, faqView, toFaqPage } from './faq.js';
|
|
29
|
+
export type { CmsFaqValue, FaqItem } from './faq.js';
|
|
30
|
+
export { cmsStats, cmsSteps, cmsTimeline, cmsGallery, cmsLogos, cmsSchedule, logosView, scheduleByDay } from './presets.js';
|
|
31
|
+
export type { CmsStatsValue, StatItem, CmsStepsValue, StepItem, CmsTimelineValue, TimelineItem, CmsGalleryValue, GalleryItem, CmsLogosValue, LogoItem, LogoItemView, CmsScheduleValue, ScheduleItem } from './presets.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { defineForm } from './form.js';
|
|
2
|
+
export { normalizeLink, asLink, linkHref } from './link.js';
|
|
3
|
+
export { normalizeImage, asImage, asImages } from './image.js';
|
|
4
|
+
export { cmsNav, navView, isActive } from './nav.js';
|
|
5
|
+
export { cmsHours, hoursView, formatTime, formatRanges, formatDate, dayNames, zonedNow, toOpeningHoursSpecification, DAY_KEYS, DEFAULT_HOURS_LABELS } from './hours.js';
|
|
6
|
+
export { cmsContact, contactView, formatAddress, telHref, whatsappHref, toPostalAddress, DEFAULT_CONTACT_LABELS } from './contact.js';
|
|
7
|
+
export { cmsSocialLinks, socialLinksView, toSameAs, SOCIAL_PLATFORMS, SOCIAL_PLATFORM_NAMES } from './social-links.js';
|
|
8
|
+
export { cmsTeam, teamView } from './team.js';
|
|
9
|
+
export { cmsTestimonials, testimonialsView, toReview } from './testimonials.js';
|
|
10
|
+
export { cmsCollection, collectionView, collectionTags, formatCollectionPrice, toItemList } from './collection.js';
|
|
11
|
+
export { cmsPricing, pricingView, formatPrice } from './pricing.js';
|
|
12
|
+
export { cmsFaq, faqView, toFaqPage } from './faq.js';
|
|
13
|
+
export { cmsStats, cmsSteps, cmsTimeline, cmsGallery, cmsLogos, cmsSchedule, logosView, scheduleByDay } from './presets.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type CmsLinkValue = {
|
|
2
|
+
label?: string;
|
|
3
|
+
/** Raw URL — used when `routeId` is absent. */
|
|
4
|
+
href?: string;
|
|
5
|
+
/** SvelteKit route id, e.g. `/blog/[slug]`. Resolved at render time. */
|
|
6
|
+
routeId?: string;
|
|
7
|
+
params?: Record<string, string>;
|
|
8
|
+
newTab?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const normalizeLink: (raw: unknown) => CmsLinkValue;
|
|
11
|
+
/** A link inside a structured value: `null` when nothing points anywhere. */
|
|
12
|
+
export declare const asLink: (raw: unknown) => CmsLinkValue | null;
|
|
13
|
+
/** The URL a link points at, or `fallback` when it points nowhere or the
|
|
14
|
+
* route cannot be resolved. */
|
|
15
|
+
export declare const linkHref: (link: CmsLinkValue | null | undefined, fallback?: string) => string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { resolveRouteUrl } from '../../components/admin-bar/resolve-route.js';
|
|
2
|
+
import { isPlainObject } from '../structured.js';
|
|
3
|
+
export const normalizeLink = (raw) => {
|
|
4
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
5
|
+
return {};
|
|
6
|
+
const r = raw;
|
|
7
|
+
const out = {};
|
|
8
|
+
if (typeof r.label === 'string')
|
|
9
|
+
out.label = r.label;
|
|
10
|
+
if (typeof r.href === 'string')
|
|
11
|
+
out.href = r.href;
|
|
12
|
+
if (typeof r.routeId === 'string')
|
|
13
|
+
out.routeId = r.routeId;
|
|
14
|
+
if (r.params && typeof r.params === 'object' && !Array.isArray(r.params)) {
|
|
15
|
+
out.params = r.params;
|
|
16
|
+
}
|
|
17
|
+
if (typeof r.newTab === 'boolean')
|
|
18
|
+
out.newTab = r.newTab;
|
|
19
|
+
return out;
|
|
20
|
+
};
|
|
21
|
+
/** A link inside a structured value: `null` when nothing points anywhere. */
|
|
22
|
+
export const asLink = (raw) => {
|
|
23
|
+
if (!isPlainObject(raw))
|
|
24
|
+
return null;
|
|
25
|
+
const link = normalizeLink(raw);
|
|
26
|
+
return link.href || link.routeId ? link : null;
|
|
27
|
+
};
|
|
28
|
+
/** The URL a link points at, or `fallback` when it points nowhere or the
|
|
29
|
+
* route cannot be resolved. */
|
|
30
|
+
export const linkHref = (link, fallback = '') => {
|
|
31
|
+
if (!link)
|
|
32
|
+
return fallback;
|
|
33
|
+
if (link.routeId) {
|
|
34
|
+
try {
|
|
35
|
+
return resolveRouteUrl(link.routeId, link.params ?? {});
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return link.href ?? fallback;
|
|
42
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ListValue } from './form.js';
|
|
2
|
+
import { type CmsLinkValue } from './link.js';
|
|
3
|
+
export type NavItem = {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
link: CmsLinkValue | null;
|
|
7
|
+
children: NavItem[];
|
|
8
|
+
};
|
|
9
|
+
export type CmsNavValue = ListValue<NavItem>;
|
|
10
|
+
export type NavItemView = {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
href: string;
|
|
14
|
+
newTab: boolean;
|
|
15
|
+
children: NavItemView[];
|
|
16
|
+
};
|
|
17
|
+
export declare const cmsNav: import("./form.js").FormShape<CmsNavValue>;
|
|
18
|
+
/** Items with resolved hrefs, dropping items that have no label. */
|
|
19
|
+
export declare const navView: (value: CmsNavValue) => NavItemView[];
|
|
20
|
+
/** Whether `item` (or one of its children) points at the current page. */
|
|
21
|
+
export declare const isActive: (item: NavItemView, url: URL | string) => boolean;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { asBoolean, asItems, asString, isPlainObject } from '../structured.js';
|
|
2
|
+
import { defineForm } from './form.js';
|
|
3
|
+
import { asLink, linkHref } from './link.js';
|
|
4
|
+
const normalizeItem = (raw, depth) => ({
|
|
5
|
+
label: asString(raw.label),
|
|
6
|
+
link: asLink(raw.link),
|
|
7
|
+
children: depth === 0 ? asItems(raw.children, (child) => normalizeItem(child, depth + 1)) : []
|
|
8
|
+
});
|
|
9
|
+
const blankItem = () => ({ label: '', link: null, children: [] });
|
|
10
|
+
const linkField = { key: 'link', label: 'Link', type: 'link' };
|
|
11
|
+
export const cmsNav = defineForm({
|
|
12
|
+
component: 'CmsNav',
|
|
13
|
+
label: 'Navigation',
|
|
14
|
+
version: 1,
|
|
15
|
+
translatable: [],
|
|
16
|
+
items: {
|
|
17
|
+
key: 'items',
|
|
18
|
+
translatable: ['label'],
|
|
19
|
+
items: { key: 'children', translatable: ['label'] }
|
|
20
|
+
},
|
|
21
|
+
normalize: (raw) => {
|
|
22
|
+
const items = Array.isArray(raw) ? raw : isPlainObject(raw) ? raw.items : [];
|
|
23
|
+
return { v: 1, items: asItems(items, (item) => normalizeItem(item, 0)) };
|
|
24
|
+
},
|
|
25
|
+
empty: () => ({ v: 1, items: [] }),
|
|
26
|
+
fields: [
|
|
27
|
+
{
|
|
28
|
+
key: 'items',
|
|
29
|
+
label: 'Links',
|
|
30
|
+
type: 'list',
|
|
31
|
+
itemLabel: 'link',
|
|
32
|
+
titleKey: 'label',
|
|
33
|
+
blank: blankItem,
|
|
34
|
+
fields: [
|
|
35
|
+
{ key: 'label', label: 'Label', type: 'text' },
|
|
36
|
+
linkField,
|
|
37
|
+
{
|
|
38
|
+
key: 'children',
|
|
39
|
+
label: 'Sub-links',
|
|
40
|
+
type: 'list',
|
|
41
|
+
itemLabel: 'sub-link',
|
|
42
|
+
titleKey: 'label',
|
|
43
|
+
blank: blankItem,
|
|
44
|
+
fields: [{ key: 'label', label: 'Label', type: 'text' }, linkField]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
const viewItem = (item) => ({
|
|
51
|
+
id: item.id,
|
|
52
|
+
label: item.label,
|
|
53
|
+
href: linkHref(item.link, '#'),
|
|
54
|
+
newTab: asBoolean(item.link?.newTab),
|
|
55
|
+
children: item.children.map(viewItem)
|
|
56
|
+
});
|
|
57
|
+
/** Items with resolved hrefs, dropping items that have no label. */
|
|
58
|
+
export const navView = (value) => value.items.filter((i) => i.label !== '').map(viewItem);
|
|
59
|
+
/** Whether `item` (or one of its children) points at the current page. */
|
|
60
|
+
export const isActive = (item, url) => {
|
|
61
|
+
const pathname = typeof url === 'string' ? url : url.pathname;
|
|
62
|
+
const path = pathname.replace(/\/+$/, '') || '/';
|
|
63
|
+
const href = item.href.replace(/\/+$/, '') || '/';
|
|
64
|
+
if (href === '#')
|
|
65
|
+
return false;
|
|
66
|
+
if (href === path)
|
|
67
|
+
return true;
|
|
68
|
+
if (href !== '/' && path.startsWith(href + '/'))
|
|
69
|
+
return true;
|
|
70
|
+
return item.children.some((c) => isActive(c, url));
|
|
71
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type FormShape, type ListValue } from './form.js';
|
|
2
|
+
import { type CmsImageValue } from './image.js';
|
|
3
|
+
import { type CmsLinkValue } from './link.js';
|
|
4
|
+
export type StatItem = {
|
|
5
|
+
id: string;
|
|
6
|
+
value: string;
|
|
7
|
+
label: string;
|
|
8
|
+
note: string;
|
|
9
|
+
};
|
|
10
|
+
export type CmsStatsValue = ListValue<StatItem>;
|
|
11
|
+
export declare const cmsStats: FormShape<ListValue<StatItem>>;
|
|
12
|
+
export type StepItem = {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
body: string;
|
|
16
|
+
image: CmsImageValue | null;
|
|
17
|
+
};
|
|
18
|
+
export type CmsStepsValue = ListValue<StepItem>;
|
|
19
|
+
export declare const cmsSteps: FormShape<ListValue<StepItem>>;
|
|
20
|
+
export type TimelineItem = {
|
|
21
|
+
id: string;
|
|
22
|
+
/** Free text: `2019`, `March 2024`. */
|
|
23
|
+
date: string;
|
|
24
|
+
title: string;
|
|
25
|
+
body: string;
|
|
26
|
+
image: CmsImageValue | null;
|
|
27
|
+
};
|
|
28
|
+
export type CmsTimelineValue = ListValue<TimelineItem>;
|
|
29
|
+
export declare const cmsTimeline: FormShape<ListValue<TimelineItem>>;
|
|
30
|
+
export type GalleryItem = {
|
|
31
|
+
id: string;
|
|
32
|
+
image: CmsImageValue | null;
|
|
33
|
+
caption: string;
|
|
34
|
+
};
|
|
35
|
+
export type CmsGalleryValue = ListValue<GalleryItem>;
|
|
36
|
+
export declare const cmsGallery: FormShape<ListValue<GalleryItem>>;
|
|
37
|
+
export type LogoItem = {
|
|
38
|
+
id: string;
|
|
39
|
+
image: CmsImageValue | null;
|
|
40
|
+
name: string;
|
|
41
|
+
link: CmsLinkValue | null;
|
|
42
|
+
};
|
|
43
|
+
export type CmsLogosValue = ListValue<LogoItem>;
|
|
44
|
+
export type LogoItemView = LogoItem & {
|
|
45
|
+
href: string | null;
|
|
46
|
+
};
|
|
47
|
+
export declare const cmsLogos: FormShape<ListValue<LogoItem>>;
|
|
48
|
+
export declare const logosView: (value: CmsLogosValue) => LogoItemView[];
|
|
49
|
+
export type ScheduleItem = {
|
|
50
|
+
id: string;
|
|
51
|
+
/** Free text: `Monday`, `Sat 12 Oct`. */
|
|
52
|
+
day: string;
|
|
53
|
+
/** Free text: `10:00`, `7 – 9pm`. */
|
|
54
|
+
time: string;
|
|
55
|
+
title: string;
|
|
56
|
+
body: string;
|
|
57
|
+
location: string;
|
|
58
|
+
};
|
|
59
|
+
export type CmsScheduleValue = ListValue<ScheduleItem>;
|
|
60
|
+
export declare const cmsSchedule: FormShape<ListValue<ScheduleItem>>;
|
|
61
|
+
/** Sessions grouped by `day`, in first-seen order. */
|
|
62
|
+
export declare const scheduleByDay: (value: CmsScheduleValue) => Array<{
|
|
63
|
+
day: string;
|
|
64
|
+
items: ScheduleItem[];
|
|
65
|
+
}>;
|