@morphemecms/cms 0.11.0 → 0.13.0-next-20261106203613
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 +31 -0
- package/astro.mjs +2 -1
- package/defaults/blockTexts.ts +9 -9
- package/defaults/collections.ts +257 -257
- package/defaults/colors.ts +74 -74
- package/defaults/company.ts +9 -9
- package/defaults/editor.ts +65 -65
- package/defaults/i18n/da-DK/blockTexts.ts +12 -0
- package/defaults/i18n/da-DK/collections.ts +220 -0
- package/defaults/i18n/da-DK/colors.ts +102 -0
- package/defaults/i18n/da-DK/company.ts +7 -0
- package/defaults/i18n/da-DK/editor.ts +13 -0
- package/defaults/i18n/da-DK/index.ts +31 -0
- package/defaults/i18n/da-DK/internalLinks.ts +12 -0
- package/defaults/i18n/da-DK/opens.ts +7 -0
- package/defaults/i18n/da-DK/popup.ts +9 -0
- package/defaults/i18n/da-DK/privacy.ts +38 -0
- package/defaults/i18n/da-DK/rules.ts +56 -0
- package/defaults/i18n/da-DK/sitedata.ts +8 -0
- package/defaults/i18n/da-DK/standard.ts +8 -0
- package/defaults/i18n/da-DK/standardPages.ts +17 -0
- package/defaults/internalLinks.ts +8 -8
- package/defaults/opens.ts +2 -2
- package/defaults/popup.tsx +7 -7
- package/defaults/privacy.ts +37 -37
- package/defaults/rules.ts +68 -66
- package/defaults/schema.ts +1 -1
- package/defaults/sitedata.ts +4 -4
- package/defaults/standard.ts +9 -6
- package/defaults/standardPages.ts +14 -14
- package/defaults/texts.ts +7 -510
- package/lib/appId.d.mts +1 -0
- package/lib/appId.mjs +6 -0
- package/lib/appRoute.d.mts +2 -2
- package/lib/appRoute.mjs +3 -3
- package/lib/appRoute.test.mjs +8 -8
- package/lib/apps.mjs +13 -3
- package/lib/apps.test.mjs +10 -1
- package/lib/contracts.d.mts +3 -2
- package/lib/contracts.mjs +3 -2
- package/lib/folderChanges.d.mts +29 -0
- package/lib/folderChanges.mjs +34 -0
- package/lib/licenses.mjs +2 -2
- package/lib/licenses.owner.test.mjs +9 -0
- package/lib/menu.mjs +4 -4
- package/lib/menu.test.mjs +101 -101
- package/lib/scheduled.mjs +1 -1
- package/lib/section-ranges.d.mts +1 -1
- package/lib/section-ranges.mjs +8 -14
- package/lib/section-ranges.test.mjs +26 -5
- package/lib/storeEntries.d.mts +3 -1
- package/lib/storeEntries.mjs +22 -8
- package/lib/storeEntries.test.mjs +59 -7
- package/lib/text-sections.d.mts +11 -1
- package/lib/text-sections.mjs +18 -8
- package/lib/text-sections.test.mjs +34 -1
- package/lib/translate.d.mts +11 -0
- package/lib/translate.mjs +13 -1
- package/lib/translate.test.mjs +23 -2
- package/lib/video-id.d.mts +6 -2
- package/lib/video-id.mjs +5 -1
- package/lib/video.d.mts +6 -2
- package/lib/video.mjs +5 -1
- package/lib/without-h1.d.mts +1 -0
- package/lib/without-h1.mjs +19 -4
- package/lib/without-h1.test.mjs +30 -1
- package/package.json +3 -2
- package/scripts/app-screenshots.mjs +9 -8
- package/scripts/check-content.mjs +12 -2
- package/scripts/check-language.mjs +0 -1
- package/scripts/check-site-types.mjs +26 -0
- package/scripts/editor-screens.mjs +3 -3
- package/scripts/site-types.mjs +14 -0
- package/scripts/site-types.test.mjs +21 -0
- package/site/activeTheme.test.mjs +52 -0
- package/site/appSelf.d.mts +2 -0
- package/site/appSelf.mjs +23 -0
- package/site/appSelf.test.mjs +35 -0
- package/site/content.ts +5 -2
- package/site/defineSite.mjs +2 -2
- package/site/schema.ts +3 -3
- package/site/siteModules.mjs +2 -1
- package/site/theme.mjs +19 -8
- package/src/Advanced.tsx +2 -2
- package/src/App.tsx +79 -78
- package/src/AppAdds.tsx +1 -1
- package/src/AppList.tsx +39 -24
- package/src/BlockDialog.tsx +39 -18
- package/src/BlockParts.tsx +6 -6
- package/src/BlockSettings.tsx +2 -2
- package/src/BrandForm.tsx +2 -2
- package/src/Colors.tsx +6 -6
- package/src/CompanyForm.tsx +1 -1
- package/src/Consents.tsx +3 -3
- package/src/CreateFab.tsx +8 -0
- package/src/DataForm.tsx +10 -20
- package/src/Deleted.tsx +3 -3
- package/src/EditorFooter.tsx +2 -2
- package/src/EntryForm.tsx +3 -3
- package/src/FooterSettings.tsx +2 -2
- package/src/FormFrame.tsx +4 -3
- package/src/HeaderSettings.tsx +3 -3
- package/src/Keywords.tsx +5 -5
- package/src/Library.tsx +75 -82
- package/src/Licenses.tsx +8 -8
- package/src/ListFab.tsx +13 -0
- package/src/MainDrawerParts.tsx +3 -3
- package/src/MainMenu.tsx +1 -1
- package/src/MarkdownEditor.tsx +88 -47
- package/src/Media.tsx +11 -20
- package/src/MediaTags.tsx +2 -2
- package/src/Notifications.tsx +2 -2
- package/src/Parts.tsx +17 -17
- package/src/Redirects.tsx +14 -22
- package/src/Screen.tsx +16 -6
- package/src/SeoScores.tsx +6 -6
- package/src/SideColumn.tsx +2 -2
- package/src/StartFab.tsx +6 -8
- package/src/ThemeStore.tsx +37 -16
- package/src/Tokens.tsx +2 -2
- package/src/apps.ts +7 -8
- package/src/checkSite.ts +86 -20
- package/src/drawerYield.d.mts +3 -0
- package/src/drawerYield.mjs +10 -0
- package/src/drawerYield.test.mjs +22 -0
- package/src/editor.css +59 -38
- package/src/examples.ts +1 -1
- package/src/fabScreens.test.mjs +28 -0
- package/src/fabScreens.ts +16 -0
- package/src/i18n/da-DK/library.ts +2 -2
- package/src/i18n/da-DK/text.ts +1 -1
- package/src/i18n/en-US/apps.ts +1 -1
- package/src/i18n/en-US/common.ts +8 -8
- package/src/i18n/en-US/library.ts +5 -5
- package/src/i18n/en-US/parts.ts +3 -3
- package/src/i18n/en-US/text.ts +1 -1
- package/src/i18n/index.ts +4 -8
- package/src/images.ts +2 -2
- package/src/legacyMove.test.mjs +137 -0
- package/src/legacyMove.ts +131 -0
- package/src/licenseData.ts +3 -3
- package/src/markdown.mjs +10 -7
- package/src/markdown.test.mjs +16 -2
- package/src/menuChildren.ts +5 -5
- package/src/menuOpen.ts +20 -4
- package/src/movedRoutes.d.mts +8 -1
- package/src/movedRoutes.mjs +102 -21
- package/src/movedRoutes.test.mjs +128 -6
- package/src/nav.ts +1 -1
- package/src/notificationData.ts +1 -1
- package/src/pageH1Panel.d.mts +4 -0
- package/src/pageH1Panel.mjs +31 -0
- package/src/pageH1Panel.test.mjs +56 -0
- package/src/pagePreview.tsx +22 -12
- package/src/schema.ts +2 -2
- package/src/sectionFrame.ts +3 -3
- package/src/sectionState.d.mts +3 -3
- package/src/sectionState.mjs +6 -6
- package/src/sectionState.test.mjs +14 -2
- package/src/sheetPlacement.d.mts +6 -4
- package/src/sheetPlacement.mjs +15 -10
- package/src/sheetPlacement.test.mjs +16 -9
- package/src/sidePanel.ts +38 -0
- package/src/site.ts +37 -13
- package/src/standard/accordion/index.tsx +1 -1
- package/src/standard/accordion/shared.ts +1 -1
- package/src/standard/buttons/index.tsx +1 -1
- package/src/standard/cards/index.tsx +1 -1
- package/src/standard/cards/shared.ts +1 -1
- package/src/standard/cookies/index.tsx +1 -1
- package/src/standard/posts/index.tsx +1 -1
- package/src/standard/testimonials/index.tsx +1 -1
- package/src/standard/testimonials/list.ts +1 -1
- package/src/store.ts +36 -31
- package/src/tagUses.d.mts +1 -1
- package/src/tagUses.mjs +2 -2
- package/src/tagUses.test.mjs +1 -1
- package/src/topBarSlot.tsx +7 -15
- package/src/ui.tsx +33 -22
- package/src/virtual.d.ts +6 -0
- package/starter/package.json +1 -1
- package/theme/components/PageHeading.astro +22 -0
- package/theme/layouts/Base.astro +2 -2
- package/theme/layouts/PageLayout.astro +37 -20
- package/theme/styles/global.css +2 -1
- package/theme/styles/ui.css +40 -10
package/src/editor.css
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
@reference '../theme/styles/base.css';
|
|
5
5
|
|
|
6
6
|
:where(:root) {
|
|
7
|
+
/* The menus and the toolbar of the text: Material 3's menu surface and shadow (elevation level 2). */
|
|
8
|
+
--editor-menu-surface: var(--surface-container-lowest);
|
|
9
|
+
--editor-menu-shadow: var(--elevation-2);
|
|
10
|
+
|
|
7
11
|
/* How a check went (CheckList: the SEO rating), as PageSpeed shows it: the mark before a check that is fine and one
|
|
8
12
|
that can be done better, and the score's ring (from 90 good, from 50 average, else bad) with its number and the
|
|
9
13
|
light circle in it; the same in every theme. */
|
|
@@ -33,13 +37,14 @@
|
|
|
33
37
|
--editor-section-rhythm: 0.625em;
|
|
34
38
|
|
|
35
39
|
/* A form's "on this page" area (SectionPane in ui.tsx) against the window's end edge: its width (the card is as wide as the navigation drawer), the card in it
|
|
36
|
-
stands --section-pane-gap in from the end edge and from
|
|
40
|
+
stands --section-pane-gap in from the end edge and from the window's top edge. */
|
|
37
41
|
--section-pane-width: calc(var(--navigation-drawer-width, 22.5rem) + var(--section-pane-gap));
|
|
38
42
|
--section-pane-gap: 2rem;
|
|
39
43
|
|
|
40
|
-
/* The side panel of a block's fields and the section settings (
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
/* The side panel of a block's fields and the section settings (BlockDialog's standard side sheet from expanded, BottomSheet
|
|
45
|
+
`panel` in ui.tsx from large): M3's side sheet is 400dp (25rem) wide at most; where the window is narrower it is half
|
|
46
|
+
the space beside the start panel, the content column taking the other half (see below). */
|
|
47
|
+
--side-panel-width: min(25rem, calc((100vw - var(--column-start)) / 2));
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
/* In dark mode the score's number in the light colours, which are read on the dark, and Google's dark colours. */
|
|
@@ -324,7 +329,7 @@
|
|
|
324
329
|
/* The main drawer's header row (MainDrawerMenuButton in ui.tsx): the account row is a pill, shaped and shaded as a drawer
|
|
325
330
|
item is when pointed at. Its inline padding puts the avatar's centre over the items' icons (the avatar is wider than the
|
|
326
331
|
24px icon: half the difference less than the items' padding). The avatar has a 1px border inside its size.
|
|
327
|
-
The
|
|
332
|
+
The pill is as high as a drawer item (56px); the row around it is the top bar's height. */
|
|
328
333
|
.editor-main-drawer-header {
|
|
329
334
|
--list-item-padding-block: 0.375rem;
|
|
330
335
|
--list-item-two-line-min-height: var(--navigation-drawer-item-height, 3.5rem);
|
|
@@ -403,10 +408,25 @@
|
|
|
403
408
|
z-index: 30;
|
|
404
409
|
}
|
|
405
410
|
|
|
411
|
+
/* A FAB whose action runs or cannot be used now (FabAction.busy, .disabled): dimmed, and nothing to press. */
|
|
412
|
+
.editor-fab :is(.extended-fab, .fab):disabled {
|
|
413
|
+
opacity: 0.38;
|
|
414
|
+
pointer-events: none;
|
|
415
|
+
}
|
|
416
|
+
|
|
406
417
|
:root:has(.editor-fab) .snackbar-stack {
|
|
407
418
|
bottom: calc(6rem + env(safe-area-inset-bottom));
|
|
408
419
|
}
|
|
409
420
|
|
|
421
|
+
/* While a form's save bar is up (FormFrame.tsx) the FAB stands above it, so it does not cover Save; the messages above both. */
|
|
422
|
+
:root:has(.editor-save-bar) .editor-fab {
|
|
423
|
+
bottom: calc(6.5rem + env(safe-area-inset-bottom));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
:root:has(.editor-save-bar):has(.editor-fab) .snackbar-stack {
|
|
427
|
+
bottom: calc(11.5rem + env(safe-area-inset-bottom));
|
|
428
|
+
}
|
|
429
|
+
|
|
410
430
|
/* From medium the navigation rail stands at the start edge of the window (MainMenu.tsx), so the messages are in the middle of
|
|
411
431
|
the content next to it and the save bar starts where it ends; at expanded and wider, while the standard drawer is open
|
|
412
432
|
in the rail's place, they keep clear of the drawer instead. The widths are the theme's. The top bar and its tabs (Screen.tsx)
|
|
@@ -461,8 +481,8 @@
|
|
|
461
481
|
not less than nothing and not more than the space left between the panels less the column. Only when that space is
|
|
462
482
|
smaller than the column does it get narrower. (Measured against the window's width, 100vw, scrollbar and all.) Below
|
|
463
483
|
medium nothing stands beside the content and the column is centred by auto margins. While the side panel is open (see
|
|
464
|
-
the end of this block) the
|
|
465
|
-
|
|
484
|
+
the end of this block) the column is centred in the space between the start panel and the side panel by auto margins
|
|
485
|
+
(the content's end padding keeps it clear of the panel), still no wider than --column-width. */
|
|
466
486
|
:root {
|
|
467
487
|
--column-start: 0px;
|
|
468
488
|
--column-end: 0px;
|
|
@@ -504,25 +524,26 @@
|
|
|
504
524
|
right: var(--section-pane-width);
|
|
505
525
|
}
|
|
506
526
|
|
|
507
|
-
/* BottomSheet's `panel` (.editor-side-panel): the theme's modal side sheet at the window's end edge
|
|
508
|
-
the
|
|
527
|
+
/* BottomSheet's `panel` (.editor-side-panel): the theme's modal side sheet at the window's end edge, the full window
|
|
528
|
+
height like the standard one; its backdrop is clear. */
|
|
509
529
|
.editor-side-panel {
|
|
510
|
-
inset:
|
|
530
|
+
inset: 0 0 0 auto;
|
|
511
531
|
width: var(--side-panel-width);
|
|
512
|
-
height:
|
|
532
|
+
height: 100dvh;
|
|
513
533
|
}
|
|
514
534
|
.editor-side-panel:dir(rtl) {
|
|
515
|
-
inset:
|
|
535
|
+
inset: 0 auto 0 0;
|
|
516
536
|
}
|
|
517
537
|
.editor-side-panel::backdrop {
|
|
518
538
|
background: transparent;
|
|
519
539
|
}
|
|
520
540
|
}
|
|
521
541
|
|
|
522
|
-
/* The block dialog's standard side sheet (.editor-side-sheet: BlockDialog.tsx, not modal, from
|
|
523
|
-
end edge
|
|
524
|
-
|
|
525
|
-
|
|
542
|
+
/* The block dialog's standard side sheet (.editor-side-sheet: BlockDialog.tsx, not modal, from expanded) stands at the window's
|
|
543
|
+
end edge from the top of the window to its bottom (M3), with its divider the whole height. BlockDialog.tsx sets
|
|
544
|
+
[data-side-panel] on the root while it is open: the top bar (.editor-top-bar-slot, topBarSlot.tsx) ends at the sheet's
|
|
545
|
+
start edge, it takes the "on this page" area's place (that is hidden), and the content, which stays in use, keeps clear of it. */
|
|
546
|
+
@media (width >= theme(--breakpoint-expanded)) {
|
|
526
547
|
:root[data-side-panel] {
|
|
527
548
|
--column-end: var(--side-panel-width);
|
|
528
549
|
}
|
|
@@ -532,19 +553,25 @@
|
|
|
532
553
|
:root[data-side-panel] .editor-content {
|
|
533
554
|
padding-inline-end: var(--side-panel-width);
|
|
534
555
|
}
|
|
556
|
+
:root[data-side-panel] .editor-column {
|
|
557
|
+
margin-inline: auto;
|
|
558
|
+
}
|
|
535
559
|
:root[data-side-panel] .editor-save-bar {
|
|
536
560
|
right: var(--side-panel-width);
|
|
537
561
|
}
|
|
538
562
|
.editor-side-sheet {
|
|
539
563
|
position: fixed;
|
|
540
|
-
inset:
|
|
564
|
+
inset: 0 0 0 auto;
|
|
541
565
|
z-index: 20;
|
|
542
566
|
width: var(--side-panel-width);
|
|
543
567
|
max-width: none;
|
|
544
|
-
height:
|
|
568
|
+
height: 100dvh;
|
|
545
569
|
}
|
|
546
570
|
.editor-side-sheet:dir(rtl) {
|
|
547
|
-
inset:
|
|
571
|
+
inset: 0 auto 0 0;
|
|
572
|
+
}
|
|
573
|
+
:root[data-side-panel] .editor-top-bar-slot {
|
|
574
|
+
padding-inline-end: var(--side-panel-width);
|
|
548
575
|
}
|
|
549
576
|
}
|
|
550
577
|
|
|
@@ -632,9 +659,9 @@ html {
|
|
|
632
659
|
gap: 0.125rem;
|
|
633
660
|
border: 1px solid var(--line);
|
|
634
661
|
border-radius: 9999px;
|
|
635
|
-
background: var(--surface);
|
|
662
|
+
background: var(--editor-menu-surface);
|
|
636
663
|
padding: 0.25rem;
|
|
637
|
-
box-shadow: var(--shadow);
|
|
664
|
+
box-shadow: var(--editor-menu-shadow);
|
|
638
665
|
}
|
|
639
666
|
|
|
640
667
|
.editor-text .site-block-menu button {
|
|
@@ -656,6 +683,12 @@ html {
|
|
|
656
683
|
color: var(--accent);
|
|
657
684
|
}
|
|
658
685
|
|
|
686
|
+
/* The text's toolbar (MarkdownEditor.tsx, Toolbar in ui.tsx): the menu surface and shadow. */
|
|
687
|
+
.editor-tools {
|
|
688
|
+
--toolbar-standard-surface: var(--editor-menu-surface);
|
|
689
|
+
box-shadow: var(--editor-menu-shadow);
|
|
690
|
+
}
|
|
691
|
+
|
|
659
692
|
/* The icons over the selected text, on a phone at the bottom over the keyboard (selectionMenu in
|
|
660
693
|
MarkdownEditor.tsx). */
|
|
661
694
|
.editor-selection-menu {
|
|
@@ -665,9 +698,9 @@ html {
|
|
|
665
698
|
gap: 0.125rem;
|
|
666
699
|
border: 1px solid var(--line);
|
|
667
700
|
border-radius: 9999px;
|
|
668
|
-
background: var(--surface);
|
|
701
|
+
background: var(--editor-menu-surface);
|
|
669
702
|
padding: 0.25rem;
|
|
670
|
-
box-shadow: var(--shadow);
|
|
703
|
+
box-shadow: var(--editor-menu-shadow);
|
|
671
704
|
}
|
|
672
705
|
|
|
673
706
|
.editor-selection-menu[hidden],
|
|
@@ -845,9 +878,7 @@ html {
|
|
|
845
878
|
|
|
846
879
|
/* A text with blocks (a page, a fixed page's sections): the cards close together, with the free line
|
|
847
880
|
between two of them to write in: thin, until the cursor is in it (editor-gap-here, MarkdownEditor.tsx)
|
|
848
|
-
or the pointer over it, when it opens up and says so.
|
|
849
|
-
line break share the cell, so the line is as tall as the hint, however many lines it wraps to, and the cursor sits
|
|
850
|
-
at the start of the hint. */
|
|
881
|
+
or the pointer over it, when it opens up and says so. */
|
|
851
882
|
.editor-spaced .site-block {
|
|
852
883
|
margin-block: 0.25rem;
|
|
853
884
|
}
|
|
@@ -868,37 +899,27 @@ html {
|
|
|
868
899
|
}
|
|
869
900
|
|
|
870
901
|
.editor-spaced .ProseMirror:focus > p.editor-gap-here:has(> br:only-child) {
|
|
871
|
-
display: grid;
|
|
872
902
|
line-height: 1.5rem;
|
|
873
903
|
}
|
|
874
904
|
|
|
875
905
|
.editor-spaced .ProseMirror:focus > p.editor-gap-here:has(> br:only-child)::before {
|
|
876
906
|
content: var(--editor-gap-text);
|
|
877
|
-
|
|
907
|
+
position: absolute;
|
|
878
908
|
color: var(--muted);
|
|
879
909
|
pointer-events: none;
|
|
880
910
|
}
|
|
881
911
|
|
|
882
|
-
.editor-spaced .ProseMirror:focus > p.editor-gap-here > br:only-child {
|
|
883
|
-
grid-area: 1 / 1;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
912
|
@media (hover: hover) {
|
|
887
913
|
.editor-spaced .ProseMirror > p:has(> br:only-child):hover {
|
|
888
|
-
display: grid;
|
|
889
914
|
line-height: 1.5rem;
|
|
890
915
|
}
|
|
891
916
|
|
|
892
917
|
.editor-spaced .ProseMirror > p:has(> br:only-child):hover::before {
|
|
893
918
|
content: var(--editor-gap-text);
|
|
894
|
-
|
|
919
|
+
position: absolute;
|
|
895
920
|
color: var(--muted);
|
|
896
921
|
pointer-events: none;
|
|
897
922
|
}
|
|
898
|
-
|
|
899
|
-
.editor-spaced .ProseMirror > p:hover > br:only-child {
|
|
900
|
-
grid-area: 1 / 1;
|
|
901
|
-
}
|
|
902
923
|
}
|
|
903
924
|
|
|
904
925
|
/* While a sheet or a dialog is open (shown modally, showModal), the page under it does not scroll (only the sheet does).
|
package/src/examples.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// What "Dele" (#
|
|
1
|
+
// What "Dele" (#appearance/parts: Parts.tsx, BlockParts.tsx) shows the parts and blocks with: made-up pages, posts, tags,
|
|
2
2
|
// testimonials, the lists' made-up rows (`examples` of a list), cookies and contact details in the editor's language,
|
|
3
3
|
// the apps' made-up settings (AppSettings.example), and a plain picture, never what the site has, so the parts look the
|
|
4
4
|
// same on every site.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Tests for src/fabScreens.ts: the start screen's FAB menu and the create actions of a list. Run: node --test src/fabScreens.test.mjs
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
import { START_FAB, listFab } from './fabScreens.ts';
|
|
5
|
+
|
|
6
|
+
const standard = { value: 'standard', label: 'Standard' };
|
|
7
|
+
const mailchimp = { value: 'mailchimp', label: 'Mailchimp' };
|
|
8
|
+
|
|
9
|
+
test('the start screen makes a page or a blog post', () => {
|
|
10
|
+
assert.deepEqual(START_FAB, ['pages', 'blog']);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('a list without kinds, or with one, has one action named after its create label', () => {
|
|
14
|
+
assert.deepEqual(listFab({ id: 'pages', create: 'New page' }, []), [{ label: 'New page', href: '#new/pages' }]);
|
|
15
|
+
assert.deepEqual(listFab({ id: 'popups', create: 'New promotion' }, [standard]), [{ label: 'New promotion', href: '#new/popups' }]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('a list of several kinds has one action per kind', () => {
|
|
19
|
+
assert.deepEqual(listFab({ id: 'popups', create: 'New promotion' }, [standard, mailchimp]), [
|
|
20
|
+
{ label: 'Standard', href: '#new/popups/standard' },
|
|
21
|
+
{ label: 'Mailchimp', href: '#new/popups/mailchimp' },
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('a list that cannot make entries has no action', () => {
|
|
26
|
+
assert.deepEqual(listFab({ id: 'orders' }, []), []);
|
|
27
|
+
assert.deepEqual(listFab({ id: 'orders' }, [standard, mailchimp]), []);
|
|
28
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// What the create FAB offers: the start screen's menu (START_FAB, the collection ids) and a list's own (listFab: one
|
|
2
|
+
// action, or one per kind). Pure; the icons and the FAB itself are added by StartFab.tsx and ListFab.tsx.
|
|
3
|
+
|
|
4
|
+
/** The collections a new entry can be made in from the start screen, in this order: pages and blog posts. */
|
|
5
|
+
export const START_FAB = ['pages', 'blog'];
|
|
6
|
+
|
|
7
|
+
/** A list's create actions, as a label and where it goes: with several kinds one per kind (`#new/<id>/<kind>`), else one
|
|
8
|
+
* named `create` (`#new/<id>`); none for a list that cannot make entries (no `create`). */
|
|
9
|
+
export function listFab(
|
|
10
|
+
collection: { id: string; create?: string },
|
|
11
|
+
kinds: { value: string; label: string }[],
|
|
12
|
+
): { label: string; href: string }[] {
|
|
13
|
+
if (!collection.create) return [];
|
|
14
|
+
if (kinds.length > 1) return kinds.map((k) => ({ label: k.label, href: `#new/${collection.id}/${k.value}` }));
|
|
15
|
+
return [{ label: collection.create, href: `#new/${collection.id}` }];
|
|
16
|
+
}
|
|
@@ -106,8 +106,8 @@ export const library: Messages['library'] = {
|
|
|
106
106
|
statusShown: { all: 'Alle', published: 'Udgivet', draft: 'Kladde', unlisted: 'Skjult', scheduled: 'Planlagt' },
|
|
107
107
|
sort: 'Sortér',
|
|
108
108
|
sorts: {
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
newest: 'Nyeste først',
|
|
110
|
+
oldest: 'Ældste først',
|
|
111
111
|
az: 'A–Å',
|
|
112
112
|
za: 'Å–A',
|
|
113
113
|
savedNewest: 'Senest ændret først',
|
package/src/i18n/da-DK/text.ts
CHANGED
|
@@ -35,5 +35,5 @@ export const text: Messages['text'] = {
|
|
|
35
35
|
hiddenOnPage: 'Skjult på siden',
|
|
36
36
|
showOnPage: 'Vis på siden',
|
|
37
37
|
hideOnPage: 'Skjul på siden',
|
|
38
|
-
typeHere: '
|
|
38
|
+
typeHere: 'Skriv tekst her …',
|
|
39
39
|
};
|
package/src/i18n/en-US/apps.ts
CHANGED
|
@@ -33,7 +33,7 @@ export const apps = {
|
|
|
33
33
|
cookiesAsk: 'Set only once the visitor has said yes in the cookie banner',
|
|
34
34
|
noCookies: 'Sets no cookies',
|
|
35
35
|
developer: 'Developer',
|
|
36
|
-
/** On a developer's page (#apps/
|
|
36
|
+
/** On a developer's page (#apps/developer/<name>): over their apps, and the number of them by their row on an app's page. */
|
|
37
37
|
developerApps: 'Apps',
|
|
38
38
|
appCount: (n: number) => (n === 1 ? '1 app' : `${n} apps`),
|
|
39
39
|
/** At the address of a developer no app has. */
|
package/src/i18n/en-US/common.ts
CHANGED
|
@@ -107,23 +107,23 @@ export const common = {
|
|
|
107
107
|
lists: 'Lists',
|
|
108
108
|
blocks: 'Block templates',
|
|
109
109
|
media: 'Files',
|
|
110
|
-
/** How the site looks (#
|
|
110
|
+
/** How the site looks (#appearance, Library.tsx Theme): the theme, the colours, the logo and icon, and the site's own
|
|
111
111
|
* parts there (appearance in packages/editor/defaults/editor.ts: "Standardsider"). */
|
|
112
112
|
appearance: 'Appearance',
|
|
113
|
-
/** The themes installed (#
|
|
113
|
+
/** The themes installed (#appearance/themes, Library.tsx Themes), the first row under "Udseende". */
|
|
114
114
|
themes: 'Themes',
|
|
115
|
-
/** The icon packs installed, to choose the one the site draws its icons with (#
|
|
115
|
+
/** The icon packs installed, to choose the one the site draws its icons with (#appearance/icon-packs, IconPacks). */
|
|
116
116
|
iconPacks: 'Icon packs',
|
|
117
|
-
/** How the site moves, Material 3's motion schemes (#
|
|
117
|
+
/** How the site moves, Material 3's motion schemes (#appearance/motion, MotionSchemes in Library.tsx). */
|
|
118
118
|
motion: 'Motion',
|
|
119
|
-
/** The block drawn in a column beside the text of the pages (#
|
|
119
|
+
/** The block drawn in a column beside the text of the pages (#appearance/side-column, SideColumn.tsx). */
|
|
120
120
|
sideColumn: 'Side column',
|
|
121
121
|
colors: 'Colors',
|
|
122
122
|
brand: 'Logo and icon',
|
|
123
123
|
blockSettings: 'Block settings',
|
|
124
124
|
/** The apps that come with the site (AppList.tsx, #apps). */
|
|
125
125
|
apps: 'Apps',
|
|
126
|
-
/** The parts the editor and the site are built of (#
|
|
126
|
+
/** The parts the editor and the site are built of (#appearance/parts: Parts.tsx, BlockParts.tsx). */
|
|
127
127
|
editorParts: 'Parts',
|
|
128
128
|
/** Under Indstillinger: the redirects and the site's own parts (#seo, Library.tsx Seo). */
|
|
129
129
|
seo: 'Search engine optimization',
|
|
@@ -131,9 +131,9 @@ export const common = {
|
|
|
131
131
|
account: 'Account',
|
|
132
132
|
/** The last section of the main drawer, and the first part of the path to a setting (placeOf in site.ts). */
|
|
133
133
|
settings: 'Settings',
|
|
134
|
-
/** Under Indstillinger: the faster editor (#
|
|
134
|
+
/** Under Indstillinger: the faster editor (#advanced, Advanced.tsx). */
|
|
135
135
|
advanced: 'Advanced',
|
|
136
|
-
/** Under Indstillinger: the licences of what the site and the editor are made with (#
|
|
136
|
+
/** Under Indstillinger: the licences of what the site and the editor are made with (#licenses, Licenses.tsx). */
|
|
137
137
|
licenses: 'Licenses',
|
|
138
138
|
/** Under Indstillinger: the CMS's version and its updates (#morpheme). */
|
|
139
139
|
morpheme: 'Morpheme',
|
|
@@ -10,7 +10,7 @@ export const library = {
|
|
|
10
10
|
createNew: 'Create',
|
|
11
11
|
githubAccount: (login: string) => `GitHub account: ${login}`,
|
|
12
12
|
logOut: 'Log out',
|
|
13
|
-
/** The bell in the top bar and its screen (#
|
|
13
|
+
/** The bell in the top bar and its screen (#notifications, Notifications.tsx): the messages shown at the bottom. */
|
|
14
14
|
notifications: 'Notifications',
|
|
15
15
|
/** The bell's name while some are not seen yet. */
|
|
16
16
|
notificationsNew: (count: number) => `Notifications, ${count} new`,
|
|
@@ -144,8 +144,8 @@ export const library = {
|
|
|
144
144
|
statusShown: { all: 'All', published: 'Published', draft: 'Draft', unlisted: 'Hidden', scheduled: 'Scheduled' },
|
|
145
145
|
sort: 'Sort',
|
|
146
146
|
sorts: {
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
newest: 'Newest first',
|
|
148
|
+
oldest: 'Oldest first',
|
|
149
149
|
az: 'A–Z',
|
|
150
150
|
za: 'Z–A',
|
|
151
151
|
/** The pages, by when they were last saved. */
|
|
@@ -153,7 +153,7 @@ export const library = {
|
|
|
153
153
|
savedOldest: 'Least recently changed first',
|
|
154
154
|
/** A list of kinds (Promovering), under a heading for each kind. */
|
|
155
155
|
type: 'By type',
|
|
156
|
-
/** "Standardblokke" (#
|
|
156
|
+
/** "Standardblokke" (#blocks), in the groups by where the blocks come from. */
|
|
157
157
|
group: 'By group',
|
|
158
158
|
},
|
|
159
159
|
/** The funnel over a list of what is shown on pages (Promovering), with its filters in one sheet. */
|
|
@@ -172,7 +172,7 @@ export const library = {
|
|
|
172
172
|
/** The filter by whether an entry is turned on, and its first choice. */
|
|
173
173
|
onOffFilter: 'Status',
|
|
174
174
|
allOnOff: 'All',
|
|
175
|
-
/** The filters over "Standardblokke" (#
|
|
175
|
+
/** The filters over "Standardblokke" (#blocks): by the group (where the block comes from) and by whether
|
|
176
176
|
* the block is on a page, each with its first choice. */
|
|
177
177
|
groupFilter: 'Group',
|
|
178
178
|
allGroups: 'All groups',
|
package/src/i18n/en-US/parts.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "Dele" (#
|
|
1
|
+
// "Dele" (#appearance/parts): the editor's parts (Parts.tsx), what each is for and the texts in the examples, and the standard
|
|
2
2
|
// blocks and the apps' (BlockParts.tsx), all with the made-up site of examples.ts.
|
|
3
3
|
export const parts = {
|
|
4
4
|
intro: (file: string) =>
|
|
@@ -253,7 +253,7 @@ export const parts = {
|
|
|
253
253
|
/** What a kind of entry an app adds (a pop-up) is drawn with. */
|
|
254
254
|
entry: { name: 'An example', heading: 'A heading', text: 'A short text that tells a little more.' },
|
|
255
255
|
},
|
|
256
|
-
/** "Dele" › "Værdier" (#
|
|
256
|
+
/** "Dele" › "Værdier" (#appearance/parts/values, Tokens.tsx): the theme's design tokens, each with what it is used for. */
|
|
257
257
|
tokens: {
|
|
258
258
|
intro: 'The theme’s values for corners, shadows, typography, and colors, as the theme sets them.',
|
|
259
259
|
groups: { shape: 'Corners', elevation: 'Shadows', type: 'Typography', color: 'Colors' },
|
|
@@ -355,7 +355,7 @@ export const parts = {
|
|
|
355
355
|
/** A token the theme does not set, with the one it gets instead. */
|
|
356
356
|
from: (token: string, value: string, from: string) => `${token}: ${value} (as ${from})`,
|
|
357
357
|
},
|
|
358
|
-
/** "Dele" (#
|
|
358
|
+
/** "Dele" (#appearance/parts) and its screens: the editor's parts, the theme's UI components, the blocks (the standard
|
|
359
359
|
* ones and the apps') and the lists of kinds ("Promotion"). */
|
|
360
360
|
screens: {
|
|
361
361
|
editor: 'Editor',
|
package/src/i18n/en-US/text.ts
CHANGED
|
@@ -43,5 +43,5 @@ export const text = {
|
|
|
43
43
|
showOnPage: 'Show on the page',
|
|
44
44
|
hideOnPage: 'Hide on the page',
|
|
45
45
|
/** Shown in an empty line of the text, under the pointer or the cursor. */
|
|
46
|
-
typeHere: '
|
|
46
|
+
typeHere: 'Write text here…',
|
|
47
47
|
};
|
package/src/i18n/index.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// (siteText, @site/texts). The blocks' drawings and the commits (siteT) stay in the site's
|
|
10
10
|
// language, so the site looks as it is, and the history is in one language, whoever saved.
|
|
11
11
|
// A value the editor writes into content (a default text) is in the site's language too (inSiteLanguage).
|
|
12
|
+
// The standard parts are written in English; their Danish is in defaults/i18n/da-DK/, the default of @site/texts.
|
|
12
13
|
// A language is a folder with the texts by the part of the editor they are in (en-US/index.ts). English is the
|
|
13
14
|
// reference: its texts are written first (Messages is its type), and every other language translates them; a new
|
|
14
15
|
// one is a copy of da-DK with its texts translated from en-US, added to `messages`. TypeScript says when a
|
|
@@ -82,7 +83,7 @@ export const t: Messages = messages[locale];
|
|
|
82
83
|
export const siteT: Messages = messages[siteLocale];
|
|
83
84
|
|
|
84
85
|
/** The language the package's standard parts (defaults/) are written in, whatever the site's language is. */
|
|
85
|
-
export const DEFAULTS_LOCALE: Locale = '
|
|
86
|
+
export const DEFAULTS_LOCALE: Locale = 'en-US';
|
|
86
87
|
|
|
87
88
|
/** Whether the site's own texts (written in the site's language) or the package's standard parts (written in
|
|
88
89
|
* DEFAULTS_LOCALE) may be shown in another language than they are written in. */
|
|
@@ -100,9 +101,7 @@ export const appTexts: Record<string, Texts> = Object.fromEntries(
|
|
|
100
101
|
}),
|
|
101
102
|
);
|
|
102
103
|
|
|
103
|
-
const own
|
|
104
|
-
? Object.assign({}, ...Object.values(appTexts).map((a) => a[locale]), (texts as Texts)[locale])
|
|
105
|
-
: {};
|
|
104
|
+
const own = Object.assign({}, ...Object.values(appTexts).map((a) => a[locale]), (texts as Texts)[locale]);
|
|
106
105
|
|
|
107
106
|
/** One of the site's own texts (src/site/, src/themes/<theme>/blocks/, src/apps/), written in the site's language, in the editor's:
|
|
108
107
|
* its translation in @site/texts or in a block's texts.ts, else as it is written. `values` fill in
|
|
@@ -111,10 +110,7 @@ export function siteText(text: string, values?: Record<string, string | number>)
|
|
|
111
110
|
return translate(own, text, values);
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
const inSite
|
|
115
|
-
siteLocale === DEFAULTS_LOCALE
|
|
116
|
-
? {}
|
|
117
|
-
: Object.assign({}, ...Object.values(appTexts).map((a) => a[siteLocale]), (texts as Texts)[siteLocale]);
|
|
113
|
+
const inSite = Object.assign({}, ...Object.values(appTexts).map((a) => a[siteLocale]), (texts as Texts)[siteLocale]);
|
|
118
114
|
|
|
119
115
|
/** A text of the package's standard parts (written in DEFAULTS_LOCALE) in the site's language, for a value that the
|
|
120
116
|
* editor writes into content (a default button text): its translation in @site/texts or in a block's texts.ts, else
|
package/src/images.ts
CHANGED
|
@@ -98,7 +98,7 @@ export async function prepareImage(file: File): Promise<Upload> {
|
|
|
98
98
|
const now = new Date();
|
|
99
99
|
const folder = `${config.folders.media}/${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
100
100
|
// A short random ending, so two pictures called IMG_1234 do not get the same name.
|
|
101
|
-
const name = `${slugify(file.name.replace(/\.[^.]*$/, '')) || '
|
|
101
|
+
const name = `${slugify(file.name.replace(/\.[^.]*$/, '')) || 'picture'}-${Math.random().toString(36).slice(2, 6)}`;
|
|
102
102
|
const ext = blob.type === 'image/webp' ? 'webp' : 'jpg';
|
|
103
103
|
const upload = {
|
|
104
104
|
url: URL.createObjectURL(blob),
|
|
@@ -119,7 +119,7 @@ export const FILE_MAX_MB = 10;
|
|
|
119
119
|
export async function prepareFile(file: File, taken: string[]): Promise<Upload> {
|
|
120
120
|
if (file.size > FILE_MAX_MB * 1024 * 1024) throw new Error(t.media.tooBig(FILE_MAX_MB));
|
|
121
121
|
const folder = config.folders.files;
|
|
122
|
-
const stem = slugify(file.name.replace(/\.[^.]*$/, '')) || '
|
|
122
|
+
const stem = slugify(file.name.replace(/\.[^.]*$/, '')) || 'file';
|
|
123
123
|
const ending = pickedEnding(file);
|
|
124
124
|
let path = `${folder}/${stem}.${ending}`;
|
|
125
125
|
for (let n = 2; taken.includes(path); n++) path = `${folder}/${stem}-${n}.${ending}`;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Tests for src/legacyMove.ts: when a legacy store copy counts as installed, and the one list of changes that moves it
|
|
2
|
+
// to its permanent folder. Run: node --test src/legacyMove.test.mjs
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { test } from 'node:test';
|
|
5
|
+
import { appliedTo, installedHere, legacyOf, mergeChanges, moveChanges } from './legacyMove.ts';
|
|
6
|
+
|
|
7
|
+
const PATHS = {
|
|
8
|
+
appsFolder: 'src/apps',
|
|
9
|
+
themesFolder: 'src/themes',
|
|
10
|
+
installed: 'src/data/apps.json',
|
|
11
|
+
settings: 'src/data/apps',
|
|
12
|
+
theme: 'src/data/theme.json',
|
|
13
|
+
icons: 'src/data/icons.json',
|
|
14
|
+
blockSettings: 'src/data/block-settings.json',
|
|
15
|
+
popups: 'src/data/popups',
|
|
16
|
+
cookieList: 'src/data/cookie-list',
|
|
17
|
+
};
|
|
18
|
+
const VENUS = { id: 'theme_fa395ad84690acbd', legacy: 'venus' };
|
|
19
|
+
const MOBILEPAY = { id: 'app_ae0b5e17636668bc', legacy: 'mobilepay' };
|
|
20
|
+
const file = (path, text, oid = `oid-${path}`) => ({ path, oid, text });
|
|
21
|
+
const json = (x) => `${JSON.stringify(x, null, 2)}\n`;
|
|
22
|
+
const next = { files: [{ path: 'theme.json', content: '{}' }], record: { version: '2.0.0', ref: '2.0.0', files: [] } };
|
|
23
|
+
const find = (changes, path) => changes.find((c) => c.path === path);
|
|
24
|
+
|
|
25
|
+
test('a legacy folder with a store.json is installed', () => {
|
|
26
|
+
assert.equal(legacyOf(VENUS, [{ id: 'venus', store: { version: '1.2.0' } }]), 'venus');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('a legacy folder without a store.json is the site own', () => {
|
|
30
|
+
assert.equal(legacyOf(VENUS, [{ id: 'venus' }]), null);
|
|
31
|
+
assert.equal(legacyOf(VENUS, []), null);
|
|
32
|
+
assert.equal(legacyOf({ id: VENUS.id }, [{ id: 'venus', store: { version: '1.2.0' } }]), null);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('moving a theme writes the new folder, deletes the old files and rewrites theme.json and icons.json', () => {
|
|
36
|
+
const old = [file('src/themes/venus/theme.json', '{"a":1}'), file('src/themes/venus/store.json', '{}')];
|
|
37
|
+
const site = [file(PATHS.theme, json({ theme: 'venus' })), file(PATHS.icons, json({ pack: 'venus/x' }))];
|
|
38
|
+
const changes = moveChanges(VENUS, 'venus', next, old, site, PATHS);
|
|
39
|
+
assert.equal(find(changes, `src/themes/${VENUS.id}/theme.json`).content, '{}');
|
|
40
|
+
assert.ok(find(changes, `src/themes/${VENUS.id}/store.json`));
|
|
41
|
+
for (const f of old) assert.deepEqual(find(changes, f.path), { path: f.path, content: null, oid: f.oid });
|
|
42
|
+
assert.deepEqual(JSON.parse(find(changes, PATHS.theme).content), { theme: VENUS.id });
|
|
43
|
+
assert.deepEqual(JSON.parse(find(changes, PATHS.icons).content), { pack: `${VENUS.id}/x` });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('a system icon pack and another theme stay as they are', () => {
|
|
47
|
+
const site = [file(PATHS.theme, json({ theme: 'saturn' })), file(PATHS.icons, json({ pack: 'phosphor' }))];
|
|
48
|
+
const changes = moveChanges(VENUS, 'venus', next, [], site, PATHS);
|
|
49
|
+
assert.equal(find(changes, PATHS.theme), undefined);
|
|
50
|
+
assert.equal(find(changes, PATHS.icons), undefined);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('moving an app renames its settings file and keeps the sorted list', () => {
|
|
54
|
+
const settings = '{ "key": "kept as it is" }\n';
|
|
55
|
+
const site = [
|
|
56
|
+
file(PATHS.installed, json({ installed: ['calendly', 'mobilepay', 'zeta'] })),
|
|
57
|
+
file('src/data/apps/mobilepay.json', settings),
|
|
58
|
+
];
|
|
59
|
+
const changes = moveChanges(MOBILEPAY, 'mobilepay', { files: [], record: next.record }, [], site, PATHS);
|
|
60
|
+
assert.deepEqual(find(changes, 'src/data/apps/mobilepay.json'), {
|
|
61
|
+
path: 'src/data/apps/mobilepay.json',
|
|
62
|
+
content: null,
|
|
63
|
+
oid: 'oid-src/data/apps/mobilepay.json',
|
|
64
|
+
});
|
|
65
|
+
assert.deepEqual(find(changes, `src/data/apps/${MOBILEPAY.id}.json`), {
|
|
66
|
+
path: `src/data/apps/${MOBILEPAY.id}.json`,
|
|
67
|
+
content: settings,
|
|
68
|
+
oid: null,
|
|
69
|
+
});
|
|
70
|
+
assert.deepEqual(JSON.parse(find(changes, PATHS.installed).content).installed, ['calendly', MOBILEPAY.id, 'zeta'].sort());
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('moving an app rewrites block-settings, popups and the cookie list, and only the legacy values', () => {
|
|
74
|
+
const site = [
|
|
75
|
+
file(PATHS.blockSettings, json({ off: ['popups/mobilepay', 'popups/other', 'text', 'x/mobilepay-2'] })),
|
|
76
|
+
file('src/data/popups/a.json', json({ type: 'mobilepay', title: 'A' })),
|
|
77
|
+
file('src/data/popups/b.json', json({ type: 'other' })),
|
|
78
|
+
file('src/data/cookie-list/c.json', json({ name: 'mobilepay', app: 'mobilepay' })),
|
|
79
|
+
file('src/data/cookie-list/d.json', json({ app: 'other' })),
|
|
80
|
+
];
|
|
81
|
+
const changes = moveChanges(MOBILEPAY, 'mobilepay', { files: [], record: next.record }, [], site, PATHS);
|
|
82
|
+
assert.deepEqual(JSON.parse(find(changes, PATHS.blockSettings).content).off, [
|
|
83
|
+
`popups/${MOBILEPAY.id}`,
|
|
84
|
+
'popups/other',
|
|
85
|
+
'text',
|
|
86
|
+
'x/mobilepay-2',
|
|
87
|
+
]);
|
|
88
|
+
assert.equal(JSON.parse(find(changes, 'src/data/popups/a.json').content).type, MOBILEPAY.id);
|
|
89
|
+
assert.equal(JSON.parse(find(changes, 'src/data/popups/a.json').content).title, 'A');
|
|
90
|
+
assert.equal(find(changes, 'src/data/popups/b.json'), undefined);
|
|
91
|
+
const cookie = JSON.parse(find(changes, 'src/data/cookie-list/c.json').content);
|
|
92
|
+
assert.deepEqual(cookie, { name: 'mobilepay', app: MOBILEPAY.id });
|
|
93
|
+
assert.equal(find(changes, 'src/data/cookie-list/d.json'), undefined);
|
|
94
|
+
assert.equal(find(changes, PATHS.theme), undefined);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('an item without a legacy gets the normal update and no delete', () => {
|
|
98
|
+
const old = [file(`src/themes/${VENUS.id}/old.json`, '{}'), file(`src/themes/${VENUS.id}/theme.json`, '{}')];
|
|
99
|
+
const changes = moveChanges({ id: VENUS.id }, null, next, old, [file(PATHS.theme, json({ theme: 'venus' }))], PATHS);
|
|
100
|
+
assert.deepEqual(find(changes, `src/themes/${VENUS.id}/old.json`), {
|
|
101
|
+
path: `src/themes/${VENUS.id}/old.json`,
|
|
102
|
+
content: null,
|
|
103
|
+
oid: 'oid-src/themes/theme_fa395ad84690acbd/old.json',
|
|
104
|
+
});
|
|
105
|
+
assert.equal(find(changes, PATHS.theme), undefined);
|
|
106
|
+
assert.equal(changes.filter((c) => c.content === null).length, 1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('a move with a release that adds a cookie changes each cookie-list file once', () => {
|
|
110
|
+
const old = file('src/data/cookie-list/a.json', json({ name: 'a', app: 'mobilepay' }), 'oid-a');
|
|
111
|
+
const site = [old];
|
|
112
|
+
const moved = moveChanges(MOBILEPAY, 'mobilepay', { files: [], record: next.record }, [], site, PATHS);
|
|
113
|
+
// What the editor's cookieChanges does for the new release on the site after the move: the new cookie is a new file.
|
|
114
|
+
const after = appliedTo(site, moved);
|
|
115
|
+
assert.equal(JSON.parse(after[0].text).app, MOBILEPAY.id);
|
|
116
|
+
const cookies = [
|
|
117
|
+
{ path: 'src/data/cookie-list/b.json', content: json({ name: 'b', app: MOBILEPAY.id }), oid: null },
|
|
118
|
+
{ path: 'src/data/cookie-list/a.json', content: null, oid: after[0].oid },
|
|
119
|
+
];
|
|
120
|
+
const all = mergeChanges(moved, cookies);
|
|
121
|
+
assert.equal(new Set(all.map((c) => c.path)).size, all.length);
|
|
122
|
+
assert.deepEqual(find(all, 'src/data/cookie-list/a.json'), { path: 'src/data/cookie-list/a.json', content: null, oid: 'oid-a' });
|
|
123
|
+
assert.equal(JSON.parse(find(all, 'src/data/cookie-list/b.json').content).app, MOBILEPAY.id);
|
|
124
|
+
// A cookie that stays: only the rewrite, once, under the new id.
|
|
125
|
+
const kept = mergeChanges(moved, []);
|
|
126
|
+
assert.equal(kept.filter((c) => c.path === old.path).length, 1);
|
|
127
|
+
assert.equal(JSON.parse(find(kept, old.path).content).app, MOBILEPAY.id);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('installedHere: counts a new id, or a legacy copy from the store, as installed', () => {
|
|
131
|
+
const entry = { id: 'app_ae0b5e17636668bc', legacy: 'mobilepay' };
|
|
132
|
+
assert.equal(installedHere(entry, ['app_ae0b5e17636668bc'], []), true);
|
|
133
|
+
assert.equal(installedHere(entry, ['mobilepay'], [{ id: 'mobilepay', store: {} }]), true);
|
|
134
|
+
assert.equal(installedHere(entry, ['mobilepay'], [{ id: 'mobilepay' }]), false);
|
|
135
|
+
assert.equal(installedHere(entry, [], [{ id: 'mobilepay', store: {} }]), false);
|
|
136
|
+
assert.equal(installedHere({ id: 'app_1' }, ['other'], [{ id: 'other', store: {} }]), false);
|
|
137
|
+
});
|