@michaelthielemann/kestrel 4.1.3 → 5.0.2

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 (342) hide show
  1. package/README.md +26 -201
  2. package/dist/boot.d.ts +45 -0
  3. package/dist/boot.js +284 -0
  4. package/dist/cast.d.ts +3 -0
  5. package/dist/cast.js +6 -0
  6. package/dist/catalogue.d.ts +11 -0
  7. package/dist/catalogue.js +1 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +55 -0
  10. package/dist/context.d.ts +62 -0
  11. package/dist/context.js +61 -0
  12. package/dist/dataflow.d.ts +3 -0
  13. package/dist/dataflow.js +65 -0
  14. package/dist/defineConfig.d.ts +162 -0
  15. package/dist/defineConfig.js +42 -0
  16. package/dist/defineContract.d.ts +12 -0
  17. package/dist/defineContract.js +16 -0
  18. package/dist/defineModule.d.ts +75 -0
  19. package/dist/defineModule.js +9 -0
  20. package/dist/definePipeline.d.ts +7 -0
  21. package/dist/definePipeline.js +11 -0
  22. package/dist/errors.d.ts +26 -0
  23. package/dist/errors.js +48 -0
  24. package/dist/index.d.ts +19 -0
  25. package/dist/index.js +14 -0
  26. package/dist/load.d.ts +7 -0
  27. package/dist/load.js +79 -0
  28. package/dist/logger.d.ts +18 -0
  29. package/dist/logger.js +36 -0
  30. package/dist/registry.d.ts +14 -0
  31. package/dist/registry.js +87 -0
  32. package/dist/result.d.ts +19 -0
  33. package/dist/result.js +19 -0
  34. package/dist/runner.d.ts +29 -0
  35. package/dist/runner.js +133 -0
  36. package/dist/schema.d.ts +7 -0
  37. package/dist/schema.js +142 -0
  38. package/dist/sort.d.ts +2 -0
  39. package/dist/sort.js +50 -0
  40. package/dist/testing/runPipeline.d.ts +9 -0
  41. package/dist/testing/runPipeline.js +22 -0
  42. package/dist/triggers/allowlist.d.ts +9 -0
  43. package/dist/triggers/allowlist.js +49 -0
  44. package/dist/triggers/cron.d.ts +10 -0
  45. package/dist/triggers/cron.js +44 -0
  46. package/dist/triggers/cronMatch.d.ts +10 -0
  47. package/dist/triggers/cronMatch.js +68 -0
  48. package/dist/triggers/http.d.ts +59 -0
  49. package/dist/triggers/http.js +300 -0
  50. package/package.json +25 -151
  51. package/NOTICE +0 -12
  52. package/kestrel.config.ts +0 -28
  53. package/layers/access/nuxt.config.ts +0 -5
  54. package/layers/access/server/middleware/00.ip-allowlist.ts +0 -52
  55. package/layers/access/server/middleware/access-guard.ts +0 -32
  56. package/layers/access/server/types.d.ts +0 -7
  57. package/layers/admin/app/components/AdminAccount.vue +0 -212
  58. package/layers/admin/app/components/AdminNav.vue +0 -44
  59. package/layers/admin/app/components/BlockFields.vue +0 -67
  60. package/layers/admin/app/components/BlockPreview.vue +0 -413
  61. package/layers/admin/app/components/BlockTree.vue +0 -433
  62. package/layers/admin/app/components/BlocksBody.vue +0 -163
  63. package/layers/admin/app/components/CollectionDeleteDialog.vue +0 -38
  64. package/layers/admin/app/components/CollectionEditor.vue +0 -324
  65. package/layers/admin/app/components/CollectionList.vue +0 -429
  66. package/layers/admin/app/components/CollectionListBulkBar.vue +0 -62
  67. package/layers/admin/app/components/CollectionListColumnsPanel.vue +0 -37
  68. package/layers/admin/app/components/CollectionListFilterPanel.vue +0 -145
  69. package/layers/admin/app/components/CollectionListPager.vue +0 -70
  70. package/layers/admin/app/components/CollectionListTable.vue +0 -347
  71. package/layers/admin/app/components/EditorStatus.vue +0 -211
  72. package/layers/admin/app/components/EditorUnsupported.vue +0 -28
  73. package/layers/admin/app/components/FieldsBody.vue +0 -23
  74. package/layers/admin/app/components/LocaleBar.vue +0 -107
  75. package/layers/admin/app/components/PageFields.vue +0 -129
  76. package/layers/admin/app/components/PageFieldsPane.vue +0 -39
  77. package/layers/admin/app/components/SeoFields.vue +0 -210
  78. package/layers/admin/app/components/SingletonEditor.vue +0 -93
  79. package/layers/admin/app/composables/useAuth.ts +0 -68
  80. package/layers/admin/app/composables/useBlockTree.ts +0 -95
  81. package/layers/admin/app/composables/useBlocks.ts +0 -7
  82. package/layers/admin/app/composables/useCachedResource.ts +0 -35
  83. package/layers/admin/app/composables/useCollectionOps.ts +0 -102
  84. package/layers/admin/app/composables/useCollections.ts +0 -6
  85. package/layers/admin/app/composables/useContentLocales.ts +0 -23
  86. package/layers/admin/app/composables/useEditForm.ts +0 -402
  87. package/layers/admin/app/composables/useLinkResolver.ts +0 -40
  88. package/layers/admin/app/composables/useListBatchActions.ts +0 -78
  89. package/layers/admin/app/composables/useListColumns.ts +0 -53
  90. package/layers/admin/app/composables/useListFilterDraft.ts +0 -129
  91. package/layers/admin/app/composables/useListRows.ts +0 -75
  92. package/layers/admin/app/composables/useListSelection.ts +0 -28
  93. package/layers/admin/app/composables/useListUrlState.ts +0 -53
  94. package/layers/admin/app/composables/usePendingPublishPoll.ts +0 -15
  95. package/layers/admin/app/composables/usePreviewViewport.ts +0 -30
  96. package/layers/admin/app/composables/usePublishStatus.ts +0 -107
  97. package/layers/admin/app/composables/useRailCollapsed.ts +0 -14
  98. package/layers/admin/app/composables/useTheme.ts +0 -36
  99. package/layers/admin/app/composables/useToolbarPanel.ts +0 -38
  100. package/layers/admin/app/composables/useUnsavedGuard.ts +0 -22
  101. package/layers/admin/app/layouts/admin.vue +0 -266
  102. package/layers/admin/app/middleware/admin-auth.ts +0 -4
  103. package/layers/admin/app/pages/admin/[collection]/[id].vue +0 -203
  104. package/layers/admin/app/pages/admin/[collection]/index.vue +0 -62
  105. package/layers/admin/app/pages/admin/index.vue +0 -118
  106. package/layers/admin/app/pages/admin/login.vue +0 -68
  107. package/layers/admin/app/pages/admin/media.vue +0 -28
  108. package/layers/admin/app/pages/admin/references.vue +0 -146
  109. package/layers/admin/app/plugins/admin-style-guard.client.ts +0 -76
  110. package/layers/admin/app/plugins/reauth.client.ts +0 -20
  111. package/layers/admin/app/utils/admin-style-guard.ts +0 -20
  112. package/layers/admin/app/utils/block-tree.ts +0 -214
  113. package/layers/admin/app/utils/collection-ops.ts +0 -69
  114. package/layers/admin/app/utils/dead-refs.ts +0 -30
  115. package/layers/admin/app/utils/edit-form.ts +0 -267
  116. package/layers/admin/app/utils/editor-expose.ts +0 -36
  117. package/layers/admin/app/utils/editor-form-context.ts +0 -82
  118. package/layers/admin/app/utils/editor-registry.ts +0 -20
  119. package/layers/admin/app/utils/field-keys.ts +0 -11
  120. package/layers/admin/app/utils/list-cell.ts +0 -46
  121. package/layers/admin/app/utils/list-columns.ts +0 -142
  122. package/layers/admin/app/utils/list-query.ts +0 -96
  123. package/layers/admin/app/utils/nav-active.ts +0 -9
  124. package/layers/admin/app/utils/populate-blocks.ts +0 -288
  125. package/layers/admin/app/utils/preview-channel.ts +0 -62
  126. package/layers/admin/app/utils/preview-viewport.ts +0 -80
  127. package/layers/admin/app/utils/reauth.ts +0 -67
  128. package/layers/admin/app/utils/record-title.ts +0 -15
  129. package/layers/admin/app/utils/register-builtin-editors.ts +0 -9
  130. package/layers/admin/app/utils/safe-redirect.ts +0 -12
  131. package/layers/admin/nuxt.config.ts +0 -20
  132. package/layers/auth/nuxt.config.ts +0 -1
  133. package/layers/auth/server/plugins/01.register-auth-pipelines.ts +0 -7
  134. package/layers/collections/nuxt.config.ts +0 -1
  135. package/layers/collections/server/plugins/01.register.ts +0 -29
  136. package/layers/collections/server/plugins/02.register-relation-populate.ts +0 -18
  137. package/layers/core/app/composables/layouts.ts +0 -5
  138. package/layers/core/app/utils/field-factories.ts +0 -129
  139. package/layers/core/app/utils/layouts.ts +0 -38
  140. package/layers/core/modules/auto-discovery/extract-block.ts +0 -179
  141. package/layers/core/modules/auto-discovery/index.ts +0 -112
  142. package/layers/core/modules/auto-discovery/package-registry.ts +0 -19
  143. package/layers/core/modules/auto-discovery/scan.ts +0 -170
  144. package/layers/core/modules/auto-discovery/virtual.d.ts +0 -20
  145. package/layers/core/modules/component-namespace/index.ts +0 -44
  146. package/layers/core/modules/component-namespace/shared.ts +0 -24
  147. package/layers/core/modules/kestrel/app-shell.ts +0 -55
  148. package/layers/core/modules/kestrel/auto-imports.ts +0 -44
  149. package/layers/core/modules/kestrel/index.ts +0 -144
  150. package/layers/core/modules/plugin-order/index.ts +0 -69
  151. package/layers/core/modules/plugin-order/plugin-order.ts +0 -273
  152. package/layers/core/nuxt.config.ts +0 -10
  153. package/layers/core/server/api/[...path].ts +0 -98
  154. package/layers/core/server/plugins/00.config.ts +0 -12
  155. package/layers/core/server/plugins/00.migrate.ts +0 -12
  156. package/layers/core/server/plugins/01.register-introspection-pipeline.ts +0 -5
  157. package/layers/core/server/plugins/01.register-openapi-pipeline.ts +0 -5
  158. package/layers/core/server/plugins/01.register-tooling-pipelines.ts +0 -5
  159. package/layers/core/server/plugins/02.schema-sync.ts +0 -58
  160. package/layers/core/server/plugins/04.outbox-worker.ts +0 -31
  161. package/layers/core/server/plugins/05.reindex-refs.ts +0 -5
  162. package/layers/core/server/routes/__kestrel/dashboard.get.ts +0 -29
  163. package/layers/core/server/tasks/db/migrate-module.ts +0 -83
  164. package/layers/core/server/tasks/db/migrate-revisions.ts +0 -20
  165. package/layers/core/server/tasks/db/migrate.ts +0 -50
  166. package/layers/core/server/utils/server-config.ts +0 -81
  167. package/layers/fields/nuxt.config.ts +0 -1
  168. package/layers/fields/server/plugins/01.register-blocks-pipeline.ts +0 -7
  169. package/layers/fields/server/plugins/01.register-field-populate.ts +0 -11
  170. package/layers/media/app/components/KestrelImg.vue +0 -94
  171. package/layers/media/app/components/MediaConflictDialog.vue +0 -35
  172. package/layers/media/app/components/MediaDeleteDialog.vue +0 -52
  173. package/layers/media/app/components/MediaGrid.vue +0 -113
  174. package/layers/media/app/components/MediaImage.vue +0 -70
  175. package/layers/media/app/components/MediaLibrary.vue +0 -456
  176. package/layers/media/app/components/MediaNewFolderDialog.vue +0 -32
  177. package/layers/media/app/components/MediaPathBar.vue +0 -163
  178. package/layers/media/app/components/MediaPicker.vue +0 -19
  179. package/layers/media/app/components/MediaRenameDialog.vue +0 -32
  180. package/layers/media/app/components/MediaTable.vue +0 -75
  181. package/layers/media/app/components/MediaThumb.vue +0 -25
  182. package/layers/media/app/components/MediaToolbar.vue +0 -90
  183. package/layers/media/app/components/MediaUploadDialog.vue +0 -87
  184. package/layers/media/app/components/MediaViewer.vue +0 -137
  185. package/layers/media/app/components/field/Media.vue +0 -263
  186. package/layers/media/app/composables/useBlurUp.ts +0 -55
  187. package/layers/media/app/composables/useMediaClipboard.ts +0 -14
  188. package/layers/media/app/composables/useMediaContextMenu.ts +0 -56
  189. package/layers/media/app/composables/useMediaDnd.ts +0 -56
  190. package/layers/media/app/composables/useMediaLibrary.ts +0 -152
  191. package/layers/media/app/composables/useMediaOps.ts +0 -50
  192. package/layers/media/app/composables/useMediaRelocate.ts +0 -39
  193. package/layers/media/app/composables/useMediaResolver.ts +0 -50
  194. package/layers/media/app/composables/useMediaUpload.ts +0 -93
  195. package/layers/media/app/composables/useMediaVariant.ts +0 -34
  196. package/layers/media/app/plugins/field-media.client.ts +0 -6
  197. package/layers/media/app/utils/ai-disclosure.ts +0 -19
  198. package/layers/media/app/utils/dnd.ts +0 -69
  199. package/layers/media/app/utils/library.ts +0 -81
  200. package/layers/media/app/utils/ops.ts +0 -60
  201. package/layers/media/app/utils/picture-model.ts +0 -125
  202. package/layers/media/app/utils/thumbhash.ts +0 -19
  203. package/layers/media/app/utils/variant-name.ts +0 -20
  204. package/layers/media/nuxt.config.ts +0 -7
  205. package/layers/media/server/middleware/ondemand-variants.ts +0 -38
  206. package/layers/media/server/plugins/01.register-media-pipelines.ts +0 -7
  207. package/layers/media/server/plugins/02.register-media.ts +0 -22
  208. package/layers/media/server/plugins/04.variant-capture.ts +0 -27
  209. package/layers/media/server/plugins/05.media-cleanup.ts +0 -5
  210. package/layers/media/server/tasks/media/backfill.ts +0 -17
  211. package/layers/public/app/app.vue +0 -5
  212. package/layers/public/app/components/BlockRenderer.vue +0 -56
  213. package/layers/public/app/components/KestrelLink.vue +0 -26
  214. package/layers/public/app/components/KestrelPreviewBridge.vue +0 -133
  215. package/layers/public/app/composables/public-page.ts +0 -24
  216. package/layers/public/app/error.vue +0 -12
  217. package/layers/public/app/layouts/default.vue +0 -5
  218. package/layers/public/app/pages/[...slug].vue +0 -239
  219. package/layers/public/app/pages/__kestrel/preview.vue +0 -46
  220. package/layers/public/app/utils/block-edit-context.ts +0 -16
  221. package/layers/public/app/utils/json-ld.ts +0 -139
  222. package/layers/public/app/utils/link-href.ts +0 -46
  223. package/layers/public/app/utils/page-head.ts +0 -84
  224. package/layers/public/app/utils/page-layout.ts +0 -18
  225. package/layers/public/app/utils/preview-protocol.ts +0 -117
  226. package/layers/public/app/utils/public-route.ts +0 -46
  227. package/layers/public/app/utils/site-head.ts +0 -43
  228. package/layers/public/modules/deploy-output/index.ts +0 -80
  229. package/layers/public/modules/prerender-routes/discover.ts +0 -37
  230. package/layers/public/modules/prerender-routes/index.ts +0 -88
  231. package/layers/public/modules/prerender-routes/routes.ts +0 -16
  232. package/layers/public/modules/prune-media/index.ts +0 -48
  233. package/layers/public/modules/prune-media/prune-media.ts +0 -81
  234. package/layers/public/nuxt.config.ts +0 -14
  235. package/layers/public/server/delivery-live/serve.ts +0 -111
  236. package/layers/public/server/middleware/delivery-live-catchall.ts +0 -7
  237. package/layers/public/server/pipelines/index.ts +0 -23
  238. package/layers/public/server/plugins/00.ensure-snapshot-triggers.ts +0 -14
  239. package/layers/public/server/plugins/01.register-public-pipelines.ts +0 -7
  240. package/layers/public/server/plugins/02.register-links.ts +0 -16
  241. package/layers/public/server/plugins/03.redirects.ts +0 -55
  242. package/layers/public/server/plugins/05.plan-publish.ts +0 -5
  243. package/layers/public/server/plugins/zz.publish.ts +0 -127
  244. package/layers/public/server/routes/llms-full.txt.get.ts +0 -99
  245. package/layers/public/server/routes/llms.txt.get.ts +0 -66
  246. package/layers/public/server/routes/redirects.json.get.ts +0 -58
  247. package/layers/public/server/routes/robots.txt.get.ts +0 -11
  248. package/layers/public/server/routes/sitemap.xml.get.ts +0 -79
  249. package/layers/public/server/tasks/publish/run.ts +0 -32
  250. package/layers/public/server/utils/publish/render-live.ts +0 -26
  251. package/layers/ui/app/assets/scss/_base.scss +0 -16
  252. package/layers/ui/app/assets/scss/_datepicker.scss +0 -82
  253. package/layers/ui/app/assets/scss/_mixins.scss +0 -50
  254. package/layers/ui/app/assets/scss/_reset.scss +0 -65
  255. package/layers/ui/app/assets/scss/_tokens.scss +0 -183
  256. package/layers/ui/app/assets/scss/main.scss +0 -4
  257. package/layers/ui/app/components/field/Boolean.vue +0 -30
  258. package/layers/ui/app/components/field/Choice.vue +0 -73
  259. package/layers/ui/app/components/field/Datetime.vue +0 -98
  260. package/layers/ui/app/components/field/DeadRefNote.vue +0 -28
  261. package/layers/ui/app/components/field/Json.vue +0 -62
  262. package/layers/ui/app/components/field/Layout.vue +0 -138
  263. package/layers/ui/app/components/field/Link.vue +0 -112
  264. package/layers/ui/app/components/field/LinkInternalPicker.vue +0 -95
  265. package/layers/ui/app/components/field/Number.vue +0 -30
  266. package/layers/ui/app/components/field/Relation.vue +0 -49
  267. package/layers/ui/app/components/field/Renderer.vue +0 -30
  268. package/layers/ui/app/components/field/Repeater.vue +0 -460
  269. package/layers/ui/app/components/field/Richtext.vue +0 -26
  270. package/layers/ui/app/components/field/Slug.vue +0 -65
  271. package/layers/ui/app/components/field/Text.vue +0 -35
  272. package/layers/ui/app/components/field/Unsupported.vue +0 -34
  273. package/layers/ui/app/components/ui/Alert.vue +0 -50
  274. package/layers/ui/app/components/ui/Brand.vue +0 -22
  275. package/layers/ui/app/components/ui/Button.vue +0 -116
  276. package/layers/ui/app/components/ui/ButtonGroup.vue +0 -96
  277. package/layers/ui/app/components/ui/Checkbox.vue +0 -53
  278. package/layers/ui/app/components/ui/CheckboxGroup.vue +0 -51
  279. package/layers/ui/app/components/ui/Combobox.vue +0 -345
  280. package/layers/ui/app/components/ui/DatePicker.vue +0 -92
  281. package/layers/ui/app/components/ui/DateRangePicker.vue +0 -100
  282. package/layers/ui/app/components/ui/Dialog.vue +0 -107
  283. package/layers/ui/app/components/ui/EmptyState.vue +0 -46
  284. package/layers/ui/app/components/ui/Field.vue +0 -58
  285. package/layers/ui/app/components/ui/Fieldset.vue +0 -65
  286. package/layers/ui/app/components/ui/Icon.vue +0 -44
  287. package/layers/ui/app/components/ui/Menu.vue +0 -54
  288. package/layers/ui/app/components/ui/NumberInput.vue +0 -175
  289. package/layers/ui/app/components/ui/Richtext.vue +0 -141
  290. package/layers/ui/app/components/ui/RichtextToolbar.vue +0 -251
  291. package/layers/ui/app/components/ui/Select.vue +0 -52
  292. package/layers/ui/app/components/ui/TextInput.vue +0 -111
  293. package/layers/ui/app/components/ui/Textarea.vue +0 -39
  294. package/layers/ui/app/components/ui/TimeInput.vue +0 -18
  295. package/layers/ui/app/components/ui/Toasts.vue +0 -103
  296. package/layers/ui/app/components/ui/Tooltip.vue +0 -45
  297. package/layers/ui/app/composables/useAdminLang.ts +0 -8
  298. package/layers/ui/app/composables/useDragReorder.ts +0 -41
  299. package/layers/ui/app/composables/useEchoGuard.ts +0 -28
  300. package/layers/ui/app/composables/useLinkField.ts +0 -70
  301. package/layers/ui/app/composables/useRecordOptions.ts +0 -84
  302. package/layers/ui/app/composables/useRepeater.ts +0 -99
  303. package/layers/ui/app/composables/useT.ts +0 -32
  304. package/layers/ui/app/composables/useToast.ts +0 -46
  305. package/layers/ui/app/i18n/de.ts +0 -432
  306. package/layers/ui/app/i18n/en.ts +0 -434
  307. package/layers/ui/app/utils/boolean-field.ts +0 -10
  308. package/layers/ui/app/utils/field-component.ts +0 -22
  309. package/layers/ui/app/utils/field-empty.ts +0 -21
  310. package/layers/ui/app/utils/field-registry.ts +0 -36
  311. package/layers/ui/app/utils/humanize.ts +0 -14
  312. package/layers/ui/app/utils/icons.ts +0 -143
  313. package/layers/ui/app/utils/iso-date.ts +0 -18
  314. package/layers/ui/app/utils/localized.ts +0 -13
  315. package/layers/ui/app/utils/record-context.ts +0 -15
  316. package/layers/ui/app/utils/reorder.ts +0 -8
  317. package/layers/ui/app/utils/richtext-preserve-class.ts +0 -31
  318. package/layers/ui/app/utils/useFieldA11y.ts +0 -40
  319. package/layers/ui/nuxt.config.ts +0 -9
  320. package/nuxt.config.ts +0 -108
  321. package/scripts/consumer-template-ci.mjs +0 -160
  322. package/scripts/copy-create-payload.mjs +0 -49
  323. package/scripts/coverage-all.mjs +0 -163
  324. package/scripts/dashboard.mjs +0 -89
  325. package/scripts/hash-password.mjs +0 -29
  326. package/scripts/kestrel.mjs +0 -240
  327. package/scripts/lib/cli.mjs +0 -129
  328. package/scripts/lib/coverage-model.mjs +0 -51
  329. package/scripts/lib/password.mjs +0 -19
  330. package/scripts/lib/scaffold.mjs +0 -205
  331. package/scripts/port-graph.mjs +0 -350
  332. package/scripts/typecheck.mjs +0 -62
  333. package/templates/starter/README.md +0 -65
  334. package/templates/starter/_env.example +0 -17
  335. package/templates/starter/_gitignore +0 -26
  336. package/templates/starter/_package.json +0 -22
  337. package/templates/starter/app/app.vue +0 -7
  338. package/templates/starter/app/blocks/Prose.vue +0 -13
  339. package/templates/starter/app/layouts/default.vue +0 -6
  340. package/templates/starter/nuxt.config.ts +0 -23
  341. package/templates/starter/pnpm-workspace.yaml +0 -8
  342. package/templates/starter/tsconfig.json +0 -3
@@ -1,32 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed, watch } from 'vue'
3
-
4
- const props = defineProps<{ open: boolean; name: string; busy?: boolean; error?: string | null }>()
5
- const emit = defineEmits<{ rename: [string]; 'update:open': [boolean] }>()
6
-
7
- const draft = ref(props.name)
8
- watch(() => props.open, (o) => { if (o) draft.value = props.name })
9
-
10
- const { t } = useT()
11
-
12
- const canRename = computed(() => {
13
- const v = draft.value.trim()
14
- return !!v && v !== props.name
15
- })
16
- function submit() { if (canRename.value && !props.busy) emit('rename', draft.value.trim()) }
17
- </script>
18
-
19
- <template>
20
- <KestrelUiDialog :open="open" :title="t('media.rename')" @update:open="(v) => emit('update:open', v)">
21
- <KestrelUiField :label="t('media.newName')">
22
- <template #default="f">
23
- <KestrelUiTextInput v-model="draft" v-bind="f" @keydown.enter="submit" />
24
- </template>
25
- </KestrelUiField>
26
- <KestrelUiAlert v-if="error" variant="error">{{ error }}</KestrelUiAlert>
27
- <template #footer>
28
- <KestrelUiButton :disabled="busy" @click="emit('update:open', false)">{{ t('common.cancel') }}</KestrelUiButton>
29
- <KestrelUiButton variant="primary" :disabled="busy || !canRename" @click="submit">{{ t('media.rename') }}</KestrelUiButton>
30
- </template>
31
- </KestrelUiDialog>
32
- </template>
@@ -1,75 +0,0 @@
1
- <script setup lang="ts">
2
- import { humanizeSize, itemKey, type LibraryItem } from '../utils/library'
3
- const { t } = useT()
4
- const props = defineProps<{ items: LibraryItem[]; isSelected: (item: LibraryItem) => boolean; dropTargetPath?: string | null; sort?: string; parentPath?: string | null; upLabel?: string }>()
5
- const emit = defineEmits<{ navigate: [string]; select: [LibraryItem, { toggle: boolean; range: boolean }]; open: [LibraryItem]; dragstart: [LibraryItem, DragEvent]; dragend: []; sort: [string] }>()
6
- const arrow = (field: string) => (props.sort === field ? ' ▲' : props.sort === `-${field}` ? ' ▼' : '')
7
- const ariaSort = (field: string): 'ascending' | 'descending' | 'none' => (props.sort === field ? 'ascending' : props.sort === `-${field}` ? 'descending' : 'none')
8
- const mods = (e: MouseEvent) => ({ toggle: e.ctrlKey || e.metaKey, range: e.shiftKey })
9
- // Plain click opens the folder; modifier-click selects it so folders can join the selection.
10
- function onFolder(item: LibraryItem, path: string, e: MouseEvent | KeyboardEvent) {
11
- if (e.ctrlKey || e.metaKey || e.shiftKey) emit('select', item, mods(e as MouseEvent))
12
- else emit('navigate', path)
13
- }
14
- function onFile(item: LibraryItem, e: MouseEvent | KeyboardEvent) { emit('select', item, mods(e as MouseEvent)) }
15
- const dims = (f: { width?: number; height?: number }) => (f.width && f.height ? `${f.width}×${f.height}` : '—')
16
- </script>
17
-
18
- <template>
19
- <table class="media-table">
20
- <thead>
21
- <tr>
22
- <th scope="col" :aria-sort="ariaSort('name')"><button type="button" class="media-table__sort" @click="emit('sort', 'name')">{{ t('media.colName') }}<span aria-hidden="true">{{ arrow('name') }}</span></button></th>
23
- <th scope="col" :aria-sort="ariaSort('type')"><button type="button" class="media-table__sort" @click="emit('sort', 'type')">{{ t('media.colType') }}<span aria-hidden="true">{{ arrow('type') }}</span></button></th>
24
- <th scope="col" :aria-sort="ariaSort('size')"><button type="button" class="media-table__sort" @click="emit('sort', 'size')">{{ t('media.colSize') }}<span aria-hidden="true">{{ arrow('size') }}</span></button></th>
25
- <th scope="col">{{ t('media.colDimensions') }}</th>
26
- </tr>
27
- </thead>
28
- <tbody>
29
- <tr v-if="parentPath != null" class="media-table__row media-table__row--up" data-test="row-folder-up"
30
- tabindex="0" :aria-label="upLabel"
31
- @click="emit('navigate', parentPath)" @keydown.enter.space.prevent="emit('navigate', parentPath)">
32
- <td><span aria-hidden="true"><KestrelUiIcon name="folder" :size="15" /></span> ..</td>
33
- <td>{{ t('media.typeFolder') }}</td><td>—</td><td>—</td>
34
- </tr>
35
- <tr v-for="item in items" :key="itemKey(item)" class="media-table__row"
36
- :class="{ 'is-selected': isSelected(item), 'is-drop-target': item.type === 'folder' && item.folder.path === dropTargetPath }"
37
- :aria-selected="isSelected(item)" tabindex="0"
38
- :data-drop-folder="item.type === 'folder' ? item.folder.path : undefined"
39
- :data-file-id="item.type === 'file' ? item.file.id : undefined"
40
- :data-test="item.type === 'folder' ? `row-folder-${item.folder.path}` : `row-file-${item.file.id}`"
41
- draggable="true"
42
- @click="item.type === 'folder' ? onFolder(item, item.folder.path, $event) : onFile(item, $event)"
43
- @dblclick="item.type === 'file' && emit('open', item)"
44
- @keydown.enter.prevent="item.type === 'folder' ? onFolder(item, item.folder.path, $event) : emit('open', item)"
45
- @keydown.space.prevent="item.type === 'folder' ? emit('select', item, { toggle: true, range: false }) : onFile(item, $event)"
46
- @dragstart="(e) => emit('dragstart', item, e)"
47
- @dragend="emit('dragend')">
48
- <template v-if="item.type === 'folder'">
49
- <td><span aria-hidden="true"><KestrelUiIcon name="folder" :size="15" /></span> {{ item.folder.name }}</td>
50
- <td>{{ t('media.typeFolder') }}</td><td>{{ item.folder.size != null ? humanizeSize(item.folder.size) : '—' }}</td><td>—</td>
51
- </template>
52
- <template v-else>
53
- <td><slot name="file-badge" :file="item.file" />{{ item.file.filename }}</td>
54
- <td>{{ item.file.mime }}</td>
55
- <td>{{ humanizeSize(item.file.size) }}</td>
56
- <td>{{ dims(item.file) }}</td>
57
- </template>
58
- </tr>
59
- </tbody>
60
- </table>
61
- </template>
62
-
63
- <style lang="scss" scoped>
64
- .media-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
65
- .media-table th, .media-table td { text-align: left; padding: var(--space-2); border-bottom: 1px solid var(--color-border); }
66
- .media-table th { color: var(--color-text-muted); font-weight: var(--weight-medium); }
67
- .media-table__sort { display: inline-flex; align-items: center; gap: var(--space-1); background: none; border: 0; padding: 0; font: inherit; font-weight: var(--weight-medium); color: var(--color-text-muted); cursor: pointer; }
68
- .media-table__sort:hover { color: var(--color-text); }
69
- .media-table__sort:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }
70
- .media-table__row { cursor: pointer; }
71
- .media-table__row:hover { background: var(--color-hover); }
72
- .media-table__row.is-selected { background: var(--color-active); box-shadow: inset 2px 0 0 var(--color-primary); }
73
- .media-table__row.is-drop-target { outline: 2px dashed var(--color-primary); outline-offset: -2px; background: var(--color-surface); }
74
- .media-table__row:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -2px; }
75
- </style>
@@ -1,25 +0,0 @@
1
- <script setup lang="ts">
2
- import { useBlurUp } from '../composables/useBlurUp'
3
-
4
- // An admin media-library thumbnail: a plain <img> with a thumbhash blur-up placeholder so the library
5
- // grid + the media field picker fill with soft previews instead of blank tiles that pop in. Product
6
- // (AdminUI) — distinct from the demo public `MediaImage`. Takes ready-made src/srcset strings (the
7
- // library API and the media resolver already build them). Placeholder-only on purpose: no sharpen
8
- // animation, so adjacent tiles in the grid never overlap with a filter-blur halo.
9
- const props = defineProps<{ src: string; srcset?: string; sizes?: string; alt?: string; thumbhash?: string | null }>()
10
- const { imgEl, placeholderStyle, onLoad } = useBlurUp(() => props.thumbhash ?? null)
11
- </script>
12
-
13
- <template>
14
- <img
15
- ref="imgEl"
16
- :src="src"
17
- :srcset="srcset"
18
- :sizes="sizes"
19
- :alt="alt ?? ''"
20
- :style="placeholderStyle"
21
- loading="lazy"
22
- decoding="async"
23
- @load="onLoad"
24
- >
25
- </template>
@@ -1,90 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref } from 'vue'
3
- import type { IconName } from '../../../ui/app/utils/icons'
4
-
5
- type View = 'grid' | 'table'
6
-
7
- const props = defineProps<{ view: View; search: string; disabled?: boolean }>()
8
- const emit = defineEmits<{
9
- 'update:view': [View]
10
- 'update:search': [string]
11
- upload: [File[]]
12
- 'new-folder': []
13
- }>()
14
-
15
- const { t } = useT()
16
-
17
- const VIEW_OPTIONS = computed<{ label: string; value: string; icon: IconName }[]>(() => [
18
- { label: t('mediaToolbar.viewGrid'), value: 'grid', icon: 'layout-grid' },
19
- { label: t('mediaToolbar.viewTable'), value: 'table', icon: 'table' },
20
- ])
21
-
22
- const viewModel = computed<string | string[] | null>({
23
- get: () => props.view,
24
- set: (v) => { if (typeof v === 'string') emit('update:view', v as View) },
25
- })
26
-
27
- const fileInput = ref<HTMLInputElement | null>(null)
28
-
29
- function onFiles(e: Event) {
30
- const input = e.target as HTMLInputElement
31
- emit('upload', Array.from(input.files ?? []))
32
- input.value = ''
33
- }
34
- </script>
35
-
36
- <template>
37
- <div class="media-toolbar">
38
- <KestrelUiTextInput
39
- class="media-toolbar__search"
40
- :model-value="search"
41
- type="search"
42
- :placeholder="t('mediaToolbar.searchPlaceholder')"
43
- :aria-label="t('mediaToolbar.searchAriaLabel')"
44
- @update:model-value="(v) => emit('update:search', v ?? '')"
45
- />
46
- <!-- View toggle + actions sit together on the right so they read as one control cluster. -->
47
- <div class="media-toolbar__actions">
48
- <KestrelUiButtonGroup v-model="viewModel" :options="VIEW_OPTIONS" :aria-label="t('mediaToolbar.viewAriaLabel')" />
49
- <span class="media-toolbar__divider" aria-hidden="true"></span>
50
- <input ref="fileInput" type="file" multiple class="media-toolbar__file" :aria-label="t('mediaToolbar.upload')" @change="onFiles" />
51
- <KestrelUiButton :disabled="disabled" @click="fileInput?.click()"><KestrelUiIcon name="upload" :size="16" /> {{ t('mediaToolbar.upload') }}</KestrelUiButton>
52
- <KestrelUiButton :disabled="disabled" @click="emit('new-folder')"><KestrelUiIcon name="folder-plus" :size="16" /> {{ t('mediaToolbar.newFolder') }}</KestrelUiButton>
53
- </div>
54
- </div>
55
- </template>
56
-
57
- <style lang="scss" scoped>
58
- .media-toolbar {
59
- display: flex;
60
- flex-wrap: wrap;
61
- gap: var(--space-3);
62
- row-gap: var(--space-2);
63
- align-items: center;
64
-
65
- &__search {
66
- flex: 1 1 16rem;
67
- min-width: 0;
68
- max-width: 24rem;
69
- margin-right: auto;
70
- }
71
-
72
- &__actions {
73
- display: flex;
74
- align-items: center;
75
- gap: var(--space-2);
76
- }
77
-
78
- &__divider {
79
- align-self: stretch;
80
- width: 1px;
81
- min-height: 1.5rem;
82
- background: var(--color-border);
83
- margin: 0 var(--space-1);
84
- }
85
-
86
- &__file {
87
- display: none;
88
- }
89
- }
90
- </style>
@@ -1,87 +0,0 @@
1
- <script setup lang="ts">
2
- import { reactive, watch } from 'vue'
3
- import type { UploadItem } from '../composables/useMediaUpload'
4
-
5
- const { t } = useT()
6
-
7
- const props = defineProps<{ conflicts: UploadItem[]; open: boolean }>()
8
- const emit = defineEmits<{
9
- resolve: [string, 'overwrite' | 'rename' | 'skip', string?]
10
- 'resolve-all': ['overwrite' | 'rename' | 'skip']
11
- 'update:open': [boolean]
12
- }>()
13
-
14
- // Per-row editable rename, seeded from each item's server suggestion.
15
- const names = reactive<Record<string, string>>({})
16
- watch(
17
- () => props.conflicts,
18
- (list) => {
19
- for (const c of list) if (!(c.id in names)) names[c.id] = c.suggestion ?? c.filename
20
- },
21
- { immediate: true, deep: true },
22
- )
23
-
24
- function rename(c: UploadItem) {
25
- emit('resolve', c.id, 'rename', names[c.id] ?? c.suggestion ?? c.filename)
26
- }
27
- </script>
28
-
29
- <template>
30
- <KestrelUiDialog
31
- :open="open"
32
- :title="t('media.conflicts.title')"
33
- :description="t('media.conflicts.desc', { count: conflicts.length })"
34
- @update:open="(v) => emit('update:open', v)"
35
- >
36
- <div class="media-conflicts__all">
37
- <span>{{ t('media.conflicts.applyAll') }}</span>
38
- <KestrelUiButton @click="emit('resolve-all', 'overwrite')">{{ t('media.conflicts.overwriteAll') }}</KestrelUiButton>
39
- <KestrelUiButton @click="emit('resolve-all', 'rename')">{{ t('media.conflicts.renameAll') }}</KestrelUiButton>
40
- <KestrelUiButton @click="emit('resolve-all', 'skip')">{{ t('media.conflicts.skipAll') }}</KestrelUiButton>
41
- </div>
42
- <ul class="media-conflicts__list">
43
- <li v-for="c in conflicts" :key="c.id" class="media-conflicts__row">
44
- <span class="media-conflicts__name">{{ c.filename }}</span>
45
- <KestrelUiTextInput v-model="names[c.id]" :aria-label="t('media.conflicts.newName', { name: c.filename })" />
46
- <div class="media-conflicts__actions">
47
- <KestrelUiButton @click="emit('resolve', c.id, 'overwrite', undefined)">{{ t('media.conflicts.overwrite') }}</KestrelUiButton>
48
- <KestrelUiButton variant="primary" @click="rename(c)">{{ t('media.conflicts.rename') }}</KestrelUiButton>
49
- <KestrelUiButton @click="emit('resolve', c.id, 'skip', undefined)">{{ t('media.conflicts.skip') }}</KestrelUiButton>
50
- </div>
51
- </li>
52
- </ul>
53
- </KestrelUiDialog>
54
- </template>
55
-
56
- <style lang="scss" scoped>
57
- .media-conflicts__all {
58
- display: flex;
59
- align-items: center;
60
- gap: var(--space-2);
61
- flex-wrap: wrap;
62
- margin-bottom: var(--space-3);
63
- }
64
- .media-conflicts__list {
65
- list-style: none;
66
- margin: 0;
67
- padding: 0;
68
- display: flex;
69
- flex-direction: column;
70
- gap: var(--space-2);
71
- }
72
- .media-conflicts__row {
73
- display: flex;
74
- align-items: center;
75
- gap: var(--space-2);
76
- flex-wrap: wrap;
77
- }
78
- .media-conflicts__name {
79
- font-weight: var(--weight-medium);
80
- min-width: 8rem;
81
- }
82
- .media-conflicts__actions {
83
- display: flex;
84
- gap: var(--space-2);
85
- margin-left: auto;
86
- }
87
- </style>
@@ -1,137 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed, watch } from 'vue'
3
- import { humanizeSize, type LibraryFile } from '../utils/library'
4
-
5
- // Fullscreen-ish preview + general info for a single file. Images additionally expose an editable alt
6
- // text (the only field worth maintaining from the library) and — when the consumer switched the feature
7
- // on — the EU AI Act Art. 50 disclosure; everything else is read-only metadata.
8
- const props = defineProps<{ open: boolean; file: LibraryFile | null; busy?: boolean; error?: string | null }>()
9
- // The disclosure rides along as a SECOND positional argument rather than reshaping the first: an outside
10
- // consumer (`extensions/galleries-secure`) handles `save` as `(alt: string)` and simply ignores the extra.
11
- const emit = defineEmits<{
12
- 'update:open': [boolean]
13
- save: [alt: string, ai?: { aiSourceType: string | null; aiNote: string | null }]
14
- }>()
15
- const { t, lang } = useT()
16
-
17
- const isImage = computed(() => props.file?.mime.startsWith('image/') ?? false)
18
- const ext = computed(() => (props.file?.filename.split('.').pop() ?? '').toUpperCase())
19
- const dims = computed(() => (props.file?.width && props.file?.height ? `${props.file.width}×${props.file.height}` : '—'))
20
- const uploaded = computed(() => {
21
- const c = props.file?.createdAt
22
- if (!c) return '—'
23
- const d = new Date(c)
24
- return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString(lang.value)
25
- })
26
-
27
- // Gates the disclosure controls only — the data is always resolved server-side, so a consumer who turns
28
- // the flag back off keeps whatever was recorded, it just stops being editable here.
29
- const aiEnabled = computed(() => useRuntimeConfig().public.aiDisclosureEnabled === true)
30
- const showAi = computed(() => isImage.value && aiEnabled.value)
31
- const AI_SOURCE_TYPES = ['trainedAlgorithmicMedia', 'compositeWithTrainedAlgorithmicMedia', 'algorithmicallyEnhanced'] as const
32
- // A leading empty option is what makes "no disclosure recorded" both representable and clearable
33
- // (mirrors how `field/Choice.vue` renders a non-required single choice).
34
- const aiSourceTypeOptions = computed(() => [
35
- { label: '—', value: '' },
36
- ...AI_SOURCE_TYPES.map((v) => ({ label: t(`mediaViewer.aiSourceType.${v}`), value: v })),
37
- ])
38
-
39
- const alt = ref('')
40
- const aiSourceType = ref('')
41
- const aiNote = ref('')
42
- const fileAlt = computed(() => props.file?.alt ?? '')
43
- const fileAiSourceType = computed(() => props.file?.aiDisclosure?.sourceType ?? '')
44
- const fileAiNote = computed(() => props.file?.aiDisclosure?.note ?? '')
45
- // Seed (and re-seed) the drafts whenever the dialog opens — immediate so a viewer mounted already-open
46
- // (or re-opened on a different file) shows the current values rather than stale/empty ones.
47
- watch(() => props.open, (o) => {
48
- if (!o) return
49
- alt.value = fileAlt.value
50
- aiSourceType.value = fileAiSourceType.value
51
- aiNote.value = fileAiNote.value
52
- }, { immediate: true })
53
-
54
- const aiDirty = computed(() => showAi.value && (aiSourceType.value !== fileAiSourceType.value || aiNote.value !== fileAiNote.value))
55
- const dirty = computed(() => isImage.value && (alt.value !== fileAlt.value || aiDirty.value))
56
- function save() {
57
- if (!dirty.value || props.busy) return
58
- // With the feature off the payload is omitted entirely, so an alt-only save can never blank a
59
- // disclosure the consumer recorded while it was on.
60
- if (!showAi.value) { emit('save', alt.value); return }
61
- emit('save', alt.value, { aiSourceType: aiSourceType.value || null, aiNote: aiNote.value.trim() || null })
62
- }
63
- </script>
64
-
65
- <template>
66
- <KestrelUiDialog :open="open" size="xl" :title="file?.filename ?? ''" @update:open="(v) => emit('update:open', v)">
67
- <div v-if="file" class="media-viewer">
68
- <div class="media-viewer__preview">
69
- <img v-if="isImage" :src="file.src" :srcset="file.srcset" sizes="(max-width: 60rem) 100vw, 60rem" :alt="file.alt ?? file.filename" />
70
- <span v-else class="media-viewer__ext" aria-hidden="true">{{ ext }}</span>
71
- </div>
72
- <aside class="media-viewer__details">
73
- <dl class="media-viewer__info">
74
- <div><dt>{{ t('media.colType') }}</dt><dd>{{ file.mime }}</dd></div>
75
- <div><dt>{{ t('media.colSize') }}</dt><dd>{{ humanizeSize(file.size) }}</dd></div>
76
- <div v-if="isImage"><dt>{{ t('media.colDimensions') }}</dt><dd>{{ dims }}</dd></div>
77
- <div><dt>{{ t('mediaViewer.folder') }}</dt><dd>{{ file.folder || '/' }}</dd></div>
78
- <div><dt>{{ t('mediaViewer.uploaded') }}</dt><dd>{{ uploaded }}</dd></div>
79
- </dl>
80
- <KestrelUiField v-if="isImage" :label="t('mediaViewer.alt')" :hint="t('mediaViewer.altHint')">
81
- <template #default="f">
82
- <KestrelUiTextInput v-model="alt" v-bind="f" @keydown.enter="save" />
83
- </template>
84
- </KestrelUiField>
85
- <div v-if="showAi" class="media-viewer__ai">
86
- <KestrelUiField :label="t('mediaViewer.aiSourceTypeLabel')" :hint="t('mediaViewer.aiSourceTypeHint')">
87
- <template #default="f">
88
- <KestrelUiSelect v-model="aiSourceType" :options="aiSourceTypeOptions" v-bind="f" />
89
- </template>
90
- </KestrelUiField>
91
- <KestrelUiField :label="t('mediaViewer.aiNote')" :hint="t('mediaViewer.aiNoteHint')">
92
- <template #default="f">
93
- <KestrelUiTextInput v-model="aiNote" v-bind="f" @keydown.enter="save" />
94
- </template>
95
- </KestrelUiField>
96
- </div>
97
- <KestrelUiAlert v-if="error" variant="error">{{ error }}</KestrelUiAlert>
98
- <!-- Optional per-file extra panel (e.g. proofing comments). Empty by default. -->
99
- <slot name="extra" :file="file" />
100
- </aside>
101
- </div>
102
- <template v-if="file" #footer>
103
- <KestrelUiButton :disabled="busy" @click="emit('update:open', false)">{{ t('common.close') }}</KestrelUiButton>
104
- <KestrelUiButton v-if="isImage" variant="primary" :disabled="busy || !dirty" @click="save">{{ t('common.save') }}</KestrelUiButton>
105
- </template>
106
- </KestrelUiDialog>
107
- </template>
108
-
109
- <style lang="scss" scoped>
110
- .media-viewer {
111
- display: grid;
112
- grid-template-columns: minmax(0, 1fr) minmax(0, 18rem);
113
- gap: var(--space-4);
114
- align-items: start;
115
- }
116
- @media (max-width: 48rem) {
117
- .media-viewer { grid-template-columns: 1fr; }
118
- }
119
- .media-viewer__preview {
120
- display: grid;
121
- place-items: center;
122
- min-height: 16rem;
123
- max-height: 70svh;
124
- overflow: hidden;
125
- background: var(--color-bg);
126
- border: 1px solid var(--color-border);
127
- border-radius: var(--radius-md);
128
- }
129
- .media-viewer__preview img { max-width: 100%; max-height: 70svh; object-fit: contain; }
130
- .media-viewer__ext { padding: var(--space-7); font-size: var(--text-xl); font-weight: var(--weight-bold); color: var(--color-text-muted); }
131
- .media-viewer__details { display: flex; flex-direction: column; gap: var(--space-4); }
132
- .media-viewer__ai { display: flex; flex-direction: column; gap: var(--space-3); }
133
- .media-viewer__info { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; }
134
- .media-viewer__info > div { display: flex; justify-content: space-between; gap: var(--space-3); font-size: var(--text-sm); }
135
- .media-viewer__info dt { color: var(--color-text-muted); }
136
- .media-viewer__info dd { margin: 0; text-align: right; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
137
- </style>