includio-cms 0.7.2 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/ROADMAP.md +54 -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 +35 -13
- 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/client/users/users-page.svelte +5 -6
- package/dist/admin/client/users/users-page.svelte.d.ts +1 -4
- package/dist/admin/components/fields/block-picker-modal.svelte +13 -4
- package/dist/admin/components/fields/blocks-field.svelte +40 -19
- 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 +27 -16
- 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/layout/layout-renderer.svelte +10 -4
- package/dist/admin/components/media/file-preview.svelte +10 -1
- 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/files-list.svelte +12 -3
- package/dist/admin/components/media/media-library.svelte +109 -37
- package/dist/admin/components/media/media-selector.svelte +90 -16
- 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/FigureNodeView.svelte +15 -10
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +53 -94
- package/dist/admin/components/tiptap/SlashCommandPopup.svelte +8 -3
- package/dist/admin/components/tiptap/editor-toolbar.svelte +28 -23
- package/dist/admin/components/tiptap/image-dialog.svelte +12 -7
- package/dist/admin/components/tiptap/inline-block-node.js +6 -5
- package/dist/admin/components/tiptap/lang.d.ts +77 -0
- package/dist/admin/components/tiptap/lang.js +170 -0
- package/dist/admin/components/tiptap/link-dialog.svelte +31 -28
- package/dist/admin/components/tiptap/slash-command.js +27 -23
- package/dist/admin/components/tiptap/table-dialog.svelte +9 -4
- package/dist/admin/components/tiptap/video-dialog.svelte +6 -1
- package/dist/admin/remote/email.remote.d.ts +1 -0
- package/dist/admin/remote/email.remote.js +5 -0
- package/dist/admin/remote/entry.remote.d.ts +2 -5
- package/dist/admin/remote/entry.remote.js +23 -28
- package/dist/admin/remote/index.d.ts +1 -0
- package/dist/admin/remote/index.js +1 -0
- 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/layout.d.ts +0 -1
- 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.13.1/index.d.ts +2 -0
- package/dist/updates/0.13.1/index.js +20 -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 +9 -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
|
@@ -10,9 +10,161 @@ function serializeField(field) {
|
|
|
10
10
|
function serializeFields(fields) {
|
|
11
11
|
return fields.map(serializeField);
|
|
12
12
|
}
|
|
13
|
+
function localize(value, languages) {
|
|
14
|
+
const obj = {};
|
|
15
|
+
for (const lang of languages) {
|
|
16
|
+
obj[lang] = value;
|
|
17
|
+
}
|
|
18
|
+
return obj;
|
|
19
|
+
}
|
|
20
|
+
function generateFieldTemplate(field, languages) {
|
|
21
|
+
const wrap = (v, hint) => {
|
|
22
|
+
if (field.localized) {
|
|
23
|
+
return { value: localize(v, languages), hint: `${hint} (localized)` };
|
|
24
|
+
}
|
|
25
|
+
return { value: v, hint };
|
|
26
|
+
};
|
|
27
|
+
switch (field.type) {
|
|
28
|
+
case 'text':
|
|
29
|
+
case 'slug':
|
|
30
|
+
return wrap('', 'string');
|
|
31
|
+
case 'content':
|
|
32
|
+
return wrap({ type: 'doc', content: [] }, 'ProseMirror JSON document');
|
|
33
|
+
case 'number':
|
|
34
|
+
return wrap(0, 'number');
|
|
35
|
+
case 'boolean':
|
|
36
|
+
return { value: false, hint: 'boolean' };
|
|
37
|
+
case 'date':
|
|
38
|
+
return wrap('2024-01-01', 'ISO date string');
|
|
39
|
+
case 'datetime':
|
|
40
|
+
return wrap('2024-01-01T00:00:00Z', 'ISO datetime string');
|
|
41
|
+
case 'file':
|
|
42
|
+
if (field.multiple) {
|
|
43
|
+
return { value: ['<media-uuid-from-upload>'], hint: 'array of media UUIDs from POST /upload' };
|
|
44
|
+
}
|
|
45
|
+
return { value: '<media-uuid-from-upload>', hint: 'media UUID from POST /upload' };
|
|
46
|
+
case 'media':
|
|
47
|
+
if (field.multiple) {
|
|
48
|
+
return { value: ['<media-uuid-from-upload>'], hint: 'array of media UUIDs (image or video)' };
|
|
49
|
+
}
|
|
50
|
+
return { value: '<media-uuid-from-upload>', hint: 'media UUID (image or video) from POST /upload' };
|
|
51
|
+
case 'select': {
|
|
52
|
+
const sf = field;
|
|
53
|
+
const opts = sf.options.map((o) => o.value);
|
|
54
|
+
if (sf.multiple) {
|
|
55
|
+
return { value: opts.length ? [opts[0]] : [], hint: `array of: ${opts.join(' | ')}` };
|
|
56
|
+
}
|
|
57
|
+
return { value: opts[0] ?? '', hint: `one of: ${opts.join(' | ')}` };
|
|
58
|
+
}
|
|
59
|
+
case 'radio': {
|
|
60
|
+
const rf = field;
|
|
61
|
+
const opts = rf.options.map((o) => o.value);
|
|
62
|
+
return { value: opts[0] ?? '', hint: `one of: ${opts.join(' | ')}` };
|
|
63
|
+
}
|
|
64
|
+
case 'checkboxes': {
|
|
65
|
+
const cf = field;
|
|
66
|
+
const opts = cf.options.map((o) => o.value);
|
|
67
|
+
return { value: opts.length ? [opts[0]] : [], hint: `array of: ${opts.join(' | ')}` };
|
|
68
|
+
}
|
|
69
|
+
case 'relation': {
|
|
70
|
+
const rf = field;
|
|
71
|
+
if (rf.multiple) {
|
|
72
|
+
return { value: ['<entry-uuid>'], hint: `array of ${rf.collection} entry UUIDs` };
|
|
73
|
+
}
|
|
74
|
+
return { value: '<entry-uuid>', hint: `${rf.collection} entry UUID` };
|
|
75
|
+
}
|
|
76
|
+
case 'object': {
|
|
77
|
+
const of = field;
|
|
78
|
+
const { template, meta } = generateTemplate(of.fields, languages);
|
|
79
|
+
return { value: template, hint: `object — ${JSON.stringify(meta)}` };
|
|
80
|
+
}
|
|
81
|
+
case 'blocks': {
|
|
82
|
+
const bf = field;
|
|
83
|
+
const blocks = bf.of.map((block) => {
|
|
84
|
+
const { template } = generateTemplate(block.fields, languages);
|
|
85
|
+
return { _slug: block.slug, ...template };
|
|
86
|
+
});
|
|
87
|
+
return { value: blocks, hint: 'array of block objects with _slug' };
|
|
88
|
+
}
|
|
89
|
+
case 'array': {
|
|
90
|
+
const af = field;
|
|
91
|
+
const examples = { text: '', number: 0, url: { url: { [languages[0]]: '' } } };
|
|
92
|
+
return { value: [examples[af.of] ?? ''], hint: `array of ${af.of}` };
|
|
93
|
+
}
|
|
94
|
+
case 'seo':
|
|
95
|
+
return {
|
|
96
|
+
value: {
|
|
97
|
+
slug: localize('', languages),
|
|
98
|
+
title: localize('', languages),
|
|
99
|
+
description: localize('', languages),
|
|
100
|
+
ogImage: '',
|
|
101
|
+
keywords: localize('', languages),
|
|
102
|
+
canonicalUrl: localize('', languages),
|
|
103
|
+
customCode: localize('', languages)
|
|
104
|
+
},
|
|
105
|
+
hint: 'SEO data — slug/title required, ogImage = media UUID or empty'
|
|
106
|
+
};
|
|
107
|
+
case 'url':
|
|
108
|
+
return {
|
|
109
|
+
value: {
|
|
110
|
+
url: localize('', languages),
|
|
111
|
+
text: localize('', languages),
|
|
112
|
+
newTab: false
|
|
113
|
+
},
|
|
114
|
+
hint: 'URL field with optional text and newTab'
|
|
115
|
+
};
|
|
116
|
+
case 'custom':
|
|
117
|
+
return { value: null, hint: 'custom field — check plugin docs' };
|
|
118
|
+
default:
|
|
119
|
+
return { value: null, hint: 'unknown field type' };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function generateTemplate(fields, languages) {
|
|
123
|
+
const template = {};
|
|
124
|
+
const meta = {};
|
|
125
|
+
for (const field of fields) {
|
|
126
|
+
const { value, hint } = generateFieldTemplate(field, languages);
|
|
127
|
+
template[field.slug] = value;
|
|
128
|
+
meta[field.slug] = hint;
|
|
129
|
+
}
|
|
130
|
+
return { template, meta };
|
|
131
|
+
}
|
|
13
132
|
export async function GET(event) {
|
|
14
133
|
const cms = getCMS();
|
|
15
134
|
const path = event.params.restPath || '';
|
|
135
|
+
// GET /schema/collections/:slug/template
|
|
136
|
+
const templateMatch = path.match(/^collections\/([^/]+)\/template$/);
|
|
137
|
+
if (templateMatch) {
|
|
138
|
+
const slug = templateMatch[1];
|
|
139
|
+
const collection = cms.collections[slug];
|
|
140
|
+
if (!collection) {
|
|
141
|
+
return json({ error: `Collection "${slug}" not found` }, { status: 404 });
|
|
142
|
+
}
|
|
143
|
+
const fields = getFieldsFromConfig(collection);
|
|
144
|
+
const { template, meta } = generateTemplate(fields, cms.languages);
|
|
145
|
+
return json({
|
|
146
|
+
data: template,
|
|
147
|
+
publish: true,
|
|
148
|
+
_meta: meta,
|
|
149
|
+
_notes: 'POST this to /collections/' + slug + ' — set publish:true to auto-publish'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// GET /schema/singletons/:slug/template
|
|
153
|
+
const singletonTemplateMatch = path.match(/^singletons\/([^/]+)\/template$/);
|
|
154
|
+
if (singletonTemplateMatch) {
|
|
155
|
+
const slug = singletonTemplateMatch[1];
|
|
156
|
+
const single = cms.singles[slug];
|
|
157
|
+
if (!single) {
|
|
158
|
+
return json({ error: `Singleton "${slug}" not found` }, { status: 404 });
|
|
159
|
+
}
|
|
160
|
+
const fields = getFieldsFromConfig(single);
|
|
161
|
+
const { template, meta } = generateTemplate(fields, cms.languages);
|
|
162
|
+
return json({
|
|
163
|
+
data: template,
|
|
164
|
+
_meta: meta,
|
|
165
|
+
_notes: 'PUT this to /singletons/' + slug
|
|
166
|
+
});
|
|
167
|
+
}
|
|
16
168
|
// GET /schema/collections/:slug
|
|
17
169
|
const collectionMatch = path.match(/^collections\/([^/]+)$/);
|
|
18
170
|
if (collectionMatch) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type RequestEvent } from '@sveltejs/kit';
|
|
2
|
-
export declare function GET(
|
|
2
|
+
export declare function GET(event: RequestEvent, slug: string): Promise<Response>;
|
|
3
3
|
export declare function PUT(event: RequestEvent, slug: string): Promise<Response>;
|
|
@@ -3,11 +3,12 @@ import { getCMS } from '../../../../core/cms.js';
|
|
|
3
3
|
import { getRawEntries } from '../../../../core/server/entries/operations/get.js';
|
|
4
4
|
import { createEntry } from '../../../../core/server/entries/operations/create.js';
|
|
5
5
|
import { upsertDraftVersion } from '../../../../core/server/entries/operations/update.js';
|
|
6
|
-
export async function GET(
|
|
6
|
+
export async function GET(event, slug) {
|
|
7
7
|
const cms = getCMS();
|
|
8
8
|
if (!cms.singles[slug]) {
|
|
9
9
|
return json({ error: `Singleton "${slug}" not found` }, { status: 404 });
|
|
10
10
|
}
|
|
11
|
+
const lang = event.url.searchParams.get('lang') || cms.languages[0] || 'en';
|
|
11
12
|
const entries = await getRawEntries({ slug });
|
|
12
13
|
const entry = entries[0];
|
|
13
14
|
if (!entry) {
|
|
@@ -24,11 +25,10 @@ export async function GET(_event, slug) {
|
|
|
24
25
|
type: entry.type,
|
|
25
26
|
createdAt: entry.createdAt,
|
|
26
27
|
updatedAt: entry.updatedAt,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
publishedVersionId: entry.publishedVersion?.id ?? null
|
|
28
|
+
draftData: entry.draftVersions[lang]?.data ?? null,
|
|
29
|
+
publishedData: entry.publishedVersions[lang]?.data ?? null,
|
|
30
|
+
draftVersionId: entry.draftVersions[lang]?.id ?? null,
|
|
31
|
+
publishedVersionId: entry.publishedVersions[lang]?.id ?? null
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
export async function PUT(event, slug) {
|
|
@@ -36,6 +36,7 @@ export async function PUT(event, slug) {
|
|
|
36
36
|
if (!cms.singles[slug]) {
|
|
37
37
|
return json({ error: `Singleton "${slug}" not found` }, { status: 404 });
|
|
38
38
|
}
|
|
39
|
+
const lang = event.url.searchParams.get('lang') || cms.languages[0] || 'en';
|
|
39
40
|
const body = await event.request.json().catch(() => null);
|
|
40
41
|
if (!body?.data || typeof body.data !== 'object') {
|
|
41
42
|
return json({ error: 'Request body must contain "data" object' }, { status: 400 });
|
|
@@ -51,7 +52,7 @@ export async function PUT(event, slug) {
|
|
|
51
52
|
return json({ error: 'Failed to create singleton entry' }, { status: 500 });
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
|
-
const version = await upsertDraftVersion(entry.id, body.data, { skipValidation: true });
|
|
55
|
+
const version = await upsertDraftVersion(entry.id, body.data, lang, { skipValidation: true });
|
|
55
56
|
return json({
|
|
56
57
|
id: entry.id,
|
|
57
58
|
versionId: version.id,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { json } from '@sveltejs/kit';
|
|
2
|
+
import { uploadFile } from '../../../../core/server/media/operations/uploadFile.js';
|
|
3
|
+
import { getCMS } from '../../../../core/cms.js';
|
|
4
|
+
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024; // 50MB
|
|
5
|
+
export async function POST(event) {
|
|
6
|
+
const form = await event.request.formData();
|
|
7
|
+
const file = form.get('file');
|
|
8
|
+
const tagIdsRaw = form.get('tagIds');
|
|
9
|
+
if (!file || file.size === 0) {
|
|
10
|
+
return json({ error: 'No file provided' }, { status: 400 });
|
|
11
|
+
}
|
|
12
|
+
if (file.size > MAX_UPLOAD_SIZE) {
|
|
13
|
+
return json({ error: 'File too large (max 50MB)' }, { status: 413 });
|
|
14
|
+
}
|
|
15
|
+
const dbFile = await uploadFile(file);
|
|
16
|
+
if (tagIdsRaw) {
|
|
17
|
+
try {
|
|
18
|
+
const tagIds = JSON.parse(tagIdsRaw);
|
|
19
|
+
if (Array.isArray(tagIds) && tagIds.length > 0) {
|
|
20
|
+
await getCMS().databaseAdapter.setMediaFileTags(dbFile.id, tagIds);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// ignore invalid tagIds JSON
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return json(dbFile, { status: 201 });
|
|
28
|
+
}
|
package/dist/admin/api/upload.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { requireAuth } from '../remote/middleware/auth.js';
|
|
2
2
|
import { uploadFile } from '../../core/server/media/operations/uploadFile.js';
|
|
3
|
+
import { getCMS } from '../../core/cms.js';
|
|
3
4
|
import { json } from '@sveltejs/kit';
|
|
4
5
|
const MAX_UPLOAD_SIZE = 50 * 1024 * 1024; // 50MB
|
|
5
6
|
export const POST = async ({ request }) => {
|
|
6
7
|
requireAuth();
|
|
7
8
|
const form = await request.formData();
|
|
8
9
|
const file = form.get('file');
|
|
10
|
+
const tagIdsRaw = form.get('tagIds');
|
|
9
11
|
if (!file || file.size === 0) {
|
|
10
12
|
return new Response('No file', { status: 400 });
|
|
11
13
|
}
|
|
@@ -13,5 +15,16 @@ export const POST = async ({ request }) => {
|
|
|
13
15
|
return new Response('File too large', { status: 413 });
|
|
14
16
|
}
|
|
15
17
|
const dbFile = await uploadFile(file);
|
|
18
|
+
if (tagIdsRaw) {
|
|
19
|
+
try {
|
|
20
|
+
const tagIds = JSON.parse(tagIdsRaw);
|
|
21
|
+
if (Array.isArray(tagIds) && tagIds.length > 0) {
|
|
22
|
+
await getCMS().databaseAdapter.setMediaFileTags(dbFile.id, tagIds);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// ignore invalid tagIds JSON
|
|
27
|
+
}
|
|
28
|
+
}
|
|
16
29
|
return json(dbFile);
|
|
17
30
|
};
|
|
@@ -378,8 +378,20 @@
|
|
|
378
378
|
pendingDeleteId = null;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
function getVersionData(entry: RawEntry): Record<string, unknown> | undefined {
|
|
382
|
+
const lang = contentLanguage.current;
|
|
383
|
+
const draft = entry.draftVersions[lang];
|
|
384
|
+
const published = entry.publishedVersions[lang];
|
|
385
|
+
if (draft?.data) return draft.data as Record<string, unknown>;
|
|
386
|
+
if (published?.data) return published.data as Record<string, unknown>;
|
|
387
|
+
// Fallback: any lang
|
|
388
|
+
for (const v of Object.values(entry.draftVersions)) if (v?.data) return v.data as Record<string, unknown>;
|
|
389
|
+
for (const v of Object.values(entry.publishedVersions)) if (v?.data) return v.data as Record<string, unknown>;
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
|
|
381
393
|
function getSearchText(entry: RawEntry): string {
|
|
382
|
-
const data = entry
|
|
394
|
+
const data = getVersionData(entry);
|
|
383
395
|
if (!data) return '';
|
|
384
396
|
return extractTextValues(data).join(' ').toLowerCase();
|
|
385
397
|
}
|
|
@@ -404,7 +416,7 @@
|
|
|
404
416
|
|
|
405
417
|
function extractSlug(entry: RawEntry): string | undefined {
|
|
406
418
|
if (!seoField) return undefined;
|
|
407
|
-
const data = entry
|
|
419
|
+
const data = getVersionData(entry);
|
|
408
420
|
if (!data) return undefined;
|
|
409
421
|
const seoData = data[seoField.slug];
|
|
410
422
|
if (seoData && typeof seoData === 'object' && 'slug' in seoData) {
|
|
@@ -414,13 +426,15 @@
|
|
|
414
426
|
}
|
|
415
427
|
|
|
416
428
|
function countA11yWarnings(entry: RawEntry): number {
|
|
417
|
-
const data = entry
|
|
429
|
+
const data = getVersionData(entry);
|
|
418
430
|
if (!data) return 0;
|
|
419
431
|
const a11yLang = interfaceLanguage.current === 'pl' ? a11yLangPl : a11yLangEn;
|
|
420
432
|
const issues = validateA11y(data as Record<string, unknown>, collection.fields, a11yLang);
|
|
421
433
|
return issues.filter((i) => i.type === 'warning').length;
|
|
422
434
|
}
|
|
423
435
|
|
|
436
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
437
|
+
|
|
424
438
|
async function fetchRelationLabels(entries: RawEntry[]): Promise<Record<string, string>> {
|
|
425
439
|
if (relationListFields.length === 0) return {};
|
|
426
440
|
|
|
@@ -429,14 +443,21 @@
|
|
|
429
443
|
uuidsByCollection.set(field.collection, new Set());
|
|
430
444
|
}
|
|
431
445
|
for (const entry of entries) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
446
|
+
// Collect UUIDs from all version sources (draft + published) to match mapEntryToRow
|
|
447
|
+
const lang = contentLanguage.current;
|
|
448
|
+
const dataSources = [
|
|
449
|
+
entry.publishedVersions[lang]?.data,
|
|
450
|
+
entry.draftVersions[lang]?.data,
|
|
451
|
+
getVersionData(entry)
|
|
452
|
+
].filter(Boolean) as Record<string, unknown>[];
|
|
453
|
+
for (const data of dataSources) {
|
|
454
|
+
for (const field of relationListFields) {
|
|
455
|
+
const raw = data[field.slug];
|
|
456
|
+
if (!raw) continue;
|
|
457
|
+
const set = uuidsByCollection.get(field.collection)!;
|
|
458
|
+
if (typeof raw === 'string' && UUID_RE.test(raw)) set.add(raw);
|
|
459
|
+
if (Array.isArray(raw)) raw.forEach((v) => typeof v === 'string' && UUID_RE.test(v) && set.add(v));
|
|
460
|
+
}
|
|
440
461
|
}
|
|
441
462
|
}
|
|
442
463
|
|
|
@@ -454,9 +475,10 @@
|
|
|
454
475
|
}
|
|
455
476
|
|
|
456
477
|
function mapEntryToRow(entry: RawEntry, lookup: Record<string, string> = {}): CollectionDataTableRow {
|
|
457
|
-
const data = entry
|
|
478
|
+
const data = getVersionData(entry) || {};
|
|
458
479
|
// For custom columns, prefer published data (complete) over draft (may be partial)
|
|
459
|
-
const
|
|
480
|
+
const lang = contentLanguage.current;
|
|
481
|
+
const columnData = entry.publishedVersions[lang]?.data || entry.draftVersions[lang]?.data || data;
|
|
460
482
|
const customData: Record<string, unknown> = {};
|
|
461
483
|
if (collection.listColumns) {
|
|
462
484
|
for (const fieldSlug of collection.listColumns) {
|
|
@@ -28,8 +28,6 @@
|
|
|
28
28
|
import { createHybridContext } from './hybrid/hybrid-context.svelte.js';
|
|
29
29
|
import { onMount } from 'svelte';
|
|
30
30
|
import { get } from 'svelte/store';
|
|
31
|
-
import { computeTranslationStatus } from '../../utils/translationStatus.js';
|
|
32
|
-
|
|
33
31
|
const contentLanguage = getContentLanguage();
|
|
34
32
|
const remotes = getRemotes();
|
|
35
33
|
const interfaceLanguage = useInterfaceLanguage();
|
|
@@ -142,10 +140,11 @@
|
|
|
142
140
|
let { collection } = entry;
|
|
143
141
|
const isArchived = $derived(!!entry.archivedAt);
|
|
144
142
|
|
|
145
|
-
// Create form once at component level
|
|
143
|
+
// Create form once at component level — localized: false since data is flat single-language
|
|
146
144
|
const collectionSchema = generateZodSchemaFromFields(
|
|
147
145
|
getFieldsFromConfig(collection),
|
|
148
|
-
contentLanguage.all
|
|
146
|
+
contentLanguage.all,
|
|
147
|
+
{ localized: false }
|
|
149
148
|
);
|
|
150
149
|
const form = superForm(defaults(editingEntry.data, zod4(collectionSchema)), {
|
|
151
150
|
validators: zod4Client(collectionSchema),
|
|
@@ -166,15 +165,6 @@
|
|
|
166
165
|
|
|
167
166
|
const AUTOSAVE_DELAY = 30000; // 30 seconds
|
|
168
167
|
|
|
169
|
-
// Reactive translation status — must live here where we can subscribe to form.form store
|
|
170
|
-
const formStore = form.form;
|
|
171
|
-
const collectionFields = getFieldsFromConfig(collection);
|
|
172
|
-
const translationStatus = $derived.by(() => {
|
|
173
|
-
if (contentLanguage.all.length <= 1) return null;
|
|
174
|
-
const data = $formStore;
|
|
175
|
-
return computeTranslationStatus(data, collectionFields, contentLanguage.all);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
168
|
function scheduleAutosave() {
|
|
179
169
|
if (isArchived) return;
|
|
180
170
|
if (autosaveTimer) {
|
|
@@ -195,15 +185,18 @@
|
|
|
195
185
|
|
|
196
186
|
saveStatus = 'saving';
|
|
197
187
|
try {
|
|
188
|
+
const currentLang = contentLanguage.current;
|
|
198
189
|
const result = await remotes.updateEntryVersionCommand({
|
|
199
190
|
entryId: entry.id,
|
|
191
|
+
lang: currentLang,
|
|
200
192
|
data: currentFormData,
|
|
201
193
|
type: 'draft'
|
|
202
194
|
});
|
|
203
195
|
lastSavedData = currentData;
|
|
204
196
|
saveStatus = 'saved';
|
|
205
197
|
// If we're editing the published version and saved a draft, track it for the banner
|
|
206
|
-
|
|
198
|
+
const publishedVersion = entry.publishedVersions[currentLang];
|
|
199
|
+
if (publishedVersion && editingEntry.id === publishedVersion.id && result?.id) {
|
|
207
200
|
savedDraftVersionId = result.id;
|
|
208
201
|
}
|
|
209
202
|
// Reset to idle after 3s
|
|
@@ -270,6 +263,7 @@
|
|
|
270
263
|
try {
|
|
271
264
|
await remotes.updateEntryVersionCommand({
|
|
272
265
|
entryId: entry.id,
|
|
266
|
+
lang: contentLanguage.current,
|
|
273
267
|
data: get(form.form),
|
|
274
268
|
type
|
|
275
269
|
});
|
|
@@ -298,6 +292,7 @@
|
|
|
298
292
|
try {
|
|
299
293
|
await remotes.updateEntryVersionCommand({
|
|
300
294
|
entryId: entry.id,
|
|
295
|
+
lang: contentLanguage.current,
|
|
301
296
|
data: validatedForm.data,
|
|
302
297
|
type,
|
|
303
298
|
scheduledAt
|
|
@@ -501,21 +496,25 @@
|
|
|
501
496
|
}
|
|
502
497
|
});
|
|
503
498
|
|
|
499
|
+
// Per-language version lookups
|
|
500
|
+
const currentPublishedVersion = $derived(entry.publishedVersions[contentLanguage.current]);
|
|
501
|
+
const currentDraftVersion = $derived(entry.draftVersions[contentLanguage.current]);
|
|
502
|
+
|
|
504
503
|
// Banner: viewing published version + a separate draft exists
|
|
505
504
|
const showDraftBanner = $derived(
|
|
506
|
-
|
|
507
|
-
editingEntry.id ===
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
505
|
+
currentPublishedVersion != null &&
|
|
506
|
+
editingEntry.id === currentPublishedVersion.id &&
|
|
507
|
+
currentDraftVersion != null &&
|
|
508
|
+
currentDraftVersion.id !== currentPublishedVersion.id &&
|
|
509
|
+
currentDraftVersion.createdAt > currentPublishedVersion.createdAt
|
|
511
510
|
);
|
|
512
511
|
|
|
513
512
|
// Banner: viewing draft + a published version exists
|
|
514
513
|
const showPublishedBanner = $derived(
|
|
515
|
-
|
|
514
|
+
currentPublishedVersion != null && editingEntry.id !== currentPublishedVersion.id
|
|
516
515
|
);
|
|
517
516
|
|
|
518
|
-
const draftVersionId = $derived(
|
|
517
|
+
const draftVersionId = $derived(currentDraftVersion?.id ?? savedDraftVersionId);
|
|
519
518
|
|
|
520
519
|
const scrollToIssue = (fieldSlug: string) => {
|
|
521
520
|
const fieldEl = document.querySelector(`[data-field-path="${fieldSlug}"]`);
|
|
@@ -554,7 +553,6 @@
|
|
|
554
553
|
fields={getFieldsFromConfig(collection)}
|
|
555
554
|
getFormData={() => get(form.form)}
|
|
556
555
|
onScrollToIssue={scrollToIssue}
|
|
557
|
-
{translationStatus}
|
|
558
556
|
/>
|
|
559
557
|
|
|
560
558
|
{#if validationErrors.length > 0}
|
|
@@ -620,7 +618,7 @@
|
|
|
620
618
|
<button
|
|
621
619
|
type="button"
|
|
622
620
|
class="font-semibold text-[var(--primary)] hover:underline"
|
|
623
|
-
onclick={() => goto(`?version=${
|
|
621
|
+
onclick={() => goto(`?version=${currentPublishedVersion!.id}`)}
|
|
624
622
|
>
|
|
625
623
|
{lang[interfaceLanguage.current].switchToPublished}
|
|
626
624
|
</button>
|
|
@@ -198,7 +198,7 @@ function tryExtractDoc(val) {
|
|
|
198
198
|
function extractDocs(data, fields) {
|
|
199
199
|
const result = [];
|
|
200
200
|
for (const field of fields) {
|
|
201
|
-
if (field.type !== 'content'
|
|
201
|
+
if (field.type !== 'content')
|
|
202
202
|
continue;
|
|
203
203
|
const val = data[field.slug];
|
|
204
204
|
const docs = tryExtractDoc(val);
|
|
@@ -212,7 +212,7 @@ function extractDocs(data, fields) {
|
|
|
212
212
|
function extractDocsForLang(data, fields, language) {
|
|
213
213
|
const result = [];
|
|
214
214
|
for (const field of fields) {
|
|
215
|
-
if (field.type !== 'content'
|
|
215
|
+
if (field.type !== 'content')
|
|
216
216
|
continue;
|
|
217
217
|
const val = data[field.slug];
|
|
218
218
|
if (!val || typeof val !== 'object')
|