@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/Media.tsx
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// saved with the tick: the file moves, the pages that use it get the new address in the same commit,
|
|
10
10
|
// and the old address sends on to it (addresses.ts). "+" uploads a new file, saved at once. The same
|
|
11
11
|
// pictures (not the other files) are the gallery a picture is chosen from in the forms (useMediaPicker).
|
|
12
|
+
// The funnel in Filer also filters by a tag (fileTagFilter.mjs; only tags some file has).
|
|
12
13
|
// The clock in the top bar, "Slettede filer", opens the files that were deleted (Deleted.tsx).
|
|
13
14
|
import { useEffect, useMemo, useRef, useState, type ReactElement } from 'react';
|
|
14
15
|
import {
|
|
@@ -41,10 +42,11 @@ import {
|
|
|
41
42
|
BottomSheet,
|
|
42
43
|
SheetTitle,
|
|
43
44
|
SideSheet,
|
|
44
|
-
|
|
45
|
+
useAtLeast,
|
|
45
46
|
EmptyNote,
|
|
46
47
|
SheetBody,
|
|
47
48
|
ListItem,
|
|
49
|
+
type Pickable,
|
|
48
50
|
} from './ui';
|
|
49
51
|
import type { Env } from './App';
|
|
50
52
|
import type { RepoFile } from './github';
|
|
@@ -54,6 +56,9 @@ import { collections } from './site';
|
|
|
54
56
|
import { readFields, splitFrontmatter } from './frontmatter.mjs';
|
|
55
57
|
import { fileKindOf, pickedKindOf, pickImage, prepareFile, prepareImage, publicPath, showMoved, shownAt, type Uploads } from './images';
|
|
56
58
|
import { fileIcons, FileView, KINDS, kindOf, type Kind } from './FileView';
|
|
59
|
+
import MediaTags from './MediaTags';
|
|
60
|
+
import { deleteRecord, recordsIn, renameRecord } from './fileRecords.mjs';
|
|
61
|
+
import { tagFilterOptions, tagIndex, withTag } from './fileTagFilter.mjs';
|
|
57
62
|
import { kindAndName, savedText } from './deploy';
|
|
58
63
|
import { locale, siteT, t } from './i18n';
|
|
59
64
|
|
|
@@ -72,6 +77,9 @@ export const filesOf = (files: RepoFile[]) =>
|
|
|
72
77
|
? files.filter((f) => f.path.startsWith(`${FILES}/`) && fileKindOf(f.path)).sort((a, b) => a.path.localeCompare(b.path, locale))
|
|
73
78
|
: [];
|
|
74
79
|
|
|
80
|
+
/** The addresses on the site of every file Filer lists: the pictures, then the PDFs, videos and zip files. */
|
|
81
|
+
export const filerAddresses = (files: RepoFile[]): string[] => [...mediaOf(files), ...filesOf(files)].map((f) => publicPath(f.path));
|
|
82
|
+
|
|
75
83
|
// What a file that uses a picture or another file is called: a page's or post's title, a fixed page's name, an
|
|
76
84
|
// entry's as its list shows it (a pop-up); null for a file the editor does not open (the list of
|
|
77
85
|
// picture sizes, image-manifest.json).
|
|
@@ -171,7 +179,7 @@ function Grid({ srcs, picked, onPick }: { srcs: string[]; picked?: string | null
|
|
|
171
179
|
const [thumbs, setThumbs] = useState<Record<string, string> | null>(null);
|
|
172
180
|
useEffect(() => void thumbnails().then(setThumbs), []);
|
|
173
181
|
return (
|
|
174
|
-
<ul className="grid grid-cols-3 gap-1.5
|
|
182
|
+
<ul className="grid grid-cols-3 gap-1.5 medium:grid-cols-4 expanded:grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] expanded:gap-2">
|
|
175
183
|
{srcs.map((src) => (
|
|
176
184
|
<li key={src}>
|
|
177
185
|
<button
|
|
@@ -200,15 +208,20 @@ const monthOf = (src: string) => {
|
|
|
200
208
|
};
|
|
201
209
|
/** The pictures with search, their order and the filters, and the month between them when they are
|
|
202
210
|
* in the order of their dates, the first letter when by name. used: the files a page uses (Filer): then
|
|
203
|
-
* the funnel opens a sheet with
|
|
211
|
+
* the funnel opens a sheet with the filters by use and by type, and by tag when tags is given (each file's
|
|
212
|
+
* tags, from tagIndex) and some file has one of the tagOptions. picked: the file shown beside it. */
|
|
204
213
|
function Gallery({
|
|
205
214
|
srcs,
|
|
206
215
|
used,
|
|
216
|
+
tags,
|
|
217
|
+
tagOptions = [],
|
|
207
218
|
picked,
|
|
208
219
|
onPick,
|
|
209
220
|
}: {
|
|
210
221
|
srcs: string[];
|
|
211
222
|
used?: Set<string>;
|
|
223
|
+
tags?: Map<string, string[]>;
|
|
224
|
+
tagOptions?: Pickable[];
|
|
212
225
|
picked?: string | null;
|
|
213
226
|
onPick: (src: string) => void;
|
|
214
227
|
}) {
|
|
@@ -226,6 +239,7 @@ function Gallery({
|
|
|
226
239
|
});
|
|
227
240
|
const [date, setDate] = useKept(all ? 'filer-dato' : null, '');
|
|
228
241
|
const [use, setUse] = useKept<'' | 'used' | 'unused'>(all ? 'filer-brug' : null, '');
|
|
242
|
+
const [tag, setTag] = useKept(all ? 'filer-tag' : null, '');
|
|
229
243
|
|
|
230
244
|
function pickSort(next: Sort) {
|
|
231
245
|
setSort(next);
|
|
@@ -239,12 +253,16 @@ function Gallery({
|
|
|
239
253
|
const years = [...new Set(months.filter(Boolean).map((m) => m.slice(0, 4)))];
|
|
240
254
|
// The types there are files of (and the one chosen); the filter by type only when there are two or more.
|
|
241
255
|
const kinds = KINDS.filter((k) => k === kind || srcs.some((src) => kindOf(src) === k));
|
|
256
|
+
// The tags some file has (and the one chosen), for the filter by tag.
|
|
257
|
+
const tagChoices = tags ? tagFilterOptions(tags, tagOptions, tag) : [];
|
|
258
|
+
const withTagged = tags ? new Set(withTag(srcs, tags, tag)) : null;
|
|
242
259
|
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
243
260
|
const shown = srcs
|
|
244
261
|
.filter((src) => words.every((w) => nameOfSrc(src).toLowerCase().includes(w)))
|
|
245
262
|
.filter((src) => !date || monthOf(src) === date || monthOf(src).startsWith(`${date}-`))
|
|
246
263
|
.filter((src) => !use || used?.has(src) === (use === 'used'))
|
|
247
264
|
.filter((src) => !kind || kindOf(src) === kind)
|
|
265
|
+
.filter((src) => !withTagged || withTagged.has(src))
|
|
248
266
|
.sort((a, b) =>
|
|
249
267
|
sort === 'name'
|
|
250
268
|
? nameOfSrc(a).localeCompare(nameOfSrc(b), locale)
|
|
@@ -256,6 +274,7 @@ function Gallery({
|
|
|
256
274
|
date && { label: dateName(date), clear: () => setDate('') },
|
|
257
275
|
use && { label: use === 'used' ? t.media.inUse : t.common.notInUse, clear: () => setUse('') },
|
|
258
276
|
kind && { label: t.media.kinds[kind].plural, clear: () => setKind('') },
|
|
277
|
+
tag && { label: tagOptions.find((o) => o.value === tag)?.label ?? tag, clear: () => setTag('') },
|
|
259
278
|
].filter((c) => !!c);
|
|
260
279
|
|
|
261
280
|
return (
|
|
@@ -277,7 +296,7 @@ function Gallery({
|
|
|
277
296
|
all && (
|
|
278
297
|
<Filters
|
|
279
298
|
title={t.media.filter}
|
|
280
|
-
selected={!!use || !!kind}
|
|
299
|
+
selected={!!use || !!kind || !!tag}
|
|
281
300
|
groups={[
|
|
282
301
|
{
|
|
283
302
|
title: t.media.use,
|
|
@@ -298,6 +317,12 @@ function Gallery({
|
|
|
298
317
|
],
|
|
299
318
|
onChange: (v: string) => setKind(v as typeof kind),
|
|
300
319
|
},
|
|
320
|
+
tagChoices.length > 0 && {
|
|
321
|
+
title: t.media.tagFilter,
|
|
322
|
+
value: tag,
|
|
323
|
+
options: [{ value: '', label: t.media.allTags }, ...tagChoices],
|
|
324
|
+
onChange: setTag,
|
|
325
|
+
},
|
|
301
326
|
].filter((g) => !!g)}
|
|
302
327
|
/>
|
|
303
328
|
)
|
|
@@ -393,7 +418,8 @@ const codeImages = () =>
|
|
|
393
418
|
.then((r) => (r.ok ? (r.json() as Promise<string[]>) : null))
|
|
394
419
|
.catch(() => null));
|
|
395
420
|
|
|
396
|
-
|
|
421
|
+
// open: the address of a file to show picked at once (the route filer/<address>); ignored when no such file exists.
|
|
422
|
+
export default function Media({ files, env, open }: { files: RepoFile[]; env: Env; open?: string }) {
|
|
397
423
|
// The pictures and the other files.
|
|
398
424
|
const pictures = [...mediaOf(files), ...filesOf(files)];
|
|
399
425
|
const [code, setCode] = useState<string[] | null>(null);
|
|
@@ -406,10 +432,13 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
406
432
|
.map((f) => f.text);
|
|
407
433
|
return new Set(pictures.map((p) => publicPath(p.path)).filter((src) => code?.includes(src) || texts.some((t) => t.includes(src))));
|
|
408
434
|
}, [files, code]);
|
|
435
|
+
// Each file's tags, for the filter by tag.
|
|
436
|
+
const tags = useMemo(() => tagIndex(recordsIn(files, config.fileRecords)), [files]);
|
|
409
437
|
const dialog = useRef<HTMLDialogElement>(null);
|
|
410
|
-
const
|
|
438
|
+
const opened = open && pictures.some((p) => publicPath(p.path) === open) ? open : null;
|
|
439
|
+
const [src, setSrc] = useState<string | null>(opened);
|
|
411
440
|
// On a computer the file picked is beside the grid; on a phone in a sheet.
|
|
412
|
-
const wide =
|
|
441
|
+
const wide = useAtLeast('expanded');
|
|
413
442
|
const closeFile = () => {
|
|
414
443
|
if (dialog.current?.open) dialog.current.close();
|
|
415
444
|
else setSrc(null);
|
|
@@ -424,9 +453,13 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
424
453
|
dialog.current.close();
|
|
425
454
|
} else if (!wide && !dialog.current?.open) dialog.current?.showModal();
|
|
426
455
|
}, [wide]);
|
|
456
|
+
// Opened at a file on a phone: the sheet opens with the screen.
|
|
457
|
+
useEffect(() => {
|
|
458
|
+
if (opened && !wide) dialog.current?.showModal();
|
|
459
|
+
}, []);
|
|
427
460
|
const [adding, setAdding] = useState(false);
|
|
428
461
|
const [deleting, setDeleting] = useState(false);
|
|
429
|
-
const [name, setName] = useState('');
|
|
462
|
+
const [name, setName] = useState(opened ? stemOf(opened) : '');
|
|
430
463
|
const [renaming, setRenaming] = useState(false);
|
|
431
464
|
// The name being changed: the field shown instead of the pencil after the name.
|
|
432
465
|
const [editing, setEditing] = useState(false);
|
|
@@ -463,7 +496,7 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
463
496
|
}
|
|
464
497
|
}
|
|
465
498
|
|
|
466
|
-
// A file nothing uses, deleted at once in its own commit (after asking).
|
|
499
|
+
// A file nothing uses, deleted at once in its own commit (after asking), with its record of tags.
|
|
467
500
|
async function remove(src: string) {
|
|
468
501
|
const file = pictures.find((p) => publicPath(p.path) === src);
|
|
469
502
|
if (!file || !(await ask(t.media.kinds[kindOf(src)].deleteAsk, t.common.delete))) return;
|
|
@@ -471,12 +504,13 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
471
504
|
env.notify({ text: t.common.deleting });
|
|
472
505
|
try {
|
|
473
506
|
const change = t.app.changes.deleted(kindAndName(t.media.kinds[kindOf(src)].word, fileName(src)));
|
|
507
|
+
const record = deleteRecord(recordsIn(files, config.fileRecords), src);
|
|
474
508
|
await env.commit(
|
|
475
509
|
siteT.media.commit.delete(siteT.media.kinds[kindOf(src)].word, src.split('/').pop()!),
|
|
476
|
-
[{ path: file.path, content: null, oid: file.oid }],
|
|
510
|
+
[{ path: file.path, content: null, oid: file.oid }, ...(record ? [record] : [])],
|
|
477
511
|
change,
|
|
478
512
|
);
|
|
479
|
-
env.forget([file.path]);
|
|
513
|
+
env.forget([file.path, ...(record ? [record.path] : [])]);
|
|
480
514
|
closeFile();
|
|
481
515
|
env.notify({ text: savedText(change), kind: 'saved' });
|
|
482
516
|
} catch (e) {
|
|
@@ -487,7 +521,8 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
487
521
|
}
|
|
488
522
|
|
|
489
523
|
// A new name for a picture, saved at once in one commit: the file under its new name (same folder
|
|
490
|
-
// and ending), the old one deleted, the texts with its address changed and the
|
|
524
|
+
// and ending), the old one deleted, the texts with its address changed, the redirect and the file's record of tags (same
|
|
525
|
+
// record file, its address changed).
|
|
491
526
|
async function rename(src: string) {
|
|
492
527
|
const file = pictures.find((p) => publicPath(p.path) === src);
|
|
493
528
|
const stem = slugify(name);
|
|
@@ -496,8 +531,16 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
496
531
|
const to = publicPath(path);
|
|
497
532
|
if (files.some((f) => f.path === path)) return env.notify({ text: t.media.nameTaken, error: true });
|
|
498
533
|
const texts = files
|
|
499
|
-
.filter(
|
|
534
|
+
.filter(
|
|
535
|
+
(f) =>
|
|
536
|
+
f.text &&
|
|
537
|
+
f.path !== REDIRECTS &&
|
|
538
|
+
!f.path.startsWith(`${MEDIA}/`) &&
|
|
539
|
+
!f.path.startsWith(`${config.fileRecords}/`) &&
|
|
540
|
+
f.text.includes(src),
|
|
541
|
+
)
|
|
500
542
|
.map((f) => ({ ...f, text: readdress(f.text, src, to) }));
|
|
543
|
+
const record = renameRecord(recordsIn(files, config.fileRecords), src, to);
|
|
501
544
|
setRenaming(true);
|
|
502
545
|
env.notify({ text: t.common.saving });
|
|
503
546
|
try {
|
|
@@ -520,13 +563,20 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
520
563
|
{ path: file.path, content: null, oid: file.oid },
|
|
521
564
|
...texts.map((f) => ({ path: f.path, content: f.text, oid: files.find((x) => x.path === f.path)!.oid })),
|
|
522
565
|
...redirectChanges,
|
|
566
|
+
...(record ? [record] : []),
|
|
523
567
|
],
|
|
524
568
|
change,
|
|
525
569
|
);
|
|
526
570
|
showMoved(src, to);
|
|
527
571
|
env.onSaved(
|
|
528
572
|
{ path, oid: oids[path], text: '' },
|
|
529
|
-
{
|
|
573
|
+
{
|
|
574
|
+
files: [...texts, ...moved, ...(record ? [{ path: record.path, oid: '', text: String(record.content) }] : [])].map((f) => ({
|
|
575
|
+
...f,
|
|
576
|
+
oid: oids[f.path],
|
|
577
|
+
})),
|
|
578
|
+
removed: [file.path],
|
|
579
|
+
},
|
|
530
580
|
);
|
|
531
581
|
setSrc(to);
|
|
532
582
|
setName(stem);
|
|
@@ -615,6 +665,7 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
615
665
|
<p className="text-muted">{t.media.noPages}</p>
|
|
616
666
|
)}
|
|
617
667
|
</div>
|
|
668
|
+
<MediaTags key={src} src={src} files={files} env={env} />
|
|
618
669
|
{code && !used.has(src) && (
|
|
619
670
|
<Button kind="outlined" wide icon={Trash} disabled={deleting} onClick={() => remove(src)}>
|
|
620
671
|
{deleting ? t.common.deleting : t.media.kinds[kindOf(src)].delete}
|
|
@@ -640,11 +691,13 @@ export default function Media({ files, env }: { files: RepoFile[]; env: Env }) {
|
|
|
640
691
|
</>
|
|
641
692
|
}
|
|
642
693
|
>
|
|
643
|
-
<div className="
|
|
694
|
+
<div className="expanded:flex expanded:items-start expanded:gap-6">
|
|
644
695
|
<div className="min-w-0 flex-1">
|
|
645
696
|
<Gallery
|
|
646
|
-
srcs={
|
|
697
|
+
srcs={filerAddresses(files)}
|
|
647
698
|
used={used}
|
|
699
|
+
tags={tags}
|
|
700
|
+
tagOptions={env.relations.categories}
|
|
648
701
|
picked={wide ? src : undefined}
|
|
649
702
|
onPick={(src) => {
|
|
650
703
|
setSrc(src);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// The tags of a file in Filer (Media.tsx): its tags as chips and "Tilføj tag", from the file's record (src/fileRecords.mjs).
|
|
2
|
+
// Each change is saved at once in its own commit, one save at a time; taps during a save go into the next.
|
|
3
|
+
// Exposes MediaTags; uses MultiChoice and SheetLink (ui.tsx), setTags (fileTags.mjs), writeRecords (fileRecords.mjs).
|
|
4
|
+
import { useRef, useState, type ReactElement } from 'react';
|
|
5
|
+
import { config } from '@site/editor';
|
|
6
|
+
import type { Env } from './App';
|
|
7
|
+
import { kindAndName, savedText } from './deploy';
|
|
8
|
+
import { leaveTo } from './FormFrame';
|
|
9
|
+
import { kindOf } from './FileView';
|
|
10
|
+
import { recordFor, recordsIn, writeRecords, type StoredRecord } from './fileRecords.mjs';
|
|
11
|
+
import { setTags } from './fileTags.mjs';
|
|
12
|
+
import type { RepoFile } from './github';
|
|
13
|
+
import { siteT, t } from './i18n';
|
|
14
|
+
import { MultiChoice, SheetLink } from './ui';
|
|
15
|
+
|
|
16
|
+
const fileName = (src: string) => src.split('/').pop() ?? '';
|
|
17
|
+
|
|
18
|
+
export default function MediaTags({ src, files, env }: { src: string; files: RepoFile[]; env: Env }): ReactElement {
|
|
19
|
+
// The record as last stored: from the files at first, then from each save's result (the files may not have updated yet).
|
|
20
|
+
const stored = useRef<StoredRecord | undefined>(recordFor(recordsIn(files, config.fileRecords), src));
|
|
21
|
+
const [value, setValue] = useState<string[]>(stored.current?.record.categories ?? []);
|
|
22
|
+
const running = useRef(false);
|
|
23
|
+
const waiting = useRef<string[] | null>(null);
|
|
24
|
+
|
|
25
|
+
// One commit for the tags `next`; throws when it fails.
|
|
26
|
+
async function save(next: string[]) {
|
|
27
|
+
const record = setTags(stored.current?.record, src, next);
|
|
28
|
+
const [change] = writeRecords(stored.current ? [stored.current] : [], config.fileRecords, [{ src, next: record }]);
|
|
29
|
+
if (!change) return;
|
|
30
|
+
const word = t.media.kinds[kindOf(src)].word;
|
|
31
|
+
const text = t.app.changes.edited(kindAndName(word, fileName(src)));
|
|
32
|
+
const oids = await env.commit(siteT.media.commit.tags(siteT.media.kinds[kindOf(src)].word, fileName(src)), [change], text);
|
|
33
|
+
if (change.content === null) {
|
|
34
|
+
stored.current = undefined;
|
|
35
|
+
env.forget([change.path]);
|
|
36
|
+
} else {
|
|
37
|
+
stored.current = { path: change.path, oid: oids[change.path], record };
|
|
38
|
+
env.onSaved({ path: change.path, oid: oids[change.path], text: change.content });
|
|
39
|
+
}
|
|
40
|
+
env.notify({ text: savedText(text), kind: 'saved' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Saves the waiting value, then the next one that came meanwhile; on an error the chips go back to what is stored.
|
|
44
|
+
async function drain() {
|
|
45
|
+
running.current = true;
|
|
46
|
+
while (waiting.current) {
|
|
47
|
+
const next = waiting.current;
|
|
48
|
+
waiting.current = null;
|
|
49
|
+
try {
|
|
50
|
+
await save(next);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
waiting.current = null;
|
|
53
|
+
setValue(stored.current?.record.categories ?? []);
|
|
54
|
+
env.notify({ text: t.common.notSaved((e as Error).message), error: true, kind: 'failed' });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
running.current = false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function change(next: string[]) {
|
|
61
|
+
setValue(next);
|
|
62
|
+
waiting.current = next;
|
|
63
|
+
if (!running.current) void drain();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div>
|
|
68
|
+
<p className="mb-1 font-medium">{t.media.tags}</p>
|
|
69
|
+
<MultiChoice
|
|
70
|
+
label={t.media.tags}
|
|
71
|
+
options={env.relations.categories}
|
|
72
|
+
value={value}
|
|
73
|
+
add={t.media.addTag}
|
|
74
|
+
keep="categories"
|
|
75
|
+
footer={(close) => (
|
|
76
|
+
<SheetLink
|
|
77
|
+
href="#ny/kategorier"
|
|
78
|
+
onClick={(e) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
close();
|
|
81
|
+
leaveTo('#ny/kategorier');
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{t.media.createTag}
|
|
85
|
+
</SheetLink>
|
|
86
|
+
)}
|
|
87
|
+
onChange={change}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// "Dele" › "UI-komponenter": the modal bottom sheet example (ContainmentParts.tsx). A button opens the theme's modal
|
|
2
|
+
// sheet in a real <dialog>, as the site does (bottomSheet() in bottomSheetControl.ts), so on a phone it opens at half
|
|
3
|
+
// height (startAt), is pulled up to full height and pulled down to close. `wide` is what it becomes from the medium
|
|
4
|
+
// breakpoint (BottomSheet's `wide`: a dialog, a side sheet or a sheet), `label` what the button says.
|
|
5
|
+
// Exposes ModalSheetSample; uses SiteBottomSheet.
|
|
6
|
+
import { useEffect, useRef } from 'react';
|
|
7
|
+
import { SiteBottomSheet, SiteButton, siteBottomSheet, siteBottomSheetClass } from './themeUi';
|
|
8
|
+
import { t } from './i18n';
|
|
9
|
+
|
|
10
|
+
const say = t.parts.screens.containment;
|
|
11
|
+
|
|
12
|
+
export default function ModalSheetSample({
|
|
13
|
+
startAt,
|
|
14
|
+
wide,
|
|
15
|
+
label,
|
|
16
|
+
}: {
|
|
17
|
+
startAt?: 'half' | 'full';
|
|
18
|
+
wide?: 'dialog' | 'side-sheet' | 'sheet';
|
|
19
|
+
label?: string;
|
|
20
|
+
}) {
|
|
21
|
+
const dialog = useRef<HTMLDialogElement>(null);
|
|
22
|
+
const controls = useRef<ReturnType<typeof siteBottomSheet>>(undefined);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (dialog.current) controls.current = siteBottomSheet(dialog.current);
|
|
25
|
+
}, []);
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<SiteButton onClick={() => controls.current?.open()}>{label ?? say.openSheet}</SiteButton>
|
|
29
|
+
<dialog ref={dialog} className={siteBottomSheetClass} aria-label={say.heading}>
|
|
30
|
+
<SiteBottomSheet
|
|
31
|
+
startAt={startAt}
|
|
32
|
+
wide={wide}
|
|
33
|
+
headline={say.heading}
|
|
34
|
+
supportingText={say.sheetText}
|
|
35
|
+
closeLabel={say.close}
|
|
36
|
+
footer={say.footer}
|
|
37
|
+
>
|
|
38
|
+
{say.paragraphs.map((text) => (
|
|
39
|
+
<p key={text} className="mb-3 px-5 pt-3 text-muted">
|
|
40
|
+
{text}
|
|
41
|
+
</p>
|
|
42
|
+
))}
|
|
43
|
+
</SiteBottomSheet>
|
|
44
|
+
</dialog>
|
|
45
|
+
</>
|
|
46
|
+
);
|
|
47
|
+
}
|
package/src/NavigationParts.tsx
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// in the middle, with a subtitle, a long headline and collapsed), a menu (the header's pages, and actions in both
|
|
5
5
|
// colours with their icons, shortcuts, texts, ticks, a choice, a disabled one, dividers, groups with a gap and with a
|
|
6
6
|
// line, and a submenu drawn open), a dropdown menu closed and drawn open, the exposed dropdown menu in both kinds in
|
|
7
|
-
// every state, the navigation drawer with an item's sub-pages unfolded, the
|
|
7
|
+
// every state, the navigation drawer with an item's sub-pages unfolded, and the standard one with sections, icons and counts,
|
|
8
|
+
// in two levels (items with a trailing arrow, and the back row over a list), the tabs (only the text, as the bar of
|
|
8
9
|
// "Mobilmenu", and primary and secondary, fixed and scrollable, with icons over and beside their labels, badges, and a
|
|
9
10
|
// tab list with a tab that cannot be chosen), and the FABs and extended FABs in their colours and sizes, an extended
|
|
10
11
|
// FAB also collapsed. Only drawn: the links lead nowhere, and nothing opens.
|
|
@@ -21,8 +22,10 @@ import {
|
|
|
21
22
|
SiteMenuGroup,
|
|
22
23
|
SiteMenuItem,
|
|
23
24
|
SiteNavigationDrawer,
|
|
25
|
+
SiteNavigationDrawerBack,
|
|
24
26
|
SiteNavigationDrawerItem,
|
|
25
27
|
SiteNavigationDrawerRow,
|
|
28
|
+
SiteNavigationDrawerSection,
|
|
26
29
|
SiteNavigationDrawerSubitem,
|
|
27
30
|
SiteNavigationDrawerToggle,
|
|
28
31
|
SiteSearch,
|
|
@@ -40,6 +43,9 @@ import { t } from './i18n';
|
|
|
40
43
|
const say = t.parts.screens.navigation;
|
|
41
44
|
const [home, open, parent, contact] = say.pages;
|
|
42
45
|
const [sub, openSub] = say.subpages;
|
|
46
|
+
const [mail, account] = say.drawerSections;
|
|
47
|
+
const [backLabel] = say.drawerBack;
|
|
48
|
+
const [inbox, messages, favourites, trash, profile] = say.drawerItems;
|
|
43
49
|
const row = 'flex flex-wrap items-center gap-4';
|
|
44
50
|
const kinds = ['primary', 'secondary'] as const;
|
|
45
51
|
const bar = say.appBar;
|
|
@@ -204,7 +210,7 @@ export default function NavigationParts() {
|
|
|
204
210
|
<SiteMenu groups="divider" aria-label={menus.divider}>
|
|
205
211
|
{groups()}
|
|
206
212
|
</SiteMenu>
|
|
207
|
-
<div className="pb-24
|
|
213
|
+
<div className="pb-24 medium:pr-72">
|
|
208
214
|
<SiteMenu aria-label={menus.share}>{groups(true)}</SiteMenu>
|
|
209
215
|
</div>
|
|
210
216
|
</div>
|
|
@@ -231,7 +237,7 @@ export default function NavigationParts() {
|
|
|
231
237
|
{(['outlined', 'filled'] as const).map((kind) => {
|
|
232
238
|
const options = menus.options.map((label, i) => ({ value: String(i), label, disabled: i === 3 }));
|
|
233
239
|
return (
|
|
234
|
-
<div key={kind} className="grid gap-6
|
|
240
|
+
<div key={kind} className="grid gap-6 medium:grid-cols-2" aria-label={menus.kinds[kind]} role="group">
|
|
235
241
|
<SiteExposedDropdownMenu kind={kind} label={menus.field} options={options} />
|
|
236
242
|
<SiteExposedDropdownMenu kind={kind} label={menus.field} options={options} value="1" />
|
|
237
243
|
<SiteExposedDropdownMenu kind={kind} label={menus.field} options={options} value="0" supporting={menus.supporting} />
|
|
@@ -245,25 +251,65 @@ export default function NavigationParts() {
|
|
|
245
251
|
</Part>
|
|
246
252
|
<Part name="NavigationDrawer" use={say.navigationDrawer}>
|
|
247
253
|
<Preview>
|
|
248
|
-
<
|
|
249
|
-
<
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
<
|
|
254
|
-
|
|
254
|
+
<div className="flex flex-wrap items-start gap-6">
|
|
255
|
+
<SiteNavigationDrawer>
|
|
256
|
+
<SiteNavigationDrawerRow>
|
|
257
|
+
<SiteNavigationDrawerItem href="">{home}</SiteNavigationDrawerItem>
|
|
258
|
+
</SiteNavigationDrawerRow>
|
|
259
|
+
<SiteNavigationDrawerRow data-active="">
|
|
260
|
+
<SiteNavigationDrawerItem href="" active>
|
|
261
|
+
{parent}
|
|
262
|
+
</SiteNavigationDrawerItem>
|
|
263
|
+
<SiteNavigationDrawerToggle label={parent} expanded />
|
|
264
|
+
</SiteNavigationDrawerRow>
|
|
265
|
+
<SiteNavigationDrawerSubitem href="">{sub}</SiteNavigationDrawerSubitem>
|
|
266
|
+
<SiteNavigationDrawerSubitem href="" active>
|
|
267
|
+
{openSub}
|
|
268
|
+
</SiteNavigationDrawerSubitem>
|
|
269
|
+
<SiteNavigationDrawerRow>
|
|
270
|
+
<SiteNavigationDrawerItem href="">{contact}</SiteNavigationDrawerItem>
|
|
271
|
+
<SiteNavigationDrawerToggle label={contact} expanded={false} />
|
|
272
|
+
</SiteNavigationDrawerRow>
|
|
273
|
+
</SiteNavigationDrawer>
|
|
274
|
+
<SiteNavigationDrawer variant="standard">
|
|
275
|
+
<SiteNavigationDrawerSection label={mail}>
|
|
276
|
+
<SiteNavigationDrawerItem href="" icon="envelope-simple" badge={24} active>
|
|
277
|
+
{inbox}
|
|
278
|
+
</SiteNavigationDrawerItem>
|
|
279
|
+
<SiteNavigationDrawerItem href="" icon="chats" badge={3}>
|
|
280
|
+
{messages}
|
|
281
|
+
</SiteNavigationDrawerItem>
|
|
282
|
+
<SiteNavigationDrawerItem href="" icon="heart">
|
|
283
|
+
{favourites}
|
|
284
|
+
</SiteNavigationDrawerItem>
|
|
285
|
+
<SiteNavigationDrawerItem href="" icon="trash">
|
|
286
|
+
{trash}
|
|
287
|
+
</SiteNavigationDrawerItem>
|
|
288
|
+
</SiteNavigationDrawerSection>
|
|
289
|
+
<SiteNavigationDrawerSection label={account}>
|
|
290
|
+
<SiteNavigationDrawerItem href="" icon="user">
|
|
291
|
+
{profile}
|
|
292
|
+
</SiteNavigationDrawerItem>
|
|
293
|
+
</SiteNavigationDrawerSection>
|
|
294
|
+
</SiteNavigationDrawer>
|
|
295
|
+
<SiteNavigationDrawer variant="standard">
|
|
296
|
+
<SiteNavigationDrawerItem icon="envelope-simple" trailing="arrow-right" active>
|
|
297
|
+
{mail}
|
|
255
298
|
</SiteNavigationDrawerItem>
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
299
|
+
<SiteNavigationDrawerItem icon="user" trailing="arrow-right">
|
|
300
|
+
{account}
|
|
301
|
+
</SiteNavigationDrawerItem>
|
|
302
|
+
</SiteNavigationDrawer>
|
|
303
|
+
<SiteNavigationDrawer variant="standard">
|
|
304
|
+
<SiteNavigationDrawerBack label={backLabel} />
|
|
305
|
+
<SiteNavigationDrawerItem href="" icon="envelope-simple" badge={24} active>
|
|
306
|
+
{inbox}
|
|
307
|
+
</SiteNavigationDrawerItem>
|
|
308
|
+
<SiteNavigationDrawerItem href="" icon="chats">
|
|
309
|
+
{messages}
|
|
310
|
+
</SiteNavigationDrawerItem>
|
|
311
|
+
</SiteNavigationDrawer>
|
|
312
|
+
</div>
|
|
267
313
|
</Preview>
|
|
268
314
|
</Part>
|
|
269
315
|
<Part name="Tabs" use={say.tabs}>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// (packages/editor/theme/ui/, through @site/editor) as the site draws them, with made-up destinations: the navigation bar
|
|
3
3
|
// with the open one and its badges, with vertical items, horizontal ones and both by its width, the navigation rail
|
|
4
4
|
// collapsed and expanded with its menu button and FAB, in sections with a count at each destination's end (the
|
|
5
|
-
//
|
|
5
|
+
// theme's rail, which sites use), expanded over the content (modal, over its scrim), and one that
|
|
6
6
|
// hides while it is collapsed, collapsed (opened by a button beside it) and expanded, the search bar and the view it
|
|
7
7
|
// opens, and the toolbars, docked and floating in both colours, across and vertical (the docked one with buttons as the
|
|
8
8
|
// site's bar at the bottom of a phone; floating ones also with a FAB, and folded into it). Only drawn: the links lead
|