includio-cms 0.1.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/ROADMAP.md +18 -13
  3. package/dist/admin/api/accept-invite.js +1 -5
  4. package/dist/admin/api/invite.js +7 -16
  5. package/dist/admin/client/account/account-page.svelte +20 -50
  6. package/dist/admin/client/account/lang.d.ts +15 -23
  7. package/dist/admin/client/account/lang.js +51 -67
  8. package/dist/admin/client/account/preferences-section.svelte +26 -84
  9. package/dist/admin/client/account/profile-section.svelte +60 -40
  10. package/dist/admin/client/account/schema.d.ts +11 -3
  11. package/dist/admin/client/account/schema.js +25 -16
  12. package/dist/admin/client/account/security-section.svelte +139 -105
  13. package/dist/admin/client/account/sessions-section.svelte +35 -34
  14. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +3 -5
  15. package/dist/admin/client/admin/admin-layout.svelte +3 -2
  16. package/dist/admin/client/admin/admin-preloader.svelte +36 -0
  17. package/dist/admin/client/admin/admin-preloader.svelte.d.ts +18 -0
  18. package/dist/admin/client/admin/dashboard-page.svelte +55 -41
  19. package/dist/admin/client/collection/a11y-score-cell.svelte +45 -0
  20. package/dist/admin/client/collection/a11y-score-cell.svelte.d.ts +6 -0
  21. package/dist/admin/client/collection/bulk-actions-bar.svelte +83 -0
  22. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +9 -0
  23. package/dist/admin/client/collection/collection-entries.svelte +255 -260
  24. package/dist/admin/client/collection/collection-view.svelte.d.ts +4 -3
  25. package/dist/admin/client/collection/collection-view.svelte.js +9 -5
  26. package/dist/admin/client/collection/collection.svelte +22 -12
  27. package/dist/admin/client/collection/data-table.svelte +50 -39
  28. package/dist/admin/client/collection/data-table.svelte.d.ts +1 -0
  29. package/dist/admin/client/collection/date-cell.svelte +7 -5
  30. package/dist/admin/client/collection/date-cell.svelte.d.ts +1 -1
  31. package/dist/admin/client/collection/empty-state.svelte +28 -0
  32. package/dist/admin/client/collection/empty-state.svelte.d.ts +9 -0
  33. package/dist/admin/client/collection/entry-link.svelte +10 -4
  34. package/dist/admin/client/collection/entry-link.svelte.d.ts +1 -0
  35. package/dist/admin/client/collection/grid-view.svelte +21 -23
  36. package/dist/admin/client/collection/grid-view.svelte.d.ts +1 -2
  37. package/dist/admin/client/collection/row-actions.svelte +60 -0
  38. package/dist/admin/client/collection/row-actions.svelte.d.ts +9 -0
  39. package/dist/admin/client/collection/status-badge.svelte +7 -8
  40. package/dist/admin/client/collection/table-pagination.svelte +122 -79
  41. package/dist/admin/client/collection/table-pagination.svelte.d.ts +1 -0
  42. package/dist/admin/client/collection/table-toolbar.svelte +108 -88
  43. package/dist/admin/client/collection/table-toolbar.svelte.d.ts +8 -9
  44. package/dist/admin/client/entry/entry-form.svelte +109 -1
  45. package/dist/admin/client/entry/entry-header.svelte +96 -37
  46. package/dist/admin/client/entry/entry-header.svelte.d.ts +5 -0
  47. package/dist/admin/client/entry/entry.svelte +171 -60
  48. package/dist/admin/client/entry/header/a11y-validator.d.ts +46 -0
  49. package/dist/admin/client/entry/header/a11y-validator.js +311 -0
  50. package/dist/admin/client/entry/header/publish-panel.svelte +373 -131
  51. package/dist/admin/client/entry/header/publish-panel.svelte.d.ts +4 -0
  52. package/dist/admin/client/entry/header/save-indicator.svelte +33 -23
  53. package/dist/admin/client/entry/header/schedule-popover.svelte +1 -1
  54. package/dist/admin/client/entry/header/status-badge.svelte +25 -118
  55. package/dist/admin/client/entry/header/version-history-sheet.svelte +314 -98
  56. package/dist/admin/client/form/form-submission/form-submission.svelte +271 -83
  57. package/dist/admin/client/form/form-submission/submission-field.svelte +12 -12
  58. package/dist/admin/client/form/form-submissions.svelte +421 -139
  59. package/dist/admin/client/form/submission-link.svelte +8 -2
  60. package/dist/admin/client/form/submission-link.svelte.d.ts +1 -0
  61. package/dist/admin/client/form/submission-status-badge.svelte +18 -4
  62. package/dist/admin/client/form/submission-status-badge.svelte.d.ts +1 -0
  63. package/dist/admin/client/login/lang.d.ts +32 -0
  64. package/dist/admin/client/login/lang.js +66 -2
  65. package/dist/admin/client/login/login-form.svelte +237 -95
  66. package/dist/admin/client/login/login-form.svelte.d.ts +2 -17
  67. package/dist/admin/client/login/login-page.svelte +34 -98
  68. package/dist/admin/client/login/reset-password-page.svelte +235 -0
  69. package/dist/admin/client/login/reset-password-page.svelte.d.ts +4 -0
  70. package/dist/admin/client/login/schema.d.ts +15 -0
  71. package/dist/admin/client/login/schema.js +21 -0
  72. package/dist/admin/client/users/accept-invite-page.svelte +166 -37
  73. package/dist/admin/client/users/create-user-dialog.svelte +15 -7
  74. package/dist/admin/client/users/delete-user-dialog.svelte +81 -16
  75. package/dist/admin/client/users/delete-user-dialog.svelte.d.ts +4 -1
  76. package/dist/admin/client/users/edit-user-dialog.svelte +3 -0
  77. package/dist/admin/client/users/invite-user-dialog.svelte +16 -3
  78. package/dist/admin/client/users/lang.d.ts +27 -0
  79. package/dist/admin/client/users/lang.js +64 -10
  80. package/dist/admin/client/users/pending-invitations.svelte +59 -23
  81. package/dist/admin/client/users/users-page.svelte +471 -72
  82. package/dist/admin/components/accessibility/accessibility-overview.svelte +2 -7
  83. package/dist/admin/components/dashboard/a11y-gauge.svelte +90 -0
  84. package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +18 -0
  85. package/dist/admin/components/dashboard/accessibility-hub.svelte +13 -12
  86. package/dist/admin/components/dashboard/form-submissions-widget.svelte +71 -113
  87. package/dist/admin/components/dashboard/index.d.ts +4 -2
  88. package/dist/admin/components/dashboard/index.js +4 -2
  89. package/dist/admin/components/dashboard/recent-activity.svelte +53 -75
  90. package/dist/admin/components/dashboard/recent-entries.svelte +94 -0
  91. package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +18 -0
  92. package/dist/admin/components/dashboard/stat-card.svelte +2 -2
  93. package/dist/admin/components/dashboard/tip-of-the-day.svelte +109 -0
  94. package/dist/admin/components/dashboard/tip-of-the-day.svelte.d.ts +3 -0
  95. package/dist/admin/components/dashboard/welcome-header.svelte +45 -0
  96. package/dist/admin/components/dashboard/welcome-header.svelte.d.ts +3 -0
  97. package/dist/admin/components/fields/{array-field.svelte → blocks-field.svelte} +4 -4
  98. package/dist/admin/components/fields/{array-field.svelte.d.ts → blocks-field.svelte.d.ts} +5 -5
  99. package/dist/admin/components/fields/content-field.svelte +27 -0
  100. package/dist/admin/components/fields/content-field.svelte.d.ts +31 -0
  101. package/dist/admin/components/fields/field-renderer.svelte +9 -7
  102. package/dist/admin/components/fields/image-field.svelte +2 -2
  103. package/dist/admin/components/fields/media-field.svelte +2 -2
  104. package/dist/admin/components/fields/seo-field.svelte +205 -25
  105. package/dist/admin/components/fields/simple-array-field.svelte +289 -0
  106. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +30 -0
  107. package/dist/admin/components/fields/slug-field.svelte +3 -2
  108. package/dist/admin/components/fields/standalone-field-renderer.svelte +148 -0
  109. package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +9 -0
  110. package/dist/admin/components/fields/text-field-wrapper.svelte +13 -1
  111. package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
  112. package/dist/admin/components/fields/url-field.svelte +5 -4
  113. package/dist/admin/components/layout/app-sidebar.svelte +27 -24
  114. package/dist/admin/components/layout/lang.d.ts +6 -0
  115. package/dist/admin/components/layout/lang.js +13 -1
  116. package/dist/admin/components/layout/layout-renderer.svelte +352 -0
  117. package/dist/admin/components/layout/layout-renderer.svelte.d.ts +14 -0
  118. package/dist/admin/components/layout/nav-breadcrumbs.svelte +4 -4
  119. package/dist/admin/components/layout/nav-collections.svelte +65 -36
  120. package/dist/admin/components/layout/nav-footer.svelte +31 -0
  121. package/dist/admin/components/layout/nav-footer.svelte.d.ts +18 -0
  122. package/dist/admin/components/layout/nav-forms.svelte +55 -30
  123. package/dist/admin/components/layout/nav-main.svelte +14 -52
  124. package/dist/admin/components/layout/nav-search.svelte +4 -3
  125. package/dist/admin/components/layout/nav-singletons.svelte +59 -17
  126. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +17 -8
  127. package/dist/admin/components/layout/site-header.svelte +74 -13
  128. package/dist/admin/components/media/alt-input.svelte +32 -22
  129. package/dist/admin/components/media/bulk-action-bar.svelte +139 -150
  130. package/dist/admin/components/media/file/file-details.svelte +299 -217
  131. package/dist/admin/components/media/file/file-miniature.svelte +54 -41
  132. package/dist/admin/components/media/file/file-miniature.svelte.d.ts +1 -0
  133. package/dist/admin/components/media/file/file-preview.svelte +1 -1
  134. package/dist/admin/components/media/file-upload.svelte +24 -26
  135. package/dist/admin/components/media/files-list.svelte +112 -40
  136. package/dist/admin/components/media/files-list.svelte.d.ts +2 -0
  137. package/dist/admin/components/media/focal-point-input.svelte +122 -26
  138. package/dist/admin/components/media/media-library.svelte +127 -70
  139. package/dist/admin/components/media/media-search.svelte +6 -6
  140. package/dist/admin/components/media/media-sort.svelte +3 -1
  141. package/dist/admin/components/media/multi-file-summary.svelte +88 -68
  142. package/dist/admin/components/media/tag-combobox.svelte +141 -66
  143. package/dist/admin/components/media/tag-combobox.svelte.d.ts +1 -0
  144. package/dist/admin/components/media/tag-sidebar.svelte +139 -121
  145. package/dist/admin/components/tiptap/FigureNodeView.svelte +144 -15
  146. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +254 -0
  147. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte.d.ts +4 -0
  148. package/dist/admin/components/tiptap/SlashCommandPopup.svelte +212 -0
  149. package/dist/admin/components/tiptap/SlashCommandPopup.svelte.d.ts +8 -0
  150. package/dist/admin/components/tiptap/content-editor.svelte +280 -0
  151. package/dist/admin/components/tiptap/content-editor.svelte.d.ts +9 -0
  152. package/dist/admin/components/tiptap/editor-toolbar.svelte +230 -0
  153. package/dist/admin/components/tiptap/editor-toolbar.svelte.d.ts +16 -0
  154. package/dist/admin/components/tiptap/heading-a11y-plugin.d.ts +2 -0
  155. package/dist/admin/components/tiptap/heading-a11y-plugin.js +67 -0
  156. package/dist/admin/components/tiptap/image-dialog.svelte +172 -11
  157. package/dist/admin/components/tiptap/inline-block-node.d.ts +19 -0
  158. package/dist/admin/components/tiptap/inline-block-node.js +98 -0
  159. package/dist/admin/components/tiptap/link-dialog.svelte +9 -4
  160. package/dist/admin/components/tiptap/slash-command.d.ts +17 -0
  161. package/dist/admin/components/tiptap/slash-command.js +181 -0
  162. package/dist/admin/components/tiptap/structured-content-utils.d.ts +21 -0
  163. package/dist/admin/components/tiptap/structured-content-utils.js +150 -0
  164. package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -190
  165. package/dist/admin/email/invite-template.d.ts +8 -0
  166. package/dist/admin/email/invite-template.js +99 -0
  167. package/dist/admin/email/reset-password-template.d.ts +7 -0
  168. package/dist/admin/email/reset-password-template.js +96 -0
  169. package/dist/admin/remote/ai.remote.d.ts +1 -0
  170. package/dist/admin/remote/ai.remote.js +4 -1
  171. package/dist/admin/remote/entry.remote.d.ts +8 -0
  172. package/dist/admin/remote/entry.remote.js +53 -4
  173. package/dist/admin/remote/preview.remote.js +2 -1
  174. package/dist/admin/shared/password-schema.d.ts +5 -0
  175. package/dist/admin/shared/password-schema.js +10 -0
  176. package/dist/admin/styles/admin.css +1530 -151
  177. package/dist/admin/utils/formatDate.d.ts +1 -0
  178. package/dist/admin/utils/formatDate.js +8 -0
  179. package/dist/admin/utils/roleLabel.d.ts +2 -0
  180. package/dist/admin/utils/roleLabel.js +13 -0
  181. package/dist/ai-claude/index.d.ts +2 -0
  182. package/dist/ai-claude/index.js +56 -0
  183. package/dist/cms/runtime/api.d.ts +6 -1
  184. package/dist/cms/runtime/api.js +3 -0
  185. package/dist/cms/runtime/schemas.d.ts +9 -1
  186. package/dist/cms/runtime/schemas.js +8 -0
  187. package/dist/cms/runtime/types.d.ts +82 -10
  188. package/dist/cms/runtime/types.js +4 -0
  189. package/dist/components/ui/accordion/accordion.stories.svelte +39 -0
  190. package/dist/components/ui/accordion/accordion.stories.svelte.d.ts +27 -0
  191. package/dist/components/ui/alert/alert.stories.svelte +53 -0
  192. package/dist/components/ui/alert/alert.stories.svelte.d.ts +27 -0
  193. package/dist/components/ui/alert/alert.svelte +5 -0
  194. package/dist/components/ui/alert/alert.svelte.d.ts +9 -0
  195. package/dist/components/ui/avatar/avatar.stories.svelte +16 -0
  196. package/dist/components/ui/avatar/avatar.stories.svelte.d.ts +27 -0
  197. package/dist/components/ui/badge/badge.stories.svelte +33 -0
  198. package/dist/components/ui/badge/badge.stories.svelte.d.ts +27 -0
  199. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +33 -0
  200. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte.d.ts +27 -0
  201. package/dist/components/ui/button/button.stories.svelte +43 -0
  202. package/dist/components/ui/button/button.stories.svelte.d.ts +27 -0
  203. package/dist/components/ui/button/button.svelte +1 -2
  204. package/dist/components/ui/button/button.svelte.d.ts +0 -3
  205. package/dist/components/ui/button-group/button-group-separator.svelte.d.ts +1 -1
  206. package/dist/components/ui/card/card.stories.svelte +42 -0
  207. package/dist/components/ui/card/card.stories.svelte.d.ts +27 -0
  208. package/dist/components/ui/command/command.stories.svelte +51 -0
  209. package/dist/components/ui/command/command.stories.svelte.d.ts +27 -0
  210. package/dist/components/ui/dialog/dialog.stories.svelte +29 -0
  211. package/dist/components/ui/dialog/dialog.stories.svelte.d.ts +27 -0
  212. package/dist/components/ui/field/field-label.svelte.d.ts +1 -1
  213. package/dist/components/ui/field/field.stories.svelte +21 -0
  214. package/dist/components/ui/field/field.stories.svelte.d.ts +27 -0
  215. package/dist/components/ui/input/input.stories.svelte +40 -0
  216. package/dist/components/ui/input/input.stories.svelte.d.ts +27 -0
  217. package/dist/components/ui/input/input.svelte +2 -4
  218. package/dist/components/ui/item/item-separator.svelte.d.ts +1 -1
  219. package/dist/components/ui/label/label.stories.svelte +20 -0
  220. package/dist/components/ui/label/label.stories.svelte.d.ts +27 -0
  221. package/dist/components/ui/popover/popover.stories.svelte +29 -0
  222. package/dist/components/ui/popover/popover.stories.svelte.d.ts +27 -0
  223. package/dist/components/ui/select/select-group-heading.svelte.d.ts +1 -1
  224. package/dist/components/ui/select/select.stories.svelte +23 -0
  225. package/dist/components/ui/select/select.stories.svelte.d.ts +27 -0
  226. package/dist/components/ui/separator/separator.stories.svelte +24 -0
  227. package/dist/components/ui/separator/separator.stories.svelte.d.ts +27 -0
  228. package/dist/components/ui/sheet/sheet.stories.svelte +29 -0
  229. package/dist/components/ui/sheet/sheet.stories.svelte.d.ts +27 -0
  230. package/dist/components/ui/sidebar/sidebar-group.svelte +3 -3
  231. package/dist/components/ui/sidebar/sidebar-group.svelte.d.ts +2 -2
  232. package/dist/components/ui/sidebar/sidebar-menu-button.svelte +28 -30
  233. package/dist/components/ui/sidebar/sidebar-menu-button.svelte.d.ts +7 -7
  234. package/dist/components/ui/sidebar/sidebar-separator.svelte.d.ts +1 -1
  235. package/dist/components/ui/sidebar/sidebar-trigger.svelte +4 -4
  236. package/dist/components/ui/sonner/sonner.stories.svelte +22 -0
  237. package/dist/components/ui/sonner/sonner.stories.svelte.d.ts +26 -0
  238. package/dist/components/ui/sonner/sonner.svelte +8 -2
  239. package/dist/components/ui/sonner/toast-demo.svelte +29 -0
  240. package/dist/components/ui/sonner/toast-demo.svelte.d.ts +6 -0
  241. package/dist/components/ui/textarea/textarea.stories.svelte +22 -0
  242. package/dist/components/ui/textarea/textarea.stories.svelte.d.ts +27 -0
  243. package/dist/components/ui/textarea/textarea.svelte +0 -2
  244. package/dist/components/ui/toggle/toggle.stories.svelte +22 -0
  245. package/dist/components/ui/toggle/toggle.stories.svelte.d.ts +27 -0
  246. package/dist/components/ui/toggle-group/toggle-group.stories.svelte +17 -0
  247. package/dist/components/ui/toggle-group/toggle-group.stories.svelte.d.ts +27 -0
  248. package/dist/components/ui/tooltip/tooltip.stories.svelte +26 -0
  249. package/dist/components/ui/tooltip/tooltip.stories.svelte.d.ts +27 -0
  250. package/dist/core/fields/fieldSchemaToTs.d.ts +1 -0
  251. package/dist/core/fields/fieldSchemaToTs.js +133 -1
  252. package/dist/core/fields/layoutUtils.d.ts +17 -0
  253. package/dist/core/fields/layoutUtils.js +149 -0
  254. package/dist/core/fields/structuredToHtml.d.ts +9 -0
  255. package/dist/core/fields/structuredToHtml.js +161 -0
  256. package/dist/core/server/entries/operations/create.js +2 -1
  257. package/dist/core/server/entries/operations/get.js +8 -6
  258. package/dist/core/server/entries/operations/update.d.ts +3 -0
  259. package/dist/core/server/entries/operations/update.js +30 -2
  260. package/dist/core/server/fields/queryStructuredContent.d.ts +15 -0
  261. package/dist/core/server/fields/queryStructuredContent.js +65 -0
  262. package/dist/core/server/fields/resolveImageFields.js +51 -2
  263. package/dist/core/server/fields/resolveRelationFields.js +2 -2
  264. package/dist/core/server/fields/resolveRichtextLinks.js +80 -13
  265. package/dist/core/server/fields/resolveUrlFields.js +57 -6
  266. package/dist/core/server/fields/slugResolver.d.ts +10 -0
  267. package/dist/core/server/fields/slugResolver.js +34 -0
  268. package/dist/core/server/generator/fields.js +15 -4
  269. package/dist/core/server/generator/generator.js +3 -2
  270. package/dist/files-local/index.js +126 -64
  271. package/dist/server/auth.d.ts +5 -0
  272. package/dist/server/auth.js +12 -1
  273. package/dist/sveltekit/components/structured-content.svelte +204 -0
  274. package/dist/sveltekit/components/structured-content.svelte.d.ts +21 -0
  275. package/dist/sveltekit/config.d.ts +13 -3
  276. package/dist/sveltekit/index.d.ts +3 -0
  277. package/dist/sveltekit/index.js +3 -0
  278. package/dist/sveltekit/server/handle.js +1 -0
  279. package/dist/types/config.d.ts +3 -0
  280. package/dist/types/fields.d.ts +19 -2
  281. package/dist/types/index.d.ts +2 -0
  282. package/dist/types/index.js +2 -0
  283. package/dist/types/layout.d.ts +54 -0
  284. package/dist/types/layout.js +6 -0
  285. package/dist/types/structured-content.d.ts +63 -0
  286. package/dist/types/structured-content.js +1 -0
  287. package/dist/updates/0.1.5/index.d.ts +2 -0
  288. package/dist/updates/0.1.5/index.js +18 -0
  289. package/dist/updates/0.2.0/index.d.ts +2 -0
  290. package/dist/updates/0.2.0/index.js +11 -0
  291. package/dist/updates/0.2.2/index.d.ts +2 -0
  292. package/dist/updates/0.2.2/index.js +13 -0
  293. package/dist/updates/0.5.0/index.d.ts +2 -0
  294. package/dist/updates/0.5.0/index.js +14 -0
  295. package/dist/updates/index.js +5 -1
  296. package/package.json +16 -9
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useInterfaceLanguage } from '../../../state/interface-language.svelte.js';
3
3
  import Button from '../../../../components/ui/button/button.svelte';
4
- import Label from '../../../../components/ui/label/label.svelte';
5
4
  import { getRemotes } from '../../../../sveltekit/index.js';
6
5
  import type { InterfaceLanguage } from '../../../../types/languages.js';
7
6
  import type { MediaFile, MediaTag } from '../../../../types/media.js';
@@ -10,38 +9,47 @@
10
9
  import TagCombobox from '../tag-combobox.svelte';
11
10
  import FileMiniature from './file-miniature.svelte';
12
11
  import MediaSelector from '../media-selector.svelte';
13
- import * as InputGroup from '../../../../components/ui/input-group/index.js';
14
12
  import * as Dialog from '../../../../components/ui/dialog/index.js';
13
+ import * as AlertDialog from '../../../../components/ui/alert-dialog/index.js';
15
14
  import Check from '@tabler/icons-svelte/icons/check';
16
15
  import Copy from '@tabler/icons-svelte/icons/copy';
17
- import Upload from '@tabler/icons-svelte/icons/upload';
18
16
  import Replace from '@tabler/icons-svelte/icons/replace';
19
17
  import CircleCheck from '@tabler/icons-svelte/icons/circle-check';
20
18
  import AlertTriangle from '@tabler/icons-svelte/icons/alert-triangle';
19
+ import Music from '@tabler/icons-svelte/icons/music';
20
+ import Upload from '@tabler/icons-svelte/icons/upload';
21
+ import Trash from '@tabler/icons-svelte/icons/trash';
22
+ import VideoOff from '@tabler/icons-svelte/icons/video-off';
23
+ import Download from '@tabler/icons-svelte/icons/download';
21
24
  import { UseClipboard } from '../../../../components/hooks/use-clipboard.svelte.js';
22
25
  import { page } from '$app/state';
23
26
  import FileNameInput from './file-name-input.svelte';
24
27
 
25
28
  let lightboxOpen = $state(false);
29
+ let deleteDialogOpen = $state(false);
30
+ let videoError = $state(false);
31
+
32
+ $effect(() => {
33
+ file.url;
34
+ videoError = false;
35
+ });
26
36
 
27
37
  const lang: Record<
28
38
  InterfaceLanguage,
29
39
  {
30
- fileDeletedToast: string;
31
- allTabs: string;
32
40
  fileDeleteLabel: string;
41
+ deleteConfirmTitle: string;
42
+ deleteConfirmDesc: string;
43
+ deleteCancel: string;
33
44
  fileNameLabel: string;
34
45
  fileUrlLabel: string;
35
46
  fileAltLabel: string;
36
- currentFilePlaceholder: string;
47
+ fileAltHint: string;
37
48
  createdAtLabel: string;
38
49
  mimeTypeLabel: string;
39
50
  sizeLabel: string;
40
51
  dimensionsLabel: string;
41
- selectedInfo: string;
42
- selectionResetLabel: string;
43
52
  durationLabel: string;
44
- minutes: string;
45
53
  transcriptLabel: string;
46
54
  audioDescriptionLabel: string;
47
55
  posterLabel: string;
@@ -51,25 +59,28 @@
51
59
  changePoster: string;
52
60
  removeFile: string;
53
61
  replaceFileLabel: string;
62
+ tagsLabel: string;
63
+ metadataLabel: string;
64
+ focalPointLabel: string;
65
+ videoUnsupported: string;
66
+ downloadFile: string;
54
67
  }
55
68
  > = {
56
69
  pl: {
57
- fileDeleteLabel: 'Usuń plik',
70
+ fileDeleteLabel: 'Usuń',
71
+ deleteConfirmTitle: 'Usunąć plik?',
72
+ deleteConfirmDesc: 'Plik zostanie trwale usunięty.',
73
+ deleteCancel: 'Anuluj',
58
74
  replaceFileLabel: 'Zamień plik',
59
- fileNameLabel: 'Nazwa',
75
+ fileNameLabel: 'Nazwa pliku',
60
76
  fileUrlLabel: 'URL',
61
77
  fileAltLabel: 'Tekst alternatywny',
62
- fileDeletedToast: 'Plik został usunięty',
63
- allTabs: 'Wszystkie',
64
- currentFilePlaceholder: 'Wybierz plik, aby zobaczyć szczegóły',
65
- createdAtLabel: 'Utworzono:',
66
- selectedInfo: 'Wybrane pliki:',
67
- selectionResetLabel: 'Resetuj wybór',
68
- mimeTypeLabel: 'Typ MIME:',
69
- sizeLabel: 'Rozmiar:',
70
- dimensionsLabel: 'Wymiary:',
71
- durationLabel: 'Czas trwania:',
72
- minutes: 'minuty',
78
+ fileAltHint: 'Dodaj opis, żeby każdy mógł zrozumieć treść obrazu',
79
+ createdAtLabel: 'Utworzono',
80
+ mimeTypeLabel: 'Typ',
81
+ sizeLabel: 'Rozmiar',
82
+ dimensionsLabel: 'Wymiary',
83
+ durationLabel: 'Czas',
73
84
  transcriptLabel: 'Transkrypcja',
74
85
  audioDescriptionLabel: 'Audiodeskrypcja',
75
86
  posterLabel: 'Poster',
@@ -77,24 +88,27 @@
77
88
  uploadTranscript: 'Dodaj transkrypcję',
78
89
  uploadAudioDescription: 'Dodaj audiodeskrypcję',
79
90
  changePoster: 'Zmień poster',
80
- removeFile: 'Usuń'
91
+ removeFile: 'Usuń',
92
+ tagsLabel: 'Tagi',
93
+ metadataLabel: 'Metadane',
94
+ focalPointLabel: 'Punkt ogniskowy',
95
+ videoUnsupported: 'Ten format wideo nie jest obsługiwany w przeglądarce',
96
+ downloadFile: 'Pobierz'
81
97
  },
82
98
  en: {
83
- fileDeleteLabel: 'Delete file',
84
- fileNameLabel: 'Name',
99
+ fileDeleteLabel: 'Delete',
100
+ deleteConfirmTitle: 'Delete file?',
101
+ deleteConfirmDesc: 'The file will be permanently deleted.',
102
+ deleteCancel: 'Cancel',
103
+ fileNameLabel: 'File name',
85
104
  fileUrlLabel: 'URL',
86
105
  fileAltLabel: 'Alt text',
87
- fileDeletedToast: 'File has been deleted',
88
- allTabs: 'All',
89
- currentFilePlaceholder: 'Select a file to see details',
90
- createdAtLabel: 'Created at:',
91
- selectedInfo: 'Selected files:',
92
- selectionResetLabel: 'Reset selection',
93
- mimeTypeLabel: 'MIME type:',
94
- sizeLabel: 'Size:',
95
- dimensionsLabel: 'Dimensions:',
96
- durationLabel: 'Duration:',
97
- minutes: 'minutes',
106
+ fileAltHint: 'Add a description so everyone can understand the image',
107
+ createdAtLabel: 'Created',
108
+ mimeTypeLabel: 'Type',
109
+ sizeLabel: 'Size',
110
+ dimensionsLabel: 'Dimensions',
111
+ durationLabel: 'Duration',
98
112
  transcriptLabel: 'Transcript',
99
113
  audioDescriptionLabel: 'Audio description',
100
114
  posterLabel: 'Poster',
@@ -103,7 +117,12 @@
103
117
  uploadAudioDescription: 'Add audio description',
104
118
  changePoster: 'Change poster',
105
119
  removeFile: 'Remove',
106
- replaceFileLabel: 'Replace file'
120
+ replaceFileLabel: 'Replace file',
121
+ tagsLabel: 'Tags',
122
+ metadataLabel: 'Metadata',
123
+ focalPointLabel: 'Focal point',
124
+ videoUnsupported: 'This video format is not supported in the browser',
125
+ downloadFile: 'Download'
107
126
  }
108
127
  };
109
128
 
@@ -126,9 +145,7 @@
126
145
  onDelete: () => void;
127
146
  onReplace: (updated: MediaFile) => void;
128
147
  onNameUpdate: (newName: string) => Promise<
129
- | {
130
- success: true;
131
- }
148
+ | { success: true }
132
149
  | { success: false; error: 'name-already-exists' }
133
150
  >;
134
151
  onTagUpdate: (tagIds: string[]) => void;
@@ -163,7 +180,6 @@
163
180
 
164
181
  let fileUrl = $derived(file.url.startsWith('/') ? page.url.origin + file.url : file.url);
165
182
 
166
- // Accessibility state for video files
167
183
  let transcriptDialogOpen = $state(false);
168
184
  let audioDescDialogOpen = $state(false);
169
185
  let posterDialogOpen = $state(false);
@@ -172,7 +188,7 @@
172
188
  let posterSelected = $state<string>('');
173
189
 
174
190
  $effect(() => {
175
- if (transcriptSelected && file.type === 'video') {
191
+ if (transcriptSelected && (file.type === 'video' || file.type === 'audio')) {
176
192
  remotes.updateMediaAccessibility({
177
193
  fileId: file.id,
178
194
  transcriptFileId: transcriptSelected
@@ -184,7 +200,7 @@
184
200
  });
185
201
 
186
202
  $effect(() => {
187
- if (audioDescSelected && file.type === 'video') {
203
+ if (audioDescSelected && (file.type === 'video' || file.type === 'audio')) {
188
204
  remotes.updateMediaAccessibility({
189
205
  fileId: file.id,
190
206
  audioDescriptionFileId: audioDescSelected
@@ -212,192 +228,270 @@
212
228
  });
213
229
  </script>
214
230
 
215
- <!-- Header z przyciskami -->
216
- <div class="flex items-center justify-end gap-2 border-b border-white/10 px-4 py-3">
217
- <input type="file" class="hidden" bind:this={replaceInputEl} onchange={handleReplace} />
218
- <Button type="button" size="sm" variant="outline" disabled={replacing} onclick={() => replaceInputEl.click()}>
219
- <Replace class="h-3.5 w-3.5 mr-1" />
220
- {lang[interfaceLanguage.current].replaceFileLabel}
221
- </Button>
222
- <Button type="button" size="sm" variant="destructive" onclick={onDelete}>
223
- {lang[interfaceLanguage.current].fileDeleteLabel}
224
- </Button>
225
- </div>
226
-
227
- <!-- Główna zawartość -->
228
- <div class="space-y-5 p-5">
229
- <!-- Preview -->
230
- <div>
231
- {#if file.type === 'video'}
232
- <div class="w-full overflow-hidden rounded-xl shadow-md">
233
- <video controls poster={file.posterUrl || file.thumbnailUrl || undefined} class="w-full max-h-48 object-contain bg-black rounded-xl">
234
- <source src={file.url} type={file.mimeType || undefined} />
235
- </video>
236
- </div>
237
- {:else}
231
+ <div class="flex h-full flex-col overflow-hidden">
232
+ <!-- Scrollable content -->
233
+ <div class="flex-1 overflow-y-auto p-5 scrollbar-thin space-y-4">
234
+ <!-- Header: title + action buttons -->
235
+ <div class="flex items-center gap-2 flex-wrap">
236
+ <h2 class="flex-1 min-w-0 truncate text-base font-bold text-foreground">{file.name}</h2>
237
+ <input type="file" class="hidden" bind:this={replaceInputEl} onchange={handleReplace} />
238
238
  <button
239
239
  type="button"
240
- class="aspect-square w-28 shrink-0 overflow-hidden rounded-xl shadow-md cursor-zoom-in hover:ring-2 hover:ring-primary/50 transition-all"
241
- onclick={() => file.type === 'image' && (lightboxOpen = true)}
240
+ class="inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:bg-lavender-lighter hover:border-lavender hover:text-primary"
241
+ disabled={replacing}
242
+ onclick={() => replaceInputEl.click()}
242
243
  >
243
- <FileMiniature {file} />
244
+ <Replace class="h-3.5 w-3.5" />
245
+ {lang[interfaceLanguage.current].replaceFileLabel}
244
246
  </button>
245
- {/if}
246
- </div>
247
+ <button
248
+ type="button"
249
+ class="inline-flex items-center gap-1 rounded-md border border-destructive/30 px-2.5 py-1 text-xs font-medium text-destructive transition-colors hover:bg-error-bg hover:border-destructive/50"
250
+ onclick={() => (deleteDialogOpen = true)}
251
+ >
252
+ <Trash class="h-3.5 w-3.5" />
253
+ {lang[interfaceLanguage.current].fileDeleteLabel}
254
+ </button>
255
+ </div>
247
256
 
248
- <!-- Tags -->
249
- <TagCombobox
250
- tags={allTags}
251
- selectedTagIds={file.tags.map((t) => t.id)}
252
- onchange={onTagUpdate}
253
- />
254
-
255
- <!-- Nazwa pliku -->
256
- <FileNameInput name={file.name} onUpdate={onNameUpdate} />
257
-
258
- <!-- URL -->
259
- <div class="space-y-1.5">
260
- <Label class="text-xs">{lang[interfaceLanguage.current].fileUrlLabel}</Label>
261
- <InputGroup.Root>
262
- <InputGroup.Input placeholder={fileUrl} readonly class="text-xs" />
263
- <InputGroup.Addon align="inline-end">
264
- <InputGroup.Button
265
- aria-label="Copy"
266
- title="Copy"
267
- size="icon-xs"
268
- onclick={() => clipboard.copy(fileUrl)}
257
+ <!-- Preview -->
258
+ <div>
259
+ {#if file.type === 'video'}
260
+ {#if videoError}
261
+ <div class="w-full aspect-video flex flex-col items-center justify-center gap-3 rounded-lg bg-muted">
262
+ <VideoOff class="h-12 w-12 text-text-light" />
263
+ <p class="text-sm text-muted-foreground text-center px-4">{lang[interfaceLanguage.current].videoUnsupported}</p>
264
+ <a
265
+ href={file.url}
266
+ download
267
+ class="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90"
268
+ >
269
+ <Download class="h-3.5 w-3.5" />
270
+ {lang[interfaceLanguage.current].downloadFile}
271
+ </a>
272
+ </div>
273
+ {:else}
274
+ <div class="w-full overflow-hidden rounded-lg shadow-sm">
275
+ <video
276
+ controls
277
+ poster={file.posterUrl || file.thumbnailUrl || undefined}
278
+ class="w-full aspect-video object-contain bg-black rounded-lg"
279
+ onerror={() => (videoError = true)}
280
+ >
281
+ <source src={file.url} />
282
+ </video>
283
+ </div>
284
+ {/if}
285
+ {:else if file.type === 'image'}
286
+ <button
287
+ type="button"
288
+ class="relative w-full aspect-video overflow-hidden rounded-lg ml-checkered-bg cursor-zoom-in hover:ring-2 hover:ring-primary/50 transition-all"
289
+ onclick={() => (lightboxOpen = true)}
269
290
  >
270
- {#if clipboard.copied}
271
- <Check class="h-3 w-3" />
272
- {:else}
273
- <Copy class="h-3 w-3" />
274
- {/if}
275
- </InputGroup.Button>
276
- </InputGroup.Addon>
277
- </InputGroup.Root>
278
- </div>
291
+ <img src={file.url} alt={file.alt || file.name} class="w-full h-full object-contain" />
292
+ </button>
293
+ {:else if file.type === 'audio'}
294
+ <div class="w-full overflow-hidden rounded-lg">
295
+ <div class="flex flex-col items-center justify-center gap-3 bg-muted py-8 rounded-t-lg">
296
+ <Music class="h-12 w-12 text-text-light" />
297
+ </div>
298
+ <audio controls class="w-full rounded-b-lg">
299
+ <source src={file.url} />
300
+ </audio>
301
+ </div>
302
+ {:else}
303
+ <div class="relative w-full aspect-video overflow-hidden rounded-lg">
304
+ <FileMiniature {file} mode="thumb" />
305
+ </div>
306
+ {/if}
307
+ </div>
279
308
 
280
- <!-- Alt text + focal point for images -->
281
- {#if file.type === 'image'}
309
+ <!-- Tags -->
282
310
  <div class="space-y-1.5">
283
- <Label class="text-xs">{lang[interfaceLanguage.current].fileAltLabel}</Label>
284
- <AltInput alt={file.alt} fileId={file.id} />
311
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].tagsLabel}</div>
312
+ <TagCombobox
313
+ tags={allTags}
314
+ selectedTagIds={file.tags.map((t) => t.id)}
315
+ onchange={onTagUpdate}
316
+ showLabel={false}
317
+ />
285
318
  </div>
286
- <FocalPointInput {file} />
287
- {/if}
288
-
289
- <!-- Accessibility section for video -->
290
- {#if file.type === 'video'}
291
- <div class="space-y-3">
292
- <Label class="text-xs font-semibold">{lang[interfaceLanguage.current].accessibilityLabel}</Label>
293
-
294
- <!-- Transcript -->
295
- <div class="flex items-center justify-between gap-2 rounded-lg border p-2">
296
- <div class="flex items-center gap-2 text-xs">
297
- {#if file.transcriptFileId}
298
- <CircleCheck class="h-4 w-4 text-green-500" />
299
- <span>{lang[interfaceLanguage.current].transcriptLabel}</span>
300
- {:else}
301
- <AlertTriangle class="h-4 w-4 text-yellow-500" />
302
- <span class="text-muted-foreground">{lang[interfaceLanguage.current].transcriptLabel}</span>
303
- {/if}
304
- </div>
305
- <div class="flex gap-1">
306
- {#if file.transcriptFileId}
307
- <Button size="sm" variant="ghost" class="h-6 text-xs" onclick={() => {
308
- remotes.updateMediaAccessibility({ fileId: file.id, transcriptFileId: null });
309
- file.transcriptFileId = null;
310
- }}>
311
- {lang[interfaceLanguage.current].removeFile}
312
- </Button>
313
- {/if}
314
- <Button size="sm" variant="outline" class="h-6 text-xs" onclick={() => (transcriptDialogOpen = true)}>
315
- <Upload class="h-3 w-3 mr-1" />
316
- {lang[interfaceLanguage.current].uploadTranscript}
317
- </Button>
318
- </div>
319
- </div>
320
319
 
321
- <!-- Audio Description -->
322
- <div class="flex items-center justify-between gap-2 rounded-lg border p-2">
323
- <div class="flex items-center gap-2 text-xs">
324
- {#if file.audioDescriptionFileId}
325
- <CircleCheck class="h-4 w-4 text-green-500" />
326
- <span>{lang[interfaceLanguage.current].audioDescriptionLabel}</span>
327
- {:else}
328
- <AlertTriangle class="h-4 w-4 text-yellow-500" />
329
- <span class="text-muted-foreground">{lang[interfaceLanguage.current].audioDescriptionLabel}</span>
330
- {/if}
331
- </div>
332
- <div class="flex gap-1">
333
- {#if file.audioDescriptionFileId}
334
- <Button size="sm" variant="ghost" class="h-6 text-xs" onclick={() => {
335
- remotes.updateMediaAccessibility({ fileId: file.id, audioDescriptionFileId: null });
336
- file.audioDescriptionFileId = null;
337
- }}>
338
- {lang[interfaceLanguage.current].removeFile}
339
- </Button>
340
- {/if}
341
- <Button size="sm" variant="outline" class="h-6 text-xs" onclick={() => (audioDescDialogOpen = true)}>
342
- <Upload class="h-3 w-3 mr-1" />
343
- {lang[interfaceLanguage.current].uploadAudioDescription}
344
- </Button>
345
- </div>
346
- </div>
320
+ <!-- Filename -->
321
+ <div class="space-y-1.5">
322
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].fileNameLabel}</div>
323
+ <FileNameInput name={file.name} onUpdate={onNameUpdate} />
324
+ </div>
347
325
 
348
- <!-- Poster override -->
349
- <div class="flex items-center justify-between gap-2 rounded-lg border p-2">
350
- <div class="flex items-center gap-2 text-xs">
351
- {#if file.posterUrl}
352
- <CircleCheck class="h-4 w-4 text-green-500" />
326
+ <!-- URL -->
327
+ <div class="space-y-1.5">
328
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].fileUrlLabel}</div>
329
+ <div class="flex items-stretch">
330
+ <input
331
+ readonly
332
+ value={fileUrl}
333
+ class="flex-1 min-w-0 rounded-l-lg bg-muted/60 border border-transparent px-2.5 py-1.5 font-mono text-xs text-foreground outline-none focus:border-primary focus:ring-2 focus:ring-primary/10 focus:bg-card"
334
+ />
335
+ <button
336
+ type="button"
337
+ class="flex items-center justify-center w-9 rounded-r-lg bg-muted/60 border border-l-0 border-transparent text-text-light transition-colors hover:bg-lavender-lighter hover:text-primary"
338
+ onclick={() => clipboard.copy(fileUrl)}
339
+ aria-label="Kopiuj URL"
340
+ >
341
+ {#if clipboard.copied}
342
+ <Check class="h-3.5 w-3.5" />
353
343
  {:else}
354
- <AlertTriangle class="h-4 w-4 text-yellow-500" />
344
+ <Copy class="h-3.5 w-3.5" />
355
345
  {/if}
356
- <span class={file.posterUrl ? '' : 'text-muted-foreground'}>{lang[interfaceLanguage.current].posterLabel}</span>
357
- </div>
358
- <Button size="sm" variant="outline" class="h-6 text-xs" onclick={() => (posterDialogOpen = true)}>
359
- {lang[interfaceLanguage.current].changePoster}
360
- </Button>
346
+ </button>
361
347
  </div>
362
348
  </div>
363
- {/if}
364
- </div>
365
349
 
366
- <!-- Metadane -->
367
- <div class="border-t border-white/10 bg-muted/20 px-5 py-4">
368
- <div class="grid grid-cols-2 gap-x-4 gap-y-1.5 text-xs">
369
- {#if file.mimeType}
370
- <span class="text-muted-foreground">{lang[interfaceLanguage.current].mimeTypeLabel}</span>
371
- <span class="font-medium">{file.mimeType}</span>
350
+ <!-- Alt text + focal point for images -->
351
+ {#if file.type === 'image'}
352
+ <div class="space-y-1.5">
353
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].fileAltLabel}</div>
354
+ <AltInput alt={file.alt} fileId={file.id} />
355
+ <p class="text-[11px] text-text-light leading-relaxed">{lang[interfaceLanguage.current].fileAltHint}</p>
356
+ </div>
357
+ <div class="space-y-1.5">
358
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].focalPointLabel}</div>
359
+ <FocalPointInput {file} />
360
+ </div>
372
361
  {/if}
373
362
 
374
- {#if file.width && file.height}
375
- <span class="text-muted-foreground">{lang[interfaceLanguage.current].dimensionsLabel}</span>
376
- <span class="font-medium">{file.width} × {file.height} px</span>
377
- {/if}
363
+ <!-- Accessibility section for video/audio -->
364
+ {#if file.type === 'video' || file.type === 'audio'}
365
+ <div class="space-y-3">
366
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].accessibilityLabel}</div>
367
+
368
+ <div class="flex items-center justify-between gap-2 rounded-lg border p-2">
369
+ <div class="flex items-center gap-2 text-xs">
370
+ {#if file.transcriptFileId}
371
+ <CircleCheck class="h-4 w-4 text-success" />
372
+ <span>{lang[interfaceLanguage.current].transcriptLabel}</span>
373
+ {:else}
374
+ <AlertTriangle class="h-4 w-4 text-warning" />
375
+ <span class="text-muted-foreground">{lang[interfaceLanguage.current].transcriptLabel}</span>
376
+ {/if}
377
+ </div>
378
+ <div class="flex gap-1">
379
+ {#if file.transcriptFileId}
380
+ <Button size="sm" variant="ghost" class="h-6 text-xs" onclick={() => {
381
+ remotes.updateMediaAccessibility({ fileId: file.id, transcriptFileId: null });
382
+ file.transcriptFileId = null;
383
+ }}>
384
+ {lang[interfaceLanguage.current].removeFile}
385
+ </Button>
386
+ {/if}
387
+ <Button size="sm" variant="outline" class="h-6 text-xs" onclick={() => (transcriptDialogOpen = true)}>
388
+ <Upload class="h-3 w-3 mr-1" />
389
+ {lang[interfaceLanguage.current].uploadTranscript}
390
+ </Button>
391
+ </div>
392
+ </div>
378
393
 
379
- {#if file.duration}
380
- <span class="text-muted-foreground">{lang[interfaceLanguage.current].durationLabel}</span>
381
- <span class="font-medium">
382
- {Math.floor(file.duration / 60)}:{String(Math.floor(file.duration % 60)).padStart(2, '0')}
383
- </span>
384
- {/if}
394
+ {#if file.type === 'video'}
395
+ <div class="flex items-center justify-between gap-2 rounded-lg border p-2">
396
+ <div class="flex items-center gap-2 text-xs">
397
+ {#if file.audioDescriptionFileId}
398
+ <CircleCheck class="h-4 w-4 text-success" />
399
+ <span>{lang[interfaceLanguage.current].audioDescriptionLabel}</span>
400
+ {:else}
401
+ <AlertTriangle class="h-4 w-4 text-warning" />
402
+ <span class="text-muted-foreground">{lang[interfaceLanguage.current].audioDescriptionLabel}</span>
403
+ {/if}
404
+ </div>
405
+ <div class="flex gap-1">
406
+ {#if file.audioDescriptionFileId}
407
+ <Button size="sm" variant="ghost" class="h-6 text-xs" onclick={() => {
408
+ remotes.updateMediaAccessibility({ fileId: file.id, audioDescriptionFileId: null });
409
+ file.audioDescriptionFileId = null;
410
+ }}>
411
+ {lang[interfaceLanguage.current].removeFile}
412
+ </Button>
413
+ {/if}
414
+ <Button size="sm" variant="outline" class="h-6 text-xs" onclick={() => (audioDescDialogOpen = true)}>
415
+ <Upload class="h-3 w-3 mr-1" />
416
+ {lang[interfaceLanguage.current].uploadAudioDescription}
417
+ </Button>
418
+ </div>
419
+ </div>
385
420
 
386
- {#if file.size}
387
- <span class="text-muted-foreground">{lang[interfaceLanguage.current].sizeLabel}</span>
388
- <span class="font-medium">{formatFileSize(file.size)}</span>
421
+ <div class="flex items-center justify-between gap-2 rounded-lg border p-2">
422
+ <div class="flex items-center gap-2 text-xs">
423
+ {#if file.posterUrl}
424
+ <CircleCheck class="h-4 w-4 text-success" />
425
+ {:else}
426
+ <AlertTriangle class="h-4 w-4 text-warning" />
427
+ {/if}
428
+ <span class={file.posterUrl ? '' : 'text-muted-foreground'}>{lang[interfaceLanguage.current].posterLabel}</span>
429
+ </div>
430
+ <Button size="sm" variant="outline" class="h-6 text-xs" onclick={() => (posterDialogOpen = true)}>
431
+ {lang[interfaceLanguage.current].changePoster}
432
+ </Button>
433
+ </div>
434
+ {/if}
435
+ </div>
389
436
  {/if}
437
+ </div>
390
438
 
391
- <span class="text-muted-foreground">{lang[interfaceLanguage.current].createdAtLabel}</span>
392
- <span class="font-medium">{new Date(file.createdAt).toLocaleDateString()}</span>
439
+ <!-- Metadata footer -->
440
+ <div class="shrink-0 border-t bg-muted/20 px-5 py-4">
441
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light mb-2">{lang[interfaceLanguage.current].metadataLabel}</div>
442
+ <div class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-[13px]">
443
+ {#if file.mimeType}
444
+ <span class="text-text-light font-medium whitespace-nowrap">{lang[interfaceLanguage.current].mimeTypeLabel}</span>
445
+ <span class="font-medium text-foreground">{file.mimeType}</span>
446
+ {/if}
447
+
448
+ {#if file.width && file.height}
449
+ <span class="text-text-light font-medium whitespace-nowrap">{lang[interfaceLanguage.current].dimensionsLabel}</span>
450
+ <span class="font-medium text-foreground">{file.width} × {file.height} px</span>
451
+ {/if}
452
+
453
+ {#if file.duration}
454
+ <span class="text-text-light font-medium whitespace-nowrap">{lang[interfaceLanguage.current].durationLabel}</span>
455
+ <span class="font-medium text-foreground">
456
+ {Math.floor(file.duration / 60)}:{String(Math.floor(file.duration % 60)).padStart(2, '0')}
457
+ </span>
458
+ {/if}
459
+
460
+ {#if file.size}
461
+ <span class="text-text-light font-medium whitespace-nowrap">{lang[interfaceLanguage.current].sizeLabel}</span>
462
+ <span class="font-medium text-foreground">{formatFileSize(file.size)}</span>
463
+ {/if}
464
+
465
+ <span class="text-text-light font-medium whitespace-nowrap">{lang[interfaceLanguage.current].createdAtLabel}</span>
466
+ <span class="font-medium text-foreground">{new Date(file.createdAt).toLocaleDateString()}</span>
467
+ </div>
393
468
  </div>
394
469
  </div>
395
470
 
471
+ <!-- Delete confirmation -->
472
+ <AlertDialog.Root bind:open={deleteDialogOpen}>
473
+ <AlertDialog.Content>
474
+ <AlertDialog.Title>{lang[interfaceLanguage.current].deleteConfirmTitle}</AlertDialog.Title>
475
+ <AlertDialog.Description>{lang[interfaceLanguage.current].deleteConfirmDesc}</AlertDialog.Description>
476
+ <AlertDialog.Footer>
477
+ <AlertDialog.Cancel>{lang[interfaceLanguage.current].deleteCancel}</AlertDialog.Cancel>
478
+ <AlertDialog.Action
479
+ onclick={() => {
480
+ onDelete();
481
+ deleteDialogOpen = false;
482
+ }}
483
+ >
484
+ {lang[interfaceLanguage.current].fileDeleteLabel}
485
+ </AlertDialog.Action>
486
+ </AlertDialog.Footer>
487
+ </AlertDialog.Content>
488
+ </AlertDialog.Root>
489
+
396
490
  <!-- Lightbox -->
397
491
  {#if file.type === 'image'}
398
492
  <Dialog.Root bind:open={lightboxOpen}>
399
493
  <Dialog.Content class="max-w-[90vw] max-h-[90vh] p-2 bg-black/95 border-none">
400
- <div class="checkered-lightbox flex items-center justify-center">
494
+ <div class="flex items-center justify-center rounded-lg overflow-hidden" style="background: repeating-conic-gradient(#2a2a2a 0% 25%, #1a1a1a 0% 50%) 50% / 20px 20px; padding: 8px;">
401
495
  <img
402
496
  src={file.url}
403
497
  alt={file.alt || file.name}
@@ -408,8 +502,8 @@
408
502
  </Dialog.Root>
409
503
  {/if}
410
504
 
411
- <!-- Accessibility dialogs for video -->
412
- {#if file.type === 'video'}
505
+ <!-- Transcript dialog (video + audio) -->
506
+ {#if file.type === 'video' || file.type === 'audio'}
413
507
  <Dialog.Root bind:open={transcriptDialogOpen}>
414
508
  <Dialog.Content class="max-w-5xl! sm:max-w-5xl!">
415
509
  <Dialog.Header>
@@ -418,7 +512,10 @@
418
512
  <MediaSelector bind:selected={transcriptSelected} />
419
513
  </Dialog.Content>
420
514
  </Dialog.Root>
515
+ {/if}
421
516
 
517
+ <!-- Audio description + poster dialogs (video only) -->
518
+ {#if file.type === 'video'}
422
519
  <Dialog.Root bind:open={audioDescDialogOpen}>
423
520
  <Dialog.Content class="max-w-5xl! sm:max-w-5xl!">
424
521
  <Dialog.Header>
@@ -437,18 +534,3 @@
437
534
  </Dialog.Content>
438
535
  </Dialog.Root>
439
536
  {/if}
440
-
441
- <style>
442
- .checkered-lightbox {
443
- background-color: #1a1a1a;
444
- background-image:
445
- linear-gradient(45deg, #2a2a2a 25%, transparent 25%),
446
- linear-gradient(-45deg, #2a2a2a 25%, transparent 25%),
447
- linear-gradient(45deg, transparent 75%, #2a2a2a 75%),
448
- linear-gradient(-45deg, transparent 75%, #2a2a2a 75%);
449
- background-size: 20px 20px;
450
- background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
451
- border-radius: 8px;
452
- padding: 8px;
453
- }
454
- </style>