includio-cms 0.1.3 → 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 (313) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/ROADMAP.md +23 -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 -256
  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/paraglide/.prettierignore +3 -0
  272. package/dist/paraglide/messages/_index.d.ts +36 -0
  273. package/dist/paraglide/messages/_index.js +72 -0
  274. package/dist/paraglide/messages/en.d.ts +5 -0
  275. package/dist/paraglide/messages/en.js +14 -0
  276. package/dist/paraglide/messages/pl.d.ts +5 -0
  277. package/dist/paraglide/messages/pl.js +14 -0
  278. package/dist/paraglide/messages.d.ts +2 -0
  279. package/dist/paraglide/messages.js +4 -0
  280. package/dist/paraglide/registry.d.ts +21 -0
  281. package/dist/paraglide/registry.js +31 -0
  282. package/dist/paraglide/runtime.d.ts +583 -0
  283. package/dist/paraglide/runtime.js +1402 -0
  284. package/dist/paraglide/server.d.ts +67 -0
  285. package/dist/paraglide/server.js +175 -0
  286. package/dist/server/auth.d.ts +5 -0
  287. package/dist/server/auth.js +12 -1
  288. package/dist/sveltekit/components/structured-content.svelte +204 -0
  289. package/dist/sveltekit/components/structured-content.svelte.d.ts +21 -0
  290. package/dist/sveltekit/config.d.ts +13 -3
  291. package/dist/sveltekit/index.d.ts +3 -0
  292. package/dist/sveltekit/index.js +3 -0
  293. package/dist/sveltekit/server/handle.js +1 -0
  294. package/dist/types/config.d.ts +3 -0
  295. package/dist/types/fields.d.ts +19 -2
  296. package/dist/types/index.d.ts +2 -0
  297. package/dist/types/index.js +2 -0
  298. package/dist/types/layout.d.ts +54 -0
  299. package/dist/types/layout.js +6 -0
  300. package/dist/types/structured-content.d.ts +63 -0
  301. package/dist/types/structured-content.js +1 -0
  302. package/dist/updates/0.1.4/index.d.ts +2 -0
  303. package/dist/updates/0.1.4/index.js +11 -0
  304. package/dist/updates/0.1.5/index.d.ts +2 -0
  305. package/dist/updates/0.1.5/index.js +18 -0
  306. package/dist/updates/0.2.0/index.d.ts +2 -0
  307. package/dist/updates/0.2.0/index.js +11 -0
  308. package/dist/updates/0.2.2/index.d.ts +2 -0
  309. package/dist/updates/0.2.2/index.js +13 -0
  310. package/dist/updates/0.5.0/index.d.ts +2 -0
  311. package/dist/updates/0.5.0/index.js +14 -0
  312. package/dist/updates/index.js +6 -1
  313. package/package.json +17 -10
@@ -3,7 +3,6 @@
3
3
  import { onMount } from 'svelte';
4
4
  import type { MediaFile, MediaTag } from '../../../types/media.js';
5
5
  import { getRemotes } from '../../context/remotes.js';
6
- import Button from '../../../components/ui/button/button.svelte';
7
6
  import { toast } from 'svelte-sonner';
8
7
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
9
8
  import type { InterfaceLanguage } from '../../../types/languages.js';
@@ -15,24 +14,41 @@
15
14
  import TagSidebar from './tag-sidebar.svelte';
16
15
  import MediaSearch from './media-search.svelte';
17
16
  import BulkActionBar from './bulk-action-bar.svelte';
17
+ import ListCheck from '@tabler/icons-svelte/icons/list-check';
18
+ import Toggle from '../../../components/ui/toggle/toggle.svelte';
18
19
 
19
20
  const lang: Record<
20
21
  InterfaceLanguage,
21
22
  {
22
23
  fileDeletedToast: string;
23
24
  currentFilePlaceholder: string;
25
+ currentFileDesc: string;
24
26
  bulkDeletedToast: string;
27
+ selectMode: string;
28
+ selectModeActive: string;
29
+ selectModeAnnounce: string;
30
+ selectModeOffAnnounce: string;
25
31
  }
26
32
  > = {
27
33
  pl: {
28
34
  fileDeletedToast: 'Plik został usunięty',
29
- currentFilePlaceholder: 'Wybierz plik, aby zobaczyć szczegóły',
30
- bulkDeletedToast: 'Pliki zostały usunięte'
35
+ currentFilePlaceholder: 'Podgląd pliku',
36
+ currentFileDesc: 'Wybierz plik z listy, aby zobaczyć szczegóły i edytować metadane.',
37
+ bulkDeletedToast: 'Pliki zostały usunięte',
38
+ selectMode: 'Wybierz',
39
+ selectModeActive: 'Zaznaczanie',
40
+ selectModeAnnounce: 'Tryb zaznaczania włączony. Klikaj pliki, aby je zaznaczyć.',
41
+ selectModeOffAnnounce: 'Tryb zaznaczania wyłączony.'
31
42
  },
32
43
  en: {
33
44
  fileDeletedToast: 'File has been deleted',
34
- currentFilePlaceholder: 'Select a file to see details',
35
- bulkDeletedToast: 'Files have been deleted'
45
+ currentFilePlaceholder: 'File preview',
46
+ currentFileDesc: 'Select a file from the list to view details and edit metadata.',
47
+ bulkDeletedToast: 'Files have been deleted',
48
+ selectMode: 'Select',
49
+ selectModeActive: 'Selecting',
50
+ selectModeAnnounce: 'Selection mode enabled. Click files to select them.',
51
+ selectModeOffAnnounce: 'Selection mode disabled.'
36
52
  }
37
53
  };
38
54
 
@@ -51,8 +67,21 @@
51
67
  let activeTagFilter = $state<string | null>(null);
52
68
  let searchQuery = $state('');
53
69
  let selectedFileIds = $state<string[]>([]);
54
- let lastClickedIndex = $state<number>(-1);
55
70
  let dropZoneRef = $state<HTMLElement | null>(null);
71
+ let selectionMode = $state(false);
72
+ let selectionAnnouncement = $state('');
73
+
74
+ function exitSelectionMode() {
75
+ selectionMode = false;
76
+ selectedFileIds = [];
77
+ currentFile = null;
78
+ selectionAnnouncement = lang[interfaceLanguage.current].selectModeOffAnnounce;
79
+ }
80
+
81
+ function enterSelectionMode() {
82
+ selectionMode = true;
83
+ selectionAnnouncement = lang[interfaceLanguage.current].selectModeAnnounce;
84
+ }
56
85
 
57
86
  const filesQuery = $derived(
58
87
  remotes.getMediaFiles({
@@ -71,21 +100,24 @@
71
100
 
72
101
  let tagsQuery = $derived(remotes.getMediaTags());
73
102
 
74
- // Multi-select with shift/ctrl
75
103
  function handleFileSelect(file: MediaFile, event?: MouseEvent) {
76
- if (event && (event.ctrlKey || event.metaKey)) {
77
- // Toggle selection
104
+ const isModifier = event && (event.ctrlKey || event.metaKey);
105
+
106
+ if (isModifier) {
107
+ // Cmd/Ctrl+click: toggle selection, auto-enter selection mode
108
+ if (!selectionMode) enterSelectionMode();
78
109
  selectedFileIds = selectedFileIds.includes(file.id)
79
110
  ? selectedFileIds.filter((id) => id !== file.id)
80
111
  : [...selectedFileIds, file.id];
81
112
  currentFile = file;
82
- } else if (event?.shiftKey && lastClickedIndex >= 0) {
83
- // Range selection - need resolved files
84
- // Just select the single file if we can't resolve the range
113
+ } else if (selectionMode) {
114
+ // Normal click in selection mode: toggle
115
+ selectedFileIds = selectedFileIds.includes(file.id)
116
+ ? selectedFileIds.filter((id) => id !== file.id)
117
+ : [...selectedFileIds, file.id];
85
118
  currentFile = file;
86
- selectedFileIds = [file.id];
87
119
  } else {
88
- // Single select
120
+ // Normal click without selection mode: open details
89
121
  selectedFileIds = [];
90
122
  currentFile = file;
91
123
  if (multiple && Array.isArray(selected)) {
@@ -98,6 +130,12 @@
98
130
  }
99
131
  }
100
132
 
133
+ function handleRangeSelect(fileIds: string[]) {
134
+ // Merge range into selection (union)
135
+ const merged = new Set([...selectedFileIds, ...fileIds]);
136
+ selectedFileIds = [...merged];
137
+ }
138
+
101
139
  async function deleteFileCommand() {
102
140
  if (currentFile) {
103
141
  await remotes.deleteMediaFile(currentFile.id);
@@ -123,13 +161,14 @@
123
161
  const result = await remotes.renameMediaFile({ fileId: currentFile.id, newName });
124
162
  if (result.success === true) {
125
163
  currentFile = { ...currentFile, name: result.name, url: result.url };
164
+ await filesQuery.refresh();
165
+ await allFilesQuery.refresh();
126
166
  }
127
167
  return result;
128
168
  }
129
169
  throw new Error('No current file selected');
130
170
  }
131
171
 
132
- // Tag CRUD
133
172
  async function handleCreateTag(name: string, color: string) {
134
173
  await remotes.createMediaTag({ name, color });
135
174
  await tagsQuery.refresh();
@@ -149,7 +188,6 @@
149
188
  await allFilesQuery.refresh();
150
189
  }
151
190
 
152
- // Bulk operations
153
191
  async function handleBulkTag(tagIds: string[]) {
154
192
  const fileIds = selectedFileIds;
155
193
  await remotes.bulkSetMediaFileTags({ fileIds, tagIds });
@@ -158,7 +196,7 @@
158
196
  }
159
197
 
160
198
  async function handleBulkDelete() {
161
- await remotes.bulkDeleteMediaFiles(selectedFileIds);
199
+ await remotes.bulkDeleteMediaFiles({ ids: selectedFileIds });
162
200
  toast.success(lang[interfaceLanguage.current].bulkDeletedToast);
163
201
  selectedFileIds = [];
164
202
  currentFile = null;
@@ -175,9 +213,9 @@
175
213
  });
176
214
  </script>
177
215
 
178
- <div class="flex h-full" bind:this={dropZoneRef}>
179
- <!-- Tag sidebar -->
180
- <div class="w-48 shrink-0 border-r p-3">
216
+ <div class="flex h-full overflow-hidden" bind:this={dropZoneRef}>
217
+ <!-- Tag sidebar (192px) -->
218
+ <aside class="w-48 min-w-48 shrink-0 border-r bg-card flex flex-col overflow-hidden" aria-label="Filtry tagów">
181
219
  {#await Promise.all([tagsQuery, allFilesQuery]) then [tags, allFiles]}
182
220
  <TagSidebar
183
221
  {tags}
@@ -189,13 +227,26 @@
189
227
  onDeleteTag={handleDeleteTag}
190
228
  />
191
229
  {/await}
192
- </div>
230
+ </aside>
193
231
 
194
- <!-- Main content -->
195
- <div class="flex flex-1 flex-col overflow-hidden">
196
- <div class="flex items-center gap-2 border-b px-4 py-3">
232
+ <!-- Main content (flex-1) -->
233
+ <section class="flex flex-1 flex-col overflow-hidden" aria-label="Pliki">
234
+ <!-- Toolbar -->
235
+ <div class="flex items-center gap-2.5 border-b bg-card px-5 py-3 shrink-0">
197
236
  <MediaSearch bind:value={searchQuery} />
198
237
  <MediaSort />
238
+ <Toggle
239
+ variant="outline"
240
+ size="sm"
241
+ pressed={selectionMode}
242
+ onPressedChange={(pressed) => pressed ? enterSelectionMode() : exitSelectionMode()}
243
+ class="gap-1.5 px-2.5 text-xs font-semibold whitespace-nowrap shrink-0 {selectionMode ? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground border-primary' : ''}"
244
+ aria-label={selectionMode ? lang[interfaceLanguage.current].selectModeActive : lang[interfaceLanguage.current].selectMode}
245
+ >
246
+ <ListCheck class="h-4 w-4" />
247
+ {selectionMode ? lang[interfaceLanguage.current].selectModeActive : lang[interfaceLanguage.current].selectMode}
248
+ </Toggle>
249
+ <div class="flex-1"></div>
199
250
  <FileUpload
200
251
  onUpload={() => { filesQuery.refresh(); allFilesQuery.refresh(); }}
201
252
  {accept}
@@ -203,66 +254,72 @@
203
254
  />
204
255
  </div>
205
256
 
206
- <div class="flex-1 overflow-y-auto p-4">
207
- <div class="grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-2">
257
+ <!-- Selection mode announcement (sr-only) -->
258
+ <div class="sr-only" aria-live="polite" role="status">{selectionAnnouncement}</div>
259
+
260
+ <!-- File grid -->
261
+ <div class="flex-1 overflow-y-auto px-5 pt-4 pb-24 scrollbar-thin" role="listbox" aria-multiselectable="true" aria-label="Siatka plików">
262
+ <div class="grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-3">
208
263
  {#await filesQuery}
209
264
  {#each Array(8) as _}
210
- <Skeleton class="block aspect-square rounded-2xl" />
265
+ <Skeleton class="block h-[168px] rounded-xl" />
211
266
  {/each}
212
267
  {:then files}
213
268
  <FilesList
214
269
  {files}
215
270
  selected={selectedFileIds.length > 0 ? selectedFileIds : (selected ?? '')}
216
271
  onSelect={handleFileSelect}
272
+ onRangeSelect={handleRangeSelect}
273
+ {selectionMode}
217
274
  />
218
275
  {/await}
219
276
  </div>
220
277
  </div>
221
- </div>
278
+ </section>
222
279
 
223
- <!-- Detail panel -->
224
- <div class="w-96 shrink-0 overflow-y-auto border-l">
225
- <div class="sticky top-0">
226
- {#if selectedFileIds.length > 1}
227
- {#await filesQuery then allFiles}
228
- {#await tagsQuery then tags}
229
- <MultiFileSummary
230
- files={allFiles.filter((f) => selectedFileIds.includes(f.id))}
231
- allTags={tags}
232
- onBulkTag={handleBulkTag}
233
- onBulkDelete={handleBulkDelete}
234
- />
235
- {/await}
280
+ <!-- Detail panel (384px) -->
281
+ <aside class="w-96 min-w-96 shrink-0 border-l bg-card flex flex-col overflow-hidden" aria-label="Szczegóły pliku">
282
+ {#if selectionMode}
283
+ {#await filesQuery then allFiles}
284
+ {#await tagsQuery then tags}
285
+ <MultiFileSummary
286
+ files={allFiles.filter((f) => selectedFileIds.includes(f.id))}
287
+ allTags={tags}
288
+ onBulkTag={handleBulkTag}
289
+ onBulkDelete={handleBulkDelete}
290
+ />
236
291
  {/await}
237
- {:else if currentFile}
238
- {#key currentFile}
239
- {#await tagsQuery then tags}
240
- <FileDetails
241
- file={currentFile}
242
- allTags={tags}
243
- onDelete={deleteFileCommand}
244
- onReplace={(updated) => {
245
- currentFile = updated;
246
- filesQuery.refresh();
247
- allFilesQuery.refresh();
248
- }}
249
- {onTagUpdate}
250
- {onNameUpdate}
251
- />
252
- {/await}
253
- {/key}
254
- {:else}
255
- <div class="flex flex-col items-center justify-center p-12 text-center">
256
- <div class="mb-3 rounded-full bg-muted/50 p-4">
257
- <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
258
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
259
- </svg>
260
- </div>
261
- <p class="text-sm text-muted-foreground">{lang[interfaceLanguage.current].currentFilePlaceholder}</p>
292
+ {/await}
293
+ {:else if currentFile}
294
+ {#key currentFile}
295
+ {#await tagsQuery then tags}
296
+ <FileDetails
297
+ file={currentFile}
298
+ allTags={tags}
299
+ onDelete={deleteFileCommand}
300
+ onReplace={(updated) => {
301
+ currentFile = updated;
302
+ filesQuery.refresh();
303
+ allFilesQuery.refresh();
304
+ }}
305
+ {onTagUpdate}
306
+ {onNameUpdate}
307
+ />
308
+ {/await}
309
+ {/key}
310
+ {:else}
311
+ <!-- Empty state -->
312
+ <div class="flex flex-1 flex-col items-center justify-center px-5 py-10 text-center">
313
+ <div class="mb-3.5 flex h-14 w-14 items-center justify-center rounded-xl bg-muted">
314
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-text-light" fill="none" viewBox="0 0 24 24" stroke="currentColor">
315
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
316
+ </svg>
262
317
  </div>
263
- {/if}
264
- </div>
265
- </div>
318
+ <p class="text-sm font-bold text-foreground">{lang[interfaceLanguage.current].currentFilePlaceholder}</p>
319
+ <p class="mt-1 text-[13px] leading-relaxed text-muted-foreground">{lang[interfaceLanguage.current].currentFileDesc}</p>
320
+ </div>
321
+ {/if}
322
+ </aside>
266
323
  </div>
267
324
 
268
325
  <!-- Bulk action bar -->
@@ -274,7 +331,7 @@
274
331
  {tags}
275
332
  onBulkTag={handleBulkTag}
276
333
  onBulkDelete={handleBulkDelete}
277
- onClear={() => (selectedFileIds = [])}
334
+ onClear={exitSelectionMode}
278
335
  onSelectAll={() => { selectedFileIds = allFiles.map((f) => f.id); }}
279
336
  onCreateTag={handleCreateTag}
280
337
  onUpdateTagColor={async (id, color) => {
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
3
3
  import type { InterfaceLanguage } from '../../../types/languages.js';
4
- import Input from '../../../components/ui/input/input.svelte';
5
4
  import Search from '@tabler/icons-svelte/icons/search';
6
5
  import X from '@tabler/icons-svelte/icons/x';
7
6
 
@@ -36,20 +35,21 @@
36
35
  }
37
36
  </script>
38
37
 
39
- <div class="relative flex-1">
40
- <Search class="text-muted-foreground pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2" />
41
- <Input
38
+ <div class="flex flex-1 items-center gap-2 rounded-lg bg-muted/60 border border-transparent px-3 py-1.5 transition-all focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/10 focus-within:bg-card max-w-xs min-w-[200px]">
39
+ <Search class="h-4 w-4 shrink-0 text-text-light" />
40
+ <input
42
41
  type="search"
42
+ class="flex-1 border-none bg-transparent text-[13px] text-foreground placeholder:text-text-light outline-none"
43
43
  placeholder={lang[interfaceLanguage.current].placeholder}
44
- class="h-9 w-full pl-9 pr-8 text-sm"
45
44
  value={inputValue}
46
45
  oninput={handleInput}
47
46
  />
48
47
  {#if inputValue}
49
48
  <button
50
49
  type="button"
51
- class="absolute right-2 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-muted-foreground hover:text-foreground transition-colors"
50
+ class="rounded-sm p-0.5 text-muted-foreground hover:text-foreground transition-colors"
52
51
  onclick={clear}
52
+ aria-label="Wyczyść wyszukiwanie"
53
53
  >
54
54
  <X class="h-3.5 w-3.5" />
55
55
  </button>
@@ -3,6 +3,7 @@
3
3
  import type { InterfaceLanguage } from '../../../types/languages.js';
4
4
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
5
5
  import { sortOptions, getMediaSort } from '../../state/media-sort.svelte.js';
6
+ import ArrowsSort from '@tabler/icons-svelte/icons/arrows-sort';
6
7
 
7
8
  const lang: Record<
8
9
  InterfaceLanguage,
@@ -34,7 +35,8 @@
34
35
  </script>
35
36
 
36
37
  <Select.Root type="single" name="sort" value={sort.current}>
37
- <Select.Trigger>
38
+ <Select.Trigger class="inline-flex items-center gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-[13px] font-medium text-muted-foreground transition-colors hover:bg-lavender-lighter hover:border-lavender hover:text-primary focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-2 h-auto">
39
+ <ArrowsSort class="h-3.5 w-3.5" />
38
40
  {triggerContent}
39
41
  </Select.Trigger>
40
42
  <Select.Content>
@@ -22,6 +22,8 @@
22
22
  deleteConfirmDesc: string;
23
23
  cancel: string;
24
24
  delete: string;
25
+ typesLabel: string;
26
+ emptyHint: string;
25
27
  }
26
28
  > = {
27
29
  pl: {
@@ -35,7 +37,9 @@
35
37
  deleteConfirmTitle: 'Usunąć pliki?',
36
38
  deleteConfirmDesc: 'Zaznaczone pliki zostaną trwale usunięte.',
37
39
  cancel: 'Anuluj',
38
- delete: 'Usuń'
40
+ delete: 'Usuń',
41
+ typesLabel: 'Typy',
42
+ emptyHint: 'Kliknij pliki, aby je zaznaczyć'
39
43
  },
40
44
  en: {
41
45
  filesSelected: 'files selected',
@@ -48,7 +52,9 @@
48
52
  deleteConfirmTitle: 'Delete files?',
49
53
  deleteConfirmDesc: 'Selected files will be permanently deleted.',
50
54
  cancel: 'Cancel',
51
- delete: 'Delete'
55
+ delete: 'Delete',
56
+ typesLabel: 'Types',
57
+ emptyHint: 'Click files to select them'
52
58
  }
53
59
  };
54
60
 
@@ -89,7 +95,6 @@
89
95
  return allTags.filter((t) => firstTagIds.has(t.id));
90
96
  });
91
97
 
92
- // Initialize bulkTagIds with shared tags when files change
93
98
  $effect(() => {
94
99
  bulkTagIds = sharedTags.map((t) => t.id);
95
100
  });
@@ -103,87 +108,101 @@
103
108
  });
104
109
  </script>
105
110
 
106
- <!-- Header -->
107
- <div class="flex items-center gap-3 border-b border-white/10 px-5 py-4">
108
- <div class="flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10">
109
- <Files class="h-5 w-5 text-primary" />
110
- </div>
111
- <div>
112
- <div class="text-lg font-semibold tabular-nums">{files.length}</div>
113
- <div class="text-xs glass-text-muted">{lang[interfaceLanguage.current].filesSelected}</div>
111
+ {#if files.length === 0}
112
+ <div class="flex flex-1 flex-col items-center justify-center px-5 py-10 text-center h-full">
113
+ <div class="mb-3.5 flex h-14 w-14 items-center justify-center rounded-xl bg-muted">
114
+ <Files class="h-6 w-6 text-text-light" />
114
115
  </div>
116
+ <p class="text-sm font-bold text-foreground">0 {lang[interfaceLanguage.current].filesSelected}</p>
117
+ <p class="mt-1 text-[13px] leading-relaxed text-muted-foreground">{lang[interfaceLanguage.current].emptyHint}</p>
115
118
  </div>
116
-
117
- <div class="space-y-4 p-5">
118
- <!-- Stats -->
119
- <div class="grid grid-cols-2 gap-3">
120
- <div class="rounded-xl bg-muted/30 px-3 py-2.5">
121
- <div class="text-xs glass-text-muted">{lang[interfaceLanguage.current].totalSize}</div>
122
- <div class="mt-0.5 text-sm font-medium">{formatFileSize(totalSize)}</div>
123
- </div>
124
- <div class="rounded-xl bg-muted/30 px-3 py-2.5">
125
- <div class="text-xs glass-text-muted">Types</div>
126
- <div class="mt-0.5 flex gap-1.5 text-sm font-medium">
127
- {#each Object.entries(typeBreakdown) as [type, count]}
128
- <span class="text-xs">{count} {type}</span>
129
- {/each}
119
+ {:else}
120
+ <div class="flex h-full flex-col overflow-hidden">
121
+ <div class="flex-1 overflow-y-auto scrollbar-thin">
122
+ <!-- Header -->
123
+ <div class="flex items-center gap-3 border-b px-5 py-4">
124
+ <div class="flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10">
125
+ <Files class="h-5 w-5 text-primary" />
126
+ </div>
127
+ <div>
128
+ <div class="text-lg font-bold tabular-nums text-foreground">{files.length}</div>
129
+ <div class="text-xs text-text-light font-medium">{lang[interfaceLanguage.current].filesSelected}</div>
130
130
  </div>
131
131
  </div>
132
- </div>
133
132
 
134
- <!-- Shared tags -->
135
- <div class="space-y-2">
136
- <div class="text-xs font-medium glass-text-muted">{lang[interfaceLanguage.current].sharedTags}</div>
137
- {#if sharedTags.length > 0}
138
- <div class="flex flex-wrap gap-1.5">
139
- {#each sharedTags as tag}
140
- <span class="inline-flex items-center gap-1.5 rounded-full bg-muted/50 px-2.5 py-1 text-xs">
141
- <span class="h-2 w-2 rounded-full" style="background-color: {tag.color}"></span>
142
- {tag.name}
143
- </span>
144
- {/each}
133
+ <div class="space-y-4 p-5">
134
+ <!-- Stats -->
135
+ <div class="grid grid-cols-2 gap-3">
136
+ <div class="rounded-xl bg-muted/40 px-3 py-2.5">
137
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].totalSize}</div>
138
+ <div class="mt-0.5 text-sm font-semibold text-foreground">{formatFileSize(totalSize)}</div>
139
+ </div>
140
+ <div class="rounded-xl bg-muted/40 px-3 py-2.5">
141
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].typesLabel}</div>
142
+ <div class="mt-0.5 flex gap-1.5 text-sm font-semibold text-foreground">
143
+ {#each Object.entries(typeBreakdown) as [type, count]}
144
+ <span class="text-xs">{count} {type}</span>
145
+ {/each}
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Shared tags -->
151
+ <div class="space-y-1.5">
152
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].sharedTags}</div>
153
+ {#if sharedTags.length > 0}
154
+ <div class="flex flex-wrap gap-1.5">
155
+ {#each sharedTags as tag}
156
+ <span class="inline-flex items-center gap-1.5 rounded-full bg-muted/50 px-2.5 py-1 text-xs font-medium">
157
+ <span class="h-1.5 w-1.5 rounded-full" style="background-color: {tag.color}"></span>
158
+ {tag.name}
159
+ </span>
160
+ {/each}
161
+ </div>
162
+ {:else}
163
+ <p class="text-xs text-muted-foreground">{lang[interfaceLanguage.current].noSharedTags}</p>
164
+ {/if}
145
165
  </div>
146
- {:else}
147
- <p class="text-xs text-muted-foreground">{lang[interfaceLanguage.current].noSharedTags}</p>
148
- {/if}
166
+
167
+ <!-- Bulk tag -->
168
+ <div class="space-y-1.5">
169
+ <div class="text-[11px] font-bold uppercase tracking-wide text-text-light">{lang[interfaceLanguage.current].setTags}</div>
170
+ <TagCombobox
171
+ tags={allTags}
172
+ selectedTagIds={bulkTagIds}
173
+ onchange={(ids) => (bulkTagIds = ids)}
174
+ showLabel={false}
175
+ />
176
+ <Button
177
+ size="sm"
178
+ variant="outline"
179
+ class="w-full"
180
+ onclick={async () => {
181
+ await onBulkTag(bulkTagIds);
182
+ }}
183
+ >
184
+ {lang[interfaceLanguage.current].apply}
185
+ </Button>
186
+ </div>
187
+ </div>
149
188
  </div>
150
189
 
151
- <!-- Bulk tag -->
152
- <div class="space-y-2">
153
- <div class="text-xs font-medium glass-text-muted">{lang[interfaceLanguage.current].setTags}</div>
154
- <TagCombobox
155
- tags={allTags}
156
- selectedTagIds={bulkTagIds}
157
- onchange={(ids) => (bulkTagIds = ids)}
158
- showLabel={false}
159
- />
190
+ <!-- Delete -->
191
+ <div class="shrink-0 border-t px-5 py-4">
160
192
  <Button
161
193
  size="sm"
162
- variant="outline"
163
- class="w-full"
164
- onclick={async () => {
165
- await onBulkTag(bulkTagIds);
166
- }}
194
+ variant="destructive"
195
+ class="w-full gap-1.5"
196
+ onclick={() => (deleteDialogOpen = true)}
167
197
  >
168
- {lang[interfaceLanguage.current].apply}
198
+ <Trash class="h-3.5 w-3.5" />
199
+ {lang[interfaceLanguage.current].deleteAll}
169
200
  </Button>
170
201
  </div>
171
202
  </div>
172
203
 
173
- <!-- Delete -->
174
- <div class="border-t border-white/10 px-5 py-4">
175
- <Button
176
- size="sm"
177
- variant="destructive"
178
- class="w-full gap-1.5"
179
- onclick={() => (deleteDialogOpen = true)}
180
- >
181
- <Trash class="h-3.5 w-3.5" />
182
- {lang[interfaceLanguage.current].deleteAll}
183
- </Button>
184
- </div>
185
-
186
204
  <AlertDialog.Root bind:open={deleteDialogOpen}>
205
+
187
206
  <AlertDialog.Content>
188
207
  <AlertDialog.Title>{lang[interfaceLanguage.current].deleteConfirmTitle}</AlertDialog.Title>
189
208
  <AlertDialog.Description>{lang[interfaceLanguage.current].deleteConfirmDesc}</AlertDialog.Description>
@@ -200,3 +219,4 @@
200
219
  </AlertDialog.Footer>
201
220
  </AlertDialog.Content>
202
221
  </AlertDialog.Root>
222
+ {/if}