includio-cms 0.7.2 → 0.13.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/CHANGELOG.md +110 -0
- package/ROADMAP.md +40 -2
- package/dist/admin/api/generate-styles.d.ts +2 -0
- package/dist/admin/api/generate-styles.js +32 -0
- package/dist/admin/api/handler.js +33 -0
- package/dist/admin/api/media-gc.js +10 -4
- package/dist/admin/api/rest/handler.js +17 -0
- package/dist/admin/api/rest/routes/collections.js +25 -13
- package/dist/admin/api/rest/routes/entries.d.ts +1 -1
- package/dist/admin/api/rest/routes/entries.js +10 -10
- package/dist/admin/api/rest/routes/media.d.ts +2 -0
- package/dist/admin/api/rest/routes/media.js +9 -0
- package/dist/admin/api/rest/routes/schema.d.ts +5 -0
- package/dist/admin/api/rest/routes/schema.js +152 -0
- package/dist/admin/api/rest/routes/singletons.d.ts +1 -1
- package/dist/admin/api/rest/routes/singletons.js +8 -7
- package/dist/admin/api/rest/routes/upload.d.ts +2 -0
- package/dist/admin/api/rest/routes/upload.js +28 -0
- package/dist/admin/api/upload.js +13 -0
- package/dist/admin/client/collection/collection-entries.svelte +19 -6
- package/dist/admin/client/entry/entry.svelte +21 -23
- package/dist/admin/client/entry/header/a11y-validator.js +2 -2
- package/dist/admin/client/entry/header/publish-panel.svelte +33 -85
- package/dist/admin/client/entry/header/status-badge.svelte +2 -2
- package/dist/admin/client/entry/header/version-history-sheet.svelte +9 -9
- package/dist/admin/client/entry/header/visibility.svelte +16 -10
- package/dist/admin/client/entry/utils.d.ts +3 -0
- package/dist/admin/client/entry/utils.js +22 -4
- package/dist/admin/client/form/form-submission/form-submission-page.svelte +4 -1
- package/dist/admin/client/form/form-submission/submission-field.svelte +10 -0
- package/dist/admin/client/index.d.ts +1 -0
- package/dist/admin/client/index.js +1 -0
- package/dist/admin/client/maintenance/maintenance-page.svelte +146 -2
- package/dist/admin/components/fields/blocks-field.svelte +9 -10
- package/dist/admin/components/fields/field-renderer.svelte +4 -8
- package/dist/admin/components/fields/object-field.svelte +7 -12
- package/dist/admin/components/fields/select-field.svelte +8 -2
- package/dist/admin/components/fields/seo-field.svelte +40 -93
- package/dist/admin/components/fields/simple-array-field.svelte +5 -5
- package/dist/admin/components/fields/text-field-wrapper.svelte +52 -197
- package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
- package/dist/admin/components/fields/url-field-wrapper.svelte +15 -25
- package/dist/admin/components/fields/url-field.svelte +61 -72
- package/dist/admin/components/media/file-upload.svelte +5 -1
- package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
- package/dist/admin/components/media/media-library.svelte +109 -37
- package/dist/admin/components/media/media-selector.svelte +79 -11
- package/dist/admin/components/media/tag-sidebar.svelte +10 -6
- package/dist/admin/components/media/tag-sidebar.svelte.d.ts +7 -2
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +21 -93
- package/dist/admin/components/tiptap/inline-block-node.js +6 -5
- package/dist/admin/components/tiptap/link-dialog.svelte +10 -11
- package/dist/admin/components/tiptap/slash-command.js +1 -1
- package/dist/admin/remote/entry.remote.d.ts +2 -5
- package/dist/admin/remote/entry.remote.js +22 -27
- package/dist/admin/remote/media.remote.d.ts +15 -0
- package/dist/admin/remote/media.remote.js +18 -2
- package/dist/admin/remote/preview.remote.js +3 -1
- package/dist/admin/utils/entryLabel.js +9 -6
- package/dist/admin/utils/translationStatus.js +1 -2
- package/dist/cli/scaffold/admin.js +34 -2
- package/dist/cms/runtime/api.d.ts +16 -12
- package/dist/cms/runtime/api.js +7 -6
- package/dist/cms/runtime/remote.js +2 -2
- package/dist/cms/runtime/schemas.d.ts +1 -1
- package/dist/cms/runtime/schemas.js +1 -1
- package/dist/cms/runtime/types.d.ts +118 -112
- package/dist/cms/runtime/types.js +0 -12
- package/dist/core/cms.d.ts +3 -1
- package/dist/core/cms.js +30 -0
- package/dist/core/fields/fieldSchemaToTs.js +9 -15
- package/dist/core/fields/formFieldSchemaToTs.js +7 -0
- package/dist/core/server/entries/operations/create.js +10 -4
- package/dist/core/server/entries/operations/get.d.ts +1 -0
- package/dist/core/server/entries/operations/get.js +186 -191
- package/dist/core/server/entries/operations/update.d.ts +6 -7
- package/dist/core/server/entries/operations/update.js +20 -38
- package/dist/core/server/fields/populateEntry.js +16 -52
- package/dist/core/server/fields/resolveImageFields.js +69 -120
- package/dist/core/server/fields/resolveRelationFields.js +30 -51
- package/dist/core/server/fields/resolveRichtextLinks.js +46 -100
- package/dist/core/server/fields/resolveTypographyOrphans.bench.d.ts +1 -0
- package/dist/core/server/fields/resolveTypographyOrphans.bench.js +87 -0
- package/dist/core/server/fields/resolveTypographyOrphans.d.ts +3 -0
- package/dist/core/server/fields/resolveTypographyOrphans.js +128 -0
- package/dist/core/server/fields/resolveUrlFields.js +47 -56
- package/dist/core/server/fields/utils/fixOrphans.d.ts +5 -0
- package/dist/core/server/fields/utils/fixOrphans.js +12 -0
- package/dist/core/server/fields/utils/imageStyles.d.ts +4 -2
- package/dist/core/server/fields/utils/imageStyles.js +41 -25
- package/dist/core/server/fields/utils/resolveMedia.js +1 -6
- package/dist/core/server/forms/submissions/operations/delete.js +26 -2
- package/dist/core/server/forms/submissions/utils/parseMultipart.d.ts +2 -0
- package/dist/core/server/forms/submissions/utils/parseMultipart.js +75 -0
- package/dist/core/server/generator/fields.d.ts +6 -0
- package/dist/core/server/generator/fields.js +43 -5
- package/dist/core/server/generator/formFieldSchemaToString.js +10 -0
- package/dist/core/server/generator/formFields.js +1 -0
- package/dist/core/server/generator/generator.js +98 -30
- package/dist/core/server/media/operations/getFiles.d.ts +5 -0
- package/dist/core/server/media/operations/getFiles.js +6 -0
- package/dist/core/server/media/operations/uploadPrivateFile.d.ts +4 -0
- package/dist/core/server/media/operations/uploadPrivateFile.js +8 -0
- package/dist/core/server/media/styles/operations/batchGenerateStyles.d.ts +16 -0
- package/dist/core/server/media/styles/operations/batchGenerateStyles.js +144 -0
- package/dist/db-postgres/index.js +303 -37
- package/dist/db-postgres/schema/entry.d.ts +0 -94
- package/dist/db-postgres/schema/entry.js +0 -6
- package/dist/db-postgres/schema/entryVersion.d.ts +17 -0
- package/dist/db-postgres/schema/entryVersion.js +1 -0
- package/dist/entity/index.d.ts +9 -4
- package/dist/entity/index.js +24 -24
- package/dist/files-local/index.js +43 -0
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/sveltekit/components/preview.svelte +2 -326
- package/dist/sveltekit/components/preview.svelte.d.ts +5 -16
- package/dist/sveltekit/server/index.d.ts +2 -1
- package/dist/sveltekit/server/index.js +2 -1
- package/dist/sveltekit/server/preview.js +4 -7
- package/dist/types/adapters/db.d.ts +15 -1
- package/dist/types/adapters/files.d.ts +6 -0
- package/dist/types/cms.d.ts +5 -0
- package/dist/types/entries.d.ts +54 -18
- package/dist/types/fields.d.ts +14 -24
- package/dist/types/formFields.d.ts +7 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/structured-content.d.ts +5 -0
- package/dist/updates/0.10.0/index.d.ts +2 -0
- package/dist/updates/0.10.0/index.js +15 -0
- package/dist/updates/0.11.0/index.d.ts +2 -0
- package/dist/updates/0.11.0/index.js +12 -0
- package/dist/updates/0.12.0/index.d.ts +2 -0
- package/dist/updates/0.12.0/index.js +12 -0
- package/dist/updates/0.13.0/index.d.ts +2 -0
- package/dist/updates/0.13.0/index.js +10 -0
- package/dist/updates/0.7.3/index.d.ts +2 -0
- package/dist/updates/0.7.3/index.js +10 -0
- package/dist/updates/0.8.0/index.d.ts +2 -0
- package/dist/updates/0.8.0/index.js +18 -0
- package/dist/updates/0.8.0/migrate.d.ts +2 -0
- package/dist/updates/0.8.0/migrate.js +101 -0
- package/dist/updates/0.9.0/index.d.ts +2 -0
- package/dist/updates/0.9.0/index.js +38 -0
- package/dist/updates/index.js +8 -1
- package/package.json +7 -6
- package/dist/admin/components/fields/image-field.svelte +0 -198
- package/dist/admin/components/fields/image-field.svelte.d.ts +0 -8
- package/dist/admin/components/fields/richtext-field.svelte +0 -13
- package/dist/admin/components/fields/richtext-field.svelte.d.ts +0 -8
- package/dist/admin/components/tiptap.svelte +0 -11
- package/dist/admin/components/tiptap.svelte.d.ts +0 -6
- package/dist/core/server/entries/utils/getEntryTranslation.d.ts +0 -1
- package/dist/core/server/entries/utils/getEntryTranslation.js +0 -18
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
|
@@ -1,31 +1,7 @@
|
|
|
1
1
|
import { getCMS } from '../../../cms.js';
|
|
2
2
|
import { populateEntryData } from '../../fields/populateEntry.js';
|
|
3
3
|
import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
|
|
4
|
-
|
|
5
|
-
function transformDataValuesToLocalized(dataValues, defaultLanguage) {
|
|
6
|
-
const result = {};
|
|
7
|
-
function transformValue(value) {
|
|
8
|
-
if (typeof value === 'string') {
|
|
9
|
-
// Transform string to localized object with default language
|
|
10
|
-
return { [defaultLanguage]: value };
|
|
11
|
-
}
|
|
12
|
-
if (Array.isArray(value)) {
|
|
13
|
-
return value.map(transformValue);
|
|
14
|
-
}
|
|
15
|
-
if (value && typeof value === 'object') {
|
|
16
|
-
const transformed = {};
|
|
17
|
-
Object.entries(value).forEach(([key, val]) => {
|
|
18
|
-
transformed[key] = transformValue(val);
|
|
19
|
-
});
|
|
20
|
-
return transformed;
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
Object.entries(dataValues).forEach(([key, value]) => {
|
|
25
|
-
result[key] = transformValue(value);
|
|
26
|
-
});
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
4
|
+
import { getEntrySlugPath, getSlugFromEntryData, getEntryPath } from '../../fields/slugResolver.js';
|
|
29
5
|
export const getDbEntries = async (options) => {
|
|
30
6
|
return getCMS().databaseAdapter.getEntries(options);
|
|
31
7
|
};
|
|
@@ -50,6 +26,33 @@ export const getDbEntryOrThrow = async (options) => {
|
|
|
50
26
|
export const countRawEntries = async (options) => {
|
|
51
27
|
return countDbEntries(options);
|
|
52
28
|
};
|
|
29
|
+
/** Helper: group versions by lang into per-lang published/scheduled/draft maps */
|
|
30
|
+
function buildPerLangVersionMaps(versions) {
|
|
31
|
+
const now = new Date();
|
|
32
|
+
const publishedVersions = {};
|
|
33
|
+
const scheduledVersions = {};
|
|
34
|
+
const draftVersions = {};
|
|
35
|
+
// Group versions by lang
|
|
36
|
+
const byLang = new Map();
|
|
37
|
+
for (const v of versions) {
|
|
38
|
+
const arr = byLang.get(v.lang) || [];
|
|
39
|
+
arr.push(v);
|
|
40
|
+
byLang.set(v.lang, arr);
|
|
41
|
+
}
|
|
42
|
+
for (const [lang, langVersions] of byLang) {
|
|
43
|
+
const sorted = langVersions.sort((a, b) => b.versionNumber - a.versionNumber);
|
|
44
|
+
// Find latest published (publishedAt <= now)
|
|
45
|
+
const published = sorted.find((v) => v.publishedAt != null && v.publishedAt <= now) || null;
|
|
46
|
+
// Find scheduled (publishedAt > now)
|
|
47
|
+
const scheduled = sorted.find((v) => v.publishedAt != null && v.publishedAt > now) || null;
|
|
48
|
+
// Draft = latest version without publishedAt, or latest version newer than published
|
|
49
|
+
const draft = sorted.find((v) => v.publishedAt == null) || null;
|
|
50
|
+
publishedVersions[lang] = published;
|
|
51
|
+
scheduledVersions[lang] = scheduled;
|
|
52
|
+
draftVersions[lang] = draft;
|
|
53
|
+
}
|
|
54
|
+
return { publishedVersions, scheduledVersions, draftVersions };
|
|
55
|
+
}
|
|
53
56
|
export const getRawEntries = async (options) => {
|
|
54
57
|
const dbEntries = await getDbEntries(options);
|
|
55
58
|
const entries = await Promise.all(dbEntries.map(async (entry) => {
|
|
@@ -57,31 +60,14 @@ export const getRawEntries = async (options) => {
|
|
|
57
60
|
const versions = await getCMS().databaseAdapter.getEntryVersions({
|
|
58
61
|
entryIds: [entry.id]
|
|
59
62
|
});
|
|
60
|
-
|
|
61
|
-
const publishedOrScheduledVersion = entry.publishedVersionId
|
|
62
|
-
? versions.find((v) => v.id === entry.publishedVersionId) || null
|
|
63
|
-
: null;
|
|
64
|
-
// Determine if it's published or scheduled based on entry.publishedAt
|
|
65
|
-
const isScheduled = publishedOrScheduledVersion &&
|
|
66
|
-
entry.publishedAt &&
|
|
67
|
-
entry.publishedAt > new Date();
|
|
68
|
-
const publishedVersion = publishedOrScheduledVersion && !isScheduled
|
|
69
|
-
? publishedOrScheduledVersion
|
|
70
|
-
: null;
|
|
71
|
-
const scheduledVersion = publishedOrScheduledVersion && isScheduled
|
|
72
|
-
? publishedOrScheduledVersion
|
|
73
|
-
: null;
|
|
74
|
-
// Draft = latest non-published version by versionNumber
|
|
75
|
-
const draftVersion = versions
|
|
76
|
-
.filter((v) => v.id !== entry.publishedVersionId)
|
|
77
|
-
.sort((a, b) => b.versionNumber - a.versionNumber)[0] || null;
|
|
63
|
+
const { publishedVersions, scheduledVersions, draftVersions } = buildPerLangVersionMaps(versions);
|
|
78
64
|
return {
|
|
79
65
|
...entry,
|
|
80
66
|
collection: getCMS().getBySlug(entry.slug),
|
|
81
67
|
versions: versions.sort((a, b) => b.versionNumber - a.versionNumber),
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
68
|
+
publishedVersions,
|
|
69
|
+
scheduledVersions,
|
|
70
|
+
draftVersions
|
|
85
71
|
};
|
|
86
72
|
}
|
|
87
73
|
catch {
|
|
@@ -106,60 +92,56 @@ export const getRawEntryOrThrow = async (options) => {
|
|
|
106
92
|
}
|
|
107
93
|
return entry;
|
|
108
94
|
};
|
|
109
|
-
function getNestedValue(obj, keys) {
|
|
110
|
-
let current = obj;
|
|
111
|
-
for (const key of keys) {
|
|
112
|
-
if (current === null || current === undefined || typeof current !== 'object')
|
|
113
|
-
return undefined;
|
|
114
|
-
current = current[key];
|
|
115
|
-
}
|
|
116
|
-
return current;
|
|
117
|
-
}
|
|
118
|
-
function matchesDataValues(data, filter, prefix = []) {
|
|
119
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
120
|
-
const path = [...prefix, key];
|
|
121
|
-
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
122
|
-
if (!matchesDataValues(data, value, path))
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
const actual = getNestedValue(data, path);
|
|
127
|
-
if (actual !== value)
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
function matchesDataLike(data, filter, prefix = []) {
|
|
134
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
135
|
-
const path = [...prefix, key];
|
|
136
|
-
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
137
|
-
if (!matchesDataLike(data, value, path))
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
else if (typeof value === 'string') {
|
|
141
|
-
const actual = getNestedValue(data, path);
|
|
142
|
-
if (typeof actual !== 'string')
|
|
143
|
-
return false;
|
|
144
|
-
if (!actual.toLowerCase().includes(value.toLowerCase()))
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
95
|
export const getEntries = async (options = {}) => {
|
|
151
|
-
const
|
|
96
|
+
const cms = getCMS();
|
|
97
|
+
const language = options.language || cms.languages[0];
|
|
152
98
|
const status = options.status || 'published';
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
99
|
+
// Fast path: DB-level pagination when limit/offset provided and adapter supports it
|
|
100
|
+
if (options.limit != null && cms.databaseAdapter.getPaginatedEntries) {
|
|
101
|
+
const rows = await cms.databaseAdapter.getPaginatedEntries({
|
|
102
|
+
slug: options.slug,
|
|
103
|
+
ids: options.ids,
|
|
104
|
+
language,
|
|
105
|
+
status,
|
|
106
|
+
dataValues: options.dataValues,
|
|
107
|
+
dataLike: options.dataLike,
|
|
108
|
+
dataILikeOr: options.dataILikeOr,
|
|
109
|
+
orderBy: options.orderBy,
|
|
110
|
+
dataOrderBy: options.dataOrderBy,
|
|
111
|
+
limit: options.limit,
|
|
112
|
+
offset: options.offset ?? 0
|
|
113
|
+
});
|
|
114
|
+
const entries = await Promise.all(rows.map(async ({ entry, version }) => {
|
|
115
|
+
try {
|
|
116
|
+
const config = cms.getBySlug(entry.slug);
|
|
117
|
+
const fields = getFieldsFromConfig(config);
|
|
118
|
+
const populatedData = await populateEntryData(version.data, fields, language);
|
|
119
|
+
const slugPath = getEntrySlugPath(entry.slug);
|
|
120
|
+
const slug = getSlugFromEntryData(version.data, slugPath, language);
|
|
121
|
+
const _url = slug ? getEntryPath(entry.slug, slug) : undefined;
|
|
122
|
+
return {
|
|
123
|
+
_id: entry.id,
|
|
124
|
+
_slug: entry.slug,
|
|
125
|
+
_type: entry.type,
|
|
126
|
+
_publishedAt: version.publishedAt,
|
|
127
|
+
_url,
|
|
128
|
+
...populatedData
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error(`[CMS] Failed to populate entry ${entry.id} (${entry.slug}):`, error);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}));
|
|
136
|
+
return entries.filter((e) => e !== null);
|
|
137
|
+
}
|
|
138
|
+
// Slow path: in-memory pagination (backward compat)
|
|
159
139
|
const ids = options.ids;
|
|
160
140
|
const slug = options.slug;
|
|
161
|
-
|
|
162
|
-
const
|
|
141
|
+
const dataValues = options.dataValues;
|
|
142
|
+
const dataLike = options.dataLike;
|
|
143
|
+
const dataILikeOr = options.dataILikeOr;
|
|
144
|
+
const dbEntries = await cms.databaseAdapter.getEntries({
|
|
163
145
|
ids,
|
|
164
146
|
slug,
|
|
165
147
|
orderBy: options.orderBy
|
|
@@ -167,87 +149,70 @@ export const getEntries = async (options = {}) => {
|
|
|
167
149
|
if (dbEntries.length === 0) {
|
|
168
150
|
return [];
|
|
169
151
|
}
|
|
170
|
-
|
|
152
|
+
const filteredEntries = status === 'archived'
|
|
153
|
+
? dbEntries.filter((e) => e.archivedAt != null)
|
|
154
|
+
: dbEntries.filter((e) => e.archivedAt == null);
|
|
155
|
+
if (filteredEntries.length === 0) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
const entriesMap = new Map(filteredEntries.map((entry) => [entry.id, entry]));
|
|
159
|
+
const entryIds = filteredEntries.map((entry) => entry.id);
|
|
160
|
+
const allVersions = await cms.databaseAdapter.getEntryVersions({
|
|
161
|
+
entryIds,
|
|
162
|
+
lang: language,
|
|
163
|
+
dataValues,
|
|
164
|
+
dataLike,
|
|
165
|
+
dataILikeOr
|
|
166
|
+
});
|
|
171
167
|
const now = new Date();
|
|
172
|
-
const
|
|
168
|
+
const versionEntries = [];
|
|
169
|
+
const versionsByEntry = new Map();
|
|
170
|
+
for (const v of allVersions) {
|
|
171
|
+
const arr = versionsByEntry.get(v.entryId) || [];
|
|
172
|
+
arr.push(v);
|
|
173
|
+
versionsByEntry.set(v.entryId, arr);
|
|
174
|
+
}
|
|
175
|
+
for (const [entryId, versions] of versionsByEntry) {
|
|
176
|
+
const dbEntry = entriesMap.get(entryId);
|
|
177
|
+
if (!dbEntry)
|
|
178
|
+
continue;
|
|
179
|
+
const sorted = versions.sort((a, b) => b.versionNumber - a.versionNumber);
|
|
180
|
+
let picked = null;
|
|
173
181
|
switch (status) {
|
|
174
182
|
case 'published':
|
|
175
|
-
|
|
183
|
+
picked = sorted.find((v) => v.publishedAt != null && v.publishedAt <= now) || null;
|
|
184
|
+
break;
|
|
176
185
|
case 'scheduled':
|
|
177
|
-
|
|
186
|
+
picked = sorted.find((v) => v.publishedAt != null && v.publishedAt > now) || null;
|
|
187
|
+
break;
|
|
178
188
|
case 'draft':
|
|
179
|
-
|
|
189
|
+
picked = sorted.find((v) => v.publishedAt == null) || null;
|
|
190
|
+
break;
|
|
180
191
|
case 'archived':
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return true;
|
|
192
|
+
picked = sorted[0] || null;
|
|
193
|
+
break;
|
|
184
194
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return [];
|
|
188
|
-
}
|
|
189
|
-
// Build entries map for quick lookup
|
|
190
|
-
const entriesMap = new Map(filteredEntries.map((entry) => [entry.id, entry]));
|
|
191
|
-
const entryIds = filteredEntries.map((entry) => entry.id);
|
|
192
|
-
// For published/scheduled: get the specific published version
|
|
193
|
-
// For draft: get latest version (highest versionNumber)
|
|
194
|
-
let versionEntries;
|
|
195
|
-
if (status === 'published' || status === 'scheduled') {
|
|
196
|
-
// Get published versions by their IDs
|
|
197
|
-
const publishedVersionIds = filteredEntries
|
|
198
|
-
.map((e) => e.publishedVersionId)
|
|
199
|
-
.filter((id) => id != null);
|
|
200
|
-
if (publishedVersionIds.length === 0)
|
|
201
|
-
return [];
|
|
202
|
-
const versions = await getCMS().databaseAdapter.getEntryVersions({
|
|
203
|
-
ids: publishedVersionIds
|
|
204
|
-
});
|
|
205
|
-
versionEntries = versions
|
|
206
|
-
.map((v) => {
|
|
207
|
-
const dbEntry = entriesMap.get(v.entryId);
|
|
208
|
-
if (!dbEntry)
|
|
209
|
-
return null;
|
|
210
|
-
return { version: v, dbEntry };
|
|
211
|
-
})
|
|
212
|
-
.filter((e) => e != null);
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
// Draft/archived: get all versions and pick latest
|
|
216
|
-
const allVersions = await getCMS().databaseAdapter.getEntryVersions({
|
|
217
|
-
entryIds
|
|
218
|
-
});
|
|
219
|
-
const latestByEntry = new Map();
|
|
220
|
-
for (const version of allVersions) {
|
|
221
|
-
const dbEntry = entriesMap.get(version.entryId);
|
|
222
|
-
if (!dbEntry)
|
|
223
|
-
continue;
|
|
224
|
-
const existing = latestByEntry.get(version.entryId);
|
|
225
|
-
if (!existing || version.versionNumber > existing.version.versionNumber) {
|
|
226
|
-
latestByEntry.set(version.entryId, { version, dbEntry });
|
|
227
|
-
}
|
|
195
|
+
if (picked) {
|
|
196
|
+
versionEntries.push({ version: picked, dbEntry });
|
|
228
197
|
}
|
|
229
|
-
versionEntries = Array.from(latestByEntry.values());
|
|
230
198
|
}
|
|
231
|
-
// Re-sort to match original entry order (DB returns sorted by sortOrder, but version fetch may reorder)
|
|
232
199
|
const entryOrder = new Map(filteredEntries.map((e, i) => [e.id, i]));
|
|
233
200
|
versionEntries.sort((a, b) => (entryOrder.get(a.dbEntry.id) ?? 0) - (entryOrder.get(b.dbEntry.id) ?? 0));
|
|
234
|
-
// Post-filter: apply dataValues/dataLike on versions
|
|
235
|
-
if (dataValues) {
|
|
236
|
-
versionEntries = versionEntries.filter((e) => matchesDataValues(e.version.data, dataValues));
|
|
237
|
-
}
|
|
238
|
-
if (dataLike) {
|
|
239
|
-
versionEntries = versionEntries.filter((e) => matchesDataLike(e.version.data, dataLike));
|
|
240
|
-
}
|
|
241
|
-
// Process entries in parallel
|
|
242
201
|
const entries = await Promise.all(versionEntries.map(async ({ version, dbEntry }) => {
|
|
243
202
|
try {
|
|
244
|
-
const config =
|
|
203
|
+
const config = cms.getBySlug(dbEntry.slug);
|
|
204
|
+
const fields = getFieldsFromConfig(config);
|
|
205
|
+
const populatedData = await populateEntryData(version.data, fields, language);
|
|
206
|
+
const slugPath = getEntrySlugPath(dbEntry.slug);
|
|
207
|
+
const slug = getSlugFromEntryData(version.data, slugPath, language);
|
|
208
|
+
const _url = slug ? getEntryPath(dbEntry.slug, slug) : undefined;
|
|
245
209
|
return {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
210
|
+
_id: dbEntry.id,
|
|
211
|
+
_slug: dbEntry.slug,
|
|
212
|
+
_type: dbEntry.type,
|
|
213
|
+
_publishedAt: version.publishedAt,
|
|
214
|
+
_url,
|
|
215
|
+
...populatedData
|
|
251
216
|
};
|
|
252
217
|
}
|
|
253
218
|
catch (error) {
|
|
@@ -255,7 +220,31 @@ export const getEntries = async (options = {}) => {
|
|
|
255
220
|
return null;
|
|
256
221
|
}
|
|
257
222
|
}));
|
|
258
|
-
|
|
223
|
+
let results = entries.filter((e) => e !== null);
|
|
224
|
+
if (options.offset)
|
|
225
|
+
results = results.slice(options.offset);
|
|
226
|
+
if (options.limit)
|
|
227
|
+
results = results.slice(0, options.limit);
|
|
228
|
+
return results;
|
|
229
|
+
};
|
|
230
|
+
export const countEntries = async (options) => {
|
|
231
|
+
const cms = getCMS();
|
|
232
|
+
const language = options.language || cms.languages[0];
|
|
233
|
+
const status = options.status || 'published';
|
|
234
|
+
if (cms.databaseAdapter.countPaginatedEntries) {
|
|
235
|
+
return cms.databaseAdapter.countPaginatedEntries({
|
|
236
|
+
slug: options.slug,
|
|
237
|
+
ids: options.ids,
|
|
238
|
+
language,
|
|
239
|
+
status,
|
|
240
|
+
dataValues: options.dataValues,
|
|
241
|
+
dataLike: options.dataLike,
|
|
242
|
+
dataILikeOr: options.dataILikeOr
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// Fallback: use old getEntries without limit/offset (counts all)
|
|
246
|
+
const entries = await getEntries({ ...options });
|
|
247
|
+
return entries.length;
|
|
259
248
|
};
|
|
260
249
|
export const getEntry = async (options = {}) => {
|
|
261
250
|
const [entry] = await getEntries({
|
|
@@ -282,41 +271,38 @@ export const getEntryLabels = async (options) => {
|
|
|
282
271
|
});
|
|
283
272
|
if (dbEntries.length === 0)
|
|
284
273
|
return [];
|
|
285
|
-
|
|
274
|
+
const entryIds = dbEntries.map((e) => e.id);
|
|
275
|
+
const language = cms.languages[0];
|
|
276
|
+
// Get versions for default language
|
|
277
|
+
const allVersions = await cms.databaseAdapter.getEntryVersions({
|
|
278
|
+
entryIds,
|
|
279
|
+
lang: language
|
|
280
|
+
});
|
|
286
281
|
const now = new Date();
|
|
287
282
|
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
283
|
const entryAdminTitle = config.entryAdminTitle;
|
|
303
|
-
let results =
|
|
304
|
-
// Pick published version or latest draft
|
|
284
|
+
let results = dbEntries.map((entry) => {
|
|
305
285
|
const entryVersions = allVersions.filter((v) => v.entryId === entry.id);
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
|
|
286
|
+
const sorted = entryVersions.sort((a, b) => b.versionNumber - a.versionNumber);
|
|
287
|
+
// Determine status from versions
|
|
288
|
+
const publishedVersion = sorted.find((v) => v.publishedAt != null && v.publishedAt <= now) || null;
|
|
289
|
+
const hasPublished = publishedVersion != null;
|
|
290
|
+
// Filter by status
|
|
291
|
+
if (statusFilter === 'published' && !hasPublished)
|
|
292
|
+
return null;
|
|
293
|
+
if (statusFilter === 'draft' && hasPublished)
|
|
294
|
+
return null;
|
|
295
|
+
const latestVersion = publishedVersion ?? sorted[0] ?? null;
|
|
311
296
|
let label = entry.id;
|
|
312
297
|
if (entryAdminTitle && latestVersion) {
|
|
313
298
|
const titleData = latestVersion.data[entryAdminTitle];
|
|
314
|
-
|
|
315
|
-
|
|
299
|
+
// Data is now flat — titleData is the string directly
|
|
300
|
+
if (typeof titleData === 'string') {
|
|
301
|
+
label = titleData || entry.id;
|
|
316
302
|
}
|
|
317
303
|
}
|
|
318
304
|
return { id: entry.id, label };
|
|
319
|
-
});
|
|
305
|
+
}).filter((r) => r != null);
|
|
320
306
|
// Post-query search filtering (case-insensitive includes)
|
|
321
307
|
if (options.search) {
|
|
322
308
|
const searchLower = options.search.toLowerCase();
|
|
@@ -358,9 +344,18 @@ export const getEntryVersion = async (options) => {
|
|
|
358
344
|
}
|
|
359
345
|
try {
|
|
360
346
|
const config = getCMS().getBySlug(dbEntry.slug);
|
|
347
|
+
const fields = getFieldsFromConfig(config);
|
|
348
|
+
const populatedData = await populateEntryData(dbEntryVersion.data, fields, language);
|
|
349
|
+
const slugPath = getEntrySlugPath(dbEntry.slug);
|
|
350
|
+
const entrySlug = getSlugFromEntryData(dbEntryVersion.data, slugPath, language);
|
|
351
|
+
const _url = entrySlug ? getEntryPath(dbEntry.slug, entrySlug) : undefined;
|
|
361
352
|
return {
|
|
362
|
-
|
|
363
|
-
|
|
353
|
+
_id: dbEntry.id,
|
|
354
|
+
_slug: dbEntry.slug,
|
|
355
|
+
_type: dbEntry.type,
|
|
356
|
+
_publishedAt: dbEntryVersion.publishedAt,
|
|
357
|
+
_url,
|
|
358
|
+
...populatedData
|
|
364
359
|
};
|
|
365
360
|
}
|
|
366
361
|
catch (error) {
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { DbEntry, DbEntryVersion } from '../../../../types/entries.js';
|
|
2
2
|
import z from 'zod';
|
|
3
3
|
export declare const updateEntrySchema: z.ZodObject<{
|
|
4
|
-
availableLocales: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
5
4
|
archivedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
|
|
6
|
-
publishedAt: z.ZodOptional<z.ZodNullable<z.ZodDate>>;
|
|
7
|
-
publishedVersionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
8
|
-
publishedBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
9
5
|
sortOrder: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
10
6
|
}, z.z.core.$strip>;
|
|
11
7
|
export declare const updateEntry: (id: string, data: Partial<DbEntry>) => Promise<DbEntry>;
|
|
@@ -17,8 +13,11 @@ export declare const updateEntryVersionSchema: z.ZodObject<{
|
|
|
17
13
|
export declare const updateEntryVersion: (id: string, data: Partial<DbEntryVersion>) => Promise<DbEntryVersion>;
|
|
18
14
|
export declare const updateEntryVersionCommandTypes: readonly ["draft", "published-now", "published-scheduled", "cancel-published"];
|
|
19
15
|
export type UpdateEntryVersionCommandType = (typeof updateEntryVersionCommandTypes)[number];
|
|
20
|
-
|
|
21
|
-
export declare const
|
|
16
|
+
/** Prune old draft versions scoped to (entryId, lang) */
|
|
17
|
+
export declare const pruneOldDraftVersions: (entryId: string, lang: string) => Promise<void>;
|
|
18
|
+
/** Upsert draft version scoped to (entryId, lang) */
|
|
19
|
+
export declare const upsertDraftVersion: (entryId: string, data: Record<string, unknown>, lang: string, options?: {
|
|
22
20
|
skipValidation?: boolean;
|
|
23
21
|
}) => Promise<DbEntryVersion>;
|
|
24
|
-
|
|
22
|
+
/** Unpublish a specific language version for an entry */
|
|
23
|
+
export declare const unpublishEntryLang: (entryId: string, lang: string) => Promise<void>;
|
|
@@ -5,11 +5,7 @@ import z from 'zod';
|
|
|
5
5
|
import { getDbEntryOrThrow, getDbEntryVersionOrThrow, getDbEntryVersions } from './get.js';
|
|
6
6
|
import { createEntryVersion } from './create.js';
|
|
7
7
|
export const updateEntrySchema = z.object({
|
|
8
|
-
availableLocales: z.array(z.string()).optional(),
|
|
9
8
|
archivedAt: z.date().nullable().optional(),
|
|
10
|
-
publishedAt: z.date().nullable().optional(),
|
|
11
|
-
publishedVersionId: z.string().uuid().nullable().optional(),
|
|
12
|
-
publishedBy: z.string().nullable().optional(),
|
|
13
9
|
sortOrder: z.number().int().nullable().optional()
|
|
14
10
|
});
|
|
15
11
|
export const updateEntry = async (id, data) => {
|
|
@@ -19,21 +15,9 @@ export const updateEntry = async (id, data) => {
|
|
|
19
15
|
}
|
|
20
16
|
const filteredData = filteredDataParse.data;
|
|
21
17
|
const dataToUpdate = {};
|
|
22
|
-
if (filteredData.availableLocales !== undefined) {
|
|
23
|
-
dataToUpdate.availableLocales = filteredData.availableLocales;
|
|
24
|
-
}
|
|
25
18
|
if (filteredData.archivedAt !== undefined) {
|
|
26
19
|
dataToUpdate.archivedAt = filteredData.archivedAt;
|
|
27
20
|
}
|
|
28
|
-
if (filteredData.publishedAt !== undefined) {
|
|
29
|
-
dataToUpdate.publishedAt = filteredData.publishedAt;
|
|
30
|
-
}
|
|
31
|
-
if (filteredData.publishedVersionId !== undefined) {
|
|
32
|
-
dataToUpdate.publishedVersionId = filteredData.publishedVersionId;
|
|
33
|
-
}
|
|
34
|
-
if (filteredData.publishedBy !== undefined) {
|
|
35
|
-
dataToUpdate.publishedBy = filteredData.publishedBy;
|
|
36
|
-
}
|
|
37
21
|
if (filteredData.sortOrder !== undefined) {
|
|
38
22
|
dataToUpdate.sortOrder = filteredData.sortOrder;
|
|
39
23
|
}
|
|
@@ -63,7 +47,10 @@ export const updateEntryVersion = async (id, data) => {
|
|
|
63
47
|
const entry = await getDbEntryOrThrow({ id: version.entryId });
|
|
64
48
|
const config = getCMS().getBySlug(entry.slug);
|
|
65
49
|
const languages = getCMS().languages;
|
|
66
|
-
|
|
50
|
+
// Validate with localized: false — data is flat single-language
|
|
51
|
+
const schema = generateZodSchemaFromFields(getFieldsFromConfig(config), languages, {
|
|
52
|
+
localized: false
|
|
53
|
+
});
|
|
67
54
|
const parsedData = schema.safeParse(filteredData.data);
|
|
68
55
|
if (!parsedData.success) {
|
|
69
56
|
throw Error('Invalid data: ' + parsedData.error.flatten());
|
|
@@ -92,28 +79,27 @@ export const updateEntryVersionCommandTypes = [
|
|
|
92
79
|
'cancel-published'
|
|
93
80
|
];
|
|
94
81
|
const MAX_DRAFT_VERSIONS = 10;
|
|
95
|
-
|
|
96
|
-
|
|
82
|
+
/** Prune old draft versions scoped to (entryId, lang) */
|
|
83
|
+
export const pruneOldDraftVersions = async (entryId, lang) => {
|
|
97
84
|
const versions = await getCMS().databaseAdapter.getEntryVersions({
|
|
98
|
-
entryIds: [entryId]
|
|
85
|
+
entryIds: [entryId],
|
|
86
|
+
lang
|
|
99
87
|
});
|
|
100
88
|
// Sort by version number desc
|
|
101
89
|
const sorted = versions.sort((a, b) => b.versionNumber - a.versionNumber);
|
|
102
|
-
//
|
|
103
|
-
const
|
|
104
|
-
const draftVersions = sorted.filter((v) => v.id !== publishedVersionId);
|
|
105
|
-
// Keep published version + up to MAX_DRAFT_VERSIONS drafts
|
|
90
|
+
// Keep published versions + up to MAX_DRAFT_VERSIONS drafts
|
|
91
|
+
const draftVersions = sorted.filter((v) => v.publishedAt == null);
|
|
106
92
|
if (draftVersions.length > MAX_DRAFT_VERSIONS) {
|
|
107
93
|
const toDelete = draftVersions.slice(MAX_DRAFT_VERSIONS);
|
|
108
94
|
await Promise.all(toDelete.map((v) => getCMS().databaseAdapter.deleteEntryVersion({ id: v.id })));
|
|
109
95
|
}
|
|
110
96
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const versions = await getDbEntryVersions({ entryIds: [entryId] });
|
|
114
|
-
// Sort desc by versionNumber, find latest
|
|
97
|
+
/** Upsert draft version scoped to (entryId, lang) */
|
|
98
|
+
export const upsertDraftVersion = async (entryId, data, lang, options) => {
|
|
99
|
+
const versions = await getDbEntryVersions({ entryIds: [entryId], lang });
|
|
100
|
+
// Sort desc by versionNumber, find latest unpublished draft
|
|
115
101
|
const sorted = versions.sort((a, b) => b.versionNumber - a.versionNumber);
|
|
116
|
-
const latestDraft = sorted.find((v) => v.
|
|
102
|
+
const latestDraft = sorted.find((v) => v.publishedAt == null);
|
|
117
103
|
if (latestDraft) {
|
|
118
104
|
// Compare data — if identical, skip
|
|
119
105
|
const existingData = JSON.stringify(latestDraft.data);
|
|
@@ -132,17 +118,13 @@ export const upsertDraftVersion = async (entryId, data, options) => {
|
|
|
132
118
|
return updated;
|
|
133
119
|
}
|
|
134
120
|
// No draft exists — create new version
|
|
135
|
-
return createEntryVersion({ entryId, data }, options);
|
|
121
|
+
return createEntryVersion({ entryId, lang, data }, options);
|
|
136
122
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
publishedVersionId: null,
|
|
140
|
-
publishedAt: null,
|
|
141
|
-
publishedBy: null
|
|
142
|
-
});
|
|
143
|
-
// Dual-write: clear version-level publishedAt for backward compat
|
|
123
|
+
/** Unpublish a specific language version for an entry */
|
|
124
|
+
export const unpublishEntryLang = async (entryId, lang) => {
|
|
144
125
|
const versions = await getCMS().databaseAdapter.getEntryVersions({
|
|
145
|
-
entryIds: [entryId]
|
|
126
|
+
entryIds: [entryId],
|
|
127
|
+
lang
|
|
146
128
|
});
|
|
147
129
|
await Promise.all(versions
|
|
148
130
|
.filter((v) => v.publishedAt !== null)
|