@jant/core 0.6.16 → 0.7.1
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/export.js +3 -1
- package/bin/commands/import-site.js +749 -220
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/setup.js +136 -0
- package/bin/commands/site/export.js +71 -34
- package/bin/commands/site/pull-media.js +2 -6
- package/bin/commands/site/snapshot/export.js +19 -61
- package/bin/commands/site/snapshot/import.js +23 -24
- package/bin/lib/d1-query.js +92 -18
- package/bin/lib/hugo-markdown.js +4 -0
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-pull-media.js +21 -28
- package/bin/lib/site-selection.js +94 -29
- package/bin/lib/site-snapshot.js +338 -3
- package/bin/lib/sql-export.js +68 -5
- package/bin/lib/wrangler-cli.js +37 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/bin/lib/zip-archive.js +187 -0
- package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
- 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-B-m7X7S5.js +261 -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-BJyd-LXE.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-BkscJx69.js +2 -0
- package/dist/client/_assets/client-DAhoqPdr.css +2 -0
- package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
- package/dist/client/_assets/client-knSEyUJO.js +295 -0
- package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
- package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
- package/dist/github-sync-Gw4orAHk.js +8398 -0
- package/dist/index.js +2 -5
- package/dist/node.js +11 -9
- package/package.json +15 -5
- 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__/dev-scripts.test.ts +203 -0
- package/src/__tests__/export-collection-order.test.ts +276 -0
- package/src/__tests__/export-feed-ids.test.ts +184 -0
- package/src/__tests__/export-feed-order.test.ts +294 -0
- package/src/__tests__/export-hugo-build.test.ts +390 -0
- package/src/__tests__/export-import-roundtrip.test.ts +94 -0
- package/src/__tests__/export-service.test.ts +842 -28
- package/src/__tests__/export-smart-collection.test.ts +329 -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__/helpers/hugo-site.ts +146 -0
- package/src/__tests__/import-site-command.test.ts +487 -1
- package/src/__tests__/mise-config.test.ts +75 -4
- package/src/__tests__/node-dev-tasks.test.ts +264 -0
- package/src/__tests__/site-export-canonical-import.test.ts +149 -0
- package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
- package/src/__tests__/snapshot-settings.test.ts +97 -0
- package/src/__tests__/snapshot-tables.test.ts +219 -0
- package/src/__tests__/sql-export.test.ts +173 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/__tests__/zip-archive.test.ts +106 -0
- package/src/app.tsx +86 -22
- package/src/auth.ts +18 -1
- package/src/client/__tests__/collection-page-actions.test.ts +51 -2
- package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
- package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
- package/src/client/__tests__/compose-bridge.test.ts +272 -0
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
- package/src/client/__tests__/compose-triggers.test.ts +90 -0
- package/src/client/__tests__/copy-field.test.ts +162 -0
- package/src/client/__tests__/custom-url-menu.test.ts +127 -6
- package/src/client/__tests__/image-processor.test.ts +324 -2
- package/src/client/__tests__/lazy-entries.test.ts +92 -0
- package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
- package/src/client/__tests__/runtime-paths.test.ts +84 -0
- package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
- package/src/client/__tests__/toast.test.ts +23 -0
- package/src/client/__tests__/video-processor-probe.test.ts +175 -0
- package/src/client/__tests__/video-processor.test.ts +181 -0
- package/src/client/audio-processor.ts +24 -15
- package/src/client/audio-waveform.ts +49 -0
- package/src/client/collection-dialog-host.ts +72 -0
- package/src/client/collection-navigation.ts +51 -16
- package/src/client/collection-page-actions.ts +26 -0
- package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
- package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
- package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
- package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
- package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
- package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
- package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
- package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
- package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
- package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
- package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
- package/src/client/components/collection-dialog-types.ts +49 -0
- package/src/client/components/collection-manager-types.ts +32 -1
- package/src/client/components/collection-types.ts +1 -15
- package/src/client/components/compose-types.ts +75 -1
- package/src/client/components/jant-collection-dialog.ts +722 -0
- package/src/client/components/jant-collection-directory.ts +567 -182
- package/src/client/components/jant-collection-form.ts +53 -232
- package/src/client/components/jant-command-palette.ts +100 -49
- package/src/client/components/jant-compose-dialog.ts +1486 -707
- package/src/client/components/jant-compose-editor.ts +673 -336
- package/src/client/components/jant-compose-fullscreen.ts +51 -43
- package/src/client/components/jant-config-editor.ts +65 -53
- package/src/client/components/jant-confirm-dialog.ts +13 -11
- package/src/client/components/jant-locale-picker.ts +418 -0
- package/src/client/components/jant-media-lightbox.ts +146 -136
- package/src/client/components/jant-nav-manager.ts +690 -366
- package/src/client/components/jant-post-menu.ts +1086 -282
- package/src/client/components/jant-post-picker.ts +324 -0
- package/src/client/components/jant-repo-picker-types.ts +6 -1
- package/src/client/components/jant-repo-picker.ts +137 -108
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +622 -543
- package/src/client/components/jant-settings-language.ts +1087 -0
- package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
- package/src/client/components/jant-text-preview.ts +94 -86
- package/src/client/components/nav-manager-types.ts +62 -1
- package/src/client/components/settings-types.ts +14 -29
- package/src/client/components/smart-collection-conditions.ts +285 -0
- package/src/client/components/smart-collection-dialog-types.ts +68 -0
- package/src/client/compose-bridge.ts +214 -141
- package/src/client/compose-launch.ts +76 -2
- package/src/client/compose-shortcuts.ts +6 -8
- package/src/client/compose-triggers.ts +50 -0
- package/src/client/copy-field.ts +108 -0
- package/src/client/custom-url-menu.ts +60 -4
- package/src/client/icons.ts +14 -0
- package/src/client/image-plan.ts +165 -0
- package/src/client/image-processor.ts +195 -117
- package/src/client/image-worker-client.ts +45 -0
- package/src/client/image-worker.ts +135 -0
- package/src/client/lazy-entries.ts +95 -0
- package/src/client/lazy-slugify.ts +2 -2
- package/src/client/media-scroll-hint.ts +43 -2
- package/src/client/mediabunny.ts +29 -0
- package/src/client/post-picker.ts +41 -0
- package/src/client/runtime-paths.ts +74 -1
- package/src/client/settings-bridge.ts +4 -10
- package/src/client/site-header-nav.js +18 -6
- package/src/client/smart-collection-dialog-host.ts +116 -0
- package/src/client/smart-collection-page-actions.ts +208 -0
- package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
- package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
- package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
- package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/extensions.ts +0 -3
- package/src/client/tiptap/floating-position.ts +216 -2
- package/src/client/tiptap/inline-image-upload.ts +7 -3
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/tiptap/structural-keymap.ts +158 -47
- package/src/client/toast.ts +13 -0
- package/src/client/types/vite-worker.d.ts +12 -0
- package/src/client/upload-with-metadata.ts +3 -13
- package/src/client/video-processor.ts +351 -107
- package/src/client-auth.ts +14 -31
- package/src/client-compose.ts +19 -0
- package/src/client-manage.ts +13 -0
- package/src/client-settings.ts +14 -0
- package/src/client-site.ts +10 -3
- package/src/client.ts +4 -0
- package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
- package/src/db/__tests__/d1-query.test.ts +56 -1
- package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
- package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0032_married_tomas.sql +3 -0
- package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
- package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
- package/src/db/migrations/meta/0032_snapshot.json +2582 -0
- package/src/db/migrations/meta/0033_snapshot.json +2816 -0
- package/src/db/migrations/meta/0034_snapshot.json +2816 -0
- package/src/db/migrations/meta/_journal.json +21 -0
- package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
- package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
- package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
- package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
- package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/_journal.json +21 -0
- package/src/db/pg/schema.ts +195 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +222 -27
- package/src/i18n/__tests__/detect.test.ts +26 -81
- package/src/i18n/__tests__/supported-locales.test.ts +83 -0
- package/src/i18n/context.tsx +12 -2
- package/src/i18n/detect.ts +7 -66
- package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
- package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
- package/src/i18n/locales/public/en.po +490 -158
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +487 -155
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +487 -155
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +555 -73
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +601 -119
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +614 -132
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +94 -0
- package/src/i18n/middleware.ts +12 -2
- package/src/i18n/supported-locales.ts +80 -0
- package/src/lib/__tests__/api-settings.test.ts +11 -2
- package/src/lib/__tests__/asset-path.test.ts +92 -0
- package/src/lib/__tests__/deferred.test.ts +90 -0
- package/src/lib/__tests__/discover-ping.test.ts +89 -0
- package/src/lib/__tests__/discover.test.ts +410 -0
- package/src/lib/__tests__/feed-policy.test.ts +26 -0
- package/src/lib/__tests__/feed.test.ts +1526 -15
- package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
- package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -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__/image.test.ts +27 -1
- 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-to-tiptap.test.ts +105 -0
- package/src/lib/__tests__/markdown.test.ts +10 -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 +131 -3
- package/src/lib/__tests__/schemas.test.ts +69 -5
- package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
- package/src/lib/__tests__/slug.test.ts +70 -0
- package/src/lib/__tests__/slugify.test.ts +85 -0
- package/src/lib/__tests__/storage.test.ts +32 -0
- package/src/lib/__tests__/thread-fold.test.ts +113 -0
- package/src/lib/__tests__/timeline.test.ts +87 -0
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
- package/src/lib/__tests__/upload-sideload.test.ts +14 -0
- package/src/lib/__tests__/url-fetch.test.ts +1 -2
- package/src/lib/__tests__/url.test.ts +78 -72
- package/src/lib/__tests__/view-language.test.ts +413 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
- package/src/lib/address.ts +57 -0
- package/src/lib/api-posts.ts +12 -0
- package/src/lib/asset-path.ts +98 -1
- package/src/lib/build-env.ts +16 -0
- package/src/lib/collection-groups.ts +5 -1
- package/src/lib/collection-paths.ts +3 -10
- package/src/lib/constants.ts +28 -3
- package/src/lib/copy-field.ts +36 -0
- package/src/lib/crypto.ts +40 -0
- package/src/lib/csp-builder.ts +2 -1
- package/src/lib/deferred.ts +79 -0
- package/src/lib/discover-ping.ts +141 -0
- package/src/lib/discover.ts +439 -0
- package/src/lib/env.ts +134 -0
- package/src/lib/errors.ts +33 -0
- package/src/lib/feed-policy.ts +236 -2
- package/src/lib/feed.ts +926 -73
- package/src/lib/filter-dimensions.ts +1349 -0
- package/src/lib/github-sync-repo-name.ts +45 -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 +62 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/image.ts +17 -4
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/markdown-manager.ts +392 -2
- 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-body-html.ts +11 -4
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +111 -6
- package/src/lib/schemas.ts +251 -15
- 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 +43 -2
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/tiptap-to-markdown.ts +13 -7
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +159 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +53 -25
- 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-setup.test.ts +163 -0
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
- package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
- package/src/node/__tests__/runtime.test.ts +75 -0
- package/src/node/index.ts +3 -0
- package/src/node/request-handler.ts +3 -1
- package/src/preset.css +76 -22
- package/src/routes/__tests__/feed-validator.test.ts +156 -0
- package/src/routes/api/__tests__/collections.test.ts +114 -0
- package/src/routes/api/__tests__/discover.test.ts +222 -0
- package/src/routes/api/__tests__/nav-items.test.ts +212 -2
- package/src/routes/api/__tests__/post-translations.test.ts +829 -0
- package/src/routes/api/__tests__/posts.test.ts +24 -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/__tests__/upload.test.ts +34 -0
- 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 +6 -3
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/internal/sites.ts +0 -1
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +161 -0
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +44 -7
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/upload.ts +10 -3
- 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 +9 -0
- package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
- package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
- package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
- package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -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 +714 -111
- package/src/routes/discover-announce.ts +96 -0
- package/src/routes/feed/__tests__/feed.test.ts +165 -2
- package/src/routes/feed/feed.ts +69 -40
- package/src/routes/feed/sitemap.ts +95 -11
- package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
- package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
- package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
- package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
- package/src/routes/pages/__tests__/collections.test.ts +9 -3
- package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
- package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
- package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
- package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
- package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
- package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
- package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
- package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
- package/src/routes/pages/archive.tsx +487 -379
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +43 -18
- 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 +228 -45
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +22 -5
- 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/__tests__/readiness.test.ts +23 -0
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +67 -1
- package/src/runtime/readiness.ts +15 -0
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/custom-url.test.ts +34 -0
- package/src/services/__tests__/github-app-installations.test.ts +153 -0
- package/src/services/__tests__/github-sync-push.test.ts +46 -0
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/media.test.ts +31 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +236 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post-timeline.test.ts +127 -0
- package/src/services/__tests__/post.test.ts +120 -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 +408 -39
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +29 -24
- 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/alias.html +27 -1
- package/src/services/export-theme/layouts/_default/list.html +2 -63
- package/src/services/export-theme/layouts/_default/rss.xml +103 -38
- package/src/services/export-theme/layouts/collections/list.html +3 -3
- package/src/services/export-theme/layouts/featured/list.html +1 -4
- package/src/services/export-theme/layouts/index.html +40 -26
- package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
- package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
- package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
- package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/head.html +1 -1
- package/src/services/export-theme/layouts/partials/header.html +5 -3
- package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
- package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
- 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/smart-collection-members.html +129 -0
- package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
- package/src/services/export-theme/layouts/post/list.html +1 -1
- package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
- package/src/services/export-theme/styles/main.css +13 -2
- package/src/services/export-theme/theme.toml +1 -1
- package/src/services/export.ts +880 -72
- package/src/services/github-app-installations.ts +171 -4
- package/src/services/github-sync.ts +69 -15
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +22 -2
- package/src/services/media.ts +22 -7
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +254 -6
- package/src/services/post.ts +1234 -190
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +27 -14
- package/src/services/site.ts +160 -32
- package/src/services/smart-collection.ts +636 -0
- package/src/services/telegram.ts +20 -22
- package/src/services/upload-session.ts +1 -3
- package/src/style-author.css +27 -0
- package/src/styles/components-author.css +1622 -0
- package/src/styles/components.css +24 -1571
- package/src/styles/site-media.css +16 -2
- package/src/styles/tokens.css +82 -12
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1090 -7216
- package/src/types/app-context.ts +23 -0
- package/src/types/bindings.ts +8 -0
- package/src/types/config.ts +94 -15
- package/src/types/constants.ts +188 -16
- package/src/types/entities.ts +87 -1
- package/src/types/operations.ts +74 -2
- package/src/types/props.ts +157 -31
- package/src/types/views.ts +55 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +201 -7
- package/src/ui/compose/ComposePrompt.tsx +1 -5
- package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
- package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
- package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
- package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
- package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
- package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
- package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
- package/src/ui/dash/settings/GeneralContent.tsx +277 -113
- package/src/ui/dash/settings/LanguageContent.tsx +409 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
- package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
- package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
- package/src/ui/dash/settings/discover-copy.ts +116 -0
- package/src/ui/feed/LinkCard.tsx +55 -45
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +90 -43
- package/src/ui/feed/QuoteCard.tsx +55 -30
- package/src/ui/feed/ThreadPreview.tsx +6 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
- package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
- package/src/ui/feed/thread-preview-state.ts +20 -6
- package/src/ui/font-themes.ts +39 -29
- package/src/ui/layouts/BaseLayout.tsx +155 -10
- package/src/ui/layouts/SiteLayout.tsx +192 -34
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
- package/src/ui/pages/ArchivePage.tsx +282 -289
- package/src/ui/pages/CollectionPage.tsx +86 -76
- package/src/ui/pages/CollectionsPage.tsx +5 -0
- package/src/ui/pages/ComposePage.tsx +8 -0
- package/src/ui/pages/PostPage.tsx +47 -0
- package/src/ui/pages/SearchPage.tsx +3 -3
- package/src/ui/pages/SmartCollectionPage.tsx +434 -0
- package/src/ui/pages/SubscribePage.tsx +236 -0
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
- package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
- package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
- package/src/ui/shared/CollectionDirectory.tsx +178 -3
- package/src/ui/shared/CollectionsManager.tsx +70 -17
- package/src/ui/shared/CopyField.tsx +127 -0
- package/src/ui/shared/EmptyPostContent.tsx +23 -0
- package/src/ui/shared/MediaGallery.tsx +125 -46
- package/src/ui/shared/Pagination.tsx +20 -5
- package/src/ui/shared/PostFooter.tsx +20 -11
- package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
- package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
- package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
- package/src/ui/shared/__tests__/pagination.test.ts +38 -24
- package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
- package/src/ui/shared/archive-labels.ts +98 -0
- package/src/ui/shared/collection-management-labels.ts +94 -16
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +2 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-C75Zc8dD.js +0 -6
- package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
- package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
- package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
- package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
- package/dist/client/_assets/client-D3VZyG4L.css +0 -2
- package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
- package/dist/env-BPYViDJJ.js +0 -281
- package/dist/export-Dl5KCiEs.js +0 -5562
- package/dist/github-api-BNF35Lkx.js +0 -176
- package/dist/github-app-DdiD-bC4.js +0 -275
- package/dist/github-sync-Bqg62IvV.js +0 -4
- package/dist/github-sync-HdK2EgvJ.js +0 -431
- package/dist/url-CbLAtlZe.js +0 -900
- package/src/client/__tests__/collection-created-notice.test.ts +0 -36
- package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
- package/src/client/__tests__/post-form-bridge.test.ts +0 -82
- package/src/client/collection-created-notice.ts +0 -66
- package/src/client/collection-form-bridge.ts +0 -191
- package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
- package/src/client/components/jant-post-form.ts +0 -422
- package/src/client/components/post-form-template.ts +0 -352
- package/src/client/components/post-form-types.ts +0 -96
- package/src/client/media-metadata.ts +0 -247
- package/src/client/post-form-bridge.ts +0 -193
- package/src/client/tiptap/exitable-marks.ts +0 -73
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which reader surfaces exist once per language.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the `langGet()` table in `routes/pages/language.tsx` — the two must
|
|
5
|
+
* change together. Kept in its own module, free of server imports, because the
|
|
6
|
+
* client needs the same answer: `viewPath()` uses it to decide whether a link
|
|
7
|
+
* carries the current view's language prefix, and getting it wrong in either
|
|
8
|
+
* direction is a bug you can see — a missing prefix drops the reader into
|
|
9
|
+
* another language, an extra one lands on a 404.
|
|
10
|
+
*
|
|
11
|
+
* Everything not listed here — the dashboard, auth, collection editors, and
|
|
12
|
+
* post permalinks — has one address site-wide.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Surfaces served per language at exactly this path. */
|
|
16
|
+
const PER_LANGUAGE_SURFACES = new Set([
|
|
17
|
+
"/",
|
|
18
|
+
"/feed",
|
|
19
|
+
"/latest",
|
|
20
|
+
"/featured",
|
|
21
|
+
"/archive",
|
|
22
|
+
"/search",
|
|
23
|
+
"/subscribe",
|
|
24
|
+
"/collections",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Surfaces whose sub-paths are all per-language too. `/collections/` is absent
|
|
29
|
+
* on purpose: aggregate selections and their feeds are, but nothing else
|
|
30
|
+
* nested under it is.
|
|
31
|
+
*/
|
|
32
|
+
const PER_LANGUAGE_SURFACE_PREFIXES = ["/latest/", "/featured/", "/archive/"];
|
|
33
|
+
|
|
34
|
+
const COLLECTIONS_PREFIX = "/collections/";
|
|
35
|
+
|
|
36
|
+
/** `/collections/{selection}` and its feed, and nothing else under it. */
|
|
37
|
+
function isCollectionSelectionSurface(path: string): boolean {
|
|
38
|
+
const rest = path.slice(COLLECTIONS_PREFIX.length);
|
|
39
|
+
if (!rest) return false;
|
|
40
|
+
|
|
41
|
+
const segments = rest.split("/");
|
|
42
|
+
if (segments[0] === "") return false;
|
|
43
|
+
if (segments.length === 1) return true;
|
|
44
|
+
return segments.length === 2 && segments[1] === "feed";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether a path exists in every language's view.
|
|
49
|
+
*
|
|
50
|
+
* @param path - Internal app path, without any language prefix. A query string
|
|
51
|
+
* or fragment is ignored, so callers can pass a built link as-is.
|
|
52
|
+
* @returns True when `/ja{path}` is a page rather than a 404
|
|
53
|
+
* @example
|
|
54
|
+
* isPerLanguageSurface("/archive?media=any"); // true — /ja/archive exists
|
|
55
|
+
* isPerLanguageSurface("/collections/a+b/feed"); // true — /ja/collections/a+b/feed exists
|
|
56
|
+
* isPerLanguageSurface("/settings/language"); // false — the dash is one place
|
|
57
|
+
*/
|
|
58
|
+
export function isPerLanguageSurface(path: string): boolean {
|
|
59
|
+
const bare = path.split(/[?#]/, 1)[0] ?? path;
|
|
60
|
+
if (PER_LANGUAGE_SURFACES.has(bare)) return true;
|
|
61
|
+
if (bare.startsWith(COLLECTIONS_PREFIX)) {
|
|
62
|
+
return isCollectionSelectionSurface(bare);
|
|
63
|
+
}
|
|
64
|
+
return PER_LANGUAGE_SURFACE_PREFIXES.some((prefix) =>
|
|
65
|
+
bare.startsWith(prefix),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { JSONContent } from "@tiptap/core";
|
|
10
10
|
import { upgradeLegacyFootnotes } from "./footnotes.js";
|
|
11
|
+
import { fillRequiredContent } from "./markdown-manager.js";
|
|
11
12
|
import {
|
|
12
13
|
renderTiptapDocument,
|
|
13
14
|
type TiptapRenderResult,
|
|
@@ -50,8 +51,9 @@ export function renderPostBodyHtml(postId: string, body: string): string {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
|
-
* Normalize recognized historical footnotes
|
|
54
|
-
*
|
|
54
|
+
* Normalize recognized historical footnotes, fill in children the schema
|
|
55
|
+
* requires (an empty list item gets its paragraph), and render the current
|
|
56
|
+
* HTML projection in one parse pass.
|
|
55
57
|
*
|
|
56
58
|
* @param postId - Immutable post TypeID
|
|
57
59
|
* @param body - Canonical TipTap JSON
|
|
@@ -73,10 +75,15 @@ export function tryPreparePostBodyHtml(
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
const upgraded = upgradeLegacyFootnotes(parsed);
|
|
78
|
+
const doc = fillRequiredContent(upgraded.doc);
|
|
79
|
+
const canonical = JSON.stringify(doc);
|
|
76
80
|
return {
|
|
77
81
|
ok: true,
|
|
78
|
-
body:
|
|
79
|
-
|
|
82
|
+
body:
|
|
83
|
+
upgraded.upgraded || canonical !== JSON.stringify(upgraded.doc)
|
|
84
|
+
? canonical
|
|
85
|
+
: body,
|
|
86
|
+
html: renderTiptapDocument(doc, { namespace: postId }),
|
|
80
87
|
upgradedLegacyFootnotes: upgraded.upgraded,
|
|
81
88
|
};
|
|
82
89
|
} catch (error) {
|
package/src/lib/post-meta.ts
CHANGED
|
@@ -97,6 +97,25 @@ function getDescriptionCandidate(post: Post): string {
|
|
|
97
97
|
return "";
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* What to call a Post in a list, when it may have no title of its own.
|
|
102
|
+
*
|
|
103
|
+
* Notes are usually untitled, so falling back to the slug would show readers
|
|
104
|
+
* and authors a URL fragment where they expect a sentence. The chain is the
|
|
105
|
+
* same one browser titles and Open Graph tags already use: title, then the
|
|
106
|
+
* quoted text, then the summary, then the opening of the body, then a link's
|
|
107
|
+
* domain.
|
|
108
|
+
*
|
|
109
|
+
* @param post - The Post to name
|
|
110
|
+
* @returns A short plain-text label, or an empty string when there is nothing
|
|
111
|
+
* to derive one from
|
|
112
|
+
* @example
|
|
113
|
+
* getPostDisplayTitle(untitledNote); // "An untitled note about espresso."
|
|
114
|
+
*/
|
|
115
|
+
export function getPostDisplayTitle(post: Post): string {
|
|
116
|
+
return getTitleCandidate(post);
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
export interface PostMeta {
|
|
101
120
|
title: string;
|
|
102
121
|
description?: string;
|
package/src/lib/render.tsx
CHANGED
|
@@ -9,9 +9,20 @@ import type { Context } from "hono";
|
|
|
9
9
|
import type { Child } from "hono/jsx";
|
|
10
10
|
import type { SiteLayoutProps } from "../types.js";
|
|
11
11
|
import { SETTINGS_KEYS } from "./constants.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
BaseLayout,
|
|
14
|
+
type PageFeedLink,
|
|
15
|
+
type ToastProps,
|
|
16
|
+
} from "../ui/layouts/BaseLayout.js";
|
|
13
17
|
import { SiteLayout } from "../ui/layouts/SiteLayout.js";
|
|
14
18
|
import type { NavigationData } from "./navigation.js";
|
|
19
|
+
import {
|
|
20
|
+
buildComposeLanguages,
|
|
21
|
+
buildLanguageSwitcher,
|
|
22
|
+
getViewLang,
|
|
23
|
+
type LanguageAlternate,
|
|
24
|
+
type LanguageSwitcherOption,
|
|
25
|
+
} from "./view-language.js";
|
|
15
26
|
|
|
16
27
|
export interface RenderPublicPageOptions {
|
|
17
28
|
/** Page title for <title> tag */
|
|
@@ -45,6 +56,23 @@ export interface RenderPublicPageOptions {
|
|
|
45
56
|
* thread root).
|
|
46
57
|
*/
|
|
47
58
|
canonicalHref?: string;
|
|
59
|
+
/**
|
|
60
|
+
* `hreflang` alternates for this page. List surfaces pass
|
|
61
|
+
* `buildSurfaceAlternates(c)`; a post passes its translations. Pages that
|
|
62
|
+
* exist once for the whole site pass nothing.
|
|
63
|
+
*/
|
|
64
|
+
alternateLanguages?: LanguageAlternate[];
|
|
65
|
+
/**
|
|
66
|
+
* The feed this page publishes, for `<link rel="alternate">` autodiscovery.
|
|
67
|
+
* Set on pages with a feed of their own — a collection, the archive — so a
|
|
68
|
+
* reader's extension finds that feed rather than only the site-wide ones.
|
|
69
|
+
*/
|
|
70
|
+
pageFeed?: PageFeedLink;
|
|
71
|
+
/**
|
|
72
|
+
* Language switcher entries. Defaults to the current surface in each of the
|
|
73
|
+
* site's languages; a post overrides it with its translations.
|
|
74
|
+
*/
|
|
75
|
+
languageSwitcher?: LanguageSwitcherOption[];
|
|
48
76
|
/** Navigation data (from getNavigationData) */
|
|
49
77
|
navData: NavigationData;
|
|
50
78
|
/** Page content JSX to render inside SiteLayout */
|
|
@@ -57,14 +85,24 @@ export interface RenderPublicPageOptions {
|
|
|
57
85
|
toast?: ToastProps;
|
|
58
86
|
/** Whether to render the shared compose dialog shell */
|
|
59
87
|
showComposeDialog?: boolean;
|
|
60
|
-
/**
|
|
61
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Override the site-wide crawler setting for this page. `true` means
|
|
90
|
+
* `noindex, nofollow`; `"follow"` keeps the URL unindexed while leaving its
|
|
91
|
+
* links crawlable. See `BaseLayoutProps["noindex"]`.
|
|
92
|
+
*/
|
|
93
|
+
noindex?: boolean | "follow";
|
|
62
94
|
/** Whether to render the site header */
|
|
63
95
|
showHeader?: boolean;
|
|
64
96
|
/** Whether to render the home branding credit after the site footer */
|
|
65
97
|
showHomeBranding?: boolean;
|
|
66
98
|
/** When set, the mobile compose FAB pre-selects this collection. */
|
|
67
99
|
composeCollectionId?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Content language of the page the composer opens from, for its default.
|
|
102
|
+
* List surfaces leave this unset and get the view language; a post page
|
|
103
|
+
* passes its post's language, since its URL carries no view prefix.
|
|
104
|
+
*/
|
|
105
|
+
composeContextLanguage?: string | null;
|
|
68
106
|
}
|
|
69
107
|
|
|
70
108
|
/**
|
|
@@ -99,6 +137,9 @@ export function renderPublicPage(c: Context, options: RenderPublicPageOptions) {
|
|
|
99
137
|
articleModifiedTime,
|
|
100
138
|
jsonLd,
|
|
101
139
|
canonicalHref,
|
|
140
|
+
alternateLanguages,
|
|
141
|
+
pageFeed,
|
|
142
|
+
languageSwitcher,
|
|
102
143
|
navData,
|
|
103
144
|
content,
|
|
104
145
|
pageChrome,
|
|
@@ -109,6 +150,7 @@ export function renderPublicPage(c: Context, options: RenderPublicPageOptions) {
|
|
|
109
150
|
showHeader,
|
|
110
151
|
showHomeBranding,
|
|
111
152
|
composeCollectionId,
|
|
153
|
+
composeContextLanguage,
|
|
112
154
|
} = options;
|
|
113
155
|
|
|
114
156
|
// Use siteDescription as meta description fallback when not explicitly provided
|
|
@@ -123,9 +165,10 @@ export function renderPublicPage(c: Context, options: RenderPublicPageOptions) {
|
|
|
123
165
|
links: navData.links,
|
|
124
166
|
currentPath: navData.currentPath,
|
|
125
167
|
sitePathPrefix: navData.sitePathPrefix,
|
|
168
|
+
basePath: navData.basePath,
|
|
126
169
|
isAuthenticated: navData.isAuthenticated,
|
|
127
170
|
collections: navData.collections,
|
|
128
|
-
|
|
171
|
+
siteAvatarThumbUrl: navData.siteAvatarThumbUrl,
|
|
129
172
|
showHeaderAvatar: navData.showHeaderAvatar,
|
|
130
173
|
siteDescriptionHtml: navData.siteDescriptionHtml,
|
|
131
174
|
siteFooterHtml: navData.siteFooterHtml,
|
|
@@ -141,11 +184,14 @@ export function renderPublicPage(c: Context, options: RenderPublicPageOptions) {
|
|
|
141
184
|
allSettings[SETTINGS_KEYS.DISCOVERY_SLASH_COMMAND_AT],
|
|
142
185
|
),
|
|
143
186
|
composeCollectionId,
|
|
187
|
+
languageSwitcher: languageSwitcher ?? buildLanguageSwitcher(c),
|
|
188
|
+
composeLanguages: buildComposeLanguages(c),
|
|
189
|
+
composeContextLanguage: composeContextLanguage ?? getViewLang(c),
|
|
144
190
|
};
|
|
145
191
|
const faviconUrl = appConfig.siteAvatarUrl || undefined;
|
|
146
192
|
const faviconVersion = appConfig.faviconVersion || undefined;
|
|
147
|
-
|
|
148
|
-
|
|
193
|
+
// `noindex` is passed straight through: BaseLayout reads the site-wide
|
|
194
|
+
// setting from this same context and is the single place the two resolve.
|
|
149
195
|
return c.html(
|
|
150
196
|
<BaseLayout
|
|
151
197
|
title={title}
|
|
@@ -162,9 +208,11 @@ export function renderPublicPage(c: Context, options: RenderPublicPageOptions) {
|
|
|
162
208
|
articleModifiedTime={articleModifiedTime}
|
|
163
209
|
jsonLd={jsonLd}
|
|
164
210
|
canonicalHref={canonicalHref}
|
|
211
|
+
alternateLanguages={alternateLanguages}
|
|
212
|
+
pageFeed={pageFeed}
|
|
165
213
|
faviconUrl={faviconUrl}
|
|
166
214
|
faviconVersion={faviconVersion}
|
|
167
|
-
noindex={
|
|
215
|
+
noindex={noindex}
|
|
168
216
|
isAuthenticated={navData.isAuthenticated}
|
|
169
217
|
toast={toast}
|
|
170
218
|
>
|
|
@@ -12,21 +12,78 @@
|
|
|
12
12
|
import type { Bindings } from "../types/bindings.js";
|
|
13
13
|
import type { AppConfig } from "../types/config.js";
|
|
14
14
|
import { CONFIG_FIELDS } from "../types/config.js";
|
|
15
|
-
import type { FeedKind } from "../types/constants.js";
|
|
15
|
+
import type { ArchiveLayout, FeedKind } from "../types/constants.js";
|
|
16
16
|
import { ASSET_BASE_SEGMENT, getPublicAssetBasePath } from "./asset-path.js";
|
|
17
|
+
import { resolveDiscoverMode } from "./discover.js";
|
|
17
18
|
import {
|
|
18
19
|
getAuthSecret,
|
|
19
20
|
getConfiguredSingleSiteUrl,
|
|
20
21
|
getConfiguredStorageDriver,
|
|
22
|
+
getDiscoverDefault,
|
|
21
23
|
getEnvString,
|
|
22
24
|
} from "./env.js";
|
|
23
|
-
import {
|
|
25
|
+
import { parseLanguageList } from "../i18n/locales.js";
|
|
26
|
+
import { getPublicUrlForProvider, getMediaUrl, getImageUrl } from "./image.js";
|
|
24
27
|
import { normalizeTimeZone } from "./timezones.js";
|
|
25
28
|
import { getSiteOrigin, getSitePathPrefix, normalizeSiteUrl } from "./url.js";
|
|
26
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Whether an empty stored value means "never configured" for this field.
|
|
32
|
+
*
|
|
33
|
+
* The test is whether the editor could have produced the empty value, and it
|
|
34
|
+
* mirrors what `normalizeConfigEditorDefinitionValue` accepts. A boolean takes
|
|
35
|
+
* only `true` or `false`; a number has to parse; an enum has to name one of its
|
|
36
|
+
* options. None of those can be stored empty, so an empty row for one of them
|
|
37
|
+
* is not a choice anybody made — it comes from a path that skips validation,
|
|
38
|
+
* and a snapshot's `db.sql`, replayed as raw SQL, is exactly that. Read as a
|
|
39
|
+
* configured value it silences the environment variable the operator did set,
|
|
40
|
+
* for as long as the row exists.
|
|
41
|
+
*
|
|
42
|
+
* Text is the opposite: clearing a description or a footer is an ordinary edit,
|
|
43
|
+
* so an empty value there is a decision and keeps its precedence. So is an enum
|
|
44
|
+
* that lists `""` among its options, the way `DASHBOARD_LANGUAGE` does, and one
|
|
45
|
+
* whose options come from a runtime source, where nothing here can rule it
|
|
46
|
+
* out.
|
|
47
|
+
*
|
|
48
|
+
* @param field - Config registry entry for the key being resolved
|
|
49
|
+
* @returns True when an empty stored value should fall through to env/default
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* isUnsetWhenEmpty(CONFIG_FIELDS.NOINDEX); // true (boolean)
|
|
54
|
+
* isUnsetWhenEmpty(CONFIG_FIELDS.SITE_FOOTER); // false (string)
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
function isUnsetWhenEmpty(
|
|
58
|
+
field: (typeof CONFIG_FIELDS)[keyof typeof CONFIG_FIELDS],
|
|
59
|
+
): boolean {
|
|
60
|
+
if (!("editor" in field)) return false;
|
|
61
|
+
const editor = field.editor;
|
|
62
|
+
switch (editor.type) {
|
|
63
|
+
case "boolean":
|
|
64
|
+
case "number":
|
|
65
|
+
return true;
|
|
66
|
+
case "enum":
|
|
67
|
+
// Only a fixed option list proves the value is impossible. `""` is a real
|
|
68
|
+
// choice for `DASHBOARD_LANGUAGE`, which offers it, and an enum drawing
|
|
69
|
+
// its options from a runtime source cannot rule it out either.
|
|
70
|
+
return (
|
|
71
|
+
"options" in editor &&
|
|
72
|
+
!(editor.options as readonly string[]).includes("")
|
|
73
|
+
);
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
27
79
|
/**
|
|
28
80
|
* Resolve a single config value following priority rules.
|
|
29
81
|
*
|
|
82
|
+
* Settings saved in the dashboard outrank the environment, which outranks the
|
|
83
|
+
* default. The exception is an empty boolean or numeric value — see
|
|
84
|
+
* {@link isUnsetWhenEmpty}, which explains why that is absence rather than
|
|
85
|
+
* choice.
|
|
86
|
+
*
|
|
30
87
|
* @param key - CONFIG_FIELDS key
|
|
31
88
|
* @param allSettings - DB settings map
|
|
32
89
|
* @param env - Worker bindings
|
|
@@ -43,7 +100,10 @@ function resolve(
|
|
|
43
100
|
|
|
44
101
|
// User-configurable: DB > ENV > Default
|
|
45
102
|
if (!field.envOnly && Object.hasOwn(allSettings, key)) {
|
|
46
|
-
|
|
103
|
+
const stored = allSettings[key] ?? "";
|
|
104
|
+
if (stored !== "" || !isUnsetWhenEmpty(field)) {
|
|
105
|
+
return stored;
|
|
106
|
+
}
|
|
47
107
|
}
|
|
48
108
|
|
|
49
109
|
// ENV > Default
|
|
@@ -147,6 +207,13 @@ function parseFeedKind(value: string, fallback: FeedKind): FeedKind {
|
|
|
147
207
|
return value === "latest" || value === "featured" ? value : fallback;
|
|
148
208
|
}
|
|
149
209
|
|
|
210
|
+
function parseArchiveLayout(
|
|
211
|
+
value: string,
|
|
212
|
+
fallback: ArchiveLayout,
|
|
213
|
+
): ArchiveLayout {
|
|
214
|
+
return value === "list" || value === "grid" ? value : fallback;
|
|
215
|
+
}
|
|
216
|
+
|
|
150
217
|
/**
|
|
151
218
|
* Build a complete AppConfig from environment bindings and DB settings.
|
|
152
219
|
*
|
|
@@ -198,6 +265,7 @@ export function resolveConfig(
|
|
|
198
265
|
// Resolve avatar URL from storage key
|
|
199
266
|
const siteAvatar = allSettings["SITE_AVATAR"] ?? "";
|
|
200
267
|
let siteAvatarUrl = "";
|
|
268
|
+
let siteAvatarThumbUrl = "";
|
|
201
269
|
if (siteAvatar) {
|
|
202
270
|
const publicUrl = getPublicUrlForProvider(
|
|
203
271
|
storageDriver,
|
|
@@ -206,6 +274,17 @@ export function resolveConfig(
|
|
|
206
274
|
localPublicUrl,
|
|
207
275
|
);
|
|
208
276
|
siteAvatarUrl = getMediaUrl(siteAvatar, publicUrl, sitePathPrefix);
|
|
277
|
+
// The header and drawer marks render this at 32px and 24px, on every page,
|
|
278
|
+
// in parallel with the render-blocking stylesheet. An untransformed avatar
|
|
279
|
+
// is whatever the author uploaded — commonly a 512px PNG — so it competes
|
|
280
|
+
// for the critical path with tens of times the bytes it can use. 128px
|
|
281
|
+
// still covers a high-DPR header and a theme that enlarges the mark.
|
|
282
|
+
siteAvatarThumbUrl = getImageUrl(siteAvatarUrl, imageTransformUrl, {
|
|
283
|
+
width: 128,
|
|
284
|
+
quality: 80,
|
|
285
|
+
format: "auto",
|
|
286
|
+
fit: "scale-down",
|
|
287
|
+
});
|
|
209
288
|
}
|
|
210
289
|
|
|
211
290
|
// Description is "explicit" when set in DB or ENV (not just the default)
|
|
@@ -216,6 +295,16 @@ export function resolveConfig(
|
|
|
216
295
|
? !!dbDescription
|
|
217
296
|
: !!envDescription;
|
|
218
297
|
|
|
298
|
+
// The owner's stored choice and the deployment's default stay apart, and
|
|
299
|
+
// the registry default must not stand in for either: an absent row is what
|
|
300
|
+
// lets `noindex` decide, and an absent binding is what makes Discover
|
|
301
|
+
// opt-in for a self-hosted site.
|
|
302
|
+
const discoverStoredValue = allSettings["DISCOVER"];
|
|
303
|
+
const discoverDefaultValue = getDiscoverDefault(env);
|
|
304
|
+
const noindex = demoMode || resolve("NOINDEX", allSettings, env) === "true";
|
|
305
|
+
const rssFeedsEnabled =
|
|
306
|
+
resolve("RSS_FEEDS_ENABLED", allSettings, env) === "true";
|
|
307
|
+
|
|
219
308
|
return {
|
|
220
309
|
// Site identity (DB > ENV > Default)
|
|
221
310
|
siteName: resolve("SITE_NAME", allSettings, env),
|
|
@@ -223,19 +312,34 @@ export function resolveConfig(
|
|
|
223
312
|
siteDescriptionExplicit,
|
|
224
313
|
siteLanguage: resolve("SITE_LANGUAGE", allSettings, env),
|
|
225
314
|
dashboardLanguage: resolve("DASHBOARD_LANGUAGE", allSettings, env),
|
|
226
|
-
|
|
315
|
+
multilingualEnabled:
|
|
316
|
+
resolve("MULTILINGUAL_ENABLED", allSettings, env) === "true",
|
|
317
|
+
additionalLanguages: parseLanguageList(
|
|
318
|
+
resolve("ADDITIONAL_LANGUAGES", allSettings, env),
|
|
319
|
+
),
|
|
227
320
|
mainRssFeed: parseFeedKind(
|
|
228
321
|
resolve("MAIN_RSS_FEED", allSettings, env),
|
|
229
322
|
"featured",
|
|
230
323
|
),
|
|
324
|
+
archiveDefaultLayout: parseArchiveLayout(
|
|
325
|
+
resolve("ARCHIVE_DEFAULT_LAYOUT", allSettings, env),
|
|
326
|
+
"list",
|
|
327
|
+
),
|
|
231
328
|
timeZone: normalizeTimeZone(resolve("TIME_ZONE", allSettings, env)),
|
|
232
329
|
siteFooter: resolve("SITE_FOOTER", allSettings, env),
|
|
233
330
|
showJantBrandingOnHome:
|
|
234
331
|
resolve("SHOW_JANT_BRANDING_ON_HOME", allSettings, env) === "true",
|
|
235
|
-
noindex
|
|
332
|
+
noindex,
|
|
333
|
+
discover: resolveDiscoverMode({
|
|
334
|
+
storedValue: discoverStoredValue,
|
|
335
|
+
defaultValue: discoverDefaultValue,
|
|
336
|
+
demoMode,
|
|
337
|
+
noindex,
|
|
338
|
+
rssFeedsEnabled,
|
|
339
|
+
}),
|
|
236
340
|
publicApiEnabled:
|
|
237
341
|
resolve("PUBLIC_API_ENABLED", allSettings, env) === "true",
|
|
238
|
-
rssFeedsEnabled
|
|
342
|
+
rssFeedsEnabled,
|
|
239
343
|
|
|
240
344
|
// Infrastructure (ENV only)
|
|
241
345
|
siteUrl,
|
|
@@ -318,6 +422,7 @@ export function resolveConfig(
|
|
|
318
422
|
siteAvatar,
|
|
319
423
|
showHeaderAvatar: allSettings["SHOW_HEADER_AVATAR"] === "true",
|
|
320
424
|
siteAvatarUrl,
|
|
425
|
+
siteAvatarThumbUrl,
|
|
321
426
|
faviconVersion: allSettings["SITE_FAVICON_VERSION"] ?? "",
|
|
322
427
|
|
|
323
428
|
// Rate limiting (ENV only). Defaults are conservative enough for a
|