includio-cms 0.5.3 → 0.5.7
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/CHANGELOG.md +71 -0
- package/ROADMAP.md +33 -0
- package/dist/admin/api/rest/handler.d.ts +7 -0
- package/dist/admin/api/rest/handler.js +116 -0
- package/dist/admin/api/rest/middleware/apiKey.d.ts +6 -0
- package/dist/admin/api/rest/middleware/apiKey.js +45 -0
- package/dist/admin/api/rest/routes/collections.d.ts +5 -0
- package/dist/admin/api/rest/routes/collections.js +104 -0
- package/dist/admin/api/rest/routes/entries.d.ts +2 -0
- package/dist/admin/api/rest/routes/entries.js +37 -0
- package/dist/admin/api/rest/routes/languages.d.ts +1 -0
- package/dist/admin/api/rest/routes/languages.js +5 -0
- package/dist/admin/api/rest/routes/schema.d.ts +2 -0
- package/dist/admin/api/rest/routes/schema.js +78 -0
- package/dist/admin/api/rest/routes/singletons.d.ts +3 -0
- package/dist/admin/api/rest/routes/singletons.js +60 -0
- package/dist/admin/auth-client.d.ts +48 -48
- package/dist/admin/client/admin/admin-after-login-layout.svelte +5 -3
- package/dist/admin/client/admin/admin-layout.svelte +0 -2
- package/dist/admin/client/collection/bulk-actions-bar.svelte +3 -1
- package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +1 -1
- package/dist/admin/client/collection/collection-entries.svelte +334 -166
- package/dist/admin/client/collection/collection-page.svelte +6 -3
- package/dist/admin/client/collection/data-table.svelte +127 -18
- package/dist/admin/client/collection/data-table.svelte.d.ts +2 -0
- package/dist/admin/client/collection/row-actions.svelte +3 -1
- package/dist/admin/client/collection/row-actions.svelte.d.ts +1 -1
- package/dist/admin/client/entry/entry-page.svelte +5 -4
- package/dist/admin/client/form/form-config-wrapper.svelte +5 -3
- package/dist/admin/client/form/form-page.svelte +5 -5
- package/dist/admin/client/form/form-submission/form-submission-page.svelte +30 -32
- package/dist/admin/components/accessibility/accessibility-overview.svelte +7 -5
- package/dist/admin/components/accessibility/accessibility-overview.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/a11y-gauge.svelte +7 -4
- package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/accessibility-hub.svelte +7 -4
- package/dist/admin/components/dashboard/accessibility-hub.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +8 -3
- package/dist/admin/components/dashboard/form-submissions-widget.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/recent-activity.svelte +8 -3
- package/dist/admin/components/dashboard/recent-activity.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/recent-entries.svelte +8 -3
- package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +2 -17
- package/dist/admin/components/fields/image-field.svelte +21 -20
- package/dist/admin/components/fields/media-field.svelte +45 -44
- package/dist/admin/components/fields/relation-field.svelte +428 -149
- package/dist/admin/components/fields/relation-picker-dialog.svelte +112 -0
- package/dist/admin/components/fields/relation-picker-dialog.svelte.d.ts +22 -0
- package/dist/admin/components/fields/seo-field.svelte +66 -10
- package/dist/admin/components/icons/icon-map.d.ts +4 -0
- package/dist/admin/components/icons/icon-map.js +143 -0
- package/dist/admin/components/layout/header-actions.svelte +0 -10
- package/dist/admin/components/layout/nav-collections.svelte +55 -57
- package/dist/admin/components/layout/nav-collections.svelte.d.ts +2 -17
- package/dist/admin/components/layout/nav-forms.svelte +49 -49
- package/dist/admin/components/layout/nav-forms.svelte.d.ts +2 -17
- package/dist/admin/components/layout/nav-search.svelte +15 -22
- package/dist/admin/components/layout/nav-singletons.svelte +35 -38
- package/dist/admin/components/layout/nav-singletons.svelte.d.ts +2 -17
- package/dist/admin/components/media/file/file-preview.svelte +92 -91
- package/dist/admin/components/media/file-preview.svelte +17 -18
- package/dist/admin/components/media/media-library.svelte +39 -41
- package/dist/admin/components/media/media-selector.svelte +8 -8
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +6 -8
- package/dist/admin/components/tiptap/content-editor.svelte +2 -2
- package/dist/admin/components/tiptap/editor-toolbar.svelte +1 -1
- package/dist/admin/components/tiptap/inline-block-node.d.ts +1 -0
- package/dist/admin/components/tiptap/inline-block-node.js +12 -1
- package/dist/admin/components/tiptap/tiptap-editor.svelte +1 -1
- package/dist/admin/remote/entry.remote.d.ts +20 -1
- package/dist/admin/remote/entry.remote.js +24 -3
- package/dist/admin/remote/invite.d.ts +2 -2
- package/dist/cli/scaffold/admin.js +8 -0
- package/dist/cms/runtime/remote.d.ts +8 -0
- package/dist/cms/runtime/remote.js +7 -0
- package/dist/cms/runtime/types.d.ts +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/components/ui/sonner/sonner.svelte +1 -2
- package/dist/core/cms.d.ts +2 -1
- package/dist/core/cms.js +2 -0
- package/dist/core/server/entries/operations/get.d.ts +11 -0
- package/dist/core/server/entries/operations/get.js +63 -1
- package/dist/core/server/entries/operations/update.d.ts +1 -0
- package/dist/core/server/entries/operations/update.js +5 -1
- package/dist/core/server/fields/resolveUrlFields.js +49 -18
- package/dist/db-postgres/schema/entry.d.ts +17 -0
- package/dist/db-postgres/schema/entry.js +4 -2
- package/dist/server/auth.d.ts +4 -4
- package/dist/sveltekit/components/structured-content.svelte +18 -5
- package/dist/sveltekit/server/handle.js +1 -0
- package/dist/types/cms.d.ts +7 -0
- package/dist/types/collections.d.ts +2 -0
- package/dist/types/config.d.ts +2 -2
- package/dist/types/entries.d.ts +7 -1
- package/dist/types/forms.d.ts +2 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -0
- package/dist/updates/0.5.4/index.d.ts +2 -0
- package/dist/updates/0.5.4/index.js +15 -0
- package/dist/updates/0.5.5/index.d.ts +2 -0
- package/dist/updates/0.5.5/index.js +20 -0
- package/dist/updates/0.5.6/index.d.ts +2 -0
- package/dist/updates/0.5.6/index.js +17 -0
- package/dist/updates/0.5.7/index.d.ts +2 -0
- package/dist/updates/0.5.7/index.js +14 -0
- package/dist/updates/index.js +5 -1
- package/package.json +6 -2
|
@@ -161,7 +161,8 @@ export const getEntries = async (options = {}) => {
|
|
|
161
161
|
// Get entries that match the slug/ids filter
|
|
162
162
|
const dbEntries = await getCMS().databaseAdapter.getEntries({
|
|
163
163
|
ids,
|
|
164
|
-
slug
|
|
164
|
+
slug,
|
|
165
|
+
orderBy: options.orderBy
|
|
165
166
|
});
|
|
166
167
|
if (dbEntries.length === 0) {
|
|
167
168
|
return [];
|
|
@@ -227,6 +228,9 @@ export const getEntries = async (options = {}) => {
|
|
|
227
228
|
}
|
|
228
229
|
versionEntries = Array.from(latestByEntry.values());
|
|
229
230
|
}
|
|
231
|
+
// Re-sort to match original entry order (DB returns sorted by sortOrder, but version fetch may reorder)
|
|
232
|
+
const entryOrder = new Map(filteredEntries.map((e, i) => [e.id, i]));
|
|
233
|
+
versionEntries.sort((a, b) => (entryOrder.get(a.dbEntry.id) ?? 0) - (entryOrder.get(b.dbEntry.id) ?? 0));
|
|
230
234
|
// Post-filter: apply dataValues/dataLike on versions
|
|
231
235
|
if (dataValues) {
|
|
232
236
|
versionEntries = versionEntries.filter((e) => matchesDataValues(e.version.data, dataValues));
|
|
@@ -267,6 +271,64 @@ export const getEntryOrThrow = async (options = {}) => {
|
|
|
267
271
|
}
|
|
268
272
|
return entry;
|
|
269
273
|
};
|
|
274
|
+
export const getEntryLabels = async (options) => {
|
|
275
|
+
const cms = getCMS();
|
|
276
|
+
const config = cms.getBySlug(options.slug);
|
|
277
|
+
if (!config || config.type !== 'collection')
|
|
278
|
+
return [];
|
|
279
|
+
const dbEntries = await getDbEntries({
|
|
280
|
+
slug: options.slug,
|
|
281
|
+
ids: options.ids
|
|
282
|
+
});
|
|
283
|
+
if (dbEntries.length === 0)
|
|
284
|
+
return [];
|
|
285
|
+
// Filter by status at entry level
|
|
286
|
+
const now = new Date();
|
|
287
|
+
const statusFilter = options.status ?? 'all';
|
|
288
|
+
const filteredDbEntries = statusFilter === 'all'
|
|
289
|
+
? dbEntries
|
|
290
|
+
: dbEntries.filter((entry) => {
|
|
291
|
+
if (statusFilter === 'published') {
|
|
292
|
+
return entry.publishedVersionId != null && entry.publishedAt != null && entry.publishedAt <= now;
|
|
293
|
+
}
|
|
294
|
+
// draft
|
|
295
|
+
return entry.publishedVersionId == null;
|
|
296
|
+
});
|
|
297
|
+
if (filteredDbEntries.length === 0)
|
|
298
|
+
return [];
|
|
299
|
+
const entryIds = filteredDbEntries.map((e) => e.id);
|
|
300
|
+
const allVersions = await cms.databaseAdapter.getEntryVersions({ entryIds });
|
|
301
|
+
const language = cms.languages[0];
|
|
302
|
+
const entryAdminTitle = config.entryAdminTitle;
|
|
303
|
+
let results = filteredDbEntries.map((entry) => {
|
|
304
|
+
// Pick published version or latest draft
|
|
305
|
+
const entryVersions = allVersions.filter((v) => v.entryId === entry.id);
|
|
306
|
+
const publishedVersion = entry.publishedVersionId
|
|
307
|
+
? entryVersions.find((v) => v.id === entry.publishedVersionId)
|
|
308
|
+
: null;
|
|
309
|
+
const latestVersion = publishedVersion ??
|
|
310
|
+
entryVersions.sort((a, b) => b.versionNumber - a.versionNumber)[0] ?? null;
|
|
311
|
+
let label = entry.id;
|
|
312
|
+
if (entryAdminTitle && latestVersion) {
|
|
313
|
+
const titleData = latestVersion.data[entryAdminTitle];
|
|
314
|
+
if (titleData && typeof titleData === 'object') {
|
|
315
|
+
label = String(titleData[language] || Object.values(titleData)[0] || entry.id);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return { id: entry.id, label };
|
|
319
|
+
});
|
|
320
|
+
// Post-query search filtering (case-insensitive includes)
|
|
321
|
+
if (options.search) {
|
|
322
|
+
const searchLower = options.search.toLowerCase();
|
|
323
|
+
results = results.filter((r) => r.label.toLowerCase().includes(searchLower));
|
|
324
|
+
}
|
|
325
|
+
const total = results.length;
|
|
326
|
+
// Apply limit
|
|
327
|
+
if (options.limit && options.limit > 0) {
|
|
328
|
+
results = results.slice(0, options.limit);
|
|
329
|
+
}
|
|
330
|
+
return results.map((r) => ({ ...r, total }));
|
|
331
|
+
};
|
|
270
332
|
export const getDbEntryVersions = async (options) => {
|
|
271
333
|
return getCMS().databaseAdapter.getEntryVersions(options);
|
|
272
334
|
};
|
|
@@ -6,6 +6,7 @@ export declare const updateEntrySchema: z.ZodObject<{
|
|
|
6
6
|
publishedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
|
|
7
7
|
publishedVersionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
8
8
|
publishedBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
9
|
+
sortOrder: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
9
10
|
}, z.z.core.$strip>;
|
|
10
11
|
export declare const updateEntry: (id: string, data: Partial<DbEntry>) => Promise<DbEntry>;
|
|
11
12
|
export declare const updateEntryVersionSchema: z.ZodObject<{
|
|
@@ -9,7 +9,8 @@ export const updateEntrySchema = z.object({
|
|
|
9
9
|
archivedAt: z.date().nullable().optional(),
|
|
10
10
|
publishedAt: z.date().nullable().optional(),
|
|
11
11
|
publishedVersionId: z.string().uuid().nullable().optional(),
|
|
12
|
-
publishedBy: z.string().nullable().optional()
|
|
12
|
+
publishedBy: z.string().nullable().optional(),
|
|
13
|
+
sortOrder: z.number().int().nullable().optional()
|
|
13
14
|
});
|
|
14
15
|
export const updateEntry = async (id, data) => {
|
|
15
16
|
const filteredDataParse = updateEntrySchema.safeParse(data);
|
|
@@ -33,6 +34,9 @@ export const updateEntry = async (id, data) => {
|
|
|
33
34
|
if (filteredData.publishedBy !== undefined) {
|
|
34
35
|
dataToUpdate.publishedBy = filteredData.publishedBy;
|
|
35
36
|
}
|
|
37
|
+
if (filteredData.sortOrder !== undefined) {
|
|
38
|
+
dataToUpdate.sortOrder = filteredData.sortOrder;
|
|
39
|
+
}
|
|
36
40
|
const updatedEntry = await getCMS().databaseAdapter.updateEntry({
|
|
37
41
|
id,
|
|
38
42
|
data: {
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import { urlFieldDataSchema, urlFieldDataWithRelationSchema } from '../../../schemas/field/url.js';
|
|
2
2
|
import { walkInlineBlockNodes, cloneDoc } from '../../../admin/components/tiptap/structured-content-utils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getDbEntries, getDbEntryVersions } from '../entries/operations/get.js';
|
|
4
4
|
import { getEntrySlugPath, getSlugFromEntryData } from './slugResolver.js';
|
|
5
5
|
import { isExternalUrl, mergeRel } from '../../fields/urlUtils.js';
|
|
6
|
+
const FLAT_KEY = '_flat';
|
|
7
|
+
/**
|
|
8
|
+
* Inline block blockData stores url/text as flat strings (denormalized per-language).
|
|
9
|
+
* Schemas expect Record<string, string>. Wrap flat strings so safeParse succeeds.
|
|
10
|
+
*/
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
function normalizeUrlFieldData(val) {
|
|
13
|
+
if (!val || typeof val !== 'object' || Array.isArray(val))
|
|
14
|
+
return val;
|
|
15
|
+
const obj = { ...val };
|
|
16
|
+
if (typeof obj.url === 'string')
|
|
17
|
+
obj.url = { [FLAT_KEY]: obj.url };
|
|
18
|
+
if (typeof obj.text === 'string')
|
|
19
|
+
obj.text = { [FLAT_KEY]: obj.text };
|
|
20
|
+
return obj;
|
|
21
|
+
}
|
|
22
|
+
function resolveFlatOrLocalized(record, language) {
|
|
23
|
+
if (FLAT_KEY in record)
|
|
24
|
+
return record[FLAT_KEY];
|
|
25
|
+
return language ? record[language] : record;
|
|
26
|
+
}
|
|
6
27
|
function applyExternalAutoDetect(resolvedUrl, extras) {
|
|
7
28
|
const url = typeof resolvedUrl === 'string' ? resolvedUrl : '';
|
|
8
29
|
if (url && isExternalUrl(url)) {
|
|
@@ -30,6 +51,7 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
30
51
|
if (allStrings)
|
|
31
52
|
normalizedVal = { url: val };
|
|
32
53
|
}
|
|
54
|
+
normalizedVal = normalizeUrlFieldData(normalizedVal);
|
|
33
55
|
const parsedVal = urlFieldDataWithRelationSchema.safeParse(normalizedVal);
|
|
34
56
|
if (parsedVal.success) {
|
|
35
57
|
entriesIds.push(parsedVal.data.id);
|
|
@@ -71,7 +93,7 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
71
93
|
case 'array':
|
|
72
94
|
if (field.of === 'url' && Array.isArray(val)) {
|
|
73
95
|
for (const item of val) {
|
|
74
|
-
const parsedVal = urlFieldDataWithRelationSchema.safeParse(item);
|
|
96
|
+
const parsedVal = urlFieldDataWithRelationSchema.safeParse(normalizeUrlFieldData(item));
|
|
75
97
|
if (parsedVal.success) {
|
|
76
98
|
entriesIds.push(parsedVal.data.id);
|
|
77
99
|
}
|
|
@@ -84,12 +106,23 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
84
106
|
collectIds(data, fields);
|
|
85
107
|
const slugMap = {};
|
|
86
108
|
if (entriesIds.length > 0) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
// Use raw DB calls to avoid recursive populateEntryData → resolveUrlFields loop
|
|
110
|
+
const dbEntries = await getDbEntries({ ids: entriesIds });
|
|
111
|
+
const publishedVersionIds = dbEntries
|
|
112
|
+
.map((e) => e.publishedVersionId)
|
|
113
|
+
.filter((id) => id != null);
|
|
114
|
+
if (publishedVersionIds.length > 0) {
|
|
115
|
+
const versions = await getDbEntryVersions({ ids: publishedVersionIds });
|
|
116
|
+
const versionByEntryId = new Map(versions.map((v) => [v.entryId, v]));
|
|
117
|
+
for (const entry of dbEntries) {
|
|
118
|
+
const version = versionByEntryId.get(entry.id);
|
|
119
|
+
if (!version)
|
|
120
|
+
continue;
|
|
121
|
+
const slugPath = getEntrySlugPath(entry.slug);
|
|
122
|
+
const slug = getSlugFromEntryData(version.data, slugPath, language);
|
|
123
|
+
if (slug)
|
|
124
|
+
slugMap[entry.id] = slug;
|
|
125
|
+
}
|
|
93
126
|
}
|
|
94
127
|
}
|
|
95
128
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -112,6 +145,7 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
112
145
|
if (allStrings)
|
|
113
146
|
normalizedVal = { url: val };
|
|
114
147
|
}
|
|
148
|
+
normalizedVal = normalizeUrlFieldData(normalizedVal);
|
|
115
149
|
const parsedValWithRelation = urlFieldDataWithRelationSchema.safeParse(normalizedVal);
|
|
116
150
|
const parsedVal = urlFieldDataSchema.safeParse(normalizedVal);
|
|
117
151
|
const parsed = parsedValWithRelation.success
|
|
@@ -123,7 +157,7 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
123
157
|
break;
|
|
124
158
|
const extras = {};
|
|
125
159
|
if (parsed.text) {
|
|
126
|
-
extras.text =
|
|
160
|
+
extras.text = resolveFlatOrLocalized(parsed.text, language);
|
|
127
161
|
}
|
|
128
162
|
if (parsed.newTab !== undefined) {
|
|
129
163
|
extras.newTab = parsed.newTab;
|
|
@@ -138,9 +172,7 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
138
172
|
break;
|
|
139
173
|
}
|
|
140
174
|
}
|
|
141
|
-
const resolvedUrl = language
|
|
142
|
-
? parsed.url[language]
|
|
143
|
-
: parsed.url;
|
|
175
|
+
const resolvedUrl = resolveFlatOrLocalized(parsed.url, language);
|
|
144
176
|
applyExternalAutoDetect(resolvedUrl, extras);
|
|
145
177
|
result[field.slug] = { url: resolvedUrl, ...extras };
|
|
146
178
|
break;
|
|
@@ -197,8 +229,9 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
197
229
|
if (field.of === 'url' && Array.isArray(val)) {
|
|
198
230
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
199
231
|
result[field.slug] = val.map((item) => {
|
|
200
|
-
const
|
|
201
|
-
const
|
|
232
|
+
const normalized = normalizeUrlFieldData(item);
|
|
233
|
+
const parsedValWithRelation = urlFieldDataWithRelationSchema.safeParse(normalized);
|
|
234
|
+
const parsedVal = urlFieldDataSchema.safeParse(normalized);
|
|
202
235
|
const parsed = parsedValWithRelation.success
|
|
203
236
|
? parsedValWithRelation.data
|
|
204
237
|
: parsedVal.success
|
|
@@ -208,7 +241,7 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
208
241
|
return item;
|
|
209
242
|
const extras = {};
|
|
210
243
|
if (parsed.text) {
|
|
211
|
-
extras.text =
|
|
244
|
+
extras.text = resolveFlatOrLocalized(parsed.text, language);
|
|
212
245
|
}
|
|
213
246
|
if (parsed.newTab !== undefined) {
|
|
214
247
|
extras.newTab = parsed.newTab;
|
|
@@ -222,9 +255,7 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
222
255
|
return { url: slug, ...extras };
|
|
223
256
|
}
|
|
224
257
|
}
|
|
225
|
-
const resolvedUrl = language
|
|
226
|
-
? parsed.url[language]
|
|
227
|
-
: parsed.url;
|
|
258
|
+
const resolvedUrl = resolveFlatOrLocalized(parsed.url, language);
|
|
228
259
|
applyExternalAutoDetect(resolvedUrl, extras);
|
|
229
260
|
return { url: resolvedUrl, ...extras };
|
|
230
261
|
});
|
|
@@ -200,6 +200,23 @@ export declare const entriesTable: import("drizzle-orm/pg-core/table", { with: {
|
|
|
200
200
|
identity: undefined;
|
|
201
201
|
generated: undefined;
|
|
202
202
|
}, {}, {}>;
|
|
203
|
+
sortOrder: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
204
|
+
name: "sort_order";
|
|
205
|
+
tableName: "entry";
|
|
206
|
+
dataType: "number";
|
|
207
|
+
columnType: "PgInteger";
|
|
208
|
+
data: number;
|
|
209
|
+
driverParam: string | number;
|
|
210
|
+
notNull: false;
|
|
211
|
+
hasDefault: false;
|
|
212
|
+
isPrimaryKey: false;
|
|
213
|
+
isAutoincrement: false;
|
|
214
|
+
hasRuntimeDefault: false;
|
|
215
|
+
enumValues: undefined;
|
|
216
|
+
baseColumn: never;
|
|
217
|
+
identity: undefined;
|
|
218
|
+
generated: undefined;
|
|
219
|
+
}, {}, {}>;
|
|
203
220
|
};
|
|
204
221
|
dialect: "pg";
|
|
205
222
|
}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
1
|
+
import { integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
2
2
|
import { entryVersionsTable } from './entryVersion.js';
|
|
3
3
|
export const entriesTable = pgTable('entry', {
|
|
4
4
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
@@ -12,5 +12,7 @@ export const entriesTable = pgTable('entry', {
|
|
|
12
12
|
// Publish state
|
|
13
13
|
publishedAt: timestamp('published_at'),
|
|
14
14
|
publishedVersionId: uuid('published_version_id').references(() => entryVersionsTable.id, { onDelete: 'set null' }),
|
|
15
|
-
publishedBy: text('published_by')
|
|
15
|
+
publishedBy: text('published_by'),
|
|
16
|
+
// Manual ordering
|
|
17
|
+
sortOrder: integer('sort_order')
|
|
16
18
|
});
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -1172,13 +1172,13 @@ export declare const auth: import("better-auth", { with: { "resolution-mode": "r
|
|
|
1172
1172
|
<AsResponse extends boolean = false, ReturnHeaders extends boolean = false>(inputCtx_0: {
|
|
1173
1173
|
body: ({
|
|
1174
1174
|
permission: {
|
|
1175
|
-
readonly user?: ("
|
|
1175
|
+
readonly user?: ("list" | "create" | "set-role" | "ban" | "impersonate" | "delete" | "set-password" | "get" | "update")[] | undefined;
|
|
1176
1176
|
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
1177
1177
|
};
|
|
1178
1178
|
permissions?: never;
|
|
1179
1179
|
} | {
|
|
1180
1180
|
permissions: {
|
|
1181
|
-
readonly user?: ("
|
|
1181
|
+
readonly user?: ("list" | "create" | "set-role" | "ban" | "impersonate" | "delete" | "set-password" | "get" | "update")[] | undefined;
|
|
1182
1182
|
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
1183
1183
|
};
|
|
1184
1184
|
permission?: never;
|
|
@@ -1275,13 +1275,13 @@ export declare const auth: import("better-auth", { with: { "resolution-mode": "r
|
|
|
1275
1275
|
$Infer: {
|
|
1276
1276
|
body: ({
|
|
1277
1277
|
permission: {
|
|
1278
|
-
readonly user?: ("
|
|
1278
|
+
readonly user?: ("list" | "create" | "set-role" | "ban" | "impersonate" | "delete" | "set-password" | "get" | "update")[] | undefined;
|
|
1279
1279
|
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
1280
1280
|
};
|
|
1281
1281
|
permissions?: never;
|
|
1282
1282
|
} | {
|
|
1283
1283
|
permissions: {
|
|
1284
|
-
readonly user?: ("
|
|
1284
|
+
readonly user?: ("list" | "create" | "set-role" | "ban" | "impersonate" | "delete" | "set-password" | "get" | "update")[] | undefined;
|
|
1285
1285
|
readonly session?: ("list" | "delete" | "revoke")[] | undefined;
|
|
1286
1286
|
};
|
|
1287
1287
|
permission?: never;
|
|
@@ -38,6 +38,15 @@
|
|
|
38
38
|
if (node.type === 'text') return node.text ?? '';
|
|
39
39
|
return node.content?.map(getTextContent).join('') ?? '';
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
function isEmptyParagraph(node: SCNode): boolean {
|
|
43
|
+
if (!node.content || node.content.length === 0) return true;
|
|
44
|
+
return node.content.every(
|
|
45
|
+
(child) =>
|
|
46
|
+
(child.type === 'text' && !child.text?.trim()) ||
|
|
47
|
+
child.type === 'hardBreak'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
41
50
|
</script>
|
|
42
51
|
|
|
43
52
|
{#snippet renderMark(text: string, marks: SCMark[], index: number)}
|
|
@@ -83,10 +92,12 @@
|
|
|
83
92
|
{node.text}
|
|
84
93
|
{/if}
|
|
85
94
|
{:else if node.type === 'paragraph'}
|
|
86
|
-
{#if
|
|
87
|
-
{
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
{#if !isEmptyParagraph(node)}
|
|
96
|
+
{#if paragraphSnippet}
|
|
97
|
+
{@render paragraphSnippet(renderChildren)}
|
|
98
|
+
{:else}
|
|
99
|
+
<p>{@render renderChildren()}</p>
|
|
100
|
+
{/if}
|
|
90
101
|
{/if}
|
|
91
102
|
{:else if node.type === 'heading'}
|
|
92
103
|
{@const level = (node.attrs?.level as number) ?? 2}
|
|
@@ -180,7 +191,9 @@
|
|
|
180
191
|
{:else if node.type === 'inlineBlock'}
|
|
181
192
|
{#if inlineBlockSnippet}
|
|
182
193
|
{@const attrs = node.attrs as unknown as SCInlineBlockAttrs}
|
|
183
|
-
{
|
|
194
|
+
<div data-inline-block={attrs.blockType}>
|
|
195
|
+
{@render inlineBlockSnippet(attrs.blockType, attrs.blockData, attrs.blockId)}
|
|
196
|
+
</div>
|
|
184
197
|
{/if}
|
|
185
198
|
{:else}
|
|
186
199
|
{@render renderChildren()}
|
|
@@ -11,6 +11,7 @@ const adminGuard = async ({ event, resolve }) => {
|
|
|
11
11
|
!event.url.pathname.startsWith('/admin/accept-invite') &&
|
|
12
12
|
!event.url.pathname.startsWith('/admin/api/accept-invite') &&
|
|
13
13
|
!event.url.pathname.startsWith('/admin/reset-password') &&
|
|
14
|
+
!event.url.pathname.startsWith('/admin/api/rest/') &&
|
|
14
15
|
(!user || !session)) {
|
|
15
16
|
setFlash({
|
|
16
17
|
message: 'You must be logged in to access the admin panel.',
|
package/dist/types/cms.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface AuthAdapter {
|
|
|
27
27
|
} | null>;
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
export interface ApiKeyConfig {
|
|
31
|
+
key: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
role?: 'admin' | 'editor';
|
|
34
|
+
}
|
|
30
35
|
export interface CMSConfig {
|
|
31
36
|
languages: Language[];
|
|
32
37
|
collections?: CollectionConfig[];
|
|
@@ -39,6 +44,7 @@ export interface CMSConfig {
|
|
|
39
44
|
plugins?: PluginConfig[];
|
|
40
45
|
ai?: AIAdapter;
|
|
41
46
|
media?: MediaConfig;
|
|
47
|
+
apiKeys?: ApiKeyConfig[];
|
|
42
48
|
}
|
|
43
49
|
export interface ICMS {
|
|
44
50
|
collections: Record<string, CollectionConfigWithType>;
|
|
@@ -52,4 +58,5 @@ export interface ICMS {
|
|
|
52
58
|
plugins: PluginConfig[];
|
|
53
59
|
aiAdapter: AIAdapter | null;
|
|
54
60
|
mediaConfig: MediaConfig;
|
|
61
|
+
apiKeys: ApiKeyConfig[];
|
|
55
62
|
}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Field } from './fields.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { IconName } from '../admin/components/icons/icon-map.js';
|
|
3
3
|
import type { Layout } from './layout.js';
|
|
4
4
|
export interface ConfigBase {
|
|
5
|
-
sidebarIcon?:
|
|
5
|
+
sidebarIcon?: IconName;
|
|
6
6
|
slug: string;
|
|
7
7
|
previewUrl?: string;
|
|
8
8
|
fields: Field[];
|
package/dist/types/entries.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface DbEntry {
|
|
|
19
19
|
publishedAt: Date | null;
|
|
20
20
|
publishedVersionId: string | null;
|
|
21
21
|
publishedBy: string | null;
|
|
22
|
+
sortOrder: number | null;
|
|
22
23
|
}
|
|
23
24
|
export interface DbEntryInsert {
|
|
24
25
|
slug: string;
|
|
@@ -30,6 +31,7 @@ export interface DbEntryInsert {
|
|
|
30
31
|
publishedAt?: Date | null;
|
|
31
32
|
publishedVersionId?: string | null;
|
|
32
33
|
publishedBy?: string | null;
|
|
34
|
+
sortOrder?: number | null;
|
|
33
35
|
}
|
|
34
36
|
export interface RawEntry extends DbEntry {
|
|
35
37
|
collection: CollectionConfigWithType | SingleConfigWithType;
|
|
@@ -68,7 +70,7 @@ export interface PaginationOptions {
|
|
|
68
70
|
limit?: number;
|
|
69
71
|
offset?: number;
|
|
70
72
|
orderBy?: {
|
|
71
|
-
column: 'createdAt' | 'updatedAt';
|
|
73
|
+
column: 'createdAt' | 'updatedAt' | 'sortOrder';
|
|
72
74
|
direction: 'asc' | 'desc';
|
|
73
75
|
};
|
|
74
76
|
}
|
|
@@ -103,6 +105,10 @@ export interface GetEntriesOptions {
|
|
|
103
105
|
dataLike?: Record<string, unknown>;
|
|
104
106
|
language?: string;
|
|
105
107
|
status?: EntryStatus;
|
|
108
|
+
orderBy?: {
|
|
109
|
+
column: 'createdAt' | 'updatedAt' | 'sortOrder';
|
|
110
|
+
direction: 'asc' | 'desc';
|
|
111
|
+
};
|
|
106
112
|
}
|
|
107
113
|
export interface GetEntryOptions {
|
|
108
114
|
id?: string;
|
package/dist/types/forms.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { FormField } from './formFields.js';
|
|
2
|
+
import type { IconName } from '../admin/components/icons/icon-map.js';
|
|
2
3
|
import type { Localized } from './languages.js';
|
|
3
4
|
export interface FormConfig {
|
|
4
5
|
slug: string;
|
|
6
|
+
sidebarIcon?: IconName;
|
|
5
7
|
fields: FormField[];
|
|
6
8
|
label: Localized;
|
|
7
9
|
notificationEmailAddresses?: string[];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,10 +4,11 @@ export { type MediaFile, type MediaFileType, type ImageStyle, type MediaTag } fr
|
|
|
4
4
|
export { type ConsentLogData } from './consent.js';
|
|
5
5
|
export { type Entry, type EntryType, type EntryStatus, type EntryVersionStatus } from './entries.js';
|
|
6
6
|
export { type ConfigBase } from './config.js';
|
|
7
|
+
export { type IconName } from '../admin/components/icons/icon-map.js';
|
|
7
8
|
export { type CollectionConfig } from './collections.js';
|
|
8
9
|
export { type SingleConfig } from './singles.js';
|
|
9
10
|
export { type FormConfig, type FormSubmission } from './forms.js';
|
|
10
11
|
export { type FormField, type FormFieldType, type FormBaseField, type FormTextField, type FormEmailField, type FormTextareaField, type FormCheckboxField, type FormSelectField } from './formFields.js';
|
|
11
|
-
export { type CMSConfig } from './cms.js';
|
|
12
|
+
export { type CMSConfig, type ApiKeyConfig } from './cms.js';
|
|
12
13
|
export { type Language, type Localized } from './languages.js';
|
|
13
14
|
export { type Layout, type LayoutNode, type LayoutPreset, type LayoutNodeType, type ColumnRatio, type SectionNode, type ColumnsNode, type CardNode, type AccordionNode, type StackNode } from './layout.js';
|
package/dist/types/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export {} from './media.js';
|
|
|
4
4
|
export {} from './consent.js';
|
|
5
5
|
export {} from './entries.js';
|
|
6
6
|
export {} from './config.js';
|
|
7
|
+
export {} from '../admin/components/icons/icon-map.js';
|
|
7
8
|
export {} from './collections.js';
|
|
8
9
|
export {} from './singles.js';
|
|
9
10
|
export {} from './forms.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.5.4',
|
|
3
|
+
date: '2026-03-04',
|
|
4
|
+
description: 'Collection ordering (DnD), custom list columns',
|
|
5
|
+
features: [
|
|
6
|
+
'Orderable collections: DnD reordering of entries in admin list with keyboard alternative (WCAG 2.1)',
|
|
7
|
+
'Custom list columns: display arbitrary entry fields as columns in collection list',
|
|
8
|
+
'sortOrder column on entries table for persistent manual ordering',
|
|
9
|
+
'reorderEntriesCommand remote for bulk sort order updates'
|
|
10
|
+
],
|
|
11
|
+
fixes: [],
|
|
12
|
+
breakingChanges: [],
|
|
13
|
+
sql: 'ALTER TABLE entry ADD COLUMN sort_order INTEGER;',
|
|
14
|
+
notes: 'Add `orderable: true` to collections that need manual ordering. Add `listColumns: ["fieldSlug"]` to show fields in the entry list.'
|
|
15
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.5.5',
|
|
3
|
+
date: '2026-03-04',
|
|
4
|
+
description: 'REST API with API key auth, orderable collection fixes',
|
|
5
|
+
features: [
|
|
6
|
+
'REST API with API key authentication — CRUD for collections, singletons, entries, schema introspection, language listing',
|
|
7
|
+
'API key configuration in CMS config (`apiKeys` array)',
|
|
8
|
+
'getEntries supports `orderBy` parameter (column + direction)',
|
|
9
|
+
'Admin scaffold generates REST API catch-all route'
|
|
10
|
+
],
|
|
11
|
+
fixes: [
|
|
12
|
+
'Orderable collections: DnD rows use proper `<tr>` element instead of `<div>` wrapper',
|
|
13
|
+
'Reorder buttons always visible (not sr-only) for better discoverability',
|
|
14
|
+
'Selection enabled for orderable collections',
|
|
15
|
+
'Relation field respects orderable collection sort order',
|
|
16
|
+
'Nav active state: exact match prevents false highlights on similarly-named routes'
|
|
17
|
+
],
|
|
18
|
+
breakingChanges: [],
|
|
19
|
+
notes: 'Add `apiKeys: [{ key: "your-secret", name: "My App" }]` to CMS config. REST API available at `/admin/api/rest/`.'
|
|
20
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.5.6',
|
|
3
|
+
date: '2026-03-05',
|
|
4
|
+
description: 'Relation field UX overhaul, collection list improvements, icon map refactor',
|
|
5
|
+
features: [
|
|
6
|
+
'Relation field: dialog picker with search, reorder, multi-select UX',
|
|
7
|
+
'`getEntryLabels` — lightweight labels endpoint for relation field options',
|
|
8
|
+
'Collection list: relation labels displayed in columns, delete restricted to archived entries',
|
|
9
|
+
'Icon map: string-based sidebar icons replace component references'
|
|
10
|
+
],
|
|
11
|
+
fixes: [
|
|
12
|
+
'TipTap: auto-expand new inline blocks, fix editor overflow, toolbar background',
|
|
13
|
+
'Runtime types: FormEntryMap rename, add remote.ts typings'
|
|
14
|
+
],
|
|
15
|
+
breakingChanges: [],
|
|
16
|
+
notes: 'Removed `mode-watcher` dependency — light theme is now hardcoded.'
|
|
17
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.5.7',
|
|
3
|
+
date: '2026-03-06',
|
|
4
|
+
description: 'SEO field with slug toggle, structured-content & URL field fixes',
|
|
5
|
+
features: [
|
|
6
|
+
'SEO field: auto/manual slug toggle with Switch component'
|
|
7
|
+
],
|
|
8
|
+
fixes: [
|
|
9
|
+
'Structured content: skip empty paragraphs, wrap inline blocks in `<div>`',
|
|
10
|
+
'`resolveUrlFields`: normalize flat string url/text values, use raw DB calls for performance',
|
|
11
|
+
'Admin UI: migrate `{#await}` blocks to reactive query pattern for consistent loading states'
|
|
12
|
+
],
|
|
13
|
+
breakingChanges: []
|
|
14
|
+
};
|
package/dist/updates/index.js
CHANGED
|
@@ -15,7 +15,11 @@ import { update as update050 } from './0.5.0/index.js';
|
|
|
15
15
|
import { update as update051 } from './0.5.1/index.js';
|
|
16
16
|
import { update as update052 } from './0.5.2/index.js';
|
|
17
17
|
import { update as update053 } from './0.5.3/index.js';
|
|
18
|
-
|
|
18
|
+
import { update as update054 } from './0.5.4/index.js';
|
|
19
|
+
import { update as update055 } from './0.5.5/index.js';
|
|
20
|
+
import { update as update056 } from './0.5.6/index.js';
|
|
21
|
+
import { update as update057 } from './0.5.7/index.js';
|
|
22
|
+
export const updates = [update0065, update0066, update0067, update0068, update0069, update010, update011, update012, update013, update014, update015, update020, update022, update050, update051, update052, update053, update054, update055, update056, update057];
|
|
19
23
|
export const getUpdatesFrom = (fromVersion) => {
|
|
20
24
|
const fromParts = fromVersion.split('.').map(Number);
|
|
21
25
|
return updates.filter((update) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "includio-cms",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
"svelte": "./dist/admin/api/*.js",
|
|
64
64
|
"node": "./dist/admin/api/*.js"
|
|
65
65
|
},
|
|
66
|
+
"./admin/api/rest/handler": {
|
|
67
|
+
"types": "./dist/admin/api/rest/handler.d.ts",
|
|
68
|
+
"svelte": "./dist/admin/api/rest/handler.js",
|
|
69
|
+
"node": "./dist/admin/api/rest/handler.js"
|
|
70
|
+
},
|
|
66
71
|
"./admin/client/account": {
|
|
67
72
|
"types": "./dist/admin/client/account/index.d.ts",
|
|
68
73
|
"svelte": "./dist/admin/client/account/index.js"
|
|
@@ -217,7 +222,6 @@
|
|
|
217
222
|
"fluent-ffmpeg": "^2.1.3",
|
|
218
223
|
"isomorphic-dompurify": "3.0.0-rc.2",
|
|
219
224
|
"lowlight": "^3.3.0",
|
|
220
|
-
"mode-watcher": "^1.0.8",
|
|
221
225
|
"nodemailer": "^7.0.11",
|
|
222
226
|
"openai": "^6.9.0",
|
|
223
227
|
"paneforge": "^1.0.2",
|