@morphemecms/cms 0.11.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 +31 -0
- package/astro.mjs +2 -1
- 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/appId.d.mts +1 -0
- package/lib/appId.mjs +6 -0
- package/lib/appRoute.d.mts +2 -2
- package/lib/appRoute.mjs +3 -3
- package/lib/appRoute.test.mjs +8 -8
- package/lib/apps.mjs +13 -3
- package/lib/apps.test.mjs +10 -1
- package/lib/contracts.d.mts +3 -2
- package/lib/contracts.mjs +3 -2
- package/lib/folderChanges.d.mts +29 -0
- package/lib/folderChanges.mjs +34 -0
- 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/section-ranges.d.mts +1 -1
- package/lib/section-ranges.mjs +8 -14
- package/lib/section-ranges.test.mjs +26 -5
- package/lib/storeEntries.d.mts +3 -1
- package/lib/storeEntries.mjs +22 -8
- package/lib/storeEntries.test.mjs +59 -7
- package/lib/text-sections.d.mts +11 -1
- package/lib/text-sections.mjs +18 -8
- package/lib/text-sections.test.mjs +34 -1
- package/lib/translate.d.mts +11 -0
- package/lib/translate.mjs +13 -1
- package/lib/translate.test.mjs +23 -2
- package/lib/video-id.d.mts +6 -2
- package/lib/video-id.mjs +5 -1
- package/lib/video.d.mts +6 -2
- package/lib/video.mjs +5 -1
- package/lib/without-h1.d.mts +1 -0
- package/lib/without-h1.mjs +19 -4
- package/lib/without-h1.test.mjs +30 -1
- package/package.json +3 -2
- package/scripts/app-screenshots.mjs +9 -8
- package/scripts/check-content.mjs +12 -2
- package/scripts/check-language.mjs +0 -1
- package/scripts/check-site-types.mjs +26 -0
- package/scripts/editor-screens.mjs +3 -3
- package/scripts/site-types.mjs +14 -0
- package/scripts/site-types.test.mjs +21 -0
- package/site/activeTheme.test.mjs +52 -0
- package/site/appSelf.d.mts +2 -0
- package/site/appSelf.mjs +23 -0
- package/site/appSelf.test.mjs +35 -0
- package/site/content.ts +5 -2
- package/site/defineSite.mjs +2 -2
- package/site/schema.ts +3 -3
- package/site/siteModules.mjs +2 -1
- package/site/theme.mjs +19 -8
- package/src/Advanced.tsx +2 -2
- package/src/App.tsx +79 -78
- package/src/AppAdds.tsx +1 -1
- package/src/AppList.tsx +39 -24
- package/src/BlockDialog.tsx +39 -18
- 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/CreateFab.tsx +8 -0
- package/src/DataForm.tsx +10 -20
- package/src/Deleted.tsx +3 -3
- package/src/EditorFooter.tsx +2 -2
- package/src/EntryForm.tsx +3 -3
- package/src/FooterSettings.tsx +2 -2
- package/src/FormFrame.tsx +4 -3
- package/src/HeaderSettings.tsx +3 -3
- package/src/Keywords.tsx +5 -5
- package/src/Library.tsx +75 -82
- package/src/Licenses.tsx +8 -8
- package/src/ListFab.tsx +13 -0
- package/src/MainDrawerParts.tsx +3 -3
- package/src/MainMenu.tsx +1 -1
- package/src/MarkdownEditor.tsx +88 -47
- package/src/Media.tsx +11 -20
- package/src/MediaTags.tsx +2 -2
- package/src/Notifications.tsx +2 -2
- package/src/Parts.tsx +17 -17
- package/src/Redirects.tsx +14 -22
- package/src/Screen.tsx +16 -6
- package/src/SeoScores.tsx +6 -6
- package/src/SideColumn.tsx +2 -2
- package/src/StartFab.tsx +6 -8
- package/src/ThemeStore.tsx +37 -16
- package/src/Tokens.tsx +2 -2
- package/src/apps.ts +7 -8
- package/src/checkSite.ts +86 -20
- package/src/drawerYield.d.mts +3 -0
- package/src/drawerYield.mjs +10 -0
- package/src/drawerYield.test.mjs +22 -0
- package/src/editor.css +59 -38
- package/src/examples.ts +1 -1
- package/src/fabScreens.test.mjs +28 -0
- package/src/fabScreens.ts +16 -0
- 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/legacyMove.test.mjs +137 -0
- package/src/legacyMove.ts +131 -0
- package/src/licenseData.ts +3 -3
- package/src/markdown.mjs +10 -7
- package/src/markdown.test.mjs +16 -2
- package/src/menuChildren.ts +5 -5
- package/src/menuOpen.ts +20 -4
- package/src/movedRoutes.d.mts +8 -1
- package/src/movedRoutes.mjs +102 -21
- package/src/movedRoutes.test.mjs +128 -6
- package/src/nav.ts +1 -1
- package/src/notificationData.ts +1 -1
- package/src/pageH1Panel.d.mts +4 -0
- package/src/pageH1Panel.mjs +31 -0
- package/src/pageH1Panel.test.mjs +56 -0
- package/src/pagePreview.tsx +22 -12
- package/src/schema.ts +2 -2
- package/src/sectionFrame.ts +3 -3
- package/src/sectionState.d.mts +3 -3
- package/src/sectionState.mjs +6 -6
- package/src/sectionState.test.mjs +14 -2
- package/src/sheetPlacement.d.mts +6 -4
- package/src/sheetPlacement.mjs +15 -10
- package/src/sheetPlacement.test.mjs +16 -9
- package/src/sidePanel.ts +38 -0
- 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 +36 -31
- package/src/tagUses.d.mts +1 -1
- package/src/tagUses.mjs +2 -2
- package/src/tagUses.test.mjs +1 -1
- package/src/topBarSlot.tsx +7 -15
- package/src/ui.tsx +33 -22
- package/src/virtual.d.ts +6 -0
- package/starter/package.json +1 -1
- package/theme/components/PageHeading.astro +22 -0
- package/theme/layouts/Base.astro +2 -2
- package/theme/layouts/PageLayout.astro +37 -20
- package/theme/styles/global.css +2 -1
- package/theme/styles/ui.css +40 -10
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The preview's copy of the coloured H1 panel of a page (theme/layouts/PageLayout.astro): the heading and the text up to the first
|
|
2
|
+
// H2 in one section (data-editor="h1-section"). Exposes setH1Panel, which makes, changes or takes away that panel in the page's
|
|
3
|
+
// document, whatever the page on the site has. Depends on lib/text-sections.mjs (sectionClass); used by pagePreview.tsx.
|
|
4
|
+
import { sectionClass } from '../lib/text-sections.mjs';
|
|
5
|
+
|
|
6
|
+
/** Puts the page's header (the layout's `article > header`) in a panel of this colour, with the intro (HTML, in the text's own
|
|
7
|
+
* class) after it; no colour (null) leaves the header without a panel. A panel the site's page has is first taken away.
|
|
8
|
+
* @param {Document} doc @param {string | null} color @param {string} introHtml */
|
|
9
|
+
export function setH1Panel(doc, color, introHtml) {
|
|
10
|
+
const header = doc.querySelector('article > header');
|
|
11
|
+
const text = doc.querySelector('[data-editor="text"]');
|
|
12
|
+
if (!header || !text) return;
|
|
13
|
+
const old = header.querySelector('[data-editor="h1-section"]');
|
|
14
|
+
if (old) {
|
|
15
|
+
old.querySelector('[data-editor="intro"]')?.remove();
|
|
16
|
+
old.replaceWith(...Array.from(old.childNodes));
|
|
17
|
+
}
|
|
18
|
+
if (!color) return;
|
|
19
|
+
const panel = doc.createElement('section');
|
|
20
|
+
panel.setAttribute('data-editor', 'h1-section');
|
|
21
|
+
panel.className = sectionClass(color);
|
|
22
|
+
panel.append(...Array.from(header.childNodes));
|
|
23
|
+
if (introHtml.trim()) {
|
|
24
|
+
const intro = doc.createElement(text.tagName);
|
|
25
|
+
intro.setAttribute('data-editor', 'intro');
|
|
26
|
+
intro.className = text.className;
|
|
27
|
+
intro.innerHTML = introHtml;
|
|
28
|
+
panel.append(intro);
|
|
29
|
+
}
|
|
30
|
+
header.append(panel);
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Tests src/pageH1Panel.mjs: the preview puts a page's header and intro in a coloured panel, or takes the panel away.
|
|
2
|
+
// Run: node --test src/pageH1Panel.test.mjs
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
5
|
+
import { JSDOM } from 'jsdom';
|
|
6
|
+
import { setH1Panel } from './pageH1Panel.mjs';
|
|
7
|
+
|
|
8
|
+
const PAGE = (header) =>
|
|
9
|
+
new JSDOM(`<body><article><header>${header}</header><div><div data-editor="text" class="prose">x</div></div></article></body>`).window
|
|
10
|
+
.document;
|
|
11
|
+
const PLAIN = '<h1 data-editor="heading">Title</h1>';
|
|
12
|
+
|
|
13
|
+
test('the header and the intro go in a panel of the colour', () => {
|
|
14
|
+
const doc = PAGE(PLAIN);
|
|
15
|
+
setH1Panel(doc, 'tint', '<p>Intro</p>');
|
|
16
|
+
const panel = doc.querySelector('header > section.text-section.text-section-tint[data-editor="h1-section"]');
|
|
17
|
+
assert.ok(panel);
|
|
18
|
+
assert.equal(panel.querySelector('h1').textContent, 'Title');
|
|
19
|
+
const intro = panel.querySelector('[data-editor="intro"]');
|
|
20
|
+
assert.equal(intro.innerHTML, '<p>Intro</p>');
|
|
21
|
+
assert.equal(intro.className, 'prose');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('without an intro the panel holds the header only', () => {
|
|
25
|
+
const doc = PAGE(PLAIN);
|
|
26
|
+
setH1Panel(doc, 'accent', ' ');
|
|
27
|
+
assert.ok(doc.querySelector('[data-editor="h1-section"] h1'));
|
|
28
|
+
assert.equal(doc.querySelector('[data-editor="intro"]'), null);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('no colour takes a panel away and keeps the header', () => {
|
|
32
|
+
const doc = PAGE(
|
|
33
|
+
`<section data-editor="h1-section" class="text-section text-section-tint">${PLAIN}<div data-editor="intro">a</div></section>`,
|
|
34
|
+
);
|
|
35
|
+
setH1Panel(doc, null, '');
|
|
36
|
+
assert.equal(doc.querySelector('[data-editor="h1-section"]'), null);
|
|
37
|
+
assert.equal(doc.querySelector('[data-editor="intro"]'), null);
|
|
38
|
+
assert.equal(doc.querySelector('header > h1').textContent, 'Title');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('another colour replaces the panel of the site', () => {
|
|
42
|
+
const doc = PAGE(
|
|
43
|
+
`<section data-editor="h1-section" class="text-section text-section-tint">${PLAIN}<div data-editor="intro">a</div></section>`,
|
|
44
|
+
);
|
|
45
|
+
setH1Panel(doc, 'surface', '<p>b</p>');
|
|
46
|
+
assert.equal(doc.querySelectorAll('[data-editor="h1-section"]').length, 1);
|
|
47
|
+
assert.ok(doc.querySelector('.text-section-surface'));
|
|
48
|
+
assert.equal(doc.querySelector('.text-section-tint'), null);
|
|
49
|
+
assert.equal(doc.querySelector('[data-editor="intro"]').innerHTML, '<p>b</p>');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('a page without a header is left as it is', () => {
|
|
53
|
+
const doc = new JSDOM('<body><div data-editor="text">x</div></body>').window.document;
|
|
54
|
+
setH1Panel(doc, 'tint', '<p>a</p>');
|
|
55
|
+
assert.equal(doc.querySelector('section'), null);
|
|
56
|
+
});
|
package/src/pagePreview.tsx
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// puts a block that ends the text (data-editor="end", with its type in data-editor-block). Only what
|
|
9
9
|
// was changed is put in, so the rest is exactly as on the site. The text is the site's Markdown
|
|
10
10
|
// (Astro's pipeline, with the HTML written in it), and its blocks are drawn as in the editor
|
|
11
|
-
// (registry.tsx preview), with the site's classes; a coloured section is a panel as on the site (lib/text-sections.mjs).
|
|
11
|
+
// (registry.tsx preview), with the site's classes; a coloured section, and a coloured H1 with the text up to the first H2 (pageH1Panel.mjs), is a panel as on the site (lib/text-sections.mjs).
|
|
12
12
|
//
|
|
13
13
|
// Exposes pageDocument, usePagePreview and PageDraft, and for the preview of a part every page has (partPreview.tsx) siteShell,
|
|
14
14
|
// documentHtml, htmlOf and PREVIEW_WIDTH.
|
|
@@ -25,7 +25,8 @@ import { useRef, useState, type ReactNode } from 'react';
|
|
|
25
25
|
import { createRoot } from 'react-dom/client';
|
|
26
26
|
import { flushSync } from 'react-dom';
|
|
27
27
|
import { parseBlock, splitBlocks } from '../lib/blocks.mjs';
|
|
28
|
-
import { sectionClass, sectionsOf } from '../lib/text-sections.mjs';
|
|
28
|
+
import { introOf, sectionClass, sectionsOf } from '../lib/text-sections.mjs';
|
|
29
|
+
import { h1SectionColor, withoutH1 } from '../lib/without-h1.mjs';
|
|
29
30
|
import { BREAKPOINTS, breakpointOf, type Breakpoint } from '../lib/breakpoints.mjs';
|
|
30
31
|
import { processor } from '../lib/markdown';
|
|
31
32
|
import { splitPicture } from '../lib/crop.mjs';
|
|
@@ -33,6 +34,7 @@ import { isPostPage, leadFill } from './postLead.mjs';
|
|
|
33
34
|
import { preview as drawBlock, siteBlocks } from './registry';
|
|
34
35
|
import { shownAt } from './images';
|
|
35
36
|
import { pageH1 } from './sections.mjs';
|
|
37
|
+
import { setH1Panel } from './pageH1Panel.mjs';
|
|
36
38
|
import type { BlockData } from './blocks';
|
|
37
39
|
import { PageFrame, BottomSheet, SegmentedButton, SheetTitle } from './ui';
|
|
38
40
|
import { t } from './i18n';
|
|
@@ -144,8 +146,11 @@ export async function pageDocument(draft: PageDraft): Promise<string> {
|
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
if (draft.body !== undefined) {
|
|
147
|
-
// A page's H1 at the top of its text is drawn in its own place above (draft.heading), as on the site
|
|
148
|
-
|
|
149
|
+
// A page's H1 at the top of its text is drawn in its own place above (draft.heading), as on the site; the colour of its
|
|
150
|
+
// section (the marker before it) puts the heading and the text up to the first H2 in one panel (setH1Panel).
|
|
151
|
+
const whole = isPostPage(draft) ? '' : await markdownHtml(draft.body);
|
|
152
|
+
const h1Color = h1SectionColor(whole);
|
|
153
|
+
let html = isPostPage(draft) ? await markdownHtml(pageH1(draft.body).rest) : withoutH1(whole);
|
|
149
154
|
// A block that ends the text, where the layout puts it.
|
|
150
155
|
const end = doc.querySelector('[data-editor="end"]');
|
|
151
156
|
const type = end?.getAttribute('data-editor-block');
|
|
@@ -171,14 +176,19 @@ export async function pageDocument(draft: PageDraft): Promise<string> {
|
|
|
171
176
|
leadBox.innerHTML = lead;
|
|
172
177
|
}
|
|
173
178
|
const types = siteBlocks.map((b) => b.type);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
const runs = (part: string) =>
|
|
180
|
+
sectionsOf(part)
|
|
181
|
+
.map((run) => {
|
|
182
|
+
const inner = splitBlocks(run.html, types)
|
|
183
|
+
.map((p) => ('html' in p ? p.html : blockHtml(p.type, p.props, draft.data)))
|
|
184
|
+
.join('');
|
|
185
|
+
return run.color ? `<section class="${sectionClass(run.color)}">${inner}</section>` : inner;
|
|
186
|
+
})
|
|
187
|
+
.join('');
|
|
188
|
+
// With a column beside the text the panel holds the heading only, as on the site.
|
|
189
|
+
const { intro, rest: below } = h1Color && !text.closest('[data-side]') ? introOf(rest) : { intro: '', rest };
|
|
190
|
+
setH1Panel(doc, h1Color, runs(intro));
|
|
191
|
+
text.innerHTML = runs(below);
|
|
182
192
|
}
|
|
183
193
|
return documentHtml(doc);
|
|
184
194
|
}
|
package/src/schema.ts
CHANGED
|
@@ -77,7 +77,7 @@ export type Field = {
|
|
|
77
77
|
* picked when it is made). */
|
|
78
78
|
locked?: boolean;
|
|
79
79
|
/** select: the choices on a page of their own under the form, at its address with the field's name after it
|
|
80
|
-
* (#apps/content-colors/
|
|
80
|
+
* (#apps/content-colors/settings/variant), in place of the sheet, drawn by this with what each gives (a picture of
|
|
81
81
|
* it); the field links there. Only a field of a part's own form (CompanyForm.tsx: an app's settings), not one in a
|
|
82
82
|
* group or a list; elsewhere the choices are in the sheet. */
|
|
83
83
|
page?: ComponentType<FieldPageProps>;
|
|
@@ -162,7 +162,7 @@ export type Template = {
|
|
|
162
162
|
* bar opens the booking sheet, as "Book en tid" does), else the entry itself opens. */
|
|
163
163
|
export type Opens = { label: string; as?: string };
|
|
164
164
|
|
|
165
|
-
/** A place in the editor (a route, e.g. '
|
|
165
|
+
/** A place in the editor (a route, e.g. 'company/contact') and the text of the link to it. */
|
|
166
166
|
export type Edit = { to: string; text: string };
|
|
167
167
|
|
|
168
168
|
// A kind of item in a list of blocks (a fixed page's sections; in the editor cards in its text, SectionsField.tsx).
|
package/src/sectionFrame.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// The dotted frame round each H2 section of a page or blog post: sets data-section-part (start, middle, end, only) and
|
|
2
|
-
// data-section-color (the
|
|
1
|
+
// The dotted frame round each H1 or H2 section of a page or blog post: sets data-section-part (start, middle, end, only) and
|
|
2
|
+
// data-section-color (the heading's section colour) on the top-level nodes of a section, which editor.css draws. Exposes
|
|
3
3
|
// sectionFrame (a Milkdown plugin). Uses lib/section-ranges.mjs and src/sectionState.mjs.
|
|
4
4
|
import { $prose } from '@milkdown/kit/utils';
|
|
5
5
|
import type { Node } from '@milkdown/kit/prose/model';
|
|
@@ -29,7 +29,7 @@ function sectionDecorations(doc: Node): Decoration[] {
|
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/** on: false for a fixed page's text (MarkdownEditor's `sections`), whose
|
|
32
|
+
/** on: false for a fixed page's text (MarkdownEditor's `sections`), whose headings are the names of its sections: no frame. */
|
|
33
33
|
export const sectionFrame = (on: boolean) =>
|
|
34
34
|
$prose(
|
|
35
35
|
() =>
|
package/src/sectionState.d.mts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
/** The top-level H2 the cursor is in, with its colour, or null. @param {import('@milkdown/kit/prose/state').EditorState} state
|
|
1
|
+
/** The top-level H1 or H2 the cursor is in, with its colour, or null. @param {import('@milkdown/kit/prose/state').EditorState} state
|
|
2
2
|
* @returns {{ pos: number, color: string | null } | null} */
|
|
3
3
|
export function sectionHeading(state: import("@milkdown/kit/prose/state").EditorState): {
|
|
4
4
|
pos: number;
|
|
5
5
|
color: string | null;
|
|
6
6
|
} | null;
|
|
7
|
-
/** Gives the H2 the cursor is in this colour (null: none). False when it is not in
|
|
7
|
+
/** Gives the H1 or H2 the cursor is in this colour (null: none). False when it is not in one, or the colour is not one of the theme's.
|
|
8
8
|
* @param {{ state: import('@milkdown/kit/prose/state').EditorState, dispatch: (tr: import('@milkdown/kit/prose/state').Transaction) => void }} view
|
|
9
9
|
* @param {string | null} color */
|
|
10
10
|
export function setSectionColor(view: {
|
|
11
11
|
state: import("@milkdown/kit/prose/state").EditorState;
|
|
12
12
|
dispatch: (tr: import("@milkdown/kit/prose/state").Transaction) => void;
|
|
13
13
|
}, color: string | null): boolean;
|
|
14
|
-
/** The colour of the section made of nodes[range.from..range.to] (sectionRanges): its H2's, null without one.
|
|
14
|
+
/** The colour of the section made of nodes[range.from..range.to] (sectionRanges): its H1's or H2's, null without one.
|
|
15
15
|
* @param {import('@milkdown/kit/prose/model').Node[]} nodes @param {{ from: number, to: number }} range @returns {string | null} */
|
|
16
16
|
export function sectionColorOf(nodes: import("@milkdown/kit/prose/model").Node[], range: {
|
|
17
17
|
from: number;
|
package/src/sectionState.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// The section colour in the editor's document (the *Sektionsfarve* button, MarkdownEditor.tsx; the frame, sectionFrame.ts): the
|
|
2
|
-
// top-level H2 the cursor is in (sectionHeading), setting its colour (setSectionColor) and the colour of a section (sectionColorOf).
|
|
2
|
+
// top-level H1 or H2 the cursor is in (sectionHeading), setting its colour (setSectionColor) and the colour of a section (sectionColorOf).
|
|
3
3
|
// Only ProseMirror's state; depends on lib/section-marker.mjs. No Milkdown, no browser.
|
|
4
4
|
import { SECTION_COLORS } from '../lib/section-marker.mjs';
|
|
5
5
|
|
|
6
|
-
/** The top-level H2 the cursor is in, with its colour, or null. @param {import('@milkdown/kit/prose/state').EditorState} state
|
|
6
|
+
/** The top-level H1 or H2 the cursor is in, with its colour, or null. @param {import('@milkdown/kit/prose/state').EditorState} state
|
|
7
7
|
* @returns {{ pos: number, color: string | null } | null} */
|
|
8
8
|
export function sectionHeading(state) {
|
|
9
9
|
const { $from } = state.selection;
|
|
10
10
|
const node = $from.parent;
|
|
11
|
-
if ($from.depth !== 1 || node.type.name !== 'heading' || node.attrs.level
|
|
11
|
+
if ($from.depth !== 1 || node.type.name !== 'heading' || node.attrs.level > 2) return null;
|
|
12
12
|
return { pos: $from.before(1), color: node.attrs.section ?? null };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/** Gives the H2 the cursor is in this colour (null: none). False when it is not in
|
|
15
|
+
/** Gives the H1 or H2 the cursor is in this colour (null: none). False when it is not in one, or the colour is not one of the theme's.
|
|
16
16
|
* @param {{ state: import('@milkdown/kit/prose/state').EditorState, dispatch: (tr: import('@milkdown/kit/prose/state').Transaction) => void }} view
|
|
17
17
|
* @param {string | null} color */
|
|
18
18
|
export function setSectionColor(view, color) {
|
|
@@ -23,9 +23,9 @@ export function setSectionColor(view, color) {
|
|
|
23
23
|
return true;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/** The colour of the section made of nodes[range.from..range.to] (sectionRanges): its H2's, null without one.
|
|
26
|
+
/** The colour of the section made of nodes[range.from..range.to] (sectionRanges): its H1's or H2's, null without one.
|
|
27
27
|
* @param {import('@milkdown/kit/prose/model').Node[]} nodes @param {{ from: number, to: number }} range @returns {string | null} */
|
|
28
28
|
export function sectionColorOf(nodes, range) {
|
|
29
|
-
const heading = nodes.slice(range.from, range.to + 1).find((n) => n.type.name === 'heading' && n.attrs.level
|
|
29
|
+
const heading = nodes.slice(range.from, range.to + 1).find((n) => n.type.name === 'heading' && n.attrs.level <= 2);
|
|
30
30
|
return heading?.attrs.section ?? null;
|
|
31
31
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Tests for src/sectionState.mjs: the H2 the cursor is in, setting its section colour, and the colour of a section.
|
|
1
|
+
// Tests for src/sectionState.mjs: the H1 or H2 the cursor is in, setting its section colour, and the colour of a section.
|
|
2
2
|
// Run: node --test packages/editor/src/sectionState.test.mjs
|
|
3
3
|
import { test } from 'node:test';
|
|
4
4
|
import assert from 'node:assert/strict';
|
|
@@ -16,11 +16,12 @@ const schema = new Schema({
|
|
|
16
16
|
},
|
|
17
17
|
});
|
|
18
18
|
const node = (type, text, attrs) => schema.node(type, attrs, text ? [schema.text(text)] : []);
|
|
19
|
-
// An H2 "Hours" (0-7, coloured tint), a paragraph (7-10), an H3 (10-
|
|
19
|
+
// An H2 "Hours" (0-7, coloured tint), a paragraph (7-10), an H3 (10-15), an H1 "Top" (15-20, coloured tint).
|
|
20
20
|
const doc = schema.node('doc', null, [
|
|
21
21
|
node('heading', 'Hours', { level: 2, section: 'tint' }),
|
|
22
22
|
node('paragraph', 'x'),
|
|
23
23
|
node('heading', 'Sub', { level: 3 }),
|
|
24
|
+
node('heading', 'Top', { level: 1, section: 'tint' }),
|
|
24
25
|
]);
|
|
25
26
|
const viewAt = (pos) => ({
|
|
26
27
|
state: EditorState.create({ doc, selection: TextSelection.create(doc, pos) }),
|
|
@@ -51,6 +52,17 @@ test('a colour the theme has not is refused, and nothing happens outside an H2',
|
|
|
51
52
|
assert.equal(view.state.doc.child(0).attrs.section, 'tint');
|
|
52
53
|
assert.equal(setSectionColor(viewAt(8), 'tint'), false);
|
|
53
54
|
});
|
|
55
|
+
test('the H1 the cursor is in', () => assert.deepEqual(sectionHeading(viewAt(17).state), { pos: 15, color: 'tint' }));
|
|
56
|
+
test('sets a colour on an H1', () => {
|
|
57
|
+
const view = viewAt(17);
|
|
58
|
+
assert.equal(setSectionColor(view, 'accent'), true);
|
|
59
|
+
assert.equal(view.state.doc.child(3).attrs.section, 'accent');
|
|
60
|
+
assert.equal(view.state.doc.child(3).attrs.level, 1);
|
|
61
|
+
});
|
|
62
|
+
test('the colour of a section is its H1’s', () => {
|
|
63
|
+
const kids = [node('heading', 'Top', { level: 1, section: 'accent' }), node('paragraph', 'x')];
|
|
64
|
+
assert.equal(sectionColorOf(kids, { from: 0, to: 1 }), 'accent');
|
|
65
|
+
});
|
|
54
66
|
test('the colour of a section is its H2’s, also after a label', () => {
|
|
55
67
|
const kids = [node('eyebrow', 'Label'), node('heading', 'Hours', { level: 2, section: 'surface' }), node('paragraph', 'x')];
|
|
56
68
|
assert.equal(sectionColorOf(kids, { from: 0, to: 2 }), 'surface');
|
package/src/sheetPlacement.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
/** step: 'choose' | 'fields' | 'section' | undefined;
|
|
2
|
-
export function sheetLook(step: any,
|
|
3
|
-
/** true exactly when sheetLook(step,
|
|
4
|
-
export function inPanel(step: any,
|
|
1
|
+
/** step: 'choose' | 'fields' | 'section' | undefined; expanded: the window is at least the `expanded` breakpoint. */
|
|
2
|
+
export function sheetLook(step: any, expanded: any): "side-sheet" | "modal-side-sheet" | "sheet";
|
|
3
|
+
/** true exactly when sheetLook(step, expanded) === 'side-sheet'. */
|
|
4
|
+
export function inPanel(step: any, expanded: any): boolean;
|
|
5
|
+
/** true when a card's pen and trash move into the side panel: the fields sheet is the side panel (inPanel('fields', expanded)) and the card has fields. */
|
|
6
|
+
export function editsInPanel(expanded: any, hasFields: any): any;
|
package/src/sheetPlacement.mjs
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
// The look of each step of the block sheet (BlockDialog.tsx): a side sheet beside the page from
|
|
2
|
-
// and the section settings, a modal side sheet below
|
|
3
|
-
// Exposes sheetLook(step,
|
|
1
|
+
// The look of each step of the block sheet (BlockDialog.tsx): a side sheet beside the page from expanded for the fields
|
|
2
|
+
// and the section settings, a modal side sheet below expanded for the fields, else the sheet from the bottom.
|
|
3
|
+
// Exposes sheetLook(step, expanded), inPanel(step, expanded) and editsInPanel(expanded, hasFields); no dependencies.
|
|
4
4
|
|
|
5
|
-
/** step: 'choose' | 'fields' | 'section' | undefined;
|
|
6
|
-
export function sheetLook(step,
|
|
7
|
-
if (step === 'fields') return
|
|
8
|
-
if (step === 'section') return
|
|
5
|
+
/** step: 'choose' | 'fields' | 'section' | undefined; expanded: the window is at least the `expanded` breakpoint. */
|
|
6
|
+
export function sheetLook(step, expanded) {
|
|
7
|
+
if (step === 'fields') return expanded ? 'side-sheet' : 'modal-side-sheet';
|
|
8
|
+
if (step === 'section') return expanded ? 'side-sheet' : 'sheet';
|
|
9
9
|
return 'sheet';
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/** true exactly when sheetLook(step,
|
|
13
|
-
export function inPanel(step,
|
|
14
|
-
return sheetLook(step,
|
|
12
|
+
/** true exactly when sheetLook(step, expanded) === 'side-sheet'. */
|
|
13
|
+
export function inPanel(step, expanded) {
|
|
14
|
+
return sheetLook(step, expanded) === 'side-sheet';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** true when a card's pen and trash move into the side panel: the fields sheet is the side panel (inPanel('fields', expanded)) and the card has fields. */
|
|
18
|
+
export function editsInPanel(expanded, hasFields) {
|
|
19
|
+
return hasFields && inPanel('fields', expanded);
|
|
15
20
|
}
|
|
@@ -1,44 +1,51 @@
|
|
|
1
1
|
// Tests for sheetPlacement.mjs: the look (side-sheet, modal-side-sheet, or sheet) and the panel flag of each step.
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import assert from 'node:assert/strict';
|
|
4
|
-
import { sheetLook, inPanel } from './sheetPlacement.mjs';
|
|
4
|
+
import { sheetLook, inPanel, editsInPanel } from './sheetPlacement.mjs';
|
|
5
5
|
|
|
6
|
-
test("'fields' below
|
|
6
|
+
test("'fields' below expanded is a modal side sheet and not in the panel", () => {
|
|
7
7
|
assert.equal(sheetLook('fields', false), 'modal-side-sheet');
|
|
8
8
|
assert.equal(inPanel('fields', false), false);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
test("'fields' at
|
|
11
|
+
test("'fields' at expanded is a side sheet and in the panel", () => {
|
|
12
12
|
assert.equal(sheetLook('fields', true), 'side-sheet');
|
|
13
13
|
assert.equal(inPanel('fields', true), true);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
test("'section' below
|
|
16
|
+
test("'section' below expanded is a sheet and not in the panel", () => {
|
|
17
17
|
assert.equal(sheetLook('section', false), 'sheet');
|
|
18
18
|
assert.equal(inPanel('section', false), false);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
test("'section' at
|
|
21
|
+
test("'section' at expanded is a side sheet and in the panel", () => {
|
|
22
22
|
assert.equal(sheetLook('section', true), 'side-sheet');
|
|
23
23
|
assert.equal(inPanel('section', true), true);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
test("'choose' below
|
|
26
|
+
test("'choose' below expanded is a sheet and not in the panel", () => {
|
|
27
27
|
assert.equal(sheetLook('choose', false), 'sheet');
|
|
28
28
|
assert.equal(inPanel('choose', false), false);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test("'choose' at
|
|
31
|
+
test("'choose' at expanded is a sheet and not in the panel", () => {
|
|
32
32
|
assert.equal(sheetLook('choose', true), 'sheet');
|
|
33
33
|
assert.equal(inPanel('choose', true), false);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
test('a closed sheet below
|
|
36
|
+
test('a closed sheet below expanded is a sheet and not in the panel', () => {
|
|
37
37
|
assert.equal(sheetLook(undefined, false), 'sheet');
|
|
38
38
|
assert.equal(inPanel(undefined, false), false);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
test('a closed sheet at
|
|
41
|
+
test('a closed sheet at expanded is a sheet and not in the panel', () => {
|
|
42
42
|
assert.equal(sheetLook(undefined, true), 'sheet');
|
|
43
43
|
assert.equal(inPanel(undefined, true), false);
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
test('a card edits in the panel only at expanded and with fields', () => {
|
|
47
|
+
assert.equal(editsInPanel(true, true), true);
|
|
48
|
+
assert.equal(editsInPanel(false, true), false);
|
|
49
|
+
assert.equal(editsInPanel(true, false), false);
|
|
50
|
+
assert.equal(editsInPanel(false, false), false);
|
|
51
|
+
});
|
package/src/sidePanel.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Whether the standard side panel (BlockDialog's standard side sheet) is open, and whether the menu button reopened the drawer
|
|
2
|
+
// while it is. Not remembered. Exposes setPanelOpen, setDrawerReopened, usePanelOpen, useDrawerReopened; read by menuOpen.ts.
|
|
3
|
+
import { useSyncExternalStore } from 'react';
|
|
4
|
+
|
|
5
|
+
let panelOpen = false;
|
|
6
|
+
let reopened = false;
|
|
7
|
+
const listeners = new Set<() => void>();
|
|
8
|
+
const notify = () => listeners.forEach((l) => l());
|
|
9
|
+
const subscribe = (changed: () => void) => {
|
|
10
|
+
listeners.add(changed);
|
|
11
|
+
return () => listeners.delete(changed);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** The panel opens or closes; closing ends a reopening of the drawer. */
|
|
15
|
+
export function setPanelOpen(next: boolean) {
|
|
16
|
+
panelOpen = next;
|
|
17
|
+
if (!next) reopened = false;
|
|
18
|
+
notify();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setDrawerReopened(next: boolean) {
|
|
22
|
+
reopened = next;
|
|
23
|
+
notify();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const usePanelOpen = () =>
|
|
27
|
+
useSyncExternalStore(
|
|
28
|
+
subscribe,
|
|
29
|
+
() => panelOpen,
|
|
30
|
+
() => false,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export const useDrawerReopened = () =>
|
|
34
|
+
useSyncExternalStore(
|
|
35
|
+
subscribe,
|
|
36
|
+
() => reopened,
|
|
37
|
+
() => false,
|
|
38
|
+
);
|
package/src/site.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { appIdOf, installedApps, withKinds, type AppDefinition, type AppList, ty
|
|
|
9
9
|
import { appBlocks, appFiles, appFolders, appPictures, appShared, appStores } from './appFiles';
|
|
10
10
|
import type { BlockDefinition, SiteBlock } from './blocks';
|
|
11
11
|
import type { ListRows } from './fields';
|
|
12
|
-
import { siteT, siteText, t, translatingSite } from './i18n';
|
|
12
|
+
import { inSiteLanguage, siteT, siteText, t, translatingSite } from './i18n';
|
|
13
13
|
import { byText, type Block, type Collection, type DataFile, type Edit, type Field, type OwnedList, type Part } from './schema';
|
|
14
14
|
import { blockTypeOf, type SharedTexts } from './shared';
|
|
15
15
|
import { appFolder, needsOf, SCREENSHOTS, shotFile } from './store';
|
|
@@ -19,17 +19,40 @@ const written = new WeakMap<object, object>();
|
|
|
19
19
|
/** A collection, a file, a part or a block as the site wrote it, in its language: for the commits. */
|
|
20
20
|
export const asWritten = <T extends object>(shown: T): T => (written.get(shown) as T | undefined) ?? shown;
|
|
21
21
|
|
|
22
|
-
// Only the keys a thing has, so it is the same as it was but for its texts.
|
|
23
|
-
function
|
|
22
|
+
// Only the keys a thing has, so it is the same as it was but for its texts. `link`: remember `from` as what it was written as.
|
|
23
|
+
function shownAs<T extends object>(from: T, texts: Partial<T>, link: boolean): T {
|
|
24
24
|
const to = { ...from };
|
|
25
25
|
for (const key of Object.keys(texts) as (keyof T)[]) if (from[key] !== undefined) to[key] = texts[key]!;
|
|
26
|
-
written.set(to, from);
|
|
26
|
+
if (link) written.set(to, from);
|
|
27
27
|
return to;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** The site's parts with every text of theirs said by `say`: the label, the hint, the link elsewhere,
|
|
31
|
-
* the message of a pattern, the options, a switch's two words … of each field, block, file and part.
|
|
32
|
-
|
|
31
|
+
* the message of a pattern, the options, a switch's two words … of each field, block, file and part.
|
|
32
|
+
* `sayWritten`: what asWritten gives back for each thing it returns, with its texts said by it (the standard parts'
|
|
33
|
+
* English in the site's language, inSiteLanguage); without it, asWritten gives back the thing as it was. */
|
|
34
|
+
export function translator(say: (text: string) => string, sayWritten?: (text: string) => string) {
|
|
35
|
+
const shownTexts = build(say, !sayWritten);
|
|
36
|
+
if (!sayWritten) return shownTexts;
|
|
37
|
+
const writtenTexts = build(sayWritten, false);
|
|
38
|
+
const both =
|
|
39
|
+
<A extends object>(one: (x: A) => A, said: (x: A) => A) =>
|
|
40
|
+
(x: A): A => {
|
|
41
|
+
const to = one(x);
|
|
42
|
+
written.set(to, said(x));
|
|
43
|
+
return to;
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
...shownTexts,
|
|
47
|
+
block: both(shownTexts.block, writtenTexts.block),
|
|
48
|
+
collection: both(shownTexts.collection, writtenTexts.collection),
|
|
49
|
+
part: both(shownTexts.part, writtenTexts.part),
|
|
50
|
+
app: both(shownTexts.app, writtenTexts.app),
|
|
51
|
+
} as typeof shownTexts;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function build(say: (text: string) => string, link: boolean) {
|
|
55
|
+
const shown = <T extends object>(from: T, texts: Partial<T>): T => shownAs(from, texts, link);
|
|
33
56
|
const maybe = (text?: string) => (text === undefined ? text : say(text));
|
|
34
57
|
const edit = (e?: Edit) => e && { ...e, text: say(e.text) };
|
|
35
58
|
const field = (f: Field): Field =>
|
|
@@ -94,7 +117,8 @@ export function translator(say: (text: string) => string) {
|
|
|
94
117
|
/** A colour under "Farver" (colorFields in @site/colors): its CSS variable without "--", its name and what it is for. */
|
|
95
118
|
type ColorField = (typeof site.colorFields)[number];
|
|
96
119
|
|
|
97
|
-
|
|
120
|
+
// A commit names a part in the site's language: the standard parts' English texts through inSiteLanguage, the site's own as written.
|
|
121
|
+
const local = translator(siteText, inSiteLanguage);
|
|
98
122
|
const each = <T>(all: T[], one: (x: T) => T) => (translatingSite ? all.map(one) : all);
|
|
99
123
|
|
|
100
124
|
// The screens under "Blokke" of the blocks with shared texts (shared.ts in the block's folder), named and
|
|
@@ -117,7 +141,7 @@ function sharedPartsOf(blocks: Folders<BlockDefinition>, shared: Folders<SharedT
|
|
|
117
141
|
fields,
|
|
118
142
|
defaults: values,
|
|
119
143
|
preview: { type, ...preview },
|
|
120
|
-
back: { href: '#
|
|
144
|
+
back: { href: '#blocks', label: t.common.parts.blocks },
|
|
121
145
|
},
|
|
122
146
|
];
|
|
123
147
|
});
|
|
@@ -142,7 +166,7 @@ export const writtenBlockParts: Part[] = [...site.blockParts, ...sharedParts, ..
|
|
|
142
166
|
/** The parts of "Virksomheden"; one with parts of its own (Part.parts) has them going back to it. */
|
|
143
167
|
export const companyParts = each(
|
|
144
168
|
site.companyParts.map((p) =>
|
|
145
|
-
p.parts ? { ...p, parts: p.parts.map((s) => ({ ...s, back: { href: `#
|
|
169
|
+
p.parts ? { ...p, parts: p.parts.map((s) => ({ ...s, back: { href: `#company/${p.id}`, label: p.label } })) } : p,
|
|
146
170
|
),
|
|
147
171
|
local.part,
|
|
148
172
|
);
|
|
@@ -155,9 +179,9 @@ export const partScreens = site.partScreens.map((s) => ({
|
|
|
155
179
|
local.part,
|
|
156
180
|
),
|
|
157
181
|
}));
|
|
158
|
-
/** The site's own parts under "Udseende" (#
|
|
182
|
+
/** The site's own parts under "Udseende" (#appearance, Library.tsx Theme), after the logo and icon, each going back to it. */
|
|
159
183
|
export const appearance = each(
|
|
160
|
-
site.appearance.map((p) => ({ ...p, back: { href: '#
|
|
184
|
+
site.appearance.map((p) => ({ ...p, back: { href: '#appearance', label: t.common.parts.appearance } })),
|
|
161
185
|
local.part,
|
|
162
186
|
);
|
|
163
187
|
/** Where a screen goes back to: the start screen. The menu's sections ("Indhold" …) are lists in the drawer, not screens,
|
|
@@ -190,7 +214,7 @@ export const placeOf = (id: string): string | undefined => groups.find((g) => g.
|
|
|
190
214
|
* ("Udseende › Standardsider"): the part with its field in the file of the standard pages. */
|
|
191
215
|
export function standardPageAt(key: string): string {
|
|
192
216
|
const chosenIn = (p: Part) => p.file === site.config.standardPages && !!p.fields?.some((f) => f.name === key);
|
|
193
|
-
const screen = [{ id: '
|
|
217
|
+
const screen = [{ id: 'appearance', label: t.common.parts.appearance, parts: appearance }, ...partScreens].find((s) =>
|
|
194
218
|
s.parts.some(chosenIn),
|
|
195
219
|
);
|
|
196
220
|
if (!screen) return '';
|
|
@@ -314,7 +338,7 @@ export const icons = {
|
|
|
314
338
|
),
|
|
315
339
|
};
|
|
316
340
|
|
|
317
|
-
/** The short lists, under "Lister" (#
|
|
341
|
+
/** The short lists, under "Lister" (#lists): the site's, the standard blocks' and the apps', which go back there too. */
|
|
318
342
|
export const lists = [...site.lists, ...ownedLists.map((l) => l.id)];
|
|
319
343
|
|
|
320
344
|
/** The lists under "Lister" now: the site's, the standard blocks' and those of the apps installed, without the cookie
|
|
@@ -14,7 +14,7 @@ export default defineBlock({
|
|
|
14
14
|
label: t.blocks.accordion.label,
|
|
15
15
|
icon: Rows,
|
|
16
16
|
hint: t.blocks.accordion.hint,
|
|
17
|
-
edit: { to: '
|
|
17
|
+
edit: { to: 'blocks/accordion', text: t.blocks.accordion.edit },
|
|
18
18
|
fields: [
|
|
19
19
|
...headFields,
|
|
20
20
|
{
|
|
@@ -5,7 +5,7 @@ import { siteT, t } from '../../i18n';
|
|
|
5
5
|
import { defineShared } from '../../shared';
|
|
6
6
|
|
|
7
7
|
/** The rows it is shown with where no page has it: its screen under "Blokke" and "Tilgængelige
|
|
8
|
-
*
|
|
8
|
+
* blocks" (index.tsx). In the site's language, like the drawing. */
|
|
9
9
|
export const example = { items: siteT.blocks.accordion.example.map((label) => ({ label, href: '' })) };
|
|
10
10
|
|
|
11
11
|
export default defineShared({
|
|
@@ -11,7 +11,7 @@ export default defineBlock({
|
|
|
11
11
|
label: t.blocks.buttons.label,
|
|
12
12
|
icon: CursorClick,
|
|
13
13
|
hint: t.blocks.buttons.hint,
|
|
14
|
-
edit: { to: '
|
|
14
|
+
edit: { to: 'blocks/buttons', text: t.blocks.buttons.edit },
|
|
15
15
|
fields: [{ ...buttonList, hint: t.blocks.buttons.itemsHint }],
|
|
16
16
|
preview: true,
|
|
17
17
|
detail: (p) => [
|
|
@@ -13,7 +13,7 @@ export default defineBlock({
|
|
|
13
13
|
label: t.blocks.cards.label,
|
|
14
14
|
icon: Cards,
|
|
15
15
|
hint: t.blocks.cards.hint,
|
|
16
|
-
edit: { to: '
|
|
16
|
+
edit: { to: 'blocks/cards', text: t.blocks.cards.edit },
|
|
17
17
|
fields: [
|
|
18
18
|
...headFields,
|
|
19
19
|
// An icon is the empty choice: so are the cards from before there was a choice.
|
|
@@ -4,7 +4,7 @@ import { siteT, t } from '../../i18n';
|
|
|
4
4
|
import { defineShared } from '../../shared';
|
|
5
5
|
|
|
6
6
|
/** The cards it is shown with where no page has it: its screen under "Blokke" and "Tilgængelige
|
|
7
|
-
*
|
|
7
|
+
* blocks" (index.tsx). In the site's language, like the drawing. With a page, so the link's text,
|
|
8
8
|
* which is edited there, is on them. */
|
|
9
9
|
export const example = { items: siteT.blocks.cards.example.map((c) => ({ ...c, href: '/' })) };
|
|
10
10
|
|
|
@@ -11,7 +11,7 @@ export default defineBlock({
|
|
|
11
11
|
label: t.blocks.cookies.label,
|
|
12
12
|
icon: Cookie,
|
|
13
13
|
hint: t.blocks.cookies.hint,
|
|
14
|
-
edit: { to: `
|
|
14
|
+
edit: { to: `list/${config.lists.cookies}`, text: t.blocks.cookies.edit },
|
|
15
15
|
fields: [],
|
|
16
16
|
example: {},
|
|
17
17
|
});
|
|
@@ -49,7 +49,7 @@ export default defineBlock({
|
|
|
49
49
|
label: t.blocks.posts.label,
|
|
50
50
|
icon: Newspaper,
|
|
51
51
|
hint: t.blocks.posts.hint,
|
|
52
|
-
edit: { to: '
|
|
52
|
+
edit: { to: 'blocks/posts', text: t.blocks.posts.edit },
|
|
53
53
|
fields: [
|
|
54
54
|
...headFields,
|
|
55
55
|
// A list (the empty choice: so are the blocks from before there was a choice), or the newest large over the list
|
|
@@ -36,7 +36,7 @@ export default defineBlock({
|
|
|
36
36
|
label: t.blocks.testimonials.label,
|
|
37
37
|
icon: Quotes,
|
|
38
38
|
hint: t.blocks.testimonials.hint,
|
|
39
|
-
edit: { to: `
|
|
39
|
+
edit: { to: `list/${config.lists.testimonials}`, text: t.blocks.testimonials.edit },
|
|
40
40
|
fields: [
|
|
41
41
|
...headFields,
|
|
42
42
|
{
|