includio-cms 0.25.0 → 0.27.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 +97 -4
- package/CHANGELOG.md +118 -0
- package/DOCS.md +1 -1
- package/README.md +2 -0
- package/ROADMAP.md +14 -0
- package/dist/admin/auth-client.d.ts +42 -42
- 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/admin-layout.svelte +12 -2
- package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -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 +240 -130
- package/dist/admin/client/collection/data-table.svelte.d.ts +9 -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/refund-dialog.svelte +37 -1
- package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
- 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 +220 -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 +29 -22
- package/dist/admin/components/fields/file-field.svelte +344 -30
- package/dist/admin/components/fields/icon-field.svelte +86 -0
- package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
- package/dist/admin/components/fields/media-field.svelte +16 -2
- package/dist/admin/components/fields/object-field.svelte +27 -7
- 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 +219 -24
- 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 +173 -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/components/variant-form/VariantAttributeRenderer.svelte +109 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
- package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
- package/dist/admin/helpers/build-icon-set-map.js +16 -0
- package/dist/admin/helpers/index.d.ts +2 -0
- package/dist/admin/helpers/index.js +2 -0
- 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 +87 -48
- package/dist/admin/remote/shop.remote.js +70 -8
- 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/state/icon-sets.svelte.d.ts +9 -0
- package/dist/admin/state/icon-sets.svelte.js +20 -0
- 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/cli/scaffold/admin.js +2 -2
- 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 +1 -1
- 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/cms.d.ts +11 -2
- package/dist/core/cms.js +29 -0
- package/dist/core/fields/fieldSchemaToTs.d.ts +7 -0
- package/dist/core/fields/fieldSchemaToTs.js +241 -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.d.ts +2 -0
- package/dist/core/server/generator/fields.js +44 -18
- package/dist/core/server/generator/formFields.js +2 -1
- package/dist/core/server/generator/generator.js +6 -5
- package/dist/core/server/generator/utils.d.ts +1 -0
- package/dist/core/server/generator/utils.js +4 -0
- package/dist/db-postgres/schema/shop/order.d.ts +37 -1
- package/dist/db-postgres/schema/shop/order.js +3 -1
- package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
- package/dist/db-postgres/schema/shop/payment.js +4 -1
- package/dist/db-postgres/schema/shop/product.d.ts +20 -0
- package/dist/db-postgres/schema/shop/product.js +3 -1
- package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
- package/dist/db-postgres/schema/shop/productVariant.js +22 -0
- package/dist/shop/cart/types.d.ts +1 -0
- package/dist/shop/client/index.d.ts +54 -0
- package/dist/shop/client/index.js +5 -1
- package/dist/shop/expiry.d.ts +35 -0
- package/dist/shop/expiry.js +68 -0
- package/dist/shop/http/balance-handler.d.ts +20 -0
- package/dist/shop/http/balance-handler.js +91 -0
- package/dist/shop/http/cart-handler.js +19 -0
- package/dist/shop/http/checkout-handler.js +19 -1
- package/dist/shop/http/index.d.ts +2 -0
- package/dist/shop/http/index.js +2 -0
- package/dist/shop/http/upcoming-handler.d.ts +16 -0
- package/dist/shop/http/upcoming-handler.js +65 -0
- package/dist/shop/http/webhook-handler.js +46 -9
- package/dist/shop/index.d.ts +4 -1
- package/dist/shop/index.js +7 -1
- package/dist/shop/server/balance-payment.d.ts +40 -0
- package/dist/shop/server/balance-payment.js +140 -0
- package/dist/shop/server/cart-hydrate.js +2 -0
- package/dist/shop/server/init.d.ts +14 -0
- package/dist/shop/server/init.js +35 -0
- package/dist/shop/server/orders.d.ts +35 -0
- package/dist/shop/server/orders.js +155 -2
- package/dist/shop/server/payment-policy.d.ts +35 -0
- package/dist/shop/server/payment-policy.js +55 -0
- package/dist/shop/server/payments.d.ts +29 -0
- package/dist/shop/server/payments.js +64 -0
- package/dist/shop/server/populate.d.ts +1 -1
- package/dist/shop/server/refund.d.ts +17 -12
- package/dist/shop/server/refund.js +96 -13
- package/dist/shop/server/shop-data.d.ts +6 -1
- package/dist/shop/server/shop-data.js +44 -7
- package/dist/shop/template.d.ts +13 -0
- package/dist/shop/template.js +98 -0
- package/dist/shop/types.d.ts +142 -1
- package/dist/shop/variant-attributes.d.ts +28 -0
- package/dist/shop/variant-attributes.js +69 -0
- package/dist/sveltekit/server/handle.js +17 -0
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +2 -0
- package/dist/types/cms.d.ts +4 -3
- package/dist/types/cms.schema.d.ts +1 -1
- package/dist/types/cms.schema.js +13 -2
- package/dist/types/fields.d.ts +56 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -1
- package/dist/types/layout.d.ts +35 -2
- package/dist/types/plugins.d.ts +40 -0
- package/dist/types/plugins.js +4 -1
- package/dist/updates/0.26.0/index.d.ts +2 -0
- package/dist/updates/0.26.0/index.js +51 -0
- package/dist/updates/0.26.1/index.d.ts +2 -0
- package/dist/updates/0.26.1/index.js +19 -0
- package/dist/updates/0.27.0/index.d.ts +2 -0
- package/dist/updates/0.27.0/index.js +50 -0
- package/dist/updates/index.js +7 -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
|
@@ -1,18 +1,110 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import * as Dialog from '../../../components/ui/dialog/index.js';
|
|
3
3
|
import { onMount } from 'svelte';
|
|
4
|
-
import
|
|
5
|
-
import { buttonVariants } from '../../../components/ui/button/button.svelte';
|
|
6
|
-
import MediaLibrary from '../media/media-library.svelte';
|
|
4
|
+
import Button from '../../../components/ui/button/button.svelte';
|
|
7
5
|
import type { FileField } from '../../../types/fields.js';
|
|
8
|
-
import
|
|
6
|
+
import type { InterfaceLanguage } from '../../../types/languages.js';
|
|
7
|
+
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
8
|
+
import Plus from '@tabler/icons-svelte/icons/plus';
|
|
9
|
+
import X from '@tabler/icons-svelte/icons/x';
|
|
10
|
+
import ChevronLeft from '@tabler/icons-svelte/icons/chevron-left';
|
|
11
|
+
import ChevronRight from '@tabler/icons-svelte/icons/chevron-right';
|
|
12
|
+
import AlertTriangle from '@tabler/icons-svelte/icons/alert-triangle';
|
|
13
|
+
import FileMiniature from '../media/file/file-miniature.svelte';
|
|
14
|
+
import { getRemotes } from '../../../sveltekit/index.js';
|
|
15
|
+
import MediaSelector from '../media/media-selector.svelte';
|
|
16
|
+
import type { MediaFile } from '../../../types/media.js';
|
|
17
|
+
import { formatFileSize, getFileExtension } from '../../utils/fileDisplay.js';
|
|
18
|
+
|
|
19
|
+
const lang: Record<
|
|
20
|
+
InterfaceLanguage,
|
|
21
|
+
{
|
|
22
|
+
selectFile: string;
|
|
23
|
+
addFile: string;
|
|
24
|
+
remove: string;
|
|
25
|
+
change: string;
|
|
26
|
+
of: string;
|
|
27
|
+
missingFile: string;
|
|
28
|
+
removeAria: string;
|
|
29
|
+
}
|
|
30
|
+
> = {
|
|
31
|
+
pl: {
|
|
32
|
+
selectFile: 'Wybierz plik',
|
|
33
|
+
addFile: 'Dodaj plik',
|
|
34
|
+
remove: 'Usuń',
|
|
35
|
+
change: 'Zmień',
|
|
36
|
+
of: 'z',
|
|
37
|
+
missingFile: 'Brakujący plik',
|
|
38
|
+
removeAria: 'Usuń plik'
|
|
39
|
+
},
|
|
40
|
+
en: {
|
|
41
|
+
selectFile: 'Select file',
|
|
42
|
+
addFile: 'Add file',
|
|
43
|
+
remove: 'Remove',
|
|
44
|
+
change: 'Change',
|
|
45
|
+
of: 'of',
|
|
46
|
+
missingFile: 'Missing file',
|
|
47
|
+
removeAria: 'Remove file'
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const interfaceLanguage = useInterfaceLanguage();
|
|
52
|
+
const remotes = getRemotes();
|
|
53
|
+
|
|
54
|
+
let currentIndex = $state(0);
|
|
55
|
+
let dialogOpen = $state(false);
|
|
56
|
+
|
|
57
|
+
function openPicker() {
|
|
58
|
+
dialogOpen = true;
|
|
59
|
+
}
|
|
9
60
|
|
|
10
61
|
type Props = {
|
|
11
62
|
field: FileField;
|
|
12
63
|
value: string | string[] | undefined;
|
|
13
64
|
};
|
|
14
65
|
|
|
15
|
-
let { field, value = $bindable()
|
|
66
|
+
let { field, value = $bindable() }: Props = $props();
|
|
67
|
+
|
|
68
|
+
// Single-file state: fetch MediaFile via a plain fetch inside an $effect instead of
|
|
69
|
+
// `$derived(remotes.getFileById(value))`. Reason: wrapping the remote query in a
|
|
70
|
+
// $derived couples its lifecycle to the derivation's refcounting — rapid toggling
|
|
71
|
+
// of value ('' → id) around dialog unmount triggered "query instance is no longer
|
|
72
|
+
// active" errors. Wzorzec z media-field.
|
|
73
|
+
let singleFile = $state<MediaFile | null>(null);
|
|
74
|
+
let singleFileReady = $state(false);
|
|
75
|
+
$effect(() => {
|
|
76
|
+
const v = value;
|
|
77
|
+
if (typeof v !== 'string' || !v) {
|
|
78
|
+
singleFile = null;
|
|
79
|
+
singleFileReady = true;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
singleFileReady = false;
|
|
83
|
+
let cancelled = false;
|
|
84
|
+
(async () => {
|
|
85
|
+
try {
|
|
86
|
+
const data = await remotes.getFileById(v);
|
|
87
|
+
if (!cancelled) {
|
|
88
|
+
singleFile = data as MediaFile;
|
|
89
|
+
singleFileReady = true;
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
if (!cancelled) {
|
|
93
|
+
singleFile = null;
|
|
94
|
+
singleFileReady = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
})();
|
|
98
|
+
return () => {
|
|
99
|
+
cancelled = true;
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const mFilesQuery = $derived(
|
|
104
|
+
Array.isArray(value) && value.length > 0
|
|
105
|
+
? remotes.getMediaFiles({ data: { ids: value as string[] } })
|
|
106
|
+
: null
|
|
107
|
+
);
|
|
16
108
|
|
|
17
109
|
onMount(() => {
|
|
18
110
|
if (value === undefined) {
|
|
@@ -21,33 +113,255 @@
|
|
|
21
113
|
});
|
|
22
114
|
</script>
|
|
23
115
|
|
|
24
|
-
{#
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<Dialog.Content class="max-h-[80vh] w-[80vw] !max-w-none overflow-y-scroll">
|
|
30
|
-
<MediaLibrary bind:selected={value} multiple={field.multiple} accept={field.accept} />
|
|
31
|
-
</Dialog.Content>
|
|
32
|
-
</Dialog.Root>
|
|
116
|
+
{#snippet filePreview(file: MediaFile)}
|
|
117
|
+
<div class="aspect-square w-full overflow-hidden rounded-2xl border">
|
|
118
|
+
<FileMiniature {file} />
|
|
119
|
+
</div>
|
|
120
|
+
{/snippet}
|
|
33
121
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
122
|
+
{#snippet missingPlaceholder()}
|
|
123
|
+
<div
|
|
124
|
+
class="flex aspect-square w-full flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-warning/50 bg-warning/5 p-6"
|
|
125
|
+
>
|
|
126
|
+
<div class="rounded-full bg-warning/10 p-3">
|
|
127
|
+
<AlertTriangle class="h-6 w-6 text-warning" />
|
|
128
|
+
</div>
|
|
129
|
+
<span class="text-sm text-warning">{lang[interfaceLanguage.current].missingFile}</span>
|
|
130
|
+
</div>
|
|
131
|
+
{/snippet}
|
|
132
|
+
|
|
133
|
+
{#snippet listRow(file: MediaFile, onRemove: () => void)}
|
|
134
|
+
{@const ext = getFileExtension(file.url)}
|
|
135
|
+
<div class="flex items-center gap-3 rounded-lg border bg-card p-2 shadow-sm">
|
|
136
|
+
<div class="relative aspect-square w-10 shrink-0 overflow-hidden rounded">
|
|
137
|
+
<FileMiniature {file} mode="thumb" />
|
|
138
|
+
</div>
|
|
139
|
+
<div class="min-w-0 flex-1">
|
|
140
|
+
<div class="flex items-center gap-1.5 min-w-0">
|
|
141
|
+
<p class="truncate text-sm font-medium">{file.name}</p>
|
|
142
|
+
{#if ext}
|
|
143
|
+
<span class="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">{ext}</span>
|
|
42
144
|
{/if}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
145
|
+
</div>
|
|
146
|
+
<p class="text-xs text-muted-foreground">{formatFileSize(file.size)}</p>
|
|
147
|
+
</div>
|
|
148
|
+
{#if !field.required || (Array.isArray(value) && value.length > 1)}
|
|
149
|
+
<Button
|
|
150
|
+
variant="ghost"
|
|
151
|
+
size="icon"
|
|
152
|
+
class="h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive"
|
|
153
|
+
aria-label={lang[interfaceLanguage.current].removeAria}
|
|
154
|
+
onclick={onRemove}
|
|
155
|
+
>
|
|
156
|
+
<X class="h-4 w-4" />
|
|
157
|
+
</Button>
|
|
51
158
|
{/if}
|
|
52
159
|
</div>
|
|
160
|
+
{/snippet}
|
|
161
|
+
|
|
162
|
+
{#snippet listMissingRow(onRemove: () => void)}
|
|
163
|
+
<div class="flex items-center gap-3 rounded-lg border border-warning/40 bg-warning/5 p-2">
|
|
164
|
+
<div class="flex aspect-square w-10 shrink-0 items-center justify-center rounded bg-warning/10">
|
|
165
|
+
<AlertTriangle class="h-4 w-4 text-warning" />
|
|
166
|
+
</div>
|
|
167
|
+
<p class="min-w-0 flex-1 truncate text-sm text-warning">{lang[interfaceLanguage.current].missingFile}</p>
|
|
168
|
+
{#if !field.required || (Array.isArray(value) && value.length > 1)}
|
|
169
|
+
<Button
|
|
170
|
+
variant="ghost"
|
|
171
|
+
size="icon"
|
|
172
|
+
class="h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive"
|
|
173
|
+
aria-label={lang[interfaceLanguage.current].removeAria}
|
|
174
|
+
onclick={onRemove}
|
|
175
|
+
>
|
|
176
|
+
<X class="h-4 w-4" />
|
|
177
|
+
</Button>
|
|
178
|
+
{/if}
|
|
179
|
+
</div>
|
|
180
|
+
{/snippet}
|
|
181
|
+
|
|
182
|
+
{#snippet mediaActions(onRemove: () => void)}
|
|
183
|
+
<div class="flex items-center justify-between gap-2 mt-1.5">
|
|
184
|
+
<Button size="sm" variant="secondary" class="h-8" onclick={openPicker}>
|
|
185
|
+
<Plus class="h-4 w-4" />
|
|
186
|
+
{lang[interfaceLanguage.current].change}
|
|
187
|
+
</Button>
|
|
188
|
+
{#if !field.required}
|
|
189
|
+
<Button variant="destructive" size="sm" class="h-8" onclick={onRemove}>
|
|
190
|
+
<X class="h-4 w-4" />
|
|
191
|
+
{lang[interfaceLanguage.current].remove}
|
|
192
|
+
</Button>
|
|
193
|
+
{/if}
|
|
194
|
+
</div>
|
|
195
|
+
{/snippet}
|
|
196
|
+
|
|
197
|
+
{#if value !== undefined}
|
|
198
|
+
{#if field.display === 'list'}
|
|
199
|
+
<!-- Compact list variant (non-visual files: PDF, audio, raw) -->
|
|
200
|
+
<div class="space-y-2 max-w-md">
|
|
201
|
+
{#if typeof value === 'string' && value !== ''}
|
|
202
|
+
{#if !singleFileReady}
|
|
203
|
+
<div class="animate-pulse bg-muted h-14 rounded-lg"></div>
|
|
204
|
+
{:else if singleFile}
|
|
205
|
+
{@render listRow(singleFile, () => { value = ''; })}
|
|
206
|
+
{:else}
|
|
207
|
+
{@render listMissingRow(() => { value = ''; })}
|
|
208
|
+
{/if}
|
|
209
|
+
{:else if Array.isArray(value) && value.length > 0}
|
|
210
|
+
{#if !mFilesQuery?.ready}
|
|
211
|
+
<div class="animate-pulse bg-muted h-14 rounded-lg"></div>
|
|
212
|
+
{:else if mFilesQuery?.current}
|
|
213
|
+
{@const files = mFilesQuery.current}
|
|
214
|
+
{#each value as id, i (id + '__' + i)}
|
|
215
|
+
{@const file = files.find((f) => f.id === id)}
|
|
216
|
+
{#if file}
|
|
217
|
+
{@render listRow(file, () => {
|
|
218
|
+
if (Array.isArray(value)) {
|
|
219
|
+
value = value.filter((_, idx) => idx !== i);
|
|
220
|
+
}
|
|
221
|
+
})}
|
|
222
|
+
{:else}
|
|
223
|
+
{@render listMissingRow(() => {
|
|
224
|
+
if (Array.isArray(value)) {
|
|
225
|
+
value = value.filter((_, idx) => idx !== i);
|
|
226
|
+
}
|
|
227
|
+
})}
|
|
228
|
+
{/if}
|
|
229
|
+
{/each}
|
|
230
|
+
{/if}
|
|
231
|
+
{/if}
|
|
232
|
+
|
|
233
|
+
{#if field.multiple || !value || (typeof value === 'string' && value === '') || (Array.isArray(value) && value.length === 0)}
|
|
234
|
+
<Button variant="outline" size="sm" onclick={openPicker}>
|
|
235
|
+
<Plus class="h-4 w-4" />
|
|
236
|
+
{field.multiple && Array.isArray(value) && value.length > 0
|
|
237
|
+
? lang[interfaceLanguage.current].addFile
|
|
238
|
+
: lang[interfaceLanguage.current].selectFile}
|
|
239
|
+
</Button>
|
|
240
|
+
{:else}
|
|
241
|
+
<Button variant="outline" size="sm" onclick={openPicker}>
|
|
242
|
+
<Plus class="h-4 w-4" />
|
|
243
|
+
{lang[interfaceLanguage.current].change}
|
|
244
|
+
</Button>
|
|
245
|
+
{/if}
|
|
246
|
+
</div>
|
|
247
|
+
{:else if value && ((typeof value === 'string' && value !== '') || (Array.isArray(value) && value.length > 0))}
|
|
248
|
+
<div class="max-w-64">
|
|
249
|
+
{#if typeof value === 'string'}
|
|
250
|
+
{#if !singleFileReady}
|
|
251
|
+
<div class="animate-pulse bg-muted w-full aspect-square rounded-2xl"></div>
|
|
252
|
+
{:else if singleFile}
|
|
253
|
+
{@render filePreview(singleFile)}
|
|
254
|
+
{@render mediaActions(() => { value = ''; })}
|
|
255
|
+
{:else}
|
|
256
|
+
{@render missingPlaceholder()}
|
|
257
|
+
{@render mediaActions(() => { value = ''; })}
|
|
258
|
+
{/if}
|
|
259
|
+
{:else if Array.isArray(value) && value.length > 0}
|
|
260
|
+
{@const valueArr = value}
|
|
261
|
+
{#if !mFilesQuery?.ready}
|
|
262
|
+
<div class="animate-pulse bg-muted w-full aspect-square rounded-2xl"></div>
|
|
263
|
+
{:else if mFilesQuery?.current}
|
|
264
|
+
{@const file = mFilesQuery.current[currentIndex]}
|
|
265
|
+
{#if file}
|
|
266
|
+
<div class="relative">
|
|
267
|
+
{@render filePreview(file)}
|
|
268
|
+
|
|
269
|
+
{#if valueArr.length > 1}
|
|
270
|
+
<div class="absolute inset-x-0 top-1/2 flex -translate-y-1/2 justify-between px-1 pointer-events-none">
|
|
271
|
+
<button
|
|
272
|
+
type="button"
|
|
273
|
+
class="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full bg-white/80 backdrop-blur shadow-md transition hover:bg-white hover:scale-105 dark:bg-background/80 dark:hover:bg-background"
|
|
274
|
+
onclick={() => { currentIndex = currentIndex > 0 ? currentIndex - 1 : valueArr.length - 1; }}
|
|
275
|
+
>
|
|
276
|
+
<ChevronLeft class="h-5 w-5" />
|
|
277
|
+
</button>
|
|
278
|
+
<button
|
|
279
|
+
type="button"
|
|
280
|
+
class="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full bg-white/80 backdrop-blur shadow-md transition hover:bg-white hover:scale-105 dark:bg-background/80 dark:hover:bg-background"
|
|
281
|
+
onclick={() => { currentIndex = currentIndex < valueArr.length - 1 ? currentIndex + 1 : 0; }}
|
|
282
|
+
>
|
|
283
|
+
<ChevronRight class="h-5 w-5" />
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
286
|
+
<div class="absolute top-2 right-2 rounded-full bg-plum-darker/60 px-2 py-0.5 text-xs font-medium text-white backdrop-blur">
|
|
287
|
+
{currentIndex + 1} / {valueArr.length}
|
|
288
|
+
</div>
|
|
289
|
+
{/if}
|
|
290
|
+
</div>
|
|
291
|
+
{@render mediaActions(() => {
|
|
292
|
+
if (Array.isArray(value)) {
|
|
293
|
+
const next = value.filter((_, i) => i !== currentIndex);
|
|
294
|
+
value = field.multiple ? next : next[0] ?? '';
|
|
295
|
+
if (currentIndex >= next.length) currentIndex = Math.max(0, next.length - 1);
|
|
296
|
+
}
|
|
297
|
+
})}
|
|
298
|
+
{:else}
|
|
299
|
+
<div class="relative">
|
|
300
|
+
{@render missingPlaceholder()}
|
|
301
|
+
{#if valueArr.length > 1}
|
|
302
|
+
<div class="absolute inset-x-0 top-1/2 flex -translate-y-1/2 justify-between px-1 pointer-events-none">
|
|
303
|
+
<button
|
|
304
|
+
type="button"
|
|
305
|
+
class="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full bg-white/80 backdrop-blur shadow-md transition hover:bg-white hover:scale-105 dark:bg-background/80 dark:hover:bg-background"
|
|
306
|
+
onclick={() => { currentIndex = currentIndex > 0 ? currentIndex - 1 : valueArr.length - 1; }}
|
|
307
|
+
>
|
|
308
|
+
<ChevronLeft class="h-5 w-5" />
|
|
309
|
+
</button>
|
|
310
|
+
<button
|
|
311
|
+
type="button"
|
|
312
|
+
class="pointer-events-auto flex h-8 w-8 items-center justify-center rounded-full bg-white/80 backdrop-blur shadow-md transition hover:bg-white hover:scale-105 dark:bg-background/80 dark:hover:bg-background"
|
|
313
|
+
onclick={() => { currentIndex = currentIndex < valueArr.length - 1 ? currentIndex + 1 : 0; }}
|
|
314
|
+
>
|
|
315
|
+
<ChevronRight class="h-5 w-5" />
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="absolute top-2 right-2 rounded-full bg-plum-darker/60 px-2 py-0.5 text-xs font-medium text-white backdrop-blur">
|
|
319
|
+
{currentIndex + 1} / {valueArr.length}
|
|
320
|
+
</div>
|
|
321
|
+
{/if}
|
|
322
|
+
</div>
|
|
323
|
+
{@render mediaActions(() => {
|
|
324
|
+
if (Array.isArray(value)) {
|
|
325
|
+
const next = value.filter((_, i) => i !== currentIndex);
|
|
326
|
+
value = field.multiple ? next : next[0] ?? '';
|
|
327
|
+
if (currentIndex >= next.length) currentIndex = Math.max(0, next.length - 1);
|
|
328
|
+
}
|
|
329
|
+
})}
|
|
330
|
+
{/if}
|
|
331
|
+
{:else}
|
|
332
|
+
{@render missingPlaceholder()}
|
|
333
|
+
{@render mediaActions(() => { value = field.multiple ? [] : ''; })}
|
|
334
|
+
{/if}
|
|
335
|
+
{/if}
|
|
336
|
+
</div>
|
|
337
|
+
{:else}
|
|
338
|
+
<button
|
|
339
|
+
type="button"
|
|
340
|
+
class="flex aspect-square max-w-64 w-full flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-border bg-card/80 p-6 transition-colors hover:border-primary/50 hover:bg-card"
|
|
341
|
+
onclick={openPicker}
|
|
342
|
+
>
|
|
343
|
+
<div class="rounded-full bg-muted p-3">
|
|
344
|
+
<Plus class="h-6 w-6 text-muted-foreground" />
|
|
345
|
+
</div>
|
|
346
|
+
<span class="text-sm text-muted-foreground">{lang[interfaceLanguage.current].selectFile}</span>
|
|
347
|
+
</button>
|
|
348
|
+
{/if}
|
|
349
|
+
|
|
350
|
+
<Dialog.Root bind:open={dialogOpen}>
|
|
351
|
+
<Dialog.Content
|
|
352
|
+
fullscreenMobile
|
|
353
|
+
class="flex h-[85vh] w-full max-w-6xl! flex-col overflow-hidden p-0 sm:max-w-6xl!"
|
|
354
|
+
>
|
|
355
|
+
<Dialog.Title class="sr-only">{lang[interfaceLanguage.current].selectFile}</Dialog.Title>
|
|
356
|
+
<Dialog.Description class="sr-only">{lang[interfaceLanguage.current].selectFile}</Dialog.Description>
|
|
357
|
+
<MediaSelector
|
|
358
|
+
bind:selected={value}
|
|
359
|
+
multiple={field.multiple}
|
|
360
|
+
accept={field.accept}
|
|
361
|
+
maxSizeMB={field.maxSizeMB}
|
|
362
|
+
onConfirm={() => (dialogOpen = false)}
|
|
363
|
+
onCancel={() => (dialogOpen = false)}
|
|
364
|
+
/>
|
|
365
|
+
</Dialog.Content>
|
|
366
|
+
</Dialog.Root>
|
|
53
367
|
{/if}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { IconField } from '../../../types/fields.js';
|
|
3
|
+
import { resolveIconSet } from '../../state/icon-sets.svelte.js';
|
|
4
|
+
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
5
|
+
import { getLocalizedLabel } from '../../utils/collectionLabel.js';
|
|
6
|
+
import IconPickerDialog from './icon-picker-dialog.svelte';
|
|
7
|
+
import Plus from '@lucide/svelte/icons/plus';
|
|
8
|
+
import X from '@lucide/svelte/icons/x';
|
|
9
|
+
import HelpCircle from '@lucide/svelte/icons/help-circle';
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
field: IconField;
|
|
13
|
+
value: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let { field, value = $bindable(), ...props }: Props = $props();
|
|
17
|
+
|
|
18
|
+
const interfaceLanguage = useInterfaceLanguage();
|
|
19
|
+
const iconSet = $derived(resolveIconSet(field.set));
|
|
20
|
+
const iconDef = $derived(value && iconSet ? iconSet.icons[value] : undefined);
|
|
21
|
+
const isMissing = $derived(!!value && !!iconSet && !iconDef);
|
|
22
|
+
const label = $derived.by(() =>
|
|
23
|
+
iconDef ? getLocalizedLabel(iconDef.label, interfaceLanguage.current) : (value ?? '')
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
let dialogOpen = $state(false);
|
|
27
|
+
|
|
28
|
+
function clear() {
|
|
29
|
+
value = '';
|
|
30
|
+
}
|
|
31
|
+
function openDialog() {
|
|
32
|
+
dialogOpen = true;
|
|
33
|
+
}
|
|
34
|
+
function onConfirm(newValue: string) {
|
|
35
|
+
value = newValue;
|
|
36
|
+
dialogOpen = false;
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
{#if !iconSet}
|
|
41
|
+
<p class="text-sm text-destructive">
|
|
42
|
+
Brak zarejestrowanego zestawu ikon. Dodaj <code>IconSetPlugin</code> do
|
|
43
|
+
<code>plugins:</code> w cms.config i przekaż przez <code>buildIconSetMap(...)</code> do AdminLayout.
|
|
44
|
+
</p>
|
|
45
|
+
{:else}
|
|
46
|
+
<div class="relative inline-block">
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onclick={openDialog}
|
|
50
|
+
class="flex h-24 w-24 flex-col items-center justify-center gap-1.5 rounded-lg border bg-muted/30 transition-colors hover:border-primary hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
51
|
+
aria-label={value ? `Zmień ikonę: ${label}` : 'Wybierz ikonę'}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{#if !value}
|
|
55
|
+
<Plus class="h-7 w-7 text-muted-foreground" />
|
|
56
|
+
<span class="text-xs text-muted-foreground">Wybierz ikonę</span>
|
|
57
|
+
{:else if isMissing}
|
|
58
|
+
<HelpCircle class="h-7 w-7 text-warning" />
|
|
59
|
+
<span class="line-clamp-1 px-1 text-xs text-muted-foreground">{value}</span>
|
|
60
|
+
{:else if iconDef}
|
|
61
|
+
{@const Comp = iconDef.component}
|
|
62
|
+
<Comp size={32} />
|
|
63
|
+
<span class="line-clamp-1 px-1 text-xs">{label}</span>
|
|
64
|
+
{/if}
|
|
65
|
+
</button>
|
|
66
|
+
|
|
67
|
+
{#if value}
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
onclick={clear}
|
|
71
|
+
class="absolute -right-2 -top-2 rounded-full border bg-background p-1 text-muted-foreground shadow-sm transition-colors hover:text-destructive"
|
|
72
|
+
aria-label="Wyczyść ikonę"
|
|
73
|
+
>
|
|
74
|
+
<X class="h-3 w-3" />
|
|
75
|
+
</button>
|
|
76
|
+
{/if}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{#if isMissing}
|
|
80
|
+
<p class="mt-2 text-sm text-warning">
|
|
81
|
+
Ikona <code>{value}</code> nie jest już dostępna w bibliotece. Wybierz inną lub usuń.
|
|
82
|
+
</p>
|
|
83
|
+
{/if}
|
|
84
|
+
|
|
85
|
+
<IconPickerDialog bind:open={dialogOpen} {iconSet} initialValue={value} {onConfirm} />
|
|
86
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IconField } from '../../../types/fields.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
field: IconField;
|
|
4
|
+
value: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
declare const IconField: import("svelte").Component<Props, {}, "value">;
|
|
7
|
+
type IconField = ReturnType<typeof IconField>;
|
|
8
|
+
export default IconField;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Dialog from '../../../components/ui/dialog/index.js';
|
|
3
|
+
import { Input } from '../../../components/ui/input/index.js';
|
|
4
|
+
import { Button } from '../../../components/ui/button/index.js';
|
|
5
|
+
import { LiveRegion } from '../../../components/ui/live-region/index.js';
|
|
6
|
+
import type { IconSetPlugin } from '../../../types/plugins.js';
|
|
7
|
+
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
8
|
+
import { getLocalizedLabel } from '../../utils/collectionLabel.js';
|
|
9
|
+
import type { InterfaceLanguage } from '../../../types/languages.js';
|
|
10
|
+
import type { Component } from 'svelte';
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
open: boolean;
|
|
14
|
+
iconSet: IconSetPlugin;
|
|
15
|
+
initialValue: string | undefined;
|
|
16
|
+
onConfirm: (value: string) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let { open = $bindable(), iconSet, initialValue, onConfirm }: Props = $props();
|
|
20
|
+
|
|
21
|
+
const lang: Record<
|
|
22
|
+
InterfaceLanguage,
|
|
23
|
+
{
|
|
24
|
+
title: string;
|
|
25
|
+
search: string;
|
|
26
|
+
cancel: string;
|
|
27
|
+
confirm: string;
|
|
28
|
+
noResults: string;
|
|
29
|
+
resultsCount: (n: number) => string;
|
|
30
|
+
close: string;
|
|
31
|
+
}
|
|
32
|
+
> = {
|
|
33
|
+
en: {
|
|
34
|
+
title: 'Select icon',
|
|
35
|
+
search: 'Search icons...',
|
|
36
|
+
cancel: 'Cancel',
|
|
37
|
+
confirm: 'OK',
|
|
38
|
+
noResults: 'No icons match',
|
|
39
|
+
resultsCount: (n) => `${n} icons`,
|
|
40
|
+
close: 'Close'
|
|
41
|
+
},
|
|
42
|
+
pl: {
|
|
43
|
+
title: 'Wybierz ikonę',
|
|
44
|
+
search: 'Szukaj ikon...',
|
|
45
|
+
cancel: 'Anuluj',
|
|
46
|
+
confirm: 'OK',
|
|
47
|
+
noResults: 'Brak pasujących ikon',
|
|
48
|
+
resultsCount: (n) => `Wyniki: ${n}`,
|
|
49
|
+
close: 'Zamknij'
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const interfaceLanguage = useInterfaceLanguage();
|
|
54
|
+
const t = $derived(lang[interfaceLanguage.current]);
|
|
55
|
+
|
|
56
|
+
let searchValue = $state('');
|
|
57
|
+
let selected = $state<string>('');
|
|
58
|
+
let wasOpen = false;
|
|
59
|
+
|
|
60
|
+
// Re-sync gdy dialog się otwiera ze świeżą wartością początkową.
|
|
61
|
+
// Wzorzec "edge-trigger": resetujemy TYLKO na przejściu closed→open,
|
|
62
|
+
// żeby kliknięcia użytkownika w grid nie były nadpisywane przez re-run efektu.
|
|
63
|
+
$effect(() => {
|
|
64
|
+
if (open && !wasOpen) {
|
|
65
|
+
selected = initialValue ?? '';
|
|
66
|
+
searchValue = '';
|
|
67
|
+
}
|
|
68
|
+
wasOpen = open;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
type IconEntry = {
|
|
72
|
+
key: string;
|
|
73
|
+
label: string;
|
|
74
|
+
keywords: string[];
|
|
75
|
+
component: Component;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const allEntries = $derived<IconEntry[]>(
|
|
79
|
+
Object.entries(iconSet.icons).map(([key, def]) => ({
|
|
80
|
+
key,
|
|
81
|
+
label: getLocalizedLabel(def.label, interfaceLanguage.current),
|
|
82
|
+
keywords: def.keywords ?? [],
|
|
83
|
+
component: def.component
|
|
84
|
+
}))
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const filtered = $derived.by(() => {
|
|
88
|
+
const q = searchValue.trim().toLowerCase();
|
|
89
|
+
if (!q) return allEntries;
|
|
90
|
+
return allEntries.filter(
|
|
91
|
+
(e) =>
|
|
92
|
+
e.key.toLowerCase().includes(q) ||
|
|
93
|
+
e.label.toLowerCase().includes(q) ||
|
|
94
|
+
e.keywords.some((k) => k.toLowerCase().includes(q))
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
function confirm() {
|
|
99
|
+
if (selected) onConfirm(selected);
|
|
100
|
+
}
|
|
101
|
+
function cancel() {
|
|
102
|
+
open = false;
|
|
103
|
+
}
|
|
104
|
+
function select(key: string) {
|
|
105
|
+
selected = key;
|
|
106
|
+
}
|
|
107
|
+
function handleOpenChange(next: boolean) {
|
|
108
|
+
open = next;
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
|
|
113
|
+
<Dialog.Content class="flex max-h-[85vh] flex-col gap-0 p-0 sm:max-w-2xl">
|
|
114
|
+
<Dialog.Header class="border-b px-4 py-3">
|
|
115
|
+
<Dialog.Title class="text-base font-semibold">{t.title}</Dialog.Title>
|
|
116
|
+
<Dialog.Description class="sr-only">{t.title}</Dialog.Description>
|
|
117
|
+
</Dialog.Header>
|
|
118
|
+
|
|
119
|
+
<div class="border-b px-4 py-2">
|
|
120
|
+
<Input
|
|
121
|
+
type="search"
|
|
122
|
+
placeholder={t.search}
|
|
123
|
+
bind:value={searchValue}
|
|
124
|
+
autofocus
|
|
125
|
+
class="h-9"
|
|
126
|
+
aria-label={t.search}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="flex-1 overflow-y-auto p-4">
|
|
131
|
+
{#if filtered.length === 0}
|
|
132
|
+
<p class="text-center text-sm text-muted-foreground py-8">{t.noResults}</p>
|
|
133
|
+
{:else}
|
|
134
|
+
<div
|
|
135
|
+
role="listbox"
|
|
136
|
+
aria-label={t.title}
|
|
137
|
+
class="grid grid-cols-3 gap-2 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6"
|
|
138
|
+
>
|
|
139
|
+
{#each filtered as entry (entry.key)}
|
|
140
|
+
{@const isSelected = selected === entry.key}
|
|
141
|
+
{@const Comp = entry.component}
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
role="option"
|
|
145
|
+
aria-selected={isSelected}
|
|
146
|
+
onclick={() => select(entry.key)}
|
|
147
|
+
ondblclick={() => {
|
|
148
|
+
select(entry.key);
|
|
149
|
+
confirm();
|
|
150
|
+
}}
|
|
151
|
+
class={[
|
|
152
|
+
'flex h-20 flex-col items-center justify-center gap-1 rounded-md border p-1.5 transition-colors',
|
|
153
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
154
|
+
isSelected
|
|
155
|
+
? 'border-primary bg-primary/10'
|
|
156
|
+
: 'border-muted bg-background hover:bg-muted/50'
|
|
157
|
+
].join(' ')}
|
|
158
|
+
>
|
|
159
|
+
<Comp size={24} />
|
|
160
|
+
<span class="line-clamp-1 w-full text-center text-[10px]">{entry.label}</span>
|
|
161
|
+
</button>
|
|
162
|
+
{/each}
|
|
163
|
+
</div>
|
|
164
|
+
{/if}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<Dialog.Footer class="border-t px-4 py-3 sm:justify-end">
|
|
168
|
+
<Button type="button" variant="outline" onclick={cancel}>{t.cancel}</Button>
|
|
169
|
+
<Button type="button" onclick={confirm} disabled={!selected}>{t.confirm}</Button>
|
|
170
|
+
</Dialog.Footer>
|
|
171
|
+
|
|
172
|
+
<LiveRegion message={t.resultsCount(filtered.length)} />
|
|
173
|
+
</Dialog.Content>
|
|
174
|
+
</Dialog.Root>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IconSetPlugin } from '../../../types/plugins.js';
|
|
2
|
+
import type { Component } from 'svelte';
|
|
3
|
+
type Props = {
|
|
4
|
+
open: boolean;
|
|
5
|
+
iconSet: IconSetPlugin;
|
|
6
|
+
initialValue: string | undefined;
|
|
7
|
+
onConfirm: (value: string) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const IconPickerDialog: Component<Props, {}, "open">;
|
|
10
|
+
type IconPickerDialog = ReturnType<typeof IconPickerDialog>;
|
|
11
|
+
export default IconPickerDialog;
|