@morphemecms/cms 0.7.0 → 0.8.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 +18 -0
- package/defaults/collections.ts +56 -0
- package/defaults/company.ts +13 -4
- package/defaults/texts.ts +17 -0
- package/icons/lucide/LICENSE-phosphor +21 -0
- package/icons/lucide/facebook-logo.svg +1 -0
- package/icons/lucide/linkedin-logo.svg +1 -0
- package/icons/lucide/pack.json +16 -1
- package/icons/lucide/pinterest-logo.svg +1 -0
- package/icons/lucide/x-logo.svg +1 -0
- package/icons/material-symbols/LICENSE-phosphor +21 -0
- package/icons/material-symbols/facebook-logo.svg +1 -0
- package/icons/material-symbols/linkedin-logo.svg +1 -0
- package/icons/material-symbols/pack.json +15 -0
- package/icons/material-symbols/pinterest-logo.svg +1 -0
- package/icons/material-symbols/x-logo.svg +1 -0
- package/icons/phosphor/facebook-logo.svg +1 -0
- package/icons/phosphor/linkedin-logo.svg +1 -0
- package/icons/phosphor/pinterest-logo.svg +1 -0
- package/icons/phosphor/x-logo.svg +1 -0
- package/lib/lead.d.mts +5 -0
- package/lib/lead.mjs +10 -0
- package/lib/lead.test.mjs +65 -0
- package/lib/licenses.mjs +1 -1
- package/package.json +2 -1
- package/scripts/icon-packs.mjs +51 -13
- package/scripts/material-fallback.test.mjs +49 -0
- package/site/icons.d.mts +13 -0
- package/site/icons.mjs +25 -3
- package/site/materialSymbols.d.mts +8 -2
- package/site/materialSymbols.mjs +27 -11
- package/site/materialSymbols.test.mjs +44 -0
- package/site/schema.ts +2 -0
- package/site/site.ts +9 -2
- package/site/social.d.mts +37 -0
- package/site/social.mjs +92 -0
- package/site/social.test.mjs +80 -0
- package/site/theme.d.mts +2 -0
- package/site/theme.mjs +28 -11
- package/site/themeImport.test.mjs +40 -0
- package/src/EntryForm.tsx +21 -58
- package/src/FormFrame.tsx +5 -5
- package/src/Parts.tsx +8 -30
- package/src/Screen.tsx +12 -5
- package/src/editor.css +70 -5
- package/src/i18n/da-DK/parts.ts +1 -1
- package/src/i18n/da-DK/theme.ts +4 -0
- package/src/i18n/en-US/parts.ts +1 -1
- package/src/i18n/en-US/theme.ts +4 -0
- package/src/icon.tsx +1 -1
- package/src/materialIcons.ts +5 -7
- package/src/pagePreview.tsx +21 -1
- package/src/postLead.d.mts +7 -0
- package/src/postLead.mjs +9 -0
- package/src/postLead.test.mjs +28 -0
- package/src/ui.tsx +41 -96
- package/src/virtual.d.ts +5 -0
- package/starter/package.json +1 -1
- package/starter/src/data/social.json +14 -0
- package/theme/components/PostAuthor.astro +20 -0
- package/theme/components/PostCard.tsx +26 -0
- package/theme/components/PostHeader.astro +36 -0
- package/theme/components/PostMeta.tsx +27 -2
- package/theme/components/RelatedPosts.astro +29 -0
- package/theme/components/ShareRow.astro +48 -0
- package/theme/layouts/PostLayout.astro +28 -54
- package/theme/styles/base.css +164 -127
- package/theme/styles/blocks.css +160 -155
- package/theme/styles/components.css +43 -34
- package/theme/styles/global.css +316 -303
- package/theme/styles/ui.css +43 -34
- package/theme/ui/copyLink.script.ts +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @morphemecms/cms
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b201ffd: Material is now every theme's fallback: a theme imports `@morphemecms/cms/theme/styles/global.css`, anything it does not style is Material's, and its own rules always win.
|
|
8
|
+
- 3c68cc9: Richer blog post page: author role, tags and share buttons in the header, a lead paragraph, and related-post cards; new Company › Social media settings.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 0358c3e: Show a page or post form's sections as an "on this page" list on a card at the end of the form, on large screens, and keep the editor's content column centred on the window.
|
|
13
|
+
- 1323fa8: Wide buttons fill the width only on phones; the save bar's buttons sit at the end on larger screens.
|
|
14
|
+
|
|
15
|
+
## 0.7.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 36e2e3b: Fix the Cloudflare build stopping with "Too many open files" by serving all Material Symbols icons as one module.
|
|
20
|
+
|
|
3
21
|
## 0.7.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/defaults/collections.ts
CHANGED
|
@@ -174,6 +174,62 @@ export const contactFields: Field[] = [
|
|
|
174
174
|
},
|
|
175
175
|
];
|
|
176
176
|
|
|
177
|
+
// The social media (src/data/social.json, a part of its own under "Virksomheden", company.ts): the site's profiles, and
|
|
178
|
+
// which share buttons a post shows. A switch that is not in the file is on (company.ts gives the form that default).
|
|
179
|
+
// A profile address: empty, or one that begins with https://.
|
|
180
|
+
const httpsAddress: [string, string] = ['^$|^https://\\S+$', 'Skriv en adresse, der begynder med https://'];
|
|
181
|
+
|
|
182
|
+
export const socialFields: Field[] = [
|
|
183
|
+
{
|
|
184
|
+
name: 'facebook',
|
|
185
|
+
label: 'Facebook',
|
|
186
|
+
type: 'string',
|
|
187
|
+
hint: 'Adressen på sitets profil. Vises i Googles data om virksomheden.',
|
|
188
|
+
pattern: httpsAddress,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'instagram',
|
|
192
|
+
label: 'Instagram',
|
|
193
|
+
type: 'string',
|
|
194
|
+
pattern: httpsAddress,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'linkedin',
|
|
198
|
+
label: 'LinkedIn',
|
|
199
|
+
type: 'string',
|
|
200
|
+
pattern: httpsAddress,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'x',
|
|
204
|
+
label: 'X',
|
|
205
|
+
type: 'string',
|
|
206
|
+
pattern: httpsAddress,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'youtube',
|
|
210
|
+
label: 'YouTube',
|
|
211
|
+
type: 'string',
|
|
212
|
+
pattern: httpsAddress,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'tiktok',
|
|
216
|
+
label: 'TikTok',
|
|
217
|
+
type: 'string',
|
|
218
|
+
pattern: httpsAddress,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'pinterest',
|
|
222
|
+
label: 'Pinterest',
|
|
223
|
+
type: 'string',
|
|
224
|
+
pattern: httpsAddress,
|
|
225
|
+
},
|
|
226
|
+
{ name: 'shareFacebook', label: 'Del på Facebook', type: 'boolean' },
|
|
227
|
+
{ name: 'shareX', label: 'Del på X', type: 'boolean' },
|
|
228
|
+
{ name: 'shareLinkedin', label: 'Del på LinkedIn', type: 'boolean' },
|
|
229
|
+
{ name: 'sharePinterest', label: 'Del på Pinterest', type: 'boolean', hint: 'Vises kun, når indlægget har et billede.' },
|
|
230
|
+
{ name: 'shareCopyLink', label: 'Kopiér link', type: 'boolean' },
|
|
231
|
+
];
|
|
232
|
+
|
|
177
233
|
// The bank details, a part of its own under "Betaling" (company.ts, src/data/payment.json).
|
|
178
234
|
export const bankFields: Field[] = [
|
|
179
235
|
{ name: 'bank', label: 'Bank', type: 'string', required: true },
|
package/defaults/company.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
// "Virksomheden": the business, in parts, each a screen of its own (CompanyForm.tsx): its phone, e-mail and address
|
|
2
2
|
// (src/data/contact.json) and its bank details (src/data/payment.json, under "Betaling", where a site may add its
|
|
3
3
|
// other ways to pay, and where the settings of the apps installed that are a way to pay come after, as MobilePay's),
|
|
4
|
-
// used all over the site
|
|
5
|
-
// the calendar in Calendly is the app Calendly's (src/apps/calendly/).
|
|
6
|
-
import { AddressBook, Bank, CreditCard } from '@phosphor-icons/react';
|
|
4
|
+
// used all over the site; and its social media (src/data/social.json): the profiles and the post's share buttons.
|
|
5
|
+
// The logo and the icon are under "Udseende" (BrandForm.tsx); the link to the calendar in Calendly is the app Calendly's (src/apps/calendly/).
|
|
6
|
+
import { AddressBook, Bank, CreditCard, ShareNetwork } from '@phosphor-icons/react';
|
|
7
7
|
import type { Part } from '../src/schema';
|
|
8
|
-
import { bankFields, contactFields } from '@site/collections';
|
|
8
|
+
import { bankFields, contactFields, socialFields } from '@site/collections';
|
|
9
9
|
|
|
10
10
|
/** The parts of "Virksomheden", in their order, at #virksomheden/<id> ("Betaling"'s at #virksomheden/betaling/<id>). */
|
|
11
11
|
export const companyParts: Part[] = [
|
|
12
12
|
{ id: 'kontakt', label: 'Kontakt', icon: AddressBook, file: 'src/data/contact.json', fields: contactFields },
|
|
13
|
+
{
|
|
14
|
+
id: 'sociale-medier',
|
|
15
|
+
label: 'Sociale medier',
|
|
16
|
+
icon: ShareNetwork,
|
|
17
|
+
file: 'src/data/social.json',
|
|
18
|
+
fields: socialFields,
|
|
19
|
+
// A switch missing from the file (or a file not there yet) is on; the form would show it off without this.
|
|
20
|
+
defaults: { shareFacebook: true, shareX: true, shareLinkedin: true, sharePinterest: true, shareCopyLink: true },
|
|
21
|
+
},
|
|
13
22
|
{
|
|
14
23
|
id: 'betaling',
|
|
15
24
|
label: 'Betaling',
|
package/defaults/texts.ts
CHANGED
|
@@ -290,6 +290,23 @@ export const texts: Record<string, Record<string, string>> = {
|
|
|
290
290
|
'Knaptekst (book)': 'Button text (book)',
|
|
291
291
|
'Knaptekst (ring)': 'Button text (call)',
|
|
292
292
|
'Telefonnummeret vises efter teksten.': 'The phone number is shown after the text.',
|
|
293
|
+
'Sociale medier': 'Social media',
|
|
294
|
+
'Adressen på sitets profil. Vises i Googles data om virksomheden.':
|
|
295
|
+
"The address of the site's profile. Shown in Google's data about the business.",
|
|
296
|
+
'Skriv en adresse, der begynder med https://': 'Write an address that starts with https://',
|
|
297
|
+
Facebook: 'Facebook',
|
|
298
|
+
Instagram: 'Instagram',
|
|
299
|
+
LinkedIn: 'LinkedIn',
|
|
300
|
+
X: 'X',
|
|
301
|
+
YouTube: 'YouTube',
|
|
302
|
+
TikTok: 'TikTok',
|
|
303
|
+
Pinterest: 'Pinterest',
|
|
304
|
+
'Del på Facebook': 'Share on Facebook',
|
|
305
|
+
'Del på X': 'Share on X',
|
|
306
|
+
'Del på LinkedIn': 'Share on LinkedIn',
|
|
307
|
+
'Del på Pinterest': 'Share on Pinterest',
|
|
308
|
+
'Vises kun, når indlægget har et billede.': 'Shown only when the post has a picture.',
|
|
309
|
+
'Kopiér link': 'Copy link',
|
|
293
310
|
Blokke: 'Blocks',
|
|
294
311
|
Blokskabeloner: 'Block templates',
|
|
295
312
|
Knapper: 'Buttons',
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Phosphor Icons
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2.04C6.5 2.04 2 6.53 2 12.06C2 17.06 5.66 21.21 10.44 21.96V14.96H7.9V12.06H10.44V9.85C10.44 7.34 11.93 5.96 14.22 5.96C15.31 5.96 16.45 6.15 16.45 6.15V8.62H15.19C13.95 8.62 13.56 9.39 13.56 10.18V12.06H16.34L15.89 14.96H13.56V21.96A10 10 0 0 0 22 12.06C22 6.53 17.5 2.04 12 2.04Z" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 3A2 2 0 0 1 21 5V19A2 2 0 0 1 19 21H5A2 2 0 0 1 3 19V5A2 2 0 0 1 5 3H19M18.5 18.5V13.2A3.26 3.26 0 0 0 15.24 9.94C14.39 9.94 13.4 10.46 12.92 11.24V10.13H10.13V18.5H12.92V13.57C12.92 12.8 13.54 12.17 14.31 12.17A1.4 1.4 0 0 1 15.71 13.57V18.5H18.5M6.88 8.56A1.68 1.68 0 0 0 8.56 6.88C8.56 5.95 7.81 5.19 6.88 5.19A1.69 1.69 0 0 0 5.19 6.88C5.19 7.81 5.95 8.56 6.88 8.56M8.27 18.5V10.13H5.5V18.5H8.27Z" /></svg>
|
package/icons/lucide/pack.json
CHANGED
|
@@ -15,8 +15,23 @@
|
|
|
15
15
|
"source": "@mdi/svg@7.4.47",
|
|
16
16
|
"licenseFile": "LICENSE-material-design-icons",
|
|
17
17
|
"icons": [
|
|
18
|
+
"facebook-logo",
|
|
18
19
|
"github-logo",
|
|
19
|
-
"google-logo"
|
|
20
|
+
"google-logo",
|
|
21
|
+
"linkedin-logo",
|
|
22
|
+
"pinterest-logo"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "Phosphor",
|
|
27
|
+
"author": "Phosphor Icons",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"url": "https://phosphoricons.com",
|
|
30
|
+
"version": "2.1.1",
|
|
31
|
+
"source": "@phosphor-icons/core@2.1.1",
|
|
32
|
+
"licenseFile": "LICENSE-phosphor",
|
|
33
|
+
"icons": [
|
|
34
|
+
"x-logo"
|
|
20
35
|
]
|
|
21
36
|
}
|
|
22
37
|
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.04,21.54C10,21.83 10.97,22 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2A10,10 0 0,0 2,12C2,16.25 4.67,19.9 8.44,21.34C8.35,20.56 8.26,19.27 8.44,18.38L9.59,13.44C9.59,13.44 9.3,12.86 9.3,11.94C9.3,10.56 10.16,9.53 11.14,9.53C12,9.53 12.4,10.16 12.4,10.97C12.4,11.83 11.83,13.06 11.54,14.24C11.37,15.22 12.06,16.08 13.06,16.08C14.84,16.08 16.22,14.18 16.22,11.5C16.22,9.1 14.5,7.46 12.03,7.46C9.21,7.46 7.55,9.56 7.55,11.77C7.55,12.63 7.83,13.5 8.29,14.07C8.38,14.13 8.38,14.21 8.35,14.36L8.06,15.45C8.06,15.62 7.95,15.68 7.78,15.56C6.5,15 5.76,13.18 5.76,11.71C5.76,8.55 8,5.68 12.32,5.68C15.76,5.68 18.44,8.15 18.44,11.43C18.44,14.87 16.31,17.63 13.26,17.63C12.29,17.63 11.34,17.11 11,16.5L10.33,18.87C10.1,19.73 9.47,20.88 9.04,21.57V21.54Z" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M215,219.85a8,8,0,0,1-7,4.15H160a8,8,0,0,1-6.75-3.71l-40.49-63.63L53.92,221.38a8,8,0,0,1-11.84-10.76l61.77-68L41.25,44.3A8,8,0,0,1,48,32H96a8,8,0,0,1,6.75,3.71l40.49,63.63,58.84-64.72a8,8,0,0,1,11.84,10.76l-61.77,67.95,62.6,98.38A8,8,0,0,1,215,219.85Z"/></svg>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Phosphor Icons
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2.04C6.5 2.04 2 6.53 2 12.06C2 17.06 5.66 21.21 10.44 21.96V14.96H7.9V12.06H10.44V9.85C10.44 7.34 11.93 5.96 14.22 5.96C15.31 5.96 16.45 6.15 16.45 6.15V8.62H15.19C13.95 8.62 13.56 9.39 13.56 10.18V12.06H16.34L15.89 14.96H13.56V21.96A10 10 0 0 0 22 12.06C22 6.53 17.5 2.04 12 2.04Z" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 3A2 2 0 0 1 21 5V19A2 2 0 0 1 19 21H5A2 2 0 0 1 3 19V5A2 2 0 0 1 5 3H19M18.5 18.5V13.2A3.26 3.26 0 0 0 15.24 9.94C14.39 9.94 13.4 10.46 12.92 11.24V10.13H10.13V18.5H12.92V13.57C12.92 12.8 13.54 12.17 14.31 12.17A1.4 1.4 0 0 1 15.71 13.57V18.5H18.5M6.88 8.56A1.68 1.68 0 0 0 8.56 6.88C8.56 5.95 7.81 5.19 6.88 5.19A1.69 1.69 0 0 0 5.19 6.88C5.19 7.81 5.95 8.56 6.88 8.56M8.27 18.5V10.13H5.5V18.5H8.27Z" /></svg>
|
|
@@ -18,12 +18,27 @@
|
|
|
18
18
|
"arrows-in-line-horizontal",
|
|
19
19
|
"arrows-out-line-horizontal",
|
|
20
20
|
"binoculars",
|
|
21
|
+
"facebook-logo",
|
|
21
22
|
"github-logo",
|
|
22
23
|
"google-logo",
|
|
23
24
|
"hard-drives",
|
|
25
|
+
"linkedin-logo",
|
|
26
|
+
"pinterest-logo",
|
|
24
27
|
"scan",
|
|
25
28
|
"swatches"
|
|
26
29
|
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "Phosphor",
|
|
33
|
+
"author": "Phosphor Icons",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"url": "https://phosphoricons.com",
|
|
36
|
+
"version": "2.1.1",
|
|
37
|
+
"source": "@phosphor-icons/core@2.1.1",
|
|
38
|
+
"licenseFile": "LICENSE-phosphor",
|
|
39
|
+
"icons": [
|
|
40
|
+
"x-logo"
|
|
41
|
+
]
|
|
27
42
|
}
|
|
28
43
|
]
|
|
29
44
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.04,21.54C10,21.83 10.97,22 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2A10,10 0 0,0 2,12C2,16.25 4.67,19.9 8.44,21.34C8.35,20.56 8.26,19.27 8.44,18.38L9.59,13.44C9.59,13.44 9.3,12.86 9.3,11.94C9.3,10.56 10.16,9.53 11.14,9.53C12,9.53 12.4,10.16 12.4,10.97C12.4,11.83 11.83,13.06 11.54,14.24C11.37,15.22 12.06,16.08 13.06,16.08C14.84,16.08 16.22,14.18 16.22,11.5C16.22,9.1 14.5,7.46 12.03,7.46C9.21,7.46 7.55,9.56 7.55,11.77C7.55,12.63 7.83,13.5 8.29,14.07C8.38,14.13 8.38,14.21 8.35,14.36L8.06,15.45C8.06,15.62 7.95,15.68 7.78,15.56C6.5,15 5.76,13.18 5.76,11.71C5.76,8.55 8,5.68 12.32,5.68C15.76,5.68 18.44,8.15 18.44,11.43C18.44,14.87 16.31,17.63 13.26,17.63C12.29,17.63 11.34,17.11 11,16.5L10.33,18.87C10.1,19.73 9.47,20.88 9.04,21.57V21.54Z" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M215,219.85a8,8,0,0,1-7,4.15H160a8,8,0,0,1-6.75-3.71l-40.49-63.63L53.92,221.38a8,8,0,0,1-11.84-10.76l61.77-68L41.25,44.3A8,8,0,0,1,48,32H96a8,8,0,0,1,6.75,3.71l40.49,63.63,58.84-64.72a8,8,0,0,1,11.84,10.76l-61.77,67.95,62.6,98.38A8,8,0,0,1,215,219.85Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm8,191.63V152h24a8,8,0,0,0,0-16H136V112a16,16,0,0,1,16-16h16a8,8,0,0,0,0-16H152a32,32,0,0,0-32,32v24H96a8,8,0,0,0,0,16h24v63.63a88,88,0,1,1,16,0Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,24H40A16,16,0,0,0,24,40V216a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V40A16,16,0,0,0,216,24Zm0,192H40V40H216V216ZM96,112v64a8,8,0,0,1-16,0V112a8,8,0,0,1,16,0Zm88,28v36a8,8,0,0,1-16,0V140a20,20,0,0,0-40,0v36a8,8,0,0,1-16,0V112a8,8,0,0,1,15.79-1.78A36,36,0,0,1,184,140ZM100,84A12,12,0,1,1,88,72,12,12,0,0,1,100,84Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,112c0,22.57-7.9,43.2-22.23,58.11C188.39,184,170.25,192,152,192c-17.88,0-29.82-5.86-37.43-12l-10.78,45.82A8,8,0,0,1,96,232a8.24,8.24,0,0,1-1.84-.21,8,8,0,0,1-6-9.62l32-136a8,8,0,0,1,15.58,3.66l-16.9,71.8C122,166,131.3,176,152,176c27.53,0,56-23.94,56-64A72,72,0,1,0,73.63,148a8,8,0,0,1-13.85,8A88,88,0,1,1,224,112Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M214.75,211.71l-62.6-98.38,61.77-67.95a8,8,0,0,0-11.84-10.76L143.24,99.34,102.75,35.71A8,8,0,0,0,96,32H48a8,8,0,0,0-6.75,12.3l62.6,98.37-61.77,68a8,8,0,1,0,11.84,10.76l58.84-64.72,40.49,63.63A8,8,0,0,0,160,224h48a8,8,0,0,0,6.75-12.29ZM164.39,208,62.57,48h29L193.43,208Z"/></svg>
|
package/lib/lead.d.mts
ADDED
package/lib/lead.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// A blog post's lead: its first paragraph, when its text starts with one (theme/layouts/PostLayout.astro draws it larger,
|
|
2
|
+
// with the featured picture after it; src/pagePreview.tsx does the same in the editor). Exposes splitLead.
|
|
3
|
+
/** @param {string} html @returns {{ lead: string, rest: string }} */
|
|
4
|
+
export function splitLead(html) {
|
|
5
|
+
const m = /^(?:\s|<!--[\s\S]*?-->)*(<p(?:\s[^>]*)?>([\s\S]*?)<\/p>)/i.exec(html);
|
|
6
|
+
if (m && m[2].replace(/<[^>]*>/g, '').trim() !== '') {
|
|
7
|
+
return { lead: m[1], rest: html.slice(m[0].length).trimStart() };
|
|
8
|
+
}
|
|
9
|
+
return { lead: '', rest: html };
|
|
10
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Tests for splitLead: the first text-bearing <p> is the lead; everything else stays in rest.
|
|
2
|
+
// Run with: node --test lib/lead.test.mjs
|
|
3
|
+
import { strict as assert } from 'node:assert';
|
|
4
|
+
import { test } from 'node:test';
|
|
5
|
+
import { splitLead } from './lead.mjs';
|
|
6
|
+
|
|
7
|
+
test('a text paragraph first is the lead', () => {
|
|
8
|
+
assert.deepEqual(splitLead('<p>Hello <a href="/x">you</a>.</p>\n<h2>Next</h2>'), {
|
|
9
|
+
lead: '<p>Hello <a href="/x">you</a>.</p>',
|
|
10
|
+
rest: '<h2>Next</h2>',
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('a heading first gives no lead', () => {
|
|
15
|
+
assert.deepEqual(splitLead('<h2>Title</h2><p>Text</p>'), { lead: '', rest: '<h2>Title</h2><p>Text</p>' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('a picture-only paragraph is no lead', () => {
|
|
19
|
+
assert.deepEqual(splitLead('<p><img src="/a.webp" alt=""></p><p>Text</p>'), {
|
|
20
|
+
lead: '',
|
|
21
|
+
rest: '<p><img src="/a.webp" alt=""></p><p>Text</p>',
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('a picture element alone is no lead', () => {
|
|
26
|
+
assert.deepEqual(splitLead('<p> <picture><img src="/a.webp" alt=""></picture> </p><p>T</p>'), {
|
|
27
|
+
lead: '',
|
|
28
|
+
rest: '<p> <picture><img src="/a.webp" alt=""></picture> </p><p>T</p>',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('a block first gives no lead', () => {
|
|
33
|
+
assert.deepEqual(splitLead('<div data-block="points" data-props="{}"></div><p>Text</p>'), {
|
|
34
|
+
lead: '',
|
|
35
|
+
rest: '<div data-block="points" data-props="{}"></div><p>Text</p>',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('leading whitespace is ignored', () => {
|
|
40
|
+
assert.deepEqual(splitLead('\n <p>Hi</p><p>More</p>'), { lead: '<p>Hi</p>', rest: '<p>More</p>' });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('a comment first is ignored', () => {
|
|
44
|
+
assert.deepEqual(splitLead('<!-- x --><p>Hi</p>'), { lead: '<p>Hi</p>', rest: '' });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('empty text', () => {
|
|
48
|
+
assert.deepEqual(splitLead(''), { lead: '', rest: '' });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('one paragraph only', () => {
|
|
52
|
+
assert.deepEqual(splitLead('<p>Only</p>'), { lead: '<p>Only</p>', rest: '' });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('a paragraph with attributes', () => {
|
|
56
|
+
assert.deepEqual(splitLead('<p class="x">Hi</p><p>B</p>'), { lead: '<p class="x">Hi</p>', rest: '<p>B</p>' });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('a list first gives no lead', () => {
|
|
60
|
+
assert.deepEqual(splitLead('<ul><li>A</li></ul>'), { lead: '', rest: '<ul><li>A</li></ul>' });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('a pre first gives no lead', () => {
|
|
64
|
+
assert.deepEqual(splitLead('<pre>x</pre><p>A</p>'), { lead: '', rest: '<pre>x</pre><p>A</p>' });
|
|
65
|
+
});
|
package/lib/licenses.mjs
CHANGED
|
@@ -428,7 +428,7 @@ const HELPERS = [
|
|
|
428
428
|
[/^\0vite\//, 'vite'],
|
|
429
429
|
[/^\0rolldown\//, 'rolldown'],
|
|
430
430
|
];
|
|
431
|
-
const OWN_VIRTUAL = [/^\0virtual:(app-folders|all-icons)$/, /^\0site-theme-icons?(:|$)/];
|
|
431
|
+
const OWN_VIRTUAL = [/^\0virtual:(app-folders|all-icons|material-symbols)$/, /^\0site-theme-icons?(:|$)/];
|
|
432
432
|
|
|
433
433
|
/** An app or a theme as licensesIntegration takes it: its kind and id, its name, version and who made it, its manifest
|
|
434
434
|
* (its defineApp or its theme.json, with `license` and `thirdParty`), its folder, and whether the site is built with it
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morphemecms/cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
"jsdom": "^29.1.1",
|
|
123
123
|
"motion": "^14.0.0",
|
|
124
124
|
"playwright": "^1.64.1",
|
|
125
|
+
"postcss": "^8.5.32",
|
|
125
126
|
"prettier": "^3.9.10",
|
|
126
127
|
"prettier-plugin-astro": "^1.1.2",
|
|
127
128
|
"prettier-plugin-tailwindcss": "^0.8.1",
|
package/scripts/icon-packs.mjs
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// site and the editor draw, by its Phosphor name (usedIcons: the site's in packages/editor/src/icon.tsx, which the
|
|
4
4
|
// content stores; the editor's as it imports them from @phosphor-icons/react and @phosphor-icons/core, found in the
|
|
5
5
|
// code; the app store's), the set's icon its map names (MATERIAL_SYMBOLS, LUCIDE; one the set has none of from
|
|
6
|
-
// Pictogrammers' Material Design Icons, MATERIAL_DESIGN_ICONS
|
|
6
|
+
// Pictogrammers' Material Design Icons, MATERIAL_DESIGN_ICONS; one neither has, X's logo, from Phosphor's filled icon,
|
|
7
|
+
// PHOSPHOR_FILL_ICONS), as one <svg> with its viewBox, without a size or a colour
|
|
7
8
|
// of its own: Svg and the theme's Vite plugin (themePlugin in packages/editor/site/theme.mjs) give it those. Phosphor's are its files
|
|
8
9
|
// as they are (so a site with Phosphor's pack draws Phosphor's own icons, unchanged); Lucide's lines in a <g> with
|
|
9
10
|
// Lucide's stroke (`-fill`: filled in). Beside them the pack's manifest (pack.json: name, author, licence, and the icons
|
|
@@ -19,7 +20,15 @@ import { execFileSync } from 'node:child_process';
|
|
|
19
20
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
20
21
|
import { ICON_PACK_FILE, ICON_PACK_LICENSE, SYSTEM_PACKS } from '@morphemecms/cms/site/iconPacks.mjs';
|
|
21
22
|
import { packFolder } from '@morphemecms/cms/site/icons.mjs';
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
MATERIAL_DESIGN_ICONS,
|
|
25
|
+
PACK_MAPS,
|
|
26
|
+
PHOSPHOR_FILL_ICONS,
|
|
27
|
+
PACK_SOURCES,
|
|
28
|
+
phosphorFile,
|
|
29
|
+
phosphorNames,
|
|
30
|
+
usedIcons,
|
|
31
|
+
} from '@morphemecms/cms/site/icons.mjs';
|
|
23
32
|
|
|
24
33
|
const MDI = 'material-design-icons';
|
|
25
34
|
const version = (spec) => spec.replace(/^.*@/, '');
|
|
@@ -90,6 +99,10 @@ function sourcesOf(id) {
|
|
|
90
99
|
const file = sourceFile[MDI](icon);
|
|
91
100
|
return { name, icon, file, text: () => bare(file) };
|
|
92
101
|
}),
|
|
102
|
+
...(PHOSPHOR_FILL_ICONS[id] ?? []).map((name) => {
|
|
103
|
+
const file = `${phosphorPkg}assets/fill/${name}-fill.svg`;
|
|
104
|
+
return { name, icon: `${name}-fill`, file, text: () => bare(file) };
|
|
105
|
+
}),
|
|
93
106
|
];
|
|
94
107
|
}
|
|
95
108
|
|
|
@@ -118,6 +131,7 @@ for (const id of SYSTEM_PACKS) {
|
|
|
118
131
|
for (const { name, text } of sourcesOf(id)) writeFileSync(`${dir}${name}.svg`, text());
|
|
119
132
|
const { name, author, license, url, package: spec } = PACK_SOURCES[id];
|
|
120
133
|
const mdi = Object.keys(MATERIAL_DESIGN_ICONS[id] ?? {}).sort();
|
|
134
|
+
const fills = [...(PHOSPHOR_FILL_ICONS[id] ?? [])].sort();
|
|
121
135
|
const manifest = {
|
|
122
136
|
name,
|
|
123
137
|
author,
|
|
@@ -125,19 +139,37 @@ for (const id of SYSTEM_PACKS) {
|
|
|
125
139
|
url,
|
|
126
140
|
version: version(spec),
|
|
127
141
|
source: spec,
|
|
128
|
-
...(mdi.length
|
|
142
|
+
...(mdi.length || fills.length
|
|
129
143
|
? {
|
|
130
144
|
includes: [
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
...(mdi.length
|
|
146
|
+
? [
|
|
147
|
+
{
|
|
148
|
+
name: PACK_SOURCES[MDI].name,
|
|
149
|
+
author: PACK_SOURCES[MDI].author,
|
|
150
|
+
license: PACK_SOURCES[MDI].license,
|
|
151
|
+
url: PACK_SOURCES[MDI].url,
|
|
152
|
+
version: version(PACK_SOURCES[MDI].package),
|
|
153
|
+
source: PACK_SOURCES[MDI].package,
|
|
154
|
+
licenseFile: `${ICON_PACK_LICENSE}-${MDI}`,
|
|
155
|
+
icons: mdi,
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
: []),
|
|
159
|
+
...(fills.length
|
|
160
|
+
? [
|
|
161
|
+
{
|
|
162
|
+
name: PACK_SOURCES.phosphor.name,
|
|
163
|
+
author: PACK_SOURCES.phosphor.author,
|
|
164
|
+
license: PACK_SOURCES.phosphor.license,
|
|
165
|
+
url: PACK_SOURCES.phosphor.url,
|
|
166
|
+
version: version(PACK_SOURCES.phosphor.package),
|
|
167
|
+
source: PACK_SOURCES.phosphor.package,
|
|
168
|
+
licenseFile: `${ICON_PACK_LICENSE}-phosphor`,
|
|
169
|
+
icons: fills,
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
: []),
|
|
141
173
|
],
|
|
142
174
|
}
|
|
143
175
|
: {}),
|
|
@@ -145,6 +177,12 @@ for (const id of SYSTEM_PACKS) {
|
|
|
145
177
|
writeFileSync(`${dir}${ICON_PACK_FILE}`, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
146
178
|
writeFileSync(`${dir}${ICON_PACK_LICENSE}`, `${readFileSync(`${pkgs[id]}LICENSE`, 'utf8').trim()}\n`);
|
|
147
179
|
if (mdi.length) writeFileSync(`${dir}${ICON_PACK_LICENSE}-${MDI}`, `${readFileSync(`${pkgs[MDI]}LICENSE`, 'utf8').trim()}\n`);
|
|
180
|
+
if (fills.length)
|
|
181
|
+
writeFileSync(
|
|
182
|
+
`${dir}${ICON_PACK_LICENSE}-phosphor`,
|
|
183
|
+
`${readFileSync(`${phosphorPkg}LICENSE`, 'utf8').trim()}
|
|
184
|
+
`,
|
|
185
|
+
);
|
|
148
186
|
console.log(
|
|
149
187
|
`${readdirSync(dir).filter((f) => f.endsWith('.svg')).length} ikoner fra ${spec}${mdi.length ? ` og ${PACK_SOURCES[MDI].package}` : ''} i ${dir}.`,
|
|
150
188
|
);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Checks that Material's CSS chain (theme/styles/: base, ui, blocks, components, global) sits in the fallback layer, so a theme wins.
|
|
2
|
+
// Reads the five files with postcss; no browser, no build. Exposes nothing; run by node --test.
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import postcss from 'postcss';
|
|
7
|
+
|
|
8
|
+
const names = ['base', 'ui', 'blocks', 'components', 'global'];
|
|
9
|
+
const roots = Object.fromEntries(
|
|
10
|
+
names.map((name) => [name, postcss.parse(readFileSync(new URL(`../theme/styles/${name}.css`, import.meta.url), 'utf8'))]),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const customOnly = (rule) => rule.nodes.every((node) => node.type === 'comment' || (node.type === 'decl' && node.prop.startsWith('--')));
|
|
14
|
+
|
|
15
|
+
// Top-level rules, also inside top-level @media and @supports, outside every layer.
|
|
16
|
+
function unlayeredRules(root) {
|
|
17
|
+
const found = [];
|
|
18
|
+
root.each((node) => {
|
|
19
|
+
if (node.type === 'rule') found.push(node);
|
|
20
|
+
else if (node.type === 'atrule' && ['media', 'supports'].includes(node.name)) {
|
|
21
|
+
node.each((child) => child.type === 'rule' && found.push(child));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return found;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('no unlayered rule sets more than custom properties', () => {
|
|
28
|
+
for (const name of names) {
|
|
29
|
+
for (const rule of unlayeredRules(roots[name]).filter((rule) => !customOnly(rule))) {
|
|
30
|
+
assert.fail(`${name}.css line ${rule.source.start.line}: "${rule.selector}" sets more than custom properties outside a layer`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('base.css orders the layers with fallback last', () => {
|
|
36
|
+
const orders = [];
|
|
37
|
+
roots.base.walkAtRules('layer', (atRule) => atRule.nodes === undefined && orders.push(atRule.params));
|
|
38
|
+
assert.ok(orders.length > 0, 'base.css has no layer order statement');
|
|
39
|
+
const names = orders[0].split(',').map((name) => name.trim());
|
|
40
|
+
assert.equal(names.at(-1), 'fallback');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('the CSS chain has no !important', () => {
|
|
44
|
+
for (const name of names) {
|
|
45
|
+
roots[name].walkDecls((decl) => {
|
|
46
|
+
assert.ok(!decl.important, `${name}.css line ${decl.source.start.line}: "${decl.prop}" is !important`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
package/site/icons.d.mts
CHANGED
|
@@ -68,17 +68,30 @@ export const MATERIAL_DESIGN_ICONS: {
|
|
|
68
68
|
'arrows-in-line-horizontal': string;
|
|
69
69
|
'arrows-out-line-horizontal': string;
|
|
70
70
|
binoculars: string;
|
|
71
|
+
'facebook-logo': string;
|
|
71
72
|
'github-logo': string;
|
|
72
73
|
'google-logo': string;
|
|
74
|
+
'linkedin-logo': string;
|
|
75
|
+
'pinterest-logo': string;
|
|
73
76
|
'hard-drives': string;
|
|
74
77
|
scan: string;
|
|
75
78
|
swatches: string;
|
|
76
79
|
};
|
|
77
80
|
lucide: {
|
|
81
|
+
'facebook-logo': string;
|
|
78
82
|
'github-logo': string;
|
|
79
83
|
'google-logo': string;
|
|
84
|
+
'linkedin-logo': string;
|
|
85
|
+
'pinterest-logo': string;
|
|
80
86
|
};
|
|
81
87
|
};
|
|
88
|
+
/** For each system pack but Phosphor's: Phosphor's names of the icons that neither the pack's set nor Material Design Icons has
|
|
89
|
+
* (X's logo: Material Design Icons has only the old Twitter bird, which X's brand rules forbid), drawn from Phosphor's filled
|
|
90
|
+
* icon (`fill/<name>-fill.svg`). */
|
|
91
|
+
export const PHOSPHOR_FILL_ICONS: {
|
|
92
|
+
'material-symbols': string[];
|
|
93
|
+
lucide: string[];
|
|
94
|
+
};
|
|
82
95
|
/** Phosphor's name → Material Symbols' (Outlined, weight 400; `-fill` the filled one), for every icon the site and the
|
|
83
96
|
* editor draw (usedIcons) but those in MATERIAL_DESIGN_ICONS['material-symbols']. */
|
|
84
97
|
export const MATERIAL_SYMBOLS: {
|