@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
|
@@ -13,19 +13,35 @@ import { z } from "zod";
|
|
|
13
13
|
import type { Bindings } from "../../types.js";
|
|
14
14
|
import type { AppVariables } from "../../types/app-context.js";
|
|
15
15
|
import { sse, dsRedirect, dsToast } from "../../lib/sse.js";
|
|
16
|
-
import { getI18n, isLocale
|
|
16
|
+
import { getI18n, isLocale } from "../../i18n/index.js";
|
|
17
17
|
import { renderPublicPage } from "../../lib/render.js";
|
|
18
18
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
19
19
|
import { buildPageTitle } from "../../lib/page-title.js";
|
|
20
20
|
import { AdminBreadcrumb } from "../../ui/shared/AdminBreadcrumb.js";
|
|
21
21
|
import { getTimeZoneOptions } from "../../lib/timezones.js";
|
|
22
|
-
import {
|
|
22
|
+
import { getOrBuildEntry } from "../../i18n/supported-locales.js";
|
|
23
|
+
import {
|
|
24
|
+
DomainError,
|
|
25
|
+
LanguageInUseError,
|
|
26
|
+
UnauthorizedError,
|
|
27
|
+
ValidationError,
|
|
28
|
+
} from "../../lib/errors.js";
|
|
23
29
|
import { SETTINGS_KEYS } from "../../lib/constants.js";
|
|
30
|
+
import { announceInBackground } from "../discover-announce.js";
|
|
31
|
+
import {
|
|
32
|
+
DISCOVER_FIRST_READ_MAX_HOURS,
|
|
33
|
+
DISCOVER_MIN_PUBLIC_POSTS,
|
|
34
|
+
getDiscoverPageUrls,
|
|
35
|
+
getDiscoverSubmitUrl,
|
|
36
|
+
measureDiscoverMaturity,
|
|
37
|
+
parseDiscoverSetting,
|
|
38
|
+
} from "../../lib/discover.js";
|
|
24
39
|
import { getAvailableThemes } from "../../lib/theme.js";
|
|
25
40
|
import { THEME_MODES, type ThemeMode } from "../../types/config.js";
|
|
26
41
|
import { BUILTIN_FONT_THEMES } from "../../ui/font-themes.js";
|
|
27
42
|
import { SettingsRootContent } from "../../ui/dash/settings/SettingsRootContent.js";
|
|
28
43
|
import { GeneralContent } from "../../ui/dash/settings/GeneralContent.js";
|
|
44
|
+
import { LanguageContent } from "../../ui/dash/settings/LanguageContent.js";
|
|
29
45
|
import { AvatarContent } from "../../ui/dash/settings/AvatarContent.js";
|
|
30
46
|
import { AccountMenuContent } from "../../ui/dash/settings/AccountMenuContent.js";
|
|
31
47
|
import { AccountContent } from "../../ui/dash/settings/AccountContent.js";
|
|
@@ -52,16 +68,24 @@ import {
|
|
|
52
68
|
} from "../../lib/telegram-settings-status.js";
|
|
53
69
|
import { TelegramContent } from "../../ui/dash/settings/TelegramContent.js";
|
|
54
70
|
import { toAbsoluteSiteUrl, toPublicPath } from "../../lib/url.js";
|
|
55
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
ContentLanguageSchema,
|
|
73
|
+
parseValidated,
|
|
74
|
+
UpdateSiteSettingsSchema,
|
|
75
|
+
} from "../../lib/schemas.js";
|
|
56
76
|
import {
|
|
57
77
|
getHostedControlPlaneAccountPasswordUrl,
|
|
58
78
|
getHostedControlPlaneAccountUrl,
|
|
59
79
|
getHostedControlPlaneProviderLabel,
|
|
60
80
|
getHostedControlPlaneSiteDeleteUrl,
|
|
61
81
|
getHostedControlPlaneSiteSettingsUrl,
|
|
82
|
+
isHostedControlPlaneEnabled,
|
|
62
83
|
} from "../../lib/hosted-signin.js";
|
|
63
84
|
import { syncHostedControlPlaneSiteAvatar } from "../../lib/hosted-control-plane-sync.js";
|
|
64
85
|
import {
|
|
86
|
+
getDiscoverDefault,
|
|
87
|
+
getDiscoverDirectoryBaseUrl,
|
|
88
|
+
getDiscoverPingUrl,
|
|
65
89
|
getGitHubAppConfig,
|
|
66
90
|
getHostedControlPlaneSsoSecret,
|
|
67
91
|
getTelegramBotPool,
|
|
@@ -95,15 +119,26 @@ type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
|
95
119
|
|
|
96
120
|
export const settingsRoutes = new Hono<Env>();
|
|
97
121
|
|
|
98
|
-
const
|
|
99
|
-
siteLanguage: z.string(),
|
|
100
|
-
// Optional for back-compat: absent = leave the dashboard language untouched,
|
|
101
|
-
// "" = clear it (follow content language).
|
|
102
|
-
dashboardLanguage: z.string().optional(),
|
|
103
|
-
cjkSerifFont: z.string(),
|
|
122
|
+
const UpdateTimeSettingsSchema = z.object({
|
|
104
123
|
timeZone: z.string(),
|
|
105
124
|
});
|
|
106
125
|
|
|
126
|
+
// Both fields are optional: the language page saves one control at a time, and
|
|
127
|
+
// an absent field means "leave it alone" rather than "clear it".
|
|
128
|
+
const UpdateLanguageSettingsSchema = z.object({
|
|
129
|
+
contentLanguage: ContentLanguageSchema.optional(),
|
|
130
|
+
dashboardLanguage: z.string().optional(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const LanguageTagSchema = z.object({
|
|
134
|
+
language: ContentLanguageSchema,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const EnableMultilingualSchema = z.object({
|
|
138
|
+
primary: ContentLanguageSchema,
|
|
139
|
+
additional: z.array(ContentLanguageSchema),
|
|
140
|
+
});
|
|
141
|
+
|
|
107
142
|
const UpdateFeedSettingsSchema = z.object({
|
|
108
143
|
mainRssFeed: z.enum(["featured", "latest"]),
|
|
109
144
|
});
|
|
@@ -116,6 +151,13 @@ const UpdateSearchSettingsSchema = z.object({
|
|
|
116
151
|
allowIndexing: z.boolean(),
|
|
117
152
|
});
|
|
118
153
|
|
|
154
|
+
// Only the two values the control writes. `featured` is still a stored value
|
|
155
|
+
// older releases wrote and the feed still declares, but nothing writes it any
|
|
156
|
+
// more: which of a blog's posts reach which list is the directory's rule.
|
|
157
|
+
const UpdateDiscoverSettingsSchema = z.object({
|
|
158
|
+
discover: z.enum(["latest", "off"]),
|
|
159
|
+
});
|
|
160
|
+
|
|
119
161
|
function publicPath(c: Context<Env>, path: string): string {
|
|
120
162
|
return toPublicPath(path, c.var.appConfig.sitePathPrefix);
|
|
121
163
|
}
|
|
@@ -124,6 +166,22 @@ function aboutEditPath(c: Context<Env>): string {
|
|
|
124
166
|
return `${publicPath(c, "/about")}?edit=1`;
|
|
125
167
|
}
|
|
126
168
|
|
|
169
|
+
/**
|
|
170
|
+
* The session id behind the current request.
|
|
171
|
+
*
|
|
172
|
+
* Every `/settings` route sits behind `requireAuth`, so a missing session here
|
|
173
|
+
* is a wiring mistake rather than a signed-out visitor — hence a throw instead
|
|
174
|
+
* of a redirect.
|
|
175
|
+
*/
|
|
176
|
+
function requireSessionId(c: Context<Env>): string {
|
|
177
|
+
const sessionId = c.var.session?.session?.id;
|
|
178
|
+
if (!sessionId) {
|
|
179
|
+
throw new UnauthorizedError();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return sessionId;
|
|
183
|
+
}
|
|
184
|
+
|
|
127
185
|
/**
|
|
128
186
|
* Breadcrumb labels for admin settings pages.
|
|
129
187
|
*
|
|
@@ -138,6 +196,7 @@ function breadcrumbLabel(
|
|
|
138
196
|
key:
|
|
139
197
|
| "settings"
|
|
140
198
|
| "general"
|
|
199
|
+
| "language"
|
|
141
200
|
| "avatar"
|
|
142
201
|
| "navigation"
|
|
143
202
|
| "colorTheme"
|
|
@@ -163,6 +222,10 @@ function breadcrumbLabel(
|
|
|
163
222
|
return i18n._(
|
|
164
223
|
msg({ message: "General", comment: "@context: Breadcrumb label" }),
|
|
165
224
|
);
|
|
225
|
+
case "language":
|
|
226
|
+
return i18n._(
|
|
227
|
+
msg({ message: "Language", comment: "@context: Breadcrumb label" }),
|
|
228
|
+
);
|
|
166
229
|
case "avatar":
|
|
167
230
|
return i18n._(
|
|
168
231
|
msg({ message: "Avatar", comment: "@context: Breadcrumb label" }),
|
|
@@ -318,11 +381,44 @@ settingsRoutes.get("/general", async (c) => {
|
|
|
318
381
|
|
|
319
382
|
const dbSiteName = allSettings["SITE_NAME"] ?? "";
|
|
320
383
|
const dbSiteDescription = allSettings["SITE_DESCRIPTION"] ?? "";
|
|
384
|
+
// The stored choice, not the effective mode: the control has to be able to
|
|
385
|
+
// show "never chosen", which is what lets `noindex` and then the deployment
|
|
386
|
+
// default decide instead.
|
|
387
|
+
const discoverSetting = parseDiscoverSetting(allSettings["DISCOVER"]);
|
|
388
|
+
// The deployment's own answer, and nothing else folded into it: `latest` on
|
|
389
|
+
// a deployment that lists its fleet, absent on an ordinary self-hosted site.
|
|
390
|
+
// The gates that sit above it — `noindex` most of all — are applied in the
|
|
391
|
+
// browser instead, because they are controls on this same page and a value
|
|
392
|
+
// resolved here would freeze at page load. The component runs the same
|
|
393
|
+
// `resolveDiscoverMode` this route would have.
|
|
394
|
+
const discoverDefault = parseDiscoverSetting(getDiscoverDefault(c.env)) ?? "";
|
|
321
395
|
|
|
322
396
|
const saved = c.req.query("saved") !== undefined;
|
|
323
|
-
|
|
397
|
+
// What the site can answer about its own standing in the directory, without
|
|
398
|
+
// asking one. The directory deliberately does not take status queries, so
|
|
399
|
+
// everything shown here is local evidence.
|
|
400
|
+
const publicPostFilters = {
|
|
401
|
+
status: "published" as const,
|
|
402
|
+
excludeReplies: true,
|
|
403
|
+
excludeLatestHidden: true,
|
|
404
|
+
excludePrivate: true,
|
|
405
|
+
};
|
|
406
|
+
const [
|
|
407
|
+
navData,
|
|
408
|
+
aboutPage,
|
|
409
|
+
announceState,
|
|
410
|
+
publicPostCount,
|
|
411
|
+
featuredPostCount,
|
|
412
|
+
] = await Promise.all([
|
|
324
413
|
getNavigationData(c),
|
|
325
414
|
c.var.services.aboutPage.getStatus(),
|
|
415
|
+
c.var.services.settings.getDiscoverAnnounceState(),
|
|
416
|
+
c.var.services.posts.count(publicPostFilters),
|
|
417
|
+
c.var.services.posts.countFeaturedThreadRoots({
|
|
418
|
+
status: "published",
|
|
419
|
+
excludePrivate: true,
|
|
420
|
+
excludeLatestHidden: true,
|
|
421
|
+
}),
|
|
326
422
|
]);
|
|
327
423
|
const siteUrlForDisplay =
|
|
328
424
|
appConfig.siteUrl || new URL(publicPath(c, "/"), c.req.url).toString();
|
|
@@ -341,13 +437,6 @@ settingsRoutes.get("/general", async (c) => {
|
|
|
341
437
|
<GeneralContent
|
|
342
438
|
siteName={dbSiteName || ""}
|
|
343
439
|
siteDescription={dbSiteDescription || ""}
|
|
344
|
-
siteLanguage={appConfig.siteLanguage}
|
|
345
|
-
dashboardLanguage={
|
|
346
|
-
isLocale(appConfig.dashboardLanguage)
|
|
347
|
-
? appConfig.dashboardLanguage
|
|
348
|
-
: resolveCatalogLocale(appConfig.siteLanguage)
|
|
349
|
-
}
|
|
350
|
-
cjkSerifFont={appConfig.cjkSerifFont}
|
|
351
440
|
siteNameFallback={appConfig.fallbacks.siteName}
|
|
352
441
|
siteDescriptionFallback={appConfig.fallbacks.siteDescription}
|
|
353
442
|
mainRssFeed={appConfig.mainRssFeed}
|
|
@@ -375,6 +464,32 @@ settingsRoutes.get("/general", async (c) => {
|
|
|
375
464
|
siteFooter={appConfig.siteFooter}
|
|
376
465
|
showJantBrandingOnHome={appConfig.showJantBrandingOnHome}
|
|
377
466
|
noindex={appConfig.noindex}
|
|
467
|
+
discover={discoverSetting ?? ""}
|
|
468
|
+
discoverDefault={discoverDefault}
|
|
469
|
+
discoverPages={getDiscoverPageUrls(
|
|
470
|
+
getDiscoverDirectoryBaseUrl(c.env),
|
|
471
|
+
)}
|
|
472
|
+
discoverStatus={{
|
|
473
|
+
announced: announceState?.ok ?? null,
|
|
474
|
+
announceError: announceState?.error ?? null,
|
|
475
|
+
announceAt: announceState?.at ?? null,
|
|
476
|
+
// No directory configured means nothing to announce to, and the
|
|
477
|
+
// whole announcement block is beside the point.
|
|
478
|
+
hasDirectory: getDiscoverPingUrl(c.env) !== undefined,
|
|
479
|
+
// A hosted fleet is enrolled by its control plane, so there is no
|
|
480
|
+
// announcement for the owner to make, chase, or retry.
|
|
481
|
+
managedByHost: isHostedControlPlaneEnabled(c.env),
|
|
482
|
+
submitUrl: getDiscoverSubmitUrl(getDiscoverDirectoryBaseUrl(c.env)),
|
|
483
|
+
// The mode the feeds actually declare, already derived once on
|
|
484
|
+
// `appConfig` — re-deriving it here is how this block used to
|
|
485
|
+
// miss the deployment default.
|
|
486
|
+
declaredMode: appConfig.discover,
|
|
487
|
+
...measureDiscoverMaturity({ publicPostCount }),
|
|
488
|
+
featuredPostCount,
|
|
489
|
+
minPublicPosts: DISCOVER_MIN_PUBLIC_POSTS,
|
|
490
|
+
firstReadMaxHours: DISCOVER_FIRST_READ_MAX_HOURS,
|
|
491
|
+
}}
|
|
492
|
+
rssFeedsEnabled={appConfig.rssFeedsEnabled}
|
|
378
493
|
demoMode={appConfig.demoMode}
|
|
379
494
|
timezones={getTimeZoneOptions(appConfig.timeZone)}
|
|
380
495
|
aboutPage={aboutPage}
|
|
@@ -451,42 +566,286 @@ settingsRoutes.post("/general", async (c) => {
|
|
|
451
566
|
}
|
|
452
567
|
});
|
|
453
568
|
|
|
454
|
-
|
|
569
|
+
// Time zone only. Language moved to its own page, so this no longer sends —
|
|
570
|
+
// and must not clear — the language fields.
|
|
571
|
+
settingsRoutes.post("/general/time", async (c) => {
|
|
455
572
|
const i18n = getI18n(c);
|
|
456
|
-
const body = parseValidated(
|
|
573
|
+
const body = parseValidated(UpdateTimeSettingsSchema, await c.req.json());
|
|
457
574
|
const toast = i18n._(
|
|
458
575
|
msg({
|
|
459
|
-
message: "
|
|
460
|
-
comment: "@context: Toast after saving
|
|
576
|
+
message: "Time zone updated.",
|
|
577
|
+
comment: "@context: Toast after saving the time zone",
|
|
461
578
|
}),
|
|
462
579
|
);
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
oldCjkSerifFont: c.var.appConfig.cjkSerifFont,
|
|
467
|
-
oldDashboardLanguage: c.var.appConfig.dashboardLanguage,
|
|
468
|
-
});
|
|
580
|
+
await c.var.services.settings.updateLocaleSettings(body, {
|
|
581
|
+
oldLanguage: c.var.appConfig.siteLanguage,
|
|
582
|
+
});
|
|
469
583
|
|
|
470
584
|
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
471
585
|
if (wantsJson) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
586
|
+
return c.json({ status: "ok" as const, toast });
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return dsToast(toast);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// ===========================================================================
|
|
593
|
+
// Language
|
|
594
|
+
//
|
|
595
|
+
// Every language setting lives on one page, and every write goes through the
|
|
596
|
+
// language service — the multilingual keys are DB-only precisely so this is the
|
|
597
|
+
// only door.
|
|
598
|
+
// ===========================================================================
|
|
599
|
+
|
|
600
|
+
settingsRoutes.get("/language", async (c) => {
|
|
601
|
+
const { appConfig } = c.var;
|
|
602
|
+
const navData = await getNavigationData(c);
|
|
603
|
+
const [state, preview] = await Promise.all([
|
|
604
|
+
c.var.services.language.getState(),
|
|
605
|
+
c.var.services.language.getEnablePreview(),
|
|
606
|
+
]);
|
|
607
|
+
|
|
608
|
+
return renderPublicPage(c, {
|
|
609
|
+
title: buildPageTitle("Language", navData.siteName),
|
|
610
|
+
navData,
|
|
611
|
+
content: (
|
|
612
|
+
<>
|
|
613
|
+
<AdminBreadcrumb
|
|
614
|
+
parent={breadcrumbLabel(c, "settings")}
|
|
615
|
+
parentHref={publicPath(c, "/settings")}
|
|
616
|
+
current={breadcrumbLabel(c, "language")}
|
|
617
|
+
/>
|
|
618
|
+
<LanguageContent
|
|
619
|
+
contentLanguage={state.primary}
|
|
620
|
+
// Unset stays unset rather than being shown as the locale it
|
|
621
|
+
// currently resolves to: "Follow content language" is a real choice
|
|
622
|
+
// on this page, and it is the one most sites are on.
|
|
623
|
+
dashboardLanguage={
|
|
624
|
+
isLocale(appConfig.dashboardLanguage)
|
|
625
|
+
? appConfig.dashboardLanguage
|
|
626
|
+
: ""
|
|
627
|
+
}
|
|
628
|
+
multilingualEnabled={state.enabled}
|
|
629
|
+
additionalLanguages={state.additional}
|
|
630
|
+
unmarkedPostCount={preview.pendingCount}
|
|
631
|
+
sitePathPrefix={appConfig.sitePathPrefix}
|
|
632
|
+
/>
|
|
633
|
+
</>
|
|
634
|
+
),
|
|
635
|
+
});
|
|
636
|
+
});
|
|
478
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Run a language mutation and report it as JSON.
|
|
640
|
+
*
|
|
641
|
+
* Every language endpoint has the same shape — do the thing, or explain in one
|
|
642
|
+
* sentence why it could not be done — so they share one wrapper rather than
|
|
643
|
+
* repeating the try/catch. The toast may be built from what the action
|
|
644
|
+
* returned, for the one operation whose outcome is not known until it runs.
|
|
645
|
+
*/
|
|
646
|
+
async function respondToLanguageAction<T>(
|
|
647
|
+
c: Context<Env>,
|
|
648
|
+
toast: string | ((result: T) => string),
|
|
649
|
+
action: () => Promise<T>,
|
|
650
|
+
): Promise<Response> {
|
|
651
|
+
try {
|
|
652
|
+
const result = await action();
|
|
479
653
|
return c.json({
|
|
480
654
|
status: "ok" as const,
|
|
481
|
-
toast,
|
|
655
|
+
toast: typeof toast === "function" ? toast(result) : toast,
|
|
482
656
|
});
|
|
657
|
+
} catch (error) {
|
|
658
|
+
// The one refusal an author will actually meet — removing or dropping a
|
|
659
|
+
// language that posts still use — gets a localized sentence naming the
|
|
660
|
+
// language; the settings page pairs it with a link to those posts.
|
|
661
|
+
if (error instanceof LanguageInUseError) {
|
|
662
|
+
const i18n = getI18n(c);
|
|
663
|
+
return c.json(
|
|
664
|
+
{
|
|
665
|
+
error: i18n._(
|
|
666
|
+
msg({
|
|
667
|
+
message:
|
|
668
|
+
"{count, plural, one {# post is} other {# posts are}} still written in {language}. Change their language, or keep the language.",
|
|
669
|
+
comment:
|
|
670
|
+
"@context: Error when a content language that posts still use would be dropped",
|
|
671
|
+
}),
|
|
672
|
+
{
|
|
673
|
+
count: error.postCount,
|
|
674
|
+
language: getOrBuildEntry(error.language).native,
|
|
675
|
+
},
|
|
676
|
+
),
|
|
677
|
+
code: error.code,
|
|
678
|
+
language: error.language,
|
|
679
|
+
},
|
|
680
|
+
400,
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
if (error instanceof DomainError) {
|
|
684
|
+
return c.json({ error: error.message, code: error.code }, 400);
|
|
685
|
+
}
|
|
686
|
+
throw error;
|
|
483
687
|
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
settingsRoutes.post("/language", async (c) => {
|
|
691
|
+
const i18n = getI18n(c);
|
|
692
|
+
const body = parseValidated(UpdateLanguageSettingsSchema, await c.req.json());
|
|
693
|
+
|
|
694
|
+
return respondToLanguageAction(
|
|
695
|
+
c,
|
|
696
|
+
i18n._(
|
|
697
|
+
msg({
|
|
698
|
+
message: "Language updated.",
|
|
699
|
+
comment: "@context: Toast after saving a language setting",
|
|
700
|
+
}),
|
|
701
|
+
),
|
|
702
|
+
async () => {
|
|
703
|
+
await c.var.services.settings.updateLocaleSettings(
|
|
704
|
+
{
|
|
705
|
+
siteLanguage: body.contentLanguage,
|
|
706
|
+
dashboardLanguage: body.dashboardLanguage,
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
oldLanguage: c.var.appConfig.siteLanguage,
|
|
710
|
+
oldDashboardLanguage: c.var.appConfig.dashboardLanguage,
|
|
711
|
+
},
|
|
712
|
+
);
|
|
713
|
+
},
|
|
714
|
+
);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
settingsRoutes.post("/language/enable", async (c) => {
|
|
718
|
+
const i18n = getI18n(c);
|
|
719
|
+
const body = parseValidated(EnableMultilingualSchema, await c.req.json());
|
|
484
720
|
|
|
485
|
-
|
|
486
|
-
|
|
721
|
+
try {
|
|
722
|
+
const { markedCount } = await c.var.services.language.enable(body);
|
|
723
|
+
return c.json({
|
|
724
|
+
status: "ok" as const,
|
|
725
|
+
toast:
|
|
726
|
+
markedCount > 0
|
|
727
|
+
? i18n._(
|
|
728
|
+
msg({
|
|
729
|
+
message:
|
|
730
|
+
"Multilingual content is on. {count, plural, one {# post was marked} other {# posts were marked}} as {language}.",
|
|
731
|
+
comment:
|
|
732
|
+
"@context: Toast after turning multilingual content on with existing posts",
|
|
733
|
+
}),
|
|
734
|
+
// The language's own name, not its tag — the author picked
|
|
735
|
+
// "繁體中文" in the dialog, not "zh-Hant".
|
|
736
|
+
{
|
|
737
|
+
count: markedCount,
|
|
738
|
+
language: getOrBuildEntry(body.primary).native,
|
|
739
|
+
},
|
|
740
|
+
)
|
|
741
|
+
: i18n._(
|
|
742
|
+
msg({
|
|
743
|
+
message: "Multilingual content is on.",
|
|
744
|
+
comment:
|
|
745
|
+
"@context: Toast after turning multilingual content on with no existing posts",
|
|
746
|
+
}),
|
|
747
|
+
),
|
|
748
|
+
});
|
|
749
|
+
} catch (error) {
|
|
750
|
+
if (error instanceof LanguageInUseError) {
|
|
751
|
+
// Worded for the dialog it lands in: the fix is putting the language
|
|
752
|
+
// back on the list right there, and the response carries the tag so
|
|
753
|
+
// the dialog can offer that as one click.
|
|
754
|
+
return c.json(
|
|
755
|
+
{
|
|
756
|
+
error: i18n._(
|
|
757
|
+
msg({
|
|
758
|
+
message:
|
|
759
|
+
"{count, plural, one {# post is} other {# posts are}} written in {language}, which is not on this list. Add it back, or change their language first.",
|
|
760
|
+
comment:
|
|
761
|
+
"@context: Refusal in the multilingual dialog when the list drops a language that posts still use",
|
|
762
|
+
}),
|
|
763
|
+
{
|
|
764
|
+
count: error.postCount,
|
|
765
|
+
language: getOrBuildEntry(error.language).native,
|
|
766
|
+
},
|
|
767
|
+
),
|
|
768
|
+
code: error.code,
|
|
769
|
+
language: error.language,
|
|
770
|
+
},
|
|
771
|
+
400,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
if (error instanceof DomainError) {
|
|
775
|
+
return c.json({ error: error.message, code: error.code }, 400);
|
|
776
|
+
}
|
|
777
|
+
throw error;
|
|
487
778
|
}
|
|
779
|
+
});
|
|
488
780
|
|
|
489
|
-
|
|
781
|
+
settingsRoutes.post("/language/disable", async (c) => {
|
|
782
|
+
const i18n = getI18n(c);
|
|
783
|
+
return respondToLanguageAction(
|
|
784
|
+
c,
|
|
785
|
+
i18n._(
|
|
786
|
+
msg({
|
|
787
|
+
message: "Multilingual content is off. Your languages are still saved.",
|
|
788
|
+
comment: "@context: Toast after turning multilingual content off",
|
|
789
|
+
}),
|
|
790
|
+
),
|
|
791
|
+
() => c.var.services.language.disable(),
|
|
792
|
+
);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
settingsRoutes.post("/language/primary", async (c) => {
|
|
796
|
+
const i18n = getI18n(c);
|
|
797
|
+
const body = parseValidated(LanguageTagSchema, await c.req.json());
|
|
798
|
+
return respondToLanguageAction(
|
|
799
|
+
c,
|
|
800
|
+
i18n._(
|
|
801
|
+
msg({
|
|
802
|
+
message: "Primary language changed.",
|
|
803
|
+
comment: "@context: Toast after changing the primary language",
|
|
804
|
+
}),
|
|
805
|
+
),
|
|
806
|
+
() => c.var.services.language.setPrimary(body.language),
|
|
807
|
+
);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
settingsRoutes.post("/language/add", async (c) => {
|
|
811
|
+
const i18n = getI18n(c);
|
|
812
|
+
const body = parseValidated(LanguageTagSchema, await c.req.json());
|
|
813
|
+
return respondToLanguageAction(
|
|
814
|
+
c,
|
|
815
|
+
i18n._(
|
|
816
|
+
msg({
|
|
817
|
+
message: "Language added.",
|
|
818
|
+
comment: "@context: Toast after adding a content language",
|
|
819
|
+
}),
|
|
820
|
+
),
|
|
821
|
+
() => c.var.services.language.addLanguage(body.language),
|
|
822
|
+
);
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
settingsRoutes.post("/language/remove", async (c) => {
|
|
826
|
+
const i18n = getI18n(c);
|
|
827
|
+
const body = parseValidated(LanguageTagSchema, await c.req.json());
|
|
828
|
+
return respondToLanguageAction(
|
|
829
|
+
c,
|
|
830
|
+
// The last language takes the feature with it, and that is the larger of
|
|
831
|
+
// the two facts — the author leaves this page single-language.
|
|
832
|
+
({ multilingualDisabled }) =>
|
|
833
|
+
multilingualDisabled
|
|
834
|
+
? i18n._(
|
|
835
|
+
msg({
|
|
836
|
+
message: "Language removed. Multilingual content is off.",
|
|
837
|
+
comment:
|
|
838
|
+
"@context: Toast after removing the last additional content language",
|
|
839
|
+
}),
|
|
840
|
+
)
|
|
841
|
+
: i18n._(
|
|
842
|
+
msg({
|
|
843
|
+
message: "Language removed.",
|
|
844
|
+
comment: "@context: Toast after removing a content language",
|
|
845
|
+
}),
|
|
846
|
+
),
|
|
847
|
+
() => c.var.services.language.removeLanguage(body.language),
|
|
848
|
+
);
|
|
490
849
|
});
|
|
491
850
|
|
|
492
851
|
settingsRoutes.post("/general/feeds", async (c) => {
|
|
@@ -527,6 +886,88 @@ settingsRoutes.post("/general/home", async (c) => {
|
|
|
527
886
|
return dsToast(toast);
|
|
528
887
|
});
|
|
529
888
|
|
|
889
|
+
/**
|
|
890
|
+
* Announce, because the owner asked.
|
|
891
|
+
*
|
|
892
|
+
* The route the settings page offers whenever the directory has not heard from
|
|
893
|
+
* this site: an announcement that failed, and one that was never made at all.
|
|
894
|
+
* Sending it is safe at any time — the receiving end treats a repeat as a
|
|
895
|
+
* no-op for a site it already knows, and it carries nothing but the feed
|
|
896
|
+
* address.
|
|
897
|
+
*/
|
|
898
|
+
settingsRoutes.post("/general/discover/announce", async (c) => {
|
|
899
|
+
const i18n = getI18n(c);
|
|
900
|
+
const { appConfig } = c.var;
|
|
901
|
+
|
|
902
|
+
const started =
|
|
903
|
+
!appConfig.demoMode &&
|
|
904
|
+
announceInBackground(c, c.var.allSettings["DISCOVER"]);
|
|
905
|
+
|
|
906
|
+
const toast = started
|
|
907
|
+
? i18n._(
|
|
908
|
+
msg({
|
|
909
|
+
message: "Announcing your site. Reload to see the result.",
|
|
910
|
+
comment:
|
|
911
|
+
"@context: Toast after sending the Discover announcement. It runs in the background, so the page does not yet know how it went.",
|
|
912
|
+
}),
|
|
913
|
+
)
|
|
914
|
+
: i18n._(
|
|
915
|
+
msg({
|
|
916
|
+
message: "This site has no directory to announce to.",
|
|
917
|
+
comment:
|
|
918
|
+
"@context: Toast when the Discover announcement cannot be sent because no directory is configured, or the site cannot be listed at all",
|
|
919
|
+
}),
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
// The settings bridge is the only caller, and it always asks for JSON — a
|
|
923
|
+
// Datastar toast reaches it as a body it cannot parse, which it reports as a
|
|
924
|
+
// failed save whatever the server actually did. Every other settings route
|
|
925
|
+
// answers both shapes; this one has to as well.
|
|
926
|
+
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
927
|
+
if (wantsJson) {
|
|
928
|
+
return c.json({ status: "ok" as const, toast });
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return dsToast(toast);
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
settingsRoutes.post("/general/discover", async (c) => {
|
|
935
|
+
const i18n = getI18n(c);
|
|
936
|
+
const body = parseValidated(UpdateDiscoverSettingsSchema, await c.req.json());
|
|
937
|
+
const { appConfig } = c.var;
|
|
938
|
+
|
|
939
|
+
const { shouldAnnounce } =
|
|
940
|
+
await c.var.services.settings.updateDiscoverSetting(body.discover, {
|
|
941
|
+
demoMode: appConfig.demoMode,
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
if (shouldAnnounce) {
|
|
945
|
+
announceInBackground(c, body.discover);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
949
|
+
if (wantsJson) {
|
|
950
|
+
return c.json({
|
|
951
|
+
status: "ok" as const,
|
|
952
|
+
toast: i18n._(
|
|
953
|
+
msg({
|
|
954
|
+
message: "Site visibility updated.",
|
|
955
|
+
comment: "@context: Toast after saving the site visibility settings",
|
|
956
|
+
}),
|
|
957
|
+
),
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
return dsToast(
|
|
962
|
+
i18n._(
|
|
963
|
+
msg({
|
|
964
|
+
message: "Site visibility updated.",
|
|
965
|
+
comment: "@context: Toast after saving the site visibility settings",
|
|
966
|
+
}),
|
|
967
|
+
),
|
|
968
|
+
);
|
|
969
|
+
});
|
|
970
|
+
|
|
530
971
|
settingsRoutes.post("/general/search", async (c) => {
|
|
531
972
|
const i18n = getI18n(c);
|
|
532
973
|
const body = parseValidated(UpdateSearchSettingsSchema, await c.req.json());
|
|
@@ -795,7 +1236,7 @@ settingsRoutes.post("/avatar/display", async (c) => {
|
|
|
795
1236
|
settingsRoutes.get("/navigation", async (c) => {
|
|
796
1237
|
const [navItems, directoryData, suggestedLinks] = await Promise.all([
|
|
797
1238
|
c.var.services.navItems.list(),
|
|
798
|
-
c.var.services.collections.listDirectoryData(),
|
|
1239
|
+
c.var.services.collections.listDirectoryData({ isAuthenticated: true }),
|
|
799
1240
|
c.var.services.navItems.listSuggestedLinks({
|
|
800
1241
|
siteOrigin: c.var.appConfig.siteOrigin,
|
|
801
1242
|
sitePathPrefix: c.var.appConfig.sitePathPrefix,
|
|
@@ -1332,7 +1773,9 @@ settingsRoutes.get("/account/delete-account", async (c) => {
|
|
|
1332
1773
|
}
|
|
1333
1774
|
|
|
1334
1775
|
const navData = await getNavigationData(c);
|
|
1335
|
-
const csrfToken = await c.var.services.auth.generateDeleteCsrfToken(
|
|
1776
|
+
const csrfToken = await c.var.services.auth.generateDeleteCsrfToken(
|
|
1777
|
+
requireSessionId(c),
|
|
1778
|
+
);
|
|
1336
1779
|
|
|
1337
1780
|
return renderPublicPage(c, {
|
|
1338
1781
|
title: buildPageTitle("Delete Account", navData.siteName),
|
|
@@ -1390,7 +1833,10 @@ settingsRoutes.post("/account/delete-account", async (c) => {
|
|
|
1390
1833
|
);
|
|
1391
1834
|
}
|
|
1392
1835
|
|
|
1393
|
-
const isValid = await c.var.services.auth.validateDeleteCsrfToken(
|
|
1836
|
+
const isValid = await c.var.services.auth.validateDeleteCsrfToken(
|
|
1837
|
+
csrfToken,
|
|
1838
|
+
requireSessionId(c),
|
|
1839
|
+
);
|
|
1394
1840
|
if (!isValid) {
|
|
1395
1841
|
return dsToast(
|
|
1396
1842
|
i18n._(
|
|
@@ -1404,11 +1850,26 @@ settingsRoutes.post("/account/delete-account", async (c) => {
|
|
|
1404
1850
|
);
|
|
1405
1851
|
}
|
|
1406
1852
|
|
|
1853
|
+
// Sign the browser out first. Every row behind this session is about to
|
|
1854
|
+
// disappear, and better-auth's cookie cache would keep answering "signed in"
|
|
1855
|
+
// from the browser's own copy for minutes afterwards — a ticket to a site
|
|
1856
|
+
// that no longer exists, handed to whoever completes the next setup.
|
|
1857
|
+
let signOutHeaders: Headers | undefined;
|
|
1858
|
+
try {
|
|
1859
|
+
const signedOut = await c.var.auth.api.signOut({
|
|
1860
|
+
headers: c.req.raw.headers,
|
|
1861
|
+
asResponse: true,
|
|
1862
|
+
});
|
|
1863
|
+
signOutHeaders = signedOut.headers;
|
|
1864
|
+
} catch {
|
|
1865
|
+
// The wipe below removes the session either way.
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1407
1868
|
await c.var.services.auth.deleteAllData({
|
|
1408
1869
|
storage: c.var.storage,
|
|
1409
1870
|
});
|
|
1410
1871
|
|
|
1411
|
-
return dsRedirect(publicPath(c, "/setup"));
|
|
1872
|
+
return dsRedirect(publicPath(c, "/setup"), { headers: signOutHeaders });
|
|
1412
1873
|
});
|
|
1413
1874
|
|
|
1414
1875
|
// ===========================================================================
|