@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,569 @@
|
|
|
1
|
+
// Checks what the editor asks of GitHub through the site's Worker, and the Worker's cache of the site's files in R2
|
|
2
|
+
// (packages/editor/worker/files.js, packages/editor/worker/editor-github.js, packages/editor/src/github.ts): the editor's own github.ts asks the Worker's
|
|
3
|
+
// own code, which asks a GitHub played by git in a repository made for the check from this one's files (each answer
|
|
4
|
+
// as GitHub's GraphQL and REST APIs give it), with R2 in memory. With the cache, without it (no binding FILES) and
|
|
5
|
+
// with an R2 that fails, the editor must get exactly the files git has, save, see the versions and the deleted files
|
|
6
|
+
// and restore them; with the cache, an opening after a save may ask GitHub for nothing but the branch's head, and
|
|
7
|
+
// everything in R2 must give its git id. Nothing goes to GitHub or Cloudflare. Run: npm run check:worker
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
13
|
+
import { build } from 'esbuild';
|
|
14
|
+
import { workerModules } from './worker-modules.mjs';
|
|
15
|
+
import { config } from '../site/siteConfig.mjs';
|
|
16
|
+
import { blobId, treeId } from '../lib/git.mjs';
|
|
17
|
+
import { ownRepoEnv } from './git-env.mjs';
|
|
18
|
+
|
|
19
|
+
const ROOT = `${process.cwd()}/`;
|
|
20
|
+
// The editor's own code (this package's src/).
|
|
21
|
+
const SRC = fileURLToPath(new URL('../src/', import.meta.url));
|
|
22
|
+
const WORK = mkdtempSync(join(tmpdir(), 'check-worker-'));
|
|
23
|
+
// The editor's r2Caching.ts, built as the browser gets it (Node does not run TypeScript in node_modules).
|
|
24
|
+
await build({
|
|
25
|
+
entryPoints: [join(SRC, 'r2Caching.ts')],
|
|
26
|
+
bundle: true,
|
|
27
|
+
format: 'esm',
|
|
28
|
+
platform: 'neutral',
|
|
29
|
+
outfile: join(WORK, 'r2Caching.mjs'),
|
|
30
|
+
logLevel: 'silent',
|
|
31
|
+
});
|
|
32
|
+
const { bucketOf, jurisdictionOf, r2CachingOn, withJurisdiction, withR2Caching } = await import(
|
|
33
|
+
pathToFileURL(join(WORK, 'r2Caching.mjs')).href
|
|
34
|
+
);
|
|
35
|
+
const REPO = join(WORK, 'repo');
|
|
36
|
+
const { owner, name, branch } = config.repo;
|
|
37
|
+
const problems = [];
|
|
38
|
+
const expect = (ok, what) => ok || problems.push(what);
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------------------------------
|
|
41
|
+
// The repository: this one's files at HEAD, in one commit (git's own variables left out, so it is this one's, also in
|
|
42
|
+
// the pre-push hook).
|
|
43
|
+
const git = (args, input, env = {}) =>
|
|
44
|
+
execFileSync('git', args, {
|
|
45
|
+
cwd: REPO,
|
|
46
|
+
input,
|
|
47
|
+
maxBuffer: 1 << 30,
|
|
48
|
+
env: {
|
|
49
|
+
...ownRepoEnv(),
|
|
50
|
+
...env,
|
|
51
|
+
GIT_AUTHOR_NAME: 'Anna',
|
|
52
|
+
GIT_AUTHOR_EMAIL: 'anna@example.com',
|
|
53
|
+
GIT_COMMITTER_NAME: 'Anna',
|
|
54
|
+
GIT_COMMITTER_EMAIL: 'anna@example.com',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const out = (...args) => git(args).toString().trim();
|
|
58
|
+
execFileSync('git', ['init', '-q', '-b', branch, REPO], { env: ownRepoEnv() });
|
|
59
|
+
const tarball = join(REPO, '.git', 'head.tar');
|
|
60
|
+
execFileSync('git', ['-C', ROOT, 'archive', '-o', tarball, 'HEAD']);
|
|
61
|
+
execFileSync('tar', ['-x', '-f', tarball, '-C', REPO]);
|
|
62
|
+
rmSync(tarball);
|
|
63
|
+
git(['add', '-A']);
|
|
64
|
+
git(['commit', '-q', '-m', 'Start']);
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------------------------------
|
|
67
|
+
// GitHub, played by git. Objects never change, so what git said about one is kept.
|
|
68
|
+
let asked = 0;
|
|
69
|
+
// All of git's objects (type and bytes) by their ids, read in one go; one made since, when it is asked for.
|
|
70
|
+
let objects = null;
|
|
71
|
+
function all() {
|
|
72
|
+
if (objects) return objects;
|
|
73
|
+
objects = new Map();
|
|
74
|
+
const dump = git(['cat-file', '--batch-all-objects', '--batch']);
|
|
75
|
+
for (let at = 0; at < dump.length;) {
|
|
76
|
+
const end = dump.indexOf(10, at);
|
|
77
|
+
const [oid, type, size] = dump.subarray(at, end).toString().split(' ');
|
|
78
|
+
objects.set(oid, { type, bytes: new Uint8Array(dump.subarray(end + 1, end + 1 + Number(size))) });
|
|
79
|
+
at = end + 1 + Number(size) + 1;
|
|
80
|
+
}
|
|
81
|
+
return objects;
|
|
82
|
+
}
|
|
83
|
+
function objectOf(oid) {
|
|
84
|
+
if (!all().has(oid)) {
|
|
85
|
+
try {
|
|
86
|
+
const type = out('cat-file', '-t', oid);
|
|
87
|
+
objects.set(oid, { type, bytes: new Uint8Array(git(['cat-file', type, oid])) });
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return objects.get(oid);
|
|
93
|
+
}
|
|
94
|
+
const kindOf = (oid) => objectOf(oid)?.type ?? null;
|
|
95
|
+
const bytesOf = (oid) => objectOf(oid).bytes;
|
|
96
|
+
// Git's rule (and GitHub's, for these files): binary when the first 8000 bytes have a zero byte.
|
|
97
|
+
const isBinary = (oid) => bytesOf(oid).subarray(0, 8000).includes(0);
|
|
98
|
+
// GitHub gives only part of a big file's text; this one plays that part.
|
|
99
|
+
const TRUNCATED = 'src/data/menu.json';
|
|
100
|
+
const truncated = new Set([out('rev-parse', `HEAD:${TRUNCATED}`)]);
|
|
101
|
+
const blobOf = (oid) =>
|
|
102
|
+
isBinary(oid)
|
|
103
|
+
? { text: null, isBinary: true, isTruncated: false }
|
|
104
|
+
: truncated.has(oid)
|
|
105
|
+
? { text: new TextDecoder().decode(bytesOf(oid)).slice(0, 20), isBinary: false, isTruncated: true }
|
|
106
|
+
: { text: new TextDecoder().decode(bytesOf(oid)), isBinary: false, isTruncated: false };
|
|
107
|
+
// A tree's entries: "<mode> <name>\0" and the 20 bytes of the id, each.
|
|
108
|
+
function entriesOf(oid) {
|
|
109
|
+
const bytes = bytesOf(oid);
|
|
110
|
+
const entries = [];
|
|
111
|
+
for (let at = 0; at < bytes.length;) {
|
|
112
|
+
const space = bytes.indexOf(32, at);
|
|
113
|
+
const zero = bytes.indexOf(0, space);
|
|
114
|
+
const mode = parseInt(new TextDecoder().decode(bytes.subarray(at, space)), 8);
|
|
115
|
+
const entryOid = Buffer.from(bytes.subarray(zero + 1, zero + 21)).toString('hex');
|
|
116
|
+
const type = mode === 0o40000 ? 'tree' : mode === 0o160000 ? 'commit' : 'blob';
|
|
117
|
+
const entryName = new TextDecoder().decode(bytes.subarray(space + 1, zero));
|
|
118
|
+
entries.push({ name: entryName, oid: entryOid, mode, type, object: type === 'blob' ? { isBinary: isBinary(entryOid) } : {} });
|
|
119
|
+
at = zero + 21;
|
|
120
|
+
}
|
|
121
|
+
// Not in git's order, as GitHub may give them: the Worker sorts them itself.
|
|
122
|
+
return entries.reverse();
|
|
123
|
+
}
|
|
124
|
+
// A commit's root tree: the first line of the commit, "tree <oid>".
|
|
125
|
+
const treeOfCommit = (commit) => new TextDecoder().decode(bytesOf(commit).subarray(5, 45));
|
|
126
|
+
const head = () => readFileSync(join(REPO, '.git/refs/heads', branch), 'utf8').trim();
|
|
127
|
+
function historyOf(path, first, after = 0) {
|
|
128
|
+
const lines = out('log', '--format=%H%x00%T%x00%P%x00%cI%x00%an', `-n${first + 1}`, `--skip=${after}`, head(), '--', path)
|
|
129
|
+
.split('\n')
|
|
130
|
+
.filter(Boolean);
|
|
131
|
+
const nodes = lines.slice(0, first).map((line) => {
|
|
132
|
+
const [oid, tree, parents, date, author] = line.split('\0');
|
|
133
|
+
const parent = parents.split(' ')[0];
|
|
134
|
+
return {
|
|
135
|
+
oid,
|
|
136
|
+
tree: { oid: tree },
|
|
137
|
+
committedDate: date,
|
|
138
|
+
author: { name: author, user: { name: author, login: 'anna' } },
|
|
139
|
+
parents: { nodes: parent ? [{ oid: parent, tree: { oid: treeOfCommit(parent) } }] : [] },
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
return { nodes, pageInfo: { hasNextPage: lines.length > first, endCursor: String(after + nodes.length) } };
|
|
143
|
+
}
|
|
144
|
+
const pathAt = (expression) => {
|
|
145
|
+
const [ref, ...rest] = expression.split(':');
|
|
146
|
+
try {
|
|
147
|
+
return out('rev-parse', `${ref === branch ? head() : ref}:${rest.join(':')}`);
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const unquote = (s) => JSON.parse(s);
|
|
153
|
+
|
|
154
|
+
function graphql({ query, variables = {} }) {
|
|
155
|
+
if (query.includes('createCommitOnBranch')) return commitOnBranch(query, variables.input);
|
|
156
|
+
const repository = {};
|
|
157
|
+
const data = { repository };
|
|
158
|
+
if (/\bviewer\s*\{/.test(query)) {
|
|
159
|
+
data.viewer = { login: 'anna', name: 'Anna' };
|
|
160
|
+
repository.viewerPermission = 'ADMIN';
|
|
161
|
+
}
|
|
162
|
+
if (query.includes('ref(qualifiedName')) {
|
|
163
|
+
const commit = head();
|
|
164
|
+
const target = { oid: commit, tree: { oid: treeOfCommit(commit) } };
|
|
165
|
+
for (const [, alias, first, path, after] of query.matchAll(
|
|
166
|
+
/(?:(f\d+): )?history\(first: (\d+), path: ("(?:[^"\\]|\\.)*"|\$path)(, after: \$after)?\)/g,
|
|
167
|
+
)) {
|
|
168
|
+
const found = historyOf(path === '$path' ? variables.path : unquote(path), Number(first), after ? Number(variables.after ?? 0) : 0);
|
|
169
|
+
target[alias ?? 'history'] = found;
|
|
170
|
+
}
|
|
171
|
+
repository.ref = { target };
|
|
172
|
+
}
|
|
173
|
+
if (query.includes('object(oid: $oid)'))
|
|
174
|
+
repository.object = kindOf(variables.oid) === 'commit' ? { tree: { oid: treeOfCommit(variables.oid) } } : null;
|
|
175
|
+
for (const [, alias, oid] of query.matchAll(/(\w+): object\(oid: ("[0-9a-f]+")\)/g)) {
|
|
176
|
+
const kind = kindOf(unquote(oid));
|
|
177
|
+
repository[alias] = kind === 'tree' ? { entries: entriesOf(unquote(oid)) } : kind === 'blob' ? blobOf(unquote(oid)) : kind ? {} : null;
|
|
178
|
+
}
|
|
179
|
+
for (const [, alias, expression] of query.matchAll(/(\w+): object\(expression: ("(?:[^"\\]|\\.)*")\)/g)) {
|
|
180
|
+
const oid = pathAt(unquote(expression));
|
|
181
|
+
repository[alias] = oid && { oid, ...(kindOf(oid) === 'blob' ? blobOf(oid) : {}) };
|
|
182
|
+
}
|
|
183
|
+
return { data };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function commitOnBranch(query, input) {
|
|
187
|
+
if (input.expectedHeadOid !== head()) return { errors: [{ message: 'Expected branch to point to another commit' }] };
|
|
188
|
+
const index = join(WORK, 'index');
|
|
189
|
+
rmSync(index, { force: true });
|
|
190
|
+
const env = { GIT_INDEX_FILE: index };
|
|
191
|
+
git(['read-tree', head()], undefined, env);
|
|
192
|
+
for (const { path, contents: base64 } of input.fileChanges.additions ?? []) {
|
|
193
|
+
const oid = git(['hash-object', '-w', '--stdin'], Buffer.from(base64, 'base64')).toString().trim();
|
|
194
|
+
const was = git(['ls-tree', head(), '--', path]).toString().split(' ')[0];
|
|
195
|
+
git(['update-index', '--add', '--cacheinfo', `${was || '100644'},${oid},${path}`], undefined, env);
|
|
196
|
+
}
|
|
197
|
+
for (const { path } of input.fileChanges.deletions ?? []) git(['update-index', '--force-remove', path], undefined, env);
|
|
198
|
+
const tree = git(['write-tree'], undefined, env).toString().trim();
|
|
199
|
+
const commit = git(['commit-tree', tree, '-p', head(), '-m', input.message.headline]).toString().trim();
|
|
200
|
+
git(['update-ref', `refs/heads/${branch}`, commit, input.expectedHeadOid]);
|
|
201
|
+
const done = { oid: commit, tree: { oid: tree } };
|
|
202
|
+
for (const [, alias, path] of query.matchAll(/(f\d+): file\(path: ("(?:[^"\\]|\\.)*")\)/g)) {
|
|
203
|
+
const oid = out('rev-parse', `${commit}:${unquote(path)}`);
|
|
204
|
+
done[alias] = { oid, object: { isBinary: isBinary(oid) } };
|
|
205
|
+
}
|
|
206
|
+
return { data: { createCommitOnBranch: { commit: done } } };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// The requests GitHub got, and what they were for.
|
|
210
|
+
const requests = [];
|
|
211
|
+
// Cloudflare's API, for /api/r2: its answer about the bucket ([status, body]), the jurisdiction it is kept within (asked
|
|
212
|
+
// outside it, Cloudflare has no such bucket), and the Worker's settings asked with.
|
|
213
|
+
let cloudflare = [500, {}];
|
|
214
|
+
let cloudflareJurisdiction = null;
|
|
215
|
+
let r2Env = {};
|
|
216
|
+
async function github(url, init) {
|
|
217
|
+
asked++;
|
|
218
|
+
requests.push(`${url} ${init.body ?? ''}`);
|
|
219
|
+
const path = new URL(url).pathname;
|
|
220
|
+
if (path === '/graphql') return Response.json(graphql(JSON.parse(init.body)));
|
|
221
|
+
if (path === `/repos/${owner}/${name}`) return Response.json({ permissions: { pull: true, push: true } });
|
|
222
|
+
const blob = path.match(new RegExp(`^/repos/${owner}/${name}/git/blobs/([0-9a-f]+)$`))?.[1];
|
|
223
|
+
if (blob) return kindOf(blob) === 'blob' ? new Response(bytesOf(blob)) : Response.json({ message: 'Not Found' }, { status: 404 });
|
|
224
|
+
if (path.startsWith('/applications/')) return new Response(null, { status: 204 });
|
|
225
|
+
throw new Error(`GitHub: ${path}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------------------------------
|
|
229
|
+
// The Worker, with R2 in memory (or none, or one that fails), and the editor's github.ts in a "browser" whose
|
|
230
|
+
// requests to /api/github/ go to it, signed in.
|
|
231
|
+
const [{ handleGitHub }, { readFiles, commitFiles }, { handleR2 }] = await workerModules('editor-github.js', 'github.js', 'r2-status.js');
|
|
232
|
+
|
|
233
|
+
function memoryBucket() {
|
|
234
|
+
const objects = new Map();
|
|
235
|
+
const puts = new Map();
|
|
236
|
+
return {
|
|
237
|
+
objects,
|
|
238
|
+
puts,
|
|
239
|
+
async get(key) {
|
|
240
|
+
const value = objects.get(key);
|
|
241
|
+
return value ? { arrayBuffer: async () => value.slice().buffer } : null;
|
|
242
|
+
},
|
|
243
|
+
async head(key) {
|
|
244
|
+
return objects.has(key) ? {} : null;
|
|
245
|
+
},
|
|
246
|
+
async put(key, value) {
|
|
247
|
+
puts.set(key, (puts.get(key) ?? 0) + 1);
|
|
248
|
+
objects.set(key, typeof value === 'string' ? new TextEncoder().encode(value) : new Uint8Array(value));
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const failing = {
|
|
253
|
+
get: async () => {
|
|
254
|
+
throw new Error('R2');
|
|
255
|
+
},
|
|
256
|
+
head: async () => {
|
|
257
|
+
throw new Error('R2');
|
|
258
|
+
},
|
|
259
|
+
put: async () => {
|
|
260
|
+
throw new Error('R2');
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
let env = {};
|
|
265
|
+
let pending = [];
|
|
266
|
+
const ctx = { waitUntil: (p) => pending.push(p) };
|
|
267
|
+
async function settle() {
|
|
268
|
+
while (pending.length) await Promise.all(pending.splice(0));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const log = console.log;
|
|
272
|
+
// The Worker's own lines (packages/editor/worker/files.js: "Filer (…): …") are left out.
|
|
273
|
+
console.log = (...args) => String(args[0]).startsWith('Filer (') || log(...args);
|
|
274
|
+
|
|
275
|
+
globalThis.localStorage = { removeItem() {}, getItem: () => null, setItem() {} };
|
|
276
|
+
globalThis.fetch = async (input, init = {}) => {
|
|
277
|
+
const url = typeof input === 'string' ? input : input.url;
|
|
278
|
+
if (url.startsWith('https://api.github.com')) return github(url, init);
|
|
279
|
+
if (url.startsWith('https://api.cloudflare.com/client/v4/accounts/account/r2/buckets/'))
|
|
280
|
+
return new Headers(init.headers).get('cf-r2-jurisdiction') === cloudflareJurisdiction
|
|
281
|
+
? Response.json(cloudflare[1], { status: cloudflare[0] })
|
|
282
|
+
: Response.json({ success: false, errors: [{ code: 10006, message: 'The specified bucket does not exist.' }] }, { status: 404 });
|
|
283
|
+
if (!url.startsWith('/api/github/')) return new Response(null, { status: 404 });
|
|
284
|
+
const request = new Request(`https://editor.example${url}`, {
|
|
285
|
+
...init,
|
|
286
|
+
headers: { ...init.headers, cookie: 'editor_session=token', 'sec-fetch-site': 'same-origin' },
|
|
287
|
+
});
|
|
288
|
+
return handleGitHub(request, env, ctx);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// The editor's github.ts, with its texts from the Danish dictionary; each import a new browser (nothing known).
|
|
292
|
+
const bundle = join(WORK, 'github.mjs');
|
|
293
|
+
await build({
|
|
294
|
+
entryPoints: [join(SRC, 'github.ts')],
|
|
295
|
+
bundle: true,
|
|
296
|
+
format: 'esm',
|
|
297
|
+
platform: 'neutral',
|
|
298
|
+
outfile: bundle,
|
|
299
|
+
logLevel: 'silent',
|
|
300
|
+
plugins: [
|
|
301
|
+
{
|
|
302
|
+
name: 'site',
|
|
303
|
+
setup(b) {
|
|
304
|
+
b.onResolve({ filter: /^@site\/config$/ }, () => ({ path: join(ROOT, 'src/site/config.ts') }));
|
|
305
|
+
b.onResolve({ filter: /^\.\/i18n$/ }, () => ({ path: 'i18n', namespace: 'i18n' }));
|
|
306
|
+
b.onLoad({ filter: /.*/, namespace: 'i18n' }, () => ({
|
|
307
|
+
contents: `import { app } from ${JSON.stringify(join(SRC, 'i18n/da-DK/app.ts'))}; export const t = { app };`,
|
|
308
|
+
resolveDir: ROOT,
|
|
309
|
+
loader: 'ts',
|
|
310
|
+
}));
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
});
|
|
315
|
+
let browsers = 0;
|
|
316
|
+
const browser = () => import(`${bundle}?${browsers++}`);
|
|
317
|
+
|
|
318
|
+
// ---------------------------------------------------------------------------------------------------
|
|
319
|
+
// What git has, as the editor had it before: each file in a folder `depth` levels down, with its text ('' for binary).
|
|
320
|
+
function filesAt(commit, folder, depth = 3) {
|
|
321
|
+
const lines = git(['ls-tree', '-r', '-z', commit, '--', folder]).toString().split('\0').filter(Boolean);
|
|
322
|
+
return lines
|
|
323
|
+
.map((line) => {
|
|
324
|
+
const [meta, path] = line.split('\t');
|
|
325
|
+
const oid = meta.split(' ')[2];
|
|
326
|
+
return { path, oid, text: blobOf(oid).text ?? '' };
|
|
327
|
+
})
|
|
328
|
+
.filter((f) => f.path.slice(folder.length + 1).split('/').length <= depth)
|
|
329
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
330
|
+
}
|
|
331
|
+
const sorted = (files) => [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
332
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
333
|
+
|
|
334
|
+
// Opens the editor as a new browser; `github`: the requests to GitHub, but those for a text GitHub gives only part of,
|
|
335
|
+
// which can never be kept.
|
|
336
|
+
async function opened(b, what) {
|
|
337
|
+
const before = requests.length;
|
|
338
|
+
const at = await b.head();
|
|
339
|
+
const loaded = await b.loadFolders(config.folders, 3, at);
|
|
340
|
+
for (const [key, folder] of Object.entries(config.folders))
|
|
341
|
+
expect(same(sorted(loaded.folders[key]), filesAt(at.commit, folder)), `${what}: ${folder} er ikke som i git`);
|
|
342
|
+
expect(loaded.commit === head(), `${what}: commit'et er ikke branchens`);
|
|
343
|
+
const partly = (r) =>
|
|
344
|
+
[...r.matchAll(/b\d+: object\(oid: \\"([0-9a-f]+)/g)].every(([, oid]) => truncated.has(oid)) && /b0: object/.test(r);
|
|
345
|
+
return { loaded, github: requests.slice(before).filter((r) => !partly(r)).length };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function run(label, bucket) {
|
|
349
|
+
env = bucket ? { FILES: bucket, GITHUB_PUBLISH_TOKEN: 'publish' } : { GITHUB_PUBLISH_TOKEN: 'publish' };
|
|
350
|
+
const cached = bucket && bucket !== failing;
|
|
351
|
+
const first = await opened(await browser(), `${label}, første åbning`);
|
|
352
|
+
await settle();
|
|
353
|
+
const second = await opened(await browser(), `${label}, anden åbning`);
|
|
354
|
+
if (cached) expect(second.github === 1, `${label}: anden åbning spurgte GitHub ${second.github} gange (kun head)`);
|
|
355
|
+
await settle();
|
|
356
|
+
|
|
357
|
+
// A save: a changed post (a page, when the site has no posts yet), a new file in a new folder, another page deleted and
|
|
358
|
+
// a new picture.
|
|
359
|
+
const b = await browser();
|
|
360
|
+
const files = first.loaded.folders;
|
|
361
|
+
const post = [...files.blog, ...files.pages].find((f) => f.path.endsWith('.md'));
|
|
362
|
+
const page = files.pages.find((f) => f.path.endsWith('.md') && f !== post);
|
|
363
|
+
const picture = new Uint8Array([0x52, 0x49, 0x46, 0x46, 0, 1, 2, 3, ...new TextEncoder().encode(label)]);
|
|
364
|
+
const changes = [
|
|
365
|
+
{ path: post.path, content: `${post.text}\nRettet (${label}).\n`, oid: post.oid },
|
|
366
|
+
{ path: `${config.folders.data}/ny-${label}/fil.json`, content: `{ "label": ${JSON.stringify(label)} }\n`, oid: null },
|
|
367
|
+
{ path: page.path, content: null, oid: page.oid },
|
|
368
|
+
{ path: `${config.folders.media}/ny-${label}.webp`, content: picture, oid: null },
|
|
369
|
+
];
|
|
370
|
+
const saved = await b.commit(`Editor: ret (${label})`, changes);
|
|
371
|
+
expect(saved.commit === head(), `${label}: gem gav ikke det nye commit`);
|
|
372
|
+
for (const c of changes)
|
|
373
|
+
if (c.content !== null)
|
|
374
|
+
expect(saved.blobs[c.path] === out('rev-parse', `HEAD:${c.path}`), `${label}: blob-id'et for ${c.path} er forkert`);
|
|
375
|
+
await settle();
|
|
376
|
+
const after = await opened(await browser(), `${label}, åbning efter gem`);
|
|
377
|
+
if (cached) expect(after.github === 1, `${label}: åbning efter gem spurgte GitHub ${after.github} gange (kun head)`);
|
|
378
|
+
|
|
379
|
+
// A file changed or created elsewhere is not overwritten.
|
|
380
|
+
for (const [change, message] of [
|
|
381
|
+
[{ path: post.path, content: 'x', oid: post.oid }, 'ændret et andet sted'],
|
|
382
|
+
[{ path: changes[1].path, content: 'x', oid: null }, 'findes allerede'],
|
|
383
|
+
])
|
|
384
|
+
try {
|
|
385
|
+
await b.commit('Editor: konflikt', [change]);
|
|
386
|
+
problems.push(`${label}: gem overskrev en fil, der er ${message}`);
|
|
387
|
+
} catch (e) {
|
|
388
|
+
expect(e instanceof b.ConflictError, `${label}: ${message} gav ${e.message}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Versions: the saved one and the one before it.
|
|
392
|
+
const { versions } = await b.versionsOf(post.path);
|
|
393
|
+
expect(versions[0]?.commit === saved.commit, `${label}: den nyeste version er ikke gemmet`);
|
|
394
|
+
expect((await b.versionText(versions[0], post.path)) === changes[0].content, `${label}: den nyeste versions tekst er forkert`);
|
|
395
|
+
expect((await b.versionText(versions[1], post.path)) === post.text, `${label}: den forrige versions tekst er forkert`);
|
|
396
|
+
expect((await b.versionText(versions[0], page.path)) === null, `${label}: en slettet fil har en tekst`);
|
|
397
|
+
|
|
398
|
+
// Deleted files: the page, with its text; restored, it is not.
|
|
399
|
+
const deleted = await b.deletedFiles(config.folders.pages);
|
|
400
|
+
const gone = deleted.find((d) => d.path === page.path);
|
|
401
|
+
expect(gone?.oid === page.oid && gone?.text === page.text && gone?.by === 'Anna', `${label}: den slettede side står ikke under Slettede`);
|
|
402
|
+
await b.commit(`Editor: gendan (${label})`, [{ path: page.path, content: page.text, oid: null }]);
|
|
403
|
+
await settle();
|
|
404
|
+
expect(
|
|
405
|
+
!(await b.deletedFiles(config.folders.pages)).some((d) => d.path === page.path),
|
|
406
|
+
`${label}: en gendannet side står under Slettede`,
|
|
407
|
+
);
|
|
408
|
+
const pictures = await b.deletedFiles(config.folders.media, { text: false });
|
|
409
|
+
expect(!pictures.length, `${label}: der er slettede billeder, som ikke er slettet`);
|
|
410
|
+
|
|
411
|
+
// A picture's bytes, and a second time from R2.
|
|
412
|
+
const oid = saved.blobs[changes[3].path];
|
|
413
|
+
expect(same([...(await b.loadBytes(oid))], [...picture]), `${label}: billedets bytes er forkerte`);
|
|
414
|
+
const old = files.media.find((f) => f.path.endsWith('.webp'));
|
|
415
|
+
if (old) {
|
|
416
|
+
expect(same([...(await b.loadBytes(old.oid))], [...bytesOf(old.oid)]), `${label}: et billedes bytes er forkerte`);
|
|
417
|
+
await settle();
|
|
418
|
+
const before = asked;
|
|
419
|
+
await (await browser()).loadBytes(old.oid);
|
|
420
|
+
if (cached) expect(asked === before, `${label}: billedet blev hentet fra GitHub igen`);
|
|
421
|
+
}
|
|
422
|
+
// Single files, and only questions through /graphql.
|
|
423
|
+
const [single] = await b.loadFiles([post.path, 'findes/ikke.md']);
|
|
424
|
+
expect(single?.text === changes[0].content && single.oid === saved.blobs[post.path], `${label}: loadFiles gav ikke filen`);
|
|
425
|
+
try {
|
|
426
|
+
await b.graphql('mutation { deleteRef(input: { refId: "x" }) { clientMutationId } }');
|
|
427
|
+
problems.push(`${label}: /api/github/graphql lod en ændring gå igennem`);
|
|
428
|
+
} catch {}
|
|
429
|
+
|
|
430
|
+
// The Worker's own saves (Udgiv planlagt, Cookiescanner).
|
|
431
|
+
const read = await readFiles(env, [post.path, 'findes/ikke.md'], ctx);
|
|
432
|
+
expect(read.head === head() && read.texts[0] === changes[0].content && read.texts[1] === undefined, `${label}: Workeren læste forkert`);
|
|
433
|
+
await commitFiles(
|
|
434
|
+
env,
|
|
435
|
+
read.head,
|
|
436
|
+
`Udgiv planlagt (${label})`,
|
|
437
|
+
[{ path: `${config.folders.data}/worker-${label}.json`, text: '{}\n' }],
|
|
438
|
+
ctx,
|
|
439
|
+
);
|
|
440
|
+
await settle();
|
|
441
|
+
const own = await opened(await browser(), `${label}, åbning efter Workerens gem`);
|
|
442
|
+
if (cached) expect(own.github === 1, `${label}: åbning efter Workerens gem spurgte GitHub ${own.github} gange (kun head)`);
|
|
443
|
+
await settle();
|
|
444
|
+
return { first: first.github, second: second.github, after: after.github };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// A request from another page (another address of the same domain) gets nothing, even with the cookie.
|
|
448
|
+
{
|
|
449
|
+
const res = await handleGitHub(
|
|
450
|
+
new Request('https://editor.example/api/github/head', { headers: { cookie: 'editor_session=token', 'sec-fetch-site': 'same-site' } }),
|
|
451
|
+
{},
|
|
452
|
+
ctx,
|
|
453
|
+
);
|
|
454
|
+
expect(res.status === 401, `en anden sides forespørgsel fik ${res.status}, ikke 401`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const bucket = memoryBucket();
|
|
458
|
+
let counts;
|
|
459
|
+
try {
|
|
460
|
+
counts = await run('r2', bucket);
|
|
461
|
+
await run('uden-r2', null);
|
|
462
|
+
await run('r2-fejler', failing);
|
|
463
|
+
await settle();
|
|
464
|
+
|
|
465
|
+
// R2 caching under "Indstillinger" › "Avanceret" (r2Caching.ts): the Worker's settings have the line and its bucket, and
|
|
466
|
+
// with it on or off they are settings wrangler reads, with the bucket bound as FILES or no bucket; on and off again,
|
|
467
|
+
// they are as they were. And /api/r2 (packages/editor/worker/r2-status.js) says what Cloudflare says of the bucket, only to someone who
|
|
468
|
+
// can save, so the switch is turned on only when Cloudflare has it.
|
|
469
|
+
{
|
|
470
|
+
const { experimental_readRawConfig } = await import('wrangler');
|
|
471
|
+
const text = readFileSync(join(ROOT, config.wrangler), 'utf8');
|
|
472
|
+
const was = r2CachingOn(text);
|
|
473
|
+
expect(was !== null, `${config.wrangler} har ingen linje til R2-caching`);
|
|
474
|
+
if (was !== null) {
|
|
475
|
+
// Where the bucket is kept ("Placering"): placed by Cloudflare or within the EU, each with R2 caching on and off.
|
|
476
|
+
for (const place of [null, 'eu']) {
|
|
477
|
+
const placed = withJurisdiction(text, place);
|
|
478
|
+
expect(jurisdictionOf(placed) === place, `R2-cachingens bucket kan ikke holdes ${place ? `i ${place}` : 'uden jurisdiktion'}`);
|
|
479
|
+
expect(bucketOf(placed) === bucketOf(text), `R2-cachingens placering ændrede bucket'ens navn`);
|
|
480
|
+
expect(
|
|
481
|
+
withJurisdiction(placed, jurisdictionOf(text)) === text,
|
|
482
|
+
`R2-cachingens placering ${place ?? 'automatisk'} og tilbage giver ikke filen igen`,
|
|
483
|
+
);
|
|
484
|
+
for (const on of [true, false]) {
|
|
485
|
+
const changed = withR2Caching(placed, on);
|
|
486
|
+
const file = join(WORK, 'wrangler.jsonc');
|
|
487
|
+
writeFileSync(file, changed);
|
|
488
|
+
const buckets = experimental_readRawConfig({ config: file }).rawConfig.r2_buckets ?? [];
|
|
489
|
+
expect(r2CachingOn(changed) === on, `R2-caching kan ikke slås ${on ? 'til' : 'fra'}`);
|
|
490
|
+
expect(
|
|
491
|
+
on
|
|
492
|
+
? buckets.length === 1 &&
|
|
493
|
+
buckets[0].binding === 'FILES' &&
|
|
494
|
+
buckets[0].bucket_name === bucketOf(changed) &&
|
|
495
|
+
(buckets[0].jurisdiction ?? null) === place
|
|
496
|
+
: !buckets.length,
|
|
497
|
+
`wrangler læser R2-caching forkert, når den er slået ${on ? 'til' : 'fra'} (${place ?? 'automatisk'})`,
|
|
498
|
+
);
|
|
499
|
+
expect(
|
|
500
|
+
withR2Caching(withR2Caching(placed, on), was) === placed,
|
|
501
|
+
`R2-caching slået ${on ? 'til' : 'fra'} og tilbage giver ikke filen igen`,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Asked as the editor asks (r2Status in r2Caching.ts): the bucket, and the jurisdiction the settings name.
|
|
508
|
+
const query = (jurisdiction = jurisdictionOf(text)) =>
|
|
509
|
+
new URLSearchParams({ bucket: bucketOf(text) ?? '', ...(jurisdiction ? { jurisdiction } : {}) });
|
|
510
|
+
const ask = (headers = { cookie: 'editor_session=token', 'sec-fetch-site': 'same-origin' }, q = query()) =>
|
|
511
|
+
handleR2(new Request(`https://editor.example/api/r2?${q}`, { headers }), r2Env).then(async (r) => ({
|
|
512
|
+
status: r.status,
|
|
513
|
+
...(await r.json()),
|
|
514
|
+
}));
|
|
515
|
+
// What Cloudflare answers about the bucket, as its API does.
|
|
516
|
+
const answers = {
|
|
517
|
+
ready: [200, { success: true, errors: [], result: { name: bucketOf(text) } }],
|
|
518
|
+
'no-bucket': [404, { success: false, errors: [{ code: 10006, message: 'The specified bucket does not exist.' }] }],
|
|
519
|
+
'no-r2': [403, { success: false, errors: [{ code: 10042, message: 'Please enable R2 through the Cloudflare Dashboard.' }] }],
|
|
520
|
+
refused: [403, { success: false, errors: [{ code: 10000, message: 'Authentication error' }] }],
|
|
521
|
+
failed: [500, { success: false, errors: [{ code: 10001, message: 'Internal error' }] }],
|
|
522
|
+
};
|
|
523
|
+
cloudflareJurisdiction = jurisdictionOf(text);
|
|
524
|
+
for (const [state, answer] of Object.entries(answers)) {
|
|
525
|
+
cloudflare = answer;
|
|
526
|
+
r2Env = { FILES: {}, CLOUDFLARE_ACCOUNT_ID: 'account', CLOUDFLARE_R2_TOKEN: 'key' };
|
|
527
|
+
const got = await ask();
|
|
528
|
+
expect(got.status === 200 && got.bucket === state && got.bound === true, `/api/r2 sagde ${got.bucket}, ikke ${state}`);
|
|
529
|
+
}
|
|
530
|
+
// A bucket kept within a jurisdiction is found only there, and a jurisdiction R2 does not have is not asked for.
|
|
531
|
+
cloudflare = answers.ready;
|
|
532
|
+
cloudflareJurisdiction = jurisdictionOf(text) ? null : 'eu';
|
|
533
|
+
const elsewhere = await ask();
|
|
534
|
+
expect(elsewhere.bucket === 'no-bucket', `/api/r2 fandt bucket'en uden for dens jurisdiktion (${elsewhere.bucket})`);
|
|
535
|
+
const unknown = await ask(undefined, query('mars'));
|
|
536
|
+
expect(unknown.status === 400, `/api/r2 spurgte om jurisdiktionen mars (${unknown.status}), ikke 400`);
|
|
537
|
+
cloudflareJurisdiction = null;
|
|
538
|
+
r2Env = {};
|
|
539
|
+
const none = await ask();
|
|
540
|
+
expect(none.bucket === 'no-key' && none.bound === false, `/api/r2 uden Workerens nøgle sagde ${none.bucket}`);
|
|
541
|
+
const other = await ask({ cookie: 'editor_session=token', 'sec-fetch-site': 'same-site' });
|
|
542
|
+
expect(other.status === 403, `/api/r2 svarede en anden side med ${other.status}, ikke 403`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Everything in R2 is the repository's, gives its git id, and was written once.
|
|
546
|
+
for (const [key, value] of bucket.objects) {
|
|
547
|
+
const [o, r, kind, oid] = key.split('/');
|
|
548
|
+
expect(o === owner && r === name, `R2: ${key} er ikke sitets repo`);
|
|
549
|
+
if (kind === 'blob') expect((await blobId(value)) === oid, `R2: ${key} giver ikke sit blob-id`);
|
|
550
|
+
else if (kind === 'tree')
|
|
551
|
+
expect((await treeId(JSON.parse(new TextDecoder().decode(value)).entries)) === oid, `R2: ${key} giver ikke sit tree-id`);
|
|
552
|
+
else problems.push(`R2: ${key} er hverken blob eller tree`);
|
|
553
|
+
}
|
|
554
|
+
for (const [key, n] of bucket.puts) expect(n === 1, `R2: ${key} blev skrevet ${n} gange`);
|
|
555
|
+
for (const oid of truncated)
|
|
556
|
+
expect(!bucket.objects.has(`${owner}/${name}/blob/${oid}`), `R2: en fil, GitHub kun gav en del af, blev gemt`);
|
|
557
|
+
} finally {
|
|
558
|
+
console.log = log;
|
|
559
|
+
rmSync(WORK, { recursive: true, force: true });
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (problems.length) {
|
|
563
|
+
console.error(`${problems.length} fejl i Workerens GitHub og cachen i R2:\n${problems.join('\n')}`);
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
566
|
+
console.log(
|
|
567
|
+
`Workerens GitHub og cachen i R2 virker: første åbning ${counts.first} forespørgsler til GitHub, anden ${counts.second}, efter et gem ${counts.after}; ` +
|
|
568
|
+
`${bucket.objects.size} objekter i R2. Også uden R2 og med et R2, der fejler.`,
|
|
569
|
+
);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Compares the pages of two built sites, so a change in how the blocks are drawn can be shown to give the same site:
|
|
2
|
+
// npx morpheme compare-html <before> [after, else dist]. Build the site before the change into a folder of its own
|
|
3
|
+
// (npx astro build --outDir <before>), make the change, build again, and compare. What only differs in how it is
|
|
4
|
+
// written is the same: whitespace between tags, comments (also those React puts between two pieces of text), the order
|
|
5
|
+
// of a tag's attributes (and one written twice, in capitals, or with an empty value; an empty class; the order of the
|
|
6
|
+
// classes; the spaces in a style; Astro's markers of a component's own <style>, data-astro-cid-…, which a part drawn by
|
|
7
|
+
// React has not: check that the component's <style> styles only what it draws itself), a closing slash in a tag
|
|
8
|
+
// (<img />), the hash in the name of a bundled file (/_astro/…), and the number of an island's prefix, which counts the
|
|
9
|
+
// React components on the page before it. The scripts Astro bundles (type="module") are left out: what they do is
|
|
10
|
+
// checked in a browser. Says the first place each page differs.
|
|
11
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
12
|
+
import { join, relative, resolve } from 'node:path';
|
|
13
|
+
|
|
14
|
+
const ROOT = `${process.cwd()}/`;
|
|
15
|
+
const [before, after = 'dist'] = process.argv.slice(2).map((d) => resolve(ROOT, d));
|
|
16
|
+
if (!before || !existsSync(before) || !existsSync(after)) {
|
|
17
|
+
console.error('Brug: npx morpheme compare-html <bygget site før> [bygget site efter, ellers dist]');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const pages = (dir) =>
|
|
22
|
+
readdirSync(dir, { recursive: true })
|
|
23
|
+
.filter((f) => f.endsWith('.html') && !f.startsWith('editor/') && statSync(join(dir, f)).isFile())
|
|
24
|
+
.sort();
|
|
25
|
+
|
|
26
|
+
const attributes = (tag) =>
|
|
27
|
+
tag.replace(/^<([a-zA-Z][\w-]*)(\s[^>]*?)?\s*\/?>$/, (all, name, attrs = '') => {
|
|
28
|
+
// An attribute written twice is the first (as a browser reads it).
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const list = [...attrs.matchAll(/\s([^\s=]+)(?:="([^"]*)")?/g)]
|
|
31
|
+
.map(([, n, v]) => [n.toLowerCase(), v])
|
|
32
|
+
.filter(([n, v]) => !(n === 'class' && !v) && !n.startsWith('data-astro-cid-') && !seen.has(n) && seen.add(n))
|
|
33
|
+
.map(([n, v]) => [
|
|
34
|
+
n,
|
|
35
|
+
n === 'style'
|
|
36
|
+
? v?.replace(/([:;])\s+/g, '$1').replace(/;$/, '')
|
|
37
|
+
: n === 'class'
|
|
38
|
+
? v?.split(/\s+/).filter(Boolean).sort().join(' ')
|
|
39
|
+
: v,
|
|
40
|
+
])
|
|
41
|
+
.map(([n, v]) => (v ? `${n}="${v}"` : n));
|
|
42
|
+
return `<${name}${list.length ? ` ${list.sort().join(' ')}` : ''}>`;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const normal = (html) =>
|
|
46
|
+
html
|
|
47
|
+
.replace(/<script type="module"[^>]*>[\s\S]*?<\/script>/g, '')
|
|
48
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
49
|
+
.replace(/(<astro-island\b[^>]*?\sprefix=")r\d+"/g, '$1r"')
|
|
50
|
+
.replace(/\/_astro\/([\w.-]+?)\.[\w-]{8}\.(\w+)/g, '/_astro/$1.$2')
|
|
51
|
+
.replace(/<[a-zA-Z][^<>]*>/g, attributes)
|
|
52
|
+
.replace(/>\s+/g, '>')
|
|
53
|
+
.replace(/\s+</g, '<')
|
|
54
|
+
.replace(/\s+/g, ' ')
|
|
55
|
+
.trim();
|
|
56
|
+
|
|
57
|
+
const was = new Set(pages(before));
|
|
58
|
+
const now = pages(after);
|
|
59
|
+
const problems = [];
|
|
60
|
+
for (const page of now) {
|
|
61
|
+
if (!was.has(page)) {
|
|
62
|
+
problems.push(`${page}: findes kun efter.`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
was.delete(page);
|
|
66
|
+
const a = normal(readFileSync(join(before, page), 'utf8'));
|
|
67
|
+
const b = normal(readFileSync(join(after, page), 'utf8'));
|
|
68
|
+
if (a === b) continue;
|
|
69
|
+
let i = 0;
|
|
70
|
+
while (a[i] === b[i]) i++;
|
|
71
|
+
const around = (s) => s.slice(Math.max(0, i - 120), i + 200);
|
|
72
|
+
problems.push(`${page}:\n før: …${around(a)}…\n efter: …${around(b)}…`);
|
|
73
|
+
}
|
|
74
|
+
for (const page of was) problems.push(`${page}: findes kun før.`);
|
|
75
|
+
|
|
76
|
+
if (problems.length) {
|
|
77
|
+
console.error(`${problems.length} af ${now.length} sider er ikke de samme (${relative(ROOT, before)} og ${relative(ROOT, after)}):\n`);
|
|
78
|
+
console.error(problems.join('\n\n'));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
console.log(`De ${now.length} sider er de samme.`);
|