includio-cms 0.1.4 → 0.5.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 (310) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/ROADMAP.md +26 -13
  3. package/dist/admin/api/accept-invite.js +1 -5
  4. package/dist/admin/api/invite.js +7 -16
  5. package/dist/admin/client/account/account-page.svelte +20 -50
  6. package/dist/admin/client/account/lang.d.ts +15 -23
  7. package/dist/admin/client/account/lang.js +51 -67
  8. package/dist/admin/client/account/preferences-section.svelte +26 -84
  9. package/dist/admin/client/account/profile-section.svelte +60 -40
  10. package/dist/admin/client/account/schema.d.ts +11 -3
  11. package/dist/admin/client/account/schema.js +25 -16
  12. package/dist/admin/client/account/security-section.svelte +139 -105
  13. package/dist/admin/client/account/sessions-section.svelte +35 -34
  14. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +3 -5
  15. package/dist/admin/client/admin/admin-layout.svelte +3 -2
  16. package/dist/admin/client/admin/admin-preloader.svelte +36 -0
  17. package/dist/admin/client/admin/admin-preloader.svelte.d.ts +18 -0
  18. package/dist/admin/client/admin/dashboard-page.svelte +35 -67
  19. package/dist/admin/client/collection/a11y-score-cell.svelte +45 -0
  20. package/dist/admin/client/collection/a11y-score-cell.svelte.d.ts +6 -0
  21. package/dist/admin/client/collection/bulk-actions-bar.svelte +100 -0
  22. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +10 -0
  23. package/dist/admin/client/collection/collection-entries.svelte +277 -258
  24. package/dist/admin/client/collection/collection-view.svelte.d.ts +4 -3
  25. package/dist/admin/client/collection/collection-view.svelte.js +9 -5
  26. package/dist/admin/client/collection/collection.svelte +22 -12
  27. package/dist/admin/client/collection/data-table.svelte +50 -39
  28. package/dist/admin/client/collection/data-table.svelte.d.ts +1 -0
  29. package/dist/admin/client/collection/date-cell.svelte +7 -5
  30. package/dist/admin/client/collection/date-cell.svelte.d.ts +1 -1
  31. package/dist/admin/client/collection/empty-state.svelte +28 -0
  32. package/dist/admin/client/collection/empty-state.svelte.d.ts +9 -0
  33. package/dist/admin/client/collection/entry-link.svelte +10 -4
  34. package/dist/admin/client/collection/entry-link.svelte.d.ts +1 -0
  35. package/dist/admin/client/collection/grid-view.svelte +21 -23
  36. package/dist/admin/client/collection/grid-view.svelte.d.ts +1 -2
  37. package/dist/admin/client/collection/row-actions.svelte +69 -0
  38. package/dist/admin/client/collection/row-actions.svelte.d.ts +10 -0
  39. package/dist/admin/client/collection/status-badge.svelte +7 -8
  40. package/dist/admin/client/collection/table-pagination.svelte +122 -79
  41. package/dist/admin/client/collection/table-pagination.svelte.d.ts +1 -0
  42. package/dist/admin/client/collection/table-toolbar.svelte +108 -88
  43. package/dist/admin/client/collection/table-toolbar.svelte.d.ts +8 -9
  44. package/dist/admin/client/entry/entry-form.svelte +109 -1
  45. package/dist/admin/client/entry/entry-header.svelte +143 -37
  46. package/dist/admin/client/entry/entry-header.svelte.d.ts +8 -0
  47. package/dist/admin/client/entry/entry.svelte +272 -61
  48. package/dist/admin/client/entry/header/a11y-validator.d.ts +47 -0
  49. package/dist/admin/client/entry/header/a11y-validator.js +352 -0
  50. package/dist/admin/client/entry/header/publish-panel.svelte +531 -129
  51. package/dist/admin/client/entry/header/publish-panel.svelte.d.ts +4 -0
  52. package/dist/admin/client/entry/header/save-indicator.svelte +33 -23
  53. package/dist/admin/client/entry/header/schedule-popover.svelte +1 -1
  54. package/dist/admin/client/entry/header/status-badge.svelte +25 -118
  55. package/dist/admin/client/entry/header/version-history-sheet.svelte +322 -98
  56. package/dist/admin/client/form/form-submission/form-submission.svelte +271 -83
  57. package/dist/admin/client/form/form-submission/submission-field.svelte +12 -12
  58. package/dist/admin/client/form/form-submissions.svelte +421 -139
  59. package/dist/admin/client/form/submission-link.svelte +8 -2
  60. package/dist/admin/client/form/submission-link.svelte.d.ts +1 -0
  61. package/dist/admin/client/form/submission-status-badge.svelte +18 -4
  62. package/dist/admin/client/form/submission-status-badge.svelte.d.ts +1 -0
  63. package/dist/admin/client/login/lang.d.ts +32 -0
  64. package/dist/admin/client/login/lang.js +66 -2
  65. package/dist/admin/client/login/login-form.svelte +237 -95
  66. package/dist/admin/client/login/login-form.svelte.d.ts +2 -17
  67. package/dist/admin/client/login/login-page.svelte +34 -98
  68. package/dist/admin/client/login/reset-password-page.svelte +235 -0
  69. package/dist/admin/client/login/reset-password-page.svelte.d.ts +4 -0
  70. package/dist/admin/client/login/schema.d.ts +15 -0
  71. package/dist/admin/client/login/schema.js +21 -0
  72. package/dist/admin/client/users/accept-invite-page.svelte +166 -37
  73. package/dist/admin/client/users/create-user-dialog.svelte +15 -7
  74. package/dist/admin/client/users/delete-user-dialog.svelte +81 -16
  75. package/dist/admin/client/users/delete-user-dialog.svelte.d.ts +4 -1
  76. package/dist/admin/client/users/edit-user-dialog.svelte +3 -0
  77. package/dist/admin/client/users/invite-user-dialog.svelte +16 -3
  78. package/dist/admin/client/users/lang.d.ts +27 -0
  79. package/dist/admin/client/users/lang.js +64 -10
  80. package/dist/admin/client/users/pending-invitations.svelte +59 -23
  81. package/dist/admin/client/users/users-page.svelte +471 -72
  82. package/dist/admin/components/accessibility/accessibility-overview.svelte +2 -7
  83. package/dist/admin/components/dashboard/a11y-gauge.svelte +90 -0
  84. package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +18 -0
  85. package/dist/admin/components/dashboard/accessibility-hub.svelte +13 -12
  86. package/dist/admin/components/dashboard/changelog-dialog.svelte +157 -0
  87. package/dist/admin/components/dashboard/changelog-dialog.svelte.d.ts +6 -0
  88. package/dist/admin/components/dashboard/form-submissions-widget.svelte +71 -113
  89. package/dist/admin/components/dashboard/index.d.ts +4 -2
  90. package/dist/admin/components/dashboard/index.js +4 -2
  91. package/dist/admin/components/dashboard/orphaned-entries-notice.svelte +240 -0
  92. package/dist/admin/components/dashboard/orphaned-entries-notice.svelte.d.ts +13 -0
  93. package/dist/admin/components/dashboard/recent-activity.svelte +53 -75
  94. package/dist/admin/components/dashboard/recent-entries.svelte +94 -0
  95. package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +18 -0
  96. package/dist/admin/components/dashboard/stat-card.svelte +2 -2
  97. package/dist/admin/components/dashboard/tip-of-the-day.svelte +109 -0
  98. package/dist/admin/components/dashboard/tip-of-the-day.svelte.d.ts +3 -0
  99. package/dist/admin/components/dashboard/welcome-header.svelte +45 -0
  100. package/dist/admin/components/dashboard/welcome-header.svelte.d.ts +3 -0
  101. package/dist/admin/components/fields/{array-field.svelte → blocks-field.svelte} +4 -4
  102. package/dist/admin/components/fields/{array-field.svelte.d.ts → blocks-field.svelte.d.ts} +5 -5
  103. package/dist/admin/components/fields/content-field.svelte +27 -0
  104. package/dist/admin/components/fields/content-field.svelte.d.ts +31 -0
  105. package/dist/admin/components/fields/field-renderer.svelte +9 -7
  106. package/dist/admin/components/fields/image-field.svelte +2 -2
  107. package/dist/admin/components/fields/media-field.svelte +2 -2
  108. package/dist/admin/components/fields/seo-field.svelte +205 -25
  109. package/dist/admin/components/fields/simple-array-field.svelte +289 -0
  110. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +30 -0
  111. package/dist/admin/components/fields/slug-field.svelte +3 -2
  112. package/dist/admin/components/fields/standalone-field-renderer.svelte +148 -0
  113. package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +9 -0
  114. package/dist/admin/components/fields/text-field-wrapper.svelte +147 -3
  115. package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
  116. package/dist/admin/components/fields/url-field.svelte +5 -4
  117. package/dist/admin/components/layout/app-sidebar.svelte +27 -24
  118. package/dist/admin/components/layout/lang.d.ts +6 -0
  119. package/dist/admin/components/layout/lang.js +13 -1
  120. package/dist/admin/components/layout/layout-renderer.svelte +352 -0
  121. package/dist/admin/components/layout/layout-renderer.svelte.d.ts +14 -0
  122. package/dist/admin/components/layout/nav-breadcrumbs.svelte +4 -4
  123. package/dist/admin/components/layout/nav-collections.svelte +65 -36
  124. package/dist/admin/components/layout/nav-footer.svelte +38 -0
  125. package/dist/admin/components/layout/nav-footer.svelte.d.ts +3 -0
  126. package/dist/admin/components/layout/nav-forms.svelte +55 -30
  127. package/dist/admin/components/layout/nav-main.svelte +14 -52
  128. package/dist/admin/components/layout/nav-search.svelte +4 -3
  129. package/dist/admin/components/layout/nav-singletons.svelte +59 -17
  130. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +17 -8
  131. package/dist/admin/components/layout/site-header.svelte +74 -13
  132. package/dist/admin/components/media/alt-input.svelte +32 -22
  133. package/dist/admin/components/media/bulk-action-bar.svelte +139 -150
  134. package/dist/admin/components/media/file/file-details.svelte +299 -217
  135. package/dist/admin/components/media/file/file-miniature.svelte +54 -41
  136. package/dist/admin/components/media/file/file-miniature.svelte.d.ts +1 -0
  137. package/dist/admin/components/media/file/file-preview.svelte +1 -1
  138. package/dist/admin/components/media/file-upload.svelte +24 -26
  139. package/dist/admin/components/media/files-list.svelte +112 -40
  140. package/dist/admin/components/media/files-list.svelte.d.ts +2 -0
  141. package/dist/admin/components/media/focal-point-input.svelte +122 -26
  142. package/dist/admin/components/media/media-library.svelte +127 -70
  143. package/dist/admin/components/media/media-search.svelte +6 -6
  144. package/dist/admin/components/media/media-sort.svelte +3 -1
  145. package/dist/admin/components/media/multi-file-summary.svelte +88 -68
  146. package/dist/admin/components/media/tag-combobox.svelte +141 -66
  147. package/dist/admin/components/media/tag-combobox.svelte.d.ts +1 -0
  148. package/dist/admin/components/media/tag-sidebar.svelte +139 -121
  149. package/dist/admin/components/tiptap/FigureNodeView.svelte +144 -15
  150. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +254 -0
  151. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte.d.ts +4 -0
  152. package/dist/admin/components/tiptap/SlashCommandPopup.svelte +212 -0
  153. package/dist/admin/components/tiptap/SlashCommandPopup.svelte.d.ts +8 -0
  154. package/dist/admin/components/tiptap/content-editor.svelte +280 -0
  155. package/dist/admin/components/tiptap/content-editor.svelte.d.ts +9 -0
  156. package/dist/admin/components/tiptap/editor-toolbar.svelte +230 -0
  157. package/dist/admin/components/tiptap/editor-toolbar.svelte.d.ts +16 -0
  158. package/dist/admin/components/tiptap/heading-a11y-plugin.d.ts +2 -0
  159. package/dist/admin/components/tiptap/heading-a11y-plugin.js +67 -0
  160. package/dist/admin/components/tiptap/image-dialog.svelte +172 -11
  161. package/dist/admin/components/tiptap/inline-block-node.d.ts +19 -0
  162. package/dist/admin/components/tiptap/inline-block-node.js +98 -0
  163. package/dist/admin/components/tiptap/link-dialog.svelte +9 -4
  164. package/dist/admin/components/tiptap/slash-command.d.ts +17 -0
  165. package/dist/admin/components/tiptap/slash-command.js +181 -0
  166. package/dist/admin/components/tiptap/structured-content-utils.d.ts +21 -0
  167. package/dist/admin/components/tiptap/structured-content-utils.js +150 -0
  168. package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -190
  169. package/dist/admin/email/invite-template.d.ts +8 -0
  170. package/dist/admin/email/invite-template.js +99 -0
  171. package/dist/admin/email/reset-password-template.d.ts +7 -0
  172. package/dist/admin/email/reset-password-template.js +96 -0
  173. package/dist/admin/remote/ai.remote.d.ts +1 -0
  174. package/dist/admin/remote/ai.remote.js +4 -1
  175. package/dist/admin/remote/entry.remote.d.ts +9 -0
  176. package/dist/admin/remote/entry.remote.js +58 -8
  177. package/dist/admin/remote/preview.remote.js +2 -1
  178. package/dist/admin/shared/password-schema.d.ts +5 -0
  179. package/dist/admin/shared/password-schema.js +10 -0
  180. package/dist/admin/state/content-language.svelte.d.ts +3 -0
  181. package/dist/admin/state/content-language.svelte.js +8 -0
  182. package/dist/admin/styles/admin.css +1530 -151
  183. package/dist/admin/utils/formatDate.d.ts +1 -0
  184. package/dist/admin/utils/formatDate.js +8 -0
  185. package/dist/admin/utils/roleLabel.d.ts +2 -0
  186. package/dist/admin/utils/roleLabel.js +13 -0
  187. package/dist/admin/utils/translationStatus.d.ts +17 -0
  188. package/dist/admin/utils/translationStatus.js +134 -0
  189. package/dist/ai-claude/index.d.ts +2 -0
  190. package/dist/ai-claude/index.js +56 -0
  191. package/dist/cms/runtime/api.d.ts +6 -1
  192. package/dist/cms/runtime/api.js +3 -0
  193. package/dist/cms/runtime/schemas.d.ts +9 -1
  194. package/dist/cms/runtime/schemas.js +8 -0
  195. package/dist/cms/runtime/types.d.ts +82 -10
  196. package/dist/cms/runtime/types.js +4 -0
  197. package/dist/components/ui/accordion/accordion.stories.svelte +39 -0
  198. package/dist/components/ui/accordion/accordion.stories.svelte.d.ts +27 -0
  199. package/dist/components/ui/alert/alert.stories.svelte +53 -0
  200. package/dist/components/ui/alert/alert.stories.svelte.d.ts +27 -0
  201. package/dist/components/ui/alert/alert.svelte +5 -0
  202. package/dist/components/ui/alert/alert.svelte.d.ts +9 -0
  203. package/dist/components/ui/avatar/avatar.stories.svelte +16 -0
  204. package/dist/components/ui/avatar/avatar.stories.svelte.d.ts +27 -0
  205. package/dist/components/ui/badge/badge.stories.svelte +33 -0
  206. package/dist/components/ui/badge/badge.stories.svelte.d.ts +27 -0
  207. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +33 -0
  208. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte.d.ts +27 -0
  209. package/dist/components/ui/button/button.stories.svelte +43 -0
  210. package/dist/components/ui/button/button.stories.svelte.d.ts +27 -0
  211. package/dist/components/ui/button/button.svelte +1 -2
  212. package/dist/components/ui/button/button.svelte.d.ts +0 -3
  213. package/dist/components/ui/button-group/button-group-separator.svelte.d.ts +1 -1
  214. package/dist/components/ui/card/card.stories.svelte +42 -0
  215. package/dist/components/ui/card/card.stories.svelte.d.ts +27 -0
  216. package/dist/components/ui/command/command.stories.svelte +51 -0
  217. package/dist/components/ui/command/command.stories.svelte.d.ts +27 -0
  218. package/dist/components/ui/dialog/dialog.stories.svelte +29 -0
  219. package/dist/components/ui/dialog/dialog.stories.svelte.d.ts +27 -0
  220. package/dist/components/ui/field/field-label.svelte.d.ts +1 -1
  221. package/dist/components/ui/field/field.stories.svelte +21 -0
  222. package/dist/components/ui/field/field.stories.svelte.d.ts +27 -0
  223. package/dist/components/ui/input/input.stories.svelte +40 -0
  224. package/dist/components/ui/input/input.stories.svelte.d.ts +27 -0
  225. package/dist/components/ui/input/input.svelte +2 -4
  226. package/dist/components/ui/item/item-separator.svelte.d.ts +1 -1
  227. package/dist/components/ui/label/label.stories.svelte +20 -0
  228. package/dist/components/ui/label/label.stories.svelte.d.ts +27 -0
  229. package/dist/components/ui/popover/popover.stories.svelte +29 -0
  230. package/dist/components/ui/popover/popover.stories.svelte.d.ts +27 -0
  231. package/dist/components/ui/select/select-group-heading.svelte.d.ts +1 -1
  232. package/dist/components/ui/select/select.stories.svelte +23 -0
  233. package/dist/components/ui/select/select.stories.svelte.d.ts +27 -0
  234. package/dist/components/ui/separator/separator.stories.svelte +24 -0
  235. package/dist/components/ui/separator/separator.stories.svelte.d.ts +27 -0
  236. package/dist/components/ui/sheet/sheet.stories.svelte +29 -0
  237. package/dist/components/ui/sheet/sheet.stories.svelte.d.ts +27 -0
  238. package/dist/components/ui/sidebar/sidebar-group.svelte +3 -3
  239. package/dist/components/ui/sidebar/sidebar-group.svelte.d.ts +2 -2
  240. package/dist/components/ui/sidebar/sidebar-menu-button.svelte +28 -30
  241. package/dist/components/ui/sidebar/sidebar-menu-button.svelte.d.ts +7 -7
  242. package/dist/components/ui/sidebar/sidebar-separator.svelte.d.ts +1 -1
  243. package/dist/components/ui/sidebar/sidebar-trigger.svelte +4 -4
  244. package/dist/components/ui/sonner/sonner.stories.svelte +22 -0
  245. package/dist/components/ui/sonner/sonner.stories.svelte.d.ts +26 -0
  246. package/dist/components/ui/sonner/sonner.svelte +8 -2
  247. package/dist/components/ui/sonner/toast-demo.svelte +29 -0
  248. package/dist/components/ui/sonner/toast-demo.svelte.d.ts +6 -0
  249. package/dist/components/ui/textarea/textarea.stories.svelte +22 -0
  250. package/dist/components/ui/textarea/textarea.stories.svelte.d.ts +27 -0
  251. package/dist/components/ui/textarea/textarea.svelte +0 -2
  252. package/dist/components/ui/toggle/toggle.stories.svelte +22 -0
  253. package/dist/components/ui/toggle/toggle.stories.svelte.d.ts +27 -0
  254. package/dist/components/ui/toggle-group/toggle-group.stories.svelte +17 -0
  255. package/dist/components/ui/toggle-group/toggle-group.stories.svelte.d.ts +27 -0
  256. package/dist/components/ui/tooltip/tooltip.stories.svelte +26 -0
  257. package/dist/components/ui/tooltip/tooltip.stories.svelte.d.ts +27 -0
  258. package/dist/core/fields/fieldSchemaToTs.d.ts +1 -0
  259. package/dist/core/fields/fieldSchemaToTs.js +133 -1
  260. package/dist/core/fields/layoutUtils.d.ts +17 -0
  261. package/dist/core/fields/layoutUtils.js +149 -0
  262. package/dist/core/fields/structuredToHtml.d.ts +9 -0
  263. package/dist/core/fields/structuredToHtml.js +161 -0
  264. package/dist/core/server/entries/operations/create.js +2 -1
  265. package/dist/core/server/entries/operations/get.js +10 -7
  266. package/dist/core/server/entries/operations/update.d.ts +3 -0
  267. package/dist/core/server/entries/operations/update.js +30 -2
  268. package/dist/core/server/fields/queryStructuredContent.d.ts +15 -0
  269. package/dist/core/server/fields/queryStructuredContent.js +65 -0
  270. package/dist/core/server/fields/resolveImageFields.js +51 -2
  271. package/dist/core/server/fields/resolveRelationFields.js +2 -2
  272. package/dist/core/server/fields/resolveRichtextLinks.js +80 -13
  273. package/dist/core/server/fields/resolveUrlFields.js +57 -6
  274. package/dist/core/server/fields/slugResolver.d.ts +10 -0
  275. package/dist/core/server/fields/slugResolver.js +34 -0
  276. package/dist/core/server/generator/fields.js +15 -4
  277. package/dist/core/server/generator/generator.js +3 -2
  278. package/dist/db-postgres/index.js +10 -6
  279. package/dist/files-local/index.js +126 -64
  280. package/dist/server/auth.d.ts +5 -0
  281. package/dist/server/auth.js +12 -1
  282. package/dist/sveltekit/components/structured-content.svelte +204 -0
  283. package/dist/sveltekit/components/structured-content.svelte.d.ts +21 -0
  284. package/dist/sveltekit/config.d.ts +13 -3
  285. package/dist/sveltekit/index.d.ts +3 -0
  286. package/dist/sveltekit/index.js +3 -0
  287. package/dist/sveltekit/server/handle.js +1 -0
  288. package/dist/types/config.d.ts +3 -0
  289. package/dist/types/entries.d.ts +3 -0
  290. package/dist/types/fields.d.ts +19 -2
  291. package/dist/types/index.d.ts +2 -0
  292. package/dist/types/index.js +2 -0
  293. package/dist/types/layout.d.ts +54 -0
  294. package/dist/types/layout.js +6 -0
  295. package/dist/types/structured-content.d.ts +63 -0
  296. package/dist/types/structured-content.js +1 -0
  297. package/dist/updates/0.1.5/index.d.ts +2 -0
  298. package/dist/updates/0.1.5/index.js +18 -0
  299. package/dist/updates/0.2.0/index.d.ts +2 -0
  300. package/dist/updates/0.2.0/index.js +11 -0
  301. package/dist/updates/0.2.2/index.d.ts +2 -0
  302. package/dist/updates/0.2.2/index.js +13 -0
  303. package/dist/updates/0.5.0/index.d.ts +2 -0
  304. package/dist/updates/0.5.0/index.js +14 -0
  305. package/dist/updates/0.5.1/index.d.ts +2 -0
  306. package/dist/updates/0.5.1/index.js +17 -0
  307. package/dist/updates/index.js +6 -1
  308. package/package.json +16 -9
  309. package/dist/admin/components/dashboard/updates-banner.svelte +0 -170
  310. package/dist/admin/components/dashboard/updates-banner.svelte.d.ts +0 -3
@@ -0,0 +1,161 @@
1
+ const VOID_TAGS = new Set(['hr', 'br', 'img']);
2
+ function escapeHtml(str) {
3
+ return str
4
+ .replace(/&/g, '&')
5
+ .replace(/</g, '&lt;')
6
+ .replace(/>/g, '&gt;')
7
+ .replace(/"/g, '&quot;');
8
+ }
9
+ function resolveUrl(url, baseUrl) {
10
+ if (!baseUrl || !url)
11
+ return url;
12
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//'))
13
+ return url;
14
+ return `${baseUrl.replace(/\/$/, '')}${url.startsWith('/') ? '' : '/'}${url}`;
15
+ }
16
+ function renderMarks(text, marks, options) {
17
+ if (!marks?.length)
18
+ return escapeHtml(text);
19
+ let html = escapeHtml(text);
20
+ for (const mark of marks) {
21
+ switch (mark.type) {
22
+ case 'bold':
23
+ html = `<strong>${html}</strong>`;
24
+ break;
25
+ case 'italic':
26
+ html = `<em>${html}</em>`;
27
+ break;
28
+ case 'underline':
29
+ html = `<u>${html}</u>`;
30
+ break;
31
+ case 'strike':
32
+ html = `<s>${html}</s>`;
33
+ break;
34
+ case 'code':
35
+ html = `<code>${html}</code>`;
36
+ break;
37
+ case 'highlight':
38
+ html = `<mark>${html}</mark>`;
39
+ break;
40
+ case 'link': {
41
+ const attrs = mark.attrs ?? {};
42
+ const href = resolveUrl(attrs.href ?? '', options.baseUrl);
43
+ const target = attrs.target;
44
+ const title = attrs.title;
45
+ const ariaLabel = attrs['aria-label'];
46
+ let attrStr = ` href="${escapeHtml(href)}"`;
47
+ if (target === '_blank') {
48
+ attrStr += ` target="_blank" rel="noopener noreferrer"`;
49
+ }
50
+ else if (target) {
51
+ attrStr += ` target="${escapeHtml(target)}"`;
52
+ }
53
+ if (title)
54
+ attrStr += ` title="${escapeHtml(title)}"`;
55
+ if (ariaLabel)
56
+ attrStr += ` aria-label="${escapeHtml(ariaLabel)}"`;
57
+ html = `<a${attrStr}>${html}</a>`;
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ return html;
63
+ }
64
+ function renderNode(node, options) {
65
+ const children = node.content?.map((c) => renderNode(c, options)).join('') ?? '';
66
+ switch (node.type) {
67
+ case 'doc':
68
+ return children;
69
+ case 'text':
70
+ return renderMarks(node.text ?? '', node.marks, options);
71
+ case 'paragraph':
72
+ return `<p>${children}</p>`;
73
+ case 'heading': {
74
+ const level = node.attrs?.level ?? 2;
75
+ return `<h${level}>${children}</h${level}>`;
76
+ }
77
+ case 'blockquote':
78
+ return `<blockquote>${children}</blockquote>`;
79
+ case 'bulletList':
80
+ return `<ul>${children}</ul>`;
81
+ case 'orderedList':
82
+ return `<ol>${children}</ol>`;
83
+ case 'listItem':
84
+ return `<li>${children}</li>`;
85
+ case 'codeBlock': {
86
+ const lang = node.attrs?.language;
87
+ const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : '';
88
+ return `<pre><code${langAttr}>${children}</code></pre>`;
89
+ }
90
+ case 'horizontalRule':
91
+ return '<hr>';
92
+ case 'hardBreak':
93
+ return '<br>';
94
+ case 'figure': {
95
+ const attrs = node.attrs ?? {};
96
+ const src = resolveUrl(attrs.src ?? '', options.baseUrl);
97
+ const alt = escapeHtml(attrs.alt ?? '');
98
+ const caption = attrs.caption;
99
+ const width = attrs.width;
100
+ const height = attrs.height;
101
+ let imgAttrs = ` src="${escapeHtml(src)}" alt="${alt}"`;
102
+ if (width)
103
+ imgAttrs += ` width="${width}"`;
104
+ if (height)
105
+ imgAttrs += ` height="${height}"`;
106
+ let html = `<figure><img${imgAttrs}>`;
107
+ if (caption)
108
+ html += `<figcaption>${escapeHtml(caption)}</figcaption>`;
109
+ html += '</figure>';
110
+ return html;
111
+ }
112
+ case 'image': {
113
+ const attrs = node.attrs ?? {};
114
+ const src = resolveUrl(attrs.src ?? '', options.baseUrl);
115
+ const alt = escapeHtml(attrs.alt ?? '');
116
+ const width = attrs.width;
117
+ const height = attrs.height;
118
+ let imgAttrs = ` src="${escapeHtml(src)}" alt="${alt}"`;
119
+ if (width)
120
+ imgAttrs += ` width="${width}"`;
121
+ if (height)
122
+ imgAttrs += ` height="${height}"`;
123
+ return `<img${imgAttrs}>`;
124
+ }
125
+ case 'video': {
126
+ const attrs = node.attrs ?? {};
127
+ const src = resolveUrl(attrs.src ?? '', options.baseUrl);
128
+ const poster = attrs.poster;
129
+ const width = attrs.width;
130
+ const height = attrs.height;
131
+ let videoAttrs = ' controls';
132
+ if (poster)
133
+ videoAttrs += ` poster="${escapeHtml(resolveUrl(poster, options.baseUrl))}"`;
134
+ if (width)
135
+ videoAttrs += ` width="${width}"`;
136
+ if (height)
137
+ videoAttrs += ` height="${height}"`;
138
+ return `<video${videoAttrs}><source src="${escapeHtml(src)}"></video>`;
139
+ }
140
+ case 'table':
141
+ return `<table>${children}</table>`;
142
+ case 'tableRow':
143
+ return `<tr>${children}</tr>`;
144
+ case 'tableCell':
145
+ return `<td>${children}</td>`;
146
+ case 'tableHeader':
147
+ return `<th>${children}</th>`;
148
+ case 'inlineBlock': {
149
+ const attrs = node.attrs ?? {};
150
+ const blockType = attrs.blockType;
151
+ const blockData = attrs.blockData ?? {};
152
+ return options.inlineBlock?.(blockType, blockData) ?? '';
153
+ }
154
+ default:
155
+ return children;
156
+ }
157
+ }
158
+ /** Convert a StructuredContentDoc to an HTML string. */
159
+ export function structuredToHtml(doc, options = {}) {
160
+ return renderNode(doc, options);
161
+ }
@@ -4,6 +4,7 @@ import { entryTypes } from '../../../../types/entries.js';
4
4
  import z from 'zod';
5
5
  import { getDbEntryOrThrow } from './get.js';
6
6
  import { generateZodSchemaFromFields } from '../../../fields/fieldSchemaToTs.js';
7
+ import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
7
8
  export const createEntrySchema = z.object({
8
9
  slug: z.string(),
9
10
  type: z.enum(entryTypes)
@@ -32,7 +33,7 @@ export const createEntryVersion = async (data, options) => {
32
33
  const entry = await getDbEntryOrThrow({ id: data.entryId });
33
34
  const config = getCMS().getBySlug(entry.slug);
34
35
  const languages = getCMS().languages;
35
- const schema = generateZodSchemaFromFields(config.fields, languages);
36
+ const schema = generateZodSchemaFromFields(getFieldsFromConfig(config), languages);
36
37
  const parsedData = schema.safeParse(data.data);
37
38
  if (!parsedData.success) {
38
39
  throw Error('Invalid data: ' + JSON.stringify(parsedData.error.flatten()));
@@ -1,5 +1,6 @@
1
1
  import { getCMS } from '../../../cms.js';
2
2
  import { populateEntryData } from '../../fields/populateEntry.js';
3
+ import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
3
4
  // Helper function to transform string values to localized objects
4
5
  function transformDataValuesToLocalized(dataValues, defaultLanguage) {
5
6
  const result = {};
@@ -70,16 +71,17 @@ export const getRawEntries = async (options) => {
70
71
  const scheduledVersion = publishedOrScheduledVersion && isScheduled
71
72
  ? publishedOrScheduledVersion
72
73
  : null;
73
- // Draft = latest version by versionNumber
74
- const draftVersions = versions
75
- .sort((a, b) => b.versionNumber - a.versionNumber);
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;
76
78
  return {
77
79
  ...entry,
78
80
  collection: getCMS().getBySlug(entry.slug),
79
81
  versions: versions.sort((a, b) => b.versionNumber - a.versionNumber),
80
82
  publishedVersion,
81
83
  scheduledVersion,
82
- draftVersion: draftVersions[0] || null
84
+ draftVersion
83
85
  };
84
86
  }
85
87
  catch {
@@ -92,7 +94,8 @@ export const getRawEntries = async (options) => {
92
94
  export const getRawEntry = async (options) => {
93
95
  const [entry] = await getRawEntries({
94
96
  ...options,
95
- ids: options.id ? [options.id] : undefined
97
+ ids: options.id ? [options.id] : undefined,
98
+ includeArchived: options.includeArchived
96
99
  });
97
100
  return entry || null;
98
101
  };
@@ -237,7 +240,7 @@ export const getEntries = async (options = {}) => {
237
240
  const config = getCMS().getBySlug(dbEntry.slug);
238
241
  return {
239
242
  ...dbEntry,
240
- data: await populateEntryData(version.data, config.fields, language),
243
+ data: await populateEntryData(version.data, getFieldsFromConfig(config), language),
241
244
  status,
242
245
  publishedAt: dbEntry.publishedAt,
243
246
  populated: true
@@ -295,7 +298,7 @@ export const getEntryVersion = async (options) => {
295
298
  const config = getCMS().getBySlug(dbEntry.slug);
296
299
  return {
297
300
  ...dbEntry,
298
- data: await populateEntryData(dbEntryVersion.data, config.fields, language)
301
+ data: await populateEntryData(dbEntryVersion.data, getFieldsFromConfig(config), language)
299
302
  };
300
303
  }
301
304
  catch (error) {
@@ -17,4 +17,7 @@ export declare const updateEntryVersion: (id: string, data: Partial<DbEntryVersi
17
17
  export declare const updateEntryVersionCommandTypes: readonly ["draft", "published-now", "published-scheduled", "cancel-published"];
18
18
  export type UpdateEntryVersionCommandType = (typeof updateEntryVersionCommandTypes)[number];
19
19
  export declare const pruneOldDraftVersions: (entryId: string) => Promise<void>;
20
+ export declare const upsertDraftVersion: (entryId: string, data: Record<string, unknown>, options?: {
21
+ skipValidation?: boolean;
22
+ }) => Promise<DbEntryVersion>;
20
23
  export declare const unpublishEntry: (entryId: string) => Promise<void>;
@@ -1,7 +1,9 @@
1
1
  import { getCMS } from '../../../cms.js';
2
2
  import { generateZodSchemaFromFields } from '../../../fields/fieldSchemaToTs.js';
3
+ import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
3
4
  import z from 'zod';
4
- import { getDbEntryOrThrow, getDbEntryVersionOrThrow } from './get.js';
5
+ import { getDbEntryOrThrow, getDbEntryVersionOrThrow, getDbEntryVersions } from './get.js';
6
+ import { createEntryVersion } from './create.js';
5
7
  export const updateEntrySchema = z.object({
6
8
  availableLocales: z.array(z.string()).optional(),
7
9
  archivedAt: z.date().nullable().optional(),
@@ -57,7 +59,7 @@ export const updateEntryVersion = async (id, data) => {
57
59
  const entry = await getDbEntryOrThrow({ id: version.entryId });
58
60
  const config = getCMS().getBySlug(entry.slug);
59
61
  const languages = getCMS().languages;
60
- const schema = generateZodSchemaFromFields(config.fields, languages);
62
+ const schema = generateZodSchemaFromFields(getFieldsFromConfig(config), languages);
61
63
  const parsedData = schema.safeParse(filteredData.data);
62
64
  if (!parsedData.success) {
63
65
  throw Error('Invalid data: ' + parsedData.error.flatten());
@@ -102,6 +104,32 @@ export const pruneOldDraftVersions = async (entryId) => {
102
104
  await Promise.all(toDelete.map((v) => getCMS().databaseAdapter.deleteEntryVersion({ id: v.id })));
103
105
  }
104
106
  };
107
+ export const upsertDraftVersion = async (entryId, data, options) => {
108
+ const entry = await getDbEntryOrThrow({ id: entryId });
109
+ const versions = await getDbEntryVersions({ entryIds: [entryId] });
110
+ // Sort desc by versionNumber, find latest non-published draft
111
+ const sorted = versions.sort((a, b) => b.versionNumber - a.versionNumber);
112
+ const latestDraft = sorted.find((v) => v.id !== entry.publishedVersionId);
113
+ if (latestDraft) {
114
+ // Compare data — if identical, skip
115
+ const existingData = JSON.stringify(latestDraft.data);
116
+ const newData = JSON.stringify(data);
117
+ if (existingData === newData) {
118
+ return latestDraft;
119
+ }
120
+ // Different data — update in place
121
+ const updated = await getCMS().databaseAdapter.updateEntryVersion({
122
+ id: latestDraft.id,
123
+ data: {
124
+ data: data,
125
+ createdAt: new Date()
126
+ }
127
+ });
128
+ return updated;
129
+ }
130
+ // No draft exists — create new version
131
+ return createEntryVersion({ entryId, data }, options);
132
+ };
105
133
  export const unpublishEntry = async (entryId) => {
106
134
  await updateEntry(entryId, {
107
135
  publishedVersionId: null,
@@ -0,0 +1,15 @@
1
+ import type { SCNode, StructuredContentDoc, SCInlineBlockAttrs } from '../../../types/structured-content.js';
2
+ export interface MediaRef {
3
+ type: 'image' | 'video';
4
+ src: string;
5
+ alt?: string;
6
+ mediaId?: string;
7
+ }
8
+ /** Extract block-level nodes, optionally filtered by type. */
9
+ export declare function extractBlocks(doc: StructuredContentDoc, type?: string): SCNode[];
10
+ /** Extract inline block nodes, optionally filtered by blockType. */
11
+ export declare function extractInlineBlocks(doc: StructuredContentDoc, blockType?: string): SCInlineBlockAttrs[];
12
+ /** Extract all plain text from doc (useful for excerpts, search indexing). */
13
+ export declare function extractText(doc: StructuredContentDoc): string;
14
+ /** Extract media references from figure/video/image nodes. */
15
+ export declare function extractMediaRefs(doc: StructuredContentDoc): MediaRef[];
@@ -0,0 +1,65 @@
1
+ import { walkNodes } from '../../../admin/components/tiptap/structured-content-utils.js';
2
+ /** Extract block-level nodes, optionally filtered by type. */
3
+ export function extractBlocks(doc, type) {
4
+ const result = [];
5
+ walkNodes(doc.content, (node) => {
6
+ if (type ? node.type === type : !['doc', 'text'].includes(node.type)) {
7
+ result.push(node);
8
+ }
9
+ });
10
+ return result;
11
+ }
12
+ /** Extract inline block nodes, optionally filtered by blockType. */
13
+ export function extractInlineBlocks(doc, blockType) {
14
+ const result = [];
15
+ walkNodes(doc.content, (node) => {
16
+ if (node.type !== 'inlineBlock' || !node.attrs)
17
+ return;
18
+ const attrs = node.attrs;
19
+ if (blockType ? attrs.blockType === blockType : true) {
20
+ result.push(attrs);
21
+ }
22
+ });
23
+ return result;
24
+ }
25
+ /** Extract all plain text from doc (useful for excerpts, search indexing). */
26
+ export function extractText(doc) {
27
+ const parts = [];
28
+ walkNodes(doc.content, (node) => {
29
+ if (node.type === 'text' && node.text) {
30
+ parts.push(node.text);
31
+ }
32
+ else if (node.type === 'hardBreak') {
33
+ parts.push('\n');
34
+ }
35
+ });
36
+ return parts.join('');
37
+ }
38
+ /** Extract media references from figure/video/image nodes. */
39
+ export function extractMediaRefs(doc) {
40
+ const refs = [];
41
+ walkNodes(doc.content, (node) => {
42
+ if (node.type === 'figure' || node.type === 'image') {
43
+ const attrs = node.attrs;
44
+ if (attrs?.src) {
45
+ refs.push({
46
+ type: 'image',
47
+ src: attrs.src,
48
+ alt: attrs.alt ?? undefined,
49
+ mediaId: attrs['data-media-id'] ?? undefined
50
+ });
51
+ }
52
+ }
53
+ else if (node.type === 'video') {
54
+ const attrs = node.attrs;
55
+ if (attrs?.src) {
56
+ refs.push({
57
+ type: 'video',
58
+ src: attrs.src,
59
+ mediaId: attrs['data-media-id'] ?? undefined
60
+ });
61
+ }
62
+ }
63
+ });
64
+ return refs;
65
+ }
@@ -3,6 +3,7 @@ const PUBLIC_URL = env.PUBLIC_URL ?? '';
3
3
  import { getCMS } from '../../cms.js';
4
4
  import z from 'zod';
5
5
  import { getImageStyles } from './utils/imageStyles.js';
6
+ import { extractMediaIds as extractMediaIdsFromDoc, walkMediaNodes, cloneDoc } from '../../../admin/components/tiptap/structured-content-utils.js';
6
7
  export async function resolveMediaFields(data, fields) {
7
8
  const mediaIds = [];
8
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -39,7 +40,7 @@ export async function resolveMediaFields(data, fields) {
39
40
  case 'object':
40
41
  collectIds(val.data, field.fields);
41
42
  break;
42
- case 'array':
43
+ case 'blocks':
43
44
  if (Array.isArray(val)) {
44
45
  val.forEach((item) => {
45
46
  // Find the object definition for this item based on its slug
@@ -50,6 +51,17 @@ export async function resolveMediaFields(data, fields) {
50
51
  });
51
52
  }
52
53
  break;
54
+ case 'content': {
55
+ // Content field is localized: { lang: StructuredContentDoc }
56
+ if (typeof val === 'object' && val !== null) {
57
+ for (const doc of Object.values(val)) {
58
+ if (doc && typeof doc === 'object' && doc.type === 'doc') {
59
+ mediaIds.push(...extractMediaIdsFromDoc(doc));
60
+ }
61
+ }
62
+ }
63
+ break;
64
+ }
53
65
  }
54
66
  }
55
67
  };
@@ -178,7 +190,7 @@ export async function resolveMediaFields(data, fields) {
178
190
  data: await resolveValues(val.data, field.fields)
179
191
  };
180
192
  break;
181
- case 'array':
193
+ case 'blocks':
182
194
  result[field.slug] = await Promise.all(
183
195
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
196
  val.map(async (item) => {
@@ -194,6 +206,43 @@ export async function resolveMediaFields(data, fields) {
194
206
  return item;
195
207
  }));
196
208
  break;
209
+ case 'content': {
210
+ // Content field is localized: { lang: StructuredContentDoc }
211
+ if (typeof val === 'object' && val !== null) {
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ const resolved = {};
214
+ for (const [lang, doc] of Object.entries(val)) {
215
+ if (doc && typeof doc === 'object' && doc.type === 'doc') {
216
+ const cloned = cloneDoc(doc);
217
+ walkMediaNodes(cloned, (node) => {
218
+ const mediaId = node.attrs?.['data-media-id'];
219
+ if (!mediaId)
220
+ return;
221
+ const mediaFile = mediaMap[mediaId];
222
+ if (!mediaFile)
223
+ return;
224
+ node.attrs.src = mediaFile.url;
225
+ if (mediaFile.width)
226
+ node.attrs.width = mediaFile.width;
227
+ if (mediaFile.height)
228
+ node.attrs.height = mediaFile.height;
229
+ if (mediaFile.alt && !node.attrs.alt)
230
+ node.attrs.alt = mediaFile.alt;
231
+ node.attrs._media = {
232
+ data: mediaFile,
233
+ ...(mediaFile.type === 'image' ? { blurDataUrl: mediaFile.blurDataUrl } : {})
234
+ };
235
+ });
236
+ resolved[lang] = cloned;
237
+ }
238
+ else {
239
+ resolved[lang] = doc;
240
+ }
241
+ }
242
+ result[field.slug] = resolved;
243
+ }
244
+ break;
245
+ }
197
246
  default:
198
247
  result[field.slug] = val;
199
248
  }
@@ -26,7 +26,7 @@ export async function resolveRelationFields(data, fields, language) {
26
26
  case 'object':
27
27
  collectIds(val.data, field.fields);
28
28
  break;
29
- case 'array':
29
+ case 'blocks':
30
30
  if (Array.isArray(val)) {
31
31
  val.forEach((item) => {
32
32
  // Find the object definition for this item based on its slug
@@ -78,7 +78,7 @@ export async function resolveRelationFields(data, fields, language) {
78
78
  data: resolveValues(val.data, field.fields)
79
79
  };
80
80
  break;
81
- case 'array':
81
+ case 'blocks':
82
82
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
83
  result[field.slug] = val.map((item) => {
84
84
  // Find the object definition for this item based on its slug
@@ -1,4 +1,6 @@
1
1
  import { getCMS } from '../../cms.js';
2
+ import { extractEntryIds as extractEntryIdsFromDoc, extractMediaIds as extractMediaIdsFromDoc, cloneDoc, walkLinkMarks, walkInlineBlockNodes } from '../../../admin/components/tiptap/structured-content-utils.js';
3
+ import { getEntrySlugPath, getSlugFromEntryData } from './slugResolver.js';
2
4
  const ENTRY_ID_RE = /data-entry-id="([0-9a-f-]{36})"/g;
3
5
  function extractEntryIds(html) {
4
6
  const ids = [];
@@ -7,15 +9,47 @@ function extractEntryIds(html) {
7
9
  }
8
10
  return ids;
9
11
  }
10
- function resolveHtml(html, entriesMap) {
12
+ function resolveHtml(html, slugMap) {
11
13
  return html.replace(/<a\s([^>]*data-entry-id="([0-9a-f-]{36})"[^>]*)>/g, (match, _attrs, id) => {
12
- const entryData = entriesMap[id]?.data;
13
- const slug = entryData?.seo?.slug;
14
+ const slug = slugMap[id];
14
15
  if (!slug)
15
16
  return match;
16
17
  return match.replace(/href="[^"]*"/, `href="${slug}"`);
17
18
  });
18
19
  }
20
+ /** Extract entry IDs from inlineBlock blockData relation fields. */
21
+ function extractEntryIdsFromInlineBlocks(doc) {
22
+ const ids = [];
23
+ walkInlineBlockNodes(doc, (node) => {
24
+ const data = node.attrs?.blockData;
25
+ if (!data || typeof data !== 'object')
26
+ return;
27
+ for (const val of Object.values(data)) {
28
+ if (typeof val === 'string' && val.length === 36)
29
+ ids.push(val);
30
+ if (Array.isArray(val)) {
31
+ for (const item of val) {
32
+ if (typeof item === 'string' && item.length === 36)
33
+ ids.push(item);
34
+ }
35
+ }
36
+ }
37
+ });
38
+ return ids;
39
+ }
40
+ function resolveContentDoc(doc, slugMap) {
41
+ const cloned = cloneDoc(doc);
42
+ walkLinkMarks(cloned, (mark) => {
43
+ const entryId = mark.attrs?.['data-entry-id'];
44
+ if (!entryId)
45
+ return;
46
+ const slug = slugMap[entryId];
47
+ if (slug && mark.attrs) {
48
+ mark.attrs.href = slug;
49
+ }
50
+ });
51
+ return cloned;
52
+ }
19
53
  export async function resolveRichtextLinks(data, fields, language) {
20
54
  const entriesIds = [];
21
55
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -39,10 +73,22 @@ export async function resolveRichtextLinks(data, fields, language) {
39
73
  }
40
74
  break;
41
75
  }
76
+ case 'content': {
77
+ // Content field is localized: { lang: StructuredContentDoc, ... }
78
+ if (typeof val === 'object' && val !== null) {
79
+ for (const doc of Object.values(val)) {
80
+ if (doc && typeof doc === 'object' && doc.type === 'doc') {
81
+ entriesIds.push(...extractEntryIdsFromDoc(doc));
82
+ entriesIds.push(...extractEntryIdsFromInlineBlocks(doc));
83
+ }
84
+ }
85
+ }
86
+ break;
87
+ }
42
88
  case 'object':
43
89
  collectIds(val.data, field.fields);
44
90
  break;
45
- case 'array':
91
+ case 'blocks':
46
92
  if (Array.isArray(val)) {
47
93
  val.forEach((item) => {
48
94
  const objectDef = field.of.find((objDef) => objDef.slug === item.slug);
@@ -71,15 +117,20 @@ export async function resolveRichtextLinks(data, fields, language) {
71
117
  const versions = await db.getEntryVersions({
72
118
  ids: publishedVersionIds
73
119
  });
74
- // Build map: entryId → { data: { seo: { slug } } } using raw version data
75
- const entriesMap = {};
120
+ // Build map: entryId → collectionSlug for config lookup
121
+ const entryConfigSlugMap = {};
122
+ for (const entry of entries) {
123
+ entryConfigSlugMap[entry.id] = entry.slug;
124
+ }
125
+ // Build map: entryId → resolved slug string
126
+ const slugMap = {};
76
127
  for (const version of versions) {
77
128
  const rawData = version.data;
78
- const seoRaw = rawData?.seo;
79
- const slugField = seoRaw?.slug;
80
- const slug = slugField?.[language];
129
+ const configSlug = entryConfigSlugMap[version.entryId];
130
+ const slugPath = configSlug ? getEntrySlugPath(configSlug) : 'seo.slug';
131
+ const slug = getSlugFromEntryData(rawData, slugPath, language);
81
132
  if (slug) {
82
- entriesMap[version.entryId] = { data: { seo: { slug } } };
133
+ slugMap[version.entryId] = slug;
83
134
  }
84
135
  }
85
136
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -99,12 +150,28 @@ export async function resolveRichtextLinks(data, fields, language) {
99
150
  const resolved = {};
100
151
  for (const [lang, html] of Object.entries(val)) {
101
152
  resolved[lang] =
102
- typeof html === 'string' ? resolveHtml(html, entriesMap) : html;
153
+ typeof html === 'string' ? resolveHtml(html, slugMap) : html;
103
154
  }
104
155
  result[field.slug] = resolved;
105
156
  }
106
157
  else if (typeof val === 'string') {
107
- result[field.slug] = resolveHtml(val, entriesMap);
158
+ result[field.slug] = resolveHtml(val, slugMap);
159
+ }
160
+ break;
161
+ }
162
+ case 'content': {
163
+ if (typeof val === 'object' && val !== null) {
164
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
165
+ const resolved = {};
166
+ for (const [lang, doc] of Object.entries(val)) {
167
+ if (doc && typeof doc === 'object' && doc.type === 'doc') {
168
+ resolved[lang] = resolveContentDoc(doc, slugMap);
169
+ }
170
+ else {
171
+ resolved[lang] = doc;
172
+ }
173
+ }
174
+ result[field.slug] = resolved;
108
175
  }
109
176
  break;
110
177
  }
@@ -114,7 +181,7 @@ export async function resolveRichtextLinks(data, fields, language) {
114
181
  data: resolveValues(val.data, field.fields)
115
182
  };
116
183
  break;
117
- case 'array':
184
+ case 'blocks':
118
185
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
186
  result[field.slug] = val.map((item) => {
120
187
  const objectDef = field.of.find((objDef) => objDef.slug === item.slug);