@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.
- package/CHANGELOG.md +14 -0
- package/lib/colors.ts +4 -0
- package/lib/releaseNotes.d.mts +26 -11
- package/lib/releaseNotes.mjs +57 -14
- package/lib/releaseNotes.test.mjs +94 -10
- package/package.json +1 -1
- package/pages/editor/index.astro +8 -5
- package/release-notes/i18n/da-DK/0.14.0.json +2 -2
- package/release-notes/i18n/da-DK/0.14.1.json +15 -0
- package/release-notes/i18n/da-DK/pending/lists-follow-md3.json +5 -0
- package/release-notes/i18n/da-DK/pending/update-card-whats-new.json +6 -0
- package/release-notes/i18n/da-DK/pending/update-card-whats-new.webp +0 -0
- package/release-notes/i18n/en-US/0.14.0.json +2 -2
- package/release-notes/i18n/en-US/0.14.1.json +15 -0
- package/release-notes/i18n/en-US/pending/lists-follow-md3.json +5 -0
- package/release-notes/i18n/en-US/pending/update-card-whats-new.json +6 -0
- package/release-notes/i18n/en-US/pending/update-card-whats-new.webp +0 -0
- package/scripts/release-notes.mjs +14 -3
- package/src/CmsPage.tsx +6 -6
- package/src/MainMenu.tsx +9 -14
- package/src/ReleaseNotes.tsx +25 -26
- package/src/UpdatesNotice.tsx +99 -15
- package/src/bundledNotes.ts +11 -0
- package/src/cmsView.d.mts +0 -1
- package/src/cmsView.mjs +2 -4
- package/src/cmsView.test.mjs +0 -6
- package/src/editor.css +11 -0
- package/src/i18n/da-DK/library.ts +4 -2
- package/src/i18n/en-US/library.ts +9 -5
- package/src/startView.d.mts +3 -2
- package/src/startView.mjs +4 -4
- package/src/startView.test.mjs +4 -4
- package/src/theme.ts +2 -2
- package/starter/package.json +1 -1
- package/theme/styles/global.css +10 -8
- package/theme/styles/ui.css +5 -1
- package/worker/cms-update.js +4 -7
- package/src/ReleaseNoteCard.tsx +0 -17
- package/src/UpdateLink.tsx +0 -21
- package/src/levelStep.d.mts +0 -10
- package/src/levelStep.mjs +0 -66
- package/src/levelStep.test.mjs +0 -119
package/src/ReleaseNotes.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
// cards
|
|
3
|
-
// "Also in this version"; for a version without notes,
|
|
4
|
-
//
|
|
5
|
-
|
|
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
|
-
<
|
|
28
|
-
<h3 className="
|
|
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
|
-
<
|
|
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
|
-
<
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
</
|
|
38
|
+
</section>
|
|
40
39
|
);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
export function ReleaseNotes({ title, entries }: { title: string; entries: VersionNotes[] }) {
|
|
44
43
|
return (
|
|
45
|
-
<
|
|
46
|
-
<
|
|
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
|
-
</
|
|
49
|
+
</article>
|
|
51
50
|
);
|
|
52
51
|
}
|
package/src/UpdatesNotice.tsx
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
// At the top of the start screen: what was updated since the last visit in this browser
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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 {
|
|
13
|
-
import {
|
|
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
|
-
/**
|
|
25
|
-
|
|
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
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
</
|
|
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
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.
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
return { running, checkedAt, latest, notes, installed, action, npmCommand, update, problems };
|
|
80
|
+
return { running, checkedAt, latest, notes, action, npmCommand, update, problems };
|
|
83
81
|
}
|
package/src/cmsView.test.mjs
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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. */
|
package/src/startView.d.mts
CHANGED
|
@@ -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
|
}
|
package/src/startView.test.mjs
CHANGED
|
@@ -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']
|
|
76
|
+
const bg = pageColor(colors[isDark(m) ? 'dark' : 'light']);
|
|
77
77
|
if (isColor(bg)) m.dataset.color = bg;
|
|
78
78
|
}
|
|
79
79
|
applyTheme(savedTheme());
|
package/starter/package.json
CHANGED
|
@@ -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.
|
|
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",
|
package/theme/styles/global.css
CHANGED
|
@@ -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,
|
|
203
|
-
and under their text and 12px between their elements, without lines between
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
10%
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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;
|
package/theme/styles/ui.css
CHANGED
|
@@ -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;
|
package/worker/cms-update.js
CHANGED
|
@@ -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
|
|
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,
|
|
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`)
|
|
215
|
-
*
|
|
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
|
|
package/src/ReleaseNoteCard.tsx
DELETED
|
@@ -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
|
-
}
|
package/src/UpdateLink.tsx
DELETED
|
@@ -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
|
-
}
|
package/src/levelStep.d.mts
DELETED
|
@@ -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
|
-
};
|