@velastack/cms 0.5.0 → 0.6.0

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.
Files changed (134) hide show
  1. package/README.md +104 -47
  2. package/dist/backend/index.d.ts +7 -5
  3. package/dist/backend/index.js +7 -5
  4. package/dist/backend/types.d.ts +1 -1
  5. package/dist/components/admin-bar/admin-bar-internal.svelte +54 -59
  6. package/dist/components/admin-bar/admin-bar.css +1 -1
  7. package/dist/components/admin-bar/admin-bar.svelte +11 -7
  8. package/dist/components/admin-bar/field-input.svelte +20 -1
  9. package/dist/components/admin-bar/field-input.svelte.d.ts +2 -1
  10. package/dist/components/admin-bar/fields-form.svelte +218 -0
  11. package/dist/components/admin-bar/fields-form.svelte.d.ts +16 -0
  12. package/dist/components/admin-bar/history-panel.svelte +4 -4
  13. package/dist/components/admin-bar/link-input.svelte +102 -0
  14. package/dist/components/admin-bar/link-input.svelte.d.ts +9 -0
  15. package/dist/components/admin-bar/list-field.svelte +93 -0
  16. package/dist/components/admin-bar/list-field.svelte.d.ts +20 -0
  17. package/dist/components/admin-bar/locales-panel.svelte +1 -1
  18. package/dist/components/admin-bar/page-options.d.ts +24 -0
  19. package/dist/components/admin-bar/page-options.js +53 -0
  20. package/dist/components/admin-bar/pages-panel.svelte +1 -1
  21. package/dist/components/admin-bar/rich-text-input.svelte +92 -0
  22. package/dist/components/admin-bar/rich-text-input.svelte.d.ts +9 -0
  23. package/dist/components/admin-bar/structured-editor-popover.svelte +15 -6
  24. package/dist/components/admin-bar/theme.svelte.js +1 -1
  25. package/dist/components/cms/cms-collection.svelte +46 -0
  26. package/dist/components/cms/cms-collection.svelte.d.ts +25 -0
  27. package/dist/components/cms/cms-contact.svelte +26 -0
  28. package/dist/components/cms/cms-contact.svelte.d.ts +14 -0
  29. package/dist/components/cms/cms-faq.svelte +22 -0
  30. package/dist/components/cms/cms-faq.svelte.d.ts +15 -0
  31. package/dist/components/cms/cms-gallery.svelte +31 -0
  32. package/dist/components/cms/cms-gallery.svelte.d.ts +16 -0
  33. package/dist/components/cms/cms-hours-editable.svelte +160 -0
  34. package/dist/components/cms/cms-hours-editable.svelte.d.ts +17 -0
  35. package/dist/components/cms/cms-hours.svelte +42 -0
  36. package/dist/components/cms/cms-hours.svelte.d.ts +19 -0
  37. package/dist/components/cms/cms-image.svelte +3 -25
  38. package/dist/components/cms/cms-image.svelte.d.ts +3 -11
  39. package/dist/components/cms/cms-link-editable.svelte +4 -60
  40. package/dist/components/cms/cms-link.svelte +3 -23
  41. package/dist/components/cms/cms-link.svelte.d.ts +3 -10
  42. package/dist/components/cms/cms-logos.svelte +29 -0
  43. package/dist/components/cms/cms-logos.svelte.d.ts +16 -0
  44. package/dist/components/cms/cms-nav.svelte +22 -0
  45. package/dist/components/cms/cms-nav.svelte.d.ts +15 -0
  46. package/dist/components/cms/cms-pricing.svelte +36 -0
  47. package/dist/components/cms/cms-pricing.svelte.d.ts +14 -0
  48. package/dist/components/cms/cms-schedule.svelte +35 -0
  49. package/dist/components/cms/cms-schedule.svelte.d.ts +16 -0
  50. package/dist/components/cms/cms-social-links.svelte +35 -0
  51. package/dist/components/cms/cms-social-links.svelte.d.ts +15 -0
  52. package/dist/components/cms/cms-stats.svelte +31 -0
  53. package/dist/components/cms/cms-stats.svelte.d.ts +16 -0
  54. package/dist/components/cms/cms-steps.svelte +31 -0
  55. package/dist/components/cms/cms-steps.svelte.d.ts +16 -0
  56. package/dist/components/cms/cms-store.svelte.js +8 -5
  57. package/dist/components/cms/cms-structured-editable.svelte +29 -0
  58. package/dist/components/cms/{cms-repeater-editable.svelte.d.ts → cms-structured-editable.svelte.d.ts} +12 -8
  59. package/dist/components/cms/cms-structured.svelte +50 -0
  60. package/dist/components/cms/cms-structured.svelte.d.ts +49 -0
  61. package/dist/components/cms/cms-team.svelte +22 -0
  62. package/dist/components/cms/cms-team.svelte.d.ts +15 -0
  63. package/dist/components/cms/cms-testimonials.svelte +36 -0
  64. package/dist/components/cms/cms-testimonials.svelte.d.ts +15 -0
  65. package/dist/components/cms/cms-timeline.svelte +35 -0
  66. package/dist/components/cms/cms-timeline.svelte.d.ts +16 -0
  67. package/dist/components/cms/index.d.ts +15 -1
  68. package/dist/components/cms/index.js +15 -1
  69. package/dist/core/cms-fetch.d.ts +16 -0
  70. package/dist/core/cms-fetch.js +24 -0
  71. package/dist/core/shapes/collection.d.ts +60 -0
  72. package/dist/core/shapes/collection.js +194 -0
  73. package/dist/core/shapes/contact.d.ts +70 -0
  74. package/dist/core/shapes/contact.js +214 -0
  75. package/dist/core/shapes/faq.d.ts +12 -0
  76. package/dist/core/shapes/faq.js +48 -0
  77. package/dist/core/shapes/form.d.ts +46 -0
  78. package/dist/core/shapes/form.js +7 -0
  79. package/dist/core/shapes/hours.d.ts +91 -0
  80. package/dist/core/shapes/hours.js +325 -0
  81. package/dist/core/shapes/image.d.ts +15 -0
  82. package/dist/core/shapes/image.js +27 -0
  83. package/dist/core/shapes/index.d.ts +31 -0
  84. package/dist/core/shapes/index.js +13 -0
  85. package/dist/core/shapes/link.d.ts +15 -0
  86. package/dist/core/shapes/link.js +42 -0
  87. package/dist/core/shapes/nav.d.ts +21 -0
  88. package/dist/core/shapes/nav.js +71 -0
  89. package/dist/core/shapes/presets.d.ts +65 -0
  90. package/dist/core/shapes/presets.js +152 -0
  91. package/dist/core/shapes/pricing.d.ts +40 -0
  92. package/dist/core/shapes/pricing.js +115 -0
  93. package/dist/core/shapes/social-links.d.ts +19 -0
  94. package/dist/core/shapes/social-links.js +83 -0
  95. package/dist/core/shapes/team.d.ts +22 -0
  96. package/dist/core/shapes/team.js +70 -0
  97. package/dist/core/shapes/testimonials.d.ts +24 -0
  98. package/dist/core/shapes/testimonials.js +86 -0
  99. package/dist/{components/cms → core}/structured-registry.d.ts +2 -2
  100. package/dist/core/structured.d.ts +33 -10
  101. package/dist/core/structured.js +98 -47
  102. package/dist/editor.d.ts +4 -0
  103. package/dist/editor.js +4 -0
  104. package/dist/index.d.ts +22 -8
  105. package/dist/index.js +24 -5
  106. package/dist/server/README.md +1 -1
  107. package/dist/server/api-adapter.js +1 -1
  108. package/dist/server/cms.d.ts +1 -1
  109. package/dist/server/cms.js +1 -1
  110. package/dist/server/load-cms.js +1 -1
  111. package/package.json +15 -11
  112. package/plugin/README.md +34 -4
  113. package/plugin/dist/adapter.d.ts +5 -5
  114. package/plugin/dist/adapter.js +21 -7
  115. package/plugin/dist/adapter.js.map +1 -1
  116. package/plugin/dist/build-state.d.ts +1 -0
  117. package/plugin/dist/build-state.js +10 -1
  118. package/plugin/dist/build-state.js.map +1 -1
  119. package/plugin/dist/build.d.ts +2 -2
  120. package/plugin/dist/build.js +8 -7
  121. package/plugin/dist/build.js.map +1 -1
  122. package/plugin/dist/index.js +5 -1
  123. package/plugin/dist/index.js.map +1 -1
  124. package/plugin/dist/manifest.d.ts +18 -0
  125. package/plugin/dist/manifest.js +78 -10
  126. package/plugin/dist/manifest.js.map +1 -1
  127. package/plugin/dist/transforms.d.ts +4 -4
  128. package/plugin/dist/transforms.js +4 -4
  129. package/dist/cms.d.ts +0 -6
  130. package/dist/cms.js +0 -27
  131. package/dist/components/cms/cms-repeater-editable.svelte +0 -137
  132. package/dist/components/cms/cms-repeater.svelte +0 -52
  133. package/dist/components/cms/cms-repeater.svelte.d.ts +0 -31
  134. /package/dist/{components/cms → core}/structured-registry.js +0 -0
@@ -0,0 +1,16 @@
1
+ /** `CmsGallery` preset: image and caption. */
2
+ export type { CmsGalleryValue, GalleryItem } from '../../core/shapes/presets.js';
3
+ export { cmsGallery } from '../../core/shapes/presets.js';
4
+ import type { Snippet } from 'svelte';
5
+ import { type CmsGalleryValue, type GalleryItem } from '../../core/shapes/presets.js';
6
+ type Props = {
7
+ name: string;
8
+ scope?: string;
9
+ /** The full value, or a bare item list. */
10
+ fallback?: Partial<CmsGalleryValue> | Partial<GalleryItem>[];
11
+ value?: unknown;
12
+ children: Snippet<[GalleryItem[]]>;
13
+ };
14
+ declare const CmsGallery: import("svelte").Component<Props, {}, "">;
15
+ type CmsGallery = ReturnType<typeof CmsGallery>;
16
+ export default CmsGallery;
@@ -0,0 +1,160 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The bespoke editor for `CmsHours`: a week grid (closed toggle, one or
4
+ * two ranges per day, copy Monday across) above the shape's other fields
5
+ * (note, exceptions, labels) rendered through the generic form.
6
+ */
7
+ import type { Snippet } from 'svelte';
8
+ import type { Tree } from '../../core/path.js';
9
+ import type { FormShape } from '../../core/shapes/form.js';
10
+ import {
11
+ DAY_KEYS,
12
+ dayNames,
13
+ type CmsHoursValue,
14
+ type DayKey,
15
+ type HoursRange
16
+ } from '../../core/shapes/hours.js';
17
+ import type { CmsField } from './use-cms-field.svelte.js';
18
+ import { cmsStore } from './cms-store.svelte.js';
19
+ import StructuredEditorPopover from '../admin-bar/structured-editor-popover.svelte';
20
+ import FieldsForm from '../admin-bar/fields-form.svelte';
21
+ import { Button } from '../admin-bar/ui/button/index.js';
22
+ import { Input } from '../admin-bar/ui/input/index.js';
23
+ import { Switch } from '../admin-bar/ui/switch/index.js';
24
+
25
+ type Props = {
26
+ field: CmsField<CmsHoursValue>;
27
+ schema: FormShape<CmsHoursValue>;
28
+ preview: Snippet;
29
+ };
30
+ let { field, schema, preview }: Props = $props();
31
+
32
+ const names = $derived(dayNames(cmsStore.activeLocale || 'en', 'long'));
33
+ const DEFAULT_RANGE: HoursRange = { open: '09:00', close: '17:00' };
34
+
35
+ const setDay = (
36
+ draft: CmsHoursValue,
37
+ key: DayKey,
38
+ patch: Partial<CmsHoursValue['days'][DayKey]>
39
+ ) => ({
40
+ ...draft,
41
+ days: { ...draft.days, [key]: { ...draft.days[key], ...patch } }
42
+ });
43
+ const setRange = (
44
+ draft: CmsHoursValue,
45
+ key: DayKey,
46
+ index: number,
47
+ patch: Partial<HoursRange>
48
+ ): CmsHoursValue =>
49
+ setDay(draft, key, {
50
+ ranges: draft.days[key].ranges.map((r, i) => (i === index ? { ...r, ...patch } : r))
51
+ });
52
+ const toggleClosed = (draft: CmsHoursValue, key: DayKey, closed: boolean): CmsHoursValue =>
53
+ setDay(draft, key, {
54
+ closed,
55
+ ranges:
56
+ !closed && draft.days[key].ranges.length === 0 ? [DEFAULT_RANGE] : draft.days[key].ranges
57
+ });
58
+ const copyMonday = (draft: CmsHoursValue, keys: readonly DayKey[]): CmsHoursValue => {
59
+ const mon = draft.days.mon;
60
+ const days = { ...draft.days };
61
+ for (const k of keys)
62
+ days[k] = { closed: mon.closed, ranges: mon.ranges.map((r) => ({ ...r })) };
63
+ return { ...draft, days };
64
+ };
65
+ </script>
66
+
67
+ <StructuredEditorPopover {field} {schema} label={schema.label} {preview}>
68
+ {#snippet editor(draft: CmsHoursValue, update: (next: CmsHoursValue) => void)}
69
+ <div class="vela:flex vela:flex-col vela:gap-2">
70
+ {#each DAY_KEYS as key (key)}
71
+ {@const day = draft.days[key]}
72
+ <div class="vela:flex vela:items-start vela:gap-2">
73
+ <label
74
+ class="vela:flex vela:w-24 vela:shrink-0 vela:items-center vela:gap-2 vela:pt-2 vela:text-[13px] vela:text-bar-text"
75
+ >
76
+ <Switch
77
+ checked={!day.closed}
78
+ onCheckedChange={(open) => update(toggleClosed(draft, key, !open))}
79
+ aria-label={`${names[key]} open`}
80
+ />
81
+ <span class="vela:truncate">{names[key]}</span>
82
+ </label>
83
+ <div class="vela:flex vela:flex-1 vela:flex-col vela:gap-1">
84
+ {#if day.closed}
85
+ <span class="vela:pt-2 vela:text-[12px] vela:text-bar-text-tertiary"
86
+ >{draft.labels.closed}</span
87
+ >
88
+ {:else}
89
+ {#each day.ranges as range, i (i)}
90
+ <div class="vela:flex vela:items-center vela:gap-1">
91
+ <Input
92
+ type="time"
93
+ value={range.open}
94
+ aria-label={`${names[key]} opens`}
95
+ oninput={(e) =>
96
+ update(setRange(draft, key, i, { open: e.currentTarget.value }))}
97
+ />
98
+ <span class="vela:text-bar-text-tertiary">–</span>
99
+ <Input
100
+ type="time"
101
+ value={range.close}
102
+ aria-label={`${names[key]} closes`}
103
+ oninput={(e) =>
104
+ update(setRange(draft, key, i, { close: e.currentTarget.value }))}
105
+ />
106
+ {#if day.ranges.length > 1}
107
+ <Button
108
+ size="icon"
109
+ variant="ghost"
110
+ class="vela:h-6 vela:w-6 vela:shrink-0"
111
+ aria-label="Remove shift"
112
+ onclick={() =>
113
+ update(
114
+ setDay(draft, key, { ranges: day.ranges.filter((_, j) => j !== i) })
115
+ )}
116
+ >
117
+ ×
118
+ </Button>
119
+ {:else}
120
+ <Button
121
+ size="xs"
122
+ variant="ghost"
123
+ class="vela:shrink-0"
124
+ aria-label="Add a second shift"
125
+ onclick={() =>
126
+ update(
127
+ setDay(draft, key, {
128
+ ranges: [...day.ranges, { open: '18:00', close: '22:00' }]
129
+ })
130
+ )}
131
+ >
132
+ + Split
133
+ </Button>
134
+ {/if}
135
+ </div>
136
+ {/each}
137
+ {/if}
138
+ </div>
139
+ </div>
140
+ {/each}
141
+ <div class="vela:flex vela:gap-1.5">
142
+ <Button
143
+ variant="outline"
144
+ size="pill"
145
+ onclick={() => update(copyMonday(draft, ['tue', 'wed', 'thu', 'fri']))}
146
+ >
147
+ Copy Monday to weekdays
148
+ </Button>
149
+ <Button variant="outline" size="pill" onclick={() => update(copyMonday(draft, DAY_KEYS))}>
150
+ Copy to all
151
+ </Button>
152
+ </div>
153
+ </div>
154
+ <FieldsForm
155
+ fields={schema.fields}
156
+ value={draft as unknown as Tree}
157
+ onChange={(next) => update(next as unknown as CmsHoursValue)}
158
+ />
159
+ {/snippet}
160
+ </StructuredEditorPopover>
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The bespoke editor for `CmsHours`: a week grid (closed toggle, one or
3
+ * two ranges per day, copy Monday across) above the shape's other fields
4
+ * (note, exceptions, labels) rendered through the generic form.
5
+ */
6
+ import type { Snippet } from 'svelte';
7
+ import type { FormShape } from '../../core/shapes/form.js';
8
+ import { type CmsHoursValue } from '../../core/shapes/hours.js';
9
+ import type { CmsField } from './use-cms-field.svelte.js';
10
+ type Props = {
11
+ field: CmsField<CmsHoursValue>;
12
+ schema: FormShape<CmsHoursValue>;
13
+ preview: Snippet;
14
+ };
15
+ declare const CmsHoursEditable: import("svelte").Component<Props, {}, "">;
16
+ type CmsHoursEditable = ReturnType<typeof CmsHoursEditable>;
17
+ export default CmsHoursEditable;
@@ -0,0 +1,42 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * `CmsHours`: opening hours, edited in a week grid and rendered from the
4
+ * derived view (`rows`, `today`, `isOpenNow`) in the visitor's locale.
5
+ * `isOpenNow` is computed when the page renders; a prerendered page
6
+ * shows the state at build time.
7
+ */
8
+ export * from '../../core/shapes/hours.js';
9
+ </script>
10
+
11
+ <script lang="ts">
12
+ import type { Snippet } from 'svelte';
13
+ import Structured from './cms-structured.svelte';
14
+ import { cmsStore } from './cms-store.svelte.js';
15
+ import {
16
+ cmsHours,
17
+ hoursView,
18
+ type CmsHoursValue,
19
+ type HoursView
20
+ } from '../../core/shapes/hours.js';
21
+
22
+ type Props = {
23
+ name: string;
24
+ scope?: string;
25
+ fallback?: Partial<CmsHoursValue>;
26
+ value?: unknown;
27
+ children: Snippet<[HoursView]>;
28
+ };
29
+ let { name, scope, fallback, value, children }: Props = $props();
30
+ const locale = $derived(cmsStore.activeLocale || 'en');
31
+ </script>
32
+
33
+ <Structured
34
+ schema={cmsHours}
35
+ {name}
36
+ {scope}
37
+ {fallback}
38
+ {value}
39
+ view={(v: CmsHoursValue) => hoursView(v, locale)}
40
+ editable={() => import('./cms-hours-editable.svelte')}
41
+ {children}
42
+ />
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `CmsHours`: opening hours, edited in a week grid and rendered from the
3
+ * derived view (`rows`, `today`, `isOpenNow`) in the visitor's locale.
4
+ * `isOpenNow` is computed when the page renders; a prerendered page
5
+ * shows the state at build time.
6
+ */
7
+ export * from '../../core/shapes/hours.js';
8
+ import type { Snippet } from 'svelte';
9
+ import { type CmsHoursValue, type HoursView } from '../../core/shapes/hours.js';
10
+ type Props = {
11
+ name: string;
12
+ scope?: string;
13
+ fallback?: Partial<CmsHoursValue>;
14
+ value?: unknown;
15
+ children: Snippet<[HoursView]>;
16
+ };
17
+ declare const CmsHours: import("svelte").Component<Props, {}, "">;
18
+ type CmsHours = ReturnType<typeof CmsHours>;
19
+ export default CmsHours;
@@ -1,34 +1,12 @@
1
1
  <script lang="ts" module>
2
- /**
3
- * Stored shape of an image slot. Seeds and fallbacks may pass a bare URL
4
- * string; it is normalized to `{ url }`. A cleared slot stores `url: null`.
5
- */
6
- export type CmsImageValue = {
7
- url?: string | null;
8
- alt?: string | null;
9
- width?: number;
10
- height?: number;
11
- };
12
-
13
- export const normalizeImage = (raw: unknown): CmsImageValue => {
14
- if (raw == null) return {};
15
- if (typeof raw === 'string') return { url: raw };
16
- if (typeof raw === 'object' && !Array.isArray(raw)) {
17
- const r = raw as Record<string, unknown>;
18
- const out: CmsImageValue = {};
19
- if (typeof r.url === 'string') out.url = r.url;
20
- if (typeof r.alt === 'string') out.alt = r.alt;
21
- if (typeof r.width === 'number') out.width = r.width;
22
- if (typeof r.height === 'number') out.height = r.height;
23
- return out;
24
- }
25
- return {};
26
- };
2
+ export { normalizeImage } from '../../core/shapes/image.js';
3
+ export type { CmsImageValue } from '../../core/shapes/image.js';
27
4
  </script>
28
5
 
29
6
  <script lang="ts">
30
7
  import type { Snippet } from 'svelte';
31
8
  import { useCmsField } from './use-cms-field.svelte.js';
9
+ import { normalizeImage, type CmsImageValue } from '../../core/shapes/image.js';
32
10
 
33
11
  type Props = {
34
12
  name: string;
@@ -1,15 +1,7 @@
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;
1
+ export { normalizeImage } from '../../core/shapes/image.js';
2
+ export type { CmsImageValue } from '../../core/shapes/image.js';
12
3
  import type { Snippet } from 'svelte';
4
+ import { type CmsImageValue } from '../../core/shapes/image.js';
13
5
  type Props = {
14
6
  name: string;
15
7
  scope?: string;
@@ -4,6 +4,7 @@
4
4
  import { page } from '$app/state';
5
5
  import { cmsStore, type CmsScopeRef } from './cms-store.svelte.js';
6
6
  import { resolveRouteUrl } from '../admin-bar/resolve-route.js';
7
+ import { fetchPageOptions, pageOptionValue, type PageOption } from '../admin-bar/page-options.js';
7
8
  import type { CmsLinkValue, CmsLinkRenderProps } from './cms-link.svelte';
8
9
  import { Button } from '../admin-bar/ui/button/index.js';
9
10
  import { Input } from '../admin-bar/ui/input/index.js';
@@ -16,19 +17,6 @@
16
17
  import XIcon from '../admin-bar/icons/x.svelte';
17
18
  import Trash from '../admin-bar/icons/trash-2.svelte';
18
19
 
19
- type PageEntry = {
20
- params: Record<string, string>;
21
- isDraft: boolean;
22
- isDeletePending: boolean;
23
- redirectTo?: string;
24
- gone?: boolean;
25
- };
26
- type PageRoute = {
27
- routeId: string;
28
- ownedParams: string[];
29
- entries: PageEntry[];
30
- };
31
-
32
20
  type Props = {
33
21
  scope: CmsScopeRef;
34
22
  name: string;
@@ -65,7 +53,7 @@
65
53
  let urlInput = $state('');
66
54
  let urlInputEl: HTMLInputElement | null = $state(null);
67
55
 
68
- let pages: PageRoute[] = $state([]);
56
+ let pageOptions: PageOption[] = $state([]);
69
57
  let pagesLoaded = $state(false);
70
58
  let selectedValue = $state('');
71
59
  let pageSelectOpen = $state(false);
@@ -128,61 +116,17 @@
128
116
  else if (e.key === 'Escape') cancelMode(e);
129
117
  };
130
118
 
131
- const optionValueFor = (routeId: string, params: Record<string, string>) =>
132
- `${routeId}::${JSON.stringify(params)}`;
133
-
134
119
  const enterPageMode = async (e: Event) => {
135
120
  e.preventDefault();
136
121
  e.stopPropagation();
137
- selectedValue = current.routeId ? optionValueFor(current.routeId, current.params ?? {}) : '';
122
+ selectedValue = current.routeId ? pageOptionValue(current.routeId, current.params ?? {}) : '';
138
123
  mode = 'page';
139
124
  if (!pagesLoaded) {
140
- try {
141
- const res = await fetch(`${endpoint}/pages`, { credentials: 'include' });
142
- if (res.ok) {
143
- const data = (await res.json()) as { routes: PageRoute[] };
144
- pages = data.routes;
145
- }
146
- } catch {
147
- /* ignore — picker shows "No pages found" */
148
- }
125
+ pageOptions = await fetchPageOptions(endpoint);
149
126
  pagesLoaded = true;
150
127
  }
151
128
  };
152
129
 
153
- const pageOptions = $derived.by(() => {
154
- const opts: {
155
- value: string;
156
- label: string;
157
- routeId: string;
158
- params: Record<string, string>;
159
- }[] = [];
160
- for (const r of pages) {
161
- const entries =
162
- r.entries.length > 0
163
- ? r.entries
164
- : [{ params: {} as Record<string, string>, isDraft: false, isDeletePending: false }];
165
- for (const ent of entries) {
166
- // Tombstones (redirected / gone) aren't real link targets.
167
- if (ent.redirectTo || ent.gone) continue;
168
- let url: string;
169
- try {
170
- url = resolveRouteUrl(r.routeId, ent.params);
171
- } catch {
172
- continue;
173
- }
174
- opts.push({
175
- value: optionValueFor(r.routeId, ent.params),
176
- label: url,
177
- routeId: r.routeId,
178
- params: ent.params
179
- });
180
- }
181
- }
182
- opts.sort((a, b) => a.label.localeCompare(b.label));
183
- return opts;
184
- });
185
-
186
130
  const applyPageValue = (value: string) => {
187
131
  const opt = pageOptions.find((o) => o.value === value);
188
132
  if (!opt) return;
@@ -1,35 +1,15 @@
1
1
  <script lang="ts" module>
2
- export type CmsLinkValue = {
3
- label?: string;
4
- /** Raw URL — used when `routeId` is absent. */
5
- href?: string;
6
- /** SvelteKit route id, e.g. `/blog/[slug]`. Resolved at render time. */
7
- routeId?: string;
8
- params?: Record<string, string>;
9
- newTab?: boolean;
10
- };
2
+ export { normalizeLink } from '../../core/shapes/link.js';
3
+ export type { CmsLinkValue } from '../../core/shapes/link.js';
11
4
 
12
5
  export type CmsLinkRenderProps = { label: string; href: string; newTab: boolean };
13
-
14
- export const normalizeLink = (raw: unknown): CmsLinkValue => {
15
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
16
- const r = raw as Record<string, unknown>;
17
- const out: CmsLinkValue = {};
18
- if (typeof r.label === 'string') out.label = r.label;
19
- if (typeof r.href === 'string') out.href = r.href;
20
- if (typeof r.routeId === 'string') out.routeId = r.routeId;
21
- if (r.params && typeof r.params === 'object' && !Array.isArray(r.params)) {
22
- out.params = r.params as Record<string, string>;
23
- }
24
- if (typeof r.newTab === 'boolean') out.newTab = r.newTab;
25
- return out;
26
- };
27
6
  </script>
28
7
 
29
8
  <script lang="ts">
30
9
  import type { Snippet } from 'svelte';
31
10
  import { useCmsField } from './use-cms-field.svelte.js';
32
11
  import { resolveRouteUrl } from '../admin-bar/resolve-route.js';
12
+ import { normalizeLink, type CmsLinkValue } from '../../core/shapes/link.js';
33
13
 
34
14
  type Props = {
35
15
  name: string;
@@ -1,19 +1,12 @@
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
- };
1
+ export { normalizeLink } from '../../core/shapes/link.js';
2
+ export type { CmsLinkValue } from '../../core/shapes/link.js';
10
3
  export type CmsLinkRenderProps = {
11
4
  label: string;
12
5
  href: string;
13
6
  newTab: boolean;
14
7
  };
15
- export declare const normalizeLink: (raw: unknown) => CmsLinkValue;
16
8
  import type { Snippet } from 'svelte';
9
+ import { type CmsLinkValue } from '../../core/shapes/link.js';
17
10
  type Props = {
18
11
  name: string;
19
12
  scope?: string;
@@ -0,0 +1,29 @@
1
+ <script lang="ts" module>
2
+ /** `CmsLogos` preset: logo image, name, optional link. */
3
+ export type { CmsLogosValue, LogoItem, LogoItemView } from '../../core/shapes/presets.js';
4
+ export { cmsLogos, logosView } from '../../core/shapes/presets.js';
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import type { Snippet } from 'svelte';
9
+ import Structured from './cms-structured.svelte';
10
+ import {
11
+ cmsLogos,
12
+ logosView,
13
+ type CmsLogosValue,
14
+ type LogoItem,
15
+ type LogoItemView
16
+ } from '../../core/shapes/presets.js';
17
+
18
+ type Props = {
19
+ name: string;
20
+ scope?: string;
21
+ /** The full value, or a bare item list. */
22
+ fallback?: Partial<CmsLogosValue> | Partial<LogoItem>[];
23
+ value?: unknown;
24
+ children: Snippet<[LogoItemView[]]>;
25
+ };
26
+ let { name, scope, fallback, value, children }: Props = $props();
27
+ </script>
28
+
29
+ <Structured schema={cmsLogos} {name} {scope} {fallback} {value} view={logosView} {children} />
@@ -0,0 +1,16 @@
1
+ /** `CmsLogos` preset: logo image, name, optional link. */
2
+ export type { CmsLogosValue, LogoItem, LogoItemView } from '../../core/shapes/presets.js';
3
+ export { cmsLogos, logosView } from '../../core/shapes/presets.js';
4
+ import type { Snippet } from 'svelte';
5
+ import { type CmsLogosValue, type LogoItem, type LogoItemView } from '../../core/shapes/presets.js';
6
+ type Props = {
7
+ name: string;
8
+ scope?: string;
9
+ /** The full value, or a bare item list. */
10
+ fallback?: Partial<CmsLogosValue> | Partial<LogoItem>[];
11
+ value?: unknown;
12
+ children: Snippet<[LogoItemView[]]>;
13
+ };
14
+ declare const CmsLogos: import("svelte").Component<Props, {}, "">;
15
+ type CmsLogos = ReturnType<typeof CmsLogos>;
16
+ export default CmsLogos;
@@ -0,0 +1,22 @@
1
+ <script lang="ts" module>
2
+ /** `CmsNav`: site navigation. Lives in the root layout; other places read it with `scope="root"`. */
3
+ export * from '../../core/shapes/nav.js';
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import type { Snippet } from 'svelte';
8
+ import Structured from './cms-structured.svelte';
9
+ import { cmsNav, navView, type CmsNavValue, type NavItemView } from '../../core/shapes/nav.js';
10
+
11
+ type Props = {
12
+ name: string;
13
+ scope?: string;
14
+ /** The full value, or a bare item list. */
15
+ fallback?: Partial<CmsNavValue> | Array<Partial<CmsNavValue['items'][number]>>;
16
+ value?: unknown;
17
+ children: Snippet<[NavItemView[]]>;
18
+ };
19
+ let { name, scope, fallback, value, children }: Props = $props();
20
+ </script>
21
+
22
+ <Structured schema={cmsNav} {name} {scope} {fallback} {value} view={navView} {children} />
@@ -0,0 +1,15 @@
1
+ /** `CmsNav`: site navigation. Lives in the root layout; other places read it with `scope="root"`. */
2
+ export * from '../../core/shapes/nav.js';
3
+ import type { Snippet } from 'svelte';
4
+ import { type CmsNavValue, type NavItemView } from '../../core/shapes/nav.js';
5
+ type Props = {
6
+ name: string;
7
+ scope?: string;
8
+ /** The full value, or a bare item list. */
9
+ fallback?: Partial<CmsNavValue> | Array<Partial<CmsNavValue['items'][number]>>;
10
+ value?: unknown;
11
+ children: Snippet<[NavItemView[]]>;
12
+ };
13
+ declare const CmsNav: import("svelte").Component<Props, {}, "">;
14
+ type CmsNav = ReturnType<typeof CmsNav>;
15
+ export default CmsNav;
@@ -0,0 +1,36 @@
1
+ <script lang="ts" module>
2
+ /** `CmsPricing`: tiers with a feature checklist, formatted in the visitor's locale. */
3
+ export * from '../../core/shapes/pricing.js';
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import type { Snippet } from 'svelte';
8
+ import Structured from './cms-structured.svelte';
9
+ import { cmsStore } from './cms-store.svelte.js';
10
+ import {
11
+ cmsPricing,
12
+ pricingView,
13
+ type CmsPricingValue,
14
+ type PricingTierView
15
+ } from '../../core/shapes/pricing.js';
16
+
17
+ type Props = {
18
+ name: string;
19
+ scope?: string;
20
+ fallback?: CmsPricingValue;
21
+ value?: unknown;
22
+ children: Snippet<[PricingTierView[]]>;
23
+ };
24
+ let { name, scope, fallback, value, children }: Props = $props();
25
+ const locale = $derived(cmsStore.activeLocale || 'en');
26
+ </script>
27
+
28
+ <Structured
29
+ schema={cmsPricing}
30
+ {name}
31
+ {scope}
32
+ {fallback}
33
+ {value}
34
+ view={(v: CmsPricingValue) => pricingView(v, locale)}
35
+ {children}
36
+ />
@@ -0,0 +1,14 @@
1
+ /** `CmsPricing`: tiers with a feature checklist, formatted in the visitor's locale. */
2
+ export * from '../../core/shapes/pricing.js';
3
+ import type { Snippet } from 'svelte';
4
+ import { type CmsPricingValue, type PricingTierView } from '../../core/shapes/pricing.js';
5
+ type Props = {
6
+ name: string;
7
+ scope?: string;
8
+ fallback?: CmsPricingValue;
9
+ value?: unknown;
10
+ children: Snippet<[PricingTierView[]]>;
11
+ };
12
+ declare const CmsPricing: import("svelte").Component<Props, {}, "">;
13
+ type CmsPricing = ReturnType<typeof CmsPricing>;
14
+ export default CmsPricing;
@@ -0,0 +1,35 @@
1
+ <script lang="ts" module>
2
+ /** `CmsSchedule` preset: day, time, title, body, location. `scheduleByDay` groups them. */
3
+ export type { CmsScheduleValue, ScheduleItem } from '../../core/shapes/presets.js';
4
+ export { cmsSchedule, scheduleByDay } from '../../core/shapes/presets.js';
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import type { Snippet } from 'svelte';
9
+ import Structured from './cms-structured.svelte';
10
+ import {
11
+ cmsSchedule,
12
+ type CmsScheduleValue,
13
+ type ScheduleItem
14
+ } from '../../core/shapes/presets.js';
15
+
16
+ type Props = {
17
+ name: string;
18
+ scope?: string;
19
+ /** The full value, or a bare item list. */
20
+ fallback?: Partial<CmsScheduleValue> | Partial<ScheduleItem>[];
21
+ value?: unknown;
22
+ children: Snippet<[ScheduleItem[]]>;
23
+ };
24
+ let { name, scope, fallback, value, children }: Props = $props();
25
+ </script>
26
+
27
+ <Structured
28
+ schema={cmsSchedule}
29
+ {name}
30
+ {scope}
31
+ {fallback}
32
+ {value}
33
+ view={(v: CmsScheduleValue) => v.items}
34
+ {children}
35
+ />
@@ -0,0 +1,16 @@
1
+ /** `CmsSchedule` preset: day, time, title, body, location. `scheduleByDay` groups them. */
2
+ export type { CmsScheduleValue, ScheduleItem } from '../../core/shapes/presets.js';
3
+ export { cmsSchedule, scheduleByDay } from '../../core/shapes/presets.js';
4
+ import type { Snippet } from 'svelte';
5
+ import { type CmsScheduleValue, type ScheduleItem } from '../../core/shapes/presets.js';
6
+ type Props = {
7
+ name: string;
8
+ scope?: string;
9
+ /** The full value, or a bare item list. */
10
+ fallback?: Partial<CmsScheduleValue> | Partial<ScheduleItem>[];
11
+ value?: unknown;
12
+ children: Snippet<[ScheduleItem[]]>;
13
+ };
14
+ declare const CmsSchedule: import("svelte").Component<Props, {}, "">;
15
+ type CmsSchedule = ReturnType<typeof CmsSchedule>;
16
+ export default CmsSchedule;