@morphemecms/cms 0.9.0-next-20261105115842 → 0.9.1
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 +24 -1
- package/defaults/collections.ts +112 -58
- package/defaults/company.ts +1 -1
- package/defaults/editor.ts +2 -2
- package/defaults/texts.ts +14 -5
- package/demo/src/content/pages/app-store.md +0 -1
- package/demo/src/content/pages/blocks.md +0 -1
- package/icons/lucide/arrows-horizontal.svg +1 -0
- package/icons/lucide/columns-plus-left.svg +1 -0
- package/icons/lucide/columns-plus-right.svg +1 -0
- package/icons/lucide/file.svg +1 -0
- package/icons/material-symbols/arrows-horizontal.svg +1 -0
- package/icons/material-symbols/columns-plus-left.svg +1 -0
- package/icons/material-symbols/columns-plus-right.svg +1 -0
- package/icons/material-symbols/file.svg +1 -0
- package/icons/phosphor/arrows-horizontal.svg +1 -0
- package/icons/phosphor/columns-plus-left.svg +1 -0
- package/icons/phosphor/columns-plus-right.svg +1 -0
- package/icons/phosphor/file.svg +1 -0
- package/lib/fold-open.d.mts +2 -0
- package/lib/fold-open.mjs +22 -0
- package/lib/fold-open.test.mjs +24 -0
- package/lib/menu.test.mjs +16 -3
- package/lib/page-template.d.mts +54 -0
- package/lib/page-template.mjs +126 -0
- package/lib/page-template.test.mjs +129 -0
- package/lib/side-block.d.mts +2 -16
- package/lib/side-block.mjs +12 -30
- package/lib/side-block.test.mjs +13 -30
- package/package.json +1 -1
- package/scripts/check-side.mjs +49 -22
- package/scripts/editor-roundtrip.mjs +15 -6
- package/scripts/fixtures/side/side-full.md +8 -0
- package/scripts/fixtures/side/side-left.md +8 -0
- package/scripts/fixtures/side/side-none.md +7 -0
- package/scripts/fixtures/side/side-right.md +8 -0
- package/scripts/fixtures/side/side-single.md +8 -0
- package/scripts/side-width.mjs +29 -0
- package/scripts/side-width.test.mjs +30 -0
- package/site/content.config.ts +3 -4
- package/site/header.d.mts +21 -0
- package/site/header.mjs +35 -0
- package/site/header.test.mjs +59 -0
- package/site/icons.d.mts +16 -0
- package/site/icons.mjs +8 -0
- package/site/site.ts +9 -4
- package/site/templates.ts +7 -0
- package/site/theme.mjs +3 -0
- package/site/tracking.client.ts +4 -4
- package/src/App.tsx +12 -24
- package/src/AppList.tsx +36 -14
- package/src/AppSettingsTab.tsx +5 -3
- package/src/BlockDialog.tsx +50 -5
- package/src/DataFileFields.tsx +35 -0
- package/src/DataForm.tsx +16 -38
- package/src/Deleted.tsx +7 -3
- package/src/EntryForm.tsx +41 -88
- package/src/FooterSettings.tsx +76 -0
- package/src/FormFrame.tsx +2 -1
- package/src/HeaderSettings.tsx +100 -0
- package/src/Library.tsx +26 -14
- package/src/MarkdownEditor.tsx +18 -36
- package/src/Media.tsx +6 -3
- package/src/Redirects.tsx +12 -9
- package/src/SectionSettings.tsx +21 -0
- package/src/SectionSettingsIcon.tsx +10 -0
- package/src/SettingsSections.tsx +57 -0
- package/src/ThemeStore.tsx +28 -9
- package/src/Versions.tsx +3 -15
- package/src/busyAction.d.mts +3 -0
- package/src/busyAction.mjs +8 -0
- package/src/busyAction.test.mjs +16 -0
- package/src/dataFileValues.ts +30 -0
- package/src/editor.css +42 -3
- package/src/fields.tsx +29 -10
- package/src/footerPreview.d.mts +46 -0
- package/src/footerPreview.mjs +84 -0
- package/src/footerPreview.test.mjs +100 -0
- package/src/formSections.ts +100 -0
- package/src/frontmatter.mjs +1 -1
- package/src/frontmatter.test.mjs +11 -11
- package/src/headerPreview.d.mts +48 -0
- package/src/headerPreview.mjs +168 -0
- package/src/headerPreview.test.mjs +183 -0
- package/src/headerTheme.tsx +58 -0
- package/src/i18n/da-DK/common.ts +1 -0
- package/src/i18n/da-DK/library.ts +2 -0
- package/src/i18n/da-DK/preview.ts +2 -0
- package/src/i18n/da-DK/text.ts +1 -0
- package/src/i18n/en-US/common.ts +1 -0
- package/src/i18n/en-US/library.ts +4 -0
- package/src/i18n/en-US/preview.ts +3 -0
- package/src/i18n/en-US/text.ts +2 -1
- package/src/images.ts +7 -2
- package/src/menuChildren.ts +1 -3
- package/src/movedRoutes.d.mts +3 -0
- package/src/movedRoutes.mjs +35 -0
- package/src/movedRoutes.test.mjs +22 -0
- package/src/pagePreview.tsx +40 -29
- package/src/partPreview.tsx +59 -0
- package/src/schema.ts +7 -1
- package/src/sheetPlacement.d.mts +3 -0
- package/src/sheetPlacement.mjs +9 -0
- package/src/sheetPlacement.test.mjs +23 -0
- package/src/standard/views.ts +3 -3
- package/src/templateField.ts +28 -0
- package/src/themes.ts +3 -0
- package/src/ui.tsx +35 -8
- package/src/useDataFiles.ts +94 -0
- package/starter/package.json +1 -1
- package/theme/components/Footer.astro +14 -7
- package/theme/components/Header.astro +58 -33
- package/theme/components/MobileNav.astro +5 -4
- package/theme/components/RelatedPosts.astro +1 -1
- package/theme/components/headerParts.ts +17 -0
- package/theme/layouts/PageLayout.astro +20 -16
- package/theme/styles/base.css +6 -5
- package/theme/styles/blocks.css +8 -12
- package/theme/styles/components.css +8 -1
- package/theme/styles/global.css +5 -9
- package/theme/styles/ui.css +111 -5
- package/theme/theme.json +1 -0
- package/theme/ui/BusyContent.tsx +16 -0
- package/theme/ui/Button.tsx +25 -5
- package/theme/ui/IconButton.tsx +17 -4
- package/scripts/fixtures/side/side-off.md +0 -8
- package/scripts/fixtures/side/side-plain.md +0 -7
- package/scripts/fixtures/side/side-wide.md +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
# @morphemecms/cms
|
|
2
2
|
|
|
3
|
-
## 0.9.
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2e5bc1d: Sites type-check again: the header settings screen passes its theme the right data.
|
|
8
|
+
|
|
9
|
+
## 0.9.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
6
12
|
|
|
7
13
|
- 34d7f2f: An installed app's page has the tabs Settings and Information.
|
|
14
|
+
- a5f16e1: Show block and section settings in a side panel on large screens.
|
|
15
|
+
- 995f08e: Buttons and icon buttons show the M3 loading indicator while their action runs.
|
|
16
|
+
- 9b1d632: An optional `src/data/header.json` adds a header action button (in place of the phone icon on larger screens) and sets the menu button's side; the new Appearance › Header and Appearance › Footer screens edit all header and footer settings with a live preview on a phone and a computer.
|
|
17
|
+
- facf878: A page chooses a template from the ones its theme offers (`template` in the front matter, "templates" in theme.json: single, full, sidebar-left, sidebar-right). The page flags `wide` and `side` are gone. A site must migrate its `wide: true` pages (for example to `template: single`) and may remove `side: false`.
|
|
8
18
|
|
|
9
19
|
### Patch Changes
|
|
10
20
|
|
|
21
|
+
- 09924d5: Install, update and uninstall buttons show the loading indicator while they run.
|
|
22
|
+
- 3a40970: The Save and "More versions" buttons use the busy button with the loading indicator.
|
|
23
|
+
- e1a9843: Media, redirect, restore, theme and move buttons show the loading indicator while they run.
|
|
11
24
|
- 46636dc: Buttons in the editor are as wide as their label on every screen (Material 3), never across the screen.
|
|
25
|
+
- c22e682: Cards side by side or under each other keep 8dp between them, set by the new token `--card-gap`.
|
|
26
|
+
- 9047684: Cards in the Cards block rise to the first elevation when pointed at, as Material 3's filled cards do.
|
|
27
|
+
- 22d9179: Cards have 16dp of room inside them, as Material 3 has, and a card's picture follows that room.
|
|
28
|
+
- a9e9e95: Cards that link are tinted when pointed at or focused, as in Material 3, and no longer get an accent outline.
|
|
12
29
|
- 35e53d0: The editor's Appstore and the cookie consent log no longer hang when an earlier request was cancelled.
|
|
13
30
|
- 13998ad: Show the row that deletes what a screen shows (Delete promotion, Delete page …) in the theme's error colour.
|
|
14
31
|
- 5c4fa60: Show the signed-in user and the site's domain at the top of the main drawer.
|
|
@@ -17,9 +34,15 @@
|
|
|
17
34
|
- fc064fe: Align the main drawer header with the drawer's icons and arrows.
|
|
18
35
|
- 2c6ee27: Show a divider under the main drawer's header row.
|
|
19
36
|
- 152fb04: Make the main drawer's header row as high as a drawer item.
|
|
37
|
+
- 7d1f31b: The text toolbar has round corners and room all round it, also while it sticks under the top bar.
|
|
20
38
|
- c19ddf7: On a phone, hide the top bar's side panel button on screens that show a back arrow.
|
|
21
39
|
- 5db8ddc: Give every list item headline Material 3's label text size and weight.
|
|
40
|
+
- efbac0c: Lists in a page's dotted section frame keep their indent, so the bullets stand clear of the line.
|
|
41
|
+
- 86b0867: The "on this page" card on large screens is as wide as the navigation drawer.
|
|
42
|
+
- 5005d1f: Replace the section colour menu with a section settings sheet.
|
|
43
|
+
- eeb55b6: The template single draws its blocks across the column again and shows its first paragraph as a lead; the new `--type-lead-*` tokens (default: title large) let a theme style every lead.
|
|
22
44
|
- 46b7672: The Appstore no longer waits for ever when the store does not answer. After 15 seconds it says "Can't reach the store. Try again in 10 minutes."
|
|
45
|
+
- 0210960: The update message stays while an update is saved.
|
|
23
46
|
|
|
24
47
|
## 0.8.0
|
|
25
48
|
|
package/defaults/collections.ts
CHANGED
|
@@ -6,12 +6,56 @@
|
|
|
6
6
|
// block "Udtalelser"'s, packages/editor/src/standard/testimonials/list.ts, an app's lists its own,
|
|
7
7
|
// src/apps/<app>/lists.ts).
|
|
8
8
|
// files: single .json files: the settings (the standard pages …), the footer and the menu.
|
|
9
|
-
import { AppWindow, SquareHalfBottom } from '@phosphor-icons/react';
|
|
9
|
+
import { AppWindow, ArrowsHorizontal, ColumnsPlusLeft, ColumnsPlusRight, File, SquareHalfBottom } from '@phosphor-icons/react';
|
|
10
10
|
import type { Collection, Field } from '../src/schema';
|
|
11
11
|
import { fromContactBar } from '../lib/contactBar';
|
|
12
12
|
import { config } from '@site/config';
|
|
13
13
|
import { standardPaths } from '@site/standardPages';
|
|
14
14
|
|
|
15
|
+
// The fields of one button of the action bar (the bottom bar on a phone, ActionBar.astro) and of the button in the header
|
|
16
|
+
// ("Headerknap", src/data/header.json): its text, where it leads, and the promotion or page it leads to.
|
|
17
|
+
const barButtonLabel: Field = {
|
|
18
|
+
name: 'label',
|
|
19
|
+
label: 'Knaptekst',
|
|
20
|
+
type: 'string',
|
|
21
|
+
max: 30,
|
|
22
|
+
hint: 'Hvis feltet er tomt, bruges standardteksten eller sidens navn.',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const barButtonTo: Field = {
|
|
26
|
+
name: 'to',
|
|
27
|
+
label: 'Fører til',
|
|
28
|
+
type: 'select',
|
|
29
|
+
required: true,
|
|
30
|
+
default: 'booking',
|
|
31
|
+
options: [
|
|
32
|
+
{ label: 'Book en tid', value: 'booking' },
|
|
33
|
+
{ label: 'Ring', value: 'phone' },
|
|
34
|
+
{ label: 'Åbn promovering', value: 'promotion', onPage: 'promotions' },
|
|
35
|
+
{ label: 'En side', value: 'page' },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const barButtonOpen: Field = {
|
|
40
|
+
name: 'open',
|
|
41
|
+
label: 'Promovering',
|
|
42
|
+
type: 'relation',
|
|
43
|
+
to: 'promotions',
|
|
44
|
+
onPage: true,
|
|
45
|
+
required: true,
|
|
46
|
+
only: { field: 'to', is: ['promotion'] },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const barButtonHref: Field = {
|
|
50
|
+
name: 'href',
|
|
51
|
+
label: 'Side',
|
|
52
|
+
type: 'relation',
|
|
53
|
+
to: 'pages',
|
|
54
|
+
fixed: true,
|
|
55
|
+
required: true,
|
|
56
|
+
only: { field: 'to', is: ['page'] },
|
|
57
|
+
};
|
|
58
|
+
|
|
15
59
|
// The page Google should count as the original of a page or a post (rel=canonical); empty is itself.
|
|
16
60
|
// Under "Søgemaskiner" with the Google texts (EntryForm.tsx).
|
|
17
61
|
const canonical: Field = {
|
|
@@ -136,7 +180,7 @@ const keywords: Field[] = [
|
|
|
136
180
|
// The business, on "Virksomheden" (CompanyForm.tsx): its name and title, phone, e-mail, address and CVR number
|
|
137
181
|
// (src/data/contact.json), shown at the top and bottom of every page and in the blocks that show them ("Kontakt" and
|
|
138
182
|
// "Adresse"); and the bank details (src/data/payment.json), shown in "Betaling". The calendar
|
|
139
|
-
// is the app Calendly's (
|
|
183
|
+
// is the app Calendly's (morpheme-apps/apps/calendly/).
|
|
140
184
|
export const contactFields: Field[] = [
|
|
141
185
|
{ name: 'name', label: 'Navn', type: 'string', required: true },
|
|
142
186
|
{ name: 'role', label: 'Titel', type: 'string', required: true },
|
|
@@ -407,22 +451,20 @@ export const collections: Collection[] = [
|
|
|
407
451
|
hint: 'Vises ikke på hjemmesiden.',
|
|
408
452
|
},
|
|
409
453
|
{
|
|
410
|
-
name: '
|
|
411
|
-
label: '
|
|
412
|
-
type: '
|
|
413
|
-
|
|
414
|
-
hint: 'Siden fylder hele bredden uden spalten ved siden af teksten.',
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
name: 'side',
|
|
418
|
-
label: 'Sidekolonne',
|
|
419
|
-
type: 'boolean',
|
|
420
|
-
toggle: ['Til', 'Fra'],
|
|
421
|
-
// Without the key the column is on; only "Fra" is written (side: false).
|
|
422
|
-
default: true,
|
|
423
|
-
missing: true,
|
|
454
|
+
name: 'template',
|
|
455
|
+
label: 'Skabelon',
|
|
456
|
+
type: 'select',
|
|
457
|
+
segments: true,
|
|
424
458
|
layout: true,
|
|
425
|
-
|
|
459
|
+
// The options the theme in use offers, the first of its list the default (templateFieldOf in src/templateField.ts).
|
|
460
|
+
offered: 'templates',
|
|
461
|
+
options: [
|
|
462
|
+
{ label: 'En kolonne', value: 'single', icon: File },
|
|
463
|
+
{ label: 'Fuld bredde', value: 'full', icon: ArrowsHorizontal },
|
|
464
|
+
{ label: 'Sidebjælke til venstre', value: 'sidebar-left', icon: ColumnsPlusLeft },
|
|
465
|
+
{ label: 'Sidebjælke til højre', value: 'sidebar-right', icon: ColumnsPlusRight },
|
|
466
|
+
],
|
|
467
|
+
hint: 'Temaet bestemmer, hvilke skabeloner der findes.',
|
|
426
468
|
},
|
|
427
469
|
{
|
|
428
470
|
name: 'review',
|
|
@@ -469,7 +511,7 @@ export const collections: Collection[] = [
|
|
|
469
511
|
// src/data/popups/<slug>.json, read by packages/editor/site/popups.ts, on every page or those picked under "Sider".
|
|
470
512
|
// The pop-ups are shown by packages/editor/theme/components/Popup.astro; a kind an app adds without a part
|
|
471
513
|
// in the pop-up (src/apps/<app>/popups/View.tsx) shows itself (the app Calendly's booking sheet,
|
|
472
|
-
//
|
|
514
|
+
// morpheme-apps/apps/calendly/, when a button opens it).
|
|
473
515
|
kind: 'folder',
|
|
474
516
|
id: 'popups',
|
|
475
517
|
create: 'Ny promovering',
|
|
@@ -695,46 +737,10 @@ export const collections: Collection[] = [
|
|
|
695
737
|
hint: 'Står under overskriften.',
|
|
696
738
|
only: { field: 'type', is: ['text'] },
|
|
697
739
|
},
|
|
698
|
-
{
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
max: 30,
|
|
703
|
-
hint: 'Hvis feltet er tomt, bruges standardteksten eller sidens navn.',
|
|
704
|
-
only: { field: 'type', is: ['button'] },
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
name: 'to',
|
|
708
|
-
label: 'Fører til',
|
|
709
|
-
type: 'select',
|
|
710
|
-
required: true,
|
|
711
|
-
default: 'booking',
|
|
712
|
-
only: { field: 'type', is: ['button'] },
|
|
713
|
-
options: [
|
|
714
|
-
{ label: 'Book en tid', value: 'booking' },
|
|
715
|
-
{ label: 'Ring', value: 'phone' },
|
|
716
|
-
{ label: 'Åbn promovering', value: 'promotion', onPage: 'promotions' },
|
|
717
|
-
{ label: 'En side', value: 'page' },
|
|
718
|
-
],
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
name: 'open',
|
|
722
|
-
label: 'Promovering',
|
|
723
|
-
type: 'relation',
|
|
724
|
-
to: 'promotions',
|
|
725
|
-
onPage: true,
|
|
726
|
-
required: true,
|
|
727
|
-
only: { field: 'to', is: ['promotion'] },
|
|
728
|
-
},
|
|
729
|
-
{
|
|
730
|
-
name: 'href',
|
|
731
|
-
label: 'Side',
|
|
732
|
-
type: 'relation',
|
|
733
|
-
to: 'pages',
|
|
734
|
-
fixed: true,
|
|
735
|
-
required: true,
|
|
736
|
-
only: { field: 'to', is: ['page'] },
|
|
737
|
-
},
|
|
740
|
+
{ ...barButtonLabel, only: { field: 'type', is: ['button'] } },
|
|
741
|
+
{ ...barButtonTo, only: { field: 'type', is: ['button'] } },
|
|
742
|
+
barButtonOpen,
|
|
743
|
+
barButtonHref,
|
|
738
744
|
],
|
|
739
745
|
},
|
|
740
746
|
],
|
|
@@ -848,4 +854,52 @@ export const collections: Collection[] = [
|
|
|
848
854
|
},
|
|
849
855
|
],
|
|
850
856
|
},
|
|
857
|
+
{
|
|
858
|
+
kind: 'files',
|
|
859
|
+
id: 'headerknap',
|
|
860
|
+
label: 'Headerknap',
|
|
861
|
+
singular: 'headerknap',
|
|
862
|
+
files: [
|
|
863
|
+
// src/data/header.json, optional (a site without it has no button and the menu button at the start), read by the header
|
|
864
|
+
// (Header.astro). The button is one of the action bar's (barButtonLabel …).
|
|
865
|
+
{
|
|
866
|
+
file: 'src/data/header.json',
|
|
867
|
+
label: 'Headeren',
|
|
868
|
+
view: '/',
|
|
869
|
+
optional: true,
|
|
870
|
+
fields: [
|
|
871
|
+
{
|
|
872
|
+
name: 'button',
|
|
873
|
+
label: 'Handlingsknap',
|
|
874
|
+
type: 'object',
|
|
875
|
+
fields: [
|
|
876
|
+
{
|
|
877
|
+
name: 'enabled',
|
|
878
|
+
label: 'Aktiveret',
|
|
879
|
+
type: 'boolean',
|
|
880
|
+
toggle: ['Til', 'Fra'],
|
|
881
|
+
default: false,
|
|
882
|
+
hint: 'Knappen står i headeren.',
|
|
883
|
+
},
|
|
884
|
+
barButtonLabel,
|
|
885
|
+
barButtonTo,
|
|
886
|
+
barButtonOpen,
|
|
887
|
+
barButtonHref,
|
|
888
|
+
],
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: 'menuSide',
|
|
892
|
+
label: 'Menuknappens placering',
|
|
893
|
+
type: 'select',
|
|
894
|
+
segments: true,
|
|
895
|
+
default: 'start',
|
|
896
|
+
options: [
|
|
897
|
+
{ label: 'Venstre', value: 'start' },
|
|
898
|
+
{ label: 'Højre', value: 'end' },
|
|
899
|
+
],
|
|
900
|
+
},
|
|
901
|
+
],
|
|
902
|
+
},
|
|
903
|
+
],
|
|
904
|
+
},
|
|
851
905
|
];
|
package/defaults/company.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// (src/data/contact.json) and its bank details (src/data/payment.json, under "Betaling", where a site may add its
|
|
3
3
|
// other ways to pay, and where the settings of the apps installed that are a way to pay come after, as MobilePay's),
|
|
4
4
|
// used all over the site; and its social media (src/data/social.json): the profiles and the post's share buttons.
|
|
5
|
-
// The logo and the icon are under "Udseende" (BrandForm.tsx); the link to the calendar in Calendly is the app Calendly's (
|
|
5
|
+
// The logo and the icon are under "Udseende" (BrandForm.tsx); the link to the calendar in Calendly is the app Calendly's (morpheme-apps/apps/calendly/).
|
|
6
6
|
import { AddressBook, Bank, CreditCard, ShareNetwork } from '@phosphor-icons/react';
|
|
7
7
|
import type { Part } from '../src/schema';
|
|
8
8
|
import { bankFields, contactFields, socialFields } from '@site/collections';
|
package/defaults/editor.ts
CHANGED
|
@@ -193,5 +193,5 @@ export const settings: string[] = settingsIds;
|
|
|
193
193
|
* bring ("Udtalelser") and the apps (their lists.ts). */
|
|
194
194
|
export const lists: string[] = [];
|
|
195
195
|
|
|
196
|
-
/** What is in the header on every page, on
|
|
197
|
-
export const header = ['menu', 'mobilmenu'];
|
|
196
|
+
/** What is in the header on every page, on one screen under "Udseende" › "Header" (#udseende/header, HeaderSettings.tsx). */
|
|
197
|
+
export const header = ['menu', 'mobilmenu', 'headerknap'];
|
package/defaults/texts.ts
CHANGED
|
@@ -26,11 +26,12 @@ export const texts: Record<string, Record<string, string>> = {
|
|
|
26
26
|
'Skærmlæsere springer billedet over.': 'Screen readers skip the picture.',
|
|
27
27
|
Til: 'On',
|
|
28
28
|
Fra: 'Off',
|
|
29
|
-
|
|
30
|
-
'
|
|
31
|
-
|
|
32
|
-
'
|
|
33
|
-
'
|
|
29
|
+
Skabelon: 'Template',
|
|
30
|
+
'En kolonne': 'Single column',
|
|
31
|
+
'Fuld bredde': 'Full width',
|
|
32
|
+
'Sidebjælke til venstre': 'Sidebar left',
|
|
33
|
+
'Sidebjælke til højre': 'Sidebar right',
|
|
34
|
+
'Temaet bestemmer, hvilke skabeloner der findes.': 'The theme decides which templates there are.',
|
|
34
35
|
Status: 'Status',
|
|
35
36
|
Kladde: 'Draft',
|
|
36
37
|
'Vises ikke på hjemmesiden.': 'Not shown on the website.',
|
|
@@ -273,6 +274,14 @@ export const texts: Record<string, Record<string, string>> = {
|
|
|
273
274
|
Mobilmenuen: 'The mobile menu',
|
|
274
275
|
'Punkterne står på en linje under headeren på en telefon og kan skubbes til siden.':
|
|
275
276
|
'The items are on a line under the header on a phone and can be swiped sideways.',
|
|
277
|
+
Headerknap: 'Header button',
|
|
278
|
+
headerknap: 'header button',
|
|
279
|
+
Headeren: 'The header',
|
|
280
|
+
Handlingsknap: 'Action button',
|
|
281
|
+
'Knappen står i headeren.': 'The button is in the header.',
|
|
282
|
+
'Menuknappens placering': 'Menu button position',
|
|
283
|
+
Venstre: 'Left',
|
|
284
|
+
Højre: 'Right',
|
|
276
285
|
Menupunkter: 'Menu items',
|
|
277
286
|
menupunkt: 'menu item',
|
|
278
287
|
Undermenu: 'Submenu',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "App store"
|
|
3
3
|
description: "Add new features to your site with one tap: apps and themes from the app store."
|
|
4
|
-
wide: true
|
|
5
4
|
seoTitle: "App store | Morpheme"
|
|
6
5
|
seoDescription: "Add booking, newsletter signups, content-based colors and new themes to your site with one tap. Every install can be undone."
|
|
7
6
|
keyword: "app store"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Blocks"
|
|
3
3
|
description: "Every block you can build a page from in Morpheme, shown live."
|
|
4
|
-
wide: true
|
|
5
4
|
seoTitle: "Blocks | Morpheme"
|
|
6
5
|
seoDescription: "Buttons, cards, points, steps, checklists, FAQs, price cards, testimonials and more: every Morpheme block, shown live."
|
|
7
6
|
keyword: "blocks"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="12" x="2" y="6" rx="2" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /> <path d="M9 3v18" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /> <path d="M15 3v18" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="12" height="20" x="6" y="2" rx="2" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M140-160q-24.75 0-42.37-17.63Q80-195.25 80-220v-520q0-24.75 17.63-42.38Q115.25-800 140-800h680q24.75 0 42.38 17.62Q880-764.75 880-740v520q0 24.75-17.62 42.37Q844.75-160 820-160H140Zm0-60h70v-520h-70v520Zm130 0h420v-520H270v520Zm480 0h70v-520h-70v520ZM270-740v520-520Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M180-120q-24.75 0-42.37-17.63Q120-155.25 120-180v-600q0-24.75 17.63-42.38Q155.25-840 180-840h600q24.75 0 42.38 17.62Q840-804.75 840-780v600q0 24.75-17.62 42.37Q804.75-120 780-120H180Zm147-60v-600H180v600h147Zm60 0h393v-600H387v600Zm-60 0H180h147Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M180-120q-24.75 0-42.37-17.63Q120-155.25 120-180v-600q0-24.75 17.63-42.38Q155.25-840 180-840h600q24.75 0 42.38 17.62Q840-804.75 840-780v600q0 24.75-17.62 42.37Q804.75-120 780-120H180Zm453-60h147v-600H633v600Zm-60 0v-600H180v600h393Zm60 0h147-147Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M740-80H220q-24 0-42-18t-18-42v-680q0-24 18-42t42-18h520q24 0 42 18t18 42v680q0 24-18 42t-42 18Zm-520-60h520v-680H220v680Zm0 0v-680 680Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M237.66,133.66l-32,32a8,8,0,0,1-11.32-11.32L212.69,136H43.31l18.35,18.34a8,8,0,0,1-11.32,11.32l-32-32a8,8,0,0,1,0-11.32l32-32a8,8,0,0,1,11.32,11.32L43.31,120H212.69l-18.35-18.34a8,8,0,0,1,11.32-11.32l32,32A8,8,0,0,1,237.66,133.66Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M128,32H104A16,16,0,0,0,88,48V208a16,16,0,0,0,16,16h24a16,16,0,0,0,16-16V48A16,16,0,0,0,128,32Zm0,176H104V48h24ZM200,32H176a16,16,0,0,0-16,16V208a16,16,0,0,0,16,16h24a16,16,0,0,0,16-16V48A16,16,0,0,0,200,32Zm0,176H176V48h24ZM72,128a8,8,0,0,1-8,8H48v16a8,8,0,0,1-16,0V136H16a8,8,0,0,1,0-16H32V104a8,8,0,0,1,16,0v16H64A8,8,0,0,1,72,128Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M80,32H56A16,16,0,0,0,40,48V208a16,16,0,0,0,16,16H80a16,16,0,0,0,16-16V48A16,16,0,0,0,80,32Zm0,176H56V48H80ZM152,32H128a16,16,0,0,0-16,16V208a16,16,0,0,0,16,16h24a16,16,0,0,0,16-16V48A16,16,0,0,0,152,32Zm0,176H128V48h24Zm96-80a8,8,0,0,1-8,8H224v16a8,8,0,0,1-16,0V136H192a8,8,0,0,1,0-16h16V104a8,8,0,0,1,16,0v16h16A8,8,0,0,1,248,128Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z"/></svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Which of a form's folding sections are open (formSections.ts): pure, so it is tested (fold-open.test.mjs). Exposes
|
|
2
|
+
// withOpen and onlyFirst. No dependencies.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The sections open after one is opened: beside the others, or with `single` (one open at a time) in their place.
|
|
6
|
+
* @template T
|
|
7
|
+
* @param {ReadonlySet<T>} open
|
|
8
|
+
* @param {T} id
|
|
9
|
+
* @param {boolean} single
|
|
10
|
+
* @returns {ReadonlySet<T>}
|
|
11
|
+
*/
|
|
12
|
+
export const withOpen = (open, id, single) => (single ? new Set([id]) : open.has(id) ? open : new Set([...open, id]));
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Only the first of the open sections, in the form's order (`order`), when more are open (the window narrowed to one at a
|
|
16
|
+
* time); else the same.
|
|
17
|
+
* @template T
|
|
18
|
+
* @param {ReadonlySet<T>} open
|
|
19
|
+
* @param {T[]} order
|
|
20
|
+
* @returns {ReadonlySet<T>}
|
|
21
|
+
*/
|
|
22
|
+
export const onlyFirst = (open, order) => (open.size > 1 ? new Set(order.filter((id) => open.has(id)).slice(0, 1)) : open);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Tests lib/fold-open.mjs: opening a section beside the others or in their place, and only the first left open.
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { onlyFirst, withOpen } from './fold-open.mjs';
|
|
5
|
+
|
|
6
|
+
test('a section opens beside the others', () => {
|
|
7
|
+
assert.deepEqual([...withOpen(new Set(['a']), 'b', false)], ['a', 'b']);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('an open section stays as it is', () => {
|
|
11
|
+
const open = new Set(['a']);
|
|
12
|
+
assert.equal(withOpen(open, 'a', false), open);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('one at a time: a section opens in the place of the others', () => {
|
|
16
|
+
assert.deepEqual([...withOpen(new Set(['a', 'c']), 'b', true)], ['b']);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('only the first open section in the order stays open', () => {
|
|
20
|
+
assert.deepEqual([...onlyFirst(new Set(['c', 'b']), ['a', 'b', 'c'])], ['b']);
|
|
21
|
+
const one = new Set(['c']);
|
|
22
|
+
assert.equal(onlyFirst(one, ['a', 'b', 'c']), one);
|
|
23
|
+
assert.equal(onlyFirst(new Set(), ['a']).size, 0);
|
|
24
|
+
});
|
package/lib/menu.test.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { currentPart, menuTree, currentPlace } from './menu.mjs';
|
|
|
8
8
|
|
|
9
9
|
const known = {
|
|
10
10
|
lists: ['udtalelser', 'cookies', 'priser'],
|
|
11
|
-
header: ['menu', 'mobilmenu'],
|
|
11
|
+
header: ['menu', 'mobilmenu', 'headerknap'],
|
|
12
12
|
partScreens: ['privatliv'],
|
|
13
13
|
cookies: 'cookies',
|
|
14
14
|
store: 'indstillinger/appstore',
|
|
@@ -54,6 +54,7 @@ test('the cookie list is under Privacy, whatever the way to it', () => {
|
|
|
54
54
|
test('the menus are under Header', () => {
|
|
55
55
|
assert.equal(currentPart('src/data/menu.json', { ...known, open: 'menu' }), 'header');
|
|
56
56
|
assert.equal(currentPart('liste/mobilmenu', known), 'header');
|
|
57
|
+
assert.equal(currentPart('src/data/header.json', { ...known, open: 'headerknap' }), 'header');
|
|
57
58
|
});
|
|
58
59
|
|
|
59
60
|
test('a screen under a part is that part', () => {
|
|
@@ -170,8 +171,8 @@ const hrefs = {
|
|
|
170
171
|
filer: '#filer',
|
|
171
172
|
'interne-links': '#seo/interne-links',
|
|
172
173
|
videresendelser: '#seo/videresendelser',
|
|
173
|
-
header: '#header',
|
|
174
|
-
footer: '#
|
|
174
|
+
header: '#udseende/header',
|
|
175
|
+
footer: '#udseende/footer',
|
|
175
176
|
virksomheden: '#virksomheden',
|
|
176
177
|
apps: '#apps',
|
|
177
178
|
appstore: '#indstillinger/appstore',
|
|
@@ -260,6 +261,18 @@ test('currentPlace of a page being edited, a list, the SEO rating, an Appearance
|
|
|
260
261
|
assert.deepEqual(currentPlace('src/data/cookie-list/a.json', menu, { ...menuKnown, open: 'cookies' }), { section: 'privatliv' });
|
|
261
262
|
});
|
|
262
263
|
|
|
264
|
+
test('currentPlace of Appearance › Header and its menus is Global › Header', () => {
|
|
265
|
+
assert.deepEqual(currentPlace('udseende/header', menu, menuKnown), { section: 'globalt', item: 'header' });
|
|
266
|
+
assert.deepEqual(currentPlace('src/data/menu.json', menu, { ...menuKnown, open: 'menu' }), { section: 'globalt', item: 'header' });
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('currentPlace of Appearance › Footer is Global › Footer, a row without children', () => {
|
|
270
|
+
assert.deepEqual(currentPlace('udseende/footer', menu, menuKnown), { section: 'globalt', item: 'footer' });
|
|
271
|
+
const global = menu.find((entry) => entry.id === 'globalt');
|
|
272
|
+
assert.equal(global.items.find((item) => item.id === 'footer').children, undefined);
|
|
273
|
+
assert.equal(global.items.find((item) => item.id === 'header').children, undefined);
|
|
274
|
+
});
|
|
275
|
+
|
|
263
276
|
const appsGroups = [{ id: 'apps', title: 'Apps', ids: ['apps', 'appstore'] }];
|
|
264
277
|
const appsPartOf = (id) => ({ label: `L ${id}`, href: id === 'appstore' ? '#indstillinger/appstore' : '#apps', count: null });
|
|
265
278
|
const appsOf = (list) => (id) => (id === 'apps' ? list.map((a) => ({ id: a, label: `A ${a}`, href: `#apps/${a}` })) : []);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return the theme's `templates` list when templateProblems finds no problem and the list is not undefined;
|
|
3
|
+
* else a copy of STANDARD_TEMPLATES.
|
|
4
|
+
*
|
|
5
|
+
* @param {{ templates?: unknown } | null | undefined} theme
|
|
6
|
+
* @returns {string[]} A copy of the theme's valid template list or of STANDARD_TEMPLATES.
|
|
7
|
+
*/
|
|
8
|
+
export function templatesOf(theme: {
|
|
9
|
+
templates?: unknown;
|
|
10
|
+
} | null | undefined): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Return page.template when it is a string in offered; else offered[0].
|
|
13
|
+
*
|
|
14
|
+
* @param {{ template?: unknown } | null | undefined} page
|
|
15
|
+
* @param {string[]} offered
|
|
16
|
+
* @returns {string} The resolved template id.
|
|
17
|
+
*/
|
|
18
|
+
export function templateOf(page: {
|
|
19
|
+
template?: unknown;
|
|
20
|
+
} | null | undefined, offered: string[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* True for 'sidebar-left' and 'sidebar-right'; false for every other value.
|
|
23
|
+
*
|
|
24
|
+
* @param {unknown} template
|
|
25
|
+
* @returns {boolean} Whether the template is a sidebar template.
|
|
26
|
+
*/
|
|
27
|
+
export function hasSidebar(template: unknown): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Return offered in the same order; without the sidebar templates when sideChosen is false.
|
|
30
|
+
*
|
|
31
|
+
* @param {string[]} offered
|
|
32
|
+
* @param {boolean} sideChosen
|
|
33
|
+
* @returns {string[]} A new array of template choices.
|
|
34
|
+
*/
|
|
35
|
+
export function templateChoices(offered: string[], sideChosen: boolean): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Problems with a theme.json `templates` value, one English sentence each.
|
|
38
|
+
* [] when the value is undefined or valid.
|
|
39
|
+
*
|
|
40
|
+
* @param {unknown} value
|
|
41
|
+
* @returns {string[]} List of problem descriptions.
|
|
42
|
+
*/
|
|
43
|
+
export function templateProblems(value: unknown): string[];
|
|
44
|
+
/**
|
|
45
|
+
* What the form writes to the front matter for the chosen template: '' (no key) for the theme's default (offered[0]),
|
|
46
|
+
* and for a page with none; else the template as it is.
|
|
47
|
+
*
|
|
48
|
+
* @param {unknown} value
|
|
49
|
+
* @param {string[]} offered
|
|
50
|
+
* @returns {unknown} '' or the value to write.
|
|
51
|
+
*/
|
|
52
|
+
export function templateWritten(value: unknown, offered: string[]): unknown;
|
|
53
|
+
export const PAGE_TEMPLATES: string[];
|
|
54
|
+
export const STANDARD_TEMPLATES: string[];
|