includio-cms 0.25.0 → 0.26.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.
- package/API.md +57 -4
- package/CHANGELOG.md +53 -0
- package/DOCS.md +1 -1
- package/README.md +2 -0
- package/ROADMAP.md +6 -0
- package/dist/admin/client/account/lang.d.ts +1 -0
- package/dist/admin/client/account/lang.js +4 -2
- package/dist/admin/client/account/profile-section.svelte +2 -2
- package/dist/admin/client/account/security-section.svelte +27 -4
- package/dist/admin/client/account/sessions-section.svelte +1 -1
- package/dist/admin/client/admin/admin-after-login-layout-content.svelte +1 -1
- package/dist/admin/client/admin/dashboard-page.svelte +34 -10
- package/dist/admin/client/collection/bulk-actions-bar.svelte +86 -44
- package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +3 -1
- package/dist/admin/client/collection/collection-entries.svelte +52 -36
- package/dist/admin/client/collection/collection-entries.svelte.d.ts +3 -0
- package/dist/admin/client/collection/collection.svelte +28 -14
- package/dist/admin/client/collection/collection.svelte.d.ts +3 -0
- package/dist/admin/client/collection/data-table.svelte +279 -130
- package/dist/admin/client/collection/data-table.svelte.d.ts +11 -0
- package/dist/admin/client/collection/date-cell.svelte +4 -4
- package/dist/admin/client/collection/row-actions.svelte +2 -1
- package/dist/admin/client/collection/sortable-header.svelte +33 -9
- package/dist/admin/client/collection/state-display.svelte +102 -0
- package/dist/admin/client/collection/state-display.svelte.d.ts +12 -0
- package/dist/admin/client/collection/status-badge.svelte +99 -11
- package/dist/admin/client/collection/status-badge.svelte.d.ts +15 -1
- package/dist/admin/client/collection/table-pagination.svelte +21 -6
- package/dist/admin/client/collection/table-toolbar.svelte +105 -80
- package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -8
- package/dist/admin/client/entry/entry-form.svelte +36 -11
- package/dist/admin/client/entry/entry-form.svelte.d.ts +1 -0
- package/dist/admin/client/entry/entry-header.svelte +22 -15
- package/dist/admin/client/entry/entry-header.svelte.d.ts +1 -0
- package/dist/admin/client/entry/entry.svelte +269 -165
- package/dist/admin/client/entry/header/a11y-header-badge.svelte +47 -0
- package/dist/admin/client/entry/header/a11y-header-badge.svelte.d.ts +8 -0
- package/dist/admin/client/entry/header/publish-panel.svelte +69 -13
- package/dist/admin/client/entry/header/save-indicator.svelte +57 -28
- package/dist/admin/client/entry/header/save-indicator.svelte.d.ts +1 -0
- package/dist/admin/client/entry/header/status-badge.svelte +60 -15
- package/dist/admin/client/entry/header/status-badge.svelte.d.ts +1 -2
- package/dist/admin/client/entry/header/version-history-sheet.svelte +1 -1
- package/dist/admin/client/entry/hybrid/hybrid-layout.svelte +74 -23
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +1 -1
- package/dist/admin/client/entry/utils.d.ts +14 -0
- package/dist/admin/client/entry/utils.js +28 -0
- package/dist/admin/client/form/form-submission/form-submission.svelte +2 -2
- package/dist/admin/client/form/form-submissions.svelte +143 -194
- package/dist/admin/client/form/form-submissions.svelte.d.ts +2 -0
- package/dist/admin/client/login/lang.d.ts +3 -0
- package/dist/admin/client/login/lang.js +10 -4
- package/dist/admin/client/login/login-form.svelte +8 -1
- package/dist/admin/client/login/reset-password-page.svelte +24 -3
- package/dist/admin/client/login/schema.d.ts +14 -2
- package/dist/admin/client/login/schema.js +19 -8
- package/dist/admin/client/maintenance/maintenance-page.svelte +16 -17
- package/dist/admin/client/media/media-page.svelte +1 -1
- package/dist/admin/client/shop/coupon-edit-page.svelte +117 -13
- package/dist/admin/client/shop/coupon-form.svelte +282 -138
- package/dist/admin/client/shop/coupon-form.svelte.d.ts +1 -9
- package/dist/admin/client/shop/coupon-new-page.svelte +40 -10
- package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +2 -17
- package/dist/admin/client/shop/coupon-schema.d.ts +28 -0
- package/dist/admin/client/shop/coupon-schema.js +53 -0
- package/dist/admin/client/shop/coupons-list-page.svelte +262 -118
- package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +16 -1
- package/dist/admin/client/shop/shipping-method-edit-page.svelte +108 -59
- package/dist/admin/client/shop/shipping-method-form.svelte +36 -9
- package/dist/admin/client/shop/shipping-method-new-page.svelte +44 -13
- package/dist/admin/client/shop/shipping-methods-list-page.svelte +101 -59
- package/dist/admin/client/shop/shop-order-detail-page.svelte +113 -84
- package/dist/admin/client/shop/shop-orders-list-page.svelte +302 -152
- package/dist/admin/client/shop/shop-orders-list-page.svelte.d.ts +18 -1
- package/dist/admin/client/shop/shop-products-list-page.svelte +355 -118
- package/dist/admin/client/shop/shop-products-list-page.svelte.d.ts +19 -1
- package/dist/admin/client/users/accept-invite-page.svelte +24 -3
- package/dist/admin/client/users/create-user-dialog.svelte +3 -8
- package/dist/admin/client/users/lang.d.ts +2 -0
- package/dist/admin/client/users/lang.js +4 -0
- package/dist/admin/client/users/pending-invitations.svelte +2 -9
- package/dist/admin/client/users/user-name-cell.svelte +20 -0
- package/dist/admin/client/users/user-name-cell.svelte.d.ts +9 -0
- package/dist/admin/client/users/user-role-badge.svelte +16 -0
- package/dist/admin/client/users/user-role-badge.svelte.d.ts +7 -0
- package/dist/admin/client/users/user-row-actions.svelte +72 -0
- package/dist/admin/client/users/user-row-actions.svelte.d.ts +20 -0
- package/dist/admin/client/users/user-sessions-sheet.svelte +2 -11
- package/dist/admin/client/users/users-page.svelte +283 -497
- package/dist/admin/client/users/users-page.svelte.d.ts +12 -1
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +59 -74
- package/dist/admin/components/dashboard/recent-activity.svelte +17 -5
- package/dist/admin/components/dashboard/recent-entries.svelte +19 -7
- package/dist/admin/components/dialogs/confirmation-dialog.svelte +105 -0
- package/dist/admin/components/dialogs/confirmation-dialog.svelte.d.ts +13 -0
- package/dist/admin/components/fields/block-picker-modal.svelte +6 -0
- package/dist/admin/components/fields/blocks-field.svelte +46 -1
- package/dist/admin/components/fields/boolean-field.svelte +1 -1
- package/dist/admin/components/fields/field-renderer.svelte +23 -21
- package/dist/admin/components/fields/file-field.svelte +344 -30
- package/dist/admin/components/fields/media-field.svelte +16 -2
- package/dist/admin/components/fields/radio-field.svelte +22 -0
- package/dist/admin/components/fields/relation-field.svelte +123 -97
- package/dist/admin/components/fields/relation-picker-dialog.svelte +2 -2
- package/dist/admin/components/fields/seo-field.svelte +60 -30
- package/dist/admin/components/fields/shop-field.svelte +9 -4
- package/dist/admin/components/fields/simple-array-field.svelte +321 -151
- package/dist/admin/components/fields/simple-array-field.svelte.d.ts +3 -0
- package/dist/admin/components/fields/slug-field.svelte +146 -21
- package/dist/admin/components/fields/text-field-wrapper.svelte +37 -20
- package/dist/admin/components/fields/text-field.svelte +7 -2
- package/dist/admin/components/fields/url-field-wrapper.svelte +10 -0
- package/dist/admin/components/fields/url-field.svelte +36 -23
- package/dist/admin/components/forms/form-error-summary.svelte +143 -0
- package/dist/admin/components/forms/form-error-summary.svelte.d.ts +27 -0
- package/dist/admin/components/layout/app-sidebar.svelte +7 -2
- package/dist/admin/components/layout/detail-page-shell.svelte +71 -0
- package/dist/admin/components/layout/detail-page-shell.svelte.d.ts +24 -0
- package/dist/admin/components/layout/lang.d.ts +5 -0
- package/dist/admin/components/layout/lang.js +10 -0
- package/dist/admin/components/layout/layout-renderer.svelte +71 -2
- package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/layout/layout-tabs.svelte +172 -0
- package/dist/admin/components/layout/layout-tabs.svelte.d.ts +24 -0
- package/dist/admin/components/layout/nav-breadcrumbs.svelte +25 -7
- package/dist/admin/components/layout/nav-collections.svelte +23 -36
- package/dist/admin/components/layout/nav-forms.svelte +19 -35
- package/dist/admin/components/layout/nav-main.svelte +3 -28
- package/dist/admin/components/layout/nav-search.svelte +70 -2
- package/dist/admin/components/layout/nav-section.svelte +77 -0
- package/dist/admin/components/layout/nav-section.svelte.d.ts +22 -0
- package/dist/admin/components/layout/nav-shop.svelte +3 -27
- package/dist/admin/components/layout/nav-singletons.svelte +16 -28
- package/dist/admin/components/layout/page-header.stories.svelte +93 -0
- package/dist/admin/components/layout/page-header.stories.svelte.d.ts +27 -0
- package/dist/admin/components/layout/page-header.svelte +68 -0
- package/dist/admin/components/layout/page-header.svelte.d.ts +17 -0
- package/dist/admin/components/layout/site-header.svelte +9 -0
- package/dist/admin/components/layout/site-header.svelte.d.ts +2 -17
- package/dist/admin/components/media/file/file-name-input.svelte +6 -2
- package/dist/admin/components/media/file/file-preview.svelte +130 -17
- package/dist/admin/components/media/file-upload.svelte +16 -7
- package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
- package/dist/admin/components/media/files-list.svelte +153 -53
- package/dist/admin/components/media/files-list.svelte.d.ts +1 -0
- package/dist/admin/components/media/media-library.svelte +577 -198
- package/dist/admin/components/media/media-library.svelte.d.ts +4 -0
- package/dist/admin/components/media/media-selector.svelte +4 -2
- package/dist/admin/components/media/media-selector.svelte.d.ts +1 -0
- package/dist/admin/components/media/tag-sidebar.svelte +4 -4
- package/dist/admin/components/tiptap/FigureNodeView.svelte +10 -0
- package/dist/admin/components/tiptap/bubble-menu.svelte +104 -0
- package/dist/admin/components/tiptap/bubble-menu.svelte.d.ts +19 -0
- package/dist/admin/components/tiptap/content-editor.svelte +28 -24
- package/dist/admin/components/tiptap/editor-toolbar.svelte +7 -7
- package/dist/admin/components/tiptap/extensions.js +5 -1
- package/dist/admin/components/tiptap/image-dialog.svelte +5 -1
- package/dist/admin/components/tiptap/link-dialog.svelte +2 -0
- package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -20
- package/dist/admin/components/tiptap/video-dialog.svelte +1 -1
- package/dist/admin/i18n/errors.d.ts +140 -0
- package/dist/admin/i18n/errors.js +151 -0
- package/dist/admin/remote/entry.remote.d.ts +59 -4
- package/dist/admin/remote/entry.remote.js +239 -62
- package/dist/admin/remote/shop.remote.d.ts +37 -32
- package/dist/admin/remote/shop.remote.js +9 -2
- package/dist/admin/shared/password-generate.d.ts +6 -0
- package/dist/admin/shared/password-generate.js +40 -0
- package/dist/admin/shared/password-schema.d.ts +6 -0
- package/dist/admin/shared/password-schema.js +10 -3
- package/dist/admin/styles/admin.css +23 -6
- package/dist/admin/styles/tokens.md +244 -0
- package/dist/admin/utils/accordionActivation.d.ts +13 -0
- package/dist/admin/utils/accordionActivation.js +35 -0
- package/dist/admin/utils/entryLabel.d.ts +23 -0
- package/dist/admin/utils/entryLabel.js +51 -12
- package/dist/admin/utils/field-a11y.d.ts +29 -0
- package/dist/admin/utils/field-a11y.js +23 -0
- package/dist/admin/utils/fieldPathElement.d.ts +9 -0
- package/dist/admin/utils/fieldPathElement.js +18 -0
- package/dist/admin/utils/fileDisplay.d.ts +10 -0
- package/dist/admin/utils/fileDisplay.js +26 -0
- package/dist/admin/utils/flattenFormErrors.d.ts +19 -0
- package/dist/admin/utils/flattenFormErrors.js +102 -0
- package/dist/admin/utils/formatters.d.ts +12 -0
- package/dist/admin/utils/{formatDate.js → formatters.js} +23 -2
- package/dist/admin/utils/scrollWithin.d.ts +9 -0
- package/dist/admin/utils/scrollWithin.js +32 -0
- package/dist/admin/utils/tabActivation.d.ts +12 -0
- package/dist/admin/utils/tabActivation.js +24 -0
- package/dist/cms/runtime/schema.d.ts +1 -0
- package/dist/cms/runtime/schema.js +1 -0
- package/dist/cms/runtime/types.d.ts +80 -7
- package/dist/components/ui/accordion/accordion-content.svelte +17 -3
- package/dist/components/ui/accordion/accordion.stories.svelte +21 -1
- package/dist/components/ui/alert/alert.stories.svelte +14 -0
- package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte +45 -0
- package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte.d.ts +27 -0
- package/dist/components/ui/avatar/avatar.stories.svelte +27 -0
- package/dist/components/ui/badge/badge.stories.svelte +15 -0
- package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +47 -0
- package/dist/components/ui/breadcrumb/breadcrumb.svelte +1 -1
- package/dist/components/ui/button/button.stories.svelte +53 -6
- package/dist/components/ui/button/button.svelte +39 -5
- package/dist/components/ui/button/button.svelte.d.ts +4 -0
- package/dist/components/ui/button-group/button-group.stories.svelte +44 -0
- package/dist/components/ui/button-group/button-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/calendar/calendar.stories.svelte +36 -0
- package/dist/components/ui/calendar/calendar.stories.svelte.d.ts +27 -0
- package/dist/components/ui/card/card.stories.svelte +7 -0
- package/dist/components/ui/carousel/carousel.stories.svelte +43 -0
- package/dist/components/ui/carousel/carousel.stories.svelte.d.ts +27 -0
- package/dist/components/ui/checkbox/checkbox.stories.svelte +67 -0
- package/dist/components/ui/checkbox/checkbox.stories.svelte.d.ts +27 -0
- package/dist/components/ui/checkbox/checkbox.svelte +3 -3
- package/dist/components/ui/command/command.stories.svelte +18 -0
- package/dist/components/ui/data-table/data-table.stories.svelte +61 -0
- package/dist/components/ui/data-table/data-table.stories.svelte.d.ts +18 -0
- package/dist/components/ui/dialog/dialog-content.svelte +5 -0
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +2 -0
- package/dist/components/ui/dialog/dialog.stories.svelte +35 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte +74 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte.d.ts +27 -0
- package/dist/components/ui/field/field-context.svelte.d.ts +22 -0
- package/dist/components/ui/field/field-context.svelte.js +9 -0
- package/dist/components/ui/field/field-control.svelte +18 -0
- package/dist/components/ui/field/field-control.svelte.d.ts +8 -0
- package/dist/components/ui/field/field-description.svelte +12 -0
- package/dist/components/ui/field/field-error.svelte +14 -6
- package/dist/components/ui/field/field-label.svelte +10 -0
- package/dist/components/ui/field/field.stories.svelte +95 -9
- package/dist/components/ui/field/field.svelte +57 -0
- package/dist/components/ui/field/field.svelte.d.ts +2 -0
- package/dist/components/ui/field/index.d.ts +3 -1
- package/dist/components/ui/field/index.js +4 -2
- package/dist/components/ui/form/form-field-errors.svelte +1 -1
- package/dist/components/ui/form/form.stories.svelte +25 -0
- package/dist/components/ui/form/form.stories.svelte.d.ts +26 -0
- package/dist/components/ui/input/input.stories.svelte +26 -0
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group.stories.svelte +43 -0
- package/dist/components/ui/input-group/input-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/item/item.stories.svelte +61 -0
- package/dist/components/ui/item/item.stories.svelte.d.ts +27 -0
- package/dist/components/ui/label/label.stories.svelte +7 -0
- package/dist/components/ui/live-region/index.d.ts +1 -0
- package/dist/components/ui/live-region/index.js +1 -0
- package/dist/components/ui/live-region/live-region-demo.svelte +32 -0
- package/dist/components/ui/live-region/live-region-demo.svelte.d.ts +7 -0
- package/dist/components/ui/live-region/live-region.stories.svelte +23 -0
- package/dist/components/ui/live-region/live-region.stories.svelte.d.ts +26 -0
- package/dist/components/ui/live-region/live-region.svelte +12 -0
- package/dist/components/ui/live-region/live-region.svelte.d.ts +8 -0
- package/dist/components/ui/popover/popover.stories.svelte +34 -0
- package/dist/components/ui/radio-group/radio-group.stories.svelte +58 -0
- package/dist/components/ui/radio-group/radio-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/resizable/resizable.stories.svelte +56 -0
- package/dist/components/ui/resizable/resizable.stories.svelte.d.ts +27 -0
- package/dist/components/ui/select/select.stories.svelte +49 -0
- package/dist/components/ui/separator/separator.stories.svelte +18 -0
- package/dist/components/ui/sheet/sheet.stories.svelte +34 -0
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte +1 -0
- package/dist/components/ui/sidebar/sidebar-trigger.svelte +1 -1
- package/dist/components/ui/sidebar/sidebar.stories.svelte +72 -0
- package/dist/components/ui/sidebar/sidebar.stories.svelte.d.ts +27 -0
- package/dist/components/ui/skeleton/skeleton.stories.svelte +39 -0
- package/dist/components/ui/skeleton/skeleton.stories.svelte.d.ts +27 -0
- package/dist/components/ui/skeleton/skeleton.svelte +6 -0
- package/dist/components/ui/sonner/index.d.ts +1 -1
- package/dist/components/ui/sonner/index.js +1 -1
- package/dist/components/ui/sonner/sonner.stories.svelte +7 -0
- package/dist/components/ui/sonner/sonner.svelte +17 -1
- package/dist/components/ui/sonner/sonner.svelte.d.ts +6 -0
- package/dist/components/ui/spinner/spinner.stories.svelte +30 -0
- package/dist/components/ui/spinner/spinner.stories.svelte.d.ts +27 -0
- package/dist/components/ui/switch/switch.stories.svelte +56 -0
- package/dist/components/ui/switch/switch.stories.svelte.d.ts +27 -0
- package/dist/components/ui/table/table-cell.svelte +1 -1
- package/dist/components/ui/table/table-head.svelte +1 -1
- package/dist/components/ui/table/table.stories.svelte +68 -0
- package/dist/components/ui/table/table.stories.svelte.d.ts +27 -0
- package/dist/components/ui/table/table.svelte +1 -1
- package/dist/components/ui/tabs/tabs.stories.svelte +48 -0
- package/dist/components/ui/tabs/tabs.stories.svelte.d.ts +27 -0
- package/dist/components/ui/textarea/textarea.stories.svelte +21 -0
- package/dist/components/ui/toggle/toggle.stories.svelte +23 -0
- package/dist/components/ui/toggle-group/toggle-group.stories.svelte +43 -0
- package/dist/components/ui/tooltip/tooltip.stories.svelte +46 -6
- package/dist/core/fields/fieldSchemaToTs.d.ts +7 -0
- package/dist/core/fields/fieldSchemaToTs.js +234 -90
- package/dist/core/fields/layoutUtils.d.ts +4 -1
- package/dist/core/fields/layoutUtils.js +41 -4
- package/dist/core/fields/resolveSeo.d.ts +70 -0
- package/dist/core/fields/resolveSeo.js +88 -0
- package/dist/core/fields/seoFieldDescriptor.d.ts +43 -0
- package/dist/core/fields/seoFieldDescriptor.js +74 -0
- package/dist/core/fields/slugPath.d.ts +13 -0
- package/dist/core/fields/slugPath.js +32 -0
- package/dist/core/fields/urlUtils.d.ts +8 -0
- package/dist/core/fields/urlUtils.js +27 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/server/entries/operations/create.js +13 -0
- package/dist/core/server/entries/operations/get.d.ts +7 -0
- package/dist/core/server/entries/operations/get.js +10 -6
- package/dist/core/server/entries/operations/slugUniqueness.d.ts +37 -0
- package/dist/core/server/entries/operations/slugUniqueness.js +116 -0
- package/dist/core/server/entries/operations/update.d.ts +6 -1
- package/dist/core/server/entries/operations/update.js +24 -1
- package/dist/core/server/fields/slugResolver.d.ts +3 -13
- package/dist/core/server/fields/slugResolver.js +8 -37
- package/dist/core/server/generator/fields.js +10 -17
- package/dist/core/server/generator/formFields.js +2 -1
- package/dist/core/server/generator/generator.js +4 -4
- package/dist/core/server/generator/utils.d.ts +1 -0
- package/dist/core/server/generator/utils.js +4 -0
- package/dist/paraglide/messages/_index.d.ts +3 -36
- package/dist/paraglide/messages/_index.js +3 -71
- package/dist/paraglide/messages/hello_world.d.ts +5 -0
- package/dist/paraglide/messages/hello_world.js +33 -0
- package/dist/paraglide/messages/login_hello.d.ts +16 -0
- package/dist/paraglide/messages/login_hello.js +34 -0
- package/dist/paraglide/messages/login_please_login.d.ts +16 -0
- package/dist/paraglide/messages/login_please_login.js +34 -0
- package/dist/shop/server/orders.d.ts +1 -0
- package/dist/shop/server/orders.js +14 -0
- package/dist/shop/server/shop-data.d.ts +2 -0
- package/dist/shop/server/shop-data.js +20 -5
- package/dist/sveltekit/server/handle.js +17 -0
- package/dist/types/cms.schema.js +4 -2
- package/dist/types/fields.d.ts +35 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/layout.d.ts +35 -2
- package/dist/updates/0.26.0/index.d.ts +2 -0
- package/dist/updates/0.26.0/index.js +51 -0
- package/dist/updates/index.js +3 -1
- package/package.json +29 -7
- package/dist/admin/client/collection/empty-state.svelte +0 -28
- package/dist/admin/client/collection/empty-state.svelte.d.ts +0 -9
- package/dist/admin/client/form/submission-status-badge.svelte +0 -41
- package/dist/admin/client/form/submission-status-badge.svelte.d.ts +0 -7
- package/dist/admin/components/media/file-preview.svelte +0 -51
- package/dist/admin/components/media/file-preview.svelte.d.ts +0 -6
- package/dist/admin/utils/formatDate.d.ts +0 -5
- package/dist/paraglide/messages/en.d.ts +0 -5
- package/dist/paraglide/messages/en.js +0 -14
- package/dist/paraglide/messages/pl.d.ts +0 -5
- package/dist/paraglide/messages/pl.js +0 -14
|
@@ -12,14 +12,24 @@
|
|
|
12
12
|
import MediaSort from './media-sort.svelte';
|
|
13
13
|
import FilesList from './files-list.svelte';
|
|
14
14
|
import TagSidebar from './tag-sidebar.svelte';
|
|
15
|
-
import MediaSearch from './media-search.svelte';
|
|
16
|
-
import BulkActionBar from './bulk-action-bar.svelte';
|
|
17
15
|
import ListCheck from '@tabler/icons-svelte/icons/list-check';
|
|
16
|
+
import TagIcon from '@tabler/icons-svelte/icons/tag';
|
|
18
17
|
import Toggle from '../../../components/ui/toggle/toggle.svelte';
|
|
18
|
+
import { Badge } from '../../../components/ui/badge/index.js';
|
|
19
|
+
import * as Popover from '../../../components/ui/popover/index.js';
|
|
20
|
+
import * as Sheet from '../../../components/ui/sheet/index.js';
|
|
21
|
+
import Button from '../../../components/ui/button/button.svelte';
|
|
22
|
+
import PageHeader from '../layout/page-header.svelte';
|
|
23
|
+
import TableToolbar from '../../client/collection/table-toolbar.svelte';
|
|
24
|
+
import TablePagination from '../../client/collection/table-pagination.svelte';
|
|
25
|
+
import BulkActionsBar from '../../client/collection/bulk-actions-bar.svelte';
|
|
26
|
+
import StateDisplay from '../../client/collection/state-display.svelte';
|
|
27
|
+
import { IsMobile } from '../../../hooks/is-mobile.svelte.js';
|
|
19
28
|
|
|
20
29
|
const lang: Record<
|
|
21
30
|
InterfaceLanguage,
|
|
22
31
|
{
|
|
32
|
+
title: string;
|
|
23
33
|
fileDeletedToast: string;
|
|
24
34
|
currentFilePlaceholder: string;
|
|
25
35
|
currentFileDesc: string;
|
|
@@ -28,10 +38,29 @@
|
|
|
28
38
|
selectModeActive: string;
|
|
29
39
|
selectModeAnnounce: string;
|
|
30
40
|
selectModeOffAnnounce: string;
|
|
31
|
-
|
|
41
|
+
search: string;
|
|
42
|
+
searchPlaceholder: string;
|
|
43
|
+
tagsFilterLabel: string;
|
|
44
|
+
typeFilterLabel: string;
|
|
45
|
+
tagSidebarLabel: string;
|
|
46
|
+
filesGridLabel: string;
|
|
47
|
+
filesListLabel: string;
|
|
48
|
+
filesLabel: string;
|
|
49
|
+
detailsLabel: string;
|
|
50
|
+
emptyTitle: string;
|
|
51
|
+
emptyDescription: string;
|
|
52
|
+
noResults: string;
|
|
53
|
+
bulkTag: string;
|
|
54
|
+
bulkSelectAll: string;
|
|
55
|
+
typeImage: string;
|
|
56
|
+
typeVideo: string;
|
|
57
|
+
typeAudio: string;
|
|
58
|
+
typePdf: string;
|
|
59
|
+
typeOther: string;
|
|
32
60
|
}
|
|
33
61
|
> = {
|
|
34
62
|
pl: {
|
|
63
|
+
title: 'Biblioteka mediów',
|
|
35
64
|
fileDeletedToast: 'Plik został usunięty',
|
|
36
65
|
currentFilePlaceholder: 'Podgląd pliku',
|
|
37
66
|
currentFileDesc: 'Wybierz plik z listy, aby zobaczyć szczegóły i edytować metadane.',
|
|
@@ -40,9 +69,28 @@
|
|
|
40
69
|
selectModeActive: 'Zaznaczanie',
|
|
41
70
|
selectModeAnnounce: 'Tryb zaznaczania włączony. Klikaj pliki, aby je zaznaczyć.',
|
|
42
71
|
selectModeOffAnnounce: 'Tryb zaznaczania wyłączony.',
|
|
43
|
-
|
|
72
|
+
search: 'Szukaj',
|
|
73
|
+
searchPlaceholder: 'Szukaj po nazwie…',
|
|
74
|
+
tagsFilterLabel: 'Tag',
|
|
75
|
+
typeFilterLabel: 'Typ',
|
|
76
|
+
tagSidebarLabel: 'Filtry tagów',
|
|
77
|
+
filesGridLabel: 'Siatka plików',
|
|
78
|
+
filesListLabel: 'Lista plików',
|
|
79
|
+
filesLabel: 'Pliki',
|
|
80
|
+
detailsLabel: 'Szczegóły pliku',
|
|
81
|
+
emptyTitle: 'Brak plików',
|
|
82
|
+
emptyDescription: 'Prześlij pierwszy plik, aby zobaczyć go w bibliotece.',
|
|
83
|
+
noResults: 'Brak wyników.',
|
|
84
|
+
bulkTag: 'Otaguj',
|
|
85
|
+
bulkSelectAll: 'Zaznacz stronę',
|
|
86
|
+
typeImage: 'Obraz',
|
|
87
|
+
typeVideo: 'Wideo',
|
|
88
|
+
typeAudio: 'Audio',
|
|
89
|
+
typePdf: 'PDF',
|
|
90
|
+
typeOther: 'Inne'
|
|
44
91
|
},
|
|
45
92
|
en: {
|
|
93
|
+
title: 'Media library',
|
|
46
94
|
fileDeletedToast: 'File has been deleted',
|
|
47
95
|
currentFilePlaceholder: 'File preview',
|
|
48
96
|
currentFileDesc: 'Select a file from the list to view details and edit metadata.',
|
|
@@ -51,7 +99,25 @@
|
|
|
51
99
|
selectModeActive: 'Selecting',
|
|
52
100
|
selectModeAnnounce: 'Selection mode enabled. Click files to select them.',
|
|
53
101
|
selectModeOffAnnounce: 'Selection mode disabled.',
|
|
54
|
-
|
|
102
|
+
search: 'Search',
|
|
103
|
+
searchPlaceholder: 'Search by name…',
|
|
104
|
+
tagsFilterLabel: 'Tag',
|
|
105
|
+
typeFilterLabel: 'Type',
|
|
106
|
+
tagSidebarLabel: 'Tag filters',
|
|
107
|
+
filesGridLabel: 'File grid',
|
|
108
|
+
filesListLabel: 'File list',
|
|
109
|
+
filesLabel: 'Files',
|
|
110
|
+
detailsLabel: 'File details',
|
|
111
|
+
emptyTitle: 'No files',
|
|
112
|
+
emptyDescription: 'Upload your first file to see it here.',
|
|
113
|
+
noResults: 'No results.',
|
|
114
|
+
bulkTag: 'Tag',
|
|
115
|
+
bulkSelectAll: 'Select page',
|
|
116
|
+
typeImage: 'Image',
|
|
117
|
+
typeVideo: 'Video',
|
|
118
|
+
typeAudio: 'Audio',
|
|
119
|
+
typePdf: 'PDF',
|
|
120
|
+
typeOther: 'Other'
|
|
55
121
|
}
|
|
56
122
|
};
|
|
57
123
|
|
|
@@ -62,32 +128,67 @@
|
|
|
62
128
|
selected?: string[] | string;
|
|
63
129
|
multiple?: boolean;
|
|
64
130
|
accept?: string;
|
|
131
|
+
showHeader?: boolean;
|
|
132
|
+
data?: MediaFile[];
|
|
133
|
+
state?: 'loading' | 'error' | 'ok';
|
|
65
134
|
};
|
|
66
135
|
|
|
67
|
-
let {
|
|
136
|
+
let {
|
|
137
|
+
selected = $bindable([]),
|
|
138
|
+
multiple = false,
|
|
139
|
+
accept,
|
|
140
|
+
showHeader = false,
|
|
141
|
+
data: injectedData,
|
|
142
|
+
state: injectedState
|
|
143
|
+
}: Props = $props();
|
|
144
|
+
|
|
145
|
+
const useInjectedData = $derived(injectedData !== undefined);
|
|
68
146
|
|
|
69
147
|
const PAGE_SIZE = 48;
|
|
148
|
+
const VIEW_MODE_STORAGE_KEY = 'includio-media-view';
|
|
149
|
+
|
|
150
|
+
function loadViewMode(): 'grid' | 'list' {
|
|
151
|
+
if (typeof window === 'undefined') return 'grid';
|
|
152
|
+
try {
|
|
153
|
+
const stored = localStorage.getItem(VIEW_MODE_STORAGE_KEY);
|
|
154
|
+
return stored === 'list' ? 'list' : 'grid';
|
|
155
|
+
} catch {
|
|
156
|
+
return 'grid';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
70
159
|
|
|
71
160
|
let currentFile: MediaFile | null = $state(null);
|
|
72
161
|
let activeTagFilter = $state<string | null>(null);
|
|
162
|
+
let activeTypeFilter = $state<string | null>(null);
|
|
73
163
|
let searchQuery = $state('');
|
|
74
164
|
let selectedFileIds = $state<string[]>([]);
|
|
75
165
|
let dropZoneRef = $state<HTMLElement | null>(null);
|
|
76
166
|
let selectionMode = $state(false);
|
|
77
167
|
let selectionAnnouncement = $state('');
|
|
78
|
-
let
|
|
79
|
-
let
|
|
168
|
+
let pagination = $state({ pageIndex: 0, pageSize: PAGE_SIZE });
|
|
169
|
+
let viewMode = $state<'grid' | 'list'>(loadViewMode());
|
|
170
|
+
|
|
171
|
+
$effect(() => {
|
|
172
|
+
if (typeof window === 'undefined') return;
|
|
173
|
+
try {
|
|
174
|
+
localStorage.setItem(VIEW_MODE_STORAGE_KEY, viewMode);
|
|
175
|
+
} catch {
|
|
176
|
+
// ignore
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const t = $derived(lang[interfaceLanguage.current]);
|
|
80
181
|
|
|
81
182
|
function exitSelectionMode() {
|
|
82
183
|
selectionMode = false;
|
|
83
184
|
selectedFileIds = [];
|
|
84
185
|
currentFile = null;
|
|
85
|
-
selectionAnnouncement =
|
|
186
|
+
selectionAnnouncement = t.selectModeOffAnnounce;
|
|
86
187
|
}
|
|
87
188
|
|
|
88
189
|
function enterSelectionMode() {
|
|
89
190
|
selectionMode = true;
|
|
90
|
-
selectionAnnouncement =
|
|
191
|
+
selectionAnnouncement = t.selectModeAnnounce;
|
|
91
192
|
}
|
|
92
193
|
|
|
93
194
|
const filterData = $derived({
|
|
@@ -97,81 +198,145 @@
|
|
|
97
198
|
search: searchQuery || undefined
|
|
98
199
|
});
|
|
99
200
|
|
|
100
|
-
// Reset page on filter/search change — track primitive sources only
|
|
101
201
|
$effect(() => {
|
|
102
202
|
void activeTagFilter;
|
|
203
|
+
void activeTypeFilter;
|
|
103
204
|
void searchQuery;
|
|
104
205
|
void accept;
|
|
105
206
|
untrack(() => {
|
|
106
|
-
|
|
107
|
-
loadedFiles = [];
|
|
207
|
+
pagination = { ...pagination, pageIndex: 0 };
|
|
108
208
|
});
|
|
109
209
|
});
|
|
110
210
|
|
|
111
211
|
const filesQuery = $derived(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
212
|
+
useInjectedData
|
|
213
|
+
? null
|
|
214
|
+
: remotes.getMediaFiles({
|
|
215
|
+
data: {
|
|
216
|
+
...filterData,
|
|
217
|
+
limit: pagination.pageSize,
|
|
218
|
+
offset: pagination.pageIndex * pagination.pageSize
|
|
219
|
+
}
|
|
220
|
+
})
|
|
119
221
|
);
|
|
120
222
|
|
|
121
223
|
const countQuery = $derived(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
224
|
+
useInjectedData
|
|
225
|
+
? null
|
|
226
|
+
: remotes.countMediaFiles({
|
|
227
|
+
data: {
|
|
228
|
+
tagIds: filterData.tagIds,
|
|
229
|
+
untagged: filterData.untagged,
|
|
230
|
+
mimeTypes: filterData.mimeTypes,
|
|
231
|
+
search: filterData.search
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const tagCountsQuery = $derived(useInjectedData ? null : remotes.getMediaTagsWithCounts());
|
|
237
|
+
const totalCountQuery = $derived(
|
|
238
|
+
useInjectedData
|
|
239
|
+
? null
|
|
240
|
+
: remotes.countMediaFiles({ data: { mimeTypes: accept?.split(',') } })
|
|
241
|
+
);
|
|
242
|
+
const untaggedCountQuery = $derived(
|
|
243
|
+
useInjectedData
|
|
244
|
+
? null
|
|
245
|
+
: remotes.countMediaFiles({ data: { mimeTypes: accept?.split(','), untagged: true } })
|
|
130
246
|
);
|
|
131
247
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
248
|
+
const fetchedFiles = $derived(
|
|
249
|
+
filesQuery?.ready && filesQuery.current ? (filesQuery.current as MediaFile[]) : []
|
|
250
|
+
);
|
|
135
251
|
|
|
136
|
-
|
|
137
|
-
$
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
252
|
+
const allInjected = $derived<MediaFile[]>(injectedData ?? []);
|
|
253
|
+
const filteredInjected = $derived.by(() => {
|
|
254
|
+
if (!useInjectedData) return [];
|
|
255
|
+
const q = searchQuery.trim().toLowerCase();
|
|
256
|
+
let list = allInjected;
|
|
257
|
+
if (q) list = list.filter((f) => f.name.toLowerCase().includes(q));
|
|
258
|
+
if (activeTypeFilter) list = list.filter((f) => f.type === activeTypeFilter);
|
|
259
|
+
if (activeTagFilter && activeTagFilter !== 'untagged') {
|
|
260
|
+
list = list.filter((f) => f.tags.some((tag) => tag.id === activeTagFilter));
|
|
261
|
+
} else if (activeTagFilter === 'untagged') {
|
|
262
|
+
list = list.filter((f) => f.tags.length === 0);
|
|
263
|
+
}
|
|
264
|
+
return list;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const visibleFiles = $derived.by<MediaFile[]>(() => {
|
|
268
|
+
if (useInjectedData) {
|
|
269
|
+
const start = pagination.pageIndex * pagination.pageSize;
|
|
270
|
+
return filteredInjected.slice(start, start + pagination.pageSize);
|
|
149
271
|
}
|
|
272
|
+
if (activeTypeFilter) return fetchedFiles.filter((f) => f.type === activeTypeFilter);
|
|
273
|
+
return fetchedFiles;
|
|
150
274
|
});
|
|
151
275
|
|
|
152
|
-
const totalCount = $derived(
|
|
153
|
-
|
|
276
|
+
const totalCount = $derived(
|
|
277
|
+
useInjectedData
|
|
278
|
+
? filteredInjected.length
|
|
279
|
+
: countQuery?.ready
|
|
280
|
+
? (countQuery.current as number)
|
|
281
|
+
: 0
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const pageCount = $derived(Math.max(1, Math.ceil(totalCount / pagination.pageSize)));
|
|
285
|
+
|
|
286
|
+
const isLoading = $derived(
|
|
287
|
+
useInjectedData
|
|
288
|
+
? injectedState === 'loading'
|
|
289
|
+
: !(filesQuery?.ready ?? false) && pagination.pageIndex === 0
|
|
290
|
+
);
|
|
291
|
+
const isError = $derived(
|
|
292
|
+
useInjectedData ? injectedState === 'error' : Boolean(filesQuery?.error)
|
|
293
|
+
);
|
|
294
|
+
const isPopulated = $derived(!isError && !isLoading && visibleFiles.length > 0);
|
|
295
|
+
|
|
296
|
+
const tagsQueryRaw = $derived(useInjectedData ? null : remotes.getMediaTags());
|
|
297
|
+
const allTags = $derived<MediaTag[]>(
|
|
298
|
+
useInjectedData
|
|
299
|
+
? Array.from(
|
|
300
|
+
new Map(
|
|
301
|
+
allInjected.flatMap((f) => f.tags).map((tag) => [tag.id, tag] as const)
|
|
302
|
+
).values()
|
|
303
|
+
)
|
|
304
|
+
: ((tagsQueryRaw?.current as MediaTag[] | undefined) ?? [])
|
|
305
|
+
);
|
|
154
306
|
|
|
155
|
-
|
|
307
|
+
const tagFilterOptions = $derived([
|
|
308
|
+
{ value: 'untagged', label: t.tagsFilterLabel + ': —' },
|
|
309
|
+
...allTags.map((tag) => ({ value: tag.id, label: tag.name }))
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
const typeFilterOptions = $derived([
|
|
313
|
+
{ value: 'image', label: t.typeImage },
|
|
314
|
+
{ value: 'video', label: t.typeVideo },
|
|
315
|
+
{ value: 'audio', label: t.typeAudio },
|
|
316
|
+
{ value: 'pdf', label: t.typePdf },
|
|
317
|
+
{ value: 'other', label: t.typeOther }
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
const dataFilters = $derived([
|
|
321
|
+
{ slug: 'tag', label: t.tagsFilterLabel, options: tagFilterOptions },
|
|
322
|
+
{ slug: 'type', label: t.typeFilterLabel, options: typeFilterOptions }
|
|
323
|
+
]);
|
|
324
|
+
const activeDataFilters = $derived({ tag: activeTagFilter, type: activeTypeFilter });
|
|
156
325
|
|
|
157
326
|
function handleFileSelect(file: MediaFile, event?: MouseEvent) {
|
|
158
327
|
const isModifier = event && (event.ctrlKey || event.metaKey);
|
|
159
|
-
|
|
160
328
|
if (isModifier) {
|
|
161
|
-
// Cmd/Ctrl+click: toggle selection, auto-enter selection mode
|
|
162
329
|
if (!selectionMode) enterSelectionMode();
|
|
163
330
|
selectedFileIds = selectedFileIds.includes(file.id)
|
|
164
331
|
? selectedFileIds.filter((id) => id !== file.id)
|
|
165
332
|
: [...selectedFileIds, file.id];
|
|
166
333
|
currentFile = file;
|
|
167
334
|
} else if (selectionMode) {
|
|
168
|
-
// Normal click in selection mode: toggle
|
|
169
335
|
selectedFileIds = selectedFileIds.includes(file.id)
|
|
170
336
|
? selectedFileIds.filter((id) => id !== file.id)
|
|
171
337
|
: [...selectedFileIds, file.id];
|
|
172
338
|
currentFile = file;
|
|
173
339
|
} else {
|
|
174
|
-
// Normal click without selection mode: open details
|
|
175
340
|
selectedFileIds = [];
|
|
176
341
|
currentFile = file;
|
|
177
342
|
if (multiple && Array.isArray(selected)) {
|
|
@@ -185,43 +350,41 @@
|
|
|
185
350
|
}
|
|
186
351
|
|
|
187
352
|
function handleRangeSelect(fileIds: string[]) {
|
|
188
|
-
// Merge range into selection (union)
|
|
189
353
|
const merged = new Set([...selectedFileIds, ...fileIds]);
|
|
190
354
|
selectedFileIds = [...merged];
|
|
191
355
|
}
|
|
192
356
|
|
|
193
357
|
function refreshAll() {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
untaggedCountQuery.refresh();
|
|
358
|
+
pagination = { ...pagination, pageIndex: 0 };
|
|
359
|
+
filesQuery?.refresh();
|
|
360
|
+
countQuery?.refresh();
|
|
361
|
+
tagCountsQuery?.refresh();
|
|
362
|
+
totalCountQuery?.refresh();
|
|
363
|
+
untaggedCountQuery?.refresh();
|
|
201
364
|
}
|
|
202
365
|
|
|
203
366
|
async function deleteFileCommand() {
|
|
204
|
-
if (currentFile) {
|
|
367
|
+
if (currentFile && !useInjectedData) {
|
|
205
368
|
await remotes.deleteMediaFile(currentFile.id);
|
|
206
|
-
toast.success(
|
|
369
|
+
toast.success(t.fileDeletedToast);
|
|
207
370
|
refreshAll();
|
|
208
371
|
currentFile = null;
|
|
209
372
|
}
|
|
210
373
|
}
|
|
211
374
|
|
|
212
375
|
async function onTagUpdate(tagIds: string[]) {
|
|
213
|
-
if (currentFile) {
|
|
376
|
+
if (currentFile && !useInjectedData) {
|
|
214
377
|
await remotes.setMediaFileTags({ fileId: currentFile.id, tagIds });
|
|
215
|
-
const tags =
|
|
378
|
+
const tags = tagsQueryRaw?.current as MediaTag[] | undefined;
|
|
216
379
|
if (tags) {
|
|
217
|
-
currentFile = { ...currentFile, tags: tags.filter((
|
|
380
|
+
currentFile = { ...currentFile, tags: tags.filter((tag) => tagIds.includes(tag.id)) };
|
|
218
381
|
}
|
|
219
382
|
refreshAll();
|
|
220
383
|
}
|
|
221
384
|
}
|
|
222
385
|
|
|
223
386
|
async function onNameUpdate(newName: string) {
|
|
224
|
-
if (currentFile) {
|
|
387
|
+
if (currentFile && !useInjectedData) {
|
|
225
388
|
const result = await remotes.renameMediaFile({ fileId: currentFile.id, newName });
|
|
226
389
|
if (result.success === true) {
|
|
227
390
|
currentFile = { ...currentFile, name: result.name, url: result.url };
|
|
@@ -233,37 +396,58 @@
|
|
|
233
396
|
}
|
|
234
397
|
|
|
235
398
|
async function handleCreateTag(name: string, color: string) {
|
|
399
|
+
if (useInjectedData) return;
|
|
236
400
|
await remotes.createMediaTag({ name, color });
|
|
237
|
-
await
|
|
238
|
-
await tagCountsQuery
|
|
401
|
+
await tagsQueryRaw?.refresh();
|
|
402
|
+
await tagCountsQuery?.refresh();
|
|
239
403
|
}
|
|
240
404
|
|
|
241
405
|
async function handleUpdateTag(id: string, name: string, color: string) {
|
|
406
|
+
if (useInjectedData) return;
|
|
242
407
|
await remotes.updateMediaTag({ id, name, color });
|
|
243
|
-
await
|
|
408
|
+
await tagsQueryRaw?.refresh();
|
|
244
409
|
refreshAll();
|
|
245
410
|
}
|
|
246
411
|
|
|
247
412
|
async function handleDeleteTag(id: string) {
|
|
413
|
+
if (useInjectedData) return;
|
|
248
414
|
await remotes.deleteMediaTag(id);
|
|
249
|
-
await
|
|
415
|
+
await tagsQueryRaw?.refresh();
|
|
250
416
|
refreshAll();
|
|
251
417
|
}
|
|
252
418
|
|
|
253
419
|
async function handleBulkTag(tagIds: string[]) {
|
|
420
|
+
if (useInjectedData) return;
|
|
254
421
|
const fileIds = selectedFileIds;
|
|
255
422
|
await remotes.bulkSetMediaFileTags({ fileIds, tagIds });
|
|
256
423
|
refreshAll();
|
|
257
424
|
}
|
|
258
425
|
|
|
259
426
|
async function handleBulkDelete() {
|
|
427
|
+
if (useInjectedData) return;
|
|
260
428
|
await remotes.bulkDeleteMediaFiles({ ids: selectedFileIds });
|
|
261
|
-
toast.success(
|
|
429
|
+
toast.success(t.bulkDeletedToast);
|
|
262
430
|
selectedFileIds = [];
|
|
263
431
|
currentFile = null;
|
|
264
432
|
refreshAll();
|
|
265
433
|
}
|
|
266
434
|
|
|
435
|
+
let bulkTagOpen = $state(false);
|
|
436
|
+
|
|
437
|
+
const isMobile = new IsMobile();
|
|
438
|
+
let tagSheetOpen = $state(false);
|
|
439
|
+
let detailsSheetOpen = $state(false);
|
|
440
|
+
|
|
441
|
+
// Auto-open the details Sheet on mobile when a file becomes selected (or
|
|
442
|
+
// bulk selection starts). Desktop keeps the inline aside, so this is a no-op
|
|
443
|
+
// when isMobile.current is false.
|
|
444
|
+
$effect(() => {
|
|
445
|
+
if (!isMobile.current) return;
|
|
446
|
+
if (currentFile || selectionMode) {
|
|
447
|
+
detailsSheetOpen = true;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
267
451
|
onMount(() => {
|
|
268
452
|
if (Array.isArray(selected)) {
|
|
269
453
|
selected = selected.filter((id) => !id.startsWith('/uploads'));
|
|
@@ -273,141 +457,336 @@
|
|
|
273
457
|
});
|
|
274
458
|
</script>
|
|
275
459
|
|
|
276
|
-
<div class="flex h-full overflow-hidden"
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
460
|
+
<div class="flex h-full flex-col overflow-hidden">
|
|
461
|
+
{#if showHeader}
|
|
462
|
+
<PageHeader title={t.title} count={!isLoading && !isError ? totalCount : undefined}>
|
|
463
|
+
{#snippet primaryActions()}
|
|
464
|
+
<FileUpload
|
|
465
|
+
onUpload={() => refreshAll()}
|
|
466
|
+
{accept}
|
|
467
|
+
bind:dropZoneRef
|
|
468
|
+
tagIds={activeTagFilter && activeTagFilter !== 'untagged' ? [activeTagFilter] : undefined}
|
|
469
|
+
/>
|
|
470
|
+
{/snippet}
|
|
471
|
+
</PageHeader>
|
|
472
|
+
{/if}
|
|
473
|
+
|
|
474
|
+
<div class="flex flex-1 overflow-hidden" bind:this={dropZoneRef}>
|
|
475
|
+
<aside
|
|
476
|
+
class="bg-card hidden w-48 min-w-48 shrink-0 flex-col overflow-hidden border-r md:flex"
|
|
477
|
+
aria-label={t.tagSidebarLabel}
|
|
478
|
+
>
|
|
479
|
+
{#if useInjectedData}
|
|
480
|
+
<div class="text-muted-foreground p-3 text-xs">
|
|
481
|
+
{allTags.length === 0 ? '—' : `${allTags.length} tag(s)`}
|
|
482
|
+
</div>
|
|
483
|
+
{:else if tagsQueryRaw?.ready && tagsQueryRaw.current}
|
|
484
|
+
<TagSidebar
|
|
485
|
+
tags={tagsQueryRaw.current}
|
|
486
|
+
tagCounts={tagCountsQuery?.ready ? tagCountsQuery.current : []}
|
|
487
|
+
totalCount={totalCountQuery?.ready ? totalCountQuery.current : 0}
|
|
488
|
+
untaggedCount={untaggedCountQuery?.ready ? untaggedCountQuery.current : 0}
|
|
489
|
+
activeFilter={activeTagFilter}
|
|
490
|
+
onFilterChange={(f) => (activeTagFilter = f)}
|
|
491
|
+
onCreateTag={handleCreateTag}
|
|
492
|
+
onUpdateTag={handleUpdateTag}
|
|
493
|
+
onDeleteTag={handleDeleteTag}
|
|
494
|
+
/>
|
|
495
|
+
{/if}
|
|
496
|
+
</aside>
|
|
497
|
+
|
|
498
|
+
<section class="flex flex-1 flex-col overflow-hidden" aria-label={t.filesLabel}>
|
|
499
|
+
<div class="bg-card shrink-0 border-b px-5 py-3">
|
|
500
|
+
<TableToolbar
|
|
501
|
+
{searchQuery}
|
|
502
|
+
searchPlaceholder={t.searchPlaceholder}
|
|
503
|
+
searchLabel={t.search}
|
|
504
|
+
onSearchChange={(q) => (searchQuery = q)}
|
|
505
|
+
hideStatusFilter
|
|
506
|
+
{viewMode}
|
|
507
|
+
onViewModeChange={(m) => (viewMode = m)}
|
|
508
|
+
{dataFilters}
|
|
509
|
+
{activeDataFilters}
|
|
510
|
+
onDataFilterChange={(slug, value) => {
|
|
511
|
+
if (slug === 'tag') activeTagFilter = value;
|
|
512
|
+
else if (slug === 'type') activeTypeFilter = value;
|
|
513
|
+
}}
|
|
514
|
+
>
|
|
515
|
+
{#snippet actions()}
|
|
516
|
+
<Sheet.Root bind:open={tagSheetOpen}>
|
|
517
|
+
<Sheet.Trigger>
|
|
518
|
+
{#snippet child({ props })}
|
|
519
|
+
<Button
|
|
520
|
+
{...props}
|
|
521
|
+
variant="outline"
|
|
522
|
+
size="sm"
|
|
523
|
+
class="gap-1.5 md:hidden"
|
|
524
|
+
aria-label={t.tagSidebarLabel}
|
|
525
|
+
>
|
|
526
|
+
<TagIcon class="size-3.5" />
|
|
527
|
+
{t.tagsFilterLabel}
|
|
528
|
+
</Button>
|
|
529
|
+
{/snippet}
|
|
530
|
+
</Sheet.Trigger>
|
|
531
|
+
<Sheet.Content side="left" class="w-72 max-w-[85vw] gap-0 p-0">
|
|
532
|
+
<Sheet.Header class="border-b p-3">
|
|
533
|
+
<Sheet.Title>{t.tagSidebarLabel}</Sheet.Title>
|
|
534
|
+
</Sheet.Header>
|
|
535
|
+
<div class="flex flex-1 flex-col overflow-hidden">
|
|
536
|
+
{#if useInjectedData}
|
|
537
|
+
<div class="text-muted-foreground p-3 text-xs">
|
|
538
|
+
{allTags.length === 0 ? '—' : `${allTags.length} tag(s)`}
|
|
539
|
+
</div>
|
|
540
|
+
{:else if tagsQueryRaw?.ready && tagsQueryRaw.current}
|
|
541
|
+
<TagSidebar
|
|
542
|
+
tags={tagsQueryRaw.current}
|
|
543
|
+
tagCounts={tagCountsQuery?.ready ? tagCountsQuery.current : []}
|
|
544
|
+
totalCount={totalCountQuery?.ready ? totalCountQuery.current : 0}
|
|
545
|
+
untaggedCount={untaggedCountQuery?.ready ? untaggedCountQuery.current : 0}
|
|
546
|
+
activeFilter={activeTagFilter}
|
|
547
|
+
onFilterChange={(f) => {
|
|
548
|
+
activeTagFilter = f;
|
|
549
|
+
tagSheetOpen = false;
|
|
550
|
+
}}
|
|
551
|
+
onCreateTag={handleCreateTag}
|
|
552
|
+
onUpdateTag={handleUpdateTag}
|
|
553
|
+
onDeleteTag={handleDeleteTag}
|
|
554
|
+
/>
|
|
555
|
+
{/if}
|
|
556
|
+
</div>
|
|
557
|
+
</Sheet.Content>
|
|
558
|
+
</Sheet.Root>
|
|
559
|
+
<MediaSort />
|
|
560
|
+
<Toggle
|
|
561
|
+
variant="outline"
|
|
562
|
+
size="sm"
|
|
563
|
+
pressed={selectionMode}
|
|
564
|
+
onPressedChange={(pressed) =>
|
|
565
|
+
pressed ? enterSelectionMode() : exitSelectionMode()}
|
|
566
|
+
class="gap-1.5 px-2.5 text-xs font-semibold whitespace-nowrap"
|
|
567
|
+
aria-label={selectionMode ? t.selectModeActive : t.selectMode}
|
|
568
|
+
>
|
|
569
|
+
<ListCheck class="h-4 w-4" />
|
|
570
|
+
{selectionMode ? t.selectModeActive : t.selectMode}
|
|
571
|
+
</Toggle>
|
|
572
|
+
{#if !showHeader}
|
|
573
|
+
<FileUpload
|
|
574
|
+
onUpload={() => refreshAll()}
|
|
575
|
+
{accept}
|
|
576
|
+
bind:dropZoneRef
|
|
577
|
+
tagIds={activeTagFilter && activeTagFilter !== 'untagged'
|
|
578
|
+
? [activeTagFilter]
|
|
579
|
+
: undefined}
|
|
580
|
+
/>
|
|
581
|
+
{/if}
|
|
582
|
+
{/snippet}
|
|
583
|
+
</TableToolbar>
|
|
584
|
+
</div>
|
|
319
585
|
|
|
320
|
-
|
|
321
|
-
<div class="sr-only" aria-live="polite" role="status">{selectionAnnouncement}</div>
|
|
586
|
+
<div class="sr-only" aria-live="polite" role="status">{selectionAnnouncement}</div>
|
|
322
587
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
{
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
588
|
+
<div
|
|
589
|
+
class="scrollbar-thin flex-1 overflow-y-auto px-5 pt-4 pb-24"
|
|
590
|
+
role={isPopulated ? 'listbox' : undefined}
|
|
591
|
+
aria-multiselectable={isPopulated ? 'true' : undefined}
|
|
592
|
+
aria-label={isPopulated ? (viewMode === 'list' ? t.filesListLabel : t.filesGridLabel) : undefined}
|
|
593
|
+
>
|
|
594
|
+
{#if isError}
|
|
595
|
+
<StateDisplay kind="error" />
|
|
596
|
+
{:else if isLoading}
|
|
597
|
+
{#if viewMode === 'list'}
|
|
598
|
+
<div class="flex flex-col gap-1.5">
|
|
599
|
+
{#each Array(8) as _}
|
|
600
|
+
<Skeleton class="block h-[56px] rounded-lg" />
|
|
601
|
+
{/each}
|
|
602
|
+
</div>
|
|
603
|
+
{:else}
|
|
604
|
+
<div class="grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-3">
|
|
605
|
+
{#each Array(8) as _}
|
|
606
|
+
<Skeleton class="block h-[168px] rounded-xl" />
|
|
607
|
+
{/each}
|
|
608
|
+
</div>
|
|
609
|
+
{/if}
|
|
610
|
+
{:else if visibleFiles.length === 0}
|
|
611
|
+
<StateDisplay kind="empty" title={t.emptyTitle} description={t.emptyDescription} />
|
|
330
612
|
{:else}
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
613
|
+
<div
|
|
614
|
+
class={viewMode === 'list'
|
|
615
|
+
? 'flex flex-col gap-1.5'
|
|
616
|
+
: 'grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-3'}
|
|
617
|
+
role="presentation"
|
|
618
|
+
>
|
|
619
|
+
<FilesList
|
|
620
|
+
files={visibleFiles}
|
|
621
|
+
selected={selectedFileIds.length > 0 ? selectedFileIds : (selected ?? '')}
|
|
622
|
+
onSelect={handleFileSelect}
|
|
623
|
+
onRangeSelect={handleRangeSelect}
|
|
624
|
+
{selectionMode}
|
|
625
|
+
{viewMode}
|
|
626
|
+
/>
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
{#if totalCount > pagination.pageSize}
|
|
630
|
+
<TablePagination
|
|
631
|
+
pageIndex={pagination.pageIndex}
|
|
632
|
+
pageSize={pagination.pageSize}
|
|
633
|
+
{pageCount}
|
|
634
|
+
totalItems={totalCount}
|
|
635
|
+
onPageChange={(p) => (pagination = { ...pagination, pageIndex: p })}
|
|
636
|
+
onPageSizeChange={(s) => (pagination = { pageIndex: 0, pageSize: s })}
|
|
637
|
+
/>
|
|
638
|
+
{/if}
|
|
639
|
+
|
|
640
|
+
{#if visibleFiles.some((f) => f.tags.length > 0)}
|
|
641
|
+
<div class="mt-6 flex flex-wrap items-center gap-1.5">
|
|
642
|
+
{#each Array.from(new Set(visibleFiles.flatMap((f) => f.tags.map((tag) => tag.id)))) as tagId (tagId)}
|
|
643
|
+
{@const tag = allTags.find((tagItem) => tagItem.id === tagId)}
|
|
644
|
+
{#if tag}
|
|
645
|
+
<Badge variant="secondary">{tag.name}</Badge>
|
|
646
|
+
{/if}
|
|
647
|
+
{/each}
|
|
648
|
+
</div>
|
|
649
|
+
{/if}
|
|
338
650
|
{/if}
|
|
339
651
|
</div>
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
</section>
|
|
354
|
-
|
|
355
|
-
<!-- Detail panel (384px) -->
|
|
356
|
-
<aside class="w-96 min-w-96 shrink-0 border-l bg-card flex flex-col overflow-hidden" aria-label="Szczegóły pliku">
|
|
357
|
-
{#if selectionMode}
|
|
358
|
-
{#if tagsQuery.ready && tagsQuery.current}
|
|
359
|
-
<MultiFileSummary
|
|
360
|
-
files={loadedFiles.filter((f) => selectedFileIds.includes(f.id))}
|
|
361
|
-
allTags={tagsQuery.current}
|
|
362
|
-
onBulkTag={handleBulkTag}
|
|
363
|
-
onBulkDelete={handleBulkDelete}
|
|
364
|
-
/>
|
|
365
|
-
{/if}
|
|
366
|
-
{:else if currentFile}
|
|
367
|
-
{#key currentFile}
|
|
368
|
-
{#if tagsQuery.ready && tagsQuery.current}
|
|
369
|
-
<FileDetails
|
|
370
|
-
file={currentFile}
|
|
371
|
-
allTags={tagsQuery.current}
|
|
372
|
-
onDelete={deleteFileCommand}
|
|
373
|
-
onReplace={(updated) => {
|
|
374
|
-
currentFile = updated;
|
|
375
|
-
refreshAll();
|
|
376
|
-
}}
|
|
377
|
-
{onTagUpdate}
|
|
378
|
-
{onNameUpdate}
|
|
652
|
+
</section>
|
|
653
|
+
|
|
654
|
+
<aside
|
|
655
|
+
class="bg-card hidden w-96 min-w-96 shrink-0 flex-col overflow-hidden border-l md:flex"
|
|
656
|
+
aria-label={t.detailsLabel}
|
|
657
|
+
>
|
|
658
|
+
{#if selectionMode}
|
|
659
|
+
{#if !useInjectedData && tagsQueryRaw?.ready && tagsQueryRaw.current}
|
|
660
|
+
<MultiFileSummary
|
|
661
|
+
files={visibleFiles.filter((f) => selectedFileIds.includes(f.id))}
|
|
662
|
+
allTags={tagsQueryRaw.current}
|
|
663
|
+
onBulkTag={handleBulkTag}
|
|
664
|
+
onBulkDelete={handleBulkDelete}
|
|
379
665
|
/>
|
|
380
666
|
{/if}
|
|
381
|
-
{
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
667
|
+
{:else if currentFile}
|
|
668
|
+
{#key currentFile}
|
|
669
|
+
{#if !useInjectedData && tagsQueryRaw?.ready && tagsQueryRaw.current}
|
|
670
|
+
<FileDetails
|
|
671
|
+
file={currentFile}
|
|
672
|
+
allTags={tagsQueryRaw.current}
|
|
673
|
+
onDelete={deleteFileCommand}
|
|
674
|
+
onReplace={(updated) => {
|
|
675
|
+
currentFile = updated;
|
|
676
|
+
refreshAll();
|
|
677
|
+
}}
|
|
678
|
+
{onTagUpdate}
|
|
679
|
+
{onNameUpdate}
|
|
680
|
+
/>
|
|
681
|
+
{/if}
|
|
682
|
+
{/key}
|
|
683
|
+
{:else}
|
|
684
|
+
<div class="flex flex-1 flex-col items-center justify-center px-5 py-10 text-center">
|
|
685
|
+
<div class="bg-muted mb-3.5 flex h-14 w-14 items-center justify-center rounded-xl">
|
|
686
|
+
<svg
|
|
687
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
688
|
+
class="text-text-light h-6 w-6"
|
|
689
|
+
fill="none"
|
|
690
|
+
viewBox="0 0 24 24"
|
|
691
|
+
stroke="currentColor"
|
|
692
|
+
>
|
|
693
|
+
<path
|
|
694
|
+
stroke-linecap="round"
|
|
695
|
+
stroke-linejoin="round"
|
|
696
|
+
stroke-width="1.5"
|
|
697
|
+
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"
|
|
698
|
+
/>
|
|
699
|
+
</svg>
|
|
700
|
+
</div>
|
|
701
|
+
<p class="text-foreground text-sm font-bold">{t.currentFilePlaceholder}</p>
|
|
702
|
+
<p class="text-muted-foreground mt-1 text-[13px] leading-relaxed">{t.currentFileDesc}</p>
|
|
389
703
|
</div>
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
{/if}
|
|
394
|
-
</aside>
|
|
704
|
+
{/if}
|
|
705
|
+
</aside>
|
|
706
|
+
</div>
|
|
395
707
|
</div>
|
|
396
708
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
{
|
|
709
|
+
<Sheet.Root bind:open={detailsSheetOpen}>
|
|
710
|
+
<Sheet.Content side="right" class="w-full max-w-md gap-0 p-0 md:hidden">
|
|
711
|
+
<Sheet.Header class="border-b p-3">
|
|
712
|
+
<Sheet.Title>{t.detailsLabel}</Sheet.Title>
|
|
713
|
+
</Sheet.Header>
|
|
714
|
+
<div class="flex flex-1 flex-col overflow-y-auto">
|
|
715
|
+
{#if selectionMode}
|
|
716
|
+
{#if !useInjectedData && tagsQueryRaw?.ready && tagsQueryRaw.current}
|
|
717
|
+
<MultiFileSummary
|
|
718
|
+
files={visibleFiles.filter((f) => selectedFileIds.includes(f.id))}
|
|
719
|
+
allTags={tagsQueryRaw.current}
|
|
720
|
+
onBulkTag={handleBulkTag}
|
|
721
|
+
onBulkDelete={handleBulkDelete}
|
|
722
|
+
/>
|
|
723
|
+
{/if}
|
|
724
|
+
{:else if currentFile}
|
|
725
|
+
{#key currentFile}
|
|
726
|
+
{#if !useInjectedData && tagsQueryRaw?.ready && tagsQueryRaw.current}
|
|
727
|
+
<FileDetails
|
|
728
|
+
file={currentFile}
|
|
729
|
+
allTags={tagsQueryRaw.current}
|
|
730
|
+
onDelete={() => {
|
|
731
|
+
deleteFileCommand();
|
|
732
|
+
detailsSheetOpen = false;
|
|
733
|
+
}}
|
|
734
|
+
onReplace={(updated) => {
|
|
735
|
+
currentFile = updated;
|
|
736
|
+
refreshAll();
|
|
737
|
+
}}
|
|
738
|
+
{onTagUpdate}
|
|
739
|
+
{onNameUpdate}
|
|
740
|
+
/>
|
|
741
|
+
{/if}
|
|
742
|
+
{/key}
|
|
743
|
+
{/if}
|
|
744
|
+
</div>
|
|
745
|
+
</Sheet.Content>
|
|
746
|
+
</Sheet.Root>
|
|
747
|
+
|
|
748
|
+
<BulkActionsBar
|
|
749
|
+
selectedCount={selectedFileIds.length}
|
|
750
|
+
onDelete={handleBulkDelete}
|
|
751
|
+
onClear={exitSelectionMode}
|
|
752
|
+
>
|
|
753
|
+
{#snippet actions()}
|
|
754
|
+
<Button
|
|
755
|
+
variant="ghost"
|
|
756
|
+
size="sm"
|
|
757
|
+
class="border border-white/20 text-white hover:bg-white/10 hover:text-white"
|
|
758
|
+
onclick={() => (selectedFileIds = visibleFiles.map((f) => f.id))}
|
|
759
|
+
>
|
|
760
|
+
{t.bulkSelectAll}
|
|
761
|
+
</Button>
|
|
762
|
+
<Popover.Root bind:open={bulkTagOpen}>
|
|
763
|
+
<Popover.Trigger>
|
|
764
|
+
{#snippet child({ props })}
|
|
765
|
+
<Button
|
|
766
|
+
{...props}
|
|
767
|
+
variant="ghost"
|
|
768
|
+
size="sm"
|
|
769
|
+
class="border border-white/20 text-white hover:bg-white/10 hover:text-white"
|
|
770
|
+
>
|
|
771
|
+
{t.bulkTag}
|
|
772
|
+
</Button>
|
|
773
|
+
{/snippet}
|
|
774
|
+
</Popover.Trigger>
|
|
775
|
+
<Popover.Content class="w-56 p-1" align="center" side="top">
|
|
776
|
+
{#each allTags as tag (tag.id)}
|
|
777
|
+
<button
|
|
778
|
+
type="button"
|
|
779
|
+
class="hover:bg-accent flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors"
|
|
780
|
+
onclick={() => {
|
|
781
|
+
handleBulkTag([tag.id]);
|
|
782
|
+
bulkTagOpen = false;
|
|
783
|
+
}}
|
|
784
|
+
>
|
|
785
|
+
<span class="size-2 rounded-full" style="background:{tag.color}"></span>
|
|
786
|
+
{tag.name}
|
|
787
|
+
</button>
|
|
788
|
+
{/each}
|
|
789
|
+
</Popover.Content>
|
|
790
|
+
</Popover.Root>
|
|
791
|
+
{/snippet}
|
|
792
|
+
</BulkActionsBar>
|