@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/MarkdownEditor.tsx
CHANGED
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
// on the site, which opens a menu (Ret, hide on the site, move, Fjern), with a free line kept between
|
|
5
5
|
// two cards to write in. A fixed page's sections are such a text too (SectionsField.tsx), so both look
|
|
6
6
|
// and work the same.
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
forwardRef,
|
|
9
|
+
Fragment as ReactFragment,
|
|
10
|
+
type MouseEvent as ReactMouseEvent,
|
|
11
|
+
useEffect,
|
|
12
|
+
useImperativeHandle,
|
|
13
|
+
useRef,
|
|
14
|
+
useState,
|
|
15
|
+
} from 'react';
|
|
8
16
|
import { editorViewCtx, type Editor } from '@milkdown/kit/core';
|
|
9
17
|
import type { CmdKey } from '@milkdown/kit/core';
|
|
10
18
|
import type { Ctx } from '@milkdown/kit/ctx';
|
|
@@ -35,6 +43,7 @@ import {
|
|
|
35
43
|
LinkSimple,
|
|
36
44
|
ListBullets,
|
|
37
45
|
ListNumbers,
|
|
46
|
+
Palette,
|
|
38
47
|
Plus,
|
|
39
48
|
Quotes,
|
|
40
49
|
TextAa,
|
|
@@ -46,7 +55,12 @@ import {
|
|
|
46
55
|
TextT,
|
|
47
56
|
} from '@phosphor-icons/react';
|
|
48
57
|
import { HTML, makeEditor, markdownOf, partsOf, siteBlockSchema, spacing } from './markdown.mjs';
|
|
58
|
+
import { firstHeading, topHeadings } from './firstHeading.mjs';
|
|
59
|
+
import { showsH1Choice } from '../lib/heading-levels.mjs';
|
|
49
60
|
import { TEXT_COLORS, colorClass } from '../lib/text-colors.mjs';
|
|
61
|
+
import { SECTION_COLORS } from '../lib/section-marker.mjs';
|
|
62
|
+
import { sectionHeading, setSectionColor } from './sectionState.mjs';
|
|
63
|
+
import { sectionFrame } from './sectionFrame';
|
|
50
64
|
import { IMAGE, type BlockAsk } from './BlockDialog';
|
|
51
65
|
import { askLink } from './LinkSheet';
|
|
52
66
|
import { Menu, MenuItem, Toolbar, ToolbarButton } from './ui';
|
|
@@ -59,6 +73,9 @@ import { createRoot } from 'react-dom/client';
|
|
|
59
73
|
import { flushSync } from 'react-dom';
|
|
60
74
|
import { t } from './i18n';
|
|
61
75
|
import { motionTiming } from '../lib/motion';
|
|
76
|
+
import { stillTimer } from '../lib/still-timer.mjs';
|
|
77
|
+
import { STILL_MS, wordRange } from './stillCursor.mjs';
|
|
78
|
+
import { hasLabel, labelledHeading, toggleLabel } from './eyebrowState.mjs';
|
|
62
79
|
// The icons of the card's menu (Phosphor, as everywhere in the editor), as SVG: the card is not React.
|
|
63
80
|
import pencilIcon from '@phosphor-icons/core/assets/regular/pencil-simple.svg?raw';
|
|
64
81
|
import eyeIcon from '@phosphor-icons/core/assets/regular/eye.svg?raw';
|
|
@@ -73,6 +90,7 @@ import h1Icon from '@phosphor-icons/core/assets/regular/text-h-one.svg?raw';
|
|
|
73
90
|
import h2Icon from '@phosphor-icons/core/assets/regular/text-h-two.svg?raw';
|
|
74
91
|
import h3Icon from '@phosphor-icons/core/assets/regular/text-h-three.svg?raw';
|
|
75
92
|
import paletteIcon from '@phosphor-icons/core/assets/regular/palette.svg?raw';
|
|
93
|
+
import tagIcon from '@phosphor-icons/core/assets/regular/tag.svg?raw';
|
|
76
94
|
// ProseMirror's own styles: keep its invisible helpers (around pictures, at the end of lines) out of the layout.
|
|
77
95
|
import '@milkdown/kit/prose/view/style/prosemirror.css';
|
|
78
96
|
|
|
@@ -82,8 +100,8 @@ export type MarkdownHandle = { markdown: () => string | null; parts: () => Retur
|
|
|
82
100
|
// addImage: asks for a picture (the gallery, Media.tsx); its address on the site. Its description is
|
|
83
101
|
// asked in the sheet. Without it there is no picture button. blocks: asks which block to put in and its fields (BlockDialog);
|
|
84
102
|
// without it there is no block button; with it a free line is kept between two blocks, and before
|
|
85
|
-
// and after them (spacing in markdown.mjs). short: a smaller field, for a short text. h1:
|
|
86
|
-
//
|
|
103
|
+
// and after them (spacing in markdown.mjs). short: a smaller field, for a short text. h1: a page's text, where the
|
|
104
|
+
// first heading is the page's H1 by itself (firstHeading.mjs); a blog post's H1 is its title.
|
|
87
105
|
type Props = {
|
|
88
106
|
value: string;
|
|
89
107
|
onChange: () => void;
|
|
@@ -98,6 +116,9 @@ type Props = {
|
|
|
98
116
|
/** The headings (H2) are the names of sections (a fixed page's, the front page's: sections.mjs), which the site
|
|
99
117
|
* draws as text: no colour for them. */
|
|
100
118
|
sections?: boolean;
|
|
119
|
+
/** Whether the text has the section frame and the colour button (pages and posts). Without it: on unless `sections`.
|
|
120
|
+
* The front page sets `sections` (its H2s are names drawn by the theme, no word colours) and `frame` (its sections take their colour). */
|
|
121
|
+
frame?: boolean;
|
|
101
122
|
};
|
|
102
123
|
|
|
103
124
|
// A part of the text at the top level, for `looks`.
|
|
@@ -446,7 +467,7 @@ async function editLink(view: EditorView) {
|
|
|
446
467
|
/** What applies where the cursor is, or to all of the selection: 'strong', 'emphasis', 'link',
|
|
447
468
|
* 'h2', 'h3', 'text' (a paragraph, not in a list or quote), 'bullet', 'ordered' and 'quote'; with a colour
|
|
448
469
|
* (text-colors.mjs) 'textColor' and the colour's class ('color-accent'). For the toolbar and the selection's menu,
|
|
449
|
-
* which mark them. */
|
|
470
|
+
* 'section' and 'section-<colour>' in a top-level H2 (sectionState.mjs), which mark them. */
|
|
450
471
|
export function activeOf(state: EditorView['state']): Set<string> {
|
|
451
472
|
const { selection, storedMarks, schema } = state;
|
|
452
473
|
const { $from, from, to, empty } = selection;
|
|
@@ -463,6 +484,11 @@ export function activeOf(state: EditorView['state']): Set<string> {
|
|
|
463
484
|
if (ancestors.includes('ordered_list')) on.add('ordered');
|
|
464
485
|
if (ancestors.includes('blockquote')) on.add('quote');
|
|
465
486
|
if ($from.parent.type.name === 'paragraph' && !on.has('bullet') && !on.has('ordered') && !on.has('quote')) on.add('text');
|
|
487
|
+
const section = sectionHeading(state);
|
|
488
|
+
if (section) {
|
|
489
|
+
on.add('section');
|
|
490
|
+
if (section.color) on.add(`section-${section.color}`);
|
|
491
|
+
}
|
|
466
492
|
return on;
|
|
467
493
|
}
|
|
468
494
|
|
|
@@ -487,15 +513,20 @@ export function paint(view: EditorView, color: string | null) {
|
|
|
487
513
|
view.focus();
|
|
488
514
|
}
|
|
489
515
|
|
|
490
|
-
// off: not there for the selection (the button is hidden).
|
|
516
|
+
// off: not there for the selection (the button is hidden). word: at a still cursor it acts on the word the cursor is
|
|
517
|
+
// in, which is selected first.
|
|
491
518
|
type Look = {
|
|
492
519
|
icon: string;
|
|
493
520
|
label: string;
|
|
494
521
|
on: (state: EditorView['state']) => boolean;
|
|
495
522
|
action: (button: HTMLElement) => void;
|
|
496
523
|
off?: (state: EditorView['state']) => boolean;
|
|
524
|
+
word?: boolean;
|
|
497
525
|
};
|
|
498
526
|
|
|
527
|
+
// The levels of the top-level headings of a document, in order.
|
|
528
|
+
const levelsOf = (doc: EditorView['state']['doc']): number[] => topHeadings(doc).map(({ node }) => node.attrs.level);
|
|
529
|
+
|
|
499
530
|
// Whether a heading (H2) is selected, or a part of one.
|
|
500
531
|
function inH2(state: EditorView['state']) {
|
|
501
532
|
let found = false;
|
|
@@ -504,10 +535,15 @@ function inH2(state: EditorView['state']) {
|
|
|
504
535
|
return found;
|
|
505
536
|
}
|
|
506
537
|
|
|
507
|
-
//
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
//
|
|
538
|
+
// Whether the cursor is in a label above a heading (eyebrow.mjs), which has no bold, italic, link or colour.
|
|
539
|
+
const inLabel = (state: EditorView['state']) => state.selection.$from.parent.type.name === 'eyebrow';
|
|
540
|
+
|
|
541
|
+
// When a word or more is selected, or the cursor has stood still in a word (STILL_MS, stillCursor.mjs): a row of icons
|
|
542
|
+
// over it, to make it bold, italic, a link, one of the site's colours (a menu under the button, opened by `colors`), the
|
|
543
|
+
// page's H1 (`h1`, only while the text has none), a heading or a subheading, a label above the heading; the ones that apply are marked. At a still
|
|
544
|
+
// cursor, bold, italic, link and colour act on the whole word, the headings on the line; a move or a keypress hides the
|
|
545
|
+
// row. On a phone, where the phone's own menu is over the selection and its handles under it, the row is at the bottom
|
|
546
|
+
// of the screen, over the keyboard. sections: no colour in a heading (H2), the name of a section.
|
|
511
547
|
function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button: HTMLElement) => void) {
|
|
512
548
|
return new Plugin({
|
|
513
549
|
view(view) {
|
|
@@ -518,30 +554,77 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
|
|
|
518
554
|
menu.hidden = true;
|
|
519
555
|
const run = <T,>(command: { key: CmdKey<T> }, payload?: T) => void callCommand(command.key, payload)(ctx);
|
|
520
556
|
const marked = (state: EditorView['state'], name: string) => activeOf(state).has(name);
|
|
521
|
-
|
|
557
|
+
// On a page the Heading button also stands for the H1 (firstHeading.mjs).
|
|
558
|
+
const heading = (state: EditorView['state'], level: number) =>
|
|
559
|
+
activeOf(state).has(`h${level}`) || (h1 && level === 2 && activeOf(state).has('h1'));
|
|
522
560
|
const looks: Look[] = [
|
|
523
|
-
{
|
|
524
|
-
|
|
561
|
+
{
|
|
562
|
+
icon: boldIcon,
|
|
563
|
+
label: t.text.bold,
|
|
564
|
+
on: (s) => marked(s, 'strong'),
|
|
565
|
+
action: () => run(toggleStrongCommand),
|
|
566
|
+
off: inLabel,
|
|
567
|
+
word: true,
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
icon: italicIcon,
|
|
571
|
+
label: t.text.italic,
|
|
572
|
+
on: (s) => marked(s, 'emphasis'),
|
|
573
|
+
action: () => run(toggleEmphasisCommand),
|
|
574
|
+
off: inLabel,
|
|
575
|
+
word: true,
|
|
576
|
+
},
|
|
525
577
|
{
|
|
526
578
|
icon: linkIcon,
|
|
527
579
|
label: t.text.link,
|
|
528
580
|
on: (s) => marked(s, 'link'),
|
|
529
581
|
action: () => void editLink(view).then(() => update(view)),
|
|
582
|
+
off: inLabel,
|
|
583
|
+
word: true,
|
|
530
584
|
},
|
|
531
585
|
{
|
|
532
586
|
icon: paletteIcon,
|
|
533
587
|
label: t.text.color,
|
|
534
588
|
on: (s) => marked(s, 'textColor'),
|
|
535
589
|
action: colors,
|
|
536
|
-
off: sections
|
|
590
|
+
off: (s) => inLabel(s) || (sections && inH2(s)),
|
|
591
|
+
word: true,
|
|
537
592
|
},
|
|
538
593
|
...(h1 ? [1, 2, 3] : [2, 3]).map((level) => ({
|
|
539
594
|
icon: [h1Icon, h2Icon, h3Icon][level - 1],
|
|
540
595
|
label: [t.text.pageHeading, t.text.heading, t.text.subheading][level - 1],
|
|
541
596
|
on: (s: EditorView['state']) => heading(s, level),
|
|
542
597
|
action: () => (heading(view.state, level) ? run(turnIntoTextCommand) : run(wrapInHeadingCommand, level)),
|
|
598
|
+
// The H1 choice is gone as soon as the page's text has an H1.
|
|
599
|
+
off: level === 1 ? (s: EditorView['state']) => !showsH1Choice(levelsOf(s.doc)) : undefined,
|
|
543
600
|
})),
|
|
601
|
+
// A label above the heading. Not on a fixed page's H1 or H2, whose headings are its sections (sections.mjs).
|
|
602
|
+
{
|
|
603
|
+
icon: tagIcon,
|
|
604
|
+
label: t.blocks.eyebrow,
|
|
605
|
+
on: hasLabel,
|
|
606
|
+
action: () => toggleLabel(view),
|
|
607
|
+
off: (s) => {
|
|
608
|
+
const heading = labelledHeading(s);
|
|
609
|
+
return !heading || (sections && heading.level <= 2);
|
|
610
|
+
},
|
|
611
|
+
},
|
|
544
612
|
];
|
|
613
|
+
// Where the cursor stood still in a word, so the row is shown for that word; null after a move. A change made by
|
|
614
|
+
// a button (acting) is no move.
|
|
615
|
+
let stillAt: number | null = null;
|
|
616
|
+
let acting = false;
|
|
617
|
+
const still = stillTimer(STILL_MS, () => {
|
|
618
|
+
if (!view.hasFocus() || !wordRange(view.state)) return;
|
|
619
|
+
stillAt = view.state.selection.from;
|
|
620
|
+
update(view);
|
|
621
|
+
});
|
|
622
|
+
const stillWord = (state: EditorView['state']) => (stillAt === state.selection.from ? wordRange(state) : null);
|
|
623
|
+
// The state as if the word at a still cursor were selected, so the buttons are marked as for the word.
|
|
624
|
+
const asShown = (state: EditorView['state']) => {
|
|
625
|
+
const word = stillWord(state);
|
|
626
|
+
return word ? state.apply(state.tr.setSelection(TextSelection.create(state.doc, word.from, word.to))) : state;
|
|
627
|
+
};
|
|
545
628
|
const buttons = looks.map((look) => {
|
|
546
629
|
const b = menu.appendChild(document.createElement('button'));
|
|
547
630
|
b.type = 'button';
|
|
@@ -550,7 +633,14 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
|
|
|
550
633
|
b.innerHTML = look.icon.replace('<svg', '<svg width="20" height="20" fill="currentColor" aria-hidden="true"');
|
|
551
634
|
if (look.action === colors) b.setAttribute('aria-haspopup', 'menu');
|
|
552
635
|
const act = () => {
|
|
553
|
-
look.
|
|
636
|
+
const word = look.word ? stillWord(view.state) : null;
|
|
637
|
+
if (word) view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, word.from, word.to)));
|
|
638
|
+
acting = true;
|
|
639
|
+
try {
|
|
640
|
+
look.action(b);
|
|
641
|
+
} finally {
|
|
642
|
+
acting = false;
|
|
643
|
+
}
|
|
554
644
|
update(view);
|
|
555
645
|
// Again when "Gem ændringer" has come after the first change.
|
|
556
646
|
setTimeout(follow, 100);
|
|
@@ -568,16 +658,16 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
|
|
|
568
658
|
});
|
|
569
659
|
function update(view: EditorView) {
|
|
570
660
|
const { selection, doc } = view.state;
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
selection instanceof TextSelection &&
|
|
574
|
-
|
|
575
|
-
doc.textBetween(selection.from, selection.to).trim() !== '';
|
|
661
|
+
const word = stillWord(view.state);
|
|
662
|
+
const selected =
|
|
663
|
+
selection instanceof TextSelection && !selection.empty && doc.textBetween(selection.from, selection.to).trim() !== '';
|
|
664
|
+
const shown = view.hasFocus() && (selected || !!word);
|
|
576
665
|
menu.hidden = !shown;
|
|
577
666
|
if (!shown) return;
|
|
667
|
+
const state = asShown(view.state);
|
|
578
668
|
looks.forEach((look, i) => {
|
|
579
|
-
buttons[i].hidden = !!look.off?.(
|
|
580
|
-
buttons[i].setAttribute('aria-pressed', String(look.on(
|
|
669
|
+
buttons[i].hidden = !!look.off?.(state);
|
|
670
|
+
buttons[i].setAttribute('aria-pressed', String(look.on(state)));
|
|
581
671
|
});
|
|
582
672
|
const { offsetWidth: width, offsetHeight: height } = menu;
|
|
583
673
|
const screen = visualViewport ?? { offsetLeft: 0, offsetTop: 0, width: innerWidth, height: innerHeight };
|
|
@@ -589,8 +679,9 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
|
|
|
589
679
|
return;
|
|
590
680
|
}
|
|
591
681
|
const text = view.dom.getBoundingClientRect();
|
|
592
|
-
const
|
|
593
|
-
const
|
|
682
|
+
const { from, to } = word ?? selection;
|
|
683
|
+
const start = view.coordsAtPos(from);
|
|
684
|
+
const end = view.coordsAtPos(to);
|
|
594
685
|
const middle = start.top === end.top ? (start.left + end.right) / 2 : text.left + text.width / 2;
|
|
595
686
|
// Over the selection; under it when the toolbar is there.
|
|
596
687
|
const bar = document.querySelector('.editor-tools')?.getBoundingClientRect().bottom ?? 0;
|
|
@@ -608,8 +699,16 @@ function selectionMenu(ctx: Ctx, h1: boolean, sections: boolean, colors: (button
|
|
|
608
699
|
visualViewport?.addEventListener('resize', follow);
|
|
609
700
|
visualViewport?.addEventListener('scroll', follow);
|
|
610
701
|
return {
|
|
611
|
-
|
|
702
|
+
// A move or a keypress hides the row at a still cursor and waits again.
|
|
703
|
+
update: (view: EditorView, prev: EditorState) => {
|
|
704
|
+
if (!acting && (!prev.selection.eq(view.state.selection) || !prev.doc.eq(view.state.doc))) {
|
|
705
|
+
stillAt = null;
|
|
706
|
+
still.restart();
|
|
707
|
+
}
|
|
708
|
+
update(view);
|
|
709
|
+
},
|
|
612
710
|
destroy: () => {
|
|
711
|
+
still.stop();
|
|
613
712
|
view.dom.removeEventListener('blur', refresh);
|
|
614
713
|
view.dom.removeEventListener('focus', refresh);
|
|
615
714
|
removeEventListener('scroll', follow);
|
|
@@ -742,10 +841,11 @@ function blockCard(
|
|
|
742
841
|
}
|
|
743
842
|
|
|
744
843
|
export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
745
|
-
{ value, onChange, addImage, blocks, sheet: sheetProp, short, h1, looks, sections },
|
|
844
|
+
{ value, onChange, addImage, blocks, sheet: sheetProp, short, h1, looks, sections, frame },
|
|
746
845
|
ref,
|
|
747
846
|
) {
|
|
748
847
|
const spaced = !!blocks;
|
|
848
|
+
const framed = frame ?? !sections;
|
|
749
849
|
const root = useRef<HTMLDivElement>(null);
|
|
750
850
|
const toolbar = useRef<HTMLDivElement>(null);
|
|
751
851
|
const editor = useRef<Editor | null>(null);
|
|
@@ -757,12 +857,20 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
|
757
857
|
looked.current = looks;
|
|
758
858
|
// What applies where the cursor is (activeOf), marked in the toolbar.
|
|
759
859
|
const [active, setActive] = useState<string[]>([]);
|
|
860
|
+
// Whether the page's text has no H1, so the H1 choice is shown (showsH1Choice).
|
|
861
|
+
const [h1Choice, setH1Choice] = useState(true);
|
|
760
862
|
// The colour button over the selection, while its menu with the colours is open.
|
|
761
863
|
const [colorsAt, setColorsAt] = useState<HTMLElement | null>(null);
|
|
762
864
|
useEffect(() => {
|
|
763
865
|
colorsAt?.setAttribute('aria-expanded', 'true');
|
|
764
866
|
return () => colorsAt?.setAttribute('aria-expanded', 'false');
|
|
765
867
|
}, [colorsAt]);
|
|
868
|
+
// The button for the colour of a section, while its menu is open.
|
|
869
|
+
const [sectionAt, setSectionAt] = useState<HTMLElement | null>(null);
|
|
870
|
+
useEffect(() => {
|
|
871
|
+
sectionAt?.setAttribute('aria-expanded', 'true');
|
|
872
|
+
return () => sectionAt?.setAttribute('aria-expanded', 'false');
|
|
873
|
+
}, [sectionAt]);
|
|
766
874
|
// The previews of the blocks, drawn again when what they show from elsewhere changes: the lists,
|
|
767
875
|
// the testimonials, the rest of the site, the page's address or a fixed page's other fields.
|
|
768
876
|
const cards = useRef(new Set<() => void>());
|
|
@@ -782,7 +890,10 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
|
782
890
|
new Plugin({
|
|
783
891
|
view: () => ({
|
|
784
892
|
update: (view) => {
|
|
785
|
-
const
|
|
893
|
+
const choice = showsH1Choice(levelsOf(view.state.doc));
|
|
894
|
+
setH1Choice((was) => (was === choice ? was : choice));
|
|
895
|
+
// On a page the first heading is the H1 by itself (firstHeading.mjs); the Heading button stands for it.
|
|
896
|
+
const now = [...activeOf(view.state)].map((name) => (h1 && name === 'h1' ? 'h2' : name)).sort();
|
|
786
897
|
setActive((was) => (was.join() === now.join() ? was : now));
|
|
787
898
|
},
|
|
788
899
|
}),
|
|
@@ -818,7 +929,7 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
|
818
929
|
},
|
|
819
930
|
}),
|
|
820
931
|
);
|
|
821
|
-
|
|
932
|
+
let made = makeEditor(root.current, value)
|
|
822
933
|
.use(cardView)
|
|
823
934
|
.use(pictureView)
|
|
824
935
|
.use(selecting)
|
|
@@ -826,9 +937,12 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
|
826
937
|
.use(marking)
|
|
827
938
|
.use(onEdit)
|
|
828
939
|
.use(looking)
|
|
940
|
+
.use(sectionFrame(framed))
|
|
829
941
|
.use(gapHere)
|
|
830
942
|
.use(history)
|
|
831
943
|
.use(clipboard);
|
|
944
|
+
// A page (h1) keeps its first heading as its H1 and has no other H1 (firstHeading.mjs); a blog post's H1 is its title.
|
|
945
|
+
if (h1) made = made.use(firstHeading);
|
|
832
946
|
(spaced ? made.use(spacing) : made).create().then((created) => {
|
|
833
947
|
if (cancelled) return created.destroy();
|
|
834
948
|
editor.current = created;
|
|
@@ -911,6 +1025,14 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
|
911
1025
|
editor.current?.action((ctx) => paint(ctx.get(editorViewCtx), name));
|
|
912
1026
|
}
|
|
913
1027
|
|
|
1028
|
+
function sectionColor(name: string | null) {
|
|
1029
|
+
editor.current?.action((ctx) => {
|
|
1030
|
+
const view = ctx.get(editorViewCtx);
|
|
1031
|
+
setSectionColor(view, name);
|
|
1032
|
+
view.focus();
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
|
|
914
1036
|
// A new picture from the gallery, then the sheet with it and what it shows, where it can still be
|
|
915
1037
|
// changed or left out (the browser's own box for what it shows without a sheet).
|
|
916
1038
|
async function image() {
|
|
@@ -968,17 +1090,27 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
|
968
1090
|
});
|
|
969
1091
|
}
|
|
970
1092
|
|
|
971
|
-
const tools: { label: string; icon: typeof TextB; action: () => void; on?: string }[] = [
|
|
1093
|
+
const tools: { label: string; icon: typeof TextB; action: (e: ReactMouseEvent<HTMLButtonElement>) => void; on?: string }[] = [
|
|
972
1094
|
{ label: t.text.bold, icon: TextB, action: () => run(toggleStrongCommand), on: 'strong' },
|
|
973
1095
|
{ label: t.text.italic, icon: TextItalic, action: () => run(toggleEmphasisCommand), on: 'emphasis' },
|
|
974
1096
|
{ label: t.text.link, icon: LinkSimple, action: link, on: 'link' },
|
|
975
|
-
...(h1 ? [{ label: t.text.pageHeading, icon: TextHOne, action: () => setKind('h1'), on: 'h1' }] : []),
|
|
1097
|
+
...(h1 && h1Choice ? [{ label: t.text.pageHeading, icon: TextHOne, action: () => setKind('h1'), on: 'h1' }] : []),
|
|
976
1098
|
{ label: t.text.heading, icon: TextHTwo, action: () => setKind('h2'), on: 'h2' },
|
|
977
1099
|
{ label: t.text.subheading, icon: TextHThree, action: () => setKind('h3'), on: 'h3' },
|
|
978
1100
|
{ label: t.text.paragraph, icon: TextT, action: () => setKind('text'), on: 'text' },
|
|
979
1101
|
{ label: t.text.bulletList, icon: ListBullets, action: () => setKind('bullet'), on: 'bullet' },
|
|
980
1102
|
{ label: t.text.orderedList, icon: ListNumbers, action: () => setKind('ordered'), on: 'ordered' },
|
|
981
1103
|
{ label: t.text.quote, icon: Quotes, action: () => setKind('quote'), on: 'quote' },
|
|
1104
|
+
// The colour of the section the cursor's H2 starts (not a fixed page's text, whose H2s are its sections' names; the front page has it).
|
|
1105
|
+
...(framed && active.includes('section')
|
|
1106
|
+
? [
|
|
1107
|
+
{
|
|
1108
|
+
label: t.text.sectionColor,
|
|
1109
|
+
icon: Palette,
|
|
1110
|
+
action: (e: ReactMouseEvent<HTMLButtonElement>) => setSectionAt(sectionAt ? null : e.currentTarget),
|
|
1111
|
+
},
|
|
1112
|
+
]
|
|
1113
|
+
: []),
|
|
982
1114
|
// A picture and the blocks are put in with "+" (a sheet with them all); without blocks (a blog
|
|
983
1115
|
// post) a picture has its own button.
|
|
984
1116
|
...(blocks
|
|
@@ -1038,7 +1170,7 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
|
1038
1170
|
<Toolbar
|
|
1039
1171
|
ref={toolbar}
|
|
1040
1172
|
label={t.text.tools}
|
|
1041
|
-
className="editor-tools sticky top-[calc(4.5rem+1px+env(safe-area-inset-top))] z-10 justify-center-safe overflow-x-auto rounded-t-panel border-b border-line
|
|
1173
|
+
className="editor-tools sticky top-[calc(4.5rem+1px+env(safe-area-inset-top))] z-10 justify-center-safe overflow-x-auto rounded-t-panel border-b border-line medium:flex-wrap"
|
|
1042
1174
|
>
|
|
1043
1175
|
{tools.map((tool) => (
|
|
1044
1176
|
<ReactFragment key={tool.label}>
|
|
@@ -1059,16 +1191,27 @@ export default forwardRef<MarkdownHandle, Props>(function MarkdownEditor(
|
|
|
1059
1191
|
<span className="text-ink">{t.text.noColor}</span>
|
|
1060
1192
|
</MenuItem>
|
|
1061
1193
|
</Menu>
|
|
1194
|
+
{/* The theme's three colours for a section (section-marker.mjs), and "Ingen"; the one it has is marked. */}
|
|
1195
|
+
<Menu anchor={sectionAt} label={t.text.sectionColor} onClose={() => setSectionAt(null)}>
|
|
1196
|
+
<MenuItem icon={Palette} selected={!active.some((a) => a.startsWith('section-'))} onClick={() => sectionColor(null)}>
|
|
1197
|
+
{t.text.sectionColors.none}
|
|
1198
|
+
</MenuItem>
|
|
1199
|
+
{SECTION_COLORS.map((name) => (
|
|
1200
|
+
<MenuItem key={name} icon={Palette} selected={active.includes(`section-${name}`)} onClick={() => sectionColor(name)}>
|
|
1201
|
+
{t.text.sectionColors[name as keyof typeof t.text.sectionColors]}
|
|
1202
|
+
</MenuItem>
|
|
1203
|
+
))}
|
|
1204
|
+
</Menu>
|
|
1062
1205
|
<Menu anchor={kindsAt} label={t.text.kind} onClose={() => setKindsAt(null)}>
|
|
1063
1206
|
{kinds.map(({ label, icon, action, on }) => (
|
|
1064
|
-
<MenuItem key={label} icon={icon} selected={active.includes(on!)} onClick={action}>
|
|
1207
|
+
<MenuItem key={label} icon={icon} selected={active.includes(on!)} onClick={action as () => void}>
|
|
1065
1208
|
{label}
|
|
1066
1209
|
</MenuItem>
|
|
1067
1210
|
))}
|
|
1068
1211
|
</Menu>
|
|
1069
1212
|
<div
|
|
1070
1213
|
ref={root}
|
|
1071
|
-
className={`editor-text prose max-w-none px-5 py-4
|
|
1214
|
+
className={`editor-text prose max-w-none px-5 py-4 medium:px-8 ${short ? 'editor-text-short' : ''} ${spaced ? 'editor-spaced' : ''}`}
|
|
1072
1215
|
/>
|
|
1073
1216
|
</div>
|
|
1074
1217
|
);
|