@xleddyl/nuxt-cms 0.9.4 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 -219
  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 -147
  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 +19 -11
  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 +40 -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 +8 -8
  124. package/dist/runtime/app/components/cms/TranslationBadges.d.vue.ts +7 -0
  125. package/dist/runtime/app/components/cms/TranslationBadges.vue +44 -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 +1153 -338
  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 +191 -54
  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
@@ -1,123 +1,142 @@
1
1
  <template>
2
2
  <KStack gap="sm" class="cms-blocks-field">
3
- <div class="cms-blocks-grid">
4
- <div
5
- v-for="(item, index) in items"
6
- :key="uidFor(item)"
7
- class="cms-block-tile"
8
- :class="{
3
+ <KTileGrid
4
+ :items="items"
5
+ :item-key="(item2) => uidFor(item2)"
6
+ :item-label="barTitle"
7
+ :label="field.label"
8
+ :columns="4"
9
+ min-item-width="9rem"
10
+ sortable
11
+ :handle="mediaOnly ? void 0 : '[data-cms-grip]'"
12
+ :disabled="disabled"
13
+ @update:items="onReorder"
14
+ @keydown="onTileKey"
15
+ @copy="onClipboard"
16
+ @cut="onClipboard"
17
+ @paste="onClipboard"
18
+ >
19
+ <template #tile="{ item, index }">
20
+ <KCard
21
+ variant="outline"
22
+ padding="none"
23
+ :animated="false"
24
+ class="cms-block-tile"
25
+ :class="{
9
26
  'is-muted': isHidden(item),
10
- 'is-dragging': dragIndex === index,
11
- 'is-drop-target': dropIndex === index && dragIndex !== index,
12
27
  'is-missing': hasMissingMedia(item),
13
- 'is-media-only': mediaOnly
28
+ 'is-invalid': tileInvalid(index)
14
29
  }"
15
- draggable="true"
16
- :tabindex="mediaOnly ? 0 : void 0"
17
- :aria-label="mediaOnly ? `${barTitle(item)}, use the arrow keys to reorder` : void 0"
18
- @dragstart="onDragStart(index, $event)"
19
- @dragover.prevent="dropIndex = index"
20
- @dragleave="onDragLeave(index)"
21
- @drop.prevent="onDrop(index)"
22
- @dragend="onDragEnd"
23
- @keydown.self="onTileKey($event, index)"
24
- >
25
- <component
26
- :is="mediaOnly ? 'div' : 'button'"
27
- :type="mediaOnly ? void 0 : 'button'"
28
- class="cms-block-preview"
29
- :aria-label="mediaOnly ? void 0 : `Edit ${labelOf(item)}`"
30
- @click="onPreviewClick(index)"
30
+ :data-cms-tile="index"
31
31
  >
32
- <img
33
- v-if="previewOf(item)?.kind === 'image'"
34
- :src="previewOf(item).url"
35
- alt=""
36
- loading="lazy"
37
- />
38
- <video
39
- v-else-if="previewOf(item)?.kind === 'video'"
40
- :src="previewOf(item).url"
41
- preload="metadata"
42
- muted
43
- playsinline
44
- />
45
- <span v-else-if="summaryOf(item)" class="cms-block-summary">
46
- {{ summaryOf(item) }}
47
- </span>
48
- <KIcon v-else :name="cmsIcon(iconOf(item))" class="cms-block-icon" />
49
-
50
- <KIcon v-if="isHidden(item)" name="eye-slash" class="cms-block-hidden" />
51
-
52
- <template v-if="mediaOnly">
53
- <span
54
- v-if="hasMissingMedia(item)"
55
- class="cms-block-missing"
56
- title="Not found in the media library"
57
- >
58
- <KIcon name="exclamation-triangle" />
59
- </span>
60
- <button
61
- type="button"
62
- class="cms-block-remove"
63
- :aria-label="`Remove ${barTitle(item)}`"
64
- title="Remove"
65
- @click.stop="remove(index)"
32
+ <component
33
+ :is="mediaOnly ? 'div' : 'button'"
34
+ :type="mediaOnly ? void 0 : 'button'"
35
+ class="cms-tile-preview"
36
+ :aria-label="mediaOnly ? void 0 : `Edit ${labelOf(item)}`"
37
+ @click="onPreviewClick(index)"
38
+ >
39
+ <KThumbnail
40
+ :src="previewOf(item)?.url"
41
+ :kind="previewOf(item)?.kind ?? 'icon'"
42
+ :icon="previewOf(item) ? void 0 : cmsIcon(iconOf(item))"
43
+ :aspect="mediaOnly ? 1 : '4/3'"
44
+ rounded="none"
45
+ :missing="mediaKeys.isMissing(mediaKeyOf(item))"
46
+ missing-text="Not found in the media library"
47
+ :reveal="['top-end']"
66
48
  >
67
- <KIcon name="x-mark" />
68
- </button>
69
- </template>
70
- </component>
49
+ <template v-if="!previewOf(item) && summaryOf(item)" #default>
50
+ <span class="cms-block-summary">{{ summaryOf(item) }}</span>
51
+ </template>
52
+ <template v-if="isHidden(item)" #top-start>
53
+ <KIcon name="eye-slash" label="Hidden" />
54
+ </template>
55
+ <template v-if="mediaOnly && !disabled" #top-end>
56
+ <KButton
57
+ icon="x-mark"
58
+ size="xs"
59
+ variant="solid"
60
+ tone="neutral"
61
+ square
62
+ tooltip="Remove"
63
+ :aria-label="`Remove ${barTitle(item)}`"
64
+ @click.stop="remove(index)"
65
+ />
66
+ </template>
67
+ </KThumbnail>
68
+ </component>
71
69
 
72
- <div v-if="!mediaOnly" class="cms-block-bar">
73
- <button
74
- type="button"
75
- class="cms-block-grip"
76
- :aria-label="`Reorder ${labelOf(item)}, use the arrow keys`"
77
- @keydown.left.prevent="index > 0 && move(index, -1)"
78
- @keydown.right.prevent="index < items.length - 1 && move(index, 1)"
79
- >
80
- <KIcon :name="cmsIcon('bars-2')" />
81
- </button>
82
- <span class="cms-block-bar-name" :title="barTitle(item)">{{ barLabel(item) }}</span>
83
- <span
84
- v-if="hasMissingMedia(item)"
85
- class="cms-block-missing-icon"
86
- title="Not found in the media library"
87
- >
88
- <KIcon name="exclamation-triangle" />
89
- </span>
90
- <KButton
91
- icon="trash"
92
- size="xs"
93
- variant="ghost"
94
- tone="danger"
95
- aria-label="Remove block"
96
- @click="remove(index)"
97
- />
98
- </div>
99
- </div>
70
+ <div v-if="!mediaOnly" class="cms-tile-bar">
71
+ <KButton
72
+ v-if="!disabled"
73
+ data-cms-grip
74
+ :icon="cmsIcon('bars-2')"
75
+ size="xs"
76
+ variant="ghost"
77
+ tone="neutral"
78
+ :aria-label="`Reorder ${labelOf(item)}, Alt with the arrow keys moves it`"
79
+ tooltip="Drag to reorder"
80
+ />
81
+ <KTooltip :text="barTitle(item)">
82
+ <span class="cms-tile-name">{{ barLabel(item) }}</span>
83
+ </KTooltip>
84
+ <KTooltip v-if="hasMissingMedia(item)" text="Not found in the media library">
85
+ <span class="cms-block-missing-icon">
86
+ <KIcon name="exclamation-triangle" label="Not found in the media library" />
87
+ </span>
88
+ </KTooltip>
89
+ <KDropdownMenu :items="tileMenu(index)" placement="bottom-end">
90
+ <KButton
91
+ :icon="cmsIcon('ellipsis-horizontal')"
92
+ size="xs"
93
+ variant="ghost"
94
+ tone="neutral"
95
+ :aria-label="`${labelOf(item)} actions`"
96
+ />
97
+ </KDropdownMenu>
98
+ <KButton
99
+ v-if="!disabled"
100
+ icon="trash"
101
+ size="xs"
102
+ variant="ghost"
103
+ tone="danger"
104
+ aria-label="Remove block"
105
+ @click="remove(index)"
106
+ />
107
+ </div>
108
+ </KCard>
109
+ </template>
100
110
 
101
- <KDropdownMenu v-if="canAdd && addItems.length > 1" :items="addItems">
102
- <button type="button" class="cms-block-add">
103
- <KIcon name="plus" />
104
- <span>Add</span>
105
- </button>
106
- </KDropdownMenu>
107
- <button
108
- v-else
109
- type="button"
110
- class="cms-block-add"
111
- :disabled="!canAdd"
112
- :title="canAdd ? void 0 : countHint"
113
- @click="addFirst"
114
- >
115
- <KIcon name="plus" />
116
- <span>{{ mediaOnly ? addMediaLabel : "Add" }}</span>
117
- </button>
118
- </div>
111
+ <template #add>
112
+ <CmsBlockPickerPopover
113
+ v-if="
114
+ canAdd && !disabled && !mediaOnly && (Object.keys(blocks).length > 1 || presets.length)
115
+ "
116
+ v-model:open="pickerMenuOpen"
117
+ :blocks="blocks"
118
+ :paste-count="pasteCount"
119
+ :presets="presets"
120
+ @pick="add"
121
+ @paste="paste(items.length, null)"
122
+ @preset="insertPreset"
123
+ >
124
+ <KCard variant="dashed" as="button" icon="plus" title="Add" />
125
+ </CmsBlockPickerPopover>
126
+ <KCard
127
+ v-else
128
+ variant="dashed"
129
+ as="button"
130
+ icon="plus"
131
+ :title="mediaOnly ? addMediaLabel : 'Add'"
132
+ :disabled="!canAdd || disabled"
133
+ :disabled-reason="disabled ? void 0 : fullReason"
134
+ @click="addFirst"
135
+ />
136
+ </template>
137
+ </KTileGrid>
119
138
 
120
- <p v-if="countHint" class="cms-field-hint">{{ countHint }}</p>
139
+ <KFieldMessage v-if="countHint" tone="neutral" :icon="false" :message="countHint" />
121
140
 
122
141
  <div v-if="visual">
123
142
  <KButton
@@ -145,6 +164,7 @@
145
164
  :field-key="fieldKey"
146
165
  :fields="{ [fieldKey]: field }"
147
166
  :title="field.label"
167
+ :readonly="disabled"
148
168
  @update:state="onVisualUpdate"
149
169
  >
150
170
  <template #start>
@@ -154,7 +174,6 @@
154
174
  variant="ghost"
155
175
  tone="neutral"
156
176
  aria-label="Back to the form"
157
- title="Back to the form"
158
177
  @click="visualOpen = false"
159
178
  />
160
179
  </template>
@@ -164,6 +183,15 @@
164
183
  </CmsVisualEditor>
165
184
  </KModal>
166
185
 
186
+ <CmsPresetModal
187
+ v-if="presetsEnabled"
188
+ v-model:open="presetOpen"
189
+ title="Save as preset"
190
+ submit-label="Save preset"
191
+ :hint="presetHint"
192
+ :save="savePreset"
193
+ />
194
+
167
195
  <KModal v-if="mediaOnly" v-model:open="pickerOpen" title="Media" size="lg">
168
196
  <CmsMediaGallery
169
197
  v-model:folder="pickerFolder"
@@ -174,26 +202,49 @@
174
202
  />
175
203
  </KModal>
176
204
 
177
- <KModal v-model:open="editorOpen" :title="editorTitle">
178
- <KStack gap="md">
179
- <KFormField
180
- v-for="(blockField, blockKey) in editorFields"
181
- :key="blockKey"
182
- :label="blockField.label"
183
- :icon="cmsIcon(fieldIcon(blockField))"
184
- :required="blockField.required"
205
+ <KModal
206
+ v-if="!nested"
207
+ v-model:open="editorOpen"
208
+ :title="editorTitle"
209
+ :trail="editorTrail"
210
+ :size="editorWide ? 'lg' : 'md'"
211
+ @navigate="navigateEditor"
212
+ >
213
+ <CmsBlockEditorProvider :scope="editorScope">
214
+ <CmsFormSections
215
+ v-if="editorBlock"
216
+ :key="editorKey"
217
+ :layout="editorLayout"
218
+ :shown="isEditorFieldShown"
185
219
  >
186
- <CmsFieldInput
187
- :model-value="editorItem?.[blockKey]"
188
- :field="blockField"
189
- :locale="locale"
190
- @update:model-value="(value) => updateEditorField(blockKey, value)"
191
- />
192
- </KFormField>
193
- </KStack>
220
+ <template #field="{ fieldKey: blockKey }">
221
+ <KFormField
222
+ :name="editorFieldName(blockKey)"
223
+ :label="editorBlock.fields[blockKey].label"
224
+ :help="editorBlock.fields[blockKey].description"
225
+ :required="editorBlock.fields[blockKey].required"
226
+ >
227
+ <CmsFieldInput
228
+ :model-value="editorItem?.[blockKey]"
229
+ :field="editorBlock.fields[blockKey]"
230
+ :locale="locale"
231
+ :entry-name="entryName"
232
+ :field-key="
233
+ editorBlock.fields[blockKey].type === 'blocks' ? blockKey : void 0
234
+ "
235
+ @update:model-value="(value) => updateEditorField(blockKey, value)"
236
+ />
237
+ </KFormField>
238
+ </template>
239
+ </CmsFormSections>
240
+ </CmsBlockEditorProvider>
194
241
 
195
242
  <template #footer>
243
+ <KStack v-if="disabled" direction="row" justify="end">
244
+ <KButton label="Done" @click="editorOpen = false" />
245
+ </KStack>
196
246
  <KStack
247
+ v-else
197
248
  direction="row"
198
249
  justify="between"
199
250
  gap="sm"
@@ -206,7 +257,7 @@
206
257
  variant="soft"
207
258
  tone="neutral"
208
259
  aria-label="Move earlier"
209
- :disabled="editorIndex === 0"
260
+ :disabled="editorPosition === 0"
210
261
  @click="moveCurrent(-1)"
211
262
  />
212
263
  <KButton
@@ -214,7 +265,7 @@
214
265
  variant="soft"
215
266
  tone="neutral"
216
267
  aria-label="Move later"
217
- :disabled="editorIndex === items.length - 1"
268
+ :disabled="editorPosition === editorList.length - 1"
218
269
  @click="moveCurrent(1)"
219
270
  />
220
271
  <KButton
@@ -222,11 +273,11 @@
222
273
  :icon="cmsIcon('document-duplicate')"
223
274
  variant="soft"
224
275
  tone="neutral"
225
- :disabled="!canAdd"
276
+ :disabled="!editorCanAdd"
226
277
  @click="duplicateCurrent"
227
278
  />
228
279
  <KButton
229
- v-if="editorItem && !declaresHiddenField(blockOf(editorItem))"
280
+ v-if="editorItem && !declaresHiddenField(editorBlock)"
230
281
  :label="isHidden(editorItem) ? 'Show' : 'Hide'"
231
282
  :icon="cmsIcon(isHidden(editorItem) ? 'eye' : 'eye-slash')"
232
283
  variant="soft"
@@ -252,10 +303,25 @@
252
303
 
253
304
  <script setup>
254
305
  import {
306
+ blockAt,
307
+ blockFormLayout,
308
+ blockListAt,
309
+ blockListPath,
310
+ blockPathIndex,
311
+ blockSchemaPath,
255
312
  canAddBlock,
313
+ formatBlockPath,
314
+ parseBlockPath,
315
+ insertBlocks,
316
+ moveBlock,
317
+ removeBlock,
318
+ resolveBlockPath,
319
+ updateBlockAt,
256
320
  declaresHiddenField,
321
+ isFieldVisible,
257
322
  isHiddenBlock,
258
323
  isTranslatableMediaField,
324
+ linkDisplayText,
259
325
  mediaFilename,
260
326
  mediaIconFor,
261
327
  mediaPublicUrl,
@@ -264,16 +330,33 @@ import {
264
330
  } from "#nuxt-cms";
265
331
  import {
266
332
  Button as KButton,
333
+ Card as KCard,
267
334
  DropdownMenu as KDropdownMenu,
335
+ FieldMessage as KFieldMessage,
268
336
  FormField as KFormField,
269
337
  Icon as KIcon,
270
338
  Modal as KModal,
271
- Stack as KStack
339
+ Stack as KStack,
340
+ Thumbnail as KThumbnail,
341
+ TileGrid as KTileGrid,
342
+ Tooltip as KTooltip,
343
+ useFieldControl,
344
+ useFormContext,
345
+ useRouteState
272
346
  } from "@xleddyl/443-components-vue";
273
- import { computed, ref, useState } from "#imports";
347
+ import { computed, inject, nextTick, onMounted, ref, useState, watch } from "#imports";
348
+ import { copyNotice, pasteBlocks, pasteNotice, pasteableCount } from "../../../shared/clipboard";
349
+ import { CMS_PRESET_ICON, presetInsertNotice } from "../../../shared/presets";
350
+ import { blockSummary, clipboardActionApplies, emptyBlock } from "../../../shared/preview";
351
+ import { useCmsClipboard } from "../../composables/cms-clipboard";
274
352
  import { useCmsMediaKeys } from "../../composables/cms-media-keys";
353
+ import { useCmsPermissions } from "../../composables/cms-permissions";
354
+ import { useCmsPresets } from "../../composables/cms-presets";
275
355
  import { useCmsRuntime } from "../../composables/cms-runtime";
276
- import { cmsIcon, fieldIcon } from "../../utils/ui";
356
+ import { useCmsOverlayFlag } from "../../composables/cms-route";
357
+ import { CMS_BLOCK_EDITOR, CmsBlockEditorProvider } from "../../utils/block-editor";
358
+ import { clipboardTarget } from "../../utils/keyboard";
359
+ import { cmsIcon } from "../../utils/ui";
277
360
  const props = defineProps({
278
361
  field: { type: Object, required: true },
279
362
  locale: { type: String, required: false },
@@ -283,9 +366,38 @@ const props = defineProps({
283
366
  const model = defineModel({ type: [Array, null], ...{ required: true } });
284
367
  const { mediaBaseUrl, i18n } = useCmsRuntime();
285
368
  const mediaKeys = useCmsMediaKeys();
369
+ const { disabled } = useFieldControl();
370
+ const parentEditor = inject(CMS_BLOCK_EDITOR, null);
371
+ const nested = computed(() => !!parentEditor && !!props.fieldKey);
372
+ const nestedListPath = computed(
373
+ () => parentEditor && props.fieldKey ? [...parentEditor.path.value, props.fieldKey] : null
374
+ );
375
+ const presetFieldKey = computed(() => {
376
+ if (!nested.value) return props.fieldKey;
377
+ const schema = parentEditor?.schemaPath.value;
378
+ return schema && props.fieldKey ? [...schema, props.fieldKey].join(".") : void 0;
379
+ });
380
+ const form = useFormContext();
381
+ const formPath = computed(() => {
382
+ if (!nested.value) return props.fieldKey ?? null;
383
+ const base = parentEditor?.formPath.value;
384
+ return base && nestedListPath.value ? `${base}.${formatBlockPath(nestedListPath.value)}` : null;
385
+ });
386
+ function tileInvalid(index) {
387
+ const base = formPath.value;
388
+ if (!base || !form) return false;
389
+ const prefix = `${base}.${index}`;
390
+ return Object.entries(form.errors).some(
391
+ ([key, message]) => !!message && (key === prefix || key.startsWith(`${prefix}.`))
392
+ );
393
+ }
286
394
  const blocks = computed(() => props.field.blocks ?? {});
287
395
  const items = computed(() => Array.isArray(model.value) ? model.value : []);
288
396
  const canAdd = computed(() => canAddBlock(props.field, items.value.length));
397
+ const fullReason = computed(() => {
398
+ const max = props.field.max ?? 0;
399
+ return canAdd.value ? void 0 : `This field holds at most ${blockCount(max)}.`;
400
+ });
289
401
  function blockCount(count) {
290
402
  return `${count} ${count === 1 ? "block" : "blocks"}`;
291
403
  }
@@ -310,10 +422,11 @@ const mediaOnly = computed(() => {
310
422
  });
311
423
  const visual = computed(() => {
312
424
  const list = Object.values(blocks.value);
313
- return !mediaOnly.value && !!props.entryName && !!props.fieldKey && !!list.length && list.every((block) => typeof block.component === "string" && !!block.component.trim());
425
+ return !mediaOnly.value && !nested.value && !!props.entryName && !!props.fieldKey && !!list.length && list.every((block) => typeof block.component === "string" && !!block.component.trim());
314
426
  });
315
- const visualOpen = ref(false);
427
+ const visualOpen = useCmsOverlayFlag("visual", !!props.fieldKey, props.fieldKey);
316
428
  function onVisualUpdate(state) {
429
+ if (disabled.value) return;
317
430
  const value = props.fieldKey ? state[props.fieldKey] : null;
318
431
  model.value = Array.isArray(value) && value.length ? value : null;
319
432
  }
@@ -325,7 +438,7 @@ const pickerOpen = ref(false);
325
438
  const pickerFolder = useState("cms-media-picker-folder", () => null);
326
439
  function addMedia(media) {
327
440
  const target = mediaOnly.value;
328
- if (!target || !canAdd.value) return;
441
+ if (!target || !canAdd.value || disabled.value) return;
329
442
  mediaKeys.remember(media.key);
330
443
  model.value = [...items.value, { type: target.type, [target.key]: media.key }];
331
444
  pickerOpen.value = false;
@@ -383,45 +496,49 @@ function iconOf(item) {
383
496
  return key ? mediaIconFor(mediaTypeForKey(key)) : "rectangle-stack";
384
497
  }
385
498
  function summaryOf(item) {
499
+ const locale = props.locale ?? i18n.defaultLocale;
386
500
  for (const [key, field] of Object.entries(blockOf(item)?.fields ?? {})) {
501
+ if (field.type === "link") {
502
+ const text2 = linkDisplayText(item[key], locale, i18n.defaultLocale);
503
+ if (text2) return text2.length > 80 ? `${text2.slice(0, 77)}\u2026` : text2;
504
+ continue;
505
+ }
387
506
  if (field.type !== "text" && field.type !== "richtext") continue;
388
507
  const raw = item[key];
389
- const value = typeof raw === "string" ? raw : raw?.[props.locale ?? i18n.defaultLocale] ?? "";
508
+ const value = typeof raw === "string" ? raw : raw?.[locale] ?? "";
390
509
  const text = value.replace(/<[^>]*>/g, " ").trim();
391
510
  if (text) return text.length > 80 ? `${text.slice(0, 77)}\u2026` : text;
392
511
  }
393
- return "";
512
+ const block = blockOf(item);
513
+ const hasNested = Object.values(block?.fields ?? {}).some((field) => field.type === "blocks");
514
+ return hasNested ? blockSummary(block, item, locale, i18n.defaultLocale) : "";
394
515
  }
395
516
  function isHidden(item) {
396
517
  return isHiddenBlock(item);
397
518
  }
398
- function updateField(index, key, value) {
399
- const current = items.value[index];
400
- if (!current) return;
401
- const updated = { ...current, [key]: value };
402
- uids.set(updated, uidFor(current));
403
- model.value = items.value.map((item, i) => i === index ? updated : item);
404
- }
405
519
  function add(type) {
406
520
  const block = blocks.value[type];
407
- if (!block || !canAdd.value) return;
408
- const empty = Object.fromEntries(Object.keys(block.fields).map((k) => [k, null]));
521
+ if (!block || !canAdd.value || disabled.value) return;
409
522
  const index = items.value.length;
410
- model.value = [...items.value, { type, ...empty }];
523
+ model.value = [...items.value, emptyBlock(type, block)];
411
524
  openEditor(index);
412
525
  }
413
- function onTileKey(event, index) {
414
- if (!mediaOnly.value) return;
415
- const action = event.key === "ArrowLeft" ? () => move(index, -1) : event.key === "ArrowRight" ? () => move(index, 1) : event.key === "Delete" || event.key === "Backspace" ? () => remove(index) : null;
416
- if (!action) return;
526
+ function onReorder(next) {
527
+ if (!disabled.value) model.value = next;
528
+ }
529
+ function onTileKey(event) {
530
+ if (!mediaOnly.value || disabled.value || event.altKey) return;
531
+ if (event.key !== "Delete" && event.key !== "Backspace") return;
532
+ const index = focusedTileIndex(event.target);
533
+ if (index === null) return;
417
534
  event.preventDefault();
418
- action();
535
+ remove(index);
419
536
  }
420
537
  function onPreviewClick(index) {
421
538
  if (!mediaOnly.value) openEditor(index);
422
539
  }
423
540
  function addFirst() {
424
- if (!canAdd.value) return;
541
+ if (!canAdd.value || disabled.value) return;
425
542
  if (mediaOnly.value) {
426
543
  pickerOpen.value = true;
427
544
  return;
@@ -430,96 +547,329 @@ function addFirst() {
430
547
  if (first) add(first);
431
548
  }
432
549
  function remove(index) {
550
+ if (disabled.value) return;
433
551
  const next = items.value.filter((_, i) => i !== index);
434
552
  model.value = next.length ? next : null;
435
553
  }
436
554
  function duplicate(index) {
437
555
  const current = items.value[index];
438
- if (!current || !canAdd.value) return;
439
- const copy = JSON.parse(JSON.stringify(current));
440
- const next = [...items.value];
441
- next.splice(index + 1, 0, copy);
442
- model.value = next;
443
- }
444
- function move(index, delta) {
445
- moveTo(index, index + delta);
446
- }
447
- function moveTo(from, to) {
448
- if (from === to || to < 0 || to >= items.value.length) return;
556
+ if (!current || !canAdd.value || disabled.value) return;
557
+ const copy2 = JSON.parse(JSON.stringify(current));
449
558
  const next = [...items.value];
450
- const [item] = next.splice(from, 1);
451
- next.splice(to, 0, item);
559
+ next.splice(index + 1, 0, copy2);
452
560
  model.value = next;
453
561
  }
454
- const dragIndex = ref(null);
455
- const dropIndex = ref(null);
456
- function onDragStart(index, event) {
457
- dragIndex.value = index;
458
- if (event.dataTransfer) {
459
- event.dataTransfer.effectAllowed = "move";
460
- event.dataTransfer.setData("text/plain", String(index));
562
+ const ROOT = "_";
563
+ const localEditorPath = ref(null);
564
+ const editorRoute = useRouteState("editblock", { location: "hash" });
565
+ const editorRouted = computed(() => !nested.value && !!props.fieldKey);
566
+ const editorPath = computed({
567
+ get: () => {
568
+ if (!editorRouted.value) return localEditorPath.value;
569
+ const path = parseBlockPath(editorRoute.value);
570
+ return path && path[0] === props.fieldKey && path.length > 1 ? path.slice(1) : null;
571
+ },
572
+ set: (path) => {
573
+ if (!editorRouted.value) localEditorPath.value = path;
574
+ else editorRoute.value = path ? formatBlockPath([props.fieldKey, ...path]) : void 0;
461
575
  }
576
+ });
577
+ const rootFields = computed(() => ({ [ROOT]: props.field }));
578
+ const rootValues = computed(() => ({ [ROOT]: items.value }));
579
+ function fullPath(path) {
580
+ return [ROOT, ...path];
462
581
  }
463
- function onDragLeave(index) {
464
- if (dropIndex.value === index) dropIndex.value = null;
465
- }
466
- function onDrop(index) {
467
- if (dragIndex.value !== null) moveTo(dragIndex.value, index);
468
- onDragEnd();
469
- }
470
- function onDragEnd() {
471
- dragIndex.value = null;
472
- dropIndex.value = null;
473
- }
474
- const addItems = computed(
475
- () => Object.entries(blocks.value).map(([type, block]) => ({
476
- label: block.label,
477
- onSelect: () => add(type)
478
- }))
582
+ const editorLocation = computed(
583
+ () => editorPath.value ? resolveBlockPath(rootFields.value, rootValues.value, fullPath(editorPath.value)) : null
479
584
  );
480
- const editorIndex = ref(null);
481
585
  const editorOpen = computed({
482
- get: () => editorIndex.value !== null,
586
+ get: () => editorPath.value !== null,
483
587
  set: (value) => {
484
- if (!value) editorIndex.value = null;
588
+ if (!value) editorPath.value = null;
485
589
  }
486
590
  });
487
- const editorItem = computed(
488
- () => editorIndex.value === null ? void 0 : items.value[editorIndex.value]
591
+ const editorItem = computed(() => editorLocation.value?.item);
592
+ const editorBlock = computed(() => editorLocation.value?.block);
593
+ const editorPosition = computed(() => editorLocation.value?.index ?? -1);
594
+ const editorList = computed(
595
+ () => editorPath.value ? blockListAt(rootValues.value, blockListPath(fullPath(editorPath.value))) ?? [] : []
596
+ );
597
+ const editorCanAdd = computed(
598
+ () => editorLocation.value ? canAddBlock(editorLocation.value.field, editorList.value.length) : canAdd.value
489
599
  );
490
- const editorFields = computed(() => editorItem.value ? blockOf(editorItem.value)?.fields : {});
491
- const editorTitle = computed(() => editorItem.value ? labelOf(editorItem.value) : "");
600
+ const editorKey = computed(() => editorPath.value ? formatBlockPath(editorPath.value) : "");
601
+ const editorLayout = computed(
602
+ () => editorBlock.value ? blockFormLayout(editorBlock.value) : { main: [] }
603
+ );
604
+ const editorWide = computed(
605
+ () => editorLayout.value.main.some((section) => section.rows.some((row) => row.length > 1)) || Object.values(editorBlock.value?.fields ?? {}).some((field) => field.type === "blocks")
606
+ );
607
+ function isEditorFieldShown(key) {
608
+ const fields = editorBlock.value?.fields ?? {};
609
+ const field = fields[key];
610
+ return !!field && isFieldVisible(field, editorItem.value, fields);
611
+ }
612
+ function itemLabel(item, block) {
613
+ return block?.label ?? String(item.type);
614
+ }
615
+ const editorTitle = computed(
616
+ () => editorItem.value ? itemLabel(editorItem.value, editorBlock.value) : ""
617
+ );
618
+ const editorAncestors = computed(() => {
619
+ const path = editorPath.value;
620
+ if (!path) return [];
621
+ const ancestors = [];
622
+ for (let end = 1; end < path.length - 1; end += 2) ancestors.push(path.slice(0, end));
623
+ return ancestors;
624
+ });
625
+ const editorTrail = computed(
626
+ () => editorAncestors.value.map((path) => {
627
+ const location = resolveBlockPath(rootFields.value, rootValues.value, fullPath(path));
628
+ return location ? itemLabel(location.item, location.block) : "";
629
+ })
630
+ );
631
+ function navigateEditor(index) {
632
+ const target = editorAncestors.value[index];
633
+ if (target) editorPath.value = target;
634
+ }
635
+ const editorScope = {
636
+ path: computed(() => editorPath.value ?? []),
637
+ schemaPath: computed(() => {
638
+ if (!editorPath.value || !props.fieldKey) return null;
639
+ const schema = blockSchemaPath(rootFields.value, rootValues.value, fullPath(editorPath.value));
640
+ const type = editorItem.value?.type;
641
+ return schema && type ? [props.fieldKey, ...schema.slice(1), String(type)] : null;
642
+ }),
643
+ formPath: computed(() => props.fieldKey && !nested.value ? props.fieldKey : null),
644
+ open: (path) => {
645
+ editorPath.value = path;
646
+ }
647
+ };
648
+ watch(editorLocation, (location) => {
649
+ if (location || !editorPath.value) return;
650
+ const path = editorPath.value;
651
+ void nextTick(() => {
652
+ if (editorPath.value === path && !blockAt(rootValues.value, fullPath(path)))
653
+ editorPath.value = null;
654
+ });
655
+ });
656
+ function editorFieldName(key) {
657
+ const base = editorScope.formPath.value;
658
+ return base && editorPath.value ? `${base}.${formatBlockPath(editorPath.value)}.${key}` : void 0;
659
+ }
660
+ function commitRoot(values) {
661
+ const next = values[ROOT];
662
+ const list = Array.isArray(next) ? next : [];
663
+ const previous = items.value;
664
+ if (list.length === previous.length) {
665
+ list.forEach((item, index) => {
666
+ const before = previous[index];
667
+ if (before && item !== before && !uids.has(item)) uids.set(item, uidFor(before));
668
+ });
669
+ }
670
+ model.value = list.length ? list : null;
671
+ }
492
672
  function openEditor(index) {
493
- editorIndex.value = index;
673
+ if (nested.value && parentEditor && nestedListPath.value) {
674
+ parentEditor.open([...nestedListPath.value, index]);
675
+ return;
676
+ }
677
+ editorPath.value = [index];
494
678
  }
495
679
  function updateEditorField(key, value) {
496
- if (editorIndex.value === null) return;
497
- updateField(editorIndex.value, key, value);
680
+ if (!editorPath.value || disabled.value) return;
681
+ commitRoot(
682
+ updateBlockAt(rootValues.value, fullPath(editorPath.value), (item) => ({
683
+ ...item,
684
+ [key]: value
685
+ }))
686
+ );
687
+ }
688
+ function leaveEditorItem() {
689
+ const path = editorPath.value;
690
+ editorPath.value = path && path.length > 2 ? path.slice(0, -2) : null;
498
691
  }
499
692
  function removeCurrent() {
500
- if (editorIndex.value === null) return;
501
- remove(editorIndex.value);
502
- editorIndex.value = null;
693
+ if (!editorPath.value || disabled.value) return;
694
+ const path = editorPath.value;
695
+ commitRoot(removeBlock(rootValues.value, fullPath(path)));
696
+ leaveEditorItem();
503
697
  }
504
698
  function moveCurrent(delta) {
505
- if (editorIndex.value === null) return;
506
- const next = editorIndex.value + delta;
507
- move(editorIndex.value, delta);
508
- editorIndex.value = next;
699
+ const path = editorPath.value;
700
+ if (!path || disabled.value) return;
701
+ const index = blockPathIndex(path);
702
+ const target = index + delta;
703
+ if (target < 0 || target >= editorList.value.length) return;
704
+ const full = fullPath(path);
705
+ commitRoot(
706
+ moveBlock(rootValues.value, full, blockListPath(full), delta > 0 ? target + 1 : target)
707
+ );
708
+ editorPath.value = [...path.slice(0, -1), target];
509
709
  }
510
710
  function toggleHiddenCurrent() {
511
- if (editorIndex.value === null || !editorItem.value) return;
512
- const index = editorIndex.value;
513
- const current = items.value[index];
514
- if (!current) return;
515
- const { hidden: _hidden, ...rest } = current;
516
- const updated = isHidden(current) ? rest : { ...rest, hidden: true };
517
- uids.set(updated, uidFor(current));
518
- model.value = items.value.map((item, i) => i === index ? updated : item);
711
+ if (!editorPath.value || !editorItem.value || disabled.value) return;
712
+ commitRoot(
713
+ updateBlockAt(rootValues.value, fullPath(editorPath.value), (current) => {
714
+ const { hidden: _hidden, ...rest } = current;
715
+ return isHidden(current) ? rest : { ...rest, hidden: true };
716
+ })
717
+ );
519
718
  }
520
719
  function duplicateCurrent() {
521
- if (editorIndex.value === null) return;
522
- duplicate(editorIndex.value);
523
- editorIndex.value = null;
720
+ const path = editorPath.value;
721
+ const current = editorItem.value;
722
+ if (!path || !current || !editorCanAdd.value || disabled.value) return;
723
+ const copy2 = JSON.parse(JSON.stringify(current));
724
+ const full = fullPath(path);
725
+ commitRoot(insertBlocks(rootValues.value, blockListPath(full), blockPathIndex(path) + 1, [copy2]));
726
+ leaveEditorItem();
727
+ }
728
+ const clipboard = useCmsClipboard();
729
+ const pasteCount = computed(() => pasteableCount(clipboard.items.value, props.field));
730
+ function copy(index, event) {
731
+ const item = items.value[index];
732
+ if (!item) return;
733
+ clipboard.copy([item], event);
734
+ clipboard.notify(copyNotice(1));
735
+ }
736
+ function cut(index, event) {
737
+ const item = items.value[index];
738
+ if (!item || disabled.value) return;
739
+ clipboard.copy([item], event);
740
+ remove(index);
741
+ clipboard.notify(copyNotice(1, true));
742
+ }
743
+ async function paste(index, text) {
744
+ if (disabled.value) return;
745
+ const pasted = await clipboard.read(text);
746
+ const result = pasted ? pasteBlocks(items.value, index, pasted, props.field, i18n) : null;
747
+ if (result?.pasted) model.value = result.list;
748
+ clipboard.notify(pasteNotice(result, props.field));
749
+ }
750
+ function tileIndex(tile) {
751
+ const index = tile ? Number(tile.dataset.cmsTile) : NaN;
752
+ return Number.isInteger(index) ? index : null;
753
+ }
754
+ function focusedTileIndex(target) {
755
+ return target instanceof Element ? tileIndex(target.querySelector(":scope > * > [data-cms-tile]")) : null;
756
+ }
757
+ function tileIndexOf(target) {
758
+ if (!(target instanceof Element)) return null;
759
+ return tileIndex(target.closest("[data-cms-tile]")) ?? focusedTileIndex(target);
760
+ }
761
+ function onClipboard(event) {
762
+ if (event.defaultPrevented) return;
763
+ const action = event.type;
764
+ if (disabled.value && action !== "copy") return;
765
+ const index = tileIndexOf(event.target);
766
+ const target = { ...clipboardTarget(event, index !== null), interactive: false };
767
+ if (!clipboardActionApplies(action, target)) return;
768
+ if (action === "paste") {
769
+ event.preventDefault();
770
+ void paste(
771
+ index === null ? items.value.length : index + 1,
772
+ event.clipboardData?.getData("text/plain") ?? null
773
+ );
774
+ } else if (index !== null) {
775
+ if (action === "copy") copy(index, event);
776
+ else cut(index, event);
777
+ }
778
+ }
779
+ const { can } = useCmsPermissions();
780
+ const presetsEnabled = computed(
781
+ () => !mediaOnly.value && !disabled.value && !!props.entryName && !!presetFieldKey.value && can(`${props.entryName}.update`)
782
+ );
783
+ const {
784
+ presets: fieldPresets,
785
+ load: loadPresets,
786
+ create: createPreset
787
+ } = useCmsPresets(
788
+ () => presetsEnabled.value ? props.entryName : void 0,
789
+ () => presetFieldKey.value
790
+ );
791
+ const presets = computed(() => presetsEnabled.value ? fieldPresets.value : []);
792
+ const pickerMenuOpen = ref(false);
793
+ const presetOpen = ref(false);
794
+ const presetItems = ref([]);
795
+ const presetHint = computed(() => {
796
+ const types = presetItems.value.map((item) => labelOf(item));
797
+ return `A copy of ${types.join(
798
+ ", "
799
+ )}. Insert it from the block picker of any field with the same blocks.`;
800
+ });
801
+ function openPresetSave(index) {
802
+ const item = items.value[index];
803
+ if (!item) return;
804
+ presetItems.value = [JSON.parse(JSON.stringify(item))];
805
+ presetOpen.value = true;
806
+ }
807
+ async function savePreset(details) {
808
+ const preset = await createPreset({ ...details, items: presetItems.value });
809
+ clipboard.notify({ title: `Preset "${preset.name}" saved`, color: "success" });
810
+ }
811
+ function insertPreset(id) {
812
+ const preset = presets.value.find((candidate) => candidate.id === id);
813
+ if (!preset || !canAdd.value || disabled.value) return;
814
+ const result = pasteBlocks(
815
+ items.value,
816
+ items.value.length,
817
+ preset.items,
818
+ props.field,
819
+ i18n
820
+ );
821
+ if (result.pasted) model.value = result.list;
822
+ clipboard.notify(presetInsertNotice(result, props.field, preset));
823
+ }
824
+ watch(pickerMenuOpen, (open) => {
825
+ if (open) void loadPresets();
826
+ });
827
+ onMounted(() => {
828
+ if (presetsEnabled.value) void loadPresets();
829
+ });
830
+ function tileMenu(index) {
831
+ const copyItem = {
832
+ label: "Copy",
833
+ icon: cmsIcon("clipboard-document"),
834
+ shortcut: "mod+c",
835
+ onSelect: () => copy(index)
836
+ };
837
+ if (disabled.value) return [[copyItem]];
838
+ const full = canAdd.value ? void 0 : countHint.value;
839
+ return [
840
+ [
841
+ copyItem,
842
+ {
843
+ label: "Cut",
844
+ icon: cmsIcon("scissors"),
845
+ shortcut: "mod+x",
846
+ onSelect: () => cut(index)
847
+ },
848
+ {
849
+ label: "Paste after",
850
+ icon: cmsIcon("clipboard"),
851
+ shortcut: "mod+v",
852
+ disabled: !!full,
853
+ disabledReason: full,
854
+ onSelect: () => void paste(index + 1)
855
+ }
856
+ ],
857
+ [
858
+ {
859
+ label: "Duplicate",
860
+ icon: cmsIcon("document-duplicate"),
861
+ disabled: !!full,
862
+ disabledReason: full,
863
+ onSelect: () => duplicate(index)
864
+ },
865
+ ...presetsEnabled.value ? [
866
+ {
867
+ label: "Save as preset",
868
+ icon: cmsIcon(CMS_PRESET_ICON),
869
+ onSelect: () => openPresetSave(index)
870
+ }
871
+ ] : []
872
+ ]
873
+ ];
524
874
  }
525
875
  </script>