@jant/core 0.6.16 → 0.7.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/README.md +11 -11
- package/bin/commands/assets/upload.js +192 -15
- package/bin/commands/import-site.js +60 -4
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/site/snapshot/export.js +1 -1
- package/bin/commands/site/snapshot/import.js +1 -1
- package/bin/lib/d1-query.js +5 -16
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-selection.js +85 -29
- package/bin/lib/wrangler-cli.js +28 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/dist/{app-C7_4oab9.js → app-K_Aa1MMn.js} +52476 -13330
- package/dist/client/.vite/manifest.json +255 -90
- package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
- package/dist/client/_assets/chunks/create-editor-CD3FhrOB.js +257 -0
- package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
- package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
- package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
- package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
- package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
- package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
- package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
- package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
- package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
- package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
- package/dist/client/_assets/chunks/sortable-list-CgaL2jCs.js +2 -0
- package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
- package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
- package/dist/client/_assets/chunks/unsafe-svg-0QCkP0vZ.js +2 -0
- package/dist/client/_assets/client-GiYENVw8.css +2 -0
- package/dist/client/_assets/client-VnFxJN7G.js +295 -0
- package/dist/client/_assets/client-auth-k8gJ6QJj.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-B_kDtWkW.js +2004 -0
- package/dist/client/_assets/client-manage-M90aSTOk.js +2170 -0
- package/dist/client/_assets/client-settings-DfYs9n1F.js +1112 -0
- package/dist/{export-Dl5KCiEs.js → github-sync-BPAvT999.js} +2254 -107
- package/dist/index.js +2 -5
- package/dist/node.js +5 -7
- package/package.json +3 -3
- package/src/__tests__/auth-session-freshness.test.ts +57 -0
- package/src/__tests__/basecoat-variants.test.ts +121 -0
- package/src/__tests__/bin/assets-upload.test.ts +68 -0
- package/src/__tests__/export-hugo-build.test.ts +260 -0
- package/src/__tests__/export-service.test.ts +231 -0
- package/src/__tests__/helpers/component-css.ts +91 -0
- package/src/__tests__/helpers/db.ts +33 -0
- package/src/__tests__/helpers/export-fixtures.ts +3 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/app.tsx +71 -16
- package/src/auth.ts +18 -1
- package/src/client/__tests__/collection-page-actions.test.ts +51 -2
- package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
- package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
- package/src/client/__tests__/compose-bridge.test.ts +272 -0
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
- package/src/client/__tests__/compose-triggers.test.ts +90 -0
- package/src/client/__tests__/copy-field.test.ts +162 -0
- package/src/client/__tests__/custom-url-menu.test.ts +127 -6
- package/src/client/__tests__/image-processor.test.ts +324 -2
- package/src/client/__tests__/lazy-entries.test.ts +92 -0
- package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
- package/src/client/__tests__/runtime-paths.test.ts +84 -0
- package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
- package/src/client/__tests__/toast.test.ts +23 -0
- package/src/client/__tests__/video-processor-probe.test.ts +175 -0
- package/src/client/__tests__/video-processor.test.ts +181 -0
- package/src/client/audio-processor.ts +24 -15
- package/src/client/audio-waveform.ts +49 -0
- package/src/client/collection-dialog-host.ts +72 -0
- package/src/client/collection-navigation.ts +51 -16
- package/src/client/collection-page-actions.ts +26 -0
- package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
- package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
- package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
- package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
- package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
- package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
- package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
- package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
- package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
- package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
- package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
- package/src/client/components/collection-dialog-types.ts +49 -0
- package/src/client/components/collection-manager-types.ts +32 -1
- package/src/client/components/collection-types.ts +1 -15
- package/src/client/components/compose-types.ts +75 -1
- package/src/client/components/jant-collection-dialog.ts +722 -0
- package/src/client/components/jant-collection-directory.ts +567 -182
- package/src/client/components/jant-collection-form.ts +53 -232
- package/src/client/components/jant-command-palette.ts +100 -49
- package/src/client/components/jant-compose-dialog.ts +1486 -707
- package/src/client/components/jant-compose-editor.ts +673 -336
- package/src/client/components/jant-compose-fullscreen.ts +51 -43
- package/src/client/components/jant-config-editor.ts +65 -53
- package/src/client/components/jant-confirm-dialog.ts +13 -11
- package/src/client/components/jant-locale-picker.ts +418 -0
- package/src/client/components/jant-media-lightbox.ts +146 -136
- package/src/client/components/jant-nav-manager.ts +690 -366
- package/src/client/components/jant-post-menu.ts +1086 -282
- package/src/client/components/jant-post-picker.ts +324 -0
- package/src/client/components/jant-repo-picker.ts +138 -106
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +617 -543
- package/src/client/components/jant-settings-language.ts +1087 -0
- package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
- package/src/client/components/jant-text-preview.ts +94 -86
- package/src/client/components/nav-manager-types.ts +62 -1
- package/src/client/components/settings-types.ts +14 -29
- package/src/client/components/smart-collection-conditions.ts +285 -0
- package/src/client/components/smart-collection-dialog-types.ts +68 -0
- package/src/client/compose-bridge.ts +214 -141
- package/src/client/compose-launch.ts +76 -2
- package/src/client/compose-shortcuts.ts +6 -8
- package/src/client/compose-triggers.ts +50 -0
- package/src/client/copy-field.ts +108 -0
- package/src/client/custom-url-menu.ts +60 -4
- package/src/client/icons.ts +14 -0
- package/src/client/image-plan.ts +165 -0
- package/src/client/image-processor.ts +195 -117
- package/src/client/image-worker-client.ts +45 -0
- package/src/client/image-worker.ts +135 -0
- package/src/client/lazy-entries.ts +95 -0
- package/src/client/lazy-slugify.ts +2 -2
- package/src/client/media-scroll-hint.ts +43 -2
- package/src/client/mediabunny.ts +29 -0
- package/src/client/post-picker.ts +41 -0
- package/src/client/runtime-paths.ts +74 -1
- package/src/client/settings-bridge.ts +4 -10
- package/src/client/site-header-nav.js +18 -6
- package/src/client/smart-collection-dialog-host.ts +116 -0
- package/src/client/smart-collection-page-actions.ts +208 -0
- package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
- package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
- package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/floating-position.ts +216 -2
- package/src/client/tiptap/inline-image-upload.ts +7 -3
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/toast.ts +13 -0
- package/src/client/types/vite-worker.d.ts +12 -0
- package/src/client/upload-with-metadata.ts +3 -13
- package/src/client/video-processor.ts +351 -107
- package/src/client-auth.ts +14 -31
- package/src/client-compose.ts +19 -0
- package/src/client-manage.ts +13 -0
- package/src/client-settings.ts +14 -0
- package/src/client-site.ts +10 -3
- package/src/client.ts +4 -0
- package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
- package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
- package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0032_married_tomas.sql +3 -0
- package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
- package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
- package/src/db/migrations/meta/0032_snapshot.json +2582 -0
- package/src/db/migrations/meta/0033_snapshot.json +2816 -0
- package/src/db/migrations/meta/0034_snapshot.json +2816 -0
- package/src/db/migrations/meta/_journal.json +21 -0
- package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
- package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
- package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
- package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
- package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/_journal.json +21 -0
- package/src/db/pg/schema.ts +195 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +222 -27
- package/src/i18n/__tests__/detect.test.ts +26 -81
- package/src/i18n/__tests__/supported-locales.test.ts +83 -0
- package/src/i18n/context.tsx +12 -2
- package/src/i18n/detect.ts +7 -66
- package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
- package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
- package/src/i18n/locales/public/en.po +490 -158
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +487 -155
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +487 -155
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +551 -69
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +597 -115
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +610 -128
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +94 -0
- package/src/i18n/middleware.ts +12 -2
- package/src/i18n/supported-locales.ts +80 -0
- package/src/lib/__tests__/api-settings.test.ts +11 -2
- package/src/lib/__tests__/asset-path.test.ts +92 -0
- package/src/lib/__tests__/deferred.test.ts +90 -0
- package/src/lib/__tests__/discover-ping.test.ts +89 -0
- package/src/lib/__tests__/discover.test.ts +410 -0
- package/src/lib/__tests__/feed-policy.test.ts +26 -0
- package/src/lib/__tests__/feed.test.ts +1526 -15
- package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
- package/src/lib/__tests__/hosted-signin.test.ts +1 -1
- package/src/lib/__tests__/http-cache.test.ts +162 -0
- package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
- package/src/lib/__tests__/jant-docs.test.ts +23 -0
- package/src/lib/__tests__/lang-detect.test.ts +231 -0
- package/src/lib/__tests__/media-helpers.test.ts +81 -0
- package/src/lib/__tests__/navigation.test.ts +135 -1
- package/src/lib/__tests__/resolve-config.test.ts +64 -3
- package/src/lib/__tests__/schemas.test.ts +42 -4
- package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
- package/src/lib/__tests__/slug.test.ts +70 -0
- package/src/lib/__tests__/slugify.test.ts +85 -0
- package/src/lib/__tests__/storage.test.ts +32 -0
- package/src/lib/__tests__/thread-fold.test.ts +113 -0
- package/src/lib/__tests__/upload-sideload.test.ts +14 -0
- package/src/lib/__tests__/url-fetch.test.ts +1 -2
- package/src/lib/__tests__/url.test.ts +78 -72
- package/src/lib/__tests__/view-language.test.ts +413 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
- package/src/lib/address.ts +57 -0
- package/src/lib/api-posts.ts +12 -0
- package/src/lib/asset-path.ts +98 -1
- package/src/lib/build-env.ts +16 -0
- package/src/lib/collection-groups.ts +5 -1
- package/src/lib/collection-paths.ts +3 -10
- package/src/lib/constants.ts +28 -3
- package/src/lib/copy-field.ts +36 -0
- package/src/lib/crypto.ts +40 -0
- package/src/lib/csp-builder.ts +2 -1
- package/src/lib/deferred.ts +79 -0
- package/src/lib/discover-ping.ts +141 -0
- package/src/lib/discover.ts +437 -0
- package/src/lib/env.ts +134 -0
- package/src/lib/errors.ts +33 -0
- package/src/lib/feed-policy.ts +236 -2
- package/src/lib/feed.ts +926 -73
- package/src/lib/filter-dimensions.ts +1349 -0
- package/src/lib/github-sync-site-config.ts +3 -0
- package/src/lib/hosted-signin.ts +1 -1
- package/src/lib/http-cache.ts +148 -0
- package/src/lib/hugo-markdown.ts +16 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/media-helpers.ts +13 -1
- package/src/lib/navigation.ts +78 -11
- package/src/lib/pagination.ts +28 -24
- package/src/lib/per-language-surfaces.ts +67 -0
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +53 -5
- package/src/lib/schemas.ts +201 -10
- package/src/lib/site-header-fragment.tsx +2 -1
- package/src/lib/site-unavailable-page.ts +47 -0
- package/src/lib/slug.ts +51 -7
- package/src/lib/slugify.ts +113 -0
- package/src/lib/startup-config.ts +4 -6
- package/src/lib/storage.ts +7 -0
- package/src/lib/summary.ts +50 -0
- package/src/lib/thread-fold.ts +161 -0
- package/src/lib/time.ts +32 -0
- package/src/lib/timeline.ts +42 -1
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +139 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +51 -23
- package/src/lib/viewer-context.ts +62 -0
- package/src/middleware/__tests__/auth.test.ts +70 -2
- package/src/middleware/__tests__/cache-control.test.ts +71 -3
- package/src/middleware/__tests__/onboarding.test.ts +76 -5
- package/src/middleware/__tests__/secure-headers.test.ts +1 -1
- package/src/middleware/__tests__/session.test.ts +181 -33
- package/src/middleware/auth.ts +50 -31
- package/src/middleware/cache-control.ts +75 -10
- package/src/middleware/config.ts +6 -6
- package/src/middleware/error-handler.ts +12 -0
- package/src/middleware/onboarding.ts +32 -10
- package/src/middleware/secure-headers.ts +1 -1
- package/src/middleware/session.ts +96 -8
- package/src/node/__tests__/cli-migrate.test.ts +21 -19
- package/src/node/__tests__/cli-reset-password.test.ts +81 -8
- package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/runtime.test.ts +37 -0
- package/src/node/index.ts +1 -0
- package/src/preset.css +76 -22
- package/src/routes/__tests__/feed-validator.test.ts +156 -0
- package/src/routes/api/__tests__/collections.test.ts +114 -0
- package/src/routes/api/__tests__/discover.test.ts +222 -0
- package/src/routes/api/__tests__/nav-items.test.ts +212 -2
- package/src/routes/api/__tests__/post-translations.test.ts +829 -0
- package/src/routes/api/__tests__/settings.test.ts +24 -3
- package/src/routes/api/__tests__/telegram.test.ts +6 -5
- package/src/routes/api/collections.ts +49 -6
- package/src/routes/api/custom-urls.ts +0 -1
- package/src/routes/api/discover.ts +95 -0
- package/src/routes/api/export.ts +3 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +159 -0
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +23 -6
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/uploads.ts +1 -2
- package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
- package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
- package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
- package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
- package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
- package/src/routes/auth/__tests__/setup.test.ts +278 -26
- package/src/routes/auth/__tests__/signin.test.ts +135 -0
- package/src/routes/auth/setup.tsx +934 -186
- package/src/routes/auth/signin.tsx +10 -8
- package/src/routes/compose.tsx +5 -0
- package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
- package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
- package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
- package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
- package/src/routes/dash/custom-urls.tsx +1 -37
- package/src/routes/dash/settings.tsx +502 -41
- package/src/routes/discover-announce.ts +96 -0
- package/src/routes/feed/__tests__/feed.test.ts +165 -2
- package/src/routes/feed/feed.ts +69 -40
- package/src/routes/feed/sitemap.ts +95 -11
- package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
- package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
- package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
- package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
- package/src/routes/pages/__tests__/collections.test.ts +9 -3
- package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
- package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
- package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
- package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
- package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
- package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
- package/src/routes/pages/archive.tsx +471 -369
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +32 -13
- package/src/routes/pages/home.tsx +25 -6
- package/src/routes/pages/language.tsx +113 -0
- package/src/routes/pages/latest.tsx +26 -16
- package/src/routes/pages/new.tsx +3 -0
- package/src/routes/pages/page.tsx +178 -17
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +18 -3
- package/src/routes/pages/smart-collection.tsx +340 -0
- package/src/routes/pages/subscribe.tsx +182 -0
- package/src/runtime/__tests__/index.test.ts +19 -0
- package/src/runtime/__tests__/node.test.ts +129 -4
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +18 -1
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +180 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post.test.ts +46 -9
- package/src/services/__tests__/settings.test.ts +228 -17
- package/src/services/__tests__/site-admin.test.ts +5 -5
- package/src/services/__tests__/smart-collection.test.ts +651 -0
- package/src/services/about-page.ts +22 -1
- package/src/services/auth.ts +51 -49
- package/src/services/bootstrap.ts +188 -38
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +27 -22
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +123 -123
- package/src/services/export-theme/layouts/_default/rss.xml +77 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
- package/src/services/export-theme/layouts/partials/pagination.html +14 -13
- package/src/services/export-theme/layouts/partials/post-card.html +8 -4
- package/src/services/export-theme/layouts/partials/reply.html +2 -1
- package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
- package/src/services/export-theme/styles/main.css +13 -1
- package/src/services/export.ts +278 -19
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +3 -1
- package/src/services/media.ts +14 -6
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +221 -6
- package/src/services/post.ts +1021 -152
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +24 -7
- package/src/services/site.ts +77 -1
- package/src/services/smart-collection.ts +636 -0
- package/src/services/telegram.ts +20 -22
- package/src/services/upload-session.ts +1 -3
- package/src/style-author.css +27 -0
- package/src/styles/components-author.css +1622 -0
- package/src/styles/components.css +24 -1571
- package/src/styles/site-media.css +16 -2
- package/src/styles/tokens.css +82 -12
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1087 -7219
- package/src/types/app-context.ts +7 -0
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +94 -15
- package/src/types/constants.ts +188 -16
- package/src/types/entities.ts +87 -1
- package/src/types/operations.ts +64 -2
- package/src/types/props.ts +157 -31
- package/src/types/views.ts +55 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +201 -7
- package/src/ui/compose/ComposePrompt.tsx +1 -5
- package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
- package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
- package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
- package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
- package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
- package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
- package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
- package/src/ui/dash/settings/GeneralContent.tsx +275 -113
- package/src/ui/dash/settings/LanguageContent.tsx +409 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
- package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
- package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
- package/src/ui/dash/settings/discover-copy.ts +116 -0
- package/src/ui/feed/LinkCard.tsx +55 -45
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +90 -43
- package/src/ui/feed/QuoteCard.tsx +55 -30
- package/src/ui/feed/ThreadPreview.tsx +6 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
- package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
- package/src/ui/feed/thread-preview-state.ts +20 -6
- package/src/ui/font-themes.ts +39 -29
- package/src/ui/layouts/BaseLayout.tsx +155 -10
- package/src/ui/layouts/SiteLayout.tsx +192 -34
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
- package/src/ui/pages/ArchivePage.tsx +282 -289
- package/src/ui/pages/CollectionPage.tsx +86 -76
- package/src/ui/pages/CollectionsPage.tsx +5 -0
- package/src/ui/pages/ComposePage.tsx +8 -0
- package/src/ui/pages/PostPage.tsx +47 -0
- package/src/ui/pages/SearchPage.tsx +3 -3
- package/src/ui/pages/SmartCollectionPage.tsx +434 -0
- package/src/ui/pages/SubscribePage.tsx +236 -0
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
- package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
- package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
- package/src/ui/shared/CollectionDirectory.tsx +178 -3
- package/src/ui/shared/CollectionsManager.tsx +70 -17
- package/src/ui/shared/CopyField.tsx +127 -0
- package/src/ui/shared/EmptyPostContent.tsx +23 -0
- package/src/ui/shared/MediaGallery.tsx +125 -46
- package/src/ui/shared/Pagination.tsx +20 -5
- package/src/ui/shared/PostFooter.tsx +20 -11
- package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
- package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
- package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
- package/src/ui/shared/__tests__/pagination.test.ts +38 -24
- package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
- package/src/ui/shared/archive-labels.ts +98 -0
- package/src/ui/shared/collection-management-labels.ts +94 -16
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +2 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-C75Zc8dD.js +0 -6
- package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
- package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
- package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
- package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
- package/dist/client/_assets/client-D3VZyG4L.css +0 -2
- package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
- package/dist/env-BPYViDJJ.js +0 -281
- package/dist/github-api-BNF35Lkx.js +0 -176
- package/dist/github-app-DdiD-bC4.js +0 -275
- package/dist/github-sync-Bqg62IvV.js +0 -4
- package/dist/github-sync-HdK2EgvJ.js +0 -431
- package/dist/url-CbLAtlZe.js +0 -900
- package/src/client/__tests__/collection-created-notice.test.ts +0 -36
- package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
- package/src/client/__tests__/post-form-bridge.test.ts +0 -82
- package/src/client/collection-created-notice.ts +0 -66
- package/src/client/collection-form-bridge.ts +0 -191
- package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
- package/src/client/components/jant-post-form.ts +0 -422
- package/src/client/components/post-form-template.ts +0 -352
- package/src/client/components/post-form-types.ts +0 -96
- package/src/client/media-metadata.ts +0 -247
- package/src/client/post-form-bridge.ts +0 -193
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
|
@@ -1,26 +1,72 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import {
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
3
|
+
import {
|
|
4
|
+
createTestDatabase,
|
|
5
|
+
DEFAULT_TEST_SITE_ID,
|
|
6
|
+
} from "../../../__tests__/helpers/db.js";
|
|
3
7
|
import { sql } from "drizzle-orm";
|
|
4
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
navItems,
|
|
10
|
+
settings,
|
|
11
|
+
siteDomains,
|
|
12
|
+
siteMembers,
|
|
13
|
+
sites,
|
|
14
|
+
} from "../../../db/schema.js";
|
|
5
15
|
import { createBootstrapService } from "../../../services/bootstrap.js";
|
|
16
|
+
import {
|
|
17
|
+
createSiteService,
|
|
18
|
+
TRANSIENT_SINGLE_SITE_ID,
|
|
19
|
+
} from "../../../services/site.js";
|
|
6
20
|
import type { Database } from "../../../db/index.js";
|
|
7
21
|
import type { BootstrapService } from "../../../services/bootstrap.js";
|
|
8
22
|
|
|
9
23
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
24
|
+
* The bootstrap service one self-hosted setup request gets: bound to the site
|
|
25
|
+
* that request resolved, the way the runtime binds it. Resolved afresh for
|
|
26
|
+
* each screen, because the account step is what creates the site the second
|
|
27
|
+
* screen finds.
|
|
28
|
+
*/
|
|
29
|
+
async function bootstrapForRequest(db: Database): Promise<BootstrapService> {
|
|
30
|
+
const { site } = await createSiteService(db).resolveSingleSite();
|
|
31
|
+
return createBootstrapService(db, site.id);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reproduces both halves of POST /setup back to back — the shape a self-hosted
|
|
36
|
+
* install ends up in — to verify setup stays idempotent even when managed shell
|
|
37
|
+
* data already exists.
|
|
12
38
|
*/
|
|
13
39
|
async function runSetupBootstrap(
|
|
14
|
-
services: {
|
|
15
|
-
overrides: Partial<
|
|
16
|
-
Parameters<BootstrapService["completeInitialSetup"]>[0]
|
|
17
|
-
> = {},
|
|
40
|
+
services: { db: Database },
|
|
41
|
+
overrides: Partial<Parameters<BootstrapService["completeSiteSetup"]>[0]> = {},
|
|
18
42
|
) {
|
|
19
|
-
await
|
|
43
|
+
await (
|
|
44
|
+
await bootstrapForRequest(services.db)
|
|
45
|
+
).provisionOwnerAccount({
|
|
20
46
|
ownerUserId: "usr_test-owner",
|
|
21
|
-
siteName: "Jant Demo",
|
|
22
|
-
...overrides,
|
|
23
47
|
});
|
|
48
|
+
await (
|
|
49
|
+
await bootstrapForRequest(services.db)
|
|
50
|
+
).completeSiteSetup(
|
|
51
|
+
{ siteName: "Jant Demo", ...overrides },
|
|
52
|
+
{ oldLanguage: "" },
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Adds a second tenant, which is what a hosted database always has. */
|
|
57
|
+
async function insertOtherSite(
|
|
58
|
+
db: Database,
|
|
59
|
+
id = "sit_other00000000000000000000",
|
|
60
|
+
) {
|
|
61
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
62
|
+
await db.insert(sites).values({
|
|
63
|
+
id,
|
|
64
|
+
key: "other",
|
|
65
|
+
status: "active",
|
|
66
|
+
createdAt: timestamp - 60,
|
|
67
|
+
updatedAt: timestamp - 60,
|
|
68
|
+
});
|
|
69
|
+
return id;
|
|
24
70
|
}
|
|
25
71
|
|
|
26
72
|
describe("Setup bootstrap logic", () => {
|
|
@@ -34,7 +80,8 @@ describe("Setup bootstrap logic", () => {
|
|
|
34
80
|
const db = testDb.db as unknown as Database;
|
|
35
81
|
services = {
|
|
36
82
|
db,
|
|
37
|
-
|
|
83
|
+
// The site both screens resolve once its row exists.
|
|
84
|
+
bootstrap: createBootstrapService(db, DEFAULT_TEST_SITE_ID),
|
|
38
85
|
};
|
|
39
86
|
});
|
|
40
87
|
|
|
@@ -46,9 +93,9 @@ describe("Setup bootstrap logic", () => {
|
|
|
46
93
|
|
|
47
94
|
expect(navItemsList.map((item) => item.systemKey)).toEqual([
|
|
48
95
|
"featured",
|
|
49
|
-
"collections",
|
|
50
96
|
"archive",
|
|
51
|
-
"
|
|
97
|
+
"collections",
|
|
98
|
+
"subscribe",
|
|
52
99
|
"settings",
|
|
53
100
|
]);
|
|
54
101
|
});
|
|
@@ -61,35 +108,150 @@ describe("Setup bootstrap logic", () => {
|
|
|
61
108
|
expect(onboardingRow?.value).toBe("completed");
|
|
62
109
|
});
|
|
63
110
|
|
|
64
|
-
|
|
111
|
+
// The state the site rests in between the two setup screens. It has to be
|
|
112
|
+
// `provisioned` and not `pending`: that is what lets an author who lost the
|
|
113
|
+
// session in between sign back in and finish, rather than being bounced back
|
|
114
|
+
// to a form that would try to create their account a second time.
|
|
115
|
+
it("leaves the site provisioned and unnamed after the account step", async () => {
|
|
116
|
+
await services.bootstrap.provisionOwnerAccount({
|
|
117
|
+
ownerUserId: "usr_test-owner",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const rows = await services.db.select().from(settings);
|
|
121
|
+
expect(rows.find((row) => row.key === "ONBOARDING_STATUS")?.value).toBe(
|
|
122
|
+
"provisioned",
|
|
123
|
+
);
|
|
124
|
+
expect(rows.find((row) => row.key === "SITE_NAME")).toBeUndefined();
|
|
125
|
+
|
|
126
|
+
const navItemsList = await services.db.select().from(navItems);
|
|
127
|
+
expect(navItemsList).toHaveLength(5);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// The hosted path through the same method: the control plane named the site
|
|
131
|
+
// at creation, and the author answering the last screen must not blank it.
|
|
132
|
+
it("leaves an existing name alone when the last screen asked for none", async () => {
|
|
133
|
+
await runSetupBootstrap(services);
|
|
134
|
+
await services.bootstrap.completeSiteSetup(
|
|
135
|
+
{ siteLanguage: "en" },
|
|
136
|
+
{ oldLanguage: "en" },
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const rows = await services.db.select().from(settings);
|
|
140
|
+
expect(rows.find((row) => row.key === "SITE_NAME")?.value).toBe(
|
|
141
|
+
"Jant Demo",
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("renames the operator's account to match the site", async () => {
|
|
146
|
+
const updateCurrentUserName = vi.fn(async () => undefined);
|
|
147
|
+
|
|
148
|
+
await services.bootstrap.provisionOwnerAccount({
|
|
149
|
+
ownerUserId: "usr_test-owner",
|
|
150
|
+
});
|
|
151
|
+
await services.bootstrap.completeSiteSetup(
|
|
152
|
+
{ siteName: " Jant Demo " },
|
|
153
|
+
{ oldLanguage: "" },
|
|
154
|
+
{ updateCurrentUserName },
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(updateCurrentUserName).toHaveBeenCalledWith("Jant Demo");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// The rename runs before onboarding closes, so a better-auth failure leaves
|
|
161
|
+
// the author on the screen they were already on instead of inside a finished
|
|
162
|
+
// site under a stale account name.
|
|
163
|
+
it("leaves setup open when the account rename fails", async () => {
|
|
164
|
+
await services.bootstrap.provisionOwnerAccount({
|
|
165
|
+
ownerUserId: "usr_test-owner",
|
|
166
|
+
});
|
|
167
|
+
await expect(
|
|
168
|
+
services.bootstrap.completeSiteSetup(
|
|
169
|
+
{ siteName: "Jant Demo" },
|
|
170
|
+
{ oldLanguage: "" },
|
|
171
|
+
{
|
|
172
|
+
updateCurrentUserName: async () => {
|
|
173
|
+
throw new Error("better-auth unavailable");
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
),
|
|
177
|
+
).rejects.toThrow("better-auth unavailable");
|
|
178
|
+
|
|
179
|
+
const rows = await services.db.select().from(settings);
|
|
180
|
+
expect(rows.find((row) => row.key === "ONBOARDING_STATUS")?.value).toBe(
|
|
181
|
+
"provisioned",
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("stores the chosen content language and timezone during setup", async () => {
|
|
65
186
|
await runSetupBootstrap(services, {
|
|
66
187
|
siteLanguage: "en",
|
|
67
|
-
cjkSerifFont: "zh-Hans",
|
|
68
188
|
timeZone: "Asia/Shanghai",
|
|
69
189
|
});
|
|
70
190
|
|
|
71
191
|
const rows = await services.db.select().from(settings);
|
|
72
192
|
expect(rows.find((row) => row.key === "SITE_LANGUAGE")?.value).toBe("en");
|
|
73
|
-
expect(rows.find((row) => row.key === "CJK_SERIF_FONT")?.value).toBe(
|
|
74
|
-
"zh-Hans",
|
|
75
|
-
);
|
|
76
193
|
expect(rows.find((row) => row.key === "TIME_ZONE")?.value).toBe(
|
|
77
194
|
"Asia/Shanghai",
|
|
78
195
|
);
|
|
79
196
|
});
|
|
80
197
|
|
|
81
|
-
it("pins the dashboard language
|
|
198
|
+
it("pins the dashboard language when only the browser knows it", async () => {
|
|
199
|
+
// Someone running an English blog from a Chinese browser: English site,
|
|
200
|
+
// Chinese dashboard. Following the content language would miss this.
|
|
201
|
+
await runSetupBootstrap(services, {
|
|
202
|
+
siteLanguage: "en",
|
|
203
|
+
browserLanguage: "zh-TW",
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const rows = await services.db.select().from(settings);
|
|
207
|
+
expect(rows.find((row) => row.key === "SITE_LANGUAGE")?.value).toBe("en");
|
|
208
|
+
expect(rows.find((row) => row.key === "DASHBOARD_LANGUAGE")?.value).toBe(
|
|
209
|
+
"zh-Hant",
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("leaves the dashboard following a content language the author chose", async () => {
|
|
214
|
+
// The reverse mismatch, and the common one: an English browser is what
|
|
215
|
+
// every unconfigured machine reports, so it is no evidence against the
|
|
216
|
+
// language just chosen by hand. The dashboard must not freeze to English.
|
|
217
|
+
await runSetupBootstrap(services, {
|
|
218
|
+
siteLanguage: "zh-Hans",
|
|
219
|
+
browserLanguage: "en",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const rows = await services.db.select().from(settings);
|
|
223
|
+
expect(rows.find((row) => row.key === "SITE_LANGUAGE")?.value).toBe(
|
|
224
|
+
"zh-Hans",
|
|
225
|
+
);
|
|
226
|
+
expect(
|
|
227
|
+
rows.find((row) => row.key === "DASHBOARD_LANGUAGE"),
|
|
228
|
+
).toBeUndefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("leaves the dashboard following when the browser sent none", async () => {
|
|
82
232
|
await runSetupBootstrap(services, { siteLanguage: "zh-TW" });
|
|
83
233
|
|
|
84
234
|
const rows = await services.db.select().from(settings);
|
|
85
|
-
// Content language stays verbatim
|
|
86
|
-
//
|
|
235
|
+
// Content language stays verbatim, and the dashboard derives zh-Hant from
|
|
236
|
+
// it at render time rather than being pinned here.
|
|
87
237
|
expect(rows.find((row) => row.key === "SITE_LANGUAGE")?.value).toBe(
|
|
88
238
|
"zh-TW",
|
|
89
239
|
);
|
|
90
|
-
expect(
|
|
91
|
-
"
|
|
92
|
-
);
|
|
240
|
+
expect(
|
|
241
|
+
rows.find((row) => row.key === "DASHBOARD_LANGUAGE"),
|
|
242
|
+
).toBeUndefined();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("leaves the dashboard following when browser and content agree", async () => {
|
|
246
|
+
await runSetupBootstrap(services, {
|
|
247
|
+
siteLanguage: "zh-Hans",
|
|
248
|
+
browserLanguage: "zh-CN",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const rows = await services.db.select().from(settings);
|
|
252
|
+
expect(
|
|
253
|
+
rows.find((row) => row.key === "DASHBOARD_LANGUAGE"),
|
|
254
|
+
).toBeUndefined();
|
|
93
255
|
});
|
|
94
256
|
|
|
95
257
|
it("is idempotent when default navigation already exists", async () => {
|
|
@@ -141,5 +303,95 @@ describe("Setup bootstrap logic", () => {
|
|
|
141
303
|
const domainRows = await services.db.select().from(siteDomains);
|
|
142
304
|
expect(siteRows).toHaveLength(1);
|
|
143
305
|
expect(domainRows).toHaveLength(0);
|
|
306
|
+
|
|
307
|
+
const rows = await services.db.select().from(settings);
|
|
308
|
+
expect(rows.find((row) => row.key === "SITE_NAME")?.siteId).toBe(
|
|
309
|
+
siteRows[0]?.id,
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Settings written under the placeholder would belong to no site at all.
|
|
314
|
+
it("refuses to finish a site the account step has not created", async () => {
|
|
315
|
+
await services.db.run(sql`DELETE FROM "site"`);
|
|
316
|
+
|
|
317
|
+
await expect(
|
|
318
|
+
createBootstrapService(
|
|
319
|
+
services.db,
|
|
320
|
+
TRANSIENT_SINGLE_SITE_ID,
|
|
321
|
+
).completeSiteSetup({ siteName: "Jant Demo" }, { oldLanguage: "" }),
|
|
322
|
+
).rejects.toThrow("completeSiteSetup needs an existing site");
|
|
323
|
+
|
|
324
|
+
expect(await services.db.select().from(settings)).toHaveLength(0);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// A hosted database holds every tenant, so "the only site" has no answer there.
|
|
329
|
+
// Setup's last screen must write to the site the request's host resolved — the
|
|
330
|
+
// one the route's membership check just read — and leave every other tenant
|
|
331
|
+
// alone. The account screen has no hosted use at all.
|
|
332
|
+
describe("Setup bootstrap on a host-based install", () => {
|
|
333
|
+
function createHostedServices() {
|
|
334
|
+
const { services, db } = createTestApp({
|
|
335
|
+
siteResolutionMode: "host-based",
|
|
336
|
+
});
|
|
337
|
+
return { services, db };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
it("finishes the request's site while other sites share the database", async () => {
|
|
341
|
+
const { services, db } = createHostedServices();
|
|
342
|
+
const otherSiteId = await insertOtherSite(db);
|
|
343
|
+
|
|
344
|
+
await services.bootstrap.completeSiteSetup(
|
|
345
|
+
{ siteLanguage: "en" },
|
|
346
|
+
{ oldLanguage: "" },
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const rows = await db.select().from(settings);
|
|
350
|
+
expect(
|
|
351
|
+
rows.find(
|
|
352
|
+
(row) =>
|
|
353
|
+
row.siteId === DEFAULT_TEST_SITE_ID &&
|
|
354
|
+
row.key === "ONBOARDING_STATUS",
|
|
355
|
+
)?.value,
|
|
356
|
+
).toBe("completed");
|
|
357
|
+
expect(rows.filter((row) => row.siteId === otherSiteId)).toHaveLength(0);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// A hosted site's owner arrives through the control plane's handoff. The
|
|
361
|
+
// account step would make whoever signed in the owner of the site the host
|
|
362
|
+
// resolved, so it refuses before writing anything to any tenant.
|
|
363
|
+
it("refuses the account step and writes nothing", async () => {
|
|
364
|
+
const { services, db } = createHostedServices();
|
|
365
|
+
await insertOtherSite(db);
|
|
366
|
+
|
|
367
|
+
await expect(
|
|
368
|
+
services.bootstrap.provisionOwnerAccount({
|
|
369
|
+
ownerUserId: "usr_test-owner",
|
|
370
|
+
}),
|
|
371
|
+
).rejects.toThrow(
|
|
372
|
+
"provisionOwnerAccount runs on self-hosted installs only",
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
expect(await db.select().from(sites)).toHaveLength(2);
|
|
376
|
+
expect(await db.select().from(siteMembers)).toHaveLength(0);
|
|
377
|
+
expect(await db.select().from(navItems)).toHaveLength(0);
|
|
378
|
+
expect(await db.select().from(settings)).toHaveLength(0);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// The control plane creates hosted sites. Materializing one here would add a
|
|
382
|
+
// stray tenant to a database that holds real ones.
|
|
383
|
+
it("never creates a site when the request resolved none", async () => {
|
|
384
|
+
const db = createTestDatabase().db as unknown as Database;
|
|
385
|
+
await db.run(sql`DELETE FROM "site"`);
|
|
386
|
+
|
|
387
|
+
await expect(
|
|
388
|
+
createBootstrapService(db, TRANSIENT_SINGLE_SITE_ID, {
|
|
389
|
+
siteResolutionMode: "host-based",
|
|
390
|
+
}).provisionOwnerAccount({ ownerUserId: "usr_test-owner" }),
|
|
391
|
+
).rejects.toThrow(
|
|
392
|
+
"provisionOwnerAccount runs on self-hosted installs only",
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
expect(await db.select().from(sites)).toHaveLength(0);
|
|
144
396
|
});
|
|
145
397
|
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { createI18n } from "../../../i18n/index.js";
|
|
4
|
+
import { attachSession } from "../../../middleware/session.js";
|
|
5
|
+
import { requireAuth } from "../../../middleware/auth.js";
|
|
6
|
+
import { signinRoutes } from "../signin.js";
|
|
7
|
+
import type { Bindings } from "../../../types.js";
|
|
8
|
+
import type { AppVariables } from "../../../types/app-context.js";
|
|
9
|
+
|
|
10
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
11
|
+
|
|
12
|
+
const SIGNIN_FORM_MARKER = 'data-bind="password"';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A session for `usr_stale` that the site knows nothing about — what a browser
|
|
16
|
+
* still holds right after a factory reset, since better-auth answers from its
|
|
17
|
+
* cookie cache for minutes after the rows are gone.
|
|
18
|
+
*/
|
|
19
|
+
function createMockAuth(signedIn: boolean) {
|
|
20
|
+
return {
|
|
21
|
+
api: {
|
|
22
|
+
getSession: async () => ({
|
|
23
|
+
headers: new Headers(),
|
|
24
|
+
response: signedIn
|
|
25
|
+
? {
|
|
26
|
+
user: { id: "usr_stale", email: "old@test.com", name: "Old" },
|
|
27
|
+
session: { id: "ses_stale" },
|
|
28
|
+
}
|
|
29
|
+
: null,
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
} as unknown as AppVariables["auth"];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createMockServices(isMember: boolean) {
|
|
36
|
+
return {
|
|
37
|
+
siteMembers: {
|
|
38
|
+
get: async () =>
|
|
39
|
+
isMember
|
|
40
|
+
? {
|
|
41
|
+
createdAt: 0,
|
|
42
|
+
role: "owner",
|
|
43
|
+
siteId: "sit_test",
|
|
44
|
+
updatedAt: 0,
|
|
45
|
+
userId: "usr_stale",
|
|
46
|
+
}
|
|
47
|
+
: null,
|
|
48
|
+
},
|
|
49
|
+
} as unknown as AppVariables["services"];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createSigninTestApp(options: {
|
|
53
|
+
signedIn: boolean;
|
|
54
|
+
isMember: boolean;
|
|
55
|
+
}) {
|
|
56
|
+
const app = new Hono<Env>();
|
|
57
|
+
app.use("*", async (c, next) => {
|
|
58
|
+
c.env = {} as Bindings;
|
|
59
|
+
c.set("auth", createMockAuth(options.signedIn));
|
|
60
|
+
c.set("services", createMockServices(options.isMember));
|
|
61
|
+
c.set("currentSite", {
|
|
62
|
+
createdAt: 0,
|
|
63
|
+
id: "sit_test",
|
|
64
|
+
key: "default",
|
|
65
|
+
status: "active",
|
|
66
|
+
updatedAt: 0,
|
|
67
|
+
});
|
|
68
|
+
c.set("currentSiteDomain", null);
|
|
69
|
+
c.set("appConfig", {
|
|
70
|
+
siteName: "Jant",
|
|
71
|
+
sitePathPrefix: "",
|
|
72
|
+
} as AppVariables["appConfig"]);
|
|
73
|
+
c.set("publicRequestUrl", "http://localhost/signin");
|
|
74
|
+
c.set("lang", "en");
|
|
75
|
+
c.set("i18n", createI18n("en"));
|
|
76
|
+
await next();
|
|
77
|
+
});
|
|
78
|
+
app.use("*", attachSession());
|
|
79
|
+
app.route("/", signinRoutes);
|
|
80
|
+
app.use("/settings", requireAuth());
|
|
81
|
+
app.get("/settings", (c) => c.text("Settings"));
|
|
82
|
+
return app;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe("GET /signin", () => {
|
|
86
|
+
it("shows the form to a session that is not a member of this site", async () => {
|
|
87
|
+
const app = createSigninTestApp({ signedIn: true, isMember: false });
|
|
88
|
+
|
|
89
|
+
const res = await app.request("/signin?redirect=%2Fsettings", {
|
|
90
|
+
redirect: "manual",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(res.status).toBe(200);
|
|
94
|
+
expect(await res.text()).toContain(SIGNIN_FORM_MARKER);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("sends a member on to where they were headed", async () => {
|
|
98
|
+
const app = createSigninTestApp({ signedIn: true, isMember: true });
|
|
99
|
+
|
|
100
|
+
const res = await app.request("/signin?redirect=%2Fsettings", {
|
|
101
|
+
redirect: "manual",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(res.status).toBe(302);
|
|
105
|
+
expect(res.headers.get("Location")).toBe("/settings");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("still shows the form to a visitor with no session", async () => {
|
|
109
|
+
const app = createSigninTestApp({ signedIn: false, isMember: false });
|
|
110
|
+
|
|
111
|
+
const res = await app.request("/signin", { redirect: "manual" });
|
|
112
|
+
|
|
113
|
+
expect(res.status).toBe(200);
|
|
114
|
+
expect(await res.text()).toContain(SIGNIN_FORM_MARKER);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("stale session redirect loop", () => {
|
|
119
|
+
it("lands on the sign-in form instead of volleying with /settings", async () => {
|
|
120
|
+
const app = createSigninTestApp({ signedIn: true, isMember: false });
|
|
121
|
+
|
|
122
|
+
// `requireAuth` requires membership, so a session left over from a deleted
|
|
123
|
+
// site is turned away here.
|
|
124
|
+
const guarded = await app.request("/settings", { redirect: "manual" });
|
|
125
|
+
expect(guarded.status).toBe(302);
|
|
126
|
+
const signinLocation = guarded.headers.get("Location");
|
|
127
|
+
expect(signinLocation).toBe("/signin?redirect=%2Fsettings");
|
|
128
|
+
|
|
129
|
+
// …and this is the hop that used to send the same session straight back to
|
|
130
|
+
// /settings, three redirects per lap until the browser gave up.
|
|
131
|
+
const signin = await app.request(signinLocation!, { redirect: "manual" });
|
|
132
|
+
expect(signin.status).toBe(200);
|
|
133
|
+
expect(await signin.text()).toContain(SIGNIN_FORM_MARKER);
|
|
134
|
+
});
|
|
135
|
+
});
|