@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
@@ -6,8 +6,13 @@
6
6
  :field-key="CONTENT_BODY_FIELD"
7
7
  :fields="config.fields"
8
8
  :title="headerTitle"
9
+ :trail="[{ label: config.label, onClick: goBack }]"
9
10
  :focus="focus"
11
+ :locked="locked"
12
+ :readonly="!!lock"
13
+ :tab-query="CMS_TAB_QUERY"
10
14
  content
15
+ @save="saveShortcut"
11
16
  >
12
17
  <template #start>
13
18
  <KButton
@@ -16,28 +21,67 @@
16
21
  variant="ghost"
17
22
  tone="neutral"
18
23
  :aria-label="`Back to ${config.label}`"
19
- :title="`Back to ${config.label}`"
20
24
  @click="goBack"
21
25
  />
22
26
  </template>
23
27
 
24
28
  <template #status>
25
29
  <CmsStatusBadge :published="published" :scheduled="scheduled" :at="scheduledAt" />
26
- <span v-if="dirty" class="cms-editor-dirty">Unsaved changes</span>
30
+ <KBadge v-if="dirty" label="Unsaved changes" tone="warning" dot />
31
+ <KTooltip v-if="lock" :text="lock">
32
+ <KBadge size="sm" variant="outline" label="Read only" />
33
+ </KTooltip>
27
34
  </template>
28
35
 
29
- <template #actions>
30
- <KPopover v-model:open="scheduleOpen" placement="bottom-end" label="Schedule">
36
+ <template v-if="config.route" #content-start="{ locale }">
37
+ <KStack gap="xs">
38
+ <span class="k-label">Page address</span>
39
+ <KStack direction="row" align="center" gap="sm">
40
+ <span
41
+ class="cms-editor-address-path k-mono"
42
+ :class="{ 'is-empty': !addressOf(formState, locale) }"
43
+ >
44
+ {{ addressOf(formState, locale) ?? "No address yet" }}
45
+ </span>
46
+ <KButton
47
+ v-if="livePath(locale)"
48
+ :href="livePath(locale)"
49
+ target="_blank"
50
+ label="Open"
51
+ :icon="cmsIcon('arrow-top-right-on-square')"
52
+ size="sm"
53
+ variant="ghost"
54
+ tone="neutral"
55
+ />
56
+ </KStack>
57
+ <KFieldMessage
58
+ v-if="addressHint(locale)"
59
+ tone="neutral"
60
+ :icon="false"
61
+ :message="addressHint(locale)"
62
+ />
63
+ </KStack>
64
+ </template>
65
+
66
+ <template #actions="{ compact }">
67
+ <KPopover
68
+ v-if="!lock && canPublish"
69
+ v-model:open="scheduleOpen"
70
+ placement="bottom-end"
71
+ label="Schedule"
72
+ width="18rem"
73
+ >
31
74
  <KButton
32
75
  :icon="cmsIcon('clock')"
33
76
  size="sm"
34
77
  variant="ghost"
35
78
  tone="neutral"
36
- :label="scheduled ? 'Reschedule' : 'Schedule'"
79
+ :label="compact ? void 0 : scheduleLabel"
80
+ :aria-label="scheduleLabel"
37
81
  :disabled="saving"
38
82
  />
39
83
  <template #content>
40
- <KStack gap="md" class="cms-editor-schedule-panel">
84
+ <KStack gap="md">
41
85
  <KFormField
42
86
  label="Publish at"
43
87
  description="The item stays hidden from the site until this moment."
@@ -64,6 +108,7 @@
64
108
  </template>
65
109
  </KPopover>
66
110
  <KButton
111
+ v-if="!lock"
67
112
  label="Save"
68
113
  size="sm"
69
114
  variant="soft"
@@ -72,6 +117,7 @@
72
117
  @click="save()"
73
118
  />
74
119
  <KButton
120
+ v-if="!lock && canPublish"
75
121
  :label="published ? 'Unpublish' : 'Publish'"
76
122
  size="sm"
77
123
  :variant="published ? 'soft' : 'solid'"
@@ -79,23 +125,24 @@
79
125
  :loading="saving"
80
126
  @click="save(published ? 'draft' : 'published')"
81
127
  />
82
- <KDropdownMenu
83
- v-if="!isNew"
84
- :items="[
85
- { label: 'Duplicate', onSelect: duplicate },
86
- { label: 'Delete', onSelect: remove }
87
- ]"
88
- placement="bottom-end"
89
- >
128
+ <KDropdownMenu v-if="moreItems.length" :items="moreItems" placement="bottom-end">
90
129
  <KButton
91
130
  :icon="cmsIcon('ellipsis-horizontal')"
92
131
  size="sm"
93
132
  variant="ghost"
94
133
  tone="neutral"
95
134
  aria-label="More actions"
96
- title="More actions"
97
135
  />
98
136
  </KDropdownMenu>
137
+ <CmsSharePreview
138
+ v-if="canShare"
139
+ v-model:open="shareOpen"
140
+ :entry-name="name"
141
+ :item-id="id"
142
+ :dirty="dirty"
143
+ :locale="editor?.locale"
144
+ :save="() => save()"
145
+ />
99
146
  </template>
100
147
  </CmsVisualEditor>
101
148
  </template>
@@ -105,6 +152,8 @@ import {
105
152
  CONTENT_BODY_FIELD,
106
153
  CONTENT_PUBLISHED_AT_FIELD,
107
154
  CONTENT_TITLE_FIELD,
155
+ contentItemPath,
156
+ contentRoutePattern,
108
157
  isScheduledContent
109
158
  } from "#nuxt-cms";
110
159
  import {
@@ -112,8 +161,6 @@ import {
112
161
  createError,
113
162
  definePageMeta,
114
163
  navigateTo,
115
- onBeforeRouteLeave,
116
- onBeforeUnmount,
117
164
  onMounted,
118
165
  ref,
119
166
  useFetch,
@@ -122,28 +169,37 @@ import {
122
169
  watch
123
170
  } from "#imports";
124
171
  import {
172
+ Badge as KBadge,
125
173
  Button as KButton,
126
174
  DropdownMenu as KDropdownMenu,
175
+ FieldMessage as KFieldMessage,
127
176
  FormField as KFormField,
128
177
  Popover as KPopover,
129
- Stack as KStack
178
+ Stack as KStack,
179
+ Tooltip as KTooltip,
180
+ toast,
181
+ useLeaveGuard
130
182
  } from "@xleddyl/443-components-vue";
131
183
  import cmsConfig from "#cms-config";
132
184
  import { duplicateEntryValues } from "../../shared/duplicate";
133
- import { readSearchFocus } from "../../shared/search";
134
- import { useCmsConfirm } from "../composables/cms-confirm";
185
+ import { entryDefaults, lockedFieldKeys } from "../../shared/field-options";
186
+ import { cloneState, mergeSavedState } from "../../shared/save-state";
187
+ import { CMS_TAB_QUERY, cmsPageKey, readSearchFocus } from "../../shared/search";
188
+ import { useCmsPermissions } from "../composables/cms-permissions";
189
+ import { useCmsRouteFlag } from "../composables/cms-route";
190
+ import { useCmsLinkedDelete } from "../composables/cms-link-usage";
135
191
  import { useCmsRuntime } from "../composables/cms-runtime";
136
- import { useCmsToast } from "../composables/cms-toast";
192
+ import { useCmsSaveHandoff } from "../composables/cms-save-handoff";
193
+ import { useCmsTranslationActions } from "../composables/cms-translation-actions";
137
194
  import { cmsApi } from "../utils/api";
138
- import { cmsIcon, errorMessage } from "../utils/ui";
195
+ import { cmsIcon, errorIssues, errorMessage } from "../utils/ui";
139
196
  definePageMeta({
140
197
  layout: "cms-editor",
141
198
  middleware: "cms-auth",
142
- key: (route2) => route2.fullPath
199
+ key: (route2) => cmsPageKey(route2, [CMS_TAB_QUERY])
143
200
  });
144
201
  const route = useRoute();
145
- const toast = useCmsToast();
146
- const confirmAction = useCmsConfirm();
202
+ const confirmLinkedDelete = useCmsLinkedDelete();
147
203
  const focus = readSearchFocus(route.query);
148
204
  const { i18n } = useCmsRuntime();
149
205
  const name = String(route.meta.cmsEntry ?? route.path.split("/")[2] ?? "");
@@ -159,9 +215,10 @@ const editor = ref(null);
159
215
  function pickFields(row) {
160
216
  return Object.fromEntries(formKeys.map((key) => [key, row[key] ?? null]));
161
217
  }
162
- const blankState = Object.fromEntries(
163
- formKeys.map((key) => [key, key === "status" ? "draft" : null])
164
- );
218
+ const blankState = {
219
+ ...Object.fromEntries(formKeys.map((key) => [key, key === "status" ? "draft" : null])),
220
+ ...entryDefaults(config.fields, i18n, { local: true })
221
+ };
165
222
  const formState = ref({ ...blankState });
166
223
  const duplicateOf = isNew && typeof route.query.duplicate === "string" && route.query.duplicate ? route.query.duplicate : null;
167
224
  const sourceId = isNew ? duplicateOf : id;
@@ -178,7 +235,57 @@ if (sourceId) {
178
235
  formState.value = duplicateOf ? duplicateEntryValues(config, data.value) : pickFields(data.value);
179
236
  }
180
237
  const snapshot = ref(JSON.stringify(duplicateOf ? blankState : formState.value));
181
- const dirty = computed(() => JSON.stringify(formState.value) !== snapshot.value);
238
+ const handoff = useCmsSaveHandoff(name);
239
+ const handedOver = id ? handoff.take(id) : null;
240
+ if (handedOver) formState.value = handedOver;
241
+ const { can, entryLock } = useCmsPermissions();
242
+ const canPublish = computed(() => can(`${name}.publish`));
243
+ const lock = computed(
244
+ () => entryLock(name, config, {
245
+ isNew,
246
+ status: JSON.parse(snapshot.value).status
247
+ })
248
+ );
249
+ const { items: translationItems } = useCmsTranslationActions({
250
+ fields: config.fields,
251
+ state: formState,
252
+ locale: () => editor.value?.locale,
253
+ disabled: () => !!lock.value,
254
+ apply: (values) => editor.value?.applyChange(values)
255
+ });
256
+ const canShare = computed(() => !isNew && !!config.route && can(`${name}.update`));
257
+ const shareOpen = useCmsRouteFlag("share");
258
+ const itemActions = computed(
259
+ () => isNew ? [] : [
260
+ ...canShare.value ? [
261
+ {
262
+ label: "Share preview",
263
+ icon: cmsIcon("link"),
264
+ onSelect: () => shareOpen.value = true
265
+ }
266
+ ] : [],
267
+ ...can(`${name}.create`) ? [
268
+ {
269
+ label: "Duplicate",
270
+ icon: cmsIcon("document-duplicate"),
271
+ onSelect: duplicate
272
+ }
273
+ ] : []
274
+ ]
275
+ );
276
+ const canDeleteItem = computed(
277
+ () => can(`${name}.delete`) && (JSON.parse(snapshot.value).status !== "published" || canPublish.value)
278
+ );
279
+ const deleteActions = computed(
280
+ () => !isNew && canDeleteItem.value ? [{ label: "Delete", icon: cmsIcon("trash"), tone: "danger", onSelect: remove }] : []
281
+ );
282
+ const moreItems = computed(
283
+ () => [itemActions.value, translationItems.value, deleteActions.value].filter((group) => group.length)
284
+ );
285
+ const locked = computed(
286
+ () => isNew ? [] : lockedFieldKeys(config.fields, JSON.parse(snapshot.value))
287
+ );
288
+ const dirty = computed(() => !lock.value && JSON.stringify(formState.value) !== snapshot.value);
182
289
  const titleText = computed(() => {
183
290
  const value = formState.value[CONTENT_TITLE_FIELD];
184
291
  if (typeof value === "string") return value;
@@ -193,11 +300,39 @@ useHead({ title: computed(() => `${headerTitle.value} \xB7 ${config.label}`) });
193
300
  const published = computed(() => formState.value.status === "published");
194
301
  const mounted = ref(false);
195
302
  const scheduled = computed(() => mounted.value && isScheduledContent(formState.value));
303
+ const scheduleLabel = computed(() => scheduled.value ? "Reschedule" : "Schedule");
196
304
  const scheduledAt = computed(() => {
197
305
  const value = formState.value[CONTENT_PUBLISHED_AT_FIELD];
198
306
  if (!mounted.value || typeof value !== "string" || !value) return "";
199
307
  return new Date(value).toLocaleString();
200
308
  });
309
+ const savedState = computed(
310
+ () => isNew ? {} : JSON.parse(snapshot.value)
311
+ );
312
+ const live = computed(
313
+ () => mounted.value && savedState.value.status === "published" && !isScheduledContent(savedState.value)
314
+ );
315
+ function addressOf(values, locale) {
316
+ return contentItemPath(config, { ...values, id }, locale);
317
+ }
318
+ function livePath(locale) {
319
+ return live.value ? addressOf(savedState.value, locale) : null;
320
+ }
321
+ function missingAddressHint(locale) {
322
+ const pattern = contentRoutePattern(config.route, locale) ?? "";
323
+ if (isNew && pattern.includes("[id]")) return "Save the item to get its address.";
324
+ if (/\[(?:year|month|day)\]/.test(pattern) && !formState.value[CONTENT_PUBLISHED_AT_FIELD])
325
+ return "The address needs a publish date.";
326
+ return "The address needs a slug.";
327
+ }
328
+ function addressHint(locale) {
329
+ const current = addressOf(formState.value, locale);
330
+ if (!current) return missingAddressHint(locale);
331
+ if (!live.value)
332
+ return scheduled.value ? "Live from the scheduled date." : "Live once published.";
333
+ if (current !== livePath(locale)) return "Save to move the page to this address.";
334
+ return "";
335
+ }
201
336
  const publishedAtField = computed(() => ({
202
337
  ...config.fields[CONTENT_PUBLISHED_AT_FIELD],
203
338
  required: true
@@ -211,25 +346,14 @@ watch(scheduleOpen, (open) => {
211
346
  const future = typeof current === "string" && new Date(current).getTime() > Date.now();
212
347
  scheduleDraft.value = future ? current : new Date(Math.ceil(Date.now() / 36e5) * 36e5 + 36e5).toISOString();
213
348
  });
214
- function onBeforeUnload(event) {
215
- event.preventDefault();
216
- }
217
- watch(dirty, (value) => {
218
- if (value) window.addEventListener("beforeunload", onBeforeUnload);
219
- else window.removeEventListener("beforeunload", onBeforeUnload);
220
- });
349
+ const leave = useLeaveGuard(dirty);
221
350
  onMounted(() => {
222
351
  mounted.value = true;
223
- if (dirty.value) window.addEventListener("beforeunload", onBeforeUnload);
224
- });
225
- onBeforeUnmount(() => window.removeEventListener("beforeunload", onBeforeUnload));
226
- onBeforeRouteLeave(async () => {
227
- if (dirty.value && !await confirmAction("You have unsaved changes. Leave anyway?"))
228
- return false;
229
352
  });
230
353
  function goBack() {
231
354
  navigateTo(`/cms/${name}`);
232
355
  }
356
+ const LATER_CHANGES = "Changes made while saving are not saved yet.";
233
357
  const SUCCESS = {
234
358
  save: "Saved",
235
359
  published: "Published",
@@ -245,19 +369,28 @@ async function save(status, changes = {}, notice) {
245
369
  status: before.status,
246
370
  ...pickRevert(before, changes)
247
371
  };
248
- toast.add({ title: "Check the highlighted fields", color: "error" });
372
+ toast.error("Check the highlighted fields");
249
373
  return false;
250
374
  }
251
375
  saving.value = true;
376
+ const submitted = cloneState(formState.value);
252
377
  try {
253
- const row = await (isNew ? cmsApi(endpoint, { method: "POST", body: formState.value }) : cmsApi(`${endpoint}/${id}`, {
378
+ const row = await (isNew ? cmsApi(endpoint, { method: "POST", body: submitted }) : cmsApi(`${endpoint}/${id}`, {
254
379
  method: "PUT",
255
- body: formState.value
380
+ body: submitted
256
381
  }));
257
- formState.value = pickFields(row);
258
- snapshot.value = JSON.stringify(formState.value);
259
- toast.add({ title: SUCCESS[notice ?? status ?? "save"] ?? "Saved", color: "success" });
260
- if (isNew && row.id) await navigateTo(`/cms/${name}/${row.id}`, { replace: true });
382
+ const saved = pickFields(row);
383
+ const merged = mergeSavedState(submitted, formState.value, saved);
384
+ formState.value = merged.state;
385
+ snapshot.value = JSON.stringify(saved);
386
+ toast.success(SUCCESS[notice ?? status ?? "save"] ?? "Saved", {
387
+ description: merged.clean ? void 0 : LATER_CHANGES
388
+ });
389
+ if (isNew && row.id) {
390
+ if (!merged.clean) handoff.stash(String(row.id), merged.state);
391
+ leave.allowNext();
392
+ await navigateTo(`/cms/${name}/${row.id}`, { replace: true });
393
+ }
261
394
  return true;
262
395
  } catch (error) {
263
396
  formState.value = {
@@ -265,12 +398,16 @@ async function save(status, changes = {}, notice) {
265
398
  status: before.status,
266
399
  ...pickRevert(before, changes)
267
400
  };
268
- toast.add({ title: "Save failed", description: errorMessage(error), color: "error" });
401
+ editor.value?.setIssues(errorIssues(error));
402
+ toast.error("Save failed", { description: errorMessage(error) });
269
403
  return false;
270
404
  } finally {
271
405
  saving.value = false;
272
406
  }
273
407
  }
408
+ function saveShortcut() {
409
+ if (!saving.value) void save();
410
+ }
274
411
  function pickRevert(before, changes) {
275
412
  return Object.fromEntries(Object.keys(changes).map((key) => [key, before[key] ?? null]));
276
413
  }
@@ -287,15 +424,15 @@ function duplicate() {
287
424
  void navigateTo({ path: `/cms/${name}/new`, query: { duplicate: id } });
288
425
  }
289
426
  async function remove() {
290
- if (!await confirmAction("Delete this item?")) return;
427
+ if (!await confirmLinkedDelete(name, id ? [id] : [], "Delete this item?")) return;
291
428
  saving.value = true;
292
429
  try {
293
430
  await cmsApi(`${endpoint}/${id}`, { method: "DELETE" });
294
431
  snapshot.value = JSON.stringify(formState.value);
295
- toast.add({ title: "Deleted", color: "success" });
432
+ toast.success("Deleted");
296
433
  goBack();
297
434
  } catch (error) {
298
- toast.add({ title: "Delete failed", description: errorMessage(error), color: "error" });
435
+ toast.error("Delete failed", { description: errorMessage(error) });
299
436
  } finally {
300
437
  saving.value = false;
301
438
  }
@@ -1,36 +1,39 @@
1
1
  <template>
2
- <div class="cms-page">
3
- <KPageHeader :title="headerTitle" :breadcrumbs="breadcrumb" breadcrumbs-ancestors>
4
- <template v-if="drafts" #badge>
5
- <CmsStatusBadge :published="published" />
6
- </template>
2
+ <div class="k-page">
3
+ <KPageHeader
4
+ :trail="breadcrumb"
5
+ trail-label="Page"
6
+ :title="headerTitle"
7
+ :icon="cmsIcon(config.icon ?? 'window')"
8
+ :description="pageRoute?.path"
9
+ >
7
10
  <template #actions>
8
- <KButton label="Back" icon="arrow-left" variant="soft" @click="goBack" />
9
- <KButton type="submit" :form="FORM_ID" label="Save" :loading="saving" />
11
+ <CmsTranslationMenu v-model="formState" :fields="fields" :disabled="!!lock" size="sm" />
10
12
  <KButton
11
- v-if="drafts"
13
+ v-if="!lock"
12
14
  type="submit"
13
15
  :form="FORM_ID"
14
- :label="published ? 'Make draft' : 'Publish'"
16
+ label="Save"
17
+ size="sm"
15
18
  :loading="saving"
16
- @click="togglePublished"
17
19
  />
18
20
  </template>
19
21
  </KPageHeader>
20
22
 
21
- <KCard variant="outline" padding="lg">
23
+ <KCard padding="lg" fill>
22
24
  <CmsEntryForm
25
+ ref="formRef"
23
26
  v-model="formState"
24
27
  :fields="fields"
25
28
  :tabs="config.tabs"
26
29
  :entry-name="name"
27
- :drafts="drafts"
28
30
  :form-id="FORM_ID"
29
- :loading="saving"
31
+ :tab-query="CMS_TAB_QUERY"
30
32
  :focus="focus"
31
33
  :path="pageRoute?.path"
34
+ :lock="lock"
35
+ :locked="locked"
32
36
  @submit="save"
33
- @error="revertStatus"
34
37
  />
35
38
  </KCard>
36
39
  </div>
@@ -43,33 +46,34 @@ import {
43
46
  createError,
44
47
  definePageMeta,
45
48
  navigateTo,
46
- onBeforeRouteLeave,
47
- onBeforeUnmount,
48
49
  ref,
49
50
  useFetch,
50
- useRoute,
51
- watch
51
+ useRoute
52
52
  } from "#imports";
53
53
  import {
54
54
  Button as KButton,
55
55
  Card as KCard,
56
- PageHeader as KPageHeader
56
+ PageHeader as KPageHeader,
57
+ toast,
58
+ useLeaveGuard
57
59
  } from "@xleddyl/443-components-vue";
58
60
  import cmsConfig from "#cms-config";
59
- import { readSearchFocus } from "../../shared/search";
60
- import { useCmsConfirm } from "../composables/cms-confirm";
61
- import { useCmsToast } from "../composables/cms-toast";
61
+ import { entryDefaults, lockedFieldKeys } from "../../shared/field-options";
62
+ import { cloneState, mergeSavedState } from "../../shared/save-state";
63
+ import { CMS_TAB_QUERY, cmsEntryLink, cmsPageKey, readSearchFocus } from "../../shared/search";
64
+ import { useCmsPermissions } from "../composables/cms-permissions";
65
+ import { CMS_NEW_RECORD } from "../composables/cms-route";
66
+ import { useCmsRuntime } from "../composables/cms-runtime";
62
67
  import { cmsApi } from "../utils/api";
63
- import { errorMessage } from "../utils/ui";
68
+ import { cmsIcon, errorIssues, errorMessage } from "../utils/ui";
64
69
  const FORM_ID = "cms-entry-form";
65
70
  definePageMeta({
66
71
  layout: "cms-admin",
67
72
  middleware: "cms-auth",
68
73
  validate: (route2) => Object.hasOwn(cmsConfig, route2.params.collection),
69
- key: (route2) => route2.fullPath
74
+ key: (route2) => cmsPageKey(route2, [CMS_TAB_QUERY])
70
75
  });
71
76
  const route = useRoute();
72
- const toast = useCmsToast();
73
77
  const focus = readSearchFocus(route.query);
74
78
  const name = route.params.collection;
75
79
  const config = cmsConfig[name];
@@ -77,9 +81,23 @@ if (!config || config.kind !== "collection" && config.kind !== "page") {
77
81
  throw createError({ statusCode: 404, statusMessage: "Unknown collection", fatal: true });
78
82
  }
79
83
  const id = route.params.id;
80
- const isPage = config.kind === "page";
81
- const pageRoute = isPage && id ? pageRouteOf(config, id) : void 0;
82
- if (isPage && !pageRoute) {
84
+ if (config.kind === "collection") {
85
+ const text = (key) => {
86
+ const value = route.query[key];
87
+ return typeof value === "string" ? value : void 0;
88
+ };
89
+ await navigateTo(
90
+ cmsEntryLink(name, id ?? CMS_NEW_RECORD, {
91
+ field: text("field"),
92
+ locale: text("locale"),
93
+ block: text("block"),
94
+ tab: text(CMS_TAB_QUERY)
95
+ }),
96
+ { replace: true }
97
+ );
98
+ }
99
+ const pageRoute = config.kind === "page" && id ? pageRouteOf(config, id) : void 0;
100
+ if (config.kind === "page" && !pageRoute) {
83
101
  throw createError({ statusCode: 404, statusMessage: "Unknown page", fatal: true });
84
102
  }
85
103
  const breadcrumb = (() => {
@@ -97,21 +115,22 @@ const breadcrumb = (() => {
97
115
  }
98
116
  return links;
99
117
  })();
100
- const isNew = !isPage && id === void 0;
101
- const drafts = !isPage && !!config.drafts;
102
118
  const fields = pageRoute ? pageFields(config, pageRoute.path) : config.fields;
103
- const headerTitle = pageRoute ? pageRoute.label : isNew ? "New entry" : "Edit entry";
104
- const fieldKeys = Object.keys(fields);
105
- const formKeys = drafts ? [...fieldKeys, "status"] : fieldKeys;
119
+ const headerTitle = pageRoute?.label ?? config.label;
120
+ const formKeys = Object.keys(fields);
106
121
  const endpoint = `/api/cms/admin/${name}`;
122
+ const { i18n } = useCmsRuntime();
107
123
  function emptyState() {
108
- return Object.fromEntries(formKeys.map((k) => [k, null]));
124
+ return {
125
+ ...Object.fromEntries(formKeys.map((k) => [k, null])),
126
+ ...entryDefaults(fields, i18n, { local: true })
127
+ };
109
128
  }
110
129
  function pickFields(row) {
111
130
  return Object.fromEntries(formKeys.map((k) => [k, row[k] ?? null]));
112
131
  }
113
132
  const formState = ref(emptyState());
114
- if (!isNew) {
133
+ if (pageRoute) {
115
134
  const { data, error } = await useFetch(`${endpoint}/${id}`);
116
135
  if (error.value || !data.value) {
117
136
  const statusCode = error.value?.statusCode ?? 404;
@@ -125,50 +144,35 @@ if (!isNew) {
125
144
  }
126
145
  const saving = ref(false);
127
146
  const snapshot = ref(JSON.stringify(formState.value));
128
- const dirty = computed(() => JSON.stringify(formState.value) !== snapshot.value);
129
- function onBeforeUnload(event) {
130
- event.preventDefault();
131
- }
132
- watch(dirty, (value) => {
133
- if (value) window.addEventListener("beforeunload", onBeforeUnload);
134
- else window.removeEventListener("beforeunload", onBeforeUnload);
135
- });
136
- onBeforeUnmount(() => window.removeEventListener("beforeunload", onBeforeUnload));
137
- const confirmAction = useCmsConfirm();
138
- onBeforeRouteLeave(async () => {
139
- if (dirty.value && !await confirmAction("You have unsaved changes. Leave anyway?"))
140
- return false;
141
- });
142
- const published = computed({
143
- get: () => formState.value.status === "published",
144
- set: (value) => {
145
- formState.value.status = value ? "published" : "draft";
146
- }
147
- });
148
- function togglePublished() {
149
- published.value = !published.value;
150
- }
151
- function revertStatus() {
152
- if (!drafts) return;
153
- formState.value.status = JSON.parse(snapshot.value).status ?? null;
154
- }
155
- function goBack() {
156
- navigateTo(`/cms/${name}`);
157
- }
147
+ const { entryLock } = useCmsPermissions();
148
+ const lock = computed(() => entryLock(name, config, { isNew: false }));
149
+ const locked = computed(
150
+ () => lockedFieldKeys(fields, JSON.parse(snapshot.value))
151
+ );
152
+ const formRef = ref(null);
153
+ const dirty = computed(() => !lock.value && JSON.stringify(formState.value) !== snapshot.value);
154
+ useLeaveGuard(dirty);
158
155
  async function save() {
159
156
  saving.value = true;
157
+ const submitted = cloneState(formState.value);
160
158
  try {
161
- await (isNew ? cmsApi(endpoint, { method: "POST", body: formState.value }) : cmsApi(`${endpoint}/${id}`, { method: "PUT", body: formState.value }));
162
- snapshot.value = JSON.stringify(formState.value);
163
- toast.add({ title: "Saved", color: "success" });
164
- goBack();
165
- } catch (error) {
166
- revertStatus();
167
- toast.add({
168
- title: "Save failed",
169
- description: errorMessage(error),
170
- color: "error"
159
+ const row = await cmsApi(`${endpoint}/${id}`, {
160
+ method: "PUT",
161
+ body: submitted
171
162
  });
163
+ const saved = pickFields(row ?? submitted);
164
+ const merged = mergeSavedState(submitted, formState.value, saved);
165
+ snapshot.value = JSON.stringify(saved);
166
+ formState.value = merged.state;
167
+ if (merged.clean) {
168
+ toast.success("Saved");
169
+ await navigateTo(`/cms/${name}`);
170
+ return;
171
+ }
172
+ toast.success("Saved", { description: "Changes made while saving are not saved yet." });
173
+ } catch (error) {
174
+ formRef.value?.setIssues(errorIssues(error));
175
+ toast.error("Save failed", { description: errorMessage(error) });
172
176
  } finally {
173
177
  saving.value = false;
174
178
  }
@@ -1,11 +1,24 @@
1
1
  <template>
2
- <div />
2
+ <div class="k-page">
3
+ <KEmptyState
4
+ :icon="cmsIcon(access?.known === false ? 'exclamation-triangle' : 'lock-closed')"
5
+ :title="access?.known === false ? 'Your role no longer exists' : 'Nothing to show yet'"
6
+ :description="
7
+ access?.known === false ? `Your account has the role \'${access.role}\', which is not configured anymore, so it grants no access. Ask an administrator to assign you a new role.` : 'Your role does not give access to any section. Ask an administrator for more permissions.'
8
+ "
9
+ fill
10
+ />
11
+ </div>
3
12
  </template>
4
13
 
5
14
  <script setup>
6
- import { definePageMeta, navigateTo } from "#imports";
7
- import cmsConfig from "#cms-config";
15
+ import { EmptyState as KEmptyState } from "@xleddyl/443-components-vue";
16
+ import { definePageMeta, navigateTo, useRoute } from "#imports";
17
+ import { useCmsPermissions } from "../composables/cms-permissions";
18
+ import { cmsIcon } from "../utils/ui";
8
19
  definePageMeta({ layout: "cms-admin", middleware: "cms-auth" });
9
- const first = Object.keys(cmsConfig)[0];
10
- await navigateTo(first ? `/cms/${first}` : "/cms/media", { replace: true });
20
+ const route = useRoute();
21
+ const { access, firstPath } = useCmsPermissions();
22
+ const first = firstPath();
23
+ if (first) await navigateTo({ path: first, query: route.query }, { replace: true });
11
24
  </script>