@xleddyl/nuxt-cms 0.9.4 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (431) hide show
  1. package/README.md +1 -1
  2. package/dist/module.d.mts +20 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +1037 -148
  5. package/dist/runtime/app/blocks/CmsBlocks.d.vue.ts +1 -1
  6. package/dist/runtime/app/blocks/CmsBlocks.vue +57 -9
  7. package/dist/runtime/app/blocks/CmsBlocks.vue.d.ts +1 -1
  8. package/dist/runtime/app/blocks/CmsField.d.ts +24 -0
  9. package/dist/runtime/app/blocks/CmsField.js +16 -0
  10. package/dist/runtime/app/blocks/node.d.ts +37 -0
  11. package/dist/runtime/app/blocks/node.js +17 -0
  12. package/dist/runtime/app/blocks/resolve.d.ts +18 -0
  13. package/dist/runtime/app/blocks/resolve.js +34 -0
  14. package/dist/runtime/app/components/cms/BlockPicker.d.vue.ts +22 -0
  15. package/dist/runtime/app/components/cms/BlockPicker.vue +87 -0
  16. package/dist/runtime/app/components/cms/BlockPicker.vue.d.ts +22 -0
  17. package/dist/runtime/app/components/cms/BlockPickerPopover.d.vue.ts +38 -0
  18. package/dist/runtime/app/components/cms/BlockPickerPopover.vue +44 -0
  19. package/dist/runtime/app/components/cms/BlockPickerPopover.vue.d.ts +38 -0
  20. package/dist/runtime/app/components/cms/BlockTree.d.vue.ts +29 -0
  21. package/dist/runtime/app/components/cms/BlockTree.vue +322 -0
  22. package/dist/runtime/app/components/cms/BlockTree.vue.d.ts +29 -0
  23. package/dist/runtime/app/components/cms/BlocksField.vue +569 -218
  24. package/dist/runtime/app/components/cms/BrandMark.vue +7 -13
  25. package/dist/runtime/app/components/cms/ConfirmGroups.vue +15 -13
  26. package/dist/runtime/app/components/cms/{BlockPalette.vue.d.ts → EditorHistory.d.vue.ts} +6 -5
  27. package/dist/runtime/app/components/cms/EditorHistory.vue +34 -0
  28. package/dist/runtime/app/components/cms/{BlockPalette.d.vue.ts → EditorHistory.vue.d.ts} +6 -5
  29. package/dist/runtime/app/components/cms/EntryDrawer.d.vue.ts +11 -5
  30. package/dist/runtime/app/components/cms/EntryDrawer.vue +183 -152
  31. package/dist/runtime/app/components/cms/EntryDrawer.vue.d.ts +11 -5
  32. package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +16 -4
  33. package/dist/runtime/app/components/cms/EntryForm.vue +106 -146
  34. package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +16 -4
  35. package/dist/runtime/app/components/cms/FieldInput.vue +10 -2
  36. package/dist/runtime/app/components/cms/FormSections.d.vue.ts +22 -0
  37. package/dist/runtime/app/components/cms/FormSections.vue +71 -0
  38. package/dist/runtime/app/components/cms/FormSections.vue.d.ts +22 -0
  39. package/dist/runtime/app/components/cms/ImportModal.vue +46 -58
  40. package/dist/runtime/app/components/cms/LinkField.d.vue.ts +16 -0
  41. package/dist/runtime/app/components/cms/LinkField.vue +250 -0
  42. package/dist/runtime/app/components/cms/LinkField.vue.d.ts +16 -0
  43. package/dist/runtime/app/components/cms/ListFilters.vue +10 -12
  44. package/dist/runtime/app/components/cms/LocaleSwitch.d.vue.ts +2 -1
  45. package/dist/runtime/app/components/cms/LocaleSwitch.vue +29 -5
  46. package/dist/runtime/app/components/cms/LocaleSwitch.vue.d.ts +2 -1
  47. package/dist/runtime/app/components/cms/MediaDetail.d.vue.ts +2 -0
  48. package/dist/runtime/app/components/cms/MediaDetail.vue +112 -83
  49. package/dist/runtime/app/components/cms/MediaDetail.vue.d.ts +2 -0
  50. package/dist/runtime/app/components/cms/MediaField.vue +35 -25
  51. package/dist/runtime/app/components/cms/MediaFolderPicker.vue +26 -71
  52. package/dist/runtime/app/components/cms/MediaGallery.d.vue.ts +1 -0
  53. package/dist/runtime/app/components/cms/MediaGallery.vue +442 -419
  54. package/dist/runtime/app/components/cms/MediaGallery.vue.d.ts +1 -0
  55. package/dist/runtime/app/components/cms/MediaThumb.vue +9 -25
  56. package/dist/runtime/app/components/cms/MediaUpload.vue +1 -1
  57. package/dist/runtime/app/components/cms/{BlockOutline.d.vue.ts → NestedBlockList.d.vue.ts} +5 -5
  58. package/dist/runtime/app/components/cms/NestedBlockList.vue +87 -0
  59. package/dist/runtime/app/components/cms/{BlockOutline.vue.d.ts → NestedBlockList.vue.d.ts} +5 -5
  60. package/dist/runtime/app/components/cms/PoweredBy.vue +12 -5
  61. package/dist/runtime/app/components/cms/PresetModal.d.vue.ts +18 -0
  62. package/dist/runtime/app/components/cms/PresetModal.vue +70 -0
  63. package/dist/runtime/app/components/cms/PresetModal.vue.d.ts +18 -0
  64. package/dist/runtime/app/components/cms/RelationField.vue +2 -8
  65. package/dist/runtime/app/components/cms/SettingsAccount.vue +60 -71
  66. package/dist/runtime/app/components/cms/SettingsColumns.vue +47 -69
  67. package/dist/runtime/app/components/cms/SettingsEmptyAdd.d.vue.ts +13 -0
  68. package/dist/runtime/app/components/cms/SettingsEmptyAdd.vue +21 -0
  69. package/dist/runtime/app/components/cms/SettingsEmptyAdd.vue.d.ts +13 -0
  70. package/dist/runtime/app/components/cms/SettingsEntries.d.vue.ts +13 -0
  71. package/dist/runtime/app/components/cms/SettingsEntries.vue +50 -0
  72. package/dist/runtime/app/components/cms/SettingsEntries.vue.d.ts +13 -0
  73. package/dist/runtime/app/components/cms/SettingsEntry.d.vue.ts +1 -10
  74. package/dist/runtime/app/components/cms/SettingsEntry.vue +11 -95
  75. package/dist/runtime/app/components/cms/SettingsEntry.vue.d.ts +1 -10
  76. package/dist/runtime/app/components/cms/SettingsEntryActions.d.vue.ts +7 -0
  77. package/dist/runtime/app/components/cms/SettingsEntryActions.vue +42 -0
  78. package/dist/runtime/app/components/cms/SettingsEntryActions.vue.d.ts +7 -0
  79. package/dist/runtime/app/components/cms/SettingsEntryTabs.d.vue.ts +7 -0
  80. package/dist/runtime/app/components/cms/SettingsEntryTabs.vue +21 -0
  81. package/dist/runtime/app/components/cms/SettingsEntryTabs.vue.d.ts +7 -0
  82. package/dist/runtime/app/components/cms/SettingsFormAction.d.vue.ts +3 -0
  83. package/dist/runtime/app/components/cms/SettingsFormAction.vue +17 -0
  84. package/dist/runtime/app/components/cms/SettingsFormAction.vue.d.ts +3 -0
  85. package/dist/runtime/app/components/cms/SettingsLayout.vue +18 -14
  86. package/dist/runtime/app/components/cms/SettingsPresetForm.d.vue.ts +11 -0
  87. package/dist/runtime/app/components/cms/SettingsPresetForm.vue +60 -0
  88. package/dist/runtime/app/components/cms/SettingsPresetForm.vue.d.ts +11 -0
  89. package/dist/runtime/app/components/cms/SettingsPresets.d.vue.ts +13 -0
  90. package/dist/runtime/app/components/cms/SettingsPresets.vue +65 -0
  91. package/dist/runtime/app/components/cms/SettingsPresets.vue.d.ts +13 -0
  92. package/dist/runtime/app/components/cms/SettingsRecordActions.d.vue.ts +32 -0
  93. package/dist/runtime/app/components/cms/SettingsRecordActions.vue +45 -0
  94. package/dist/runtime/app/components/cms/SettingsRecordActions.vue.d.ts +32 -0
  95. package/dist/runtime/app/components/cms/SettingsRedirectActions.d.vue.ts +3 -0
  96. package/dist/runtime/app/components/cms/SettingsRedirectActions.vue +59 -0
  97. package/dist/runtime/app/components/cms/SettingsRedirectActions.vue.d.ts +3 -0
  98. package/dist/runtime/app/components/cms/SettingsRedirectForm.d.vue.ts +11 -0
  99. package/dist/runtime/app/components/cms/SettingsRedirectForm.vue +77 -0
  100. package/dist/runtime/app/components/cms/SettingsRedirectForm.vue.d.ts +11 -0
  101. package/dist/runtime/app/components/cms/SettingsRedirects.d.vue.ts +13 -0
  102. package/dist/runtime/app/components/cms/SettingsRedirects.vue +117 -0
  103. package/dist/runtime/app/components/cms/SettingsRedirects.vue.d.ts +13 -0
  104. package/dist/runtime/app/components/cms/SettingsSecretCard.d.vue.ts +17 -0
  105. package/dist/runtime/app/components/cms/SettingsSecretCard.vue +43 -0
  106. package/dist/runtime/app/components/cms/SettingsSecretCard.vue.d.ts +17 -0
  107. package/dist/runtime/app/components/cms/SettingsSite.d.vue.ts +3 -0
  108. package/dist/runtime/app/components/cms/SettingsSite.vue +140 -0
  109. package/dist/runtime/app/components/cms/SettingsSite.vue.d.ts +3 -0
  110. package/dist/runtime/app/components/cms/SettingsUserCreate.d.vue.ts +8 -0
  111. package/dist/runtime/app/components/cms/SettingsUserCreate.vue +82 -0
  112. package/dist/runtime/app/components/cms/SettingsUserCreate.vue.d.ts +8 -0
  113. package/dist/runtime/app/components/cms/SettingsUserDetail.d.vue.ts +11 -0
  114. package/dist/runtime/app/components/cms/SettingsUserDetail.vue +179 -0
  115. package/dist/runtime/app/components/cms/SettingsUserDetail.vue.d.ts +11 -0
  116. package/dist/runtime/app/components/cms/SettingsUsers.d.vue.ts +9 -6
  117. package/dist/runtime/app/components/cms/SettingsUsers.vue +88 -234
  118. package/dist/runtime/app/components/cms/SettingsUsers.vue.d.ts +9 -6
  119. package/dist/runtime/app/components/cms/SharePreview.d.vue.ts +18 -0
  120. package/dist/runtime/app/components/cms/SharePreview.vue +219 -0
  121. package/dist/runtime/app/components/cms/SharePreview.vue.d.ts +18 -0
  122. package/dist/runtime/app/components/cms/SlugField.vue +5 -3
  123. package/dist/runtime/app/components/cms/StatusBadge.vue +9 -8
  124. package/dist/runtime/app/components/cms/TranslationBadges.d.vue.ts +7 -0
  125. package/dist/runtime/app/components/cms/TranslationBadges.vue +45 -0
  126. package/dist/runtime/app/components/cms/TranslationBadges.vue.d.ts +7 -0
  127. package/dist/runtime/app/components/cms/TranslationMenu.d.vue.ts +17 -0
  128. package/dist/runtime/app/components/cms/TranslationMenu.vue +28 -0
  129. package/dist/runtime/app/components/cms/TranslationMenu.vue.d.ts +17 -0
  130. package/dist/runtime/app/components/cms/VisualEditor.d.vue.ts +22 -4
  131. package/dist/runtime/app/components/cms/VisualEditor.vue +1152 -335
  132. package/dist/runtime/app/components/cms/VisualEditor.vue.d.ts +22 -4
  133. package/dist/runtime/app/components/cms/WelcomeModal.vue +32 -50
  134. package/dist/runtime/app/composables/cms-clipboard.d.ts +8 -0
  135. package/dist/runtime/app/composables/cms-clipboard.js +38 -0
  136. package/dist/runtime/app/composables/cms-confirm.d.ts +6 -8
  137. package/dist/runtime/app/composables/cms-confirm.js +5 -7
  138. package/dist/runtime/app/composables/cms-entry-disabled.d.ts +5 -1
  139. package/dist/runtime/app/composables/cms-entry-disabled.js +7 -0
  140. package/dist/runtime/app/composables/cms-entry-form.d.ts +15 -0
  141. package/dist/runtime/app/composables/cms-entry-form.js +40 -0
  142. package/dist/runtime/app/composables/cms-entry-settings.d.ts +30 -0
  143. package/dist/runtime/app/composables/cms-entry-settings.js +122 -0
  144. package/dist/runtime/app/composables/cms-entry.d.ts +5 -1
  145. package/dist/runtime/app/composables/cms-entry.js +36 -11
  146. package/dist/runtime/app/composables/cms-history.d.ts +10 -0
  147. package/dist/runtime/app/composables/cms-history.js +24 -0
  148. package/dist/runtime/app/composables/cms-link-targets.d.ts +10 -0
  149. package/dist/runtime/app/composables/cms-link-targets.js +40 -0
  150. package/dist/runtime/app/composables/cms-link-usage.d.ts +1 -0
  151. package/dist/runtime/app/composables/cms-link-usage.js +45 -0
  152. package/dist/runtime/app/composables/cms-media-library.d.ts +19 -9
  153. package/dist/runtime/app/composables/cms-media-library.js +31 -13
  154. package/dist/runtime/app/composables/cms-media-uploader.js +6 -20
  155. package/dist/runtime/app/composables/cms-permissions.d.ts +18 -0
  156. package/dist/runtime/app/composables/cms-permissions.js +54 -0
  157. package/dist/runtime/app/composables/cms-presets.d.ts +11 -0
  158. package/dist/runtime/app/composables/cms-presets.js +40 -0
  159. package/dist/runtime/app/composables/cms-preview-mode.d.ts +4 -0
  160. package/dist/runtime/app/composables/cms-preview-mode.js +8 -0
  161. package/dist/runtime/app/composables/cms-query.d.ts +3 -0
  162. package/dist/runtime/app/composables/cms-query.js +45 -20
  163. package/dist/runtime/app/composables/cms-record-list.d.ts +31 -0
  164. package/dist/runtime/app/composables/cms-record-list.js +75 -0
  165. package/dist/runtime/app/composables/cms-route.d.ts +6 -0
  166. package/dist/runtime/app/composables/cms-route.js +36 -0
  167. package/dist/runtime/app/composables/cms-runtime.d.ts +1 -0
  168. package/dist/runtime/app/composables/cms-save-handoff.d.ts +4 -0
  169. package/dist/runtime/app/composables/cms-save-handoff.js +19 -0
  170. package/dist/runtime/app/composables/cms-seo.d.ts +12 -0
  171. package/dist/runtime/app/composables/cms-seo.js +69 -0
  172. package/dist/runtime/app/composables/cms-settings-form.d.ts +13 -0
  173. package/dist/runtime/app/composables/cms-settings-form.js +29 -0
  174. package/dist/runtime/app/composables/cms-settings-sections.d.ts +26 -0
  175. package/dist/runtime/app/composables/cms-settings-sections.js +181 -0
  176. package/dist/runtime/app/composables/cms-settings-stores.d.ts +52 -0
  177. package/dist/runtime/app/composables/cms-settings-stores.js +156 -0
  178. package/dist/runtime/app/composables/cms-settings.d.ts +3 -3
  179. package/dist/runtime/app/composables/cms-settings.js +14 -8
  180. package/dist/runtime/app/composables/cms-site-disabled.d.ts +7 -0
  181. package/dist/runtime/app/composables/cms-site-disabled.js +8 -0
  182. package/dist/runtime/app/composables/cms-site.d.ts +7 -0
  183. package/dist/runtime/app/composables/cms-site.js +22 -0
  184. package/dist/runtime/app/composables/cms-toast.d.ts +2 -8
  185. package/dist/runtime/app/composables/cms-toast.js +3 -9
  186. package/dist/runtime/app/composables/cms-translation-actions.d.ts +15 -0
  187. package/dist/runtime/app/composables/cms-translation-actions.js +108 -0
  188. package/dist/runtime/app/css-url.d.ts +4 -0
  189. package/dist/runtime/app/image/CmsImage.d.vue.ts +89 -0
  190. package/dist/runtime/app/image/CmsImage.vue +91 -0
  191. package/dist/runtime/app/image/CmsImage.vue.d.ts +89 -0
  192. package/dist/runtime/app/layouts/cms-admin.d.vue.ts +2 -2
  193. package/dist/runtime/app/layouts/cms-admin.vue +98 -76
  194. package/dist/runtime/app/layouts/cms-admin.vue.d.ts +2 -2
  195. package/dist/runtime/app/layouts/cms-editor.vue +7 -2
  196. package/dist/runtime/app/links/CmsLink.d.vue.ts +18 -0
  197. package/dist/runtime/app/links/CmsLink.vue +18 -0
  198. package/dist/runtime/app/links/CmsLink.vue.d.ts +18 -0
  199. package/dist/runtime/app/middleware/cms-auth.js +13 -1
  200. package/dist/runtime/app/pages/admin-collection.vue +473 -297
  201. package/dist/runtime/app/pages/admin-content.vue +190 -53
  202. package/dist/runtime/app/pages/admin-entry.vue +79 -75
  203. package/dist/runtime/app/pages/admin-index.vue +18 -5
  204. package/dist/runtime/app/pages/admin-login.vue +1 -1
  205. package/dist/runtime/app/pages/admin-logout.vue +1 -1
  206. package/dist/runtime/app/pages/admin-media.vue +3 -3
  207. package/dist/runtime/app/pages/admin-preview.vue +112 -24
  208. package/dist/runtime/app/plugins/cms-field.d.ts +2 -0
  209. package/dist/runtime/app/plugins/cms-field.js +8 -0
  210. package/dist/runtime/app/plugins/cms-preview-banner.client.d.ts +2 -0
  211. package/dist/runtime/app/plugins/cms-preview-banner.client.js +19 -0
  212. package/dist/runtime/app/plugins/cms-preview.d.ts +2 -0
  213. package/dist/runtime/app/plugins/cms-preview.js +27 -0
  214. package/dist/runtime/app/preview/CmsPreviewBanner.d.vue.ts +7 -0
  215. package/dist/runtime/app/preview/CmsPreviewBanner.vue +63 -0
  216. package/dist/runtime/app/preview/CmsPreviewBanner.vue.d.ts +7 -0
  217. package/dist/runtime/app/preview/PreviewOverlay.d.vue.ts +17 -3
  218. package/dist/runtime/app/preview/PreviewOverlay.vue +629 -166
  219. package/dist/runtime/app/preview/PreviewOverlay.vue.d.ts +17 -3
  220. package/dist/runtime/app/preview/field-marker.d.ts +12 -0
  221. package/dist/runtime/app/preview/field-marker.js +20 -0
  222. package/dist/runtime/app/preview/inline-text.d.ts +15 -0
  223. package/dist/runtime/app/preview/inline-text.js +158 -0
  224. package/dist/runtime/app/utils/block-editor.d.ts +22 -0
  225. package/dist/runtime/app/utils/block-editor.js +12 -0
  226. package/dist/runtime/app/utils/keyboard.d.ts +6 -0
  227. package/dist/runtime/app/utils/keyboard.js +52 -0
  228. package/dist/runtime/app/utils/redirects.d.ts +6 -0
  229. package/dist/runtime/app/utils/redirects.js +7 -0
  230. package/dist/runtime/app/utils/ui.d.ts +8 -0
  231. package/dist/runtime/app/utils/ui.js +15 -1
  232. package/dist/runtime/app/utils/users.d.ts +15 -0
  233. package/dist/runtime/app/utils/users.js +14 -0
  234. package/dist/runtime/assets/cms.css +1 -1
  235. package/dist/runtime/generate.js +16 -2
  236. package/dist/runtime/server/api/collection-export.get.js +3 -2
  237. package/dist/runtime/server/api/collection-import.post.js +39 -11
  238. package/dist/runtime/server/api/collection.get.js +27 -9
  239. package/dist/runtime/server/api/collection.post.js +28 -11
  240. package/dist/runtime/server/api/collection.put.js +35 -13
  241. package/dist/runtime/server/api/item.delete.js +19 -7
  242. package/dist/runtime/server/api/item.get.js +3 -2
  243. package/dist/runtime/server/api/item.put.js +52 -18
  244. package/dist/runtime/server/api/link-usage.post.d.ts +5 -0
  245. package/dist/runtime/server/api/link-usage.post.js +21 -0
  246. package/dist/runtime/server/api/media-delete.post.js +4 -2
  247. package/dist/runtime/server/api/media-download.get.js +2 -2
  248. package/dist/runtime/server/api/media-folder.delete.js +8 -2
  249. package/dist/runtime/server/api/media-folder.patch.js +10 -2
  250. package/dist/runtime/server/api/media-folder.post.js +4 -2
  251. package/dist/runtime/server/api/media-move.post.js +7 -4
  252. package/dist/runtime/server/api/media-presign.post.js +2 -2
  253. package/dist/runtime/server/api/media-upload.put.js +2 -2
  254. package/dist/runtime/server/api/media-usage.post.d.ts +2 -4
  255. package/dist/runtime/server/api/media-usage.post.js +13 -9
  256. package/dist/runtime/server/api/media.delete.js +4 -2
  257. package/dist/runtime/server/api/media.get.js +22 -7
  258. package/dist/runtime/server/api/media.post.js +4 -2
  259. package/dist/runtime/server/api/media.put.js +33 -16
  260. package/dist/runtime/server/api/preset.delete.d.ts +4 -0
  261. package/dist/runtime/server/api/preset.delete.js +9 -0
  262. package/dist/runtime/server/api/preset.put.d.ts +3 -0
  263. package/dist/runtime/server/api/preset.put.js +23 -0
  264. package/dist/runtime/server/api/presets-field.get.d.ts +3 -0
  265. package/dist/runtime/server/api/presets-field.get.js +13 -0
  266. package/dist/runtime/server/api/presets-field.post.d.ts +3 -0
  267. package/dist/runtime/server/api/presets-field.post.js +36 -0
  268. package/dist/runtime/server/api/presets.get.d.ts +3 -0
  269. package/dist/runtime/server/api/presets.get.js +7 -0
  270. package/dist/runtime/server/api/preview-link.delete.d.ts +4 -0
  271. package/dist/runtime/server/api/preview-link.delete.js +15 -0
  272. package/dist/runtime/server/api/preview-links.get.d.ts +3 -0
  273. package/dist/runtime/server/api/preview-links.get.js +10 -0
  274. package/dist/runtime/server/api/preview-links.post.d.ts +3 -0
  275. package/dist/runtime/server/api/preview-links.post.js +17 -0
  276. package/dist/runtime/server/api/preview.get.d.ts +3 -0
  277. package/dist/runtime/server/api/preview.get.js +7 -0
  278. package/dist/runtime/server/api/redirect.delete.d.ts +4 -0
  279. package/dist/runtime/server/api/redirect.delete.js +10 -0
  280. package/dist/runtime/server/api/redirect.put.d.ts +3 -0
  281. package/dist/runtime/server/api/redirect.put.js +8 -0
  282. package/dist/runtime/server/api/redirects-import.post.d.ts +3 -0
  283. package/dist/runtime/server/api/redirects-import.post.js +13 -0
  284. package/dist/runtime/server/api/redirects.get.d.ts +3 -0
  285. package/dist/runtime/server/api/redirects.get.js +7 -0
  286. package/dist/runtime/server/api/redirects.post.d.ts +3 -0
  287. package/dist/runtime/server/api/redirects.post.js +7 -0
  288. package/dist/runtime/server/api/search.get.js +8 -3
  289. package/dist/runtime/server/api/settings.delete.js +2 -2
  290. package/dist/runtime/server/api/settings.get.js +4 -2
  291. package/dist/runtime/server/api/settings.put.js +2 -2
  292. package/dist/runtime/server/api/site.get.d.ts +3 -0
  293. package/dist/runtime/server/api/site.get.js +7 -0
  294. package/dist/runtime/server/api/site.put.d.ts +3 -0
  295. package/dist/runtime/server/api/site.put.js +16 -0
  296. package/dist/runtime/server/api/tags.get.js +3 -2
  297. package/dist/runtime/server/api/user.delete.js +8 -2
  298. package/dist/runtime/server/api/user.put.js +14 -4
  299. package/dist/runtime/server/api/users.get.js +2 -2
  300. package/dist/runtime/server/api/users.post.js +8 -4
  301. package/dist/runtime/server/middleware/hidden-site.d.ts +2 -0
  302. package/dist/runtime/server/middleware/hidden-site.js +8 -0
  303. package/dist/runtime/server/middleware/preview-links.d.ts +2 -0
  304. package/dist/runtime/server/middleware/preview-links.js +6 -0
  305. package/dist/runtime/server/middleware/redirects.d.ts +2 -0
  306. package/dist/runtime/server/middleware/redirects.js +18 -0
  307. package/dist/runtime/server/plugins/session-validate.js +4 -1
  308. package/dist/runtime/server/plugins/webhooks.d.ts +3 -0
  309. package/dist/runtime/server/plugins/webhooks.js +27 -0
  310. package/dist/runtime/server/routes/graphql.js +5 -3
  311. package/dist/runtime/server/routes/robots.d.ts +2 -0
  312. package/dist/runtime/server/routes/robots.js +16 -0
  313. package/dist/runtime/server/routes/sitemap.d.ts +2 -0
  314. package/dist/runtime/server/routes/sitemap.js +67 -0
  315. package/dist/runtime/server/utils/cached-value.d.ts +5 -0
  316. package/dist/runtime/server/utils/cached-value.js +24 -0
  317. package/dist/runtime/server/utils/content-routes.d.ts +5 -0
  318. package/dist/runtime/server/utils/content-routes.js +29 -0
  319. package/dist/runtime/server/utils/field-rules.d.ts +19 -0
  320. package/dist/runtime/server/utils/field-rules.js +72 -0
  321. package/dist/runtime/server/utils/graphql.d.ts +3 -1
  322. package/dist/runtime/server/utils/graphql.js +143 -28
  323. package/dist/runtime/server/utils/hooks.d.ts +14 -0
  324. package/dist/runtime/server/utils/hooks.js +71 -0
  325. package/dist/runtime/server/utils/import.d.ts +7 -2
  326. package/dist/runtime/server/utils/import.js +108 -14
  327. package/dist/runtime/server/utils/link-usage.d.ts +4 -0
  328. package/dist/runtime/server/utils/link-usage.js +28 -0
  329. package/dist/runtime/server/utils/media-references.d.ts +6 -1
  330. package/dist/runtime/server/utils/media-references.js +49 -20
  331. package/dist/runtime/server/utils/media-usage.d.ts +2 -0
  332. package/dist/runtime/server/utils/media-usage.js +5 -3
  333. package/dist/runtime/server/utils/page-rows.d.ts +2 -0
  334. package/dist/runtime/server/utils/page-rows.js +42 -29
  335. package/dist/runtime/server/utils/permissions.d.ts +14 -0
  336. package/dist/runtime/server/utils/permissions.js +43 -0
  337. package/dist/runtime/server/utils/presets.d.ts +18 -0
  338. package/dist/runtime/server/utils/presets.js +68 -0
  339. package/dist/runtime/server/utils/preview-links.d.ts +207 -0
  340. package/dist/runtime/server/utils/preview-links.js +161 -0
  341. package/dist/runtime/server/utils/redirects.d.ts +31 -0
  342. package/dist/runtime/server/utils/redirects.js +240 -0
  343. package/dist/runtime/server/utils/registry.js +4 -1
  344. package/dist/runtime/server/utils/relations.d.ts +8 -0
  345. package/dist/runtime/server/utils/relations.js +25 -6
  346. package/dist/runtime/server/utils/require-admin.d.ts +0 -1
  347. package/dist/runtime/server/utils/require-admin.js +4 -8
  348. package/dist/runtime/server/utils/search.d.ts +1 -0
  349. package/dist/runtime/server/utils/search.js +44 -19
  350. package/dist/runtime/server/utils/site.d.ts +23 -0
  351. package/dist/runtime/server/utils/site.js +79 -0
  352. package/dist/runtime/server/utils/sitemap.d.ts +12 -0
  353. package/dist/runtime/server/utils/sitemap.js +109 -0
  354. package/dist/runtime/server/utils/user-management.d.ts +4 -0
  355. package/dist/runtime/server/utils/user-management.js +20 -0
  356. package/dist/runtime/server/utils/users.d.ts +1 -1
  357. package/dist/runtime/server/utils/users.js +1 -1
  358. package/dist/runtime/server/utils/visibility.d.ts +1 -0
  359. package/dist/runtime/server/utils/visibility.js +6 -1
  360. package/dist/runtime/server/utils/webhooks.d.ts +24 -0
  361. package/dist/runtime/server/utils/webhooks.js +90 -0
  362. package/dist/runtime/shared/block-paths.d.ts +66 -0
  363. package/dist/runtime/shared/block-paths.js +310 -0
  364. package/dist/runtime/shared/block-picker.d.ts +18 -0
  365. package/dist/runtime/shared/block-picker.js +31 -0
  366. package/dist/runtime/shared/block-signature.d.ts +4 -0
  367. package/dist/runtime/shared/block-signature.js +46 -0
  368. package/dist/runtime/shared/clipboard.d.ts +31 -0
  369. package/dist/runtime/shared/clipboard.js +206 -0
  370. package/dist/runtime/shared/conditions.d.ts +36 -0
  371. package/dist/runtime/shared/conditions.js +100 -0
  372. package/dist/runtime/shared/content-routes.d.ts +42 -0
  373. package/dist/runtime/shared/content-routes.js +237 -0
  374. package/dist/runtime/shared/duplicate.js +2 -1
  375. package/dist/runtime/shared/editor-layout.d.ts +18 -0
  376. package/dist/runtime/shared/editor-layout.js +9 -0
  377. package/dist/runtime/shared/field-options.d.ts +21 -0
  378. package/dist/runtime/shared/field-options.js +90 -0
  379. package/dist/runtime/shared/graphql-sdl.d.ts +1 -0
  380. package/dist/runtime/shared/graphql-sdl.js +50 -26
  381. package/dist/runtime/shared/history.d.ts +27 -0
  382. package/dist/runtime/shared/history.js +123 -0
  383. package/dist/runtime/shared/hooks.d.ts +45 -0
  384. package/dist/runtime/shared/hooks.js +28 -0
  385. package/dist/runtime/shared/index.d.ts +90 -21
  386. package/dist/runtime/shared/index.js +121 -37
  387. package/dist/runtime/shared/inline-text.d.ts +38 -0
  388. package/dist/runtime/shared/inline-text.js +85 -0
  389. package/dist/runtime/shared/layout.d.ts +2 -1
  390. package/dist/runtime/shared/layout.js +3 -0
  391. package/dist/runtime/shared/links.d.ts +48 -0
  392. package/dist/runtime/shared/links.js +155 -0
  393. package/dist/runtime/shared/list-filters.d.ts +2 -0
  394. package/dist/runtime/shared/list-filters.js +2 -0
  395. package/dist/runtime/shared/media-focal.d.ts +30 -0
  396. package/dist/runtime/shared/media-focal.js +52 -0
  397. package/dist/runtime/shared/migrations-dir.d.ts +14 -0
  398. package/dist/runtime/shared/migrations-dir.js +48 -2
  399. package/dist/runtime/shared/presets.d.ts +67 -0
  400. package/dist/runtime/shared/presets.js +127 -0
  401. package/dist/runtime/shared/preview-links.d.ts +64 -0
  402. package/dist/runtime/shared/preview-links.js +54 -0
  403. package/dist/runtime/shared/preview.d.ts +124 -21
  404. package/dist/runtime/shared/preview.js +419 -81
  405. package/dist/runtime/shared/redirects.d.ts +75 -0
  406. package/dist/runtime/shared/redirects.js +230 -0
  407. package/dist/runtime/shared/roles.d.ts +57 -0
  408. package/dist/runtime/shared/roles.js +293 -0
  409. package/dist/runtime/shared/save-state.d.ts +8 -0
  410. package/dist/runtime/shared/save-state.js +15 -0
  411. package/dist/runtime/shared/search.d.ts +16 -1
  412. package/dist/runtime/shared/search.js +41 -7
  413. package/dist/runtime/shared/seo.d.ts +167 -0
  414. package/dist/runtime/shared/seo.js +335 -0
  415. package/dist/runtime/shared/transfer.js +2 -1
  416. package/dist/runtime/shared/translations.d.ts +29 -0
  417. package/dist/runtime/shared/translations.js +123 -0
  418. package/dist/runtime/shared/users.d.ts +1 -1
  419. package/dist/runtime/shared/validation.d.ts +23 -1
  420. package/dist/runtime/shared/validation.js +214 -64
  421. package/dist/runtime/webhooks.d.ts +33 -0
  422. package/dist/runtime/webhooks.js +57 -0
  423. package/dist/types.d.mts +5 -1
  424. package/package.json +6 -2
  425. package/dist/runtime/app/components/cms/BlockOutline.vue +0 -88
  426. package/dist/runtime/app/components/cms/BlockPalette.vue +0 -31
  427. package/dist/runtime/app/components/cms/SettingsModal.d.vue.ts +0 -12
  428. package/dist/runtime/app/components/cms/SettingsModal.vue +0 -126
  429. package/dist/runtime/app/components/cms/SettingsModal.vue.d.ts +0 -12
  430. package/dist/runtime/app/composables/cms-field-focus.d.ts +0 -4
  431. package/dist/runtime/app/composables/cms-field-focus.js +0 -20
@@ -22,28 +22,46 @@ import { useRuntimeConfig } from "#imports";
22
22
  import {
23
23
  CONTENT_SLUG_FIELD,
24
24
  CONTENT_PUBLISHED_AT_FIELD,
25
+ contentItemPath,
26
+ contentItemPaths,
27
+ contentRouteLocales,
25
28
  decodeTranslatableMedia,
26
29
  entryFieldsFor,
30
+ blockFieldsDeep,
31
+ blocksFieldPaths,
27
32
  hasTranslatableBlockFields,
33
+ isExternalLinkUrl,
28
34
  isMultiValueField,
29
35
  isPrivateField,
36
+ isRoutedContent,
37
+ isStringListField,
30
38
  isTranslatableMediaField,
39
+ linkHref,
40
+ linkLabelText,
41
+ localeText,
31
42
  localizeBlocks,
43
+ mapBlocks,
32
44
  mediaPublicUrl,
33
45
  mediaTypeFor,
34
46
  pickTranslatedMedia,
47
+ previewGrantAllows,
35
48
  publicBlocks,
49
+ readLinkValue,
50
+ relationTarget,
36
51
  translatableFieldKeys
37
52
  } from "../../shared/index.js";
38
53
  import {
54
+ SITE_QUERY_FIELD,
39
55
  blockTypeName,
40
56
  blocksFieldTypeName,
41
57
  renderGraphqlSdl,
42
58
  typeName
43
59
  } from "../../shared/graphql-sdl.js";
44
60
  import { normalizeTimestampFields } from "../../shared/timestamps.js";
61
+ import { findContentByPath } from "./content-routes.js";
45
62
  import { useMediaIndex } from "./media-index.js";
46
63
  import { readPages } from "./page-storage.js";
64
+ import { readSiteSettings } from "./site.js";
47
65
  import {
48
66
  isListFilterOperator,
49
67
  joinListCondition,
@@ -52,7 +70,7 @@ import {
52
70
  } from "./list-filter.js";
53
71
  import { getContentI18n, resolveTable, tableColumns } from "./registry.js";
54
72
  import { singleRowFirst } from "./single-row.js";
55
- import { visibilityConditions } from "./visibility.js";
73
+ import { visibilityConditions, visibleOrPreviewConditions } from "./visibility.js";
56
74
  const MAX_LIMIT = 100;
57
75
  const DEFAULT_LIMIT = 50;
58
76
  const LOCALE = Symbol("locale");
@@ -99,6 +117,12 @@ function tableFor(name) {
99
117
  function publishedFilter(entry, table) {
100
118
  return visibilityConditions(entry, tableColumns(table));
101
119
  }
120
+ function previewItemId(ctx, name) {
121
+ return previewGrantAllows(ctx.preview, name) ? ctx.preview.itemId : void 0;
122
+ }
123
+ function singleItemFilter(entry, table, ctx, name) {
124
+ return visibleOrPreviewConditions(entry, tableColumns(table), previewItemId(ctx, name));
125
+ }
102
126
  function localeOf(parent) {
103
127
  return parent[LOCALE] ?? getContentI18n().defaultLocale;
104
128
  }
@@ -112,6 +136,23 @@ function resolveLocaleArg(locale) {
112
136
  }
113
137
  return String(locale);
114
138
  }
139
+ function linkSource(value, locale) {
140
+ const link = readLinkValue(value);
141
+ if (!link || !link.url && !(link.entry && link.id)) return null;
142
+ return { link, locale };
143
+ }
144
+ function blockLinks(field, value, locale) {
145
+ return mapBlocks(field, value, (item, block) => {
146
+ const keys = Object.entries(block?.fields ?? {}).filter(([, blockField]) => blockField.type === "link").map(([key]) => key);
147
+ if (!keys.length) return item;
148
+ const resolved = { ...item };
149
+ for (const key of keys) resolved[key] = linkSource(resolved[key], locale);
150
+ return resolved;
151
+ });
152
+ }
153
+ function hasBlockLinks(field) {
154
+ return blockFieldsDeep(field).some((blockField) => blockField.type === "link");
155
+ }
115
156
  function localizeRow(entry, row, locale) {
116
157
  const { defaultLocale } = getContentI18n();
117
158
  const result = normalizeTimestampFields({ ...row, [LOCALE]: locale });
@@ -126,9 +167,15 @@ function localizeRow(entry, row, locale) {
126
167
  result[key] = value?.[locale] ?? value?.[defaultLocale] ?? null;
127
168
  }
128
169
  for (const [key, field] of Object.entries(fields)) {
129
- if (isPrivateField(field) || field.type !== "blocks") continue;
170
+ if (isPrivateField(field)) continue;
171
+ if (field.type === "link") {
172
+ result[key] = linkSource(result[key], locale);
173
+ continue;
174
+ }
175
+ if (field.type !== "blocks") continue;
130
176
  const visible = publicBlocks(field, result[key]);
131
- result[key] = hasTranslatableBlockFields(field) ? localizeBlocks(field, visible, locale, defaultLocale) : visible;
177
+ const localized = hasTranslatableBlockFields(field) ? localizeBlocks(field, visible, locale, defaultLocale) : visible;
178
+ result[key] = hasBlockLinks(field) ? blockLinks(field, localized, locale) : localized;
132
179
  }
133
180
  return result;
134
181
  }
@@ -235,8 +282,8 @@ async function resolveMedia(ctx, key) {
235
282
  if (isLocalMedia()) {
236
283
  const file = (await useMediaIndex()).get(key);
237
284
  if (!file) return null;
238
- const alt = await loadMediaAlt(ctx).load(key);
239
- return mediaObject(key, { ...file, alt: alt ?? null });
285
+ const meta = await loadMediaMeta(ctx).load(key);
286
+ return mediaObject(key, { ...file, ...meta });
240
287
  }
241
288
  const row = await loadMedia(ctx).load(key);
242
289
  return row ? mediaObject(key, row) : null;
@@ -262,9 +309,9 @@ function loadManyToMany(ctx, name, key, field) {
262
309
  `m2m:${name}:${key}`,
263
310
  async (sourceIds) => {
264
311
  const config = cmsConfig;
265
- const targetEntry = config[field.to];
312
+ const targetEntry = config[relationTarget(field)];
266
313
  const join = cmsTables[`${name}_${key}`];
267
- const target = tableFor(field.to);
314
+ const target = tableFor(relationTarget(field));
268
315
  const joinCols = tableColumns(join);
269
316
  const rows = await useDb().select({ sourceId: joinCols.sourceId, target }).from(join).innerJoin(target, eq(joinCols.targetId, tableColumns(target).id)).where(
270
317
  and(inArray(joinCols.sourceId, sourceIds), ...publishedFilter(targetEntry, target))
@@ -294,14 +341,47 @@ function loadMedia(ctx) {
294
341
  );
295
342
  });
296
343
  }
297
- function loadMediaAlt(ctx) {
298
- return getLoader(ctx, "mediaAlt", async (keys) => {
344
+ function loadMediaMeta(ctx) {
345
+ return getLoader(ctx, "mediaMeta", async (keys) => {
299
346
  const media = cmsTables.cms_media;
300
347
  const columns = tableColumns(media);
301
- const rows = await useDb().select({ key: columns.key, alt: columns.alt }).from(media).where(inArray(columns.key, keys));
302
- return new Map(rows.map((row) => [row.key, row.alt ?? null]));
348
+ const rows = await useDb().select({
349
+ key: columns.key,
350
+ alt: columns.alt,
351
+ focalX: columns.focalX,
352
+ focalY: columns.focalY
353
+ }).from(media).where(inArray(columns.key, keys));
354
+ return new Map(
355
+ rows.map((row) => [
356
+ row.key,
357
+ {
358
+ alt: row.alt ?? null,
359
+ focalX: row.focalX ?? null,
360
+ focalY: row.focalY ?? null
361
+ }
362
+ ])
363
+ );
303
364
  });
304
365
  }
366
+ async function resolveLinkHref(ctx, source) {
367
+ const config = cmsConfig;
368
+ const { link, locale } = source;
369
+ const entry = link.entry ? config[link.entry] : void 0;
370
+ if (link.url || !link.entry || !link.id || !isRoutedContent(entry))
371
+ return linkHref(config, link, locale);
372
+ const target = await loadById(ctx, link.entry).load(link.id);
373
+ return target ? linkHref(config, link, locale, target) : null;
374
+ }
375
+ function linkResolvers() {
376
+ return {
377
+ href: (source, _args, ctx) => resolveLinkHref(ctx, source),
378
+ label: (source) => linkLabelText(source.link.label, source.locale, getContentI18n().defaultLocale),
379
+ newTab: (source) => !!source.link.newTab,
380
+ external: (source) => isExternalLinkUrl(source.link.url),
381
+ entry: (source) => source.link.url ? null : source.link.entry ?? null,
382
+ id: (source) => source.link.url ? null : source.link.id ?? null
383
+ };
384
+ }
305
385
  function mediaObject(key, row) {
306
386
  return {
307
387
  key,
@@ -312,6 +392,8 @@ function mediaObject(key, row) {
312
392
  size: row?.size ?? null,
313
393
  width: row?.width ?? null,
314
394
  height: row?.height ?? null,
395
+ focalX: row?.focalX ?? null,
396
+ focalY: row?.focalY ?? null,
315
397
  type: mediaTypeFor(row?.mime, key)
316
398
  };
317
399
  }
@@ -322,25 +404,33 @@ function entryResolvers(config, name, entry) {
322
404
  if (field.type === "relation" && field.cardinality === "many-to-many") {
323
405
  resolvers[key] = async (parent, _args, ctx) => {
324
406
  const rows = await loadManyToMany(ctx, name, key, field).load(parent.id);
325
- return (rows ?? []).map((row) => localizeRow(config[field.to], row, localeOf(parent)));
407
+ return (rows ?? []).map(
408
+ (row) => localizeRow(config[relationTarget(field)], row, localeOf(parent))
409
+ );
326
410
  };
327
411
  } else if (field.type === "relation") {
328
412
  resolvers[key] = async (parent, _args, ctx) => {
329
413
  const id = parent[key];
330
414
  if (id == null) return null;
331
- const row = await loadById(ctx, field.to).load(id);
332
- return row ? localizeRow(config[field.to], row, localeOf(parent)) : null;
415
+ const row = await loadById(ctx, relationTarget(field)).load(id);
416
+ return row ? localizeRow(config[relationTarget(field)], row, localeOf(parent)) : null;
333
417
  };
334
418
  } else if (field.type === "media") {
335
419
  resolvers[key] = (parent, _args, ctx) => resolveMedia(ctx, parent[key]);
420
+ } else if (isStringListField(field)) {
421
+ resolvers[key] = (parent) => Array.isArray(parent[key]) ? parent[key] : [];
336
422
  }
337
423
  }
424
+ if (isRoutedContent(entry)) {
425
+ resolvers.path = (parent) => contentItemPath(entry, parent, localeOf(parent));
426
+ resolvers.paths = (parent) => contentItemPaths(entry, parent, contentRouteLocales(getContentI18n()));
427
+ }
338
428
  return resolvers;
339
429
  }
340
- function blockResolvers(name, key, field) {
430
+ function blockResolvers(name, schemaPath, field) {
341
431
  const resolvers = {};
342
- resolvers[blocksFieldTypeName(name, key)] = {
343
- __resolveType: (value) => blockTypeName(name, key, String(value.type))
432
+ resolvers[blocksFieldTypeName(name, schemaPath)] = {
433
+ __resolveType: (value) => blockTypeName(name, schemaPath, String(value.type))
344
434
  };
345
435
  for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
346
436
  const mediaFields = Object.entries(block.fields).filter(([, f]) => f.type === "media");
@@ -349,7 +439,7 @@ function blockResolvers(name, key, field) {
349
439
  for (const [blockFieldKey] of mediaFields) {
350
440
  fieldLevel[blockFieldKey] = (parent, _args, ctx) => resolveMedia(ctx, parent[blockFieldKey]);
351
441
  }
352
- resolvers[blockTypeName(name, key, blockName)] = fieldLevel;
442
+ resolvers[blockTypeName(name, schemaPath, blockName)] = fieldLevel;
353
443
  }
354
444
  return resolvers;
355
445
  }
@@ -358,14 +448,24 @@ export function buildCmsSchema() {
358
448
  const entries = Object.entries(config);
359
449
  const queryResolvers = {};
360
450
  const typeResolvers = {};
451
+ queryResolvers[SITE_QUERY_FIELD] = async (_, args, ctx) => {
452
+ const locale = resolveLocaleArg(args.locale);
453
+ const { defaultLocale } = getContentI18n();
454
+ const settings = await readSiteSettings();
455
+ return {
456
+ name: localeText(settings.name, locale, defaultLocale),
457
+ titleTemplate: settings.titleTemplate,
458
+ description: localeText(settings.description, locale, defaultLocale),
459
+ image: await resolveMedia(ctx, settings.image),
460
+ hidden: settings.hidden
461
+ };
462
+ };
361
463
  for (const [name, entry] of entries) {
362
464
  const gqlType = typeName(name);
363
465
  const fieldLevel = entryResolvers(config, name, entry);
364
466
  if (Object.keys(fieldLevel).length) typeResolvers[gqlType] = fieldLevel;
365
- for (const [key, field] of Object.entries(entryFieldsFor(entry))) {
366
- if (field.type === "blocks" && !isPrivateField(field))
367
- Object.assign(typeResolvers, blockResolvers(name, key, field));
368
- }
467
+ for (const { field, schemaPath } of blocksFieldPaths(entryFieldsFor(entry)))
468
+ Object.assign(typeResolvers, blockResolvers(name, schemaPath, field));
369
469
  if (entry.kind === "page") {
370
470
  queryResolvers[name] = async (_, args) => {
371
471
  const locale = resolveLocaleArg(args.locale);
@@ -401,18 +501,32 @@ export function buildCmsSchema() {
401
501
  const rows = await useDb().select().from(table).where(where.length ? and(...where) : void 0).orderBy(...sortOrder(entry, table, args.sort)).limit(limit).offset(offset);
402
502
  return rows.map((row) => localizeRow(entry, row, locale));
403
503
  };
404
- queryResolvers[`${name}ById`] = async (_, args) => {
504
+ queryResolvers[`${name}ById`] = async (_, args, ctx) => {
405
505
  const locale = resolveLocaleArg(args.locale);
406
506
  const table = tableFor(name);
407
- const [row] = await useDb().select().from(table).where(and(eq(tableColumns(table).id, args.id), ...publishedFilter(entry, table))).limit(1);
507
+ const [row] = await useDb().select().from(table).where(
508
+ and(
509
+ eq(tableColumns(table).id, args.id),
510
+ ...singleItemFilter(entry, table, ctx, name)
511
+ )
512
+ ).limit(1);
408
513
  return row ? localizeRow(entry, row, locale) : null;
409
514
  };
410
515
  if (entry.kind === "content") {
411
- queryResolvers[`${name}BySlug`] = async (_, args) => {
516
+ queryResolvers[`${name}BySlug`] = async (_, args, ctx) => {
412
517
  const locale = resolveLocaleArg(args.locale);
413
518
  const table = tableFor(name);
414
519
  const slug = tableColumns(table)[CONTENT_SLUG_FIELD];
415
- const [row] = await useDb().select().from(table).where(and(eq(slug, args.slug), ...publishedFilter(entry, table))).limit(1);
520
+ const [row] = await useDb().select().from(table).where(and(eq(slug, args.slug), ...singleItemFilter(entry, table, ctx, name))).limit(1);
521
+ return row ? localizeRow(entry, row, locale) : null;
522
+ };
523
+ }
524
+ if (isRoutedContent(entry)) {
525
+ queryResolvers[`${name}ByPath`] = async (_, args, ctx) => {
526
+ const locale = resolveLocaleArg(args.locale);
527
+ const row = await findContentByPath(name, args.path, locale, {
528
+ previewId: previewItemId(ctx, name)
529
+ });
416
530
  return row ? localizeRow(entry, row, locale) : null;
417
531
  };
418
532
  }
@@ -436,11 +550,12 @@ export function buildCmsSchema() {
436
550
  typeDefs: renderGraphqlSdl(config),
437
551
  resolvers: {
438
552
  JSON: jsonScalar,
553
+ CmsLink: linkResolvers(),
439
554
  Query: queryResolvers,
440
555
  ...typeResolvers
441
556
  }
442
557
  });
443
558
  }
444
- export function createGraphqlContext() {
445
- return { loaders: /* @__PURE__ */ new Map() };
559
+ export function createGraphqlContext(preview = null) {
560
+ return { loaders: /* @__PURE__ */ new Map(), preview };
446
561
  }
@@ -0,0 +1,14 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { CmsEntryChange, CmsHookName, CmsHookUser, CmsMediaChange, CmsRuntimeHooks } from '../../shared/hooks.js';
3
+ import type { CmsSessionUser } from '../../shared/users.js';
4
+ type HookCallback = (...args: unknown[]) => unknown;
5
+ export interface CmsHookHost {
6
+ callHookWith: (caller: (hooks: HookCallback[], args: unknown[]) => Promise<void>, name: string, ...args: unknown[]) => Promise<void>;
7
+ }
8
+ export type CmsHookLogger = (message: string, error: unknown) => void;
9
+ export declare function hookUser(user: CmsSessionUser): CmsHookUser;
10
+ export declare function runHooksIsolated(name: string, hooks: HookCallback[], args: unknown[], log?: CmsHookLogger): Promise<void>;
11
+ export declare function dispatchCmsHook<Name extends CmsHookName>(host: () => CmsHookHost, event: H3Event, name: Name, payload: Parameters<CmsRuntimeHooks[Name]>[0], log?: CmsHookLogger): Promise<void>;
12
+ export declare function emitEntryChange(event: H3Event, user: CmsSessionUser, change: Omit<CmsEntryChange, 'user' | 'at' | 'path' | 'status' | 'item'> & Partial<Pick<CmsEntryChange, 'path' | 'status' | 'item'>>): Promise<void>;
13
+ export declare function emitMediaChange(event: H3Event, user: CmsSessionUser, change: Pick<CmsMediaChange, 'action' | 'keys'> & Partial<Pick<CmsMediaChange, 'folder' | 'from'>>): Promise<void>;
14
+ export {};
@@ -0,0 +1,71 @@
1
+ import { useNitroApp } from "nitropack/runtime";
2
+ import { CMS_ENTRY_CHANGE_HOOK, CMS_MEDIA_CHANGE_HOOK } from "../../shared/hooks.js";
3
+ const logHookError = (message, error) => console.error(message, error);
4
+ export function hookUser(user) {
5
+ return {
6
+ id: user.id ?? null,
7
+ email: user.email,
8
+ name: user.name ?? null,
9
+ role: user.role ?? (user.id ? "admin" : "superadmin")
10
+ };
11
+ }
12
+ export function runHooksIsolated(name, hooks, args, log = logHookError) {
13
+ return Promise.all(
14
+ hooks.map(
15
+ (hook) => Promise.resolve().then(() => hook(...args)).catch((error) => {
16
+ log(`[nuxt-cms] a '${name}' hook failed:`, error);
17
+ })
18
+ )
19
+ ).then(() => void 0);
20
+ }
21
+ export function dispatchCmsHook(host, event, name, payload, log = logHookError) {
22
+ const context = { event };
23
+ const task = Promise.resolve().then(
24
+ () => host().callHookWith(
25
+ (hooks, args) => runHooksIsolated(name, hooks, args, log),
26
+ name,
27
+ payload,
28
+ context
29
+ )
30
+ ).catch((error) => {
31
+ log(`[nuxt-cms] the '${name}' hooks failed:`, error);
32
+ });
33
+ const waitUntil = event.waitUntil;
34
+ if (typeof waitUntil === "function") {
35
+ try {
36
+ waitUntil.call(event, task);
37
+ } catch (error) {
38
+ log(`[nuxt-cms] waitUntil failed for '${name}':`, error);
39
+ }
40
+ }
41
+ return task;
42
+ }
43
+ function cloneItem(item) {
44
+ try {
45
+ return structuredClone(item);
46
+ } catch {
47
+ return { ...item };
48
+ }
49
+ }
50
+ function hookHost() {
51
+ return useNitroApp().hooks;
52
+ }
53
+ export function emitEntryChange(event, user, change) {
54
+ return dispatchCmsHook(hookHost, event, CMS_ENTRY_CHANGE_HOOK, {
55
+ path: null,
56
+ status: null,
57
+ ...change,
58
+ item: change.item ? cloneItem(change.item) : null,
59
+ user: hookUser(user),
60
+ at: (/* @__PURE__ */ new Date()).toISOString()
61
+ });
62
+ }
63
+ export function emitMediaChange(event, user, change) {
64
+ return dispatchCmsHook(hookHost, event, CMS_MEDIA_CHANGE_HOOK, {
65
+ folder: null,
66
+ from: null,
67
+ ...change,
68
+ user: hookUser(user),
69
+ at: (/* @__PURE__ */ new Date()).toISOString()
70
+ });
71
+ }
@@ -1,8 +1,9 @@
1
1
  import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
2
2
  import type { CmsDb } from '#cms-db';
3
- import type { CmsEntry, CmsI18n } from '../../shared/index.js';
3
+ import type { CmsConfig, CmsEntry, CmsI18n } from '../../shared/index.js';
4
4
  import type { CmsImportAction, CmsImportIssue, CmsImportMode, CmsImportPreview, CmsImportSourceRow, CmsTransferFormat } from '../../shared/transfer.js';
5
5
  import type { MediaLookup } from './media-references.js';
6
+ import type { CmsRedirectPlan } from '../../shared/index.js';
6
7
  type Row = Record<string, unknown>;
7
8
  export interface PreparedImportRow {
8
9
  index: number;
@@ -18,8 +19,10 @@ export interface ImportContext {
18
19
  table: SQLiteTable;
19
20
  mode: CmsImportMode;
20
21
  media: MediaLookup | null;
22
+ canPublish?: boolean;
21
23
  }
22
- export declare function validateImportRows(entry: CmsEntry, rows: CmsImportSourceRow[], i18n: CmsI18n): PreparedImportRow[];
24
+ export declare function existingImportIds(db: CmsDb, table: SQLiteTable, rows: CmsImportSourceRow[]): Promise<Set<string>>;
25
+ export declare function validateImportRows(entry: CmsEntry, rows: CmsImportSourceRow[], i18n: CmsI18n, isCreate?: (id: string | null) => boolean, config?: CmsConfig): PreparedImportRow[];
23
26
  export declare function checkImportRows(rows: PreparedImportRow[], context: ImportContext): Promise<PreparedImportRow[]>;
24
27
  export declare function importPreview(entry: CmsEntry, rows: PreparedImportRow[], options: {
25
28
  format: CmsTransferFormat;
@@ -27,5 +30,7 @@ export declare function importPreview(entry: CmsEntry, rows: PreparedImportRow[]
27
30
  defaultLocale: string;
28
31
  errors?: string[];
29
32
  }): CmsImportPreview;
33
+ export declare function assignImportIds(entry: CmsEntry, rows: PreparedImportRow[]): PreparedImportRow[];
30
34
  export declare function importWriteStatements(db: CmsDb, name: string, entry: CmsEntry, table: SQLiteTable, rows: PreparedImportRow[]): PromiseLike<unknown>[];
35
+ export declare function planImportRedirects(db: CmsDb, entry: CmsEntry, table: SQLiteTable, rows: PreparedImportRow[]): Promise<CmsRedirectPlan | null>;
31
36
  export {};
@@ -1,7 +1,20 @@
1
- import { eq, inArray } from "drizzle-orm";
1
+ import { eq, inArray, sql } from "drizzle-orm";
2
+ import {
3
+ isRoutedContent,
4
+ mergeContentMoves,
5
+ relationTarget,
6
+ rowAfterUpdate
7
+ } from "../../shared/index.js";
2
8
  import { uniqueFieldKeys } from "../../shared/duplicate.js";
9
+ import {
10
+ changedImmutableKeys,
11
+ immutableFieldKeys,
12
+ isCaseInsensitiveUnique,
13
+ uniqueCompareValue,
14
+ withEntryDefaults
15
+ } from "../../shared/field-options.js";
3
16
  import { IMPORT_ID_PATTERN, importIssuePath, importRowTitle } from "../../shared/transfer.js";
4
- import { buildEntrySchema } from "../../shared/validation.js";
17
+ import { buildEntrySchema, validationMessages } from "../../shared/validation.js";
5
18
  import { customId } from "./custom-id.js";
6
19
  import { mediaReferences } from "./media-references.js";
7
20
  import {
@@ -11,11 +24,18 @@ import {
11
24
  withTimestamps,
12
25
  withUpdatedAt
13
26
  } from "./registry.js";
27
+ import { contentMovesOf, planContentRedirects } from "./redirects.js";
14
28
  import { manyToManyStatements, splitRelationValues } from "./relations.js";
15
29
  import { chunks, KEY_CHUNK } from "./write-statements.js";
16
30
  const META_KEYS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
17
- export function validateImportRows(entry, rows, i18n) {
18
- const schema = buildEntrySchema(entry, i18n);
31
+ export async function existingImportIds(db, table, rows) {
32
+ const ids = rows.flatMap(
33
+ (row) => typeof row.values.id === "string" && IMPORT_ID_PATTERN.test(row.values.id) ? [row.values.id] : []
34
+ );
35
+ return ids.length ? existingIds(db, table, ids) : /* @__PURE__ */ new Set();
36
+ }
37
+ export function validateImportRows(entry, rows, i18n, isCreate = () => true, config) {
38
+ const schema = buildEntrySchema({ ...entry, config }, i18n);
19
39
  return rows.map((row) => {
20
40
  const errors = [...row.errors];
21
41
  const rawId = row.values.id;
@@ -28,9 +48,10 @@ export function validateImportRows(entry, rows, i18n) {
28
48
  message: "Invalid id: use letters, numbers, _ and -, up to 128 characters"
29
49
  });
30
50
  }
31
- const input = Object.fromEntries(
51
+ const given = Object.fromEntries(
32
52
  Object.entries(row.values).filter(([key]) => !META_KEYS.has(key))
33
53
  );
54
+ const input = isCreate(id) ? withEntryDefaults(entry.fields, given, i18n) : given;
34
55
  const result = schema.safeParse(input);
35
56
  if (!result.success) {
36
57
  const reported = new Set(errors.map((issue) => issue.path));
@@ -82,16 +103,17 @@ async function checkIds(rows, context) {
82
103
  async function checkRelations(rows, context) {
83
104
  const fileIds = new Set(rows.flatMap((row) => row.id ? [row.id] : []));
84
105
  for (const [key, field] of Object.entries(context.entry.fields)) {
85
- if (field.type !== "relation" || !field.to) continue;
86
- const target = resolveTable(field.to);
106
+ const to = relationTarget(field);
107
+ if (field.type !== "relation" || !to) continue;
108
+ const target = resolveTable(to);
87
109
  if (!target) continue;
88
110
  const wanted = rows.flatMap((row) => relationIds(row.values?.[key]));
89
111
  if (!wanted.length) continue;
90
112
  const found = await existingIds(context.db, target, wanted);
91
- if (field.to === context.name) for (const id of fileIds) found.add(id);
113
+ if (to === context.name) for (const id of fileIds) found.add(id);
92
114
  for (const row of rows) {
93
115
  const missing = relationIds(row.values?.[key]).filter((id) => !found.has(id));
94
- if (missing.length) addError(row, key, `Unknown ${field.to} id: ${missing.join(", ")}`);
116
+ if (missing.length) addError(row, key, `Unknown ${to} id: ${missing.join(", ")}`);
95
117
  }
96
118
  }
97
119
  }
@@ -99,25 +121,45 @@ async function checkUnique(rows, context) {
99
121
  const columns = context.table;
100
122
  for (const key of uniqueFieldKeys(context.entry)) {
101
123
  const column = columns[key];
124
+ const field = context.entry.fields[key];
102
125
  if (!column) continue;
103
126
  const owners = /* @__PURE__ */ new Map();
104
127
  for (const row of rows) {
105
128
  const value = row.values?.[key];
106
- if (typeof value !== "string" || !value) continue;
107
- const owner = owners.get(value);
129
+ if (!(typeof value === "string" && value || typeof value === "number")) continue;
130
+ const compared = uniqueCompareValue(field, value);
131
+ const owner = owners.get(compared);
108
132
  if (owner) addError(row, key, `Same value as row ${owner.index}`);
109
- else owners.set(value, row);
133
+ else owners.set(compared, row);
110
134
  }
135
+ const lower = isCaseInsensitiveUnique(field);
111
136
  for (const chunk of chunks([...owners.keys()], KEY_CHUNK)) {
112
- const found = await context.db.select({ id: columns.id, value: column }).from(context.table).where(inArray(column, chunk));
137
+ const found = await context.db.select({ id: columns.id, value: column }).from(context.table).where(lower ? inArray(sql`lower(${column})`, chunk) : inArray(column, chunk));
113
138
  for (const match of found) {
114
- const row = owners.get(String(match.value));
139
+ const row = owners.get(uniqueCompareValue(field, match.value));
115
140
  if (row && row.id !== String(match.id))
116
141
  addError(row, key, "This value is already used by another entry");
117
142
  }
118
143
  }
119
144
  }
120
145
  }
146
+ async function checkImmutable(rows, context) {
147
+ const columns = context.table;
148
+ const keys = immutableFieldKeys(context.entry.fields).filter((key) => columns[key]);
149
+ const updated = rows.filter((row) => row.action === "update" && row.id && row.values);
150
+ if (!keys.length || !updated.length) return;
151
+ const saved = /* @__PURE__ */ new Map();
152
+ const selection = Object.fromEntries(keys.map((key) => [key, columns[key]]));
153
+ for (const chunk of chunks([...new Set(updated.map((row) => row.id))], KEY_CHUNK)) {
154
+ const found = await context.db.select({ ...selection, id: columns.id }).from(context.table).where(inArray(columns.id, chunk));
155
+ for (const match of found) saved.set(String(match.id), match);
156
+ }
157
+ const message = validationMessages().immutable;
158
+ for (const row of updated) {
159
+ for (const key of changedImmutableKeys(context.entry.fields, saved.get(row.id), row.values))
160
+ addError(row, key, message);
161
+ }
162
+ }
121
163
  async function checkMedia(rows, context) {
122
164
  if (!context.media) return;
123
165
  const references = new Map(
@@ -133,11 +175,37 @@ async function checkMedia(rows, context) {
133
175
  }
134
176
  }
135
177
  }
178
+ async function publishedIds(db, table, ids) {
179
+ const columns = table;
180
+ const found = /* @__PURE__ */ new Set();
181
+ for (const chunk of chunks([...new Set(ids)], KEY_CHUNK)) {
182
+ const rows = await db.select({ id: columns.id, status: columns.status }).from(table).where(inArray(columns.id, chunk));
183
+ for (const row of rows) if (row.status === "published") found.add(String(row.id));
184
+ }
185
+ return found;
186
+ }
187
+ async function checkPublish(rows, context) {
188
+ if (context.canPublish !== false || !context.entry.drafts) return;
189
+ const permission = `${context.name}.publish`;
190
+ for (const row of rows) {
191
+ if (row.values?.status === "published")
192
+ addError(row, "status", `Publishing needs the ${permission} permission`);
193
+ }
194
+ const updated = rows.flatMap((row) => row.action === "update" && row.id ? [row.id] : []);
195
+ if (!updated.length) return;
196
+ const published = await publishedIds(context.db, context.table, updated);
197
+ for (const row of rows) {
198
+ if (row.action === "update" && row.id && published.has(row.id))
199
+ addError(row, "id", `Editing a published item needs the ${permission} permission`);
200
+ }
201
+ }
136
202
  export async function checkImportRows(rows, context) {
137
203
  await checkIds(rows, context);
138
204
  await checkRelations(rows, context);
139
205
  await checkUnique(rows, context);
206
+ await checkImmutable(rows, context);
140
207
  await checkMedia(rows, context);
208
+ await checkPublish(rows, context);
141
209
  return rows;
142
210
  }
143
211
  export function importPreview(entry, rows, options) {
@@ -165,6 +233,9 @@ function deferredSelfRelations(entry, name) {
165
233
  ([, field]) => field.type === "relation" && field.to === name && field.cardinality !== "many-to-many" && !field.required
166
234
  ).map(([key]) => key);
167
235
  }
236
+ export function assignImportIds(entry, rows) {
237
+ return rows.map((row) => row.values && !row.id ? { ...row, id: customId(entry.id) } : row);
238
+ }
168
239
  export function importWriteStatements(db, name, entry, table, rows) {
169
240
  const idCol = table.id;
170
241
  const created = new Set(
@@ -200,3 +271,26 @@ export function importWriteStatements(db, name, entry, table, rows) {
200
271
  }
201
272
  return [...main, ...later];
202
273
  }
274
+ export async function planImportRedirects(db, entry, table, rows) {
275
+ if (!isRoutedContent(entry)) return null;
276
+ const updates = rows.filter((row) => row.action === "update" && row.values && row.id);
277
+ if (!updates.length) return null;
278
+ const idCol = table.id;
279
+ const before = /* @__PURE__ */ new Map();
280
+ for (const ids of chunks(
281
+ updates.map((row) => row.id),
282
+ KEY_CHUNK
283
+ )) {
284
+ const found = await db.select().from(table).where(inArray(idCol, ids));
285
+ for (const row of found) before.set(String(row.id), row);
286
+ }
287
+ const moves = [];
288
+ for (const row of updates) {
289
+ const existing = before.get(row.id);
290
+ if (!existing) continue;
291
+ const { values } = splitRelationValues(entry, encodeColumnValues(entry, row.values));
292
+ const after = rowAfterUpdate(existing, withContentDefaults(entry, values));
293
+ moves.push(contentMovesOf(entry, existing, after));
294
+ }
295
+ return planContentRedirects(mergeContentMoves(moves));
296
+ }
@@ -0,0 +1,4 @@
1
+ import type { MediaUsage } from '../../shared/index.js';
2
+ import type { MediaUsageSource } from './media-references.js';
3
+ export declare function collectLinkUsage(sources: MediaUsageSource[], keys: string[]): Record<string, MediaUsage[]>;
4
+ export declare function loadLinkUsage(keys: string[], include?: (name: string) => boolean): Promise<Record<string, MediaUsage[]>>;
@@ -0,0 +1,28 @@
1
+ import { linkReferences, linkTargetKey } from "../../shared/index.js";
2
+ import { usageFields, usageTitle } from "./media-references.js";
3
+ import { usageSources } from "./media-usage.js";
4
+ export function collectLinkUsage(sources, keys) {
5
+ const usage = Object.fromEntries(keys.map((key) => [key, []]));
6
+ for (const { name, entry, rows } of sources) {
7
+ for (const row of rows) {
8
+ const id = entry.kind === "single" ? null : String(row.id);
9
+ for (const reference of linkReferences(usageFields(entry, row), row)) {
10
+ if (reference.entry === name && reference.id === id) continue;
11
+ const list = usage[linkTargetKey(reference.entry, reference.id)];
12
+ if (!list) continue;
13
+ list.push({
14
+ collection: name,
15
+ label: entry.label,
16
+ kind: entry.kind,
17
+ id,
18
+ title: usageTitle(entry, row),
19
+ field: reference.field
20
+ });
21
+ }
22
+ }
23
+ }
24
+ return usage;
25
+ }
26
+ export async function loadLinkUsage(keys, include) {
27
+ return collectLinkUsage(await usageSources(include), keys);
28
+ }