@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/site/apps.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
// The apps on the site (src/apps/<app>/, packages/editor/src/apps.ts): which are installed
|
|
2
|
-
// "Indstillinger" › "Apps" in the editor (apps.json), and each app's settings (apps/<app>.json), as the
|
|
1
|
+
// The apps on the site (src/apps/<app>/, packages/editor/src/apps.ts): which are installed (and which of them are
|
|
2
|
+
// turned off) under "Indstillinger" › "Apps" in the editor (apps.json), and each app's settings (apps/<app>.json), as the
|
|
3
3
|
// site's build reads them. What an app puts on every page (AppSlot.astro) is there only while it is
|
|
4
4
|
// installed; what was made with it (a pop-up, a block on a page) stays when it is uninstalled.
|
|
5
5
|
import data from '@data/apps.json';
|
|
6
|
+
import { activeOf, readAppsFile } from '../lib/appsFile.mjs';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
const state = readAppsFile(JSON.stringify(data));
|
|
9
|
+
|
|
10
|
+
/** The ids of the apps installed, also those turned off. */
|
|
11
|
+
export const installed: string[] = state.installed;
|
|
12
|
+
|
|
13
|
+
/** The ids of the apps installed and not turned off: what runs, and puts something on the site. */
|
|
14
|
+
export const active: string[] = activeOf(state);
|
|
9
15
|
|
|
10
16
|
// Each app's saved settings, by its id (the file's name).
|
|
11
17
|
const saved = Object.fromEntries(
|
package/site/consent.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// What the cookie banner asks about: the categories of cookies (@morphemecms/cms/lib/cookieCategories.mjs) of what the
|
|
2
|
-
//
|
|
2
|
+
// active apps (installed, not turned off) load with their settings (`cookies` in their index.ts: Google Analytics statistics, Google Ads
|
|
3
3
|
// marketing, Google Tag Manager both), each a yes or no of its own; and whether the site has the banner (and
|
|
4
4
|
// "Cookieindstillinger" in the footer) at all: when it is switched on ("Privatliv" › "Cookiebanner",
|
|
5
5
|
// src/data/cookies.json) and there is something to ask about. For the theme (Base.astro, Footer.astro,
|
|
@@ -8,7 +8,7 @@ import type { AppDefinition } from '../src/apps';
|
|
|
8
8
|
import { appIdOf } from '../src/apps';
|
|
9
9
|
import { CONSENT_CATEGORIES, type ConsentCategory } from '../lib/cookieCategories.mjs';
|
|
10
10
|
import cookies from '@data/cookies.json';
|
|
11
|
-
import {
|
|
11
|
+
import { active, settingsOf } from './apps';
|
|
12
12
|
|
|
13
13
|
// Only the apps' index.ts, without their blocks (as opens.ts).
|
|
14
14
|
const appFiles = import.meta.glob<{ default: AppDefinition }>('/src/apps/*/index.ts', { eager: true });
|
|
@@ -16,7 +16,7 @@ const appFiles = import.meta.glob<{ default: AppDefinition }>('/src/apps/*/index
|
|
|
16
16
|
const asked = new Set(
|
|
17
17
|
Object.entries(appFiles).flatMap(([path, { default: app }]) => {
|
|
18
18
|
const id = appIdOf(path);
|
|
19
|
-
return
|
|
19
|
+
return active.includes(id) ? (app.cookies?.(settingsOf(id, app.settings?.values)) ?? []) : [];
|
|
20
20
|
}),
|
|
21
21
|
);
|
|
22
22
|
|
package/site/defineSite.mjs
CHANGED
|
@@ -54,7 +54,7 @@ const CHOICES = {
|
|
|
54
54
|
|
|
55
55
|
/** The same on every site. */
|
|
56
56
|
const STANDARD = {
|
|
57
|
-
/** The Worker's settings: the editor turns R2 caching on and off in them (
|
|
57
|
+
/** The Worker's settings: the editor turns R2 caching on and off in them (Settings › Developer › Caching and builds, src/r2Caching.ts). */
|
|
58
58
|
wrangler: 'wrangler.jsonc',
|
|
59
59
|
/** The folders the editor loads: the blog posts, the pages, the data files, the pictures and the PDFs, videos and zip
|
|
60
60
|
* files (shown with the pictures in "Filer", at /filer/<name> on the site). */
|
package/site/icons.d.mts
CHANGED
|
@@ -154,6 +154,7 @@ export const MATERIAL_SYMBOLS: {
|
|
|
154
154
|
desktop: string;
|
|
155
155
|
'device-mobile': string;
|
|
156
156
|
'door-open': string;
|
|
157
|
+
'dots-three-vertical': string;
|
|
157
158
|
'download-simple': string;
|
|
158
159
|
'envelope-simple': string;
|
|
159
160
|
'envelope-simple-fill': string;
|
|
@@ -345,6 +346,7 @@ export const LUCIDE: {
|
|
|
345
346
|
desktop: string;
|
|
346
347
|
'device-mobile': string;
|
|
347
348
|
'door-open': string;
|
|
349
|
+
'dots-three-vertical': string;
|
|
348
350
|
'download-simple': string;
|
|
349
351
|
'envelope-simple': string;
|
|
350
352
|
'envelope-simple-fill': string;
|
|
@@ -536,6 +538,7 @@ export const PACK_MAPS: {
|
|
|
536
538
|
desktop: string;
|
|
537
539
|
'device-mobile': string;
|
|
538
540
|
'door-open': string;
|
|
541
|
+
'dots-three-vertical': string;
|
|
539
542
|
'download-simple': string;
|
|
540
543
|
'envelope-simple': string;
|
|
541
544
|
'envelope-simple-fill': string;
|
|
@@ -725,6 +728,7 @@ export const PACK_MAPS: {
|
|
|
725
728
|
desktop: string;
|
|
726
729
|
'device-mobile': string;
|
|
727
730
|
'door-open': string;
|
|
731
|
+
'dots-three-vertical': string;
|
|
728
732
|
'download-simple': string;
|
|
729
733
|
'envelope-simple': string;
|
|
730
734
|
'envelope-simple-fill': string;
|
package/site/icons.mjs
CHANGED
|
@@ -168,6 +168,7 @@ export const MATERIAL_SYMBOLS = {
|
|
|
168
168
|
desktop: 'desktop_windows',
|
|
169
169
|
'device-mobile': 'mobile',
|
|
170
170
|
'door-open': 'door_open',
|
|
171
|
+
'dots-three-vertical': 'more_vert',
|
|
171
172
|
'download-simple': 'download',
|
|
172
173
|
'envelope-simple': 'mail',
|
|
173
174
|
'envelope-simple-fill': 'mail-fill',
|
|
@@ -360,6 +361,7 @@ export const LUCIDE = {
|
|
|
360
361
|
desktop: 'monitor',
|
|
361
362
|
'device-mobile': 'smartphone',
|
|
362
363
|
'door-open': 'door-open',
|
|
364
|
+
'dots-three-vertical': 'ellipsis-vertical',
|
|
363
365
|
'download-simple': 'download',
|
|
364
366
|
'envelope-simple': 'mail',
|
|
365
367
|
'envelope-simple-fill': 'mail-fill',
|
package/site/pageColors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The colours of one page, for the theme (Base.astro, which knows no app by name): the site's own (src/data/colors.json,
|
|
2
|
-
// "Farver"), changed by the apps installed that change a page's colours (hooks/page-colors.ts, PageColorsHook in
|
|
2
|
+
// "Farver"), changed by the active apps (installed, not turned off) that change a page's colours (hooks/page-colors.ts, PageColorsHook in
|
|
3
3
|
// @morphemecms/cms/lib/hooks.ts), in the order of their ids, each given the colours the one before left. An app gets the
|
|
4
4
|
// page's address, its pictures, its own settings, the colours' roles, and the pixels of a picture read small, so it
|
|
5
5
|
// never reads the site's files itself; what it gives is kept only when it is the site's colours, by their names, and
|
|
@@ -12,7 +12,7 @@ import path from 'node:path';
|
|
|
12
12
|
import sharp from 'sharp';
|
|
13
13
|
import colors from '@data/colors.json';
|
|
14
14
|
import { colorFields, colorPairs, colorRoles } from '@site/colors';
|
|
15
|
-
import {
|
|
15
|
+
import { active, settingsOf } from './apps';
|
|
16
16
|
import { appIdOf, type AppDefinition } from '@morphemecms/cms/src/apps';
|
|
17
17
|
import { colorProblems, lowContrast, MODES, type Colors } from '@morphemecms/cms/lib/colors';
|
|
18
18
|
import { completeColors } from '@morphemecms/cms/lib/dynamic-colors';
|
|
@@ -31,7 +31,7 @@ const appFiles = import.meta.glob<{ default: AppDefinition }>('/src/apps/*/index
|
|
|
31
31
|
const hookFiles = import.meta.glob<{ default: PageColorsHook }>('/src/apps/*/hooks/page-colors.ts', { eager: true });
|
|
32
32
|
const hooks = Object.entries(hookFiles)
|
|
33
33
|
.map(([file, { default: hook }]) => ({ id: appIdOf(file), hook }))
|
|
34
|
-
.filter(({ id }) =>
|
|
34
|
+
.filter(({ id }) => active.includes(id))
|
|
35
35
|
.sort((a, b) => a.id.localeCompare(b.id))
|
|
36
36
|
.map(({ id, hook }) => ({
|
|
37
37
|
id,
|
package/site/siteCsp.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** What the site's
|
|
1
|
+
/** What the site's active apps (installed, not turned off) load from elsewhere, and the addresses allowed in the editor.
|
|
2
2
|
* @returns {import('../lib/csp.mjs').Sources[]} */
|
|
3
3
|
export function siteSources(): import("../lib/csp.mjs").Sources[];
|
|
4
4
|
/** What is wrong with the addresses allowed in the editor (src/data/csp.json): not JSON, or not written as an app's
|
package/site/siteCsp.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// Read by the build (packages/editor/astro.mjs) and by npm run check:content (appCspProblems), so no TypeScript here.
|
|
7
7
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
8
8
|
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { activeOf, readAppsFile } from '../lib/appsFile.mjs';
|
|
9
10
|
import { allows, sourcesProblems } from '../lib/csp.mjs';
|
|
10
11
|
|
|
11
12
|
// The site's src/, from its root (the working directory).
|
|
@@ -22,10 +23,10 @@ const appSources = (app) => {
|
|
|
22
23
|
const ALLOWED = new URL('data/csp.json', SRC);
|
|
23
24
|
const allowedSources = () => (existsSync(ALLOWED) ? JSON.parse(readFileSync(ALLOWED, 'utf8')) : {});
|
|
24
25
|
|
|
25
|
-
/** What the site's
|
|
26
|
+
/** What the site's active apps (installed, not turned off) load from elsewhere, and the addresses allowed in the editor.
|
|
26
27
|
* @returns {import('../lib/csp.mjs').Sources[]} */
|
|
27
28
|
export function siteSources() {
|
|
28
|
-
return [...(
|
|
29
|
+
return [...activeOf(readAppsFile(readFileSync(new URL('data/apps.json', SRC), 'utf8'))).map(appSources), allowedSources()];
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/** What is wrong with the addresses allowed in the editor (src/data/csp.json): not JSON, or not written as an app's
|
package/src/ActionParts.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "
|
|
1
|
+
// "Parts" › "UI-komponenter" (BlockParts.tsx), the group "Handlinger": the theme's buttons that do more than one thing
|
|
2
2
|
// (packages/editor/theme/ui/, through @site/editor) as the site draws them: button groups in both kinds with toggles on
|
|
3
3
|
// and off, in every size and in both shapes, segmented buttons with one and with several chosen, split buttons in their
|
|
4
4
|
// kinds and sizes, closed and with the menu open, and FAB menus in their colour sets, from FABs of each size, closed
|
package/src/Advanced.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "
|
|
1
|
+
// "Caching and builds" (#developer/caching), under "Developer" (Library.tsx Developer): 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
|
|
@@ -13,7 +13,7 @@ import { EmptyNote, GroupTitle, LinkButton, MenuRow, ProblemNote, SupportingText
|
|
|
13
13
|
import { ConflictError, type RepoFile } from './github';
|
|
14
14
|
import type { Env } from './App';
|
|
15
15
|
import { savedText } from './deploy';
|
|
16
|
-
import {
|
|
16
|
+
import { backToDeveloper } from './Library';
|
|
17
17
|
import {
|
|
18
18
|
bucketOf,
|
|
19
19
|
JURISDICTIONS,
|
|
@@ -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
|
|
35
|
+
export const CACHING_ROUTE = 'developer/caching';
|
|
36
36
|
|
|
37
37
|
// "Placering" when Cloudflare places the bucket (no jurisdiction).
|
|
38
38
|
const AUTO = 'auto';
|
|
@@ -170,7 +170,7 @@ export default function Advanced({ env }: { env: Env }) {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
return (
|
|
173
|
-
<Screen title={t.common.parts.
|
|
173
|
+
<Screen title={t.common.parts.caching} wide back={backToDeveloper}>
|
|
174
174
|
<section className="mb-8">
|
|
175
175
|
<GroupTitle>{t.advanced.r2Caching}</GroupTitle>
|
|
176
176
|
<div className="px-4">
|
package/src/App.tsx
CHANGED
|
@@ -26,16 +26,17 @@
|
|
|
26
26
|
// appearance/footer the footer's texts and links with its preview (FooterSettings.tsx; once at src/data/footer.json),
|
|
27
27
|
// appearance/colors the colours (Colors.tsx;
|
|
28
28
|
// appearance/colors/fallback-colors the site's own while an app changes the pages'),
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
29
|
+
// developer the developer screens (Library.tsx Developer), under "Settings": developer/caching the copy of the site's files in R2 and the build settings (Advanced.tsx), and
|
|
30
|
+
// developer/parts the parts (Library.tsx PartsHome): developer/parts/editor the editor's, every
|
|
31
|
+
// one in every state (Parts.tsx), developer/parts/ui the theme's UI components, developer/parts/blocks the
|
|
32
|
+
// blocks, the standard ones and the apps', and developer/parts/list/<list> each kind of a list of kinds
|
|
33
|
+
// ("Promovering"), its own and the apps' (BlockParts.tsx), and developer/parts/values the
|
|
33
34
|
// theme's design tokens, "Værdier" (Tokens.tsx)
|
|
34
35
|
// block-settings the blocks that can be put in, "Blokindstillinger" (BlockSettings.tsx)
|
|
35
|
-
// advanced "Avanceret", under "Indstillinger": the copy of the site's files in R2 (Advanced.tsx)
|
|
36
36
|
// morpheme "Morpheme", under "Indstillinger": the CMS's version, a newer one and its notes, an update's progress (CmsPage.tsx)
|
|
37
37
|
// licenses "Licenser", under "Indstillinger": the licences of what the site and the editor are made with, and
|
|
38
38
|
// licenses/<id> one with its text (Licenses.tsx)
|
|
39
|
+
// status "System status", from the footer: GitHub's and Cloudflare's status (ServiceStatus.tsx)
|
|
39
40
|
// apps the apps that come with the site, apps/<app> one with its button to install it and, while it
|
|
40
41
|
// is installed, the tabs of its page (AppList.tsx AppPage): Settings at apps/<app>, with its settings
|
|
41
42
|
// form (also on the screen with its category, analytics/<app>, AppList.tsx settingsRoute), and
|
|
@@ -97,9 +98,10 @@ import { BlocksParts, ComponentParts, KindParts } from './BlockParts';
|
|
|
97
98
|
import Tokens from './Tokens';
|
|
98
99
|
import BrandForm from './BrandForm';
|
|
99
100
|
import BlockSettings from './BlockSettings';
|
|
100
|
-
import Advanced, {
|
|
101
|
+
import Advanced, { CACHING_ROUTE } from './Advanced';
|
|
101
102
|
import CmsPage, { CMS_ROUTE } from './CmsPage';
|
|
102
103
|
import Licenses, { LICENSES_ROUTE, LicensePage } from './Licenses';
|
|
104
|
+
import ServiceStatus, { STATUS_ROUTE } from './ServiceStatus';
|
|
103
105
|
import { StoreThemePage } from './ThemeStore';
|
|
104
106
|
import { ThemeStoreScreen } from './ThemeStoreScreen';
|
|
105
107
|
import {
|
|
@@ -114,7 +116,7 @@ import {
|
|
|
114
116
|
StoreAppPage,
|
|
115
117
|
StorePage,
|
|
116
118
|
} from './AppList';
|
|
117
|
-
import { installedApps } from './apps';
|
|
119
|
+
import { activeApps, installedApps } from './apps';
|
|
118
120
|
import {
|
|
119
121
|
DEVELOPER_ROUTE,
|
|
120
122
|
readApp,
|
|
@@ -142,6 +144,8 @@ import {
|
|
|
142
144
|
Account,
|
|
143
145
|
Blocks,
|
|
144
146
|
Company,
|
|
147
|
+
DEV_MENU_ROUTE,
|
|
148
|
+
Developer,
|
|
145
149
|
List,
|
|
146
150
|
Lists,
|
|
147
151
|
PartsHome,
|
|
@@ -624,7 +628,7 @@ function Editor() {
|
|
|
624
628
|
|
|
625
629
|
// What the installed apps have to say (AppNotifications in apps.ts): asked when the editor opens and every 15 minutes
|
|
626
630
|
// while it is open; each comes once, at the bottom and under "Notifikationer", while its type is on (notificationData.ts).
|
|
627
|
-
const notifying =
|
|
631
|
+
const notifying = activeApps(files ?? [])
|
|
628
632
|
.filter((id) => apps.some((a) => a.id === id && a.notifications))
|
|
629
633
|
.join();
|
|
630
634
|
useEffect(() => {
|
|
@@ -784,9 +788,11 @@ function Page({ route, files, env, user, builtAt, onLogout }: PageProps) {
|
|
|
784
788
|
return <Colors env={env} files={files} fallback={route === FALLBACK_ROUTE} />;
|
|
785
789
|
if (route === 'company/logo') return <BrandForm env={env} />;
|
|
786
790
|
if (route === 'block-settings') return <BlockSettings env={env} files={files} />;
|
|
787
|
-
if (route ===
|
|
791
|
+
if (route === DEV_MENU_ROUTE) return <Developer />;
|
|
792
|
+
if (route === CACHING_ROUTE) return <Advanced env={env} />;
|
|
788
793
|
if (route === CMS_ROUTE) return <CmsPage />;
|
|
789
794
|
if (route === LICENSES_ROUTE) return <Licenses />;
|
|
795
|
+
if (route === STATUS_ROUTE) return <ServiceStatus />;
|
|
790
796
|
if (route.startsWith(`${LICENSES_ROUTE}/`)) return <LicensePage key={route} id={route.slice(LICENSES_ROUTE.length + 1)} />;
|
|
791
797
|
if (route === 'apps') return <Apps env={env} files={files} />;
|
|
792
798
|
if (route === STORE_ROUTE) return <StorePage env={env} files={files} />;
|
|
@@ -807,14 +813,13 @@ function Page({ route, files, env, user, builtAt, onLogout }: PageProps) {
|
|
|
807
813
|
const [, screenApp, screenId, ...deeper] = route.split('/');
|
|
808
814
|
const ownScreen =
|
|
809
815
|
route.startsWith('apps/') && !deeper.length && apps.find((a) => a.id === screenApp && screenId && a.screens?.[screenId]);
|
|
810
|
-
if (ownScreen &&
|
|
811
|
-
|
|
812
|
-
if (route === '
|
|
813
|
-
if (route === '
|
|
814
|
-
if (route === '
|
|
815
|
-
if (route
|
|
816
|
-
if (route
|
|
817
|
-
if (route === 'appearance/parts/values') return <Tokens />;
|
|
816
|
+
if (ownScreen && activeApps(files).includes(ownScreen.id)) return <AppScreenPage key={route} app={ownScreen} id={screenId} env={env} />;
|
|
817
|
+
if (route === 'developer/parts') return <PartsHome />;
|
|
818
|
+
if (route === 'developer/parts/editor') return <Parts env={env} />;
|
|
819
|
+
if (route === 'developer/parts/ui') return <ComponentParts />;
|
|
820
|
+
if (route === 'developer/parts/blocks') return <BlocksParts />;
|
|
821
|
+
if (route.startsWith('developer/parts/list/')) return <KindParts key={route} id={route.slice('developer/parts/list/'.length)} />;
|
|
822
|
+
if (route === 'developer/parts/values') return <Tokens />;
|
|
818
823
|
if (route.startsWith('deleted/')) return <Deleted id={route.slice('deleted/'.length)} files={files} env={env} />;
|
|
819
824
|
// company/<part>, or company/<part>/<its part> (Part.parts).
|
|
820
825
|
const [, companyId, ownId, ...more] = route.split('/');
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The "Aktiveret" (Enabled) switch of an installed app, shown on its page's Settings tab directly below the app's description.
|
|
2
|
+
// Exposes AppEnabledSwitch (default). Depends on ./ui (SwitchRow) and ./i18n (t).
|
|
3
|
+
import { SwitchRow } from './ui';
|
|
4
|
+
import { t } from './i18n';
|
|
5
|
+
|
|
6
|
+
/** `enabled`: the app is not turned off; `busy`: something is saving; `onEnabled`: called with the new state. */
|
|
7
|
+
export default function AppEnabledSwitch({
|
|
8
|
+
enabled,
|
|
9
|
+
busy,
|
|
10
|
+
onEnabled,
|
|
11
|
+
}: {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
busy: boolean;
|
|
14
|
+
onEnabled: (on: boolean) => void;
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<div className="mb-8 px-4">
|
|
18
|
+
<SwitchRow label={t.apps.enabled} texts={[t.library.on, t.library.off]} selected={enabled} disabled={busy} onChange={onEnabled} />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
package/src/AppList.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// "Indstillinger" › "Apps" (#apps): the apps installed (src/apps/, apps.ts: the site's own and those from the app store,
|
|
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
|
-
// that it has a newer version, with search (by name, who made it and what it does), the order (A–Å, Å–A, kept in this
|
|
3
|
+
// that it has a newer version or is turned off (liveStatus, also in "Appstore"), 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 (only apps: the themes are in "Theme store", ThemeStoreScreen.tsx; the search row and the
|
|
7
7
|
// store notices both screens share are StoreSearchRow.tsx, StoreStatus.tsx and storeSort.tsx). An app's page
|
|
8
|
-
// (#apps/<app>) is the same for every app and without examples: its name, who made it, whether it is installed
|
|
8
|
+
// (#apps/<app>) is the same for every app and without examples: its name, who made it, whether it is installed (or turned
|
|
9
|
+
// off, with the switch "Aktiveret" right below its description on the Settings tab, AppEnabledSwitch.tsx), its
|
|
9
10
|
// version (from the store) and what it does, "Installer" (or "Opdater til …"); installed, it has two tabs: "Indstillinger"
|
|
10
11
|
// (#apps/<app>: its settings form, saved with "Gem ændringer" in their file, config.apps.settings, also on the screen
|
|
11
12
|
// under "Indstillinger" with its kind, "Analytics" or "Promovering"; AppFormPage.tsx; then what it shows itself, what it
|
|
@@ -32,7 +33,8 @@ import { ConflictError, type Change, type RepoFile } from './github';
|
|
|
32
33
|
import { cookieChanges, inCookieList } from './cookieList';
|
|
33
34
|
import type { Env } from './App';
|
|
34
35
|
import { ask, choose } from './ask';
|
|
35
|
-
import { installedApps, settingsFile, type SiteApp } from './apps';
|
|
36
|
+
import { appsState, installedApps, settingsFile, type SiteApp } from './apps';
|
|
37
|
+
import { appsFileText, withInstalled, withOff } from '../lib/appsFile.mjs';
|
|
36
38
|
import { AnyIcon } from './icon';
|
|
37
39
|
import {
|
|
38
40
|
appCategories,
|
|
@@ -59,7 +61,7 @@ import { config } from '@site/editor';
|
|
|
59
61
|
import { allThemes, ThemeRow, useThemeCatalog } from './ThemeStore';
|
|
60
62
|
import { siteT, siteText, t } from './i18n';
|
|
61
63
|
import { OwnerLicenses, StoreLicenseRows } from './Licenses';
|
|
62
|
-
import {
|
|
64
|
+
import { StoreNotice, StoreRound, useStoreConnected, useStoreKey } from './StoreStatus';
|
|
63
65
|
import { StoreSearchRow, useStateFilter } from './StoreSearchRow';
|
|
64
66
|
import { sortChoice, useSort } from './storeSort';
|
|
65
67
|
import { told } from './notificationData';
|
|
@@ -68,6 +70,7 @@ import AppInfoTab from './AppInfoTab';
|
|
|
68
70
|
import AppAdds from './AppAdds';
|
|
69
71
|
import AppFormPage from './AppFormPage';
|
|
70
72
|
import AppSettingsTab from './AppSettingsTab';
|
|
73
|
+
import AppEnabledSwitch from './AppEnabledSwitch';
|
|
71
74
|
import { INFO_KEY } from '../lib/appRoute.mjs';
|
|
72
75
|
import { busyFor } from './busyAction.mjs';
|
|
73
76
|
|
|
@@ -185,6 +188,10 @@ export function kindsOf(app: SiteApp) {
|
|
|
185
188
|
|
|
186
189
|
const statusOf = (installed: boolean) => (installed ? t.apps.installed : t.apps.notInstalled);
|
|
187
190
|
|
|
191
|
+
/** What an installed app's row and page head say instead of "Installed": "Deaktiveret" while it is turned off, else "Opdatering
|
|
192
|
+
* findes" when it has a newer version; undefined when neither. */
|
|
193
|
+
const liveStatus = (off: boolean, update: boolean) => (off ? t.apps.deactivated : update ? t.apps.updateFound : undefined);
|
|
194
|
+
|
|
188
195
|
/** The store's catalog (store.ts) for a screen: null while it is read, 'failed' when it could not be; `skip`: not read
|
|
189
196
|
* (the page of one of the site's own apps). */
|
|
190
197
|
function useCatalog(env: Env, skip = false): ShownApp[] | null | 'failed' {
|
|
@@ -219,6 +226,7 @@ const storeIcon = (name?: string) => <LeadingIcon>{name && <AnyIcon name={name}
|
|
|
219
226
|
|
|
220
227
|
export function Apps({ env, files }: { env: Env; files: RepoFile[] }) {
|
|
221
228
|
const installed = installedApps(files);
|
|
229
|
+
const { off } = appsState(files);
|
|
222
230
|
const catalog = useCatalog(env);
|
|
223
231
|
const store = Array.isArray(catalog) ? catalog : [];
|
|
224
232
|
const [query, setQuery] = useKept('apps-search', '');
|
|
@@ -236,7 +244,7 @@ export function Apps({ env, files }: { env: Env; files: RepoFile[] }) {
|
|
|
236
244
|
publisher: a.publisher,
|
|
237
245
|
hint: a.hint,
|
|
238
246
|
icon: <LeadingIcon icon={a.icon} />,
|
|
239
|
-
status: updateOf(a, catalog)
|
|
247
|
+
status: liveStatus(off.includes(a.id), !!updateOf(a, catalog)) ?? '',
|
|
240
248
|
})),
|
|
241
249
|
...installed
|
|
242
250
|
.filter((id) => !apps.some((a) => a.id === id))
|
|
@@ -320,14 +328,14 @@ const developerHref = (name: string) => `#${DEVELOPER_ROUTE}/${slugify(name)}`;
|
|
|
320
328
|
|
|
321
329
|
/** An app's row in "Appstore" and on a developer's page: its icon, its name, what it does, and whether it is installed,
|
|
322
330
|
* opening its page. */
|
|
323
|
-
function AppRow({ app, installed }: { app: ReturnType<typeof allApps>[number]; installed: boolean }) {
|
|
331
|
+
function AppRow({ app, installed, off }: { app: ReturnType<typeof allApps>[number]; installed: boolean; off: boolean }) {
|
|
324
332
|
return (
|
|
325
333
|
<ListItem
|
|
326
334
|
href={`#apps/${app.id}`}
|
|
327
335
|
leading={app.glyph}
|
|
328
336
|
headline={app.label}
|
|
329
337
|
supporting={app.hint}
|
|
330
|
-
count={installed && t.apps.installed}
|
|
338
|
+
count={installed && (liveStatus(off, false) ?? t.apps.installed)}
|
|
331
339
|
arrow
|
|
332
340
|
/>
|
|
333
341
|
);
|
|
@@ -337,6 +345,7 @@ function AppRow({ app, installed }: { app: ReturnType<typeof allApps>[number]; i
|
|
|
337
345
|
* own), each opening its page. */
|
|
338
346
|
export function DeveloperPage({ slug, env, files }: { slug: string; env: Env; files: RepoFile[] }) {
|
|
339
347
|
const installed = installedApps(files);
|
|
348
|
+
const { off } = appsState(files);
|
|
340
349
|
const catalog = useCatalog(env);
|
|
341
350
|
const themeCatalog = useThemeCatalog(env);
|
|
342
351
|
const theirs = allApps(catalog)
|
|
@@ -364,7 +373,7 @@ export function DeveloperPage({ slug, env, files }: { slug: string; env: Env; fi
|
|
|
364
373
|
{theirs.length > 0 && (
|
|
365
374
|
<Group title={t.apps.developerApps}>
|
|
366
375
|
{theirs.map((a) => (
|
|
367
|
-
<AppRow key={a.id} app={a} installed={installed.includes(a.id)} />
|
|
376
|
+
<AppRow key={a.id} app={a} installed={installed.includes(a.id)} off={off.includes(a.id)} />
|
|
368
377
|
))}
|
|
369
378
|
</Group>
|
|
370
379
|
)}
|
|
@@ -404,6 +413,7 @@ export function StorePage({ env, files }: { env: Env; files: RepoFile[] }) {
|
|
|
404
413
|
|
|
405
414
|
function StoreScreen({ env, files, again }: { env: Env; files: RepoFile[]; again: () => void }) {
|
|
406
415
|
const installed = installedApps(files);
|
|
416
|
+
const { off } = appsState(files);
|
|
407
417
|
const key = useStoreKey();
|
|
408
418
|
const catalog = useCatalog(env);
|
|
409
419
|
const connected = useStoreConnected(env);
|
|
@@ -449,12 +459,11 @@ function StoreScreen({ env, files, again }: { env: Env; files: RepoFile[]; again
|
|
|
449
459
|
appGroups.map((g) => (
|
|
450
460
|
<Group key={g.key ?? 'all'} title={g.key ?? undefined}>
|
|
451
461
|
{g.items.map((a) => (
|
|
452
|
-
<AppRow key={a.id} app={a} installed={installed.includes(a.id)} />
|
|
462
|
+
<AppRow key={a.id} app={a} installed={installed.includes(a.id)} off={off.includes(a.id)} />
|
|
453
463
|
))}
|
|
454
464
|
</Group>
|
|
455
465
|
))
|
|
456
466
|
)}
|
|
457
|
-
<StoreFooter keyInfo={key} again={again} />
|
|
458
467
|
</Screen>
|
|
459
468
|
);
|
|
460
469
|
}
|
|
@@ -517,7 +526,7 @@ export async function autoUpdate(env: Env, files: RepoFile[]) {
|
|
|
517
526
|
}
|
|
518
527
|
if (changedFiles(app.id, record, current).length) continue;
|
|
519
528
|
const wanted = { files: await env.store.files(next), record: recordOf(next) };
|
|
520
|
-
const changes = [...appChanges(app.id, current, wanted,
|
|
529
|
+
const changes = [...appChanges(app.id, current, wanted, file), ...cookieChanges(app.id, cookiesIn(wanted.files), files)];
|
|
521
530
|
const change = t.app.changes.updated(kindAndName(t.app.changes.app, next.label), next.version);
|
|
522
531
|
await saveApp(env, siteT.apps.commit.update(next.written, next.version), change, changes, file);
|
|
523
532
|
updated.set(app.id, next.version);
|
|
@@ -542,7 +551,6 @@ function useStoreActions(env: Env, files: RepoFile[]) {
|
|
|
542
551
|
// Which action runs, on which app; `asking` while a dialog waits for the user (busyAction.mjs shows only the pressed button).
|
|
543
552
|
const [running, setRunning] = useState<{ id: string; action: StoreAction; asking: boolean } | null>(null);
|
|
544
553
|
const saving = !!running;
|
|
545
|
-
const installed = installedApps(files);
|
|
546
554
|
const file = files.find((f) => f.path === config.apps.installed);
|
|
547
555
|
|
|
548
556
|
async function change(
|
|
@@ -566,7 +574,7 @@ function useStoreActions(env: Env, files: RepoFile[]) {
|
|
|
566
574
|
setRunning({ id, action, asking: false });
|
|
567
575
|
env.notify({ text: text.busy });
|
|
568
576
|
const cookies = cookieChanges(id, wanted ? cookiesIn(wanted.files) : null, files);
|
|
569
|
-
await saveApp(env, text.commit, text.change, [...appChanges(id, current, wanted,
|
|
577
|
+
await saveApp(env, text.commit, text.change, [...appChanges(id, current, wanted, file), ...cookies], file);
|
|
570
578
|
env.notify({ text: text.done, kind: text.kind ?? 'saved' });
|
|
571
579
|
return true;
|
|
572
580
|
} catch (e) {
|
|
@@ -854,7 +862,7 @@ export function AppScreenPage({ app, id, env }: { app: SiteApp; id: string; env:
|
|
|
854
862
|
* not installed, with the pictures, what it needs, its cookies, who made it and where it is used). */
|
|
855
863
|
function AppContent({ app, env, files, tab, fields }: AppProps & { tab: AppTab; fields?: ReactNode }) {
|
|
856
864
|
const notify = env.notify;
|
|
857
|
-
const [toggling, setToggling] = useState<'install' | 'uninstall' | null>(null);
|
|
865
|
+
const [toggling, setToggling] = useState<'install' | 'uninstall' | 'switch' | null>(null);
|
|
858
866
|
const store = useStoreActions(env, files);
|
|
859
867
|
const saving = !!toggling || store.saving;
|
|
860
868
|
const catalog = useCatalog(env);
|
|
@@ -863,6 +871,7 @@ function AppContent({ app, env, files, tab, fields }: AppProps & { tab: AppTab;
|
|
|
863
871
|
const inStore = Array.isArray(catalog) ? catalog.find((a) => a.id === app.id) : undefined;
|
|
864
872
|
const installed = installedApps(files);
|
|
865
873
|
const on = installed.includes(app.id);
|
|
874
|
+
const off = on && appsState(files).off.includes(app.id);
|
|
866
875
|
// Where it is used: while it is, it cannot be uninstalled.
|
|
867
876
|
const uses = on ? usesOf(app, files) : [];
|
|
868
877
|
const stuck = uses.length > 0;
|
|
@@ -874,8 +883,7 @@ function AppContent({ app, env, files, tab, fields }: AppProps & { tab: AppTab;
|
|
|
874
883
|
if (!yes && !(await ask(t.apps.askUninstall(app.label), t.apps.uninstall))) return;
|
|
875
884
|
if (!yes && app.store) return store.uninstall(app.id, app.label, asWritten(app).label);
|
|
876
885
|
const file = files.find((f) => f.path === config.apps.installed);
|
|
877
|
-
const
|
|
878
|
-
const text = `${JSON.stringify({ installed: [...new Set(next)].sort() }, null, 2)}\n`;
|
|
886
|
+
const text = appsFileText(withInstalled(appsState(files), app.id, yes));
|
|
879
887
|
if (text === file?.text) return;
|
|
880
888
|
setToggling(yes ? 'install' : 'uninstall');
|
|
881
889
|
notify({ text: yes ? t.apps.installing : t.apps.uninstalling });
|
|
@@ -895,14 +903,32 @@ function AppContent({ app, env, files, tab, fields }: AppProps & { tab: AppTab;
|
|
|
895
903
|
}
|
|
896
904
|
}
|
|
897
905
|
|
|
906
|
+
// Turned on or off, saved at once in its own commit: the app stays installed with its folder, settings and version.
|
|
907
|
+
async function turn(enabled: boolean) {
|
|
908
|
+
const file = files.find((f) => f.path === config.apps.installed);
|
|
909
|
+
const text = appsFileText(withOff(appsState(files), app.id, !enabled));
|
|
910
|
+
if (text === file?.text) return;
|
|
911
|
+
setToggling('switch');
|
|
912
|
+
try {
|
|
913
|
+
const change = t.app.changes[enabled ? 'turnedOn' : 'turnedOff'](kindAndName(t.app.changes.app, app.label));
|
|
914
|
+
const content = { path: config.apps.installed, content: text, oid: file?.oid || null };
|
|
915
|
+
await saveApp(env, siteT.apps.commit[enabled ? 'turnOn' : 'turnOff'](asWritten(app).label), change, [content], file);
|
|
916
|
+
notify({ text: change, kind: 'saved' });
|
|
917
|
+
} catch (e) {
|
|
918
|
+
notify({ text: e instanceof ConflictError ? e.message : t.common.notSaved((e as Error).message), error: true, kind: 'failed' });
|
|
919
|
+
} finally {
|
|
920
|
+
setToggling(null);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
898
924
|
return (
|
|
899
925
|
<div>
|
|
900
926
|
<AppHead
|
|
901
927
|
icon={<LeadingIcon icon={app.icon} selected={on} />}
|
|
902
928
|
label={app.label}
|
|
903
929
|
publisher={app.publisher}
|
|
904
|
-
status={on && update
|
|
905
|
-
version={app
|
|
930
|
+
status={(on && liveStatus(off, !!update)) || statusOf(on)}
|
|
931
|
+
version={versionOf(app) || app.version}
|
|
906
932
|
hint={app.hint}
|
|
907
933
|
/>
|
|
908
934
|
{(!on || (update && app.store)) && (
|
|
@@ -920,12 +946,14 @@ function AppContent({ app, env, files, tab, fields }: AppProps & { tab: AppTab;
|
|
|
920
946
|
)}
|
|
921
947
|
{on && tab === 'settings' ? (
|
|
922
948
|
<>
|
|
949
|
+
<AppEnabledSwitch enabled={!off} busy={saving} onEnabled={turn} />
|
|
923
950
|
{fields}
|
|
924
951
|
<AppSettingsTab
|
|
925
952
|
app={app}
|
|
926
953
|
env={env}
|
|
927
954
|
files={files}
|
|
928
955
|
busy={saving}
|
|
956
|
+
enabled={!off}
|
|
929
957
|
uninstalling={toggling === 'uninstall' || store.busy(app.id, 'uninstall')}
|
|
930
958
|
stuck={stuck}
|
|
931
959
|
onUninstall={() => install(false)}
|
|
@@ -946,7 +974,7 @@ function AppContent({ app, env, files, tab, fields }: AppProps & { tab: AppTab;
|
|
|
946
974
|
id={`app/${app.id}`}
|
|
947
975
|
name={app.label}
|
|
948
976
|
author={app.publisher}
|
|
949
|
-
version={app
|
|
977
|
+
version={versionOf(app) || app.version}
|
|
950
978
|
declared={app}
|
|
951
979
|
/>
|
|
952
980
|
}
|
package/src/AppSettingsTab.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// The Settings tab of an installed app's page, below its head and its settings form: what the app shows itself (`page
|
|
2
|
-
// what it adds with its switches, its kinds of notifications with their switches, and "Afinstaller"
|
|
1
|
+
// The Settings tab of an installed app's page, below its head and its settings form: what the app shows itself (`page`,
|
|
2
|
+
// not while it is off), what it adds with its switches, its kinds of notifications with their switches, and "Afinstaller"
|
|
3
|
+
// with its text (it shows the loading indicator while it uninstalls). The "Aktiveret" switch is AppEnabledSwitch.tsx, in AppList.tsx.
|
|
3
4
|
// Exposes AppSettingsTab (default). Depends on AppAdds.tsx, AppNotificationSwitches.tsx, ./ui and notificationData.ts.
|
|
4
5
|
import { Button, SupportingText } from './ui';
|
|
5
6
|
import AppAdds from './AppAdds';
|
|
@@ -10,12 +11,14 @@ import type { SiteApp } from './apps';
|
|
|
10
11
|
import { appType } from './notificationData';
|
|
11
12
|
import { siteText, t } from './i18n';
|
|
12
13
|
|
|
13
|
-
/** `busy`: something is saving; `
|
|
14
|
+
/** `busy`: something is saving; `enabled`: the app is not turned off (hides `app.page` while it is off); `uninstalling`: this app is
|
|
15
|
+
* uninstalled now; `stuck`: the app is in use, so it cannot be uninstalled; `onUninstall`: "Afinstaller". */
|
|
14
16
|
export default function AppSettingsTab({
|
|
15
17
|
app,
|
|
16
18
|
env,
|
|
17
19
|
files,
|
|
18
20
|
busy,
|
|
21
|
+
enabled,
|
|
19
22
|
uninstalling,
|
|
20
23
|
stuck,
|
|
21
24
|
onUninstall,
|
|
@@ -24,6 +27,7 @@ export default function AppSettingsTab({
|
|
|
24
27
|
env: Env;
|
|
25
28
|
files: RepoFile[];
|
|
26
29
|
busy: boolean;
|
|
30
|
+
enabled: boolean;
|
|
27
31
|
uninstalling: boolean;
|
|
28
32
|
stuck: boolean;
|
|
29
33
|
onUninstall: () => void;
|
|
@@ -34,8 +38,8 @@ export default function AppSettingsTab({
|
|
|
34
38
|
}));
|
|
35
39
|
return (
|
|
36
40
|
<>
|
|
37
|
-
{/* What the app shows itself (its `page`), while it is installed. */}
|
|
38
|
-
{app.page && (
|
|
41
|
+
{/* What the app shows itself (its `page`), while it is installed and not off. */}
|
|
42
|
+
{app.page && enabled && (
|
|
39
43
|
<div data-app-page={app.id}>
|
|
40
44
|
<app.page api={(path, init) => env.appApi(app.id, path, init)} />
|
|
41
45
|
</div>
|