@xleddyl/nuxt-cms 0.9.3 → 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 (433) 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 +29 -10
  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 +145 -29
  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/single-row.d.ts +3 -0
  351. package/dist/runtime/server/utils/single-row.js +4 -0
  352. package/dist/runtime/server/utils/site.d.ts +23 -0
  353. package/dist/runtime/server/utils/site.js +79 -0
  354. package/dist/runtime/server/utils/sitemap.d.ts +12 -0
  355. package/dist/runtime/server/utils/sitemap.js +109 -0
  356. package/dist/runtime/server/utils/user-management.d.ts +4 -0
  357. package/dist/runtime/server/utils/user-management.js +20 -0
  358. package/dist/runtime/server/utils/users.d.ts +1 -1
  359. package/dist/runtime/server/utils/users.js +1 -1
  360. package/dist/runtime/server/utils/visibility.d.ts +1 -0
  361. package/dist/runtime/server/utils/visibility.js +6 -1
  362. package/dist/runtime/server/utils/webhooks.d.ts +24 -0
  363. package/dist/runtime/server/utils/webhooks.js +90 -0
  364. package/dist/runtime/shared/block-paths.d.ts +66 -0
  365. package/dist/runtime/shared/block-paths.js +310 -0
  366. package/dist/runtime/shared/block-picker.d.ts +18 -0
  367. package/dist/runtime/shared/block-picker.js +31 -0
  368. package/dist/runtime/shared/block-signature.d.ts +4 -0
  369. package/dist/runtime/shared/block-signature.js +46 -0
  370. package/dist/runtime/shared/clipboard.d.ts +31 -0
  371. package/dist/runtime/shared/clipboard.js +206 -0
  372. package/dist/runtime/shared/conditions.d.ts +36 -0
  373. package/dist/runtime/shared/conditions.js +100 -0
  374. package/dist/runtime/shared/content-routes.d.ts +42 -0
  375. package/dist/runtime/shared/content-routes.js +237 -0
  376. package/dist/runtime/shared/duplicate.js +2 -1
  377. package/dist/runtime/shared/editor-layout.d.ts +18 -0
  378. package/dist/runtime/shared/editor-layout.js +9 -0
  379. package/dist/runtime/shared/field-options.d.ts +21 -0
  380. package/dist/runtime/shared/field-options.js +90 -0
  381. package/dist/runtime/shared/graphql-sdl.d.ts +1 -0
  382. package/dist/runtime/shared/graphql-sdl.js +50 -26
  383. package/dist/runtime/shared/history.d.ts +27 -0
  384. package/dist/runtime/shared/history.js +123 -0
  385. package/dist/runtime/shared/hooks.d.ts +45 -0
  386. package/dist/runtime/shared/hooks.js +28 -0
  387. package/dist/runtime/shared/index.d.ts +90 -21
  388. package/dist/runtime/shared/index.js +121 -37
  389. package/dist/runtime/shared/inline-text.d.ts +38 -0
  390. package/dist/runtime/shared/inline-text.js +85 -0
  391. package/dist/runtime/shared/layout.d.ts +2 -1
  392. package/dist/runtime/shared/layout.js +3 -0
  393. package/dist/runtime/shared/links.d.ts +48 -0
  394. package/dist/runtime/shared/links.js +155 -0
  395. package/dist/runtime/shared/list-filters.d.ts +2 -0
  396. package/dist/runtime/shared/list-filters.js +2 -0
  397. package/dist/runtime/shared/media-focal.d.ts +30 -0
  398. package/dist/runtime/shared/media-focal.js +52 -0
  399. package/dist/runtime/shared/migrations-dir.d.ts +14 -0
  400. package/dist/runtime/shared/migrations-dir.js +48 -2
  401. package/dist/runtime/shared/presets.d.ts +67 -0
  402. package/dist/runtime/shared/presets.js +127 -0
  403. package/dist/runtime/shared/preview-links.d.ts +64 -0
  404. package/dist/runtime/shared/preview-links.js +54 -0
  405. package/dist/runtime/shared/preview.d.ts +124 -21
  406. package/dist/runtime/shared/preview.js +419 -81
  407. package/dist/runtime/shared/redirects.d.ts +75 -0
  408. package/dist/runtime/shared/redirects.js +230 -0
  409. package/dist/runtime/shared/roles.d.ts +57 -0
  410. package/dist/runtime/shared/roles.js +293 -0
  411. package/dist/runtime/shared/save-state.d.ts +8 -0
  412. package/dist/runtime/shared/save-state.js +15 -0
  413. package/dist/runtime/shared/search.d.ts +16 -1
  414. package/dist/runtime/shared/search.js +41 -7
  415. package/dist/runtime/shared/seo.d.ts +167 -0
  416. package/dist/runtime/shared/seo.js +335 -0
  417. package/dist/runtime/shared/transfer.js +2 -1
  418. package/dist/runtime/shared/translations.d.ts +29 -0
  419. package/dist/runtime/shared/translations.js +123 -0
  420. package/dist/runtime/shared/users.d.ts +1 -1
  421. package/dist/runtime/shared/validation.d.ts +23 -1
  422. package/dist/runtime/shared/validation.js +214 -64
  423. package/dist/runtime/webhooks.d.ts +33 -0
  424. package/dist/runtime/webhooks.js +57 -0
  425. package/dist/types.d.mts +5 -1
  426. package/package.json +6 -2
  427. package/dist/runtime/app/components/cms/BlockOutline.vue +0 -88
  428. package/dist/runtime/app/components/cms/BlockPalette.vue +0 -31
  429. package/dist/runtime/app/components/cms/SettingsModal.d.vue.ts +0 -12
  430. package/dist/runtime/app/components/cms/SettingsModal.vue +0 -126
  431. package/dist/runtime/app/components/cms/SettingsModal.vue.d.ts +0 -12
  432. package/dist/runtime/app/composables/cms-field-focus.d.ts +0 -4
  433. 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,
@@ -51,7 +69,8 @@ import {
51
69
  listFilterValues
52
70
  } from "./list-filter.js";
53
71
  import { getContentI18n, resolveTable, tableColumns } from "./registry.js";
54
- import { visibilityConditions } from "./visibility.js";
72
+ import { singleRowFirst } from "./single-row.js";
73
+ import { visibilityConditions, visibleOrPreviewConditions } from "./visibility.js";
55
74
  const MAX_LIMIT = 100;
56
75
  const DEFAULT_LIMIT = 50;
57
76
  const LOCALE = Symbol("locale");
@@ -98,6 +117,12 @@ function tableFor(name) {
98
117
  function publishedFilter(entry, table) {
99
118
  return visibilityConditions(entry, tableColumns(table));
100
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
+ }
101
126
  function localeOf(parent) {
102
127
  return parent[LOCALE] ?? getContentI18n().defaultLocale;
103
128
  }
@@ -111,6 +136,23 @@ function resolveLocaleArg(locale) {
111
136
  }
112
137
  return String(locale);
113
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
+ }
114
156
  function localizeRow(entry, row, locale) {
115
157
  const { defaultLocale } = getContentI18n();
116
158
  const result = normalizeTimestampFields({ ...row, [LOCALE]: locale });
@@ -125,9 +167,15 @@ function localizeRow(entry, row, locale) {
125
167
  result[key] = value?.[locale] ?? value?.[defaultLocale] ?? null;
126
168
  }
127
169
  for (const [key, field] of Object.entries(fields)) {
128
- 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;
129
176
  const visible = publicBlocks(field, result[key]);
130
- 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;
131
179
  }
132
180
  return result;
133
181
  }
@@ -234,8 +282,8 @@ async function resolveMedia(ctx, key) {
234
282
  if (isLocalMedia()) {
235
283
  const file = (await useMediaIndex()).get(key);
236
284
  if (!file) return null;
237
- const alt = await loadMediaAlt(ctx).load(key);
238
- return mediaObject(key, { ...file, alt: alt ?? null });
285
+ const meta = await loadMediaMeta(ctx).load(key);
286
+ return mediaObject(key, { ...file, ...meta });
239
287
  }
240
288
  const row = await loadMedia(ctx).load(key);
241
289
  return row ? mediaObject(key, row) : null;
@@ -261,9 +309,9 @@ function loadManyToMany(ctx, name, key, field) {
261
309
  `m2m:${name}:${key}`,
262
310
  async (sourceIds) => {
263
311
  const config = cmsConfig;
264
- const targetEntry = config[field.to];
312
+ const targetEntry = config[relationTarget(field)];
265
313
  const join = cmsTables[`${name}_${key}`];
266
- const target = tableFor(field.to);
314
+ const target = tableFor(relationTarget(field));
267
315
  const joinCols = tableColumns(join);
268
316
  const rows = await useDb().select({ sourceId: joinCols.sourceId, target }).from(join).innerJoin(target, eq(joinCols.targetId, tableColumns(target).id)).where(
269
317
  and(inArray(joinCols.sourceId, sourceIds), ...publishedFilter(targetEntry, target))
@@ -293,14 +341,47 @@ function loadMedia(ctx) {
293
341
  );
294
342
  });
295
343
  }
296
- function loadMediaAlt(ctx) {
297
- return getLoader(ctx, "mediaAlt", async (keys) => {
344
+ function loadMediaMeta(ctx) {
345
+ return getLoader(ctx, "mediaMeta", async (keys) => {
298
346
  const media = cmsTables.cms_media;
299
347
  const columns = tableColumns(media);
300
- const rows = await useDb().select({ key: columns.key, alt: columns.alt }).from(media).where(inArray(columns.key, keys));
301
- 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
+ );
302
364
  });
303
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
+ }
304
385
  function mediaObject(key, row) {
305
386
  return {
306
387
  key,
@@ -311,6 +392,8 @@ function mediaObject(key, row) {
311
392
  size: row?.size ?? null,
312
393
  width: row?.width ?? null,
313
394
  height: row?.height ?? null,
395
+ focalX: row?.focalX ?? null,
396
+ focalY: row?.focalY ?? null,
314
397
  type: mediaTypeFor(row?.mime, key)
315
398
  };
316
399
  }
@@ -321,25 +404,33 @@ function entryResolvers(config, name, entry) {
321
404
  if (field.type === "relation" && field.cardinality === "many-to-many") {
322
405
  resolvers[key] = async (parent, _args, ctx) => {
323
406
  const rows = await loadManyToMany(ctx, name, key, field).load(parent.id);
324
- 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
+ );
325
410
  };
326
411
  } else if (field.type === "relation") {
327
412
  resolvers[key] = async (parent, _args, ctx) => {
328
413
  const id = parent[key];
329
414
  if (id == null) return null;
330
- const row = await loadById(ctx, field.to).load(id);
331
- 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;
332
417
  };
333
418
  } else if (field.type === "media") {
334
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] : [];
335
422
  }
336
423
  }
424
+ if (isRoutedContent(entry)) {
425
+ resolvers.path = (parent) => contentItemPath(entry, parent, localeOf(parent));
426
+ resolvers.paths = (parent) => contentItemPaths(entry, parent, contentRouteLocales(getContentI18n()));
427
+ }
337
428
  return resolvers;
338
429
  }
339
- function blockResolvers(name, key, field) {
430
+ function blockResolvers(name, schemaPath, field) {
340
431
  const resolvers = {};
341
- resolvers[blocksFieldTypeName(name, key)] = {
342
- __resolveType: (value) => blockTypeName(name, key, String(value.type))
432
+ resolvers[blocksFieldTypeName(name, schemaPath)] = {
433
+ __resolveType: (value) => blockTypeName(name, schemaPath, String(value.type))
343
434
  };
344
435
  for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
345
436
  const mediaFields = Object.entries(block.fields).filter(([, f]) => f.type === "media");
@@ -348,7 +439,7 @@ function blockResolvers(name, key, field) {
348
439
  for (const [blockFieldKey] of mediaFields) {
349
440
  fieldLevel[blockFieldKey] = (parent, _args, ctx) => resolveMedia(ctx, parent[blockFieldKey]);
350
441
  }
351
- resolvers[blockTypeName(name, key, blockName)] = fieldLevel;
442
+ resolvers[blockTypeName(name, schemaPath, blockName)] = fieldLevel;
352
443
  }
353
444
  return resolvers;
354
445
  }
@@ -357,14 +448,24 @@ export function buildCmsSchema() {
357
448
  const entries = Object.entries(config);
358
449
  const queryResolvers = {};
359
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
+ };
360
463
  for (const [name, entry] of entries) {
361
464
  const gqlType = typeName(name);
362
465
  const fieldLevel = entryResolvers(config, name, entry);
363
466
  if (Object.keys(fieldLevel).length) typeResolvers[gqlType] = fieldLevel;
364
- for (const [key, field] of Object.entries(entryFieldsFor(entry))) {
365
- if (field.type === "blocks" && !isPrivateField(field))
366
- Object.assign(typeResolvers, blockResolvers(name, key, field));
367
- }
467
+ for (const { field, schemaPath } of blocksFieldPaths(entryFieldsFor(entry)))
468
+ Object.assign(typeResolvers, blockResolvers(name, schemaPath, field));
368
469
  if (entry.kind === "page") {
369
470
  queryResolvers[name] = async (_, args) => {
370
471
  const locale = resolveLocaleArg(args.locale);
@@ -383,7 +484,7 @@ export function buildCmsSchema() {
383
484
  const locale = resolveLocaleArg(args.locale);
384
485
  const table = tableFor(name);
385
486
  const published = publishedFilter(entry, table);
386
- const [row] = await useDb().select().from(table).where(published.length ? and(...published) : void 0).limit(1);
487
+ const [row] = await useDb().select().from(table).where(published.length ? and(...published) : void 0).orderBy(singleRowFirst(tableColumns(table).id, entry.id)).limit(1);
387
488
  return row ? localizeRow(entry, row, locale) : null;
388
489
  };
389
490
  continue;
@@ -400,18 +501,32 @@ export function buildCmsSchema() {
400
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);
401
502
  return rows.map((row) => localizeRow(entry, row, locale));
402
503
  };
403
- queryResolvers[`${name}ById`] = async (_, args) => {
504
+ queryResolvers[`${name}ById`] = async (_, args, ctx) => {
404
505
  const locale = resolveLocaleArg(args.locale);
405
506
  const table = tableFor(name);
406
- 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);
407
513
  return row ? localizeRow(entry, row, locale) : null;
408
514
  };
409
515
  if (entry.kind === "content") {
410
- queryResolvers[`${name}BySlug`] = async (_, args) => {
516
+ queryResolvers[`${name}BySlug`] = async (_, args, ctx) => {
411
517
  const locale = resolveLocaleArg(args.locale);
412
518
  const table = tableFor(name);
413
519
  const slug = tableColumns(table)[CONTENT_SLUG_FIELD];
414
- 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
+ });
415
530
  return row ? localizeRow(entry, row, locale) : null;
416
531
  };
417
532
  }
@@ -435,11 +550,12 @@ export function buildCmsSchema() {
435
550
  typeDefs: renderGraphqlSdl(config),
436
551
  resolvers: {
437
552
  JSON: jsonScalar,
553
+ CmsLink: linkResolvers(),
438
554
  Query: queryResolvers,
439
555
  ...typeResolvers
440
556
  }
441
557
  });
442
558
  }
443
- export function createGraphqlContext() {
444
- return { loaders: /* @__PURE__ */ new Map() };
559
+ export function createGraphqlContext(preview = null) {
560
+ return { loaders: /* @__PURE__ */ new Map(), preview };
445
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[]>>;