@morphemecms/cms 0.10.1-next-20261105180809 → 0.11.0
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 -1
- package/package.json +1 -1
- package/src/AppList.tsx +2 -1
- package/src/BlockDialog.tsx +1 -1
- package/src/CompanyForm.tsx +9 -1
- package/src/apps.ts +3 -0
- package/src/googleResult.d.mts +23 -11
- package/src/googleResult.mjs +11 -2
- package/src/i18n/da-DK/app.ts +2 -1
- package/src/i18n/en-US/app.ts +2 -1
- package/src/partChange.test.mjs +44 -0
- package/src/partChange.ts +24 -0
- package/src/schema.ts +5 -0
- package/starter/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# @morphemecms/cms
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 999d21e: Save notices name the version: app installs, and an app's settings through `changedTo`.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 5a64119: Sites type-check again: the block side sheet's save button narrows to the fields step before it reads the block's type, so `BlockDialog.tsx` no longer fails `tsc` in a site.
|
|
12
|
+
- 4d4d32a: Sites type-check again: the types of `searchedAs` and `pageTitle` make every input optional except `site`, so the share preview no longer fails `tsc` in a site.
|
|
13
|
+
|
|
14
|
+
## 0.10.1
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
|
6
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morphemecms/cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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/src/AppList.tsx
CHANGED
|
@@ -118,6 +118,7 @@ export function appSettingsPart(app: SiteApp): Part | null {
|
|
|
118
118
|
fields: app.settings.fields,
|
|
119
119
|
defaults: app.settings.values,
|
|
120
120
|
commit: siteT.apps.settingsOf(asWritten(app).label),
|
|
121
|
+
changedTo: app.settings.changedTo,
|
|
121
122
|
back: home ? { href: `#${home.route}`, label: home.label } : { href: appHref(app), label: app.label },
|
|
122
123
|
};
|
|
123
124
|
}
|
|
@@ -726,7 +727,7 @@ function useStoreActions(env: Env, files: RepoFile[]) {
|
|
|
726
727
|
busy: t.apps.installing,
|
|
727
728
|
done: t.apps.isInstalledSoon(app.label),
|
|
728
729
|
commit: siteT.apps.commit.install(app.written),
|
|
729
|
-
change: t.app.changes.installed(kindAndName(t.app.changes.app, app.label)),
|
|
730
|
+
change: t.app.changes.installed(kindAndName(t.app.changes.app, app.label), app.version),
|
|
730
731
|
},
|
|
731
732
|
async () => ({ files: await env.store.files(app), record: recordOf(app) }),
|
|
732
733
|
),
|
package/src/BlockDialog.tsx
CHANGED
|
@@ -372,7 +372,7 @@ export function useBlockDialog(
|
|
|
372
372
|
className={kind === 'standard' ? 'editor-side-sheet' : undefined}
|
|
373
373
|
actions={
|
|
374
374
|
<>
|
|
375
|
-
<Button onClick={() => (open.step === '
|
|
375
|
+
<Button onClick={() => (open.step === 'fields' ? save(open.type) : saveSection())}>
|
|
376
376
|
{open.step === 'fields' && open.isNew ? t.blocks.insert : t.common.save}
|
|
377
377
|
</Button>
|
|
378
378
|
<Button kind="outlined" onClick={() => void leave()}>
|
package/src/CompanyForm.tsx
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// app's settings, AppList.tsx appSettingsPart). A field with its choices on a page of their own (Field.page: an app's
|
|
6
6
|
// "Variant") has it at the form's address with its name after it (`at`, `page`, App.tsx): the form stays open under it
|
|
7
7
|
// with what is in it, saved or not (useFieldPages), a choice there goes back to it, and it comes back where it was.
|
|
8
|
+
// A save says what it changed through partChange.ts ("… set to <value>" when the part has `changedTo`).
|
|
8
9
|
// Exposes CompanyForm and usePartForm; the fields (with the block preview) are PartFields.tsx, a field's page PartFieldPage.tsx,
|
|
9
10
|
// the providers around them PartFormProviders.tsx.
|
|
10
11
|
import { useState } from 'react';
|
|
@@ -14,6 +15,7 @@ import PartFields, { hasPreview } from './PartFields';
|
|
|
14
15
|
import PartFieldPage from './PartFieldPage';
|
|
15
16
|
import PartFormProviders from './PartFormProviders';
|
|
16
17
|
import { useFieldPages } from './useFieldPages';
|
|
18
|
+
import { partChange } from './partChange';
|
|
17
19
|
import type { Part } from './schema';
|
|
18
20
|
import { clean, problemAt } from './check';
|
|
19
21
|
import { useWrongField, type FieldContext } from './fields';
|
|
@@ -69,10 +71,16 @@ export function usePartForm({ part, env, files }: Props) {
|
|
|
69
71
|
if (kept) return notify(kept);
|
|
70
72
|
const message = siteT.company.commit.edit(part.commit ?? asWritten(part).label.toLowerCase());
|
|
71
73
|
const changes = [{ path, content: text, oid: base.oid || null }];
|
|
72
|
-
const change = t.app.changes.edited(lower(part.label));
|
|
73
74
|
setSaving(true);
|
|
74
75
|
notify({ text: t.common.saving });
|
|
75
76
|
try {
|
|
77
|
+
const change = await partChange(
|
|
78
|
+
lower(part.label),
|
|
79
|
+
cleaned,
|
|
80
|
+
part.changedTo,
|
|
81
|
+
{ api: (p, init) => env.appApi(part.id, p, init) },
|
|
82
|
+
t.app.changes,
|
|
83
|
+
);
|
|
76
84
|
const oids = await env.commit(message, changes, change);
|
|
77
85
|
const saved = { path, oid: oids[path], text };
|
|
78
86
|
setBase(saved);
|
package/src/apps.ts
CHANGED
|
@@ -55,6 +55,9 @@ export type AppSettings = {
|
|
|
55
55
|
/** Made-up settings over `values`, which its blocks and kinds are drawn with under "Dele" (exampleData in
|
|
56
56
|
* examples.ts), where the site's own are never shown (MobilePay's number). */
|
|
57
57
|
example?: Record<string, unknown>;
|
|
58
|
+
/** The value a save of these settings sets, said in the notice after it ("CMS-version set to 0.10.0"), asked with the
|
|
59
|
+
* saved values and the app's own addresses in the Worker (`api`); undefined, or a failure, says "CMS-version changed". */
|
|
60
|
+
changedTo?: (values: Record<string, unknown>, props: AppPageProps) => Promise<string | undefined>;
|
|
58
61
|
};
|
|
59
62
|
export const defineSettings = (settings: AppSettings) => settings;
|
|
60
63
|
|
package/src/googleResult.d.mts
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The page's own title as the site writes it in <title> when it has no Google title.
|
|
3
|
+
* @param {{ title?: string | null; front?: boolean; siteName?: string | null }} input
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
2
6
|
export function pageTitle({ title, front, siteName }: {
|
|
3
|
-
title
|
|
4
|
-
front?: boolean
|
|
5
|
-
siteName
|
|
7
|
+
title?: string | null;
|
|
8
|
+
front?: boolean;
|
|
9
|
+
siteName?: string | null;
|
|
6
10
|
}): string;
|
|
7
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* The title and description a Google result shows for the page. `site`: { name, description }.
|
|
13
|
+
* @param {{ title?: string | null; front?: boolean; seoTitle?: string | null; seoDescription?: string | null;
|
|
14
|
+
* description?: string | null; site: { name?: string | null; description?: string | null } }} input
|
|
15
|
+
* @returns {{ title: string; description: string }}
|
|
16
|
+
*/
|
|
8
17
|
export function searchedAs({ title, front, seoTitle, seoDescription, description, site }: {
|
|
9
|
-
title
|
|
10
|
-
front?: boolean
|
|
11
|
-
seoTitle
|
|
12
|
-
seoDescription
|
|
13
|
-
description
|
|
14
|
-
site:
|
|
18
|
+
title?: string | null;
|
|
19
|
+
front?: boolean;
|
|
20
|
+
seoTitle?: string | null;
|
|
21
|
+
seoDescription?: string | null;
|
|
22
|
+
description?: string | null;
|
|
23
|
+
site: {
|
|
24
|
+
name?: string | null;
|
|
25
|
+
description?: string | null;
|
|
26
|
+
};
|
|
15
27
|
}): {
|
|
16
28
|
title: string;
|
|
17
29
|
description: string;
|
package/src/googleResult.mjs
CHANGED
|
@@ -6,7 +6,11 @@ const SEPARATOR = ' › ';
|
|
|
6
6
|
|
|
7
7
|
const text = (s) => String(s ?? '').trim();
|
|
8
8
|
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* The page's own title as the site writes it in <title> when it has no Google title.
|
|
11
|
+
* @param {{ title?: string | null; front?: boolean; siteName?: string | null }} input
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
10
14
|
export function pageTitle({ title, front = false, siteName }) {
|
|
11
15
|
const own = text(title);
|
|
12
16
|
if (!own) return String(siteName ?? '');
|
|
@@ -14,7 +18,12 @@ export function pageTitle({ title, front = false, siteName }) {
|
|
|
14
18
|
return `${own} | ${siteName ?? ''}`;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The title and description a Google result shows for the page. `site`: { name, description }.
|
|
23
|
+
* @param {{ title?: string | null; front?: boolean; seoTitle?: string | null; seoDescription?: string | null;
|
|
24
|
+
* description?: string | null; site: { name?: string | null; description?: string | null } }} input
|
|
25
|
+
* @returns {{ title: string; description: string }}
|
|
26
|
+
*/
|
|
18
27
|
export function searchedAs({ title, front = false, seoTitle, seoDescription, description, site }) {
|
|
19
28
|
const resultTitle = text(seoTitle) || pageTitle({ title, front, siteName: site.name });
|
|
20
29
|
const resultDescription = text(seoDescription) || text(description) || String(site.description ?? '');
|
package/src/i18n/da-DK/app.ts
CHANGED
|
@@ -11,6 +11,7 @@ export const app: Messages['app'] = {
|
|
|
11
11
|
changes: {
|
|
12
12
|
named: (kind, name) => `${kind} “${name}”`,
|
|
13
13
|
edited: (what) => `${what} ændret`,
|
|
14
|
+
setTo: (what, value) => `${what} sat til ${value}`,
|
|
14
15
|
created: (what) => `${what} oprettet`,
|
|
15
16
|
added: (what) => `${what} tilføjet`,
|
|
16
17
|
deleted: (what) => `${what} slettet`,
|
|
@@ -21,7 +22,7 @@ export const app: Messages['app'] = {
|
|
|
21
22
|
turnedOn: (what) => `${what} slået til`,
|
|
22
23
|
turnedOff: (what) => `${what} slået fra`,
|
|
23
24
|
chosen: (what) => `${what} valgt`,
|
|
24
|
-
installed: (what) => `${what} installeret
|
|
25
|
+
installed: (what, version) => (version ? `${what} ${version} installeret` : `${what} installeret`),
|
|
25
26
|
updated: (what, version) => `${what} opdateret til ${version}`,
|
|
26
27
|
uninstalled: (what) => `${what} afinstalleret`,
|
|
27
28
|
more: (count) => (count === 1 ? '1 ændring mere' : `${count} ændringer mere`),
|
package/src/i18n/en-US/app.ts
CHANGED
|
@@ -15,6 +15,7 @@ export const app = {
|
|
|
15
15
|
changes: {
|
|
16
16
|
named: (kind: string, name: string) => `${kind} “${name}”`,
|
|
17
17
|
edited: (what: string) => `${what} changed`,
|
|
18
|
+
setTo: (what: string, value: string) => `${what} set to ${value}`,
|
|
18
19
|
created: (what: string) => `${what} created`,
|
|
19
20
|
added: (what: string) => `${what} added`,
|
|
20
21
|
deleted: (what: string) => `${what} deleted`,
|
|
@@ -25,7 +26,7 @@ export const app = {
|
|
|
25
26
|
turnedOn: (what: string) => `${what} turned on`,
|
|
26
27
|
turnedOff: (what: string) => `${what} turned off`,
|
|
27
28
|
chosen: (what: string) => `${what} chosen`,
|
|
28
|
-
installed: (what: string) => `${what} installed
|
|
29
|
+
installed: (what: string, version?: string) => (version ? `${what} ${version} installed` : `${what} installed`),
|
|
29
30
|
updated: (what: string, version: string) => `${what} updated to ${version}`,
|
|
30
31
|
uninstalled: (what: string) => `${what} uninstalled`,
|
|
31
32
|
/** After the first three. */
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Tests for src/partChange.ts: the text of a part's save, with or without the value its `changedTo` gives.
|
|
2
|
+
// Run: node --test src/partChange.test.mjs
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { test } from 'node:test';
|
|
5
|
+
import { partChange } from './partChange.ts';
|
|
6
|
+
|
|
7
|
+
const texts = { edited: (what) => `${what} changed`, setTo: (what, value) => `${what} set to ${value}` };
|
|
8
|
+
const props = { api: async () => new Response('') };
|
|
9
|
+
const run = (changedTo) => partChange('cms-version', {}, changedTo, props, texts);
|
|
10
|
+
|
|
11
|
+
test('no hook says changed', async () => {
|
|
12
|
+
assert.equal(await run(undefined), 'cms-version changed');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('a value says set to it', async () => {
|
|
16
|
+
assert.equal(await run(async () => '0.10.0'), 'cms-version set to 0.10.0');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('an undefined value says changed', async () => {
|
|
20
|
+
assert.equal(await run(async () => undefined), 'cms-version changed');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('an empty value says changed', async () => {
|
|
24
|
+
assert.equal(await run(async () => ''), 'cms-version changed');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('a throw says changed', async () => {
|
|
28
|
+
assert.equal(
|
|
29
|
+
await run(async () => {
|
|
30
|
+
throw new Error('x');
|
|
31
|
+
}),
|
|
32
|
+
'cms-version changed',
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('the hook gets the values and the api', async () => {
|
|
37
|
+
const calls = [];
|
|
38
|
+
const values = { a: 1 };
|
|
39
|
+
await partChange('x', values, async (...args) => void calls.push(args), props, texts);
|
|
40
|
+
assert.equal(calls.length, 1);
|
|
41
|
+
assert.equal(calls[0][0], values);
|
|
42
|
+
assert.equal(calls[0][1], props);
|
|
43
|
+
assert.equal(typeof calls[0][1].api, 'function');
|
|
44
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The text of a part's save (CompanyForm.tsx usePartForm): "<label> set to <value>" or "<label> changed".
|
|
2
|
+
// Exposes partChange. Depends only on types, so it runs under node --test; the texts come in as `texts` (t.app.changes).
|
|
3
|
+
import type { AppPageProps } from './apps';
|
|
4
|
+
import type { Part } from './schema';
|
|
5
|
+
|
|
6
|
+
/** The texts it needs, from `t.app.changes`. */
|
|
7
|
+
export type ChangeTexts = { edited: (what: string) => string; setTo: (what: string, value: string) => string };
|
|
8
|
+
|
|
9
|
+
/** The text of a part's save: "<label> set to <value>" with the value its `changedTo` gives, else "<label> changed".
|
|
10
|
+
* `what` is the part's label as it stands in a sentence (lower). A throw, a rejection, no value or an empty one gives
|
|
11
|
+
* "changed". */
|
|
12
|
+
export async function partChange(
|
|
13
|
+
what: string,
|
|
14
|
+
values: Record<string, unknown>,
|
|
15
|
+
changedTo: Part['changedTo'],
|
|
16
|
+
props: AppPageProps,
|
|
17
|
+
texts: ChangeTexts,
|
|
18
|
+
): Promise<string> {
|
|
19
|
+
try {
|
|
20
|
+
const value = await changedTo?.(values, props);
|
|
21
|
+
if (value) return texts.setTo(what, value);
|
|
22
|
+
} catch {}
|
|
23
|
+
return texts.edited(what);
|
|
24
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { Icon } from '@phosphor-icons/react';
|
|
|
8
8
|
import type { ComponentType, ReactNode } from 'react';
|
|
9
9
|
import type { BlockData } from './blocks';
|
|
10
10
|
import type { AppCategory } from './store';
|
|
11
|
+
import type { AppPageProps } from './apps';
|
|
11
12
|
import type { SiteData } from '@site/editor';
|
|
12
13
|
import { locale } from './i18n';
|
|
13
14
|
|
|
@@ -385,6 +386,10 @@ export type Part = {
|
|
|
385
386
|
/** What the commit says was edited, in the site's language, when not its name in lower case (an
|
|
386
387
|
* app's settings, AppList.tsx: "indstillinger for Mailchimp"). */
|
|
387
388
|
commit?: string;
|
|
389
|
+
/** The value a save of its fields sets, said in the notice after it ("CMS-version set to 0.10.0"), asked with the saved
|
|
390
|
+
* values and the app's own addresses in the Worker (`api`); undefined, or a failure, says "CMS-version changed"
|
|
391
|
+
* (partChange.ts). An app's settings copy it from AppSettings.changedTo (AppList.tsx appSettingsPart). */
|
|
392
|
+
changedTo?: (values: Record<string, unknown>, props: AppPageProps) => Promise<string | undefined>;
|
|
388
393
|
/** Parts of its own, instead of fields: a row that opens a screen with them, each at
|
|
389
394
|
* #<screen>/<id>/<part> and going back to it ("Virksomheden" › "Betaling" with "Bank"). */
|
|
390
395
|
parts?: Part[];
|
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.
|
|
35
|
+
"@morphemecms/cms": "0.11.0",
|
|
36
36
|
"@phosphor-icons/core": "^2.1.1",
|
|
37
37
|
"@phosphor-icons/react": "^2.1.10",
|
|
38
38
|
"@tailwindcss/typography": "^0.5.20",
|