@morphemecms/cms 0.12.0 → 0.13.0-next-20261106203613
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 +11 -0
- package/defaults/blockTexts.ts +9 -9
- package/defaults/collections.ts +257 -257
- package/defaults/colors.ts +74 -74
- package/defaults/company.ts +9 -9
- package/defaults/editor.ts +65 -65
- package/defaults/i18n/da-DK/blockTexts.ts +12 -0
- package/defaults/i18n/da-DK/collections.ts +220 -0
- package/defaults/i18n/da-DK/colors.ts +102 -0
- package/defaults/i18n/da-DK/company.ts +7 -0
- package/defaults/i18n/da-DK/editor.ts +13 -0
- package/defaults/i18n/da-DK/index.ts +31 -0
- package/defaults/i18n/da-DK/internalLinks.ts +12 -0
- package/defaults/i18n/da-DK/opens.ts +7 -0
- package/defaults/i18n/da-DK/popup.ts +9 -0
- package/defaults/i18n/da-DK/privacy.ts +38 -0
- package/defaults/i18n/da-DK/rules.ts +56 -0
- package/defaults/i18n/da-DK/sitedata.ts +8 -0
- package/defaults/i18n/da-DK/standard.ts +8 -0
- package/defaults/i18n/da-DK/standardPages.ts +17 -0
- package/defaults/internalLinks.ts +8 -8
- package/defaults/opens.ts +2 -2
- package/defaults/popup.tsx +7 -7
- package/defaults/privacy.ts +37 -37
- package/defaults/rules.ts +68 -66
- package/defaults/schema.ts +1 -1
- package/defaults/sitedata.ts +4 -4
- package/defaults/standard.ts +9 -6
- package/defaults/standardPages.ts +14 -14
- package/defaults/texts.ts +7 -510
- package/lib/appRoute.d.mts +2 -2
- package/lib/appRoute.mjs +3 -3
- package/lib/appRoute.test.mjs +8 -8
- package/lib/licenses.mjs +2 -2
- package/lib/licenses.owner.test.mjs +9 -0
- package/lib/menu.mjs +4 -4
- package/lib/menu.test.mjs +101 -101
- package/lib/scheduled.mjs +1 -1
- package/lib/storeEntries.test.mjs +3 -0
- package/lib/translate.d.mts +11 -0
- package/lib/translate.mjs +13 -1
- package/lib/translate.test.mjs +23 -2
- package/package.json +1 -1
- package/scripts/app-screenshots.mjs +7 -7
- package/scripts/check-content.mjs +10 -1
- package/scripts/check-language.mjs +0 -1
- package/scripts/editor-screens.mjs +3 -3
- package/site/defineSite.mjs +2 -2
- package/site/schema.ts +3 -3
- package/src/Advanced.tsx +2 -2
- package/src/App.tsx +78 -77
- package/src/AppAdds.tsx +1 -1
- package/src/AppList.tsx +15 -15
- package/src/BlockDialog.tsx +1 -1
- package/src/BlockParts.tsx +6 -6
- package/src/BlockSettings.tsx +2 -2
- package/src/BrandForm.tsx +2 -2
- package/src/Colors.tsx +6 -6
- package/src/CompanyForm.tsx +1 -1
- package/src/Consents.tsx +3 -3
- package/src/DataForm.tsx +3 -3
- package/src/Deleted.tsx +3 -3
- package/src/EditorFooter.tsx +2 -2
- package/src/EntryForm.tsx +2 -2
- package/src/FooterSettings.tsx +2 -2
- package/src/HeaderSettings.tsx +3 -3
- package/src/Keywords.tsx +5 -5
- package/src/Library.tsx +71 -71
- package/src/Licenses.tsx +8 -8
- package/src/MainDrawerParts.tsx +3 -3
- package/src/MainMenu.tsx +1 -1
- package/src/Media.tsx +7 -7
- package/src/MediaTags.tsx +2 -2
- package/src/Notifications.tsx +2 -2
- package/src/Parts.tsx +17 -17
- package/src/Redirects.tsx +8 -8
- package/src/Screen.tsx +3 -3
- package/src/SeoScores.tsx +6 -6
- package/src/SideColumn.tsx +2 -2
- package/src/StartFab.tsx +2 -2
- package/src/ThemeStore.tsx +4 -4
- package/src/Tokens.tsx +2 -2
- package/src/apps.ts +5 -5
- package/src/checkSite.ts +86 -20
- package/src/editor.css +4 -16
- package/src/examples.ts +1 -1
- package/src/fabScreens.test.mjs +4 -4
- package/src/fabScreens.ts +4 -4
- package/src/i18n/da-DK/library.ts +2 -2
- package/src/i18n/da-DK/text.ts +1 -1
- package/src/i18n/en-US/apps.ts +1 -1
- package/src/i18n/en-US/common.ts +8 -8
- package/src/i18n/en-US/library.ts +5 -5
- package/src/i18n/en-US/parts.ts +3 -3
- package/src/i18n/en-US/text.ts +1 -1
- package/src/i18n/index.ts +4 -8
- package/src/images.ts +2 -2
- package/src/licenseData.ts +3 -3
- package/src/menuChildren.ts +5 -5
- package/src/movedRoutes.d.mts +8 -1
- package/src/movedRoutes.mjs +100 -19
- package/src/movedRoutes.test.mjs +123 -6
- package/src/nav.ts +1 -1
- package/src/notificationData.ts +1 -1
- package/src/schema.ts +2 -2
- package/src/site.ts +37 -13
- package/src/standard/accordion/index.tsx +1 -1
- package/src/standard/accordion/shared.ts +1 -1
- package/src/standard/buttons/index.tsx +1 -1
- package/src/standard/cards/index.tsx +1 -1
- package/src/standard/cards/shared.ts +1 -1
- package/src/standard/cookies/index.tsx +1 -1
- package/src/standard/posts/index.tsx +1 -1
- package/src/standard/testimonials/index.tsx +1 -1
- package/src/standard/testimonials/list.ts +1 -1
- package/src/store.ts +4 -4
- package/src/tagUses.d.mts +1 -1
- package/src/tagUses.mjs +2 -2
- package/src/tagUses.test.mjs +1 -1
- package/src/ui.tsx +7 -7
- package/theme/layouts/Base.astro +2 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// packages/editor/src/icon.tsx), there or on one of the built site's pages. An icon drawn from Material Symbols instead
|
|
5
5
|
// of the icon pack (data-icon-fallback) is only listed, with where it is: it does not fail.
|
|
6
6
|
// The screens are found as the editor's own links: from the start screen (the drawer's links are in the page also while it is
|
|
7
|
-
// closed) and "Dele", #
|
|
7
|
+
// closed) and "Dele", #appearance/parts, "Temaer", #appearance/themes, "Søgemaskineoptimering", #seo, "Appstore", #settings/appstore, and "Apps", #apps, which the menu no longer links to), and
|
|
8
8
|
// from each screen it opens, two steps down. GitHub is answered from the files in this repository, so it needs no login
|
|
9
9
|
// and changes nothing. It runs on the built site: npm run build && npm run check:screens (or on another
|
|
10
10
|
// folder with a built site, its path after the command: packages/editor/scripts/check-themes.mjs).
|
|
@@ -71,7 +71,7 @@ for (const [width, height] of [
|
|
|
71
71
|
// From the start screen (the drawer's links), and from "Dele", "Temaer", "Søgemaskineoptimering" and "Appstore"
|
|
72
72
|
// (the apps' pages and their developers'), whose screens are a step further down than the others. "Apps" (the list of
|
|
73
73
|
// the apps installed) has no row in the menu, so it is a start of its own.
|
|
74
|
-
const starts = ['', '
|
|
74
|
+
const starts = ['', 'appearance/parts', 'appearance/themes', 'seo', 'settings/appstore', 'apps'];
|
|
75
75
|
const seen = new Set(starts);
|
|
76
76
|
let next = starts;
|
|
77
77
|
for (let depth = 0; depth <= 2 && next.length; depth++) {
|
|
@@ -117,7 +117,7 @@ for (const [state, { shows, hides }] of Object.entries(keyStates)) {
|
|
|
117
117
|
const page = await browser.newPage({ viewport: { width: 390, height: 844 }, locale: 'da-DK' });
|
|
118
118
|
page.on('pageerror', (e) => problems.push(`Appstore (${state}): ${e.message}`));
|
|
119
119
|
await openEditor(page, site, {}, state);
|
|
120
|
-
await page.evaluate(() => (location.hash = '
|
|
120
|
+
await page.evaluate(() => (location.hash = 'settings/appstore'));
|
|
121
121
|
await page.waitForTimeout(500);
|
|
122
122
|
const text = await page.evaluate(() => document.body.innerText);
|
|
123
123
|
for (const x of shows) if (!text.includes(x)) problems.push(`Appstore (${state}): "${x}" mangler`);
|
package/site/defineSite.mjs
CHANGED
|
@@ -99,10 +99,10 @@ const STANDARD = {
|
|
|
99
99
|
menu: 'src/data/menu.json',
|
|
100
100
|
/** Addresses of the site that are not pages, which a new page or post cannot have. */
|
|
101
101
|
reserved: ['/editor/', '/admin/', '/oauth/', '/images/', '/filer/', '/ny/', '/404/'],
|
|
102
|
-
/** The lists the standard blocks use, by what they hold, and their ids (their addresses, #
|
|
102
|
+
/** The lists the standard blocks use, by what they hold, and their ids (their addresses, #list/<id>): the
|
|
103
103
|
* testimonials, the list the block "Udtalelser" brings (src/standard/testimonials/list.ts), the tags, which a
|
|
104
104
|
* testimonial's "Opret et tag" makes a new one of, and the cookies, the list the block "Cookieliste" brings. */
|
|
105
|
-
lists: { testimonials: '
|
|
105
|
+
lists: { testimonials: 'testimonials', tags: 'tags', cookies: 'cookies' },
|
|
106
106
|
/** The folder of the site's cookie list, "Cookies" under "Lister" (the standard block "Cookieliste",
|
|
107
107
|
* src/standard/cookies/list.ts): a file per cookie, those an app sets added when it is installed and taken out when it
|
|
108
108
|
* is uninstalled (src/cookieList.ts). */
|
package/site/schema.ts
CHANGED
|
@@ -131,7 +131,7 @@ export function webPage({
|
|
|
131
131
|
isPartOf: { '@id': ids.website },
|
|
132
132
|
about: { '@id': ids.business },
|
|
133
133
|
...(image ? { primaryImageOfPage: abs(image) } : {}),
|
|
134
|
-
...(trail.length ? { breadcrumb: { '@id': `${url}#
|
|
134
|
+
...(trail.length ? { breadcrumb: { '@id': `${url}#breadcrumbs` } } : {}),
|
|
135
135
|
...(questions.length
|
|
136
136
|
? {
|
|
137
137
|
mainEntity: questions.map((q) => ({
|
|
@@ -146,7 +146,7 @@ export function webPage({
|
|
|
146
146
|
? [
|
|
147
147
|
{
|
|
148
148
|
'@type': 'BreadcrumbList',
|
|
149
|
-
'@id': `${url}#
|
|
149
|
+
'@id': `${url}#breadcrumbs`,
|
|
150
150
|
itemListElement: trail.map((c, i) => ({ '@type': 'ListItem', position: i + 1, name: c.name, item: abs(c.href) })),
|
|
151
151
|
},
|
|
152
152
|
]
|
|
@@ -175,7 +175,7 @@ export function blogPosting({
|
|
|
175
175
|
const url = abs(path);
|
|
176
176
|
return {
|
|
177
177
|
'@type': 'BlogPosting',
|
|
178
|
-
'@id': `${url}#
|
|
178
|
+
'@id': `${url}#post`,
|
|
179
179
|
headline: title,
|
|
180
180
|
description,
|
|
181
181
|
datePublished: date.toISOString(),
|
package/src/Advanced.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "Avanceret" (#
|
|
1
|
+
// "Avanceret" (#advanced), under "Indstillinger": R2 caching, the copy of the site's files in Cloudflare R2 the Worker
|
|
2
2
|
// answers the editor from (packages/editor/worker/files.js), turned on and off with "Aktiveret" in the Worker's settings (r2Caching.ts),
|
|
3
3
|
// saved at once in its own commit. Off, the Worker asks GitHub for everything. It can only be turned on when Cloudflare
|
|
4
4
|
// has the bucket (the Worker asks, /api/r2), else a deploy would fail; under the switch, why not, and, until the site is
|
|
@@ -32,7 +32,7 @@ import { buildSettingsOf, withBuildSetting, type BuildSetting } from './buildSet
|
|
|
32
32
|
import { BUILD_SETTINGS_PATH } from '../lib/build-settings.mjs';
|
|
33
33
|
import { siteT, t } from './i18n';
|
|
34
34
|
|
|
35
|
-
export const ADVANCED_ROUTE = '
|
|
35
|
+
export const ADVANCED_ROUTE = 'advanced';
|
|
36
36
|
|
|
37
37
|
// "Placering" when Cloudflare places the bucket (no jurisdiction).
|
|
38
38
|
const AUTO = 'auto';
|
package/src/App.tsx
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
// The editor at /editor/ (docs/editor.md): sign in with GitHub, pick a part of the site, edit and
|
|
2
2
|
// save. The layout: the full-height navigation rail or drawer (MainMenu.tsx) with, beside it, the top bar (topBarSlot.tsx) over the screen. The address after # says what is open, so reloading keeps it:
|
|
3
3
|
// (empty) the start page
|
|
4
|
-
//
|
|
5
|
-
// (AppDefinition.tabs):
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// list/<id> the list of a part, e.g. list/priser, and list/<id>/<app> a tab an installed app adds to it
|
|
5
|
+
// (AppDefinition.tabs): list/cookies/cookiescanner
|
|
6
|
+
// lists the short lists (the testimonials, the apps' lists)
|
|
7
|
+
// settings/appstore the app store, its apps, and settings/appstore/themes its themes (AppList.tsx StorePage); the
|
|
8
8
|
// other settings are screens of their own, in the drawer's last section (MainMenu.tsx)
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// new/<id> a new entry, new/<id>/<kind> one of a kind (a pop-up: standard, or an app's)
|
|
10
|
+
// company the business, and company/<part> its parts: contact, payment; and
|
|
11
|
+
// company/payment/<part> its own parts: mobilepay, bank
|
|
12
12
|
// <screen> a screen with parts of the site's own (partScreens), and <screen>/<part> its parts:
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// its category:
|
|
16
|
-
//
|
|
17
|
-
// seo "Søgemaskineoptimering" (Library.tsx Seo): seo/
|
|
18
|
-
// (Redirects.tsx): seo/
|
|
19
|
-
// the addresses visitors came to that do not exist; seo/
|
|
13
|
+
// privacy/cookie-banner, privacy/privacy-pages, or one of the editor's own screens (Part.opens):
|
|
14
|
+
// privacy/consents the visitors' answers in the cookie banner (Consents.tsx); and the settings of the apps installed with
|
|
15
|
+
// its category: analytics/google-analytics
|
|
16
|
+
// files the uploaded pictures, PDFs, videos and zip files (Media.tsx)
|
|
17
|
+
// seo "Søgemaskineoptimering" (Library.tsx Seo): seo/redirects the redirects' two lists
|
|
18
|
+
// (Redirects.tsx): seo/redirects/all the redirects, and seo/redirects/not-found
|
|
19
|
+
// the addresses visitors came to that do not exist; seo/rating the SEO rating of every page and
|
|
20
20
|
// post (SeoScores.tsx); and seo/<part> the site's own (seoParts)
|
|
21
|
-
//
|
|
22
|
-
// a theme's page, with the button that swaps to it (ThemePage),
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// and icon (BrandForm.tsx),
|
|
29
|
-
// one in every state (Parts.tsx),
|
|
30
|
-
// blocks, the standard ones and the apps', and
|
|
31
|
-
// ("Promovering"), its own and the apps' (BlockParts.tsx), and
|
|
21
|
+
// appearance how the site looks (Library.tsx Theme): appearance/themes the themes installed (Themes) and appearance/themes/<theme>
|
|
22
|
+
// a theme's page, with the button that swaps to it (ThemePage), appearance/icon-packs the icon packs (IconPacks),
|
|
23
|
+
// appearance/motion how it moves (MotionSchemes), appearance/side-column the block beside the pages' text (SideColumn),
|
|
24
|
+
// appearance/header the header's menus and settings with its preview (HeaderSettings.tsx; once at header),
|
|
25
|
+
// appearance/footer the footer's texts and links with its preview (FooterSettings.tsx; once at src/data/footer.json),
|
|
26
|
+
// appearance/colors the colours (Colors.tsx;
|
|
27
|
+
// appearance/colors/fallback-colors the site's own while an app changes the pages'), appearance/logo the logo
|
|
28
|
+
// and icon (BrandForm.tsx), appearance/parts the parts (Library.tsx PartsHome): appearance/parts/editor the editor's, every
|
|
29
|
+
// one in every state (Parts.tsx), appearance/parts/ui the theme's UI components, appearance/parts/blocks the
|
|
30
|
+
// blocks, the standard ones and the apps', and appearance/parts/list/<list> each kind of a list of kinds
|
|
31
|
+
// ("Promovering"), its own and the apps' (BlockParts.tsx), and appearance/parts/values the
|
|
32
32
|
// theme's design tokens, "Værdier" (Tokens.tsx)
|
|
33
|
-
//
|
|
34
|
-
//
|
|
33
|
+
// block-settings the blocks that can be put in, "Blokindstillinger" (BlockSettings.tsx)
|
|
34
|
+
// advanced "Avanceret", under "Indstillinger": the copy of the site's files in R2 (Advanced.tsx)
|
|
35
35
|
// morpheme "Morpheme", under "Indstillinger": the CMS's version, a newer one and its notes, an update's progress (CmsPage.tsx)
|
|
36
|
-
//
|
|
37
|
-
//
|
|
36
|
+
// licenses "Licenser", under "Indstillinger": the licences of what the site and the editor are made with, and
|
|
37
|
+
// licenses/<id> one with its text (Licenses.tsx)
|
|
38
38
|
// apps the apps that come with the site, apps/<app> one with its button to install it and, while it
|
|
39
39
|
// is installed, the tabs of its page (AppList.tsx AppPage): Settings at apps/<app>, with its settings
|
|
40
|
-
// form (also on the screen with its category,
|
|
41
|
-
// Information at apps/<app>/
|
|
42
|
-
// apps/<app>/
|
|
43
|
-
// (Field.page), over its Settings tab, which keeps what is in it: apps/content-colors/
|
|
40
|
+
// form (also on the screen with its category, analytics/<app>, AppList.tsx settingsRoute), and
|
|
41
|
+
// Information at apps/<app>/about (before an app's own screens, AppDefinition.screens)
|
|
42
|
+
// apps/<app>/settings/<field> the page of a field of its settings with its choices on a page of their own
|
|
43
|
+
// (Field.page), over its Settings tab, which keeps what is in it: apps/content-colors/settings/variant
|
|
44
44
|
// <form>/<field> the page of a field of a part's form with its choices on a page of their own (Field.page), over the
|
|
45
45
|
// form, which keeps what is in it (formAt)
|
|
46
|
-
//
|
|
47
|
-
//
|
|
46
|
+
// blocks the blocks, and blocks/<block> the texts all its copies have (blockTexts.ts)
|
|
47
|
+
// deleted/<id> what was deleted from a list, or deleted/files the files (Deleted.tsx)
|
|
48
48
|
// <file path> a file being edited, e.g. src/content/blog/mit-foerste-indlaeg.md
|
|
49
49
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
50
50
|
import {
|
|
@@ -276,9 +276,9 @@ function routeOf(hash: string) {
|
|
|
276
276
|
const route = movedRoute(screenOf(hash), STORE_MOVED);
|
|
277
277
|
// An app's settings: on the screen with its category, when it has one ("Analytics", AppList.tsx settingsRoute), and so
|
|
278
278
|
// the pages of their fields (Field.page) after them. Otherwise they are on the Settings tab of its page: apps/<id>
|
|
279
|
-
// (a field's page, apps/<id>/
|
|
280
|
-
const app = apps.find((a) => route === `apps/${a.id}/
|
|
281
|
-
const rest = app ? route.slice(`apps/${app.id}/
|
|
279
|
+
// (a field's page, apps/<id>/settings/<field>, stays, drawn by the same page).
|
|
280
|
+
const app = apps.find((a) => route === `apps/${a.id}/settings` || route.startsWith(`apps/${a.id}/settings/`));
|
|
281
|
+
const rest = app ? route.slice(`apps/${app.id}/settings`.length) : '';
|
|
282
282
|
const kindScreen = app && settingsHome(app) && settingsRoute(app);
|
|
283
283
|
const moved = !app ? route : kindScreen ? `${kindScreen}${rest}` : rest ? route : `apps/${app.id}`;
|
|
284
284
|
if (moved !== screenOf(hash)) history.replaceState(history.state, '', `#${moved}`);
|
|
@@ -557,7 +557,7 @@ function Editor() {
|
|
|
557
557
|
replace([file, ...(more?.files ?? [])], more?.removed);
|
|
558
558
|
// A new entry, or a post that got a new address: from now on it is edited as the file it
|
|
559
559
|
// became. A form that saves another file than the one at its address (Virksomheden) stays where it is.
|
|
560
|
-
if (route.startsWith('
|
|
560
|
+
if (route.startsWith('new/') || more?.removed.includes(route)) {
|
|
561
561
|
history.replaceState(history.state, '', `#${file.path}`);
|
|
562
562
|
setRoute(file.path);
|
|
563
563
|
}
|
|
@@ -578,7 +578,7 @@ function Editor() {
|
|
|
578
578
|
replace([], [file.path]);
|
|
579
579
|
// Back to the list, as with the back arrow: the deleted entry is not left behind it in the history.
|
|
580
580
|
setUnsaved(false);
|
|
581
|
-
up(`#
|
|
581
|
+
up(`#list/${collection.id}`);
|
|
582
582
|
notify({ text: savedText(change), kind: 'saved' });
|
|
583
583
|
} catch (e) {
|
|
584
584
|
notify({ text: t.app.notDeleted((e as Error).message), error: true, kind: 'failed' });
|
|
@@ -689,15 +689,15 @@ function Editor() {
|
|
|
689
689
|
/** The part whose form is at an address (CompanyForm.tsx): one of "Virksomheden"'s (or of its parts', with the settings of the apps of their kinds), "Søgemaskineoptimering"'s, "Udseende"'s, or one on a screen of parts with
|
|
690
690
|
* the settings of the apps of its kinds ("Analytics"). */
|
|
691
691
|
function partAt(route: string, files: RepoFile[]): Part | null {
|
|
692
|
-
//
|
|
692
|
+
// company/<part>, or company/<part>/<its part> (Part.parts).
|
|
693
693
|
const [, companyId, ownId, ...more] = route.split('/');
|
|
694
|
-
const company = route.startsWith('
|
|
694
|
+
const company = route.startsWith('company/') && !more.length && companyParts.find((p) => p.id === companyId);
|
|
695
695
|
return (
|
|
696
696
|
(company &&
|
|
697
697
|
(ownId ? [...(company.parts ?? []), ...appPartsIn(company, files)].find((p) => p.id === ownId) : company.file && company)) ||
|
|
698
|
-
(route.startsWith('
|
|
698
|
+
(route.startsWith('blocks/') && blockParts.find((p) => p.id === route.slice(7))) ||
|
|
699
699
|
(route.startsWith('seo/') && seoParts.find((p) => p.id === route.slice(4))) ||
|
|
700
|
-
(route.startsWith('
|
|
700
|
+
(route.startsWith('appearance/') && appearance.find((p) => p.id === route.slice('appearance/'.length))) ||
|
|
701
701
|
partScreens.flatMap((s) => [...s.parts, ...appPartsIn(s, files)].filter((p) => route === `${s.id}/${p.id}`))[0] ||
|
|
702
702
|
null
|
|
703
703
|
);
|
|
@@ -718,13 +718,13 @@ type PageProps = { route: string; files: RepoFile[]; env: Env; user: Viewer | nu
|
|
|
718
718
|
|
|
719
719
|
function Page({ route, files, env, user, builtAt, onLogout }: PageProps) {
|
|
720
720
|
if (!route) return <Start builtAt={builtAt} />;
|
|
721
|
-
if (route === '
|
|
722
|
-
if (route === '
|
|
723
|
-
if (route === '
|
|
724
|
-
if (route === '
|
|
725
|
-
if (route === '
|
|
726
|
-
if (route === '
|
|
727
|
-
return <Media key={route} files={files} env={env} open={route === '
|
|
721
|
+
if (route === 'account') return <Account user={user} files={files} onLogout={onLogout} />;
|
|
722
|
+
if (route === 'notifications') return <Notifications />;
|
|
723
|
+
if (route === 'lists') return <Lists files={files} />;
|
|
724
|
+
if (route === 'company') return <Company files={files} />;
|
|
725
|
+
if (route === 'blocks') return <Blocks files={files} />;
|
|
726
|
+
if (route === 'files' || route.startsWith('files/'))
|
|
727
|
+
return <Media key={route} files={files} env={env} open={route === 'files' ? undefined : '/' + route.slice('files/'.length)} />;
|
|
728
728
|
if (route === 'seo') return <Seo />;
|
|
729
729
|
if (route === REDIRECTS_ROUTE) return <Redirects env={env} />;
|
|
730
730
|
if (route === SEO_SCORES_ROUTE) return <SeoScores files={files} env={env} />;
|
|
@@ -735,22 +735,23 @@ function Page({ route, files, env, user, builtAt, onLogout }: PageProps) {
|
|
|
735
735
|
// A part of a screen of parts that is one of the editor's own screens (Part.opens), going back to that screen.
|
|
736
736
|
const opening = partScreens.flatMap((s) => s.parts.filter((p) => p.opens && route === `${s.id}/${p.id}`).map((p) => ({ s, p })))[0];
|
|
737
737
|
if (opening?.p.opens === 'consents') return <Consents env={env} back={{ href: `#${opening.s.id}`, label: opening.s.label }} />;
|
|
738
|
-
if (route === '
|
|
739
|
-
if (route === '
|
|
740
|
-
if (route === '
|
|
741
|
-
if (route === '
|
|
742
|
-
if (route === '
|
|
743
|
-
if (route === '
|
|
744
|
-
if (route === '
|
|
738
|
+
if (route === 'appearance') return <Theme files={files} />;
|
|
739
|
+
if (route === 'appearance/themes') return <Themes env={env} files={files} />;
|
|
740
|
+
if (route === 'appearance/icon-packs') return <IconPacks env={env} files={files} />;
|
|
741
|
+
if (route === 'appearance/motion') return <MotionSchemes env={env} files={files} />;
|
|
742
|
+
if (route === 'appearance/side-column') return <SideColumn env={env} files={files} />;
|
|
743
|
+
if (route === 'appearance/header') return <HeaderSettings env={env} />;
|
|
744
|
+
if (route === 'appearance/footer') return <FooterSettings env={env} />;
|
|
745
745
|
// A theme installed has its page; one from the store that is not (or not yet built), its page in the store.
|
|
746
|
-
const themeShown = route.startsWith('
|
|
746
|
+
const themeShown = route.startsWith('appearance/themes/') && themes.find((x) => x.id === route.slice('appearance/themes/'.length));
|
|
747
747
|
if (themeShown) return <ThemePage key={themeShown.id} of={themeShown} env={env} files={files} />;
|
|
748
|
-
if (/^
|
|
748
|
+
if (/^appearance\/themes\/[^/]+$/.test(route))
|
|
749
|
+
return <StoreThemePage key={route} id={route.slice('appearance/themes/'.length)} env={env} />;
|
|
749
750
|
// The colours, or the site's own on a page of the same form while an app changes the pages' (FALLBACK_ROUTE).
|
|
750
|
-
if (route === '
|
|
751
|
+
if (route === 'appearance/colors' || route === FALLBACK_ROUTE)
|
|
751
752
|
return <Colors env={env} files={files} fallback={route === FALLBACK_ROUTE} />;
|
|
752
|
-
if (route === '
|
|
753
|
-
if (route === '
|
|
753
|
+
if (route === 'appearance/logo') return <BrandForm env={env} />;
|
|
754
|
+
if (route === 'block-settings') return <BlockSettings env={env} files={files} />;
|
|
754
755
|
if (route === ADVANCED_ROUTE) return <Advanced env={env} />;
|
|
755
756
|
if (route === CMS_ROUTE) return <CmsPage />;
|
|
756
757
|
if (route === LICENSES_ROUTE) return <Licenses />;
|
|
@@ -761,7 +762,7 @@ function Page({ route, files, env, user, builtAt, onLogout }: PageProps) {
|
|
|
761
762
|
if (route.startsWith(`${DEVELOPER_ROUTE}/`))
|
|
762
763
|
return <DeveloperPage key={route} slug={route.slice(DEVELOPER_ROUTE.length + 1)} env={env} files={files} />;
|
|
763
764
|
// An app in the site (its own, or one from the store while it is installed) has its page, with the tabs Settings
|
|
764
|
-
// (apps/<id>, and a settings field's page, apps/<id>/
|
|
765
|
+
// (apps/<id>, and a settings field's page, apps/<id>/settings/<field>) and Information (apps/<id>/about, matched
|
|
765
766
|
// before an app's own screens); one from the store that is not (or no longer) installed, its page in the store.
|
|
766
767
|
const shownApp = route.startsWith('apps/') ? apps.find((a) => appRouteOf(route, a.id)) : undefined;
|
|
767
768
|
const shown = shownApp && appRouteOf(route, shownApp.id);
|
|
@@ -776,29 +777,29 @@ function Page({ route, files, env, user, builtAt, onLogout }: PageProps) {
|
|
|
776
777
|
route.startsWith('apps/') && !deeper.length && apps.find((a) => a.id === screenApp && screenId && a.screens?.[screenId]);
|
|
777
778
|
if (ownScreen && installedApps(files).includes(ownScreen.id))
|
|
778
779
|
return <AppScreenPage key={route} app={ownScreen} id={screenId} env={env} />;
|
|
779
|
-
if (route === '
|
|
780
|
-
if (route === '
|
|
781
|
-
if (route === '
|
|
782
|
-
if (route === '
|
|
783
|
-
if (route.startsWith('
|
|
784
|
-
if (route === '
|
|
785
|
-
if (route.startsWith('
|
|
786
|
-
//
|
|
780
|
+
if (route === 'appearance/parts') return <PartsHome />;
|
|
781
|
+
if (route === 'appearance/parts/editor') return <Parts env={env} />;
|
|
782
|
+
if (route === 'appearance/parts/ui') return <ComponentParts />;
|
|
783
|
+
if (route === 'appearance/parts/blocks') return <BlocksParts />;
|
|
784
|
+
if (route.startsWith('appearance/parts/list/')) return <KindParts key={route} id={route.slice('appearance/parts/list/'.length)} />;
|
|
785
|
+
if (route === 'appearance/parts/values') return <Tokens />;
|
|
786
|
+
if (route.startsWith('deleted/')) return <Deleted id={route.slice('deleted/'.length)} files={files} env={env} />;
|
|
787
|
+
// company/<part>, or company/<part>/<its part> (Part.parts).
|
|
787
788
|
const [, companyId, ownId, ...more] = route.split('/');
|
|
788
|
-
const company = route.startsWith('
|
|
789
|
+
const company = route.startsWith('company/') && !more.length && companyParts.find((p) => p.id === companyId);
|
|
789
790
|
if (company && company.parts && !ownId) return <Company part={company} files={files} />;
|
|
790
791
|
// A part's form, or the page of one of its fields (Field.page) with the form under it: the same form, so it keeps what
|
|
791
792
|
// is in it while the editor goes between them (useFormPages, inForm).
|
|
792
793
|
const form = formAt(route, files);
|
|
793
794
|
if (form) return <CompanyForm part={form.part} at={form.at} page={form.page} env={env} files={files} />;
|
|
794
|
-
// A list, or a tab an installed app adds to it (
|
|
795
|
+
// A list, or a tab an installed app adds to it (list/<id>/<app>).
|
|
795
796
|
const [, listId, listTab, ...below] = route.split('/');
|
|
796
|
-
const listed = route.startsWith('
|
|
797
|
+
const listed = route.startsWith('list/') && !below.length && collections.find((c) => c.id === listId);
|
|
797
798
|
if (listed) return <List key={listed.id} collection={listed} tab={listTab} files={files} env={env} />;
|
|
798
799
|
|
|
799
|
-
//
|
|
800
|
+
// new/<list>, or new/<list>/<kind> for one of its kinds (a pop-up: standard, mailchimp).
|
|
800
801
|
const [, newIn, newKind] = route.split('/');
|
|
801
|
-
const creating = route.startsWith('
|
|
802
|
+
const creating = route.startsWith('new/') && collections.find((c) => c.id === newIn && c.create);
|
|
802
803
|
if (creating) {
|
|
803
804
|
if (creating.kind === 'markdown') return <EntryForm collection={creating} file={newMarkdown(creating, notOffered(files))} env={env} />;
|
|
804
805
|
if (creating.kind === 'folder')
|
package/src/AppAdds.tsx
CHANGED
|
@@ -29,7 +29,7 @@ function addsOf(app: SiteApp, files: RepoFile[]) {
|
|
|
29
29
|
...kindsOf(app).map((k) => ({
|
|
30
30
|
...k,
|
|
31
31
|
block: undefined,
|
|
32
|
-
create: k.create ? { href: `#
|
|
32
|
+
create: k.create ? { href: `#new/${k.key}/${app.id}`, label: k.create } : undefined,
|
|
33
33
|
uses: entriesOf(app, k.key, files),
|
|
34
34
|
})),
|
|
35
35
|
];
|
package/src/AppList.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// with one from the store not yet built as "Installeres…"), each with who made it under its name and, from the store,
|
|
3
3
|
// that it has a newer version, with search (by name, who made it and what it does), the order (A–Å, Å–A, kept in this
|
|
4
4
|
// browser) and headings by the first letter, as the lists under "Indhold" have. "Indstillinger" › "Appstore"
|
|
5
|
-
// (#
|
|
5
|
+
// (#settings/appstore): all apps, those in the app store (store.ts) and the site's own, installed or not, with search (by name, what they
|
|
6
6
|
// do, who made them and the words they are found by), the same order and a funnel by category. An app's page
|
|
7
7
|
// (#apps/<app>) is the same for every app and without examples: its name, who made it, whether it is installed, its
|
|
8
8
|
// version (from the store) and what it does, "Installer" (or "Opdater til …"); installed, it has two tabs: "Indstillinger"
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
// adds (AppAdds.tsx: its blocks, the lists it brings: "Priser", each opening its list, and its kinds of the site's lists:
|
|
12
12
|
// a Mailchimp popup; each with its switch at the end of its row and opening a sheet with where it is used and the same
|
|
13
13
|
// switch, as under "Blokindstillinger", and a kind's button to a new one, while it is on), its notifications with their
|
|
14
|
-
// switches, and "Afinstaller", AppSettingsTab.tsx) and "Information" (#apps/<app>/
|
|
14
|
+
// switches, and "Afinstaller", AppSettingsTab.tsx) and "Information" (#apps/<app>/about, AppInfoTab.tsx: its pictures, what
|
|
15
15
|
// it does more fully ("Om appen"), what it needs of the site ("Rettigheder", needsOf in store.ts: its code on every page,
|
|
16
16
|
// the addresses it loads from and sends forms to, its part of the Worker, a step in the build), the cookies it sets (and
|
|
17
|
-
// whether the cookie banner asks first), who made it (a row to their page, #apps/
|
|
17
|
+
// whether the cookie banner asks first), who made it (a row to their page, #apps/developer/<name>, with all their apps),
|
|
18
18
|
// and where it is used); one not installed has no tabs; installing, updating and uninstalling are each saved at once in its own commit,
|
|
19
19
|
// as a block turned on or off under "Blokindstillinger". The button pressed shows the loading indicator while it runs (busyAction.mjs). One of the site's own is installed or uninstalled in the site's
|
|
20
20
|
// file of apps; one from the store is copied into the site, or deleted from it, with it (appChanges in store.ts), and
|
|
@@ -99,10 +99,10 @@ export function settingsHome(app: SiteApp): { route: string; label: string } | u
|
|
|
99
99
|
const screen = partScreens.find((s) => s.apps?.includes(kind));
|
|
100
100
|
if (screen) return { route: screen.id, label: screen.label };
|
|
101
101
|
const company = companyParts.find((p) => p.parts && p.apps?.includes(kind));
|
|
102
|
-
return company && { route: `
|
|
102
|
+
return company && { route: `company/${company.id}`, label: company.label };
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
/** Where an app's settings are: on the screen with its kind (#
|
|
105
|
+
/** Where an app's settings are: on the screen with its kind (#analytics/google-analytics, #company/payment/mobilepay),
|
|
106
106
|
* else on the Settings tab of its page (#apps/<id>); null without settings. */
|
|
107
107
|
export function settingsRoute(app: SiteApp): string | null {
|
|
108
108
|
if (!app.settings) return null;
|
|
@@ -161,7 +161,7 @@ export const listsOf = (app: SiteApp, files: RepoFile[]) =>
|
|
|
161
161
|
key: l.id,
|
|
162
162
|
icon: l.icon,
|
|
163
163
|
label: l.label,
|
|
164
|
-
href: `#
|
|
164
|
+
href: `#list/${l.id}`,
|
|
165
165
|
count: files.filter((f) => f.path.startsWith(`${l.folder}/`) && f.path.endsWith('.json')).length,
|
|
166
166
|
}));
|
|
167
167
|
|
|
@@ -191,7 +191,7 @@ export function kindsOf(app: SiteApp) {
|
|
|
191
191
|
icon: kind.icon,
|
|
192
192
|
label: kind.label,
|
|
193
193
|
hint: kind.hint,
|
|
194
|
-
href: `#
|
|
194
|
+
href: `#list/${c.id}`,
|
|
195
195
|
one: c.singular,
|
|
196
196
|
more: c.label,
|
|
197
197
|
create: c.create,
|
|
@@ -289,7 +289,7 @@ export function Apps({ env, files }: { env: Env; files: RepoFile[] }) {
|
|
|
289
289
|
const installed = installedApps(files);
|
|
290
290
|
const catalog = useCatalog(env);
|
|
291
291
|
const store = Array.isArray(catalog) ? catalog : [];
|
|
292
|
-
const [query, setQuery] = useKept('apps-
|
|
292
|
+
const [query, setQuery] = useKept('apps-search', '');
|
|
293
293
|
const [sort, pickSort] = useSort();
|
|
294
294
|
|
|
295
295
|
// The apps installed: the site's own and those from the store; and one from the store in the file of apps that the
|
|
@@ -401,7 +401,7 @@ function AppRow({ app, installed }: { app: ReturnType<typeof allApps>[number]; i
|
|
|
401
401
|
);
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
/** A developer's page (#apps/
|
|
404
|
+
/** A developer's page (#apps/developer/<name>): their name, and all their apps and themes (in the store and the site's
|
|
405
405
|
* own), each opening its page. */
|
|
406
406
|
export function DeveloperPage({ slug, env, files }: { slug: string; env: Env; files: RepoFile[] }) {
|
|
407
407
|
const installed = installedApps(files);
|
|
@@ -462,9 +462,9 @@ export function DeveloperRow({ name }: { name: string }) {
|
|
|
462
462
|
);
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
-
/** "Appstore" (#
|
|
465
|
+
/** "Appstore" (#settings/appstore, under "Indstillinger"): in a tab of its own each in the top app bar, all apps,
|
|
466
466
|
* those in the app store and the site's own, each with what it does and whether it is installed, and all themes
|
|
467
|
-
* (#
|
|
467
|
+
* (#settings/appstore/themes, ThemeStore.tsx), those in the store
|
|
468
468
|
* and those installed, each with its version and who made it; each opening its page. With search (by name, what it
|
|
469
469
|
* does, who made it and the words it is found by, in both languages), the order and the filters by category (apps)
|
|
470
470
|
* and by whether it is installed, each picked one as a chip. */
|
|
@@ -483,10 +483,10 @@ function StoreScreen({ env, files, tab, again }: { env: Env; files: RepoFile[];
|
|
|
483
483
|
const catalog = useCatalog(env);
|
|
484
484
|
const themeCatalog = useThemeCatalog(env);
|
|
485
485
|
const connected = useStoreConnected(env);
|
|
486
|
-
const [query, setQuery] = useKept('
|
|
486
|
+
const [query, setQuery] = useKept('store-search', '');
|
|
487
487
|
const [sort, pickSort] = useSort();
|
|
488
|
-
const [category, setCategory] = useKept<AppCategory | ''>('
|
|
489
|
-
const [state, setState] = useKept<'' | 'installed' | 'notInstalled'>('
|
|
488
|
+
const [category, setCategory] = useKept<AppCategory | ''>('store-category', '');
|
|
489
|
+
const [state, setState] = useKept<'' | 'installed' | 'notInstalled'>('store-status', '');
|
|
490
490
|
const apps = tab === 'apps';
|
|
491
491
|
const read = apps ? catalog : themeCatalog;
|
|
492
492
|
|
|
@@ -948,7 +948,7 @@ type AppProps = { app: SiteApp; env: Env; files: RepoFile[] };
|
|
|
948
948
|
|
|
949
949
|
type AppTab = 'settings' | 'information';
|
|
950
950
|
|
|
951
|
-
/** The tabs of an installed app's page, in the top bar: Settings (#apps/<id>) and Information (#apps/<id>/
|
|
951
|
+
/** The tabs of an installed app's page, in the top bar: Settings (#apps/<id>) and Information (#apps/<id>/about). A form
|
|
952
952
|
* with unsaved changes asks first, as for every address (App.tsx). */
|
|
953
953
|
const tabsOf = (app: SiteApp, tab: AppTab): TabChoice<AppTab> => ({
|
|
954
954
|
label: t.apps.tabs,
|
package/src/BlockDialog.tsx
CHANGED
|
@@ -116,7 +116,7 @@ const plain = (s: string) =>
|
|
|
116
116
|
|
|
117
117
|
type InsertSort = 'az' | 'za' | 'group';
|
|
118
118
|
const INSERT_SORTS: InsertSort[] = ['az', 'za', 'group'];
|
|
119
|
-
const INSERT_SORT_KEY = 'editor-sort-
|
|
119
|
+
const INSERT_SORT_KEY = 'editor-sort-insert';
|
|
120
120
|
|
|
121
121
|
// A block's group: the app's name for an app's block, otherwise the standard blocks (also the picture and HTML).
|
|
122
122
|
const groupOf = (b: SiteBlock) => (b.app ? (apps.find((a) => a.id === b.app)?.label ?? b.app) : t.blockSettings.standard);
|
package/src/BlockParts.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// "Dele" › "UI-komponenter" (#
|
|
2
|
-
// ("Promovering", #
|
|
1
|
+
// "Dele" › "UI-komponenter" (#appearance/parts/ui), "Dele" › "Blokke" (#appearance/parts/blocks) and "Dele" › a list of kinds
|
|
2
|
+
// ("Promovering", #appearance/parts/list/<id>): how the site looks, every part in every version, to see and agree on it.
|
|
3
3
|
// "UI-komponenter" also has the active theme's coverage (summary, mark on each part, filter by style) and the site frame's parts.
|
|
4
4
|
// "UI-komponenter" has the theme's UI components (its buttons and icon buttons, text fields and checkboxes …,
|
|
5
5
|
// packages/editor/theme/ui/, through @site/editor) in every kind, size and state; "Blokke" every block, the standard
|
|
@@ -52,7 +52,7 @@ import { t } from './i18n';
|
|
|
52
52
|
import { Svg } from './icon';
|
|
53
53
|
|
|
54
54
|
const { screens } = t.parts;
|
|
55
|
-
const back = { href: '#
|
|
55
|
+
const back = { href: '#appearance/parts', label: t.common.parts.editorParts };
|
|
56
56
|
|
|
57
57
|
// The fields filled in with the plain picture where they have none (a card's, a list's items').
|
|
58
58
|
function pictured(fields: Field[], props: BlockProps): BlockProps {
|
|
@@ -520,7 +520,7 @@ function CardParts() {
|
|
|
520
520
|
);
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
-
/** "Dele" › "UI-komponenter" (#
|
|
523
|
+
/** "Dele" › "UI-komponenter" (#appearance/parts/ui): the theme's UI components in every kind, size and state. */
|
|
524
524
|
export function ComponentParts() {
|
|
525
525
|
const groups = [
|
|
526
526
|
{ id: 'ui', title: screens.ui },
|
|
@@ -587,7 +587,7 @@ export function ComponentParts() {
|
|
|
587
587
|
);
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
-
/** "Dele" › "Blokke" (#
|
|
590
|
+
/** "Dele" › "Blokke" (#appearance/parts/blocks): every block, the standard ones and then the apps', each a group of its
|
|
591
591
|
* own, in all its versions. */
|
|
592
592
|
export function BlocksParts() {
|
|
593
593
|
const data = useMemo(exampleData, []);
|
|
@@ -616,7 +616,7 @@ export function BlocksParts() {
|
|
|
616
616
|
/** The lists of kinds ("Promovering"), each a screen of "Dele". */
|
|
617
617
|
export const kindLists = () => collections.filter((c): c is FolderCollection => c.kind === 'folder' && !!c.kinds && !!c.fields?.length);
|
|
618
618
|
|
|
619
|
-
/** "Dele" › a list of kinds ("Promovering", #
|
|
619
|
+
/** "Dele" › a list of kinds ("Promovering", #appearance/parts/list/<id>): each of its kinds as the site draws it, its own
|
|
620
620
|
* (FolderCollection.draws: the pop-up, the bars) and the apps' (AppKind: Mailchimp's signup, once for each of its
|
|
621
621
|
* states), each a group of its own, as a new one of the kind with the made-up texts (kindExample). */
|
|
622
622
|
export function KindParts({ id }: { id: string }) {
|
package/src/BlockSettings.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "Blokke" › "Blokindstillinger" (#
|
|
1
|
+
// "Blokke" › "Blokindstillinger" (#block-settings): the blocks that can be put in the text of a page, in
|
|
2
2
|
// groups, the editor's standard blocks (packages/editor/src/standard/) and those of each app installed under "Apps",
|
|
3
3
|
// under the app's name (e.g. "Mailchimp",
|
|
4
4
|
// src/apps/, AppList.tsx; an app not installed has none here), each with how many pages it is on at its right
|
|
@@ -243,7 +243,7 @@ export function KindSwitch({ app, list, env, files }: { app: SiteApp; list: stri
|
|
|
243
243
|
type Sort = 'group' | 'az' | 'za';
|
|
244
244
|
const SORTS: Sort[] = ['group', 'az', 'za'];
|
|
245
245
|
// The order picked last time, in this browser.
|
|
246
|
-
const SORT_KEY = 'editor-sort-
|
|
246
|
+
const SORT_KEY = 'editor-sort-block-settings';
|
|
247
247
|
type OnOff = '' | 'on' | 'off';
|
|
248
248
|
type Use = '' | 'used' | 'unused';
|
|
249
249
|
|
package/src/BrandForm.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "Udseende" › "Logo og ikon" (#
|
|
1
|
+
// "Udseende" › "Logo og ikon" (#appearance/logo): a new logo (the mark by the site's name) and a new icon (the browser
|
|
2
2
|
// tab, Google and the editor's app icon), made from a picture the owner picks (brand.ts), in the site's colours
|
|
3
3
|
// ("Farver"). Both are saved together in one commit with "Gem ændringer".
|
|
4
4
|
import { useEffect, useState } from 'react';
|
|
@@ -120,7 +120,7 @@ export default function BrandForm({ env }: { env: Env }) {
|
|
|
120
120
|
return (
|
|
121
121
|
<FormFrame
|
|
122
122
|
title={t.common.parts.brand}
|
|
123
|
-
back={{ href: '#
|
|
123
|
+
back={{ href: '#appearance', label: t.common.parts.appearance }}
|
|
124
124
|
dirty={dirty}
|
|
125
125
|
saving={saving || drawing}
|
|
126
126
|
onSave={save}
|
package/src/Colors.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "Udseende" › "Farver" (#
|
|
1
|
+
// "Udseende" › "Farver" (#appearance/colors): the site's colours, for light and dark mode, in the site's file of
|
|
2
2
|
// colours (config.colors, packages/editor/lib/colors.ts). The first tab, "Generator", where the screen starts, or one
|
|
3
3
|
// mode at a time, picked with the tabs after it, and the editor is shown in the mode while the screen is open
|
|
4
4
|
// (theme.ts holdTheme). Each mode has a little page drawn in its colours, the pairs that are too hard to
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// farver", src/apps/content-colors/: pageColors in packages/editor/site/pageColors.ts). While an app installed changes the pages'
|
|
16
16
|
// colours (hooks/page-colors.ts), each mode shows the pages' colours instead (PageColors), as the site was last built
|
|
17
17
|
// (/editor/page-colors.json, packages/editor/scripts/page-colors.mjs): first a notice that the apps give them, with a button to the
|
|
18
|
-
// site's own, which are only the fallback ("Reservefarver", #
|
|
18
|
+
// site's own, which are only the fallback ("Reservefarver", #appearance/colors/fallback-colors, a page of the same form, so
|
|
19
19
|
// what is unsaved stays), then the apps, each to its settings, the pages with colours from a picture (the picture, the
|
|
20
20
|
// page and its colours in the mode open), and those with the site's own. Saved with "Gem ændringer" in one commit; the
|
|
21
21
|
// editor shows the new colours at once (theme.ts applyColors), the site when it is built again.
|
|
@@ -112,7 +112,7 @@ const CHANGE = t.app.changes.edited(t.app.changes.colors);
|
|
|
112
112
|
type Tab = Mode | 'generator';
|
|
113
113
|
|
|
114
114
|
/** The page with the site's own colours, while an app changes the pages' colours: the fallback. */
|
|
115
|
-
export const FALLBACK_ROUTE = '
|
|
115
|
+
export const FALLBACK_ROUTE = 'appearance/colors/fallback-colors';
|
|
116
116
|
|
|
117
117
|
/** The apps installed that change the pages' colours (hooks/page-colors.ts). */
|
|
118
118
|
const colorApps = (files: RepoFile[]) => {
|
|
@@ -139,7 +139,7 @@ export default function Colors({ env, files, fallback }: { env: Env; files: Repo
|
|
|
139
139
|
const changing = useMemo(() => colorApps(files), [files]);
|
|
140
140
|
useEffect(() => () => holdTheme(null), []);
|
|
141
141
|
// While an app changes the pages' colours, the modes show the pages', and the site's own are on a page of the form.
|
|
142
|
-
useFormPages(changing.length ? ['
|
|
142
|
+
useFormPages(changing.length ? ['appearance/colors', FALLBACK_ROUTE] : []);
|
|
143
143
|
const ownShown = fallback || !changing.length;
|
|
144
144
|
const appNames = new Intl.ListFormat(locale).format(changing.map((a) => a.label));
|
|
145
145
|
const file = files.find((f) => f.path === config.colors);
|
|
@@ -207,8 +207,8 @@ export default function Colors({ env, files, fallback }: { env: Env; files: Repo
|
|
|
207
207
|
title={changing.length && fallback ? t.colors.pages.fallback : t.common.parts.colors}
|
|
208
208
|
back={
|
|
209
209
|
changing.length && fallback
|
|
210
|
-
? { href: '#
|
|
211
|
-
: { href: '#
|
|
210
|
+
? { href: '#appearance/colors', label: t.common.parts.colors, guard: (go) => go() }
|
|
211
|
+
: { href: '#appearance', label: t.common.parts.appearance }
|
|
212
212
|
}
|
|
213
213
|
dirty={dirty}
|
|
214
214
|
saving={saving}
|
package/src/CompanyForm.tsx
CHANGED
|
@@ -113,7 +113,7 @@ export default function CompanyForm({ part, env, files, at: route, page }: Props
|
|
|
113
113
|
) : (
|
|
114
114
|
<FormFrame
|
|
115
115
|
title={part.label}
|
|
116
|
-
back={part.back ?? { href: '#
|
|
116
|
+
back={part.back ?? { href: '#company', label: t.common.parts.company }}
|
|
117
117
|
dirty={dirty}
|
|
118
118
|
saving={saving}
|
|
119
119
|
onSave={save}
|