@morphemecms/cms 0.14.1 → 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 +7 -0
- package/lib/colors.ts +4 -0
- package/lib/releaseNotes.d.mts +13 -0
- package/lib/releaseNotes.mjs +21 -2
- package/lib/releaseNotes.test.mjs +18 -0
- 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/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/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/src/CmsPage.tsx +1 -1
- package/src/ReleaseNotes.tsx +25 -26
- package/src/UpdatesNotice.tsx +99 -15
- 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/theme/styles/global.css +10 -8
- package/theme/styles/ui.css +5 -1
- package/src/ReleaseNoteCard.tsx +0 -17
- package/src/UpdateLink.tsx +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @morphemecms/cms
|
|
2
2
|
|
|
3
|
+
## 0.14.2-next-20261107122546
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 13dbbca: Lists follow Material 3's list spec: the label text in body large (16/24, weight 400), also beside a switch, and the editor's rows on Surface over a page in Surface Container.
|
|
8
|
+
- 3fbb65f: The start screen's update card shows the new version and its first news, and the Morpheme page shows the release notes as an article.
|
|
9
|
+
|
|
3
10
|
## 0.14.1
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/lib/colors.ts
CHANGED
|
@@ -43,6 +43,10 @@ function declarations(set: ColorSet): string {
|
|
|
43
43
|
.join('');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** The colour of the editor's page in a mode: Surface Container, which Material 3's segmented lists stand on (editor.css),
|
|
47
|
+
* or the background when the colours have no such role. */
|
|
48
|
+
export const pageColor = (set: ColorSet): string => set['surface-container'] ?? set.bg;
|
|
49
|
+
|
|
46
50
|
/** The colours as CSS for a <style> in the <head>: light mode on :root; dark mode when the phone or
|
|
47
51
|
* computer is set to it (unless light is picked, data-theme="light" on <html>) and when dark is picked.
|
|
48
52
|
* Throws on anything that is not a colour, so a broken file never reaches a page. */
|
package/lib/releaseNotes.d.mts
CHANGED
|
@@ -37,6 +37,19 @@ export function imagePath(version: string, locale: string, id: string): string;
|
|
|
37
37
|
* @returns {ReleaseNote[] | undefined}
|
|
38
38
|
*/
|
|
39
39
|
export function notesFor(notes: Record<string, ReleaseNote[]> | undefined, locale: string): ReleaseNote[] | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The notes that are new since `from` (the version seen last), in `locale` (else English): those of the entries newer than
|
|
42
|
+
* it, the newest version first, its cards before its lines. A test version (0.14.1-next.2) has not had the notes of its
|
|
43
|
+
* own version yet. Nothing when `from` is not a version.
|
|
44
|
+
* @param {{ version: string, notes?: Record<string, ReleaseNote[]> }[]} entries
|
|
45
|
+
* @param {string | undefined} from
|
|
46
|
+
* @param {string} locale
|
|
47
|
+
* @returns {ReleaseNote[]}
|
|
48
|
+
*/
|
|
49
|
+
export function notesSince(entries: {
|
|
50
|
+
version: string;
|
|
51
|
+
notes?: Record<string, ReleaseNote[]>;
|
|
52
|
+
}[], from: string | undefined, locale: string): ReleaseNote[];
|
|
40
53
|
/**
|
|
41
54
|
* The notes of every released version in the running minor line (x.y.0 up to `version`), from the files the package carries
|
|
42
55
|
* (release-notes/i18n/<locale>/<version>.json), newest first. A card gets the address of its screenshot
|
package/lib/releaseNotes.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Pure helpers for release notes: checks pending notes (cards with a screenshot of the right type and size, lines), groups them per version and locale,
|
|
2
|
-
// picks a locale's notes, builds the notes of the running minor line from the package files and reads a version's notes and screenshots from its tar.
|
|
2
|
+
// picks a locale's notes and those new since a version, builds the notes of the running minor line from the package files and reads a version's notes and screenshots from its tar.
|
|
3
3
|
// Also builds the Worker's screenshot address and KV key, parses the address and adds the address to a version's cards.
|
|
4
|
-
// Exposes MAX_IMAGE_BYTES, IMAGE_WIDTH, MAX_IMAGE_HEIGHT, webpSize, problemsOf, released, imagePath, notesFor, bundledNotes, staleImageDirs, notesFromTar, imageUrl, imageKey,
|
|
4
|
+
// Exposes MAX_IMAGE_BYTES, IMAGE_WIDTH, MAX_IMAGE_HEIGHT, webpSize, problemsOf, released, imagePath, notesFor, notesSince, bundledNotes, staleImageDirs, notesFromTar, imageUrl, imageKey,
|
|
5
5
|
// parseImagePath and withImageUrls. Depends on ./cms-version.mjs and ./changelog.mjs; no node: imports.
|
|
6
6
|
import { compareVersions } from './cms-version.mjs';
|
|
7
7
|
import { bytesFromTar, fileFromTar } from './changelog.mjs';
|
|
@@ -155,6 +155,25 @@ export function notesFor(notes, locale) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
const PLAIN = /^\d+\.\d+\.\d+$/;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The notes that are new since `from` (the version seen last), in `locale` (else English): those of the entries newer than
|
|
161
|
+
* it, the newest version first, its cards before its lines. A test version (0.14.1-next.2) has not had the notes of its
|
|
162
|
+
* own version yet. Nothing when `from` is not a version.
|
|
163
|
+
* @param {{ version: string, notes?: Record<string, ReleaseNote[]> }[]} entries
|
|
164
|
+
* @param {string | undefined} from
|
|
165
|
+
* @param {string} locale
|
|
166
|
+
* @returns {ReleaseNote[]}
|
|
167
|
+
*/
|
|
168
|
+
export function notesSince(entries, from, locale) {
|
|
169
|
+
const [base, pre] = String(from ?? '').split('-');
|
|
170
|
+
if (!PLAIN.test(base)) return [];
|
|
171
|
+
const notes = entries
|
|
172
|
+
.filter((entry) => PLAIN.test(entry.version) && compareVersions(entry.version, base) >= (pre === undefined ? 1 : 0))
|
|
173
|
+
.sort((a, b) => compareVersions(b.version, a.version))
|
|
174
|
+
.flatMap((entry) => notesFor(entry.notes, locale) ?? []);
|
|
175
|
+
return [...notes.filter((note) => note.kind === 'card'), ...notes.filter((note) => note.kind === 'line')];
|
|
176
|
+
}
|
|
158
177
|
const sameMinor = (a, b) => a.split('.').slice(0, 2).join('.') === b.split('.').slice(0, 2).join('.');
|
|
159
178
|
|
|
160
179
|
/**
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
released,
|
|
12
12
|
imagePath,
|
|
13
13
|
notesFor,
|
|
14
|
+
notesSince,
|
|
14
15
|
bundledNotes,
|
|
15
16
|
staleImageDirs,
|
|
16
17
|
notesFromTar,
|
|
@@ -164,6 +165,23 @@ test('notesFor falls back to the en-US notes', () => {
|
|
|
164
165
|
});
|
|
165
166
|
test('notesFor gives undefined without notes', () => assert.equal(notesFor(undefined, 'da-DK'), undefined));
|
|
166
167
|
|
|
168
|
+
const shipped = (version, notes) => ({ version, text: '', notes: { 'en-US': notes } });
|
|
169
|
+
const ids = (notes) => notes.map((note) => note.id);
|
|
170
|
+
|
|
171
|
+
test('notesSince gives the notes of the newer versions, newest first, cards before lines', () => {
|
|
172
|
+
const entries = [shipped('0.14.0', [card('a'), line('b')]), shipped('0.14.2', [line('c'), card('d')]), shipped('0.14.1', [line('e')])];
|
|
173
|
+
assert.deepEqual(ids(notesSince(entries, '0.14.0', 'da-DK')), ['d', 'c', 'e']);
|
|
174
|
+
assert.deepEqual(ids(notesSince(entries, '0.13.4', 'en-US')), ['d', 'a', 'c', 'e', 'b']);
|
|
175
|
+
assert.deepEqual(notesSince(entries, '0.14.2', 'en-US'), []);
|
|
176
|
+
});
|
|
177
|
+
test('notesSince counts the version of a test version as new', () => {
|
|
178
|
+
assert.deepEqual(ids(notesSince([shipped('0.14.1', [line('a')])], '0.14.1-next.2', 'en-US')), ['a']);
|
|
179
|
+
});
|
|
180
|
+
test('notesSince gives nothing without a version seen', () => {
|
|
181
|
+
assert.deepEqual(notesSince([shipped('0.14.1', [line('a')])], undefined, 'en-US'), []);
|
|
182
|
+
assert.deepEqual(notesSince([shipped('0.14.1', [line('a')])], 'latest', 'en-US'), []);
|
|
183
|
+
});
|
|
184
|
+
|
|
167
185
|
const file = (version, notes) => ({ version, notes });
|
|
168
186
|
const dir = '../release-notes/i18n';
|
|
169
187
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morphemecms/cms",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2-next-20261107122546",
|
|
4
4
|
"description": "Morpheme CMS: the editor for a site built with Astro, its screens, its blocks and the rules a site gives it. See README.md.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Lars Adolfsen",
|
package/pages/editor/index.astro
CHANGED
|
@@ -29,6 +29,9 @@ import { colorRoles } from '@site/colors';
|
|
|
29
29
|
// "Bevægelse" in its place at once (packages/editor/src/theme.ts).
|
|
30
30
|
import { motionCss } from '@morphemecms/cms/lib/motion';
|
|
31
31
|
import { motionScheme } from '@morphemecms/cms/site/motion';
|
|
32
|
+
import { pageColor } from '@morphemecms/cms/lib/colors';
|
|
33
|
+
|
|
34
|
+
const shown = completeColors(colors, colorRoles);
|
|
32
35
|
---
|
|
33
36
|
|
|
34
37
|
<!doctype html>
|
|
@@ -48,11 +51,11 @@ import { motionScheme } from '@morphemecms/cms/site/motion';
|
|
|
48
51
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
49
52
|
<meta name="apple-mobile-web-app-title" content={config.name} />
|
|
50
53
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
|
51
|
-
<!-- The top bar in the
|
|
52
|
-
own mode, for a picked one. -->
|
|
53
|
-
<meta name="theme-color" content={
|
|
54
|
-
<meta name="theme-color" content={
|
|
55
|
-
<style id="site-colors" is:inline set:html={colorsCss(
|
|
54
|
+
<!-- The top bar in the editor's page colour (pageColor), the screens' own and the app bar's at the top; data-color: that
|
|
55
|
+
of its own mode, for a picked one. -->
|
|
56
|
+
<meta name="theme-color" content={pageColor(shown.light)} data-color={pageColor(shown.light)} media="(prefers-color-scheme: light)" />
|
|
57
|
+
<meta name="theme-color" content={pageColor(shown.dark)} data-color={pageColor(shown.dark)} media="(prefers-color-scheme: dark)" />
|
|
58
|
+
<style id="site-colors" is:inline set:html={colorsCss(shown)}></style>
|
|
56
59
|
<style id="site-motion" is:inline set:html={motionCss(motionScheme)}></style>
|
|
57
60
|
<!-- Light or dark as picked on "Konto" (packages/editor/src/theme.ts), before the page is drawn. The
|
|
58
61
|
language (lang) is the site's until the editor has picked its own (packages/editor/src/i18n/). -->
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
{
|
|
43
43
|
"id": "quiet-lamps-wander",
|
|
44
44
|
"kind": "card",
|
|
45
|
-
"title": "Se, hvad der er nyt
|
|
46
|
-
"text": "
|
|
45
|
+
"title": "Se, hvad der er nyt",
|
|
46
|
+
"text": "Det kan være svært at følge med i, hvad der er nyt i Morpheme. Derfor får du nu et kort overblik over de nye funktioner efter hver opdatering. Du finder det hele samlet på siden Morpheme."
|
|
47
47
|
}
|
|
48
48
|
]
|
|
49
49
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "update-card-whats-new",
|
|
3
|
+
"kind": "card",
|
|
4
|
+
"title": "Nyhederne møder dig på startskærmen",
|
|
5
|
+
"text": "Efter en opdatering skal du ikke selv lede efter, hvad der er nyt. Øverst på startskærmen ser du nu den nye version og de vigtigste nyheder, med et billede af den største. Tryk på Se, hvad der er nyt for at se det hele."
|
|
6
|
+
}
|
|
Binary file
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
{
|
|
43
43
|
"id": "quiet-lamps-wander",
|
|
44
44
|
"kind": "card",
|
|
45
|
-
"title": "See what's new
|
|
46
|
-
"text": "
|
|
45
|
+
"title": "See what's new",
|
|
46
|
+
"text": "It can be hard to keep up with what's new in Morpheme. So after each update, you now get a short overview of the new features. You'll find it all in one place on the Morpheme page."
|
|
47
47
|
}
|
|
48
48
|
]
|
|
49
49
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "update-card-whats-new",
|
|
3
|
+
"kind": "card",
|
|
4
|
+
"title": "What's new meets you on the start screen",
|
|
5
|
+
"text": "After an update, you shouldn't have to look for what's new yourself. The top of the start screen now shows the new version and the main news, with a picture of the biggest one. Tap See what's new to see it all."
|
|
6
|
+
}
|
|
Binary file
|
package/src/CmsPage.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// "Morpheme" (#morpheme), under "Indstillinger": the CMS version the site runs, the notes of its minor line (from the
|
|
2
2
|
// package itself, bundledNotes.ts), a newer one with its notes, why it is not taken by itself, and an update's progress, and the site's connection to the
|
|
3
3
|
// Morpheme store (MorphemeConnection) as its last group. Exposes CMS_ROUTE and the page. Depends on cmsView.mjs, bundledNotes.ts,
|
|
4
|
-
// ReleaseNotes (the notes as
|
|
4
|
+
// ReleaseNotes (the notes as an article), MorphemeConnection and the Worker's /api/cms.
|
|
5
5
|
import { useCallback, useEffect, useState } from 'react';
|
|
6
6
|
import { CheckCircle, Clock, Info, Sparkle, WarningCircle } from '@phosphor-icons/react';
|
|
7
7
|
import Screen from './Screen';
|
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
|
}
|
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/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/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
|
-
}
|