@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/site/autolinks.mjs
CHANGED
|
@@ -1,200 +1,200 @@
|
|
|
1
|
-
// Automatic internal links. The words come from the pages and posts themselves: their keywords
|
|
2
|
-
// (`keyword`, the primary one, and `keywords`, more separated by commas; under
|
|
3
|
-
// "Søgemaskiner" in the editor; the front page's link to / and the blog page's to /blog/, where they are shown, and the
|
|
4
|
-
// 404 page's to nowhere). src/data/internal-links.json, under "Søgemaskineoptimering" ›
|
|
5
|
-
// "Interne links" in the editor (packages/editor/defaults/internalLinks.ts): `enabled` turns the links on or off, `maxPerPage` is the
|
|
6
|
-
// most per page. A word links to the page it is a
|
|
7
|
-
// keyword of. Unlike linking every occurrence of a word, a target is
|
|
8
|
-
// linked once per page, at most `maxPerPage` links per page, never to the page itself, never inside
|
|
9
|
-
// headings or existing links, and never as part of a word joined with a hyphen (not "web" in "web-design"). Used by the Markdown pipeline (packages/editor/astro.mjs) and by an import
|
|
10
|
-
// script, which removes the old plugin links so they are not doubled. Each link has data-link="auto", so how many there
|
|
11
|
-
// are of each word can be counted after the build (packages/editor/scripts/link-counts.mjs, /editor/links.json).
|
|
12
|
-
import { createHash } from 'node:crypto';
|
|
13
|
-
import fs from 'node:fs';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
import { parse } from 'yaml';
|
|
16
|
-
import { isPreview, livePath } from './noindex.mjs';
|
|
17
|
-
import { stateOf } from '@morphemecms/cms/lib/status.mjs';
|
|
18
|
-
|
|
19
|
-
const FILE = path.resolve('src/data/internal-links.json');
|
|
20
|
-
// The front page, the blog page and the 404 page ("Indstillinger" › "Udseende" › "Standardsider",
|
|
21
|
-
// packages/editor/defaults/standardPages.ts): the front page's address sends on to /, the blog page's to /blog/, and the 404 page has none.
|
|
22
|
-
const STANDARD = path.resolve('src/data/standard-pages.json');
|
|
23
|
-
const PAGES = path.resolve('src/content/pages');
|
|
24
|
-
const POSTS = path.resolve('src/content/blog');
|
|
25
|
-
// What a word may not touch to be linked: a letter, a digit, or a hyphen joining it to another word.
|
|
26
|
-
const LETTER = '\\p{L}\\p{N}-';
|
|
27
|
-
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
-
const split = (text) =>
|
|
29
|
-
String(text ?? '')
|
|
30
|
-
.split(',')
|
|
31
|
-
.map((w) => w.trim())
|
|
32
|
-
.filter(Boolean);
|
|
33
|
-
|
|
34
|
-
let standard;
|
|
35
|
-
/** A page's address as it is linked to: the front page's is /, the blog page's /blog/, and the 404 page's none (null). */
|
|
36
|
-
function linked(href) {
|
|
37
|
-
standard ??= (() => {
|
|
38
|
-
try {
|
|
39
|
-
return JSON.parse(fs.readFileSync(STANDARD, 'utf8'));
|
|
40
|
-
} catch {
|
|
41
|
-
return {};
|
|
42
|
-
}
|
|
43
|
-
})();
|
|
44
|
-
if (href === standard.front) return '/';
|
|
45
|
-
if (href === standard.blog) return '/blog/';
|
|
46
|
-
return href === standard.notFound ? null : href;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** The .md files in a folder and its subfolders, as paths relative to it (none when there is no folder). */
|
|
50
|
-
const files = (dir) =>
|
|
51
|
-
fs.existsSync(dir) ? fs.readdirSync(dir, { recursive: true, encoding: 'utf8' }).filter((f) => f.endsWith('.md')) : [];
|
|
52
|
-
const frontmatter = (file) => parse(fs.readFileSync(file, 'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? '') ?? {};
|
|
53
|
-
|
|
54
|
-
/** The keywords of the pages and posts: [{ href, primary, secondary }]. A page's address is its
|
|
55
|
-
* `path`, or else its file name (getPages() in packages/editor/site/content.ts); a new version of a page (/ny/…)
|
|
56
|
-
* gives its keywords to the page it is for, when that page exists. Only published ones are linked to
|
|
57
|
-
* (packages/editor/lib/status.mjs). */
|
|
58
|
-
function keywordRules() {
|
|
59
|
-
const entries = [
|
|
60
|
-
...files(PAGES).map((f) => {
|
|
61
|
-
const data = frontmatter(path.join(PAGES, f));
|
|
62
|
-
const id = f
|
|
63
|
-
.replace(/\\/g, '/')
|
|
64
|
-
.replace(/\.md$/, '')
|
|
65
|
-
.replace(/(^|\/)index$/, '');
|
|
66
|
-
return { href: data.path || `/${id}/`, data };
|
|
67
|
-
}),
|
|
68
|
-
...files(POSTS).map((f) => ({ href: `/${path.basename(f, '.md')}/`, data: frontmatter(path.join(POSTS, f)) })),
|
|
69
|
-
].filter((e) => stateOf(e.data) === 'published');
|
|
70
|
-
const live = new Set(
|
|
71
|
-
entries
|
|
72
|
-
.map((e) => e.href)
|
|
73
|
-
.filter((href) => !isPreview(href))
|
|
74
|
-
.map(linked),
|
|
75
|
-
);
|
|
76
|
-
return entries
|
|
77
|
-
.map(({ href, data }) => ({ href: linked(livePath(href)), primary: split(data.keyword), secondary: split(data.keywords) }))
|
|
78
|
-
.filter((r) => r.href && live.has(r.href) && (r.primary.length || r.secondary.length))
|
|
79
|
-
.sort((a, b) => a.href.localeCompare(b.href));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let cached;
|
|
83
|
-
/** { enabled, maxPerPage, words: Map<lowercase word, href>, pattern } */
|
|
84
|
-
export function loadLinks() {
|
|
85
|
-
if (cached) return cached;
|
|
86
|
-
const data = JSON.parse(fs.readFileSync(FILE, 'utf8'));
|
|
87
|
-
const enabled = data.enabled !== false;
|
|
88
|
-
const maxPerPage = Number.isFinite(data.maxPerPage) ? data.maxPerPage : 8;
|
|
89
|
-
// A word used for two pages links to the one it is the primary keyword of, then to the one it is
|
|
90
|
-
// a secondary keyword of; between equals, to the first by address.
|
|
91
|
-
const rules = keywordRules();
|
|
92
|
-
const ranked = [...rules.map((r) => ({ href: r.href, words: r.primary })), ...rules.map((r) => ({ href: r.href, words: r.secondary }))];
|
|
93
|
-
const words = new Map();
|
|
94
|
-
for (const { href, words: list } of ranked)
|
|
95
|
-
for (const word of list) {
|
|
96
|
-
const key = word.toLowerCase();
|
|
97
|
-
if (!words.has(key)) words.set(key, href);
|
|
98
|
-
else if (words.get(key) !== href) console.warn(`interne links: "${word}" linker til ${words.get(key)}, ikke ${href}`);
|
|
99
|
-
}
|
|
100
|
-
// One regex for all words, longest first so "tryg tilknytning" wins over shorter matches.
|
|
101
|
-
const all = [...words.keys()].sort((a, b) => b.length - a.length).map(escape);
|
|
102
|
-
const pattern = new RegExp(`(?<![${LETTER}])(${all.join('|')})(?![${LETTER}])`, 'giu');
|
|
103
|
-
cached = { enabled, maxPerPage, words, pattern };
|
|
104
|
-
return cached;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Not inside links, headings, code, or the offer and price boxes.
|
|
108
|
-
const SKIP = new Set(['aside', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'code', 'pre', 'button', 'figcaption', 'dl']);
|
|
109
|
-
|
|
110
|
-
/** The URL of the page being rendered: front matter `path` (pages) or /<file name>/ (posts). A new
|
|
111
|
-
* version of a page (/ny/…) counts as the page it is for, so it links as it will once it replaces it. */
|
|
112
|
-
function pagePath(file) {
|
|
113
|
-
const fm = file.data?.astro?.frontmatter;
|
|
114
|
-
if (fm?.path) return linked(livePath(fm.path));
|
|
115
|
-
const name = path.basename(file.path ?? '', '.md');
|
|
116
|
-
return name ? linked(`/${name}/`) : '';
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** The pages a block in the text links to (packages/editor/lib/blocks.mjs): its `href` fields (emner, priskort,
|
|
120
|
-
* knapper, Kort), the links in its texts (spørgsmål og svar) and the posts it picks. */
|
|
121
|
-
function blockLinks(node) {
|
|
122
|
-
let props;
|
|
123
|
-
try {
|
|
124
|
-
props = JSON.parse(String(node.properties.dataProps ?? '{}'));
|
|
125
|
-
} catch {
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
128
|
-
const links = [];
|
|
129
|
-
const visit = (value, key) => {
|
|
130
|
-
if (typeof value === 'string') {
|
|
131
|
-
if (key === 'href' && value.startsWith('/')) links.push(value.split('#')[0]);
|
|
132
|
-
for (const m of value.matchAll(/\]\((\/[^)\s#]*)/g)) links.push(m[1]);
|
|
133
|
-
} else if (Array.isArray(value)) value.forEach((v) => visit(v, key));
|
|
134
|
-
else if (value && typeof value === 'object') Object.entries(value).forEach(([k, v]) => visit(v, k));
|
|
135
|
-
};
|
|
136
|
-
visit(props);
|
|
137
|
-
if (node.properties.dataBlock === 'posts' && Array.isArray(props.picked)) links.push(...props.picked.map((id) => `/${id}/`));
|
|
138
|
-
return links;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** What every page's links are made from: the words and where each links to, whether they are on and how many there
|
|
142
|
-
* are per page, and this file. Given to the plugin in packages/editor/astro.mjs, so it is in Astro's config: a page's text is
|
|
143
|
-
* kept rendered between builds (node_modules/.astro) until it or the config changes, and a keyword changed on one page
|
|
144
|
-
* changes the links on the others, so then every page is rendered again. */
|
|
145
|
-
export function linksDigest() {
|
|
146
|
-
const { enabled, maxPerPage, words } = loadLinks();
|
|
147
|
-
const text = JSON.stringify([enabled, maxPerPage, [...words], fs.readFileSync(new URL(import.meta.url), 'utf8')]);
|
|
148
|
-
return createHash('sha256').update(text).digest('hex').slice(0, 16);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Rehype plugin; `digest` (linksDigest) is not used here. */
|
|
152
|
-
export function rehypeAutoLinks() {
|
|
153
|
-
return (tree, file) => {
|
|
154
|
-
const { enabled, maxPerPage, words, pattern } = loadLinks();
|
|
155
|
-
if (!enabled || !words.size) return;
|
|
156
|
-
const self = pagePath(file);
|
|
157
|
-
const used = new Set([self]);
|
|
158
|
-
// Targets the text already links to by hand, or a block on the page links to, are not linked again.
|
|
159
|
-
const collect = (node) => {
|
|
160
|
-
if (node.type === 'element' && node.tagName === 'a') used.add(String(node.properties?.href ?? '').split('#')[0]);
|
|
161
|
-
if (node.type === 'element' && node.properties?.dataBlock) blockLinks(node).forEach((href) => used.add(href));
|
|
162
|
-
node.children?.forEach(collect);
|
|
163
|
-
};
|
|
164
|
-
collect(tree);
|
|
165
|
-
let count = 0;
|
|
166
|
-
|
|
167
|
-
const walk = (node) => {
|
|
168
|
-
if (!node.children || count >= maxPerPage) return;
|
|
169
|
-
if (node.type === 'element' && SKIP.has(node.tagName)) return;
|
|
170
|
-
const out = [];
|
|
171
|
-
for (const child of node.children) {
|
|
172
|
-
if (child.type !== 'text' || count >= maxPerPage) {
|
|
173
|
-
walk(child);
|
|
174
|
-
out.push(child);
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
let last = 0;
|
|
178
|
-
for (const m of child.value.matchAll(pattern)) {
|
|
179
|
-
const href = words.get(m[1].toLowerCase());
|
|
180
|
-
if (used.has(href) || count >= maxPerPage) continue;
|
|
181
|
-
used.add(href);
|
|
182
|
-
count++;
|
|
183
|
-
if (m.index > last) out.push({ type: 'text', value: child.value.slice(last, m.index) });
|
|
184
|
-
// data-link="auto": counted after the build (packages/editor/scripts/link-counts.mjs), for the editor's list of keywords.
|
|
185
|
-
out.push({ type: 'element', tagName: 'a', properties: { href, dataLink: 'auto' }, children: [{ type: 'text', value: m[1] }] });
|
|
186
|
-
last = m.index + m[1].length;
|
|
187
|
-
}
|
|
188
|
-
if (last < child.value.length) out.push({ type: 'text', value: child.value.slice(last) });
|
|
189
|
-
}
|
|
190
|
-
node.children = out;
|
|
191
|
-
};
|
|
192
|
-
walk(tree);
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** For the migration: turn links whose text is one of the words back into plain text. */
|
|
197
|
-
export function unlinkAutoWords(markdown) {
|
|
198
|
-
const { words } = loadLinks();
|
|
199
|
-
return markdown.replace(/(?<!!)\[([^\]\[]+)\]\((\/[^)\s]*)\)/g, (all, text) => (words.has(text.trim().toLowerCase()) ? text : all));
|
|
200
|
-
}
|
|
1
|
+
// Automatic internal links. The words come from the pages and posts themselves: their keywords
|
|
2
|
+
// (`keyword`, the primary one, and `keywords`, more separated by commas; under
|
|
3
|
+
// "Søgemaskiner" in the editor; the front page's link to / and the blog page's to /blog/, where they are shown, and the
|
|
4
|
+
// 404 page's to nowhere). src/data/internal-links.json, under "Søgemaskineoptimering" ›
|
|
5
|
+
// "Interne links" in the editor (packages/editor/defaults/internalLinks.ts): `enabled` turns the links on or off, `maxPerPage` is the
|
|
6
|
+
// most per page. A word links to the page it is a
|
|
7
|
+
// keyword of. Unlike linking every occurrence of a word, a target is
|
|
8
|
+
// linked once per page, at most `maxPerPage` links per page, never to the page itself, never inside
|
|
9
|
+
// headings or existing links, and never as part of a word joined with a hyphen (not "web" in "web-design"). Used by the Markdown pipeline (packages/editor/astro.mjs) and by an import
|
|
10
|
+
// script, which removes the old plugin links so they are not doubled. Each link has data-link="auto", so how many there
|
|
11
|
+
// are of each word can be counted after the build (packages/editor/scripts/link-counts.mjs, /editor/links.json).
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { parse } from 'yaml';
|
|
16
|
+
import { isPreview, livePath } from './noindex.mjs';
|
|
17
|
+
import { stateOf } from '@morphemecms/cms/lib/status.mjs';
|
|
18
|
+
|
|
19
|
+
const FILE = path.resolve('src/data/internal-links.json');
|
|
20
|
+
// The front page, the blog page and the 404 page ("Indstillinger" › "Udseende" › "Standardsider",
|
|
21
|
+
// packages/editor/defaults/standardPages.ts): the front page's address sends on to /, the blog page's to /blog/, and the 404 page has none.
|
|
22
|
+
const STANDARD = path.resolve('src/data/standard-pages.json');
|
|
23
|
+
const PAGES = path.resolve('src/content/pages');
|
|
24
|
+
const POSTS = path.resolve('src/content/blog');
|
|
25
|
+
// What a word may not touch to be linked: a letter, a digit, or a hyphen joining it to another word.
|
|
26
|
+
const LETTER = '\\p{L}\\p{N}-';
|
|
27
|
+
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
+
const split = (text) =>
|
|
29
|
+
String(text ?? '')
|
|
30
|
+
.split(',')
|
|
31
|
+
.map((w) => w.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
|
|
34
|
+
let standard;
|
|
35
|
+
/** A page's address as it is linked to: the front page's is /, the blog page's /blog/, and the 404 page's none (null). */
|
|
36
|
+
function linked(href) {
|
|
37
|
+
standard ??= (() => {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(fs.readFileSync(STANDARD, 'utf8'));
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
if (href === standard.front) return '/';
|
|
45
|
+
if (href === standard.blog) return '/blog/';
|
|
46
|
+
return href === standard.notFound ? null : href;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The .md files in a folder and its subfolders, as paths relative to it (none when there is no folder). */
|
|
50
|
+
const files = (dir) =>
|
|
51
|
+
fs.existsSync(dir) ? fs.readdirSync(dir, { recursive: true, encoding: 'utf8' }).filter((f) => f.endsWith('.md')) : [];
|
|
52
|
+
const frontmatter = (file) => parse(fs.readFileSync(file, 'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? '') ?? {};
|
|
53
|
+
|
|
54
|
+
/** The keywords of the pages and posts: [{ href, primary, secondary }]. A page's address is its
|
|
55
|
+
* `path`, or else its file name (getPages() in packages/editor/site/content.ts); a new version of a page (/ny/…)
|
|
56
|
+
* gives its keywords to the page it is for, when that page exists. Only published ones are linked to
|
|
57
|
+
* (packages/editor/lib/status.mjs). */
|
|
58
|
+
function keywordRules() {
|
|
59
|
+
const entries = [
|
|
60
|
+
...files(PAGES).map((f) => {
|
|
61
|
+
const data = frontmatter(path.join(PAGES, f));
|
|
62
|
+
const id = f
|
|
63
|
+
.replace(/\\/g, '/')
|
|
64
|
+
.replace(/\.md$/, '')
|
|
65
|
+
.replace(/(^|\/)index$/, '');
|
|
66
|
+
return { href: data.path || `/${id}/`, data };
|
|
67
|
+
}),
|
|
68
|
+
...files(POSTS).map((f) => ({ href: `/${path.basename(f, '.md')}/`, data: frontmatter(path.join(POSTS, f)) })),
|
|
69
|
+
].filter((e) => stateOf(e.data) === 'published');
|
|
70
|
+
const live = new Set(
|
|
71
|
+
entries
|
|
72
|
+
.map((e) => e.href)
|
|
73
|
+
.filter((href) => !isPreview(href))
|
|
74
|
+
.map(linked),
|
|
75
|
+
);
|
|
76
|
+
return entries
|
|
77
|
+
.map(({ href, data }) => ({ href: linked(livePath(href)), primary: split(data.keyword), secondary: split(data.keywords) }))
|
|
78
|
+
.filter((r) => r.href && live.has(r.href) && (r.primary.length || r.secondary.length))
|
|
79
|
+
.sort((a, b) => a.href.localeCompare(b.href));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let cached;
|
|
83
|
+
/** { enabled, maxPerPage, words: Map<lowercase word, href>, pattern } */
|
|
84
|
+
export function loadLinks() {
|
|
85
|
+
if (cached) return cached;
|
|
86
|
+
const data = JSON.parse(fs.readFileSync(FILE, 'utf8'));
|
|
87
|
+
const enabled = data.enabled !== false;
|
|
88
|
+
const maxPerPage = Number.isFinite(data.maxPerPage) ? data.maxPerPage : 8;
|
|
89
|
+
// A word used for two pages links to the one it is the primary keyword of, then to the one it is
|
|
90
|
+
// a secondary keyword of; between equals, to the first by address.
|
|
91
|
+
const rules = keywordRules();
|
|
92
|
+
const ranked = [...rules.map((r) => ({ href: r.href, words: r.primary })), ...rules.map((r) => ({ href: r.href, words: r.secondary }))];
|
|
93
|
+
const words = new Map();
|
|
94
|
+
for (const { href, words: list } of ranked)
|
|
95
|
+
for (const word of list) {
|
|
96
|
+
const key = word.toLowerCase();
|
|
97
|
+
if (!words.has(key)) words.set(key, href);
|
|
98
|
+
else if (words.get(key) !== href) console.warn(`interne links: "${word}" linker til ${words.get(key)}, ikke ${href}`);
|
|
99
|
+
}
|
|
100
|
+
// One regex for all words, longest first so "tryg tilknytning" wins over shorter matches.
|
|
101
|
+
const all = [...words.keys()].sort((a, b) => b.length - a.length).map(escape);
|
|
102
|
+
const pattern = new RegExp(`(?<![${LETTER}])(${all.join('|')})(?![${LETTER}])`, 'giu');
|
|
103
|
+
cached = { enabled, maxPerPage, words, pattern };
|
|
104
|
+
return cached;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Not inside links, headings, code, or the offer and price boxes.
|
|
108
|
+
const SKIP = new Set(['aside', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'code', 'pre', 'button', 'figcaption', 'dl']);
|
|
109
|
+
|
|
110
|
+
/** The URL of the page being rendered: front matter `path` (pages) or /<file name>/ (posts). A new
|
|
111
|
+
* version of a page (/ny/…) counts as the page it is for, so it links as it will once it replaces it. */
|
|
112
|
+
function pagePath(file) {
|
|
113
|
+
const fm = file.data?.astro?.frontmatter;
|
|
114
|
+
if (fm?.path) return linked(livePath(fm.path));
|
|
115
|
+
const name = path.basename(file.path ?? '', '.md');
|
|
116
|
+
return name ? linked(`/${name}/`) : '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The pages a block in the text links to (packages/editor/lib/blocks.mjs): its `href` fields (emner, priskort,
|
|
120
|
+
* knapper, Kort), the links in its texts (spørgsmål og svar) and the posts it picks. */
|
|
121
|
+
function blockLinks(node) {
|
|
122
|
+
let props;
|
|
123
|
+
try {
|
|
124
|
+
props = JSON.parse(String(node.properties.dataProps ?? '{}'));
|
|
125
|
+
} catch {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const links = [];
|
|
129
|
+
const visit = (value, key) => {
|
|
130
|
+
if (typeof value === 'string') {
|
|
131
|
+
if (key === 'href' && value.startsWith('/')) links.push(value.split('#')[0]);
|
|
132
|
+
for (const m of value.matchAll(/\]\((\/[^)\s#]*)/g)) links.push(m[1]);
|
|
133
|
+
} else if (Array.isArray(value)) value.forEach((v) => visit(v, key));
|
|
134
|
+
else if (value && typeof value === 'object') Object.entries(value).forEach(([k, v]) => visit(v, k));
|
|
135
|
+
};
|
|
136
|
+
visit(props);
|
|
137
|
+
if (node.properties.dataBlock === 'posts' && Array.isArray(props.picked)) links.push(...props.picked.map((id) => `/${id}/`));
|
|
138
|
+
return links;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** What every page's links are made from: the words and where each links to, whether they are on and how many there
|
|
142
|
+
* are per page, and this file. Given to the plugin in packages/editor/astro.mjs, so it is in Astro's config: a page's text is
|
|
143
|
+
* kept rendered between builds (node_modules/.astro) until it or the config changes, and a keyword changed on one page
|
|
144
|
+
* changes the links on the others, so then every page is rendered again. */
|
|
145
|
+
export function linksDigest() {
|
|
146
|
+
const { enabled, maxPerPage, words } = loadLinks();
|
|
147
|
+
const text = JSON.stringify([enabled, maxPerPage, [...words], fs.readFileSync(new URL(import.meta.url), 'utf8')]);
|
|
148
|
+
return createHash('sha256').update(text).digest('hex').slice(0, 16);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Rehype plugin; `digest` (linksDigest) is not used here. */
|
|
152
|
+
export function rehypeAutoLinks() {
|
|
153
|
+
return (tree, file) => {
|
|
154
|
+
const { enabled, maxPerPage, words, pattern } = loadLinks();
|
|
155
|
+
if (!enabled || !words.size) return;
|
|
156
|
+
const self = pagePath(file);
|
|
157
|
+
const used = new Set([self]);
|
|
158
|
+
// Targets the text already links to by hand, or a block on the page links to, are not linked again.
|
|
159
|
+
const collect = (node) => {
|
|
160
|
+
if (node.type === 'element' && node.tagName === 'a') used.add(String(node.properties?.href ?? '').split('#')[0]);
|
|
161
|
+
if (node.type === 'element' && node.properties?.dataBlock) blockLinks(node).forEach((href) => used.add(href));
|
|
162
|
+
node.children?.forEach(collect);
|
|
163
|
+
};
|
|
164
|
+
collect(tree);
|
|
165
|
+
let count = 0;
|
|
166
|
+
|
|
167
|
+
const walk = (node) => {
|
|
168
|
+
if (!node.children || count >= maxPerPage) return;
|
|
169
|
+
if (node.type === 'element' && SKIP.has(node.tagName)) return;
|
|
170
|
+
const out = [];
|
|
171
|
+
for (const child of node.children) {
|
|
172
|
+
if (child.type !== 'text' || count >= maxPerPage) {
|
|
173
|
+
walk(child);
|
|
174
|
+
out.push(child);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
let last = 0;
|
|
178
|
+
for (const m of child.value.matchAll(pattern)) {
|
|
179
|
+
const href = words.get(m[1].toLowerCase());
|
|
180
|
+
if (used.has(href) || count >= maxPerPage) continue;
|
|
181
|
+
used.add(href);
|
|
182
|
+
count++;
|
|
183
|
+
if (m.index > last) out.push({ type: 'text', value: child.value.slice(last, m.index) });
|
|
184
|
+
// data-link="auto": counted after the build (packages/editor/scripts/link-counts.mjs), for the editor's list of keywords.
|
|
185
|
+
out.push({ type: 'element', tagName: 'a', properties: { href, dataLink: 'auto' }, children: [{ type: 'text', value: m[1] }] });
|
|
186
|
+
last = m.index + m[1].length;
|
|
187
|
+
}
|
|
188
|
+
if (last < child.value.length) out.push({ type: 'text', value: child.value.slice(last) });
|
|
189
|
+
}
|
|
190
|
+
node.children = out;
|
|
191
|
+
};
|
|
192
|
+
walk(tree);
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** For the migration: turn links whose text is one of the words back into plain text. */
|
|
197
|
+
export function unlinkAutoWords(markdown) {
|
|
198
|
+
const { words } = loadLinks();
|
|
199
|
+
return markdown.replace(/(?<!!)\[([^\]\[]+)\]\((\/[^)\s]*)\)/g, (all, text) => (words.has(text.trim().toLowerCase()) ? text : all));
|
|
200
|
+
}
|
package/site/blockScripts.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
// What the blocks do in a browser on the site: script.ts in a theme's blocks/<type>/ (the rows that fold out, the
|
|
2
|
-
// testimonials' "Læs mere" …; the theme in use's, else the standard theme's, as their look, sites.ts), what the theme's
|
|
3
|
-
// UI components do (ui/<name>.script.ts: a carousel's page indicator, by `ui/<name>`), in an app's
|
|
4
|
-
// block (src/apps/<app>/blocks/<type>/), and in an app's part of a list's entries (src/apps/<app>/<list>/: Mailchimp's
|
|
5
|
-
// signup in a pop-up). Each is a function that finds its blocks on the page and does nothing without them; the theme's
|
|
6
|
-
// Base.astro runs them all on every page, so they also work where a site or an app shows a block's parts of its own
|
|
7
|
-
// (this site's front page).
|
|
8
|
-
type Script = () => void;
|
|
9
|
-
|
|
10
|
-
const byType = (found: Record<string, Script>) =>
|
|
11
|
-
Object.fromEntries(Object.entries(found).map(([file, run]) => [file.split('/').at(-2)!, run]));
|
|
12
|
-
|
|
13
|
-
// The theme's UI components' by their name (ui/carousel.script.ts: ui/carousel).
|
|
14
|
-
const byUi = (found: Record<string, Script>) =>
|
|
15
|
-
Object.fromEntries(Object.entries(found).map(([file, run]) => [`ui/${file.split('/').at(-1)!.replace('.script.ts', '')}`, run]));
|
|
16
|
-
|
|
17
|
-
/** What each block does in a browser, by its type. */
|
|
18
|
-
export const blockScripts: Record<string, Script> = {
|
|
19
|
-
...byType(import.meta.glob<Script>('@saturn/blocks/*/script.ts', { eager: true, import: 'default' })),
|
|
20
|
-
...byType(import.meta.glob<Script>('@theme/blocks/*/script.ts', { eager: true, import: 'default' })),
|
|
21
|
-
...byUi(import.meta.glob<Script>('@saturn/ui/*.script.ts', { eager: true, import: 'default' })),
|
|
22
|
-
...byUi(import.meta.glob<Script>('@theme/ui/*.script.ts', { eager: true, import: 'default' })),
|
|
23
|
-
...byType(import.meta.glob<Script>('/src/apps/*/blocks/*/script.ts', { eager: true, import: 'default' })),
|
|
24
|
-
// An app's part of its kind of a list's entries (Mailchimp's signup in a pop-up), by the app and the list.
|
|
25
|
-
...Object.fromEntries(
|
|
26
|
-
Object.entries(import.meta.glob<Script>('/src/apps/*/*/script.ts', { eager: true, import: 'default' })).map(([file, run]) => [
|
|
27
|
-
file.split('/').slice(-3, -1).join('/'),
|
|
28
|
-
run,
|
|
29
|
-
]),
|
|
30
|
-
),
|
|
31
|
-
};
|
|
1
|
+
// What the blocks do in a browser on the site: script.ts in a theme's blocks/<type>/ (the rows that fold out, the
|
|
2
|
+
// testimonials' "Læs mere" …; the theme in use's, else the standard theme's, as their look, sites.ts), what the theme's
|
|
3
|
+
// UI components do (ui/<name>.script.ts: a carousel's page indicator, by `ui/<name>`), in an app's
|
|
4
|
+
// block (src/apps/<app>/blocks/<type>/), and in an app's part of a list's entries (src/apps/<app>/<list>/: Mailchimp's
|
|
5
|
+
// signup in a pop-up). Each is a function that finds its blocks on the page and does nothing without them; the theme's
|
|
6
|
+
// Base.astro runs them all on every page, so they also work where a site or an app shows a block's parts of its own
|
|
7
|
+
// (this site's front page).
|
|
8
|
+
type Script = () => void;
|
|
9
|
+
|
|
10
|
+
const byType = (found: Record<string, Script>) =>
|
|
11
|
+
Object.fromEntries(Object.entries(found).map(([file, run]) => [file.split('/').at(-2)!, run]));
|
|
12
|
+
|
|
13
|
+
// The theme's UI components' by their name (ui/carousel.script.ts: ui/carousel).
|
|
14
|
+
const byUi = (found: Record<string, Script>) =>
|
|
15
|
+
Object.fromEntries(Object.entries(found).map(([file, run]) => [`ui/${file.split('/').at(-1)!.replace('.script.ts', '')}`, run]));
|
|
16
|
+
|
|
17
|
+
/** What each block does in a browser, by its type. */
|
|
18
|
+
export const blockScripts: Record<string, Script> = {
|
|
19
|
+
...byType(import.meta.glob<Script>('@saturn/blocks/*/script.ts', { eager: true, import: 'default' })),
|
|
20
|
+
...byType(import.meta.glob<Script>('@theme/blocks/*/script.ts', { eager: true, import: 'default' })),
|
|
21
|
+
...byUi(import.meta.glob<Script>('@saturn/ui/*.script.ts', { eager: true, import: 'default' })),
|
|
22
|
+
...byUi(import.meta.glob<Script>('@theme/ui/*.script.ts', { eager: true, import: 'default' })),
|
|
23
|
+
...byType(import.meta.glob<Script>('/src/apps/*/blocks/*/script.ts', { eager: true, import: 'default' })),
|
|
24
|
+
// An app's part of its kind of a list's entries (Mailchimp's signup in a pop-up), by the app and the list.
|
|
25
|
+
...Object.fromEntries(
|
|
26
|
+
Object.entries(import.meta.glob<Script>('/src/apps/*/*/script.ts', { eager: true, import: 'default' })).map(([file, run]) => [
|
|
27
|
+
file.split('/').slice(-3, -1).join('/'),
|
|
28
|
+
run,
|
|
29
|
+
]),
|
|
30
|
+
),
|
|
31
|
+
};
|
package/site/categories.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
// Tags (called categories in the code): one JSON file per tag in src/data/categories/, edited in the
|
|
2
|
-
// editor (/editor/, "Tags"). The file name is the slug: the address /blog/kategori/<slug>/ and the
|
|
3
|
-
// value stored in the `categories` of the blog posts and the testimonials. Only a blog category
|
|
4
|
-
// (`blogCategory`, "Blogkategori" in the editor) is shown on the blog: a pill on the blog index and
|
|
5
|
-
// on its posts, and its own page. The others only pick posts and testimonials.
|
|
6
|
-
type CategoryData = { name: string; blogCategory?: boolean; seoTitle?: string; seoDescription?: string; shareImage?: string };
|
|
7
|
-
|
|
8
|
-
const files = import.meta.glob<CategoryData>('@data/categories/*.json', { eager: true, import: 'default' });
|
|
9
|
-
|
|
10
|
-
export const categories = Object.entries(files)
|
|
11
|
-
.map(([file, c]) => ({ slug: file.replace(/^.*\/|\.json$/g, ''), ...c }))
|
|
12
|
-
.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
13
|
-
|
|
14
|
-
const notOnBlog = new Set(categories.filter((c) => c.blogCategory === false).map((c) => c.slug));
|
|
15
|
-
/** Whether the tag is shown on the blog (a tag without a file, or from before the field, is). */
|
|
16
|
-
export const isBlogCategory = (slug: string) => !notOnBlog.has(slug);
|
|
1
|
+
// Tags (called categories in the code): one JSON file per tag in src/data/categories/, edited in the
|
|
2
|
+
// editor (/editor/, "Tags"). The file name is the slug: the address /blog/kategori/<slug>/ and the
|
|
3
|
+
// value stored in the `categories` of the blog posts and the testimonials. Only a blog category
|
|
4
|
+
// (`blogCategory`, "Blogkategori" in the editor) is shown on the blog: a pill on the blog index and
|
|
5
|
+
// on its posts, and its own page. The others only pick posts and testimonials.
|
|
6
|
+
type CategoryData = { name: string; blogCategory?: boolean; seoTitle?: string; seoDescription?: string; shareImage?: string };
|
|
7
|
+
|
|
8
|
+
const files = import.meta.glob<CategoryData>('@data/categories/*.json', { eager: true, import: 'default' });
|
|
9
|
+
|
|
10
|
+
export const categories = Object.entries(files)
|
|
11
|
+
.map(([file, c]) => ({ slug: file.replace(/^.*\/|\.json$/g, ''), ...c }))
|
|
12
|
+
.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
13
|
+
|
|
14
|
+
const notOnBlog = new Set(categories.filter((c) => c.blogCategory === false).map((c) => c.slug));
|
|
15
|
+
/** Whether the tag is shown on the blog (a tag without a file, or from before the field, is). */
|
|
16
|
+
export const isBlogCategory = (slug: string) => !notOnBlog.has(slug);
|
package/site/consent.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
// What the cookie banner asks about: the categories of cookies (@morphemecms/cms/lib/cookieCategories.mjs) of what the
|
|
2
|
-
// installed apps load with their settings (`cookies` in their index.ts: Google Analytics statistics, Google Ads
|
|
3
|
-
// marketing, Google Tag Manager both), each a yes or no of its own; and whether the site has the banner (and
|
|
4
|
-
// "Cookieindstillinger" in the footer) at all: when it is switched on ("Privatliv" › "Cookiebanner",
|
|
5
|
-
// src/data/cookies.json) and there is something to ask about. For the theme (Base.astro, Footer.astro,
|
|
6
|
-
// CookieConsent.astro), which knows no app by name.
|
|
7
|
-
import type { AppDefinition } from '../src/apps';
|
|
8
|
-
import { appIdOf } from '../src/apps';
|
|
9
|
-
import { CONSENT_CATEGORIES, type ConsentCategory } from '../lib/cookieCategories.mjs';
|
|
10
|
-
import cookies from '@data/cookies.json';
|
|
11
|
-
import { installed, settingsOf } from './apps';
|
|
12
|
-
|
|
13
|
-
// Only the apps' index.ts, without their blocks (as opens.ts).
|
|
14
|
-
const appFiles = import.meta.glob<{ default: AppDefinition }>('/src/apps/*/index.ts', { eager: true });
|
|
15
|
-
|
|
16
|
-
const asked = new Set(
|
|
17
|
-
Object.entries(appFiles).flatMap(([path, { default: app }]) => {
|
|
18
|
-
const id = appIdOf(path);
|
|
19
|
-
return installed.includes(id) ? (app.cookies?.(settingsOf(id, app.settings?.values)) ?? []) : [];
|
|
20
|
-
}),
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
/** The categories the banner asks about, in its order. */
|
|
24
|
-
export const consentCategories: ConsentCategory[] = CONSENT_CATEGORIES.filter((c) => asked.has(c));
|
|
25
|
-
|
|
26
|
-
export const cookieBanner = cookies.enabled && consentCategories.length > 0;
|
|
1
|
+
// What the cookie banner asks about: the categories of cookies (@morphemecms/cms/lib/cookieCategories.mjs) of what the
|
|
2
|
+
// installed apps load with their settings (`cookies` in their index.ts: Google Analytics statistics, Google Ads
|
|
3
|
+
// marketing, Google Tag Manager both), each a yes or no of its own; and whether the site has the banner (and
|
|
4
|
+
// "Cookieindstillinger" in the footer) at all: when it is switched on ("Privatliv" › "Cookiebanner",
|
|
5
|
+
// src/data/cookies.json) and there is something to ask about. For the theme (Base.astro, Footer.astro,
|
|
6
|
+
// CookieConsent.astro), which knows no app by name.
|
|
7
|
+
import type { AppDefinition } from '../src/apps';
|
|
8
|
+
import { appIdOf } from '../src/apps';
|
|
9
|
+
import { CONSENT_CATEGORIES, type ConsentCategory } from '../lib/cookieCategories.mjs';
|
|
10
|
+
import cookies from '@data/cookies.json';
|
|
11
|
+
import { installed, settingsOf } from './apps';
|
|
12
|
+
|
|
13
|
+
// Only the apps' index.ts, without their blocks (as opens.ts).
|
|
14
|
+
const appFiles = import.meta.glob<{ default: AppDefinition }>('/src/apps/*/index.ts', { eager: true });
|
|
15
|
+
|
|
16
|
+
const asked = new Set(
|
|
17
|
+
Object.entries(appFiles).flatMap(([path, { default: app }]) => {
|
|
18
|
+
const id = appIdOf(path);
|
|
19
|
+
return installed.includes(id) ? (app.cookies?.(settingsOf(id, app.settings?.values)) ?? []) : [];
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/** The categories the banner asks about, in its order. */
|
|
24
|
+
export const consentCategories: ConsentCategory[] = CONSENT_CATEGORIES.filter((c) => asked.has(c));
|
|
25
|
+
|
|
26
|
+
export const cookieBanner = cookies.enabled && consentCategories.length > 0;
|