@velastack/cms 0.3.1 → 0.5.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.
- package/README.md +107 -18
- package/dist/backend/index.d.ts +1 -1
- package/dist/backend/routes/release.js +2 -2
- 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/migrations/0004-release-publisher.d.ts +1 -0
- package/dist/backend/store/migrations/0004-release-publisher.js +15 -0
- package/dist/backend/store/migrations/index.js +3 -1
- package/dist/backend/store/queries.d.ts +9 -3
- package/dist/backend/store/queries.js +105 -11
- 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 +220 -0
- package/dist/components/admin-bar/field-input.svelte.d.ts +29 -0
- package/dist/components/admin-bar/history-panel.svelte +4 -5
- 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/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/reorder-buttons.svelte +52 -0
- package/dist/components/admin-bar/reorder-buttons.svelte.d.ts +10 -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 +250 -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-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-image-editable.svelte +13 -4
- package/dist/components/cms/cms-image.svelte +37 -30
- package/dist/components/cms/cms-image.svelte.d.ts +11 -4
- package/dist/components/cms/cms-link.svelte +26 -19
- package/dist/components/cms/cms-link.svelte.d.ts +2 -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-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-repeater.svelte +15 -18
- package/dist/components/cms/cms-repeater.svelte.d.ts +1 -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-store.svelte.d.ts +14 -0
- package/dist/components/cms/cms-store.svelte.js +39 -2
- package/dist/components/cms/cms-text.svelte +23 -30
- package/dist/components/cms/cms-text.svelte.d.ts +3 -1
- package/dist/components/cms/scope.d.ts +25 -5
- package/dist/components/cms/structured-registry.d.ts +10 -0
- package/dist/components/cms/structured-registry.js +6 -0
- 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/structured.d.ts +108 -0
- package/dist/core/structured.js +184 -0
- package/dist/core/wire.d.ts +42 -2
- package/dist/editor.d.ts +22 -0
- package/dist/editor.js +21 -0
- package/dist/index.d.ts +12 -4
- package/dist/index.js +13 -3
- package/dist/server/load-cms.js +1 -0
- package/package.json +15 -6
- 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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured values: the shapes behind the editor components (hours, team,
|
|
3
|
+
* testimonials, …) and the two rules every one of them follows.
|
|
4
|
+
*
|
|
5
|
+
* 1. **Versioned shapes, whole-object writes.** A structured value is a plain
|
|
6
|
+
* object with a `v` version number. Components never patch a leaf inside
|
|
7
|
+
* it; they read the whole value through the schema's `normalize`, change
|
|
8
|
+
* it, and write the whole object back. A field the editor clears is
|
|
9
|
+
* written as `null`, never dropped: the draft and release trees deep-merge
|
|
10
|
+
* over the published tree, and a missing key merges to "unchanged" while
|
|
11
|
+
* `null` merges to "cleared". That is what stops a cleared field from
|
|
12
|
+
* reverting to the template's demo copy.
|
|
13
|
+
*
|
|
14
|
+
* 2. **Localisation is a string overlay, never a second structure.** The
|
|
15
|
+
* default locale stores the full value at `<name>`. Every other locale
|
|
16
|
+
* stores only strings, at `<name>.$t.<id>.<field>`, keyed by the stable
|
|
17
|
+
* `id` of the list item they translate (the reserved id `_` addresses the
|
|
18
|
+
* value's own root fields). Because objects deep-merge across locales,
|
|
19
|
+
* reading `es` yields the default structure plus the `es` overlay, and
|
|
20
|
+
* {@link applyTranslations} folds the strings in. Structure edits happen in
|
|
21
|
+
* the default locale only, so the item list can never fork between
|
|
22
|
+
* languages.
|
|
23
|
+
*/
|
|
24
|
+
import { get } from './path.js';
|
|
25
|
+
/** Branch under a structured value that holds a locale's string overlay. */
|
|
26
|
+
export const TRANSLATIONS_KEY = '$t';
|
|
27
|
+
/** Overlay id that addresses the structured value's root fields. */
|
|
28
|
+
export const ROOT_ID = '_';
|
|
29
|
+
export const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
30
|
+
export const defineStructured = (schema) => ({
|
|
31
|
+
...schema,
|
|
32
|
+
read: (raw, fallback) => {
|
|
33
|
+
if (raw === undefined)
|
|
34
|
+
return fallback === undefined ? schema.empty() : fallback;
|
|
35
|
+
if (raw === null)
|
|
36
|
+
return schema.empty();
|
|
37
|
+
const overlay = extractTranslations(raw);
|
|
38
|
+
const value = schema.normalize(stripTranslations(raw));
|
|
39
|
+
return overlay ? applyTranslations(value, overlay, schema) : value;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
/** Path of one translated string: `<name>.$t.<id>.<field>`. */
|
|
43
|
+
export const translationPath = (name, id, field) => `${name}.${TRANSLATIONS_KEY}.${id}.${field}`;
|
|
44
|
+
/** The `$t` branch of a stored value, when it is one. */
|
|
45
|
+
export const extractTranslations = (raw) => {
|
|
46
|
+
if (!isPlainObject(raw))
|
|
47
|
+
return undefined;
|
|
48
|
+
const t = raw[TRANSLATIONS_KEY];
|
|
49
|
+
if (!isPlainObject(t))
|
|
50
|
+
return undefined;
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const [id, fields] of Object.entries(t)) {
|
|
53
|
+
if (!isPlainObject(fields))
|
|
54
|
+
continue;
|
|
55
|
+
const strings = {};
|
|
56
|
+
for (const [field, v] of Object.entries(fields)) {
|
|
57
|
+
if (typeof v === 'string')
|
|
58
|
+
strings[field] = v;
|
|
59
|
+
}
|
|
60
|
+
out[id] = strings;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
};
|
|
64
|
+
/** A copy of `raw` without its `$t` branch, so a whole-object write in the
|
|
65
|
+
* default locale never carries another locale's strings. */
|
|
66
|
+
export const stripTranslations = (raw) => {
|
|
67
|
+
if (!isPlainObject(raw) || !(TRANSLATIONS_KEY in raw))
|
|
68
|
+
return raw;
|
|
69
|
+
const { [TRANSLATIONS_KEY]: _omit, ...rest } = raw;
|
|
70
|
+
return rest;
|
|
71
|
+
};
|
|
72
|
+
const overlayString = (overlay, id, field) => {
|
|
73
|
+
const v = overlay[id]?.[field];
|
|
74
|
+
return typeof v === 'string' && v !== '' ? v : undefined;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Fold a locale's string overlay into a normalized default-locale value.
|
|
78
|
+
* Root fields come from the `_` id; item fields from the item's own `id`.
|
|
79
|
+
* Only non-empty strings override, so an untranslated field shows the
|
|
80
|
+
* default-locale text rather than a blank.
|
|
81
|
+
*/
|
|
82
|
+
export const applyTranslations = (value, overlay, schema) => {
|
|
83
|
+
if (!overlay)
|
|
84
|
+
return value;
|
|
85
|
+
const out = { ...value };
|
|
86
|
+
for (const field of schema.translatable) {
|
|
87
|
+
const s = overlayString(overlay, ROOT_ID, field);
|
|
88
|
+
if (s !== undefined)
|
|
89
|
+
out[field] = s;
|
|
90
|
+
}
|
|
91
|
+
if (schema.items) {
|
|
92
|
+
const list = value[schema.items.key];
|
|
93
|
+
if (Array.isArray(list)) {
|
|
94
|
+
out[schema.items.key] = list.map((item) => {
|
|
95
|
+
if (!isPlainObject(item) || typeof item.id !== 'string')
|
|
96
|
+
return item;
|
|
97
|
+
const next = { ...item };
|
|
98
|
+
for (const field of schema.items.translatable) {
|
|
99
|
+
const s = overlayString(overlay, item.id, field);
|
|
100
|
+
if (s !== undefined)
|
|
101
|
+
next[field] = s;
|
|
102
|
+
}
|
|
103
|
+
return next;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Every translatable, non-empty string in a normalized default-locale value.
|
|
111
|
+
* Drives the popover's translation tab and the Locales panel's counts.
|
|
112
|
+
*/
|
|
113
|
+
export const translatableFields = (value, schema) => {
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const field of schema.translatable) {
|
|
116
|
+
const s = value[field];
|
|
117
|
+
if (typeof s === 'string' && s !== '')
|
|
118
|
+
out.push({ id: ROOT_ID, field, source: s });
|
|
119
|
+
}
|
|
120
|
+
if (schema.items) {
|
|
121
|
+
const list = value[schema.items.key];
|
|
122
|
+
if (Array.isArray(list)) {
|
|
123
|
+
for (const item of list) {
|
|
124
|
+
if (!isPlainObject(item) || typeof item.id !== 'string')
|
|
125
|
+
continue;
|
|
126
|
+
for (const field of schema.items.translatable) {
|
|
127
|
+
const s = item[field];
|
|
128
|
+
if (typeof s === 'string' && s !== '')
|
|
129
|
+
out.push({ id: item.id, field, source: s });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
};
|
|
136
|
+
/** How many translatable strings a locale's overlay still lacks. */
|
|
137
|
+
export const countTranslations = (value, overlay, schema) => {
|
|
138
|
+
const fields = translatableFields(value, schema);
|
|
139
|
+
let missing = 0;
|
|
140
|
+
for (const f of fields) {
|
|
141
|
+
if (!overlay || overlayString(overlay, f.id, f.field) === undefined)
|
|
142
|
+
missing += 1;
|
|
143
|
+
}
|
|
144
|
+
return { total: fields.length, missing };
|
|
145
|
+
};
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Normalizer building blocks
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
export const asString = (v, fallback = '') => typeof v === 'string' ? v : fallback;
|
|
150
|
+
export const asNumber = (v, fallback = null) => typeof v === 'number' && Number.isFinite(v) ? v : fallback;
|
|
151
|
+
export const asBoolean = (v, fallback = false) => typeof v === 'boolean' ? v : fallback;
|
|
152
|
+
/**
|
|
153
|
+
* Normalize a list of items, guaranteeing every item carries a string `id`.
|
|
154
|
+
* An item without one (a hand-written seed, say) gets a deterministic
|
|
155
|
+
* `item-<index>` so reads stay stable until the editor next writes the list,
|
|
156
|
+
* at which point the ids are persisted.
|
|
157
|
+
*/
|
|
158
|
+
export const asItems = (raw, normalizeItem) => {
|
|
159
|
+
if (!Array.isArray(raw))
|
|
160
|
+
return [];
|
|
161
|
+
const out = [];
|
|
162
|
+
raw.forEach((entry, index) => {
|
|
163
|
+
if (!isPlainObject(entry))
|
|
164
|
+
return;
|
|
165
|
+
const id = typeof entry.id === 'string' && entry.id !== '' ? entry.id : `item-${index}`;
|
|
166
|
+
out.push({ id, ...normalizeItem(entry, index) });
|
|
167
|
+
});
|
|
168
|
+
return out;
|
|
169
|
+
};
|
|
170
|
+
/** A short random id for a newly added item. Stable once written. */
|
|
171
|
+
export const newItemId = () => {
|
|
172
|
+
const bytes = new Uint8Array(6);
|
|
173
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues)
|
|
174
|
+
crypto.getRandomValues(bytes);
|
|
175
|
+
else
|
|
176
|
+
for (let i = 0; i < bytes.length; i++)
|
|
177
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
178
|
+
let out = '';
|
|
179
|
+
for (const b of bytes)
|
|
180
|
+
out += b.toString(16).padStart(2, '0');
|
|
181
|
+
return out;
|
|
182
|
+
};
|
|
183
|
+
/** Read a structured value's overlay for one locale out of a whole scope tree. */
|
|
184
|
+
export const overlayAt = (tree, name) => extractTranslations(get(tree, name));
|
package/dist/core/wire.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* `published_at` stay snake_case because that is what the JSON carries.
|
|
12
12
|
*/
|
|
13
13
|
import type { Tree } from './path.js';
|
|
14
|
-
import type { PageDeleteOutcome } from './page-entry.js';
|
|
14
|
+
import type { PageDeleteOutcome, PageEntry } from './page-entry.js';
|
|
15
15
|
export type { PageDeleteOutcome };
|
|
16
16
|
/** One pending change in a release, without the bookkeeping the store adds. */
|
|
17
17
|
export type ReleaseItemCore = {
|
|
@@ -54,10 +54,20 @@ export type PublishedReleaseItem = ReleaseItemCore & {
|
|
|
54
54
|
priorTree: Tree | null;
|
|
55
55
|
priorTombstone?: PageDeleteOutcome;
|
|
56
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* The editor who published a release, captured at publish time. `email` and
|
|
59
|
+
* `name` are null only on rows published before they were recorded, where the
|
|
60
|
+
* account no longer resolves.
|
|
61
|
+
*/
|
|
62
|
+
export type ReleasePublisher = {
|
|
63
|
+
id: string;
|
|
64
|
+
email: string | null;
|
|
65
|
+
name: string | null;
|
|
66
|
+
};
|
|
57
67
|
export type PublishedRelease = {
|
|
58
68
|
id: string;
|
|
59
69
|
name?: string;
|
|
60
|
-
publishedBy:
|
|
70
|
+
publishedBy: ReleasePublisher;
|
|
61
71
|
publishedAt: string;
|
|
62
72
|
preview_key: string;
|
|
63
73
|
items: PublishedReleaseItem[];
|
|
@@ -172,6 +182,36 @@ export type CmsDeployState = {
|
|
|
172
182
|
};
|
|
173
183
|
latest?: CmsDeployRun | null;
|
|
174
184
|
};
|
|
185
|
+
/**
|
|
186
|
+
* A project's published content in one object: `layouts` and `pages` keyed
|
|
187
|
+
* `[locale][routeId]`, plus the non-localised `site` tree. `POST /seed`
|
|
188
|
+
* accepts it and `GET /export` returns it, so a template's published
|
|
189
|
+
* `content/` can seed a fresh project and a project can be dumped back into
|
|
190
|
+
* the same shape. A page keyed by route id may be a bare tree (a static page,
|
|
191
|
+
* `params: {}`) or a full `PageEntry[]`.
|
|
192
|
+
*/
|
|
193
|
+
export type CmsSeed = {
|
|
194
|
+
layouts?: Record<string, Record<string, Tree>>;
|
|
195
|
+
pages?: Record<string, Record<string, Tree | PageEntry[]>>;
|
|
196
|
+
site?: Tree;
|
|
197
|
+
};
|
|
198
|
+
export type SeedRequest = CmsSeed & {
|
|
199
|
+
/** Overwrite a project that already has published rows. */
|
|
200
|
+
force?: boolean;
|
|
201
|
+
};
|
|
202
|
+
export type SeedSummary = {
|
|
203
|
+
locales: string[];
|
|
204
|
+
layouts: number;
|
|
205
|
+
pages: number;
|
|
206
|
+
site: boolean;
|
|
207
|
+
};
|
|
208
|
+
export type SeedResponse = {
|
|
209
|
+
ok: true;
|
|
210
|
+
seeded: SeedSummary;
|
|
211
|
+
} | {
|
|
212
|
+
ok: false;
|
|
213
|
+
reason: 'already-seeded';
|
|
214
|
+
};
|
|
175
215
|
export type DocsResponse = {
|
|
176
216
|
contents: Tree;
|
|
177
217
|
} | PageDeleteOutcome;
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@velastack/cms/editor` — the editing chrome a structured component's
|
|
3
|
+
* editable sibling imports lazily, so none of it reaches the public bundle:
|
|
4
|
+
*
|
|
5
|
+
* ```svelte
|
|
6
|
+
* {#await import('@velastack/cms/editor') then { StructuredEditorPopover, FieldInput, ReorderButtons }}
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export { default as StructuredEditorPopover } from './components/admin-bar/structured-editor-popover.svelte';
|
|
10
|
+
export { default as FieldInput } from './components/admin-bar/field-input.svelte';
|
|
11
|
+
export type { FieldInputType } from './components/admin-bar/field-input.svelte';
|
|
12
|
+
export { default as ReorderButtons } from './components/admin-bar/reorder-buttons.svelte';
|
|
13
|
+
export { default as CssRoot } from './components/admin-bar/css-root.svelte';
|
|
14
|
+
export { Button } from './components/admin-bar/ui/button/index.js';
|
|
15
|
+
export { Input } from './components/admin-bar/ui/input/index.js';
|
|
16
|
+
export { Textarea } from './components/admin-bar/ui/textarea/index.js';
|
|
17
|
+
export { Switch } from './components/admin-bar/ui/switch/index.js';
|
|
18
|
+
export { Checkbox } from './components/admin-bar/ui/checkbox/index.js';
|
|
19
|
+
export * as Popover from './components/admin-bar/ui/popover/index.js';
|
|
20
|
+
export * as Tabs from './components/admin-bar/ui/tabs/index.js';
|
|
21
|
+
export * as Sheet from './components/admin-bar/ui/sheet/index.js';
|
|
22
|
+
export { default as CmsMediaPicker } from './components/cms/cms-media-picker.svelte';
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@velastack/cms/editor` — the editing chrome a structured component's
|
|
3
|
+
* editable sibling imports lazily, so none of it reaches the public bundle:
|
|
4
|
+
*
|
|
5
|
+
* ```svelte
|
|
6
|
+
* {#await import('@velastack/cms/editor') then { StructuredEditorPopover, FieldInput, ReorderButtons }}
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export { default as StructuredEditorPopover } from './components/admin-bar/structured-editor-popover.svelte';
|
|
10
|
+
export { default as FieldInput } from './components/admin-bar/field-input.svelte';
|
|
11
|
+
export { default as ReorderButtons } from './components/admin-bar/reorder-buttons.svelte';
|
|
12
|
+
export { default as CssRoot } from './components/admin-bar/css-root.svelte';
|
|
13
|
+
export { Button } from './components/admin-bar/ui/button/index.js';
|
|
14
|
+
export { Input } from './components/admin-bar/ui/input/index.js';
|
|
15
|
+
export { Textarea } from './components/admin-bar/ui/textarea/index.js';
|
|
16
|
+
export { Switch } from './components/admin-bar/ui/switch/index.js';
|
|
17
|
+
export { Checkbox } from './components/admin-bar/ui/checkbox/index.js';
|
|
18
|
+
export * as Popover from './components/admin-bar/ui/popover/index.js';
|
|
19
|
+
export * as Tabs from './components/admin-bar/ui/tabs/index.js';
|
|
20
|
+
export * as Sheet from './components/admin-bar/ui/sheet/index.js';
|
|
21
|
+
export { default as CmsMediaPicker } from './components/cms/cms-media-picker.svelte';
|
package/dist/index.d.ts
CHANGED
|
@@ -7,21 +7,29 @@
|
|
|
7
7
|
export { default as CmsText } from './components/cms/cms-text.svelte';
|
|
8
8
|
export { default as CmsRichText } from './components/cms/cms-rich-text.svelte';
|
|
9
9
|
export { default as CmsMarkdown } from './components/cms/cms-markdown.svelte';
|
|
10
|
-
export { default as CmsImage } from './components/cms/cms-image.svelte';
|
|
10
|
+
export { default as CmsImage, normalizeImage } from './components/cms/cms-image.svelte';
|
|
11
|
+
export type { CmsImageValue } from './components/cms/cms-image.svelte';
|
|
11
12
|
export { default as CmsBoolean } from './components/cms/cms-boolean.svelte';
|
|
12
13
|
export { default as CmsNumber } from './components/cms/cms-number.svelte';
|
|
13
14
|
export type { CmsNumberProps } from './components/cms/cms-number.svelte';
|
|
14
15
|
export { default as CmsDateTime } from './components/cms/cms-date-time.svelte';
|
|
15
16
|
export type { CmsDateTimeMode, CmsDateTimeProps } from './components/cms/cms-date-time.svelte';
|
|
16
|
-
export { default as CmsLink } from './components/cms/cms-link.svelte';
|
|
17
|
+
export { default as CmsLink, normalizeLink } from './components/cms/cms-link.svelte';
|
|
17
18
|
export type { CmsLinkValue, CmsLinkRenderProps } from './components/cms/cms-link.svelte';
|
|
18
19
|
export { default as CmsRepeater } from './components/cms/cms-repeater.svelte';
|
|
19
20
|
export { default as CmsEntries } from './components/cms/cms-entries.svelte';
|
|
20
21
|
export { default as AdminBar } from './components/admin-bar/admin-bar.svelte';
|
|
21
|
-
export {
|
|
22
|
+
export { useCmsField, resolveScopeRef, ROOT_SCOPE_ID } from './components/cms/use-cms-field.svelte.js';
|
|
23
|
+
export type { CmsField, CmsFieldInput } from './components/cms/use-cms-field.svelte.js';
|
|
24
|
+
export { defineStructured, applyTranslations, countTranslations, extractTranslations, stripTranslations, translatableFields, translationPath, asBoolean, asItems, asNumber, asString, newItemId, ROOT_ID, TRANSLATIONS_KEY } from './core/structured.js';
|
|
25
|
+
export type { Structured, StructuredItem, StructuredSchema, TranslatableField, TranslationOverlay } from './core/structured.js';
|
|
26
|
+
export { registerStructured, getStructured } from './components/cms/structured-registry.js';
|
|
27
|
+
export { toMetaTags } from './core/metadata.js';
|
|
28
|
+
export type { MetaTagsOptions, MetaTagsOutput } from './core/metadata.js';
|
|
29
|
+
export { definePage, DEFAULT_METADATA_SCHEMA, metadataSchemaFor } from './components/admin-bar/page-config.js';
|
|
22
30
|
export type { CmsPageConfig, CmsCreatablePageConfig, CmsStaticPageConfig, CmsPageField, CmsPageMetadataSchema, MetadataFieldSchema, MetadataPrimitive } from './components/admin-bar/page-config.js';
|
|
23
31
|
export { cmsStore } from './components/cms/cms-store.svelte.js';
|
|
24
32
|
export { cms } from './components/cms/cms-store.svelte.js';
|
|
25
33
|
export { CMS_SCOPE, getCmsScope, getCmsValue, getPageScope } from './components/cms/scope.js';
|
|
26
34
|
export { installCmsScope } from './components/cms/install-scope.svelte.js';
|
|
27
|
-
export type { CmsEntry, CmsManifest, CmsManifestRoute, CmsManifestScope, CmsPayload, CmsScope, CmsScopeEntry } from './components/cms/scope.js';
|
|
35
|
+
export type { CmsEntry, CmsManifest, CmsManifestRoute, CmsManifestScope, CmsManifestUsage, CmsPayload, CmsScope, CmsScopeEntry, SiteFieldSchema, SiteFieldType, SiteSchema } from './components/cms/scope.js';
|
package/dist/index.js
CHANGED
|
@@ -8,17 +8,27 @@
|
|
|
8
8
|
export { default as CmsText } from './components/cms/cms-text.svelte';
|
|
9
9
|
export { default as CmsRichText } from './components/cms/cms-rich-text.svelte';
|
|
10
10
|
export { default as CmsMarkdown } from './components/cms/cms-markdown.svelte';
|
|
11
|
-
export { default as CmsImage } from './components/cms/cms-image.svelte';
|
|
11
|
+
export { default as CmsImage, normalizeImage } from './components/cms/cms-image.svelte';
|
|
12
12
|
export { default as CmsBoolean } from './components/cms/cms-boolean.svelte';
|
|
13
13
|
export { default as CmsNumber } from './components/cms/cms-number.svelte';
|
|
14
14
|
export { default as CmsDateTime } from './components/cms/cms-date-time.svelte';
|
|
15
|
-
export { default as CmsLink } from './components/cms/cms-link.svelte';
|
|
15
|
+
export { default as CmsLink, normalizeLink } from './components/cms/cms-link.svelte';
|
|
16
16
|
export { default as CmsRepeater } from './components/cms/cms-repeater.svelte';
|
|
17
17
|
export { default as CmsEntries } from './components/cms/cms-entries.svelte';
|
|
18
18
|
// Admin bar (sync wrapper; admin-bar-internal + seo-panel are async chunks)
|
|
19
19
|
export { default as AdminBar } from './components/admin-bar/admin-bar.svelte';
|
|
20
|
+
// Building blocks for structured components: the field hook, the value
|
|
21
|
+
// rules (versioned shapes, `$t` overlay) and the registry the Locales panel
|
|
22
|
+
// reads. The editor popover and field inputs are admin-bar chunks, imported
|
|
23
|
+
// lazily by editable siblings:
|
|
24
|
+
// `import('@velastack/cms/editor')`
|
|
25
|
+
export { useCmsField, resolveScopeRef, ROOT_SCOPE_ID } from './components/cms/use-cms-field.svelte.js';
|
|
26
|
+
export { defineStructured, applyTranslations, countTranslations, extractTranslations, stripTranslations, translatableFields, translationPath, asBoolean, asItems, asNumber, asString, newItemId, ROOT_ID, TRANSLATIONS_KEY } from './core/structured.js';
|
|
27
|
+
export { registerStructured, getStructured } from './components/cms/structured-registry.js';
|
|
28
|
+
// Page metadata → svelte-meta-tags props.
|
|
29
|
+
export { toMetaTags } from './core/metadata.js';
|
|
20
30
|
// Per-route page configuration — declare in `page.cms.ts` next to `+page.svelte`.
|
|
21
|
-
export { definePage } from './components/admin-bar/page-config.js';
|
|
31
|
+
export { definePage, DEFAULT_METADATA_SCHEMA, metadataSchemaFor } from './components/admin-bar/page-config.js';
|
|
22
32
|
// Edit-mode store
|
|
23
33
|
export { cmsStore } from './components/cms/cms-store.svelte.js';
|
|
24
34
|
// Merged CMS read view (server payload + client overlay), shaped like CmsPayload
|
package/dist/server/load-cms.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velastack/cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A scope-aware CMS for SvelteKit, for static or dynamic sites.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"engines": {
|
|
25
|
-
"node": "
|
|
25
|
+
"node": ">=22.12.0"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"dev": "vite dev",
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
"svelte": "./dist/index.js",
|
|
58
58
|
"default": "./dist/index.js"
|
|
59
59
|
},
|
|
60
|
+
"./editor": {
|
|
61
|
+
"types": "./dist/editor.d.ts",
|
|
62
|
+
"svelte": "./dist/editor.js",
|
|
63
|
+
"default": "./dist/editor.js"
|
|
64
|
+
},
|
|
60
65
|
"./server": {
|
|
61
66
|
"types": "./dist/server/index.d.ts",
|
|
62
67
|
"default": "./dist/server/index.js"
|
|
@@ -72,11 +77,15 @@
|
|
|
72
77
|
"./adapter": {
|
|
73
78
|
"types": "./plugin/dist/adapter.d.ts",
|
|
74
79
|
"default": "./plugin/dist/adapter.js"
|
|
80
|
+
},
|
|
81
|
+
"./build": {
|
|
82
|
+
"types": "./plugin/dist/build.d.ts",
|
|
83
|
+
"default": "./plugin/dist/build.js"
|
|
75
84
|
}
|
|
76
85
|
},
|
|
77
86
|
"peerDependencies": {
|
|
78
87
|
"@sveltejs/kit": "^2.0.0",
|
|
79
|
-
"better-sqlite3": "^
|
|
88
|
+
"better-sqlite3": "^12.0.0 || ^13.0.0",
|
|
80
89
|
"marked": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
|
81
90
|
"svelte": "^5.0.0",
|
|
82
91
|
"vite": "^8.0.0"
|
|
@@ -98,7 +107,7 @@
|
|
|
98
107
|
"@tailwindcss/cli": "^4.3.3",
|
|
99
108
|
"@tailwindcss/vite": "^4.3.3",
|
|
100
109
|
"@types/node": "^26.4.0",
|
|
101
|
-
"better-sqlite3": "^
|
|
110
|
+
"better-sqlite3": "^13.0.3",
|
|
102
111
|
"marked": "^18.0.11",
|
|
103
112
|
"prettier": "^3.9.6",
|
|
104
113
|
"prettier-plugin-svelte": "^4.1.1",
|
|
@@ -110,7 +119,7 @@
|
|
|
110
119
|
"tw-animate-css": "^1.4.0",
|
|
111
120
|
"typescript": "^6.0.3",
|
|
112
121
|
"vite": "^8.2.2",
|
|
113
|
-
"vitest": "^
|
|
122
|
+
"vitest": "^5.0.1"
|
|
114
123
|
},
|
|
115
124
|
"dependencies": {
|
|
116
125
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
@@ -119,7 +128,7 @@
|
|
|
119
128
|
"@tiptap/pm": "^3.30.6",
|
|
120
129
|
"@tiptap/starter-kit": "^3.30.6",
|
|
121
130
|
"bits-ui": "^2.19.0",
|
|
122
|
-
"devalue": "^
|
|
131
|
+
"devalue": "^6.0.0",
|
|
123
132
|
"tailwind-merge": "^3.6.0"
|
|
124
133
|
}
|
|
125
134
|
}
|
package/plugin/README.md
CHANGED
|
@@ -31,6 +31,11 @@ type CmsPluginOptions = {
|
|
|
31
31
|
libDir?: string; // default: 'src/lib'
|
|
32
32
|
components?: ExternalCmsComponentSpec[];
|
|
33
33
|
traverse?: (string | RegExp)[];
|
|
34
|
+
endpoint?: string; // media download at build time
|
|
35
|
+
locales?: string[]; // default: ['en']; locales[0] is the default locale
|
|
36
|
+
mediaDir?: string;
|
|
37
|
+
mediaPrefix?: string;
|
|
38
|
+
content?: string | false; // default: 'content' — see "Content manifest"
|
|
34
39
|
};
|
|
35
40
|
|
|
36
41
|
type ExternalCmsComponentSpec =
|
|
@@ -48,6 +53,14 @@ type ExternalCmsComponentSpec =
|
|
|
48
53
|
`source === pattern || source.startsWith(pattern + '/')`. Auto-populated with
|
|
49
54
|
every `components[].source`.
|
|
50
55
|
|
|
56
|
+
- `content` — directory of per-locale content manifests. See below.
|
|
57
|
+
|
|
58
|
+
## Content manifest
|
|
59
|
+
|
|
60
|
+
When `<content>/<defaultLocale>.json` exists, the plugin reads it — an object keyed by scope id (`layout:/`, `page:/(public)/about`, `site`) — and injects each usage's value as a `fallback={…}` prop (`initial` for `CmsBoolean`) into every walked `.svelte` file, route entrypoints and shared components alike. A `scope="root"` usage reads `layout:/`; any other usage reads the scopes that reach its file, leaf first. Components in source stay name-only; the value the visitor sees before the CMS answers comes from the manifest. The file is watched, so edits apply in dev.
|
|
61
|
+
|
|
62
|
+
The same operation is available to other tooling from `@velastack/cms/build` (`scanUsages`, `injectFallbacks` / `inlineFallbacks`, `buildManifest`, `fallbackResolverFor`), so `pack` and `verify` in a templates repo share this parser instead of carrying their own.
|
|
63
|
+
|
|
51
64
|
## Virtual modules
|
|
52
65
|
|
|
53
66
|
### `virtual:vela-cms/manifest`
|
|
@@ -57,6 +70,7 @@ import { cmsManifest } from 'virtual:vela-cms/manifest';
|
|
|
57
70
|
|
|
58
71
|
type CmsManifest = {
|
|
59
72
|
version: 1;
|
|
73
|
+
// each scope lists `fields` (names) and `usages` ({ name, component, preset? })
|
|
60
74
|
routes: Record<string, { scopes: CmsManifestScope[] }>;
|
|
61
75
|
};
|
|
62
76
|
|
|
@@ -107,6 +121,8 @@ For each route entrypoint (`+page.svelte` or `+layout.svelte`), the plugin
|
|
|
107
121
|
parses with `svelte/compiler` and follows static imports of `.svelte` files.
|
|
108
122
|
Imports get classified as CMS components by:
|
|
109
123
|
|
|
124
|
+
0. **`@velastack/cms` by name** — every named import from the package
|
|
125
|
+
specifier, with no resolver needed.
|
|
110
126
|
1. **In-tree barrel** — anything from `<libDir>/components/cms/index.{ts,js}`.
|
|
111
127
|
Named imports are CMS components; default isn't.
|
|
112
128
|
2. **In-tree files** — `.svelte` files directly under `<libDir>/components/cms/`.
|
|
@@ -116,8 +132,12 @@ Imports get classified as CMS components by:
|
|
|
116
132
|
4. **External `components` spec** — user-declared packs.
|
|
117
133
|
|
|
118
134
|
Component usages with a `name=<static>` prop are recorded as field
|
|
119
|
-
references; `value=<…>` opts the call out (it's a
|
|
120
|
-
field).
|
|
135
|
+
references with their component type; `value=<…>` opts the call out (it's a
|
|
136
|
+
runtime override, not a field). A `scope="root"` (or `scope="layout:/x"`)
|
|
137
|
+
attribute records the usage under that layout scope instead of the file's
|
|
138
|
+
own. Dynamic `name={expr}` props and `scope=` attributes naming a scope
|
|
139
|
+
outside the route's chain are reported as warnings on the build result and
|
|
140
|
+
logged by the plugin.
|
|
121
141
|
|
|
122
142
|
## HMR
|
|
123
143
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { buildManifest, resolveScopeAttr, ROOT_SCOPE_ID, type BuildManifestOptions, type BuildManifestResult, type CmsManifest, type CmsManifestScope, type CmsManifestUsage } from './manifest.js';
|
|
2
|
+
export { buildManifest, resolveScopeAttr, ROOT_SCOPE_ID };
|
|
3
|
+
export type Tree = Record<string, unknown>;
|
|
4
|
+
export type { BuildManifestOptions, BuildManifestResult, CmsManifest, CmsManifestScope, CmsManifestUsage };
|
|
5
|
+
export type CmsUsage = {
|
|
6
|
+
/** Static `name=` literal, or `null` when absent or dynamic. */
|
|
7
|
+
name: string | null;
|
|
8
|
+
/** Canonical exported component name (`CmsText`, `CmsHours`, …). */
|
|
9
|
+
component: string;
|
|
10
|
+
/** Local binding used in the template. */
|
|
11
|
+
local: string;
|
|
12
|
+
/** Resolved scope id from `scope=`, or `null` for the enclosing route scope. */
|
|
13
|
+
scope: string | null;
|
|
14
|
+
preset: string | null;
|
|
15
|
+
/** `name=` was present but not a static string. */
|
|
16
|
+
dynamic: boolean;
|
|
17
|
+
hasValue: boolean;
|
|
18
|
+
hasFallback: boolean;
|
|
19
|
+
line: number;
|
|
20
|
+
/** Source offsets of the component node. */
|
|
21
|
+
start: number;
|
|
22
|
+
end: number;
|
|
23
|
+
};
|
|
24
|
+
export type ScanOptions = {
|
|
25
|
+
filename?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Extra local names to treat as CMS components, for wrappers a template
|
|
28
|
+
* declares itself. Package imports and `$lib/components/cms/*` are always
|
|
29
|
+
* recognized.
|
|
30
|
+
*/
|
|
31
|
+
components?: string[];
|
|
32
|
+
};
|
|
33
|
+
export type ScanResult = {
|
|
34
|
+
usages: CmsUsage[];
|
|
35
|
+
/** Dynamic names, which no manifest can record. */
|
|
36
|
+
warnings: string[];
|
|
37
|
+
};
|
|
38
|
+
/** Every CMS component usage in one `.svelte` source. */
|
|
39
|
+
export declare const scanUsages: (source: string, options?: ScanOptions) => ScanResult;
|
|
40
|
+
/** Returns the value to inject for a usage, or `undefined` to leave it alone. */
|
|
41
|
+
export type FallbackResolver = (usage: CmsUsage) => unknown;
|
|
42
|
+
export type InjectOptions = ScanOptions & {
|
|
43
|
+
/** Replace an existing `fallback` / `initial` prop instead of skipping it. */
|
|
44
|
+
overwrite?: boolean;
|
|
45
|
+
};
|
|
46
|
+
export type InjectResult = {
|
|
47
|
+
code: string;
|
|
48
|
+
/** Names that received a fallback. */
|
|
49
|
+
injected: string[];
|
|
50
|
+
/** Static names the resolver had no value for. */
|
|
51
|
+
missing: string[];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Write values into a file's CMS usages as `fallback={…}` props (the JSON
|
|
55
|
+
* literal of the value), leaving the source otherwise untouched. `resolve`
|
|
56
|
+
* is a content tree keyed by name, or a function for callers that know
|
|
57
|
+
* which scope each usage's value lives in (see {@link fallbackResolverFor}).
|
|
58
|
+
*
|
|
59
|
+
* Usages with a `value=` prop, a dynamic name, or (unless `overwrite`) an
|
|
60
|
+
* existing fallback are skipped. The plugin calls this in memory at build
|
|
61
|
+
* time in the templates repo; `pack` calls it to write the tarball's
|
|
62
|
+
* components, which is why it is also exported as {@link inlineFallbacks}.
|
|
63
|
+
*/
|
|
64
|
+
export declare const injectFallbacks: (source: string, resolve: Tree | FallbackResolver, options?: InjectOptions) => InjectResult;
|
|
65
|
+
/** Same operation as {@link injectFallbacks}, named for the `pack` step that
|
|
66
|
+
* writes the result to disk. */
|
|
67
|
+
export declare const inlineFallbacks: (source: string, resolve: Tree | FallbackResolver, options?: InjectOptions) => InjectResult;
|
|
68
|
+
/**
|
|
69
|
+
* A resolver for one file, given a built manifest and a content manifest
|
|
70
|
+
* keyed by scope id (`layout:/`, `page:/(public)/about`, …). A usage with
|
|
71
|
+
* `scope=` reads that scope; otherwise the scopes that reach the file are
|
|
72
|
+
* tried leaf first, and the first one holding the value wins.
|
|
73
|
+
*/
|
|
74
|
+
export declare const fallbackResolverFor: (result: Pick<BuildManifestResult, "scopesByFile">, file: string, content: Record<string, Tree>) => FallbackResolver;
|