@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
package/worker/files.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// The site's files in its repository on GitHub, for the editor (packages/editor/worker/editor-github.js) and the Worker's own saves
|
|
2
|
+
// (packages/editor/worker/github.js), through a cache in R2 (the binding FILES in wrangler.jsonc). See docs/editor.md, Sådan gemmer den.
|
|
3
|
+
//
|
|
4
|
+
// Git's objects never change, so the cache never has to be told that something changed: a file's content is kept under
|
|
5
|
+
// its blob id and a folder under its tree id (its entries' names, ids and modes, and whether GitHub calls each file
|
|
6
|
+
// binary), both written once, and only when they give git's own id (packages/editor/lib/git.mjs). A commit's files are
|
|
7
|
+
// its root tree. GitHub is asked for the branch's head every time (one small request) and otherwise only for what R2
|
|
8
|
+
// does not have; what the Worker saves itself goes into R2 at once. Without the binding (a preview of a branch), or
|
|
9
|
+
// when R2 fails, everything comes from GitHub as before, and a save never waits for R2. The keys start with the
|
|
10
|
+
// repository (src/site/config.ts), so the sites' files can never mix:
|
|
11
|
+
// <owner>/<repo>/blob/<oid> a file's bytes
|
|
12
|
+
// <owner>/<repo>/tree/<oid> a folder: { "entries": [{ name, oid, mode, type, binary? }] }, in git's order
|
|
13
|
+
// Each request logs how many requests went to GitHub and how many objects were read and written in R2.
|
|
14
|
+
import { config } from '@site/config';
|
|
15
|
+
import { blobId, sortTree, TREE_MODE, treeId } from '../lib/git.mjs';
|
|
16
|
+
|
|
17
|
+
const { owner, name, branch } = config.repo;
|
|
18
|
+
const KEY = `${owner}/${name}`;
|
|
19
|
+
const API = 'https://api.github.com';
|
|
20
|
+
const encoder = new TextEncoder();
|
|
21
|
+
// GitHub's text of a file is its bytes as UTF-8; bytes that are not (another encoding) are asked of GitHub.
|
|
22
|
+
const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
23
|
+
// The most objects asked of GitHub in one request.
|
|
24
|
+
const BATCH = 100;
|
|
25
|
+
const FILE_MODE = 0o100644;
|
|
26
|
+
|
|
27
|
+
/** An answer from GitHub that was not a success: its status (401: the sign-in is no longer valid). */
|
|
28
|
+
export class GitHubError extends Error {
|
|
29
|
+
constructor(message, status) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.status = status;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Each commit's root tree, as GitHub gave it (a commit never changes).
|
|
36
|
+
const roots = new Map();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The site's files for one request, asked with `token` (the editor's sign-in, or the Worker's GITHUB_PUBLISH_TOKEN).
|
|
40
|
+
* `ctx`: the request's, so R2 is written after the answer has gone; without it, `finish` waits for the writing.
|
|
41
|
+
*/
|
|
42
|
+
export function filesOf(env, token, ctx) {
|
|
43
|
+
const bucket = env.FILES;
|
|
44
|
+
const count = { github: 0, read: 0, written: 0 };
|
|
45
|
+
const pending = [];
|
|
46
|
+
// The trees already read in this request.
|
|
47
|
+
const trees = new Map();
|
|
48
|
+
|
|
49
|
+
/** fetch for GitHub, counted. */
|
|
50
|
+
function ask(url, init = {}) {
|
|
51
|
+
count.github++;
|
|
52
|
+
return fetch(url, {
|
|
53
|
+
...init,
|
|
54
|
+
headers: { authorization: `Bearer ${token}`, 'user-agent': `${name}-worker`, ...init.headers },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function graphql(query, variables = {}) {
|
|
59
|
+
const res = await ask(`${API}/graphql`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'content-type': 'application/json' },
|
|
62
|
+
body: JSON.stringify({ query, variables }),
|
|
63
|
+
});
|
|
64
|
+
const body = await res.json().catch(() => ({}));
|
|
65
|
+
if (!res.ok || body.errors?.length) throw new GitHubError(body.errors?.[0]?.message ?? body.message ?? '', res.status);
|
|
66
|
+
return body.data;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function stored(kind, oid) {
|
|
70
|
+
if (!bucket) return null;
|
|
71
|
+
try {
|
|
72
|
+
count.read++;
|
|
73
|
+
const object = await bucket.get(`${KEY}/${kind}/${oid}`);
|
|
74
|
+
return object ? new Uint8Array(await object.arrayBuffer()) : null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Writes once: `check` first (an R2 read is cheaper than a write), when the object may be there already.
|
|
81
|
+
function keep(kind, oid, value, check = false) {
|
|
82
|
+
if (!bucket) return;
|
|
83
|
+
later(
|
|
84
|
+
(async () => {
|
|
85
|
+
const key = `${KEY}/${kind}/${oid}`;
|
|
86
|
+
if (check) {
|
|
87
|
+
count.read++;
|
|
88
|
+
if (await bucket.head(key)) return;
|
|
89
|
+
}
|
|
90
|
+
count.written++;
|
|
91
|
+
await bucket.put(key, value);
|
|
92
|
+
})().catch(() => {}),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function later(promise) {
|
|
97
|
+
pending.push(promise);
|
|
98
|
+
ctx?.waitUntil(promise);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The commit the branch is at and its root tree, always from GitHub. */
|
|
102
|
+
async function head() {
|
|
103
|
+
const data = await graphql(
|
|
104
|
+
`
|
|
105
|
+
query ($owner: String!, $name: String!, $ref: String!) {
|
|
106
|
+
repository(owner: $owner, name: $name) {
|
|
107
|
+
ref(qualifiedName: $ref) {
|
|
108
|
+
target {
|
|
109
|
+
oid
|
|
110
|
+
... on Commit {
|
|
111
|
+
tree {
|
|
112
|
+
oid
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
`,
|
|
120
|
+
{ owner, name, ref: `refs/heads/${branch}` },
|
|
121
|
+
);
|
|
122
|
+
const { oid, tree } = data.repository.ref.target;
|
|
123
|
+
roots.set(oid, tree.oid);
|
|
124
|
+
return { commit: oid, tree: tree.oid };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A commit's root tree. */
|
|
128
|
+
async function rootOf(commit) {
|
|
129
|
+
if (!roots.has(commit)) {
|
|
130
|
+
const data = await graphql(
|
|
131
|
+
`
|
|
132
|
+
query ($owner: String!, $name: String!, $oid: GitObjectID!) {
|
|
133
|
+
repository(owner: $owner, name: $name) {
|
|
134
|
+
object(oid: $oid) {
|
|
135
|
+
... on Commit {
|
|
136
|
+
tree {
|
|
137
|
+
oid
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
`,
|
|
144
|
+
{ owner, name, oid: commit },
|
|
145
|
+
);
|
|
146
|
+
roots.set(commit, data.repository.object.tree.oid);
|
|
147
|
+
}
|
|
148
|
+
return roots.get(commit);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Trees by their ids: their entries, or null for what is not a tree. From R2, the rest from GitHub in one request. */
|
|
152
|
+
async function treesOf(oids) {
|
|
153
|
+
const wanted = [...new Set(oids)].filter((oid) => !trees.has(oid));
|
|
154
|
+
const found = await Promise.all(wanted.map((oid) => stored('tree', oid)));
|
|
155
|
+
const missing = [];
|
|
156
|
+
wanted.forEach((oid, i) => {
|
|
157
|
+
try {
|
|
158
|
+
if (found[i]) return trees.set(oid, JSON.parse(new TextDecoder().decode(found[i])).entries);
|
|
159
|
+
} catch {}
|
|
160
|
+
missing.push(oid);
|
|
161
|
+
});
|
|
162
|
+
for (let at = 0; at < missing.length; at += BATCH) {
|
|
163
|
+
const some = missing.slice(at, at + BATCH);
|
|
164
|
+
const data = await graphql(
|
|
165
|
+
`query ($owner: String!, $name: String!) { repository(owner: $owner, name: $name) {
|
|
166
|
+
${some.map((oid, i) => `t${i}: object(oid: ${JSON.stringify(oid)}) { ... on Tree { entries { name oid mode type object { ... on Blob { isBinary } } } } }`).join('\n')}
|
|
167
|
+
} }`,
|
|
168
|
+
{ owner, name },
|
|
169
|
+
);
|
|
170
|
+
await Promise.all(
|
|
171
|
+
some.map(async (oid, i) => {
|
|
172
|
+
const got = data.repository[`t${i}`]?.entries;
|
|
173
|
+
const entries = got
|
|
174
|
+
? sortTree(
|
|
175
|
+
got.map((e) => ({
|
|
176
|
+
name: e.name,
|
|
177
|
+
oid: e.oid,
|
|
178
|
+
mode: e.mode,
|
|
179
|
+
type: e.type,
|
|
180
|
+
...(e.type === 'blob' ? { binary: e.object?.isBinary === true } : {}),
|
|
181
|
+
})),
|
|
182
|
+
)
|
|
183
|
+
: null;
|
|
184
|
+
trees.set(oid, entries);
|
|
185
|
+
if (entries && (await treeId(entries)) === oid) keep('tree', oid, JSON.stringify({ entries }));
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return new Map(oids.map((oid) => [oid, trees.get(oid) ?? null]));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The trees at some paths (`{ root, path }`, '' the root itself): their ids, or null where there is no folder. */
|
|
193
|
+
async function foldersAt(places) {
|
|
194
|
+
let at = places.map(({ root, path }) => ({ oid: root, rest: path ? path.split('/') : [] }));
|
|
195
|
+
for (;;) {
|
|
196
|
+
const on = at.filter((a) => a.oid && a.rest.length);
|
|
197
|
+
if (!on.length) return at.map((a) => a.oid);
|
|
198
|
+
const got = await treesOf(on.map((a) => a.oid));
|
|
199
|
+
at = at.map((a) => {
|
|
200
|
+
if (!a.oid || !a.rest.length) return a;
|
|
201
|
+
const entry = got.get(a.oid)?.find((e) => e.name === a.rest[0]);
|
|
202
|
+
return { oid: entry?.type === 'tree' ? entry.oid : null, rest: a.rest.slice(1) };
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** What is at some paths under a root tree: each one's entry (a file, a folder), or null. */
|
|
208
|
+
async function entriesAt(root, paths) {
|
|
209
|
+
const dirs = await foldersAt(paths.map((p) => ({ root, path: parentOf(p) })));
|
|
210
|
+
const got = await treesOf(dirs.filter(Boolean));
|
|
211
|
+
return paths.map((p, i) => (dirs[i] && got.get(dirs[i])?.find((e) => e.name === baseOf(p))) || null);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The files in some folders under a root tree, `depth` levels of folders down: `{ path, oid, binary }`, a list for
|
|
215
|
+
* each folder (empty when it is not there). */
|
|
216
|
+
async function filesIn(root, folders, depth) {
|
|
217
|
+
const starts = await foldersAt(folders.map((path) => ({ root, path })));
|
|
218
|
+
const lists = folders.map(() => []);
|
|
219
|
+
let level = starts.flatMap((oid, i) => (oid ? [{ oid, path: folders[i], list: lists[i] }] : []));
|
|
220
|
+
for (let d = 1; d <= depth && level.length; d++) {
|
|
221
|
+
const got = await treesOf(level.map((l) => l.oid));
|
|
222
|
+
const next = [];
|
|
223
|
+
for (const l of level)
|
|
224
|
+
for (const e of got.get(l.oid) ?? []) {
|
|
225
|
+
const path = l.path ? `${l.path}/${e.name}` : e.name;
|
|
226
|
+
if (e.type === 'tree') next.push({ oid: e.oid, path, list: l.list });
|
|
227
|
+
else if (e.type === 'blob') l.list.push({ path, oid: e.oid, binary: e.binary === true });
|
|
228
|
+
}
|
|
229
|
+
level = next;
|
|
230
|
+
}
|
|
231
|
+
return lists;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Files' texts by their blob ids, as GitHub gives them: null for one GitHub calls binary, undefined for one that is
|
|
235
|
+
* not there. From R2, the rest from GitHub in one request. */
|
|
236
|
+
async function texts(oids) {
|
|
237
|
+
const wanted = [...new Set(oids)];
|
|
238
|
+
const found = await Promise.all(wanted.map((oid) => stored('blob', oid)));
|
|
239
|
+
const out = new Map();
|
|
240
|
+
const missing = [];
|
|
241
|
+
wanted.forEach((oid, i) => {
|
|
242
|
+
const text = found[i] && textOf(found[i]);
|
|
243
|
+
if (text === null || text === undefined) missing.push(oid);
|
|
244
|
+
else out.set(oid, text);
|
|
245
|
+
});
|
|
246
|
+
for (let at = 0; at < missing.length; at += BATCH) {
|
|
247
|
+
const some = missing.slice(at, at + BATCH);
|
|
248
|
+
const data = await graphql(
|
|
249
|
+
`query ($owner: String!, $name: String!) { repository(owner: $owner, name: $name) {
|
|
250
|
+
${some.map((oid, i) => `b${i}: object(oid: ${JSON.stringify(oid)}) { ... on Blob { text isBinary isTruncated } }`).join('\n')}
|
|
251
|
+
} }`,
|
|
252
|
+
{ owner, name },
|
|
253
|
+
);
|
|
254
|
+
await Promise.all(
|
|
255
|
+
some.map(async (oid, i) => {
|
|
256
|
+
const blob = data.repository[`b${i}`];
|
|
257
|
+
if (!blob) return;
|
|
258
|
+
if (blob.isBinary || blob.text == null) return out.set(oid, null);
|
|
259
|
+
out.set(oid, blob.text);
|
|
260
|
+
// Kept only whole and unchanged: the text's bytes are the file's.
|
|
261
|
+
const bytes = encoder.encode(blob.text);
|
|
262
|
+
if (!blob.isTruncated && (await blobId(bytes)) === oid) keep('blob', oid, bytes);
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return new Map(oids.map((oid) => [oid, out.get(oid)]));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** A file's bytes by its blob id: from R2, else from GitHub (kept when they give the id). `repo`: another repository
|
|
270
|
+
* (an app in the store), only from GitHub. Null when GitHub has no such file. */
|
|
271
|
+
async function bytes(oid, repo = config.repo) {
|
|
272
|
+
const own = repo.owner === owner && repo.name === name;
|
|
273
|
+
const found = own ? await stored('blob', oid) : null;
|
|
274
|
+
if (found) return found;
|
|
275
|
+
const res = await ask(`${API}/repos/${repo.owner}/${repo.name}/git/blobs/${oid}`, {
|
|
276
|
+
headers: { accept: 'application/vnd.github.raw+json' },
|
|
277
|
+
});
|
|
278
|
+
if (res.status === 404) return null;
|
|
279
|
+
if (!res.ok) throw new GitHubError('', res.status);
|
|
280
|
+
const got = new Uint8Array(await res.arrayBuffer());
|
|
281
|
+
if (own && (await blobId(got)) === oid) keep('blob', oid, got);
|
|
282
|
+
return got;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Saves files in one commit on the branch, if it is still at `at` (`{ commit, tree }`). `changes`: `{ path, contents }`,
|
|
287
|
+
* the contents as base64 (as GitHub takes them), null to delete the file. Gives the new commit and each saved file's
|
|
288
|
+
* blob id. The new blobs and folders go into R2 afterwards: worked out from the folders before the commit, and kept
|
|
289
|
+
* only when they give the root tree GitHub made (so they are the commit's).
|
|
290
|
+
*/
|
|
291
|
+
async function save(at, message, changes) {
|
|
292
|
+
const kept = changes.filter((c) => c.contents !== null);
|
|
293
|
+
const data = await graphql(
|
|
294
|
+
`mutation ($input: CreateCommitOnBranchInput!) {
|
|
295
|
+
createCommitOnBranch(input: $input) { commit { oid tree { oid }
|
|
296
|
+
${kept.map((c, i) => `f${i}: file(path: ${JSON.stringify(c.path)}) { oid object { ... on Blob { isBinary } } }`).join('\n')}
|
|
297
|
+
} }
|
|
298
|
+
}`,
|
|
299
|
+
{
|
|
300
|
+
input: {
|
|
301
|
+
branch: { repositoryNameWithOwner: KEY, branchName: branch },
|
|
302
|
+
expectedHeadOid: at.commit,
|
|
303
|
+
message: { headline: message },
|
|
304
|
+
fileChanges: {
|
|
305
|
+
additions: kept.map((c) => ({ path: c.path, contents: c.contents })),
|
|
306
|
+
deletions: changes.filter((c) => c.contents === null).map((c) => ({ path: c.path })),
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
const done = data.createCommitOnBranch.commit;
|
|
312
|
+
roots.set(done.oid, done.tree.oid);
|
|
313
|
+
const saved = new Map(kept.map((c, i) => [c.path, { oid: done[`f${i}`].oid, binary: done[`f${i}`].object?.isBinary === true }]));
|
|
314
|
+
if (bucket) later(keepSaved(at.tree, done.tree.oid, changes, saved).catch(() => {}));
|
|
315
|
+
return { commit: done.oid, blobs: Object.fromEntries([...saved].map(([path, f]) => [path, f.oid])) };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function keepSaved(before, after, changes, saved) {
|
|
319
|
+
// The folders the changes are in, with all above them, and their entries before the commit (none: a new folder).
|
|
320
|
+
const dirs = [...new Set(changes.flatMap((c) => ancestorsOf(c.path)))];
|
|
321
|
+
const oids = await foldersAt(dirs.map((path) => ({ root: before, path })));
|
|
322
|
+
const got = await treesOf(oids.filter(Boolean));
|
|
323
|
+
if (oids.some((oid) => oid && !got.get(oid))) return;
|
|
324
|
+
const folders = new Map(dirs.map((d, i) => [d, new Map((oids[i] ? got.get(oids[i]) : []).map((e) => [e.name, e]))]));
|
|
325
|
+
for (const c of changes) {
|
|
326
|
+
const entries = folders.get(parentOf(c.path));
|
|
327
|
+
const was = entries.get(baseOf(c.path));
|
|
328
|
+
if (c.contents === null) entries.delete(baseOf(c.path));
|
|
329
|
+
else {
|
|
330
|
+
const { oid, binary } = saved.get(c.path);
|
|
331
|
+
entries.set(baseOf(c.path), { name: baseOf(c.path), oid, mode: was?.type === 'blob' ? was.mode : FILE_MODE, type: 'blob', binary });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// From the deepest folder up: each one's new id in the folder above it (an empty folder is gone).
|
|
335
|
+
const made = [];
|
|
336
|
+
for (const dir of dirs.sort((a, b) => depthOf(b) - depthOf(a))) {
|
|
337
|
+
const entries = sortTree([...folders.get(dir).values()]);
|
|
338
|
+
const oid = entries.length ? await treeId(entries) : null;
|
|
339
|
+
if (oid) made.push({ oid, entries });
|
|
340
|
+
if (!dir) {
|
|
341
|
+
if (oid !== after) return;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const above = folders.get(parentOf(dir));
|
|
345
|
+
if (oid) above.set(baseOf(dir), { name: baseOf(dir), oid, mode: TREE_MODE, type: 'tree' });
|
|
346
|
+
else above.delete(baseOf(dir));
|
|
347
|
+
}
|
|
348
|
+
for (const { oid, entries } of made) keep('tree', oid, JSON.stringify({ entries }), true);
|
|
349
|
+
for (const c of changes) {
|
|
350
|
+
if (c.contents === null) continue;
|
|
351
|
+
const contents = fromBase64(c.contents);
|
|
352
|
+
if ((await blobId(contents)) === saved.get(c.path).oid) keep('blob', saved.get(c.path).oid, contents, true);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Logs what the request asked of GitHub and R2 (`what`: which), once R2 is written. */
|
|
357
|
+
function finish(what) {
|
|
358
|
+
const all = Promise.allSettled(pending).then(() => {
|
|
359
|
+
if (count.github || count.read || count.written)
|
|
360
|
+
console.log(
|
|
361
|
+
`Filer (${what}): ${count.github} til GitHub, ${count.read} læst og ${count.written} skrevet i R2${bucket ? '' : ' (ingen R2)'}`,
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
ctx?.waitUntil(all);
|
|
365
|
+
return ctx ? Promise.resolve() : all;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return { ask, graphql, head, rootOf, treesOf, foldersAt, entriesAt, filesIn, texts, bytes, save, finish };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// A file's text, as GitHub gives it: undefined when its bytes are not UTF-8, or start with a byte order mark, which
|
|
372
|
+
// GitHub may give otherwise (both asked of GitHub).
|
|
373
|
+
function textOf(bytes) {
|
|
374
|
+
try {
|
|
375
|
+
const text = decoder.decode(bytes);
|
|
376
|
+
return text.startsWith('') ? undefined : text;
|
|
377
|
+
} catch {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const parentOf = (path) => path.split('/').slice(0, -1).join('/');
|
|
383
|
+
const baseOf = (path) => path.split('/').at(-1);
|
|
384
|
+
const depthOf = (path) => (path ? path.split('/').length : 0);
|
|
385
|
+
// The folders a path is in, from the root ('') down.
|
|
386
|
+
const ancestorsOf = (path) => path.split('/').map((_, i, parts) => parts.slice(0, i).join('/'));
|
|
387
|
+
|
|
388
|
+
/** Bytes as base64, as GitHub takes a file's contents. */
|
|
389
|
+
export function toBase64(bytes) {
|
|
390
|
+
let binary = '';
|
|
391
|
+
for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
|
392
|
+
return btoa(binary);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function fromBase64(text) {
|
|
396
|
+
if (Uint8Array.fromBase64) return Uint8Array.fromBase64(text);
|
|
397
|
+
const binary = atob(text);
|
|
398
|
+
const bytes = new Uint8Array(binary.length);
|
|
399
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
400
|
+
return bytes;
|
|
401
|
+
}
|
package/worker/github.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The Worker's own saves to the repository on GitHub (no one signed in): the planned pages and posts it publishes
|
|
2
|
+
// (packages/editor/worker/publish.js) and what an app saves itself (`github` in the tools an app's part of the Worker is given,
|
|
3
|
+
// packages/editor/worker/index.js), each in one commit on the site's branch, which builds the site. With the Worker secret
|
|
4
|
+
// GITHUB_PUBLISH_TOKEN: a GitHub token with Contents: Read and write on the repository (docs/editor.md, Opsætning).
|
|
5
|
+
// Through the cache of the site's files in R2, like the editor's (packages/editor/worker/files.js): the files are read from it, and
|
|
6
|
+
// what is saved goes into it. `ctx`: the request's, so R2 is written after the answer has gone. `token`: another sign-in
|
|
7
|
+
// than the Worker's own (someone signed in to the editor who asked for it, packages/editor/worker/index.js), whose name the commit is in.
|
|
8
|
+
import { filesOf, toBase64 } from './files.js';
|
|
9
|
+
|
|
10
|
+
/** The branch's head and the files at it, by their paths (a file that is not there: undefined). */
|
|
11
|
+
export async function readFiles(env, paths, ctx, token = env.GITHUB_PUBLISH_TOKEN) {
|
|
12
|
+
const files = filesOf(env, token, ctx);
|
|
13
|
+
try {
|
|
14
|
+
const at = await files.head();
|
|
15
|
+
const entries = await files.entriesAt(at.tree, paths);
|
|
16
|
+
const texts = await files.texts(entries.flatMap((e) => (e?.type === 'blob' ? [e.oid] : [])));
|
|
17
|
+
return { head: at.commit, texts: entries.map((e) => (e?.type === 'blob' ? (texts.get(e.oid) ?? null) : undefined)) };
|
|
18
|
+
} finally {
|
|
19
|
+
await files.finish('Workeren læser');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Saves the files (`{ path, text }`) in one commit on the branch, if its head is still `head`. */
|
|
24
|
+
export async function commitFiles(env, head, headline, changes, ctx, token = env.GITHUB_PUBLISH_TOKEN) {
|
|
25
|
+
const files = filesOf(env, token, ctx);
|
|
26
|
+
try {
|
|
27
|
+
const at = { commit: head, tree: await files.rootOf(head) };
|
|
28
|
+
await files.save(
|
|
29
|
+
at,
|
|
30
|
+
headline,
|
|
31
|
+
changes.map((f) => ({ path: f.path, contents: toBase64(new TextEncoder().encode(f.text)) })),
|
|
32
|
+
);
|
|
33
|
+
} finally {
|
|
34
|
+
await files.finish('Workeren gemmer');
|
|
35
|
+
}
|
|
36
|
+
}
|
package/worker/index.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// A thin Worker in front of the static site (dist/), the same for every site: what it has of the site comes from
|
|
2
|
+
// `@site/config` and `@site/worker` (the aliases in the site's wrangler.jsonc; this site's are src/site/config.ts and
|
|
3
|
+
// src/site/worker.js). Everything is served from the assets, including the rules in public/_redirects. The Worker
|
|
4
|
+
// only adds what static files cannot:
|
|
5
|
+
// - noindex on the staging hostname (*.workers.dev). NEVER on the site's own domain.
|
|
6
|
+
// - the site's redirects from its old addresses (`oldAddresses`, `{ ids, paths }`), e.g. from WordPress: files in
|
|
7
|
+
// all sizes, attachment pages and ?attachment_id= / ?p= / ?page_id= (query strings, which _redirects cannot match).
|
|
8
|
+
// - redirects for pages and posts that got a new address in the editor (src/data/redirects.json,
|
|
9
|
+
// packages/editor/src/addresses.ts), followed to where they end, also after one of the redirects above or
|
|
10
|
+
// in public/_redirects, so a visitor is sent on once.
|
|
11
|
+
// - the own addresses of the pages that are the front page and the blog page (src/data/standard-pages.json) to / and
|
|
12
|
+
// /blog/, where they are shown, also at the end of a redirect.
|
|
13
|
+
// - GitHub sign-in for the editor at /editor/ (/oauth/*, packages/editor/worker/oauth.js), kept in a cookie only the Worker reads
|
|
14
|
+
// (packages/editor/worker/session.js), and what the editor asks of GitHub with it (/api/github/*, packages/editor/worker/editor-github.js), the site's
|
|
15
|
+
// files through the cache in R2 (packages/editor/worker/files.js).
|
|
16
|
+
// - the addresses visitors came to that do not exist, for the editor (/api/not-found, packages/editor/worker/not-found.js).
|
|
17
|
+
// - the visitors' answers in the cookie banner, the consent log (/api/consent, packages/editor/worker/consent.js, in D1), and each hour
|
|
18
|
+
// those older than two years deleted.
|
|
19
|
+
// - whether R2 caching can be turned on in the editor: whether Cloudflare has its bucket (/api/r2, packages/editor/worker/r2-status.js).
|
|
20
|
+
// - the editor on its own address when EDITOR_HOST is set (packages/editor/worker/editor-host.js); noindex there too.
|
|
21
|
+
// - each hour, the pages and posts that are Planlagt published once their time has come (scheduled, packages/editor/worker/publish.js).
|
|
22
|
+
// - the editor's notifications on the phone, also when the editor is closed (/api/push, packages/editor/worker/push.js): each minute,
|
|
23
|
+
// whether the site has been built with a save, and an app's notifications (`notify` in its tools).
|
|
24
|
+
// - the apps' own parts (src/apps/<app>/worker.js, worker/apps.js), while they are installed: their addresses under
|
|
25
|
+
// /api/<app>/ and what they do each hour (scheduled), e.g. Cookiescanner's scan of the site's cookies, and their texts
|
|
26
|
+
// for their notifications. What the apps
|
|
27
|
+
// put in the build (dist/_apps/<app>/, packages/editor/lib/apps.mjs) is served to no one; the apps read it.
|
|
28
|
+
import { apps, appTexts, appWorkers, moved, oldAddresses as map, standard } from '@site/worker';
|
|
29
|
+
import { handleOAuth } from './oauth.js';
|
|
30
|
+
import { editorHost } from './editor-host.js';
|
|
31
|
+
import { handleNotFound, isVisit, json, recordNotFound } from './not-found.js';
|
|
32
|
+
import { forgetOldConsents, handleConsent } from './consent.js';
|
|
33
|
+
import { access, mayEdit } from './session.js';
|
|
34
|
+
import { handleGitHub } from './editor-github.js';
|
|
35
|
+
import { handleR2 } from './r2-status.js';
|
|
36
|
+
import { publishScheduled, SCHEDULED_LIST } from './publish.js';
|
|
37
|
+
import { commitFiles, readFiles } from './github.js';
|
|
38
|
+
import { handlePush, notifyAll, waitForBuilds } from './push.js';
|
|
39
|
+
|
|
40
|
+
const STAGING = /\.workers\.dev$/i;
|
|
41
|
+
// The cron in wrangler.jsonc that runs each minute.
|
|
42
|
+
const EACH_MINUTE = '* * * * *';
|
|
43
|
+
|
|
44
|
+
// The parts of the Worker of the apps installed (src/data/apps.json), by the app's id.
|
|
45
|
+
const installed = Object.fromEntries(Object.entries(appWorkers).filter(([id]) => apps.installed?.includes(id)));
|
|
46
|
+
// What an app is given, besides the request: whether its sender can save the site (signed in with GitHub), an answer
|
|
47
|
+
// in JSON, the Worker's own saves to the repository (`github`, packages/editor/worker/github.js: `read` and `commit`, with the request's
|
|
48
|
+
// `ctx`, so the cache in R2 is written after the answer has gone; given a request as the last argument, with its sender's
|
|
49
|
+
// sign-in instead, in their name, when they can save the site: a save they asked for in the editor) and its notifications on the phone
|
|
50
|
+
// (`notify(env, { type, id, text, error, to })`, packages/editor/worker/push.js: `type` one of the types in `notifications` in its
|
|
51
|
+
// index.ts, `text` as written in the site's language, `to` the editor's screen it opens).
|
|
52
|
+
const tools = (app, ctx) => ({
|
|
53
|
+
mayEdit,
|
|
54
|
+
json,
|
|
55
|
+
github: {
|
|
56
|
+
read: async (env, paths, as) => readFiles(env, paths, ctx, as && (await signedIn(as))),
|
|
57
|
+
commit: async (env, head, headline, files, as) => commitFiles(env, head, headline, files, ctx, as && (await signedIn(as))),
|
|
58
|
+
},
|
|
59
|
+
notify: (env, n) => notifyAll(env, { ...n, type: `${app}/${n.type}` }, appTexts[app]),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** The sign-in of a request from someone who can save the site, for a save they asked for; else it fails. */
|
|
63
|
+
async function signedIn(request) {
|
|
64
|
+
const signIn = await access(request);
|
|
65
|
+
if (!signIn?.push) throw new Error('Kun en, der kan gemme hjemmesiden, kan gemme.');
|
|
66
|
+
return signIn.token;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The answer of an installed app's part of the Worker at its address (/api/<app>/…), or undefined for another one. */
|
|
70
|
+
function appAnswer(request, url, env, ctx) {
|
|
71
|
+
const id = /^\/api\/([\w-]+)(?:\/|$)/.exec(url.pathname)?.[1];
|
|
72
|
+
if (!id || !(id in appWorkers)) return undefined;
|
|
73
|
+
const app = installed[id];
|
|
74
|
+
return app?.fetch ? app.fetch(request, env, ctx, tools(id, ctx)) : json({ error: 'not installed' }, 404);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Where the front page and the blog page are shown, by their own addresses.
|
|
78
|
+
const shownAt = (pages) => ({ [pages.front]: '/', [pages.blog]: '/blog/' });
|
|
79
|
+
|
|
80
|
+
// Where an address ends after the editor's redirects (at most ten, against a loop; the editor checks
|
|
81
|
+
// for them before it saves), and the front page's and the blog page's own addresses at / and /blog/.
|
|
82
|
+
export function onward(target, redirects = moved, pages = standard) {
|
|
83
|
+
let to = target;
|
|
84
|
+
for (let i = 0; i < 10 && redirects[to.split('#')[0]]; i++) to = redirects[to.split('#')[0]];
|
|
85
|
+
const [path, hash] = to.split('#');
|
|
86
|
+
const at = shownAt(pages)[path];
|
|
87
|
+
return at ? `${at}${hash === undefined ? '' : `#${hash}`}` : to;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function redirectFor(url) {
|
|
91
|
+
// WordPress answered these on the front page only: /?attachment_id=123, /?p=123, /?page_id=123.
|
|
92
|
+
const id =
|
|
93
|
+
url.pathname === '/' && (url.searchParams.get('attachment_id') ?? url.searchParams.get('p') ?? url.searchParams.get('page_id'));
|
|
94
|
+
if (id && map.ids[id]) return onward(map.ids[id]);
|
|
95
|
+
const path = decoded(url.pathname);
|
|
96
|
+
const at = shownAt(standard)[path];
|
|
97
|
+
if (at) return at;
|
|
98
|
+
const target = moved[path] ?? map.paths[path];
|
|
99
|
+
return target && onward(target);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// An address with a broken %-sign (/%E0%A4%A/) cannot be decoded; it is looked up as it is (and ends
|
|
103
|
+
// on the 404 page) rather than making the Worker fail.
|
|
104
|
+
function decoded(path) {
|
|
105
|
+
try {
|
|
106
|
+
return decodeURIComponent(path);
|
|
107
|
+
} catch {
|
|
108
|
+
return path;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Old links without the trailing slash (/kontakt instead of /kontakt/): the assets only redirect those
|
|
113
|
+
// for pages that exist, and _redirects rules only match the exact address. Look up the address with
|
|
114
|
+
// the slash and send straight to where that ends, so there is one redirect and no 404.
|
|
115
|
+
async function withSlash(url, request, env) {
|
|
116
|
+
if (url.pathname.endsWith('/') || /\.[a-z0-9]+$/i.test(url.pathname)) return undefined;
|
|
117
|
+
const slashed = new URL(url);
|
|
118
|
+
slashed.pathname += '/';
|
|
119
|
+
const mapped = redirectFor(slashed);
|
|
120
|
+
if (mapped) return mapped;
|
|
121
|
+
const res = await env.ASSETS.fetch(new Request(slashed, { method: 'HEAD', headers: request.headers, redirect: 'manual' }));
|
|
122
|
+
if (res.status >= 300 && res.status < 400) return onwardOf(res.headers.get('location'), url) ?? undefined;
|
|
123
|
+
return res.ok ? slashed.pathname + slashed.search : undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// A redirect from the assets (public/_redirects) to an address the editor has sent on: straight on.
|
|
127
|
+
function onwardOf(location, url) {
|
|
128
|
+
if (!location) return location;
|
|
129
|
+
const to = new URL(location, url.origin);
|
|
130
|
+
const end = to.origin === url.origin ? onward(to.pathname + to.hash) : to.pathname + to.hash;
|
|
131
|
+
return end === to.pathname + to.hash ? location : end;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default {
|
|
135
|
+
async fetch(request, env, ctx) {
|
|
136
|
+
const url = new URL(request.url);
|
|
137
|
+
// The editor on its own address: what belongs on the other one is sent there.
|
|
138
|
+
const elsewhere = editorHost(url, env, request);
|
|
139
|
+
if (elsewhere) return elsewhere;
|
|
140
|
+
// Sign-in for /editor/ first: /oauth/auth has no trailing slash and must not be redirected.
|
|
141
|
+
const oauth = url.pathname.startsWith('/oauth/') ? await handleOAuth(request, env) : null;
|
|
142
|
+
if (url.pathname === '/api/not-found') return handleNotFound(request, env);
|
|
143
|
+
if (url.pathname === '/api/consent') return handleConsent(request, env);
|
|
144
|
+
if (url.pathname === '/api/r2') return handleR2(request, env);
|
|
145
|
+
if (url.pathname.startsWith('/api/github/')) return handleGitHub(request, env, ctx);
|
|
146
|
+
if (/^\/api\/push(\/wait)?\/?$/.test(url.pathname)) return handlePush(request, env);
|
|
147
|
+
const answer = appAnswer(request, url, env, ctx);
|
|
148
|
+
if (answer) return answer;
|
|
149
|
+
// The build's lists (what is Planlagt) and the apps' files (what the cookie scan visits) are the Worker's own.
|
|
150
|
+
if (url.pathname === SCHEDULED_LIST || url.pathname.startsWith('/_apps/')) return new Response('Not found', { status: 404 });
|
|
151
|
+
const target = oauth ? undefined : (redirectFor(url) ?? (await withSlash(url, request, env)));
|
|
152
|
+
let response = target
|
|
153
|
+
? new Response(null, { status: 301, headers: { location: new URL(target, url.origin).href } })
|
|
154
|
+
: (oauth ?? (await env.ASSETS.fetch(request)));
|
|
155
|
+
const location = !target && !oauth && response.status >= 300 && response.status < 400 && response.headers.get('location');
|
|
156
|
+
const end = location && onwardOf(location, url);
|
|
157
|
+
if (end && end !== location)
|
|
158
|
+
response = new Response(null, { status: response.status, headers: { location: new URL(end, url.origin).href } });
|
|
159
|
+
|
|
160
|
+
const editor = url.hostname === env.EDITOR_HOST?.trim().toLowerCase();
|
|
161
|
+
const staging = STAGING.test(url.hostname) || editor;
|
|
162
|
+
// A visitor on the site's 404 page: the address is kept for the editor, after the answer has gone.
|
|
163
|
+
// Also on *.workers.dev, the site's address until its own domain points here; the previews of
|
|
164
|
+
// branches have no storage and keep nothing.
|
|
165
|
+
if (!editor && response.status === 404 && isVisit(request, url)) ctx.waitUntil(recordNotFound(request, url, env));
|
|
166
|
+
if (!staging) return response;
|
|
167
|
+
const staged = new Response(response.body, response);
|
|
168
|
+
staged.headers.set('X-Robots-Tag', 'noindex, nofollow');
|
|
169
|
+
return staged;
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
// The crons in wrangler.jsonc: each minute, whether the site has been built with a save (packages/editor/worker/push.js); each hour,
|
|
173
|
+
// the rest (also the answers in the consent log older than two years deleted).
|
|
174
|
+
async scheduled(controller, env, ctx) {
|
|
175
|
+
if (controller.cron === EACH_MINUTE) return ctx.waitUntil(waitForBuilds(env));
|
|
176
|
+
ctx.waitUntil(
|
|
177
|
+
Promise.all([
|
|
178
|
+
publishScheduled(env),
|
|
179
|
+
forgetOldConsents(env),
|
|
180
|
+
...Object.entries(installed).map(([id, app]) => app.scheduled?.(env, ctx, tools(id, ctx))),
|
|
181
|
+
]),
|
|
182
|
+
);
|
|
183
|
+
},
|
|
184
|
+
};
|