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.
Files changed (185) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/ROADMAP.md +54 -2
  3. package/dist/admin/api/generate-styles.d.ts +2 -0
  4. package/dist/admin/api/generate-styles.js +32 -0
  5. package/dist/admin/api/handler.js +33 -0
  6. package/dist/admin/api/media-gc.js +10 -4
  7. package/dist/admin/api/rest/handler.js +17 -0
  8. package/dist/admin/api/rest/routes/collections.js +25 -13
  9. package/dist/admin/api/rest/routes/entries.d.ts +1 -1
  10. package/dist/admin/api/rest/routes/entries.js +10 -10
  11. package/dist/admin/api/rest/routes/media.d.ts +2 -0
  12. package/dist/admin/api/rest/routes/media.js +9 -0
  13. package/dist/admin/api/rest/routes/schema.d.ts +5 -0
  14. package/dist/admin/api/rest/routes/schema.js +152 -0
  15. package/dist/admin/api/rest/routes/singletons.d.ts +1 -1
  16. package/dist/admin/api/rest/routes/singletons.js +8 -7
  17. package/dist/admin/api/rest/routes/upload.d.ts +2 -0
  18. package/dist/admin/api/rest/routes/upload.js +28 -0
  19. package/dist/admin/api/upload.js +13 -0
  20. package/dist/admin/client/collection/collection-entries.svelte +35 -13
  21. package/dist/admin/client/entry/entry.svelte +21 -23
  22. package/dist/admin/client/entry/header/a11y-validator.js +2 -2
  23. package/dist/admin/client/entry/header/publish-panel.svelte +33 -85
  24. package/dist/admin/client/entry/header/status-badge.svelte +2 -2
  25. package/dist/admin/client/entry/header/version-history-sheet.svelte +9 -9
  26. package/dist/admin/client/entry/header/visibility.svelte +16 -10
  27. package/dist/admin/client/entry/utils.d.ts +3 -0
  28. package/dist/admin/client/entry/utils.js +22 -4
  29. package/dist/admin/client/form/form-submission/form-submission-page.svelte +4 -1
  30. package/dist/admin/client/form/form-submission/submission-field.svelte +10 -0
  31. package/dist/admin/client/index.d.ts +1 -0
  32. package/dist/admin/client/index.js +1 -0
  33. package/dist/admin/client/maintenance/maintenance-page.svelte +146 -2
  34. package/dist/admin/client/users/users-page.svelte +5 -6
  35. package/dist/admin/client/users/users-page.svelte.d.ts +1 -4
  36. package/dist/admin/components/fields/block-picker-modal.svelte +13 -4
  37. package/dist/admin/components/fields/blocks-field.svelte +40 -19
  38. package/dist/admin/components/fields/field-renderer.svelte +4 -8
  39. package/dist/admin/components/fields/object-field.svelte +7 -12
  40. package/dist/admin/components/fields/select-field.svelte +8 -2
  41. package/dist/admin/components/fields/seo-field.svelte +40 -93
  42. package/dist/admin/components/fields/simple-array-field.svelte +27 -16
  43. package/dist/admin/components/fields/text-field-wrapper.svelte +52 -197
  44. package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
  45. package/dist/admin/components/fields/url-field-wrapper.svelte +15 -25
  46. package/dist/admin/components/fields/url-field.svelte +61 -72
  47. package/dist/admin/components/layout/layout-renderer.svelte +10 -4
  48. package/dist/admin/components/media/file-preview.svelte +10 -1
  49. package/dist/admin/components/media/file-upload.svelte +5 -1
  50. package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
  51. package/dist/admin/components/media/files-list.svelte +12 -3
  52. package/dist/admin/components/media/media-library.svelte +109 -37
  53. package/dist/admin/components/media/media-selector.svelte +90 -16
  54. package/dist/admin/components/media/tag-sidebar.svelte +10 -6
  55. package/dist/admin/components/media/tag-sidebar.svelte.d.ts +7 -2
  56. package/dist/admin/components/tiptap/FigureNodeView.svelte +15 -10
  57. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +53 -94
  58. package/dist/admin/components/tiptap/SlashCommandPopup.svelte +8 -3
  59. package/dist/admin/components/tiptap/editor-toolbar.svelte +28 -23
  60. package/dist/admin/components/tiptap/image-dialog.svelte +12 -7
  61. package/dist/admin/components/tiptap/inline-block-node.js +6 -5
  62. package/dist/admin/components/tiptap/lang.d.ts +77 -0
  63. package/dist/admin/components/tiptap/lang.js +170 -0
  64. package/dist/admin/components/tiptap/link-dialog.svelte +31 -28
  65. package/dist/admin/components/tiptap/slash-command.js +27 -23
  66. package/dist/admin/components/tiptap/table-dialog.svelte +9 -4
  67. package/dist/admin/components/tiptap/video-dialog.svelte +6 -1
  68. package/dist/admin/remote/email.remote.d.ts +1 -0
  69. package/dist/admin/remote/email.remote.js +5 -0
  70. package/dist/admin/remote/entry.remote.d.ts +2 -5
  71. package/dist/admin/remote/entry.remote.js +23 -28
  72. package/dist/admin/remote/index.d.ts +1 -0
  73. package/dist/admin/remote/index.js +1 -0
  74. package/dist/admin/remote/media.remote.d.ts +15 -0
  75. package/dist/admin/remote/media.remote.js +18 -2
  76. package/dist/admin/remote/preview.remote.js +3 -1
  77. package/dist/admin/utils/entryLabel.js +9 -6
  78. package/dist/admin/utils/translationStatus.js +1 -2
  79. package/dist/cli/scaffold/admin.js +34 -2
  80. package/dist/cms/runtime/api.d.ts +16 -12
  81. package/dist/cms/runtime/api.js +7 -6
  82. package/dist/cms/runtime/remote.js +2 -2
  83. package/dist/cms/runtime/schemas.d.ts +1 -1
  84. package/dist/cms/runtime/schemas.js +1 -1
  85. package/dist/cms/runtime/types.d.ts +118 -112
  86. package/dist/cms/runtime/types.js +0 -12
  87. package/dist/core/cms.d.ts +3 -1
  88. package/dist/core/cms.js +30 -0
  89. package/dist/core/fields/fieldSchemaToTs.js +9 -15
  90. package/dist/core/fields/formFieldSchemaToTs.js +7 -0
  91. package/dist/core/server/entries/operations/create.js +10 -4
  92. package/dist/core/server/entries/operations/get.d.ts +1 -0
  93. package/dist/core/server/entries/operations/get.js +186 -191
  94. package/dist/core/server/entries/operations/update.d.ts +6 -7
  95. package/dist/core/server/entries/operations/update.js +20 -38
  96. package/dist/core/server/fields/populateEntry.js +16 -52
  97. package/dist/core/server/fields/resolveImageFields.js +69 -120
  98. package/dist/core/server/fields/resolveRelationFields.js +30 -51
  99. package/dist/core/server/fields/resolveRichtextLinks.js +46 -100
  100. package/dist/core/server/fields/resolveTypographyOrphans.bench.d.ts +1 -0
  101. package/dist/core/server/fields/resolveTypographyOrphans.bench.js +87 -0
  102. package/dist/core/server/fields/resolveTypographyOrphans.d.ts +3 -0
  103. package/dist/core/server/fields/resolveTypographyOrphans.js +128 -0
  104. package/dist/core/server/fields/resolveUrlFields.js +47 -56
  105. package/dist/core/server/fields/utils/fixOrphans.d.ts +5 -0
  106. package/dist/core/server/fields/utils/fixOrphans.js +12 -0
  107. package/dist/core/server/fields/utils/imageStyles.d.ts +4 -2
  108. package/dist/core/server/fields/utils/imageStyles.js +41 -25
  109. package/dist/core/server/fields/utils/resolveMedia.js +1 -6
  110. package/dist/core/server/forms/submissions/operations/delete.js +26 -2
  111. package/dist/core/server/forms/submissions/utils/parseMultipart.d.ts +2 -0
  112. package/dist/core/server/forms/submissions/utils/parseMultipart.js +75 -0
  113. package/dist/core/server/generator/fields.d.ts +6 -0
  114. package/dist/core/server/generator/fields.js +43 -5
  115. package/dist/core/server/generator/formFieldSchemaToString.js +10 -0
  116. package/dist/core/server/generator/formFields.js +1 -0
  117. package/dist/core/server/generator/generator.js +98 -30
  118. package/dist/core/server/media/operations/getFiles.d.ts +5 -0
  119. package/dist/core/server/media/operations/getFiles.js +6 -0
  120. package/dist/core/server/media/operations/uploadPrivateFile.d.ts +4 -0
  121. package/dist/core/server/media/operations/uploadPrivateFile.js +8 -0
  122. package/dist/core/server/media/styles/operations/batchGenerateStyles.d.ts +16 -0
  123. package/dist/core/server/media/styles/operations/batchGenerateStyles.js +144 -0
  124. package/dist/db-postgres/index.js +303 -37
  125. package/dist/db-postgres/schema/entry.d.ts +0 -94
  126. package/dist/db-postgres/schema/entry.js +0 -6
  127. package/dist/db-postgres/schema/entryVersion.d.ts +17 -0
  128. package/dist/db-postgres/schema/entryVersion.js +1 -0
  129. package/dist/entity/index.d.ts +9 -4
  130. package/dist/entity/index.js +24 -24
  131. package/dist/files-local/index.js +43 -0
  132. package/dist/paraglide/messages/_index.d.ts +36 -3
  133. package/dist/paraglide/messages/_index.js +71 -3
  134. package/dist/paraglide/messages/en.d.ts +5 -0
  135. package/dist/paraglide/messages/en.js +14 -0
  136. package/dist/paraglide/messages/pl.d.ts +5 -0
  137. package/dist/paraglide/messages/pl.js +14 -0
  138. package/dist/sveltekit/components/preview.svelte +2 -326
  139. package/dist/sveltekit/components/preview.svelte.d.ts +5 -16
  140. package/dist/sveltekit/server/index.d.ts +2 -1
  141. package/dist/sveltekit/server/index.js +2 -1
  142. package/dist/sveltekit/server/preview.js +4 -7
  143. package/dist/types/adapters/db.d.ts +15 -1
  144. package/dist/types/adapters/files.d.ts +6 -0
  145. package/dist/types/cms.d.ts +5 -0
  146. package/dist/types/entries.d.ts +54 -18
  147. package/dist/types/fields.d.ts +14 -24
  148. package/dist/types/formFields.d.ts +7 -2
  149. package/dist/types/index.d.ts +2 -2
  150. package/dist/types/layout.d.ts +0 -1
  151. package/dist/types/structured-content.d.ts +5 -0
  152. package/dist/updates/0.10.0/index.d.ts +2 -0
  153. package/dist/updates/0.10.0/index.js +15 -0
  154. package/dist/updates/0.11.0/index.d.ts +2 -0
  155. package/dist/updates/0.11.0/index.js +12 -0
  156. package/dist/updates/0.12.0/index.d.ts +2 -0
  157. package/dist/updates/0.12.0/index.js +12 -0
  158. package/dist/updates/0.13.0/index.d.ts +2 -0
  159. package/dist/updates/0.13.0/index.js +10 -0
  160. package/dist/updates/0.13.1/index.d.ts +2 -0
  161. package/dist/updates/0.13.1/index.js +20 -0
  162. package/dist/updates/0.7.3/index.d.ts +2 -0
  163. package/dist/updates/0.7.3/index.js +10 -0
  164. package/dist/updates/0.8.0/index.d.ts +2 -0
  165. package/dist/updates/0.8.0/index.js +18 -0
  166. package/dist/updates/0.8.0/migrate.d.ts +2 -0
  167. package/dist/updates/0.8.0/migrate.js +101 -0
  168. package/dist/updates/0.9.0/index.d.ts +2 -0
  169. package/dist/updates/0.9.0/index.js +38 -0
  170. package/dist/updates/index.js +9 -1
  171. package/package.json +7 -6
  172. package/dist/admin/components/fields/image-field.svelte +0 -198
  173. package/dist/admin/components/fields/image-field.svelte.d.ts +0 -8
  174. package/dist/admin/components/fields/richtext-field.svelte +0 -13
  175. package/dist/admin/components/fields/richtext-field.svelte.d.ts +0 -8
  176. package/dist/admin/components/tiptap.svelte +0 -11
  177. package/dist/admin/components/tiptap.svelte.d.ts +0 -6
  178. package/dist/core/server/entries/utils/getEntryTranslation.d.ts +0 -1
  179. package/dist/core/server/entries/utils/getEntryTranslation.js +0 -18
  180. package/dist/paraglide/messages/hello_world.d.ts +0 -5
  181. package/dist/paraglide/messages/hello_world.js +0 -33
  182. package/dist/paraglide/messages/login_hello.d.ts +0 -16
  183. package/dist/paraglide/messages/login_hello.js +0 -34
  184. package/dist/paraglide/messages/login_please_login.d.ts +0 -16
  185. package/dist/paraglide/messages/login_please_login.js +0 -34
package/CHANGELOG.md CHANGED
@@ -3,6 +3,134 @@
3
3
  All notable changes to includio-cms are documented here.
4
4
  Generated from `src/lib/updates/` — do not edit manually.
5
5
 
6
+ ## 0.13.1 — 2026-03-20
7
+
8
+ Admin UI i18n, codegen cleanup, docs rewrite
9
+
10
+ ### Added
11
+ - Admin UI i18n — TipTap editor, blocks field, array field, media components support pl/en interface language
12
+ - Inline block accordion labels — show field value in collapsed block header
13
+ - Blocks field UrlFieldData label support
14
+ - Email configuration remote endpoint
15
+
16
+ ### Fixed
17
+ - Codegen: remove unused FlatImageFieldData/FlatVideoFieldData, use ImageFieldData/VideoFieldData
18
+ - Layout renderer: hide card header when label is empty
19
+ - Collection entries: improved relation label fetching with UUID validation and multi-version support
20
+ - Users page: client-side email config check, remove server load dependency
21
+ - Entry remote: stricter UUID validation for ids parameter
22
+ - Layout type: remove unused label property from layout node
23
+
24
+ ## 0.13.0 — 2026-03-19
25
+
26
+ Private file uploads for form submissions
27
+
28
+ ### Added
29
+ - Private file uploads — form submissions can upload files to a non-public directory with access control
30
+
31
+ ## 0.12.0 — 2026-03-18
32
+
33
+ File field type, dataOrderBy, and _url population
34
+
35
+ ### Added
36
+ - File field type with multipart upload, file validation, and rate limiting
37
+ - `dataOrderBy` — sort entries by JSON data fields in queries
38
+ - Auto-populate `_url` field on entries from slug resolver and `pathTemplate`
39
+
40
+ ## 0.11.0 — 2026-03-18
41
+
42
+ Codegen overhaul and REST API consolidation
43
+
44
+ ### Added
45
+ - Codegen: flat entry types, inline block types, `countEntries` query helper, simplified API surface
46
+ - REST API: catch-all route handler, file upload endpoint, media endpoint, schema template endpoints
47
+ - Admin scaffold updated for new REST route structure
48
+
49
+ ## 0.10.0 — 2026-03-18
50
+
51
+ Remove image, video, richtext field types — consolidate to media and content
52
+
53
+ ### Breaking
54
+ - Removed `image` field type — use `media` with `accept: "image/*"` instead
55
+ - Removed `richtext` field type — use `content` (structured ProseMirror JSON) instead
56
+ - Removed `ImageField` and `RichtextField` TypeScript interfaces
57
+ - `image-field.svelte` and `richtext-field.svelte` components removed
58
+ - `tiptap.svelte` (HTML richtext wrapper) removed
59
+
60
+ ### Notes
61
+
62
+ No automatic data migration. Replace `type: "image"` with `type: "media", accept: "image/*"` in your collection/singleton configs. Replace `type: "richtext"` with `type: "content"`. Existing stored data (media UUIDs, ProseMirror docs) remains compatible.
63
+
64
+ ## 0.9.0 — 2026-03-17
65
+
66
+ Per-language entry versions — separate entry_version per language
67
+
68
+ ### Added
69
+ - Per-language entry versions: each language gets its own entry_version with flat data
70
+ - Per-language publication: publish/unpublish each language independently
71
+ - Simplified data format: no more nested `{ pl: "X", en: "Y" }` — values are flat strings
72
+ - Language switching in admin loads different version data
73
+
74
+ ### Breaking
75
+ - `entry_version` table: new `lang` column (NOT NULL)
76
+ - `entry` table: removed `published_at`, `published_version_id`, `published_by`, `available_locales` columns
77
+ - `RawEntry`: `publishedVersion`/`draftVersion`/`scheduledVersion` replaced with per-lang `publishedVersions`/`draftVersions`/`scheduledVersions` records
78
+ - `DbEntryVersion`/`DbEntryVersionInsert`: new required `lang` field
79
+ - `translateObject` removed — data is single-language per version
80
+ - Resolve functions (relation, media, url, richtext) expect flat content/richtext values, not `Record<lang, value>`
81
+ - `upsertDraftVersion` and `pruneOldDraftVersions` now require `lang` parameter
82
+ - `unpublishEntry` renamed to `unpublishEntryLang` with required `lang` parameter
83
+
84
+ ### Migration
85
+
86
+ ```sql
87
+ -- Step 1: Add lang column as nullable
88
+ ALTER TABLE entry_version ADD COLUMN lang TEXT;
89
+
90
+ -- Step 2: Run migration script to split multi-lang versions (scripts/migrate-lang-versions.ts)
91
+ -- After migration, set NOT NULL:
92
+ ALTER TABLE entry_version ALTER COLUMN lang SET NOT NULL;
93
+
94
+ -- Step 3: Create index for performance
95
+ CREATE INDEX idx_entry_version_publish ON entry_version(entry_id, lang, published_at);
96
+
97
+ -- Step 4: Remove entry-level publish columns
98
+ ALTER TABLE entry DROP COLUMN IF EXISTS published_at;
99
+ ALTER TABLE entry DROP COLUMN IF EXISTS published_version_id;
100
+ ALTER TABLE entry DROP COLUMN IF EXISTS published_by;
101
+ ALTER TABLE entry DROP COLUMN IF EXISTS available_locales;
102
+ ```
103
+
104
+ ### Notes
105
+
106
+ Run the migration script BEFORE setting lang to NOT NULL. See scripts/migrate-lang-versions.ts for data migration.
107
+
108
+ ## 0.8.0 — 2026-03-17
109
+
110
+ Flat entry format — remove data wrapper from objects/blocks
111
+
112
+ ### Added
113
+ - Flat entry format: objects use `_slug` + spread fields instead of `slug` + `data` wrapper
114
+ - Blocks use `_slug` instead of `slug` with flat fields
115
+ - Entry type unified: `FlatEntry` renamed to `Entry`, old `Entry` removed
116
+
117
+ ### Breaking
118
+ - Object field data format: `{ slug, data: {...} }` → `{ _slug, ...fields }`
119
+ - Blocks field data format: `{ _id, slug, data: {...} }` → `{ _id, _slug, ...fields }`
120
+ - `FlatEntry` type removed — use `Entry` instead
121
+ - `flattenEntry`/`flattenFields` removed — data is flat from resolve functions
122
+
123
+ ### Notes
124
+
125
+ Run `includio update` to migrate existing entry data in the database.
126
+
127
+ ## 0.7.3 — 2026-03-17
128
+
129
+ Typography orphan fix for Polish conjunctions
130
+
131
+ ### Added
132
+ - Read-time orphan fix — replace space after single-letter Polish conjunctions (i, w, z, o, u, a) with non-breaking space `\u00A0`
133
+
6
134
  ## 0.7.2 — 2026-03-16
7
135
 
8
136
  CLI create-user command
package/ROADMAP.md CHANGED
@@ -167,14 +167,66 @@
167
167
 
168
168
  - [x] `[feature]` `[P1]` CLI `create-user` command — interactive user creation with role assignment <!-- files: src/lib/cli/create-user.ts, src/lib/cli/index.ts -->
169
169
 
170
- ## 0.8.0SEO module
170
+ ## 0.7.3Typography
171
+
172
+ - [x] `[feature]` `[P1]` Read-time orphan fix — replace space after single-letter Polish conjunctions with `\u00A0` <!-- files: src/lib/core/server/fields/resolveTypographyOrphans.ts, src/lib/core/server/fields/utils/fixOrphans.ts -->
173
+
174
+ ## 0.8.0 — Flat entry format
175
+
176
+ - [x] `[breaking]` `[P0]` Flat entry format: objects use `_slug` + spread fields instead of `slug` + `data` wrapper
177
+ - [x] `[breaking]` `[P0]` Blocks use `_slug` instead of `slug` with flat fields
178
+ - [x] `[breaking]` `[P0]` `FlatEntry` type removed — use `Entry` instead
179
+
180
+ ## 0.9.0 — Per-language entry versions
181
+
182
+ - [x] `[breaking]` `[P0]` Per-language entry versions: each language gets its own `entry_version` with flat data
183
+ - [x] `[feature]` `[P0]` Per-language publication: publish/unpublish each language independently
184
+ - [x] `[breaking]` `[P0]` Simplified data format: no more nested `{ pl: "X", en: "Y" }` — values are flat strings
185
+
186
+ ## 0.10.0 — Field type consolidation
187
+
188
+ - [x] `[breaking]` `[P1]` Remove `image` field type — use `media` with `accept: "image/*"`
189
+ - [x] `[breaking]` `[P1]` Remove `richtext` field type — use `content` (structured JSON)
190
+ - [x] `[chore]` `[P1]` Remove `ImageField`, `RichtextField` interfaces + components
191
+
192
+ ## 0.11.0 — Codegen & REST consolidation
193
+
194
+ - [x] `[feature]` `[P1]` Codegen: flat types, inline block types, `countEntries`, simplified API
195
+ - [x] `[feature]` `[P1]` REST API: catch-all route, upload endpoint, media endpoint, schema templates
196
+ - [x] `[chore]` `[P1]` Admin scaffold updated for new REST route structure
197
+
198
+ ## 0.12.0 — File field, dataOrderBy, _url
199
+
200
+ - [x] `[feature]` `[P1]` File field type with multipart upload, validation, rate limiting
201
+ - [x] `[feature]` `[P1]` `dataOrderBy` — sort entries by JSON data fields
202
+ - [x] `[feature]` `[P1]` Auto-populate `_url` field on entries from slug resolver
203
+
204
+ ## 0.13.0 — Private file uploads
205
+
206
+ - [x] `[feature]` `[P1]` Private file uploads — form submissions can upload files to non-public directory
207
+
208
+ ## 0.13.1 — Admin i18n, codegen cleanup, docs
209
+
210
+ - [x] `[feature]` `[P1]` Admin UI i18n — TipTap, blocks, array, media components support pl/en <!-- files: src/lib/admin/components/tiptap/lang.ts -->
211
+ - [x] `[feature]` `[P2]` Inline block accordion labels — show field value in collapsed header <!-- files: src/lib/admin/components/tiptap/InlineBlockNodeView.svelte -->
212
+ - [x] `[feature]` `[P2]` Blocks field UrlFieldData label support <!-- files: src/lib/admin/components/fields/blocks-field.svelte -->
213
+ - [x] `[feature]` `[P2]` Email configuration remote endpoint <!-- files: src/lib/admin/remote/email.remote.ts -->
214
+ - [x] `[fix]` `[P1]` Codegen: remove unused Flat*FieldData types, use ImageFieldData/VideoFieldData <!-- files: src/lib/core/server/generator/fields.ts, src/lib/core/server/generator/generator.ts -->
215
+ - [x] `[fix]` `[P1]` Layout renderer: hide card header when label empty <!-- files: src/lib/admin/components/layout/layout-renderer.svelte -->
216
+ - [x] `[fix]` `[P1]` Collection entries: improved relation label fetching (UUID validation, multi-version) <!-- files: src/lib/admin/client/collection/collection-entries.svelte -->
217
+ - [x] `[fix]` `[P1]` Users page: client-side email config, remove server load <!-- files: src/lib/admin/client/users/users-page.svelte -->
218
+ - [x] `[fix]` `[P2]` Entry remote: stricter UUID validation for ids <!-- files: src/lib/admin/remote/entry.remote.ts -->
219
+ - [x] `[fix]` `[P2]` Layout type: remove unused label property <!-- files: src/lib/types/layout.ts -->
220
+ - [x] `[chore]` `[P1]` Documentation rewrite — new pages for blocks, content, media-field, layout; expanded API, auth, entries, forms, plugins docs
221
+
222
+ ## 0.14.0 — SEO module
171
223
 
172
224
  - [ ] `[feature]` `[P1]` SERP preview + character limits for title/description <!-- files: src/lib/admin/components/fields/seo-field.svelte -->
173
225
  - [ ] `[feature]` `[P1]` Global SEO settings
174
226
  - [ ] `[feature]` `[P1]` Dedicated frontend SEO components <!-- files: src/lib/sveltekit/components/seo.svelte -->
175
227
  - [ ] `[feature]` `[P2]` Sitemap generation
176
228
 
177
- ## 0.9.0 — WCAG/ATAG compliance
229
+ ## 0.15.0 — WCAG/ATAG compliance
178
230
 
179
231
  - [ ] `[chore]` `[P0]` Full WCAG/ATAG audit
180
232
  - [ ] `[feature]` `[P0]` Accessibility rework based on audit findings
@@ -0,0 +1,2 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ export declare const POST: RequestHandler;
@@ -0,0 +1,32 @@
1
+ import { requireRole } from '../remote/middleware/auth.js';
2
+ import { batchGenerateAllStyles } from '../../core/server/media/styles/operations/batchGenerateStyles.js';
3
+ export const POST = async () => {
4
+ requireRole('admin');
5
+ const abort = new AbortController();
6
+ const encoder = new TextEncoder();
7
+ const stream = new ReadableStream({
8
+ async start(controller) {
9
+ try {
10
+ for await (const event of batchGenerateAllStyles(abort.signal)) {
11
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
12
+ }
13
+ }
14
+ catch (e) {
15
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', error: e instanceof Error ? e.message : String(e) })}\n\n`));
16
+ }
17
+ finally {
18
+ controller.close();
19
+ }
20
+ },
21
+ cancel() {
22
+ abort.abort();
23
+ }
24
+ });
25
+ return new Response(stream, {
26
+ headers: {
27
+ 'Content-Type': 'text/event-stream',
28
+ 'Cache-Control': 'no-cache',
29
+ Connection: 'keep-alive'
30
+ }
31
+ });
32
+ };
@@ -5,6 +5,10 @@ import * as replaceHandlers from './replace.js';
5
5
  import * as inviteHandlers from './invite.js';
6
6
  import * as acceptInviteHandlers from './accept-invite.js';
7
7
  import * as mediaGcHandlers from './media-gc.js';
8
+ import * as generateStylesHandlers from './generate-styles.js';
9
+ import { requireAuth } from '../remote/middleware/auth.js';
10
+ import { getCMS } from '../../core/cms.js';
11
+ import { lookup } from 'mrmime';
8
12
  export function createAdminApiHandler(options) {
9
13
  const routes = {
10
14
  upload: uploadHandlers,
@@ -13,14 +17,43 @@ export function createAdminApiHandler(options) {
13
17
  invite: inviteHandlers,
14
18
  'accept-invite': acceptInviteHandlers,
15
19
  'media-gc': mediaGcHandlers,
20
+ 'generate-styles': generateStylesHandlers,
16
21
  ...options?.extraRoutes
17
22
  };
23
+ const privateMediaGet = async (event) => {
24
+ requireAuth();
25
+ const path = event.params.path;
26
+ const filename = path.replace('media/private/', '');
27
+ if (!filename || filename.includes('/') || filename.includes('..')) {
28
+ return json({ error: 'Invalid filename' }, { status: 400 });
29
+ }
30
+ const adapter = getCMS().filesAdapter;
31
+ if (!adapter.downloadPrivateFile) {
32
+ return json({ error: 'Not supported' }, { status: 501 });
33
+ }
34
+ const file = await adapter.downloadPrivateFile(filename);
35
+ if (!file) {
36
+ return json({ error: 'Not found' }, { status: 404 });
37
+ }
38
+ const contentType = lookup(filename) || 'application/octet-stream';
39
+ return new Response(await file.arrayBuffer(), {
40
+ headers: {
41
+ 'Content-Type': contentType,
42
+ 'Content-Disposition': `inline; filename="${filename}"`,
43
+ 'Cache-Control': 'private, no-store'
44
+ }
45
+ });
46
+ };
18
47
  function handle(method) {
19
48
  return (event) => {
20
49
  const path = event.params.path;
21
50
  if (!path) {
22
51
  return json({ error: 'Not found' }, { status: 404 });
23
52
  }
53
+ // Handle media/private/[filename] route
54
+ if (path.startsWith('media/private/') && method === 'GET') {
55
+ return privateMediaGet(event);
56
+ }
24
57
  const handler = routes[path]?.[method];
25
58
  if (!handler) {
26
59
  return json({ error: 'Not found' }, { status: 404 });
@@ -1,13 +1,19 @@
1
1
  import { requireRole } from '../remote/middleware/auth.js';
2
- import { getImageStylesStats, purgeAllImageStyles } from '../../core/server/media/operations/purgeImageStyles.js';
2
+ import { purgeAllImageStyles } from '../../core/server/media/operations/purgeImageStyles.js';
3
3
  import { getReconciliationReport, deleteOrphanedDiskFiles } from '../../core/server/media/operations/reconcileMedia.js';
4
+ import { getStylesStatus } from '../../core/server/media/styles/operations/batchGenerateStyles.js';
4
5
  import { json } from '@sveltejs/kit';
5
6
  export const GET = async ({ url }) => {
6
7
  requireRole('admin');
7
- const stats = await getImageStylesStats();
8
- const report = await getReconciliationReport();
8
+ const [stylesStatus, report] = await Promise.all([
9
+ getStylesStatus(),
10
+ getReconciliationReport()
11
+ ]);
9
12
  return json({
10
- imageStylesCount: stats.count,
13
+ imageStylesCount: stylesStatus.existingStyles,
14
+ processableImagesCount: stylesStatus.processableImages,
15
+ expectedStylesCount: stylesStatus.expectedStyles,
16
+ missingStylesCount: stylesStatus.missingStyles,
11
17
  orphanedDiskFiles: report.orphanedDisk,
12
18
  missingDiskRecords: report.missingDisk
13
19
  });
@@ -5,6 +5,8 @@ import * as languagesRoutes from './routes/languages.js';
5
5
  import * as collectionsRoutes from './routes/collections.js';
6
6
  import * as singletonsRoutes from './routes/singletons.js';
7
7
  import * as entriesRoutes from './routes/entries.js';
8
+ import * as uploadRoutes from './routes/upload.js';
9
+ import * as mediaRoutes from './routes/media.js';
8
10
  function matchRoute(method, path) {
9
11
  // Schema routes
10
12
  if (method === 'GET' && path === 'schema') {
@@ -17,6 +19,15 @@ function matchRoute(method, path) {
17
19
  if (method === 'GET' && path === 'languages') {
18
20
  return { handler: 'languages', params: [] };
19
21
  }
22
+ // Upload: POST /upload
23
+ if (method === 'POST' && path === 'upload') {
24
+ return { handler: 'upload', params: [] };
25
+ }
26
+ // Media: GET /media/:id
27
+ const mediaMatch = path.match(/^media\/([^/]+)$/);
28
+ if (method === 'GET' && mediaMatch) {
29
+ return { handler: 'media', params: [mediaMatch[1]] };
30
+ }
20
31
  // Entries lifecycle: POST /entries/:id/:action
21
32
  const entriesMatch = path.match(/^entries\/([^/]+)\/(publish|unpublish|archive|unarchive)$/);
22
33
  if (method === 'POST' && entriesMatch) {
@@ -53,6 +64,12 @@ export function createRestApiHandler() {
53
64
  }
54
65
  try {
55
66
  switch (route.handler) {
67
+ case 'upload':
68
+ return await uploadRoutes.POST(event);
69
+ case 'media': {
70
+ const [mediaId] = route.params;
71
+ return await mediaRoutes.GET(event, mediaId);
72
+ }
56
73
  case 'schema': {
57
74
  // Pass restPath minus 'schema/' prefix
58
75
  const schemaPath = restPath === 'schema' ? '' : restPath.slice(7);
@@ -2,13 +2,14 @@ import { json } from '@sveltejs/kit';
2
2
  import { getCMS } from '../../../../core/cms.js';
3
3
  import { getRawEntries, getRawEntry, countRawEntries } from '../../../../core/server/entries/operations/get.js';
4
4
  import { createEntry } from '../../../../core/server/entries/operations/create.js';
5
- import { upsertDraftVersion } from '../../../../core/server/entries/operations/update.js';
5
+ import { upsertDraftVersion, updateEntry, updateEntryVersion } from '../../../../core/server/entries/operations/update.js';
6
6
  import { deleteEntry } from '../../../../core/server/entries/operations/delete.js';
7
7
  export async function GET(event, slug, id) {
8
8
  const cms = getCMS();
9
9
  if (!cms.collections[slug]) {
10
10
  return json({ error: `Collection "${slug}" not found` }, { status: 404 });
11
11
  }
12
+ const lang = event.url.searchParams.get('lang') || cms.languages[0] || 'en';
12
13
  // GET /collections/:slug/:id — single entry
13
14
  if (id) {
14
15
  const entry = await getRawEntry({ id, slug, includeArchived: true });
@@ -21,14 +22,12 @@ export async function GET(event, slug, id) {
21
22
  type: entry.type,
22
23
  createdAt: entry.createdAt,
23
24
  updatedAt: entry.updatedAt,
24
- publishedAt: entry.publishedAt,
25
25
  archivedAt: entry.archivedAt,
26
- availableLocales: entry.availableLocales,
27
26
  sortOrder: entry.sortOrder,
28
- draftData: entry.draftVersion?.data ?? null,
29
- publishedData: entry.publishedVersion?.data ?? null,
30
- draftVersionId: entry.draftVersion?.id ?? null,
31
- publishedVersionId: entry.publishedVersion?.id ?? null
27
+ draftData: entry.draftVersions[lang]?.data ?? null,
28
+ publishedData: entry.publishedVersions[lang]?.data ?? null,
29
+ draftVersionId: entry.draftVersions[lang]?.id ?? null,
30
+ publishedVersionId: entry.publishedVersions[lang]?.id ?? null
32
31
  });
33
32
  }
34
33
  // GET /collections/:slug — list
@@ -48,12 +47,10 @@ export async function GET(event, slug, id) {
48
47
  type: entry.type,
49
48
  createdAt: entry.createdAt,
50
49
  updatedAt: entry.updatedAt,
51
- publishedAt: entry.publishedAt,
52
50
  archivedAt: entry.archivedAt,
53
- availableLocales: entry.availableLocales,
54
51
  sortOrder: entry.sortOrder,
55
- draftData: entry.draftVersion?.data ?? null,
56
- publishedData: entry.publishedVersion?.data ?? null
52
+ draftData: entry.draftVersions[lang]?.data ?? null,
53
+ publishedData: entry.publishedVersions[lang]?.data ?? null
57
54
  }));
58
55
  return json({ items, total, limit, offset });
59
56
  }
@@ -62,11 +59,25 @@ export async function POST(event, slug) {
62
59
  if (!cms.collections[slug]) {
63
60
  return json({ error: `Collection "${slug}" not found` }, { status: 404 });
64
61
  }
62
+ const lang = event.url.searchParams.get('lang') || cms.languages[0] || 'en';
65
63
  const body = await event.request.json().catch(() => null);
66
64
  const entry = await createEntry({ slug, type: 'collection' });
67
65
  // If data provided, save as draft
68
66
  if (body?.data && typeof body.data === 'object') {
69
- await upsertDraftVersion(entry.id, body.data, { skipValidation: true });
67
+ await upsertDraftVersion(entry.id, body.data, lang, { skipValidation: true });
68
+ }
69
+ // Auto-publish if requested
70
+ if (body?.publish) {
71
+ const raw = await getRawEntry({ id: entry.id, slug });
72
+ const draftVersion = raw?.draftVersions[lang];
73
+ if (draftVersion) {
74
+ const user = event.locals.user;
75
+ await updateEntryVersion(draftVersion.id, {
76
+ publishedAt: new Date(),
77
+ publishedBy: user?.id ?? 'api'
78
+ });
79
+ }
80
+ return json({ id: entry.id, slug: entry.slug, published: true }, { status: 201 });
70
81
  }
71
82
  return json({ id: entry.id, slug: entry.slug }, { status: 201 });
72
83
  }
@@ -79,11 +90,12 @@ export async function PUT(event, slug, id) {
79
90
  if (!entry) {
80
91
  return json({ error: 'Entry not found' }, { status: 404 });
81
92
  }
93
+ const lang = event.url.searchParams.get('lang') || cms.languages[0] || 'en';
82
94
  const body = await event.request.json().catch(() => null);
83
95
  if (!body?.data || typeof body.data !== 'object') {
84
96
  return json({ error: 'Request body must contain "data" object' }, { status: 400 });
85
97
  }
86
- const version = await upsertDraftVersion(entry.id, body.data, { skipValidation: true });
98
+ const version = await upsertDraftVersion(entry.id, body.data, lang, { skipValidation: true });
87
99
  return json({
88
100
  id: entry.id,
89
101
  versionId: version.id,
@@ -1,2 +1,2 @@
1
1
  import { type RequestEvent } from '@sveltejs/kit';
2
- export declare function POST(_event: RequestEvent, id: string, action: string): Promise<Response>;
2
+ export declare function POST(event: RequestEvent, id: string, action: string): Promise<Response>;
@@ -1,26 +1,26 @@
1
1
  import { json } from '@sveltejs/kit';
2
- import { requireAuth } from '../../../remote/middleware/auth.js';
2
+ import { getCMS } from '../../../../core/cms.js';
3
3
  import { getRawEntryOrThrow } from '../../../../core/server/entries/operations/get.js';
4
- import { updateEntry, unpublishEntry } from '../../../../core/server/entries/operations/update.js';
5
- export async function POST(_event, id, action) {
6
- const { user } = requireAuth();
4
+ import { updateEntry, updateEntryVersion, unpublishEntryLang } from '../../../../core/server/entries/operations/update.js';
5
+ export async function POST(event, id, action) {
6
+ const user = event.locals.user;
7
+ const lang = event.url.searchParams.get('lang') || getCMS().languages[0] || 'en';
7
8
  switch (action) {
8
9
  case 'publish': {
9
10
  const entry = await getRawEntryOrThrow({ id, includeArchived: false });
10
- const draftVersion = entry.draftVersion;
11
+ const draftVersion = entry.draftVersions[lang];
11
12
  if (!draftVersion) {
12
13
  return json({ error: 'No draft version to publish' }, { status: 400 });
13
14
  }
14
- await updateEntry(id, {
15
- publishedVersionId: draftVersion.id,
15
+ await updateEntryVersion(draftVersion.id, {
16
16
  publishedAt: new Date(),
17
- publishedBy: user.id,
18
- archivedAt: null
17
+ publishedBy: user.id
19
18
  });
19
+ await updateEntry(id, { archivedAt: null });
20
20
  return json({ success: true, publishedVersionId: draftVersion.id });
21
21
  }
22
22
  case 'unpublish': {
23
- await unpublishEntry(id);
23
+ await unpublishEntryLang(id, lang);
24
24
  return json({ success: true });
25
25
  }
26
26
  case 'archive': {
@@ -0,0 +1,2 @@
1
+ import { type RequestEvent } from '@sveltejs/kit';
2
+ export declare function GET(_event: RequestEvent, id: string): Promise<Response>;
@@ -0,0 +1,9 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { getFile } from '../../../../core/server/media/operations/getFiles.js';
3
+ export async function GET(_event, id) {
4
+ const file = await getFile(id);
5
+ if (!file) {
6
+ return json({ error: 'Media file not found' }, { status: 404 });
7
+ }
8
+ return json(file);
9
+ }
@@ -1,2 +1,7 @@
1
1
  import { type RequestEvent } from '@sveltejs/kit';
2
+ import type { Field } from '../../../../types/fields.js';
3
+ export declare function generateTemplate(fields: Field[], languages: string[]): {
4
+ template: Record<string, unknown>;
5
+ meta: Record<string, string>;
6
+ };
2
7
  export declare function GET(event: RequestEvent): Promise<Response>;