@velastack/cms 0.1.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/LICENSE +21 -0
- package/README.md +644 -0
- package/dist/cms.d.ts +6 -0
- package/dist/cms.js +30 -0
- package/dist/components/admin-bar/admin-bar-internal.svelte +1697 -0
- package/dist/components/admin-bar/admin-bar-internal.svelte.d.ts +12 -0
- package/dist/components/admin-bar/admin-bar.css +5 -0
- package/dist/components/admin-bar/admin-bar.svelte +355 -0
- package/dist/components/admin-bar/admin-bar.svelte.d.ts +7 -0
- package/dist/components/admin-bar/click-outside.d.ts +8 -0
- package/dist/components/admin-bar/click-outside.js +17 -0
- package/dist/components/admin-bar/css-root.svelte +23 -0
- package/dist/components/admin-bar/css-root.svelte.d.ts +19 -0
- package/dist/components/admin-bar/delete-page-dialog.svelte +326 -0
- package/dist/components/admin-bar/delete-page-dialog.svelte.d.ts +32 -0
- package/dist/components/admin-bar/history-panel.svelte +264 -0
- package/dist/components/admin-bar/history-panel.svelte.d.ts +8 -0
- package/dist/components/admin-bar/icons/bold.svelte +13 -0
- package/dist/components/admin-bar/icons/bold.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/check.svelte +11 -0
- package/dist/components/admin-bar/icons/check.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/chevron-down.svelte +11 -0
- package/dist/components/admin-bar/icons/chevron-down.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/chevron-right.svelte +11 -0
- package/dist/components/admin-bar/icons/chevron-right.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/chevron-up.svelte +11 -0
- package/dist/components/admin-bar/icons/chevron-up.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/code.svelte +14 -0
- package/dist/components/admin-bar/icons/code.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/external-link.svelte +15 -0
- package/dist/components/admin-bar/icons/external-link.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/file.svelte +19 -0
- package/dist/components/admin-bar/icons/file.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/heading-2.svelte +16 -0
- package/dist/components/admin-bar/icons/heading-2.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/heading-3.svelte +17 -0
- package/dist/components/admin-bar/icons/heading-3.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/icon.svelte +41 -0
- package/dist/components/admin-bar/icons/icon.svelte.d.ts +21 -0
- package/dist/components/admin-bar/icons/images.svelte +16 -0
- package/dist/components/admin-bar/icons/images.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/italic.svelte +15 -0
- package/dist/components/admin-bar/icons/italic.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/link-2-off.svelte +16 -0
- package/dist/components/admin-bar/icons/link-2-off.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/link.svelte +14 -0
- package/dist/components/admin-bar/icons/link.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/list-ordered.svelte +18 -0
- package/dist/components/admin-bar/icons/list-ordered.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/list.svelte +18 -0
- package/dist/components/admin-bar/icons/list.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/loader-circle.svelte +11 -0
- package/dist/components/admin-bar/icons/loader-circle.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/minus.svelte +11 -0
- package/dist/components/admin-bar/icons/minus.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/quote.svelte +24 -0
- package/dist/components/admin-bar/icons/quote.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/search.svelte +14 -0
- package/dist/components/admin-bar/icons/search.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/trash-2.svelte +17 -0
- package/dist/components/admin-bar/icons/trash-2.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/triangle-alert.svelte +15 -0
- package/dist/components/admin-bar/icons/triangle-alert.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/types.d.ts +7 -0
- package/dist/components/admin-bar/icons/types.js +1 -0
- package/dist/components/admin-bar/icons/undo-2.svelte +14 -0
- package/dist/components/admin-bar/icons/undo-2.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/upload.svelte +15 -0
- package/dist/components/admin-bar/icons/upload.svelte.d.ts +3 -0
- package/dist/components/admin-bar/icons/x.svelte +14 -0
- package/dist/components/admin-bar/icons/x.svelte.d.ts +3 -0
- package/dist/components/admin-bar/keyboard-shortcuts-dialog.svelte +96 -0
- package/dist/components/admin-bar/keyboard-shortcuts-dialog.svelte.d.ts +7 -0
- package/dist/components/admin-bar/locales-panel.svelte +137 -0
- package/dist/components/admin-bar/locales-panel.svelte.d.ts +9 -0
- package/dist/components/admin-bar/media-panel.svelte +282 -0
- package/dist/components/admin-bar/media-panel.svelte.d.ts +7 -0
- package/dist/components/admin-bar/new-page-chooser-dialog.svelte +56 -0
- package/dist/components/admin-bar/new-page-chooser-dialog.svelte.d.ts +10 -0
- package/dist/components/admin-bar/new-page-dialog.svelte +126 -0
- package/dist/components/admin-bar/new-page-dialog.svelte.d.ts +14 -0
- package/dist/components/admin-bar/page-config.d.ts +61 -0
- package/dist/components/admin-bar/page-config.js +24 -0
- package/dist/components/admin-bar/pages-panel.svelte +639 -0
- package/dist/components/admin-bar/pages-panel.svelte.d.ts +13 -0
- package/dist/components/admin-bar/panel-footer.svelte +21 -0
- package/dist/components/admin-bar/panel-footer.svelte.d.ts +8 -0
- package/dist/components/admin-bar/panel-header.svelte +29 -0
- package/dist/components/admin-bar/panel-header.svelte.d.ts +10 -0
- package/dist/components/admin-bar/panel.svelte +41 -0
- package/dist/components/admin-bar/panel.svelte.d.ts +10 -0
- package/dist/components/admin-bar/publish-dialog.svelte +387 -0
- package/dist/components/admin-bar/publish-dialog.svelte.d.ts +16 -0
- package/dist/components/admin-bar/resolve-route.d.ts +20 -0
- package/dist/components/admin-bar/resolve-route.js +50 -0
- package/dist/components/admin-bar/seo-panel.svelte +387 -0
- package/dist/components/admin-bar/seo-panel.svelte.d.ts +7 -0
- package/dist/components/admin-bar/share-preview-link-dialog.svelte +97 -0
- package/dist/components/admin-bar/share-preview-link-dialog.svelte.d.ts +9 -0
- package/dist/components/admin-bar/site-settings-panel.svelte +225 -0
- package/dist/components/admin-bar/site-settings-panel.svelte.d.ts +7 -0
- package/dist/components/admin-bar/status-pill.svelte +49 -0
- package/dist/components/admin-bar/status-pill.svelte.d.ts +11 -0
- package/dist/components/admin-bar/theme.svelte.d.ts +16 -0
- package/dist/components/admin-bar/theme.svelte.js +39 -0
- package/dist/components/admin-bar/ui/badge/badge.svelte +57 -0
- package/dist/components/admin-bar/ui/badge/badge.svelte.d.ts +16 -0
- package/dist/components/admin-bar/ui/badge/index.d.ts +4 -0
- package/dist/components/admin-bar/ui/badge/index.js +4 -0
- package/dist/components/admin-bar/ui/button/button.svelte +73 -0
- package/dist/components/admin-bar/ui/button/button.svelte.d.ts +16 -0
- package/dist/components/admin-bar/ui/button/index.d.ts +4 -0
- package/dist/components/admin-bar/ui/button/index.js +4 -0
- package/dist/components/admin-bar/ui/collapsible/collapsible-content.svelte +7 -0
- package/dist/components/admin-bar/ui/collapsible/collapsible-content.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/collapsible/collapsible-trigger.svelte +7 -0
- package/dist/components/admin-bar/ui/collapsible/collapsible-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/collapsible/collapsible.svelte +7 -0
- package/dist/components/admin-bar/ui/collapsible/collapsible.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/collapsible/index.d.ts +4 -0
- package/dist/components/admin-bar/ui/collapsible/index.js +6 -0
- package/dist/components/admin-bar/ui/dialog/dialog-close.svelte +7 -0
- package/dist/components/admin-bar/ui/dialog/dialog-close.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/dialog/dialog-content.svelte +46 -0
- package/dist/components/admin-bar/ui/dialog/dialog-content.svelte.d.ts +11 -0
- package/dist/components/admin-bar/ui/dialog/dialog-description.svelte +17 -0
- package/dist/components/admin-bar/ui/dialog/dialog-description.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/dialog/dialog-footer.svelte +23 -0
- package/dist/components/admin-bar/ui/dialog/dialog-footer.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/dialog/dialog-header.svelte +20 -0
- package/dist/components/admin-bar/ui/dialog/dialog-header.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/dialog/dialog-overlay.svelte +20 -0
- package/dist/components/admin-bar/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/dialog/dialog-portal.svelte +7 -0
- package/dist/components/admin-bar/ui/dialog/dialog-portal.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/dialog/dialog-title.svelte +17 -0
- package/dist/components/admin-bar/ui/dialog/dialog-title.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/dialog/dialog-trigger.svelte +7 -0
- package/dist/components/admin-bar/ui/dialog/dialog-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/dialog/dialog.svelte +7 -0
- package/dist/components/admin-bar/ui/dialog/dialog.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/dialog/index.d.ts +11 -0
- package/dist/components/admin-bar/ui/dialog/index.js +13 -0
- package/dist/components/admin-bar/ui/input/index.d.ts +3 -0
- package/dist/components/admin-bar/ui/input/index.js +3 -0
- package/dist/components/admin-bar/ui/input/input.svelte +24 -0
- package/dist/components/admin-bar/ui/input/input.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/kbd/index.d.ts +4 -0
- package/dist/components/admin-bar/ui/kbd/index.js +6 -0
- package/dist/components/admin-bar/ui/kbd/kbd-group.svelte +20 -0
- package/dist/components/admin-bar/ui/kbd/kbd-group.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/kbd/kbd-shortcut.svelte +52 -0
- package/dist/components/admin-bar/ui/kbd/kbd-shortcut.svelte.d.ts +8 -0
- package/dist/components/admin-bar/ui/kbd/kbd.svelte +27 -0
- package/dist/components/admin-bar/ui/kbd/kbd.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/label/index.d.ts +3 -0
- package/dist/components/admin-bar/ui/label/index.js +3 -0
- package/dist/components/admin-bar/ui/label/label.svelte +20 -0
- package/dist/components/admin-bar/ui/label/label.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/menubar/index.d.ts +18 -0
- package/dist/components/admin-bar/ui/menubar/index.js +20 -0
- package/dist/components/admin-bar/ui/menubar/menubar-checkbox-item.svelte +46 -0
- package/dist/components/admin-bar/ui/menubar/menubar-checkbox-item.svelte.d.ts +10 -0
- package/dist/components/admin-bar/ui/menubar/menubar-content.svelte +28 -0
- package/dist/components/admin-bar/ui/menubar/menubar-content.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/menubar/menubar-group-heading.svelte +25 -0
- package/dist/components/admin-bar/ui/menubar/menubar-group-heading.svelte.d.ts +8 -0
- package/dist/components/admin-bar/ui/menubar/menubar-group.svelte +12 -0
- package/dist/components/admin-bar/ui/menubar/menubar-group.svelte.d.ts +7 -0
- package/dist/components/admin-bar/ui/menubar/menubar-item.svelte +27 -0
- package/dist/components/admin-bar/ui/menubar/menubar-item.svelte.d.ts +8 -0
- package/dist/components/admin-bar/ui/menubar/menubar-label.svelte +25 -0
- package/dist/components/admin-bar/ui/menubar/menubar-label.svelte.d.ts +8 -0
- package/dist/components/admin-bar/ui/menubar/menubar-menu.svelte +7 -0
- package/dist/components/admin-bar/ui/menubar/menubar-menu.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/menubar/menubar-portal.svelte +7 -0
- package/dist/components/admin-bar/ui/menubar/menubar-portal.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/menubar/menubar-radio-group.svelte +11 -0
- package/dist/components/admin-bar/ui/menubar/menubar-radio-group.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/menubar/menubar-radio-item.svelte +37 -0
- package/dist/components/admin-bar/ui/menubar/menubar-radio-item.svelte.d.ts +8 -0
- package/dist/components/admin-bar/ui/menubar/menubar-separator.svelte +17 -0
- package/dist/components/admin-bar/ui/menubar/menubar-separator.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/menubar/menubar-shortcut.svelte +23 -0
- package/dist/components/admin-bar/ui/menubar/menubar-shortcut.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/menubar/menubar-sub-content.svelte +20 -0
- package/dist/components/admin-bar/ui/menubar/menubar-sub-content.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/menubar/menubar-sub-trigger.svelte +29 -0
- package/dist/components/admin-bar/ui/menubar/menubar-sub-trigger.svelte.d.ts +8 -0
- package/dist/components/admin-bar/ui/menubar/menubar-sub.svelte +7 -0
- package/dist/components/admin-bar/ui/menubar/menubar-sub.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/menubar/menubar-trigger.svelte +20 -0
- package/dist/components/admin-bar/ui/menubar/menubar-trigger.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/menubar/menubar.svelte +20 -0
- package/dist/components/admin-bar/ui/menubar/menubar.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/select/index.d.ts +12 -0
- package/dist/components/admin-bar/ui/select/index.js +14 -0
- package/dist/components/admin-bar/ui/select/select-content.svelte +45 -0
- package/dist/components/admin-bar/ui/select/select-content.svelte.d.ts +11 -0
- package/dist/components/admin-bar/ui/select/select-group-heading.svelte +21 -0
- package/dist/components/admin-bar/ui/select/select-group-heading.svelte.d.ts +10 -0
- package/dist/components/admin-bar/ui/select/select-group.svelte +17 -0
- package/dist/components/admin-bar/ui/select/select-group.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/select/select-item.svelte +40 -0
- package/dist/components/admin-bar/ui/select/select-item.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/select/select-label.svelte +20 -0
- package/dist/components/admin-bar/ui/select/select-label.svelte.d.ts +6 -0
- package/dist/components/admin-bar/ui/select/select-portal.svelte +7 -0
- package/dist/components/admin-bar/ui/select/select-portal.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/select/select-scroll-down-button.svelte +23 -0
- package/dist/components/admin-bar/ui/select/select-scroll-down-button.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/select/select-scroll-up-button.svelte +23 -0
- package/dist/components/admin-bar/ui/select/select-scroll-up-button.svelte.d.ts +5 -0
- package/dist/components/admin-bar/ui/select/select-separator.svelte +21 -0
- package/dist/components/admin-bar/ui/select/select-separator.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/select/select-trigger.svelte +31 -0
- package/dist/components/admin-bar/ui/select/select-trigger.svelte.d.ts +8 -0
- package/dist/components/admin-bar/ui/select/select.svelte +11 -0
- package/dist/components/admin-bar/ui/select/select.svelte.d.ts +3 -0
- package/dist/components/admin-bar/ui/separator/index.d.ts +2 -0
- package/dist/components/admin-bar/ui/separator/index.js +4 -0
- package/dist/components/admin-bar/ui/separator/separator.svelte +21 -0
- package/dist/components/admin-bar/ui/separator/separator.svelte.d.ts +4 -0
- package/dist/components/admin-bar/ui/textarea/index.d.ts +3 -0
- package/dist/components/admin-bar/ui/textarea/index.js +3 -0
- package/dist/components/admin-bar/ui/textarea/textarea.svelte +21 -0
- package/dist/components/admin-bar/ui/textarea/textarea.svelte.d.ts +5 -0
- package/dist/components/admin-bar/user-avatar.svelte +29 -0
- package/dist/components/admin-bar/user-avatar.svelte.d.ts +7 -0
- package/dist/components/admin-bar/utils.d.ts +10 -0
- package/dist/components/admin-bar/utils.js +46 -0
- package/dist/components/admin-bar/variants.d.ts +20 -0
- package/dist/components/admin-bar/variants.js +14 -0
- package/dist/components/cms/cms-boolean.svelte +102 -0
- package/dist/components/cms/cms-boolean.svelte.d.ts +10 -0
- package/dist/components/cms/cms-date-time-editable.svelte +66 -0
- package/dist/components/cms/cms-date-time-editable.svelte.d.ts +11 -0
- package/dist/components/cms/cms-date-time.svelte +67 -0
- package/dist/components/cms/cms-date-time.svelte.d.ts +19 -0
- package/dist/components/cms/cms-entries.svelte +24 -0
- package/dist/components/cms/cms-entries.svelte.d.ts +31 -0
- package/dist/components/cms/cms-image-editable.svelte +439 -0
- package/dist/components/cms/cms-image-editable.svelte.d.ts +11 -0
- package/dist/components/cms/cms-image.svelte +78 -0
- package/dist/components/cms/cms-image.svelte.d.ts +18 -0
- package/dist/components/cms/cms-link-editable.svelte +429 -0
- package/dist/components/cms/cms-link-editable.svelte.d.ts +13 -0
- package/dist/components/cms/cms-link.svelte +92 -0
- package/dist/components/cms/cms-link.svelte.d.ts +25 -0
- package/dist/components/cms/cms-markdown-editable.svelte +74 -0
- package/dist/components/cms/cms-markdown-editable.svelte.d.ts +9 -0
- package/dist/components/cms/cms-markdown.svelte +56 -0
- package/dist/components/cms/cms-markdown.svelte.d.ts +10 -0
- package/dist/components/cms/cms-media-picker.svelte +150 -0
- package/dist/components/cms/cms-media-picker.svelte.d.ts +8 -0
- package/dist/components/cms/cms-number-editable.svelte +77 -0
- package/dist/components/cms/cms-number-editable.svelte.d.ts +13 -0
- package/dist/components/cms/cms-number.svelte +63 -0
- package/dist/components/cms/cms-number.svelte.d.ts +16 -0
- package/dist/components/cms/cms-repeater-editable.svelte +137 -0
- package/dist/components/cms/cms-repeater-editable.svelte.d.ts +31 -0
- package/dist/components/cms/cms-repeater.svelte +55 -0
- package/dist/components/cms/cms-repeater.svelte.d.ts +30 -0
- package/dist/components/cms/cms-rich-text-editable.svelte +101 -0
- package/dist/components/cms/cms-rich-text-editable.svelte.d.ts +9 -0
- package/dist/components/cms/cms-rich-text-toolbar.svelte +246 -0
- package/dist/components/cms/cms-rich-text-toolbar.svelte.d.ts +9 -0
- package/dist/components/cms/cms-rich-text.svelte +74 -0
- package/dist/components/cms/cms-rich-text.svelte.d.ts +10 -0
- package/dist/components/cms/cms-store.svelte.d.ts +294 -0
- package/dist/components/cms/cms-store.svelte.js +625 -0
- package/dist/components/cms/cms-text.svelte +131 -0
- package/dist/components/cms/cms-text.svelte.d.ts +11 -0
- package/dist/components/cms/index.d.ts +13 -0
- package/dist/components/cms/index.js +11 -0
- package/dist/components/cms/install-scope.svelte.d.ts +7 -0
- package/dist/components/cms/install-scope.svelte.js +33 -0
- package/dist/components/cms/locale-merge.d.ts +41 -0
- package/dist/components/cms/locale-merge.js +77 -0
- package/dist/components/cms/overlay-sync.d.ts +82 -0
- package/dist/components/cms/overlay-sync.js +76 -0
- package/dist/components/cms/path.d.ts +30 -0
- package/dist/components/cms/path.js +156 -0
- package/dist/components/cms/scope.d.ts +120 -0
- package/dist/components/cms/scope.js +23 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +28 -0
- package/dist/srv/README.md +341 -0
- package/dist/srv/api-adapter.d.ts +20 -0
- package/dist/srv/api-adapter.js +178 -0
- package/dist/srv/cms.d.ts +62 -0
- package/dist/srv/cms.js +35 -0
- package/dist/srv/index.d.ts +5 -0
- package/dist/srv/index.js +4 -0
- package/dist/srv/load-cms.d.ts +105 -0
- package/dist/srv/load-cms.js +227 -0
- package/dist/srv/mock-adapter.d.ts +96 -0
- package/dist/srv/mock-adapter.js +171 -0
- package/dist/srv/types.d.ts +114 -0
- package/dist/srv/types.js +1 -0
- package/package.json +116 -0
- package/plugin/README.md +137 -0
- package/plugin/dist/adapter.d.ts +35 -0
- package/plugin/dist/adapter.js +346 -0
- package/plugin/dist/adapter.js.map +1 -0
- package/plugin/dist/build-state.d.ts +10 -0
- package/plugin/dist/build-state.js +26 -0
- package/plugin/dist/build-state.js.map +1 -0
- package/plugin/dist/fallback-shim.js +194 -0
- package/plugin/dist/index.d.ts +56 -0
- package/plugin/dist/index.js +396 -0
- package/plugin/dist/index.js.map +1 -0
- package/plugin/dist/manifest.d.ts +76 -0
- package/plugin/dist/manifest.js +367 -0
- package/plugin/dist/manifest.js.map +1 -0
- package/plugin/dist/media.d.ts +36 -0
- package/plugin/dist/media.js +153 -0
- package/plugin/dist/media.js.map +1 -0
- package/plugin/dist/parse-svelte.d.ts +35 -0
- package/plugin/dist/parse-svelte.js +120 -0
- package/plugin/dist/parse-svelte.js.map +1 -0
- package/plugin/dist/route-tree.d.ts +27 -0
- package/plugin/dist/route-tree.js +69 -0
- package/plugin/dist/route-tree.js.map +1 -0
- package/plugin/dist/transforms.d.ts +42 -0
- package/plugin/dist/transforms.js +95 -0
- package/plugin/dist/transforms.js.map +1 -0
|
@@ -0,0 +1,1697 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
afterNavigate,
|
|
4
|
+
beforeNavigate,
|
|
5
|
+
goto,
|
|
6
|
+
invalidateAll,
|
|
7
|
+
replaceState
|
|
8
|
+
} from '$app/navigation';
|
|
9
|
+
import { page } from '$app/state';
|
|
10
|
+
import { pages } from 'virtual:vela-cms/pages';
|
|
11
|
+
import { cmsStore, type PageDeleteOutcome } from '../cms/cms-store.svelte.js';
|
|
12
|
+
import { deriveOverlayIntents } from '../cms/overlay-sync.js';
|
|
13
|
+
import type { CmsPayload, CmsScopeEntry } from '../cms/scope.js';
|
|
14
|
+
import CssRoot from './css-root.svelte';
|
|
15
|
+
import { adminBarTheme, type AdminBarTheme } from './theme.svelte.js';
|
|
16
|
+
import DeletePageDialog, {
|
|
17
|
+
type DeletePageMode,
|
|
18
|
+
type RedirectTarget
|
|
19
|
+
} from './delete-page-dialog.svelte';
|
|
20
|
+
import HistoryPanel from './history-panel.svelte';
|
|
21
|
+
import KeyboardShortcutsDialog from './keyboard-shortcuts-dialog.svelte';
|
|
22
|
+
import MediaPanel from './media-panel.svelte';
|
|
23
|
+
import NewPageChooserDialog from './new-page-chooser-dialog.svelte';
|
|
24
|
+
import NewPageDialog from './new-page-dialog.svelte';
|
|
25
|
+
import { isCreatable, type CmsCreatablePageConfigWithRouteId } from './page-config.js';
|
|
26
|
+
import PagesPanel from './pages-panel.svelte';
|
|
27
|
+
import PublishDialog from './publish-dialog.svelte';
|
|
28
|
+
import { resolveRouteOnlyParams, resolveRouteUrl } from './resolve-route.js';
|
|
29
|
+
import SharePreviewLinkDialog from './share-preview-link-dialog.svelte';
|
|
30
|
+
import StatusPill from './status-pill.svelte';
|
|
31
|
+
import { Button } from './ui/button/index.js';
|
|
32
|
+
import { KbdShortcut } from './ui/kbd/index.js';
|
|
33
|
+
import * as Menubar from './ui/menubar/index.js';
|
|
34
|
+
import UserAvatar from './user-avatar.svelte';
|
|
35
|
+
|
|
36
|
+
const menubarTriggerBase =
|
|
37
|
+
'vela:bg-transparent vela:hover:bg-[var(--cms-bar-bg-hover)] vela:aria-expanded:bg-[var(--cms-bar-bg-hover)] vela:rounded-md vela:px-2 vela:py-1 vela:text-[13px] vela:font-medium vela:cursor-pointer';
|
|
38
|
+
const menubarContentClass =
|
|
39
|
+
'vela:bg-bar-bg vela:text-bar-text vela:border vela:border-[var(--cms-bar-divider)] vela:shadow-[0_8px_24px_rgba(0,0,0,0.35)]';
|
|
40
|
+
|
|
41
|
+
// Editing dims the menubar — meta-actions are subordinated while editing.
|
|
42
|
+
const menubarTriggerClass = $derived(
|
|
43
|
+
cmsStore.isEditing
|
|
44
|
+
? `${menubarTriggerBase} vela:text-bar-text-tertiary vela:hover:text-bar-text-secondary vela:aria-expanded:text-bar-text-secondary`
|
|
45
|
+
: `${menubarTriggerBase} vela:text-bar-text-secondary vela:hover:text-bar-text vela:aria-expanded:text-bar-text`
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const menuContentClassExt = `${menubarContentClass} vela:min-w-56 vela:p-1.5`;
|
|
49
|
+
const menuItemClass =
|
|
50
|
+
'vela:flex vela:items-center vela:gap-2 vela:px-2 vela:py-1.5 vela:rounded-md vela:text-[13px] vela:cursor-pointer vela:outline-none vela:focus:bg-[var(--cms-bar-bg-hover)] vela:focus:text-bar-text vela:data-disabled:opacity-40 vela:data-disabled:pointer-events-none';
|
|
51
|
+
const menuItemDestructiveClass =
|
|
52
|
+
'vela:flex vela:items-center vela:gap-2 vela:px-2 vela:py-1.5 vela:rounded-md vela:text-[13px] vela:cursor-pointer vela:outline-none vela:text-[var(--cms-status-error-text-base)] vela:focus:bg-[var(--cms-status-error-bg-hover)] vela:focus:text-[var(--cms-status-error-text-hover)] vela:data-disabled:opacity-40 vela:data-disabled:pointer-events-none';
|
|
53
|
+
const menuLabelClass =
|
|
54
|
+
'vela:px-2 vela:pt-2.5 vela:pb-1 vela:text-[10px] vela:font-semibold vela:uppercase vela:tracking-widest vela:text-bar-text-tertiary';
|
|
55
|
+
const menuShortcutClass = 'vela:ml-auto';
|
|
56
|
+
const menuSeparatorClass = 'vela:my-1 vela:h-px vela:bg-[var(--cms-bar-divider)]';
|
|
57
|
+
const menuCheckIndicatorClass =
|
|
58
|
+
'vela:flex vela:items-center vela:gap-2 vela:px-2 vela:py-1.5 vela:pl-7 vela:rounded-md vela:text-[13px] vela:cursor-pointer vela:outline-none vela:focus:bg-[var(--cms-bar-bg-hover)] vela:focus:text-bar-text vela:data-disabled:opacity-40';
|
|
59
|
+
const avatarTriggerClass =
|
|
60
|
+
'vela:bg-transparent vela:border-0 vela:p-0 vela:rounded-full vela:cursor-pointer vela:focus:outline-none vela:aria-expanded:ring-2 vela:aria-expanded:ring-[var(--cms-accent)] vela:aria-expanded:ring-offset-2 vela:aria-expanded:ring-offset-[var(--cms-bar-bg)]';
|
|
61
|
+
|
|
62
|
+
type Props = {
|
|
63
|
+
user: { id: string; name: string };
|
|
64
|
+
endpoint: string;
|
|
65
|
+
onClose: () => void;
|
|
66
|
+
onLogout: () => void;
|
|
67
|
+
};
|
|
68
|
+
let { user, endpoint, onClose, onLogout }: Props = $props();
|
|
69
|
+
|
|
70
|
+
const setThemePref = (next: AdminBarTheme) => adminBarTheme.setPref(next);
|
|
71
|
+
|
|
72
|
+
// Auto-discovered from `page.cms.ts` files via the Vite plugin's
|
|
73
|
+
// `virtual:vela-cms/pages` module — consumers don't pass these.
|
|
74
|
+
const creatablePages = $derived(Object.values(pages).filter(isCreatable));
|
|
75
|
+
|
|
76
|
+
let seoOpen = $state(false);
|
|
77
|
+
let historyOpen = $state(false);
|
|
78
|
+
let pagesOpen = $state(false);
|
|
79
|
+
let mediaOpen = $state(false);
|
|
80
|
+
let localesOpen = $state(false);
|
|
81
|
+
let publishOpen = $state(false);
|
|
82
|
+
let chooserOpen = $state(false);
|
|
83
|
+
let shareLinkOpen = $state(false);
|
|
84
|
+
let shortcutsOpen = $state(false);
|
|
85
|
+
let siteSettingsOpen = $state(false);
|
|
86
|
+
const anyPanelOpen = $derived(
|
|
87
|
+
seoOpen ||
|
|
88
|
+
historyOpen ||
|
|
89
|
+
pagesOpen ||
|
|
90
|
+
mediaOpen ||
|
|
91
|
+
localesOpen ||
|
|
92
|
+
publishOpen ||
|
|
93
|
+
chooserOpen ||
|
|
94
|
+
shareLinkOpen ||
|
|
95
|
+
shortcutsOpen ||
|
|
96
|
+
siteSettingsOpen
|
|
97
|
+
);
|
|
98
|
+
let publishing = $state(false);
|
|
99
|
+
let publishError = $state<string | null>(null);
|
|
100
|
+
|
|
101
|
+
let newDialog = $state<CmsCreatablePageConfigWithRouteId | null>(null);
|
|
102
|
+
let newPageError = $state<string | null>(null);
|
|
103
|
+
let newPageCreating = $state(false);
|
|
104
|
+
|
|
105
|
+
// Delete-page dialog. Shared between the in-panel "Delete" button and the
|
|
106
|
+
// `Page → Delete Page…` menu entry; pages-panel calls `requestDelete` via
|
|
107
|
+
// its `onRequestDelete` prop, the menu calls it directly.
|
|
108
|
+
type WirePageEntry = {
|
|
109
|
+
params: Record<string, string>;
|
|
110
|
+
isDraft?: boolean;
|
|
111
|
+
isDeletePending?: boolean;
|
|
112
|
+
redirectTo?: string;
|
|
113
|
+
gone?: boolean;
|
|
114
|
+
metadata?: Record<string, unknown>;
|
|
115
|
+
};
|
|
116
|
+
type WirePageRoute = { routeId: string; entries: WirePageEntry[] };
|
|
117
|
+
let deleteDialogOpen = $state(false);
|
|
118
|
+
let deleteTarget = $state<{
|
|
119
|
+
routeId: string;
|
|
120
|
+
params: Record<string, string>;
|
|
121
|
+
url: string;
|
|
122
|
+
isDraft: boolean;
|
|
123
|
+
} | null>(null);
|
|
124
|
+
let deleteRoutes = $state<WirePageRoute[]>([]);
|
|
125
|
+
let deleteError = $state<string | null>(null);
|
|
126
|
+
let deleting = $state(false);
|
|
127
|
+
// Source content captured when the dialog is opened in duplicate mode.
|
|
128
|
+
// `metadata` seeds the input field values; `tree` is the rest of the
|
|
129
|
+
// source's content (non-`metadata` branch) that gets copied to the new
|
|
130
|
+
// page after create succeeds.
|
|
131
|
+
let duplicateSource = $state<{
|
|
132
|
+
tree: Record<string, unknown>;
|
|
133
|
+
metadata: Record<string, unknown>;
|
|
134
|
+
} | null>(null);
|
|
135
|
+
|
|
136
|
+
// Only one panel/dialog open at a time. Every "open X" routes through here.
|
|
137
|
+
const closeAllPanels = () => {
|
|
138
|
+
seoOpen = false;
|
|
139
|
+
historyOpen = false;
|
|
140
|
+
pagesOpen = false;
|
|
141
|
+
mediaOpen = false;
|
|
142
|
+
localesOpen = false;
|
|
143
|
+
publishOpen = false;
|
|
144
|
+
chooserOpen = false;
|
|
145
|
+
shareLinkOpen = false;
|
|
146
|
+
shortcutsOpen = false;
|
|
147
|
+
siteSettingsOpen = false;
|
|
148
|
+
newDialog = null;
|
|
149
|
+
newPageError = null;
|
|
150
|
+
duplicateSource = null;
|
|
151
|
+
if (!deleting) {
|
|
152
|
+
deleteDialogOpen = false;
|
|
153
|
+
deleteTarget = null;
|
|
154
|
+
deleteError = null;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const counts = $derived(cmsStore.workingCopyCounts);
|
|
159
|
+
const previewKey = $derived(cmsStore.openRelease?.preview_key ?? null);
|
|
160
|
+
|
|
161
|
+
// `?version=<key>` opens a past published release in read-only snapshot
|
|
162
|
+
// view. Mutually exclusive with the draft `?preview=` flow: when set, we
|
|
163
|
+
// suppress draft overlays/auto-add and disable edit-mode entry.
|
|
164
|
+
const versionKey = $derived(page.url.searchParams.get('version'));
|
|
165
|
+
let versionRelease = $state<{ id: string; preview_key: string; name?: string } | null>(null);
|
|
166
|
+
|
|
167
|
+
// `?locale=` is the editor's preview override; it wins over the server-resolved
|
|
168
|
+
// `cms.locale` so locale switching works even when the consumer's `getLocale`
|
|
169
|
+
// doesn't honor the param (and on static-export sites where the server load
|
|
170
|
+
// can't be re-run). Falls back to `cms.locale` when no override is set.
|
|
171
|
+
const currentLocale = $derived(
|
|
172
|
+
page.url.searchParams.get('locale') ?? (page.data.cms as CmsPayload | undefined)?.locale ?? ''
|
|
173
|
+
);
|
|
174
|
+
const supportedLocales = $derived((page.data.cms as CmsPayload | undefined)?.locales ?? []);
|
|
175
|
+
|
|
176
|
+
// Sub-bar (DESIGN.md §2) hangs below the main bar in edit/pending modes,
|
|
177
|
+
// holding the contextual status pill + action buttons. Only the clean state
|
|
178
|
+
// pill remains in the main bar.
|
|
179
|
+
const subBarVisible = $derived(cmsStore.isEditing || counts.total > 0 || !!versionKey);
|
|
180
|
+
|
|
181
|
+
const previewUrl = $derived.by(() => {
|
|
182
|
+
if (versionKey) {
|
|
183
|
+
const url = new URL(page.url);
|
|
184
|
+
url.searchParams.delete('edit');
|
|
185
|
+
url.searchParams.delete('preview');
|
|
186
|
+
url.searchParams.set('version', versionKey);
|
|
187
|
+
return url.toString();
|
|
188
|
+
}
|
|
189
|
+
const key = previewKey;
|
|
190
|
+
if (!key) return '';
|
|
191
|
+
const url = new URL(page.url);
|
|
192
|
+
url.searchParams.delete('edit');
|
|
193
|
+
url.searchParams.set('preview', key);
|
|
194
|
+
return url.toString();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const currentScopes = (): CmsScopeEntry[] => {
|
|
198
|
+
const cms = page.data.cms as CmsPayload | undefined;
|
|
199
|
+
return cms ? Object.values(cms.scopes) : [];
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const currentEntriesRouteIds = (): string[] => {
|
|
203
|
+
const cms = page.data.cms as CmsPayload | undefined;
|
|
204
|
+
return cms ? Object.keys(cms.entries ?? {}) : [];
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const setPreviewParam = async (key: string | null, opts: { replace?: boolean } = {}) => {
|
|
208
|
+
const url = new URL(page.url);
|
|
209
|
+
const current = url.searchParams.get('preview');
|
|
210
|
+
url.searchParams.delete('edit');
|
|
211
|
+
if (key) {
|
|
212
|
+
if (current === key) return;
|
|
213
|
+
url.searchParams.set('preview', key);
|
|
214
|
+
} else {
|
|
215
|
+
if (!current) return;
|
|
216
|
+
url.searchParams.delete('preview');
|
|
217
|
+
}
|
|
218
|
+
const path = url.pathname + url.search + url.hash;
|
|
219
|
+
if (opts.replace) {
|
|
220
|
+
replaceState(path, page.state);
|
|
221
|
+
} else {
|
|
222
|
+
await goto(path, { keepFocus: true, noScroll: true });
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const setVersionParam = async (key: string | null, opts: { replace?: boolean } = {}) => {
|
|
227
|
+
const url = new URL(page.url);
|
|
228
|
+
const current = url.searchParams.get('version');
|
|
229
|
+
url.searchParams.delete('edit');
|
|
230
|
+
url.searchParams.delete('preview');
|
|
231
|
+
if (key) {
|
|
232
|
+
if (current === key) return;
|
|
233
|
+
url.searchParams.set('version', key);
|
|
234
|
+
} else {
|
|
235
|
+
if (!current) return;
|
|
236
|
+
url.searchParams.delete('version');
|
|
237
|
+
}
|
|
238
|
+
const path = url.pathname + url.search + url.hash;
|
|
239
|
+
if (opts.replace) {
|
|
240
|
+
replaceState(path, page.state);
|
|
241
|
+
} else {
|
|
242
|
+
await goto(path, { keepFocus: true, noScroll: true });
|
|
243
|
+
// Force the server load to re-fetch. SvelteKit's URL tracking
|
|
244
|
+
// doesn't always pick up our `?version=` access (it lives behind
|
|
245
|
+
// the `loadCms` helper) — visible on Exit, where we'd otherwise
|
|
246
|
+
// keep showing the past release's `page.data.cms.docs`.
|
|
247
|
+
await invalidateAll();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Set true while setLocaleParam(null) is mid-flight so beforeNavigate doesn't
|
|
252
|
+
// re-carry the old `?locale=` and undo the strip.
|
|
253
|
+
let clearingLocale = false;
|
|
254
|
+
|
|
255
|
+
// Override the locale for the editor preview. Pure client-side: the overlay
|
|
256
|
+
// effect re-fetches `${endpoint}/docs?locale=…` on the URL change, so this
|
|
257
|
+
// works whether or not the consumer's `getLocale(url)` honors `?locale=`
|
|
258
|
+
// (and on static-export sites with no server load to re-run). Like the
|
|
259
|
+
// other gating params, we strip the param when `null`.
|
|
260
|
+
const setLocaleParam = async (locale: string | null, opts: { replace?: boolean } = {}) => {
|
|
261
|
+
const url = new URL(page.url);
|
|
262
|
+
const current = url.searchParams.get('locale');
|
|
263
|
+
if (locale) {
|
|
264
|
+
if (current === locale) return;
|
|
265
|
+
url.searchParams.set('locale', locale);
|
|
266
|
+
} else {
|
|
267
|
+
if (!current) return;
|
|
268
|
+
url.searchParams.delete('locale');
|
|
269
|
+
}
|
|
270
|
+
const path = url.pathname + url.search + url.hash;
|
|
271
|
+
if (opts.replace) {
|
|
272
|
+
replaceState(path, page.state);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!locale) {
|
|
276
|
+
clearingLocale = true;
|
|
277
|
+
try {
|
|
278
|
+
await goto(path, { keepFocus: true, noScroll: true });
|
|
279
|
+
} finally {
|
|
280
|
+
clearingLocale = false;
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
await goto(path, { keepFocus: true, noScroll: true });
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Set true while exitVersionView is mid-flight so beforeNavigate doesn't
|
|
288
|
+
// re-inject `?version=` and undo the exit.
|
|
289
|
+
let exitingVersion = false;
|
|
290
|
+
|
|
291
|
+
const exitVersionView = async () => {
|
|
292
|
+
exitingVersion = true;
|
|
293
|
+
try {
|
|
294
|
+
await setVersionParam(null);
|
|
295
|
+
} finally {
|
|
296
|
+
exitingVersion = false;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// Resolve the `?version=` URL param to its `PublishedRelease` (so we know
|
|
301
|
+
// the id for regenerate). Re-fetched whenever the version key changes.
|
|
302
|
+
const fetchVersionRelease = async (key: string) => {
|
|
303
|
+
const res = await fetch(`${endpoint}/release/history`, { credentials: 'include' });
|
|
304
|
+
if (!res.ok) {
|
|
305
|
+
versionRelease = null;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const data = (await res.json()) as {
|
|
309
|
+
history: Array<{ id: string; preview_key: string; name?: string }>;
|
|
310
|
+
};
|
|
311
|
+
const match = data.history.find((r) => r.preview_key === key) ?? null;
|
|
312
|
+
versionRelease = match
|
|
313
|
+
? { id: match.id, preview_key: match.preview_key, name: match.name }
|
|
314
|
+
: null;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Push the URL-derived editor locale into the store so field components'
|
|
318
|
+
// `getValue`/`setValue` defaults route to the bucket the editor is actually
|
|
319
|
+
// previewing — even when the consumer's `getLocale(url)` doesn't honor the
|
|
320
|
+
// param. Without this, edits made in `?locale=es` would land in the `en`
|
|
321
|
+
// bucket and never appear.
|
|
322
|
+
$effect(() => {
|
|
323
|
+
cmsStore.setActiveLocale(currentLocale || null);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Tracks whether `fetchOpenRelease` has resolved at least once this session.
|
|
327
|
+
// Until then, the overlay-sync effect must NOT strip `?preview=` from the
|
|
328
|
+
// URL — `cmsStore.openRelease` is null pre-fetch, but that's "unknown",
|
|
329
|
+
// not "no draft". Without this gate, a hard refresh on `?preview=<key>`
|
|
330
|
+
// would treat the param as stale and replaceState it away.
|
|
331
|
+
let openReleaseFetched = $state(false);
|
|
332
|
+
|
|
333
|
+
// On mount and on subsequent URL/store changes: derive what to do (URL sync,
|
|
334
|
+
// overlay loads, clears) as a list of pure intents, then dispatch.
|
|
335
|
+
// Decision logic lives in `overlay-sync.ts` and is unit-tested separately.
|
|
336
|
+
$effect(() => {
|
|
337
|
+
// Synchronous reads so Svelte tracks these as effect dependencies — reads
|
|
338
|
+
// inside the async IIFE below run on a microtask and aren't tracked.
|
|
339
|
+
const intents = deriveOverlayIntents({
|
|
340
|
+
versionKey,
|
|
341
|
+
locale: currentLocale,
|
|
342
|
+
pageLocale: (page.data.cms as CmsPayload | undefined)?.locale ?? currentLocale,
|
|
343
|
+
openReleaseKey: cmsStore.openRelease?.preview_key ?? null,
|
|
344
|
+
openReleaseFetched,
|
|
345
|
+
previewParam: page.url.searchParams.get('preview')
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (intents.length === 0) return;
|
|
349
|
+
|
|
350
|
+
void (async () => {
|
|
351
|
+
for (const intent of intents) {
|
|
352
|
+
switch (intent.kind) {
|
|
353
|
+
case 'fetch-open-release':
|
|
354
|
+
await cmsStore.fetchOpenRelease(endpoint);
|
|
355
|
+
openReleaseFetched = true;
|
|
356
|
+
break;
|
|
357
|
+
case 'fetch-version-release':
|
|
358
|
+
await fetchVersionRelease(intent.versionKey);
|
|
359
|
+
break;
|
|
360
|
+
case 'set-preview-param':
|
|
361
|
+
await setPreviewParam(intent.value, { replace: true });
|
|
362
|
+
break;
|
|
363
|
+
case 'load-version-overlay':
|
|
364
|
+
// Don't null versionRelease here — `fetch-version-release` ran
|
|
365
|
+
// just before and populated it. Only non-version paths null it.
|
|
366
|
+
await Promise.all([
|
|
367
|
+
cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), null, {
|
|
368
|
+
reset: true,
|
|
369
|
+
versionKey: intent.versionKey,
|
|
370
|
+
locale: intent.locale
|
|
371
|
+
}),
|
|
372
|
+
cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), null, {
|
|
373
|
+
reset: true,
|
|
374
|
+
versionKey: intent.versionKey,
|
|
375
|
+
locale: intent.locale
|
|
376
|
+
}),
|
|
377
|
+
cmsStore.loadAndApplySiteOverlay(endpoint, null, { versionKey: intent.versionKey })
|
|
378
|
+
]);
|
|
379
|
+
break;
|
|
380
|
+
case 'load-overlay':
|
|
381
|
+
versionRelease = null;
|
|
382
|
+
await Promise.all([
|
|
383
|
+
cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), intent.previewKey, {
|
|
384
|
+
reset: true,
|
|
385
|
+
locale: intent.locale
|
|
386
|
+
}),
|
|
387
|
+
cmsStore.loadAndApplyEntriesOverlay(
|
|
388
|
+
endpoint,
|
|
389
|
+
currentEntriesRouteIds(),
|
|
390
|
+
intent.previewKey,
|
|
391
|
+
{ reset: true, locale: intent.locale }
|
|
392
|
+
),
|
|
393
|
+
cmsStore.loadAndApplySiteOverlay(endpoint, intent.previewKey)
|
|
394
|
+
]);
|
|
395
|
+
break;
|
|
396
|
+
case 'clear-overlay':
|
|
397
|
+
versionRelease = null;
|
|
398
|
+
cmsStore.clearOverlay();
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
})();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Carry the active gating params (`?version=` if in version-view, else
|
|
406
|
+
// `?preview=`, plus `?locale=` if set) into every internal navigation so
|
|
407
|
+
// the overlay and preview-locale survive link clicks (and any `goto()` in
|
|
408
|
+
// app code). We cancel the in-flight nav and re-issue with the params
|
|
409
|
+
// appended; already-correct nav targets pass through unchanged. Full-page
|
|
410
|
+
// unloads (`willUnload`) and external/hash-only navs bypass.
|
|
411
|
+
beforeNavigate((nav) => {
|
|
412
|
+
if (nav.type === 'leave' || nav.willUnload) return;
|
|
413
|
+
if (!nav.to) return;
|
|
414
|
+
if (nav.to.url.origin !== location.origin) return;
|
|
415
|
+
|
|
416
|
+
// While clearingLocale is true, the user intentionally stripped `?locale=`;
|
|
417
|
+
// don't re-carry the old value from `page.url` (which hasn't updated yet).
|
|
418
|
+
const localeParam = clearingLocale ? null : page.url.searchParams.get('locale');
|
|
419
|
+
const localeNeedsCarry = (target: URL) =>
|
|
420
|
+
!!localeParam && target.searchParams.get('locale') !== localeParam;
|
|
421
|
+
const carryLocaleOnto = (url: URL) => {
|
|
422
|
+
if (localeParam) url.searchParams.set('locale', localeParam);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
if (versionKey) {
|
|
426
|
+
const targetVersion = nav.to.url.searchParams.get('version');
|
|
427
|
+
// Target carries its own `?version=` (same or different — e.g. clicking
|
|
428
|
+
// View on another release): respect it, don't clobber. Locale still
|
|
429
|
+
// rides along if missing on target.
|
|
430
|
+
if (targetVersion) {
|
|
431
|
+
if (!localeNeedsCarry(nav.to.url)) return;
|
|
432
|
+
nav.cancel();
|
|
433
|
+
const url = new URL(nav.to.url);
|
|
434
|
+
carryLocaleOnto(url);
|
|
435
|
+
void goto(url.pathname + url.search + url.hash, {
|
|
436
|
+
keepFocus: true,
|
|
437
|
+
noScroll: true
|
|
438
|
+
});
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
// Target has no `?version=`. If we're exiting on purpose, let the nav
|
|
442
|
+
// proceed; otherwise it's a normal link click and we carry version over.
|
|
443
|
+
if (exitingVersion) return;
|
|
444
|
+
nav.cancel();
|
|
445
|
+
const url = new URL(nav.to.url);
|
|
446
|
+
url.searchParams.delete('preview');
|
|
447
|
+
url.searchParams.set('version', versionKey);
|
|
448
|
+
carryLocaleOnto(url);
|
|
449
|
+
void goto(url.pathname + url.search + url.hash, {
|
|
450
|
+
keepFocus: true,
|
|
451
|
+
noScroll: true
|
|
452
|
+
});
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Target is jumping to a `?version=` URL (e.g. View button from history
|
|
457
|
+
// panel) — let it through unmodified except for locale carry.
|
|
458
|
+
if (nav.to.url.searchParams.get('version')) {
|
|
459
|
+
if (!localeNeedsCarry(nav.to.url)) return;
|
|
460
|
+
nav.cancel();
|
|
461
|
+
const url = new URL(nav.to.url);
|
|
462
|
+
carryLocaleOnto(url);
|
|
463
|
+
void goto(url.pathname + url.search + url.hash, {
|
|
464
|
+
keepFocus: true,
|
|
465
|
+
noScroll: true
|
|
466
|
+
});
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const key = cmsStore.openRelease?.preview_key ?? null;
|
|
471
|
+
const previewMatches = !key || nav.to.url.searchParams.get('preview') === key;
|
|
472
|
+
if (previewMatches && !localeNeedsCarry(nav.to.url)) return;
|
|
473
|
+
|
|
474
|
+
nav.cancel();
|
|
475
|
+
const url = new URL(nav.to.url);
|
|
476
|
+
if (key) url.searchParams.set('preview', key);
|
|
477
|
+
carryLocaleOnto(url);
|
|
478
|
+
void goto(url.pathname + url.search + url.hash, {
|
|
479
|
+
keepFocus: true,
|
|
480
|
+
noScroll: true
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Re-apply the overlay on every internal navigation. SvelteKit may strip
|
|
485
|
+
// the gating param when nav targets aren't preserve-params links, so we
|
|
486
|
+
// reapply it via replaceState too. Merge (not reset) so unchanged-layout
|
|
487
|
+
// overlays keep showing while new-page-scope fetches resolve.
|
|
488
|
+
afterNavigate(() => {
|
|
489
|
+
if (versionKey) {
|
|
490
|
+
const url = new URL(page.url);
|
|
491
|
+
if (url.searchParams.get('version') !== versionKey) {
|
|
492
|
+
url.searchParams.delete('preview');
|
|
493
|
+
url.searchParams.set('version', versionKey);
|
|
494
|
+
replaceState(url.pathname + url.search + url.hash, page.state);
|
|
495
|
+
}
|
|
496
|
+
// `reset` so a nav from one release to another wipes the prior
|
|
497
|
+
// snapshot's overlay before merging in the new one — otherwise
|
|
498
|
+
// stale fields from the previous release leak through on scopes
|
|
499
|
+
// the new release doesn't touch.
|
|
500
|
+
void cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), null, {
|
|
501
|
+
reset: true,
|
|
502
|
+
versionKey,
|
|
503
|
+
locale: currentLocale
|
|
504
|
+
});
|
|
505
|
+
void cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), null, {
|
|
506
|
+
reset: true,
|
|
507
|
+
versionKey,
|
|
508
|
+
locale: currentLocale
|
|
509
|
+
});
|
|
510
|
+
void cmsStore.loadAndApplySiteOverlay(endpoint, null, { versionKey });
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const key = cmsStore.openRelease?.preview_key ?? null;
|
|
515
|
+
if (!key) {
|
|
516
|
+
// No draft and no version → clear any overlay left over from a
|
|
517
|
+
// previous version-mode visit. Without this, exiting version view
|
|
518
|
+
// leaves the past release's content stuck on top of the freshly
|
|
519
|
+
// re-fetched `page.data.cms.docs`.
|
|
520
|
+
cmsStore.clearOverlay();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const url = new URL(page.url);
|
|
524
|
+
if (url.searchParams.get('preview') !== key) {
|
|
525
|
+
url.searchParams.set('preview', key);
|
|
526
|
+
replaceState(url.pathname + url.search + url.hash, page.state);
|
|
527
|
+
}
|
|
528
|
+
void cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), key, { locale: currentLocale });
|
|
529
|
+
void cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), key, {
|
|
530
|
+
locale: currentLocale
|
|
531
|
+
});
|
|
532
|
+
void cmsStore.loadAndApplySiteOverlay(endpoint, key);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
const onSave = async () => {
|
|
536
|
+
const result = await cmsStore.save(endpoint);
|
|
537
|
+
if (!result.ok) return;
|
|
538
|
+
cmsStore.isEditing = false;
|
|
539
|
+
seoOpen = false;
|
|
540
|
+
const newKey = result.release?.preview_key ?? null;
|
|
541
|
+
if (newKey) await setPreviewParam(newKey, { replace: true });
|
|
542
|
+
await Promise.all([
|
|
543
|
+
cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), newKey, {
|
|
544
|
+
locale: currentLocale
|
|
545
|
+
}),
|
|
546
|
+
cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), newKey, {
|
|
547
|
+
locale: currentLocale
|
|
548
|
+
}),
|
|
549
|
+
cmsStore.loadAndApplySiteOverlay(endpoint, newKey)
|
|
550
|
+
]);
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const onToggleEdit = () => {
|
|
554
|
+
// Past releases are read-only — no edit, no save, no publish flow.
|
|
555
|
+
if (versionKey) return;
|
|
556
|
+
if (cmsStore.isEditing) {
|
|
557
|
+
if (cmsStore.isDirty && !confirm('Discard unsaved changes?')) return;
|
|
558
|
+
cmsStore.clearDrafts();
|
|
559
|
+
cmsStore.isEditing = false;
|
|
560
|
+
seoOpen = false;
|
|
561
|
+
} else {
|
|
562
|
+
cmsStore.isEditing = true;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const onOpenPublish = () => {
|
|
567
|
+
if (counts.total === 0) return;
|
|
568
|
+
closeAllPanels();
|
|
569
|
+
publishError = null;
|
|
570
|
+
publishOpen = true;
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const onOpenPages = () => {
|
|
574
|
+
closeAllPanels();
|
|
575
|
+
pagesOpen = true;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const onOpenMedia = () => {
|
|
579
|
+
closeAllPanels();
|
|
580
|
+
mediaOpen = true;
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const onOpenLocales = () => {
|
|
584
|
+
closeAllPanels();
|
|
585
|
+
localesOpen = true;
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const onOpenHistory = () => {
|
|
589
|
+
closeAllPanels();
|
|
590
|
+
historyOpen = true;
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const onConfirmPublish = async (name: string | undefined) => {
|
|
594
|
+
publishing = true;
|
|
595
|
+
publishError = null;
|
|
596
|
+
try {
|
|
597
|
+
const body: { name?: string } = {};
|
|
598
|
+
if (name) body.name = name;
|
|
599
|
+
const res = await fetch(`${endpoint}/release/publish`, {
|
|
600
|
+
credentials: 'include',
|
|
601
|
+
method: 'POST',
|
|
602
|
+
headers: { 'content-type': 'application/json' },
|
|
603
|
+
body: JSON.stringify(body)
|
|
604
|
+
});
|
|
605
|
+
if (!res.ok) {
|
|
606
|
+
publishError = (await res.text()) || 'Could not publish release.';
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
cmsStore.setOpenRelease(null);
|
|
610
|
+
publishOpen = false;
|
|
611
|
+
await setPreviewParam(null, { replace: true });
|
|
612
|
+
// Refetch `/docs` and `/pages` to pick up the just-published content
|
|
613
|
+
// as overlay. `page.data.cms.{docs,entries}` was loaded against the
|
|
614
|
+
// pre-publish published state and we can't re-run server load on
|
|
615
|
+
// static-export sites; the fresh overlays mask that staleness.
|
|
616
|
+
await Promise.all([
|
|
617
|
+
cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), null, {
|
|
618
|
+
reset: true,
|
|
619
|
+
locale: currentLocale
|
|
620
|
+
}),
|
|
621
|
+
cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), null, {
|
|
622
|
+
reset: true,
|
|
623
|
+
locale: currentLocale
|
|
624
|
+
}),
|
|
625
|
+
cmsStore.loadAndApplySiteOverlay(endpoint, null)
|
|
626
|
+
]);
|
|
627
|
+
} finally {
|
|
628
|
+
publishing = false;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
const refreshAfterReleaseChange = async () => {
|
|
633
|
+
const key = cmsStore.openRelease?.preview_key ?? null;
|
|
634
|
+
await setPreviewParam(key, { replace: true });
|
|
635
|
+
await Promise.all([
|
|
636
|
+
cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), key, {
|
|
637
|
+
reset: true,
|
|
638
|
+
locale: currentLocale
|
|
639
|
+
}),
|
|
640
|
+
cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), key, {
|
|
641
|
+
reset: true,
|
|
642
|
+
locale: currentLocale
|
|
643
|
+
}),
|
|
644
|
+
cmsStore.loadAndApplySiteOverlay(endpoint, key)
|
|
645
|
+
]);
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
// View toggles + bar position. Local state — Highlight/DraftMarkers/Grid have
|
|
649
|
+
// no functional effect until step 7's editing affordances land; Preview-as is
|
|
650
|
+
// also visual-only for now. Position swaps the bar between top and bottom.
|
|
651
|
+
let highlightSlots = $state(true);
|
|
652
|
+
let showDraftMarkers = $state(true);
|
|
653
|
+
let showGrid = $state(false);
|
|
654
|
+
let previewAs = $state<'desktop' | 'tablet' | 'mobile' | 'signed-out'>('desktop');
|
|
655
|
+
let barPosition = $state<'top' | 'bottom'>('top');
|
|
656
|
+
|
|
657
|
+
// Items targeting the current page URL in the active editor locale. Used
|
|
658
|
+
// for "Discard changes on this page" and the visibility flag for that
|
|
659
|
+
// button. Cross-locale isn't intended here — the editor's intent is
|
|
660
|
+
// "discard what I'm currently editing", not "every locale's edits".
|
|
661
|
+
const currentPageItems = $derived(
|
|
662
|
+
(cmsStore.openRelease?.items ?? []).filter((item) => {
|
|
663
|
+
if (item.kind === 'layout' || item.kind === 'site') return false;
|
|
664
|
+
if (item.locale !== currentLocale) return false;
|
|
665
|
+
try {
|
|
666
|
+
return resolveRouteUrl(item.routeId, item.params) === page.url.pathname;
|
|
667
|
+
} catch {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
})
|
|
671
|
+
);
|
|
672
|
+
const hasCurrentPageDrafts = $derived(currentPageItems.length > 0);
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Current page identity + creatable config when it has owned params (i.e.
|
|
676
|
+
* is a parameterized template instance). Drives the `Page → Delete Page…`
|
|
677
|
+
* menu's enable state — static pages and unconfigured routes can't be
|
|
678
|
+
* deleted from the data layer alone.
|
|
679
|
+
*/
|
|
680
|
+
const currentDeletableConfig = $derived.by((): CmsCreatablePageConfigWithRouteId | null => {
|
|
681
|
+
const cms = page.data.cms as CmsPayload | undefined;
|
|
682
|
+
const pp = cms?.page;
|
|
683
|
+
if (!pp) return null;
|
|
684
|
+
if (Object.keys(pp.params).length === 0) return null;
|
|
685
|
+
const config = pages[pp.routeId];
|
|
686
|
+
if (!config) return null;
|
|
687
|
+
return isCreatable(config) ? config : null;
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
const paramsEqual = (a: Record<string, string>, b: Record<string, string>): boolean => {
|
|
691
|
+
const aKeys = Object.keys(a);
|
|
692
|
+
if (aKeys.length !== Object.keys(b).length) return false;
|
|
693
|
+
for (const k of aKeys) if (a[k] !== b[k]) return false;
|
|
694
|
+
return true;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const isCurrentPage = (routeId: string, params: Record<string, string>): boolean => {
|
|
698
|
+
const cms = page.data.cms as CmsPayload | undefined;
|
|
699
|
+
const pp = cms?.page;
|
|
700
|
+
return !!pp && pp.routeId === routeId && paramsEqual(pp.params, params);
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Selectable redirect targets for the delete dialog — every published,
|
|
705
|
+
* non-tombstoned, non-draft entry across all routes. Sourced from the
|
|
706
|
+
* cached `/pages` snapshot fetched by `requestDelete`.
|
|
707
|
+
*/
|
|
708
|
+
const redirectTargets = $derived.by((): RedirectTarget[] => {
|
|
709
|
+
const out: RedirectTarget[] = [];
|
|
710
|
+
for (const r of deleteRoutes) {
|
|
711
|
+
for (const e of r.entries) {
|
|
712
|
+
if (e.isDraft || e.isDeletePending || e.redirectTo || e.gone) continue;
|
|
713
|
+
let url: string;
|
|
714
|
+
try {
|
|
715
|
+
url = resolveRouteUrl(r.routeId, e.params);
|
|
716
|
+
} catch {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
out.push({ url, routeId: r.routeId, params: e.params });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
out.sort((a, b) => a.url.localeCompare(b.url));
|
|
723
|
+
return out;
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Map of source-url → terminal-redirect-url across published tombstones
|
|
728
|
+
* plus the working release. Lets the dialog auto-flatten `A → B → C` to
|
|
729
|
+
* `A → C` at submit time so we don't write redirect chains.
|
|
730
|
+
*/
|
|
731
|
+
const redirectsByUrl = $derived.by((): Record<string, string> => {
|
|
732
|
+
const out: Record<string, string> = {};
|
|
733
|
+
for (const r of deleteRoutes) {
|
|
734
|
+
for (const e of r.entries) {
|
|
735
|
+
if (!e.redirectTo) continue;
|
|
736
|
+
try {
|
|
737
|
+
out[resolveRouteUrl(r.routeId, e.params)] = e.redirectTo;
|
|
738
|
+
} catch {
|
|
739
|
+
/* skip */
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// Only consider release items in the active editor locale: redirect
|
|
744
|
+
// flattening is a runtime URL-routing concern, and at runtime the
|
|
745
|
+
// locale's tombstone is what serves the request. Mixing locales here
|
|
746
|
+
// would let an `es`-only staged redirect bend the chain shown to an
|
|
747
|
+
// `en` editor staging a different redirect.
|
|
748
|
+
for (const item of cmsStore.openRelease?.items ?? []) {
|
|
749
|
+
if (item.kind !== 'page-delete') continue;
|
|
750
|
+
if (item.outcome?.kind !== 'redirect') continue;
|
|
751
|
+
if (item.locale !== currentLocale) continue;
|
|
752
|
+
try {
|
|
753
|
+
out[resolveRouteUrl(item.routeId, item.params)] = item.outcome.to;
|
|
754
|
+
} catch {
|
|
755
|
+
/* skip */
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return out;
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Open the delete dialog targeting `(routeId, params)`. Both the
|
|
763
|
+
* `pages-panel` row button and the `Page → Delete Page…` menu route
|
|
764
|
+
* through here. Re-fetches `/pages` so target/chain data and the entry's
|
|
765
|
+
* draft flag are fresh.
|
|
766
|
+
*/
|
|
767
|
+
const requestDelete = async (
|
|
768
|
+
routeId: string,
|
|
769
|
+
params: Record<string, string>,
|
|
770
|
+
isDraftHint = false
|
|
771
|
+
) => {
|
|
772
|
+
let url: string;
|
|
773
|
+
try {
|
|
774
|
+
url = resolveRouteUrl(routeId, params);
|
|
775
|
+
} catch {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
let isDraft = isDraftHint;
|
|
780
|
+
try {
|
|
781
|
+
// `?locale=` so the published-state lookup matches what the editor
|
|
782
|
+
// is currently previewing — a row may be a published draft in `en`
|
|
783
|
+
// and not yet exist in `es`, and the delete dialog's flow must reflect
|
|
784
|
+
// the locale being acted on.
|
|
785
|
+
const res = await fetch(`${endpoint}/pages?locale=${encodeURIComponent(currentLocale)}`, {
|
|
786
|
+
credentials: 'include'
|
|
787
|
+
});
|
|
788
|
+
if (res.ok) {
|
|
789
|
+
const data = (await res.json()) as { routes: WirePageRoute[] };
|
|
790
|
+
deleteRoutes = data.routes;
|
|
791
|
+
const route = data.routes.find((r) => r.routeId === routeId);
|
|
792
|
+
const entry = route?.entries.find((e) => paramsEqual(e.params, params));
|
|
793
|
+
if (entry) isDraft = entry.isDraft ?? false;
|
|
794
|
+
}
|
|
795
|
+
} catch {
|
|
796
|
+
/* keep stale `deleteRoutes` and the caller's hint */
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
deleteTarget = { routeId, params, url, isDraft };
|
|
800
|
+
deleteError = null;
|
|
801
|
+
deleteDialogOpen = true;
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
const onDeletePage = () => {
|
|
805
|
+
const cms = page.data.cms as CmsPayload | undefined;
|
|
806
|
+
const pp = cms?.page;
|
|
807
|
+
if (!pp || !currentDeletableConfig) return;
|
|
808
|
+
closeAllPanels();
|
|
809
|
+
void requestDelete(pp.routeId, pp.params);
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
const onDeleteConfirm = async (mode: DeletePageMode, target?: string) => {
|
|
813
|
+
if (!deleteTarget) return;
|
|
814
|
+
const { routeId, params, isDraft } = deleteTarget;
|
|
815
|
+
const onCurrent = isCurrentPage(routeId, params);
|
|
816
|
+
deleting = true;
|
|
817
|
+
deleteError = null;
|
|
818
|
+
try {
|
|
819
|
+
let outcome: PageDeleteOutcome | undefined;
|
|
820
|
+
if (mode === 'gone') outcome = { kind: 'gone' };
|
|
821
|
+
else if (mode === 'redirect' && target) outcome = { kind: 'redirect', to: target };
|
|
822
|
+
const body: Record<string, unknown> = { routeId, locale: currentLocale, params };
|
|
823
|
+
// Drafts always hard-discard regardless of mode — the dialog gates
|
|
824
|
+
// gone/redirect off when isDraft, but defend in depth.
|
|
825
|
+
if (!isDraft && outcome) body.outcome = outcome;
|
|
826
|
+
const res = await fetch(`${endpoint}/pages`, {
|
|
827
|
+
method: 'DELETE',
|
|
828
|
+
credentials: 'include',
|
|
829
|
+
headers: { 'content-type': 'application/json' },
|
|
830
|
+
body: JSON.stringify(body)
|
|
831
|
+
});
|
|
832
|
+
if (!res.ok) {
|
|
833
|
+
deleteError = 'Could not delete page.';
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
deleteDialogOpen = false;
|
|
837
|
+
deleteTarget = null;
|
|
838
|
+
await cmsStore.fetchOpenRelease(endpoint);
|
|
839
|
+
if (!onCurrent) await refreshAfterReleaseChange();
|
|
840
|
+
} finally {
|
|
841
|
+
deleting = false;
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
const onCopyPreviewLink = async () => {
|
|
846
|
+
if (!previewUrl) return;
|
|
847
|
+
try {
|
|
848
|
+
await navigator.clipboard.writeText(previewUrl);
|
|
849
|
+
} catch {
|
|
850
|
+
/* clipboard may be unavailable; swallow silently */
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
const onOpenInNewTab = () => {
|
|
855
|
+
const url = previewUrl || page.url.toString();
|
|
856
|
+
window.open(url, '_blank', 'noopener');
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
const onRegeneratePreviewKey = async () => {
|
|
860
|
+
if (versionKey && versionRelease) {
|
|
861
|
+
const res = await fetch(`${endpoint}/release/history/${versionRelease.id}/preview-key`, {
|
|
862
|
+
method: 'POST',
|
|
863
|
+
credentials: 'include'
|
|
864
|
+
});
|
|
865
|
+
if (!res.ok) return;
|
|
866
|
+
const data = (await res.json()) as { preview_key: string };
|
|
867
|
+
versionRelease = { ...versionRelease, preview_key: data.preview_key };
|
|
868
|
+
await setVersionParam(data.preview_key, { replace: true });
|
|
869
|
+
await Promise.all([
|
|
870
|
+
cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), null, {
|
|
871
|
+
reset: true,
|
|
872
|
+
versionKey: data.preview_key,
|
|
873
|
+
locale: currentLocale
|
|
874
|
+
}),
|
|
875
|
+
cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), null, {
|
|
876
|
+
reset: true,
|
|
877
|
+
versionKey: data.preview_key,
|
|
878
|
+
locale: currentLocale
|
|
879
|
+
})
|
|
880
|
+
]);
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const res = await fetch(`${endpoint}/release/preview-key`, {
|
|
885
|
+
method: 'POST',
|
|
886
|
+
credentials: 'include'
|
|
887
|
+
});
|
|
888
|
+
if (!res.ok) return;
|
|
889
|
+
await cmsStore.fetchOpenRelease(endpoint);
|
|
890
|
+
await refreshAfterReleaseChange();
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
const onDiscardAllChanges = async () => {
|
|
894
|
+
if (!confirm('Discard all unpublished changes in your working copy?')) return;
|
|
895
|
+
const res = await fetch(`${endpoint}/release/discard`, {
|
|
896
|
+
method: 'POST',
|
|
897
|
+
credentials: 'include'
|
|
898
|
+
});
|
|
899
|
+
if (!res.ok) return;
|
|
900
|
+
cmsStore.setOpenRelease(null);
|
|
901
|
+
cmsStore.clearDrafts();
|
|
902
|
+
await refreshAfterReleaseChange();
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
const onDiscardCurrentPage = async () => {
|
|
906
|
+
if (currentPageItems.length === 0) return;
|
|
907
|
+
if (!confirm('Discard unpublished changes on this page?')) return;
|
|
908
|
+
for (const item of currentPageItems) {
|
|
909
|
+
if (item.kind !== 'page') continue;
|
|
910
|
+
await fetch(`${endpoint}/release/items`, {
|
|
911
|
+
method: 'DELETE',
|
|
912
|
+
credentials: 'include',
|
|
913
|
+
headers: { 'content-type': 'application/json' },
|
|
914
|
+
body: JSON.stringify({
|
|
915
|
+
kind: 'page',
|
|
916
|
+
routeId: item.routeId,
|
|
917
|
+
locale: item.locale,
|
|
918
|
+
params: item.params
|
|
919
|
+
})
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
await cmsStore.fetchOpenRelease(endpoint);
|
|
923
|
+
await refreshAfterReleaseChange();
|
|
924
|
+
cmsStore.isEditing = false;
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// "Publish this page…" and "Review & publish…" open the same modal; the
|
|
928
|
+
// user filters down to the current page via the per-row checkboxes.
|
|
929
|
+
const onPublishThisPage = onOpenPublish;
|
|
930
|
+
|
|
931
|
+
const onNewFromTemplate = (config: CmsCreatablePageConfigWithRouteId) => {
|
|
932
|
+
openNewPageDialog(config);
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
// "New page…" always opens the chooser.
|
|
936
|
+
const onNewPage = () => {
|
|
937
|
+
if (creatablePages.length === 0) return;
|
|
938
|
+
closeAllPanels();
|
|
939
|
+
chooserOpen = true;
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
const onHideBar = () => onClose();
|
|
943
|
+
const onShareLink = () => {
|
|
944
|
+
closeAllPanels();
|
|
945
|
+
shareLinkOpen = true;
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
const onOpenSeo = () => {
|
|
949
|
+
closeAllPanels();
|
|
950
|
+
seoOpen = true;
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
// SEO edits are their own pending change — flush metadata drafts to the
|
|
954
|
+
// working copy without entering or exiting page edit mode.
|
|
955
|
+
const onSaveSeo = async () => {
|
|
956
|
+
const result = await cmsStore.save(endpoint);
|
|
957
|
+
if (!result.ok) return;
|
|
958
|
+
seoOpen = false;
|
|
959
|
+
const newKey = result.release?.preview_key ?? null;
|
|
960
|
+
if (newKey) await setPreviewParam(newKey, { replace: true });
|
|
961
|
+
await Promise.all([
|
|
962
|
+
cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), newKey, {
|
|
963
|
+
locale: currentLocale
|
|
964
|
+
}),
|
|
965
|
+
cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), newKey, {
|
|
966
|
+
locale: currentLocale
|
|
967
|
+
}),
|
|
968
|
+
cmsStore.loadAndApplySiteOverlay(endpoint, newKey)
|
|
969
|
+
]);
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
const onOpenSiteSettings = () => {
|
|
973
|
+
closeAllPanels();
|
|
974
|
+
siteSettingsOpen = true;
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// Mirrors `onSaveSeo`: flush the site-settings draft as part of the open
|
|
978
|
+
// release, refresh overlays so the just-saved values show via the
|
|
979
|
+
// preview-overlay path on the live page.
|
|
980
|
+
const onSaveSiteSettings = async () => {
|
|
981
|
+
const result = await cmsStore.save(endpoint);
|
|
982
|
+
if (!result.ok) return;
|
|
983
|
+
siteSettingsOpen = false;
|
|
984
|
+
const newKey = result.release?.preview_key ?? null;
|
|
985
|
+
if (newKey) await setPreviewParam(newKey, { replace: true });
|
|
986
|
+
await Promise.all([
|
|
987
|
+
cmsStore.loadAndApplyOverlay(endpoint, currentScopes(), newKey, {
|
|
988
|
+
locale: currentLocale
|
|
989
|
+
}),
|
|
990
|
+
cmsStore.loadAndApplyEntriesOverlay(endpoint, currentEntriesRouteIds(), newKey, {
|
|
991
|
+
locale: currentLocale
|
|
992
|
+
}),
|
|
993
|
+
cmsStore.loadAndApplySiteOverlay(endpoint, newKey)
|
|
994
|
+
]);
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
const onShowKeyboardShortcuts = () => {
|
|
998
|
+
closeAllPanels();
|
|
999
|
+
shortcutsOpen = true;
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
// Global keyboard shortcuts (DESIGN.md §9). Single-letter keys only fire
|
|
1003
|
+
// when no input is focused; modifier-keyed shortcuts always fire.
|
|
1004
|
+
$effect(() => {
|
|
1005
|
+
const isEditableTarget = (el: EventTarget | null): boolean => {
|
|
1006
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
1007
|
+
const tag = el.tagName;
|
|
1008
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
|
|
1009
|
+
if (el.isContentEditable) return true;
|
|
1010
|
+
return false;
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
const handler = (e: KeyboardEvent) => {
|
|
1014
|
+
const meta = e.metaKey || e.ctrlKey;
|
|
1015
|
+
const editable = isEditableTarget(e.target);
|
|
1016
|
+
|
|
1017
|
+
if (meta) {
|
|
1018
|
+
const k = e.key.toLowerCase();
|
|
1019
|
+
if (k === 's' && cmsStore.isEditing) {
|
|
1020
|
+
e.preventDefault();
|
|
1021
|
+
void onSave();
|
|
1022
|
+
} else if (k === 'e') {
|
|
1023
|
+
e.preventDefault();
|
|
1024
|
+
onToggleEdit();
|
|
1025
|
+
} else if (k === 'p') {
|
|
1026
|
+
e.preventDefault();
|
|
1027
|
+
onOpenPublish();
|
|
1028
|
+
} else if (k === 'k') {
|
|
1029
|
+
e.preventDefault();
|
|
1030
|
+
if (pagesOpen) pagesOpen = false;
|
|
1031
|
+
else onOpenPages();
|
|
1032
|
+
} else if (k === 'm') {
|
|
1033
|
+
e.preventDefault();
|
|
1034
|
+
if (mediaOpen) mediaOpen = false;
|
|
1035
|
+
else onOpenMedia();
|
|
1036
|
+
} else if (k === 'h') {
|
|
1037
|
+
e.preventDefault();
|
|
1038
|
+
if (historyOpen) historyOpen = false;
|
|
1039
|
+
else onOpenHistory();
|
|
1040
|
+
} else if (k === 'i') {
|
|
1041
|
+
e.preventDefault();
|
|
1042
|
+
if (seoOpen) seoOpen = false;
|
|
1043
|
+
else onOpenSeo();
|
|
1044
|
+
} else if (k === 'l' && supportedLocales.length > 1) {
|
|
1045
|
+
e.preventDefault();
|
|
1046
|
+
if (localesOpen) localesOpen = false;
|
|
1047
|
+
else onOpenLocales();
|
|
1048
|
+
} else if (k === 'n') {
|
|
1049
|
+
e.preventDefault();
|
|
1050
|
+
onNewPage();
|
|
1051
|
+
} else if (k === '.') {
|
|
1052
|
+
e.preventDefault();
|
|
1053
|
+
onClose();
|
|
1054
|
+
} else if (k === ',') {
|
|
1055
|
+
e.preventDefault();
|
|
1056
|
+
if (siteSettingsOpen) siteSettingsOpen = false;
|
|
1057
|
+
else onOpenSiteSettings();
|
|
1058
|
+
} else if (e.key === 'Enter') {
|
|
1059
|
+
e.preventDefault();
|
|
1060
|
+
onOpenInNewTab();
|
|
1061
|
+
}
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// Single-letter shortcuts gated on no input being focused.
|
|
1066
|
+
if (editable) return;
|
|
1067
|
+
if (e.key === 'Escape' && cmsStore.isEditing && !anyPanelOpen) {
|
|
1068
|
+
// When a panel is open, Escape closes the panel (each panel
|
|
1069
|
+
// owns its own listener); the bar should not also exit edit
|
|
1070
|
+
// mode out from under it.
|
|
1071
|
+
e.preventDefault();
|
|
1072
|
+
onToggleEdit();
|
|
1073
|
+
} else if (e.key === '/') {
|
|
1074
|
+
e.preventDefault();
|
|
1075
|
+
if (pagesOpen) pagesOpen = false;
|
|
1076
|
+
else onOpenPages();
|
|
1077
|
+
} else if (e.key === '?') {
|
|
1078
|
+
e.preventDefault();
|
|
1079
|
+
onShowKeyboardShortcuts();
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
window.addEventListener('keydown', handler);
|
|
1083
|
+
return () => window.removeEventListener('keydown', handler);
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
const openNewPageDialog = (config: CmsCreatablePageConfigWithRouteId) => {
|
|
1087
|
+
closeAllPanels();
|
|
1088
|
+
newDialog = config;
|
|
1089
|
+
newPageError = null;
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
const onRequestDuplicate = async (
|
|
1093
|
+
config: CmsCreatablePageConfigWithRouteId,
|
|
1094
|
+
sourceParams: Record<string, string>
|
|
1095
|
+
) => {
|
|
1096
|
+
const qs = new URLSearchParams({
|
|
1097
|
+
kind: 'page',
|
|
1098
|
+
routeId: config.routeId,
|
|
1099
|
+
params: JSON.stringify(sourceParams)
|
|
1100
|
+
});
|
|
1101
|
+
const previewKey = cmsStore.openRelease?.preview_key;
|
|
1102
|
+
if (previewKey) qs.set('preview', previewKey);
|
|
1103
|
+
const res = await fetch(`${endpoint}/docs?${qs}`, { credentials: 'include' });
|
|
1104
|
+
const sourceContents: Record<string, unknown> = res.ok
|
|
1105
|
+
? ((await res.json()) as { contents: Record<string, unknown> }).contents
|
|
1106
|
+
: {};
|
|
1107
|
+
const { metadata: meta, ...rest } = sourceContents as { metadata?: unknown } & Record<
|
|
1108
|
+
string,
|
|
1109
|
+
unknown
|
|
1110
|
+
>;
|
|
1111
|
+
const sourceMetadata =
|
|
1112
|
+
meta && typeof meta === 'object' && !Array.isArray(meta)
|
|
1113
|
+
? (meta as Record<string, unknown>)
|
|
1114
|
+
: {};
|
|
1115
|
+
closeAllPanels();
|
|
1116
|
+
duplicateSource = { tree: rest, metadata: sourceMetadata };
|
|
1117
|
+
newDialog = config;
|
|
1118
|
+
newPageError = null;
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
const closeNewPageDialog = () => {
|
|
1122
|
+
if (newPageCreating) return;
|
|
1123
|
+
newDialog = null;
|
|
1124
|
+
newPageError = null;
|
|
1125
|
+
duplicateSource = null;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
const handleCreate = async (rawValues: Record<string, string>) => {
|
|
1129
|
+
const target = newDialog;
|
|
1130
|
+
if (!target) return;
|
|
1131
|
+
newPageCreating = true;
|
|
1132
|
+
newPageError = null;
|
|
1133
|
+
try {
|
|
1134
|
+
let transformed: { params: Record<string, string>; metadata?: Record<string, unknown> };
|
|
1135
|
+
try {
|
|
1136
|
+
transformed = target.transform(rawValues);
|
|
1137
|
+
} catch {
|
|
1138
|
+
newPageError = 'Could not derive params from input.';
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const { params: newParams, metadata: transformMeta = {} } = transformed;
|
|
1142
|
+
|
|
1143
|
+
// Duplicating: keep the source's metadata and content as the base,
|
|
1144
|
+
// then let the transform's output (e.g. the new title) override on
|
|
1145
|
+
// matching keys. Without this merge a duplicated room would lose its
|
|
1146
|
+
// description, price, etc.
|
|
1147
|
+
const dup = duplicateSource;
|
|
1148
|
+
const metadata = dup ? { ...dup.metadata, ...transformMeta } : transformMeta;
|
|
1149
|
+
|
|
1150
|
+
const createRes = await fetch(`${endpoint}/pages`, {
|
|
1151
|
+
method: 'POST',
|
|
1152
|
+
credentials: 'include',
|
|
1153
|
+
headers: { 'content-type': 'application/json' },
|
|
1154
|
+
body: JSON.stringify({
|
|
1155
|
+
routeId: target.routeId,
|
|
1156
|
+
locale: currentLocale,
|
|
1157
|
+
params: newParams,
|
|
1158
|
+
metadata
|
|
1159
|
+
})
|
|
1160
|
+
});
|
|
1161
|
+
if (createRes.status === 409) {
|
|
1162
|
+
newPageError = 'A page with these values already exists.';
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
if (!createRes.ok) {
|
|
1166
|
+
newPageError = 'Could not create page.';
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (dup && Object.keys(dup.tree).length > 0) {
|
|
1171
|
+
await fetch(`${endpoint}/release/items`, {
|
|
1172
|
+
credentials: 'include',
|
|
1173
|
+
method: 'POST',
|
|
1174
|
+
headers: { 'content-type': 'application/json' },
|
|
1175
|
+
body: JSON.stringify({
|
|
1176
|
+
items: [
|
|
1177
|
+
{
|
|
1178
|
+
kind: 'page',
|
|
1179
|
+
routeId: target.routeId,
|
|
1180
|
+
locale: currentLocale,
|
|
1181
|
+
params: newParams,
|
|
1182
|
+
tree: dup.tree
|
|
1183
|
+
}
|
|
1184
|
+
]
|
|
1185
|
+
})
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
await cmsStore.fetchOpenRelease(endpoint);
|
|
1190
|
+
const key = cmsStore.openRelease?.preview_key ?? null;
|
|
1191
|
+
const url = new URL(resolveRouteUrl(target.routeId, newParams), page.url.origin);
|
|
1192
|
+
if (key) url.searchParams.set('preview', key);
|
|
1193
|
+
newDialog = null;
|
|
1194
|
+
duplicateSource = null;
|
|
1195
|
+
pagesOpen = false;
|
|
1196
|
+
cmsStore.isEditing = true;
|
|
1197
|
+
await goto(url, { keepFocus: true, noScroll: true });
|
|
1198
|
+
} finally {
|
|
1199
|
+
newPageCreating = false;
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
</script>
|
|
1203
|
+
|
|
1204
|
+
<CssRoot>
|
|
1205
|
+
<div style:--cms-panel-top={subBarVisible && barPosition === 'top' ? '112px' : '72px'}>
|
|
1206
|
+
<!-- Shadow ghost: same shape and position as the bar pill but at a lower
|
|
1207
|
+
z-index than the sub-bar (9998) and panels (9998). The pill itself
|
|
1208
|
+
carries no shadow, so the bar's drop shadow is occluded by anything
|
|
1209
|
+
sitting underneath the bar instead of being painted over the top. -->
|
|
1210
|
+
<div
|
|
1211
|
+
aria-hidden="true"
|
|
1212
|
+
class="vela:fixed vela:left-1/2 vela:-translate-x-1/2 vela:z-[9997]
|
|
1213
|
+
vela:w-full vela:max-w-[560px] vela:mx-4 vela:sm:mx-auto
|
|
1214
|
+
vela:h-12 vela:rounded-full
|
|
1215
|
+
vela:shadow-[0_8px_24px_rgba(0,0,0,0.25)]
|
|
1216
|
+
{barPosition === 'bottom' ? 'vela:bottom-4' : 'vela:top-4'}"
|
|
1217
|
+
></div>
|
|
1218
|
+
|
|
1219
|
+
<div
|
|
1220
|
+
class="vela:fixed vela:left-1/2 vela:-translate-x-1/2 vela:z-[9999]
|
|
1221
|
+
vela:flex vela:items-center vela:justify-between vela:gap-3
|
|
1222
|
+
vela:w-full vela:max-w-[560px] vela:mx-4 vela:sm:mx-auto
|
|
1223
|
+
vela:h-12 vela:px-3 vela:rounded-full
|
|
1224
|
+
vela:bg-bar-bg vela:text-bar-text
|
|
1225
|
+
{barPosition === 'bottom' ? 'vela:bottom-4' : 'vela:top-4'}"
|
|
1226
|
+
>
|
|
1227
|
+
<div class="vela:flex vela:items-center vela:gap-3">
|
|
1228
|
+
<span
|
|
1229
|
+
class="vela:text-[11px] vela:font-semibold vela:tracking-wider vela:uppercase
|
|
1230
|
+
vela:text-bar-text-tertiary"
|
|
1231
|
+
>
|
|
1232
|
+
CMS
|
|
1233
|
+
</span>
|
|
1234
|
+
|
|
1235
|
+
{#if !subBarVisible}
|
|
1236
|
+
<StatusPill variant="clean">All published</StatusPill>
|
|
1237
|
+
{/if}
|
|
1238
|
+
</div>
|
|
1239
|
+
|
|
1240
|
+
<div class="vela:flex vela:items-center vela:gap-2">
|
|
1241
|
+
<Menubar.Root
|
|
1242
|
+
class="vela:bg-transparent vela:border-0 vela:p-0 vela:gap-0.5 vela:h-auto vela:rounded-none"
|
|
1243
|
+
>
|
|
1244
|
+
<Menubar.Menu>
|
|
1245
|
+
<Menubar.Trigger class={menubarTriggerClass}>Page</Menubar.Trigger>
|
|
1246
|
+
<Menubar.Content class={menuContentClassExt} align="end">
|
|
1247
|
+
<Menubar.Item
|
|
1248
|
+
class={menuItemClass}
|
|
1249
|
+
onSelect={onToggleEdit}
|
|
1250
|
+
disabled={cmsStore.isEditing || !!versionKey}
|
|
1251
|
+
>
|
|
1252
|
+
Edit
|
|
1253
|
+
<KbdShortcut keys="⌘E" class={menuShortcutClass} />
|
|
1254
|
+
</Menubar.Item>
|
|
1255
|
+
<Menubar.Item class={menuItemClass} onSelect={onOpenSeo}>
|
|
1256
|
+
SEO & Metadata
|
|
1257
|
+
<KbdShortcut keys="⌘I" class={menuShortcutClass} />
|
|
1258
|
+
</Menubar.Item>
|
|
1259
|
+
<Menubar.Separator class={menuSeparatorClass} />
|
|
1260
|
+
<Menubar.Item
|
|
1261
|
+
class={menuItemClass}
|
|
1262
|
+
onSelect={onCopyPreviewLink}
|
|
1263
|
+
disabled={!previewUrl}
|
|
1264
|
+
>
|
|
1265
|
+
Copy Preview Link
|
|
1266
|
+
</Menubar.Item>
|
|
1267
|
+
<Menubar.Item class={menuItemClass} onSelect={onOpenInNewTab}>
|
|
1268
|
+
Open In New Tab
|
|
1269
|
+
<KbdShortcut keys="⌘↵" class={menuShortcutClass} />
|
|
1270
|
+
</Menubar.Item>
|
|
1271
|
+
<Menubar.Separator class={menuSeparatorClass} />
|
|
1272
|
+
<Menubar.Item
|
|
1273
|
+
class={menuItemClass}
|
|
1274
|
+
onSelect={onDiscardCurrentPage}
|
|
1275
|
+
disabled={!hasCurrentPageDrafts}
|
|
1276
|
+
>
|
|
1277
|
+
Discard Draft…
|
|
1278
|
+
</Menubar.Item>
|
|
1279
|
+
<Menubar.Item
|
|
1280
|
+
class={menuItemClass}
|
|
1281
|
+
onSelect={onPublishThisPage}
|
|
1282
|
+
disabled={!hasCurrentPageDrafts}
|
|
1283
|
+
>
|
|
1284
|
+
Publish…
|
|
1285
|
+
<KbdShortcut keys="⌘P" class={menuShortcutClass} />
|
|
1286
|
+
</Menubar.Item>
|
|
1287
|
+
<Menubar.Separator class={menuSeparatorClass} />
|
|
1288
|
+
<Menubar.Item
|
|
1289
|
+
class={menuItemDestructiveClass}
|
|
1290
|
+
onSelect={onDeletePage}
|
|
1291
|
+
disabled={!currentDeletableConfig}
|
|
1292
|
+
>
|
|
1293
|
+
Delete Page…
|
|
1294
|
+
</Menubar.Item>
|
|
1295
|
+
</Menubar.Content>
|
|
1296
|
+
</Menubar.Menu>
|
|
1297
|
+
|
|
1298
|
+
<Menubar.Menu>
|
|
1299
|
+
<Menubar.Trigger class={menubarTriggerClass}>Site</Menubar.Trigger>
|
|
1300
|
+
<Menubar.Content class={menuContentClassExt} align="end">
|
|
1301
|
+
<Menubar.Item
|
|
1302
|
+
class={menuItemClass}
|
|
1303
|
+
onSelect={onNewPage}
|
|
1304
|
+
disabled={creatablePages.length === 0}
|
|
1305
|
+
>
|
|
1306
|
+
New Page…
|
|
1307
|
+
<KbdShortcut keys="⌘N" class={menuShortcutClass} />
|
|
1308
|
+
</Menubar.Item>
|
|
1309
|
+
<Menubar.Item class={menuItemClass} onSelect={onOpenPages}>
|
|
1310
|
+
All Pages
|
|
1311
|
+
<KbdShortcut keys="⌘K" class={menuShortcutClass} />
|
|
1312
|
+
</Menubar.Item>
|
|
1313
|
+
<Menubar.Item class={menuItemClass} onSelect={onOpenMedia}>
|
|
1314
|
+
Media Library
|
|
1315
|
+
<KbdShortcut keys="⌘M" class={menuShortcutClass} />
|
|
1316
|
+
</Menubar.Item>
|
|
1317
|
+
{#if supportedLocales.length > 1}
|
|
1318
|
+
<Menubar.Item class={menuItemClass} onSelect={onOpenLocales}>
|
|
1319
|
+
Locales
|
|
1320
|
+
<KbdShortcut keys="⌘L" class={menuShortcutClass} />
|
|
1321
|
+
</Menubar.Item>
|
|
1322
|
+
{/if}
|
|
1323
|
+
|
|
1324
|
+
<Menubar.Label class={menuLabelClass}>Working copy</Menubar.Label>
|
|
1325
|
+
<Menubar.Item
|
|
1326
|
+
class={menuItemClass}
|
|
1327
|
+
onSelect={onOpenPublish}
|
|
1328
|
+
disabled={counts.total === 0}
|
|
1329
|
+
>
|
|
1330
|
+
Review & Publish…
|
|
1331
|
+
{#if counts.total > 0}
|
|
1332
|
+
<span class="vela:ml-auto vela:flex vela:items-center vela:gap-2">
|
|
1333
|
+
<span
|
|
1334
|
+
class="vela:inline-flex vela:items-center vela:justify-center
|
|
1335
|
+
vela:min-w-[18px] vela:h-[18px] vela:px-1 vela:rounded-full
|
|
1336
|
+
vela:text-[10px] vela:font-semibold
|
|
1337
|
+
vela:bg-[var(--cms-status-warn-bg)]
|
|
1338
|
+
vela:text-[var(--cms-status-warn-text)]">{counts.total}</span
|
|
1339
|
+
>
|
|
1340
|
+
<KbdShortcut keys="⌘P" />
|
|
1341
|
+
</span>
|
|
1342
|
+
{:else}
|
|
1343
|
+
<KbdShortcut keys="⌘P" class={menuShortcutClass} />
|
|
1344
|
+
{/if}
|
|
1345
|
+
</Menubar.Item>
|
|
1346
|
+
<Menubar.Item class={menuItemClass} onSelect={onShareLink} disabled={!previewUrl}>
|
|
1347
|
+
Share Preview Link…
|
|
1348
|
+
</Menubar.Item>
|
|
1349
|
+
<Menubar.Item
|
|
1350
|
+
class={menuItemDestructiveClass}
|
|
1351
|
+
onSelect={onDiscardAllChanges}
|
|
1352
|
+
disabled={counts.total === 0}
|
|
1353
|
+
>
|
|
1354
|
+
Discard All Changes…
|
|
1355
|
+
</Menubar.Item>
|
|
1356
|
+
|
|
1357
|
+
<Menubar.Label class={menuLabelClass}>History</Menubar.Label>
|
|
1358
|
+
<Menubar.Item class={menuItemClass} onSelect={onOpenHistory}>
|
|
1359
|
+
Recent Releases
|
|
1360
|
+
<KbdShortcut keys="⌘H" class={menuShortcutClass} />
|
|
1361
|
+
</Menubar.Item>
|
|
1362
|
+
|
|
1363
|
+
<Menubar.Label class={menuLabelClass}>Settings</Menubar.Label>
|
|
1364
|
+
<Menubar.Item class={menuItemClass} onSelect={onOpenSiteSettings}>
|
|
1365
|
+
Site Settings
|
|
1366
|
+
<KbdShortcut keys="⌘," class={menuShortcutClass} />
|
|
1367
|
+
</Menubar.Item>
|
|
1368
|
+
</Menubar.Content>
|
|
1369
|
+
</Menubar.Menu>
|
|
1370
|
+
|
|
1371
|
+
<Menubar.Menu>
|
|
1372
|
+
<Menubar.Trigger class={menubarTriggerClass}>View</Menubar.Trigger>
|
|
1373
|
+
<Menubar.Content class={menuContentClassExt} align="end">
|
|
1374
|
+
<Menubar.Label class={menuLabelClass}>Bar</Menubar.Label>
|
|
1375
|
+
<Menubar.Item class={menuItemClass} onSelect={onHideBar}>
|
|
1376
|
+
Hide Bar
|
|
1377
|
+
<KbdShortcut keys="⌘." class={menuShortcutClass} />
|
|
1378
|
+
</Menubar.Item>
|
|
1379
|
+
<Menubar.Sub>
|
|
1380
|
+
<Menubar.SubTrigger class={menuItemClass}>Position</Menubar.SubTrigger>
|
|
1381
|
+
<Menubar.SubContent class={menuContentClassExt}>
|
|
1382
|
+
<Menubar.Item
|
|
1383
|
+
class={menuCheckIndicatorClass}
|
|
1384
|
+
onSelect={() => (barPosition = 'top')}
|
|
1385
|
+
>
|
|
1386
|
+
<span class="vela:absolute vela:left-2">
|
|
1387
|
+
{barPosition === 'top' ? '✓' : ''}
|
|
1388
|
+
</span>
|
|
1389
|
+
Top
|
|
1390
|
+
</Menubar.Item>
|
|
1391
|
+
<Menubar.Item
|
|
1392
|
+
class={menuCheckIndicatorClass}
|
|
1393
|
+
onSelect={() => (barPosition = 'bottom')}
|
|
1394
|
+
>
|
|
1395
|
+
<span class="vela:absolute vela:left-2">
|
|
1396
|
+
{barPosition === 'bottom' ? '✓' : ''}
|
|
1397
|
+
</span>
|
|
1398
|
+
Bottom
|
|
1399
|
+
</Menubar.Item>
|
|
1400
|
+
</Menubar.SubContent>
|
|
1401
|
+
</Menubar.Sub>
|
|
1402
|
+
|
|
1403
|
+
<Menubar.Label class={menuLabelClass}>Page indicators</Menubar.Label>
|
|
1404
|
+
<Menubar.CheckboxItem class={menuCheckIndicatorClass} bind:checked={highlightSlots}>
|
|
1405
|
+
Highlight Editable Areas
|
|
1406
|
+
<KbdShortcut keys="⌘⇧E" class={menuShortcutClass} />
|
|
1407
|
+
</Menubar.CheckboxItem>
|
|
1408
|
+
<Menubar.CheckboxItem class={menuCheckIndicatorClass} bind:checked={showDraftMarkers}>
|
|
1409
|
+
Show Draft Markers On Links
|
|
1410
|
+
</Menubar.CheckboxItem>
|
|
1411
|
+
|
|
1412
|
+
<Menubar.Label class={menuLabelClass}>Preview As</Menubar.Label>
|
|
1413
|
+
<Menubar.RadioGroup bind:value={previewAs}>
|
|
1414
|
+
<Menubar.RadioItem class={menuCheckIndicatorClass} value="desktop">
|
|
1415
|
+
Desktop
|
|
1416
|
+
</Menubar.RadioItem>
|
|
1417
|
+
<Menubar.RadioItem class={menuCheckIndicatorClass} value="tablet">
|
|
1418
|
+
Tablet
|
|
1419
|
+
</Menubar.RadioItem>
|
|
1420
|
+
<Menubar.RadioItem class={menuCheckIndicatorClass} value="mobile">
|
|
1421
|
+
Mobile
|
|
1422
|
+
</Menubar.RadioItem>
|
|
1423
|
+
<Menubar.RadioItem class={menuCheckIndicatorClass} value="signed-out">
|
|
1424
|
+
Signed-out visitor
|
|
1425
|
+
</Menubar.RadioItem>
|
|
1426
|
+
</Menubar.RadioGroup>
|
|
1427
|
+
|
|
1428
|
+
<Menubar.Label class={menuLabelClass}>Appearance</Menubar.Label>
|
|
1429
|
+
<Menubar.Sub>
|
|
1430
|
+
<Menubar.SubTrigger class={menuItemClass}>Theme</Menubar.SubTrigger>
|
|
1431
|
+
<Menubar.SubContent class={menuContentClassExt}>
|
|
1432
|
+
<Menubar.Item
|
|
1433
|
+
class={menuCheckIndicatorClass}
|
|
1434
|
+
onSelect={() => setThemePref('system')}
|
|
1435
|
+
>
|
|
1436
|
+
<span class="vela:absolute vela:left-2">
|
|
1437
|
+
{adminBarTheme.pref === 'system' ? '✓' : ''}
|
|
1438
|
+
</span>
|
|
1439
|
+
System
|
|
1440
|
+
</Menubar.Item>
|
|
1441
|
+
<Menubar.Item
|
|
1442
|
+
class={menuCheckIndicatorClass}
|
|
1443
|
+
onSelect={() => setThemePref('light')}
|
|
1444
|
+
>
|
|
1445
|
+
<span class="vela:absolute vela:left-2">
|
|
1446
|
+
{adminBarTheme.pref === 'light' ? '✓' : ''}
|
|
1447
|
+
</span>
|
|
1448
|
+
Light
|
|
1449
|
+
</Menubar.Item>
|
|
1450
|
+
<Menubar.Item
|
|
1451
|
+
class={menuCheckIndicatorClass}
|
|
1452
|
+
onSelect={() => setThemePref('dark')}
|
|
1453
|
+
>
|
|
1454
|
+
<span class="vela:absolute vela:left-2">
|
|
1455
|
+
{adminBarTheme.pref === 'dark' ? '✓' : ''}
|
|
1456
|
+
</span>
|
|
1457
|
+
Dark
|
|
1458
|
+
</Menubar.Item>
|
|
1459
|
+
</Menubar.SubContent>
|
|
1460
|
+
</Menubar.Sub>
|
|
1461
|
+
|
|
1462
|
+
<Menubar.Separator class={menuSeparatorClass} />
|
|
1463
|
+
<Menubar.Item class={menuItemClass} onSelect={onShowKeyboardShortcuts}>
|
|
1464
|
+
Keyboard Shortcuts…
|
|
1465
|
+
<KbdShortcut keys="?" class={menuShortcutClass} />
|
|
1466
|
+
</Menubar.Item>
|
|
1467
|
+
</Menubar.Content>
|
|
1468
|
+
</Menubar.Menu>
|
|
1469
|
+
|
|
1470
|
+
<Menubar.Menu>
|
|
1471
|
+
<Menubar.Trigger class={avatarTriggerClass} aria-label="Account menu">
|
|
1472
|
+
<UserAvatar name={user.name} />
|
|
1473
|
+
</Menubar.Trigger>
|
|
1474
|
+
<Menubar.Content class={menuContentClassExt} align="end">
|
|
1475
|
+
<Menubar.Item class={menuItemClass} onSelect={onLogout}
|
|
1476
|
+
>Log Out {user.name}</Menubar.Item
|
|
1477
|
+
>
|
|
1478
|
+
</Menubar.Content>
|
|
1479
|
+
</Menubar.Menu>
|
|
1480
|
+
</Menubar.Root>
|
|
1481
|
+
|
|
1482
|
+
<Button
|
|
1483
|
+
variant="ghost"
|
|
1484
|
+
size="pill-icon"
|
|
1485
|
+
aria-label="Close"
|
|
1486
|
+
onclick={onClose}
|
|
1487
|
+
class="vela:h-6 vela:w-6"
|
|
1488
|
+
>
|
|
1489
|
+
×
|
|
1490
|
+
</Button>
|
|
1491
|
+
</div>
|
|
1492
|
+
</div>
|
|
1493
|
+
|
|
1494
|
+
{#if subBarVisible}
|
|
1495
|
+
<!-- Sub-bar: bg starts behind the main bar (same top, taller height) so it
|
|
1496
|
+
reads as a footer/extension. Top corners are square; bottom matches
|
|
1497
|
+
the main bar's pill curvature. Bg is mostly transparent so the page
|
|
1498
|
+
shows through. The visible band is the 40px below the main bar. -->
|
|
1499
|
+
<div
|
|
1500
|
+
class="vela:fixed vela:left-1/2 vela:-translate-x-1/2 vela:z-[9998]
|
|
1501
|
+
vela:flex vela:justify-between vela:gap-3
|
|
1502
|
+
vela:w-full vela:max-w-[560px] vela:mx-4 vela:sm:mx-auto
|
|
1503
|
+
vela:h-16 vela:px-3
|
|
1504
|
+
vela:text-bar-text
|
|
1505
|
+
vela:bg-[var(--cms-sub-bar-bg)]
|
|
1506
|
+
vela:border vela:border-[var(--cms-sub-bar-border)]
|
|
1507
|
+
{barPosition === 'bottom'
|
|
1508
|
+
? 'vela:bottom-10 vela:pt-2 vela:items-start vela:rounded-t-3xl'
|
|
1509
|
+
: 'vela:top-10 vela:pb-2 vela:items-end vela:rounded-b-3xl'}"
|
|
1510
|
+
>
|
|
1511
|
+
<div class="vela:flex vela:items-center">
|
|
1512
|
+
{#if versionKey}
|
|
1513
|
+
<StatusPill
|
|
1514
|
+
variant="edit"
|
|
1515
|
+
class="vela:h-5.5 vela:pl-2 vela:pr-2.5 vela:gap-1 vela:text-[11px]"
|
|
1516
|
+
>
|
|
1517
|
+
Viewing release {versionRelease?.name ?? versionRelease?.id.slice(0, 8) ?? ''}
|
|
1518
|
+
</StatusPill>
|
|
1519
|
+
{:else if cmsStore.isEditing}
|
|
1520
|
+
<StatusPill
|
|
1521
|
+
variant="edit"
|
|
1522
|
+
class="vela:h-5.5 vela:pl-2 vela:pr-2.5 vela:gap-1 vela:text-[11px]"
|
|
1523
|
+
>
|
|
1524
|
+
Editing
|
|
1525
|
+
</StatusPill>
|
|
1526
|
+
{:else}
|
|
1527
|
+
<StatusPill
|
|
1528
|
+
variant="warn"
|
|
1529
|
+
onclick={onOpenPages}
|
|
1530
|
+
class="vela:h-5.5 vela:pl-2 vela:pr-2.5 vela:gap-1 vela:text-[11px]"
|
|
1531
|
+
>
|
|
1532
|
+
{counts.total}
|
|
1533
|
+
{counts.total === 1 ? 'draft' : 'drafts'}
|
|
1534
|
+
</StatusPill>
|
|
1535
|
+
{/if}
|
|
1536
|
+
</div>
|
|
1537
|
+
|
|
1538
|
+
<div class="vela:flex vela:items-center vela:gap-1.5">
|
|
1539
|
+
{#if versionKey}
|
|
1540
|
+
<Button
|
|
1541
|
+
size="pill"
|
|
1542
|
+
onclick={exitVersionView}
|
|
1543
|
+
class="vela:h-5.5 vela:px-2.5 vela:text-[10px]"
|
|
1544
|
+
>
|
|
1545
|
+
Exit
|
|
1546
|
+
</Button>
|
|
1547
|
+
{:else if cmsStore.isEditing}
|
|
1548
|
+
<Button
|
|
1549
|
+
variant="ghost"
|
|
1550
|
+
size="pill"
|
|
1551
|
+
onclick={onToggleEdit}
|
|
1552
|
+
class="vela:h-5.5 vela:px-2.5 vela:text-[10px]"
|
|
1553
|
+
>
|
|
1554
|
+
Cancel
|
|
1555
|
+
</Button>
|
|
1556
|
+
<Button size="pill" onclick={onSave} class="vela:h-5.5 vela:px-2.5 vela:text-[10px]">
|
|
1557
|
+
Save
|
|
1558
|
+
</Button>
|
|
1559
|
+
{:else}
|
|
1560
|
+
<Button
|
|
1561
|
+
variant="ghost"
|
|
1562
|
+
size="pill"
|
|
1563
|
+
onclick={onDiscardAllChanges}
|
|
1564
|
+
class="vela:h-5.5 vela:px-2.5 vela:text-[10px]"
|
|
1565
|
+
>
|
|
1566
|
+
Discard all changes…
|
|
1567
|
+
</Button>
|
|
1568
|
+
<Button
|
|
1569
|
+
size="pill"
|
|
1570
|
+
onclick={onOpenPublish}
|
|
1571
|
+
class="vela:h-5.5 vela:px-2.5 vela:text-[10px]"
|
|
1572
|
+
>
|
|
1573
|
+
Publish…
|
|
1574
|
+
</Button>
|
|
1575
|
+
{/if}
|
|
1576
|
+
</div>
|
|
1577
|
+
</div>
|
|
1578
|
+
{/if}
|
|
1579
|
+
|
|
1580
|
+
{#if seoOpen}
|
|
1581
|
+
{#await import('./seo-panel.svelte') then { default: SeoPanel }}
|
|
1582
|
+
<SeoPanel onClose={() => (seoOpen = false)} onSave={onSaveSeo} />
|
|
1583
|
+
{/await}
|
|
1584
|
+
{/if}
|
|
1585
|
+
|
|
1586
|
+
{#if siteSettingsOpen}
|
|
1587
|
+
{#await import('./site-settings-panel.svelte') then { default: SiteSettingsPanel }}
|
|
1588
|
+
<SiteSettingsPanel onClose={() => (siteSettingsOpen = false)} onSave={onSaveSiteSettings} />
|
|
1589
|
+
{/await}
|
|
1590
|
+
{/if}
|
|
1591
|
+
|
|
1592
|
+
{#if historyOpen}
|
|
1593
|
+
<HistoryPanel
|
|
1594
|
+
{endpoint}
|
|
1595
|
+
onClose={() => (historyOpen = false)}
|
|
1596
|
+
onChanged={refreshAfterReleaseChange}
|
|
1597
|
+
/>
|
|
1598
|
+
{/if}
|
|
1599
|
+
|
|
1600
|
+
{#if pagesOpen}
|
|
1601
|
+
<PagesPanel
|
|
1602
|
+
{endpoint}
|
|
1603
|
+
{creatablePages}
|
|
1604
|
+
onClose={() => (pagesOpen = false)}
|
|
1605
|
+
onChanged={refreshAfterReleaseChange}
|
|
1606
|
+
onRequestNew={openNewPageDialog}
|
|
1607
|
+
{onRequestDuplicate}
|
|
1608
|
+
onRequestDelete={(routeId, params, isDraft) => void requestDelete(routeId, params, isDraft)}
|
|
1609
|
+
/>
|
|
1610
|
+
{/if}
|
|
1611
|
+
|
|
1612
|
+
{#if mediaOpen}
|
|
1613
|
+
<MediaPanel {endpoint} onClose={() => (mediaOpen = false)} />
|
|
1614
|
+
{/if}
|
|
1615
|
+
|
|
1616
|
+
{#if localesOpen}
|
|
1617
|
+
{#await import('./locales-panel.svelte') then { default: LocalesPanel }}
|
|
1618
|
+
<LocalesPanel
|
|
1619
|
+
{currentLocale}
|
|
1620
|
+
locales={supportedLocales}
|
|
1621
|
+
onClose={() => (localesOpen = false)}
|
|
1622
|
+
onSelectLocale={(locale) => {
|
|
1623
|
+
const target = locale === supportedLocales[0] ? null : locale;
|
|
1624
|
+
void setLocaleParam(target);
|
|
1625
|
+
}}
|
|
1626
|
+
/>
|
|
1627
|
+
{/await}
|
|
1628
|
+
{/if}
|
|
1629
|
+
|
|
1630
|
+
{#if cmsStore.openRelease}
|
|
1631
|
+
<PublishDialog
|
|
1632
|
+
open={publishOpen}
|
|
1633
|
+
onOpenChange={(next) => (publishOpen = next)}
|
|
1634
|
+
items={cmsStore.openRelease.items}
|
|
1635
|
+
{publishing}
|
|
1636
|
+
error={publishError}
|
|
1637
|
+
{user}
|
|
1638
|
+
{endpoint}
|
|
1639
|
+
onConfirm={onConfirmPublish}
|
|
1640
|
+
/>
|
|
1641
|
+
{/if}
|
|
1642
|
+
|
|
1643
|
+
{#if newDialog}
|
|
1644
|
+
<NewPageDialog
|
|
1645
|
+
open={!!newDialog}
|
|
1646
|
+
onOpenChange={(next) => {
|
|
1647
|
+
if (!next) closeNewPageDialog();
|
|
1648
|
+
}}
|
|
1649
|
+
config={newDialog}
|
|
1650
|
+
creating={newPageCreating}
|
|
1651
|
+
error={newPageError}
|
|
1652
|
+
onCreate={handleCreate}
|
|
1653
|
+
mode={duplicateSource ? 'duplicate' : 'new'}
|
|
1654
|
+
initialValues={duplicateSource
|
|
1655
|
+
? Object.fromEntries(
|
|
1656
|
+
Object.entries(duplicateSource.metadata).filter(([, v]) => typeof v === 'string') as [
|
|
1657
|
+
string,
|
|
1658
|
+
string
|
|
1659
|
+
][]
|
|
1660
|
+
)
|
|
1661
|
+
: {}}
|
|
1662
|
+
/>
|
|
1663
|
+
{/if}
|
|
1664
|
+
|
|
1665
|
+
<DeletePageDialog
|
|
1666
|
+
open={deleteDialogOpen}
|
|
1667
|
+
onOpenChange={(next) => {
|
|
1668
|
+
if (!next && deleting) return;
|
|
1669
|
+
deleteDialogOpen = next;
|
|
1670
|
+
if (!next) deleteTarget = null;
|
|
1671
|
+
}}
|
|
1672
|
+
url={deleteTarget?.url ?? ''}
|
|
1673
|
+
isDraft={deleteTarget?.isDraft ?? false}
|
|
1674
|
+
targets={redirectTargets}
|
|
1675
|
+
{redirectsByUrl}
|
|
1676
|
+
{deleting}
|
|
1677
|
+
error={deleteError}
|
|
1678
|
+
onConfirm={onDeleteConfirm}
|
|
1679
|
+
/>
|
|
1680
|
+
|
|
1681
|
+
<NewPageChooserDialog
|
|
1682
|
+
open={chooserOpen}
|
|
1683
|
+
onOpenChange={(next) => (chooserOpen = next)}
|
|
1684
|
+
{creatablePages}
|
|
1685
|
+
onSelect={onNewFromTemplate}
|
|
1686
|
+
/>
|
|
1687
|
+
|
|
1688
|
+
<SharePreviewLinkDialog
|
|
1689
|
+
open={shareLinkOpen}
|
|
1690
|
+
onOpenChange={(next) => (shareLinkOpen = next)}
|
|
1691
|
+
{previewUrl}
|
|
1692
|
+
onRegenerate={onRegeneratePreviewKey}
|
|
1693
|
+
/>
|
|
1694
|
+
|
|
1695
|
+
<KeyboardShortcutsDialog open={shortcutsOpen} onOpenChange={(next) => (shortcutsOpen = next)} />
|
|
1696
|
+
</div>
|
|
1697
|
+
</CssRoot>
|