@morphemecms/cms 0.1.0 → 0.2.0-next-20261104150211
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 +12 -0
- package/LICENSE +21 -21
- package/README.md +51 -51
- package/astro.mjs +162 -162
- package/bin/morpheme.mjs +38 -38
- package/client.d.ts +10 -10
- package/defaults/blockTexts.ts +33 -33
- package/defaults/collections.ts +774 -774
- package/defaults/colors.ts +125 -125
- package/defaults/company.ts +20 -20
- package/defaults/editor.ts +178 -178
- package/defaults/internalLinks.ts +30 -30
- package/defaults/opens.ts +18 -18
- package/defaults/popup.tsx +93 -93
- package/defaults/privacy.ts +79 -79
- package/defaults/rules.ts +450 -450
- package/defaults/schema.ts +16 -16
- package/defaults/sitedata.ts +129 -129
- package/defaults/standard.ts +106 -106
- package/defaults/standardPages.ts +87 -87
- package/defaults/texts.ts +481 -481
- package/demo/README.md +26 -26
- package/demo/src/content/blog/build-a-page-from-blocks.md +19 -19
- package/demo/src/content/blog/colors-everyone-can-read.md +19 -19
- package/demo/src/content/blog/edit-your-website-from-your-phone.md +21 -21
- package/demo/src/content/blog/every-save-is-a-version.md +21 -21
- package/demo/src/content/blog/schedule-a-post.md +17 -17
- package/demo/src/content/blog/secure-from-day-one.md +17 -17
- package/demo/src/content/blog/seo-without-the-guesswork.md +23 -23
- package/demo/src/content/blog/switch-themes-without-losing-anything.md +23 -23
- package/demo/src/content/blog/the-app-store.md +21 -21
- package/demo/src/content/pages/about.md +22 -22
- package/demo/src/content/pages/app-store.md +30 -30
- package/demo/src/content/pages/blocks.md +98 -98
- package/demo/src/content/pages/blog-page.md +11 -11
- package/demo/src/content/pages/contact.md +16 -16
- package/demo/src/content/pages/get-started.md +20 -20
- package/demo/src/content/pages/home.md +26 -26
- package/demo/src/content/pages/page-not-found.md +11 -11
- package/demo/src/content/pages/privacy-policy.md +24 -24
- package/demo/src/data/blocks.json +25 -25
- package/demo/src/data/booking.json +6 -6
- package/demo/src/data/categories/apps.json +6 -6
- package/demo/src/data/categories/design.json +6 -6
- package/demo/src/data/categories/editing.json +6 -6
- package/demo/src/data/categories/security.json +6 -6
- package/demo/src/data/categories/seo.json +6 -6
- package/demo/src/data/colors.json +75 -75
- package/demo/src/data/contact.json +8 -8
- package/demo/src/data/cookies.json +21 -21
- package/demo/src/data/footer.json +27 -27
- package/demo/src/data/menu.json +35 -35
- package/demo/src/data/mobile-menu.json +25 -25
- package/demo/src/data/popups/booking-bar.json +13 -13
- package/demo/src/data/popups/try-the-editor.json +16 -16
- package/demo/src/data/standard-pages.json +8 -8
- package/demo/src/data/testimonials/sample-bakery.json +10 -10
- package/demo/src/data/testimonials/sample-clinic.json +10 -10
- package/demo/src/data/testimonials/sample-studio.json +10 -10
- package/demo/src/site/colors.ts +11 -11
- package/demo/src/site/config.ts +19 -19
- package/icons/lucide/LICENSE +43 -43
- package/icons/lucide/address-book.svg +1 -1
- package/icons/lucide/app-window.svg +1 -1
- package/icons/lucide/arrow-counter-clockwise.svg +1 -1
- package/icons/lucide/arrow-down.svg +1 -1
- package/icons/lucide/arrow-left.svg +1 -1
- package/icons/lucide/arrow-right.svg +1 -1
- package/icons/lucide/arrow-square-out.svg +1 -1
- package/icons/lucide/arrow-u-up-left.svg +1 -1
- package/icons/lucide/arrow-u-up-right.svg +1 -1
- package/icons/lucide/arrow-up-right.svg +1 -1
- package/icons/lucide/arrow-up.svg +1 -1
- package/icons/lucide/arrows-down-up.svg +1 -1
- package/icons/lucide/arrows-in-line-horizontal.svg +1 -1
- package/icons/lucide/arrows-out-line-horizontal.svg +1 -1
- package/icons/lucide/article.svg +1 -1
- package/icons/lucide/bank.svg +1 -1
- package/icons/lucide/bell.svg +1 -1
- package/icons/lucide/bicycle.svg +1 -1
- package/icons/lucide/binoculars.svg +1 -1
- package/icons/lucide/book-open-text.svg +1 -1
- package/icons/lucide/book-open.svg +1 -1
- package/icons/lucide/browser.svg +1 -1
- package/icons/lucide/buildings.svg +1 -1
- package/icons/lucide/bus.svg +1 -1
- package/icons/lucide/calendar-blank.svg +1 -1
- package/icons/lucide/calendar-check.svg +1 -1
- package/icons/lucide/car.svg +1 -1
- package/icons/lucide/cards.svg +1 -1
- package/icons/lucide/caret-down.svg +1 -1
- package/icons/lucide/caret-left.svg +1 -1
- package/icons/lucide/caret-right.svg +1 -1
- package/icons/lucide/caret-up.svg +1 -1
- package/icons/lucide/certificate.svg +1 -1
- package/icons/lucide/chart-line-up.svg +1 -1
- package/icons/lucide/chats.svg +1 -1
- package/icons/lucide/check-circle.svg +1 -1
- package/icons/lucide/check.svg +1 -1
- package/icons/lucide/circle-half.svg +1 -1
- package/icons/lucide/circle.svg +1 -1
- package/icons/lucide/clock-counter-clockwise.svg +1 -1
- package/icons/lucide/clock.svg +1 -1
- package/icons/lucide/code.svg +1 -1
- package/icons/lucide/coffee.svg +1 -1
- package/icons/lucide/columns.svg +1 -1
- package/icons/lucide/cookie.svg +1 -1
- package/icons/lucide/copy.svg +1 -1
- package/icons/lucide/credit-card.svg +1 -1
- package/icons/lucide/currency-circle-dollar.svg +1 -1
- package/icons/lucide/cursor-click.svg +1 -1
- package/icons/lucide/database.svg +1 -1
- package/icons/lucide/desktop.svg +1 -1
- package/icons/lucide/device-mobile.svg +1 -1
- package/icons/lucide/door-open.svg +1 -1
- package/icons/lucide/download-simple.svg +1 -1
- package/icons/lucide/envelope-simple.svg +1 -1
- package/icons/lucide/eye-slash.svg +1 -1
- package/icons/lucide/eye.svg +1 -1
- package/icons/lucide/file-pdf.svg +1 -1
- package/icons/lucide/file-text.svg +1 -1
- package/icons/lucide/file-video.svg +1 -1
- package/icons/lucide/file-zip.svg +1 -1
- package/icons/lucide/flower-lotus.svg +1 -1
- package/icons/lucide/folder-simple.svg +1 -1
- package/icons/lucide/funnel.svg +1 -1
- package/icons/lucide/gauge.svg +1 -1
- package/icons/lucide/gear.svg +1 -1
- package/icons/lucide/github-logo.svg +1 -1
- package/icons/lucide/globe.svg +1 -1
- package/icons/lucide/google-logo.svg +1 -1
- package/icons/lucide/graduation-cap.svg +1 -1
- package/icons/lucide/hammer.svg +1 -1
- package/icons/lucide/hand-heart.svg +1 -1
- package/icons/lucide/handshake.svg +1 -1
- package/icons/lucide/hard-drives.svg +1 -1
- package/icons/lucide/heart.svg +1 -1
- package/icons/lucide/house.svg +1 -1
- package/icons/lucide/image-square.svg +1 -1
- package/icons/lucide/image.svg +1 -1
- package/icons/lucide/info.svg +1 -1
- package/icons/lucide/keyboard.svg +1 -1
- package/icons/lucide/leaf.svg +1 -1
- package/icons/lucide/lifebuoy.svg +1 -1
- package/icons/lucide/lightbulb.svg +1 -1
- package/icons/lucide/link-break.svg +1 -1
- package/icons/lucide/link-simple.svg +1 -1
- package/icons/lucide/list-bullets.svg +1 -1
- package/icons/lucide/list-checks.svg +1 -1
- package/icons/lucide/list-numbers.svg +1 -1
- package/icons/lucide/list.svg +1 -1
- package/icons/lucide/lock-simple-open.svg +1 -1
- package/icons/lucide/lock-simple.svg +1 -1
- package/icons/lucide/magnifying-glass.svg +1 -1
- package/icons/lucide/map-pin.svg +1 -1
- package/icons/lucide/map-trifold.svg +1 -1
- package/icons/lucide/medal.svg +1 -1
- package/icons/lucide/megaphone.svg +1 -1
- package/icons/lucide/minus.svg +1 -1
- package/icons/lucide/moon.svg +1 -1
- package/icons/lucide/newspaper.svg +1 -1
- package/icons/lucide/pack.json +23 -23
- package/icons/lucide/paint-brush-broad.svg +1 -1
- package/icons/lucide/palette.svg +1 -1
- package/icons/lucide/paper-plane-tilt.svg +1 -1
- package/icons/lucide/pencil-simple.svg +1 -1
- package/icons/lucide/phone.svg +1 -1
- package/icons/lucide/play-fill.svg +1 -1
- package/icons/lucide/plus.svg +1 -1
- package/icons/lucide/prohibit.svg +1 -1
- package/icons/lucide/puzzle-piece.svg +1 -1
- package/icons/lucide/question.svg +1 -1
- package/icons/lucide/quotes.svg +1 -1
- package/icons/lucide/rows.svg +1 -1
- package/icons/lucide/ruler.svg +1 -1
- package/icons/lucide/scan.svg +1 -1
- package/icons/lucide/shapes.svg +1 -1
- package/icons/lucide/share-network.svg +1 -1
- package/icons/lucide/shield-check.svg +1 -1
- package/icons/lucide/sidebar-simple.svg +1 -1
- package/icons/lucide/sign-out.svg +1 -1
- package/icons/lucide/signpost.svg +1 -1
- package/icons/lucide/sliders-horizontal.svg +1 -1
- package/icons/lucide/smiley.svg +1 -1
- package/icons/lucide/sparkle.svg +1 -1
- package/icons/lucide/square-half-bottom.svg +1 -1
- package/icons/lucide/square.svg +1 -1
- package/icons/lucide/squares-four.svg +1 -1
- package/icons/lucide/sticker.svg +1 -1
- package/icons/lucide/storefront.svg +1 -1
- package/icons/lucide/sun.svg +1 -1
- package/icons/lucide/swatches.svg +1 -1
- package/icons/lucide/tag.svg +1 -1
- package/icons/lucide/text-aa.svg +1 -1
- package/icons/lucide/text-align-left.svg +1 -1
- package/icons/lucide/text-b.svg +1 -1
- package/icons/lucide/text-h-one.svg +1 -1
- package/icons/lucide/text-h-three.svg +1 -1
- package/icons/lucide/text-h-two.svg +1 -1
- package/icons/lucide/text-italic.svg +1 -1
- package/icons/lucide/text-t.svg +1 -1
- package/icons/lucide/ticket.svg +1 -1
- package/icons/lucide/toggle-right.svg +1 -1
- package/icons/lucide/train.svg +1 -1
- package/icons/lucide/trash.svg +1 -1
- package/icons/lucide/upload-simple.svg +1 -1
- package/icons/lucide/user-circle.svg +1 -1
- package/icons/lucide/user.svg +1 -1
- package/icons/lucide/users-three.svg +1 -1
- package/icons/lucide/users.svg +1 -1
- package/icons/lucide/video-camera.svg +1 -1
- package/icons/lucide/warning-circle.svg +1 -1
- package/icons/lucide/warning.svg +1 -1
- package/icons/lucide/wave-sine.svg +1 -1
- package/icons/lucide/wheelchair.svg +1 -1
- package/icons/lucide/wrench.svg +1 -1
- package/icons/lucide/x.svg +1 -1
- package/icons/material-symbols/LICENSE +201 -201
- package/icons/material-symbols/address-book.svg +1 -1
- package/icons/material-symbols/app-window.svg +1 -1
- package/icons/material-symbols/arrow-counter-clockwise.svg +1 -1
- package/icons/material-symbols/arrow-down.svg +1 -1
- package/icons/material-symbols/arrow-left.svg +1 -1
- package/icons/material-symbols/arrow-right.svg +1 -1
- package/icons/material-symbols/arrow-square-out.svg +1 -1
- package/icons/material-symbols/arrow-u-up-left.svg +1 -1
- package/icons/material-symbols/arrow-u-up-right.svg +1 -1
- package/icons/material-symbols/arrow-up-right.svg +1 -1
- package/icons/material-symbols/arrow-up.svg +1 -1
- package/icons/material-symbols/arrows-down-up.svg +1 -1
- package/icons/material-symbols/arrows-in-line-horizontal.svg +1 -1
- package/icons/material-symbols/arrows-out-line-horizontal.svg +1 -1
- package/icons/material-symbols/article.svg +1 -1
- package/icons/material-symbols/bank.svg +1 -1
- package/icons/material-symbols/bell.svg +1 -1
- package/icons/material-symbols/bicycle.svg +1 -1
- package/icons/material-symbols/binoculars.svg +1 -1
- package/icons/material-symbols/book-open-text.svg +1 -1
- package/icons/material-symbols/book-open.svg +1 -1
- package/icons/material-symbols/browser.svg +1 -1
- package/icons/material-symbols/buildings.svg +1 -1
- package/icons/material-symbols/bus.svg +1 -1
- package/icons/material-symbols/calendar-blank.svg +1 -1
- package/icons/material-symbols/calendar-check.svg +1 -1
- package/icons/material-symbols/car.svg +1 -1
- package/icons/material-symbols/cards.svg +1 -1
- package/icons/material-symbols/caret-down.svg +1 -1
- package/icons/material-symbols/caret-left.svg +1 -1
- package/icons/material-symbols/caret-right.svg +1 -1
- package/icons/material-symbols/caret-up.svg +1 -1
- package/icons/material-symbols/certificate.svg +1 -1
- package/icons/material-symbols/chart-line-up.svg +1 -1
- package/icons/material-symbols/chats.svg +1 -1
- package/icons/material-symbols/check-circle.svg +1 -1
- package/icons/material-symbols/check.svg +1 -1
- package/icons/material-symbols/circle-half.svg +1 -1
- package/icons/material-symbols/circle.svg +1 -1
- package/icons/material-symbols/clock-counter-clockwise.svg +1 -1
- package/icons/material-symbols/clock.svg +1 -1
- package/icons/material-symbols/code.svg +1 -1
- package/icons/material-symbols/coffee.svg +1 -1
- package/icons/material-symbols/columns.svg +1 -1
- package/icons/material-symbols/cookie.svg +1 -1
- package/icons/material-symbols/copy.svg +1 -1
- package/icons/material-symbols/credit-card.svg +1 -1
- package/icons/material-symbols/currency-circle-dollar.svg +1 -1
- package/icons/material-symbols/cursor-click.svg +1 -1
- package/icons/material-symbols/database.svg +1 -1
- package/icons/material-symbols/desktop.svg +1 -1
- package/icons/material-symbols/device-mobile.svg +1 -1
- package/icons/material-symbols/door-open.svg +1 -1
- package/icons/material-symbols/download-simple.svg +1 -1
- package/icons/material-symbols/envelope-simple.svg +1 -1
- package/icons/material-symbols/eye-slash.svg +1 -1
- package/icons/material-symbols/eye.svg +1 -1
- package/icons/material-symbols/file-pdf.svg +1 -1
- package/icons/material-symbols/file-text.svg +1 -1
- package/icons/material-symbols/file-video.svg +1 -1
- package/icons/material-symbols/file-zip.svg +1 -1
- package/icons/material-symbols/flower-lotus.svg +1 -1
- package/icons/material-symbols/folder-simple.svg +1 -1
- package/icons/material-symbols/funnel.svg +1 -1
- package/icons/material-symbols/gauge.svg +1 -1
- package/icons/material-symbols/gear.svg +1 -1
- package/icons/material-symbols/github-logo.svg +1 -1
- package/icons/material-symbols/globe.svg +1 -1
- package/icons/material-symbols/google-logo.svg +1 -1
- package/icons/material-symbols/graduation-cap.svg +1 -1
- package/icons/material-symbols/hammer.svg +1 -1
- package/icons/material-symbols/hand-heart.svg +1 -1
- package/icons/material-symbols/handshake.svg +1 -1
- package/icons/material-symbols/hard-drives.svg +1 -1
- package/icons/material-symbols/heart.svg +1 -1
- package/icons/material-symbols/house.svg +1 -1
- package/icons/material-symbols/image-square.svg +1 -1
- package/icons/material-symbols/image.svg +1 -1
- package/icons/material-symbols/info.svg +1 -1
- package/icons/material-symbols/keyboard.svg +1 -1
- package/icons/material-symbols/leaf.svg +1 -1
- package/icons/material-symbols/lifebuoy.svg +1 -1
- package/icons/material-symbols/lightbulb.svg +1 -1
- package/icons/material-symbols/link-break.svg +1 -1
- package/icons/material-symbols/link-simple.svg +1 -1
- package/icons/material-symbols/list-bullets.svg +1 -1
- package/icons/material-symbols/list-checks.svg +1 -1
- package/icons/material-symbols/list-numbers.svg +1 -1
- package/icons/material-symbols/list.svg +1 -1
- package/icons/material-symbols/lock-simple-open.svg +1 -1
- package/icons/material-symbols/lock-simple.svg +1 -1
- package/icons/material-symbols/magnifying-glass.svg +1 -1
- package/icons/material-symbols/map-pin.svg +1 -1
- package/icons/material-symbols/map-trifold.svg +1 -1
- package/icons/material-symbols/medal.svg +1 -1
- package/icons/material-symbols/megaphone.svg +1 -1
- package/icons/material-symbols/minus.svg +1 -1
- package/icons/material-symbols/moon.svg +1 -1
- package/icons/material-symbols/newspaper.svg +1 -1
- package/icons/material-symbols/pack.json +29 -29
- package/icons/material-symbols/paint-brush-broad.svg +1 -1
- package/icons/material-symbols/palette.svg +1 -1
- package/icons/material-symbols/paper-plane-tilt.svg +1 -1
- package/icons/material-symbols/pencil-simple.svg +1 -1
- package/icons/material-symbols/phone.svg +1 -1
- package/icons/material-symbols/play-fill.svg +1 -1
- package/icons/material-symbols/plus.svg +1 -1
- package/icons/material-symbols/prohibit.svg +1 -1
- package/icons/material-symbols/puzzle-piece.svg +1 -1
- package/icons/material-symbols/question.svg +1 -1
- package/icons/material-symbols/quotes.svg +1 -1
- package/icons/material-symbols/rows.svg +1 -1
- package/icons/material-symbols/ruler.svg +1 -1
- package/icons/material-symbols/scan.svg +1 -1
- package/icons/material-symbols/shapes.svg +1 -1
- package/icons/material-symbols/share-network.svg +1 -1
- package/icons/material-symbols/shield-check.svg +1 -1
- package/icons/material-symbols/sidebar-simple.svg +1 -1
- package/icons/material-symbols/sign-out.svg +1 -1
- package/icons/material-symbols/signpost.svg +1 -1
- package/icons/material-symbols/sliders-horizontal.svg +1 -1
- package/icons/material-symbols/smiley.svg +1 -1
- package/icons/material-symbols/sparkle.svg +1 -1
- package/icons/material-symbols/square-half-bottom.svg +1 -1
- package/icons/material-symbols/square.svg +1 -1
- package/icons/material-symbols/squares-four.svg +1 -1
- package/icons/material-symbols/sticker.svg +1 -1
- package/icons/material-symbols/storefront.svg +1 -1
- package/icons/material-symbols/sun.svg +1 -1
- package/icons/material-symbols/swatches.svg +1 -1
- package/icons/material-symbols/tag.svg +1 -1
- package/icons/material-symbols/text-aa.svg +1 -1
- package/icons/material-symbols/text-align-left.svg +1 -1
- package/icons/material-symbols/text-b.svg +1 -1
- package/icons/material-symbols/text-h-one.svg +1 -1
- package/icons/material-symbols/text-h-three.svg +1 -1
- package/icons/material-symbols/text-h-two.svg +1 -1
- package/icons/material-symbols/text-italic.svg +1 -1
- package/icons/material-symbols/text-t.svg +1 -1
- package/icons/material-symbols/ticket.svg +1 -1
- package/icons/material-symbols/toggle-right.svg +1 -1
- package/icons/material-symbols/train.svg +1 -1
- package/icons/material-symbols/trash.svg +1 -1
- package/icons/material-symbols/upload-simple.svg +1 -1
- package/icons/material-symbols/user-circle.svg +1 -1
- package/icons/material-symbols/user.svg +1 -1
- package/icons/material-symbols/users-three.svg +1 -1
- package/icons/material-symbols/users.svg +1 -1
- package/icons/material-symbols/video-camera.svg +1 -1
- package/icons/material-symbols/warning-circle.svg +1 -1
- package/icons/material-symbols/warning.svg +1 -1
- package/icons/material-symbols/wave-sine.svg +1 -1
- package/icons/material-symbols/wheelchair.svg +1 -1
- package/icons/material-symbols/wrench.svg +1 -1
- package/icons/material-symbols/x.svg +1 -1
- package/icons/phosphor/pack.json +8 -8
- package/lib/apps.mjs +142 -139
- package/lib/apps.test.d.mts +1 -0
- package/lib/apps.test.mjs +50 -0
- package/lib/blocks.mjs +90 -90
- package/lib/caseCollisions.d.mts +4 -0
- package/lib/caseCollisions.mjs +62 -0
- package/lib/caseCollisions.test.d.mts +1 -0
- package/lib/caseCollisions.test.mjs +74 -0
- package/lib/colors.ts +86 -86
- package/lib/contactBar.ts +18 -18
- package/lib/contracts.d.mts +15 -0
- package/lib/contracts.mjs +17 -0
- package/lib/cookieCategories.mjs +28 -28
- package/lib/crop.mjs +83 -83
- package/lib/csp.mjs +130 -130
- package/lib/cspSources.mjs +60 -60
- package/lib/dynamic-colors.ts +426 -426
- package/lib/git.mjs +64 -64
- package/lib/hooks.ts +51 -51
- package/lib/inline.ts +20 -20
- package/lib/license-texts/Apache-2.0.txt +190 -190
- package/lib/license-texts/BSD-2-Clause.txt +24 -24
- package/lib/license-texts/BSD-3-Clause.txt +28 -28
- package/lib/license-texts/ISC.txt +15 -15
- package/lib/license-texts/MIT.txt +21 -21
- package/lib/licenses.d.mts +1 -1
- package/lib/licenses.mjs +683 -683
- package/lib/markdown.ts +42 -42
- package/lib/motion-schemes.mjs +5 -5
- package/lib/motion.ts +131 -131
- package/lib/notice.mjs +34 -34
- package/lib/scheduled.mjs +37 -37
- package/lib/snackbar.ts +149 -149
- package/lib/status.mjs +18 -18
- package/lib/storeEntries.d.mts +18 -0
- package/lib/storeEntries.mjs +64 -0
- package/lib/storeEntries.test.d.mts +1 -0
- package/lib/storeEntries.test.mjs +71 -0
- package/lib/svg.mjs +231 -231
- package/lib/text-colors.mjs +43 -43
- package/lib/video-id.mjs +72 -72
- package/lib/video.mjs +91 -91
- package/package.json +136 -131
- package/pages/404.astro +10 -10
- package/pages/[...slug].astro +51 -51
- package/pages/_headers.txt +31 -31
- package/pages/blog/index.astro +10 -10
- package/pages/blog/kategori/[category].astro +18 -18
- package/pages/editor/code-images.json.js +25 -25
- package/pages/editor/index.astro +75 -75
- package/pages/editor/manifest.webmanifest.js +23 -23
- package/pages/editor/sw.js +33 -33
- package/pages/editor/sw.js.js +4 -4
- package/pages/editor/thumbnails.json.js +21 -21
- package/pages/editor/version.json.js +22 -22
- package/pages/headers.js +4 -4
- package/pages/index.astro +10 -10
- package/pages/ny.astro +6 -6
- package/pages/robots.txt.js +3 -3
- package/pages/rss.xml.js +20 -20
- package/scripts/app-screenshots.mjs +164 -164
- package/scripts/app-workers.mjs +6 -6
- package/scripts/bin.mjs +12 -12
- package/scripts/built-site.mjs +134 -99
- package/scripts/check-consent.mjs +113 -113
- package/scripts/check-content.mjs +151 -149
- package/scripts/check-demo.mjs +24 -24
- package/scripts/check-store.mjs +160 -0
- package/scripts/check-themes.mjs +45 -45
- package/scripts/check-worker.mjs +569 -569
- package/scripts/compare-html.mjs +81 -81
- package/scripts/declarations.mjs +10 -0
- package/scripts/declarations.test.mjs +32 -0
- package/scripts/demo-site.mjs +42 -42
- package/scripts/editor-icons.mjs +56 -56
- package/scripts/editor-roundtrip.mjs +134 -134
- package/scripts/editor-screens.mjs +101 -101
- package/scripts/git-env.mjs +9 -9
- package/scripts/icon-packs.mjs +151 -151
- package/scripts/link-counts.mjs +22 -22
- package/scripts/page-colors.mjs +42 -42
- package/scripts/releases.mjs +80 -80
- package/scripts/responsive-images.mjs +94 -94
- package/scripts/scheduled-list.mjs +15 -15
- package/scripts/theme-screenshots.mjs +88 -88
- package/scripts/types.mjs +17 -21
- package/scripts/video-thumbnails.mjs +89 -89
- package/scripts/worker-modules.mjs +34 -34
- package/site/appCookies.mjs +74 -74
- package/site/apps.ts +21 -21
- package/site/autolinks.mjs +200 -200
- package/site/blockScripts.ts +31 -31
- package/site/categories.ts +16 -16
- package/site/consent.ts +26 -26
- package/site/content.config.ts +84 -84
- package/site/content.ts +104 -104
- package/site/cookieList.ts +17 -17
- package/site/defineSite.d.mts +2 -14
- package/site/defineSite.mjs +131 -136
- package/site/directives/interact.mjs +31 -31
- package/site/editor-version.mjs +26 -26
- package/site/fill.ts +13 -13
- package/site/format.ts +11 -11
- package/site/home.ts +66 -66
- package/site/iconPacks.mjs +43 -43
- package/site/icons.mjs +605 -605
- package/site/images.mjs +157 -157
- package/site/motion.ts +13 -13
- package/site/noindex.mjs +21 -21
- package/site/pageColors.ts +110 -110
- package/site/popups.ts +98 -98
- package/site/schema.ts +189 -189
- package/site/sections.ts +3 -3
- package/site/seo.ts +28 -28
- package/site/site.ts +76 -76
- package/site/siteConfig.mjs +8 -8
- package/site/siteCsp.mjs +75 -75
- package/site/siteModules.mjs +27 -27
- package/site/standardPages.ts +59 -59
- package/site/storeTheme.d.mts +6 -0
- package/site/storeTheme.mjs +29 -0
- package/site/storeTheme.test.d.mts +1 -0
- package/site/storeTheme.test.mjs +53 -0
- package/site/testimonials.ts +48 -48
- package/site/text.ts +43 -43
- package/site/theme.mjs +483 -472
- package/site/tracking.client.ts +225 -225
- package/site/tracking.ts +9 -9
- package/src/ActionParts.tsx +205 -205
- package/src/Advanced.tsx +152 -152
- package/src/App.tsx +901 -904
- package/src/AppList.tsx +1362 -1345
- package/src/BlockDialog.tsx +360 -360
- package/src/BlockParts.tsx +652 -652
- package/src/BlockSettings.tsx +533 -533
- package/src/BrandForm.tsx +164 -164
- package/src/Colors.tsx +476 -476
- package/src/CommunicationParts.tsx +199 -199
- package/src/CompanyForm.tsx +209 -209
- package/src/Consents.tsx +175 -175
- package/src/ContainmentParts.tsx +185 -185
- package/src/Crop.tsx +152 -152
- package/src/DataForm.tsx +434 -434
- package/src/Deleted.tsx +177 -177
- package/src/EntryForm.tsx +740 -740
- package/src/FileView.tsx +30 -30
- package/src/FormFrame.tsx +237 -237
- package/src/Keywords.tsx +225 -225
- package/src/Library.tsx +1563 -1563
- package/src/Licenses.tsx +336 -336
- package/src/LinkSheet.tsx +138 -138
- package/src/MarkdownEditor.tsx +1075 -1075
- package/src/Media.tsx +677 -677
- package/src/NavigationParts.tsx +350 -350
- package/src/NavigationRestParts.tsx +247 -247
- package/src/Notifications.tsx +59 -59
- package/src/PagePicker.tsx +136 -136
- package/src/Parts.tsx +991 -991
- package/src/PartsShown.tsx +168 -168
- package/src/PickerParts.tsx +183 -183
- package/src/Redirects.tsx +450 -450
- package/src/Screen.tsx +184 -184
- package/src/SectionsField.tsx +65 -65
- package/src/SelectionParts.tsx +134 -134
- package/src/SeoScores.tsx +184 -184
- package/src/Sheet.tsx +35 -35
- package/src/ThemeStore.tsx +376 -376
- package/src/Tokens.tsx +346 -346
- package/src/Versions.tsx +214 -214
- package/src/addresses.ts +75 -75
- package/src/allIcons.ts +7 -7
- package/src/appFiles.ts +29 -29
- package/src/apps.ts +281 -281
- package/src/ask.tsx +123 -123
- package/src/blocks.ts +251 -251
- package/src/brand.ts +153 -153
- package/src/cache.ts +42 -42
- package/src/check.ts +285 -285
- package/src/checkSite.ts +256 -256
- package/src/{consents.ts → consentData.ts} +26 -26
- package/src/cookieList.ts +70 -70
- package/src/countries.ts +59 -59
- package/src/deploy.ts +155 -155
- package/src/editor.css +602 -602
- package/src/examples.ts +85 -85
- package/src/fields.tsx +942 -942
- package/src/frontmatter.mjs +42 -42
- package/src/github.ts +376 -376
- package/src/i18n/da-DK/advanced.ts +28 -28
- package/src/i18n/da-DK/app.ts +71 -71
- package/src/i18n/da-DK/apps.ts +90 -90
- package/src/i18n/da-DK/blockSettings.ts +38 -38
- package/src/i18n/da-DK/blocks.ts +408 -408
- package/src/i18n/da-DK/check.ts +25 -25
- package/src/i18n/da-DK/colors.ts +64 -64
- package/src/i18n/da-DK/common.ts +118 -118
- package/src/i18n/da-DK/company.ts +24 -24
- package/src/i18n/da-DK/consents.ts +23 -23
- package/src/i18n/da-DK/forms.ts +116 -116
- package/src/i18n/da-DK/history.ts +35 -35
- package/src/i18n/da-DK/index.ts +49 -49
- package/src/i18n/da-DK/keywords.ts +19 -19
- package/src/i18n/da-DK/library.ts +135 -135
- package/src/i18n/da-DK/licenses.ts +25 -25
- package/src/i18n/da-DK/media.ts +79 -79
- package/src/i18n/da-DK/parts.ts +696 -696
- package/src/i18n/da-DK/preview.ts +13 -13
- package/src/i18n/da-DK/redirects.ts +51 -51
- package/src/i18n/da-DK/seo.ts +20 -20
- package/src/i18n/da-DK/text.ts +35 -35
- package/src/i18n/da-DK/theme.ts +41 -41
- package/src/i18n/en-US/advanced.ts +32 -32
- package/src/i18n/en-US/app.ts +84 -84
- package/src/i18n/en-US/apps.ts +134 -134
- package/src/i18n/en-US/blockSettings.ts +48 -48
- package/src/i18n/en-US/blocks.ts +441 -441
- package/src/i18n/en-US/check.ts +31 -31
- package/src/i18n/en-US/colors.ts +82 -82
- package/src/i18n/en-US/common.ts +144 -144
- package/src/i18n/en-US/company.ts +25 -25
- package/src/i18n/en-US/consents.ts +25 -25
- package/src/i18n/en-US/forms.ts +145 -145
- package/src/i18n/en-US/history.ts +43 -43
- package/src/i18n/en-US/index.ts +51 -51
- package/src/i18n/en-US/keywords.ts +24 -24
- package/src/i18n/en-US/library.ts +176 -176
- package/src/i18n/en-US/licenses.ts +36 -36
- package/src/i18n/en-US/media.ts +80 -80
- package/src/i18n/en-US/parts.ts +718 -718
- package/src/i18n/en-US/preview.ts +15 -15
- package/src/i18n/en-US/redirects.ts +66 -66
- package/src/i18n/en-US/seo.ts +26 -26
- package/src/i18n/en-US/text.ts +41 -41
- package/src/i18n/en-US/theme.ts +41 -41
- package/src/i18n/index.ts +124 -124
- package/src/icon.tsx +160 -160
- package/src/iconPacks.ts +89 -89
- package/src/images.ts +163 -163
- package/src/{licenses.ts → licenseData.ts} +79 -79
- package/src/markdown.mjs +204 -204
- package/src/nav.ts +68 -68
- package/src/notFound.ts +28 -28
- package/src/{notifications.ts → notificationData.ts} +223 -223
- package/src/pagePreview.tsx +204 -204
- package/src/r2Caching.ts +55 -55
- package/src/{redirects.ts → redirectData.ts} +149 -149
- package/src/registry.tsx +188 -188
- package/src/schema.ts +417 -417
- package/src/sections.mjs +303 -303
- package/src/seo.ts +333 -333
- package/src/shared.ts +44 -44
- package/src/sidebar.ts +31 -31
- package/src/site.ts +317 -317
- package/src/standard/Block.astro +19 -19
- package/src/standard/BlockSite.astro +18 -18
- package/src/standard/accordion/index.tsx +47 -47
- package/src/standard/accordion/load.ts +23 -23
- package/src/standard/accordion/shared.ts +15 -15
- package/src/standard/accordion/view.ts +32 -32
- package/src/standard/buttons/fields.ts +98 -98
- package/src/standard/buttons/index.tsx +23 -23
- package/src/standard/buttons/load.ts +34 -34
- package/src/standard/buttons/shared.ts +12 -12
- package/src/standard/buttons/view.ts +30 -30
- package/src/standard/cards/index.tsx +71 -71
- package/src/standard/cards/load.ts +17 -17
- package/src/standard/cards/shared.ts +27 -27
- package/src/standard/cards/view.ts +29 -29
- package/src/standard/checklist/index.tsx +27 -27
- package/src/standard/checklist/load.ts +5 -5
- package/src/standard/checklist/view.ts +13 -13
- package/src/standard/cookies/index.tsx +17 -17
- package/src/standard/cookies/list.ts +56 -56
- package/src/standard/cookies/load.ts +11 -11
- package/src/standard/cookies/shared.ts +20 -20
- package/src/standard/cookies/view.ts +28 -28
- package/src/standard/faq/index.tsx +29 -29
- package/src/standard/faq/load.ts +14 -14
- package/src/standard/faq/view.ts +21 -21
- package/src/standard/head.ts +23 -23
- package/src/standard/hero/fields.ts +48 -48
- package/src/standard/hero/index.tsx +57 -57
- package/src/standard/hero/load.ts +23 -23
- package/src/standard/hero/view.ts +38 -38
- package/src/standard/links/index.tsx +41 -41
- package/src/standard/links/load.ts +17 -17
- package/src/standard/links/view.ts +25 -25
- package/src/standard/list/index.tsx +50 -50
- package/src/standard/list/load.ts +5 -5
- package/src/standard/list/view.ts +18 -18
- package/src/standard/load.ts +7 -7
- package/src/standard/notice/index.tsx +30 -30
- package/src/standard/notice/load.ts +16 -16
- package/src/standard/notice/view.ts +10 -10
- package/src/standard/points/index.tsx +42 -42
- package/src/standard/points/load.ts +5 -5
- package/src/standard/points/view.ts +12 -12
- package/src/standard/posts/index.tsx +132 -132
- package/src/standard/posts/load.ts +36 -36
- package/src/standard/posts/shared.ts +21 -21
- package/src/standard/posts/view.ts +63 -63
- package/src/standard/pricecards/index.tsx +57 -57
- package/src/standard/pricecards/load.ts +11 -11
- package/src/standard/pricecards/view.ts +50 -50
- package/src/standard/sharedTexts.ts +6 -6
- package/src/standard/site.ts +124 -124
- package/src/standard/siteLooks.ts +19 -19
- package/src/standard/sites.ts +13 -13
- package/src/standard/steps/index.tsx +28 -28
- package/src/standard/steps/load.ts +5 -5
- package/src/standard/steps/view.ts +11 -11
- package/src/standard/testimonials/index.tsx +87 -87
- package/src/standard/testimonials/list.ts +43 -43
- package/src/standard/testimonials/load.ts +58 -58
- package/src/standard/testimonials/shared.ts +13 -13
- package/src/standard/testimonials/view.ts +76 -76
- package/src/standard/themeBlocks.ts +13 -13
- package/src/standard/video/index.tsx +23 -23
- package/src/standard/video/load.ts +10 -10
- package/src/standard/video/view.ts +42 -42
- package/src/standard/view.tsx +105 -105
- package/src/standard/views.ts +37 -37
- package/src/store.ts +479 -499
- package/src/theme.ts +90 -90
- package/src/themeUi.ts +97 -97
- package/src/themes.ts +91 -91
- package/src/ui.tsx +3402 -3402
- package/src/virtual.d.ts +5 -5
- package/src/when.ts +22 -22
- package/starter/.githooks/pre-push +4 -4
- package/starter/.nvmrc +1 -1
- package/starter/.prettierignore +11 -11
- package/starter/.prettierrc.json +7 -7
- package/starter/README.md +27 -26
- package/starter/astro.config.mjs +5 -5
- package/starter/package.json +67 -67
- package/starter/public/_redirects +1 -1
- package/starter/public/favicon.svg +1 -1
- package/starter/public/logo.svg +1 -1
- package/starter/src/content/pages/blog.md +8 -8
- package/starter/src/content/pages/forside.md +9 -9
- package/starter/src/content/pages/kontakt.md +7 -7
- package/starter/src/content/pages/privatlivspolitik.md +9 -9
- package/starter/src/content/pages/siden-findes-ikke.md +7 -7
- package/starter/src/content.config.ts +2 -2
- package/starter/src/data/apps.json +1 -1
- package/starter/src/data/block-settings.json +3 -3
- package/starter/src/data/blocks.json +1 -1
- package/starter/src/data/booking.json +6 -6
- package/starter/src/data/colors.json +70 -70
- package/starter/src/data/contact.json +8 -8
- package/starter/src/data/cookies.json +21 -21
- package/starter/src/data/csp.json +1 -1
- package/starter/src/data/footer.json +15 -15
- package/starter/src/data/internal-links.json +4 -4
- package/starter/src/data/menu.json +4 -4
- package/starter/src/data/mobile-menu.json +4 -4
- package/starter/src/data/old-addresses.json +1 -1
- package/starter/src/data/redirects.json +1 -1
- package/starter/src/data/standard-pages.json +8 -8
- package/starter/src/data/theme.json +1 -1
- package/starter/src/env.d.ts +2 -2
- package/starter/src/site/config.ts +13 -13
- package/starter/src/site/worker.js +14 -14
- package/starter/tsconfig.json +17 -17
- package/starter/worker/apps.js +6 -6
- package/starter/wrangler.jsonc +39 -39
- package/theme/LICENSE +21 -21
- package/theme/blocks/ArrowLink.tsx +14 -14
- package/theme/blocks/BlockHead.tsx +26 -26
- package/theme/blocks/Panel.tsx +32 -32
- package/theme/blocks/Person.tsx +26 -26
- package/theme/blocks/accordion/PageRow.tsx +25 -25
- package/theme/blocks/accordion/View.tsx +17 -17
- package/theme/blocks/buttons/Buttons.tsx +28 -28
- package/theme/blocks/buttons/View.tsx +12 -12
- package/theme/blocks/cards/LinkCards.tsx +72 -72
- package/theme/blocks/cards/View.tsx +20 -20
- package/theme/blocks/checklist/Checks.tsx +15 -15
- package/theme/blocks/checklist/View.tsx +14 -14
- package/theme/blocks/cookies/View.tsx +36 -36
- package/theme/blocks/faq/Accordion.tsx +34 -34
- package/theme/blocks/faq/View.tsx +16 -16
- package/theme/blocks/faq/script.ts +56 -56
- package/theme/blocks/hero/View.tsx +44 -44
- package/theme/blocks/links/LinkGroups.tsx +24 -24
- package/theme/blocks/links/View.tsx +17 -17
- package/theme/blocks/list/Rows.tsx +24 -24
- package/theme/blocks/list/View.tsx +14 -14
- package/theme/blocks/notice/View.tsx +13 -13
- package/theme/blocks/points/PointList.tsx +34 -34
- package/theme/blocks/points/View.tsx +14 -14
- package/theme/blocks/posts/View.tsx +26 -26
- package/theme/blocks/pricecards/View.tsx +40 -40
- package/theme/blocks/steps/StepList.tsx +23 -23
- package/theme/blocks/steps/View.tsx +14 -14
- package/theme/blocks/testimonials/List.tsx +46 -46
- package/theme/blocks/testimonials/Slider.tsx +44 -44
- package/theme/blocks/testimonials/Testimonial.tsx +124 -124
- package/theme/blocks/testimonials/View.tsx +21 -21
- package/theme/blocks/testimonials/script.ts +52 -52
- package/theme/blocks/video/View.tsx +41 -41
- package/theme/components/ActionBar.astro +63 -63
- package/theme/components/AppSlot.astro +21 -21
- package/theme/components/ArrowLink.tsx +21 -21
- package/theme/components/BlogIndex.astro +48 -48
- package/theme/components/BlogPosts.astro +18 -18
- package/theme/components/BlogPosts.tsx +45 -45
- package/theme/components/BookButton.tsx +22 -22
- package/theme/components/Container.astro +11 -11
- package/theme/components/CookieConsent.astro +121 -121
- package/theme/components/EndBlock.astro +21 -21
- package/theme/components/FontPreload.astro +6 -6
- package/theme/components/Footer.astro +85 -85
- package/theme/components/Header.astro +186 -186
- package/theme/components/Icon.astro +16 -16
- package/theme/components/Img.astro +29 -29
- package/theme/components/Img.tsx +33 -33
- package/theme/components/Inline.astro +11 -11
- package/theme/components/Logo.astro +23 -23
- package/theme/components/LogoMark.astro +10 -10
- package/theme/components/Markdown.astro +17 -17
- package/theme/components/MobileMenu.tsx +224 -224
- package/theme/components/MobileNav.astro +33 -33
- package/theme/components/PageHeader.astro +22 -22
- package/theme/components/PageSection.astro +24 -24
- package/theme/components/PhoneButton.tsx +11 -11
- package/theme/components/Popup.astro +145 -145
- package/theme/components/PopupButton.tsx +24 -24
- package/theme/components/PopupView.tsx +48 -48
- package/theme/components/PostList.tsx +58 -58
- package/theme/components/PostMeta.tsx +17 -17
- package/theme/components/RichText.astro +28 -28
- package/theme/components/Section.astro +29 -29
- package/theme/components/SectionHeading.tsx +8 -8
- package/theme/components/TextSection.astro +16 -16
- package/theme/components/home/PageBlock.astro +19 -19
- package/theme/components/home/Text.astro +13 -13
- package/theme/components/loadPopup.ts +22 -22
- package/theme/components/loadPosts.ts +16 -16
- package/theme/components/posts.ts +15 -15
- package/theme/editor/looks.mjs +7 -7
- package/theme/editor/previews.tsx +56 -56
- package/theme/layouts/Base.astro +273 -273
- package/theme/layouts/PageLayout.astro +126 -126
- package/theme/layouts/PostLayout.astro +137 -137
- package/theme/scripts/motion.ts +29 -29
- package/theme/styles/base.css +320 -320
- package/theme/styles/blocks.css +1808 -1808
- package/theme/styles/components.css +206 -206
- package/theme/styles/global.css +1302 -1302
- package/theme/styles/ui.css +6544 -6544
- package/theme/templates/Blog.astro +38 -38
- package/theme/templates/Category.astro +19 -19
- package/theme/templates/Home.astro +36 -36
- package/theme/templates/New.astro +68 -68
- package/theme/templates/NotFound.astro +117 -117
- package/theme/theme.json +28 -28
- package/theme/ui/Badge.tsx +45 -45
- package/theme/ui/BottomSheet.tsx +148 -148
- package/theme/ui/Button.tsx +79 -79
- package/theme/ui/ButtonGroup.tsx +31 -31
- package/theme/ui/Card.tsx +45 -45
- package/theme/ui/Carousel.tsx +124 -124
- package/theme/ui/Checkbox.tsx +59 -59
- package/theme/ui/Chip.tsx +101 -101
- package/theme/ui/ChipSet.tsx +27 -27
- package/theme/ui/DatePicker.tsx +149 -149
- package/theme/ui/DatePickerContainer.tsx +316 -316
- package/theme/ui/Dialog.tsx +100 -100
- package/theme/ui/Divider.tsx +27 -27
- package/theme/ui/ExposedDropdownMenu.tsx +77 -77
- package/theme/ui/Fab.tsx +88 -88
- package/theme/ui/FabMenu.tsx +67 -67
- package/theme/ui/IconButton.tsx +78 -78
- package/theme/ui/List.tsx +287 -287
- package/theme/ui/LoadingIndicator.tsx +29 -29
- package/theme/ui/Menu.tsx +278 -278
- package/theme/ui/NavigationBar.tsx +62 -62
- package/theme/ui/NavigationDrawer.tsx +80 -80
- package/theme/ui/NavigationRail.tsx +154 -154
- package/theme/ui/ProgressIndicator.tsx +162 -162
- package/theme/ui/RadioButton.tsx +50 -50
- package/theme/ui/Search.tsx +129 -129
- package/theme/ui/SegmentedButton.tsx +49 -49
- package/theme/ui/SideSheet.tsx +54 -54
- package/theme/ui/Slider.tsx +155 -155
- package/theme/ui/Snackbar.tsx +67 -67
- package/theme/ui/SplitButton.tsx +67 -67
- package/theme/ui/Switch.tsx +60 -60
- package/theme/ui/Tabs.tsx +114 -114
- package/theme/ui/TextField.tsx +278 -278
- package/theme/ui/TimePicker.tsx +128 -128
- package/theme/ui/TimePickerContainer.tsx +234 -234
- package/theme/ui/Toolbar.tsx +67 -67
- package/theme/ui/Tooltip.tsx +58 -58
- package/theme/ui/TopAppBar.tsx +77 -77
- package/theme/ui/bottomSheet.script.ts +8 -8
- package/theme/ui/{bottomSheet.ts → bottomSheetControl.ts} +211 -211
- package/theme/ui/carousel.script.ts +28 -28
- package/theme/ui/carouselLayout.ts +229 -229
- package/theme/ui/checkbox.script.ts +8 -8
- package/theme/ui/datePicker.script.ts +302 -302
- package/theme/ui/dates.ts +185 -185
- package/theme/ui/{dialog.ts → dialogControl.ts} +48 -48
- package/theme/ui/dropdownMenu.script.ts +166 -166
- package/theme/ui/fabMenu.script.ts +57 -57
- package/theme/ui/navigationRail.script.ts +47 -47
- package/theme/ui/ripple.script.ts +159 -159
- package/theme/ui/search.script.ts +92 -92
- package/theme/ui/{sideSheet.ts → sideSheetControl.ts} +48 -48
- package/theme/ui/slider.script.ts +12 -12
- package/theme/ui/sliderUpdate.ts +52 -52
- package/theme/ui/snackbar.script.ts +16 -16
- package/theme/ui/splitButton.script.ts +63 -63
- package/theme/ui/tabs.script.ts +48 -48
- package/theme/ui/textField.script.ts +17 -17
- package/theme/ui/textFieldUpdate.ts +44 -44
- package/theme/ui/timePicker.script.ts +248 -248
- package/theme/ui/toolbar.script.ts +43 -43
- package/theme/ui/tooltip.script.ts +68 -68
- package/worker/consent.js +141 -141
- package/worker/editor-github.js +167 -167
- package/worker/editor-host.js +74 -74
- package/worker/files.js +401 -401
- package/worker/github.js +36 -36
- package/worker/index.js +188 -184
- package/worker/not-found.js +97 -97
- package/worker/oauth.js +95 -95
- package/worker/publish.js +52 -52
- package/worker/push.js +270 -270
- package/worker/r2-status.js +50 -50
- package/worker/session.js +58 -58
- package/worker/store.js +46 -0
package/src/check.ts
CHANGED
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
// The rules a value must keep to before it is saved, from its fields (collections.ts): what must be
|
|
2
|
-
// filled in, the form of a text (a pattern, an address, a number in pairs), the kind of each value
|
|
3
|
-
// and that a choice is one of the options. The editor checks with them before it saves (fields.tsx
|
|
4
|
-
// and the forms), and the site before it is built (packages/editor/scripts/check-content.mjs), so what the editor
|
|
5
|
-
// would not save does not reach the site either. Also how a value is saved (clean) and a new one
|
|
6
|
-
// (newValue). No React here, so the build can use it.
|
|
7
|
-
import type { Field, Template } from './schema';
|
|
8
|
-
import { addressOf } from './addresses';
|
|
9
|
-
import { codeAt, regionOf } from './countries';
|
|
10
|
-
import { blocksIn } from '../lib/blocks.mjs';
|
|
11
|
-
import { t } from './i18n';
|
|
12
|
-
|
|
13
|
-
type Item = Record<string, unknown>;
|
|
14
|
-
|
|
15
|
-
/** The fields and value an object's fields are in: an item of a list is in the object with the list
|
|
16
|
-
* (a block's cards in the block), which may be in another. */
|
|
17
|
-
export type Around = { fields: Field[]; value: Record<string, unknown> | undefined; around?: Around };
|
|
18
|
-
|
|
19
|
-
/** Whether a field is in use: always, or (Field.only) while the field it depends on has one of the
|
|
20
|
-
* values (its default when it has none yet) and is in use itself (a pop-up's delay, by when it opens,
|
|
21
|
-
* which a kind without it does not have). That field is next to it or, for a field of an item in a
|
|
22
|
-
* list, next to the list (`around`): a card's icon by the block's design. */
|
|
23
|
-
export function inUse(field: Field, fields: Field[], value: Record<string, unknown> | undefined, around?: Around): boolean {
|
|
24
|
-
if (!field.only) return true;
|
|
25
|
-
const name = field.only.field;
|
|
26
|
-
let at: Around = { fields, value, around };
|
|
27
|
-
while (at.around && !at.fields.some((f) => f.name === name)) at = at.around;
|
|
28
|
-
const by = at.fields.find((f) => f.name === name);
|
|
29
|
-
return (
|
|
30
|
-
field.only.is.includes(String(at.value?.[name] ?? by?.default ?? '')) &&
|
|
31
|
-
(!by || by === field || inUse(by, at.fields, at.value, at.around))
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// The block an item of a list of blocks is (undefined for a type the editor does not know).
|
|
36
|
-
const blockOf = (field: Field, item: unknown) => field.blocks!.find((b) => b.type === (item as Item)?.type);
|
|
37
|
-
|
|
38
|
-
// The kind of value a field holds, and what is said when it holds another (a file edited by hand).
|
|
39
|
-
const kindOf = (f: Field) =>
|
|
40
|
-
f.type === 'list' || f.type === 'blocks' || f.multiple
|
|
41
|
-
? 'list'
|
|
42
|
-
: f.type === 'object'
|
|
43
|
-
? 'object'
|
|
44
|
-
: f.type === 'number'
|
|
45
|
-
? 'number'
|
|
46
|
-
: f.type === 'boolean'
|
|
47
|
-
? 'boolean'
|
|
48
|
-
: 'text';
|
|
49
|
-
function ofItsKind(f: Field, v: unknown): boolean {
|
|
50
|
-
// Only shown, never edited here: whatever the file has.
|
|
51
|
-
if (f.type === 'readonly') return true;
|
|
52
|
-
switch (kindOf(f)) {
|
|
53
|
-
case 'list':
|
|
54
|
-
return Array.isArray(v);
|
|
55
|
-
case 'object':
|
|
56
|
-
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
57
|
-
case 'number':
|
|
58
|
-
return typeof v === 'number' && Number.isFinite(v);
|
|
59
|
-
case 'boolean':
|
|
60
|
-
return typeof v === 'boolean';
|
|
61
|
-
default:
|
|
62
|
-
return typeof v === 'string';
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// A new, empty item of a list.
|
|
67
|
-
export const newItem = (field: Field) => (field.field ? '' : newValue(field.fields!));
|
|
68
|
-
|
|
69
|
-
/** A saved value with the defaults of the fields it has no value for (a field added after it was
|
|
70
|
-
* saved, the pop-up's "Rullet ned"), after its own values. */
|
|
71
|
-
export const withDefaults = (fields: Field[], value: Record<string, unknown>): Record<string, unknown> => ({
|
|
72
|
-
...value,
|
|
73
|
-
...Object.fromEntries(fields.filter((f) => f.default !== undefined && value?.[f.name] === undefined).map((f) => [f.name, f.default])),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
/** A saved entry in a form: its own values, and for a field it has none for, what that means (Field.missing), else
|
|
77
|
-
* its value in a new entry (newValue). */
|
|
78
|
-
export const savedValue = (fields: Field[], value: Record<string, unknown>): Record<string, unknown> => ({
|
|
79
|
-
...newValue(fields),
|
|
80
|
-
...Object.fromEntries(fields.filter((f) => f.missing !== undefined).map((f) => [f.name, f.missing])),
|
|
81
|
-
...value,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// An empty entry for a list of objects or a new file: the fields' defaults, lists empty.
|
|
85
|
-
export function newValue(fields: Field[]): Record<string, unknown> {
|
|
86
|
-
return Object.fromEntries(
|
|
87
|
-
fields.map((f) => [
|
|
88
|
-
f.name,
|
|
89
|
-
f.default ??
|
|
90
|
-
(f.type === 'list' || f.type === 'blocks' || f.multiple
|
|
91
|
-
? []
|
|
92
|
-
: f.type === 'object'
|
|
93
|
-
? newValue(f.fields!)
|
|
94
|
-
: f.type === 'boolean'
|
|
95
|
-
? false
|
|
96
|
-
: ''),
|
|
97
|
-
]),
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// A number typed with or without spaces (or dots, dashes, the site's own country code before a phone
|
|
102
|
-
// number) as its digits in pairs, "30 64 16 12"; a text that is not only digits as it was typed.
|
|
103
|
-
// abroad: a phone number with another country code, which is not counted or taken off.
|
|
104
|
-
function digitsOf(field: Field, text: string, abroad = false) {
|
|
105
|
-
let digits = text.replace(/[\s.\-()]/g, '').replace(/^\+/, '');
|
|
106
|
-
const own = !abroad && field.phone?.slice(1);
|
|
107
|
-
if (own && typeof field.digitsInPairs === 'number' && digits.length === field.digitsInPairs + own.length && digits.startsWith(own))
|
|
108
|
-
digits = digits.slice(own.length);
|
|
109
|
-
return digits;
|
|
110
|
-
}
|
|
111
|
-
function pairsOf(field: Field, text: string) {
|
|
112
|
-
const digits = digitsOf(field, text);
|
|
113
|
-
return /^\d+$/.test(digits) ? digits.match(/\d{1,2}/g)!.join(' ') : text;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** A phone number's country code and the number after it (Field.phone): one without a code, or with
|
|
117
|
-
* the site's own, is in the site's own country; one typed with another (+46 or 0046, with a space
|
|
118
|
-
* after it or not) has it taken off. */
|
|
119
|
-
export function phoneParts(field: Field, text: string): [code: string, number: string] {
|
|
120
|
-
const typed = text.trim();
|
|
121
|
-
const m = typed.match(/^(?:\+|00)(\d*)(.*)$/s);
|
|
122
|
-
if (!m) return [field.phone!, typed];
|
|
123
|
-
// Written "+46 70 12 34 56", the code is the digits before the space; together, the longest that is one.
|
|
124
|
-
const whole = regionOf(`+${m[1]}`) ? `+${m[1]}` : undefined;
|
|
125
|
-
const code = (m[2] && whole) || codeAt(m[1]) || whole;
|
|
126
|
-
if (!code) return [field.phone!, typed];
|
|
127
|
-
return [code, `${m[1].slice(code.length - 1)}${m[2]}`.trim()];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** A phone number as it is saved: with the site's own country code, the number alone; with another,
|
|
131
|
-
* the code before it. A number typed with a code keeps that one. */
|
|
132
|
-
export function phoneText(field: Field, code: string, number: string) {
|
|
133
|
-
const typed = number.trim();
|
|
134
|
-
return !typed || /^(\+|00)/.test(typed) || code === field.phone ? number : `${code} ${number}`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// A phone number in another country is written as it was typed (its groups differ from country to
|
|
138
|
-
// country), only with one space between them.
|
|
139
|
-
export function inPairs(field: Field, text: string) {
|
|
140
|
-
if (!field.phone) return pairsOf(field, text);
|
|
141
|
-
const [code, number] = phoneParts(field, text);
|
|
142
|
-
return phoneText(field, code, code === field.phone ? pairsOf(field, number) : number.replace(/\s+/g, ' '));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// What is wrong with a text that must have a form (a pattern, a number in pairs), or null.
|
|
146
|
-
export function problemOf(field: Field, typed: string): string | null {
|
|
147
|
-
const text = typed.trim();
|
|
148
|
-
if (!text) return null;
|
|
149
|
-
if (field.pattern && !new RegExp(field.pattern[0]).test(text)) return field.pattern[1];
|
|
150
|
-
if (field.address && !addressOf(text, field.address)) return t.check.address;
|
|
151
|
-
if (field.digitsInPairs) {
|
|
152
|
-
// A phone number in another country: only digits, however many it has there.
|
|
153
|
-
const [code, number] = field.phone ? phoneParts(field, text) : [field.phone, text];
|
|
154
|
-
const abroad = code !== field.phone;
|
|
155
|
-
const digits = digitsOf(field, number, abroad);
|
|
156
|
-
if (!/^\d+$/.test(digits)) return t.check.onlyDigits;
|
|
157
|
-
if (typeof field.digitsInPairs === 'number' && !abroad && digits.length !== field.digitsInPairs)
|
|
158
|
-
return t.check.digits(field.digitsInPairs);
|
|
159
|
-
}
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** The first problem with a value: the message for the person editing, and the field it is about, as
|
|
164
|
-
* the names and item numbers to it joined with dots ("items.0.title"), so it can be shown there. */
|
|
165
|
-
export type Problem = { text: string; path: string };
|
|
166
|
-
|
|
167
|
-
// The first problem with a value, as a message for the person editing, or null.
|
|
168
|
-
export function check(fields: Field[], value: Record<string, unknown>, where = '', around?: Around): string | null {
|
|
169
|
-
return problemAt(fields, value, where, around)?.text ?? null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** As check, with the field the problem is about (Problem). */
|
|
173
|
-
export function problemAt(fields: Field[], value: Record<string, unknown>, where = '', around?: Around, at = ''): Problem | null {
|
|
174
|
-
const here: Around = { fields, value, around };
|
|
175
|
-
for (const f of fields) {
|
|
176
|
-
if (!inUse(f, fields, value, around)) continue;
|
|
177
|
-
const v = value?.[f.name];
|
|
178
|
-
const label = where ? `${where} › ${f.label}` : f.label;
|
|
179
|
-
const path = at ? `${at}.${f.name}` : f.name;
|
|
180
|
-
const empty = v == null || v === '' || (Array.isArray(v) && v.length === 0);
|
|
181
|
-
const wrong = (text: string) => ({ text, path });
|
|
182
|
-
if (f.required && empty) return wrong(t.check.fillIn(label));
|
|
183
|
-
if (!empty && !ofItsKind(f, v)) return wrong(t.check.wrong(label, t.check.kinds[kindOf(f)]));
|
|
184
|
-
if (f.type === 'select' && f.options && !empty && !f.options.some((o) => o.value === v))
|
|
185
|
-
return wrong(t.check.notAnOption(label, String(v)));
|
|
186
|
-
const form = typeof v === 'string' && problemOf(f, v);
|
|
187
|
-
if (form) return wrong(t.check.wrong(label, form));
|
|
188
|
-
if (f.type === 'markdown' && f.template && typeof v === 'string') {
|
|
189
|
-
const [rule] = textProblems(f.template, v);
|
|
190
|
-
if (rule) return wrong(t.check.inField(label, rule));
|
|
191
|
-
}
|
|
192
|
-
if (f.type === 'object') {
|
|
193
|
-
const problem = problemAt(f.fields!, v as Record<string, unknown>, label, here, path);
|
|
194
|
-
if (problem) return problem;
|
|
195
|
-
}
|
|
196
|
-
if (f.type === 'blocks' && Array.isArray(v) && f.template) {
|
|
197
|
-
const name = (type: string) => f.blocks!.find((b) => b.type === type)?.label ?? type;
|
|
198
|
-
const [rule] = templateProblems(f.template, v as Item[], name);
|
|
199
|
-
if (rule) return wrong(t.check.inField(label, rule));
|
|
200
|
-
}
|
|
201
|
-
if (f.type === 'blocks' && Array.isArray(v)) {
|
|
202
|
-
// Hidden blocks are not on the site, so they may be unfinished.
|
|
203
|
-
for (const [i, item] of (v as Item[]).entries()) {
|
|
204
|
-
const block = blockOf(f, item);
|
|
205
|
-
if (item.hidden || !block) continue;
|
|
206
|
-
const problem = problemAt(block.fields, item, `${label} › ${block.label}`, undefined, `${path}.${i}`);
|
|
207
|
-
if (problem) return problem;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (f.type === 'list' && Array.isArray(v)) {
|
|
211
|
-
if (f.maxItems !== undefined && v.length > f.maxItems) return wrong(t.check.atMostItems(label, f.maxItems));
|
|
212
|
-
for (const [i, item] of v.entries()) {
|
|
213
|
-
const named = `${label} ${i + 1}`;
|
|
214
|
-
const problem = f.field
|
|
215
|
-
? problemAt([f.field], { [f.field.name]: item }, named, here, `${path}.${i}`)
|
|
216
|
-
: problemAt(f.fields!, item as Record<string, unknown>, named, here, `${path}.${i}`);
|
|
217
|
-
if (problem) return problem;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// The value as it is saved: optional fields left empty (text, number, list) are left out, as in the
|
|
225
|
-
// existing files (their readers treat a missing field as empty). Keys keep their order.
|
|
226
|
-
export function clean(fields: Field[], value: Record<string, unknown>, around?: Around): Record<string, unknown> {
|
|
227
|
-
const out: Record<string, unknown> = {};
|
|
228
|
-
const here: Around = { fields, value, around };
|
|
229
|
-
for (const [key, v] of Object.entries(value)) {
|
|
230
|
-
const f = fields.find((x) => x.name === key);
|
|
231
|
-
const empty = v == null || v === '' || (Array.isArray(v) && v.length === 0);
|
|
232
|
-
if (f && ((!f.required && empty) || !inUse(f, fields, value, around))) continue;
|
|
233
|
-
if (f?.digitsInPairs && typeof v === 'string') out[key] = inPairs(f, v);
|
|
234
|
-
else if (f?.pattern && typeof v === 'string') out[key] = v.trim();
|
|
235
|
-
else if (f?.type === 'object') out[key] = clean(f.fields!, v as Record<string, unknown>, here);
|
|
236
|
-
else if (f?.type === 'list' && f.fields) out[key] = (v as Record<string, unknown>[]).map((item) => clean(f.fields!, item, here));
|
|
237
|
-
// A block keeps its type first and `hidden` last, and only when it is hidden.
|
|
238
|
-
else if (f?.type === 'blocks')
|
|
239
|
-
out[key] = (v as Item[]).map(({ type, hidden, ...item }) => ({
|
|
240
|
-
type,
|
|
241
|
-
...clean(blockOf(f, { type })?.fields ?? [], item),
|
|
242
|
-
...(hidden ? { hidden: true } : {}),
|
|
243
|
-
}));
|
|
244
|
-
else out[key] = v;
|
|
245
|
-
}
|
|
246
|
-
return out;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// A picture in Markdown (MarkdownEditor.tsx puts them in).
|
|
250
|
-
const PICTURES = /!\[(?:\\.|[^\]])*\]\([^)\s]+/g;
|
|
251
|
-
|
|
252
|
-
// A block's name by its type: a standard block's (the editor's texts), a picture's, else its type.
|
|
253
|
-
const nameOf = (type: string) =>
|
|
254
|
-
type === 'image' ? t.blocks.picture.label : ((t.blocks as unknown as Record<string, { label?: string }>)[type]?.label ?? type);
|
|
255
|
-
|
|
256
|
-
/** What is wrong with the blocks and pictures in the text of a markdown field by its template (Field.template): one
|
|
257
|
-
* that cannot be there, and too many of a type ('image': the pictures). */
|
|
258
|
-
export function textProblems(template: Template, markdown: string): string[] {
|
|
259
|
-
const types = [...blocksIn(markdown).map((b) => b.type), ...[...markdown.matchAll(PICTURES)].map(() => 'image')];
|
|
260
|
-
const problems: string[] = [];
|
|
261
|
-
for (const type of new Set(types)) {
|
|
262
|
-
if (template.blocks && !template.blocks.includes(type)) problems.push(t.check.cannotBeInText(nameOf(type)));
|
|
263
|
-
const max = template.max?.[type];
|
|
264
|
-
if (max !== undefined && types.filter((x) => x === type).length > max) problems.push(t.check.atMostInText(nameOf(type), max));
|
|
265
|
-
}
|
|
266
|
-
return problems;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/** What is wrong with the blocks of a text by its template (collections.ts): one that cannot be there,
|
|
270
|
-
* too many of a type, and one that must be on the page (required, locked) but is not, or is hidden.
|
|
271
|
-
* blocks: all of them in order, hidden ones too ({ type, hidden }); name: a type's name. */
|
|
272
|
-
export function templateProblems(template: Template, blocks: Item[], name: (type: string) => string): string[] {
|
|
273
|
-
const problems: string[] = [];
|
|
274
|
-
const count = (type: string, shown = false) => blocks.filter((b) => b.type === type && !(shown && b.hidden)).length;
|
|
275
|
-
for (const type of new Set(blocks.map((b) => String(b.type)))) {
|
|
276
|
-
if (template.blocks && !template.blocks.includes(type)) problems.push(t.check.cannotBeHere(name(type)));
|
|
277
|
-
const max = template.max?.[type];
|
|
278
|
-
if (max !== undefined && count(type) > max) problems.push(t.check.atMost(name(type), max));
|
|
279
|
-
}
|
|
280
|
-
for (const type of [...(template.required ?? []), ...(template.locked ?? [])]) {
|
|
281
|
-
if (!count(type)) problems.push(t.check.mustBeOnPage(name(type)));
|
|
282
|
-
else if (!count(type, true)) problems.push(t.check.mustBeShown(name(type)));
|
|
283
|
-
}
|
|
284
|
-
return [...new Set(problems)];
|
|
285
|
-
}
|
|
1
|
+
// The rules a value must keep to before it is saved, from its fields (collections.ts): what must be
|
|
2
|
+
// filled in, the form of a text (a pattern, an address, a number in pairs), the kind of each value
|
|
3
|
+
// and that a choice is one of the options. The editor checks with them before it saves (fields.tsx
|
|
4
|
+
// and the forms), and the site before it is built (packages/editor/scripts/check-content.mjs), so what the editor
|
|
5
|
+
// would not save does not reach the site either. Also how a value is saved (clean) and a new one
|
|
6
|
+
// (newValue). No React here, so the build can use it.
|
|
7
|
+
import type { Field, Template } from './schema';
|
|
8
|
+
import { addressOf } from './addresses';
|
|
9
|
+
import { codeAt, regionOf } from './countries';
|
|
10
|
+
import { blocksIn } from '../lib/blocks.mjs';
|
|
11
|
+
import { t } from './i18n';
|
|
12
|
+
|
|
13
|
+
type Item = Record<string, unknown>;
|
|
14
|
+
|
|
15
|
+
/** The fields and value an object's fields are in: an item of a list is in the object with the list
|
|
16
|
+
* (a block's cards in the block), which may be in another. */
|
|
17
|
+
export type Around = { fields: Field[]; value: Record<string, unknown> | undefined; around?: Around };
|
|
18
|
+
|
|
19
|
+
/** Whether a field is in use: always, or (Field.only) while the field it depends on has one of the
|
|
20
|
+
* values (its default when it has none yet) and is in use itself (a pop-up's delay, by when it opens,
|
|
21
|
+
* which a kind without it does not have). That field is next to it or, for a field of an item in a
|
|
22
|
+
* list, next to the list (`around`): a card's icon by the block's design. */
|
|
23
|
+
export function inUse(field: Field, fields: Field[], value: Record<string, unknown> | undefined, around?: Around): boolean {
|
|
24
|
+
if (!field.only) return true;
|
|
25
|
+
const name = field.only.field;
|
|
26
|
+
let at: Around = { fields, value, around };
|
|
27
|
+
while (at.around && !at.fields.some((f) => f.name === name)) at = at.around;
|
|
28
|
+
const by = at.fields.find((f) => f.name === name);
|
|
29
|
+
return (
|
|
30
|
+
field.only.is.includes(String(at.value?.[name] ?? by?.default ?? '')) &&
|
|
31
|
+
(!by || by === field || inUse(by, at.fields, at.value, at.around))
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The block an item of a list of blocks is (undefined for a type the editor does not know).
|
|
36
|
+
const blockOf = (field: Field, item: unknown) => field.blocks!.find((b) => b.type === (item as Item)?.type);
|
|
37
|
+
|
|
38
|
+
// The kind of value a field holds, and what is said when it holds another (a file edited by hand).
|
|
39
|
+
const kindOf = (f: Field) =>
|
|
40
|
+
f.type === 'list' || f.type === 'blocks' || f.multiple
|
|
41
|
+
? 'list'
|
|
42
|
+
: f.type === 'object'
|
|
43
|
+
? 'object'
|
|
44
|
+
: f.type === 'number'
|
|
45
|
+
? 'number'
|
|
46
|
+
: f.type === 'boolean'
|
|
47
|
+
? 'boolean'
|
|
48
|
+
: 'text';
|
|
49
|
+
function ofItsKind(f: Field, v: unknown): boolean {
|
|
50
|
+
// Only shown, never edited here: whatever the file has.
|
|
51
|
+
if (f.type === 'readonly') return true;
|
|
52
|
+
switch (kindOf(f)) {
|
|
53
|
+
case 'list':
|
|
54
|
+
return Array.isArray(v);
|
|
55
|
+
case 'object':
|
|
56
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
57
|
+
case 'number':
|
|
58
|
+
return typeof v === 'number' && Number.isFinite(v);
|
|
59
|
+
case 'boolean':
|
|
60
|
+
return typeof v === 'boolean';
|
|
61
|
+
default:
|
|
62
|
+
return typeof v === 'string';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A new, empty item of a list.
|
|
67
|
+
export const newItem = (field: Field) => (field.field ? '' : newValue(field.fields!));
|
|
68
|
+
|
|
69
|
+
/** A saved value with the defaults of the fields it has no value for (a field added after it was
|
|
70
|
+
* saved, the pop-up's "Rullet ned"), after its own values. */
|
|
71
|
+
export const withDefaults = (fields: Field[], value: Record<string, unknown>): Record<string, unknown> => ({
|
|
72
|
+
...value,
|
|
73
|
+
...Object.fromEntries(fields.filter((f) => f.default !== undefined && value?.[f.name] === undefined).map((f) => [f.name, f.default])),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/** A saved entry in a form: its own values, and for a field it has none for, what that means (Field.missing), else
|
|
77
|
+
* its value in a new entry (newValue). */
|
|
78
|
+
export const savedValue = (fields: Field[], value: Record<string, unknown>): Record<string, unknown> => ({
|
|
79
|
+
...newValue(fields),
|
|
80
|
+
...Object.fromEntries(fields.filter((f) => f.missing !== undefined).map((f) => [f.name, f.missing])),
|
|
81
|
+
...value,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// An empty entry for a list of objects or a new file: the fields' defaults, lists empty.
|
|
85
|
+
export function newValue(fields: Field[]): Record<string, unknown> {
|
|
86
|
+
return Object.fromEntries(
|
|
87
|
+
fields.map((f) => [
|
|
88
|
+
f.name,
|
|
89
|
+
f.default ??
|
|
90
|
+
(f.type === 'list' || f.type === 'blocks' || f.multiple
|
|
91
|
+
? []
|
|
92
|
+
: f.type === 'object'
|
|
93
|
+
? newValue(f.fields!)
|
|
94
|
+
: f.type === 'boolean'
|
|
95
|
+
? false
|
|
96
|
+
: ''),
|
|
97
|
+
]),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// A number typed with or without spaces (or dots, dashes, the site's own country code before a phone
|
|
102
|
+
// number) as its digits in pairs, "30 64 16 12"; a text that is not only digits as it was typed.
|
|
103
|
+
// abroad: a phone number with another country code, which is not counted or taken off.
|
|
104
|
+
function digitsOf(field: Field, text: string, abroad = false) {
|
|
105
|
+
let digits = text.replace(/[\s.\-()]/g, '').replace(/^\+/, '');
|
|
106
|
+
const own = !abroad && field.phone?.slice(1);
|
|
107
|
+
if (own && typeof field.digitsInPairs === 'number' && digits.length === field.digitsInPairs + own.length && digits.startsWith(own))
|
|
108
|
+
digits = digits.slice(own.length);
|
|
109
|
+
return digits;
|
|
110
|
+
}
|
|
111
|
+
function pairsOf(field: Field, text: string) {
|
|
112
|
+
const digits = digitsOf(field, text);
|
|
113
|
+
return /^\d+$/.test(digits) ? digits.match(/\d{1,2}/g)!.join(' ') : text;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A phone number's country code and the number after it (Field.phone): one without a code, or with
|
|
117
|
+
* the site's own, is in the site's own country; one typed with another (+46 or 0046, with a space
|
|
118
|
+
* after it or not) has it taken off. */
|
|
119
|
+
export function phoneParts(field: Field, text: string): [code: string, number: string] {
|
|
120
|
+
const typed = text.trim();
|
|
121
|
+
const m = typed.match(/^(?:\+|00)(\d*)(.*)$/s);
|
|
122
|
+
if (!m) return [field.phone!, typed];
|
|
123
|
+
// Written "+46 70 12 34 56", the code is the digits before the space; together, the longest that is one.
|
|
124
|
+
const whole = regionOf(`+${m[1]}`) ? `+${m[1]}` : undefined;
|
|
125
|
+
const code = (m[2] && whole) || codeAt(m[1]) || whole;
|
|
126
|
+
if (!code) return [field.phone!, typed];
|
|
127
|
+
return [code, `${m[1].slice(code.length - 1)}${m[2]}`.trim()];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A phone number as it is saved: with the site's own country code, the number alone; with another,
|
|
131
|
+
* the code before it. A number typed with a code keeps that one. */
|
|
132
|
+
export function phoneText(field: Field, code: string, number: string) {
|
|
133
|
+
const typed = number.trim();
|
|
134
|
+
return !typed || /^(\+|00)/.test(typed) || code === field.phone ? number : `${code} ${number}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// A phone number in another country is written as it was typed (its groups differ from country to
|
|
138
|
+
// country), only with one space between them.
|
|
139
|
+
export function inPairs(field: Field, text: string) {
|
|
140
|
+
if (!field.phone) return pairsOf(field, text);
|
|
141
|
+
const [code, number] = phoneParts(field, text);
|
|
142
|
+
return phoneText(field, code, code === field.phone ? pairsOf(field, number) : number.replace(/\s+/g, ' '));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// What is wrong with a text that must have a form (a pattern, a number in pairs), or null.
|
|
146
|
+
export function problemOf(field: Field, typed: string): string | null {
|
|
147
|
+
const text = typed.trim();
|
|
148
|
+
if (!text) return null;
|
|
149
|
+
if (field.pattern && !new RegExp(field.pattern[0]).test(text)) return field.pattern[1];
|
|
150
|
+
if (field.address && !addressOf(text, field.address)) return t.check.address;
|
|
151
|
+
if (field.digitsInPairs) {
|
|
152
|
+
// A phone number in another country: only digits, however many it has there.
|
|
153
|
+
const [code, number] = field.phone ? phoneParts(field, text) : [field.phone, text];
|
|
154
|
+
const abroad = code !== field.phone;
|
|
155
|
+
const digits = digitsOf(field, number, abroad);
|
|
156
|
+
if (!/^\d+$/.test(digits)) return t.check.onlyDigits;
|
|
157
|
+
if (typeof field.digitsInPairs === 'number' && !abroad && digits.length !== field.digitsInPairs)
|
|
158
|
+
return t.check.digits(field.digitsInPairs);
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The first problem with a value: the message for the person editing, and the field it is about, as
|
|
164
|
+
* the names and item numbers to it joined with dots ("items.0.title"), so it can be shown there. */
|
|
165
|
+
export type Problem = { text: string; path: string };
|
|
166
|
+
|
|
167
|
+
// The first problem with a value, as a message for the person editing, or null.
|
|
168
|
+
export function check(fields: Field[], value: Record<string, unknown>, where = '', around?: Around): string | null {
|
|
169
|
+
return problemAt(fields, value, where, around)?.text ?? null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** As check, with the field the problem is about (Problem). */
|
|
173
|
+
export function problemAt(fields: Field[], value: Record<string, unknown>, where = '', around?: Around, at = ''): Problem | null {
|
|
174
|
+
const here: Around = { fields, value, around };
|
|
175
|
+
for (const f of fields) {
|
|
176
|
+
if (!inUse(f, fields, value, around)) continue;
|
|
177
|
+
const v = value?.[f.name];
|
|
178
|
+
const label = where ? `${where} › ${f.label}` : f.label;
|
|
179
|
+
const path = at ? `${at}.${f.name}` : f.name;
|
|
180
|
+
const empty = v == null || v === '' || (Array.isArray(v) && v.length === 0);
|
|
181
|
+
const wrong = (text: string) => ({ text, path });
|
|
182
|
+
if (f.required && empty) return wrong(t.check.fillIn(label));
|
|
183
|
+
if (!empty && !ofItsKind(f, v)) return wrong(t.check.wrong(label, t.check.kinds[kindOf(f)]));
|
|
184
|
+
if (f.type === 'select' && f.options && !empty && !f.options.some((o) => o.value === v))
|
|
185
|
+
return wrong(t.check.notAnOption(label, String(v)));
|
|
186
|
+
const form = typeof v === 'string' && problemOf(f, v);
|
|
187
|
+
if (form) return wrong(t.check.wrong(label, form));
|
|
188
|
+
if (f.type === 'markdown' && f.template && typeof v === 'string') {
|
|
189
|
+
const [rule] = textProblems(f.template, v);
|
|
190
|
+
if (rule) return wrong(t.check.inField(label, rule));
|
|
191
|
+
}
|
|
192
|
+
if (f.type === 'object') {
|
|
193
|
+
const problem = problemAt(f.fields!, v as Record<string, unknown>, label, here, path);
|
|
194
|
+
if (problem) return problem;
|
|
195
|
+
}
|
|
196
|
+
if (f.type === 'blocks' && Array.isArray(v) && f.template) {
|
|
197
|
+
const name = (type: string) => f.blocks!.find((b) => b.type === type)?.label ?? type;
|
|
198
|
+
const [rule] = templateProblems(f.template, v as Item[], name);
|
|
199
|
+
if (rule) return wrong(t.check.inField(label, rule));
|
|
200
|
+
}
|
|
201
|
+
if (f.type === 'blocks' && Array.isArray(v)) {
|
|
202
|
+
// Hidden blocks are not on the site, so they may be unfinished.
|
|
203
|
+
for (const [i, item] of (v as Item[]).entries()) {
|
|
204
|
+
const block = blockOf(f, item);
|
|
205
|
+
if (item.hidden || !block) continue;
|
|
206
|
+
const problem = problemAt(block.fields, item, `${label} › ${block.label}`, undefined, `${path}.${i}`);
|
|
207
|
+
if (problem) return problem;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (f.type === 'list' && Array.isArray(v)) {
|
|
211
|
+
if (f.maxItems !== undefined && v.length > f.maxItems) return wrong(t.check.atMostItems(label, f.maxItems));
|
|
212
|
+
for (const [i, item] of v.entries()) {
|
|
213
|
+
const named = `${label} ${i + 1}`;
|
|
214
|
+
const problem = f.field
|
|
215
|
+
? problemAt([f.field], { [f.field.name]: item }, named, here, `${path}.${i}`)
|
|
216
|
+
: problemAt(f.fields!, item as Record<string, unknown>, named, here, `${path}.${i}`);
|
|
217
|
+
if (problem) return problem;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// The value as it is saved: optional fields left empty (text, number, list) are left out, as in the
|
|
225
|
+
// existing files (their readers treat a missing field as empty). Keys keep their order.
|
|
226
|
+
export function clean(fields: Field[], value: Record<string, unknown>, around?: Around): Record<string, unknown> {
|
|
227
|
+
const out: Record<string, unknown> = {};
|
|
228
|
+
const here: Around = { fields, value, around };
|
|
229
|
+
for (const [key, v] of Object.entries(value)) {
|
|
230
|
+
const f = fields.find((x) => x.name === key);
|
|
231
|
+
const empty = v == null || v === '' || (Array.isArray(v) && v.length === 0);
|
|
232
|
+
if (f && ((!f.required && empty) || !inUse(f, fields, value, around))) continue;
|
|
233
|
+
if (f?.digitsInPairs && typeof v === 'string') out[key] = inPairs(f, v);
|
|
234
|
+
else if (f?.pattern && typeof v === 'string') out[key] = v.trim();
|
|
235
|
+
else if (f?.type === 'object') out[key] = clean(f.fields!, v as Record<string, unknown>, here);
|
|
236
|
+
else if (f?.type === 'list' && f.fields) out[key] = (v as Record<string, unknown>[]).map((item) => clean(f.fields!, item, here));
|
|
237
|
+
// A block keeps its type first and `hidden` last, and only when it is hidden.
|
|
238
|
+
else if (f?.type === 'blocks')
|
|
239
|
+
out[key] = (v as Item[]).map(({ type, hidden, ...item }) => ({
|
|
240
|
+
type,
|
|
241
|
+
...clean(blockOf(f, { type })?.fields ?? [], item),
|
|
242
|
+
...(hidden ? { hidden: true } : {}),
|
|
243
|
+
}));
|
|
244
|
+
else out[key] = v;
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// A picture in Markdown (MarkdownEditor.tsx puts them in).
|
|
250
|
+
const PICTURES = /!\[(?:\\.|[^\]])*\]\([^)\s]+/g;
|
|
251
|
+
|
|
252
|
+
// A block's name by its type: a standard block's (the editor's texts), a picture's, else its type.
|
|
253
|
+
const nameOf = (type: string) =>
|
|
254
|
+
type === 'image' ? t.blocks.picture.label : ((t.blocks as unknown as Record<string, { label?: string }>)[type]?.label ?? type);
|
|
255
|
+
|
|
256
|
+
/** What is wrong with the blocks and pictures in the text of a markdown field by its template (Field.template): one
|
|
257
|
+
* that cannot be there, and too many of a type ('image': the pictures). */
|
|
258
|
+
export function textProblems(template: Template, markdown: string): string[] {
|
|
259
|
+
const types = [...blocksIn(markdown).map((b) => b.type), ...[...markdown.matchAll(PICTURES)].map(() => 'image')];
|
|
260
|
+
const problems: string[] = [];
|
|
261
|
+
for (const type of new Set(types)) {
|
|
262
|
+
if (template.blocks && !template.blocks.includes(type)) problems.push(t.check.cannotBeInText(nameOf(type)));
|
|
263
|
+
const max = template.max?.[type];
|
|
264
|
+
if (max !== undefined && types.filter((x) => x === type).length > max) problems.push(t.check.atMostInText(nameOf(type), max));
|
|
265
|
+
}
|
|
266
|
+
return problems;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** What is wrong with the blocks of a text by its template (collections.ts): one that cannot be there,
|
|
270
|
+
* too many of a type, and one that must be on the page (required, locked) but is not, or is hidden.
|
|
271
|
+
* blocks: all of them in order, hidden ones too ({ type, hidden }); name: a type's name. */
|
|
272
|
+
export function templateProblems(template: Template, blocks: Item[], name: (type: string) => string): string[] {
|
|
273
|
+
const problems: string[] = [];
|
|
274
|
+
const count = (type: string, shown = false) => blocks.filter((b) => b.type === type && !(shown && b.hidden)).length;
|
|
275
|
+
for (const type of new Set(blocks.map((b) => String(b.type)))) {
|
|
276
|
+
if (template.blocks && !template.blocks.includes(type)) problems.push(t.check.cannotBeHere(name(type)));
|
|
277
|
+
const max = template.max?.[type];
|
|
278
|
+
if (max !== undefined && count(type) > max) problems.push(t.check.atMost(name(type), max));
|
|
279
|
+
}
|
|
280
|
+
for (const type of [...(template.required ?? []), ...(template.locked ?? [])]) {
|
|
281
|
+
if (!count(type)) problems.push(t.check.mustBeOnPage(name(type)));
|
|
282
|
+
else if (!count(type, true)) problems.push(t.check.mustBeShown(name(type)));
|
|
283
|
+
}
|
|
284
|
+
return [...new Set(problems)];
|
|
285
|
+
}
|