@morphemecms/cms 0.13.0 → 0.14.0-next-20261107093528
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 +18 -0
- package/defaults/editor.ts +3 -14
- package/icons/lucide/dots-three-vertical.svg +1 -0
- package/icons/material-symbols/dots-three-vertical.svg +1 -0
- package/icons/phosphor/dots-three-vertical.svg +1 -0
- package/lib/appChanges.d.mts +16 -0
- package/lib/appChanges.mjs +20 -0
- package/lib/appChanges.test.mjs +30 -0
- package/lib/apps.d.mts +2 -2
- package/lib/apps.mjs +13 -7
- package/lib/appsFile.d.mts +20 -0
- package/lib/appsFile.mjs +57 -0
- package/lib/appsFile.test.mjs +53 -0
- package/lib/build-settings.mjs +1 -1
- package/lib/changelog.d.mts +7 -0
- package/lib/changelog.mjs +16 -5
- package/lib/footerClearance.d.mts +1 -0
- package/lib/footerClearance.mjs +9 -0
- package/lib/footerClearance.test.mjs +24 -0
- package/lib/footerLinks.d.mts +12 -0
- package/lib/footerLinks.mjs +26 -0
- package/lib/footerLinks.test.mjs +32 -0
- package/lib/google-result.test.mjs +40 -9
- package/lib/menu.mjs +1 -1
- package/lib/menu.test.mjs +9 -8
- package/lib/releaseNotes.d.mts +136 -0
- package/lib/releaseNotes.mjs +249 -0
- package/lib/releaseNotes.test.mjs +267 -0
- package/lib/serviceFeed.d.mts +2 -0
- package/lib/serviceFeed.mjs +13 -0
- package/lib/serviceFeed.test.mjs +53 -0
- package/lib/serviceStatus.d.mts +72 -0
- package/lib/serviceStatus.mjs +143 -0
- package/lib/serviceStatus.test.mjs +136 -0
- package/lib/strayDividers.d.mts +6 -0
- package/lib/strayDividers.mjs +15 -0
- package/lib/strayDividers.test.mjs +25 -0
- package/package.json +5 -3
- package/release-notes/i18n/da-DK/pending/apps-turn-off.json +6 -0
- package/release-notes/i18n/da-DK/pending/apps-turn-off.webp +0 -0
- package/release-notes/i18n/da-DK/pending/developer-menu.json +5 -0
- package/release-notes/i18n/da-DK/pending/dynamic-lists-page.json +5 -0
- package/release-notes/i18n/da-DK/pending/editor-footer.json +6 -0
- package/release-notes/i18n/da-DK/pending/editor-footer.webp +0 -0
- package/release-notes/i18n/da-DK/pending/editor-toolbar-dock.json +5 -0
- package/release-notes/i18n/da-DK/pending/google-preview-closer.json +6 -0
- package/release-notes/i18n/da-DK/pending/google-preview-closer.webp +0 -0
- package/release-notes/i18n/da-DK/pending/morpheme-connection-page.json +5 -0
- package/release-notes/i18n/da-DK/pending/quiet-lamps-wander.json +6 -0
- package/release-notes/i18n/da-DK/pending/quiet-lamps-wander.webp +0 -0
- package/release-notes/i18n/en-US/pending/apps-turn-off.json +6 -0
- package/release-notes/i18n/en-US/pending/apps-turn-off.webp +0 -0
- package/release-notes/i18n/en-US/pending/developer-menu.json +5 -0
- package/release-notes/i18n/en-US/pending/dynamic-lists-page.json +5 -0
- package/release-notes/i18n/en-US/pending/editor-footer.json +6 -0
- package/release-notes/i18n/en-US/pending/editor-footer.webp +0 -0
- package/release-notes/i18n/en-US/pending/editor-toolbar-dock.json +5 -0
- package/release-notes/i18n/en-US/pending/google-preview-closer.json +6 -0
- package/release-notes/i18n/en-US/pending/google-preview-closer.webp +0 -0
- package/release-notes/i18n/en-US/pending/morpheme-connection-page.json +5 -0
- package/release-notes/i18n/en-US/pending/quiet-lamps-wander.json +6 -0
- package/release-notes/i18n/en-US/pending/quiet-lamps-wander.webp +0 -0
- package/scripts/app-screenshots.mjs +4 -4
- package/scripts/build-settings-problems.mjs +1 -1
- package/scripts/built-site.mjs +1 -1
- package/scripts/check-content.mjs +1 -1
- package/scripts/check-release-notes.mjs +12 -0
- package/scripts/check-worker.mjs +1 -1
- package/scripts/editor-screens.mjs +32 -16
- package/scripts/release-notes-pending.mjs +46 -0
- package/scripts/release-notes.mjs +39 -0
- package/site/apps.ts +10 -4
- package/site/consent.ts +3 -3
- package/site/defineSite.mjs +1 -1
- package/site/icons.d.mts +4 -0
- package/site/icons.mjs +2 -0
- package/site/pageColors.ts +3 -3
- package/site/siteCsp.d.mts +1 -1
- package/site/siteCsp.mjs +3 -2
- package/src/ActionParts.tsx +1 -1
- package/src/Advanced.tsx +4 -4
- package/src/App.tsx +22 -17
- package/src/AppEnabledSwitch.tsx +21 -0
- package/src/AppList.tsx +47 -19
- package/src/AppSettingsTab.tsx +9 -5
- package/src/BlockParts.tsx +7 -7
- package/src/BlockSettings.tsx +3 -3
- package/src/CmsPage.tsx +11 -10
- package/src/Colors.tsx +2 -2
- package/src/CommunicationParts.tsx +18 -2
- package/src/ContainmentParts.tsx +1 -1
- package/src/DataForm.tsx +1 -0
- package/src/EditorFooter.tsx +19 -9
- package/src/EntryForm.tsx +1 -0
- package/src/FooterCopyright.tsx +7 -0
- package/src/FooterDivider.tsx +5 -0
- package/src/FooterLink.tsx +18 -0
- package/src/FooterLinks.tsx +34 -0
- package/src/Library.tsx +21 -10
- package/src/MainDrawerParts.tsx +1 -1
- package/src/MarkdownEditor.tsx +1 -1
- package/src/ModalSheetSample.tsx +1 -1
- package/src/MorphemeConnection.tsx +30 -0
- package/src/MorphemeLogo.tsx +9 -0
- package/src/NavigationParts.tsx +1 -1
- package/src/NavigationRestParts.tsx +1 -1
- package/src/Parts.tsx +9 -9
- package/src/PartsShown.tsx +1 -1
- package/src/PickerParts.tsx +1 -1
- package/src/ReleaseNoteCard.tsx +17 -0
- package/src/ReleaseNotes.tsx +52 -0
- package/src/Screen.tsx +21 -12
- package/src/SearchPreview.tsx +37 -14
- package/src/SelectionParts.tsx +1 -1
- package/src/ServiceStatus.tsx +59 -0
- package/src/SideColumn.tsx +2 -2
- package/src/StoreConnectNotice.tsx +1 -1
- package/src/StoreConnectedRow.tsx +20 -14
- package/src/StoreStatus.tsx +2 -10
- package/src/ThemeStoreScreen.tsx +1 -2
- package/src/Tokens.tsx +2 -2
- package/src/apps.ts +16 -14
- package/src/buildSettings.ts +1 -1
- package/src/cmsView.d.mts +1 -0
- package/src/cmsView.mjs +4 -2
- package/src/cmsView.test.mjs +6 -0
- package/src/editor.css +13 -2
- package/src/examples.ts +2 -2
- package/src/fabPresence.tsx +24 -0
- package/src/googleResult.d.mts +12 -1
- package/src/googleResult.mjs +19 -2
- package/src/i18n/da-DK/apps.ts +5 -1
- package/src/i18n/da-DK/cms.ts +3 -0
- package/src/i18n/da-DK/common.ts +3 -2
- package/src/i18n/da-DK/footer.ts +26 -0
- package/src/i18n/da-DK/index.ts +2 -0
- package/src/i18n/da-DK/parts.ts +5 -0
- package/src/i18n/en-US/advanced.ts +1 -1
- package/src/i18n/en-US/apps.ts +6 -1
- package/src/i18n/en-US/cms.ts +5 -2
- package/src/i18n/en-US/common.ts +6 -4
- package/src/i18n/en-US/footer.ts +24 -0
- package/src/i18n/en-US/index.ts +2 -0
- package/src/i18n/en-US/parts.ts +8 -3
- package/src/menuChildren.ts +6 -14
- package/src/movedRoutes.mjs +12 -8
- package/src/movedRoutes.test.mjs +13 -10
- package/src/r2Caching.ts +1 -1
- package/src/registry.tsx +2 -2
- package/src/schema.ts +1 -1
- package/src/site.ts +2 -2
- package/src/statusReport.ts +24 -0
- package/src/store.ts +4 -8
- package/src/themeUi.ts +7 -7
- package/src/ui.tsx +4 -2
- package/src/useFooterClearance.ts +30 -0
- package/src/useStrayDividers.ts +24 -0
- package/theme/components/AppSlot.astro +4 -4
- package/theme/styles/global.css +10 -0
- package/theme/styles/ui.css +13 -0
- package/theme/ui/Badge.tsx +9 -3
- package/worker/cms-update.js +49 -12
- package/worker/index.js +10 -4
- package/worker/plan-status.js +1 -1
- package/worker/r2-status.js +1 -1
- package/worker/service-status.js +18 -0
package/src/googleResult.d.mts
CHANGED
|
@@ -28,5 +28,16 @@ export function searchedAs({ title, front, seoTitle, seoDescription, description
|
|
|
28
28
|
title: string;
|
|
29
29
|
description: string;
|
|
30
30
|
};
|
|
31
|
-
/** The address line of a Google result: the domain
|
|
31
|
+
/** The address line of a Google result: "https://" and the domain, then the path's parts, joined by " › ". */
|
|
32
32
|
export function searchCrumbs(domain: any, path: any): string;
|
|
33
|
+
/**
|
|
34
|
+
* Splits `value` into parts so that each occurrence of `keyword` (case-insensitive, literal) is a bold part, as Google
|
|
35
|
+
* bolds the searched words in a description. The original case is kept. A blank keyword gives one plain part.
|
|
36
|
+
* @param {string} value
|
|
37
|
+
* @param {string} [keyword]
|
|
38
|
+
* @returns {{ text: string; bold: boolean }[]}
|
|
39
|
+
*/
|
|
40
|
+
export function boldParts(value: string, keyword?: string): {
|
|
41
|
+
text: string;
|
|
42
|
+
bold: boolean;
|
|
43
|
+
}[];
|
package/src/googleResult.mjs
CHANGED
|
@@ -30,10 +30,27 @@ export function searchedAs({ title, front = false, seoTitle, seoDescription, des
|
|
|
30
30
|
return { title: resultTitle, description: resultDescription };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** The address line of a Google result: the domain
|
|
33
|
+
/** The address line of a Google result: "https://" and the domain, then the path's parts, joined by " › ". */
|
|
34
34
|
export function searchCrumbs(domain, path) {
|
|
35
35
|
const parts = String(path ?? '')
|
|
36
36
|
.split('/')
|
|
37
37
|
.filter((part) => part !== '');
|
|
38
|
-
return [domain
|
|
38
|
+
return [`https://${domain}`, ...parts].join(SEPARATOR);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Splits `value` into parts so that each occurrence of `keyword` (case-insensitive, literal) is a bold part, as Google
|
|
43
|
+
* bolds the searched words in a description. The original case is kept. A blank keyword gives one plain part.
|
|
44
|
+
* @param {string} value
|
|
45
|
+
* @param {string} [keyword]
|
|
46
|
+
* @returns {{ text: string; bold: boolean }[]}
|
|
47
|
+
*/
|
|
48
|
+
export function boldParts(value, keyword) {
|
|
49
|
+
const word = text(keyword);
|
|
50
|
+
if (!word) return [{ text: value, bold: false }];
|
|
51
|
+
const pattern = new RegExp(`(${word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
52
|
+
return value
|
|
53
|
+
.split(pattern)
|
|
54
|
+
.map((part, i) => ({ text: part, bold: i % 2 === 1 }))
|
|
55
|
+
.filter((part) => part.text !== '');
|
|
39
56
|
}
|
package/src/i18n/da-DK/apps.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type { Messages } from '../en-US';
|
|
|
3
3
|
export const apps: Messages['apps'] = {
|
|
4
4
|
installed: 'Installeret',
|
|
5
5
|
notInstalled: 'Ikke installeret',
|
|
6
|
+
enabled: 'Aktiveret',
|
|
7
|
+
deactivated: 'Deaktiveret',
|
|
6
8
|
publisher: (name) => `Udvikler: ${name}`,
|
|
7
9
|
screenshots: 'Skærmbilleder',
|
|
8
10
|
about: 'Om appen',
|
|
@@ -46,7 +48,7 @@ export const apps: Messages['apps'] = {
|
|
|
46
48
|
notUsed: 'Bruges ingen steder',
|
|
47
49
|
stuck: 'Appen bruges og kan ikke afinstalleres. Fjern den fra stederne ovenfor først.',
|
|
48
50
|
blocks: 'Blokke',
|
|
49
|
-
lists: '
|
|
51
|
+
lists: 'Dynamiske lister',
|
|
50
52
|
tabSettings: 'Indstillinger',
|
|
51
53
|
tabInfo: 'Information',
|
|
52
54
|
tabs: 'Appens dele',
|
|
@@ -94,6 +96,8 @@ export const apps: Messages['apps'] = {
|
|
|
94
96
|
commit: {
|
|
95
97
|
install: (app) => `Editor: installer appen ${app}`,
|
|
96
98
|
uninstall: (app) => `Editor: afinstaller appen ${app}`,
|
|
99
|
+
turnOn: (app) => `Editor: slå appen ${app} til`,
|
|
100
|
+
turnOff: (app) => `Editor: slå appen ${app} fra`,
|
|
97
101
|
update: (app, version) => `Editor: opdater appen ${app} til ${version}`,
|
|
98
102
|
},
|
|
99
103
|
};
|
package/src/i18n/da-DK/cms.ts
CHANGED
|
@@ -7,6 +7,9 @@ export const cms: Messages['cms'] = {
|
|
|
7
7
|
upToDate: 'Opdateret',
|
|
8
8
|
available: (v) => `Version ${v} er klar`,
|
|
9
9
|
whatsNew: 'Nyheder',
|
|
10
|
+
newInYourVersion: 'Nyt i din version',
|
|
11
|
+
alsoInThisVersion: 'Også i denne version',
|
|
12
|
+
storeConnection: 'Forbindelse til Appstore',
|
|
10
13
|
major: 'En ny hovedversion installeres ikke af sig selv.',
|
|
11
14
|
skipped: (v) => `Version ${v} blev fortrudt, fordi hjemmesiden ikke kunne opdateres med den.`,
|
|
12
15
|
needsNpm: 'Opdatering kræver npm. Kør dette i hjemmesidens repo:',
|
package/src/i18n/da-DK/common.ts
CHANGED
|
@@ -91,7 +91,7 @@ export const common: Messages['common'] = {
|
|
|
91
91
|
company: 'Virksomheden',
|
|
92
92
|
header: 'Header',
|
|
93
93
|
footer: 'Footer',
|
|
94
|
-
lists: '
|
|
94
|
+
lists: 'Dynamiske lister',
|
|
95
95
|
blocks: 'Blokskabeloner',
|
|
96
96
|
media: 'Filer',
|
|
97
97
|
appearance: 'Udseende',
|
|
@@ -110,7 +110,8 @@ export const common: Messages['common'] = {
|
|
|
110
110
|
redirects: 'Videresendelser',
|
|
111
111
|
account: 'Konto',
|
|
112
112
|
settings: 'Indstillinger',
|
|
113
|
-
|
|
113
|
+
developer: 'Udvikler',
|
|
114
|
+
caching: 'Caching og builds',
|
|
114
115
|
licenses: 'Licenser',
|
|
115
116
|
morpheme: 'Morpheme',
|
|
116
117
|
},
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// What the editor's footer and its "System status" screen say (EditorFooter.tsx, ServiceStatus.tsx).
|
|
2
|
+
import type { Messages } from '../en-US';
|
|
3
|
+
|
|
4
|
+
export const footer: Messages['footer'] = {
|
|
5
|
+
support: 'Support',
|
|
6
|
+
status: 'Driftsstatus',
|
|
7
|
+
terms: 'Vilkår',
|
|
8
|
+
security: 'Rapportér sikkerhedsfejl',
|
|
9
|
+
privacy: 'Privatlivspolitik',
|
|
10
|
+
openStatusPage: 'Åbn statusside',
|
|
11
|
+
loading: 'Tjekker status…',
|
|
12
|
+
state: {
|
|
13
|
+
ok: 'Alle systemer virker',
|
|
14
|
+
minor: 'Nogle systemer er langsomme',
|
|
15
|
+
major: 'Nogle systemer er nede',
|
|
16
|
+
unknown: 'Kan ikke tjekke status',
|
|
17
|
+
},
|
|
18
|
+
component: {
|
|
19
|
+
operational: 'Virker',
|
|
20
|
+
degraded_performance: 'Langsom',
|
|
21
|
+
under_maintenance: 'Vedligeholdes',
|
|
22
|
+
partial_outage: 'Delvist nede',
|
|
23
|
+
major_outage: 'Nede',
|
|
24
|
+
unknown: 'Kan ikke tjekkes',
|
|
25
|
+
},
|
|
26
|
+
};
|
package/src/i18n/da-DK/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { common } from './common';
|
|
|
12
12
|
import { company } from './company';
|
|
13
13
|
import { consents } from './consents';
|
|
14
14
|
import { forms } from './forms';
|
|
15
|
+
import { footer } from './footer';
|
|
15
16
|
import { history } from './history';
|
|
16
17
|
import { keywords } from './keywords';
|
|
17
18
|
import { licenses } from './licenses';
|
|
@@ -39,6 +40,7 @@ export const daDK: Messages = {
|
|
|
39
40
|
colors,
|
|
40
41
|
redirects,
|
|
41
42
|
consents,
|
|
43
|
+
footer,
|
|
42
44
|
seo,
|
|
43
45
|
keywords,
|
|
44
46
|
licenses,
|
package/src/i18n/da-DK/parts.ts
CHANGED
|
@@ -635,6 +635,11 @@ export const parts: Messages['parts'] = {
|
|
|
635
635
|
number: 'Antal',
|
|
636
636
|
many: 'Mere end 999',
|
|
637
637
|
onIconButton: 'På en ikonknap',
|
|
638
|
+
statusTones: 'Statustoner',
|
|
639
|
+
toneOk: 'Virker',
|
|
640
|
+
toneMinor: 'Langsom',
|
|
641
|
+
toneMajor: 'Delvist nede',
|
|
642
|
+
toneUnknown: 'Kan ikke tjekkes',
|
|
638
643
|
new: 'Nyt',
|
|
639
644
|
count: (n) => `${n} nye`,
|
|
640
645
|
messages: 'Beskeder',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "
|
|
1
|
+
// "Caching and builds" under Developer (Advanced.tsx): the faster editor (a copy of the website's files in Cloudflare R2,
|
|
2
2
|
// r2Caching.ts), and the switches for updating the website (buildSettings.ts).
|
|
3
3
|
export const advanced = {
|
|
4
4
|
r2Caching: 'Faster editor',
|
package/src/i18n/en-US/apps.ts
CHANGED
|
@@ -5,6 +5,9 @@ import type { AppHook } from '../../../lib/hooks';
|
|
|
5
5
|
export const apps = {
|
|
6
6
|
installed: 'Installed',
|
|
7
7
|
notInstalled: 'Not installed',
|
|
8
|
+
/** The switch at the top of an installed app's Settings tab, and its status under its name while it is off. */
|
|
9
|
+
enabled: 'Enabled',
|
|
10
|
+
deactivated: 'Deactivated',
|
|
8
11
|
/** Under its name on its page: who made it. */
|
|
9
12
|
publisher: (name: string) => `Developer: ${name}`,
|
|
10
13
|
/** On its page: its pictures (each opening it whole), what it does more fully, what it needs of the site
|
|
@@ -70,7 +73,7 @@ export const apps = {
|
|
|
70
73
|
/** Over the blocks it adds, and the lists it brings ("Priser"); its kinds of the site's lists (a Mailchimp popup)
|
|
71
74
|
* are under the list's name. */
|
|
72
75
|
blocks: 'Blocks',
|
|
73
|
-
lists: '
|
|
76
|
+
lists: 'Dynamic lists',
|
|
74
77
|
/** The tabs of an installed app's page, in the top bar: its settings, and what it says about itself. */
|
|
75
78
|
tabSettings: 'Settings',
|
|
76
79
|
tabInfo: 'Information',
|
|
@@ -140,6 +143,8 @@ export const apps = {
|
|
|
140
143
|
commit: {
|
|
141
144
|
install: (app: string) => `Editor: install the app ${app}`,
|
|
142
145
|
uninstall: (app: string) => `Editor: uninstall the app ${app}`,
|
|
146
|
+
turnOn: (app: string) => `Editor: turn on the app ${app}`,
|
|
147
|
+
turnOff: (app: string) => `Editor: turn off the app ${app}`,
|
|
143
148
|
update: (app: string, version: string) => `Editor: update the app ${app} to ${version}`,
|
|
144
149
|
},
|
|
145
150
|
};
|
package/src/i18n/en-US/cms.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// "Morpheme" under "Indstillinger" (the CMS's update page, #morpheme): the running version,
|
|
2
|
-
// and an update's progress; also the notifications and commit messages (worker/cms-update.js).
|
|
1
|
+
// "Morpheme" under "Indstillinger" (the CMS's update page, #morpheme): the running version, the notes of the update just
|
|
2
|
+
// installed, a new one and its notes, and an update's progress; also the notifications and commit messages (worker/cms-update.js).
|
|
3
3
|
|
|
4
4
|
export const cms = {
|
|
5
5
|
running: (v: string) => `Version ${v}`,
|
|
@@ -9,6 +9,9 @@ export const cms = {
|
|
|
9
9
|
upToDate: 'Up to date',
|
|
10
10
|
available: (v: string) => `Version ${v} is available`,
|
|
11
11
|
whatsNew: 'What’s new',
|
|
12
|
+
newInYourVersion: 'New in your version',
|
|
13
|
+
alsoInThisVersion: 'Also in this version',
|
|
14
|
+
storeConnection: 'Store connection',
|
|
12
15
|
major: 'A new major version isn’t installed by itself.',
|
|
13
16
|
skipped: (v: string) => `Version ${v} was undone because the website couldn’t be updated with it.`,
|
|
14
17
|
/** Followed by the command, in a code block. */
|
package/src/i18n/en-US/common.ts
CHANGED
|
@@ -104,7 +104,7 @@ export const common = {
|
|
|
104
104
|
company: 'Business',
|
|
105
105
|
header: 'Header',
|
|
106
106
|
footer: 'Footer',
|
|
107
|
-
lists: '
|
|
107
|
+
lists: 'Dynamic lists',
|
|
108
108
|
blocks: 'Block templates',
|
|
109
109
|
media: 'Files',
|
|
110
110
|
/** How the site looks (#appearance, Library.tsx Theme): the theme, the colours, the logo and icon, and the site's own
|
|
@@ -125,7 +125,7 @@ export const common = {
|
|
|
125
125
|
blockSettings: 'Block settings',
|
|
126
126
|
/** The apps that come with the site (AppList.tsx, #apps). */
|
|
127
127
|
apps: 'Apps',
|
|
128
|
-
/** The parts the editor and the site are built of (#
|
|
128
|
+
/** The parts the editor and the site are built of (#developer/parts: Parts.tsx, BlockParts.tsx). */
|
|
129
129
|
editorParts: 'Parts',
|
|
130
130
|
/** Under Indstillinger: the redirects and the site's own parts (#seo, Library.tsx Seo). */
|
|
131
131
|
seo: 'Search engine optimization',
|
|
@@ -133,8 +133,10 @@ export const common = {
|
|
|
133
133
|
account: 'Account',
|
|
134
134
|
/** The last section of the main drawer, and the first part of the path to a setting (placeOf in site.ts). */
|
|
135
135
|
settings: 'Settings',
|
|
136
|
-
/** Under
|
|
137
|
-
|
|
136
|
+
/** Under Settings: the developer screens, the parts and the caching (#developer, Library.tsx Developer). */
|
|
137
|
+
developer: 'Developer',
|
|
138
|
+
/** Under Developer: the faster editor and the build settings (#developer/caching, Advanced.tsx). */
|
|
139
|
+
caching: 'Caching and builds',
|
|
138
140
|
/** Under Indstillinger: the licences of what the site and the editor are made with (#licenses, Licenses.tsx). */
|
|
139
141
|
licenses: 'Licenses',
|
|
140
142
|
/** Under Indstillinger: the CMS's version and its updates (#morpheme). */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// What the editor's footer and its "System status" screen say (EditorFooter.tsx, ServiceStatus.tsx).
|
|
2
|
+
export const footer = {
|
|
3
|
+
support: 'Support',
|
|
4
|
+
status: 'System status',
|
|
5
|
+
terms: 'Terms of use',
|
|
6
|
+
security: 'Report security issues',
|
|
7
|
+
privacy: 'Privacy policy',
|
|
8
|
+
openStatusPage: 'Open status page',
|
|
9
|
+
loading: 'Checking the status…',
|
|
10
|
+
state: {
|
|
11
|
+
ok: 'All systems work',
|
|
12
|
+
minor: 'Some systems are slow',
|
|
13
|
+
major: 'Some systems are down',
|
|
14
|
+
unknown: 'Can’t check the status',
|
|
15
|
+
},
|
|
16
|
+
component: {
|
|
17
|
+
operational: 'Working',
|
|
18
|
+
degraded_performance: 'Slow',
|
|
19
|
+
under_maintenance: 'Under maintenance',
|
|
20
|
+
partial_outage: 'Partly down',
|
|
21
|
+
major_outage: 'Down',
|
|
22
|
+
unknown: 'Can’t check',
|
|
23
|
+
},
|
|
24
|
+
};
|
package/src/i18n/en-US/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { common } from './common';
|
|
|
12
12
|
import { company } from './company';
|
|
13
13
|
import { consents } from './consents';
|
|
14
14
|
import { forms } from './forms';
|
|
15
|
+
import { footer } from './footer';
|
|
15
16
|
import { history } from './history';
|
|
16
17
|
import { keywords } from './keywords';
|
|
17
18
|
import { licenses } from './licenses';
|
|
@@ -39,6 +40,7 @@ export const enUS = {
|
|
|
39
40
|
colors,
|
|
40
41
|
redirects,
|
|
41
42
|
consents,
|
|
43
|
+
footer,
|
|
42
44
|
seo,
|
|
43
45
|
keywords,
|
|
44
46
|
licenses,
|
package/src/i18n/en-US/parts.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "
|
|
1
|
+
// "Parts" (#developer/parts): the editor's parts (Parts.tsx), what each is for and the texts in the examples, and the standard
|
|
2
2
|
// blocks and the apps' (BlockParts.tsx), all with the made-up site of examples.ts.
|
|
3
3
|
export const parts = {
|
|
4
4
|
intro: (file: string) =>
|
|
@@ -253,7 +253,7 @@ export const parts = {
|
|
|
253
253
|
/** What a kind of entry an app adds (a pop-up) is drawn with. */
|
|
254
254
|
entry: { name: 'An example', heading: 'A heading', text: 'A short text that tells a little more.' },
|
|
255
255
|
},
|
|
256
|
-
/** "
|
|
256
|
+
/** "Parts" › "Værdier" (#developer/parts/values, Tokens.tsx): the theme's design tokens, each with what it is used for. */
|
|
257
257
|
tokens: {
|
|
258
258
|
intro: 'The theme’s values for corners, shadows, typography, and colors, as the theme sets them.',
|
|
259
259
|
groups: { shape: 'Corners', elevation: 'Shadows', type: 'Typography', color: 'Colors' },
|
|
@@ -355,7 +355,7 @@ export const parts = {
|
|
|
355
355
|
/** A token the theme does not set, with the one it gets instead. */
|
|
356
356
|
from: (token: string, value: string, from: string) => `${token}: ${value} (as ${from})`,
|
|
357
357
|
},
|
|
358
|
-
/** "
|
|
358
|
+
/** "Parts" (#developer/parts) and its screens: the editor's parts, the theme's UI components, the blocks (the standard
|
|
359
359
|
* ones and the apps') and the lists of kinds ("Promotion"). */
|
|
360
360
|
screens: {
|
|
361
361
|
editor: 'Editor',
|
|
@@ -659,6 +659,11 @@ export const parts = {
|
|
|
659
659
|
number: 'Number',
|
|
660
660
|
many: 'More than 999',
|
|
661
661
|
onIconButton: 'On an icon button',
|
|
662
|
+
statusTones: 'Status tones',
|
|
663
|
+
toneOk: 'Working',
|
|
664
|
+
toneMinor: 'Slow',
|
|
665
|
+
toneMajor: 'Partly down',
|
|
666
|
+
toneUnknown: "Can't check",
|
|
662
667
|
new: 'New',
|
|
663
668
|
count: (n: number) => `${n} new`,
|
|
664
669
|
messages: 'Messages',
|
package/src/menuChildren.ts
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
|
-
// The second level of the main menu (MainMenu.tsx, lib/menu.mjs menuTree): the
|
|
2
|
-
// the
|
|
3
|
-
// Uses site.ts (
|
|
4
|
-
import {
|
|
1
|
+
// The second level of the main menu (MainMenu.tsx, lib/menu.mjs menuTree): the parts under Business,
|
|
2
|
+
// the active apps (installed, not turned off) under Apps and the two lists under Redirects. Exposes childrenOf.
|
|
3
|
+
// Uses site.ts (apps, companyParts), apps.ts (activeApps) and Redirects.tsx (TAB_ROUTES).
|
|
4
|
+
import { activeApps } from './apps';
|
|
5
5
|
import { t } from './i18n';
|
|
6
|
-
import { hrefOf } from './Library';
|
|
7
6
|
import { TAB_ROUTES } from './Redirects';
|
|
8
7
|
import type { RepoFile } from './github';
|
|
9
|
-
import { apps,
|
|
8
|
+
import { apps, companyParts } from './site';
|
|
10
9
|
|
|
11
10
|
export type MenuChild = { id: string; label: string; href: string };
|
|
12
11
|
|
|
13
|
-
const listOf = (ids: string[]): MenuChild[] =>
|
|
14
|
-
ids.flatMap((id) => {
|
|
15
|
-
const c = collections.find((x) => x.id === id);
|
|
16
|
-
return c ? [{ id, label: c.label, href: hrefOf(c) }] : [];
|
|
17
|
-
});
|
|
18
|
-
|
|
19
12
|
/** The sub-items of a menu item, in their order: none for most. */
|
|
20
13
|
export function childrenOf(id: string, files: RepoFile[]): MenuChild[] {
|
|
21
|
-
if (id === 'lists') return listOf(listsShown(files));
|
|
22
14
|
if (id === 'company') return companyParts.map((p) => ({ id: p.id, label: p.label, href: `#company/${p.id}` }));
|
|
23
15
|
if (id === 'apps') {
|
|
24
|
-
const installed =
|
|
16
|
+
const installed = activeApps(files);
|
|
25
17
|
return apps.filter((a) => installed.includes(a.id)).map((a) => ({ id: a.id, label: a.label, href: `#apps/${a.id}` }));
|
|
26
18
|
}
|
|
27
19
|
if (id === 'redirects') {
|
package/src/movedRoutes.mjs
CHANGED
|
@@ -6,12 +6,14 @@ export const MOVED = /** @type {Record<string, string>} */ ({
|
|
|
6
6
|
farver: 'appearance/colors',
|
|
7
7
|
'virksomheden/logo': 'company/logo',
|
|
8
8
|
'tema/blokvalg': 'block-settings',
|
|
9
|
-
dele: '
|
|
9
|
+
dele: 'developer/parts',
|
|
10
10
|
tema: 'appearance',
|
|
11
11
|
'tema/farver': 'appearance/colors',
|
|
12
12
|
'tema/logo': 'company/logo',
|
|
13
|
-
'tema/dele': '
|
|
14
|
-
'tema/dele/editor': '
|
|
13
|
+
'tema/dele': 'developer/parts',
|
|
14
|
+
'tema/dele/editor': 'developer/parts/editor',
|
|
15
|
+
// "Advanced" is "Caching and builds" under "Developer" now.
|
|
16
|
+
advanced: 'developer/caching',
|
|
15
17
|
'virksomheden/bank': 'company/payment/bank',
|
|
16
18
|
videresendelser: 'seo/redirects',
|
|
17
19
|
'videresendelser/alle': 'seo/redirects/all',
|
|
@@ -50,10 +52,12 @@ export const RENAMED = /** @type {Record<string, string>} */ ({
|
|
|
50
52
|
'udseende/sidekolonne': 'appearance/side-column',
|
|
51
53
|
'udseende/farver': 'appearance/colors',
|
|
52
54
|
'udseende/farver/reservefarver': 'appearance/colors/fallback-colors',
|
|
53
|
-
|
|
54
|
-
'
|
|
55
|
-
'udseende/dele
|
|
56
|
-
'udseende/dele/
|
|
55
|
+
// "Parts" moved from Appearance to Developer, with all its screens.
|
|
56
|
+
'appearance/parts': 'developer/parts',
|
|
57
|
+
'udseende/dele': 'developer/parts',
|
|
58
|
+
'udseende/dele/blokke': 'developer/parts/blocks',
|
|
59
|
+
'udseende/dele/liste': 'developer/parts/list',
|
|
60
|
+
'udseende/dele/vaerdier': 'developer/parts/values',
|
|
57
61
|
'udseende/standardsider': 'appearance/default-pages',
|
|
58
62
|
maaling: 'analytics',
|
|
59
63
|
privatliv: 'privacy',
|
|
@@ -85,7 +89,7 @@ export const RENAMED = /** @type {Record<string, string>} */ ({
|
|
|
85
89
|
'seo/interne-links': 'seo/internal-links',
|
|
86
90
|
'seo/vurdering': 'seo/rating',
|
|
87
91
|
blokvalg: 'block-settings',
|
|
88
|
-
avanceret: '
|
|
92
|
+
avanceret: 'developer/caching',
|
|
89
93
|
licenser: 'licenses',
|
|
90
94
|
konto: 'account',
|
|
91
95
|
notifikationer: 'notifications',
|
package/src/movedRoutes.test.mjs
CHANGED
|
@@ -31,9 +31,9 @@ test('the extra moves come first', () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
test('only the old prefix of an address is renamed, the rest stays', () => {
|
|
34
|
-
const renamed = { indstillinger: 'settings', 'udseende/dele': '
|
|
34
|
+
const renamed = { indstillinger: 'settings', 'udseende/dele': 'developer/parts', liste: 'list' };
|
|
35
35
|
assert.equal(movedRoute('indstillinger/appstore', {}, renamed), 'settings/appstore');
|
|
36
|
-
assert.equal(movedRoute('udseende/dele/liste/kontakt', {}, renamed), '
|
|
36
|
+
assert.equal(movedRoute('udseende/dele/liste/kontakt', {}, renamed), 'developer/parts/liste/kontakt');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
test('a key segment * matches any one segment and keeps it', () => {
|
|
@@ -51,7 +51,7 @@ test('data inside an address is not renamed', () => {
|
|
|
51
51
|
test('old prefixes are renamed, the data after them stays', () => {
|
|
52
52
|
assert.equal(movedRoute('filer/filer/doc.pdf'), 'files/filer/doc.pdf');
|
|
53
53
|
assert.equal(movedRoute('liste/temaer'), 'list/temaer');
|
|
54
|
-
assert.equal(movedRoute('udseende/dele/liste/kontakt'), '
|
|
54
|
+
assert.equal(movedRoute('udseende/dele/liste/kontakt'), 'developer/parts/list/kontakt');
|
|
55
55
|
assert.equal(movedRoute('indstillinger/appstore'), 'settings/appstore');
|
|
56
56
|
assert.equal(movedRoute('udseende/farver'), 'appearance/colors');
|
|
57
57
|
});
|
|
@@ -71,10 +71,13 @@ test('every old route prefix opens the screen where it is now', () => {
|
|
|
71
71
|
'udseende/bevaegelse': 'appearance/motion',
|
|
72
72
|
'udseende/sidekolonne': 'appearance/side-column',
|
|
73
73
|
'udseende/farver/reservefarver': 'appearance/colors/fallback-colors',
|
|
74
|
-
'udseende/dele': '
|
|
75
|
-
'udseende/dele/editor': '
|
|
76
|
-
'udseende/dele/blokke': '
|
|
77
|
-
'udseende/dele/vaerdier': '
|
|
74
|
+
'udseende/dele': 'developer/parts',
|
|
75
|
+
'udseende/dele/editor': 'developer/parts/editor',
|
|
76
|
+
'udseende/dele/blokke': 'developer/parts/blocks',
|
|
77
|
+
'udseende/dele/vaerdier': 'developer/parts/values',
|
|
78
|
+
'appearance/parts': 'developer/parts',
|
|
79
|
+
'appearance/parts/list/x': 'developer/parts/list/x',
|
|
80
|
+
advanced: 'developer/caching',
|
|
78
81
|
'udseende/standardsider': 'appearance/default-pages',
|
|
79
82
|
'udseende/logo': 'company/logo',
|
|
80
83
|
maaling: 'analytics',
|
|
@@ -112,7 +115,7 @@ test('every old route prefix opens the screen where it is now', () => {
|
|
|
112
115
|
'seo/interne-links': 'seo/internal-links',
|
|
113
116
|
'seo/vurdering': 'seo/rating',
|
|
114
117
|
blokvalg: 'block-settings',
|
|
115
|
-
avanceret: '
|
|
118
|
+
avanceret: 'developer/caching',
|
|
116
119
|
licenser: 'licenses',
|
|
117
120
|
konto: 'account',
|
|
118
121
|
notifikationer: 'notifications',
|
|
@@ -139,12 +142,12 @@ test('the old colours route opens the colours screen under Appearance', () => {
|
|
|
139
142
|
});
|
|
140
143
|
|
|
141
144
|
test('an old parts route keeps a list id and opens at its English route', () => {
|
|
142
|
-
assert.equal(movedRoute('udseende/dele/liste/kontakt'), '
|
|
145
|
+
assert.equal(movedRoute('udseende/dele/liste/kontakt'), 'developer/parts/list/kontakt');
|
|
143
146
|
});
|
|
144
147
|
|
|
145
148
|
test('the old theme routes open the screens under Appearance', () => {
|
|
146
149
|
assert.equal(movedRoute('tema'), 'appearance');
|
|
147
|
-
assert.equal(movedRoute('tema/dele/editor'), '
|
|
150
|
+
assert.equal(movedRoute('tema/dele/editor'), 'developer/parts/editor');
|
|
148
151
|
});
|
|
149
152
|
|
|
150
153
|
test('the old booking and newsletter settings open the apps by their permanent ids', () => {
|
package/src/r2Caching.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Also here: planStatus, whether the account has Workers Paid (/api/plan, packages/editor/worker/plan-status.js), asked for "Betalt bygning".
|
|
2
2
|
// R2 caching: the Worker's copy of the site's files in R2 (packages/editor/worker/files.js), turned on and off under "Indstillinger" ›
|
|
3
|
-
// "
|
|
3
|
+
// "Caching and builds" (Advanced.tsx). It is the line of its bucket (`"r2_buckets": …`, the binding FILES) in the Worker's settings
|
|
4
4
|
// (config.wrangler): there when it is on, there as a comment ("// …") when it is off, so a deploy never asks for a bucket
|
|
5
5
|
// Cloudflare does not have. Changed as text, so the file's comments stay. It can only be turned on when the Worker says
|
|
6
6
|
// Cloudflare has the bucket (/api/r2, packages/editor/worker/r2-status.js). Where the bucket is kept ("Placering"): placed by Cloudflare,
|
package/src/registry.tsx
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
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
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
|
-
import {
|
|
15
|
+
import { activeApps, appIdOf } from './apps';
|
|
16
16
|
import { htmlBlock, type BlockData, type BlockDefinition, type BlockProps, type DrawBlock, type SiteBlock } from './blocks';
|
|
17
17
|
import { check, templateProblems } from './check';
|
|
18
18
|
import { slugOf, type Field, type Lead, type Template } from './schema';
|
|
@@ -82,7 +82,7 @@ export function offBlocks(files: { path: string; text: string }[]): string[] {
|
|
|
82
82
|
/** The blocks not offered to put in, and that a new page does not start with: those turned off, and
|
|
83
83
|
* those of the apps not installed (AppList.tsx). */
|
|
84
84
|
export function notOffered(files: { path: string; text: string }[]): string[] {
|
|
85
|
-
const installed =
|
|
85
|
+
const installed = activeApps(files);
|
|
86
86
|
return [...offBlocks(files), ...siteBlocks.filter((b) => b.app && !installed.includes(b.app)).map((b) => b.type)];
|
|
87
87
|
}
|
|
88
88
|
|
package/src/schema.ts
CHANGED
|
@@ -285,7 +285,7 @@ export type KindPart = {
|
|
|
285
285
|
/** A list a standard block or an app brings ("Udtalelser", "Priser"), as the site's own, with its icon under "Lister". */
|
|
286
286
|
export type OwnedList = FolderCollection & {
|
|
287
287
|
icon: Icon;
|
|
288
|
-
/** Made-up rows, as the files have them, for the parts under "
|
|
288
|
+
/** Made-up rows, as the files have them, for the parts under "Parts" and an app's pictures for the store (examples.ts). */
|
|
289
289
|
examples?: Record<string, unknown>[];
|
|
290
290
|
};
|
|
291
291
|
|
package/src/site.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// collection, a file, a part or a block as the site wrote it.
|
|
6
6
|
import * as site from '@site/editor';
|
|
7
7
|
import type { ColorPair } from '../lib/colors';
|
|
8
|
-
import {
|
|
8
|
+
import { activeApps, appIdOf, withKinds, type AppDefinition, type AppList, type SiteApp } from './apps';
|
|
9
9
|
import { appBlocks, appFiles, appFolders, appPictures, appShared, appStores } from './appFiles';
|
|
10
10
|
import type { BlockDefinition, SiteBlock } from './blocks';
|
|
11
11
|
import type { ListRows } from './fields';
|
|
@@ -345,7 +345,7 @@ export const lists = [...site.lists, ...ownedLists.map((l) => l.id)];
|
|
|
345
345
|
/** The lists under "Lister" now: the site's, the standard blocks' and those of the apps installed, without the cookie
|
|
346
346
|
* list, which is a row under "Privatliv" (privacy.ts). */
|
|
347
347
|
export const listsShown = (files: { path: string; text: string }[]) => {
|
|
348
|
-
const installed =
|
|
348
|
+
const installed = activeApps(files);
|
|
349
349
|
return [...site.lists, ...[...standardLists, ...listsOf(writtenApps.filter((a) => installed.includes(a.id)))].map((l) => l.id)].filter(
|
|
350
350
|
(id) => id !== site.config.lists.cookies,
|
|
351
351
|
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The status of GitHub and Cloudflare, as the Worker says (/api/status, worker/service-status.js): what the editor's footer
|
|
2
|
+
// and its "System status" screen (ServiceStatus.tsx) show. Exposes serviceStatus. The types are in lib/serviceStatus.mjs.
|
|
3
|
+
// The last finished report is kept for a minute (a value, never a pending promise), so a navigation does not ask again.
|
|
4
|
+
import type { StatusReport } from '../lib/serviceStatus.mjs';
|
|
5
|
+
|
|
6
|
+
const KEEP_MS = 60_000;
|
|
7
|
+
// A slow Worker must not hold the dot or the status screen: after this the report is null (unknown).
|
|
8
|
+
const TIMEOUT_MS = 5000;
|
|
9
|
+
let kept: { report: StatusReport; at: number } | null = null;
|
|
10
|
+
|
|
11
|
+
/** The report, or null when it can't be fetched (no session, not JSON, offline, no answer in 5 seconds). A report less than a minute old is
|
|
12
|
+
* reused unless `fresh`; a failure or a timeout is not kept. */
|
|
13
|
+
export async function serviceStatus({ fresh = false }: { fresh?: boolean } = {}): Promise<StatusReport | null> {
|
|
14
|
+
if (!fresh && kept && Date.now() - kept.at < KEEP_MS) return kept.report;
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch('/api/status', { cache: 'no-store', signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
17
|
+
if (!res.ok || !res.headers.get('content-type')?.includes('application/json')) return null;
|
|
18
|
+
const report = (await res.json()) as StatusReport;
|
|
19
|
+
kept = { report, at: Date.now() };
|
|
20
|
+
return report;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/store.ts
CHANGED
|
@@ -29,6 +29,7 @@ import type { StoreLicenses } from './licenseData';
|
|
|
29
29
|
import { APP_API, THEME_API } from '../lib/contracts.mjs';
|
|
30
30
|
import { RECORD, STANDARD, isText, validApp, validTheme } from '../lib/storeEntries.mjs';
|
|
31
31
|
import { timedFetch } from './storeFetch';
|
|
32
|
+
import { appChanges as appChangesIn } from '../lib/appChanges.mjs';
|
|
32
33
|
import { folderChanges } from '../lib/folderChanges.mjs';
|
|
33
34
|
|
|
34
35
|
// The contracts this editor runs (lib/contracts.mjs, also read by the site's Worker).
|
|
@@ -425,20 +426,15 @@ export function themeChanges(
|
|
|
425
426
|
}
|
|
426
427
|
|
|
427
428
|
/** The changes that make an app's folder in the site (`current`, its files now) the store's version, with the note of
|
|
428
|
-
* where it came from, or remove it (`next` null: uninstalling); and the site's file of apps with it among those
|
|
429
|
-
* installed or not
|
|
429
|
+
* where it came from, or remove it (`next` null: uninstalling); and the site's file of apps (`file`) with it among those
|
|
430
|
+
* installed or not, an update keeping it turned off (lib/appChanges.mjs). In one commit. */
|
|
430
431
|
export function appChanges(
|
|
431
432
|
id: string,
|
|
432
433
|
current: RepoFile[],
|
|
433
434
|
next: { files: { path: string; content: string | Uint8Array }[]; record: StoreRecord } | null,
|
|
434
|
-
installed: string[],
|
|
435
435
|
file: RepoFile | undefined,
|
|
436
436
|
): Change[] {
|
|
437
|
-
|
|
438
|
-
const ids = [...new Set(installed.filter((x) => x !== id).concat(next ? [id] : []))].sort();
|
|
439
|
-
const text = `${JSON.stringify({ installed: ids }, null, 2)}\n`;
|
|
440
|
-
if (text !== file?.text) changes.push({ path: config.apps.installed, content: text, oid: file?.oid || null });
|
|
441
|
-
return changes;
|
|
437
|
+
return appChangesIn(appFolder(id), config.apps.installed, id, current, next, file);
|
|
442
438
|
}
|
|
443
439
|
|
|
444
440
|
/** The note in an app's (or a theme's) folder of where it came from (store.json), from its files now (`current`). */
|