@morphemecms/cms 0.3.0 → 0.4.0-next-20261104164754
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 +12 -0
- package/README.md +1 -1
- package/bin/morpheme.mjs +1 -1
- package/defaults/editor.ts +3 -1
- package/lib/changelog.d.mts +24 -0
- package/lib/changelog.mjs +65 -0
- package/lib/changelog.test.d.mts +1 -0
- package/lib/changelog.test.mjs +67 -0
- package/lib/cms-version.d.mts +56 -0
- package/lib/cms-version.mjs +85 -0
- package/lib/cms-version.test.d.mts +1 -0
- package/lib/cms-version.test.mjs +68 -0
- package/lib/lockfile.d.mts +19 -0
- package/lib/lockfile.mjs +135 -0
- package/lib/lockfile.test.d.mts +1 -0
- package/lib/lockfile.test.mjs +88 -0
- package/lib/range.d.mts +4 -0
- package/lib/range.mjs +104 -0
- package/lib/range.test.d.mts +1 -0
- package/lib/range.test.mjs +46 -0
- package/package.json +2 -1
- package/scripts/built-site.mjs +14 -0
- package/scripts/check-cms-update.mjs +405 -0
- package/site/theme.d.mts +1 -1
- package/site/theme.mjs +2 -2
- package/src/App.tsx +3 -0
- package/src/CmsPage.tsx +139 -0
- package/src/Library.tsx +2 -0
- package/src/appFiles.ts +2 -2
- package/src/apps.ts +2 -2
- package/src/blocks.ts +1 -1
- package/src/cmsView.d.mts +15 -0
- package/src/cmsView.mjs +78 -0
- package/src/cmsView.test.d.mts +1 -0
- package/src/cmsView.test.mjs +146 -0
- package/src/frontmatter.mjs +4 -1
- package/src/frontmatter.test.mjs +6 -0
- package/src/i18n/da-DK/cms.ts +30 -0
- package/src/i18n/da-DK/common.ts +1 -0
- package/src/i18n/da-DK/index.ts +2 -0
- package/src/i18n/en-US/cms.ts +34 -0
- package/src/i18n/en-US/common.ts +2 -0
- package/src/i18n/en-US/index.ts +2 -0
- package/src/registry.tsx +3 -3
- package/src/schema.ts +1 -1
- package/starter/.prettierignore +1 -0
- package/starter/README.md +1 -0
- package/starter/package.json +1 -1
- package/starter/wrangler.jsonc +1 -1
- package/worker/cms-update.js +245 -0
- package/worker/github.js +8 -6
- package/worker/index.js +6 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function cmsView(status: any, now?: number): {
|
|
2
|
+
running: any;
|
|
3
|
+
checkedAt: any;
|
|
4
|
+
latest: any;
|
|
5
|
+
notes: any[];
|
|
6
|
+
action: string | null;
|
|
7
|
+
npmCommand: string | null;
|
|
8
|
+
update: {
|
|
9
|
+
state: any;
|
|
10
|
+
to: any;
|
|
11
|
+
at: any;
|
|
12
|
+
} | null;
|
|
13
|
+
problems: any[];
|
|
14
|
+
};
|
|
15
|
+
export const DONE_SHOWN: number;
|
package/src/cmsView.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Transforms CMS status into a view for the update page.
|
|
2
|
+
// Exposes cmsView and DONE_SHOWN.
|
|
3
|
+
// Depends on cms-version for version comparison.
|
|
4
|
+
|
|
5
|
+
import { compareVersions } from '../lib/cms-version.mjs';
|
|
6
|
+
|
|
7
|
+
export const DONE_SHOWN = 24 * 60 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
export function cmsView(status, now = Date.now()) {
|
|
10
|
+
const running = status.running;
|
|
11
|
+
const checkedAt = status.checked?.at ?? null;
|
|
12
|
+
|
|
13
|
+
let latest = null;
|
|
14
|
+
if (status.checked?.latest) {
|
|
15
|
+
try {
|
|
16
|
+
if (compareVersions(status.checked.latest, running) === 1) {
|
|
17
|
+
latest = status.checked.latest;
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// Pre-release or invalid version, ignore
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let notes = [];
|
|
25
|
+
if (latest !== null && status.notes && status.notes.length > 0) {
|
|
26
|
+
notes = [...status.notes];
|
|
27
|
+
try {
|
|
28
|
+
notes.sort((a, b) => compareVersions(b.version, a.version));
|
|
29
|
+
} catch {
|
|
30
|
+
// Keep original order if sorting fails
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const busy = status.update?.state === 'waiting' || status.update?.state === 'undoing';
|
|
35
|
+
|
|
36
|
+
let action = null;
|
|
37
|
+
if (latest !== null && !busy) {
|
|
38
|
+
if (status.checked?.problem?.kind === 'major') {
|
|
39
|
+
action = 'update';
|
|
40
|
+
} else if (status.checked?.problem?.kind === 'skipped') {
|
|
41
|
+
action = 'try-again';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let npmCommand = null;
|
|
46
|
+
if (latest !== null && status.checked?.problem?.kind === 'needs-npm') {
|
|
47
|
+
npmCommand = `npm install @morphemecms/cms@${latest}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let update = null;
|
|
51
|
+
if (status.update) {
|
|
52
|
+
const state = status.update.state;
|
|
53
|
+
if (state === 'waiting' || state === 'undoing' || state === 'undone' || state === 'stuck') {
|
|
54
|
+
update = { state, to: status.update.to, at: status.update.at };
|
|
55
|
+
} else if (state === 'done') {
|
|
56
|
+
const at = Date.parse(status.update.at);
|
|
57
|
+
if (now - at < DONE_SHOWN) {
|
|
58
|
+
update = { state, to: status.update.to, at: status.update.at };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const problems = [];
|
|
64
|
+
if (status.token === false) {
|
|
65
|
+
problems.push('no-token');
|
|
66
|
+
}
|
|
67
|
+
if (status.checked?.problem) {
|
|
68
|
+
const kind = status.checked.problem.kind;
|
|
69
|
+
if (kind === 'unreachable' || kind === 'cannot-write' || kind === 'not-managed') {
|
|
70
|
+
problems.push(kind);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (status.checked?.noChecks === true) {
|
|
74
|
+
problems.push('slow-undo');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { running, checkedAt, latest, notes, action, npmCommand, update, problems };
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Tests for cmsView.
|
|
2
|
+
// Verifies status transformation and output shape.
|
|
3
|
+
// Run: node --test packages/editor/src/cmsView.test.mjs
|
|
4
|
+
|
|
5
|
+
import { test } from 'node:test';
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { cmsView } from './cmsView.mjs';
|
|
8
|
+
|
|
9
|
+
const baseStatus = {
|
|
10
|
+
running: '0.1.1',
|
|
11
|
+
token: true,
|
|
12
|
+
kv: true,
|
|
13
|
+
checked: { at: '2026-11-04T10:00:00.000Z', latest: '0.1.1' },
|
|
14
|
+
update: null,
|
|
15
|
+
skipped: [],
|
|
16
|
+
notes: [],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const now = Date.parse('2026-11-04T12:00:00.000Z');
|
|
20
|
+
|
|
21
|
+
function makeStatus(overrides) {
|
|
22
|
+
const result = { ...baseStatus, ...overrides };
|
|
23
|
+
|
|
24
|
+
if (!('checked' in overrides)) {
|
|
25
|
+
result.checked = baseStatus.checked;
|
|
26
|
+
} else if (overrides.checked === null) {
|
|
27
|
+
result.checked = null;
|
|
28
|
+
} else {
|
|
29
|
+
result.checked = { ...baseStatus.checked, ...overrides.checked };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!('update' in overrides)) {
|
|
33
|
+
result.update = baseStatus.update;
|
|
34
|
+
} else if (overrides.update === null) {
|
|
35
|
+
result.update = null;
|
|
36
|
+
} else {
|
|
37
|
+
result.update = { ...baseStatus.update, ...overrides.update };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test('up to date', () => {
|
|
44
|
+
const view = cmsView(baseStatus, now);
|
|
45
|
+
assert.equal(view.latest, null);
|
|
46
|
+
assert.equal(view.action, null);
|
|
47
|
+
assert.equal(view.npmCommand, null);
|
|
48
|
+
assert.equal(view.update, null);
|
|
49
|
+
assert.deepEqual(view.problems, []);
|
|
50
|
+
assert.equal(view.checkedAt, '2026-11-04T10:00:00.000Z');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('not checked', () => {
|
|
54
|
+
const status = makeStatus({ checked: null });
|
|
55
|
+
const view = cmsView(status, now);
|
|
56
|
+
assert.equal(view.checkedAt, null);
|
|
57
|
+
assert.equal(view.latest, null);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('patch waiting', () => {
|
|
61
|
+
const status = makeStatus({
|
|
62
|
+
checked: { latest: '0.1.2' },
|
|
63
|
+
update: { commit: 'a1', from: '0.1.1', to: '0.1.2', at: '2026-11-04T11:00:00.000Z', state: 'waiting' },
|
|
64
|
+
});
|
|
65
|
+
const view = cmsView(status, now);
|
|
66
|
+
assert.equal(view.update.state, 'waiting');
|
|
67
|
+
assert.equal(view.update.to, '0.1.2');
|
|
68
|
+
assert.equal(view.action, null);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('major', () => {
|
|
72
|
+
const status = makeStatus({ checked: { latest: '1.0.0', problem: { kind: 'major' } } });
|
|
73
|
+
const view = cmsView(status, now);
|
|
74
|
+
assert.equal(view.latest, '1.0.0');
|
|
75
|
+
assert.equal(view.action, 'update');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('major while undoing', () => {
|
|
79
|
+
const status = makeStatus({
|
|
80
|
+
checked: { latest: '1.0.0', problem: { kind: 'major' } },
|
|
81
|
+
update: { commit: 'a1', from: '1.0.0', to: '0.1.1', at: '2026-11-04T11:00:00.000Z', state: 'undoing' },
|
|
82
|
+
});
|
|
83
|
+
const view = cmsView(status, now);
|
|
84
|
+
assert.equal(view.action, null);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('skipped', () => {
|
|
88
|
+
const status = makeStatus({ checked: { latest: '0.1.3', problem: { kind: 'skipped' } } });
|
|
89
|
+
const view = cmsView(status, now);
|
|
90
|
+
assert.equal(view.action, 'try-again');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('needs npm', () => {
|
|
94
|
+
const status = makeStatus({ checked: { latest: '0.1.2', problem: { kind: 'needs-npm', detail: 'x' } } });
|
|
95
|
+
const view = cmsView(status, now);
|
|
96
|
+
assert.equal(view.npmCommand, 'npm install @morphemecms/cms@0.1.2');
|
|
97
|
+
assert.equal(view.action, null);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('no token', () => {
|
|
101
|
+
const status = makeStatus({ token: false });
|
|
102
|
+
const view = cmsView(status, now);
|
|
103
|
+
assert.deepEqual(view.problems, ['no-token']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('cannot write', () => {
|
|
107
|
+
const status = makeStatus({ checked: { problem: { kind: 'cannot-write' } } });
|
|
108
|
+
const view = cmsView(status, now);
|
|
109
|
+
assert.deepEqual(view.problems, ['cannot-write']);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('slow undo', () => {
|
|
113
|
+
const status = makeStatus({ checked: { noChecks: true } });
|
|
114
|
+
const view = cmsView(status, now);
|
|
115
|
+
assert.ok(view.problems.includes('slow-undo'));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('done two days ago', () => {
|
|
119
|
+
const status = makeStatus({ update: { commit: 'a1', from: '0.1.1', to: '0.1.2', at: '2026-11-02T12:00:00.000Z', state: 'done' } });
|
|
120
|
+
const view = cmsView(status, now);
|
|
121
|
+
assert.equal(view.update, null);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('done an hour ago', () => {
|
|
125
|
+
const status = makeStatus({ update: { commit: 'a1', from: '0.1.1', to: '0.1.2', at: '2026-11-04T11:00:00.000Z', state: 'done' } });
|
|
126
|
+
const view = cmsView(status, now);
|
|
127
|
+
assert.equal(view.update.state, 'done');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('notes order', () => {
|
|
131
|
+
const status = makeStatus({
|
|
132
|
+
checked: { latest: '0.1.3' },
|
|
133
|
+
notes: [
|
|
134
|
+
{ version: '0.1.2', text: 'b' },
|
|
135
|
+
{ version: '0.1.3', text: 'c' },
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
const view = cmsView(status, now);
|
|
139
|
+
assert.equal(view.notes[0].version, '0.1.3');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('pre-release running', () => {
|
|
143
|
+
const status = makeStatus({ running: '0.2.0-next.1', checked: { latest: '0.2.0' } });
|
|
144
|
+
const view = cmsView(status, now);
|
|
145
|
+
assert.equal(view.latest, null);
|
|
146
|
+
});
|
package/src/frontmatter.mjs
CHANGED
|
@@ -28,7 +28,10 @@ export function writeFields(yaml, changes) {
|
|
|
28
28
|
if (value !== '' && value != null) doc.set(key, toNode(doc, key, value));
|
|
29
29
|
else if (doc.has(key)) doc.delete(key);
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
if (!doc.contents?.items?.length) return '';
|
|
32
|
+
const text = doc.toString(STRINGIFY).replace(/\n$/, '');
|
|
33
|
+
// The yaml package writes \n; a file with \r\n keeps its line breaks (a checkout on Windows).
|
|
34
|
+
return yaml.includes('\r\n') ? text.replace(/\n/g, '\r\n') : text;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
function toNode(doc, key, value) {
|
package/src/frontmatter.test.mjs
CHANGED
|
@@ -25,3 +25,9 @@ test('a switch without a missing value is written as it is', () => {
|
|
|
25
25
|
assert.equal(booleanWrite(true, undefined), true);
|
|
26
26
|
assert.equal(booleanWrite(false, undefined), false);
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
test('front matter with CRLF line breaks is written back unchanged, and added fields use CRLF too', () => {
|
|
30
|
+
const crlf = 'title: "Om os"\r\nwide: false';
|
|
31
|
+
assert.equal(writeFields(crlf, {}), crlf);
|
|
32
|
+
assert.equal(writeFields(crlf, { side: false }), `${crlf}\r\nside: false`);
|
|
33
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Messages } from '../en-US';
|
|
2
|
+
|
|
3
|
+
export const cms: Messages['cms'] = {
|
|
4
|
+
running: (v) => `Version ${v}`,
|
|
5
|
+
checked: (when) => `Tjekket ${when}`,
|
|
6
|
+
notChecked: 'Ikke tjekket endnu',
|
|
7
|
+
upToDate: 'Opdateret',
|
|
8
|
+
available: (v) => `Version ${v} er klar`,
|
|
9
|
+
whatsNew: 'Nyheder',
|
|
10
|
+
major: 'En ny hovedversion installeres ikke af sig selv.',
|
|
11
|
+
skipped: (v) => `${v} blev fortrudt, fordi siden ikke kunne bygges.`,
|
|
12
|
+
needsNpm: 'Den kræver npm. Kør dette i sitets repo:',
|
|
13
|
+
update: 'Opdater',
|
|
14
|
+
tryAgain: 'Prøv igen',
|
|
15
|
+
waiting: (v) => `Opdaterer til ${v}. Venter på, at siden bygges.`,
|
|
16
|
+
undoing: (v) => `Fortryder opdateringen til ${v}.`,
|
|
17
|
+
done: (v) => `Opdateret til ${v}`,
|
|
18
|
+
undone: (v) => `Opdateringen til ${v} blev fortrudt, fordi siden ikke kunne bygges.`,
|
|
19
|
+
stuck: (v) => `Hverken opdateringen til ${v} eller fortrydelsen kunne bygges. Siden skal rettes i hånden.`,
|
|
20
|
+
unreachable: 'npm eller GitHub kunne ikke nås.',
|
|
21
|
+
cannotWrite: 'Sitets GitHub-nøgle kan ikke gemme. Giv den Contents: Read and write.',
|
|
22
|
+
noToken: 'Sitet har ingen GitHub-nøgle (GITHUB_PUBLISH_TOKEN).',
|
|
23
|
+
notManaged: 'Sitets package.json bestemmer versionen.',
|
|
24
|
+
slowUndo: 'Nøglen kan ikke læse check runs, så en fejlet opdatering fortrydes først efter 20 minutter.',
|
|
25
|
+
notifyDone: (v) => `Morpheme er opdateret til ${v}`,
|
|
26
|
+
notifyUndone: (v) => `Opdateringen af Morpheme til ${v} er fortrudt, fordi siden ikke kunne bygges`,
|
|
27
|
+
notifyStuck: (v) => `Morpheme ${v} kunne ikke bygges, og det kunne fortrydelsen heller ikke. Ret siden i hånden.`,
|
|
28
|
+
commitUpdate: (v) => `Opdater Morpheme til ${v}`,
|
|
29
|
+
commitUndo: (v) => `Fortryd opdateringen af Morpheme til ${v}`,
|
|
30
|
+
};
|
package/src/i18n/da-DK/common.ts
CHANGED
|
@@ -110,6 +110,7 @@ export const common: Messages['common'] = {
|
|
|
110
110
|
settings: 'Indstillinger',
|
|
111
111
|
advanced: 'Avanceret',
|
|
112
112
|
licenses: 'Licenser',
|
|
113
|
+
morpheme: 'Morpheme',
|
|
113
114
|
},
|
|
114
115
|
saved: (change) => `${change}. Du får besked, når hjemmesiden er opdateret.`,
|
|
115
116
|
today: 'i dag',
|
package/src/i18n/da-DK/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { apps } from './apps';
|
|
|
6
6
|
import { blocks } from './blocks';
|
|
7
7
|
import { blockSettings } from './blockSettings';
|
|
8
8
|
import { check } from './check';
|
|
9
|
+
import { cms } from './cms';
|
|
9
10
|
import { colors } from './colors';
|
|
10
11
|
import { common } from './common';
|
|
11
12
|
import { company } from './company';
|
|
@@ -41,6 +42,7 @@ export const daDK: Messages = {
|
|
|
41
42
|
seo,
|
|
42
43
|
keywords,
|
|
43
44
|
licenses,
|
|
45
|
+
cms,
|
|
44
46
|
parts,
|
|
45
47
|
history,
|
|
46
48
|
preview,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// "Morpheme" under "Indstillinger" (the CMS's update page, #morpheme): the running version, a new one and its notes,
|
|
2
|
+
// and an update's progress; also the Worker's notifications and commit messages (worker/cms-update.js).
|
|
3
|
+
|
|
4
|
+
export const cms = {
|
|
5
|
+
running: (v: string) => `Version ${v}`,
|
|
6
|
+
/** When the Worker last asked npm, as when.ts writes it. */
|
|
7
|
+
checked: (when: string) => `Checked ${when}`,
|
|
8
|
+
notChecked: 'Not checked yet',
|
|
9
|
+
upToDate: 'Up to date',
|
|
10
|
+
available: (v: string) => `Version ${v} is available`,
|
|
11
|
+
whatsNew: 'What’s new',
|
|
12
|
+
major: 'A new major version isn’t installed by itself.',
|
|
13
|
+
skipped: (v: string) => `${v} was undone because the site couldn’t be built.`,
|
|
14
|
+
/** Followed by the command, in a code block. */
|
|
15
|
+
needsNpm: 'It needs npm. Run this in the site’s repository:',
|
|
16
|
+
update: 'Update',
|
|
17
|
+
tryAgain: 'Try again',
|
|
18
|
+
waiting: (v: string) => `Updating to ${v}. Waiting for the site to be built.`,
|
|
19
|
+
undoing: (v: string) => `Undoing the update to ${v}.`,
|
|
20
|
+
done: (v: string) => `Updated to ${v}`,
|
|
21
|
+
undone: (v: string) => `The update to ${v} was undone because the site couldn’t be built.`,
|
|
22
|
+
stuck: (v: string) => `The update to ${v} and its undo couldn’t be built. The site must be fixed by hand.`,
|
|
23
|
+
unreachable: 'npm or GitHub couldn’t be reached.',
|
|
24
|
+
cannotWrite: 'The site’s GitHub token can’t save. Give it Contents: Read and write.',
|
|
25
|
+
noToken: 'The site has no GitHub token (GITHUB_PUBLISH_TOKEN).',
|
|
26
|
+
notManaged: 'The site’s package.json decides the version.',
|
|
27
|
+
slowUndo: 'The token can’t read check runs, so a failed update is undone after 20 minutes.',
|
|
28
|
+
/** The site's notifications (Notifications.tsx) and the commits the Worker writes, in the site's language. */
|
|
29
|
+
notifyDone: (v: string) => `Morpheme was updated to ${v}`,
|
|
30
|
+
notifyUndone: (v: string) => `The update of Morpheme to ${v} was undone because the site couldn’t be built`,
|
|
31
|
+
notifyStuck: (v: string) => `Morpheme ${v} couldn’t be built, and neither could its undo. Fix the site by hand.`,
|
|
32
|
+
commitUpdate: (v: string) => `Update Morpheme to ${v}`,
|
|
33
|
+
commitUndo: (v: string) => `Undo the update of Morpheme to ${v}`,
|
|
34
|
+
};
|
package/src/i18n/en-US/common.ts
CHANGED
|
@@ -134,6 +134,8 @@ export const common = {
|
|
|
134
134
|
advanced: 'Advanced',
|
|
135
135
|
/** Under Indstillinger: the licences of what the site and the editor are made with (#licenser, Licenses.tsx). */
|
|
136
136
|
licenses: 'Licenses',
|
|
137
|
+
/** Under Indstillinger: the CMS's version and its updates (#morpheme). */
|
|
138
|
+
morpheme: 'Morpheme',
|
|
137
139
|
},
|
|
138
140
|
/** Right after a save, with what it changed (savedText in deploy.ts); the site is rebuilt in 1-2 minutes. */
|
|
139
141
|
saved: (change: string) => `${change}. You’ll get a message when the website is updated.`,
|
package/src/i18n/en-US/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { apps } from './apps';
|
|
|
6
6
|
import { blocks } from './blocks';
|
|
7
7
|
import { blockSettings } from './blockSettings';
|
|
8
8
|
import { check } from './check';
|
|
9
|
+
import { cms } from './cms';
|
|
9
10
|
import { colors } from './colors';
|
|
10
11
|
import { common } from './common';
|
|
11
12
|
import { company } from './company';
|
|
@@ -41,6 +42,7 @@ export const enUS = {
|
|
|
41
42
|
seo,
|
|
42
43
|
keywords,
|
|
43
44
|
licenses,
|
|
45
|
+
cms,
|
|
44
46
|
parts,
|
|
45
47
|
history,
|
|
46
48
|
preview,
|
package/src/registry.tsx
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// and its look is the theme's (packages/editor/theme/blocks/<type>/, or the theme in use's): View.tsx, which draws its
|
|
10
10
|
// view in the editor (blockViews, from standard/themeBlocks.ts) and on the site. A new folder is a new block, in the editor and on
|
|
11
11
|
// the site; nothing else lists them. The apps (src/apps/<app>/, apps.ts) add blocks the same way, in their folder's
|
|
12
|
-
// blocks/, each with its own View.tsx
|
|
13
|
-
// only the look of the standard ones.
|
|
12
|
+
// blocks/, each with its own View.tsx and viewOf.ts for view.ts (it would differ from View.tsx only in case); they
|
|
13
|
+
// are offered once the app is installed. A theme has no blocks of its own, only the look of the standard ones.
|
|
14
14
|
import type { ReactNode } from 'react';
|
|
15
15
|
import { appIdOf, installedApps } from './apps';
|
|
16
16
|
import { htmlBlock, type BlockData, type BlockDefinition, type BlockProps, type DrawBlock, type SiteBlock } from './blocks';
|
|
@@ -60,7 +60,7 @@ export const siteBlocks: SiteBlock[] = [
|
|
|
60
60
|
...Object.entries(fromApps).map(([path, m]) =>
|
|
61
61
|
localSiteBlock({
|
|
62
62
|
...m.default,
|
|
63
|
-
draw: drawn(appViews[beside(path, 'View.tsx')]?.default, appViewOfs[beside(path, '
|
|
63
|
+
draw: drawn(appViews[beside(path, 'View.tsx')]?.default, appViewOfs[beside(path, 'viewOf.ts')]?.viewOf),
|
|
64
64
|
type: typeOf(path),
|
|
65
65
|
app: appIdOf(path),
|
|
66
66
|
}),
|
package/src/schema.ts
CHANGED
|
@@ -264,7 +264,7 @@ export const entryName = (collection: FolderCollection, data: Record<string, unk
|
|
|
264
264
|
|
|
265
265
|
/** An app's part of an entry of its kind, drawn in the list's own drawing (a Mailchimp signup in a pop-up's box), on the
|
|
266
266
|
* site and in the editor alike: its drawing (`View`, given its view and the entry's own button to show where it wants)
|
|
267
|
-
* and what makes its view in the editor (`viewOf`, as
|
|
267
|
+
* and what makes its view in the editor (`viewOf`, in its viewOf.ts, as an app's block). */
|
|
268
268
|
export type KindPart = {
|
|
269
269
|
View: ComponentType<{ view: any; button: ReactNode }>;
|
|
270
270
|
viewOf: (values: Record<string, any>, data: BlockData) => unknown;
|
package/starter/.prettierignore
CHANGED
package/starter/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Editoren, standardtemaet *Material* og sitets Worker er pakken [`@morphemecms/cm
|
|
|
9
9
|
|
|
10
10
|
- `src/site/config.ts`: sitets navn, domæne, sprog og GitHub-repo; resten er pakkens standard (`defineSite`).
|
|
11
11
|
- `src/site/worker.js`: det, Workeren har fra sitet.
|
|
12
|
+
- Hemmeligheden `GITHUB_PUBLISH_TOKEN` (`npx wrangler secret put GITHUB_PUBLISH_TOKEN`) lader Workeren skrive til sitets GitHub-repo, også når den opdaterer CMS'et. Tokenet skal have **Contents: Read and write** og **Checks: Read**.
|
|
12
13
|
- Hemmeligheden `MORPHEME_STORE_KEY` (`npx wrangler secret put MORPHEME_STORE_KEY`) forbinder sitet til Morpheme-butikken; uden den har *Appstore* kun sitets egne apps og temaer.
|
|
13
14
|
- `src/content/`: siderne og indlæggene, `src/data/`: sitets indstillinger, `public/`: billeder, logo og ikoner.
|
|
14
15
|
- `src/themes/` og `src/apps/`: temaer og apps fra appbutikken, når der er installeret nogen.
|
package/starter/package.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@fontsource-variable/roboto": "^5.3.0",
|
|
32
32
|
"@material/material-color-utilities": "^0.3.0",
|
|
33
33
|
"@milkdown/kit": "^7.22.3",
|
|
34
|
-
"@morphemecms/cms": "
|
|
34
|
+
"@morphemecms/cms": "0.3.0",
|
|
35
35
|
"@phosphor-icons/core": "^2.1.1",
|
|
36
36
|
"@phosphor-icons/react": "^2.1.10",
|
|
37
37
|
"@tailwindcss/typography": "^0.5.20",
|
package/starter/wrangler.jsonc
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"$schema": "node_modules/wrangler/config-schema.json",
|
|
6
6
|
"name": "morpheme-site",
|
|
7
7
|
"compatibility_date": "2026-09-01",
|
|
8
|
-
"compatibility_flags": ["nodejs_compat"],
|
|
8
|
+
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], // public fetch: the store is on this account
|
|
9
9
|
"main": "node_modules/@morphemecms/cms/worker/index.js",
|
|
10
10
|
// What the Worker has from this site: its settings and src/site/worker.js (its redirects, apps and contact).
|
|
11
11
|
"alias": { "@site/config": "./src/site/config.ts", "@site/worker": "./src/site/worker.js" },
|