@morphemecms/cms 0.5.0 → 0.7.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/CHANGELOG.md +16 -0
- package/astro.mjs +3 -0
- package/bin/morpheme.mjs +3 -1
- package/defaults/blockTexts.ts +2 -2
- package/defaults/collections.ts +10 -0
- package/defaults/editor.ts +57 -53
- package/defaults/privacy.ts +4 -1
- package/defaults/rules.ts +36 -38
- package/defaults/standardPages.ts +0 -3
- package/defaults/texts.ts +11 -2
- package/demo/src/content/pages/about.md +2 -0
- package/icons/lucide/browser-fill.svg +1 -0
- package/icons/lucide/buildings-fill.svg +1 -0
- package/icons/lucide/certificate-fill.svg +1 -0
- package/icons/lucide/chart-line-up-fill.svg +1 -0
- package/icons/lucide/chats-fill.svg +1 -0
- package/icons/lucide/envelope-simple-fill.svg +1 -0
- package/icons/lucide/file-text-fill.svg +1 -0
- package/icons/lucide/folder-simple-fill.svg +1 -0
- package/icons/lucide/gauge.svg +1 -1
- package/icons/lucide/gear-six.svg +1 -0
- package/icons/lucide/globe-simple.svg +1 -0
- package/icons/lucide/heart-fill.svg +1 -0
- package/icons/lucide/layout.svg +1 -0
- package/icons/lucide/list-checks-fill.svg +1 -0
- package/icons/lucide/magnifying-glass-fill.svg +1 -0
- package/icons/lucide/megaphone-fill.svg +1 -0
- package/icons/lucide/newspaper-fill.svg +1 -0
- package/icons/lucide/paint-brush-broad-fill.svg +1 -0
- package/icons/lucide/puzzle-piece-fill.svg +1 -0
- package/icons/lucide/shield-check-fill.svg +1 -0
- package/icons/lucide/sidebar-simple.svg +1 -1
- package/icons/lucide/sidebar.svg +1 -0
- package/icons/lucide/square-half-bottom-fill.svg +1 -0
- package/icons/lucide/square-half.svg +1 -0
- package/icons/lucide/squares-four-fill.svg +1 -0
- package/icons/lucide/storefront-fill.svg +1 -0
- package/icons/lucide/tag-fill.svg +1 -0
- package/icons/lucide/toggle-right-fill.svg +1 -0
- package/icons/lucide/trash-fill.svg +1 -0
- package/icons/lucide/user-fill.svg +1 -0
- package/icons/lucide/wrench-fill.svg +1 -0
- package/icons/material-symbols/browser-fill.svg +1 -0
- package/icons/material-symbols/buildings-fill.svg +1 -0
- package/icons/material-symbols/certificate-fill.svg +1 -0
- package/icons/material-symbols/chart-line-up-fill.svg +1 -0
- package/icons/material-symbols/chats-fill.svg +1 -0
- package/icons/material-symbols/envelope-simple-fill.svg +1 -0
- package/icons/material-symbols/file-text-fill.svg +1 -0
- package/icons/material-symbols/folder-simple-fill.svg +1 -0
- package/icons/material-symbols/gauge.svg +1 -1
- package/icons/material-symbols/gear-six.svg +1 -0
- package/icons/material-symbols/globe-simple.svg +1 -0
- package/icons/material-symbols/heart-fill.svg +1 -0
- package/icons/material-symbols/layout.svg +1 -0
- package/icons/material-symbols/list-checks-fill.svg +1 -0
- package/icons/material-symbols/magnifying-glass-fill.svg +1 -0
- package/icons/material-symbols/megaphone-fill.svg +1 -0
- package/icons/material-symbols/newspaper-fill.svg +1 -0
- package/icons/material-symbols/paint-brush-broad-fill.svg +1 -0
- package/icons/material-symbols/puzzle-piece-fill.svg +1 -0
- package/icons/material-symbols/shield-check-fill.svg +1 -0
- package/icons/material-symbols/sidebar-simple.svg +1 -1
- package/icons/material-symbols/sidebar.svg +1 -0
- package/icons/material-symbols/square-half-bottom-fill.svg +1 -0
- package/icons/material-symbols/square-half.svg +1 -0
- package/icons/material-symbols/squares-four-fill.svg +1 -0
- package/icons/material-symbols/storefront-fill.svg +1 -0
- package/icons/material-symbols/tag-fill.svg +1 -0
- package/icons/material-symbols/toggle-right-fill.svg +1 -0
- package/icons/material-symbols/trash-fill.svg +1 -0
- package/icons/material-symbols/user-fill.svg +1 -0
- package/icons/material-symbols/wrench-fill.svg +1 -0
- package/icons/phosphor/browser-fill.svg +1 -0
- package/icons/phosphor/buildings-fill.svg +1 -0
- package/icons/phosphor/certificate-fill.svg +1 -0
- package/icons/phosphor/chart-line-up-fill.svg +1 -0
- package/icons/phosphor/chats-fill.svg +1 -0
- package/icons/phosphor/envelope-simple-fill.svg +1 -0
- package/icons/phosphor/file-text-fill.svg +1 -0
- package/icons/phosphor/folder-simple-fill.svg +1 -0
- package/icons/phosphor/gear-six.svg +1 -0
- package/icons/phosphor/globe-simple.svg +1 -0
- package/icons/phosphor/heart-fill.svg +1 -0
- package/icons/phosphor/layout.svg +1 -0
- package/icons/phosphor/list-checks-fill.svg +1 -0
- package/icons/phosphor/magnifying-glass-fill.svg +1 -0
- package/icons/phosphor/megaphone-fill.svg +1 -0
- package/icons/phosphor/newspaper-fill.svg +1 -0
- package/icons/phosphor/paint-brush-broad-fill.svg +1 -0
- package/icons/phosphor/puzzle-piece-fill.svg +1 -0
- package/icons/phosphor/shield-check-fill.svg +1 -0
- package/icons/phosphor/sidebar.svg +1 -0
- package/icons/phosphor/square-half-bottom-fill.svg +1 -0
- package/icons/phosphor/square-half.svg +1 -0
- package/icons/phosphor/squares-four-fill.svg +1 -0
- package/icons/phosphor/storefront-fill.svg +1 -0
- package/icons/phosphor/tag-fill.svg +1 -0
- package/icons/phosphor/toggle-right-fill.svg +1 -0
- package/icons/phosphor/trash-fill.svg +1 -0
- package/icons/phosphor/user-fill.svg +1 -0
- package/icons/phosphor/wrench-fill.svg +1 -0
- package/lib/breakpoints.d.mts +13 -0
- package/lib/breakpoints.mjs +20 -0
- package/lib/breakpoints.test.mjs +39 -0
- package/lib/build-lane.d.mts +15 -0
- package/lib/build-lane.mjs +23 -0
- package/lib/build-lane.test.mjs +74 -0
- package/lib/build-settings.d.mts +16 -0
- package/lib/build-settings.mjs +39 -0
- package/lib/build-settings.test.mjs +41 -0
- package/lib/contracts.d.mts +3 -2
- package/lib/contracts.mjs +3 -2
- package/lib/drawer-swipe.d.mts +10 -0
- package/lib/drawer-swipe.mjs +17 -0
- package/lib/drawer-swipe.test.mjs +33 -0
- package/lib/drawerKeys.d.mts +9 -0
- package/lib/drawerKeys.mjs +26 -0
- package/lib/drawerKeys.test.mjs +21 -0
- package/lib/eyebrow.d.mts +6 -0
- package/lib/eyebrow.mjs +24 -0
- package/lib/eyebrow.test.mjs +20 -0
- package/lib/front-sections.test.mjs +82 -0
- package/lib/heading-levels.d.mts +7 -0
- package/lib/heading-levels.mjs +16 -0
- package/lib/heading-levels.test.mjs +37 -0
- package/lib/inline.ts +3 -2
- package/lib/markdown.ts +2 -0
- package/lib/materialIcon.d.mts +21 -0
- package/lib/materialIcon.mjs +29 -0
- package/lib/materialIcon.test.mjs +45 -0
- package/lib/menu-layout.d.mts +11 -0
- package/lib/menu-layout.mjs +14 -0
- package/lib/menu-layout.test.mjs +23 -0
- package/lib/menu.d.mts +59 -0
- package/lib/menu.mjs +129 -0
- package/lib/menu.test.mjs +291 -0
- package/lib/rehype-emphasize.d.mts +4 -0
- package/lib/rehype-emphasize.mjs +20 -0
- package/lib/rehype-emphasize.test.mjs +39 -0
- package/lib/section-in-view.d.mts +8 -0
- package/lib/section-in-view.mjs +25 -0
- package/lib/section-in-view.test.mjs +35 -0
- package/lib/section-marker.d.mts +5 -0
- package/lib/section-marker.mjs +18 -0
- package/lib/section-marker.test.mjs +22 -0
- package/lib/section-ranges.d.mts +12 -0
- package/lib/section-ranges.mjs +43 -0
- package/lib/section-ranges.test.mjs +61 -0
- package/lib/sheet-stops.d.mts +68 -0
- package/lib/sheet-stops.mjs +198 -0
- package/lib/sheet-stops.test.mjs +155 -0
- package/lib/still-timer.d.mts +5 -0
- package/lib/still-timer.mjs +17 -0
- package/lib/still-timer.test.mjs +42 -0
- package/lib/text-sections.d.mts +10 -0
- package/lib/text-sections.mjs +45 -0
- package/lib/text-sections.test.mjs +78 -0
- package/lib/video.mjs +2 -1
- package/lib/without-h1.d.mts +1 -0
- package/lib/without-h1.mjs +7 -0
- package/lib/without-h1.test.mjs +26 -0
- package/lib/word-at.d.mts +6 -0
- package/lib/word-at.mjs +20 -0
- package/lib/word-at.test.mjs +19 -0
- package/package.json +3 -1
- package/pages/editor/version.json.js +22 -2
- package/scripts/already-checked.mjs +11 -0
- package/scripts/already-checked.test.mjs +18 -0
- package/scripts/build-settings-problems.mjs +27 -0
- package/scripts/build-settings-problems.test.mjs +35 -0
- package/scripts/check-content.mjs +42 -4
- package/scripts/check-worker.mjs +96 -4
- package/scripts/ci-build.mjs +85 -0
- package/scripts/ci-build.test.mjs +137 -0
- package/scripts/editor-roundtrip.mjs +62 -3
- package/scripts/editor-screens.mjs +21 -7
- package/scripts/git-env.mjs +6 -0
- package/scripts/step-command.mjs +34 -0
- package/scripts/step-command.test.mjs +50 -0
- package/scripts/text-problems.mjs +83 -0
- package/scripts/text-problems.test.mjs +74 -0
- package/scripts/worker-modules.mjs +11 -1
- package/site/breakpoints.d.mts +6 -0
- package/site/breakpoints.mjs +52 -0
- package/site/breakpoints.test.mjs +36 -0
- package/site/casing.d.mts +3 -0
- package/site/casing.mjs +26 -0
- package/site/content.config.ts +2 -0
- package/site/content.ts +2 -4
- package/site/defineSite.d.mts +1 -0
- package/site/defineSite.mjs +3 -0
- package/site/home.ts +3 -2
- package/site/icons.d.mts +116 -4
- package/site/icons.mjs +62 -6
- package/site/images.mjs +1 -1
- package/site/live-diff.d.mts +22 -0
- package/site/live-diff.mjs +98 -0
- package/site/live-diff.test.mjs +235 -0
- package/site/materialSymbols.d.mts +5 -0
- package/site/materialSymbols.mjs +19 -0
- package/site/sections.ts +1 -1
- package/site/theme.d.mts +1 -1
- package/site/theme.mjs +7 -1
- package/src/ActionParts.tsx +1 -1
- package/src/Advanced.tsx +101 -20
- package/src/App.tsx +47 -121
- package/src/AppList.tsx +1 -1
- package/src/BlockParts.tsx +3 -3
- package/src/Colors.tsx +1 -1
- package/src/CommunicationParts.tsx +1 -1
- package/src/ContainmentParts.tsx +80 -28
- package/src/Deleted.tsx +18 -4
- package/src/EditorFooter.tsx +14 -0
- package/src/EntryForm.tsx +75 -39
- package/src/FormFrame.tsx +3 -8
- package/src/Library.tsx +46 -51
- package/src/Licenses.tsx +27 -11
- package/src/MainDrawerParts.tsx +60 -0
- package/src/MainMenu.tsx +136 -0
- package/src/MarkdownEditor.tsx +175 -32
- package/src/Media.tsx +69 -16
- package/src/MediaTags.tsx +91 -0
- package/src/ModalSheetSample.tsx +47 -0
- package/src/NavigationParts.tsx +67 -21
- package/src/NavigationRestParts.tsx +1 -1
- package/src/Parts.tsx +71 -0
- package/src/PartsShown.tsx +1 -1
- package/src/PickerParts.tsx +2 -2
- package/src/Redirects.tsx +3 -2
- package/src/Screen.tsx +93 -90
- package/src/SelectionParts.tsx +22 -6
- package/src/SiteLogoLink.tsx +14 -0
- package/src/StartFab.tsx +22 -0
- package/src/StartStatus.tsx +1 -1
- package/src/autoTags.d.mts +28 -0
- package/src/autoTags.mjs +68 -0
- package/src/autoTags.test.mjs +109 -0
- package/src/buildSettings.test.mjs +20 -0
- package/src/buildSettings.ts +20 -0
- package/src/checkSite.ts +14 -1
- package/src/deploy.ts +7 -9
- package/src/editor.css +150 -17
- package/src/eyebrow.d.mts +5 -0
- package/src/eyebrow.mjs +53 -0
- package/src/eyebrowState.d.mts +21 -0
- package/src/eyebrowState.mjs +62 -0
- package/src/eyebrowState.test.mjs +78 -0
- package/src/fileRecords.d.mts +115 -0
- package/src/fileRecords.mjs +224 -0
- package/src/fileRecords.test.mjs +224 -0
- package/src/fileTagFilter.d.mts +30 -0
- package/src/fileTagFilter.mjs +44 -0
- package/src/fileTagFilter.test.mjs +57 -0
- package/src/fileTags.d.mts +56 -0
- package/src/fileTags.mjs +100 -0
- package/src/fileTags.test.mjs +127 -0
- package/src/firstHeading.d.mts +6 -0
- package/src/firstHeading.mjs +37 -0
- package/src/i18n/da-DK/advanced.ts +27 -12
- package/src/i18n/da-DK/app.ts +9 -7
- package/src/i18n/da-DK/apps.ts +16 -16
- package/src/i18n/da-DK/blockSettings.ts +9 -9
- package/src/i18n/da-DK/blocks.ts +2 -2
- package/src/i18n/da-DK/check.ts +5 -2
- package/src/i18n/da-DK/cms.ts +14 -12
- package/src/i18n/da-DK/colors.ts +5 -5
- package/src/i18n/da-DK/common.ts +2 -2
- package/src/i18n/da-DK/company.ts +3 -3
- package/src/i18n/da-DK/consents.ts +6 -6
- package/src/i18n/da-DK/forms.ts +6 -6
- package/src/i18n/da-DK/history.ts +6 -6
- package/src/i18n/da-DK/keywords.ts +2 -2
- package/src/i18n/da-DK/library.ts +17 -15
- package/src/i18n/da-DK/licenses.ts +4 -4
- package/src/i18n/da-DK/media.ts +10 -4
- package/src/i18n/da-DK/parts.ts +62 -25
- package/src/i18n/da-DK/preview.ts +5 -6
- package/src/i18n/da-DK/redirects.ts +7 -7
- package/src/i18n/da-DK/seo.ts +1 -1
- package/src/i18n/da-DK/text.ts +2 -0
- package/src/i18n/da-DK/theme.ts +5 -5
- package/src/i18n/en-US/advanced.ts +34 -18
- package/src/i18n/en-US/app.ts +7 -5
- package/src/i18n/en-US/apps.ts +9 -9
- package/src/i18n/en-US/blockSettings.ts +4 -4
- package/src/i18n/en-US/blocks.ts +6 -6
- package/src/i18n/en-US/check.ts +6 -3
- package/src/i18n/en-US/cms.ts +15 -14
- package/src/i18n/en-US/colors.ts +18 -18
- package/src/i18n/en-US/common.ts +3 -3
- package/src/i18n/en-US/consents.ts +4 -4
- package/src/i18n/en-US/forms.ts +5 -5
- package/src/i18n/en-US/history.ts +2 -2
- package/src/i18n/en-US/library.ts +21 -15
- package/src/i18n/en-US/licenses.ts +6 -6
- package/src/i18n/en-US/media.ts +7 -1
- package/src/i18n/en-US/parts.ts +60 -21
- package/src/i18n/en-US/preview.ts +5 -6
- package/src/i18n/en-US/redirects.ts +3 -3
- package/src/i18n/en-US/seo.ts +1 -1
- package/src/i18n/en-US/text.ts +3 -0
- package/src/i18n/en-US/theme.ts +6 -6
- package/src/icon.tsx +90 -27
- package/src/licenseData.test.mjs +28 -0
- package/src/licenseData.ts +12 -1
- package/src/markdown.d.mts +2 -0
- package/src/markdown.mjs +73 -5
- package/src/markdown.test.mjs +74 -0
- package/src/materialIcons.ts +8 -0
- package/src/menuChildren.ts +36 -0
- package/src/menuOpen.ts +34 -0
- package/src/pagePreview.tsx +23 -19
- package/src/r2Caching.ts +14 -0
- package/src/sectionFrame.ts +39 -0
- package/src/sectionState.d.mts +19 -0
- package/src/sectionState.mjs +31 -0
- package/src/sectionState.test.mjs +59 -0
- package/src/sections.d.mts +9 -7
- package/src/sections.mjs +74 -27
- package/src/sections.test.mjs +59 -0
- package/src/sidebar.ts +2 -2
- package/src/site.ts +27 -17
- package/src/stillCursor.d.mts +8 -0
- package/src/stillCursor.mjs +19 -0
- package/src/stillCursor.test.mjs +26 -0
- package/src/tagUses.d.mts +47 -0
- package/src/tagUses.mjs +121 -0
- package/src/tagUses.test.mjs +141 -0
- package/src/themeUi.ts +10 -0
- package/src/topBarSlot.tsx +37 -0
- package/src/ui.tsx +654 -150
- package/src/virtual.d.ts +5 -0
- package/starter/README.md +2 -1
- package/starter/package.json +2 -1
- package/starter/wrangler.jsonc +3 -2
- package/theme/blocks/cards/LinkCards.tsx +2 -1
- package/theme/blocks/hero/View.tsx +2 -1
- package/theme/blocks/video/View.tsx +2 -1
- package/theme/components/BlogIndex.astro +2 -2
- package/theme/components/BlogPosts.astro +1 -1
- package/theme/components/BlogPosts.tsx +9 -6
- package/theme/components/Container.astro +1 -1
- package/theme/components/CookieConsent.astro +1 -1
- package/theme/components/EndBlock.astro +1 -1
- package/theme/components/Footer.astro +5 -5
- package/theme/components/Header.astro +3 -3
- package/theme/components/Icon.astro +2 -2
- package/theme/components/MobileMenu.tsx +72 -61
- package/theme/components/MobileNav.astro +2 -2
- package/theme/components/PageHeader.astro +2 -2
- package/theme/components/PageSection.astro +1 -1
- package/theme/components/PopupButton.tsx +2 -2
- package/theme/components/PopupView.tsx +2 -2
- package/theme/components/PostList.tsx +7 -3
- package/theme/components/RichText.astro +22 -7
- package/theme/components/Section.astro +17 -10
- package/theme/components/SectionHeading.tsx +1 -1
- package/theme/components/home/PageBlock.astro +4 -4
- package/theme/components/home/Text.astro +3 -3
- package/theme/layouts/PageLayout.astro +3 -3
- package/theme/layouts/PostLayout.astro +9 -8
- package/theme/styles/base.css +13 -3
- package/theme/styles/blocks.css +97 -10
- package/theme/styles/components.css +9 -9
- package/theme/styles/elevation.test.mjs +168 -0
- package/theme/styles/global.css +65 -59
- package/theme/styles/ui.css +460 -131
- package/theme/templates/Home.astro +1 -1
- package/theme/templates/New.astro +5 -5
- package/theme/templates/NotFound.astro +2 -2
- package/theme/ui/BottomSheet.tsx +118 -21
- package/theme/ui/BottomSheetResizeIcon.tsx +13 -0
- package/theme/ui/Button.tsx +4 -3
- package/theme/ui/Dialog.tsx +1 -1
- package/theme/ui/FabMenu.tsx +13 -12
- package/theme/ui/LoadingIndicator.tsx +1 -1
- package/theme/ui/NavigationDrawer.tsx +116 -15
- package/theme/ui/NavigationRail.tsx +22 -13
- package/theme/ui/Search.tsx +1 -1
- package/theme/ui/SettingRow.tsx +31 -0
- package/theme/ui/Switch.tsx +11 -11
- package/theme/ui/TextField.tsx +17 -6
- package/theme/ui/bottomSheet.script.ts +3 -2
- package/theme/ui/bottomSheetControl.ts +131 -46
- package/theme/ui/fabMenu.script.ts +69 -53
- package/theme/ui/modalSheetSize.ts +214 -0
- package/theme/ui/navigationDrawerControl.ts +35 -0
- package/theme/ui/search.script.ts +4 -2
- package/worker/index.js +3 -0
- package/worker/plan-status.js +46 -0
- package/worker/push.js +7 -5
- package/worker/r2-status.js +2 -1
- package/icons/lucide/desktop.svg +0 -1
- package/icons/material-symbols/desktop.svg +0 -1
- package/icons/phosphor/desktop.svg +0 -1
package/src/ContainmentParts.tsx
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
// "Dele" › "UI-komponenter" (BlockParts.tsx), the group "Dialoger, ark og karrusel": the theme's dialog, bottom sheet and
|
|
2
|
-
// (packages/editor/theme/ui/, through @site/editor) as the site draws them, with made-up texts and the plain
|
|
3
|
-
// basic dialog, one with an icon, a list and a divider, the container alone (a pop-up's box) and a
|
|
4
|
-
// modal bottom sheet
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// "Dele" › "UI-komponenter" (BlockParts.tsx), the group "Dialoger, ark og karrusel": the theme's dialog, bottom sheet and
|
|
2
|
+
// carousel (packages/editor/theme/ui/, through @site/editor) as the site draws them, with made-up texts and the plain
|
|
3
|
+
// picture: a basic dialog, one with an icon, a list and a divider, the container alone (a pop-up's box) and a
|
|
4
|
+
// full-screen one; the modal bottom sheet, three times, one per wide (a button opens it in a real dialog: on a phone at
|
|
5
|
+
// half height, pulled up to full height; on a computer a dialog, a side sheet or a sheet with margins) and the standard
|
|
6
|
+
// one collapsed, half expanded and expanded (and with the resize and collapse buttons) at the bottom of a frame with a
|
|
7
|
+
// page behind it (it can be dragged and tapped there, as on the site; from the medium width they become a side sheet,
|
|
8
|
+
// and one with wide="sheet" stays a sheet); and the carousel in each of Material's
|
|
9
|
+
// layouts. The modal sheet, the standard sheet and the carousels do something here; the other buttons lead nowhere.
|
|
7
10
|
import type { ReactNode } from 'react';
|
|
8
|
-
import { SiteBottomSheet, SiteButton, SiteCarousel, SiteCheckbox, SiteDialog, SiteTextField
|
|
11
|
+
import { SiteBottomSheet, SiteButton, SiteCarousel, SiteCheckbox, SiteDialog, SiteTextField } from './themeUi';
|
|
12
|
+
import ModalSheetSample from './ModalSheetSample';
|
|
9
13
|
import { Part } from './PartsShown';
|
|
10
14
|
import { Preview } from './ui';
|
|
11
15
|
import { examplePicture } from './examples';
|
|
@@ -44,7 +48,7 @@ function Dialogs() {
|
|
|
44
48
|
</>
|
|
45
49
|
);
|
|
46
50
|
return (
|
|
47
|
-
<div className="grid gap-6 p-4
|
|
51
|
+
<div className="grid gap-6 p-4 expanded:grid-cols-2">
|
|
48
52
|
<Shown label={say.dialogKinds.basic}>
|
|
49
53
|
<SiteDialog headline={say.heading} supportingText={say.text} actions={actions} />
|
|
50
54
|
</Shown>
|
|
@@ -87,37 +91,85 @@ function Dialogs() {
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
function BottomSheets() {
|
|
90
|
-
|
|
94
|
+
// The frame holds the sheet (contain: layout) and is as high as a phone screen to it (a size container).
|
|
95
|
+
const frame = (label: string, sheet: ReactNode) => (
|
|
91
96
|
<Shown label={label}>
|
|
92
|
-
|
|
93
|
-
<div className="[container-type:size] relative h-96 overflow-hidden rounded-xl border border-line [contain:layout]">
|
|
97
|
+
<div className="[container-type:size] relative h-160 overflow-hidden rounded-xl border border-line [contain:layout]">
|
|
94
98
|
<p className="p-4 text-muted">{say.page}</p>
|
|
95
|
-
|
|
96
|
-
kind="standard"
|
|
97
|
-
state={state}
|
|
98
|
-
headline={say.heading}
|
|
99
|
-
supportingText={say.sheetText}
|
|
100
|
-
dragHandleLabel={say.dragHandle}
|
|
101
|
-
>
|
|
102
|
-
{numbers.slice(0, 4).map((n) => (
|
|
103
|
-
<p key={n} className="mb-3 text-muted">
|
|
104
|
-
{say.text}
|
|
105
|
-
</p>
|
|
106
|
-
))}
|
|
107
|
-
</SiteBottomSheet>
|
|
99
|
+
{sheet}
|
|
108
100
|
</div>
|
|
109
101
|
</Shown>
|
|
110
102
|
);
|
|
103
|
+
const texts = (count: number) =>
|
|
104
|
+
Array.from({ length: count }, (_, i) => (
|
|
105
|
+
<p key={i + 1} className="mb-3 text-muted">
|
|
106
|
+
{say.text}
|
|
107
|
+
</p>
|
|
108
|
+
));
|
|
109
|
+
const standard = (state: 'collapsed' | 'half-expanded' | 'expanded', label: string) =>
|
|
110
|
+
frame(
|
|
111
|
+
label,
|
|
112
|
+
<SiteBottomSheet
|
|
113
|
+
kind="standard"
|
|
114
|
+
state={state}
|
|
115
|
+
headline={say.heading}
|
|
116
|
+
supportingText={say.sheetText}
|
|
117
|
+
peek={say.sheetPeek}
|
|
118
|
+
dragHandleLabel={say.dragHandle}
|
|
119
|
+
>
|
|
120
|
+
{texts(4)}
|
|
121
|
+
</SiteBottomSheet>,
|
|
122
|
+
);
|
|
111
123
|
return (
|
|
112
|
-
<div className="grid gap-6 p-4
|
|
124
|
+
<div className="grid gap-6 p-4 expanded:grid-cols-2">
|
|
113
125
|
<Shown label={say.sheetKinds.modal}>
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
<ModalSheetSample />
|
|
127
|
+
</Shown>
|
|
128
|
+
<Shown label={say.sheetKinds.modalSideSheet}>
|
|
129
|
+
<ModalSheetSample wide="side-sheet" label={say.openSideSheet} />
|
|
130
|
+
</Shown>
|
|
131
|
+
<Shown label={say.sheetKinds.modalSheet}>
|
|
132
|
+
<ModalSheetSample wide="sheet" label={say.openWideSheet} />
|
|
117
133
|
</Shown>
|
|
118
134
|
{standard('collapsed', say.sheetKinds.collapsed)}
|
|
119
135
|
{standard('half-expanded', say.sheetKinds.halfExpanded)}
|
|
120
136
|
{standard('expanded', say.sheetKinds.expanded)}
|
|
137
|
+
{frame(
|
|
138
|
+
say.sheetKinds.wideSheet,
|
|
139
|
+
<SiteBottomSheet
|
|
140
|
+
kind="standard"
|
|
141
|
+
wide="sheet"
|
|
142
|
+
headline={say.heading}
|
|
143
|
+
supportingText={say.sheetText}
|
|
144
|
+
dragHandleLabel={say.dragHandle}
|
|
145
|
+
>
|
|
146
|
+
{texts(4)}
|
|
147
|
+
</SiteBottomSheet>,
|
|
148
|
+
)}
|
|
149
|
+
{frame(
|
|
150
|
+
say.sheetKinds.fixed,
|
|
151
|
+
<SiteBottomSheet kind="standard" states={['expanded']} dragHandle={false} label={say.sheetLabel}>
|
|
152
|
+
<p className="text-muted">{say.text}</p>
|
|
153
|
+
</SiteBottomSheet>,
|
|
154
|
+
)}
|
|
155
|
+
{frame(
|
|
156
|
+
say.sheetKinds.resizeButton,
|
|
157
|
+
<SiteBottomSheet kind="standard" headline={say.heading} dragHandle={false} resizeLabel={say.resize}>
|
|
158
|
+
{texts(4)}
|
|
159
|
+
</SiteBottomSheet>,
|
|
160
|
+
)}
|
|
161
|
+
{frame(
|
|
162
|
+
say.sheetKinds.collapseButton,
|
|
163
|
+
<SiteBottomSheet
|
|
164
|
+
kind="standard"
|
|
165
|
+
state="expanded"
|
|
166
|
+
headline={say.heading}
|
|
167
|
+
dragHandleLabel={say.dragHandle}
|
|
168
|
+
collapseLabel={say.collapse}
|
|
169
|
+
>
|
|
170
|
+
{texts(12)}
|
|
171
|
+
</SiteBottomSheet>,
|
|
172
|
+
)}
|
|
121
173
|
</div>
|
|
122
174
|
);
|
|
123
175
|
}
|
package/src/Deleted.tsx
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
// in one commit, under its old name: a page or post at its old address (not when another has it, or
|
|
5
5
|
// the site sends it on, which it says, and where to), a price or testimonial last in the list. A
|
|
6
6
|
// file from "Filer" is shown in a sheet first (FileView.tsx; a PDF, a video or a zip file with its icon
|
|
7
|
-
// and name over it), with "Gendan" under it.
|
|
7
|
+
// and name over it), with "Gendan" under it, and comes back with its record of tags (fileRecords.mjs) when it
|
|
8
|
+
// was deleted together with it and no live record has its address.
|
|
8
9
|
import { useEffect, useRef, useState } from 'react';
|
|
9
10
|
import { ArrowCounterClockwise } from '@phosphor-icons/react';
|
|
10
11
|
import Screen from './Screen';
|
|
11
12
|
import { Button, listClass, BottomSheet, SheetTitle, EmptyNote, SheetBody, ProblemNote, ListItem } from './ui';
|
|
12
|
-
import { fileKindOf, typeOf } from './images';
|
|
13
|
+
import { fileKindOf, publicPath, typeOf } from './images';
|
|
14
|
+
import { deletedRecordFor, recordsIn } from './fileRecords.mjs';
|
|
13
15
|
import { fileIcons, FileView, kindOf } from './FileView';
|
|
14
16
|
import { ask } from './ask';
|
|
15
17
|
import { REDIRECT_FILES } from './redirectData';
|
|
@@ -99,14 +101,26 @@ export default function Deleted({ id, files, env }: { id: string; files: RepoFil
|
|
|
99
101
|
const { order: _, ...rest } = JSON.parse(file.text) as Record<string, unknown>;
|
|
100
102
|
content = `${JSON.stringify({ order: env.nextOrder(collection!), ...rest }, null, 2)}\n`;
|
|
101
103
|
}
|
|
104
|
+
// A file from "Filer" comes back with its record of tags: the one deleted in the same commit (same time).
|
|
105
|
+
const changes: { path: string; content: string | Uint8Array; oid: string | null }[] = [{ path: file.path, content, oid: null }];
|
|
106
|
+
if (pictures) {
|
|
107
|
+
const deletedRecords = await env.deleted(config.fileRecords, { text: true });
|
|
108
|
+
const record = deletedRecordFor(publicPath(file.path), file.at, deletedRecords, recordsIn(files, config.fileRecords));
|
|
109
|
+
if (record) changes.push({ path: record.path, content: record.text, oid: null });
|
|
110
|
+
}
|
|
102
111
|
const what = pictures ? siteT.media.kinds[kindOf(file.path)].word : asWritten(collection!).singular;
|
|
103
112
|
const change = t.app.changes.restored(kindAndName(pictures ? t.media.kinds[kindOf(file.path)].word : collection!.singular, name));
|
|
104
113
|
const oids = await env.commit(
|
|
105
114
|
siteT.history.commit.restoreDeleted(what, `"${pictures ? fileName(file.path) : slugOf(file.path, folder)}"`),
|
|
106
|
-
|
|
115
|
+
changes,
|
|
107
116
|
change,
|
|
108
117
|
);
|
|
109
|
-
env.onSaved(
|
|
118
|
+
env.onSaved(
|
|
119
|
+
{ path: file.path, oid: oids[file.path], text: typeof content === 'string' ? content : '' },
|
|
120
|
+
changes.length > 1
|
|
121
|
+
? { files: changes.slice(1).map((c) => ({ path: c.path, oid: oids[c.path], text: String(c.content) })), removed: [] }
|
|
122
|
+
: undefined,
|
|
123
|
+
);
|
|
110
124
|
setDeleted((all) => all?.filter((f) => f.path !== file.path) ?? null);
|
|
111
125
|
dialog.current?.close();
|
|
112
126
|
env.notify({ text: savedText(change), kind: 'saved', edit: pictures ? undefined : { to: file.path, text: t.history.open } });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The editor's footer: one line under the content of every screen (Screen.tsx), with a link to "Licenser" (#licenser,
|
|
2
|
+
// Licenses.tsx). Exposes EditorFooter. Uses LinkButton from ui.tsx; the safe area at the bottom is Screen's main.
|
|
3
|
+
import { t } from './i18n';
|
|
4
|
+
import { LinkButton } from './ui';
|
|
5
|
+
|
|
6
|
+
export default function EditorFooter() {
|
|
7
|
+
return (
|
|
8
|
+
<footer className="mt-12 flex justify-center">
|
|
9
|
+
<LinkButton href="#licenser" kind="text" size="small">
|
|
10
|
+
{t.common.parts.licenses}
|
|
11
|
+
</LinkButton>
|
|
12
|
+
</footer>
|
|
13
|
+
);
|
|
14
|
+
}
|
package/src/EntryForm.tsx
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Only what was changed is written: untouched fields keep their exact text, and the body is only
|
|
3
3
|
// rewritten by Milkdown when the text was edited (packages/editor/scripts/editor-roundtrip.mjs checks that this
|
|
4
4
|
// gives the same page). A new address (Adresse) moves the page or post, with a redirect from the
|
|
5
|
-
// old one (addresses.ts).
|
|
5
|
+
// old one (addresses.ts). The Filer files the page newly refers to get the page's tags in the same commit (autoTags.mjs).
|
|
6
|
+
// An earlier version (Versions.tsx) is opened as changes that are not saved,
|
|
6
7
|
// at the address the page or post has now.
|
|
8
|
+
// The form's sections are listed in a pane (large and wider) or a side sheet (below): SectionList in ui.tsx.
|
|
7
9
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
8
10
|
import { flushSync } from 'react-dom';
|
|
9
11
|
import { slugify, slugOf, type Field, type MarkdownCollection } from './schema';
|
|
@@ -22,8 +24,11 @@ import { useBlockDialog } from './BlockDialog';
|
|
|
22
24
|
import { blockHtml } from '../lib/blocks.mjs';
|
|
23
25
|
import { sectionBlocks } from './blocks';
|
|
24
26
|
import { looksOf, pageH1 } from './sections.mjs';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
+
import { sectionInView, spyLine } from '../lib/section-in-view.mjs';
|
|
28
|
+
import { publicPath, shownAt, useUploads } from './images';
|
|
29
|
+
import { filerAddresses, useMediaPicker } from './Media';
|
|
30
|
+
import { autoTagChanges, newlyUsed } from './autoTags.mjs';
|
|
31
|
+
import { recordsIn } from './fileRecords.mjs';
|
|
27
32
|
import { inUse, problemAt } from './check';
|
|
28
33
|
import { isOver } from '../lib/notice.mjs';
|
|
29
34
|
import { splitPicture } from '../lib/crop.mjs';
|
|
@@ -34,11 +39,13 @@ import {
|
|
|
34
39
|
CheckList,
|
|
35
40
|
FoldSection,
|
|
36
41
|
FormColumns,
|
|
37
|
-
ModalNavigationDrawer,
|
|
38
|
-
NavigationDrawer,
|
|
39
|
-
NavigationDrawerButton,
|
|
40
42
|
type NavigationItem,
|
|
43
|
+
SectionList,
|
|
44
|
+
SectionPane,
|
|
45
|
+
SectionSheet,
|
|
46
|
+
SectionsButton,
|
|
41
47
|
SharePreview,
|
|
48
|
+
useAtLeast,
|
|
42
49
|
useInView,
|
|
43
50
|
} from './ui';
|
|
44
51
|
import FormFrame, { savedStatus, useSavedAt } from './FormFrame';
|
|
@@ -60,8 +67,8 @@ const SHARE = SHARE_FIELDS;
|
|
|
60
67
|
// The sections of the form, under each other on one page, each folding in and out under its heading (FoldSection in
|
|
61
68
|
// ui.tsx): the text with the title and heading (open from the start), the status, the other fields, those for search
|
|
62
69
|
// engines, those for social media, and the earlier versions (folded from the start, so the page is short; nothing is remembered till
|
|
63
|
-
// the next time). Its
|
|
64
|
-
// right of the top bar) opens one and scrolls to it, and marks the one in view. A field that keeps the form from being
|
|
70
|
+
// the next time). Its list (on large and wider screens in a pane at the end of the form, narrower in a side sheet from the
|
|
71
|
+
// button at the right of the top bar) opens one and scrolls to it, and marks the one in view. A field that keeps the form from being
|
|
65
72
|
// saved has its section opened and is scrolled to (FieldInput in fields.tsx).
|
|
66
73
|
type Section = 'content' | 'status' | 'other' | 'seo' | 'share' | 'versions';
|
|
67
74
|
const sectionOf = (path: string): Section => {
|
|
@@ -102,9 +109,8 @@ export const newMarkdown = (collection: MarkdownCollection, off: string[] = []):
|
|
|
102
109
|
|
|
103
110
|
// The address of a saved page or post: a page's own `path`, or else /<file name>/ (packages/editor/site/content.ts).
|
|
104
111
|
// '' for a new one.
|
|
105
|
-
// Where the redirects are, as the
|
|
106
|
-
|
|
107
|
-
const redirectsPath = () => [placeOf('seo'), t.common.parts.seo, t.common.parts.redirects, t.redirects.all].filter(Boolean).join(' › ');
|
|
112
|
+
// Where the redirects are, as the menu has it: "Indhold" › "Videresendelser" › "Alle videresendelser".
|
|
113
|
+
const redirectsPath = () => [placeOf('videresendelser'), t.common.parts.redirects, t.redirects.all].filter(Boolean).join(' › ');
|
|
108
114
|
|
|
109
115
|
function addressNow(collection: MarkdownCollection, file: RepoFile, data: Record<string, unknown>) {
|
|
110
116
|
if (!file.path) return '';
|
|
@@ -220,8 +226,11 @@ function Form({
|
|
|
220
226
|
(text) => notify({ text, error: true }),
|
|
221
227
|
(path) => unfold(sectionOf(path)),
|
|
222
228
|
);
|
|
223
|
-
// On
|
|
224
|
-
|
|
229
|
+
// On large and wider screens: the sections pane at the end of the form, until its button puts it away; narrower, the
|
|
230
|
+
// sections are in a side sheet, opened by the same button.
|
|
231
|
+
const large = useAtLeast('large');
|
|
232
|
+
const [pane, setPane] = useState(true);
|
|
233
|
+
const [sheet, setSheet] = useState(false);
|
|
225
234
|
const has = (id: Section) => collection.fields.some(inSection(id));
|
|
226
235
|
const sections: NavigationItem[] = [
|
|
227
236
|
{ id: 'content', label: t.forms.sections.content, icon: Article },
|
|
@@ -232,8 +241,8 @@ function Form({
|
|
|
232
241
|
...(isNew ? [] : [{ id: 'versions', label: t.history.versions, icon: ClockCounterClockwise }]),
|
|
233
242
|
];
|
|
234
243
|
const shows = (id: Section) => sections.some((s) => s.id === id);
|
|
235
|
-
// The section in view, marked in the
|
|
236
|
-
// screen (at the bottom of the page, the last one in view). A tap in the
|
|
244
|
+
// The section in view, marked in the sections list: while the page scrolls, the last one whose top is above a third of the
|
|
245
|
+
// screen (at the bottom of the page, the last one in view). A tap in the list marks its own and scrolls there; the
|
|
237
246
|
// mark stays meanwhile (`steering`, until the scroll ends).
|
|
238
247
|
const [section, setSection] = useState<Section>('content');
|
|
239
248
|
const boxes = useRef<Partial<Record<Section, HTMLElement | null>>>({});
|
|
@@ -249,7 +258,7 @@ function Form({
|
|
|
249
258
|
// The sections that are open; at first only the text (and "Søgemaskiner" when it is opened at it, `opening`).
|
|
250
259
|
const [open, setOpen] = useState<ReadonlySet<Section>>(() => new Set(['content', ...(opening === 'seo' ? ['seo' as const] : [])]));
|
|
251
260
|
const unfold = (id: Section) => setOpen((was) => (was.has(id) ? was : new Set([...was, id])));
|
|
252
|
-
// A section opened by its heading is marked in the
|
|
261
|
+
// A section opened by its heading is marked in the sections list; one folded, the one in view now (the page is shorter,
|
|
253
262
|
// without a scroll).
|
|
254
263
|
const toggle = (id: Section) => {
|
|
255
264
|
const opening = !open.has(id);
|
|
@@ -267,14 +276,11 @@ function Form({
|
|
|
267
276
|
spy.current = () => {
|
|
268
277
|
frame = 0;
|
|
269
278
|
if (performance.now() < steering.current) return;
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (top !== undefined && (here === undefined || top <= line)) here = id;
|
|
276
|
-
}
|
|
277
|
-
if (here) setSection(here);
|
|
279
|
+
const atBottom = innerHeight + scrollY >= document.documentElement.scrollHeight - 8;
|
|
280
|
+
const tops: Record<string, number | undefined> = {};
|
|
281
|
+
for (const id of order.current) tops[id] = boxes.current[id]?.getBoundingClientRect().top;
|
|
282
|
+
const here = sectionInView(order.current, tops, spyLine(innerHeight, atBottom));
|
|
283
|
+
if (here) setSection(here as Section);
|
|
278
284
|
};
|
|
279
285
|
const onScroll = () => {
|
|
280
286
|
if (!frame) frame = requestAnimationFrame(spy.current);
|
|
@@ -285,7 +291,7 @@ function Form({
|
|
|
285
291
|
cancelAnimationFrame(frame);
|
|
286
292
|
};
|
|
287
293
|
}, []);
|
|
288
|
-
// Opens a section and scrolls to it (or to `box` in it: the text), after the
|
|
294
|
+
// Opens a section and scrolls to it (or to `box` in it: the text), after the side sheet below large has been told to close (the page does
|
|
289
295
|
// not scroll while a dialog is open). It is opened at once (flushSync), so it is there to scroll to.
|
|
290
296
|
const textBox = useRef<HTMLDivElement>(null);
|
|
291
297
|
const goTo = (id: Section, box?: HTMLElement | null) => {
|
|
@@ -301,11 +307,11 @@ function Form({
|
|
|
301
307
|
if (shows('seo')) goTo('seo');
|
|
302
308
|
}, []);
|
|
303
309
|
// The field marked as wrong is scrolled to by itself (FieldInput), its section opened (useWrongField) and marked in the
|
|
304
|
-
//
|
|
310
|
+
// sections list.
|
|
305
311
|
useEffect(() => {
|
|
306
312
|
if (wrongField.field) steer(sectionOf(wrongField.field.path));
|
|
307
313
|
}, [wrongField.field?.at]);
|
|
308
|
-
// A section: where it is, for the
|
|
314
|
+
// A section: where it is, for the sections list, its heading (its name in the list), and whether it is open.
|
|
309
315
|
const sectionProps = (id: Section) => ({
|
|
310
316
|
ref: (el: HTMLElement | null) => void (boxes.current[id] = el),
|
|
311
317
|
title: sections.find((s) => s.id === id)!.label,
|
|
@@ -362,7 +368,9 @@ function Form({
|
|
|
362
368
|
site: sharing,
|
|
363
369
|
});
|
|
364
370
|
|
|
365
|
-
// A picture is kept in the browser until "Gem", which uploads it with the page.
|
|
371
|
+
// A picture is kept in the browser until "Gem", which uploads it with the page. A file in Filer the page refers to
|
|
372
|
+
// now and did not in its last saved version (a picture, a PDF field, a link) gets the page's tags at "Gem" (autoTags.mjs).
|
|
373
|
+
const tagged = collection.fields.some((f) => f.name === 'categories' && f.type === 'relation' && f.to === 'categories');
|
|
366
374
|
const pictures = useUploads(notify);
|
|
367
375
|
const [pickImage, picker] = useMediaPicker(env.media, pictures);
|
|
368
376
|
|
|
@@ -525,12 +533,22 @@ function Form({
|
|
|
525
533
|
// Pictures that were added and are still used.
|
|
526
534
|
const used = pictures.usedIn(next);
|
|
527
535
|
const removed = base.path && path !== base.path ? [base.path] : [];
|
|
536
|
+
// The page's tags to the Filer files it newly refers to (the text as last saved against the text now), in the same commit.
|
|
537
|
+
const recordChanges = tagged
|
|
538
|
+
? autoTagChanges(
|
|
539
|
+
recordsIn(env.files, config.fileRecords),
|
|
540
|
+
config.fileRecords,
|
|
541
|
+
newlyUsed(isNew ? '' : base.text, next, [...new Set([...filerAddresses(env.files), ...used.map((u) => publicPath(u.path))])]),
|
|
542
|
+
Array.isArray(saved.categories) ? (saved.categories as string[]) : [],
|
|
543
|
+
)
|
|
544
|
+
: [];
|
|
528
545
|
const changes: Change[] = [
|
|
529
546
|
{ path, content: next, oid: path === base.path ? base.oid || null : null },
|
|
530
547
|
...removed.map((p) => ({ path: p, content: null, oid: base.oid })),
|
|
531
548
|
...others.map((f) => ({ path: f.path, content: f.text, oid: f.oid })),
|
|
532
549
|
...redirectChanges,
|
|
533
550
|
...used.map((u) => ({ path: u.path, content: u.bytes, oid: null })),
|
|
551
|
+
...recordChanges,
|
|
534
552
|
];
|
|
535
553
|
const what = `"${slugOf(path, collection.folder)}"`;
|
|
536
554
|
const message = moved
|
|
@@ -565,6 +583,9 @@ function Form({
|
|
|
565
583
|
...redirectChanges
|
|
566
584
|
.filter((c) => c.path === REDIRECTS)
|
|
567
585
|
.map((c) => ({ path: REDIRECTS, oid: oids[REDIRECTS], text: String(c.content) })),
|
|
586
|
+
...recordChanges
|
|
587
|
+
.filter((c) => typeof c.content === 'string')
|
|
588
|
+
.map((c) => ({ path: c.path, oid: oids[c.path], text: String(c.content) })),
|
|
568
589
|
],
|
|
569
590
|
removed,
|
|
570
591
|
});
|
|
@@ -657,14 +678,11 @@ function Form({
|
|
|
657
678
|
status={savedStatus({ savedAt, saving, deleting, isNew })}
|
|
658
679
|
view={isNew ? undefined : url}
|
|
659
680
|
drawer={
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
<NavigationDrawerButton label={t.forms.sections.title} open={drawer} onClick={() => setDrawer(!drawer)} />
|
|
666
|
-
</div>
|
|
667
|
-
</>
|
|
681
|
+
<SectionsButton
|
|
682
|
+
label={t.forms.sections.title}
|
|
683
|
+
open={large ? pane : sheet}
|
|
684
|
+
onClick={() => (large ? setPane(!pane) : setSheet(true))}
|
|
685
|
+
/>
|
|
668
686
|
}
|
|
669
687
|
dirty={dirty}
|
|
670
688
|
saving={saving || deleting}
|
|
@@ -673,12 +691,10 @@ function Form({
|
|
|
673
691
|
onPreview={showPreview}
|
|
674
692
|
onDelete={collection.deletable && !isNew ? () => env.remove(collection, base, setDeleting) : undefined}
|
|
675
693
|
deleteLabel={t.forms.deleteOne(collection.singular)}
|
|
676
|
-
panel
|
|
677
694
|
>
|
|
678
695
|
{version && <VersionNotice version={version} onCurrent={() => onVersion(null)} />}
|
|
679
696
|
<FormColumns
|
|
680
697
|
sticky
|
|
681
|
-
wideOnly
|
|
682
698
|
main={
|
|
683
699
|
<>
|
|
684
700
|
<FoldSection {...sectionProps('content')}>
|
|
@@ -700,6 +716,7 @@ function Form({
|
|
|
700
716
|
: undefined
|
|
701
717
|
}
|
|
702
718
|
sections={front}
|
|
719
|
+
frame
|
|
703
720
|
/>
|
|
704
721
|
</div>
|
|
705
722
|
</FoldSection>
|
|
@@ -739,10 +756,29 @@ function Form({
|
|
|
739
756
|
)}
|
|
740
757
|
</>
|
|
741
758
|
}
|
|
742
|
-
|
|
759
|
+
pane={
|
|
760
|
+
pane ? (
|
|
761
|
+
<SectionPane title={t.forms.sections.title} open>
|
|
762
|
+
<SectionList title={t.forms.sections.title} items={sections} value={section} onChange={pick} />
|
|
763
|
+
</SectionPane>
|
|
764
|
+
) : undefined
|
|
765
|
+
}
|
|
743
766
|
/>
|
|
744
767
|
</FormFrame>
|
|
745
768
|
{blockDialog}
|
|
769
|
+
{!large && (
|
|
770
|
+
<SectionSheet title={t.forms.sections.title} open={sheet} onClose={() => setSheet(false)}>
|
|
771
|
+
<SectionList
|
|
772
|
+
title={t.forms.sections.title}
|
|
773
|
+
items={sections}
|
|
774
|
+
value={section}
|
|
775
|
+
onChange={(id) => {
|
|
776
|
+
setSheet(false);
|
|
777
|
+
pick(id);
|
|
778
|
+
}}
|
|
779
|
+
/>
|
|
780
|
+
</SectionSheet>
|
|
781
|
+
)}
|
|
746
782
|
{picker}
|
|
747
783
|
{previewSheet}
|
|
748
784
|
</WrongField.Provider>
|
package/src/FormFrame.tsx
CHANGED
|
@@ -13,7 +13,6 @@ import { hasUnsaved, setUnsaved } from './deploy';
|
|
|
13
13
|
import { askToNotify } from './notificationData';
|
|
14
14
|
import { choose } from './ask';
|
|
15
15
|
import { when } from './when';
|
|
16
|
-
import { useSidebar } from './sidebar';
|
|
17
16
|
import type { Env } from './App';
|
|
18
17
|
import { t } from './i18n';
|
|
19
18
|
|
|
@@ -33,7 +32,7 @@ type Props<T extends string> = {
|
|
|
33
32
|
view?: string;
|
|
34
33
|
/** More in the top bar, before the link to the site. */
|
|
35
34
|
actions?: ReactNode;
|
|
36
|
-
/** The
|
|
35
|
+
/** The button at the end of the top bar, after the account (a form's SectionsButton in ui.tsx). */
|
|
37
36
|
drawer?: ReactNode;
|
|
38
37
|
/** The form's tabs, in the top bar (Screen). */
|
|
39
38
|
tabs?: TabChoice<T>;
|
|
@@ -150,8 +149,6 @@ export default function FormFrame<T extends string>({
|
|
|
150
149
|
wide,
|
|
151
150
|
children,
|
|
152
151
|
}: Props<T>) {
|
|
153
|
-
// On a computer the save bar starts where the sidebar (or its rail, sidebar.ts) ends.
|
|
154
|
-
const sidebar = useSidebar();
|
|
155
152
|
const save = useRef(onSave);
|
|
156
153
|
save.current = onSave;
|
|
157
154
|
useEffect(() => {
|
|
@@ -207,10 +204,8 @@ export default function FormFrame<T extends string>({
|
|
|
207
204
|
</div>
|
|
208
205
|
)}
|
|
209
206
|
{(dirty || saving) && (
|
|
210
|
-
<div
|
|
211
|
-
className=
|
|
212
|
-
>
|
|
213
|
-
<div className="mx-auto max-w-3xl px-4 py-3 md:px-6">
|
|
207
|
+
<div className="editor-save-bar fixed inset-x-0 bottom-0 z-20 border-t border-line bg-surface/90 pb-[env(safe-area-inset-bottom)] backdrop-blur-md">
|
|
208
|
+
<div className="mx-auto max-w-3xl px-4 py-3 medium:px-6">
|
|
214
209
|
{/* "Forhåndsvis" beside "Gem". */}
|
|
215
210
|
<div className={onPreview ? 'grid grid-cols-[auto_1fr] gap-2' : ''}>
|
|
216
211
|
{onPreview && (
|