@morphemecms/cms 0.2.0 → 0.3.0
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 +6 -0
- package/defaults/collections.ts +11 -0
- package/defaults/editor.ts +2 -0
- package/defaults/standardPages.ts +3 -17
- package/defaults/texts.ts +3 -0
- package/icons/lucide/calendar-x.svg +1 -0
- package/icons/lucide/identification-card.svg +1 -0
- package/icons/lucide/money.svg +1 -0
- package/icons/lucide/receipt.svg +1 -0
- package/icons/lucide/wallet.svg +1 -0
- package/icons/material-symbols/calendar-x.svg +1 -0
- package/icons/material-symbols/identification-card.svg +1 -0
- package/icons/material-symbols/money.svg +1 -0
- package/icons/material-symbols/receipt.svg +1 -0
- package/icons/material-symbols/wallet.svg +1 -0
- package/icons/phosphor/calendar-x.svg +1 -0
- package/icons/phosphor/identification-card.svg +1 -0
- package/icons/phosphor/money.svg +1 -0
- package/icons/phosphor/receipt.svg +1 -0
- package/icons/phosphor/wallet.svg +1 -0
- package/lib/side-block.d.mts +45 -0
- package/lib/side-block.mjs +75 -0
- package/lib/side-block.test.d.mts +1 -0
- package/lib/side-block.test.mjs +79 -0
- package/package.json +1 -1
- package/scripts/check-side.mjs +56 -0
- package/scripts/check-themes.mjs +15 -1
- package/scripts/editor-roundtrip.mjs +13 -2
- package/scripts/fixtures/side/block/View.tsx +9 -0
- package/scripts/fixtures/side/block/load.ts +8 -0
- package/scripts/fixtures/side/side-off.md +8 -0
- package/scripts/fixtures/side/side-plain.md +7 -0
- package/scripts/fixtures/side/side-wide.md +8 -0
- package/scripts/fixtures/side/side.json +3 -0
- package/scripts/icon-packs.mjs +3 -3
- package/site/content.config.ts +4 -0
- package/site/defineSite.d.mts +7 -1
- package/site/defineSite.mjs +7 -1
- package/site/icons.d.mts +21 -0
- package/site/icons.mjs +16 -1
- package/site/schema.ts +1 -1
- package/site/side.ts +10 -0
- package/site/site.ts +2 -0
- package/src/App.tsx +3 -1
- package/src/EntryForm.tsx +17 -6
- package/src/Library.tsx +9 -2
- package/src/SideColumn.tsx +83 -0
- package/src/frontmatter.d.mts +2 -0
- package/src/frontmatter.mjs +6 -0
- package/src/frontmatter.test.d.mts +1 -0
- package/src/frontmatter.test.mjs +27 -0
- package/src/i18n/da-DK/app.ts +1 -0
- package/src/i18n/da-DK/common.ts +1 -0
- package/src/i18n/da-DK/library.ts +9 -0
- package/src/i18n/en-US/app.ts +1 -0
- package/src/i18n/en-US/common.ts +2 -0
- package/src/i18n/en-US/library.ts +12 -0
- package/src/schema.ts +2 -0
- package/src/standard/Block.astro +7 -2
- package/src/standard/BlockSite.astro +2 -2
- package/src/standard/accordion/lead.ts +4 -0
- package/src/standard/cards/lead.ts +4 -0
- package/src/standard/frontLeads.ts +12 -0
- package/src/standard/hero/index.tsx +1 -1
- package/src/standard/hero/lead.ts +4 -0
- package/src/standard/leads.d.mts +8 -0
- package/src/standard/leads.mjs +14 -0
- package/src/standard/leads.test.d.mts +1 -0
- package/src/standard/leads.test.mjs +27 -0
- package/src/standard/posts/lead.ts +4 -0
- package/src/standard/steps/lead.ts +4 -0
- package/src/standard/testimonials/lead.ts +4 -0
- package/src/standard/view.tsx +5 -2
- package/src/standard/views.ts +8 -0
- package/theme/layouts/PageLayout.astro +19 -5
- package/theme/styles/components.css +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @morphemecms/cms
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 07db64b: A site can show one block in a column beside the text of its pages: a block says it can with `beside: true` in its `load.ts`, the owner picks it under Appearance › Side column (`src/data/side.json`), and a page can turn it off (`side: false`); each block now gives its own front-page lead in a `lead.ts`; `defineSite` takes `logo` for the structured data; and the icon packs have calendar-x, identification-card, money, receipt and wallet.
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/defaults/collections.ts
CHANGED
|
@@ -347,6 +347,17 @@ export const collections: Collection[] = [
|
|
|
347
347
|
layout: true,
|
|
348
348
|
hint: 'Siden fylder hele bredden uden spalten ved siden af teksten.',
|
|
349
349
|
},
|
|
350
|
+
{
|
|
351
|
+
name: 'side',
|
|
352
|
+
label: 'Sidekolonne',
|
|
353
|
+
type: 'boolean',
|
|
354
|
+
toggle: ['Til', 'Fra'],
|
|
355
|
+
// Without the key the column is on; only "Fra" is written (side: false).
|
|
356
|
+
default: true,
|
|
357
|
+
missing: true,
|
|
358
|
+
layout: true,
|
|
359
|
+
hint: 'Blokken ved siden af teksten vises på siden. Den vælges under Udseende › Sidekolonne.',
|
|
360
|
+
},
|
|
350
361
|
{
|
|
351
362
|
name: 'review',
|
|
352
363
|
label: 'Til gennemsyn',
|
package/defaults/editor.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
PuzzlePiece,
|
|
40
40
|
Shapes,
|
|
41
41
|
ShieldCheck,
|
|
42
|
+
SidebarSimple,
|
|
42
43
|
Signpost,
|
|
43
44
|
Smiley,
|
|
44
45
|
SlidersHorizontal,
|
|
@@ -133,6 +134,7 @@ export const icons: Record<string, Icon> = {
|
|
|
133
134
|
logo: Sticker,
|
|
134
135
|
ikonpakker: Smiley,
|
|
135
136
|
bevaegelse: WaveSine,
|
|
137
|
+
sidekolonne: SidebarSimple,
|
|
136
138
|
dele: Shapes,
|
|
137
139
|
};
|
|
138
140
|
|
|
@@ -9,25 +9,11 @@
|
|
|
9
9
|
// "Kontakt", the footer's "Find vej" and Google's contact details (the structured data) go to it. The privacy policy and
|
|
10
10
|
// the cookie policy are shown at their own addresses too; the cookie banner's "Læs mere" goes to the cookie policy (its
|
|
11
11
|
// heading about cookies, when it is the privacy policy: cookiesHref in packages/editor/site/standardPages.ts). What of a section of the front page is only text and pictures is
|
|
12
|
-
// written as text above its card (its lead, packages/editor/src/sections.mjs): in the editor, when that page is open
|
|
12
|
+
// written as text above its card (its lead, lead.ts in the block's folder, frontLeads.ts; packages/editor/src/sections.mjs): in the editor, when that page is open
|
|
13
13
|
// (EntryForm.tsx), and on the site, when it is read into sections. No React here, so the build can use it.
|
|
14
|
-
import type {
|
|
14
|
+
import type { Template } from '../src/schema';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
* the standard block "Banner", whose text up to its "Knapper" block (with its heading and picture) is the text at the
|
|
18
|
-
* top; the block and the text after it are on its card. */
|
|
19
|
-
export const frontLeads: Record<string, Lead> = {
|
|
20
|
-
hero: { body: 'text', upTo: 'buttons' },
|
|
21
|
-
accordion: { heading: 'heading', body: 'intro' },
|
|
22
|
-
testimonials: { heading: 'heading' },
|
|
23
|
-
presentation: { heading: 'heading', body: 'paragraphs', list: true },
|
|
24
|
-
cards: { heading: 'heading' },
|
|
25
|
-
book: { image: true },
|
|
26
|
-
steps: { heading: 'heading' },
|
|
27
|
-
prices: { heading: 'heading', body: 'notes', list: true },
|
|
28
|
-
posts: { heading: 'heading' },
|
|
29
|
-
address: { image: true, heading: 'heading', body: 'text' },
|
|
30
|
-
};
|
|
16
|
+
export { frontLeads } from '../src/standard/frontLeads';
|
|
31
17
|
|
|
32
18
|
/** A standard page: its field in the file (`key`), its name there and in a message ("Forsiden …") in the site's language,
|
|
33
19
|
* where it is shown (null: nowhere at an address, the 404 page; left out: at its own address), the rules its page has
|
package/defaults/texts.ts
CHANGED
|
@@ -26,6 +26,9 @@ export const texts: Record<string, Record<string, string>> = {
|
|
|
26
26
|
'Skærmlæsere springer billedet over.': 'Screen readers skip the picture.',
|
|
27
27
|
Til: 'On',
|
|
28
28
|
Fra: 'Off',
|
|
29
|
+
Sidekolonne: 'Side column',
|
|
30
|
+
'Blokken ved siden af teksten vises på siden. Den vælges under Udseende › Sidekolonne.':
|
|
31
|
+
'The block beside the text is shown on the page. It is chosen under Appearance › Side column.',
|
|
29
32
|
'Hele bredden': 'Full width',
|
|
30
33
|
'Siden fylder hele bredden uden spalten ved siden af teksten.': 'The page fills the whole width without the column beside the text.',
|
|
31
34
|
Status: 'Status',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v3" /> <path d="M16 2v3" /> <rect x="3" y="3" width="18" height="18" rx="2" /> <path d="M3 9h18" /> <path d="m14 13-4 4" /> <path d="m10 13 4 4" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 19a4 4 0 00-8 0" /> <path d="M16 10h2" /> <path d="M16 14h2" /> <circle cx="9" cy="12" r="3" /> <rect x="2" y="5" width="20" height="14" rx="2" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="12" x="2" y="6" rx="2" /> <circle cx="12" cy="12" r="2" /> <path d="M6 12h.01M18 12h.01" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17V7" /> <path d="M16 8h-6a2 2 0 0 0 0 4h4a2 2 0 0 1 0 4H8" /> <path d="M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" /> <path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" /></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m381-218-43-43 100-99-100-99 43-43 99 100 99-100 43 43-100 99 100 99-43 43-99-100-99 100ZM180-80q-24 0-42-18t-18-42v-620q0-24 18-42t42-18h65v-60h65v60h340v-60h65v60h65q24 0 42 18t18 42v620q0 24-18 42t-42 18H180Zm0-60h600v-430H180v430Zm0-490h600v-130H180v130Zm0 0v-130 130Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M580-450h180v-60H580v60Zm0-120h180v-60H580v60ZM200-320h320v-19q0-42-42.5-68.5T360-434q-75 0-117.5 26.5T200-339v19Zm211.5-195.42q21.5-21.42 21.5-51.5t-21.42-51.58q-21.42-21.5-51.5-21.5t-51.58 21.42q-21.5 21.42-21.5 51.5t21.42 51.58q21.42 21.5 51.5 21.5t51.58-21.42ZM140-160q-24 0-42-18t-18-42v-520q0-24 18-42t42-18h680q24 0 42 18t18 42v520q0 24-18 42t-42 18H140Zm0-60h680v-520H140v520Zm0 0v-520 520Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M540-420q-50 0-85-35t-35-85q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35ZM220-280q-24.75 0-42.37-17.63Q160-315.25 160-340v-400q0-24.75 17.63-42.38Q195.25-800 220-800h640q24.75 0 42.38 17.62Q920-764.75 920-740v400q0 24.75-17.62 42.37Q884.75-280 860-280H220Zm100-60h440q0-42 29-71t71-29v-200q-42 0-71-29t-29-71H320q0 42-29 71t-71 29v200q42 0 71 29t29 71Zm480 180H100q-24.75 0-42.37-17.63Q40-195.25 40-220v-460h60v460h700v60ZM220-340v-400 400Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-81v-798l60 60 60-60 60 60 60-60 60 60 60-60 60 60 60-60 60 60 60-60 60 60 60-60v798l-60-60-60 60-60-60-60 60-60-60-60 60-60-59.85L360-81l-60-59.85L240-81l-60-59.85L120-81Zm117-215h490v-60H237v60Zm0-154h490v-60H237v60Zm0-155h490v-60H237v60Zm-57 423h600v-596H180v596Zm0-596v596-596Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M240-160q-66 0-113-47T80-320v-320q0-66 47-113t113-47h480q66 0 113 47t47 113v320q0 66-47 113t-113 47H240Zm0-470h480q29 0 54.5 9t45.5 26v-45q0-42-29-71t-71-29H240q-42 0-71 29t-29 71v45q20-17 45.5-26t54.5-9Zm-97 136 477 115q7 2 14.5.5T647-385l160-134q-13-23-36-37t-51-14H240q-35 0-62 21.5T143-494Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM72,48v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80H48V48ZM208,208H48V96H208V208Zm-50.34-74.34L139.31,152l18.35,18.34a8,8,0,0,1-11.32,11.32L128,163.31l-18.34,18.35a8,8,0,0,1-11.32-11.32L116.69,152,98.34,133.66a8,8,0,0,1,11.32-11.32L128,140.69l18.34-18.35a8,8,0,0,1,11.32,11.32Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M200,112a8,8,0,0,1-8,8H152a8,8,0,0,1,0-16h40A8,8,0,0,1,200,112Zm-8,24H152a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Zm40-80V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56ZM216,200V56H40V200H216Zm-80.26-34a8,8,0,1,1-15.5,4c-2.63-10.26-13.06-18-24.25-18s-21.61,7.74-24.25,18a8,8,0,1,1-15.5-4,39.84,39.84,0,0,1,17.19-23.34,32,32,0,1,1,45.12,0A39.76,39.76,0,0,1,135.75,166ZM96,136a16,16,0,1,0-16-16A16,16,0,0,0,96,136Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M128,88a40,40,0,1,0,40,40A40,40,0,0,0,128,88Zm0,64a24,24,0,1,1,24-24A24,24,0,0,1,128,152ZM240,56H16a8,8,0,0,0-8,8V192a8,8,0,0,0,8,8H240a8,8,0,0,0,8-8V64A8,8,0,0,0,240,56ZM193.65,184H62.35A56.78,56.78,0,0,0,24,145.65v-35.3A56.78,56.78,0,0,0,62.35,72h131.3A56.78,56.78,0,0,0,232,110.35v35.3A56.78,56.78,0,0,0,193.65,184ZM232,93.37A40.81,40.81,0,0,1,210.63,72H232ZM45.37,72A40.81,40.81,0,0,1,24,93.37V72ZM24,162.63A40.81,40.81,0,0,1,45.37,184H24ZM210.63,184A40.81,40.81,0,0,1,232,162.63V184Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M72,104a8,8,0,0,1,8-8h96a8,8,0,0,1,0,16H80A8,8,0,0,1,72,104Zm8,40h96a8,8,0,0,0,0-16H80a8,8,0,0,0,0,16ZM232,56V208a8,8,0,0,1-11.58,7.15L192,200.94l-28.42,14.21a8,8,0,0,1-7.16,0L128,200.94,99.58,215.15a8,8,0,0,1-7.16,0L64,200.94,35.58,215.15A8,8,0,0,1,24,208V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56Zm-16,0H40V195.06l20.42-10.22a8,8,0,0,1,7.16,0L96,199.06l28.42-14.22a8,8,0,0,1,7.16,0L160,199.06l28.42-14.22a8,8,0,0,1,7.16,0L216,195.06Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,64H56a8,8,0,0,1,0-16H192a8,8,0,0,0,0-16H56A24,24,0,0,0,32,56V184a24,24,0,0,0,24,24H216a16,16,0,0,0,16-16V80A16,16,0,0,0,216,64Zm0,128H56a8,8,0,0,1-8-8V78.63A23.84,23.84,0,0,0,56,80H216Zm-48-60a12,12,0,1,1,12,12A12,12,0,0,1,168,132Z"/></svg>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The block type to draw beside a page's text, or null.
|
|
3
|
+
* @param {{ wide?: boolean, side?: boolean }} page the page's front matter
|
|
4
|
+
* @param {{ block: string | null } | null} setting the contents of src/data/side.json, or null when the file is missing
|
|
5
|
+
* @param {(type: string) => boolean} beside whether that type's load.ts has beside: true
|
|
6
|
+
* @returns {string | null}
|
|
7
|
+
*/
|
|
8
|
+
export function sideBlockOf(page: {
|
|
9
|
+
wide?: boolean;
|
|
10
|
+
side?: boolean;
|
|
11
|
+
}, setting: {
|
|
12
|
+
block: string | null;
|
|
13
|
+
} | null, beside: (type: string) => boolean): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Whether a block's load.ts source says `export const beside = true` (the editor never imports load.ts, so it reads the text).
|
|
16
|
+
* @param {string} source
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
export function besideInSource(source: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The installed block types that can be drawn beside a page's text, for the choice under "Udseende" › "Sidekolonne".
|
|
22
|
+
* @param {{ type: string, label: string, app?: string }[]} blocks every block the editor knows (siteBlocks)
|
|
23
|
+
* @param {Record<string, string>} sources the text of each block's load.ts, by type
|
|
24
|
+
* @param {string[]} installed the ids of the apps installed
|
|
25
|
+
* @returns {{ type: string, label: string }[]}
|
|
26
|
+
*/
|
|
27
|
+
export function sideChoices(blocks: {
|
|
28
|
+
type: string;
|
|
29
|
+
label: string;
|
|
30
|
+
app?: string;
|
|
31
|
+
}[], sources: Record<string, string>, installed: string[]): {
|
|
32
|
+
type: string;
|
|
33
|
+
label: string;
|
|
34
|
+
}[];
|
|
35
|
+
/**
|
|
36
|
+
* Whether the page form shows the switch "Sidekolonne": src/data/side.json chooses a block and the page is not wide.
|
|
37
|
+
* @param {{ block?: unknown } | null | undefined} setting the contents of src/data/side.json
|
|
38
|
+
* @param {{ wide?: unknown }} page the page's values
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
export function sideSwitchShown(setting: {
|
|
42
|
+
block?: unknown;
|
|
43
|
+
} | null | undefined, page: {
|
|
44
|
+
wide?: unknown;
|
|
45
|
+
}): boolean;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Determines the side block type to draw beside a page's text, and what the editor offers about it.
|
|
2
|
+
// Exposes sideBlockOf (the site), besideInSource, sideChoices and sideSwitchShown (the editor: Sidekolonne).
|
|
3
|
+
// No dependencies.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The block type to draw beside a page's text, or null.
|
|
7
|
+
* @param {{ wide?: boolean, side?: boolean }} page the page's front matter
|
|
8
|
+
* @param {{ block: string | null } | null} setting the contents of src/data/side.json, or null when the file is missing
|
|
9
|
+
* @param {(type: string) => boolean} beside whether that type's load.ts has beside: true
|
|
10
|
+
* @returns {string | null}
|
|
11
|
+
*/
|
|
12
|
+
export function sideBlockOf(page, setting, beside) {
|
|
13
|
+
// Treat null or undefined page as empty object
|
|
14
|
+
const p = page || {};
|
|
15
|
+
|
|
16
|
+
// Rule 1: When setting is null or undefined, return null
|
|
17
|
+
if (setting === null || setting === undefined) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Rule 2: When setting.block is not a non-empty string, return null
|
|
22
|
+
if (typeof setting.block !== 'string' || setting.block.length === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Rule 3: When page.wide === true, return null
|
|
27
|
+
if (p.wide === true) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Rule 4: When page.side === false, return null
|
|
32
|
+
if (p.side === false) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Rule 5: When beside(setting.block) is not true, return null
|
|
37
|
+
if (beside(setting.block) !== true) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Rule 6: Return setting.block
|
|
42
|
+
return setting.block;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether a block's load.ts source says `export const beside = true` (the editor never imports load.ts, so it reads the text).
|
|
47
|
+
* @param {string} source
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export function besideInSource(source) {
|
|
51
|
+
return /^export\s+const\s+beside\s*=\s*true\s*;?\s*$/m.test(source);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The installed block types that can be drawn beside a page's text, for the choice under "Udseende" › "Sidekolonne".
|
|
56
|
+
* @param {{ type: string, label: string, app?: string }[]} blocks every block the editor knows (siteBlocks)
|
|
57
|
+
* @param {Record<string, string>} sources the text of each block's load.ts, by type
|
|
58
|
+
* @param {string[]} installed the ids of the apps installed
|
|
59
|
+
* @returns {{ type: string, label: string }[]}
|
|
60
|
+
*/
|
|
61
|
+
export function sideChoices(blocks, sources, installed) {
|
|
62
|
+
return blocks
|
|
63
|
+
.filter((b) => (!b.app || installed.includes(b.app)) && besideInSource(sources[b.type] ?? ''))
|
|
64
|
+
.map((b) => ({ type: b.type, label: b.label }));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether the page form shows the switch "Sidekolonne": src/data/side.json chooses a block and the page is not wide.
|
|
69
|
+
* @param {{ block?: unknown } | null | undefined} setting the contents of src/data/side.json
|
|
70
|
+
* @param {{ wide?: unknown }} page the page's values
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
export function sideSwitchShown(setting, page) {
|
|
74
|
+
return typeof setting?.block === 'string' && setting.block.length > 0 && page?.wide !== true;
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Tests for sideBlockOf (the site) and besideInSource, sideChoices and sideSwitchShown (the editor's Sidekolonne).
|
|
2
|
+
// Validates side block selection logic.
|
|
3
|
+
// Depends on lib/side-block.mjs.
|
|
4
|
+
import { test } from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { besideInSource, sideBlockOf, sideChoices, sideSwitchShown } from './side-block.mjs';
|
|
7
|
+
|
|
8
|
+
const yes = () => true;
|
|
9
|
+
const no = () => false;
|
|
10
|
+
|
|
11
|
+
test('returns null when setting is null', () => {
|
|
12
|
+
assert.equal(sideBlockOf({}, null, yes), null);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('returns null when setting.block is null', () => {
|
|
16
|
+
assert.equal(sideBlockOf({}, { block: null }, yes), null);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('returns block type when valid', () => {
|
|
20
|
+
assert.equal(sideBlockOf({}, { block: 'side-card' }, yes), 'side-card');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('returns null when page.wide is true', () => {
|
|
24
|
+
assert.equal(sideBlockOf({ wide: true }, { block: 'side-card' }, yes), null);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('returns null when page.side is false', () => {
|
|
28
|
+
assert.equal(sideBlockOf({ side: false }, { block: 'side-card' }, yes), null);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('returns block type when page.side is true', () => {
|
|
32
|
+
assert.equal(sideBlockOf({ side: true }, { block: 'side-card' }, yes), 'side-card');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('returns null when beside returns false', () => {
|
|
36
|
+
assert.equal(sideBlockOf({}, { block: 'side-card' }, no), null);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('beside is called with the chosen type', () => {
|
|
40
|
+
const seen = [];
|
|
41
|
+
sideBlockOf({}, { block: 'side-card' }, (t) => {
|
|
42
|
+
seen.push(t);
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
assert.deepEqual(seen, ['side-card']);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('treats undefined page as empty object', () => {
|
|
49
|
+
assert.equal(sideBlockOf(undefined, { block: 'side-card' }, yes), 'side-card');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('besideInSource reads export const beside = true', () => {
|
|
53
|
+
assert.equal(besideInSource('import x from "y";\nexport const beside = true;\nexport const load = 1;'), true);
|
|
54
|
+
assert.equal(besideInSource('export const beside = false;'), false);
|
|
55
|
+
assert.equal(besideInSource('// export const beside = true;'), false);
|
|
56
|
+
assert.equal(besideInSource('export const load = 1;'), false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('sideChoices lists installed blocks with beside, by label', () => {
|
|
60
|
+
const blocks = [
|
|
61
|
+
{ type: 'hero', label: 'Hero' },
|
|
62
|
+
{ type: 'toc', label: 'Contents' },
|
|
63
|
+
{ type: 'chat', label: 'Chat', app: 'chat' },
|
|
64
|
+
{ type: 'gone', label: 'Gone', app: 'gone' },
|
|
65
|
+
];
|
|
66
|
+
const beside = 'export const beside = true;';
|
|
67
|
+
const sources = { toc: beside, chat: beside, gone: beside, hero: 'export const load = 1;' };
|
|
68
|
+
assert.deepEqual(sideChoices(blocks, sources, ['chat']), [
|
|
69
|
+
{ type: 'toc', label: 'Contents' },
|
|
70
|
+
{ type: 'chat', label: 'Chat' },
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('sideSwitchShown needs a chosen block and a page that is not wide', () => {
|
|
75
|
+
assert.equal(sideSwitchShown({ block: 'toc' }, {}), true);
|
|
76
|
+
assert.equal(sideSwitchShown({ block: 'toc' }, { wide: true }), false);
|
|
77
|
+
assert.equal(sideSwitchShown({ block: null }, {}), false);
|
|
78
|
+
assert.equal(sideSwitchShown(null, {}), false);
|
|
79
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morphemecms/cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Morpheme CMS: the editor for a site built with Astro, its screens, its blocks and the rules a site gives it. See README.md.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Lars Adolfsen",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Checks the column beside a page's text (src/data/side.json, `beside` in a block's load.ts, PageLayout.astro) with the
|
|
2
|
+
// theme now in src/data/theme.json: puts a TEST-ONLY block and three pages in the site (scripts/fixtures/side/), builds it
|
|
3
|
+
// in a folder of its own and looks in the HTML. Exposes checkSide(root, theme); used by check-themes.mjs. Removes what it
|
|
4
|
+
// put in the site, and the side.json it wrote, however it ends.
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { binOf } from './bin.mjs';
|
|
11
|
+
|
|
12
|
+
const fixtures = fileURLToPath(new URL('fixtures/side/', import.meta.url));
|
|
13
|
+
const BLOCK = 'src/apps/side-fixture/blocks/side-test';
|
|
14
|
+
const PAGES = ['side-plain', 'side-wide', 'side-off'];
|
|
15
|
+
|
|
16
|
+
/** What the fixture's pages must show: [page, whether the column is on it]. */
|
|
17
|
+
export const expected = [
|
|
18
|
+
['side-plain', true],
|
|
19
|
+
['side-wide', false],
|
|
20
|
+
['side-off', false],
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/** Whether the built page has the block beside its text, and its text's length (data-side-length). */
|
|
24
|
+
export const columnOf = (html) => ({
|
|
25
|
+
column: /<aside class="page-side">[\s\S]*?data-side-test="\/side-plain\/"[\s\S]*?<\/aside>/.test(html),
|
|
26
|
+
grid: html.includes('class="page-with-side"'),
|
|
27
|
+
anywhere: html.includes('data-side-test'),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/** Builds the site with the fixture and checks the three cases for the theme in use; throws with what is wrong. */
|
|
31
|
+
export function checkSide(root) {
|
|
32
|
+
const put = [BLOCK, 'src/data/side.json', ...PAGES.map((p) => `src/content/pages/${p}.md`)].map((f) => join(root, f));
|
|
33
|
+
if (put.some(existsSync)) throw new Error('Remove the side-fixture files from src/ first (scripts/check-side.mjs puts them there).');
|
|
34
|
+
const out = mkdtempSync(join(tmpdir(), 'side-'));
|
|
35
|
+
try {
|
|
36
|
+
mkdirSync(join(root, BLOCK), { recursive: true });
|
|
37
|
+
cpSync(join(fixtures, 'block'), join(root, BLOCK), { recursive: true });
|
|
38
|
+
cpSync(join(fixtures, 'side.json'), join(root, 'src/data/side.json'));
|
|
39
|
+
for (const p of PAGES) cpSync(join(fixtures, `${p}.md`), join(root, `src/content/pages/${p}.md`));
|
|
40
|
+
execFileSync(process.execPath, [binOf('astro'), 'build', '--outDir', out], { cwd: root, stdio: 'inherit' });
|
|
41
|
+
const problems = [];
|
|
42
|
+
for (const [page, on] of expected) {
|
|
43
|
+
const html = readFileSync(join(out, page, 'index.html'), 'utf8');
|
|
44
|
+
const found = columnOf(html);
|
|
45
|
+
if (on && !(found.column && found.grid)) problems.push(`/${page}/ has no column beside its text`);
|
|
46
|
+
if (!on && (found.anywhere || found.grid)) problems.push(`/${page}/ has the column, and should not`);
|
|
47
|
+
}
|
|
48
|
+
const plain = columnOf(readFileSync(join(out, 'side-plain/index.html'), 'utf8'));
|
|
49
|
+
if (!plain.anywhere) problems.push('the block was not drawn at all');
|
|
50
|
+
if (problems.length) throw new Error(problems.join('\n'));
|
|
51
|
+
} finally {
|
|
52
|
+
rmSync(out, { recursive: true, force: true });
|
|
53
|
+
rmSync(join(root, 'src/apps/side-fixture'), { recursive: true, force: true });
|
|
54
|
+
for (const f of put.slice(1)) rmSync(f, { force: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
package/scripts/check-themes.mjs
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// others than the one in use (src/data/theme.json), the editor's check of the content (check-content.mjs), the site
|
|
3
3
|
// built (in a folder of its own, so dist/ stays the theme in use's) and every screen of the editor opened
|
|
4
4
|
// (editor-screens.mjs). The theme in use is checked by the steps before (npm run build, npm run check:screens).
|
|
5
|
-
// src/data/theme.json is the theme being checked while it runs, and as it was after.
|
|
5
|
+
// src/data/theme.json is the theme being checked while it runs, and as it was after. The theme in use too builds once with
|
|
6
|
+
// a test block beside the text of the pages (check-side.mjs), as each of the others does after its screens.
|
|
6
7
|
import { execFileSync } from 'node:child_process';
|
|
7
8
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
9
|
import { tmpdir } from 'node:os';
|
|
@@ -10,6 +11,7 @@ import { join } from 'node:path';
|
|
|
10
11
|
import { fileURLToPath } from 'node:url';
|
|
11
12
|
import { activeTheme, installedThemes } from '@morphemecms/cms/site/theme.mjs';
|
|
12
13
|
import { binOf } from './bin.mjs';
|
|
14
|
+
import { checkSide } from './check-side.mjs';
|
|
13
15
|
|
|
14
16
|
const ROOT = `${process.cwd()}/`;
|
|
15
17
|
const file = join(ROOT, 'src/data/theme.json');
|
|
@@ -18,7 +20,18 @@ const others = installedThemes().filter((t) => t !== activeTheme());
|
|
|
18
20
|
const run = (cmd, args) => execFileSync(cmd, args, { cwd: ROOT, stdio: 'inherit' });
|
|
19
21
|
|
|
20
22
|
const failed = [];
|
|
23
|
+
const side = (theme) => {
|
|
24
|
+
try {
|
|
25
|
+
console.log(`
|
|
26
|
+
Kolonnen ved siden af teksten med temaet ${theme}:`);
|
|
27
|
+
checkSide(ROOT);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(error.message);
|
|
30
|
+
failed.push(`${theme} (kolonnen)`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
21
33
|
try {
|
|
34
|
+
side(activeTheme());
|
|
22
35
|
for (const theme of others) {
|
|
23
36
|
console.log(`\nTemaet ${theme}:`);
|
|
24
37
|
writeFileSync(file, `${JSON.stringify({ theme }, null, 2)}\n`);
|
|
@@ -27,6 +40,7 @@ try {
|
|
|
27
40
|
run(process.execPath, [fileURLToPath(new URL('check-content.mjs', import.meta.url))]);
|
|
28
41
|
run(process.execPath, [binOf('astro'), 'build', '--outDir', out]);
|
|
29
42
|
run(process.execPath, [fileURLToPath(new URL('editor-screens.mjs', import.meta.url)), out]);
|
|
43
|
+
checkSide(ROOT);
|
|
30
44
|
} catch {
|
|
31
45
|
failed.push(theme);
|
|
32
46
|
} finally {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// sections as blocks (packages/editor/src/sections.mjs) and read back: the sections must be the same, a section
|
|
6
6
|
// of text with the same heading and the same HTML. Every page is also read into sections as the front page (as the
|
|
7
7
|
// site reads the page that is the front page without the editor, partsOfMarkdown in sections.mjs, packages/editor/site/home.ts):
|
|
8
|
-
// they must be those the editor reads. Run: npm run check:editor [files...]
|
|
8
|
+
// they must be those the editor reads. A page with the switch "Sidekolonne" off and on again has the same front matter. Run: npm run check:editor [files...]
|
|
9
9
|
// Milkdown needs a DOM; jsdom stands in for the browser.
|
|
10
10
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
@@ -45,7 +45,7 @@ const textOf = Object.fromEntries(
|
|
|
45
45
|
.flatMap((c) => c.files)
|
|
46
46
|
.map((f) => [f.file, f.fields.find((x) => x.type === 'blocks')]),
|
|
47
47
|
);
|
|
48
|
-
const { splitFrontmatter, writeFields } = await import('@morphemecms/cms/src/frontmatter.mjs');
|
|
48
|
+
const { booleanWrite, splitFrontmatter, writeFields } = await import('@morphemecms/cms/src/frontmatter.mjs');
|
|
49
49
|
|
|
50
50
|
const walk = (dir) =>
|
|
51
51
|
(existsSync(dir) ? readdirSync(dir, { withFileTypes: true }) : []).flatMap((e) =>
|
|
@@ -103,6 +103,17 @@ for (const file of files) {
|
|
|
103
103
|
console.log(`\n✗ ${file}\n front matter skrives ikke tilbage uændret`);
|
|
104
104
|
continue;
|
|
105
105
|
}
|
|
106
|
+
// The switch "Sidekolonne" (side): off writes `side: false`; on again removes the key, and the front matter is as before.
|
|
107
|
+
if (file.includes('src/content/pages/')) {
|
|
108
|
+
const off = writeFields(yaml, { side: booleanWrite(false, true) });
|
|
109
|
+
if (!/^side: false$/m.test(off) || writeFields(off, { side: booleanWrite(true, true) }) !== writeFields(yaml, { side: '' })) {
|
|
110
|
+
failed++;
|
|
111
|
+
console.log(`
|
|
112
|
+
✗ ${file}
|
|
113
|
+
Sidekolonne slået fra og til giver ikke det samme front matter`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
106
117
|
const root = document.createElement('div');
|
|
107
118
|
// As in the editor: with the free lines between the blocks, written without them.
|
|
108
119
|
const editor = await makeEditor(root, body).use(spacing).create();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// TEST-ONLY drawing of the block in load.ts (scripts/check-side.mjs): a paragraph the check looks for in the built page.
|
|
2
|
+
// Exposes a React component given the block's view.
|
|
3
|
+
export default function SideTest({ view }: { view: { path: string; length: number } }) {
|
|
4
|
+
return (
|
|
5
|
+
<p data-side-test={view.path} data-side-length={view.length}>
|
|
6
|
+
Side test block
|
|
7
|
+
</p>
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// TEST-ONLY block for check:themes (scripts/check-side.mjs): a block that can be drawn beside a page's text (`beside`).
|
|
2
|
+
// Its view is the page's address and the length of the text it was given, so the check sees that both reach the block.
|
|
3
|
+
import type { Load } from '@morphemecms/cms/src/standard/view';
|
|
4
|
+
|
|
5
|
+
export const beside = true;
|
|
6
|
+
|
|
7
|
+
export const load: Load<{ path: string; length: number }> = async (props, at) =>
|
|
8
|
+
Object.keys(props).length === 0 && at.html ? { path: at.path ?? '', length: at.html.length } : null;
|
package/scripts/icon-packs.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
19
19
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import { ICON_PACK_FILE, ICON_PACK_LICENSE, SYSTEM_PACKS } from '@morphemecms/cms/site/iconPacks.mjs';
|
|
21
21
|
import { packFolder } from '@morphemecms/cms/site/icons.mjs';
|
|
22
|
-
import { MATERIAL_DESIGN_ICONS, PACK_MAPS, PACK_SOURCES, phosphorFile, usedIcons } from '@morphemecms/cms/site/icons.mjs';
|
|
22
|
+
import { MATERIAL_DESIGN_ICONS, PACK_MAPS, PACK_SOURCES, phosphorFile, phosphorNames, usedIcons } from '@morphemecms/cms/site/icons.mjs';
|
|
23
23
|
|
|
24
24
|
const MDI = 'material-design-icons';
|
|
25
25
|
const version = (spec) => spec.replace(/^.*@/, '');
|
|
@@ -78,7 +78,7 @@ const lines = (fill) => (inner) =>
|
|
|
78
78
|
|
|
79
79
|
// What each pack's files are, by Phosphor's name: its set's, and those from Material Design Icons.
|
|
80
80
|
function sourcesOf(id) {
|
|
81
|
-
if (id === 'phosphor') return used.map((name) => ({ name, text: () => phosphorFile(name) }));
|
|
81
|
+
if (id === 'phosphor') return phosphorNames(used).map((name) => ({ name, text: () => phosphorFile(name) }));
|
|
82
82
|
const map = PACK_MAPS[id];
|
|
83
83
|
const mdi = MATERIAL_DESIGN_ICONS[id] ?? {};
|
|
84
84
|
return [
|
|
@@ -104,7 +104,7 @@ for (const id of SYSTEM_PACKS) {
|
|
|
104
104
|
);
|
|
105
105
|
for (const { name, icon, file } of sources)
|
|
106
106
|
if (file && !existsSync(file)) problems.push(`${PACK_SOURCES[id].name} har intet "${icon}" (for ${name}).`);
|
|
107
|
-
if (id === 'phosphor') for (const name of used) if (!phosphorFile(name)) problems.push(`Phosphor har intet "${name}".`);
|
|
107
|
+
if (id === 'phosphor') for (const name of phosphorNames(used)) if (!phosphorFile(name)) problems.push(`Phosphor har intet "${name}".`);
|
|
108
108
|
}
|
|
109
109
|
if (problems.length) {
|
|
110
110
|
for (const p of problems) console.error(p);
|
package/site/content.config.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// The site's content collections (blog posts, pages) and their front matter. Exposes `collections` for Astro.
|
|
2
|
+
// Depends on astro:content.
|
|
1
3
|
import { defineCollection, z } from 'astro:content';
|
|
2
4
|
import { glob } from 'astro/loaders';
|
|
3
5
|
|
|
@@ -74,6 +76,8 @@ const pages = defineCollection({
|
|
|
74
76
|
noindex: z.boolean().optional().default(false),
|
|
75
77
|
// Across the whole page, without the owner's card beside the text (PageLayout.astro).
|
|
76
78
|
wide: z.boolean().optional().default(false),
|
|
79
|
+
// `false`: without the block beside the text, which src/data/side.json chooses (PageLayout.astro).
|
|
80
|
+
side: z.boolean().optional(),
|
|
77
81
|
// A new version of a page (/ny/…): what the owner should look at or answer before it replaces the
|
|
78
82
|
// current page. Shown at the top of the new version and on /ny/.
|
|
79
83
|
review: z.string().optional(),
|
package/site/defineSite.d.mts
CHANGED
|
@@ -45,7 +45,7 @@ export type SiteOwn = {
|
|
|
45
45
|
* What a site can say of itself, else the standard: the owner's picture (beside a blog post, and shown when a page with
|
|
46
46
|
* none of its own is shared), the booking page and the pages a booking ends on, the page about the owner, the block
|
|
47
47
|
* drawn after every blog post, the word a page's title can end in, pictures that are the same though their names say
|
|
48
|
-
* otherwise,
|
|
48
|
+
* otherwise, the file of its redirects from old addresses, and the logo in the structured data.
|
|
49
49
|
*/
|
|
50
50
|
export type SiteChoices = {
|
|
51
51
|
/**
|
|
@@ -91,6 +91,11 @@ export type SiteChoices = {
|
|
|
91
91
|
rules: string;
|
|
92
92
|
map: string;
|
|
93
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* The address of the site's logo in the structured data (the business's `logo`,
|
|
96
|
+
* site/schema.ts), a path on the site ("/logo.svg").
|
|
97
|
+
*/
|
|
98
|
+
logo: string;
|
|
94
99
|
};
|
|
95
100
|
export type SiteConfig = SiteOwn & SiteChoices & typeof STANDARD;
|
|
96
101
|
declare namespace STANDARD {
|
|
@@ -113,6 +118,7 @@ declare namespace STANDARD {
|
|
|
113
118
|
let theme: string;
|
|
114
119
|
let icons: string;
|
|
115
120
|
let motion: string;
|
|
121
|
+
let side: string;
|
|
116
122
|
let themes: string;
|
|
117
123
|
let csp: string;
|
|
118
124
|
let colors: string;
|