@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
@@ -1,9 +1,8 @@
1
- // A titled list of versions with their release notes on the Morpheme page (#morpheme): per version a small heading, its
2
- // cards (screenshot, title, text) in the editor's language (else English), then its other changes as a short list under
3
- // "Also in this version"; for a version without notes, one row with its text. Exposes ReleaseNotes and VersionNotes.
4
- // Depends on ReleaseNoteCard, ui.tsx, lib/releaseNotes.mjs (notesFor) and i18n.
5
- import { ReleaseNoteCard } from './ReleaseNoteCard';
6
- import { GroupTitle, Group, ListItem } from './ui';
1
+ // The release notes on the Morpheme page (#morpheme), as an article: its title, then per version a small heading, its
2
+ // cards in the editor's language (else English) as sections with a title and a text, and its other changes as a list
3
+ // under "Also in this version"; for a version without notes, its text. No cards and no screenshots: the start screen's
4
+ // update card (UpdatesNotice.tsx) shows the pictures. Exposes ReleaseNotes and VersionNotes. Depends on
5
+ // lib/releaseNotes.mjs (notesFor) and i18n.
7
6
  import { locale, t } from './i18n';
8
7
  import { notesFor } from '../lib/releaseNotes.mjs';
9
8
  import type { ReleaseNote } from '../lib/releaseNotes.mjs';
@@ -13,40 +12,40 @@ export type VersionNotes = { version: string; text: string; notes?: Record<strin
13
12
  type Card = Extract<ReleaseNote, { kind: 'card' }>;
14
13
 
15
14
  function VersionEntry({ entry }: { entry: VersionNotes }) {
16
- const notes = notesFor(entry.notes, locale);
17
- if (!notes?.length) {
18
- return (
19
- <Group>
20
- <ListItem headline={t.cms.running(entry.version)} supporting={entry.text} />
21
- </Group>
22
- );
23
- }
15
+ const notes = notesFor(entry.notes, locale) ?? [];
24
16
  const cards = notes.filter((note): note is Card => note.kind === 'card');
25
17
  const lines = notes.filter((note) => note.kind === 'line');
26
18
  return (
27
- <div className="mb-6 space-y-3">
28
- <h3 className="px-4 text-sm font-medium text-muted">{t.cms.running(entry.version)}</h3>
19
+ <section className="space-y-5 border-t border-line pt-5 first-of-type:border-t-0 first-of-type:pt-0">
20
+ <h3 className="text-sm font-medium text-muted">{t.cms.running(entry.version)}</h3>
21
+ {!notes.length && entry.text && <p className="text-base">{entry.text}</p>}
29
22
  {cards.map((note) => (
30
- <ReleaseNoteCard key={note.id} note={note} />
23
+ <div key={note.id} className="space-y-1">
24
+ <h4 className="text-lg font-medium">{note.title}</h4>
25
+ <p className="text-base">{note.text}</p>
26
+ </div>
31
27
  ))}
32
28
  {lines.length > 0 && (
33
- <Group title={t.cms.alsoInThisVersion}>
34
- {lines.map((note) => (
35
- <ListItem key={note.id} headline={note.text} />
36
- ))}
37
- </Group>
29
+ <div className="space-y-2">
30
+ <h4 className="text-base font-medium">{t.cms.alsoInThisVersion}</h4>
31
+ <ul className="list-disc space-y-1 ps-5 text-base marker:text-accent">
32
+ {lines.map((note) => (
33
+ <li key={note.id}>{note.text}</li>
34
+ ))}
35
+ </ul>
36
+ </div>
38
37
  )}
39
- </div>
38
+ </section>
40
39
  );
41
40
  }
42
41
 
43
42
  export function ReleaseNotes({ title, entries }: { title: string; entries: VersionNotes[] }) {
44
43
  return (
45
- <section className="mb-8">
46
- <GroupTitle>{title}</GroupTitle>
44
+ <article className="mb-8 max-w-prose space-y-5 px-4 not-first:mt-8">
45
+ <h2 className="text-2xl">{title}</h2>
47
46
  {entries.map((entry) => (
48
47
  <VersionEntry key={entry.version} entry={entry} />
49
48
  ))}
50
- </section>
49
+ </article>
51
50
  );
52
51
  }
@@ -1,16 +1,28 @@
1
- // At the top of the start screen: what was updated since the last visit in this browser (the CMS and the apps installed
2
- // from the store), with a close button that marks it seen. Exposes UpdatesNotice. The logic is startView.mjs's
3
- // (changesSince), the storage startStorage.ts's, the card the theme's (Notice in ui.tsx).
1
+ // At the top of the start screen: what was updated since the last visit in this browser, as a card with a close button
2
+ // that marks it seen, in the accent (the editor's blue). For the CMS: its new version large, how many new things it brings and the first of them from the
3
+ // notes the package carries (bundledNotes.ts: the first with a screenshot large, then the titles and texts of the next),
4
+ // and a button to the Morpheme page with all of them; for each app installed from the store: a row with its icon and new
5
+ // version, to its Information tab. Exposes UpdatesNotice. The logic is startView.mjs's (changesSince) and
6
+ // releaseNotes.mjs's (notesSince), the storage startStorage.ts's, the card, rows and buttons the theme's (themeUi.ts, ui.tsx).
4
7
  import { useEffect, useState } from 'react';
8
+ import { ArrowRight, Sparkle } from '@phosphor-icons/react';
5
9
  import { version } from '../package.json';
6
- import { t } from './i18n';
10
+ import { locale, t } from './i18n';
7
11
  import { installedApps } from './apps';
8
12
  import { apps } from './site';
9
13
  import { changesSince, versioned, type Versions } from './startView.mjs';
10
14
  import { keepStored, SEEN, storedSeen } from './startStorage';
11
15
  import type { RepoFile } from './github';
12
- import { Notice } from './ui';
13
- import { UpdateLink } from './UpdateLink';
16
+ import { SiteCard } from './themeUi';
17
+ import { CloseButton, LinkButton, ListItem, plainListClass } from './ui';
18
+ import { CMS_ROUTE } from './CmsPage';
19
+ import { appHref } from './AppList';
20
+ import { runningNotes } from './bundledNotes';
21
+ import { notesSince, type ReleaseNote } from '../lib/releaseNotes.mjs';
22
+ import { INFO_KEY } from '../lib/appRoute.mjs';
23
+
24
+ /** How many of the new things the card shows; the Morpheme page has them all. */
25
+ const SHOWN = 3;
14
26
 
15
27
  /** The CMS's version and those of the apps installed from the store, as the editor is built with them now. */
16
28
  function versionsNow(files: RepoFile[]): Versions {
@@ -21,8 +33,29 @@ function versionsNow(files: RepoFile[]): Versions {
21
33
  };
22
34
  }
23
35
 
24
- /** Each change is a link to what is new in it (the CMS's page, an app's Information tab). The first visit, and a visit
25
- * with nothing new, only notes the versions. */
36
+ /** The first new thing with a screenshot, large: the screenshot fills the top (its top part), its title under it. */
37
+ function Lead({ note }: { note: Extract<ReleaseNote, { kind: 'card' }> }) {
38
+ return (
39
+ <div className="overflow-hidden rounded-xl bg-accent-container text-on-accent-container">
40
+ <img src={note.image} alt="" className="block h-44 w-full object-cover object-top" />
41
+ <p className="px-4 py-3 font-medium">{note.title}</p>
42
+ </div>
43
+ );
44
+ }
45
+
46
+ /** One more new thing: a sparkle and its title (a card) or its text (a line). */
47
+ function Highlight({ note }: { note: ReleaseNote }) {
48
+ return (
49
+ <li className="flex items-center gap-3 rounded-xl bg-accent-container p-3 text-on-accent-container">
50
+ <span className="grid size-10 shrink-0 place-items-center rounded-full bg-accent text-on-accent">
51
+ <Sparkle size={20} weight="fill" aria-hidden />
52
+ </span>
53
+ <span className={note.kind === 'card' ? 'font-medium' : 'text-sm'}>{note.kind === 'card' ? note.title : note.text}</span>
54
+ </li>
55
+ );
56
+ }
57
+
58
+ /** The first visit, and a visit with nothing new, only notes the versions. */
26
59
  export function UpdatesNotice({ files }: { files: RepoFile[] }) {
27
60
  const now = versionsNow(files);
28
61
  const [changes, setChanges] = useState(() => changesSince(storedSeen(), now));
@@ -35,15 +68,66 @@ export function UpdatesNotice({ files }: { files: RepoFile[] }) {
35
68
  keepStored(SEEN, versioned(now));
36
69
  setChanges([]);
37
70
  };
71
+ const cms = changes.find((c) => c.id === 'cms');
72
+ const updatedApps = changes.flatMap((c) => {
73
+ const app = c.id === 'cms' ? null : apps.find((a) => a.id === c.id);
74
+ return app ? [{ app, version: c.version }] : [];
75
+ });
76
+ const news = cms ? notesSince(runningNotes, cms.from, locale) : [];
77
+ const lead = news.find((note): note is Extract<ReleaseNote, { kind: 'card' }> => note.kind === 'card' && !!note.image);
78
+ const rest = news.filter((note) => note !== lead);
38
79
  return (
39
- <div className="mb-8">
40
- <Notice title={t.library.updatesTitle} onClose={close}>
41
- <ul className="space-y-1">
42
- {changes.map((c) => (
43
- <UpdateLink key={c.id} change={c} />
80
+ <SiteCard
81
+ kind="filled"
82
+ as="section"
83
+ role="status"
84
+ aria-labelledby="updates-title"
85
+ className="relative mb-8 text-on-accent [--card-filled-surface:var(--accent)] [--card-padding:0] [--icon-button-standard-text:var(--on-accent)]"
86
+ >
87
+ <CloseButton onClick={close} />
88
+ <div className="flex items-center gap-4 p-4 pe-14">
89
+ <span className="grid size-12 shrink-0 place-items-center rounded-full bg-on-accent text-accent">
90
+ <Sparkle size={26} weight="fill" aria-hidden />
91
+ </span>
92
+ <div className="min-w-0">
93
+ <h2 id="updates-title" className="text-sm font-medium">
94
+ {t.library.updatesTitle}
95
+ </h2>
96
+ <p className="text-2xl leading-tight">{cms ? t.library.cmsVersion(cms.version) : t.library.appsUpdated(updatedApps.length)}</p>
97
+ {news.length > 0 && <p className="text-sm opacity-85">{t.library.newThings(news.length)}</p>}
98
+ </div>
99
+ </div>
100
+ {news.length > 0 && (
101
+ <div className="space-y-2 px-4">
102
+ {lead && <Lead note={lead} />}
103
+ <ul className="space-y-2">
104
+ {rest.slice(0, lead ? SHOWN - 1 : SHOWN).map((note) => (
105
+ <Highlight key={note.id} note={note} />
106
+ ))}
107
+ </ul>
108
+ </div>
109
+ )}
110
+ {cms && (
111
+ <div className="px-4 pt-4 pb-4">
112
+ <LinkButton kind="tonal" href={`#${CMS_ROUTE}`} icon={ArrowRight}>
113
+ {t.library.seeWhatsNew}
114
+ </LinkButton>
115
+ </div>
116
+ )}
117
+ {updatedApps.length > 0 && (
118
+ <ul className={`${plainListClass} mx-4 mb-4 overflow-hidden rounded-xl bg-accent-container text-on-accent-container`}>
119
+ {updatedApps.map(({ app, version }) => (
120
+ <ListItem
121
+ key={app.id}
122
+ href={`${appHref(app)}/${INFO_KEY}`}
123
+ icon={app.icon}
124
+ headline={app.label}
125
+ supporting={t.library.appUpdatedTo(version)}
126
+ arrow
127
+ />
44
128
  ))}
45
129
  </ul>
46
- </Notice>
47
- </div>
130
+ )}
131
+ </SiteCard>
48
132
  );
49
133
  }
@@ -0,0 +1,11 @@
1
+ // The release notes of the minor line this package is in (x.y.0 up to its version): read at build time from the package's own release-notes/ files.
2
+ // Exposes runningNotes (the entries the Morpheme page shows, newest first; empty when the line has none).
3
+ // Depends on lib/releaseNotes.mjs (bundledNotes) and package.json's version. The globs are relative, so they work here and in a site's node_modules.
4
+ import { bundledNotes } from '../lib/releaseNotes.mjs';
5
+ import { version } from '../package.json';
6
+ import type { ReleasedNotes } from '../lib/releaseNotes.mjs';
7
+
8
+ const files = import.meta.glob<ReleasedNotes>('../release-notes/i18n/*/*.json', { eager: true, import: 'default' });
9
+ const images = import.meta.glob<string>('../release-notes/i18n/*/*/*.webp', { eager: true, query: '?url', import: 'default' });
10
+
11
+ export const runningNotes = bundledNotes(files, images, version);
package/src/cmsView.d.mts CHANGED
@@ -3,7 +3,6 @@ export function cmsView(status: any, now?: number): {
3
3
  checkedAt: any;
4
4
  latest: any;
5
5
  notes: any[];
6
- installed: any;
7
6
  action: string | null;
8
7
  npmCommand: string | null;
9
8
  update: {
package/src/cmsView.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // Transforms CMS status into a view for the update page.
2
- // Exposes cmsView and DONE_SHOWN. `installed` passes the notes of the update just installed on as the Worker gives them.
2
+ // Exposes cmsView and DONE_SHOWN.
3
3
  // Depends on cms-version for version comparison.
4
4
 
5
5
  import { compareVersions } from '../lib/cms-version.mjs';
@@ -77,7 +77,5 @@ export function cmsView(status, now = Date.now()) {
77
77
  problems.push('slow-undo');
78
78
  }
79
79
 
80
- const installed = Array.isArray(status.installed) ? status.installed : [];
81
-
82
- return { running, checkedAt, latest, notes, installed, action, npmCommand, update, problems };
80
+ return { running, checkedAt, latest, notes, action, npmCommand, update, problems };
83
81
  }
@@ -150,9 +150,3 @@ test('pre-release running', () => {
150
150
  const view = cmsView(status, now);
151
151
  assert.equal(view.latest, null);
152
152
  });
153
-
154
- test('passes the installed notes on, or an empty list', () => {
155
- const installed = [{ version: '0.1.1', text: 'Fixes', notes: { 'en-US': [] } }];
156
- assert.deepEqual(cmsView(makeStatus({ installed }), now).installed, installed);
157
- assert.deepEqual(cmsView(makeStatus({}), now).installed, []);
158
- });
package/src/editor.css CHANGED
@@ -483,6 +483,17 @@
483
483
  font-weight: var(--type-label-medium-weight);
484
484
  }
485
485
 
486
+ /* Material 3 Expressive's segmented lists (m3.material.io/components/lists/specs, colour role 1): each item on Surface (the
487
+ theme's page colour, --bg) over a Surface Container, as Material's own examples and Android's settings show them. So
488
+ the editor's page, its top app bar and the label of an outlined field (--md-background) are that container, the top
489
+ app bar a step higher once the page scrolls under it, and the items on Surface. html:root, so it wins over the theme's
490
+ :root. */
491
+ html:root {
492
+ --md-background: var(--surface-container, var(--bg));
493
+ --top-app-bar-stuck-surface: var(--surface-container-high, var(--elevation-3-surface));
494
+ --list-segmented-surface: var(--bg);
495
+ }
496
+
486
497
  /* The content column (.editor-column: Screen.tsx's tabs row and its main) stands centred on the window, not in the space
487
498
  beside the panels. --column-start and --column-end are the widths of the panels beside the content: the navigation
488
499
  rail from medium, the standard drawer instead of it from expanded while it is open (:root[data-sidebar='open']; the
@@ -9,8 +9,10 @@ export const library: Messages['library'] = {
9
9
  continueEmpty: 'De sider og indlæg, du redigerer, vises her.',
10
10
  recentEdit: (kind, when) => `${kind} · Redigeret ${when}`,
11
11
  updatesTitle: 'Opdateret siden dit sidste besøg',
12
- cmsName: 'Morpheme',
13
- updatedTo: (name, version) => `${name} er opdateret til ${version}`,
12
+ appsUpdated: (count) => (count === 1 ? '1 app er opdateret' : `${count} apps er opdateret`),
13
+ newThings: (count) => (count === 1 ? '1 nyhed' : `${count} nyheder`),
14
+ seeWhatsNew: 'Se, hvad der er nyt',
15
+ appUpdatedTo: (version) => `Opdateret til ${version}`,
14
16
  githubAccount: (login) => `GitHub-konto: ${login}`,
15
17
  logOut: 'Log ud',
16
18
  notifications: 'Notifikationer',
@@ -14,12 +14,16 @@ export const library = {
14
14
  continueEmpty: 'The pages and posts you edit show up here.',
15
15
  /** Under each of them: its kind ("Page") and when it was saved ("today at 1:42 PM", when.ts). */
16
16
  recentEdit: (kind: string, when: string) => `${kind} · Edited ${when}`,
17
- /** At the top of the start screen: what was updated since the last visit in this browser, each a link to what's new. */
17
+ /** At the top of the start screen (UpdatesNotice.tsx): the card with what was updated since the last visit in this
18
+ * browser. Its title, over the CMS's new version (cmsVersion) or, with only apps updated, how many. */
18
19
  updatesTitle: 'Updated since your last visit',
19
- /** The CMS's name in that notice. */
20
- cmsName: 'Morpheme',
21
- /** The CMS or an app with its new version. */
22
- updatedTo: (name: string, version: string) => `${name} is updated to ${version}`,
20
+ appsUpdated: (count: number) => (count === 1 ? '1 app updated' : `${count} apps updated`),
21
+ /** Under the CMS's new version: how many new things its release notes have since the version seen last. */
22
+ newThings: (count: number) => (count === 1 ? '1 new thing' : `${count} new things`),
23
+ /** The button to the Morpheme page, with all the release notes. */
24
+ seeWhatsNew: 'See what’s new',
25
+ /** Under an updated app's name. */
26
+ appUpdatedTo: (version: string) => `Updated to ${version}`,
23
27
  githubAccount: (login: string) => `GitHub account: ${login}`,
24
28
  logOut: 'Log out',
25
29
  /** The bell in the top bar and its side sheet (Notifications.tsx): the messages shown at the bottom. */
@@ -42,15 +42,16 @@ export function continueShown(closedAt: unknown, latestAt: number | undefined):
42
42
  */
43
43
  /**
44
44
  * What has a version other than at the last visit: the CMS (`id` "cms") and each app installed then and now, with its
45
- * version now. Nothing at the first visit (`seen` null): there is nothing to compare with. An app installed since is
45
+ * version now and the one seen (`from`). Nothing at the first visit (`seen` null): there is nothing to compare with. An app installed since is
46
46
  * not an update.
47
47
  * @param {Versions | null} seen
48
48
  * @param {Versions} now
49
- * @returns {{ id: string, version: string }[]}
49
+ * @returns {{ id: string, version: string, from: string }[]}
50
50
  */
51
51
  export function changesSince(seen: Versions | null, now: Versions): {
52
52
  id: string;
53
53
  version: string;
54
+ from: string;
54
55
  }[];
55
56
  /**
56
57
  * What the editor's start screen (Library.tsx Start, ContinueCard.tsx, UpdatesNotice.tsx) shows besides its FAB: the pages and blog posts
package/src/startView.mjs CHANGED
@@ -90,19 +90,19 @@ export function continueShown(closedAt, latestAt) {
90
90
 
91
91
  /**
92
92
  * What has a version other than at the last visit: the CMS (`id` "cms") and each app installed then and now, with its
93
- * version now. Nothing at the first visit (`seen` null): there is nothing to compare with. An app installed since is
93
+ * version now and the one seen (`from`). Nothing at the first visit (`seen` null): there is nothing to compare with. An app installed since is
94
94
  * not an update.
95
95
  * @param {Versions | null} seen
96
96
  * @param {Versions} now
97
- * @returns {{ id: string, version: string }[]}
97
+ * @returns {{ id: string, version: string, from: string }[]}
98
98
  */
99
99
  export function changesSince(seen, now) {
100
100
  if (!seen) return [];
101
101
  const changed = [];
102
- if (seen.cms && now.cms && seen.cms !== now.cms) changed.push({ id: 'cms', version: now.cms });
102
+ if (seen.cms && now.cms && seen.cms !== now.cms) changed.push({ id: 'cms', version: now.cms, from: seen.cms });
103
103
  for (const [id, version] of Object.entries(now.apps)) {
104
104
  const before = seen.apps?.[id];
105
- if (before && version && before !== version) changed.push({ id, version });
105
+ if (before && version && before !== version) changed.push({ id, version, from: before });
106
106
  }
107
107
  return changed;
108
108
  }
@@ -38,8 +38,8 @@ test('finds the CMS and the apps with another version', () => {
38
38
  const seen = { cms: '1.0.0', apps: { a: '1.0.0', b: '2.0.0' } };
39
39
  const now = { cms: '1.1.0', apps: { a: '1.0.0', b: '2.1.0' } };
40
40
  assert.deepEqual(changesSince(seen, now), [
41
- { id: 'cms', version: '1.1.0' },
42
- { id: 'b', version: '2.1.0' },
41
+ { id: 'cms', version: '1.1.0', from: '1.0.0' },
42
+ { id: 'b', version: '2.1.0', from: '2.0.0' },
43
43
  ]);
44
44
  });
45
45
 
@@ -107,8 +107,8 @@ test('stored data is read only with the current version', () => {
107
107
 
108
108
  test('a version that went down counts as a change (current behaviour)', () => {
109
109
  assert.deepEqual(changesSince({ cms: '1.1.0', apps: { a: '2.0.0' } }, { cms: '1.0.0', apps: { a: '1.0.0' } }), [
110
- { id: 'cms', version: '1.0.0' },
111
- { id: 'a', version: '1.0.0' },
110
+ { id: 'cms', version: '1.0.0', from: '1.1.0' },
111
+ { id: 'a', version: '1.0.0', from: '2.0.0' },
112
112
  ]);
113
113
  });
114
114
 
package/src/theme.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // (the theme's styles/global.css); the script in the head of packages/editor/pages/editor/index.astro does the same
4
4
  // before the page is drawn, so it does not flash in the other colours. Its own key, so the site's
5
5
  // choice (`theme`) is not changed by the editor's.
6
- import { colorsCss, isColor, type Colors } from '../lib/colors';
6
+ import { colorsCss, isColor, pageColor, type Colors } from '../lib/colors';
7
7
  import { motionCss, type MotionScheme } from '../lib/motion';
8
8
 
9
9
  export type Theme = 'light' | 'auto' | 'dark';
@@ -73,7 +73,7 @@ export function applyColors(colors: Colors) {
73
73
  const style = document.getElementById('site-colors');
74
74
  if (style && style.textContent !== css) style.textContent = css;
75
75
  for (const m of bars()) {
76
- const bg = colors[isDark(m) ? 'dark' : 'light'].bg;
76
+ const bg = pageColor(colors[isDark(m) ? 'dark' : 'light']);
77
77
  if (isColor(bg)) m.dataset.color = bg;
78
78
  }
79
79
  applyTheme(savedTheme());
@@ -32,7 +32,7 @@
32
32
  "@material-symbols/svg-400": "0.48.2",
33
33
  "@material/material-color-utilities": "^0.3.0",
34
34
  "@milkdown/kit": "^7.22.3",
35
- "@morphemecms/cms": "0.14.0",
35
+ "@morphemecms/cms": "0.14.1",
36
36
  "@phosphor-icons/core": "^2.1.1",
37
37
  "@phosphor-icons/react": "^2.1.10",
38
38
  "@tailwindcss/typography": "^0.5.20",
@@ -199,14 +199,15 @@
199
199
  --card-filled-surface: var(--md-surface-container-highest);
200
200
  --card-outlined-surface: var(--surface);
201
201
  --card-outlined-line: var(--line);
202
- /* Lists (M3 Expressive's list tokens): items 56, 72 and 88px high by their lines, 16px in from their sides, 10px over
203
- and under their text and 12px between their elements, without lines between them; 20px icons; the headline in body
204
- large, the supporting text in body medium, the overline and the trailing text in label small; a state layer in the
205
- text's colour when pointed at (8%) and pressed (10%), nothing moving aside; disabled at 38%, a segmented item on a
206
- 10% layer of the text's colour. Their corners extra-small, medium pointed at, large pressed and focused, large
207
- increased selected; a selected one in the secondary container. Segmented: each item on a surface container (a step
208
- up from the page, as Material's items on the surface over a container), 2px between them and the list's corners
209
- large. */
202
+ /* Lists (M3 Expressive's list tokens, m3.material.io/components/lists/specs): items 56, 72 and 88px high by their lines,
203
+ 16px in from their sides, 10px over and under their text and 12px between their elements, without lines between
204
+ them; 20px icons; the label text in body large (16/24, 400, 0.5; a setting row's name too), the supporting text in
205
+ body medium, the overline and the trailing text in label small; a state layer in the text's colour when pointed at
206
+ (8%) and pressed (10%), nothing moving aside; disabled at 38%, a segmented item on a 10% layer of the text's colour.
207
+ Their corners extra-small (4px), medium (12px) pointed at, pressed and selected; a selected one in the secondary
208
+ container. Segmented: 2px between the items, the list's outer corners medium (12px), each item on a surface
209
+ container (a step up from the page; the editor puts the items on the surface over a container page, as Material's
210
+ spec does, editor.css). */
210
211
  --list-divider: transparent;
211
212
  --list-item-min-height: 3.5rem;
212
213
  --list-item-two-line-min-height: 4.5rem;
@@ -229,6 +230,7 @@
229
230
  --list-item-selected-surface: var(--secondary-container);
230
231
  --list-item-selected-text: var(--on-secondary-container);
231
232
  --list-item-selected-weight: 400;
233
+ --setting-row-label-weight: var(--type-body-large-weight);
232
234
  --list-item-leading-icon-selected-color: var(--accent);
233
235
  --list-item-disabled-opacity: 0.38;
234
236
  --list-segmented-gap: 0.125rem;
@@ -4418,7 +4418,8 @@
4418
4418
  */
4419
4419
  @layer components {
4420
4420
  /* A setting's row (SettingRow.tsx): the name and the line under it, then what sets it, on one line; the switch's
4421
- and the editor's dropdown row both. */
4421
+ and the editor's dropdown row both. The name in a list item's label type (--list-item-headline-*), so a row with a
4422
+ switch reads as the list rows around it. */
4422
4423
  .setting-row {
4423
4424
  display: flex;
4424
4425
  align-items: center;
@@ -4430,7 +4431,10 @@
4430
4431
  }
4431
4432
  .setting-row-label {
4432
4433
  display: block;
4434
+ font-size: var(--list-item-headline-size, var(--type-body-large-size));
4435
+ line-height: var(--list-item-headline-line-height, var(--type-body-large-line-height));
4433
4436
  font-weight: var(--setting-row-label-weight, var(--switch-label-weight, 500));
4437
+ letter-spacing: var(--list-item-headline-letter-spacing, var(--type-body-large-tracking));
4434
4438
  }
4435
4439
  .setting-row-supporting {
4436
4440
  display: block;
@@ -3,7 +3,7 @@
3
3
  // Exposes RUNNING, checkForUpdate, startUpdate, watchUpdate, cmsStatus and handleCms (/api/cms, and GET /api/cms/note-image/<version>/<locale>/<id>,
4
4
  // behind the same login check). Needs GITHUB_PUBLISH_TOKEN and the KV binding PUSH. Keeps each new version's changelog text and release
5
5
  // notes (en-US, da-DK) as cms:notes:<version> and each card screenshot as cms:note-image:<version>:<locale>:<id>; cmsStatus returns
6
- // the notes to come and the installed ones, with the screenshot address on each card. Depends on github.js, session.js, push.js and lib/{lockfile,cms-version,changelog,releaseNotes}.mjs;
6
+ // the notes to come, with the screenshot address on each card. Depends on github.js, session.js, push.js and lib/{lockfile,cms-version,changelog,releaseNotes}.mjs;
7
7
  // I/O is injected (`deps`).
8
8
  import { config } from '@site/config';
9
9
  import { cms as cmsEn } from '../src/i18n/en-US/cms';
@@ -11,7 +11,7 @@ import { cms as cmsDa } from '../src/i18n/da-DK/cms';
11
11
  import { addToLock, NeedsNpm } from '../lib/lockfile.mjs';
12
12
  import { compareVersions, managedVersion, nextVersion, PACKAGE, watchStep } from '../lib/cms-version.mjs';
13
13
  import { fileFromTar, gunzip, sectionsBetween } from '../lib/changelog.mjs';
14
- import { imageKey, installedVersions, notesFromTar, parseImagePath, withImageUrls } from '../lib/releaseNotes.mjs';
14
+ import { imageKey, notesFromTar, parseImagePath, withImageUrls } from '../lib/releaseNotes.mjs';
15
15
  import pkg from '../package.json';
16
16
  import { GitHubError } from './files.js';
17
17
  import { commitFiles, readFiles } from './github.js';
@@ -211,8 +211,8 @@ export async function watchUpdate(env, deps = defaultDeps, now = Date.now()) {
211
211
 
212
212
  /**
213
213
  * What the page shows: the running version, the token, what npm said, the update, the skipped versions, the notes of the
214
- * versions still to come (`notes`) and those of the versions installed (`installed`, see installedVersions). Each entry is
215
- * `{ version, text, notes? }`; `notes` maps a locale to its release notes. Both lists stay empty when the running version is a pre-release.
214
+ * versions still to come (`notes`). Each entry is `{ version, text, notes? }`; `notes` maps a locale to its release notes. The list stays
215
+ * empty when the running version is a pre-release. The running version's own notes are not here: the editor reads them from the package (src/bundledNotes.ts).
216
216
  */
217
217
  export async function cmsStatus(env, running = RUNNING) {
218
218
  const kv = env.PUSH;
@@ -224,7 +224,6 @@ export async function cmsStatus(env, running = RUNNING) {
224
224
  update: null,
225
225
  skipped: [],
226
226
  notes: [],
227
- installed: [],
228
227
  };
229
228
  const plain = /^\d+\.\d+\.\d+$/;
230
229
  if (!kv) return status;
@@ -241,8 +240,6 @@ export async function cmsStatus(env, running = RUNNING) {
241
240
  );
242
241
  wanted.sort((a, b) => compareVersions(b, a));
243
242
  status.notes = entries(await Promise.all(wanted.map((v) => kv.get(`cms:notes:${v}`, 'json'))));
244
- const installed = installedVersions(status.update, running, kept);
245
- status.installed = entries(await Promise.all(installed.map((v) => kv.get(`cms:notes:${v}`, 'json'))));
246
243
  return status;
247
244
  }
248
245
 
@@ -1,17 +0,0 @@
1
- // One release note card on the Morpheme page (#morpheme): the theme's outlined card with the note's screenshot on top (when it
2
- // has one, whole at the card's width and its own height), its title and its text. Exposes ReleaseNoteCard. Depends on the theme's card (themeUi.ts) and the ReleaseNote
3
- // type of lib/releaseNotes.mjs.
4
- import { SiteCard } from './themeUi';
5
- import type { ReleaseNote } from '../lib/releaseNotes.mjs';
6
-
7
- export function ReleaseNoteCard({ note }: { note: Extract<ReleaseNote, { kind: 'card' }> }) {
8
- return (
9
- <SiteCard kind="outlined" as="article" className="space-y-3 overflow-hidden [--card-padding:0_0_1rem]">
10
- {note.image && <img src={note.image} alt={note.title} loading="lazy" className="block h-auto w-full" />}
11
- <div className="space-y-2 px-4">
12
- <h3 className="text-lg font-medium">{note.title}</h3>
13
- <p className="text-base">{note.text}</p>
14
- </div>
15
- </SiteCard>
16
- );
17
- }
@@ -1,21 +0,0 @@
1
- // One line of the start screen's updates notice: a link to what is new in the CMS or in an app (its Information tab).
2
- // Exposes UpdateLink. Depends on appHref (AppList.tsx), CMS_ROUTE and INFO_KEY.
3
- import { t } from './i18n';
4
- import { CMS_ROUTE } from './CmsPage';
5
- import { appHref } from './AppList';
6
- import { apps } from './site';
7
- import { INFO_KEY } from '../lib/appRoute.mjs';
8
-
9
- /** `change.id` is "cms" or an app's id; `change.version` the version it is updated to. */
10
- export function UpdateLink({ change }: { change: { id: string; version: string } }) {
11
- const app = change.id === 'cms' ? null : apps.find((a) => a.id === change.id);
12
- const name = app ? app.label : t.library.cmsName;
13
- const href = app ? `${appHref(app)}/${INFO_KEY}` : `#${CMS_ROUTE}`;
14
- return (
15
- <li>
16
- <a href={href} className="underline underline-offset-2 hover:text-accent">
17
- {t.library.updatedTo(name, change.version)}
18
- </a>
19
- </li>
20
- );
21
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Watches the window's history for the level's step. `set(active, toFirst)`: whether the level is shown (a step is added
3
- * while it is), and what the back gesture does on it (shows the first level). `stop()` takes the step out and stops.
4
- * @param {Pick<Window, 'history' | 'location' | 'addEventListener' | 'removeEventListener' | 'document'>} win
5
- */
6
- export function levelStep(win: Pick<Window, "history" | "location" | "addEventListener" | "removeEventListener" | "document">): {
7
- /** @param {boolean} active @param {() => void} back */
8
- set(active: boolean, back: () => void): void;
9
- stop(): void;
10
- };