@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Tests for src/tagUses.mjs. Run: node --test packages/editor/src/*.test.mjs
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
import { blockHtml } from '../lib/blocks.mjs';
|
|
5
|
+
import { promotionTags, recordUse, tagUses, taggedWith } from './tagUses.mjs';
|
|
6
|
+
|
|
7
|
+
const folders = {
|
|
8
|
+
blog: 'src/content/blog',
|
|
9
|
+
pages: 'src/content/pages',
|
|
10
|
+
testimonials: 'src/data/testimonials',
|
|
11
|
+
popups: 'src/data/popups',
|
|
12
|
+
fileRecords: 'src/data/files',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const andromeda = {
|
|
16
|
+
path: 'src/data/files/k3j9x2ab.json',
|
|
17
|
+
text: '{"src":"/images/uploads/2026/10/andromeda.webp","categories":["design"],"removed":["seo"]}',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** A Markdown file with front matter categories. @param {string[]} categories */
|
|
21
|
+
const md = (categories) => `---\ntitle: "T"\ncategories: ${JSON.stringify(categories)}\n---\nBody\n`;
|
|
22
|
+
|
|
23
|
+
/** The paths of the files using a tag. @param {{ path: string; text: string }[]} files @param {string} slug */
|
|
24
|
+
const pathsOf = (files, slug) => [...(tagUses(files, folders).get(slug)?.keys() ?? [])];
|
|
25
|
+
|
|
26
|
+
test('a post counts for the tags in its front matter', () => {
|
|
27
|
+
const files = [{ path: 'src/content/blog/a.md', text: md(['seo']) }];
|
|
28
|
+
assert.deepEqual(pathsOf(files, 'seo'), ['src/content/blog/a.md']);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('a page counts for the tags in its front matter', () => {
|
|
32
|
+
const files = [{ path: 'src/content/pages/about.md', text: md(['design']) }];
|
|
33
|
+
assert.deepEqual(pathsOf(files, 'design'), ['src/content/pages/about.md']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('a page with a posts block counts for the tags the block picks', () => {
|
|
37
|
+
const page = { path: 'src/content/pages/home.md', text: `Intro\n${blockHtml('posts', { categories: ['apps'] })}\n` };
|
|
38
|
+
assert.deepEqual(pathsOf([page], 'apps'), ['src/content/pages/home.md']);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('a page with a testimonials block counts for the tag in its link', () => {
|
|
42
|
+
const page = { path: 'src/content/pages/home.md', text: `${blockHtml('testimonials', { link: 'seo' })}\n` };
|
|
43
|
+
assert.deepEqual(pathsOf([page], 'seo'), ['src/content/pages/home.md']);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('a page that has a tag in its front matter and in a block is counted once', () => {
|
|
47
|
+
const page = {
|
|
48
|
+
path: 'src/content/pages/home.md',
|
|
49
|
+
text: `---\ncategories: ["seo"]\n---\n${blockHtml('posts', { categories: ['seo'] })}\n`,
|
|
50
|
+
};
|
|
51
|
+
assert.equal(tagUses([page], folders).get('seo')?.size, 1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('a testimonial counts for the tags in its categories', () => {
|
|
55
|
+
const file = { path: 'src/data/testimonials/x.json', text: '{"categories":["editing"]}' };
|
|
56
|
+
assert.deepEqual(pathsOf([file], 'editing'), ['src/data/testimonials/x.json']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('a promotion counts for the tags when it is shown on tagged posts', () => {
|
|
60
|
+
const tagged = { path: 'src/data/popups/p.json', text: '{"where":"tags","tags":["seo"]}' };
|
|
61
|
+
assert.deepEqual(pathsOf([tagged], 'seo'), ['src/data/popups/p.json']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('a promotion shown on all posts does not count for a tag', () => {
|
|
65
|
+
const all = { path: 'src/data/popups/p.json', text: '{"where":"all","tags":["seo"]}' };
|
|
66
|
+
assert.deepEqual(pathsOf([all], 'seo'), []);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('a tag file is not a use', () => {
|
|
70
|
+
const file = { path: 'src/data/categories/seo.json', text: '{"categories":["seo"]}' };
|
|
71
|
+
assert.equal(tagUses([file], folders).size, 0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('a JSON file in the blog folder is ignored', () => {
|
|
75
|
+
const file = { path: 'src/content/blog/notes.json', text: '{"categories":["seo"]}' };
|
|
76
|
+
assert.equal(tagUses([file], folders).size, 0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('a testimonial with broken JSON is skipped without an error', () => {
|
|
80
|
+
const file = { path: 'src/data/testimonials/bad.json', text: '{' };
|
|
81
|
+
assert.equal(tagUses([file], folders).size, 0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('promotionTags gives the tags of a promotion shown on tagged posts', () => {
|
|
85
|
+
assert.deepEqual(promotionTags({ where: 'tags', tags: ['a', 2] }), ['a', '2']);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('promotionTags gives no tags for a promotion shown elsewhere', () => {
|
|
89
|
+
assert.deepEqual(promotionTags({ where: 'pages', tags: ['a'] }), []);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('promotionTags gives no tags without a tag list', () => {
|
|
93
|
+
assert.deepEqual(promotionTags({ where: 'tags' }), []);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('taggedWith lists the pages and testimonials with a tag, not blocks', () => {
|
|
97
|
+
const about = { path: 'src/content/pages/about.md', text: md(['design']) };
|
|
98
|
+
const post = { path: 'src/content/blog/a.md', text: blockHtml('posts', { categories: ['design'] }) };
|
|
99
|
+
const testimonial = { path: 'src/data/testimonials/x.json', text: '{"categories":["design"]}' };
|
|
100
|
+
assert.deepEqual(taggedWith([about, post, testimonial], 'design', folders), {
|
|
101
|
+
posts: [],
|
|
102
|
+
pages: [about],
|
|
103
|
+
testimonials: [testimonial],
|
|
104
|
+
records: [],
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('a file record counts for the tags in its categories, not its removed tags', () => {
|
|
109
|
+
assert.deepEqual(pathsOf([andromeda], 'design'), ['src/data/files/k3j9x2ab.json']);
|
|
110
|
+
assert.deepEqual(pathsOf([andromeda], 'seo'), []);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('a file record is not counted when the folders have no file records folder', () => {
|
|
114
|
+
const { fileRecords: _omitted, ...withoutRecords } = folders;
|
|
115
|
+
assert.equal(tagUses([andromeda], withoutRecords).size, 0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('a file record in a subfolder of the records folder is not counted', () => {
|
|
119
|
+
const nested = { path: 'src/data/files/sub/x.json', text: andromeda.text };
|
|
120
|
+
assert.equal(tagUses([nested], folders).size, 0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('taggedWith lists the file records with a tag in their categories', () => {
|
|
124
|
+
assert.deepEqual(taggedWith([andromeda], 'design', folders).records, [andromeda]);
|
|
125
|
+
assert.deepEqual(taggedWith([andromeda], 'seo', folders).records, []);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('recordUse gives the Filer path and the file name of a file record', () => {
|
|
129
|
+
assert.deepEqual(recordUse(andromeda, 'src/data/files'), {
|
|
130
|
+
path: 'filer/images/uploads/2026/10/andromeda.webp',
|
|
131
|
+
name: 'andromeda.webp',
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('recordUse gives null for a file that is not a file record', () => {
|
|
136
|
+
assert.equal(recordUse({ path: 'src/content/pages/about.md', text: '' }, 'src/data/files'), null);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('recordUse gives null for a file record with broken JSON', () => {
|
|
140
|
+
assert.equal(recordUse({ path: 'src/data/files/x.json', text: '{' }, 'src/data/files'), null);
|
|
141
|
+
});
|
package/src/themeUi.ts
CHANGED
|
@@ -14,11 +14,13 @@ export {
|
|
|
14
14
|
export { default as SiteCheckbox } from '@theme/ui/Checkbox';
|
|
15
15
|
export { default as SiteChip, chipKinds } from '@theme/ui/Chip';
|
|
16
16
|
export { default as SiteChipSet, chipSetLayouts } from '@theme/ui/ChipSet';
|
|
17
|
+
export { bottomSheet as siteBottomSheet } from '@theme/ui/bottomSheetControl';
|
|
17
18
|
export { default as SiteDialog, dialogModalClass as siteDialogModalClass } from '@theme/ui/Dialog';
|
|
18
19
|
export {
|
|
19
20
|
default as SiteBottomSheet,
|
|
20
21
|
BottomSheetHeader as SiteBottomSheetHeader,
|
|
21
22
|
bottomSheetCoverClass,
|
|
23
|
+
bottomSheetClass as siteBottomSheetClass,
|
|
22
24
|
bottomSheetContainerClass as siteBottomSheetContainerClass,
|
|
23
25
|
bottomSheetFitClass as siteBottomSheetFitClass,
|
|
24
26
|
bottomSheetFullClass as siteBottomSheetFullClass,
|
|
@@ -47,9 +49,13 @@ export { placeMenu as sitePlaceMenu } from '@theme/ui/dropdownMenu.script';
|
|
|
47
49
|
export { default as SiteExposedDropdownMenu } from '@theme/ui/ExposedDropdownMenu';
|
|
48
50
|
export {
|
|
49
51
|
NavigationDrawer as SiteNavigationDrawer,
|
|
52
|
+
NavigationDrawerBack as SiteNavigationDrawerBack,
|
|
53
|
+
focusNavigationDrawerItem as siteFocusNavigationDrawerItem,
|
|
50
54
|
navigationDrawerDialogClass as siteNavigationDrawerDialogClass,
|
|
55
|
+
navigationDrawerKeys as siteNavigationDrawerKeys,
|
|
51
56
|
NavigationDrawerItem as SiteNavigationDrawerItem,
|
|
52
57
|
NavigationDrawerRow as SiteNavigationDrawerRow,
|
|
58
|
+
NavigationDrawerSection as SiteNavigationDrawerSection,
|
|
53
59
|
NavigationDrawerSubitem as SiteNavigationDrawerSubitem,
|
|
54
60
|
NavigationDrawerToggle as SiteNavigationDrawerToggle,
|
|
55
61
|
} from '@theme/ui/NavigationDrawer';
|
|
@@ -61,12 +67,16 @@ export { default as SiteButtonGroup, buttonGroupKinds } from '@theme/ui/ButtonGr
|
|
|
61
67
|
export { default as SiteSegmentedButton } from '@theme/ui/SegmentedButton';
|
|
62
68
|
export { default as SiteSplitButton, splitButtonKinds } from '@theme/ui/SplitButton';
|
|
63
69
|
export { default as SiteFabMenu, fabMenuKinds } from '@theme/ui/FabMenu';
|
|
70
|
+
export { setUpFabMenu } from '@theme/ui/fabMenu.script';
|
|
64
71
|
/** The theme's selection parts, dividers and side sheets, for "Dele" › "UI-komponenter" (SelectionParts.tsx). */
|
|
65
72
|
export { default as SiteSwitch } from '@theme/ui/Switch';
|
|
73
|
+
export { default as SiteSettingRow } from '@theme/ui/SettingRow';
|
|
66
74
|
export { default as SiteRadioButton, RadioGroup as SiteRadioGroup } from '@theme/ui/RadioButton';
|
|
67
75
|
export { default as SiteSlider } from '@theme/ui/Slider';
|
|
68
76
|
export { default as SiteDivider } from '@theme/ui/Divider';
|
|
69
77
|
export { default as SiteSideSheet } from '@theme/ui/SideSheet';
|
|
78
|
+
export { sideSheetClass as siteSideSheetDialogClass } from '@theme/ui/SideSheet';
|
|
79
|
+
export { sideSheet as siteSideSheet } from '@theme/ui/sideSheetControl';
|
|
70
80
|
/** The theme's communication parts, for "Dele" › "UI-komponenter" (CommunicationParts.tsx). */
|
|
71
81
|
export { default as SiteBadge } from '@theme/ui/Badge';
|
|
72
82
|
export { default as SiteLoadingIndicator } from '@theme/ui/LoadingIndicator';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Where the editor's top bar stands: one sticky place at the top of the content column, beside the full-height navigation
|
|
2
|
+
// rail or drawer (`aside`; App.tsx wraps the layout in TopBarLayout), which Screen.tsx draws its bar into. Exposes
|
|
3
|
+
// TopBarLayout and useTopBarSlot. It keeps `--top-bar-height` (the bar's height) on the layout, for the sections pane.
|
|
4
|
+
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
const Slot = createContext<HTMLElement | null>(null);
|
|
7
|
+
|
|
8
|
+
/** The element a Screen draws its top bar into, or null outside TopBarLayout (the bar is then drawn in the screen). */
|
|
9
|
+
export const useTopBarSlot = () => useContext(Slot);
|
|
10
|
+
|
|
11
|
+
/** The layout round the editor: `above` (what scrolls away over everything), then a row of `aside` (the rail or the drawer,
|
|
12
|
+
* full height) and the column of the bar's place and `children`, which are not drawn before the place is there, so a
|
|
13
|
+
* screen's bar does not first stand in the screen and then move. */
|
|
14
|
+
export function TopBarLayout({ above, aside, children }: { above?: ReactNode; aside?: ReactNode; children: ReactNode }) {
|
|
15
|
+
const [slot, setSlot] = useState<HTMLDivElement | null>(null);
|
|
16
|
+
const [layout, setLayout] = useState<HTMLDivElement | null>(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!slot || !layout) return;
|
|
19
|
+
const measure = () => layout.style.setProperty('--top-bar-height', `${slot.offsetHeight}px`);
|
|
20
|
+
measure();
|
|
21
|
+
const watch = new ResizeObserver(measure);
|
|
22
|
+
watch.observe(slot);
|
|
23
|
+
return () => watch.disconnect();
|
|
24
|
+
}, [slot, layout]);
|
|
25
|
+
return (
|
|
26
|
+
<div ref={setLayout} className="[--top-bar-height:4rem]">
|
|
27
|
+
{above}
|
|
28
|
+
<div className="flex">
|
|
29
|
+
{aside}
|
|
30
|
+
<div className="min-w-0 flex-1">
|
|
31
|
+
<div ref={setSlot} className="sticky top-0 z-20" />
|
|
32
|
+
<Slot.Provider value={slot}>{slot && children}</Slot.Provider>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|