@michaelthielemann/kestrel 1.2.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 (395) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +12 -0
  3. package/README.md +234 -0
  4. package/kestrel.config.ts +28 -0
  5. package/layers/access/nuxt.config.ts +5 -0
  6. package/layers/access/server/middleware/00.ip-allowlist.ts +67 -0
  7. package/layers/access/server/middleware/access-guard.ts +30 -0
  8. package/layers/access/server/types.d.ts +8 -0
  9. package/layers/access/server/utils/csrf.ts +39 -0
  10. package/layers/access/server/utils/grant-registry.ts +36 -0
  11. package/layers/access/server/utils/guard.ts +44 -0
  12. package/layers/access/server/utils/ip-allowlist.ts +76 -0
  13. package/layers/access/server/utils/policy.ts +98 -0
  14. package/layers/access/server/utils/public-resources.ts +11 -0
  15. package/layers/access/server/utils/render-context.ts +41 -0
  16. package/layers/access/server/utils/require-admin.ts +15 -0
  17. package/layers/admin/app/components/AdminAccount.vue +212 -0
  18. package/layers/admin/app/components/AdminNav.vue +44 -0
  19. package/layers/admin/app/components/BlockFields.vue +68 -0
  20. package/layers/admin/app/components/BlockPreview.vue +401 -0
  21. package/layers/admin/app/components/BlockTree.vue +433 -0
  22. package/layers/admin/app/components/BlocksBody.vue +162 -0
  23. package/layers/admin/app/components/CollectionDeleteDialog.vue +38 -0
  24. package/layers/admin/app/components/CollectionEditor.vue +203 -0
  25. package/layers/admin/app/components/CollectionList.vue +1091 -0
  26. package/layers/admin/app/components/EditorStatus.vue +199 -0
  27. package/layers/admin/app/components/EditorUnsupported.vue +28 -0
  28. package/layers/admin/app/components/FieldsBody.vue +23 -0
  29. package/layers/admin/app/components/LocaleBar.vue +105 -0
  30. package/layers/admin/app/components/PageFields.vue +101 -0
  31. package/layers/admin/app/components/PageFieldsPane.vue +39 -0
  32. package/layers/admin/app/components/SeoFields.vue +169 -0
  33. package/layers/admin/app/components/SingletonEditor.vue +92 -0
  34. package/layers/admin/app/composables/useAuth.ts +68 -0
  35. package/layers/admin/app/composables/useBlockTree.ts +95 -0
  36. package/layers/admin/app/composables/useBlocks.ts +7 -0
  37. package/layers/admin/app/composables/useCachedResource.ts +35 -0
  38. package/layers/admin/app/composables/useCollectionOps.ts +53 -0
  39. package/layers/admin/app/composables/useCollections.ts +7 -0
  40. package/layers/admin/app/composables/useContentLocales.ts +23 -0
  41. package/layers/admin/app/composables/useEditForm.ts +370 -0
  42. package/layers/admin/app/composables/useLinkResolver.ts +40 -0
  43. package/layers/admin/app/composables/useListColumns.ts +53 -0
  44. package/layers/admin/app/composables/useListUrlState.ts +53 -0
  45. package/layers/admin/app/composables/usePendingPublishPoll.ts +15 -0
  46. package/layers/admin/app/composables/usePreviewViewport.ts +30 -0
  47. package/layers/admin/app/composables/usePublishStatus.ts +98 -0
  48. package/layers/admin/app/composables/useRailCollapsed.ts +14 -0
  49. package/layers/admin/app/composables/useTheme.ts +36 -0
  50. package/layers/admin/app/composables/useUnsavedGuard.ts +22 -0
  51. package/layers/admin/app/layouts/admin.vue +267 -0
  52. package/layers/admin/app/middleware/admin-auth.ts +4 -0
  53. package/layers/admin/app/pages/admin/[collection]/[id].vue +202 -0
  54. package/layers/admin/app/pages/admin/[collection]/index.vue +62 -0
  55. package/layers/admin/app/pages/admin/index.vue +118 -0
  56. package/layers/admin/app/pages/admin/login.vue +68 -0
  57. package/layers/admin/app/pages/admin/media.vue +28 -0
  58. package/layers/admin/app/pages/admin/references.vue +146 -0
  59. package/layers/admin/app/plugins/admin-style-guard.client.ts +76 -0
  60. package/layers/admin/app/plugins/reauth.client.ts +20 -0
  61. package/layers/admin/app/utils/admin-style-guard.ts +20 -0
  62. package/layers/admin/app/utils/block-tree.ts +214 -0
  63. package/layers/admin/app/utils/collection-ops.ts +44 -0
  64. package/layers/admin/app/utils/dead-refs.ts +30 -0
  65. package/layers/admin/app/utils/edit-form.ts +223 -0
  66. package/layers/admin/app/utils/editor-expose.ts +26 -0
  67. package/layers/admin/app/utils/editor-form-context.ts +83 -0
  68. package/layers/admin/app/utils/editor-registry.ts +20 -0
  69. package/layers/admin/app/utils/field-keys.ts +11 -0
  70. package/layers/admin/app/utils/list-columns.ts +141 -0
  71. package/layers/admin/app/utils/list-query.ts +96 -0
  72. package/layers/admin/app/utils/nav-active.ts +9 -0
  73. package/layers/admin/app/utils/populate-blocks.ts +288 -0
  74. package/layers/admin/app/utils/preview-channel.ts +62 -0
  75. package/layers/admin/app/utils/preview-viewport.ts +80 -0
  76. package/layers/admin/app/utils/reauth.ts +68 -0
  77. package/layers/admin/app/utils/record-title.ts +15 -0
  78. package/layers/admin/app/utils/register-builtin-editors.ts +9 -0
  79. package/layers/admin/app/utils/safe-redirect.ts +12 -0
  80. package/layers/admin/nuxt.config.ts +17 -0
  81. package/layers/auth/nuxt.config.ts +1 -0
  82. package/layers/auth/server/api/auth/login.post.ts +38 -0
  83. package/layers/auth/server/api/auth/logout.post.ts +9 -0
  84. package/layers/auth/server/api/auth/session.get.ts +1 -0
  85. package/layers/auth/server/utils/admin-credential.ts +42 -0
  86. package/layers/auth/server/utils/client-ip.ts +88 -0
  87. package/layers/auth/server/utils/login-throttle.ts +126 -0
  88. package/layers/auth/server/utils/password.ts +52 -0
  89. package/layers/auth/server/utils/session-cookie.ts +44 -0
  90. package/layers/auth/server/utils/session-epoch.ts +96 -0
  91. package/layers/auth/server/utils/session.ts +85 -0
  92. package/layers/collections/nuxt.config.ts +1 -0
  93. package/layers/collections/server/collections/pages.ts +19 -0
  94. package/layers/collections/server/plugins/01.register.ts +32 -0
  95. package/layers/collections/server/plugins/02.register-relation-populate.ts +17 -0
  96. package/layers/collections/server/utils/populate-relations.ts +66 -0
  97. package/layers/core/app/utils/bulk-actions.ts +9 -0
  98. package/layers/core/app/utils/filter-ops.ts +98 -0
  99. package/layers/core/app/utils/list-limits.ts +27 -0
  100. package/layers/core/app/utils/locale-path.ts +12 -0
  101. package/layers/core/app/utils/slugify.ts +16 -0
  102. package/layers/core/modules/auto-discovery/extract-block.ts +176 -0
  103. package/layers/core/modules/auto-discovery/index.ts +44 -0
  104. package/layers/core/modules/auto-discovery/scan.ts +78 -0
  105. package/layers/core/modules/auto-discovery/virtual.d.ts +12 -0
  106. package/layers/core/modules/kestrel/index.ts +86 -0
  107. package/layers/core/nuxt.config.ts +8 -0
  108. package/layers/core/server/api/[collection]/[id]/dead-refs.get.ts +8 -0
  109. package/layers/core/server/api/[collection]/[id]/translations.get.ts +4 -0
  110. package/layers/core/server/api/[collection]/[id].delete.ts +5 -0
  111. package/layers/core/server/api/[collection]/[id].get.ts +8 -0
  112. package/layers/core/server/api/[collection]/[id].patch.ts +8 -0
  113. package/layers/core/server/api/[collection]/bulk.post.ts +37 -0
  114. package/layers/core/server/api/[collection]/index.get.ts +19 -0
  115. package/layers/core/server/api/[collection]/index.post.ts +8 -0
  116. package/layers/core/server/api/[collection]/index.put.ts +7 -0
  117. package/layers/core/server/api/[collection]/options.get.ts +18 -0
  118. package/layers/core/server/api/[collection]/translations.get.ts +25 -0
  119. package/layers/core/server/api/collections/[name].get.ts +4 -0
  120. package/layers/core/server/api/collections/index.get.ts +3 -0
  121. package/layers/core/server/api/references/broken.get.ts +3 -0
  122. package/layers/core/server/api/references/referrers.get.ts +24 -0
  123. package/layers/core/server/database/record-refs.ts +20 -0
  124. package/layers/core/server/plugins/00.migrate.ts +11 -0
  125. package/layers/core/server/plugins/02.schema-sync.ts +63 -0
  126. package/layers/core/server/plugins/03.record-refs.ts +12 -0
  127. package/layers/core/server/schema/bootstrap.ts +41 -0
  128. package/layers/core/server/schema/desired.ts +99 -0
  129. package/layers/core/server/schema/dev-sync.ts +23 -0
  130. package/layers/core/server/schema/dialect.ts +59 -0
  131. package/layers/core/server/schema/diff.ts +110 -0
  132. package/layers/core/server/schema/introspect.ts +67 -0
  133. package/layers/core/server/schema/model.ts +61 -0
  134. package/layers/core/server/schema/render-sqlite.ts +89 -0
  135. package/layers/core/server/schema/sync.ts +201 -0
  136. package/layers/core/server/tasks/db/migrate.ts +54 -0
  137. package/layers/core/server/utils/blocks.ts +6 -0
  138. package/layers/core/server/utils/collection-types.ts +27 -0
  139. package/layers/core/server/utils/crud.ts +577 -0
  140. package/layers/core/server/utils/db.ts +26 -0
  141. package/layers/core/server/utils/defineCollection.ts +207 -0
  142. package/layers/core/server/utils/duplicate.ts +162 -0
  143. package/layers/core/server/utils/field-layout.ts +125 -0
  144. package/layers/core/server/utils/filter-predicate.ts +101 -0
  145. package/layers/core/server/utils/http.ts +50 -0
  146. package/layers/core/server/utils/if-unmodified.ts +13 -0
  147. package/layers/core/server/utils/kestrel-config.ts +410 -0
  148. package/layers/core/server/utils/key-lock.ts +33 -0
  149. package/layers/core/server/utils/locale.ts +53 -0
  150. package/layers/core/server/utils/page-route.ts +58 -0
  151. package/layers/core/server/utils/page-slug.ts +127 -0
  152. package/layers/core/server/utils/picker.ts +82 -0
  153. package/layers/core/server/utils/populate.ts +48 -0
  154. package/layers/core/server/utils/prerender-memo.ts +23 -0
  155. package/layers/core/server/utils/read-capture.ts +37 -0
  156. package/layers/core/server/utils/record-ref-index.ts +200 -0
  157. package/layers/core/server/utils/registry.ts +24 -0
  158. package/layers/core/server/utils/resolve-scope.ts +79 -0
  159. package/layers/core/server/utils/seo.ts +24 -0
  160. package/layers/core/server/utils/serialize-collection.ts +149 -0
  161. package/layers/core/server/utils/server-config.ts +18 -0
  162. package/layers/core/server/utils/sql.ts +2 -0
  163. package/layers/core/server/utils/storage.local.ts +117 -0
  164. package/layers/core/server/utils/storage.s3.ts +197 -0
  165. package/layers/core/server/utils/storage.ts +90 -0
  166. package/layers/core/server/utils/write-events.ts +31 -0
  167. package/layers/fields/app/utils/condition.ts +71 -0
  168. package/layers/fields/app/utils/define-block.ts +13 -0
  169. package/layers/fields/app/utils/field-constraints.ts +17 -0
  170. package/layers/fields/app/utils/field-factories.ts +129 -0
  171. package/layers/fields/app/utils/field-meta.ts +33 -0
  172. package/layers/fields/app/utils/field-validate.ts +75 -0
  173. package/layers/fields/app/utils/field-value.ts +7 -0
  174. package/layers/fields/app/utils/richtext-links.ts +48 -0
  175. package/layers/fields/nuxt.config.ts +1 -0
  176. package/layers/fields/server/api/blocks/index.get.ts +9 -0
  177. package/layers/fields/server/field-registry/index.ts +248 -0
  178. package/layers/fields/server/field-registry/naming.ts +10 -0
  179. package/layers/fields/server/field-registry/sanitize.ts +40 -0
  180. package/layers/fields/server/field-registry/types.ts +19 -0
  181. package/layers/fields/server/plugins/01.register-field-populate.ts +11 -0
  182. package/layers/fields/server/utils/block-ids.ts +43 -0
  183. package/layers/fields/server/utils/block-populate.ts +41 -0
  184. package/layers/fields/server/utils/buildCollection.ts +88 -0
  185. package/layers/fields/server/utils/buildFieldSchema.ts +22 -0
  186. package/layers/fields/server/utils/buildTable.ts +107 -0
  187. package/layers/fields/server/utils/conditional-required.ts +25 -0
  188. package/layers/fields/server/utils/defineBlock.ts +86 -0
  189. package/layers/fields/server/utils/defineFieldType.ts +33 -0
  190. package/layers/fields/server/utils/extract-refs.ts +152 -0
  191. package/layers/fields/server/utils/field-populate.ts +68 -0
  192. package/layers/media/app/components/KestrelImg.vue +65 -0
  193. package/layers/media/app/components/MediaConflictDialog.vue +35 -0
  194. package/layers/media/app/components/MediaDeleteDialog.vue +52 -0
  195. package/layers/media/app/components/MediaGrid.vue +113 -0
  196. package/layers/media/app/components/MediaImage.vue +70 -0
  197. package/layers/media/app/components/MediaLibrary.vue +451 -0
  198. package/layers/media/app/components/MediaNewFolderDialog.vue +32 -0
  199. package/layers/media/app/components/MediaPathBar.vue +163 -0
  200. package/layers/media/app/components/MediaPicker.vue +19 -0
  201. package/layers/media/app/components/MediaRenameDialog.vue +32 -0
  202. package/layers/media/app/components/MediaTable.vue +75 -0
  203. package/layers/media/app/components/MediaThumb.vue +25 -0
  204. package/layers/media/app/components/MediaToolbar.vue +90 -0
  205. package/layers/media/app/components/MediaUploadDialog.vue +87 -0
  206. package/layers/media/app/components/MediaViewer.vue +88 -0
  207. package/layers/media/app/components/field/Media.vue +261 -0
  208. package/layers/media/app/composables/useBlurUp.ts +55 -0
  209. package/layers/media/app/composables/useMediaClipboard.ts +14 -0
  210. package/layers/media/app/composables/useMediaContextMenu.ts +56 -0
  211. package/layers/media/app/composables/useMediaDnd.ts +56 -0
  212. package/layers/media/app/composables/useMediaLibrary.ts +151 -0
  213. package/layers/media/app/composables/useMediaOps.ts +50 -0
  214. package/layers/media/app/composables/useMediaRelocate.ts +39 -0
  215. package/layers/media/app/composables/useMediaResolver.ts +50 -0
  216. package/layers/media/app/composables/useMediaUpload.ts +93 -0
  217. package/layers/media/app/composables/useMediaVariant.ts +34 -0
  218. package/layers/media/app/plugins/field-media.client.ts +6 -0
  219. package/layers/media/app/utils/dnd.ts +69 -0
  220. package/layers/media/app/utils/library.ts +79 -0
  221. package/layers/media/app/utils/ops.ts +62 -0
  222. package/layers/media/app/utils/picture-model.ts +125 -0
  223. package/layers/media/app/utils/thumbhash.ts +19 -0
  224. package/layers/media/app/utils/variant-name.ts +20 -0
  225. package/layers/media/nuxt.config.ts +3 -0
  226. package/layers/media/server/api/media/[id]/usages.get.ts +8 -0
  227. package/layers/media/server/api/media/[id].delete.ts +18 -0
  228. package/layers/media/server/api/media/[id].get.ts +10 -0
  229. package/layers/media/server/api/media/[id].patch.ts +41 -0
  230. package/layers/media/server/api/media/backfill.post.ts +18 -0
  231. package/layers/media/server/api/media/copy.post.ts +7 -0
  232. package/layers/media/server/api/media/delete.post.ts +13 -0
  233. package/layers/media/server/api/media/folders.post.ts +18 -0
  234. package/layers/media/server/api/media/index.get.ts +15 -0
  235. package/layers/media/server/api/media/index.post.ts +157 -0
  236. package/layers/media/server/api/media/library.get.ts +17 -0
  237. package/layers/media/server/api/media/move.post.ts +7 -0
  238. package/layers/media/server/api/media/rename.post.ts +7 -0
  239. package/layers/media/server/api/media/resolve.get.ts +13 -0
  240. package/layers/media/server/collections/media-settings.ts +25 -0
  241. package/layers/media/server/collections/media.ts +29 -0
  242. package/layers/media/server/database/folders.ts +10 -0
  243. package/layers/media/server/middleware/ondemand-variants.ts +37 -0
  244. package/layers/media/server/plugins/02.register-media.ts +19 -0
  245. package/layers/media/server/plugins/03.media-cleanup.ts +20 -0
  246. package/layers/media/server/plugins/04.variant-capture.ts +23 -0
  247. package/layers/media/server/schema-tables/folders.ts +4 -0
  248. package/layers/media/server/tasks/media/backfill.ts +18 -0
  249. package/layers/media/server/utils/backfill.ts +132 -0
  250. package/layers/media/server/utils/derive.ts +59 -0
  251. package/layers/media/server/utils/folder-paths.ts +42 -0
  252. package/layers/media/server/utils/folders.ts +12 -0
  253. package/layers/media/server/utils/library.ts +90 -0
  254. package/layers/media/server/utils/media-cleanup.ts +27 -0
  255. package/layers/media/server/utils/media-enabled.ts +23 -0
  256. package/layers/media/server/utils/media-ops.ts +118 -0
  257. package/layers/media/server/utils/media-write.ts +13 -0
  258. package/layers/media/server/utils/naming.ts +52 -0
  259. package/layers/media/server/utils/ondemand.ts +69 -0
  260. package/layers/media/server/utils/persist-upload.ts +62 -0
  261. package/layers/media/server/utils/populate.ts +47 -0
  262. package/layers/media/server/utils/record.ts +63 -0
  263. package/layers/media/server/utils/relocate-ops.ts +341 -0
  264. package/layers/media/server/utils/relocate-plan.ts +63 -0
  265. package/layers/media/server/utils/resolve.ts +86 -0
  266. package/layers/media/server/utils/sanitize-svg.ts +12 -0
  267. package/layers/media/server/utils/sniff.ts +68 -0
  268. package/layers/media/server/utils/storage-relocate.ts +139 -0
  269. package/layers/media/server/utils/translations.ts +19 -0
  270. package/layers/media/server/utils/usages.ts +66 -0
  271. package/layers/media/server/utils/variant-capture.ts +70 -0
  272. package/layers/media/server/utils/variants.ts +108 -0
  273. package/layers/public/app/app.vue +5 -0
  274. package/layers/public/app/components/BlockRenderer.vue +56 -0
  275. package/layers/public/app/components/KestrelLink.vue +26 -0
  276. package/layers/public/app/components/KestrelPreviewBridge.vue +133 -0
  277. package/layers/public/app/composables/public-page.ts +24 -0
  278. package/layers/public/app/error.vue +12 -0
  279. package/layers/public/app/layouts/default.vue +5 -0
  280. package/layers/public/app/pages/[...slug].vue +157 -0
  281. package/layers/public/app/pages/__kestrel/preview.vue +34 -0
  282. package/layers/public/app/utils/block-edit-context.ts +16 -0
  283. package/layers/public/app/utils/link-href.ts +46 -0
  284. package/layers/public/app/utils/page-head.ts +84 -0
  285. package/layers/public/app/utils/preview-protocol.ts +81 -0
  286. package/layers/public/app/utils/public-route.ts +46 -0
  287. package/layers/public/modules/deploy-output/deploy-output.ts +370 -0
  288. package/layers/public/modules/deploy-output/index.ts +82 -0
  289. package/layers/public/modules/prerender-routes/discover.ts +37 -0
  290. package/layers/public/modules/prerender-routes/index.ts +85 -0
  291. package/layers/public/modules/prerender-routes/routes.ts +16 -0
  292. package/layers/public/modules/prune-media/index.ts +48 -0
  293. package/layers/public/modules/prune-media/prune-media.ts +81 -0
  294. package/layers/public/nuxt.config.ts +11 -0
  295. package/layers/public/server/api/links/resolve.get.ts +19 -0
  296. package/layers/public/server/api/publish-status.get.ts +44 -0
  297. package/layers/public/server/api/route.get.ts +18 -0
  298. package/layers/public/server/database/publish-deps.ts +12 -0
  299. package/layers/public/server/database/publish-status.ts +19 -0
  300. package/layers/public/server/plugins/02.register-links.ts +17 -0
  301. package/layers/public/server/plugins/zz.publish.ts +63 -0
  302. package/layers/public/server/routes/llms.txt.get.ts +72 -0
  303. package/layers/public/server/routes/robots.txt.get.ts +8 -0
  304. package/layers/public/server/routes/sitemap.xml.get.ts +66 -0
  305. package/layers/public/server/schema-tables/publish-deps.ts +4 -0
  306. package/layers/public/server/schema-tables/publish-status.ts +4 -0
  307. package/layers/public/server/tasks/publish/run.ts +26 -0
  308. package/layers/public/server/utils/link-resolve.ts +35 -0
  309. package/layers/public/server/utils/llms.ts +39 -0
  310. package/layers/public/server/utils/page-resolve.ts +86 -0
  311. package/layers/public/server/utils/populate-links.ts +66 -0
  312. package/layers/public/server/utils/publish/coalesce.ts +22 -0
  313. package/layers/public/server/utils/publish/deps-persistence.ts +43 -0
  314. package/layers/public/server/utils/publish/deps.ts +87 -0
  315. package/layers/public/server/utils/publish/invalidation.ts +141 -0
  316. package/layers/public/server/utils/publish/publish-status.ts +54 -0
  317. package/layers/public/server/utils/publish/publisher.ts +291 -0
  318. package/layers/public/server/utils/publish/queue.ts +67 -0
  319. package/layers/public/server/utils/publish/route-for-record.ts +16 -0
  320. package/layers/public/server/utils/publish/route-keys.ts +13 -0
  321. package/layers/public/server/utils/site-url.ts +32 -0
  322. package/layers/public/server/utils/sitemap.ts +97 -0
  323. package/layers/ui/app/assets/scss/_base.scss +16 -0
  324. package/layers/ui/app/assets/scss/_datepicker.scss +82 -0
  325. package/layers/ui/app/assets/scss/_mixins.scss +50 -0
  326. package/layers/ui/app/assets/scss/_reset.scss +65 -0
  327. package/layers/ui/app/assets/scss/_tokens.scss +183 -0
  328. package/layers/ui/app/assets/scss/main.scss +4 -0
  329. package/layers/ui/app/components/field/Boolean.vue +30 -0
  330. package/layers/ui/app/components/field/Choice.vue +68 -0
  331. package/layers/ui/app/components/field/Datetime.vue +96 -0
  332. package/layers/ui/app/components/field/DeadRefNote.vue +28 -0
  333. package/layers/ui/app/components/field/Json.vue +63 -0
  334. package/layers/ui/app/components/field/Layout.vue +140 -0
  335. package/layers/ui/app/components/field/Link.vue +112 -0
  336. package/layers/ui/app/components/field/LinkInternalPicker.vue +95 -0
  337. package/layers/ui/app/components/field/Number.vue +30 -0
  338. package/layers/ui/app/components/field/Relation.vue +49 -0
  339. package/layers/ui/app/components/field/Renderer.vue +30 -0
  340. package/layers/ui/app/components/field/Repeater.vue +460 -0
  341. package/layers/ui/app/components/field/Richtext.vue +26 -0
  342. package/layers/ui/app/components/field/Slug.vue +66 -0
  343. package/layers/ui/app/components/field/Text.vue +35 -0
  344. package/layers/ui/app/components/field/Unsupported.vue +34 -0
  345. package/layers/ui/app/components/ui/Alert.vue +50 -0
  346. package/layers/ui/app/components/ui/Brand.vue +22 -0
  347. package/layers/ui/app/components/ui/Button.vue +116 -0
  348. package/layers/ui/app/components/ui/ButtonGroup.vue +96 -0
  349. package/layers/ui/app/components/ui/Checkbox.vue +52 -0
  350. package/layers/ui/app/components/ui/CheckboxGroup.vue +50 -0
  351. package/layers/ui/app/components/ui/Combobox.vue +343 -0
  352. package/layers/ui/app/components/ui/DatePicker.vue +92 -0
  353. package/layers/ui/app/components/ui/DateRangePicker.vue +100 -0
  354. package/layers/ui/app/components/ui/Dialog.vue +107 -0
  355. package/layers/ui/app/components/ui/EmptyState.vue +46 -0
  356. package/layers/ui/app/components/ui/Field.vue +57 -0
  357. package/layers/ui/app/components/ui/Fieldset.vue +64 -0
  358. package/layers/ui/app/components/ui/Icon.vue +44 -0
  359. package/layers/ui/app/components/ui/Menu.vue +54 -0
  360. package/layers/ui/app/components/ui/NumberInput.vue +173 -0
  361. package/layers/ui/app/components/ui/Richtext.vue +119 -0
  362. package/layers/ui/app/components/ui/RichtextToolbar.vue +253 -0
  363. package/layers/ui/app/components/ui/Select.vue +51 -0
  364. package/layers/ui/app/components/ui/TextInput.vue +110 -0
  365. package/layers/ui/app/components/ui/Textarea.vue +38 -0
  366. package/layers/ui/app/components/ui/TimeInput.vue +17 -0
  367. package/layers/ui/app/components/ui/Toasts.vue +103 -0
  368. package/layers/ui/app/components/ui/Tooltip.vue +45 -0
  369. package/layers/ui/app/composables/useAdminLang.ts +8 -0
  370. package/layers/ui/app/composables/useDragReorder.ts +41 -0
  371. package/layers/ui/app/composables/useEchoGuard.ts +28 -0
  372. package/layers/ui/app/composables/useLinkField.ts +70 -0
  373. package/layers/ui/app/composables/useRecordOptions.ts +84 -0
  374. package/layers/ui/app/composables/useRepeater.ts +99 -0
  375. package/layers/ui/app/composables/useT.ts +32 -0
  376. package/layers/ui/app/composables/useToast.ts +46 -0
  377. package/layers/ui/app/i18n/de.ts +406 -0
  378. package/layers/ui/app/i18n/en.ts +440 -0
  379. package/layers/ui/app/utils/boolean-field.ts +10 -0
  380. package/layers/ui/app/utils/field-component.ts +22 -0
  381. package/layers/ui/app/utils/field-empty.ts +21 -0
  382. package/layers/ui/app/utils/field-registry.ts +36 -0
  383. package/layers/ui/app/utils/humanize.ts +14 -0
  384. package/layers/ui/app/utils/icons.ts +147 -0
  385. package/layers/ui/app/utils/iso-date.ts +18 -0
  386. package/layers/ui/app/utils/localized.ts +13 -0
  387. package/layers/ui/app/utils/record-context.ts +15 -0
  388. package/layers/ui/app/utils/reorder.ts +8 -0
  389. package/layers/ui/app/utils/richtext-preserve-class.ts +31 -0
  390. package/layers/ui/app/utils/useFieldA11y.ts +40 -0
  391. package/layers/ui/nuxt.config.ts +5 -0
  392. package/nuxt.config.ts +105 -0
  393. package/package.json +122 -0
  394. package/scripts/hash-password.mjs +26 -0
  395. package/scripts/typecheck.mjs +51 -0
@@ -0,0 +1,46 @@
1
+ import { reactive } from 'vue'
2
+
3
+ export type ToastType = 'success' | 'error' | 'info'
4
+ export interface Toast { id: number; type: ToastType; message: string }
5
+
6
+ /**
7
+ * Transient notification queue: `push` returns an id, auto-expires after `timeout` (0 = sticky),
8
+ * and the queue is capped (oldest dropped). Pure factory so the logic is unit-testable; `useToast`
9
+ * shares one instance app-wide. The teleported `<UiToasts>` container renders `items`.
10
+ */
11
+ export function createToastStore(opts: { limit?: number; defaultTimeout?: number } = {}) {
12
+ const limit = opts.limit ?? 4
13
+ const defaultTimeout = opts.defaultTimeout ?? 4000
14
+ const items = reactive<Toast[]>([])
15
+ let seq = 0
16
+
17
+ function dismiss(id: number): void {
18
+ const i = items.findIndex((t) => t.id === id)
19
+ if (i !== -1) items.splice(i, 1)
20
+ }
21
+
22
+ function push(type: ToastType, message: string, timeout = defaultTimeout): number {
23
+ const id = ++seq
24
+ items.push({ id, type, message })
25
+ while (items.length > limit) items.shift()
26
+ if (timeout > 0) setTimeout(() => dismiss(id), timeout)
27
+ return id
28
+ }
29
+
30
+ return {
31
+ items,
32
+ dismiss,
33
+ push,
34
+ success: (m: string, t?: number) => push('success', m, t),
35
+ error: (m: string, t?: number) => push('error', m, t),
36
+ info: (m: string, t?: number) => push('info', m, t),
37
+ }
38
+ }
39
+
40
+ let store: ReturnType<typeof createToastStore> | null = null
41
+
42
+ /** App-wide toast queue (singleton). */
43
+ export function useToast() {
44
+ if (!store) store = createToastStore()
45
+ return store
46
+ }
@@ -0,0 +1,406 @@
1
+ import type { Catalog } from '../composables/useT'
2
+
3
+ // German admin-UI chrome. May be partial — `useT` falls back to `en` (then the key) for any missing key.
4
+ export const de: Catalog = {
5
+ 'common.new': '{label} anlegen',
6
+ 'common.save': 'Speichern',
7
+ 'common.cancel': 'Abbrechen',
8
+ 'common.create': 'Erstellen',
9
+ 'common.delete': 'Löschen',
10
+ 'common.close': 'Schließen',
11
+ 'common.retry': 'Erneut versuchen',
12
+
13
+ 'nav.dashboard': 'Übersicht',
14
+ 'nav.signOut': 'Abmelden',
15
+ 'theme.dark': 'Dunkelmodus',
16
+ 'theme.light': 'Hellmodus',
17
+ 'a11y.expandSidebar': 'Seitenleiste ausklappen',
18
+ 'a11y.collapseSidebar': 'Seitenleiste einklappen',
19
+ 'a11y.toLight': 'Zum Hellmodus wechseln',
20
+ 'a11y.toDark': 'Zum Dunkelmodus wechseln',
21
+ 'a11y.contentLocale': 'Inhaltssprache',
22
+ 'a11y.rowActions': 'Aktionen',
23
+ 'lang.label': 'Sprache',
24
+
25
+ // account menu (rail foot)
26
+ 'account.label': 'Konto',
27
+ 'account.name': 'Admin',
28
+
29
+ // text input (password reveal)
30
+ 'input.showPassword': 'Passwort anzeigen',
31
+ 'input.hidePassword': 'Passwort verbergen',
32
+
33
+ 'login.signIn': 'Anmelden',
34
+ 'login.password': 'Passwort',
35
+ 'login.invalid': 'Ungültige Anmeldedaten',
36
+ 'login.error': 'Etwas ist schiefgelaufen — bitte erneut versuchen',
37
+
38
+ 'dash.title': 'Übersicht',
39
+ 'dash.lede': 'Wähle eine Collection, um ihre Inhalte zu verwalten.',
40
+ 'dash.empty.title': 'Noch keine Collections',
41
+ 'dash.empty.desc': 'Definiere eine Collection, um Inhalte zu verwalten.',
42
+ 'dash.mode.singleton': 'Singleton',
43
+ 'dash.mode.collection': 'Collection',
44
+
45
+ 'collection.unknown': 'Unbekannte Collection: {name}',
46
+
47
+ 'editor.back': '{collection}',
48
+ 'editor.newRecord': '{collection} anlegen',
49
+ 'editor.editRecord': '{collection} #{id} bearbeiten',
50
+ 'editor.discardConfirm': 'Ungespeicherte Änderungen verwerfen?',
51
+ 'editor.fixPageFields': 'Bitte korrigiere die markierten Seiten-Felder vor dem Speichern.',
52
+ 'editor.fixBlockContent': 'Einige Block-Inhalte sind ungültig. Prüfe die markierten Block-Felder.',
53
+ 'editor.saveConflict': 'Das steht im Konflikt mit einem vorhandenen Datensatz.',
54
+ 'editor.saveFailed': 'Speichern fehlgeschlagen.',
55
+ 'toast.saved': 'Gespeichert',
56
+ 'toast.deleted': 'Gelöscht',
57
+ 'toast.duplicated': 'Dupliziert',
58
+ 'toast.published': 'Veröffentlicht',
59
+ 'toast.unpublished': 'Zurückgezogen',
60
+ 'toast.region': 'Benachrichtigungen',
61
+ 'toast.dismiss': 'Schließen',
62
+
63
+ 'list.translations': 'Übersetzungen',
64
+ 'list.deadRefs': 'Verweise',
65
+ 'list.filter': 'Filter',
66
+ 'list.filterPlaceholder': '{name} filtern',
67
+ 'list.filterClearAll': 'Alle Filter zurücksetzen',
68
+ 'list.removeFilter': 'Filter {name} entfernen',
69
+ 'list.noFilters': 'Keine filterbaren Spalten.',
70
+ // Filter-Operatoren (generischer Satz) + datetime-spezifische Varianten (filter.opDate.*) + Bool-Werte
71
+ 'filter.operator': 'Operator',
72
+ 'filter.op.eq': 'gleich',
73
+ 'filter.op.ne': 'ungleich',
74
+ 'filter.op.lt': 'kleiner als',
75
+ 'filter.op.lte': 'kleiner gleich',
76
+ 'filter.op.gt': 'größer als',
77
+ 'filter.op.gte': 'größer gleich',
78
+ 'filter.op.contains': 'enthält',
79
+ 'filter.op.notContains': 'enthält nicht',
80
+ 'filter.opDate.lt': 'vor',
81
+ 'filter.opDate.lte': 'am oder vor',
82
+ 'filter.opDate.gt': 'nach',
83
+ 'filter.opDate.gte': 'am oder nach',
84
+ 'filter.bool.true': 'Ja',
85
+ 'filter.bool.false': 'Nein',
86
+ 'list.columns': 'Spalten',
87
+ 'list.columnsReset': 'Auf Standard zurücksetzen',
88
+ 'list.col.id': 'ID',
89
+ 'list.col.slug': 'Slug',
90
+ 'list.col.status': 'Status',
91
+ 'list.col.createdAt': 'Erstellt',
92
+ 'list.col.updatedAt': 'Geändert',
93
+ 'list.editLocale': '{loc} bearbeiten',
94
+ 'list.createLocale': '{loc} erstellen',
95
+ 'list.empty.title': 'Noch keine Einträge',
96
+ 'list.empty.desc': 'Noch nichts in {name} — erstelle den ersten Eintrag.',
97
+ 'list.noMatch.title': 'Keine passenden Einträge',
98
+ 'list.noMatch.desc': 'Keine {name} entsprechen den aktiven Filtern.',
99
+ 'list.prev': 'Zurück',
100
+ 'list.page': 'Seite {page} von {totalPages} · {total} gesamt',
101
+ 'list.next': 'Weiter',
102
+ 'list.perPage': 'Pro Seite',
103
+ 'list.result': '{total} Ergebnis',
104
+ 'list.results': '{total} Ergebnisse',
105
+ 'list.loadError': 'Diese Collection konnte nicht geladen werden.',
106
+ // Zeilenauswahl + Sammelaktionen
107
+ 'list.selectAll': 'Alle Zeilen auf dieser Seite auswählen',
108
+ 'list.selectRow': '{name} auswählen',
109
+ 'list.selected': '{n} ausgewählt',
110
+ 'list.clearSelection': 'Aufheben',
111
+ 'list.rowEdit': '{name} bearbeiten',
112
+ 'list.rowDuplicate': '{name} duplizieren',
113
+ 'list.rowDelete': '{name} löschen',
114
+ 'list.bulkDelete': 'Löschen',
115
+ 'list.bulkPublish': 'Veröffentlichen',
116
+ 'list.bulkUnpublish': 'Zurückziehen',
117
+ 'list.deleteSummary': '{n} Datensatz/-sätze löschen? Das kann nicht rückgängig gemacht werden.',
118
+ 'list.deleteReferrersWarn': 'Auf {n} davon verweisen andere Datensätze, die dann einen defekten Verweis behalten.',
119
+ 'list.deleteRefsUnverified': 'Verweise konnten nicht geprüft werden — einige Datensätze könnten mit einem defekten Verweis zurückbleiben. Mit Vorsicht fortfahren.',
120
+ 'list.opFailed': 'Die Aktion konnte nicht abgeschlossen werden.',
121
+ 'list.refCheckFailed': 'Verweise konnten nicht geprüft werden.',
122
+
123
+ 'preview.ariaLabel': 'Live-Vorschau',
124
+ 'preview.label': 'Vorschau',
125
+ 'preview.deviceLabel': 'Vorschau-Größe',
126
+ 'preview.device.desktop': 'Desktop',
127
+ 'preview.device.tablet': 'Tablet',
128
+ 'preview.device.mobile': 'Mobil',
129
+ 'preview.refresh': 'Vorschau aktualisieren',
130
+ 'preview.auto': 'Auto',
131
+ 'preview.width': 'Breite',
132
+ 'preview.height': 'Höhe',
133
+
134
+ 'pageSettings.slugLabel': 'Slug',
135
+ 'pageSettings.slugHint': 'URL-Pfad, z. B. /about (leer = automatisch aus dem Titel)',
136
+ 'pageSettings.statusLabel': 'Status',
137
+ 'pageSettings.statusHint': 'Entwurf bleibt offline; Veröffentlicht rendert die Seite.',
138
+ 'pageSettings.statusDraft': 'Entwurf',
139
+ 'pageSettings.statusPublished': 'Veröffentlicht',
140
+
141
+ 'editor.openInNewTab': 'In neuem Tab öffnen',
142
+ // editor status light — LEFT lamp: ONE word + details tooltip (save/publish status)
143
+ 'editorStatus.word.saving': 'Speichert…',
144
+ 'editorStatus.word.unsaved': 'Ungespeichert',
145
+ 'editorStatus.word.draft': 'Entwurf',
146
+ 'editorStatus.word.published': 'Veröffentlicht',
147
+ 'editorStatus.word.saved': 'Gespeichert',
148
+ 'editorStatus.detail.saving': 'Änderungen werden gespeichert…',
149
+ 'editorStatus.detail.unsaved': 'Es gibt ungespeicherte lokale Änderungen.',
150
+ 'editorStatus.detail.draft': 'Als Entwurf gespeichert — nicht auf der Live-Seite.',
151
+ 'editorStatus.detail.published': 'Gespeichert und veröffentlicht.',
152
+ 'editorStatus.detail.saved': 'Alle Änderungen gespeichert.',
153
+ // editor status light — RIGHT lamp: ONE word + details tooltip (static-page generation status)
154
+ 'editorStatus.word.live': 'Live',
155
+ 'editorStatus.word.generating': 'Generiert…',
156
+ 'editorStatus.word.notBuilt': 'Nicht gebaut',
157
+ 'editorStatus.word.notLive': 'Nicht live',
158
+ 'editorStatus.word.error': 'Fehler',
159
+ 'editorStatus.live.liveS3': 'Live — Seite auf S3 abgelegt.',
160
+ 'editorStatus.live.liveLocal': 'Live — Seite in den statischen Output geschrieben.',
161
+ 'editorStatus.live.pending': 'Wird veröffentlicht…',
162
+ 'editorStatus.live.notBuilt': 'In dieser Umgebung nicht generiert — die statische Seite wird beim Deploy gebaut.',
163
+ 'editorStatus.live.error': 'Generieren der statischen Seite fehlgeschlagen.',
164
+ 'editorStatus.live.draft': 'Entwurf — nicht generiert. Erscheint erst nach Veröffentlichung auf der Live-Seite.',
165
+ 'editorStatus.live.updatedAt': 'Zuletzt veröffentlicht {time}',
166
+
167
+ 'seo.sectionLabel': 'SEO',
168
+ 'seo.metaTitle': 'Meta-Titel',
169
+ 'seo.metaDescription': 'Meta-Beschreibung',
170
+ 'seo.noindexLabel': 'Diese Seite vor Suchmaschinen verbergen (noindex)',
171
+ 'seo.untitled': '(ohne Titel)',
172
+ 'seo.noDescription': 'Keine Meta-Beschreibung — Suchmaschinen wählen ein Snippet.',
173
+ 'field.boolean.yes': 'Ja',
174
+ 'field.boolean.no': 'Nein',
175
+ 'editor.unsupported': 'Für den Typ „{type}“ ist kein Editor registriert.',
176
+ 'seo.socialImage': 'Social-Share-Bild',
177
+ 'seo.socialImageHint': 'Wird als Vorschaubild beim Teilen der Seite angezeigt (og:image).',
178
+
179
+ 'localeBar.fieldLabel': 'Sprache',
180
+ 'localeBar.groupLabel': 'Inhaltssprache',
181
+ 'localeBar.editLocale': '{loc} bearbeiten',
182
+ 'localeBar.copyInto': '{loc} in {current} kopieren',
183
+ 'localeBar.createTranslation': '{loc}-Übersetzung erstellen',
184
+ 'localeBar.copyFailed': 'Konnte nicht von {locale} kopieren.',
185
+
186
+ 'history.label': 'Bearbeitungsverlauf',
187
+ 'history.undo': 'Rückgängig',
188
+ 'history.redo': 'Wiederholen',
189
+
190
+ 'blocks.empty': 'Noch keine Blöcke.',
191
+ 'blocks.moveUp': 'Block {n} nach oben verschieben',
192
+ 'blocks.moveDown': 'Block {n} nach unten verschieben',
193
+ 'blocks.duplicate': 'Block {n} duplizieren',
194
+ 'blocks.remove': 'Block {n} entfernen',
195
+ 'blocks.add': 'Block hinzufügen',
196
+ 'blocks.pickType': 'Block-Typ wählen',
197
+ 'blocks.addOfType': '{type} hinzufügen',
198
+ 'blocks.page': 'Seite',
199
+ 'blocks.pageFields': 'Seiten-Felder',
200
+ 'blocks.invalid': 'Enthält Fehler',
201
+ 'blocks.addInto': 'Zu {slot} hinzufügen',
202
+ 'blocks.noFields': 'Dieser Block hat keine bearbeitbaren Felder.',
203
+ 'blocks.fieldsLabel': 'Block-Felder',
204
+ 'blocks.treeLabel': 'Block-Struktur',
205
+
206
+ // dead references (derived warnings)
207
+ 'deadRefs.listWarning': 'Verweist auf einen gelöschten oder unveröffentlichten Datensatz',
208
+ 'deadRefs.blockBadge': 'Defekter Verweis',
209
+ 'deadRefs.fieldNote': 'Verweist auf einen gelöschten oder unveröffentlichten Datensatz.',
210
+
211
+ // broken-references report (the reverse "what links here" view)
212
+ 'refs.title': 'Defekte Verweise',
213
+ 'refs.lede': 'Datensätze, die auf ein gelöschtes oder unveröffentlichtes Ziel verweisen. Ihre veröffentlichte Ausgabe behält den ungültigen Verweis, bis du sie erneut bearbeitest.',
214
+ 'refs.loadError': 'Der Verweis-Bericht konnte nicht geladen werden — er ist nicht verifiziert.',
215
+ 'refs.empty.title': 'Keine defekten Verweise',
216
+ 'refs.empty.desc': 'Jeder Verweis zeigt auf einen vorhandenen, veröffentlichten Datensatz.',
217
+ 'refs.colReferrer': 'Datensatz',
218
+ 'refs.colTarget': 'Verweist auf',
219
+ 'refs.colReason': 'Problem',
220
+ 'refs.reason.missing': 'Gelöscht',
221
+ 'refs.reason.unpublished': 'Unveröffentlicht',
222
+ 'refs.dashAlert': '{n} defekte(r) Verweis(e) — prüfen',
223
+
224
+ 'gallery.title': 'Feld-Galerie',
225
+ 'gallery.lede.before': 'Alle implementierten Feld-Widgets, live. Die Relation- und interne-Link-Picker fragen ',
226
+ 'gallery.lede.after': ' ab – erstelle ein oder zwei Posts, um Ergebnisse zu sehen.',
227
+
228
+ 'media.title': 'Medien',
229
+ 'media.picker.titleSingle': 'Datei auswählen',
230
+ 'media.picker.titleMultiple': 'Medien auswählen',
231
+ // media — right-click context menu
232
+ 'media.menu.cut': 'Ausschneiden',
233
+ 'media.menu.copy': 'Kopieren',
234
+ 'media.menu.paste': 'Einfügen',
235
+ 'media.menu.pasteInto': 'In Ordner einfügen',
236
+ 'media.menu.rename': 'Umbenennen',
237
+ 'media.menu.delete': 'Löschen',
238
+ 'media.menu.deleteN': '{n} Elemente löschen',
239
+ // media — error fallbacks (the server statusMessage is preferred when present)
240
+ 'media.error.load': 'Medien konnten nicht geladen werden.',
241
+ 'media.error.relocate': 'Der Vorgang konnte nicht abgeschlossen werden.',
242
+ 'media.error.relocateConflict': 'Einige Elemente konnten nicht verschoben oder kopiert werden.',
243
+ 'media.folderEmpty': 'Dieser Ordner ist leer.',
244
+ 'media.folderNotFound': 'Dieser Ordner existiert nicht.',
245
+ 'media.loadMore': 'Mehr laden',
246
+ 'media.clipboardItem': 'Element',
247
+ 'media.clipboardItems': 'Elemente',
248
+ 'media.clipboardCut': 'ausgeschnitten',
249
+ 'media.clipboardCopied': 'kopiert',
250
+ 'media.pasteHere': 'Hier einfügen',
251
+ 'media.clear': 'Leeren',
252
+ 'media.uploading': 'Wird hochgeladen… ',
253
+ 'media.uploadUploaded': 'hochgeladen',
254
+ 'media.uploadFailed': 'fehlgeschlagen',
255
+ 'media.uploadSkipped': 'übersprungen',
256
+ 'media.uploadError': '{name}: {reason}',
257
+ 'media.uploadFailedReason': 'Upload fehlgeschlagen',
258
+ 'media.selected': 'ausgewählt',
259
+ 'media.useSelected': 'Auswahl verwenden',
260
+ 'media.dropToUpload': 'Dateien zum Hochladen ablegen',
261
+ 'media.conflicts.title': 'Upload-Konflikte auflösen',
262
+ 'media.conflicts.desc': '{count} Datei(en) existiert bereits. Wähle, wie jede aufgelöst werden soll.',
263
+ 'media.conflicts.applyAll': 'Auf alle anwenden:',
264
+ 'media.conflicts.overwriteAll': 'Alle überschreiben',
265
+ 'media.conflicts.renameAll': 'Alle umbenennen',
266
+ 'media.conflicts.skipAll': 'Alle überspringen',
267
+ 'media.conflicts.newName': 'Neuer Name für {name}',
268
+ 'media.conflicts.overwrite': 'Überschreiben',
269
+ 'media.conflicts.rename': 'Umbenennen',
270
+ 'media.conflicts.skip': 'Überspringen',
271
+ 'media.newFolder': 'Neuer Ordner',
272
+ 'media.folderName': 'Ordnername',
273
+ 'media.folderNamePlaceholder': 'z.B. Urlaub',
274
+ 'media.rename': 'Umbenennen',
275
+ 'media.newName': 'Neuer Name',
276
+ 'media.deleteSummary': 'Löscht {files} {filesUnit} und {folders} {foldersUnit} — {size}.',
277
+ 'media.file': 'Datei',
278
+ 'media.files': 'Dateien',
279
+ 'media.folder': 'Ordner',
280
+ 'media.folders': 'Ordner',
281
+ 'media.usageWarning': 'Diese Dateien werden von veröffentlichten Inhalten verwendet, und Links werden ungültig:',
282
+ 'media.conflictTitleMove': 'Verschieben — Konflikte',
283
+ 'media.conflictTitleCopy': 'Kopieren — Konflikte',
284
+ 'media.conflictItems': '{count} Element(e) existiert/en bereits in "{dest}":',
285
+ 'media.rootFolder': 'Stammverzeichnis',
286
+ 'media.conflictFolderExists': 'Ordner existiert bereits',
287
+ 'media.conflictFileExists': 'Datei existiert bereits',
288
+ 'media.skip': 'Überspringen',
289
+ 'media.replace': 'Ersetzen',
290
+ 'media.keepBoth': 'Beide behalten',
291
+ 'media.colName': 'Name',
292
+ 'media.colType': 'Typ',
293
+ 'media.colSize': 'Größe',
294
+ 'media.colDimensions': 'Abmessungen',
295
+ 'media.typeFolder': 'Ordner',
296
+ 'media.parentFolder': 'Zum übergeordneten Ordner',
297
+
298
+ 'mediaViewer.folder': 'Ordner',
299
+ 'mediaViewer.uploaded': 'Hochgeladen',
300
+ 'mediaViewer.alt': 'Alt-Text',
301
+ 'mediaViewer.altHint': 'Beschreibt das Bild für Screenreader und SEO.',
302
+ 'mediaViewer.saveFailed': 'Alt-Text konnte nicht gespeichert werden.',
303
+
304
+ // richtext toolbar
305
+ 'richtext.bold': 'Fett',
306
+ 'richtext.italic': 'Kursiv',
307
+ 'richtext.underline': 'Unterstrichen',
308
+ 'richtext.strikethrough': 'Durchgestrichen',
309
+ 'richtext.inlineCode': 'Inline-Code',
310
+ 'richtext.highlight': 'Hervorheben',
311
+ 'richtext.subscript': 'Tiefgestellt',
312
+ 'richtext.superscript': 'Hochgestellt',
313
+ 'richtext.bulletList': 'Aufzählungsliste',
314
+ 'richtext.orderedList': 'Nummerierte Liste',
315
+ 'richtext.blockquote': 'Blockzitat',
316
+ 'richtext.codeBlock': 'Code-Block',
317
+ 'richtext.horizontalRule': 'Horizontale Linie',
318
+ 'richtext.alignLeft': 'Linksbündig',
319
+ 'richtext.alignCenter': 'Zentriert',
320
+ 'richtext.alignRight': 'Rechtsbündig',
321
+ 'richtext.justify': 'Blocksatz',
322
+ 'richtext.clearFormatting': 'Formatierung löschen',
323
+ 'richtext.undo': 'Rückgängig',
324
+ 'richtext.redo': 'Wiederholen',
325
+ 'richtext.paragraph': 'Absatz',
326
+ 'richtext.heading1': 'Überschrift 1',
327
+ 'richtext.heading2': 'Überschrift 2',
328
+ 'richtext.heading3': 'Überschrift 3',
329
+ 'richtext.heading4': 'Überschrift 4',
330
+ 'richtext.heading5': 'Überschrift 5',
331
+ 'richtext.heading6': 'Überschrift 6',
332
+ 'richtext.blockType': 'Blocktyp',
333
+ 'richtext.link': 'Link',
334
+ 'richtext.linkInternal': 'Interner Link',
335
+ 'richtext.urlPlaceholder': 'https://…',
336
+ 'richtext.linkUrl': 'Link-URL',
337
+ 'richtext.apply': 'Übernehmen',
338
+
339
+ // media toolbar
340
+ 'mediaToolbar.searchPlaceholder': 'Suchen…',
341
+ 'mediaToolbar.searchAriaLabel': 'Medien durchsuchen',
342
+ 'mediaToolbar.viewGrid': 'Raster',
343
+ 'mediaToolbar.viewTable': 'Tabelle',
344
+ 'mediaToolbar.viewAriaLabel': 'Ansicht',
345
+ 'mediaToolbar.upload': 'Hochladen',
346
+ 'mediaToolbar.newFolder': 'Neuer Ordner',
347
+
348
+ 'mediaPath.navAriaLabel': 'Pfad',
349
+ 'mediaPath.editAriaLabel': 'Pfad bearbeiten',
350
+ 'mediaPath.inputAriaLabel': 'Pfad eingeben',
351
+
352
+ 'nav.collections': 'Collections',
353
+
354
+ 'field.datetime.range_start': 'Start',
355
+ 'field.datetime.range_end': 'Ende',
356
+ 'field.link.type_internal': 'Intern',
357
+ 'field.link.type_url': 'URL',
358
+ 'field.link.type_email': 'E-Mail',
359
+ 'field.link.type_phone': 'Telefon',
360
+ 'field.link.link_type': 'Link-Typ',
361
+ 'field.link.url_placeholder': 'https://…',
362
+ 'field.link.label_placeholder': 'Linktext (optional)',
363
+ 'field.link.hash_placeholder': 'Anker (optional, z. B. abschnitt-id)',
364
+ 'field.relation.placeholder': 'Suchen…',
365
+ 'field.linkPicker.loadError': 'Collections konnten nicht geladen werden.',
366
+ 'field.linkPicker.chooseCollection': 'Collection wählen…',
367
+ 'field.linkPicker.collectionLabel': 'Collection',
368
+ 'field.linkPicker.searchRecords': 'Einträge suchen…',
369
+ 'field.repeater.moved': 'Eintrag an Position {n} von {total} verschoben',
370
+ 'field.repeater.removed': 'Eintrag {n} entfernt, noch {remaining} übrig',
371
+ 'field.repeater.duplicated': 'Eintrag {n} dupliziert',
372
+ 'field.repeater.inserted': 'Eintrag an Position {n} eingefügt',
373
+ 'field.repeater.insert_label': 'Eintrag an Position {n} einfügen',
374
+ 'field.repeater.item_label': 'Eintrag {n}',
375
+ 'field.repeater.move_up': 'Eintrag {n} nach oben verschieben',
376
+ 'field.repeater.move_down': 'Eintrag {n} nach unten verschieben',
377
+ 'field.repeater.duplicate_label': 'Eintrag {n} duplizieren',
378
+ 'field.repeater.remove_label': 'Eintrag {n} entfernen',
379
+ 'field.repeater.empty': 'Noch keine Einträge.',
380
+ 'field.repeater.add': 'Eintrag hinzufügen',
381
+ 'field.media.empty': 'Keine Medien ausgewählt.',
382
+ 'field.media.add': 'Medien hinzufügen',
383
+ 'field.media.replace': 'Ersetzen',
384
+ 'field.media.select': 'Medien auswählen',
385
+ 'field.media.remove': '{name} entfernen',
386
+ 'field.media.itemAt': 'Medium #{pos}',
387
+ 'field.media.move_earlier': '{name} nach vorne verschieben',
388
+ 'field.media.move_later': '{name} nach hinten verschieben',
389
+ 'field.media.moved': '{name} an Position {pos} von {total} verschoben',
390
+
391
+ 'combobox.moved': '{label} an Position {pos} von {total} verschoben',
392
+ 'combobox.move_earlier': '{label} nach vorne verschieben',
393
+ 'combobox.move_later': '{label} nach hinten verschieben',
394
+ 'combobox.remove': '{label} entfernen',
395
+ 'combobox.clear': 'Auswahl aufheben',
396
+ 'combobox.toggle_options': 'Optionen ein-/ausblenden',
397
+ 'combobox.loading': 'Wird geladen…',
398
+ 'combobox.no_results': 'Keine Ergebnisse',
399
+
400
+ 'datePicker.openCalendar': 'Kalender öffnen',
401
+ 'datePicker.previousMonth': 'Vorheriger Monat',
402
+ 'datePicker.nextMonth': 'Nächster Monat',
403
+ 'dateRange.openCalendar': 'Kalender öffnen',
404
+ 'dateRange.previousMonth': 'Vorheriger Monat',
405
+ 'dateRange.nextMonth': 'Nächster Monat',
406
+ }