@morphemecms/cms 0.14.0 → 0.14.2-next-20261107122546

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/lib/colors.ts +4 -0
  3. package/lib/releaseNotes.d.mts +26 -11
  4. package/lib/releaseNotes.mjs +57 -14
  5. package/lib/releaseNotes.test.mjs +94 -10
  6. package/package.json +1 -1
  7. package/pages/editor/index.astro +8 -5
  8. package/release-notes/i18n/da-DK/0.14.0.json +2 -2
  9. package/release-notes/i18n/da-DK/0.14.1.json +15 -0
  10. package/release-notes/i18n/da-DK/pending/lists-follow-md3.json +5 -0
  11. package/release-notes/i18n/da-DK/pending/update-card-whats-new.json +6 -0
  12. package/release-notes/i18n/da-DK/pending/update-card-whats-new.webp +0 -0
  13. package/release-notes/i18n/en-US/0.14.0.json +2 -2
  14. package/release-notes/i18n/en-US/0.14.1.json +15 -0
  15. package/release-notes/i18n/en-US/pending/lists-follow-md3.json +5 -0
  16. package/release-notes/i18n/en-US/pending/update-card-whats-new.json +6 -0
  17. package/release-notes/i18n/en-US/pending/update-card-whats-new.webp +0 -0
  18. package/scripts/release-notes.mjs +14 -3
  19. package/src/CmsPage.tsx +6 -6
  20. package/src/MainMenu.tsx +9 -14
  21. package/src/ReleaseNotes.tsx +25 -26
  22. package/src/UpdatesNotice.tsx +99 -15
  23. package/src/bundledNotes.ts +11 -0
  24. package/src/cmsView.d.mts +0 -1
  25. package/src/cmsView.mjs +2 -4
  26. package/src/cmsView.test.mjs +0 -6
  27. package/src/editor.css +11 -0
  28. package/src/i18n/da-DK/library.ts +4 -2
  29. package/src/i18n/en-US/library.ts +9 -5
  30. package/src/startView.d.mts +3 -2
  31. package/src/startView.mjs +4 -4
  32. package/src/startView.test.mjs +4 -4
  33. package/src/theme.ts +2 -2
  34. package/starter/package.json +1 -1
  35. package/theme/styles/global.css +10 -8
  36. package/theme/styles/ui.css +5 -1
  37. package/worker/cms-update.js +4 -7
  38. package/src/ReleaseNoteCard.tsx +0 -17
  39. package/src/UpdateLink.tsx +0 -21
  40. package/src/levelStep.d.mts +0 -10
  41. package/src/levelStep.mjs +0 -66
  42. package/src/levelStep.test.mjs +0 -119
package/src/levelStep.mjs DELETED
@@ -1,66 +0,0 @@
1
- // The second level of the editor's modal main menu (MainMenu.tsx) as a step in the browser's history: while a section's
2
- // list is shown in the modal drawer, the phone's back gesture (and the browser's back button) goes to the first level
3
- // instead of to the screen before. The step has the same address and the same place as the screen's (history.state,
4
- // nav.ts), so going back from it fires no hashchange and nav.ts never sees it. Leaving the level another way (the back
5
- // row, Escape, the drawer closed) takes the step out again; a link in the drawer is followed once the step is out, so the
6
- // history never keeps an empty step. Exposes levelStep. A window (the browser's; a fake one in the tests).
7
-
8
- /**
9
- * Watches the window's history for the level's step. `set(active, toFirst)`: whether the level is shown (a step is added
10
- * while it is), and what the back gesture does on it (shows the first level). `stop()` takes the step out and stops.
11
- * @param {Pick<Window, 'history' | 'location' | 'addEventListener' | 'removeEventListener' | 'document'>} win
12
- */
13
- export function levelStep(win) {
14
- let pushed = false;
15
- /** @type {string | null} A link's address, followed once the step is out. */
16
- let pending = null;
17
- let toFirst = () => {};
18
-
19
- const leave = () => {
20
- if (!pushed) return;
21
- pushed = false;
22
- win.history.back();
23
- };
24
- const onPop = () => {
25
- // Back from the step: the first level, on the same screen.
26
- if (pushed) {
27
- pushed = false;
28
- toFirst();
29
- return;
30
- }
31
- // The step taken out for a link: now the link.
32
- if (pending !== null) {
33
- const href = pending;
34
- pending = null;
35
- win.location.hash = href;
36
- }
37
- };
38
- /** @param {MouseEvent} e */
39
- const onClick = (e) => {
40
- if (!pushed || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
41
- const target = /** @type {Element | null} */ (e.target);
42
- const a = target?.closest?.('a[href^="#"]');
43
- if (!a || a.getAttribute('target')) return;
44
- e.preventDefault();
45
- pending = a.getAttribute('href');
46
- leave();
47
- };
48
- win.addEventListener('popstate', onPop);
49
- win.document.addEventListener('click', onClick, true);
50
-
51
- return {
52
- /** @param {boolean} active @param {() => void} back */
53
- set(active, back) {
54
- toFirst = back;
55
- if (active && !pushed) {
56
- win.history.pushState({ ...(win.history.state ?? {}), level: true }, '');
57
- pushed = true;
58
- } else if (!active) leave();
59
- },
60
- stop() {
61
- leave();
62
- win.removeEventListener('popstate', onPop);
63
- win.document.removeEventListener('click', onClick, true);
64
- },
65
- };
66
- }
@@ -1,119 +0,0 @@
1
- // Tests levelStep.mjs: the modal main menu's second level as a step in the history, with a fake window whose history
2
- // fires popstate as the browser's does (a step back on the same address: no hashchange).
3
- // Run: node --test src/levelStep.test.mjs
4
- import { test } from 'node:test';
5
- import assert from 'node:assert/strict';
6
- import { levelStep } from './levelStep.mjs';
7
-
8
- function fakeWindow(hash = '#list/pages') {
9
- const listeners = { popstate: new Set(), click: new Set() };
10
- const entries = [{ state: { at: 3, prev: '' }, hash }];
11
- let index = 0;
12
- const win = {
13
- location: {
14
- get hash() {
15
- return entries[index].hash;
16
- },
17
- set hash(next) {
18
- entries.splice(index + 1);
19
- entries.push({ state: null, hash: next });
20
- index += 1;
21
- },
22
- },
23
- history: {
24
- get state() {
25
- return entries[index].state;
26
- },
27
- pushState(state) {
28
- entries.splice(index + 1);
29
- entries.push({ state, hash: entries[index].hash });
30
- index += 1;
31
- },
32
- back() {
33
- index -= 1;
34
- for (const l of listeners.popstate) l();
35
- },
36
- },
37
- addEventListener: (type, l) => listeners[type].add(l),
38
- removeEventListener: (type, l) => listeners[type].delete(l),
39
- document: {
40
- addEventListener: (type, l) => listeners[type].add(l),
41
- removeEventListener: (type, l) => listeners[type].delete(l),
42
- },
43
- };
44
- const click = (href, extra = {}) => {
45
- const a = { getAttribute: (name) => (name === 'href' ? href : (extra[name] ?? null)) };
46
- const e = {
47
- button: 0,
48
- defaultPrevented: false,
49
- target: { closest: () => a },
50
- preventDefault() {
51
- this.defaultPrevented = true;
52
- },
53
- };
54
- for (const l of listeners.click) l(e);
55
- return e;
56
- };
57
- return { win, entries: () => entries.slice(0, index + 1), click, listeners };
58
- }
59
-
60
- test('the level shown adds a step with the same address and place', () => {
61
- const { win, entries } = fakeWindow();
62
- levelStep(win).set(true, () => {});
63
- assert.equal(entries().length, 2);
64
- assert.equal(entries()[1].hash, '#list/pages');
65
- assert.deepEqual(entries()[1].state, { at: 3, prev: '', level: true });
66
- });
67
-
68
- test('the back gesture on the level shows the first level and stays on the screen', () => {
69
- const { win, entries } = fakeWindow();
70
- let first = 0;
71
- levelStep(win).set(true, () => (first += 1));
72
- win.history.back();
73
- assert.equal(first, 1);
74
- assert.equal(entries().length, 1);
75
- assert.equal(win.location.hash, '#list/pages');
76
- });
77
-
78
- test('leaving the level another way takes the step out, without the first level called again', () => {
79
- const { win, entries } = fakeWindow();
80
- let first = 0;
81
- const step = levelStep(win);
82
- step.set(true, () => (first += 1));
83
- step.set(false, () => (first += 1));
84
- assert.equal(entries().length, 1);
85
- assert.equal(first, 0);
86
- });
87
-
88
- test('a link on the level is followed after the step is out: no empty step', () => {
89
- const { win, entries, click } = fakeWindow();
90
- const step = levelStep(win);
91
- step.set(true, () => {});
92
- const e = click('#list/posts');
93
- assert.equal(e.defaultPrevented, true);
94
- assert.deepEqual(
95
- entries().map((s) => s.hash),
96
- ['#list/pages', '#list/posts'],
97
- );
98
- // The drawer closes on the link: nothing more to take out.
99
- step.set(false, () => {});
100
- assert.equal(entries().length, 2);
101
- });
102
-
103
- test('links are left alone at the first level and in a new tab', () => {
104
- const { win, click } = fakeWindow();
105
- const step = levelStep(win);
106
- assert.equal(click('#list/posts').defaultPrevented, false);
107
- step.set(true, () => {});
108
- assert.equal(click('#list/posts', { target: '_blank' }).defaultPrevented, false);
109
- });
110
-
111
- test('stop takes the step out and stops listening', () => {
112
- const { win, entries, listeners } = fakeWindow();
113
- const step = levelStep(win);
114
- step.set(true, () => {});
115
- step.stop();
116
- assert.equal(entries().length, 1);
117
- assert.equal(listeners.popstate.size, 0);
118
- assert.equal(listeners.click.size, 0);
119
- });