@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
package/src/editor.css
CHANGED
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
/* The main drawer's header row (MainDrawerMenuButton in ui.tsx): the account row is a pill, shaped and shaded as a drawer
|
|
330
330
|
item is when pointed at. Its inline padding puts the avatar's centre over the items' icons (the avatar is wider than the
|
|
331
331
|
24px icon: half the difference less than the items' padding). The avatar has a 1px border inside its size.
|
|
332
|
-
The
|
|
332
|
+
The pill is as high as a drawer item (56px); the row around it is the top bar's height. */
|
|
333
333
|
.editor-main-drawer-header {
|
|
334
334
|
--list-item-padding-block: 0.375rem;
|
|
335
335
|
--list-item-two-line-min-height: var(--navigation-drawer-item-height, 3.5rem);
|
|
@@ -878,9 +878,7 @@ html {
|
|
|
878
878
|
|
|
879
879
|
/* A text with blocks (a page, a fixed page's sections): the cards close together, with the free line
|
|
880
880
|
between two of them to write in: thin, until the cursor is in it (editor-gap-here, MarkdownEditor.tsx)
|
|
881
|
-
or the pointer over it, when it opens up and says so.
|
|
882
|
-
line break share the cell, so the line is as tall as the hint, however many lines it wraps to, and the cursor sits
|
|
883
|
-
at the start of the hint. */
|
|
881
|
+
or the pointer over it, when it opens up and says so. */
|
|
884
882
|
.editor-spaced .site-block {
|
|
885
883
|
margin-block: 0.25rem;
|
|
886
884
|
}
|
|
@@ -901,37 +899,27 @@ html {
|
|
|
901
899
|
}
|
|
902
900
|
|
|
903
901
|
.editor-spaced .ProseMirror:focus > p.editor-gap-here:has(> br:only-child) {
|
|
904
|
-
display: grid;
|
|
905
902
|
line-height: 1.5rem;
|
|
906
903
|
}
|
|
907
904
|
|
|
908
905
|
.editor-spaced .ProseMirror:focus > p.editor-gap-here:has(> br:only-child)::before {
|
|
909
906
|
content: var(--editor-gap-text);
|
|
910
|
-
|
|
907
|
+
position: absolute;
|
|
911
908
|
color: var(--muted);
|
|
912
909
|
pointer-events: none;
|
|
913
910
|
}
|
|
914
911
|
|
|
915
|
-
.editor-spaced .ProseMirror:focus > p.editor-gap-here > br:only-child {
|
|
916
|
-
grid-area: 1 / 1;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
912
|
@media (hover: hover) {
|
|
920
913
|
.editor-spaced .ProseMirror > p:has(> br:only-child):hover {
|
|
921
|
-
display: grid;
|
|
922
914
|
line-height: 1.5rem;
|
|
923
915
|
}
|
|
924
916
|
|
|
925
917
|
.editor-spaced .ProseMirror > p:has(> br:only-child):hover::before {
|
|
926
918
|
content: var(--editor-gap-text);
|
|
927
|
-
|
|
919
|
+
position: absolute;
|
|
928
920
|
color: var(--muted);
|
|
929
921
|
pointer-events: none;
|
|
930
922
|
}
|
|
931
|
-
|
|
932
|
-
.editor-spaced .ProseMirror > p:hover > br:only-child {
|
|
933
|
-
grid-area: 1 / 1;
|
|
934
|
-
}
|
|
935
923
|
}
|
|
936
924
|
|
|
937
925
|
/* While a sheet or a dialog is open (shown modally, showModal), the page under it does not scroll (only the sheet does).
|
package/src/examples.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// What "Dele" (#
|
|
1
|
+
// What "Dele" (#appearance/parts: Parts.tsx, BlockParts.tsx) shows the parts and blocks with: made-up pages, posts, tags,
|
|
2
2
|
// testimonials, the lists' made-up rows (`examples` of a list), cookies and contact details in the editor's language,
|
|
3
3
|
// the apps' made-up settings (AppSettings.example), and a plain picture, never what the site has, so the parts look the
|
|
4
4
|
// same on every site.
|
package/src/fabScreens.test.mjs
CHANGED
|
@@ -11,14 +11,14 @@ test('the start screen makes a page or a blog post', () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
test('a list without kinds, or with one, has one action named after its create label', () => {
|
|
14
|
-
assert.deepEqual(listFab({ id: 'pages', create: 'New page' }, []), [{ label: 'New page', href: '#
|
|
15
|
-
assert.deepEqual(listFab({ id: 'popups', create: 'New promotion' }, [standard]), [{ label: 'New promotion', href: '#
|
|
14
|
+
assert.deepEqual(listFab({ id: 'pages', create: 'New page' }, []), [{ label: 'New page', href: '#new/pages' }]);
|
|
15
|
+
assert.deepEqual(listFab({ id: 'popups', create: 'New promotion' }, [standard]), [{ label: 'New promotion', href: '#new/popups' }]);
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
test('a list of several kinds has one action per kind', () => {
|
|
19
19
|
assert.deepEqual(listFab({ id: 'popups', create: 'New promotion' }, [standard, mailchimp]), [
|
|
20
|
-
{ label: 'Standard', href: '#
|
|
21
|
-
{ label: 'Mailchimp', href: '#
|
|
20
|
+
{ label: 'Standard', href: '#new/popups/standard' },
|
|
21
|
+
{ label: 'Mailchimp', href: '#new/popups/mailchimp' },
|
|
22
22
|
]);
|
|
23
23
|
});
|
|
24
24
|
|
package/src/fabScreens.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
/** The collections a new entry can be made in from the start screen, in this order: pages and blog posts. */
|
|
5
5
|
export const START_FAB = ['pages', 'blog'];
|
|
6
6
|
|
|
7
|
-
/** A list's create actions, as a label and where it goes: with several kinds one per kind (`#
|
|
8
|
-
* named `create` (`#
|
|
7
|
+
/** A list's create actions, as a label and where it goes: with several kinds one per kind (`#new/<id>/<kind>`), else one
|
|
8
|
+
* named `create` (`#new/<id>`); none for a list that cannot make entries (no `create`). */
|
|
9
9
|
export function listFab(
|
|
10
10
|
collection: { id: string; create?: string },
|
|
11
11
|
kinds: { value: string; label: string }[],
|
|
12
12
|
): { label: string; href: string }[] {
|
|
13
13
|
if (!collection.create) return [];
|
|
14
|
-
if (kinds.length > 1) return kinds.map((k) => ({ label: k.label, href: `#
|
|
15
|
-
return [{ label: collection.create, href: `#
|
|
14
|
+
if (kinds.length > 1) return kinds.map((k) => ({ label: k.label, href: `#new/${collection.id}/${k.value}` }));
|
|
15
|
+
return [{ label: collection.create, href: `#new/${collection.id}` }];
|
|
16
16
|
}
|
|
@@ -106,8 +106,8 @@ export const library: Messages['library'] = {
|
|
|
106
106
|
statusShown: { all: 'Alle', published: 'Udgivet', draft: 'Kladde', unlisted: 'Skjult', scheduled: 'Planlagt' },
|
|
107
107
|
sort: 'Sortér',
|
|
108
108
|
sorts: {
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
newest: 'Nyeste først',
|
|
110
|
+
oldest: 'Ældste først',
|
|
111
111
|
az: 'A–Å',
|
|
112
112
|
za: 'Å–A',
|
|
113
113
|
savedNewest: 'Senest ændret først',
|
package/src/i18n/da-DK/text.ts
CHANGED
|
@@ -35,5 +35,5 @@ export const text: Messages['text'] = {
|
|
|
35
35
|
hiddenOnPage: 'Skjult på siden',
|
|
36
36
|
showOnPage: 'Vis på siden',
|
|
37
37
|
hideOnPage: 'Skjul på siden',
|
|
38
|
-
typeHere: '
|
|
38
|
+
typeHere: 'Skriv tekst her …',
|
|
39
39
|
};
|
package/src/i18n/en-US/apps.ts
CHANGED
|
@@ -33,7 +33,7 @@ export const apps = {
|
|
|
33
33
|
cookiesAsk: 'Set only once the visitor has said yes in the cookie banner',
|
|
34
34
|
noCookies: 'Sets no cookies',
|
|
35
35
|
developer: 'Developer',
|
|
36
|
-
/** On a developer's page (#apps/
|
|
36
|
+
/** On a developer's page (#apps/developer/<name>): over their apps, and the number of them by their row on an app's page. */
|
|
37
37
|
developerApps: 'Apps',
|
|
38
38
|
appCount: (n: number) => (n === 1 ? '1 app' : `${n} apps`),
|
|
39
39
|
/** At the address of a developer no app has. */
|
package/src/i18n/en-US/common.ts
CHANGED
|
@@ -107,23 +107,23 @@ export const common = {
|
|
|
107
107
|
lists: 'Lists',
|
|
108
108
|
blocks: 'Block templates',
|
|
109
109
|
media: 'Files',
|
|
110
|
-
/** How the site looks (#
|
|
110
|
+
/** How the site looks (#appearance, Library.tsx Theme): the theme, the colours, the logo and icon, and the site's own
|
|
111
111
|
* parts there (appearance in packages/editor/defaults/editor.ts: "Standardsider"). */
|
|
112
112
|
appearance: 'Appearance',
|
|
113
|
-
/** The themes installed (#
|
|
113
|
+
/** The themes installed (#appearance/themes, Library.tsx Themes), the first row under "Udseende". */
|
|
114
114
|
themes: 'Themes',
|
|
115
|
-
/** The icon packs installed, to choose the one the site draws its icons with (#
|
|
115
|
+
/** The icon packs installed, to choose the one the site draws its icons with (#appearance/icon-packs, IconPacks). */
|
|
116
116
|
iconPacks: 'Icon packs',
|
|
117
|
-
/** How the site moves, Material 3's motion schemes (#
|
|
117
|
+
/** How the site moves, Material 3's motion schemes (#appearance/motion, MotionSchemes in Library.tsx). */
|
|
118
118
|
motion: 'Motion',
|
|
119
|
-
/** The block drawn in a column beside the text of the pages (#
|
|
119
|
+
/** The block drawn in a column beside the text of the pages (#appearance/side-column, SideColumn.tsx). */
|
|
120
120
|
sideColumn: 'Side column',
|
|
121
121
|
colors: 'Colors',
|
|
122
122
|
brand: 'Logo and icon',
|
|
123
123
|
blockSettings: 'Block settings',
|
|
124
124
|
/** The apps that come with the site (AppList.tsx, #apps). */
|
|
125
125
|
apps: 'Apps',
|
|
126
|
-
/** The parts the editor and the site are built of (#
|
|
126
|
+
/** The parts the editor and the site are built of (#appearance/parts: Parts.tsx, BlockParts.tsx). */
|
|
127
127
|
editorParts: 'Parts',
|
|
128
128
|
/** Under Indstillinger: the redirects and the site's own parts (#seo, Library.tsx Seo). */
|
|
129
129
|
seo: 'Search engine optimization',
|
|
@@ -131,9 +131,9 @@ export const common = {
|
|
|
131
131
|
account: 'Account',
|
|
132
132
|
/** The last section of the main drawer, and the first part of the path to a setting (placeOf in site.ts). */
|
|
133
133
|
settings: 'Settings',
|
|
134
|
-
/** Under Indstillinger: the faster editor (#
|
|
134
|
+
/** Under Indstillinger: the faster editor (#advanced, Advanced.tsx). */
|
|
135
135
|
advanced: 'Advanced',
|
|
136
|
-
/** Under Indstillinger: the licences of what the site and the editor are made with (#
|
|
136
|
+
/** Under Indstillinger: the licences of what the site and the editor are made with (#licenses, Licenses.tsx). */
|
|
137
137
|
licenses: 'Licenses',
|
|
138
138
|
/** Under Indstillinger: the CMS's version and its updates (#morpheme). */
|
|
139
139
|
morpheme: 'Morpheme',
|
|
@@ -10,7 +10,7 @@ export const library = {
|
|
|
10
10
|
createNew: 'Create',
|
|
11
11
|
githubAccount: (login: string) => `GitHub account: ${login}`,
|
|
12
12
|
logOut: 'Log out',
|
|
13
|
-
/** The bell in the top bar and its screen (#
|
|
13
|
+
/** The bell in the top bar and its screen (#notifications, Notifications.tsx): the messages shown at the bottom. */
|
|
14
14
|
notifications: 'Notifications',
|
|
15
15
|
/** The bell's name while some are not seen yet. */
|
|
16
16
|
notificationsNew: (count: number) => `Notifications, ${count} new`,
|
|
@@ -144,8 +144,8 @@ export const library = {
|
|
|
144
144
|
statusShown: { all: 'All', published: 'Published', draft: 'Draft', unlisted: 'Hidden', scheduled: 'Scheduled' },
|
|
145
145
|
sort: 'Sort',
|
|
146
146
|
sorts: {
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
newest: 'Newest first',
|
|
148
|
+
oldest: 'Oldest first',
|
|
149
149
|
az: 'A–Z',
|
|
150
150
|
za: 'Z–A',
|
|
151
151
|
/** The pages, by when they were last saved. */
|
|
@@ -153,7 +153,7 @@ export const library = {
|
|
|
153
153
|
savedOldest: 'Least recently changed first',
|
|
154
154
|
/** A list of kinds (Promovering), under a heading for each kind. */
|
|
155
155
|
type: 'By type',
|
|
156
|
-
/** "Standardblokke" (#
|
|
156
|
+
/** "Standardblokke" (#blocks), in the groups by where the blocks come from. */
|
|
157
157
|
group: 'By group',
|
|
158
158
|
},
|
|
159
159
|
/** The funnel over a list of what is shown on pages (Promovering), with its filters in one sheet. */
|
|
@@ -172,7 +172,7 @@ export const library = {
|
|
|
172
172
|
/** The filter by whether an entry is turned on, and its first choice. */
|
|
173
173
|
onOffFilter: 'Status',
|
|
174
174
|
allOnOff: 'All',
|
|
175
|
-
/** The filters over "Standardblokke" (#
|
|
175
|
+
/** The filters over "Standardblokke" (#blocks): by the group (where the block comes from) and by whether
|
|
176
176
|
* the block is on a page, each with its first choice. */
|
|
177
177
|
groupFilter: 'Group',
|
|
178
178
|
allGroups: 'All groups',
|
package/src/i18n/en-US/parts.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "Dele" (#
|
|
1
|
+
// "Dele" (#appearance/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
|
-
/** "Dele" › "Værdier" (#
|
|
256
|
+
/** "Dele" › "Værdier" (#appearance/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
|
-
/** "Dele" (#
|
|
358
|
+
/** "Dele" (#appearance/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',
|
package/src/i18n/en-US/text.ts
CHANGED
|
@@ -43,5 +43,5 @@ export const text = {
|
|
|
43
43
|
showOnPage: 'Show on the page',
|
|
44
44
|
hideOnPage: 'Hide on the page',
|
|
45
45
|
/** Shown in an empty line of the text, under the pointer or the cursor. */
|
|
46
|
-
typeHere: '
|
|
46
|
+
typeHere: 'Write text here…',
|
|
47
47
|
};
|
package/src/i18n/index.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// (siteText, @site/texts). The blocks' drawings and the commits (siteT) stay in the site's
|
|
10
10
|
// language, so the site looks as it is, and the history is in one language, whoever saved.
|
|
11
11
|
// A value the editor writes into content (a default text) is in the site's language too (inSiteLanguage).
|
|
12
|
+
// The standard parts are written in English; their Danish is in defaults/i18n/da-DK/, the default of @site/texts.
|
|
12
13
|
// A language is a folder with the texts by the part of the editor they are in (en-US/index.ts). English is the
|
|
13
14
|
// reference: its texts are written first (Messages is its type), and every other language translates them; a new
|
|
14
15
|
// one is a copy of da-DK with its texts translated from en-US, added to `messages`. TypeScript says when a
|
|
@@ -82,7 +83,7 @@ export const t: Messages = messages[locale];
|
|
|
82
83
|
export const siteT: Messages = messages[siteLocale];
|
|
83
84
|
|
|
84
85
|
/** The language the package's standard parts (defaults/) are written in, whatever the site's language is. */
|
|
85
|
-
export const DEFAULTS_LOCALE: Locale = '
|
|
86
|
+
export const DEFAULTS_LOCALE: Locale = 'en-US';
|
|
86
87
|
|
|
87
88
|
/** Whether the site's own texts (written in the site's language) or the package's standard parts (written in
|
|
88
89
|
* DEFAULTS_LOCALE) may be shown in another language than they are written in. */
|
|
@@ -100,9 +101,7 @@ export const appTexts: Record<string, Texts> = Object.fromEntries(
|
|
|
100
101
|
}),
|
|
101
102
|
);
|
|
102
103
|
|
|
103
|
-
const own
|
|
104
|
-
? Object.assign({}, ...Object.values(appTexts).map((a) => a[locale]), (texts as Texts)[locale])
|
|
105
|
-
: {};
|
|
104
|
+
const own = Object.assign({}, ...Object.values(appTexts).map((a) => a[locale]), (texts as Texts)[locale]);
|
|
106
105
|
|
|
107
106
|
/** One of the site's own texts (src/site/, src/themes/<theme>/blocks/, src/apps/), written in the site's language, in the editor's:
|
|
108
107
|
* its translation in @site/texts or in a block's texts.ts, else as it is written. `values` fill in
|
|
@@ -111,10 +110,7 @@ export function siteText(text: string, values?: Record<string, string | number>)
|
|
|
111
110
|
return translate(own, text, values);
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
const inSite
|
|
115
|
-
siteLocale === DEFAULTS_LOCALE
|
|
116
|
-
? {}
|
|
117
|
-
: Object.assign({}, ...Object.values(appTexts).map((a) => a[siteLocale]), (texts as Texts)[siteLocale]);
|
|
113
|
+
const inSite = Object.assign({}, ...Object.values(appTexts).map((a) => a[siteLocale]), (texts as Texts)[siteLocale]);
|
|
118
114
|
|
|
119
115
|
/** A text of the package's standard parts (written in DEFAULTS_LOCALE) in the site's language, for a value that the
|
|
120
116
|
* editor writes into content (a default button text): its translation in @site/texts or in a block's texts.ts, else
|
package/src/images.ts
CHANGED
|
@@ -98,7 +98,7 @@ export async function prepareImage(file: File): Promise<Upload> {
|
|
|
98
98
|
const now = new Date();
|
|
99
99
|
const folder = `${config.folders.media}/${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
100
100
|
// A short random ending, so two pictures called IMG_1234 do not get the same name.
|
|
101
|
-
const name = `${slugify(file.name.replace(/\.[^.]*$/, '')) || '
|
|
101
|
+
const name = `${slugify(file.name.replace(/\.[^.]*$/, '')) || 'picture'}-${Math.random().toString(36).slice(2, 6)}`;
|
|
102
102
|
const ext = blob.type === 'image/webp' ? 'webp' : 'jpg';
|
|
103
103
|
const upload = {
|
|
104
104
|
url: URL.createObjectURL(blob),
|
|
@@ -119,7 +119,7 @@ export const FILE_MAX_MB = 10;
|
|
|
119
119
|
export async function prepareFile(file: File, taken: string[]): Promise<Upload> {
|
|
120
120
|
if (file.size > FILE_MAX_MB * 1024 * 1024) throw new Error(t.media.tooBig(FILE_MAX_MB));
|
|
121
121
|
const folder = config.folders.files;
|
|
122
|
-
const stem = slugify(file.name.replace(/\.[^.]*$/, '')) || '
|
|
122
|
+
const stem = slugify(file.name.replace(/\.[^.]*$/, '')) || 'file';
|
|
123
123
|
const ending = pickedEnding(file);
|
|
124
124
|
let path = `${folder}/${stem}.${ending}`;
|
|
125
125
|
for (let n = 2; taken.includes(path); n++) path = `${folder}/${stem}-${n}.${ending}`;
|
package/src/licenseData.ts
CHANGED
|
@@ -20,8 +20,8 @@ export type ThirdParty =
|
|
|
20
20
|
/** The licence of one's own work that grants nothing ("all rights reserved"), as npm writes it. */
|
|
21
21
|
export const UNLICENSED = 'UNLICENSED';
|
|
22
22
|
|
|
23
|
-
/** Who brings a work: the editor, the site's own code, or an app or a theme (`app/<id>`, `
|
|
24
|
-
export type LicenseWhere = 'editor' | 'site' | `app/${string}` | `
|
|
23
|
+
/** Who brings a work: the editor, the site's own code, or an app or a theme (`app/<id>`, `theme/<id>`, LicenseOwner.id). */
|
|
24
|
+
export type LicenseWhere = 'editor' | 'site' | `app/${string}` | `theme/${string}`;
|
|
25
25
|
|
|
26
26
|
/** A third-party work as the build found it: its id (a package's name; `ikoner/<id>` for an icon set; `<owner>/<name>`
|
|
27
27
|
* for another work an app or a theme declares), its name, version, who made it, its licence's name and text (`standard`:
|
|
@@ -46,7 +46,7 @@ export type LicensedWork = {
|
|
|
46
46
|
/** An app or a theme with its own licence (`text`: LICENSE in its folder, none when it is UNLICENSED) and the works it
|
|
47
47
|
* brings (their ids: those it declares, and those shipped through them alone), and whether the site is built with it. */
|
|
48
48
|
export type LicenseOwner = {
|
|
49
|
-
id: `app/${string}` | `
|
|
49
|
+
id: `app/${string}` | `theme/${string}`;
|
|
50
50
|
kind: 'app' | 'theme';
|
|
51
51
|
/** Its folder's name in src/apps/ or src/themes/. */
|
|
52
52
|
key: string;
|
package/src/menuChildren.ts
CHANGED
|
@@ -18,16 +18,16 @@ const listOf = (ids: string[]): MenuChild[] =>
|
|
|
18
18
|
|
|
19
19
|
/** The sub-items of a menu item, in their order: none for most. */
|
|
20
20
|
export function childrenOf(id: string, files: RepoFile[]): MenuChild[] {
|
|
21
|
-
if (id === '
|
|
22
|
-
if (id === '
|
|
21
|
+
if (id === 'lists') return listOf(listsShown(files));
|
|
22
|
+
if (id === 'company') return companyParts.map((p) => ({ id: p.id, label: p.label, href: `#company/${p.id}` }));
|
|
23
23
|
if (id === 'apps') {
|
|
24
24
|
const installed = installedApps(files);
|
|
25
25
|
return apps.filter((a) => installed.includes(a.id)).map((a) => ({ id: a.id, label: a.label, href: `#apps/${a.id}` }));
|
|
26
26
|
}
|
|
27
|
-
if (id === '
|
|
27
|
+
if (id === 'redirects') {
|
|
28
28
|
return [
|
|
29
|
-
{ id: '
|
|
30
|
-
{ id: '
|
|
29
|
+
{ id: 'all', label: t.redirects.all, href: `#${TAB_ROUTES.redirects}` },
|
|
30
|
+
{ id: 'not-found', label: t.redirects.missing.title, href: `#${TAB_ROUTES.missing}` },
|
|
31
31
|
];
|
|
32
32
|
}
|
|
33
33
|
return [];
|
package/src/movedRoutes.d.mts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
/** Where each moved screen is now, by its old address. */
|
|
2
2
|
export const MOVED: Record<string, string>;
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Old route prefix (Danish, one or more segments, as the routes were before the ids got English names) -> its new
|
|
5
|
+
* English prefix. Only a route that starts with the old prefix is renamed, and the rest of it is data (a file path, a
|
|
6
|
+
* slug, an id) that keeps its name. A key segment `*` matches any one segment and keeps it (in the value too).
|
|
7
|
+
* Old links keep working through it. The longest matching key wins.
|
|
8
|
+
*/
|
|
9
|
+
export const RENAMED: Record<string, string>;
|
|
10
|
+
export function movedRoute(route: string, more?: Record<string, string>, renamed?: Record<string, string>): string;
|
package/src/movedRoutes.mjs
CHANGED
|
@@ -1,35 +1,116 @@
|
|
|
1
|
-
// The editor's screens that have moved, by their old address after the "#": a link or a bookmark to one
|
|
2
|
-
// it is now. Exposes MOVED and movedRoute (pure). Used by App.tsx (routeOf), which adds the app store's own.
|
|
1
|
+
// The editor's screens that have moved or been renamed, by their old address after the "#": a link or a bookmark to one
|
|
2
|
+
// opens it where it is now. Exposes MOVED, RENAMED and movedRoute (pure). Used by App.tsx (routeOf), which adds the app store's own.
|
|
3
3
|
|
|
4
4
|
/** Where each moved screen is now, by its old address. */
|
|
5
5
|
export const MOVED = /** @type {Record<string, string>} */ ({
|
|
6
|
-
farver: '
|
|
7
|
-
'virksomheden/logo': '
|
|
8
|
-
'tema/blokvalg': '
|
|
9
|
-
dele: '
|
|
10
|
-
tema: '
|
|
11
|
-
'tema/farver': '
|
|
12
|
-
'tema/logo': '
|
|
13
|
-
'tema/dele': '
|
|
14
|
-
'tema/dele/editor': '
|
|
15
|
-
'virksomheden/bank': '
|
|
16
|
-
videresendelser: 'seo/
|
|
17
|
-
'videresendelser/alle': 'seo/
|
|
18
|
-
'videresendelser/ikke-fundet': 'seo/
|
|
6
|
+
farver: 'appearance/colors',
|
|
7
|
+
'virksomheden/logo': 'appearance/logo',
|
|
8
|
+
'tema/blokvalg': 'block-settings',
|
|
9
|
+
dele: 'appearance/parts',
|
|
10
|
+
tema: 'appearance',
|
|
11
|
+
'tema/farver': 'appearance/colors',
|
|
12
|
+
'tema/logo': 'appearance/logo',
|
|
13
|
+
'tema/dele': 'appearance/parts',
|
|
14
|
+
'tema/dele/editor': 'appearance/parts/editor',
|
|
15
|
+
'virksomheden/bank': 'company/payment/bank',
|
|
16
|
+
videresendelser: 'seo/redirects',
|
|
17
|
+
'videresendelser/alle': 'seo/redirects/all',
|
|
18
|
+
'videresendelser/ikke-fundet': 'seo/redirects/not-found',
|
|
19
19
|
// The settings of the booking and newsletter apps were on a screen "Promovering"; they are on the apps' own pages now.
|
|
20
20
|
promovering: 'apps',
|
|
21
21
|
'promovering/calendly': 'apps/app_a030dfba69793996',
|
|
22
22
|
'promovering/mailchimp': 'apps/app_5c4b6ba067ef127f',
|
|
23
23
|
// The header's menus and settings were on a screen of their own under "Globalt"; they are under "Udseende" now.
|
|
24
|
-
header: '
|
|
24
|
+
header: 'appearance/header',
|
|
25
25
|
// The footer's form was the file's own screen; it is under "Udseende" now.
|
|
26
|
-
'src/data/footer.json': '
|
|
26
|
+
'src/data/footer.json': 'appearance/footer',
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Old route prefix (Danish, one or more segments, as the routes were before the ids got English names) -> its new
|
|
31
|
+
* English prefix. Only a route that starts with the old prefix is renamed, and the rest of it is data (a file path, a
|
|
32
|
+
* slug, an id) that keeps its name. A key segment `*` matches any one segment and keeps it (in the value too).
|
|
33
|
+
* Old links keep working through it. The longest matching key wins.
|
|
34
|
+
*/
|
|
35
|
+
export const RENAMED = /** @type {Record<string, string>} */ ({
|
|
36
|
+
indstillinger: 'settings',
|
|
37
|
+
'indstillinger/appstore/temaer': 'settings/appstore/themes',
|
|
38
|
+
'apps/*/indstillinger': 'apps/*/settings',
|
|
39
|
+
'apps/*/om': 'apps/*/about',
|
|
40
|
+
'apps/udvikler': 'apps/developer',
|
|
41
|
+
udseende: 'appearance',
|
|
42
|
+
'udseende/temaer': 'appearance/themes',
|
|
43
|
+
'udseende/ikonpakker': 'appearance/icon-packs',
|
|
44
|
+
'udseende/bevaegelse': 'appearance/motion',
|
|
45
|
+
'udseende/sidekolonne': 'appearance/side-column',
|
|
46
|
+
'udseende/farver': 'appearance/colors',
|
|
47
|
+
'udseende/farver/reservefarver': 'appearance/colors/fallback-colors',
|
|
48
|
+
'udseende/dele': 'appearance/parts',
|
|
49
|
+
'udseende/dele/blokke': 'appearance/parts/blocks',
|
|
50
|
+
'udseende/dele/liste': 'appearance/parts/list',
|
|
51
|
+
'udseende/dele/vaerdier': 'appearance/parts/values',
|
|
52
|
+
'udseende/standardsider': 'appearance/default-pages',
|
|
53
|
+
maaling: 'analytics',
|
|
54
|
+
privatliv: 'privacy',
|
|
55
|
+
'privatliv/cookiebanner': 'privacy/cookie-banner',
|
|
56
|
+
'privatliv/samtykker': 'privacy/consents',
|
|
57
|
+
'privatliv/privatlivssider': 'privacy/privacy-pages',
|
|
58
|
+
lister: 'lists',
|
|
59
|
+
liste: 'list',
|
|
60
|
+
'liste/kategorier': 'list/tags',
|
|
61
|
+
'liste/udtalelser': 'list/testimonials',
|
|
62
|
+
'liste/mobilmenu': 'list/mobile-menu',
|
|
63
|
+
ny: 'new',
|
|
64
|
+
'ny/kategorier': 'new/tags',
|
|
65
|
+
'ny/udtalelser': 'new/testimonials',
|
|
66
|
+
slettet: 'deleted',
|
|
67
|
+
'slettet/kategorier': 'deleted/tags',
|
|
68
|
+
'slettet/udtalelser': 'deleted/testimonials',
|
|
69
|
+
'slettet/mobilmenu': 'deleted/mobile-menu',
|
|
70
|
+
'slettet/filer': 'deleted/files',
|
|
71
|
+
virksomheden: 'company',
|
|
72
|
+
'virksomheden/kontakt': 'company/contact',
|
|
73
|
+
'virksomheden/sociale-medier': 'company/social-media',
|
|
74
|
+
'virksomheden/betaling': 'company/payment',
|
|
75
|
+
blokke: 'blocks',
|
|
76
|
+
filer: 'files',
|
|
77
|
+
'seo/videresendelser': 'seo/redirects',
|
|
78
|
+
'seo/videresendelser/alle': 'seo/redirects/all',
|
|
79
|
+
'seo/videresendelser/ikke-fundet': 'seo/redirects/not-found',
|
|
80
|
+
'seo/interne-links': 'seo/internal-links',
|
|
81
|
+
'seo/vurdering': 'seo/rating',
|
|
82
|
+
blokvalg: 'block-settings',
|
|
83
|
+
avanceret: 'advanced',
|
|
84
|
+
licenser: 'licenses',
|
|
85
|
+
konto: 'account',
|
|
86
|
+
notifikationer: 'notifications',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The address a screen is at now: where it moved to (`more` first, then MOVED), else the address with its longest old
|
|
91
|
+
* prefix in `renamed` replaced and the rest as it is, else the same address.
|
|
31
92
|
* @param {string} route
|
|
32
93
|
* @param {Record<string, string>} [more]
|
|
94
|
+
* @param {Record<string, string>} [renamed]
|
|
33
95
|
* @returns {string}
|
|
34
96
|
*/
|
|
35
|
-
export const movedRoute = (route, more = {}
|
|
97
|
+
export const movedRoute = (route, more = {}, renamed = RENAMED) => {
|
|
98
|
+
const moved = more[route] ?? MOVED[route];
|
|
99
|
+
if (moved !== undefined) return moved;
|
|
100
|
+
const parts = route.split('/');
|
|
101
|
+
let best = null;
|
|
102
|
+
for (const key of Object.keys(renamed)) {
|
|
103
|
+
const old = key.split('/');
|
|
104
|
+
if (old.length > parts.length || (best && old.length <= best.old.length)) continue;
|
|
105
|
+
if (old.every((segment, n) => segment === '*' || segment === parts[n])) best = { key, old };
|
|
106
|
+
}
|
|
107
|
+
if (!best) return route;
|
|
108
|
+
const kept = parts.slice(0, best.old.length);
|
|
109
|
+
let n = 0;
|
|
110
|
+
const now = renamed[best.key].split('/').map((segment) => {
|
|
111
|
+
if (segment !== '*') return segment;
|
|
112
|
+
while (best.old[n] !== '*') n += 1;
|
|
113
|
+
return kept[n++];
|
|
114
|
+
});
|
|
115
|
+
return [...now, ...parts.slice(best.old.length)].join('/');
|
|
116
|
+
};
|