@morphemecms/cms 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +51 -3
- package/astro.d.mts +2 -0
- package/astro.mjs +162 -0
- package/bin/morpheme.mjs +38 -0
- package/client.d.ts +10 -0
- package/defaults/blockTexts.ts +33 -0
- package/defaults/collections.ts +774 -0
- package/defaults/colors.ts +125 -0
- package/defaults/company.ts +20 -0
- package/defaults/editor.ts +178 -0
- package/defaults/internalLinks.ts +30 -0
- package/defaults/opens.ts +18 -0
- package/defaults/popup.tsx +93 -0
- package/defaults/privacy.ts +79 -0
- package/defaults/rules.ts +450 -0
- package/defaults/schema.ts +16 -0
- package/defaults/sitedata.ts +129 -0
- package/defaults/standard.ts +106 -0
- package/defaults/standardPages.ts +87 -0
- package/defaults/texts.ts +481 -0
- package/demo/README.md +26 -0
- package/demo/public/apple-touch-icon.png +0 -0
- package/demo/public/editor/apple-touch-icon.png +0 -0
- package/demo/public/editor/icon-192.png +0 -0
- package/demo/public/editor/icon-512.png +0 -0
- package/demo/public/editor/icon-maskable-512.png +0 -0
- package/demo/public/favicon.ico +0 -0
- package/demo/public/favicon.svg +1 -0
- package/demo/public/images/uploads/2026/10/andromeda.webp +0 -0
- package/demo/public/images/uploads/2026/10/earth-from-space.webp +0 -0
- package/demo/public/images/uploads/2026/10/editor-blog.webp +0 -0
- package/demo/public/images/uploads/2026/10/editor-colors.webp +0 -0
- package/demo/public/images/uploads/2026/10/editor-home.webp +0 -0
- package/demo/public/images/uploads/2026/10/editor-page.webp +0 -0
- package/demo/public/images/uploads/2026/10/editor-pages.webp +0 -0
- package/demo/public/images/uploads/2026/10/editor-seo.webp +0 -0
- package/demo/public/images/uploads/2026/10/editor-start.webp +0 -0
- package/demo/public/images/uploads/2026/10/editor-themes.webp +0 -0
- package/demo/public/images/uploads/2026/10/moon.webp +0 -0
- package/demo/public/images/uploads/2026/10/saturn.webp +0 -0
- package/demo/public/logo.svg +1 -0
- package/demo/src/content/blog/build-a-page-from-blocks.md +19 -0
- package/demo/src/content/blog/colors-everyone-can-read.md +19 -0
- package/demo/src/content/blog/edit-your-website-from-your-phone.md +21 -0
- package/demo/src/content/blog/every-save-is-a-version.md +21 -0
- package/demo/src/content/blog/schedule-a-post.md +17 -0
- package/demo/src/content/blog/secure-from-day-one.md +17 -0
- package/demo/src/content/blog/seo-without-the-guesswork.md +23 -0
- package/demo/src/content/blog/switch-themes-without-losing-anything.md +23 -0
- package/demo/src/content/blog/the-app-store.md +21 -0
- package/demo/src/content/pages/about.md +22 -0
- package/demo/src/content/pages/app-store.md +30 -0
- package/demo/src/content/pages/blocks.md +98 -0
- package/demo/src/content/pages/blog-page.md +11 -0
- package/demo/src/content/pages/contact.md +16 -0
- package/demo/src/content/pages/get-started.md +20 -0
- package/demo/src/content/pages/home.md +26 -0
- package/demo/src/content/pages/page-not-found.md +11 -0
- package/demo/src/content/pages/privacy-policy.md +24 -0
- package/demo/src/data/blocks.json +25 -0
- package/demo/src/data/booking.json +6 -0
- package/demo/src/data/categories/apps.json +6 -0
- package/demo/src/data/categories/design.json +6 -0
- package/demo/src/data/categories/editing.json +6 -0
- package/demo/src/data/categories/security.json +6 -0
- package/demo/src/data/categories/seo.json +6 -0
- package/demo/src/data/colors.json +75 -0
- package/demo/src/data/contact.json +8 -0
- package/demo/src/data/cookies.json +21 -0
- package/demo/src/data/footer.json +27 -0
- package/demo/src/data/menu.json +35 -0
- package/demo/src/data/mobile-menu.json +25 -0
- package/demo/src/data/popups/booking-bar.json +13 -0
- package/demo/src/data/popups/try-the-editor.json +16 -0
- package/demo/src/data/standard-pages.json +8 -0
- package/demo/src/data/testimonials/sample-bakery.json +10 -0
- package/demo/src/data/testimonials/sample-clinic.json +10 -0
- package/demo/src/data/testimonials/sample-studio.json +10 -0
- package/demo/src/site/colors.ts +11 -0
- package/demo/src/site/config.ts +19 -0
- package/icons/lucide/LICENSE +43 -0
- package/icons/lucide/LICENSE-material-design-icons +20 -0
- package/icons/lucide/address-book.svg +1 -0
- package/icons/lucide/app-window.svg +1 -0
- package/icons/lucide/arrow-counter-clockwise.svg +1 -0
- package/icons/lucide/arrow-down.svg +1 -0
- package/icons/lucide/arrow-left.svg +1 -0
- package/icons/lucide/arrow-right.svg +1 -0
- package/icons/lucide/arrow-square-out.svg +1 -0
- package/icons/lucide/arrow-u-up-left.svg +1 -0
- package/icons/lucide/arrow-u-up-right.svg +1 -0
- package/icons/lucide/arrow-up-right.svg +1 -0
- package/icons/lucide/arrow-up.svg +1 -0
- package/icons/lucide/arrows-down-up.svg +1 -0
- package/icons/lucide/arrows-in-line-horizontal.svg +1 -0
- package/icons/lucide/arrows-out-line-horizontal.svg +1 -0
- package/icons/lucide/article.svg +1 -0
- package/icons/lucide/bank.svg +1 -0
- package/icons/lucide/bell.svg +1 -0
- package/icons/lucide/bicycle.svg +1 -0
- package/icons/lucide/binoculars.svg +1 -0
- package/icons/lucide/book-open-text.svg +1 -0
- package/icons/lucide/book-open.svg +1 -0
- package/icons/lucide/browser.svg +1 -0
- package/icons/lucide/buildings.svg +1 -0
- package/icons/lucide/bus.svg +1 -0
- package/icons/lucide/calendar-blank.svg +1 -0
- package/icons/lucide/calendar-check.svg +1 -0
- package/icons/lucide/car.svg +1 -0
- package/icons/lucide/cards.svg +1 -0
- package/icons/lucide/caret-down.svg +1 -0
- package/icons/lucide/caret-left.svg +1 -0
- package/icons/lucide/caret-right.svg +1 -0
- package/icons/lucide/caret-up.svg +1 -0
- package/icons/lucide/certificate.svg +1 -0
- package/icons/lucide/chart-line-up.svg +1 -0
- package/icons/lucide/chats.svg +1 -0
- package/icons/lucide/check-circle.svg +1 -0
- package/icons/lucide/check.svg +1 -0
- package/icons/lucide/circle-half.svg +1 -0
- package/icons/lucide/circle.svg +1 -0
- package/icons/lucide/clock-counter-clockwise.svg +1 -0
- package/icons/lucide/clock.svg +1 -0
- package/icons/lucide/code.svg +1 -0
- package/icons/lucide/coffee.svg +1 -0
- package/icons/lucide/columns.svg +1 -0
- package/icons/lucide/cookie.svg +1 -0
- package/icons/lucide/copy.svg +1 -0
- package/icons/lucide/credit-card.svg +1 -0
- package/icons/lucide/currency-circle-dollar.svg +1 -0
- package/icons/lucide/cursor-click.svg +1 -0
- package/icons/lucide/database.svg +1 -0
- package/icons/lucide/desktop.svg +1 -0
- package/icons/lucide/device-mobile.svg +1 -0
- package/icons/lucide/door-open.svg +1 -0
- package/icons/lucide/download-simple.svg +1 -0
- package/icons/lucide/envelope-simple.svg +1 -0
- package/icons/lucide/eye-slash.svg +1 -0
- package/icons/lucide/eye.svg +1 -0
- package/icons/lucide/file-pdf.svg +1 -0
- package/icons/lucide/file-text.svg +1 -0
- package/icons/lucide/file-video.svg +1 -0
- package/icons/lucide/file-zip.svg +1 -0
- package/icons/lucide/flower-lotus.svg +1 -0
- package/icons/lucide/folder-simple.svg +1 -0
- package/icons/lucide/funnel.svg +1 -0
- package/icons/lucide/gauge.svg +1 -0
- package/icons/lucide/gear.svg +1 -0
- package/icons/lucide/github-logo.svg +1 -0
- package/icons/lucide/globe.svg +1 -0
- package/icons/lucide/google-logo.svg +1 -0
- package/icons/lucide/graduation-cap.svg +1 -0
- package/icons/lucide/hammer.svg +1 -0
- package/icons/lucide/hand-heart.svg +1 -0
- package/icons/lucide/handshake.svg +1 -0
- package/icons/lucide/hard-drives.svg +1 -0
- package/icons/lucide/heart.svg +1 -0
- package/icons/lucide/house.svg +1 -0
- package/icons/lucide/image-square.svg +1 -0
- package/icons/lucide/image.svg +1 -0
- package/icons/lucide/info.svg +1 -0
- package/icons/lucide/keyboard.svg +1 -0
- package/icons/lucide/leaf.svg +1 -0
- package/icons/lucide/lifebuoy.svg +1 -0
- package/icons/lucide/lightbulb.svg +1 -0
- package/icons/lucide/link-break.svg +1 -0
- package/icons/lucide/link-simple.svg +1 -0
- package/icons/lucide/list-bullets.svg +1 -0
- package/icons/lucide/list-checks.svg +1 -0
- package/icons/lucide/list-numbers.svg +1 -0
- package/icons/lucide/list.svg +1 -0
- package/icons/lucide/lock-simple-open.svg +1 -0
- package/icons/lucide/lock-simple.svg +1 -0
- package/icons/lucide/magnifying-glass.svg +1 -0
- package/icons/lucide/map-pin.svg +1 -0
- package/icons/lucide/map-trifold.svg +1 -0
- package/icons/lucide/medal.svg +1 -0
- package/icons/lucide/megaphone.svg +1 -0
- package/icons/lucide/minus.svg +1 -0
- package/icons/lucide/moon.svg +1 -0
- package/icons/lucide/newspaper.svg +1 -0
- package/icons/lucide/pack.json +23 -0
- package/icons/lucide/paint-brush-broad.svg +1 -0
- package/icons/lucide/palette.svg +1 -0
- package/icons/lucide/paper-plane-tilt.svg +1 -0
- package/icons/lucide/pencil-simple.svg +1 -0
- package/icons/lucide/phone.svg +1 -0
- package/icons/lucide/play-fill.svg +1 -0
- package/icons/lucide/plus.svg +1 -0
- package/icons/lucide/prohibit.svg +1 -0
- package/icons/lucide/puzzle-piece.svg +1 -0
- package/icons/lucide/question.svg +1 -0
- package/icons/lucide/quotes.svg +1 -0
- package/icons/lucide/rows.svg +1 -0
- package/icons/lucide/ruler.svg +1 -0
- package/icons/lucide/scan.svg +1 -0
- package/icons/lucide/shapes.svg +1 -0
- package/icons/lucide/share-network.svg +1 -0
- package/icons/lucide/shield-check.svg +1 -0
- package/icons/lucide/sidebar-simple.svg +1 -0
- package/icons/lucide/sign-out.svg +1 -0
- package/icons/lucide/signpost.svg +1 -0
- package/icons/lucide/sliders-horizontal.svg +1 -0
- package/icons/lucide/smiley.svg +1 -0
- package/icons/lucide/sparkle.svg +1 -0
- package/icons/lucide/square-half-bottom.svg +1 -0
- package/icons/lucide/square.svg +1 -0
- package/icons/lucide/squares-four.svg +1 -0
- package/icons/lucide/sticker.svg +1 -0
- package/icons/lucide/storefront.svg +1 -0
- package/icons/lucide/sun.svg +1 -0
- package/icons/lucide/swatches.svg +1 -0
- package/icons/lucide/tag.svg +1 -0
- package/icons/lucide/text-aa.svg +1 -0
- package/icons/lucide/text-align-left.svg +1 -0
- package/icons/lucide/text-b.svg +1 -0
- package/icons/lucide/text-h-one.svg +1 -0
- package/icons/lucide/text-h-three.svg +1 -0
- package/icons/lucide/text-h-two.svg +1 -0
- package/icons/lucide/text-italic.svg +1 -0
- package/icons/lucide/text-t.svg +1 -0
- package/icons/lucide/ticket.svg +1 -0
- package/icons/lucide/toggle-right.svg +1 -0
- package/icons/lucide/train.svg +1 -0
- package/icons/lucide/trash.svg +1 -0
- package/icons/lucide/upload-simple.svg +1 -0
- package/icons/lucide/user-circle.svg +1 -0
- package/icons/lucide/user.svg +1 -0
- package/icons/lucide/users-three.svg +1 -0
- package/icons/lucide/users.svg +1 -0
- package/icons/lucide/video-camera.svg +1 -0
- package/icons/lucide/warning-circle.svg +1 -0
- package/icons/lucide/warning.svg +1 -0
- package/icons/lucide/wave-sine.svg +1 -0
- package/icons/lucide/wheelchair.svg +1 -0
- package/icons/lucide/wrench.svg +1 -0
- package/icons/lucide/x.svg +1 -0
- package/icons/material-symbols/LICENSE +201 -0
- package/icons/material-symbols/LICENSE-material-design-icons +20 -0
- package/icons/material-symbols/address-book.svg +1 -0
- package/icons/material-symbols/app-window.svg +1 -0
- package/icons/material-symbols/arrow-counter-clockwise.svg +1 -0
- package/icons/material-symbols/arrow-down.svg +1 -0
- package/icons/material-symbols/arrow-left.svg +1 -0
- package/icons/material-symbols/arrow-right.svg +1 -0
- package/icons/material-symbols/arrow-square-out.svg +1 -0
- package/icons/material-symbols/arrow-u-up-left.svg +1 -0
- package/icons/material-symbols/arrow-u-up-right.svg +1 -0
- package/icons/material-symbols/arrow-up-right.svg +1 -0
- package/icons/material-symbols/arrow-up.svg +1 -0
- package/icons/material-symbols/arrows-down-up.svg +1 -0
- package/icons/material-symbols/arrows-in-line-horizontal.svg +1 -0
- package/icons/material-symbols/arrows-out-line-horizontal.svg +1 -0
- package/icons/material-symbols/article.svg +1 -0
- package/icons/material-symbols/bank.svg +1 -0
- package/icons/material-symbols/bell.svg +1 -0
- package/icons/material-symbols/bicycle.svg +1 -0
- package/icons/material-symbols/binoculars.svg +1 -0
- package/icons/material-symbols/book-open-text.svg +1 -0
- package/icons/material-symbols/book-open.svg +1 -0
- package/icons/material-symbols/browser.svg +1 -0
- package/icons/material-symbols/buildings.svg +1 -0
- package/icons/material-symbols/bus.svg +1 -0
- package/icons/material-symbols/calendar-blank.svg +1 -0
- package/icons/material-symbols/calendar-check.svg +1 -0
- package/icons/material-symbols/car.svg +1 -0
- package/icons/material-symbols/cards.svg +1 -0
- package/icons/material-symbols/caret-down.svg +1 -0
- package/icons/material-symbols/caret-left.svg +1 -0
- package/icons/material-symbols/caret-right.svg +1 -0
- package/icons/material-symbols/caret-up.svg +1 -0
- package/icons/material-symbols/certificate.svg +1 -0
- package/icons/material-symbols/chart-line-up.svg +1 -0
- package/icons/material-symbols/chats.svg +1 -0
- package/icons/material-symbols/check-circle.svg +1 -0
- package/icons/material-symbols/check.svg +1 -0
- package/icons/material-symbols/circle-half.svg +1 -0
- package/icons/material-symbols/circle.svg +1 -0
- package/icons/material-symbols/clock-counter-clockwise.svg +1 -0
- package/icons/material-symbols/clock.svg +1 -0
- package/icons/material-symbols/code.svg +1 -0
- package/icons/material-symbols/coffee.svg +1 -0
- package/icons/material-symbols/columns.svg +1 -0
- package/icons/material-symbols/cookie.svg +1 -0
- package/icons/material-symbols/copy.svg +1 -0
- package/icons/material-symbols/credit-card.svg +1 -0
- package/icons/material-symbols/currency-circle-dollar.svg +1 -0
- package/icons/material-symbols/cursor-click.svg +1 -0
- package/icons/material-symbols/database.svg +1 -0
- package/icons/material-symbols/desktop.svg +1 -0
- package/icons/material-symbols/device-mobile.svg +1 -0
- package/icons/material-symbols/door-open.svg +1 -0
- package/icons/material-symbols/download-simple.svg +1 -0
- package/icons/material-symbols/envelope-simple.svg +1 -0
- package/icons/material-symbols/eye-slash.svg +1 -0
- package/icons/material-symbols/eye.svg +1 -0
- package/icons/material-symbols/file-pdf.svg +1 -0
- package/icons/material-symbols/file-text.svg +1 -0
- package/icons/material-symbols/file-video.svg +1 -0
- package/icons/material-symbols/file-zip.svg +1 -0
- package/icons/material-symbols/flower-lotus.svg +1 -0
- package/icons/material-symbols/folder-simple.svg +1 -0
- package/icons/material-symbols/funnel.svg +1 -0
- package/icons/material-symbols/gauge.svg +1 -0
- package/icons/material-symbols/gear.svg +1 -0
- package/icons/material-symbols/github-logo.svg +1 -0
- package/icons/material-symbols/globe.svg +1 -0
- package/icons/material-symbols/google-logo.svg +1 -0
- package/icons/material-symbols/graduation-cap.svg +1 -0
- package/icons/material-symbols/hammer.svg +1 -0
- package/icons/material-symbols/hand-heart.svg +1 -0
- package/icons/material-symbols/handshake.svg +1 -0
- package/icons/material-symbols/hard-drives.svg +1 -0
- package/icons/material-symbols/heart.svg +1 -0
- package/icons/material-symbols/house.svg +1 -0
- package/icons/material-symbols/image-square.svg +1 -0
- package/icons/material-symbols/image.svg +1 -0
- package/icons/material-symbols/info.svg +1 -0
- package/icons/material-symbols/keyboard.svg +1 -0
- package/icons/material-symbols/leaf.svg +1 -0
- package/icons/material-symbols/lifebuoy.svg +1 -0
- package/icons/material-symbols/lightbulb.svg +1 -0
- package/icons/material-symbols/link-break.svg +1 -0
- package/icons/material-symbols/link-simple.svg +1 -0
- package/icons/material-symbols/list-bullets.svg +1 -0
- package/icons/material-symbols/list-checks.svg +1 -0
- package/icons/material-symbols/list-numbers.svg +1 -0
- package/icons/material-symbols/list.svg +1 -0
- package/icons/material-symbols/lock-simple-open.svg +1 -0
- package/icons/material-symbols/lock-simple.svg +1 -0
- package/icons/material-symbols/magnifying-glass.svg +1 -0
- package/icons/material-symbols/map-pin.svg +1 -0
- package/icons/material-symbols/map-trifold.svg +1 -0
- package/icons/material-symbols/medal.svg +1 -0
- package/icons/material-symbols/megaphone.svg +1 -0
- package/icons/material-symbols/minus.svg +1 -0
- package/icons/material-symbols/moon.svg +1 -0
- package/icons/material-symbols/newspaper.svg +1 -0
- package/icons/material-symbols/pack.json +29 -0
- package/icons/material-symbols/paint-brush-broad.svg +1 -0
- package/icons/material-symbols/palette.svg +1 -0
- package/icons/material-symbols/paper-plane-tilt.svg +1 -0
- package/icons/material-symbols/pencil-simple.svg +1 -0
- package/icons/material-symbols/phone.svg +1 -0
- package/icons/material-symbols/play-fill.svg +1 -0
- package/icons/material-symbols/plus.svg +1 -0
- package/icons/material-symbols/prohibit.svg +1 -0
- package/icons/material-symbols/puzzle-piece.svg +1 -0
- package/icons/material-symbols/question.svg +1 -0
- package/icons/material-symbols/quotes.svg +1 -0
- package/icons/material-symbols/rows.svg +1 -0
- package/icons/material-symbols/ruler.svg +1 -0
- package/icons/material-symbols/scan.svg +1 -0
- package/icons/material-symbols/shapes.svg +1 -0
- package/icons/material-symbols/share-network.svg +1 -0
- package/icons/material-symbols/shield-check.svg +1 -0
- package/icons/material-symbols/sidebar-simple.svg +1 -0
- package/icons/material-symbols/sign-out.svg +1 -0
- package/icons/material-symbols/signpost.svg +1 -0
- package/icons/material-symbols/sliders-horizontal.svg +1 -0
- package/icons/material-symbols/smiley.svg +1 -0
- package/icons/material-symbols/sparkle.svg +1 -0
- package/icons/material-symbols/square-half-bottom.svg +1 -0
- package/icons/material-symbols/square.svg +1 -0
- package/icons/material-symbols/squares-four.svg +1 -0
- package/icons/material-symbols/sticker.svg +1 -0
- package/icons/material-symbols/storefront.svg +1 -0
- package/icons/material-symbols/sun.svg +1 -0
- package/icons/material-symbols/swatches.svg +1 -0
- package/icons/material-symbols/tag.svg +1 -0
- package/icons/material-symbols/text-aa.svg +1 -0
- package/icons/material-symbols/text-align-left.svg +1 -0
- package/icons/material-symbols/text-b.svg +1 -0
- package/icons/material-symbols/text-h-one.svg +1 -0
- package/icons/material-symbols/text-h-three.svg +1 -0
- package/icons/material-symbols/text-h-two.svg +1 -0
- package/icons/material-symbols/text-italic.svg +1 -0
- package/icons/material-symbols/text-t.svg +1 -0
- package/icons/material-symbols/ticket.svg +1 -0
- package/icons/material-symbols/toggle-right.svg +1 -0
- package/icons/material-symbols/train.svg +1 -0
- package/icons/material-symbols/trash.svg +1 -0
- package/icons/material-symbols/upload-simple.svg +1 -0
- package/icons/material-symbols/user-circle.svg +1 -0
- package/icons/material-symbols/user.svg +1 -0
- package/icons/material-symbols/users-three.svg +1 -0
- package/icons/material-symbols/users.svg +1 -0
- package/icons/material-symbols/video-camera.svg +1 -0
- package/icons/material-symbols/warning-circle.svg +1 -0
- package/icons/material-symbols/warning.svg +1 -0
- package/icons/material-symbols/wave-sine.svg +1 -0
- package/icons/material-symbols/wheelchair.svg +1 -0
- package/icons/material-symbols/wrench.svg +1 -0
- package/icons/material-symbols/x.svg +1 -0
- package/icons/phosphor/LICENSE +21 -0
- package/icons/phosphor/address-book.svg +1 -0
- package/icons/phosphor/app-window.svg +1 -0
- package/icons/phosphor/arrow-counter-clockwise.svg +1 -0
- package/icons/phosphor/arrow-down.svg +1 -0
- package/icons/phosphor/arrow-left.svg +1 -0
- package/icons/phosphor/arrow-right.svg +1 -0
- package/icons/phosphor/arrow-square-out.svg +1 -0
- package/icons/phosphor/arrow-u-up-left.svg +1 -0
- package/icons/phosphor/arrow-u-up-right.svg +1 -0
- package/icons/phosphor/arrow-up-right.svg +1 -0
- package/icons/phosphor/arrow-up.svg +1 -0
- package/icons/phosphor/arrows-down-up.svg +1 -0
- package/icons/phosphor/arrows-in-line-horizontal.svg +1 -0
- package/icons/phosphor/arrows-out-line-horizontal.svg +1 -0
- package/icons/phosphor/article.svg +1 -0
- package/icons/phosphor/bank.svg +1 -0
- package/icons/phosphor/bell.svg +1 -0
- package/icons/phosphor/bicycle.svg +1 -0
- package/icons/phosphor/binoculars.svg +1 -0
- package/icons/phosphor/book-open-text.svg +1 -0
- package/icons/phosphor/book-open.svg +1 -0
- package/icons/phosphor/browser.svg +1 -0
- package/icons/phosphor/buildings.svg +1 -0
- package/icons/phosphor/bus.svg +1 -0
- package/icons/phosphor/calendar-blank.svg +1 -0
- package/icons/phosphor/calendar-check.svg +1 -0
- package/icons/phosphor/car.svg +1 -0
- package/icons/phosphor/cards.svg +1 -0
- package/icons/phosphor/caret-down.svg +1 -0
- package/icons/phosphor/caret-left.svg +1 -0
- package/icons/phosphor/caret-right.svg +1 -0
- package/icons/phosphor/caret-up.svg +1 -0
- package/icons/phosphor/certificate.svg +1 -0
- package/icons/phosphor/chart-line-up.svg +1 -0
- package/icons/phosphor/chats.svg +1 -0
- package/icons/phosphor/check-circle.svg +1 -0
- package/icons/phosphor/check.svg +1 -0
- package/icons/phosphor/circle-half.svg +1 -0
- package/icons/phosphor/circle.svg +1 -0
- package/icons/phosphor/clock-counter-clockwise.svg +1 -0
- package/icons/phosphor/clock.svg +1 -0
- package/icons/phosphor/code.svg +1 -0
- package/icons/phosphor/coffee.svg +1 -0
- package/icons/phosphor/columns.svg +1 -0
- package/icons/phosphor/cookie.svg +1 -0
- package/icons/phosphor/copy.svg +1 -0
- package/icons/phosphor/credit-card.svg +1 -0
- package/icons/phosphor/currency-circle-dollar.svg +1 -0
- package/icons/phosphor/cursor-click.svg +1 -0
- package/icons/phosphor/database.svg +1 -0
- package/icons/phosphor/desktop.svg +1 -0
- package/icons/phosphor/device-mobile.svg +1 -0
- package/icons/phosphor/door-open.svg +1 -0
- package/icons/phosphor/download-simple.svg +1 -0
- package/icons/phosphor/envelope-simple.svg +1 -0
- package/icons/phosphor/eye-slash.svg +1 -0
- package/icons/phosphor/eye.svg +1 -0
- package/icons/phosphor/file-pdf.svg +1 -0
- package/icons/phosphor/file-text.svg +1 -0
- package/icons/phosphor/file-video.svg +1 -0
- package/icons/phosphor/file-zip.svg +1 -0
- package/icons/phosphor/flower-lotus.svg +1 -0
- package/icons/phosphor/folder-simple.svg +1 -0
- package/icons/phosphor/funnel.svg +1 -0
- package/icons/phosphor/gauge.svg +1 -0
- package/icons/phosphor/gear.svg +1 -0
- package/icons/phosphor/github-logo.svg +1 -0
- package/icons/phosphor/globe.svg +1 -0
- package/icons/phosphor/google-logo.svg +1 -0
- package/icons/phosphor/graduation-cap.svg +1 -0
- package/icons/phosphor/hammer.svg +1 -0
- package/icons/phosphor/hand-heart.svg +1 -0
- package/icons/phosphor/handshake.svg +1 -0
- package/icons/phosphor/hard-drives.svg +1 -0
- package/icons/phosphor/heart.svg +1 -0
- package/icons/phosphor/house.svg +1 -0
- package/icons/phosphor/image-square.svg +1 -0
- package/icons/phosphor/image.svg +1 -0
- package/icons/phosphor/info.svg +1 -0
- package/icons/phosphor/keyboard.svg +1 -0
- package/icons/phosphor/leaf.svg +1 -0
- package/icons/phosphor/lifebuoy.svg +1 -0
- package/icons/phosphor/lightbulb.svg +1 -0
- package/icons/phosphor/link-break.svg +1 -0
- package/icons/phosphor/link-simple.svg +1 -0
- package/icons/phosphor/list-bullets.svg +1 -0
- package/icons/phosphor/list-checks.svg +1 -0
- package/icons/phosphor/list-numbers.svg +1 -0
- package/icons/phosphor/list.svg +1 -0
- package/icons/phosphor/lock-simple-open.svg +1 -0
- package/icons/phosphor/lock-simple.svg +1 -0
- package/icons/phosphor/magnifying-glass.svg +1 -0
- package/icons/phosphor/map-pin.svg +1 -0
- package/icons/phosphor/map-trifold.svg +1 -0
- package/icons/phosphor/medal.svg +1 -0
- package/icons/phosphor/megaphone.svg +1 -0
- package/icons/phosphor/minus.svg +1 -0
- package/icons/phosphor/moon.svg +1 -0
- package/icons/phosphor/newspaper.svg +1 -0
- package/icons/phosphor/pack.json +8 -0
- package/icons/phosphor/paint-brush-broad.svg +1 -0
- package/icons/phosphor/palette.svg +1 -0
- package/icons/phosphor/paper-plane-tilt.svg +1 -0
- package/icons/phosphor/pencil-simple.svg +1 -0
- package/icons/phosphor/phone.svg +1 -0
- package/icons/phosphor/play-fill.svg +1 -0
- package/icons/phosphor/plus.svg +1 -0
- package/icons/phosphor/prohibit.svg +1 -0
- package/icons/phosphor/puzzle-piece.svg +1 -0
- package/icons/phosphor/question.svg +1 -0
- package/icons/phosphor/quotes.svg +1 -0
- package/icons/phosphor/rows.svg +1 -0
- package/icons/phosphor/ruler.svg +1 -0
- package/icons/phosphor/scan.svg +1 -0
- package/icons/phosphor/shapes.svg +1 -0
- package/icons/phosphor/share-network.svg +1 -0
- package/icons/phosphor/shield-check.svg +1 -0
- package/icons/phosphor/sidebar-simple.svg +1 -0
- package/icons/phosphor/sign-out.svg +1 -0
- package/icons/phosphor/signpost.svg +1 -0
- package/icons/phosphor/sliders-horizontal.svg +1 -0
- package/icons/phosphor/smiley.svg +1 -0
- package/icons/phosphor/sparkle.svg +1 -0
- package/icons/phosphor/square-half-bottom.svg +1 -0
- package/icons/phosphor/square.svg +1 -0
- package/icons/phosphor/squares-four.svg +1 -0
- package/icons/phosphor/sticker.svg +1 -0
- package/icons/phosphor/storefront.svg +1 -0
- package/icons/phosphor/sun.svg +1 -0
- package/icons/phosphor/swatches.svg +1 -0
- package/icons/phosphor/tag.svg +1 -0
- package/icons/phosphor/text-aa.svg +1 -0
- package/icons/phosphor/text-align-left.svg +1 -0
- package/icons/phosphor/text-b.svg +1 -0
- package/icons/phosphor/text-h-one.svg +1 -0
- package/icons/phosphor/text-h-three.svg +1 -0
- package/icons/phosphor/text-h-two.svg +1 -0
- package/icons/phosphor/text-italic.svg +1 -0
- package/icons/phosphor/text-t.svg +1 -0
- package/icons/phosphor/ticket.svg +1 -0
- package/icons/phosphor/toggle-right.svg +1 -0
- package/icons/phosphor/train.svg +1 -0
- package/icons/phosphor/trash.svg +1 -0
- package/icons/phosphor/upload-simple.svg +1 -0
- package/icons/phosphor/user-circle.svg +1 -0
- package/icons/phosphor/user.svg +1 -0
- package/icons/phosphor/users-three.svg +1 -0
- package/icons/phosphor/users.svg +1 -0
- package/icons/phosphor/video-camera.svg +1 -0
- package/icons/phosphor/warning-circle.svg +1 -0
- package/icons/phosphor/warning.svg +1 -0
- package/icons/phosphor/wave-sine.svg +1 -0
- package/icons/phosphor/wheelchair.svg +1 -0
- package/icons/phosphor/wrench.svg +1 -0
- package/icons/phosphor/x.svg +1 -0
- package/lib/apps.d.mts +28 -0
- package/lib/apps.mjs +139 -0
- package/lib/blocks.d.mts +23 -0
- package/lib/blocks.mjs +90 -0
- package/lib/colors.ts +86 -0
- package/lib/contactBar.ts +18 -0
- package/lib/cookieCategories.d.mts +17 -0
- package/lib/cookieCategories.mjs +28 -0
- package/lib/crop.d.mts +34 -0
- package/lib/crop.mjs +83 -0
- package/lib/csp.d.mts +22 -0
- package/lib/csp.mjs +130 -0
- package/lib/cspSources.d.mts +24 -0
- package/lib/cspSources.mjs +60 -0
- package/lib/dynamic-colors.ts +426 -0
- package/lib/git.d.mts +24 -0
- package/lib/git.mjs +64 -0
- package/lib/hooks.ts +51 -0
- package/lib/inline.ts +20 -0
- package/lib/license-texts/Apache-2.0.txt +191 -0
- package/lib/license-texts/BSD-2-Clause.txt +24 -0
- package/lib/license-texts/BSD-3-Clause.txt +28 -0
- package/lib/license-texts/ISC.txt +15 -0
- package/lib/license-texts/MIT.txt +21 -0
- package/lib/licenses.d.mts +190 -0
- package/lib/licenses.mjs +683 -0
- package/lib/markdown.ts +42 -0
- package/lib/motion-schemes.d.mts +2 -0
- package/lib/motion-schemes.mjs +5 -0
- package/lib/motion.ts +131 -0
- package/lib/notice.d.mts +3 -0
- package/lib/notice.mjs +34 -0
- package/lib/scheduled.d.mts +15 -0
- package/lib/scheduled.mjs +37 -0
- package/lib/snackbar.ts +149 -0
- package/lib/status.d.mts +7 -0
- package/lib/status.mjs +18 -0
- package/lib/svg.d.mts +22 -0
- package/lib/svg.mjs +231 -0
- package/lib/text-colors.d.mts +11 -0
- package/lib/text-colors.mjs +43 -0
- package/lib/video-id.d.mts +14 -0
- package/lib/video-id.mjs +72 -0
- package/lib/video.d.mts +20 -0
- package/lib/video.mjs +91 -0
- package/package.json +131 -6
- package/pages/404.astro +10 -0
- package/pages/[...slug].astro +51 -0
- package/pages/_headers.txt +31 -0
- package/pages/blog/index.astro +10 -0
- package/pages/blog/kategori/[category].astro +18 -0
- package/pages/editor/code-images.json.js +25 -0
- package/pages/editor/index.astro +75 -0
- package/pages/editor/manifest.webmanifest.js +23 -0
- package/pages/editor/sw.js +33 -0
- package/pages/editor/sw.js.js +4 -0
- package/pages/editor/thumbnails.json.js +21 -0
- package/pages/editor/version.json.js +22 -0
- package/pages/headers.js +4 -0
- package/pages/index.astro +10 -0
- package/pages/ny.astro +6 -0
- package/pages/robots.txt.js +3 -0
- package/pages/rss.xml.js +20 -0
- package/scripts/app-screenshots.mjs +164 -0
- package/scripts/app-workers.mjs +6 -0
- package/scripts/bin.mjs +12 -0
- package/scripts/built-site.mjs +99 -0
- package/scripts/check-consent.mjs +113 -0
- package/scripts/check-content.mjs +149 -0
- package/scripts/check-demo.mjs +24 -0
- package/scripts/check-themes.mjs +45 -0
- package/scripts/check-worker.mjs +569 -0
- package/scripts/compare-html.mjs +81 -0
- package/scripts/demo-site.mjs +42 -0
- package/scripts/editor-icons.mjs +56 -0
- package/scripts/editor-roundtrip.mjs +134 -0
- package/scripts/editor-screens.mjs +101 -0
- package/scripts/git-env.mjs +9 -0
- package/scripts/icon-packs.mjs +151 -0
- package/scripts/link-counts.mjs +22 -0
- package/scripts/page-colors.mjs +42 -0
- package/scripts/releases.d.mts +10 -0
- package/scripts/releases.mjs +80 -0
- package/scripts/responsive-images.mjs +94 -0
- package/scripts/scheduled-list.mjs +15 -0
- package/scripts/theme-screenshots.mjs +88 -0
- package/scripts/types.mjs +21 -0
- package/scripts/video-thumbnails.mjs +89 -0
- package/scripts/worker-modules.mjs +34 -0
- package/site/appCookies.d.mts +9 -0
- package/site/appCookies.mjs +74 -0
- package/site/apps.ts +21 -0
- package/site/autolinks.d.mts +11 -0
- package/site/autolinks.mjs +200 -0
- package/site/blockScripts.ts +31 -0
- package/site/categories.ts +16 -0
- package/site/consent.ts +26 -0
- package/site/content.config.ts +84 -0
- package/site/content.ts +104 -0
- package/site/cookieList.ts +17 -0
- package/site/defineSite.d.mts +142 -0
- package/site/defineSite.mjs +136 -0
- package/site/directives/interact.d.mts +2 -0
- package/site/directives/interact.mjs +31 -0
- package/site/editor-version.d.mts +1 -0
- package/site/editor-version.mjs +26 -0
- package/site/fill.ts +13 -0
- package/site/format.ts +11 -0
- package/site/home.ts +66 -0
- package/site/iconPacks.d.mts +20 -0
- package/site/iconPacks.mjs +43 -0
- package/site/icons.d.mts +693 -0
- package/site/icons.mjs +605 -0
- package/site/images.d.mts +43 -0
- package/site/images.mjs +157 -0
- package/site/motion.ts +13 -0
- package/site/noindex.d.mts +6 -0
- package/site/noindex.mjs +21 -0
- package/site/pageColors.ts +110 -0
- package/site/popups.ts +98 -0
- package/site/schema.ts +189 -0
- package/site/sections.ts +3 -0
- package/site/seo.ts +28 -0
- package/site/site.ts +76 -0
- package/site/siteConfig.d.mts +1 -0
- package/site/siteConfig.mjs +8 -0
- package/site/siteCsp.d.mts +10 -0
- package/site/siteCsp.mjs +75 -0
- package/site/siteModules.d.mts +7 -0
- package/site/siteModules.mjs +27 -0
- package/site/standardPages.ts +59 -0
- package/site/testimonials.ts +48 -0
- package/site/text.ts +43 -0
- package/site/theme.d.mts +83 -0
- package/site/theme.mjs +472 -0
- package/site/tracking.client.ts +225 -0
- package/site/tracking.ts +9 -0
- package/src/ActionParts.tsx +205 -0
- package/src/Advanced.tsx +152 -0
- package/src/App.tsx +904 -0
- package/src/AppList.tsx +1345 -0
- package/src/BlockDialog.tsx +360 -0
- package/src/BlockParts.tsx +652 -0
- package/src/BlockSettings.tsx +533 -0
- package/src/BrandForm.tsx +164 -0
- package/src/Colors.tsx +476 -0
- package/src/CommunicationParts.tsx +199 -0
- package/src/CompanyForm.tsx +209 -0
- package/src/Consents.tsx +175 -0
- package/src/ContainmentParts.tsx +185 -0
- package/src/Crop.tsx +152 -0
- package/src/DataForm.tsx +434 -0
- package/src/Deleted.tsx +177 -0
- package/src/EntryForm.tsx +740 -0
- package/src/FileView.tsx +30 -0
- package/src/FormFrame.tsx +237 -0
- package/src/Keywords.tsx +225 -0
- package/src/Library.tsx +1563 -0
- package/src/Licenses.tsx +336 -0
- package/src/LinkSheet.tsx +138 -0
- package/src/MarkdownEditor.tsx +1075 -0
- package/src/Media.tsx +677 -0
- package/src/NavigationParts.tsx +350 -0
- package/src/NavigationRestParts.tsx +247 -0
- package/src/Notifications.tsx +59 -0
- package/src/PagePicker.tsx +136 -0
- package/src/Parts.tsx +991 -0
- package/src/PartsShown.tsx +168 -0
- package/src/PickerParts.tsx +183 -0
- package/src/Redirects.tsx +450 -0
- package/src/Screen.tsx +184 -0
- package/src/SectionsField.tsx +65 -0
- package/src/SelectionParts.tsx +134 -0
- package/src/SeoScores.tsx +184 -0
- package/src/Sheet.tsx +35 -0
- package/src/ThemeStore.tsx +376 -0
- package/src/Tokens.tsx +346 -0
- package/src/Versions.tsx +214 -0
- package/src/addresses.ts +75 -0
- package/src/allIcons.ts +7 -0
- package/src/appFiles.ts +29 -0
- package/src/apps.ts +281 -0
- package/src/ask.tsx +123 -0
- package/src/blocks.ts +251 -0
- package/src/brand.ts +153 -0
- package/src/cache.ts +42 -0
- package/src/check.ts +285 -0
- package/src/checkSite.ts +256 -0
- package/src/consents.ts +26 -0
- package/src/cookieList.ts +70 -0
- package/src/countries.ts +59 -0
- package/src/deploy.ts +155 -0
- package/src/editor.css +602 -0
- package/src/examples.ts +85 -0
- package/src/fields.tsx +942 -0
- package/src/frontmatter.d.mts +6 -0
- package/src/frontmatter.mjs +42 -0
- package/src/github.ts +376 -0
- package/src/i18n/da-DK/advanced.ts +28 -0
- package/src/i18n/da-DK/app.ts +71 -0
- package/src/i18n/da-DK/apps.ts +90 -0
- package/src/i18n/da-DK/blockSettings.ts +38 -0
- package/src/i18n/da-DK/blocks.ts +408 -0
- package/src/i18n/da-DK/check.ts +25 -0
- package/src/i18n/da-DK/colors.ts +64 -0
- package/src/i18n/da-DK/common.ts +118 -0
- package/src/i18n/da-DK/company.ts +24 -0
- package/src/i18n/da-DK/consents.ts +23 -0
- package/src/i18n/da-DK/forms.ts +116 -0
- package/src/i18n/da-DK/history.ts +35 -0
- package/src/i18n/da-DK/index.ts +49 -0
- package/src/i18n/da-DK/keywords.ts +19 -0
- package/src/i18n/da-DK/library.ts +135 -0
- package/src/i18n/da-DK/licenses.ts +25 -0
- package/src/i18n/da-DK/media.ts +79 -0
- package/src/i18n/da-DK/parts.ts +696 -0
- package/src/i18n/da-DK/preview.ts +13 -0
- package/src/i18n/da-DK/redirects.ts +51 -0
- package/src/i18n/da-DK/seo.ts +20 -0
- package/src/i18n/da-DK/text.ts +35 -0
- package/src/i18n/da-DK/theme.ts +41 -0
- package/src/i18n/en-US/advanced.ts +32 -0
- package/src/i18n/en-US/app.ts +84 -0
- package/src/i18n/en-US/apps.ts +134 -0
- package/src/i18n/en-US/blockSettings.ts +48 -0
- package/src/i18n/en-US/blocks.ts +441 -0
- package/src/i18n/en-US/check.ts +31 -0
- package/src/i18n/en-US/colors.ts +82 -0
- package/src/i18n/en-US/common.ts +144 -0
- package/src/i18n/en-US/company.ts +25 -0
- package/src/i18n/en-US/consents.ts +25 -0
- package/src/i18n/en-US/forms.ts +145 -0
- package/src/i18n/en-US/history.ts +43 -0
- package/src/i18n/en-US/index.ts +51 -0
- package/src/i18n/en-US/keywords.ts +24 -0
- package/src/i18n/en-US/library.ts +176 -0
- package/src/i18n/en-US/licenses.ts +36 -0
- package/src/i18n/en-US/media.ts +80 -0
- package/src/i18n/en-US/parts.ts +718 -0
- package/src/i18n/en-US/preview.ts +15 -0
- package/src/i18n/en-US/redirects.ts +66 -0
- package/src/i18n/en-US/seo.ts +26 -0
- package/src/i18n/en-US/text.ts +41 -0
- package/src/i18n/en-US/theme.ts +41 -0
- package/src/i18n/index.ts +124 -0
- package/src/icon.tsx +160 -0
- package/src/iconPacks.ts +89 -0
- package/src/images.ts +163 -0
- package/src/licenses.ts +79 -0
- package/src/markdown.d.mts +12 -0
- package/src/markdown.mjs +204 -0
- package/src/nav.ts +68 -0
- package/src/notFound.ts +28 -0
- package/src/notifications.ts +223 -0
- package/src/pagePreview.tsx +204 -0
- package/src/r2Caching.ts +55 -0
- package/src/redirects.ts +149 -0
- package/src/registry.tsx +188 -0
- package/src/schema.ts +417 -0
- package/src/sections.d.mts +45 -0
- package/src/sections.mjs +303 -0
- package/src/seo.ts +333 -0
- package/src/shared.ts +44 -0
- package/src/sidebar.ts +31 -0
- package/src/site.ts +317 -0
- package/src/standard/Block.astro +19 -0
- package/src/standard/BlockSite.astro +18 -0
- package/src/standard/accordion/index.tsx +47 -0
- package/src/standard/accordion/load.ts +23 -0
- package/src/standard/accordion/shared.ts +15 -0
- package/src/standard/accordion/view.ts +32 -0
- package/src/standard/buttons/fields.ts +98 -0
- package/src/standard/buttons/index.tsx +23 -0
- package/src/standard/buttons/load.ts +34 -0
- package/src/standard/buttons/shared.ts +12 -0
- package/src/standard/buttons/view.ts +30 -0
- package/src/standard/cards/index.tsx +71 -0
- package/src/standard/cards/load.ts +17 -0
- package/src/standard/cards/shared.ts +27 -0
- package/src/standard/cards/view.ts +29 -0
- package/src/standard/checklist/index.tsx +27 -0
- package/src/standard/checklist/load.ts +5 -0
- package/src/standard/checklist/view.ts +13 -0
- package/src/standard/cookies/index.tsx +17 -0
- package/src/standard/cookies/list.ts +56 -0
- package/src/standard/cookies/load.ts +11 -0
- package/src/standard/cookies/shared.ts +20 -0
- package/src/standard/cookies/view.ts +28 -0
- package/src/standard/faq/index.tsx +29 -0
- package/src/standard/faq/load.ts +14 -0
- package/src/standard/faq/view.ts +21 -0
- package/src/standard/head.ts +23 -0
- package/src/standard/hero/fields.ts +48 -0
- package/src/standard/hero/index.tsx +57 -0
- package/src/standard/hero/load.ts +23 -0
- package/src/standard/hero/view.ts +38 -0
- package/src/standard/links/index.tsx +41 -0
- package/src/standard/links/load.ts +17 -0
- package/src/standard/links/view.ts +25 -0
- package/src/standard/list/index.tsx +50 -0
- package/src/standard/list/load.ts +5 -0
- package/src/standard/list/view.ts +18 -0
- package/src/standard/load.ts +7 -0
- package/src/standard/notice/index.tsx +30 -0
- package/src/standard/notice/load.ts +16 -0
- package/src/standard/notice/view.ts +10 -0
- package/src/standard/points/index.tsx +42 -0
- package/src/standard/points/load.ts +5 -0
- package/src/standard/points/view.ts +12 -0
- package/src/standard/posts/index.tsx +132 -0
- package/src/standard/posts/load.ts +36 -0
- package/src/standard/posts/shared.ts +21 -0
- package/src/standard/posts/view.ts +63 -0
- package/src/standard/pricecards/index.tsx +57 -0
- package/src/standard/pricecards/load.ts +11 -0
- package/src/standard/pricecards/view.ts +50 -0
- package/src/standard/sharedTexts.ts +6 -0
- package/src/standard/site.ts +124 -0
- package/src/standard/siteLooks.ts +19 -0
- package/src/standard/sites.ts +13 -0
- package/src/standard/steps/index.tsx +28 -0
- package/src/standard/steps/load.ts +5 -0
- package/src/standard/steps/view.ts +11 -0
- package/src/standard/testimonials/index.tsx +87 -0
- package/src/standard/testimonials/list.ts +43 -0
- package/src/standard/testimonials/load.ts +58 -0
- package/src/standard/testimonials/shared.ts +13 -0
- package/src/standard/testimonials/view.ts +76 -0
- package/src/standard/themeBlocks.ts +13 -0
- package/src/standard/video/index.tsx +23 -0
- package/src/standard/video/load.ts +10 -0
- package/src/standard/video/view.ts +42 -0
- package/src/standard/view.tsx +105 -0
- package/src/standard/views.ts +37 -0
- package/src/store.ts +499 -0
- package/src/theme.ts +90 -0
- package/src/themeUi.ts +97 -0
- package/src/themes.ts +91 -0
- package/src/ui.tsx +3402 -0
- package/src/virtual.d.ts +5 -0
- package/src/when.ts +22 -0
- package/starter/.githooks/pre-push +4 -0
- package/starter/.nvmrc +1 -0
- package/starter/.prettierignore +11 -0
- package/starter/.prettierrc.json +7 -0
- package/starter/README.md +26 -0
- package/starter/astro.config.mjs +5 -0
- package/starter/package.json +67 -0
- package/starter/public/_redirects +1 -0
- package/starter/public/apple-touch-icon.png +0 -0
- package/starter/public/editor/apple-touch-icon.png +0 -0
- package/starter/public/editor/icon-192.png +0 -0
- package/starter/public/editor/icon-512.png +0 -0
- package/starter/public/editor/icon-maskable-512.png +0 -0
- package/starter/public/favicon.ico +0 -0
- package/starter/public/favicon.svg +1 -0
- package/starter/public/logo.svg +1 -0
- package/starter/src/content/pages/blog.md +8 -0
- package/starter/src/content/pages/forside.md +9 -0
- package/starter/src/content/pages/kontakt.md +7 -0
- package/starter/src/content/pages/privatlivspolitik.md +9 -0
- package/starter/src/content/pages/siden-findes-ikke.md +7 -0
- package/starter/src/content.config.ts +2 -0
- package/starter/src/data/apps.json +1 -0
- package/starter/src/data/block-settings.json +3 -0
- package/starter/src/data/blocks.json +1 -0
- package/starter/src/data/booking.json +6 -0
- package/starter/src/data/colors.json +70 -0
- package/starter/src/data/contact.json +8 -0
- package/starter/src/data/cookies.json +21 -0
- package/starter/src/data/csp.json +1 -0
- package/starter/src/data/footer.json +15 -0
- package/starter/src/data/internal-links.json +4 -0
- package/starter/src/data/menu.json +4 -0
- package/starter/src/data/mobile-menu.json +4 -0
- package/starter/src/data/old-addresses.json +1 -0
- package/starter/src/data/redirects.json +1 -0
- package/starter/src/data/standard-pages.json +8 -0
- package/starter/src/data/theme.json +1 -0
- package/starter/src/env.d.ts +2 -0
- package/starter/src/site/config.ts +13 -0
- package/starter/src/site/worker.js +14 -0
- package/starter/tsconfig.json +17 -0
- package/starter/worker/apps.js +6 -0
- package/starter/wrangler.jsonc +39 -0
- package/theme/LICENSE +21 -0
- package/theme/blocks/ArrowLink.tsx +14 -0
- package/theme/blocks/BlockHead.tsx +26 -0
- package/theme/blocks/Panel.tsx +32 -0
- package/theme/blocks/Person.tsx +26 -0
- package/theme/blocks/accordion/PageRow.tsx +25 -0
- package/theme/blocks/accordion/View.tsx +17 -0
- package/theme/blocks/buttons/Buttons.tsx +28 -0
- package/theme/blocks/buttons/View.tsx +12 -0
- package/theme/blocks/cards/LinkCards.tsx +72 -0
- package/theme/blocks/cards/View.tsx +20 -0
- package/theme/blocks/checklist/Checks.tsx +15 -0
- package/theme/blocks/checklist/View.tsx +14 -0
- package/theme/blocks/cookies/View.tsx +36 -0
- package/theme/blocks/faq/Accordion.tsx +34 -0
- package/theme/blocks/faq/View.tsx +16 -0
- package/theme/blocks/faq/script.ts +56 -0
- package/theme/blocks/hero/View.tsx +44 -0
- package/theme/blocks/links/LinkGroups.tsx +24 -0
- package/theme/blocks/links/View.tsx +17 -0
- package/theme/blocks/list/Rows.tsx +24 -0
- package/theme/blocks/list/View.tsx +14 -0
- package/theme/blocks/notice/View.tsx +13 -0
- package/theme/blocks/points/PointList.tsx +34 -0
- package/theme/blocks/points/View.tsx +14 -0
- package/theme/blocks/posts/View.tsx +26 -0
- package/theme/blocks/pricecards/View.tsx +40 -0
- package/theme/blocks/steps/StepList.tsx +23 -0
- package/theme/blocks/steps/View.tsx +14 -0
- package/theme/blocks/testimonials/List.tsx +46 -0
- package/theme/blocks/testimonials/Slider.tsx +44 -0
- package/theme/blocks/testimonials/Testimonial.tsx +124 -0
- package/theme/blocks/testimonials/View.tsx +21 -0
- package/theme/blocks/testimonials/script.ts +52 -0
- package/theme/blocks/video/View.tsx +41 -0
- package/theme/components/ActionBar.astro +63 -0
- package/theme/components/AppSlot.astro +21 -0
- package/theme/components/ArrowLink.tsx +21 -0
- package/theme/components/BlogIndex.astro +48 -0
- package/theme/components/BlogPosts.astro +18 -0
- package/theme/components/BlogPosts.tsx +45 -0
- package/theme/components/BookButton.tsx +22 -0
- package/theme/components/Container.astro +11 -0
- package/theme/components/CookieConsent.astro +121 -0
- package/theme/components/EndBlock.astro +21 -0
- package/theme/components/FontPreload.astro +6 -0
- package/theme/components/Footer.astro +85 -0
- package/theme/components/Header.astro +186 -0
- package/theme/components/Icon.astro +16 -0
- package/theme/components/Img.astro +29 -0
- package/theme/components/Img.tsx +33 -0
- package/theme/components/Inline.astro +11 -0
- package/theme/components/Logo.astro +23 -0
- package/theme/components/LogoMark.astro +10 -0
- package/theme/components/Markdown.astro +17 -0
- package/theme/components/MobileMenu.tsx +224 -0
- package/theme/components/MobileNav.astro +33 -0
- package/theme/components/PageHeader.astro +22 -0
- package/theme/components/PageSection.astro +24 -0
- package/theme/components/PhoneButton.tsx +11 -0
- package/theme/components/Popup.astro +145 -0
- package/theme/components/PopupButton.tsx +24 -0
- package/theme/components/PopupView.tsx +48 -0
- package/theme/components/PostList.tsx +58 -0
- package/theme/components/PostMeta.tsx +17 -0
- package/theme/components/RichText.astro +28 -0
- package/theme/components/Section.astro +29 -0
- package/theme/components/SectionHeading.tsx +8 -0
- package/theme/components/TextSection.astro +16 -0
- package/theme/components/home/PageBlock.astro +19 -0
- package/theme/components/home/Text.astro +13 -0
- package/theme/components/loadPopup.ts +22 -0
- package/theme/components/loadPosts.ts +16 -0
- package/theme/components/posts.ts +15 -0
- package/theme/editor/looks.mjs +7 -0
- package/theme/editor/previews.tsx +56 -0
- package/theme/layouts/Base.astro +273 -0
- package/theme/layouts/PageLayout.astro +126 -0
- package/theme/layouts/PostLayout.astro +137 -0
- package/theme/screenshots/1-front.jpg +0 -0
- package/theme/screenshots/2-front-dark.jpg +0 -0
- package/theme/screenshots/3-blog.jpg +0 -0
- package/theme/screenshots/4-contact.jpg +0 -0
- package/theme/scripts/motion.ts +29 -0
- package/theme/styles/base.css +320 -0
- package/theme/styles/blocks.css +1808 -0
- package/theme/styles/components.css +206 -0
- package/theme/styles/global.css +1302 -0
- package/theme/styles/ui.css +6544 -0
- package/theme/templates/Blog.astro +38 -0
- package/theme/templates/Category.astro +19 -0
- package/theme/templates/Home.astro +36 -0
- package/theme/templates/New.astro +68 -0
- package/theme/templates/NotFound.astro +117 -0
- package/theme/theme.json +28 -0
- package/theme/ui/Badge.tsx +45 -0
- package/theme/ui/BottomSheet.tsx +148 -0
- package/theme/ui/Button.tsx +79 -0
- package/theme/ui/ButtonGroup.tsx +31 -0
- package/theme/ui/Card.tsx +45 -0
- package/theme/ui/Carousel.tsx +124 -0
- package/theme/ui/Checkbox.tsx +59 -0
- package/theme/ui/Chip.tsx +101 -0
- package/theme/ui/ChipSet.tsx +27 -0
- package/theme/ui/DatePicker.tsx +149 -0
- package/theme/ui/DatePickerContainer.tsx +316 -0
- package/theme/ui/Dialog.tsx +100 -0
- package/theme/ui/Divider.tsx +27 -0
- package/theme/ui/ExposedDropdownMenu.tsx +77 -0
- package/theme/ui/Fab.tsx +88 -0
- package/theme/ui/FabMenu.tsx +67 -0
- package/theme/ui/IconButton.tsx +78 -0
- package/theme/ui/List.tsx +287 -0
- package/theme/ui/LoadingIndicator.tsx +29 -0
- package/theme/ui/Menu.tsx +278 -0
- package/theme/ui/NavigationBar.tsx +62 -0
- package/theme/ui/NavigationDrawer.tsx +80 -0
- package/theme/ui/NavigationRail.tsx +154 -0
- package/theme/ui/ProgressIndicator.tsx +162 -0
- package/theme/ui/RadioButton.tsx +50 -0
- package/theme/ui/Search.tsx +129 -0
- package/theme/ui/SegmentedButton.tsx +49 -0
- package/theme/ui/SideSheet.tsx +54 -0
- package/theme/ui/Slider.tsx +155 -0
- package/theme/ui/Snackbar.tsx +67 -0
- package/theme/ui/SplitButton.tsx +67 -0
- package/theme/ui/Switch.tsx +60 -0
- package/theme/ui/Tabs.tsx +114 -0
- package/theme/ui/TextField.tsx +278 -0
- package/theme/ui/TimePicker.tsx +128 -0
- package/theme/ui/TimePickerContainer.tsx +234 -0
- package/theme/ui/Toolbar.tsx +67 -0
- package/theme/ui/Tooltip.tsx +58 -0
- package/theme/ui/TopAppBar.tsx +77 -0
- package/theme/ui/bottomSheet.script.ts +8 -0
- package/theme/ui/bottomSheet.ts +211 -0
- package/theme/ui/carousel.script.ts +28 -0
- package/theme/ui/carouselLayout.ts +229 -0
- package/theme/ui/checkbox.script.ts +8 -0
- package/theme/ui/datePicker.script.ts +302 -0
- package/theme/ui/dates.ts +185 -0
- package/theme/ui/dialog.ts +48 -0
- package/theme/ui/dropdownMenu.script.ts +166 -0
- package/theme/ui/fabMenu.script.ts +57 -0
- package/theme/ui/navigationRail.script.ts +47 -0
- package/theme/ui/ripple.script.ts +159 -0
- package/theme/ui/search.script.ts +92 -0
- package/theme/ui/sideSheet.ts +48 -0
- package/theme/ui/slider.script.ts +12 -0
- package/theme/ui/sliderUpdate.ts +52 -0
- package/theme/ui/snackbar.script.ts +16 -0
- package/theme/ui/splitButton.script.ts +63 -0
- package/theme/ui/tabs.script.ts +48 -0
- package/theme/ui/textField.script.ts +17 -0
- package/theme/ui/textFieldUpdate.ts +44 -0
- package/theme/ui/timePicker.script.ts +248 -0
- package/theme/ui/toolbar.script.ts +43 -0
- package/theme/ui/tooltip.script.ts +68 -0
- package/worker/consent.js +141 -0
- package/worker/editor-github.js +167 -0
- package/worker/editor-host.js +74 -0
- package/worker/files.js +401 -0
- package/worker/github.js +36 -0
- package/worker/index.js +184 -0
- package/worker/not-found.js +97 -0
- package/worker/oauth.js +95 -0
- package/worker/publish.js +52 -0
- package/worker/push.js +270 -0
- package/worker/r2-status.js +50 -0
- package/worker/session.js +58 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// A promotion saved as a contact bar (the kind "Kontaktlinje", `contactbar`: the buttons "Ring" and "Kontakt" at the
|
|
2
|
+
// bottom of a phone's screen, with the fields `phoneLabel`, `contactLabel` and `contactPage`), which is now an action
|
|
3
|
+
// bar ("Handlingslinje", `bookingbar`) with those two buttons: the first calls, the second goes to its page (without
|
|
4
|
+
// one, the contact page, `contactPath`), each with its own text. The site reads it so (site/popups.ts), and the editor
|
|
5
|
+
// too (`upgrade` on "Promovering" in defaults/collections.ts), which saves it so. Any other promotion is as it is.
|
|
6
|
+
export function fromContactBar(data: Record<string, unknown>, contactPath: string): Record<string, unknown> {
|
|
7
|
+
if (data.type !== 'contactbar') return data;
|
|
8
|
+
const { phoneLabel, contactLabel, contactPage, ...rest } = data;
|
|
9
|
+
const text = (value: unknown) => (typeof value === 'string' ? value.trim() : '');
|
|
10
|
+
return {
|
|
11
|
+
...rest,
|
|
12
|
+
type: 'bookingbar',
|
|
13
|
+
buttons: [
|
|
14
|
+
{ label: text(phoneLabel), to: 'phone' },
|
|
15
|
+
{ label: text(contactLabel), to: 'page', href: text(contactPage) || contactPath },
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** @typedef {'necessary' | 'preferences' | 'statistics' | 'marketing' | 'targeting'} CookieCategory
|
|
2
|
+
* @typedef {Exclude<CookieCategory, 'necessary'>} ConsentCategory One the visitor says yes or no to. */
|
|
3
|
+
/** The categories, in the order the cookie banner and the cookie list show them. @type {readonly CookieCategory[]} */
|
|
4
|
+
export const COOKIE_CATEGORIES: readonly CookieCategory[];
|
|
5
|
+
/** Those the visitor says yes or no to in the cookie banner. @type {readonly ConsentCategory[]} */
|
|
6
|
+
export const CONSENT_CATEGORIES: readonly ConsentCategory[];
|
|
7
|
+
export function isCookieCategory(value: unknown): value is CookieCategory;
|
|
8
|
+
export function isConsentCategory(value: unknown): value is ConsentCategory;
|
|
9
|
+
export function categoryOf({ category, when }: {
|
|
10
|
+
category?: unknown;
|
|
11
|
+
when?: unknown;
|
|
12
|
+
}): CookieCategory;
|
|
13
|
+
export type CookieCategory = "necessary" | "preferences" | "statistics" | "marketing" | "targeting";
|
|
14
|
+
/**
|
|
15
|
+
* One the visitor says yes or no to.
|
|
16
|
+
*/
|
|
17
|
+
export type ConsentCategory = Exclude<CookieCategory, "necessary">;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// The categories of cookies (and of what else a site stores in a visitor's browser), as a site declares them: each
|
|
2
|
+
// cookie in the cookie list and in an app's cookies.json has one (`category`). Necessary ones are set without asking;
|
|
3
|
+
// the visitor says yes or no to each of the others in the cookie banner, one at a time, so a yes to statistics is not
|
|
4
|
+
// a yes to marketing. An app that loads something that sets cookies starts after a yes to its categories (`cookies` in
|
|
5
|
+
// its index.ts, afterConsent in the site's tracking script). Marketing is measuring whether the ads work; targeted ads
|
|
6
|
+
// (`targeting`: using the visit to show the visitor ads, remarketing) are a yes of their own, asked about only when an
|
|
7
|
+
// app is set to use them. No dependencies, so the site's scripts, its Worker and its checks can use it.
|
|
8
|
+
|
|
9
|
+
/** @typedef {'necessary' | 'preferences' | 'statistics' | 'marketing' | 'targeting'} CookieCategory
|
|
10
|
+
* @typedef {Exclude<CookieCategory, 'necessary'>} ConsentCategory One the visitor says yes or no to. */
|
|
11
|
+
|
|
12
|
+
/** The categories, in the order the cookie banner and the cookie list show them. @type {readonly CookieCategory[]} */
|
|
13
|
+
export const COOKIE_CATEGORIES = ['necessary', 'preferences', 'statistics', 'marketing', 'targeting'];
|
|
14
|
+
|
|
15
|
+
/** Those the visitor says yes or no to in the cookie banner. @type {readonly ConsentCategory[]} */
|
|
16
|
+
export const CONSENT_CATEGORIES = ['preferences', 'statistics', 'marketing', 'targeting'];
|
|
17
|
+
|
|
18
|
+
/** Whether a value is a category. @param {unknown} value @returns {value is CookieCategory} */
|
|
19
|
+
export const isCookieCategory = (value) => COOKIE_CATEGORIES.includes(/** @type {CookieCategory} */ (value));
|
|
20
|
+
|
|
21
|
+
/** Whether a value is a category the visitor says yes or no to. @param {unknown} value @returns {value is ConsentCategory} */
|
|
22
|
+
export const isConsentCategory = (value) => CONSENT_CATEGORIES.includes(/** @type {ConsentCategory} */ (value));
|
|
23
|
+
|
|
24
|
+
/** A cookie's category as its file has it (one in the cookie list, or in an app's cookies.json); one from before the
|
|
25
|
+
* categories has `when` instead ('always': set also without a yes, so necessary), and one without either (npm run
|
|
26
|
+
* check:content says so) counts as marketing, the one a visitor says yes to last.
|
|
27
|
+
* @param {{ category?: unknown, when?: unknown }} cookie @returns {CookieCategory} */
|
|
28
|
+
export const categoryOf = ({ category, when }) => (isCookieCategory(category) ? category : when === 'always' ? 'necessary' : 'marketing');
|
package/lib/crop.d.mts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Width divided by height of a format ('2:3'), or 0 when it is none. */
|
|
2
|
+
export function ratioOf(format: any): number;
|
|
3
|
+
/** Whether a picture's address is marked decorative, and the address without the mark (else as it is). */
|
|
4
|
+
export function decorativeOf(address: any): {
|
|
5
|
+
address: string;
|
|
6
|
+
decorative: boolean;
|
|
7
|
+
};
|
|
8
|
+
/** The address marked decorative, or without the mark. */
|
|
9
|
+
export function decorated(address: any, decorative: any): string;
|
|
10
|
+
/** The picture's own address, its format and crop ([left, top, width, height]) when it has them (an
|
|
11
|
+
* address without a format is left as it is), and whether it is decorative. */
|
|
12
|
+
export function splitPicture(address: any): {
|
|
13
|
+
src: string;
|
|
14
|
+
format: string | null;
|
|
15
|
+
crop: number[] | null;
|
|
16
|
+
decorative: boolean;
|
|
17
|
+
};
|
|
18
|
+
/** The address with the format and crop after it (only the address without a format), marked decorative when it is. */
|
|
19
|
+
export function joinPicture(src: any, format: any, crop: any, decorative?: boolean): string;
|
|
20
|
+
/** The largest part of a picture (aspect: its width divided by its height) in a format, in the middle. */
|
|
21
|
+
export function middleCrop(aspect: any, format: any): number[] | null;
|
|
22
|
+
/** The styles of a cut picture: its box (in the format) and the picture in it, as CSS text; how much
|
|
23
|
+
* wider than the box the picture is (for its sizes). Without a crop the picture is its own box. */
|
|
24
|
+
export function cropStyle(format: any, crop: any): {
|
|
25
|
+
box: null;
|
|
26
|
+
img: string;
|
|
27
|
+
scale: number;
|
|
28
|
+
} | {
|
|
29
|
+
box: string;
|
|
30
|
+
img: string;
|
|
31
|
+
scale: number;
|
|
32
|
+
};
|
|
33
|
+
/** The formats a picture can be cut to, as width:height. */
|
|
34
|
+
export const FORMATS: string[];
|
package/lib/crop.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// A picture in the text cut to a format (its sides, as 2:3) in the editor (Crop.tsx), written after
|
|
2
|
+
// its address in the Markdown, so the file and the picture stay as they are:
|
|
3
|
+
// 
|
|
4
|
+
// crop: the part of the picture that is shown, in percent of its width and height: from the left,
|
|
5
|
+
// from the top, its width and its height. Shown so on the site (rehypeResponsiveImages, images.mjs)
|
|
6
|
+
// and on its card in the editor (MarkdownEditor.tsx). Without crop the middle of it is shown.
|
|
7
|
+
// A decorative picture ("Dekorativt billede": screen readers skip it) is marked there too, alone or after
|
|
8
|
+
// the format and crop, and drawn with alt="" on the site:
|
|
9
|
+
// 
|
|
10
|
+
// The mark is part of the address, so Milkdown keeps it as it is, and a visitor never sees it.
|
|
11
|
+
|
|
12
|
+
/** The formats a picture can be cut to, as width:height. */
|
|
13
|
+
export const FORMATS = ['1:1', '4:5', '3:4', '2:3', '3:2', '4:3', '16:9'];
|
|
14
|
+
|
|
15
|
+
const FORMAT = /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/;
|
|
16
|
+
|
|
17
|
+
/** Width divided by height of a format ('2:3'), or 0 when it is none. */
|
|
18
|
+
export function ratioOf(format) {
|
|
19
|
+
const m = String(format ?? '').match(FORMAT);
|
|
20
|
+
return m && +m[1] > 0 && +m[2] > 0 ? +m[1] / +m[2] : 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DECORATIVE = 'decorative';
|
|
24
|
+
|
|
25
|
+
/** Whether a picture's address is marked decorative, and the address without the mark (else as it is). */
|
|
26
|
+
export function decorativeOf(address) {
|
|
27
|
+
const text = String(address);
|
|
28
|
+
const at = text.indexOf('#');
|
|
29
|
+
const params = at < 0 ? [] : text.slice(at + 1).split('&');
|
|
30
|
+
const rest = params.filter((p) => p !== DECORATIVE);
|
|
31
|
+
if (rest.length === params.length) return { address: text, decorative: false };
|
|
32
|
+
return { address: text.slice(0, at) + (rest.length ? `#${rest.join('&')}` : ''), decorative: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The address marked decorative, or without the mark. */
|
|
36
|
+
export function decorated(address, decorative) {
|
|
37
|
+
const plain = decorativeOf(address).address;
|
|
38
|
+
return decorative ? `${plain}${plain.includes('#') ? '&' : '#'}${DECORATIVE}` : plain;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The picture's own address, its format and crop ([left, top, width, height]) when it has them (an
|
|
42
|
+
* address without a format is left as it is), and whether it is decorative. */
|
|
43
|
+
export function splitPicture(address) {
|
|
44
|
+
const { address: own, decorative } = decorativeOf(address);
|
|
45
|
+
const [src, fragment = ''] = own.split('#');
|
|
46
|
+
const params = new URLSearchParams(fragment);
|
|
47
|
+
const format = params.get('format');
|
|
48
|
+
if (!ratioOf(format)) return { src: own, format: '', crop: null, decorative };
|
|
49
|
+
const crop = (params.get('crop') ?? '').split(',').map(Number);
|
|
50
|
+
const fits = crop.length === 4 && crop.every((n) => Number.isFinite(n) && n >= 0 && n <= 100) && crop[2] > 0 && crop[3] > 0;
|
|
51
|
+
return { src, format, crop: fits ? crop : null, decorative };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const round = (n) => Math.round(n * 10) / 10;
|
|
55
|
+
|
|
56
|
+
/** The address with the format and crop after it (only the address without a format), marked decorative when it is. */
|
|
57
|
+
export function joinPicture(src, format, crop, decorative = false) {
|
|
58
|
+
if (!ratioOf(format)) return decorated(src, decorative);
|
|
59
|
+
return decorated(`${src}#format=${format}${crop ? `&crop=${crop.map(round).join(',')}` : ''}`, decorative);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The largest part of a picture (aspect: its width divided by its height) in a format, in the middle. */
|
|
63
|
+
export function middleCrop(aspect, format) {
|
|
64
|
+
const ratio = ratioOf(format);
|
|
65
|
+
if (!ratio || !aspect) return null;
|
|
66
|
+
const [w, h] = aspect > ratio ? [(ratio / aspect) * 100, 100] : [100, (aspect / ratio) * 100];
|
|
67
|
+
return [(100 - w) / 2, (100 - h) / 2, w, h];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The styles of a cut picture: its box (in the format) and the picture in it, as CSS text; how much
|
|
71
|
+
* wider than the box the picture is (for its sizes). Without a crop the picture is its own box. */
|
|
72
|
+
export function cropStyle(format, crop) {
|
|
73
|
+
const [a, b] = format.split(':');
|
|
74
|
+
const aspect = `aspect-ratio: ${a} / ${b}`;
|
|
75
|
+
if (!crop) return { box: null, img: `${aspect}; object-fit: cover`, scale: 1 };
|
|
76
|
+
const [x, y, w] = crop;
|
|
77
|
+
return {
|
|
78
|
+
box: aspect,
|
|
79
|
+
// The picture's width in percent of the box's, moved by percent of its own size.
|
|
80
|
+
img: `width: ${Math.round((100 / w) * 100 * 100) / 100}%; transform: translate(${-round(x)}%, ${-round(y)}%)`,
|
|
81
|
+
scale: 100 / w,
|
|
82
|
+
};
|
|
83
|
+
}
|
package/lib/csp.d.mts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The hash of each inline script that runs in a page, as CSP writes it. @param {string} html */
|
|
2
|
+
export function scriptHashes(html: string): any[];
|
|
3
|
+
/** The other addresses (origins) a page's HTML loads its scripts, frames and pictures from.
|
|
4
|
+
* @param {string} html @returns {Required<Pick<Sources, 'script' | 'frame' | 'img'>>} */
|
|
5
|
+
export function pageOrigins(html: string): Required<Pick<Sources, "script" | "frame" | "img">>;
|
|
6
|
+
/** A page's policy: on the site, or the editor's (`editor`). @param {string} html
|
|
7
|
+
* @param {{ editor?: boolean, site?: Sources, images?: string[] }} options */
|
|
8
|
+
export function pagePolicy(html: string, { editor, site, images }: {
|
|
9
|
+
editor?: boolean;
|
|
10
|
+
site?: Sources;
|
|
11
|
+
images?: string[];
|
|
12
|
+
}): string;
|
|
13
|
+
/** The policy as the first thing in a page's <head> (after its charset, which must stay in its first bytes).
|
|
14
|
+
* @param {string} html @param {string} value */
|
|
15
|
+
export function withPolicy(html: string, value: string): string | undefined;
|
|
16
|
+
/** The Astro integration that writes the policy into every page built (packages/editor/astro.mjs).
|
|
17
|
+
* @param {{ site?: Sources }} [options] @returns {import('astro').AstroIntegration} */
|
|
18
|
+
export function csp({ site }?: {
|
|
19
|
+
site?: Sources;
|
|
20
|
+
}): import("astro").AstroIntegration;
|
|
21
|
+
export type Sources = import("./cspSources.mjs").Sources;
|
|
22
|
+
export { allows, isSource, joined, KINDS, kindOf, sourcesProblems } from "./cspSources.mjs";
|
package/lib/csp.mjs
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// The Content Security Policy of every page the site builds, as a <meta http-equiv> at the top of its <head>: the
|
|
2
|
+
// browser runs only the scripts the build put in the page (each inline one by its hash) and those from the site's own
|
|
3
|
+
// address, and loads from elsewhere only what is allowed below. So HTML that gets into a page after the build (in the
|
|
4
|
+
// editor's preview, by a fault in a script) can run nothing. What the build wrote is trusted: the addresses of the
|
|
5
|
+
// scripts, frames and pictures in a page (HTML written in a page's text, an embed) are allowed on that page.
|
|
6
|
+
// Allowed everywhere besides: the videos (video.mjs plays them from YouTube's and Vimeo's players), what the site gives
|
|
7
|
+
// (`site`: its measuring, packages/editor/site/siteCsp.mjs) and what each app installed gives (`src/apps/<app>/csp.json`, in
|
|
8
|
+
// packages/editor/site/siteCsp.mjs too), with the addresses allowed in the editor (the site's own, config.csp). The editor's page (/editor/) has its own, stricter policy: GitHub, the stills of the videos,
|
|
9
|
+
// and nothing of the site's or the apps'. Styles may be inline (the pages have style="…" and React sets them).
|
|
10
|
+
// A meta tag cannot say who may frame a page; X-Frame-Options in packages/editor/pages/_headers.txt does.
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { joined } from './cspSources.mjs';
|
|
16
|
+
|
|
17
|
+
// What a site or an app may add to the policy, and the checks of it, without Node, so the site's Worker can use them too.
|
|
18
|
+
export { allows, isSource, joined, KINDS, kindOf, sourcesProblems } from './cspSources.mjs';
|
|
19
|
+
|
|
20
|
+
/** @typedef {import('./cspSources.mjs').Sources} Sources */
|
|
21
|
+
|
|
22
|
+
/** What the videos need on the site: their players, put in place when a video is played (video.mjs). */
|
|
23
|
+
const VIDEOS = { frame: ['https://www.youtube-nocookie.com', 'https://player.vimeo.com'] };
|
|
24
|
+
|
|
25
|
+
/** What the editor needs (GitHub it asks through the site's Worker, on its own address: github.ts): Vimeo's details of a video and the stills of the videos
|
|
26
|
+
* (standard/video/view.ts), and what it makes in the browser (pictures, files) as blob: and data:. */
|
|
27
|
+
const EDITOR = {
|
|
28
|
+
img: ['blob:', 'https://i.ytimg.com', 'https://i.vimeocdn.com'],
|
|
29
|
+
connect: ['blob:', 'data:', 'https://vimeo.com'],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// An inline script that runs (not JSON, JSON-LD or a template).
|
|
33
|
+
const RUNS = /^(module|text\/javascript|application\/javascript)?$/i;
|
|
34
|
+
const SCRIPT = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
35
|
+
const attr = (attrs, name) => new RegExp(`\\s${name}\\s*=\\s*"([^"]*)"`, 'i').exec(attrs)?.[1];
|
|
36
|
+
|
|
37
|
+
/** The hash of each inline script that runs in a page, as CSP writes it. @param {string} html */
|
|
38
|
+
export function scriptHashes(html) {
|
|
39
|
+
const hashes = new Set();
|
|
40
|
+
for (const [, attrs, code] of html.matchAll(SCRIPT))
|
|
41
|
+
if (attr(attrs, 'src') === undefined && RUNS.test(attr(attrs, 'type') ?? ''))
|
|
42
|
+
hashes.add(`'sha256-${createHash('sha256').update(code, 'utf8').digest('base64')}'`);
|
|
43
|
+
return [...hashes];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The other addresses (origins) a page's HTML loads its scripts, frames and pictures from.
|
|
47
|
+
* @param {string} html @returns {Required<Pick<Sources, 'script' | 'frame' | 'img'>>} */
|
|
48
|
+
export function pageOrigins(html) {
|
|
49
|
+
const found = { script: new Set(), frame: new Set(), img: new Set() };
|
|
50
|
+
const add = (set, url) => {
|
|
51
|
+
try {
|
|
52
|
+
const { protocol, origin } = new URL(url.replace(/&/g, '&'));
|
|
53
|
+
if (protocol === 'https:' || protocol === 'http:') set.add(origin);
|
|
54
|
+
} catch {}
|
|
55
|
+
};
|
|
56
|
+
for (const [, attrs] of html.matchAll(/<script\b([^>]*)>/gi)) add(found.script, attr(attrs, 'src') ?? '');
|
|
57
|
+
for (const [, attrs] of html.matchAll(/<iframe\b([^>]*)>/gi)) add(found.frame, attr(attrs, 'src') ?? attr(attrs, 'data-src') ?? '');
|
|
58
|
+
for (const [, attrs] of html.matchAll(/<(?:img|source)\b([^>]*)>/gi)) {
|
|
59
|
+
add(found.img, attr(attrs, 'src') ?? '');
|
|
60
|
+
for (const part of (attr(attrs, 'srcset') ?? '').split(',')) add(found.img, part.trim().split(/\s+/)[0]);
|
|
61
|
+
}
|
|
62
|
+
return { script: [...found.script], frame: [...found.frame], img: [...found.img] };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The policy, from its directives (an empty one is left out, `none` when it must say so).
|
|
66
|
+
* @param {Record<string, (string | undefined)[]>} directives */
|
|
67
|
+
function policy(directives) {
|
|
68
|
+
return Object.entries(directives)
|
|
69
|
+
.map(([name, values]) => [name, [...new Set(values.filter(Boolean))]])
|
|
70
|
+
.map(([name, values]) => `${name} ${values.length ? values.join(' ') : "'none'"}`)
|
|
71
|
+
.join('; ');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A page's policy: on the site, or the editor's (`editor`). @param {string} html
|
|
75
|
+
* @param {{ editor?: boolean, site?: Sources, images?: string[] }} options */
|
|
76
|
+
export function pagePolicy(html, { editor = false, site = {}, images = [] }) {
|
|
77
|
+
const page = pageOrigins(html);
|
|
78
|
+
const s = editor ? joined(EDITOR, { img: images }) : joined(VIDEOS, site, page);
|
|
79
|
+
return policy({
|
|
80
|
+
'default-src': ["'self'"],
|
|
81
|
+
'script-src': ["'self'", ...scriptHashes(html), ...s.script],
|
|
82
|
+
'style-src': ["'self'", "'unsafe-inline'", ...s.style],
|
|
83
|
+
'img-src': ["'self'", 'data:', ...s.img],
|
|
84
|
+
'font-src': ["'self'", 'data:'],
|
|
85
|
+
'connect-src': ["'self'", ...s.connect],
|
|
86
|
+
'media-src': ["'self'", ...(editor ? ['blob:', 'data:'] : [])],
|
|
87
|
+
// The editor shows a page in a frame of its own (srcdoc, PageFrame in ui.tsx).
|
|
88
|
+
'frame-src': editor ? ["'self'", 'blob:', 'data:'] : s.frame,
|
|
89
|
+
'worker-src': ["'self'"],
|
|
90
|
+
'form-action': ["'self'", ...s.form],
|
|
91
|
+
'object-src': [],
|
|
92
|
+
'base-uri': ["'self'"],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The policy as the first thing in a page's <head> (after its charset, which must stay in its first bytes).
|
|
97
|
+
* @param {string} html @param {string} value */
|
|
98
|
+
export function withPolicy(html, value) {
|
|
99
|
+
const meta = `<meta http-equiv="Content-Security-Policy" content="${value.replace(/&/g, '&').replace(/"/g, '"')}">`;
|
|
100
|
+
const at = /<meta\s+charset\s*=\s*"?[\w-]+"?\s*\/?>/i.exec(html) ?? /<head\b[^>]*>/i.exec(html);
|
|
101
|
+
if (!at) return undefined;
|
|
102
|
+
const end = at.index + at[0].length;
|
|
103
|
+
return html.slice(0, end) + meta + html.slice(end);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The Astro integration that writes the policy into every page built (packages/editor/astro.mjs).
|
|
107
|
+
* @param {{ site?: Sources }} [options] @returns {import('astro').AstroIntegration} */
|
|
108
|
+
export function csp({ site = {} } = {}) {
|
|
109
|
+
return {
|
|
110
|
+
name: 'content-security-policy',
|
|
111
|
+
hooks: {
|
|
112
|
+
'astro:build:done': ({ dir, logger }) => {
|
|
113
|
+
const root = fileURLToPath(dir);
|
|
114
|
+
const files = readdirSync(root, { recursive: true, encoding: 'utf8' }).filter((f) => f.endsWith('.html'));
|
|
115
|
+
const pages = files.map((file) => ({ file, html: readFileSync(join(root, file), 'utf8') }));
|
|
116
|
+
// The editor shows the site's pages in its preview, with their pictures.
|
|
117
|
+
const images = [...new Set(pages.flatMap((p) => pageOrigins(p.html).img))];
|
|
118
|
+
const missing = [];
|
|
119
|
+
for (const { file, html } of pages) {
|
|
120
|
+
const editor = file.split(/[\\/]/)[0] === 'editor';
|
|
121
|
+
const out = withPolicy(html, pagePolicy(html, { editor, site, images }));
|
|
122
|
+
if (out) writeFileSync(join(root, file), out);
|
|
123
|
+
else missing.push(file);
|
|
124
|
+
}
|
|
125
|
+
if (missing.length) throw new Error(`Content-Security-Policy: ingen <head> i ${missing.join(', ')}`);
|
|
126
|
+
logger.info(`${pages.length} sider har en Content-Security-Policy.`);
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The kind of sources a directive of the policy takes, as a browser names it when it stops something
|
|
2
|
+
* (securitypolicyviolation's violatedDirective: img-src, script-src-elem …); undefined for one a site or an app cannot
|
|
3
|
+
* add to (font-src, media-src …). @param {string} directive */
|
|
4
|
+
export function kindOf(directive: string): string | undefined;
|
|
5
|
+
/** What is wrong with sources written in a file (an app's csp.json), each as a sentence; none when they are right.
|
|
6
|
+
* @param {unknown} sources @param {string} file */
|
|
7
|
+
export function sourcesProblems(sources: unknown, file: string): string[];
|
|
8
|
+
/** Whether the sources allow an address (its origin, or one under a `*.` domain), as any kind or as `kind`.
|
|
9
|
+
* @param {Sources} sources @param {string} url @param {string} [kind] */
|
|
10
|
+
export function allows(sources: Sources, url: string, kind?: string): any;
|
|
11
|
+
/** The sources together. @param {Sources[]} all @returns {Required<Sources>} */
|
|
12
|
+
export function joined(...all: Sources[]): Required<Sources>;
|
|
13
|
+
/** @typedef {{ script?: string[], style?: string[], img?: string[], connect?: string[], frame?: string[], form?: string[] }} Sources */
|
|
14
|
+
/** The kinds of sources a site or an app can add (a Sources' keys, csp.json's). */
|
|
15
|
+
export const KINDS: string[];
|
|
16
|
+
export function isSource(source: unknown): boolean;
|
|
17
|
+
export type Sources = {
|
|
18
|
+
script?: string[];
|
|
19
|
+
style?: string[];
|
|
20
|
+
img?: string[];
|
|
21
|
+
connect?: string[];
|
|
22
|
+
frame?: string[];
|
|
23
|
+
form?: string[];
|
|
24
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// What a site or an app may add to the pages' Content Security Policy (csp.mjs), by kind (an app's csp.json, the
|
|
2
|
+
// site's own addresses), and the checks of it. No dependencies, so the site's Worker can use it too (the app
|
|
3
|
+
// "Cookiescanner" allows an address the policy stopped).
|
|
4
|
+
|
|
5
|
+
/** @typedef {{ script?: string[], style?: string[], img?: string[], connect?: string[], frame?: string[], form?: string[] }} Sources */
|
|
6
|
+
|
|
7
|
+
/** The kinds of sources a site or an app can add (a Sources' keys, csp.json's). */
|
|
8
|
+
export const KINDS = ['script', 'style', 'img', 'connect', 'frame', 'form'];
|
|
9
|
+
|
|
10
|
+
// A source as a site or an app gives it: an address (origin) on https, with `*.` for all under a domain. Nothing that
|
|
11
|
+
// lets everything through (`*`, `https:`, 'unsafe-inline', 'unsafe-eval').
|
|
12
|
+
const SOURCE = /^https:\/\/(\*\.)?[a-z0-9-]+(\.[a-z0-9-]+)+(:\d+)?$/;
|
|
13
|
+
|
|
14
|
+
/** Whether a source is one a site or an app may give (see SOURCE). @param {unknown} source */
|
|
15
|
+
export const isSource = (source) => typeof source === 'string' && SOURCE.test(source);
|
|
16
|
+
|
|
17
|
+
/** The kind of sources a directive of the policy takes, as a browser names it when it stops something
|
|
18
|
+
* (securitypolicyviolation's violatedDirective: img-src, script-src-elem …); undefined for one a site or an app cannot
|
|
19
|
+
* add to (font-src, media-src …). @param {string} directive */
|
|
20
|
+
export function kindOf(directive) {
|
|
21
|
+
if (directive === 'form-action') return 'form';
|
|
22
|
+
if (directive === 'child-src') return 'frame';
|
|
23
|
+
const kind = /^(script|style|img|connect|frame)-src(-elem|-attr)?$/.exec(directive)?.[1];
|
|
24
|
+
return kind;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What is wrong with sources written in a file (an app's csp.json), each as a sentence; none when they are right.
|
|
28
|
+
* @param {unknown} sources @param {string} file */
|
|
29
|
+
export function sourcesProblems(sources, file) {
|
|
30
|
+
if (!sources || typeof sources !== 'object' || Array.isArray(sources)) return [`${file}: skal være et objekt med ${KINDS.join(', ')}.`];
|
|
31
|
+
return Object.entries(sources).flatMap(([kind, values]) =>
|
|
32
|
+
!KINDS.includes(kind)
|
|
33
|
+
? [`${file}: "${kind}" er ikke en af ${KINDS.join(', ')}.`]
|
|
34
|
+
: !Array.isArray(values)
|
|
35
|
+
? [`${file}: "${kind}" skal være en liste af adresser.`]
|
|
36
|
+
: values
|
|
37
|
+
.filter((v) => !isSource(v))
|
|
38
|
+
.map(
|
|
39
|
+
(v) =>
|
|
40
|
+
`${file}: ${JSON.stringify(v)} under "${kind}" er ikke en adresse som https://eksempel.dk eller https://*.eksempel.dk.`,
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Whether the sources allow an address (its origin, or one under a `*.` domain), as any kind or as `kind`.
|
|
46
|
+
* @param {Sources} sources @param {string} url @param {string} [kind] */
|
|
47
|
+
export function allows(sources, url, kind) {
|
|
48
|
+
const { origin, host } = new URL(url);
|
|
49
|
+
return (kind ? (sources[kind] ?? []) : Object.values(sources).flat()).some(
|
|
50
|
+
(s) => s === origin || (s.startsWith('https://*.') && host.endsWith(s.slice('https://*'.length))),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The sources together. @param {Sources[]} all @returns {Required<Sources>} */
|
|
55
|
+
export function joined(...all) {
|
|
56
|
+
/** @type {Required<Sources>} */
|
|
57
|
+
const sum = Object.fromEntries(KINDS.map((kind) => [kind, []]));
|
|
58
|
+
for (const sources of all) for (const kind of KINDS) sum[kind].push(...(sources[kind] ?? []));
|
|
59
|
+
return sum;
|
|
60
|
+
}
|