@jant/core 0.6.15 → 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 +67 -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-C-oi_t8W.js → app-K_Aa1MMn.js} +52623 -13193
- 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-CsFx6F_M.js → github-sync-BPAvT999.js} +2343 -110
- 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__/mise-config.test.ts +22 -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 +524 -2
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +57 -8
- 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 +88 -1
- 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__/compose-format-convert.test.ts +0 -19
- 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 +79 -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 +3283 -389
- package/src/client/components/__tests__/jant-compose-editor.test.ts +581 -86
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +34 -89
- 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-format-convert.ts +5 -1
- package/src/client/components/compose-types.ts +121 -3
- 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 +106 -49
- package/src/client/components/jant-compose-dialog.ts +2757 -1194
- package/src/client/components/jant-compose-editor.ts +866 -397
- package/src/client/components/jant-compose-fullscreen.ts +53 -47
- 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 +1142 -268
- 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 +336 -242
- package/src/client/compose-launch.ts +89 -2
- package/src/client/compose-shortcuts.ts +9 -7
- 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/post-refresh.ts +135 -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/__tests__/link-input-rules.test.ts +121 -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-input-rules.ts +4 -4
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/toast.ts +143 -38
- 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__/backfill-thread-activity.test.ts +234 -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/__tests__/thread-activity.test.ts +113 -0
- package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0031_many_ego.sql +3 -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/0031_snapshot.json +2562 -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 +28 -0
- package/src/db/migrations/pg/0029_rare_hairball.sql +3 -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/0029_snapshot.json +3289 -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 +28 -0
- package/src/db/pg/schema.ts +213 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +242 -27
- package/src/db/thread-activity.ts +101 -0
- 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 +568 -168
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +562 -162
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +562 -162
- 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 +1691 -12
- 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__/markdown.test.ts +16 -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 +88 -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__/translit.test.ts +87 -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 +93 -34
- 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 +1021 -66
- 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 +21 -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/link-preview.ts +25 -0
- package/src/lib/markdown-manager.ts +6 -1
- 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-display.ts +42 -9
- 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 +81 -10
- package/src/lib/tiptap-render.ts +5 -4
- package/src/lib/translit.ts +288 -0
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +188 -24
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +79 -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__/palette.test.ts +44 -0
- 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 +172 -9
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/__tests__/posts.test.ts +38 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +30 -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 +11 -2
- 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/__tests__/sitemap.test.ts +68 -1
- 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 +657 -1
- 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__/featured.test.ts +6 -2
- 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__/preview.test.ts +3 -1
- 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 +507 -357
- 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 +183 -19
- 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 +139 -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-timeline.test.ts +142 -11
- package/src/services/__tests__/post.test.ts +323 -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 +330 -47
- 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 +281 -24
- 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 +225 -7
- package/src/services/post.ts +1318 -252
- package/src/services/search.ts +48 -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 -1585
- package/src/styles/site-media.css +20 -6
- package/src/styles/tokens.css +144 -13
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1224 -7276
- 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 +92 -1
- package/src/types/operations.ts +64 -2
- package/src/types/props.ts +168 -30
- package/src/types/views.ts +61 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +215 -21
- 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/LinkPreview.tsx +2 -7
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +103 -17
- 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 +401 -301
- 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 +122 -3
- 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/DraftPreviewBar.tsx +25 -9
- 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 +55 -23
- 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/custom-icons.ts +5 -0
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +8 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-BxCOR3Uc.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-pLre2DM_.js +0 -1
- package/dist/client/_assets/client-B_eGKN5p.css +0 -2
- package/dist/client/_assets/client-DxY5BFe8.js +0 -296
- package/dist/client/_assets/client-auth-CakPESv9.js +0 -5533
- package/dist/env-OHRKGcMj.js +0 -281
- package/dist/github-api-BgSiE71w.js +0 -176
- package/dist/github-app-BbklkFmU.js +0 -275
- package/dist/github-sync-Cq1pzzOI.js +0 -431
- package/dist/github-sync-ppWXN3jb.js +0 -4
- package/dist/url-BMYO-Zlt.js +0 -395
- 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
|
@@ -14,6 +14,7 @@ import { SigninSchema } from "../../lib/schemas.js";
|
|
|
14
14
|
import { buildPageTitle } from "../../lib/page-title.js";
|
|
15
15
|
import { getI18n } from "../../i18n/index.js";
|
|
16
16
|
import { getHostedControlPlaneSigninUrl } from "../../lib/hosted-signin.js";
|
|
17
|
+
import { isCurrentSiteMember } from "../../middleware/auth.js";
|
|
17
18
|
import { isSafeInternalRedirect, toPublicPath } from "../../lib/url.js";
|
|
18
19
|
|
|
19
20
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
@@ -128,7 +129,10 @@ signinRoutes.get("/signin", async (c) => {
|
|
|
128
129
|
? rawRedirect
|
|
129
130
|
: undefined;
|
|
130
131
|
|
|
131
|
-
|
|
132
|
+
// Membership, not merely a session: `requireAuth` sends non-members here, so
|
|
133
|
+
// reading `isAuthenticated` instead would send them straight back and the two
|
|
134
|
+
// would volley until the browser refused to follow another hop.
|
|
135
|
+
if (await isCurrentSiteMember(c)) {
|
|
132
136
|
return c.redirect(
|
|
133
137
|
toPublicPath(redirect ?? "/", c.var.appConfig.sitePathPrefix),
|
|
134
138
|
);
|
|
@@ -144,14 +148,12 @@ signinRoutes.get("/signin", async (c) => {
|
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
const i18n = getI18n(c);
|
|
147
|
-
|
|
151
|
+
// No `?setup` counterpart: setup signs its new owner in on the spot and
|
|
152
|
+
// hands them straight to the next screen, so nobody arrives here from it.
|
|
148
153
|
const isReset = c.req.query("reset") !== undefined;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} else if (isReset) {
|
|
153
|
-
toast = { message: "Password reset. Sign in with your new password." };
|
|
154
|
-
}
|
|
154
|
+
const toast = isReset
|
|
155
|
+
? { message: "Password reset. Sign in with your new password." }
|
|
156
|
+
: undefined;
|
|
155
157
|
|
|
156
158
|
return c.html(
|
|
157
159
|
<BaseLayout
|
package/src/routes/compose.tsx
CHANGED
|
@@ -89,6 +89,8 @@ composeRoutes.post("/", async (c) => {
|
|
|
89
89
|
collectionIds: data.collectionIds,
|
|
90
90
|
replyToId: data.replyToId,
|
|
91
91
|
quietReply: data.quietReply,
|
|
92
|
+
language: data.language,
|
|
93
|
+
translationOfId: data.translationOfId,
|
|
92
94
|
publishedAt: data.publishedAt,
|
|
93
95
|
},
|
|
94
96
|
data.attachments,
|
|
@@ -211,7 +213,9 @@ composeRoutes.post("/thread", async (c) => {
|
|
|
211
213
|
postSchemas.map((data, index) => ({
|
|
212
214
|
data: {
|
|
213
215
|
format: data.format,
|
|
214
|
-
|
|
216
|
+
// Every post may carry its own permalink; blank means the service
|
|
217
|
+
// generates one (title-derived for the root, random id for replies).
|
|
218
|
+
slug: data.slug || undefined,
|
|
215
219
|
title:
|
|
216
220
|
data.format === "quote"
|
|
217
221
|
? data.sourceName || undefined
|
|
@@ -229,7 +233,12 @@ composeRoutes.post("/thread", async (c) => {
|
|
|
229
233
|
collectionIds: index === 0 ? data.collectionIds : undefined,
|
|
230
234
|
replyToId: index === 0 ? data.replyToId : undefined,
|
|
231
235
|
quietReply: data.quietReply,
|
|
232
|
-
|
|
236
|
+
// Thread-level, so only the root states them; replies inherit.
|
|
237
|
+
language: index === 0 ? data.language : undefined,
|
|
238
|
+
translationOfId: index === 0 ? data.translationOfId : undefined,
|
|
239
|
+
// Replies may carry their own date; when they don't, the service
|
|
240
|
+
// inherits the root's rather than stamping "now".
|
|
241
|
+
publishedAt: data.publishedAt,
|
|
233
242
|
},
|
|
234
243
|
attachments: data.attachments,
|
|
235
244
|
})),
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The archive custom URL is readable forever and creatable never.
|
|
6
|
+
*
|
|
7
|
+
* Hand-typing `format=note&title=none` into a settings text field is the
|
|
8
|
+
* problem smart collections replace. Removing it from the form is not the same
|
|
9
|
+
* as removing it — an endpoint that still accepts it has not stopped offering
|
|
10
|
+
* it — so the refusal lives in the schema and again in the service.
|
|
11
|
+
*/
|
|
12
|
+
describe("archive custom URLs", () => {
|
|
13
|
+
it("refuses to create one", async () => {
|
|
14
|
+
const { services } = createTestApp({ authenticated: true });
|
|
15
|
+
|
|
16
|
+
await expect(
|
|
17
|
+
services.customUrls.create({
|
|
18
|
+
path: "/notes-only",
|
|
19
|
+
targetType: "archive",
|
|
20
|
+
archiveQuery: "format=note",
|
|
21
|
+
}),
|
|
22
|
+
).rejects.toThrow(/smart collection/i);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("still reads, lists, and deletes one that already exists", async () => {
|
|
26
|
+
const { services } = createTestApp({ authenticated: true });
|
|
27
|
+
|
|
28
|
+
// How a real legacy row exists: written before the create path was closed.
|
|
29
|
+
const record = await services.paths.create({
|
|
30
|
+
path: "/notes-only",
|
|
31
|
+
kind: "archive",
|
|
32
|
+
archiveQuery: "format=note&title=none",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const listed = await services.customUrls.list();
|
|
36
|
+
expect(listed.map((entry) => entry.path)).toContain("notes-only");
|
|
37
|
+
expect(
|
|
38
|
+
listed.find((entry) => entry.path === "notes-only")?.targetType,
|
|
39
|
+
).toBe("archive");
|
|
40
|
+
|
|
41
|
+
expect(await services.customUrls.delete(record.id)).toBe(true);
|
|
42
|
+
expect(await services.paths.getByPath("/notes-only")).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the Custom URLs settings page shows for a legacy archive path.
|
|
3
|
+
*
|
|
4
|
+
* The row is the whole affordance: it prints the query the path holds, and its
|
|
5
|
+
* menu deletes. It deliberately offers no "turn into a smart collection" —
|
|
6
|
+
* that row *is* the registration of its own address, so the collection it would
|
|
7
|
+
* become could never be saved at the URL the offer was about.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
12
|
+
import { customUrlsRoutes } from "../custom-urls.js";
|
|
13
|
+
|
|
14
|
+
function createCustomUrlsTestApp() {
|
|
15
|
+
const testApp = createTestApp({ authenticated: true });
|
|
16
|
+
const { app } = testApp;
|
|
17
|
+
|
|
18
|
+
app.use("*", async (c, next) => {
|
|
19
|
+
c.set("publicPath", c.req.path);
|
|
20
|
+
c.set("publicRequestUrl", c.req.url);
|
|
21
|
+
await next();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
app.route("/settings/custom-urls", customUrlsRoutes);
|
|
25
|
+
|
|
26
|
+
return testApp;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("custom URLs settings page", () => {
|
|
30
|
+
it("shows a legacy archive path and offers only deletion", async () => {
|
|
31
|
+
const { app, services } = createCustomUrlsTestApp();
|
|
32
|
+
|
|
33
|
+
// How a real legacy row exists: written before the create path was closed.
|
|
34
|
+
await services.paths.create({
|
|
35
|
+
path: "/notes-only",
|
|
36
|
+
kind: "archive",
|
|
37
|
+
archiveQuery: "format=note&title=none",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const res = await app.request("/settings/custom-urls");
|
|
41
|
+
expect(res.status).toBe(200);
|
|
42
|
+
|
|
43
|
+
const html = await res.text();
|
|
44
|
+
expect(html).toContain("/notes-only");
|
|
45
|
+
expect(html).toContain("/archive?format=note&title=none");
|
|
46
|
+
expect(html).toContain('data-custom-url-action="delete"');
|
|
47
|
+
expect(html).not.toContain('data-custom-url-action="upgrade"');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for POST /settings/account/delete-account.
|
|
3
|
+
*
|
|
4
|
+
* The route is a POST returning a Datastar redirect, so it can be exercised
|
|
5
|
+
* end to end without the SSR setup the settings pages need.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { Hono } from "hono";
|
|
10
|
+
import { createI18n } from "../../../i18n/index.js";
|
|
11
|
+
import { settingsRoutes } from "../settings.js";
|
|
12
|
+
import type { Bindings } from "../../../types.js";
|
|
13
|
+
import type { AppVariables } from "../../../types/app-context.js";
|
|
14
|
+
|
|
15
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
16
|
+
|
|
17
|
+
const CLEARED_SESSION_COOKIE =
|
|
18
|
+
"better-auth.session_token=; Path=/; Max-Age=0; HttpOnly";
|
|
19
|
+
|
|
20
|
+
function createDeleteAccountTestApp() {
|
|
21
|
+
const calls: string[] = [];
|
|
22
|
+
|
|
23
|
+
const signOut = vi.fn(async () => {
|
|
24
|
+
calls.push("signOut");
|
|
25
|
+
return new Response(null, {
|
|
26
|
+
headers: { "Set-Cookie": CLEARED_SESSION_COOKIE },
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
const deleteAllData = vi.fn(async () => {
|
|
30
|
+
calls.push("deleteAllData");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const app = new Hono<Env>();
|
|
34
|
+
app.use("*", async (c, next) => {
|
|
35
|
+
c.env = {} as Bindings;
|
|
36
|
+
c.set("auth", { api: { signOut } } as unknown as AppVariables["auth"]);
|
|
37
|
+
c.set("services", {
|
|
38
|
+
auth: {
|
|
39
|
+
validateDeleteCsrfToken: async (_token: string, sessionId: string) =>
|
|
40
|
+
sessionId === "ses_test",
|
|
41
|
+
deleteAllData,
|
|
42
|
+
},
|
|
43
|
+
} as unknown as AppVariables["services"]);
|
|
44
|
+
c.set("storage", null);
|
|
45
|
+
c.set("session", {
|
|
46
|
+
session: { id: "ses_test" },
|
|
47
|
+
user: { id: "usr_test" },
|
|
48
|
+
} as AppVariables["session"]);
|
|
49
|
+
c.set("isAuthenticated", true);
|
|
50
|
+
c.set("currentSite", {
|
|
51
|
+
createdAt: 0,
|
|
52
|
+
id: "sit_test",
|
|
53
|
+
key: "default",
|
|
54
|
+
status: "active",
|
|
55
|
+
updatedAt: 0,
|
|
56
|
+
});
|
|
57
|
+
c.set("appConfig", {
|
|
58
|
+
siteName: "Jant",
|
|
59
|
+
sitePathPrefix: "",
|
|
60
|
+
} as AppVariables["appConfig"]);
|
|
61
|
+
c.set("lang", "en");
|
|
62
|
+
c.set("i18n", createI18n("en"));
|
|
63
|
+
await next();
|
|
64
|
+
});
|
|
65
|
+
app.route("/settings", settingsRoutes);
|
|
66
|
+
|
|
67
|
+
return { app, calls, signOut, deleteAllData };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function requestDelete(app: Hono<Env>) {
|
|
71
|
+
return app.request("/settings/account/delete-account", {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "x-csrf-token": "valid-token" },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe("POST /settings/account/delete-account", () => {
|
|
78
|
+
it("clears the session cookie so the browser keeps no ticket to the deleted site", async () => {
|
|
79
|
+
const { app } = createDeleteAccountTestApp();
|
|
80
|
+
|
|
81
|
+
const res = await requestDelete(app);
|
|
82
|
+
|
|
83
|
+
expect(res.status).toBe(200);
|
|
84
|
+
expect(res.headers.getSetCookie()).toContain(CLEARED_SESSION_COOKIE);
|
|
85
|
+
await expect(res.text()).resolves.toContain("/setup");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("signs out before the wipe, while the session row still exists", async () => {
|
|
89
|
+
const { app, calls } = createDeleteAccountTestApp();
|
|
90
|
+
|
|
91
|
+
await requestDelete(app);
|
|
92
|
+
|
|
93
|
+
expect(calls).toEqual(["signOut", "deleteAllData"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("still wipes the site when signing out fails", async () => {
|
|
97
|
+
const { app, calls, signOut, deleteAllData } = createDeleteAccountTestApp();
|
|
98
|
+
signOut.mockRejectedValueOnce(new Error("no session"));
|
|
99
|
+
|
|
100
|
+
const res = await requestDelete(app);
|
|
101
|
+
|
|
102
|
+
expect(res.status).toBe(200);
|
|
103
|
+
expect(calls).toEqual(["deleteAllData"]);
|
|
104
|
+
expect(deleteAllData).toHaveBeenCalledOnce();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the Jant Discover settings routes.
|
|
3
|
+
*
|
|
4
|
+
* Both are POSTs with no SSR, so `settingsRoutes` can be mounted and driven
|
|
5
|
+
* directly. What they are really guarding is the contract with
|
|
6
|
+
* `client/settings-bridge.ts`: it sends `Accept: application/json` and parses
|
|
7
|
+
* the body with `readJsonObject`, which throws on anything else — so a route
|
|
8
|
+
* that answers with a Datastar toast reads to the owner as a failed save no
|
|
9
|
+
* matter what the server did.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it, vi } from "vitest";
|
|
13
|
+
import { Hono } from "hono";
|
|
14
|
+
import { createI18n } from "../../../i18n/index.js";
|
|
15
|
+
import { settingsRoutes } from "../settings.js";
|
|
16
|
+
import type { Bindings } from "../../../types.js";
|
|
17
|
+
import type { AppVariables } from "../../../types/app-context.js";
|
|
18
|
+
|
|
19
|
+
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
20
|
+
|
|
21
|
+
function createDiscoverTestApp(
|
|
22
|
+
overrides: {
|
|
23
|
+
env?: Partial<Bindings>;
|
|
24
|
+
storedDiscover?: string;
|
|
25
|
+
demoMode?: boolean;
|
|
26
|
+
rssFeedsEnabled?: boolean;
|
|
27
|
+
} = {},
|
|
28
|
+
) {
|
|
29
|
+
const announceToDiscover = vi.fn(async () => ({
|
|
30
|
+
at: 0,
|
|
31
|
+
ok: true,
|
|
32
|
+
feedUrl: "https://blog.example/latest/feed",
|
|
33
|
+
}));
|
|
34
|
+
const updateDiscoverSetting = vi.fn(async () => ({ shouldAnnounce: true }));
|
|
35
|
+
|
|
36
|
+
const app = new Hono<Env>();
|
|
37
|
+
app.use("*", async (c, next) => {
|
|
38
|
+
c.env = (overrides.env ?? {}) as Bindings;
|
|
39
|
+
c.set("services", {
|
|
40
|
+
settings: { announceToDiscover, updateDiscoverSetting },
|
|
41
|
+
} as unknown as AppVariables["services"]);
|
|
42
|
+
c.set("allSettings", {
|
|
43
|
+
...(overrides.storedDiscover === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: { DISCOVER: overrides.storedDiscover }),
|
|
46
|
+
});
|
|
47
|
+
c.set("isAuthenticated", true);
|
|
48
|
+
c.set("appConfig", {
|
|
49
|
+
demoMode: overrides.demoMode ?? false,
|
|
50
|
+
noindex: false,
|
|
51
|
+
rssFeedsEnabled: overrides.rssFeedsEnabled ?? true,
|
|
52
|
+
siteUrl: "https://blog.example",
|
|
53
|
+
sitePathPrefix: "",
|
|
54
|
+
} as AppVariables["appConfig"]);
|
|
55
|
+
c.set("lang", "en");
|
|
56
|
+
c.set("i18n", createI18n("en"));
|
|
57
|
+
await next();
|
|
58
|
+
});
|
|
59
|
+
app.route("/settings", settingsRoutes);
|
|
60
|
+
|
|
61
|
+
return { app, announceToDiscover, updateDiscoverSetting };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function postJson(app: Hono<Env>, path: string, body: unknown = {}) {
|
|
65
|
+
return app.request(path, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
// Exactly what `settings-bridge.ts` sends.
|
|
70
|
+
Accept: "application/json",
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify(body),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("POST /settings/general/discover/announce", () => {
|
|
77
|
+
it("answers JSON, so the bridge does not read a success as a failure", async () => {
|
|
78
|
+
const { app, announceToDiscover } = createDiscoverTestApp({
|
|
79
|
+
storedDiscover: "latest",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const res = await postJson(app, "/settings/general/discover/announce");
|
|
83
|
+
|
|
84
|
+
expect(res.status).toBe(200);
|
|
85
|
+
// The assertion that matters: a parseable body. A Datastar toast is
|
|
86
|
+
// `text/html`, and `readJsonObject` throws on it.
|
|
87
|
+
await expect(res.json()).resolves.toMatchObject({
|
|
88
|
+
status: "ok",
|
|
89
|
+
toast: expect.stringContaining("Announcing your site"),
|
|
90
|
+
});
|
|
91
|
+
expect(announceToDiscover).toHaveBeenCalledOnce();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("says so in JSON when there is nothing to announce to", async () => {
|
|
95
|
+
const { app, announceToDiscover } = createDiscoverTestApp({
|
|
96
|
+
storedDiscover: "latest",
|
|
97
|
+
env: { DISCOVER_PING_URL: "" },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const res = await postJson(app, "/settings/general/discover/announce");
|
|
101
|
+
|
|
102
|
+
await expect(res.json()).resolves.toMatchObject({
|
|
103
|
+
status: "ok",
|
|
104
|
+
toast: expect.stringContaining("no directory"),
|
|
105
|
+
});
|
|
106
|
+
expect(announceToDiscover).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("never announces a demo site", async () => {
|
|
110
|
+
const { app, announceToDiscover } = createDiscoverTestApp({
|
|
111
|
+
storedDiscover: "latest",
|
|
112
|
+
demoMode: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await postJson(app, "/settings/general/discover/announce");
|
|
116
|
+
|
|
117
|
+
expect(announceToDiscover).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("does not announce a feed address that would answer 404", async () => {
|
|
121
|
+
// Feeds are off, so `/latest/feed` does not exist to be polled.
|
|
122
|
+
const { app, announceToDiscover } = createDiscoverTestApp({
|
|
123
|
+
storedDiscover: "latest",
|
|
124
|
+
rssFeedsEnabled: false,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await postJson(app, "/settings/general/discover/announce");
|
|
128
|
+
|
|
129
|
+
expect(announceToDiscover).not.toHaveBeenCalled();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("still answers a Datastar toast when JSON was not asked for", async () => {
|
|
133
|
+
const { app } = createDiscoverTestApp({ storedDiscover: "latest" });
|
|
134
|
+
|
|
135
|
+
const res = await app.request("/settings/general/discover/announce", {
|
|
136
|
+
method: "POST",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(res.headers.get("Content-Type")).toContain("text/html");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("POST /settings/general/discover", () => {
|
|
144
|
+
it("announces the feed the newly saved mode names, not the stored one", async () => {
|
|
145
|
+
// `allSettings` is the snapshot taken before the request ran, so it still
|
|
146
|
+
// holds the previous answer on the save that triggers the announcement.
|
|
147
|
+
// `featured` is what older releases stored, and it names a different feed
|
|
148
|
+
// from the `latest` the control now writes.
|
|
149
|
+
const { app, announceToDiscover } = createDiscoverTestApp({
|
|
150
|
+
storedDiscover: "featured",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await postJson(app, "/settings/general/discover", {
|
|
154
|
+
discover: "latest",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(announceToDiscover).toHaveBeenCalledWith(
|
|
158
|
+
expect.objectContaining({
|
|
159
|
+
feedUrl: "https://blog.example/latest/feed",
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A ping means "read me now", and the moment a blog leaves is the moment
|
|
166
|
+
* that matters most: without it the directory keeps the blog until its next
|
|
167
|
+
* scheduled read, and the owner watches something they just removed sit
|
|
168
|
+
* there for another hour.
|
|
169
|
+
*/
|
|
170
|
+
it("pings the always-served feed when the owner switches Discover off", async () => {
|
|
171
|
+
const { app, announceToDiscover } = createDiscoverTestApp({
|
|
172
|
+
storedDiscover: "featured",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await postJson(app, "/settings/general/discover", { discover: "off" });
|
|
176
|
+
|
|
177
|
+
// `/featured/feed` is not it: the mode that named it is gone, and the
|
|
178
|
+
// declaration a directory has to read sits in every feed.
|
|
179
|
+
expect(announceToDiscover).toHaveBeenCalledWith(
|
|
180
|
+
expect.objectContaining({
|
|
181
|
+
feedUrl: "https://blog.example/latest/feed",
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// The other ways a site resolves to `none` are not an owner saying stop, and
|
|
187
|
+
// the address they would name answers 404.
|
|
188
|
+
it("sends no stop ping from a site whose feeds are off", async () => {
|
|
189
|
+
const { app, announceToDiscover } = createDiscoverTestApp({
|
|
190
|
+
storedDiscover: "latest",
|
|
191
|
+
rssFeedsEnabled: false,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await postJson(app, "/settings/general/discover", { discover: "off" });
|
|
195
|
+
|
|
196
|
+
expect(announceToDiscover).not.toHaveBeenCalled();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("does not announce when the save was not an opt-in", async () => {
|
|
200
|
+
const { app, announceToDiscover, updateDiscoverSetting } =
|
|
201
|
+
createDiscoverTestApp({ storedDiscover: "latest" });
|
|
202
|
+
updateDiscoverSetting.mockResolvedValueOnce({ shouldAnnounce: false });
|
|
203
|
+
|
|
204
|
+
await postJson(app, "/settings/general/discover", {
|
|
205
|
+
discover: "latest",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// A request the schema rejects never reaches the service, and would pass
|
|
209
|
+
// the assertion below for the wrong reason.
|
|
210
|
+
expect(updateDiscoverSetting).toHaveBeenCalledOnce();
|
|
211
|
+
expect(announceToDiscover).not.toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -272,6 +272,7 @@ function CustomUrlsListContent({
|
|
|
272
272
|
class="custom-url-menu"
|
|
273
273
|
role="menu"
|
|
274
274
|
data-custom-url-menu
|
|
275
|
+
popover="manual"
|
|
275
276
|
hidden
|
|
276
277
|
>
|
|
277
278
|
<button
|
|
@@ -434,15 +435,6 @@ function NewCustomUrlContent({
|
|
|
434
435
|
}),
|
|
435
436
|
)}
|
|
436
437
|
</option>
|
|
437
|
-
<option value="archive">
|
|
438
|
-
{i18n._(
|
|
439
|
-
msg({
|
|
440
|
-
message: "Archive",
|
|
441
|
-
comment:
|
|
442
|
-
"@context: Custom URL type option for filtered archive view",
|
|
443
|
-
}),
|
|
444
|
-
)}
|
|
445
|
-
</option>
|
|
446
438
|
</select>
|
|
447
439
|
</div>
|
|
448
440
|
|
|
@@ -494,33 +486,6 @@ function NewCustomUrlContent({
|
|
|
494
486
|
</div>
|
|
495
487
|
</div>
|
|
496
488
|
|
|
497
|
-
<div data-show="$targetType === 'archive'" class="field">
|
|
498
|
-
<label class="label">
|
|
499
|
-
{i18n._(
|
|
500
|
-
msg({
|
|
501
|
-
message: "Query Parameters",
|
|
502
|
-
comment: "@context: Archive custom URL query params field",
|
|
503
|
-
}),
|
|
504
|
-
)}
|
|
505
|
-
</label>
|
|
506
|
-
<input
|
|
507
|
-
type="text"
|
|
508
|
-
data-bind="archiveQuery"
|
|
509
|
-
class="input"
|
|
510
|
-
placeholder="format=note&title=none&visibility=public&view=list"
|
|
511
|
-
/>
|
|
512
|
-
<p class="text-xs text-muted-foreground mt-1">
|
|
513
|
-
{i18n._(
|
|
514
|
-
msg({
|
|
515
|
-
message:
|
|
516
|
-
"Archive filter parameters (e.g. format=note&view=list)",
|
|
517
|
-
comment:
|
|
518
|
-
"@context: Help text for archive custom URL query params",
|
|
519
|
-
}),
|
|
520
|
-
)}
|
|
521
|
-
</p>
|
|
522
|
-
</div>
|
|
523
|
-
|
|
524
489
|
<div
|
|
525
490
|
data-show="$targetType === 'post' || $targetType === 'collection'"
|
|
526
491
|
class="field"
|
|
@@ -705,7 +670,6 @@ customUrlsRoutes.post("/", async (c) => {
|
|
|
705
670
|
targetId,
|
|
706
671
|
toPath: body.toPath,
|
|
707
672
|
redirectType,
|
|
708
|
-
archiveQuery: body.archiveQuery,
|
|
709
673
|
});
|
|
710
674
|
|
|
711
675
|
return dsRedirect(
|