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
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { buildSeoZodShape } from './seoFieldDescriptor.js';
|
|
3
|
+
import { isValidUrlValue } from './urlUtils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Localized generic validation messages. `generateZodSchemaFromField` gets the
|
|
6
|
+
* preferred language via `options.messageLanguage` (the interface language on
|
|
7
|
+
* the client); server-side / seed schemas leave it undefined → Polish.
|
|
8
|
+
*/
|
|
9
|
+
function validationMessages(messageLanguage) {
|
|
10
|
+
const en = messageLanguage === 'en';
|
|
11
|
+
return {
|
|
12
|
+
required: en ? 'This field is required' : 'Pole wymagane',
|
|
13
|
+
minChars: (n) => (en ? `At least ${n} characters` : `Minimum ${n} znaków`),
|
|
14
|
+
maxChars: (n) => (en ? `At most ${n} characters` : `Maksimum ${n} znaków`),
|
|
15
|
+
invalidFormat: en ? 'Invalid format' : 'Nieprawidłowy format',
|
|
16
|
+
numberRequired: en ? 'A number is required' : 'Wymagana liczba',
|
|
17
|
+
minValue: (n) => `Minimum: ${n}`,
|
|
18
|
+
maxValue: (n) => (en ? `Maximum: ${n}` : `Maksimum: ${n}`),
|
|
19
|
+
exactItems: (n) => en ? `Exactly ${n} items required` : `Wymagane dokładnie ${n} elementów`,
|
|
20
|
+
minItems: (n) => (en ? `At least ${n} items` : `Minimum ${n} elementów`),
|
|
21
|
+
maxItems: (n) => (en ? `At most ${n} items` : `Maksimum ${n} elementów`),
|
|
22
|
+
invalidUrl: en ? 'Invalid URL' : 'Nieprawidłowy adres URL',
|
|
23
|
+
unknownBlock: (t) => (en ? `Unknown block type: ${t}` : `Nieznany typ bloku: ${t}`),
|
|
24
|
+
invalidBlock: en ? 'Invalid block data' : 'Nieprawidłowe dane bloku'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
2
27
|
let _customFieldsOverride = null;
|
|
3
28
|
/**
|
|
4
29
|
* Set custom fields map for client-side use (where getCMS() is unavailable).
|
|
@@ -27,20 +52,39 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
27
52
|
parentRequired: true
|
|
28
53
|
}) {
|
|
29
54
|
const localized = options.localized !== false;
|
|
55
|
+
const msg = validationMessages(options.messageLanguage);
|
|
30
56
|
switch (field.type) {
|
|
31
57
|
case 'text': {
|
|
32
58
|
let schema = z.string();
|
|
33
59
|
if (field.required && options.parentRequired && field.minLength === undefined)
|
|
34
|
-
schema = schema.min(1, { message:
|
|
60
|
+
schema = schema.min(1, { message: msg.required });
|
|
35
61
|
if (field.minLength !== undefined)
|
|
36
|
-
schema = schema.min(field.minLength, { message:
|
|
62
|
+
schema = schema.min(field.minLength, { message: msg.minChars(field.minLength) });
|
|
37
63
|
if (field.maxLength !== undefined)
|
|
38
|
-
schema = schema.max(field.maxLength, { message:
|
|
39
|
-
if (field.pattern !== undefined)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
schema = schema.max(field.maxLength, { message: msg.maxChars(field.maxLength) });
|
|
65
|
+
if (field.pattern !== undefined) {
|
|
66
|
+
// Localized = `Partial<Record<lang, string>> | string` (union),
|
|
67
|
+
// so TS can't index it by a free `string`. Resolve via a local
|
|
68
|
+
// view to pick the preferred-language message, with a fallback
|
|
69
|
+
// to the first available entry.
|
|
70
|
+
const localized = field.patternError;
|
|
71
|
+
const patternMsg = (typeof localized === 'object' &&
|
|
72
|
+
localized &&
|
|
73
|
+
options.messageLanguage &&
|
|
74
|
+
localized[options.messageLanguage]) ||
|
|
75
|
+
(typeof localized === 'object' && localized && Object.values(localized)[0]) ||
|
|
76
|
+
(typeof localized === 'string' && localized) ||
|
|
77
|
+
msg.invalidFormat;
|
|
78
|
+
schema = schema.regex(new RegExp(field.pattern), { message: patternMsg });
|
|
79
|
+
}
|
|
80
|
+
if (!localized) {
|
|
81
|
+
return field.required ? schema : schema.optional().default('');
|
|
82
|
+
}
|
|
83
|
+
const localizedSchema = z.object(Object.fromEntries(languages.map((lang) => [lang, schema])));
|
|
84
|
+
if (field.required)
|
|
85
|
+
return localizedSchema;
|
|
86
|
+
const emptyLang = Object.fromEntries(languages.map((lang) => [lang, '']));
|
|
87
|
+
return localizedSchema.optional().default(emptyLang);
|
|
44
88
|
}
|
|
45
89
|
case 'content': {
|
|
46
90
|
const markSchema = z.object({
|
|
@@ -64,7 +108,8 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
64
108
|
for (const blockDef of field.inlineBlocks) {
|
|
65
109
|
blockSchemas.set(blockDef.slug, generateZodSchemaFromFields(blockDef.fields, languages, {
|
|
66
110
|
parentRequired: false,
|
|
67
|
-
localized: false
|
|
111
|
+
localized: false,
|
|
112
|
+
messageLanguage: options.messageLanguage
|
|
68
113
|
}));
|
|
69
114
|
}
|
|
70
115
|
docSchema = z
|
|
@@ -83,7 +128,7 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
83
128
|
if (!schema) {
|
|
84
129
|
ctx.addIssue({
|
|
85
130
|
code: z.ZodIssueCode.custom,
|
|
86
|
-
message:
|
|
131
|
+
message: msg.unknownBlock(blockType),
|
|
87
132
|
path: ['content', i, 'attrs', 'blockType']
|
|
88
133
|
});
|
|
89
134
|
continue;
|
|
@@ -96,7 +141,7 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
96
141
|
catch {
|
|
97
142
|
ctx.addIssue({
|
|
98
143
|
code: z.ZodIssueCode.custom,
|
|
99
|
-
message:
|
|
144
|
+
message: msg.invalidBlock,
|
|
100
145
|
path: ['content', i, 'attrs', 'blockData']
|
|
101
146
|
});
|
|
102
147
|
continue;
|
|
@@ -119,59 +164,81 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
119
164
|
return z.object(Object.fromEntries(languages.map((lang) => [lang, docSchema])));
|
|
120
165
|
}
|
|
121
166
|
case 'number': {
|
|
122
|
-
let schema = z.number({ message:
|
|
167
|
+
let schema = z.number({ message: msg.numberRequired });
|
|
123
168
|
if (field.min !== undefined)
|
|
124
|
-
schema = schema.min(field.min, { message:
|
|
169
|
+
schema = schema.min(field.min, { message: msg.minValue(field.min) });
|
|
125
170
|
if (field.max !== undefined)
|
|
126
|
-
schema = schema.max(field.max, { message:
|
|
127
|
-
return schema;
|
|
171
|
+
schema = schema.max(field.max, { message: msg.maxValue(field.max) });
|
|
172
|
+
return field.required ? schema : schema.optional();
|
|
128
173
|
}
|
|
129
174
|
case 'boolean':
|
|
130
|
-
|
|
175
|
+
// A non-required toggle absent from the data must not fail
|
|
176
|
+
// validation — treat missing as `false` (matches the UI toggle's
|
|
177
|
+
// off state). Same class of bug as non-required select/radio.
|
|
178
|
+
return field.required ? z.boolean() : z.boolean().optional().default(false);
|
|
131
179
|
case 'date':
|
|
132
|
-
case 'datetime':
|
|
133
|
-
|
|
180
|
+
case 'datetime': {
|
|
181
|
+
const dt = z.string().datetime();
|
|
182
|
+
if (field.required)
|
|
183
|
+
return dt;
|
|
184
|
+
// Empty string is the editor's "no value" state; accept it and
|
|
185
|
+
// default to '' so missing fields don't fail validation.
|
|
186
|
+
return z.union([z.literal(''), dt]).optional().default('');
|
|
187
|
+
}
|
|
134
188
|
case 'file':
|
|
135
189
|
case 'media': {
|
|
136
190
|
const singleSchema = field.required
|
|
137
|
-
? z.string().uuid({ message:
|
|
138
|
-
: z.string();
|
|
139
|
-
return field.multiple ? z.array(
|
|
191
|
+
? z.string().uuid({ message: msg.required })
|
|
192
|
+
: z.string().optional().default('');
|
|
193
|
+
return field.multiple ? z.array(z.string()).default([]) : singleSchema;
|
|
140
194
|
}
|
|
141
195
|
case 'select':
|
|
142
196
|
case 'radio': {
|
|
143
197
|
const enumValues = field.options.map((o) => o.value);
|
|
144
198
|
const enumObject = z.enum(enumValues);
|
|
145
199
|
if (field.type === 'select' && field.multiple) {
|
|
146
|
-
return z.array(enumObject);
|
|
200
|
+
return z.array(enumObject).default([]);
|
|
147
201
|
}
|
|
148
|
-
|
|
202
|
+
// Non-required: union of enum + '' sentinel. Superforms requires a
|
|
203
|
+
// multi-type union to carry a default, so anchor it on the '' sentinel.
|
|
204
|
+
return field.required ? enumObject : enumObject.or(z.literal('')).default('');
|
|
149
205
|
}
|
|
150
206
|
case 'checkboxes': {
|
|
151
|
-
const
|
|
152
|
-
return z.array(z.enum(
|
|
207
|
+
const enumValues = field.options.map((o) => o.value);
|
|
208
|
+
return z.array(z.enum(enumValues)).default([]);
|
|
153
209
|
}
|
|
154
210
|
case 'relation': {
|
|
155
|
-
|
|
211
|
+
if (field.multiple) {
|
|
212
|
+
return z.array(z.string()).default([]);
|
|
213
|
+
}
|
|
156
214
|
if (field.required) {
|
|
157
|
-
|
|
215
|
+
return z.string().uuid({ message: msg.required });
|
|
158
216
|
}
|
|
159
|
-
return
|
|
217
|
+
return z.string().optional().default('');
|
|
160
218
|
}
|
|
161
219
|
case 'object': {
|
|
220
|
+
// Children's `required` is enforced when the object itself is required OR
|
|
221
|
+
// a parent context (e.g. a blocks item) says it must be validated.
|
|
162
222
|
const data = generateZodSchemaFromFields(field.fields, languages, {
|
|
163
|
-
parentRequired: field.required,
|
|
164
|
-
localized: options.localized
|
|
223
|
+
parentRequired: !!field.required || options.parentRequired === true,
|
|
224
|
+
localized: options.localized,
|
|
225
|
+
messageLanguage: options.messageLanguage
|
|
165
226
|
});
|
|
227
|
+
// An optional object stays `.partial()` (a non-required field may be
|
|
228
|
+
// absent); a present field that is `required` still has to satisfy its
|
|
229
|
+
// `.min(1)`/etc. constraint.
|
|
166
230
|
const base = !field.required ? data.partial() : data;
|
|
167
231
|
return base.extend({
|
|
168
232
|
_slug: z.literal(field.slug)
|
|
169
233
|
});
|
|
170
234
|
}
|
|
171
235
|
case 'blocks': {
|
|
236
|
+
// Each block item, once added, should have its own required fields filled
|
|
237
|
+
// (validation runs only on publish, drafts are unvalidated).
|
|
172
238
|
const schemas = field.of.map((f) => generateZodSchemaFromField(f, languages, {
|
|
173
|
-
parentRequired:
|
|
174
|
-
localized: options.localized
|
|
239
|
+
parentRequired: true,
|
|
240
|
+
localized: options.localized,
|
|
241
|
+
messageLanguage: options.messageLanguage
|
|
175
242
|
}));
|
|
176
243
|
const itemSchema = schemas.length > 1
|
|
177
244
|
? z.discriminatedUnion('_slug', schemas)
|
|
@@ -182,17 +249,17 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
182
249
|
field.minItems === field.maxItems &&
|
|
183
250
|
field.minItems > 0) {
|
|
184
251
|
schema = schema.length(field.minItems, {
|
|
185
|
-
message:
|
|
252
|
+
message: msg.exactItems(field.minItems)
|
|
186
253
|
});
|
|
187
254
|
}
|
|
188
255
|
else {
|
|
189
256
|
if (field.minItems !== undefined)
|
|
190
257
|
schema = schema.min(field.minItems, {
|
|
191
|
-
message:
|
|
258
|
+
message: msg.minItems(field.minItems)
|
|
192
259
|
});
|
|
193
260
|
if (field.maxItems !== undefined)
|
|
194
261
|
schema = schema.max(field.maxItems, {
|
|
195
|
-
message:
|
|
262
|
+
message: msg.maxItems(field.maxItems)
|
|
196
263
|
});
|
|
197
264
|
}
|
|
198
265
|
return schema.default([]); // Default to empty array if not required
|
|
@@ -208,53 +275,83 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
208
275
|
case 'number':
|
|
209
276
|
itemSchema = z.number();
|
|
210
277
|
break;
|
|
211
|
-
case 'url':
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
278
|
+
case 'url': {
|
|
279
|
+
// Harmonized with the standalone url field: same shape, same
|
|
280
|
+
// superRefine. Each item carries url/id/text/newTab/rel,
|
|
281
|
+
// respects `localized` like the rest of the schema, and
|
|
282
|
+
// rejects malformed values via isValidUrlValue.
|
|
283
|
+
if (!localized) {
|
|
284
|
+
itemSchema = z
|
|
285
|
+
.object({
|
|
286
|
+
url: z.string().default(''),
|
|
287
|
+
id: z.string().optional(),
|
|
288
|
+
text: z.string().default('').optional(),
|
|
289
|
+
newTab: z.boolean().optional(),
|
|
290
|
+
rel: z.string().optional()
|
|
291
|
+
})
|
|
292
|
+
.superRefine((data, ctx) => {
|
|
293
|
+
const isLinked = typeof data.id === 'string' && data.id.length > 0;
|
|
294
|
+
if (isLinked)
|
|
295
|
+
return;
|
|
296
|
+
if (!isValidUrlValue(data.url ?? '')) {
|
|
297
|
+
ctx.addIssue({
|
|
298
|
+
code: z.ZodIssueCode.custom,
|
|
299
|
+
message: msg.invalidUrl,
|
|
300
|
+
path: ['url']
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
const langString = z.string().default('');
|
|
307
|
+
itemSchema = z
|
|
308
|
+
.object({
|
|
309
|
+
url: z.object(Object.fromEntries(languages.map((lang) => [lang, langString]))),
|
|
310
|
+
id: z.string().optional(),
|
|
311
|
+
text: z
|
|
312
|
+
.object(Object.fromEntries(languages.map((lang) => [lang, langString])))
|
|
313
|
+
.optional(),
|
|
314
|
+
newTab: z.boolean().optional(),
|
|
315
|
+
rel: z.string().optional()
|
|
316
|
+
})
|
|
317
|
+
.superRefine((data, ctx) => {
|
|
318
|
+
const isLinked = typeof data.id === 'string' && data.id.length > 0;
|
|
319
|
+
if (isLinked)
|
|
320
|
+
return;
|
|
321
|
+
for (const lang of languages) {
|
|
322
|
+
const val = data.url[lang] ?? '';
|
|
323
|
+
if (!isValidUrlValue(val)) {
|
|
324
|
+
ctx.addIssue({
|
|
325
|
+
code: z.ZodIssueCode.custom,
|
|
326
|
+
message: msg.invalidUrl,
|
|
327
|
+
path: ['url', lang]
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
218
333
|
break;
|
|
334
|
+
}
|
|
219
335
|
}
|
|
220
336
|
let schema = z.array(itemSchema);
|
|
221
337
|
if (field.minItems !== undefined)
|
|
222
338
|
schema = schema.min(field.minItems, {
|
|
223
|
-
message:
|
|
339
|
+
message: msg.minItems(field.minItems)
|
|
224
340
|
});
|
|
225
341
|
if (field.maxItems !== undefined)
|
|
226
342
|
schema = schema.max(field.maxItems, {
|
|
227
|
-
message:
|
|
343
|
+
message: msg.maxItems(field.maxItems)
|
|
228
344
|
});
|
|
229
345
|
return schema.default([]);
|
|
230
346
|
}
|
|
231
347
|
case 'slug': {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return
|
|
348
|
+
if (field.required) {
|
|
349
|
+
return z.string().min(1, { message: msg.required });
|
|
350
|
+
}
|
|
351
|
+
return z.string().optional().default('');
|
|
236
352
|
}
|
|
237
353
|
case 'seo': {
|
|
238
|
-
|
|
239
|
-
return z.object({
|
|
240
|
-
slug: z.string(),
|
|
241
|
-
canonicalUrl: z.string().optional(),
|
|
242
|
-
title: z.string(),
|
|
243
|
-
description: z.string(),
|
|
244
|
-
keywords: z.string().optional(),
|
|
245
|
-
ogImage: z.string(),
|
|
246
|
-
customCode: z.string().optional()
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
return z.object({
|
|
250
|
-
slug: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()]))),
|
|
251
|
-
canonicalUrl: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string().optional()]))),
|
|
252
|
-
title: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()]))),
|
|
253
|
-
description: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()]))),
|
|
254
|
-
keywords: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string().optional()]))),
|
|
255
|
-
ogImage: z.string(),
|
|
256
|
-
customCode: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string().optional()])))
|
|
257
|
-
});
|
|
354
|
+
return z.object(buildSeoZodShape(languages, localized));
|
|
258
355
|
}
|
|
259
356
|
case 'shop': {
|
|
260
357
|
// Shop data is stored in shop_products table (keyed by entry_id), not in entry.data.
|
|
@@ -271,7 +368,36 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
271
368
|
flatShape.text = z.string().default('');
|
|
272
369
|
if (field.newTab)
|
|
273
370
|
flatShape.newTab = z.boolean().optional();
|
|
274
|
-
|
|
371
|
+
const flatSchema = z.object(flatShape).superRefine((data, ctx) => {
|
|
372
|
+
const isLinked = typeof data.id === 'string' && data.id.length > 0;
|
|
373
|
+
if (isLinked)
|
|
374
|
+
return;
|
|
375
|
+
const url = typeof data.url === 'string' ? data.url : '';
|
|
376
|
+
if (field.required && url.trim() === '') {
|
|
377
|
+
ctx.addIssue({
|
|
378
|
+
code: z.ZodIssueCode.too_small,
|
|
379
|
+
minimum: 1,
|
|
380
|
+
type: 'string',
|
|
381
|
+
inclusive: true,
|
|
382
|
+
message: msg.required,
|
|
383
|
+
path: ['url']
|
|
384
|
+
});
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (!isValidUrlValue(url)) {
|
|
388
|
+
ctx.addIssue({
|
|
389
|
+
code: z.ZodIssueCode.custom,
|
|
390
|
+
message: msg.invalidUrl,
|
|
391
|
+
path: ['url']
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
if (field.required)
|
|
396
|
+
return flatSchema;
|
|
397
|
+
const flatDefault = { url: '' };
|
|
398
|
+
if (field.text)
|
|
399
|
+
flatDefault.text = '';
|
|
400
|
+
return flatSchema.optional().default(flatDefault);
|
|
275
401
|
}
|
|
276
402
|
const langString = z.string().default('');
|
|
277
403
|
const urlShape = {
|
|
@@ -284,27 +410,39 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
284
410
|
if (field.newTab) {
|
|
285
411
|
urlShape.newTab = z.boolean().optional();
|
|
286
412
|
}
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
413
|
+
const localizedSchema = z.object(urlShape).superRefine((data, ctx) => {
|
|
414
|
+
const isLinked = data.id != null && data.id.length > 0;
|
|
415
|
+
if (isLinked)
|
|
416
|
+
return;
|
|
417
|
+
for (const lang of languages) {
|
|
418
|
+
const val = data.url[lang] ?? '';
|
|
419
|
+
if (field.required && val.trim() === '') {
|
|
420
|
+
ctx.addIssue({
|
|
421
|
+
code: z.ZodIssueCode.too_small,
|
|
422
|
+
minimum: 1,
|
|
423
|
+
type: 'string',
|
|
424
|
+
inclusive: true,
|
|
425
|
+
message: msg.required,
|
|
426
|
+
path: ['url', lang]
|
|
427
|
+
});
|
|
428
|
+
continue;
|
|
304
429
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
430
|
+
if (!isValidUrlValue(val)) {
|
|
431
|
+
ctx.addIssue({
|
|
432
|
+
code: z.ZodIssueCode.custom,
|
|
433
|
+
message: msg.invalidUrl,
|
|
434
|
+
path: ['url', lang]
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
if (field.required)
|
|
440
|
+
return localizedSchema;
|
|
441
|
+
const emptyLangMap = Object.fromEntries(languages.map((l) => [l, '']));
|
|
442
|
+
const localizedDefault = { url: emptyLangMap };
|
|
443
|
+
if (field.text)
|
|
444
|
+
localizedDefault.text = emptyLangMap;
|
|
445
|
+
return localizedSchema.optional().default(localizedDefault);
|
|
308
446
|
}
|
|
309
447
|
case 'custom': {
|
|
310
448
|
const customDef = getCustomFieldDef(field.fieldType);
|
|
@@ -317,9 +455,15 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
317
455
|
}
|
|
318
456
|
}
|
|
319
457
|
export function generateZodSchemaFromFields(fields, languages, options = {}) {
|
|
458
|
+
// Top-level fields are required-validated by default (consistent with the
|
|
459
|
+
// single-field default). Nested callers (object/blocks/content) always pass
|
|
460
|
+
// an explicit `parentRequired`; entry-level callers only pass `localized`,
|
|
461
|
+
// so without this merge a top-level `required` text field never got
|
|
462
|
+
// `.min(1)` and empty values slipped through validation.
|
|
463
|
+
const resolvedOptions = { parentRequired: true, ...options };
|
|
320
464
|
return z.object({
|
|
321
465
|
...Object.fromEntries(fields.map((f) => {
|
|
322
|
-
let schema = generateZodSchemaFromField(f, languages,
|
|
466
|
+
let schema = generateZodSchemaFromField(f, languages, resolvedOptions);
|
|
323
467
|
// Fields with showWhen are conditionally visible — make them optional
|
|
324
468
|
// so hidden fields don't block validation
|
|
325
469
|
if (f.showWhen) {
|
|
@@ -31,9 +31,12 @@ export declare function getDistributedObjectSlugs(nodes: LayoutNode[], fields: F
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function buildFormPath(dotPath: string): string;
|
|
33
33
|
export interface LayoutValidationError {
|
|
34
|
-
type: 'missing_field' | 'duplicate_field' | 'depth_exceeded' | 'columns_mismatch';
|
|
34
|
+
type: 'missing_field' | 'duplicate_field' | 'depth_exceeded' | 'columns_mismatch' | 'nested_tabs' | 'invalid_tabs_structure';
|
|
35
35
|
message: string;
|
|
36
36
|
}
|
|
37
|
+
/** Expand layout field references (incl. dot-notation & whole objects) into
|
|
38
|
+
* concrete leaf paths. Used by the renderer to map a tab subtree → its fields. */
|
|
39
|
+
export declare function collectLeafPathsForNodes(nodes: LayoutNode[], fields: Field[]): string[];
|
|
37
40
|
/** Validate layout against fields — returns errors or empty array.
|
|
38
41
|
* Supports dot-notation paths (e.g. 'hero.title'). */
|
|
39
42
|
export declare function validateLayout(nodes: LayoutNode[], fields: Field[]): LayoutValidationError[];
|
|
@@ -109,6 +109,15 @@ export function buildFormPath(dotPath) {
|
|
|
109
109
|
function columnCount(ratio) {
|
|
110
110
|
return ratio.split(' ').length;
|
|
111
111
|
}
|
|
112
|
+
/** Expand layout field references (incl. dot-notation & whole objects) into
|
|
113
|
+
* concrete leaf paths. Used by the renderer to map a tab subtree → its fields. */
|
|
114
|
+
export function collectLeafPathsForNodes(nodes, fields) {
|
|
115
|
+
const leaves = [];
|
|
116
|
+
for (const ref of collectFieldSlugs(nodes)) {
|
|
117
|
+
leaves.push(...expandToLeafPaths(ref, fields));
|
|
118
|
+
}
|
|
119
|
+
return leaves;
|
|
120
|
+
}
|
|
112
121
|
/** Validate layout against fields — returns errors or empty array.
|
|
113
122
|
* Supports dot-notation paths (e.g. 'hero.title'). */
|
|
114
123
|
export function validateLayout(nodes, fields) {
|
|
@@ -140,13 +149,41 @@ export function validateLayout(nodes, fields) {
|
|
|
140
149
|
seenLeaves.add(leaf);
|
|
141
150
|
}
|
|
142
151
|
}
|
|
143
|
-
// Check depth + columns
|
|
144
|
-
|
|
152
|
+
// Check depth + columns + tabs structure.
|
|
153
|
+
// `tabs`/`tab` are structural wrappers — they do NOT count toward visual
|
|
154
|
+
// nesting depth, but `tabs` may not be nested inside another `tab`/`tabs`.
|
|
155
|
+
function walk(nodeList, depth, insideTabs) {
|
|
145
156
|
if (depth > 2) {
|
|
146
157
|
errors.push({ type: 'depth_exceeded', message: `Nesting depth ${depth} exceeds max of 2` });
|
|
147
158
|
return;
|
|
148
159
|
}
|
|
149
160
|
for (const node of nodeList) {
|
|
161
|
+
if (node.type === 'tabs') {
|
|
162
|
+
if (insideTabs) {
|
|
163
|
+
errors.push({ type: 'nested_tabs', message: 'Nested tabs are not allowed' });
|
|
164
|
+
}
|
|
165
|
+
for (const child of node.children) {
|
|
166
|
+
if (child.type !== 'tab') {
|
|
167
|
+
errors.push({
|
|
168
|
+
type: 'invalid_tabs_structure',
|
|
169
|
+
message: `"tabs" children must be "tab" nodes, got "${child.type}"`
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const child of node.children) {
|
|
174
|
+
if (child.type === 'tab' && isLayoutBranch(child)) {
|
|
175
|
+
walk(child.children, depth, true);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (node.type === 'tab') {
|
|
181
|
+
errors.push({
|
|
182
|
+
type: 'invalid_tabs_structure',
|
|
183
|
+
message: '"tab" is only valid as a direct child of "tabs"'
|
|
184
|
+
});
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
150
187
|
if (node.type === 'columns') {
|
|
151
188
|
const expected = columnCount(node.ratio);
|
|
152
189
|
const actual = node.children?.length ?? 0;
|
|
@@ -158,11 +195,11 @@ export function validateLayout(nodes, fields) {
|
|
|
158
195
|
}
|
|
159
196
|
}
|
|
160
197
|
if (isLayoutBranch(node)) {
|
|
161
|
-
walk(node.children, depth + 1);
|
|
198
|
+
walk(node.children, depth + 1, insideTabs);
|
|
162
199
|
}
|
|
163
200
|
}
|
|
164
201
|
}
|
|
165
|
-
walk(nodes, 0);
|
|
202
|
+
walk(nodes, 0, false);
|
|
166
203
|
return errors;
|
|
167
204
|
}
|
|
168
205
|
/** Expand a preset into LayoutNode[] */
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { SeoFieldData } from '../../types/fields.js';
|
|
2
|
+
import type { MediaFile } from '../../types/media.js';
|
|
3
|
+
/**
|
|
4
|
+
* Normalized SEO data — localized values flattened by `language`. A stable
|
|
5
|
+
* read-path over the v1.0-frozen `entry.seo.*` contract. Use it to build a
|
|
6
|
+
* custom `<head>` or to feed the `<Seo>` component.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Every field is optional, even though `slug` and `title` are *required* in
|
|
10
|
+
* {@link SeoFieldData}: a missing, partial, or garbage `entry.seo` resolves to
|
|
11
|
+
* an all-`undefined` shape rather than throwing.
|
|
12
|
+
*
|
|
13
|
+
* `ogImage` is special. The frozen SEO Zod schema treats it as a flat
|
|
14
|
+
* `z.string()` (never per-language localized), so at rest it is a media-UUID
|
|
15
|
+
* string. After server media-hydration it becomes a {@link MediaFile} object.
|
|
16
|
+
* `resolveSeo` therefore passes `ogImage` through unchanged — it may be a
|
|
17
|
+
* `MediaFile` (hydrated) or a string (raw), and is never language-flattened.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export type ResolvedSeo = {
|
|
22
|
+
[K in keyof SeoFieldData]: K extends 'ogImage' ? string | MediaFile | undefined : string | undefined;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Resolve `entry.seo` into flat fields, flattening localized objects by
|
|
26
|
+
* `language`.
|
|
27
|
+
*
|
|
28
|
+
* For each of the six text keys: a `string` value passes through; a localized
|
|
29
|
+
* `{ lang: string }` object resolves to `obj[language]` (or `undefined` if the
|
|
30
|
+
* requested language is missing); anything else resolves to `undefined`.
|
|
31
|
+
*
|
|
32
|
+
* `ogImage` is exempt from flattening: it passes through unchanged as a string
|
|
33
|
+
* or a {@link MediaFile} object (only `null`/`undefined` → `undefined`).
|
|
34
|
+
*
|
|
35
|
+
* The name `resolveSeo` is a deliberate, v1.0-frozen choice — it aligns with
|
|
36
|
+
* the `resolveEntryUrl` / `resolveMedia*` resolver family.
|
|
37
|
+
*
|
|
38
|
+
* @param entry - The (possibly resolved/hydrated) entry, or any object with an
|
|
39
|
+
* optional `seo` property. A non-object or missing `seo` yields all-`undefined`.
|
|
40
|
+
* @param language - The active language code. Required to flatten localized
|
|
41
|
+
* `{ lang: string }` values; localized fields resolve to `undefined` without it.
|
|
42
|
+
* @returns A {@link ResolvedSeo} — all keys present, each `string`/`MediaFile`
|
|
43
|
+
* (only `ogImage`) or `undefined`.
|
|
44
|
+
* @public
|
|
45
|
+
* @example
|
|
46
|
+
* ```svelte
|
|
47
|
+
* <script lang="ts">
|
|
48
|
+
* import { resolveSeo } from 'includio-cms/core';
|
|
49
|
+
* import { Seo } from 'includio-cms';
|
|
50
|
+
* let { entry, language } = $props();
|
|
51
|
+
* const seo = $derived(resolveSeo(entry, language));
|
|
52
|
+
* </script>
|
|
53
|
+
*
|
|
54
|
+
* <svelte:head>
|
|
55
|
+
* {#if seo.title}<title>{seo.title}</title>{/if}
|
|
56
|
+
* {#if seo.canonicalUrl}<link rel="canonical" href={seo.canonicalUrl} />{/if}
|
|
57
|
+
* </svelte:head>
|
|
58
|
+
*
|
|
59
|
+
* <Seo
|
|
60
|
+
* title={seo.title}
|
|
61
|
+
* description={seo.description}
|
|
62
|
+
* keywords={seo.keywords}
|
|
63
|
+
* ogImage={seo.ogImage}
|
|
64
|
+
* canonicalUrl={seo.canonicalUrl}
|
|
65
|
+
* />
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveSeo(entry: {
|
|
69
|
+
seo?: Record<string, unknown>;
|
|
70
|
+
} | Record<string, unknown>, language?: string): ResolvedSeo;
|