@jant/core 0.6.16 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/bin/commands/assets/upload.js +192 -15
- package/bin/commands/import-site.js +60 -4
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/site/snapshot/export.js +1 -1
- package/bin/commands/site/snapshot/import.js +1 -1
- package/bin/lib/d1-query.js +5 -16
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-selection.js +85 -29
- package/bin/lib/wrangler-cli.js +28 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/dist/{app-C7_4oab9.js → app-K_Aa1MMn.js} +52476 -13330
- package/dist/client/.vite/manifest.json +255 -90
- package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
- package/dist/client/_assets/chunks/create-editor-CD3FhrOB.js +257 -0
- package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
- package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
- package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
- package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
- package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
- package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
- package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
- package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
- package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
- package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
- package/dist/client/_assets/chunks/sortable-list-CgaL2jCs.js +2 -0
- package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
- package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
- package/dist/client/_assets/chunks/unsafe-svg-0QCkP0vZ.js +2 -0
- package/dist/client/_assets/client-GiYENVw8.css +2 -0
- package/dist/client/_assets/client-VnFxJN7G.js +295 -0
- package/dist/client/_assets/client-auth-k8gJ6QJj.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-B_kDtWkW.js +2004 -0
- package/dist/client/_assets/client-manage-M90aSTOk.js +2170 -0
- package/dist/client/_assets/client-settings-DfYs9n1F.js +1112 -0
- package/dist/{export-Dl5KCiEs.js → github-sync-BPAvT999.js} +2254 -107
- package/dist/index.js +2 -5
- package/dist/node.js +5 -7
- package/package.json +3 -3
- package/src/__tests__/auth-session-freshness.test.ts +57 -0
- package/src/__tests__/basecoat-variants.test.ts +121 -0
- package/src/__tests__/bin/assets-upload.test.ts +68 -0
- package/src/__tests__/export-hugo-build.test.ts +260 -0
- package/src/__tests__/export-service.test.ts +231 -0
- package/src/__tests__/helpers/component-css.ts +91 -0
- package/src/__tests__/helpers/db.ts +33 -0
- package/src/__tests__/helpers/export-fixtures.ts +3 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/app.tsx +71 -16
- package/src/auth.ts +18 -1
- package/src/client/__tests__/collection-page-actions.test.ts +51 -2
- package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
- package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
- package/src/client/__tests__/compose-bridge.test.ts +272 -0
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
- package/src/client/__tests__/compose-triggers.test.ts +90 -0
- package/src/client/__tests__/copy-field.test.ts +162 -0
- package/src/client/__tests__/custom-url-menu.test.ts +127 -6
- package/src/client/__tests__/image-processor.test.ts +324 -2
- package/src/client/__tests__/lazy-entries.test.ts +92 -0
- package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
- package/src/client/__tests__/runtime-paths.test.ts +84 -0
- package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
- package/src/client/__tests__/toast.test.ts +23 -0
- package/src/client/__tests__/video-processor-probe.test.ts +175 -0
- package/src/client/__tests__/video-processor.test.ts +181 -0
- package/src/client/audio-processor.ts +24 -15
- package/src/client/audio-waveform.ts +49 -0
- package/src/client/collection-dialog-host.ts +72 -0
- package/src/client/collection-navigation.ts +51 -16
- package/src/client/collection-page-actions.ts +26 -0
- package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
- package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
- package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
- package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
- package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
- package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
- package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
- package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
- package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
- package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
- package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
- package/src/client/components/collection-dialog-types.ts +49 -0
- package/src/client/components/collection-manager-types.ts +32 -1
- package/src/client/components/collection-types.ts +1 -15
- package/src/client/components/compose-types.ts +75 -1
- package/src/client/components/jant-collection-dialog.ts +722 -0
- package/src/client/components/jant-collection-directory.ts +567 -182
- package/src/client/components/jant-collection-form.ts +53 -232
- package/src/client/components/jant-command-palette.ts +100 -49
- package/src/client/components/jant-compose-dialog.ts +1486 -707
- package/src/client/components/jant-compose-editor.ts +673 -336
- package/src/client/components/jant-compose-fullscreen.ts +51 -43
- package/src/client/components/jant-config-editor.ts +65 -53
- package/src/client/components/jant-confirm-dialog.ts +13 -11
- package/src/client/components/jant-locale-picker.ts +418 -0
- package/src/client/components/jant-media-lightbox.ts +146 -136
- package/src/client/components/jant-nav-manager.ts +690 -366
- package/src/client/components/jant-post-menu.ts +1086 -282
- package/src/client/components/jant-post-picker.ts +324 -0
- package/src/client/components/jant-repo-picker.ts +138 -106
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +617 -543
- package/src/client/components/jant-settings-language.ts +1087 -0
- package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
- package/src/client/components/jant-text-preview.ts +94 -86
- package/src/client/components/nav-manager-types.ts +62 -1
- package/src/client/components/settings-types.ts +14 -29
- package/src/client/components/smart-collection-conditions.ts +285 -0
- package/src/client/components/smart-collection-dialog-types.ts +68 -0
- package/src/client/compose-bridge.ts +214 -141
- package/src/client/compose-launch.ts +76 -2
- package/src/client/compose-shortcuts.ts +6 -8
- package/src/client/compose-triggers.ts +50 -0
- package/src/client/copy-field.ts +108 -0
- package/src/client/custom-url-menu.ts +60 -4
- package/src/client/icons.ts +14 -0
- package/src/client/image-plan.ts +165 -0
- package/src/client/image-processor.ts +195 -117
- package/src/client/image-worker-client.ts +45 -0
- package/src/client/image-worker.ts +135 -0
- package/src/client/lazy-entries.ts +95 -0
- package/src/client/lazy-slugify.ts +2 -2
- package/src/client/media-scroll-hint.ts +43 -2
- package/src/client/mediabunny.ts +29 -0
- package/src/client/post-picker.ts +41 -0
- package/src/client/runtime-paths.ts +74 -1
- package/src/client/settings-bridge.ts +4 -10
- package/src/client/site-header-nav.js +18 -6
- package/src/client/smart-collection-dialog-host.ts +116 -0
- package/src/client/smart-collection-page-actions.ts +208 -0
- package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
- package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
- package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/floating-position.ts +216 -2
- package/src/client/tiptap/inline-image-upload.ts +7 -3
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/toast.ts +13 -0
- package/src/client/types/vite-worker.d.ts +12 -0
- package/src/client/upload-with-metadata.ts +3 -13
- package/src/client/video-processor.ts +351 -107
- package/src/client-auth.ts +14 -31
- package/src/client-compose.ts +19 -0
- package/src/client-manage.ts +13 -0
- package/src/client-settings.ts +14 -0
- package/src/client-site.ts +10 -3
- package/src/client.ts +4 -0
- package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
- package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
- package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0032_married_tomas.sql +3 -0
- package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
- package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
- package/src/db/migrations/meta/0032_snapshot.json +2582 -0
- package/src/db/migrations/meta/0033_snapshot.json +2816 -0
- package/src/db/migrations/meta/0034_snapshot.json +2816 -0
- package/src/db/migrations/meta/_journal.json +21 -0
- package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
- package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
- package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
- package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
- package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/_journal.json +21 -0
- package/src/db/pg/schema.ts +195 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +222 -27
- package/src/i18n/__tests__/detect.test.ts +26 -81
- package/src/i18n/__tests__/supported-locales.test.ts +83 -0
- package/src/i18n/context.tsx +12 -2
- package/src/i18n/detect.ts +7 -66
- package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
- package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
- package/src/i18n/locales/public/en.po +490 -158
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +487 -155
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +487 -155
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +551 -69
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +597 -115
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +610 -128
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +94 -0
- package/src/i18n/middleware.ts +12 -2
- package/src/i18n/supported-locales.ts +80 -0
- package/src/lib/__tests__/api-settings.test.ts +11 -2
- package/src/lib/__tests__/asset-path.test.ts +92 -0
- package/src/lib/__tests__/deferred.test.ts +90 -0
- package/src/lib/__tests__/discover-ping.test.ts +89 -0
- package/src/lib/__tests__/discover.test.ts +410 -0
- package/src/lib/__tests__/feed-policy.test.ts +26 -0
- package/src/lib/__tests__/feed.test.ts +1526 -15
- package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
- package/src/lib/__tests__/hosted-signin.test.ts +1 -1
- package/src/lib/__tests__/http-cache.test.ts +162 -0
- package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
- package/src/lib/__tests__/jant-docs.test.ts +23 -0
- package/src/lib/__tests__/lang-detect.test.ts +231 -0
- package/src/lib/__tests__/media-helpers.test.ts +81 -0
- package/src/lib/__tests__/navigation.test.ts +135 -1
- package/src/lib/__tests__/resolve-config.test.ts +64 -3
- package/src/lib/__tests__/schemas.test.ts +42 -4
- package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
- package/src/lib/__tests__/slug.test.ts +70 -0
- package/src/lib/__tests__/slugify.test.ts +85 -0
- package/src/lib/__tests__/storage.test.ts +32 -0
- package/src/lib/__tests__/thread-fold.test.ts +113 -0
- package/src/lib/__tests__/upload-sideload.test.ts +14 -0
- package/src/lib/__tests__/url-fetch.test.ts +1 -2
- package/src/lib/__tests__/url.test.ts +78 -72
- package/src/lib/__tests__/view-language.test.ts +413 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
- package/src/lib/address.ts +57 -0
- package/src/lib/api-posts.ts +12 -0
- package/src/lib/asset-path.ts +98 -1
- package/src/lib/build-env.ts +16 -0
- package/src/lib/collection-groups.ts +5 -1
- package/src/lib/collection-paths.ts +3 -10
- package/src/lib/constants.ts +28 -3
- package/src/lib/copy-field.ts +36 -0
- package/src/lib/crypto.ts +40 -0
- package/src/lib/csp-builder.ts +2 -1
- package/src/lib/deferred.ts +79 -0
- package/src/lib/discover-ping.ts +141 -0
- package/src/lib/discover.ts +437 -0
- package/src/lib/env.ts +134 -0
- package/src/lib/errors.ts +33 -0
- package/src/lib/feed-policy.ts +236 -2
- package/src/lib/feed.ts +926 -73
- package/src/lib/filter-dimensions.ts +1349 -0
- package/src/lib/github-sync-site-config.ts +3 -0
- package/src/lib/hosted-signin.ts +1 -1
- package/src/lib/http-cache.ts +148 -0
- package/src/lib/hugo-markdown.ts +16 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/media-helpers.ts +13 -1
- package/src/lib/navigation.ts +78 -11
- package/src/lib/pagination.ts +28 -24
- package/src/lib/per-language-surfaces.ts +67 -0
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +53 -5
- package/src/lib/schemas.ts +201 -10
- package/src/lib/site-header-fragment.tsx +2 -1
- package/src/lib/site-unavailable-page.ts +47 -0
- package/src/lib/slug.ts +51 -7
- package/src/lib/slugify.ts +113 -0
- package/src/lib/startup-config.ts +4 -6
- package/src/lib/storage.ts +7 -0
- package/src/lib/summary.ts +50 -0
- package/src/lib/thread-fold.ts +161 -0
- package/src/lib/time.ts +32 -0
- package/src/lib/timeline.ts +42 -1
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +139 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +51 -23
- package/src/lib/viewer-context.ts +62 -0
- package/src/middleware/__tests__/auth.test.ts +70 -2
- package/src/middleware/__tests__/cache-control.test.ts +71 -3
- package/src/middleware/__tests__/onboarding.test.ts +76 -5
- package/src/middleware/__tests__/secure-headers.test.ts +1 -1
- package/src/middleware/__tests__/session.test.ts +181 -33
- package/src/middleware/auth.ts +50 -31
- package/src/middleware/cache-control.ts +75 -10
- package/src/middleware/config.ts +6 -6
- package/src/middleware/error-handler.ts +12 -0
- package/src/middleware/onboarding.ts +32 -10
- package/src/middleware/secure-headers.ts +1 -1
- package/src/middleware/session.ts +96 -8
- package/src/node/__tests__/cli-migrate.test.ts +21 -19
- package/src/node/__tests__/cli-reset-password.test.ts +81 -8
- package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/runtime.test.ts +37 -0
- package/src/node/index.ts +1 -0
- package/src/preset.css +76 -22
- package/src/routes/__tests__/feed-validator.test.ts +156 -0
- package/src/routes/api/__tests__/collections.test.ts +114 -0
- package/src/routes/api/__tests__/discover.test.ts +222 -0
- package/src/routes/api/__tests__/nav-items.test.ts +212 -2
- package/src/routes/api/__tests__/post-translations.test.ts +829 -0
- package/src/routes/api/__tests__/settings.test.ts +24 -3
- package/src/routes/api/__tests__/telegram.test.ts +6 -5
- package/src/routes/api/collections.ts +49 -6
- package/src/routes/api/custom-urls.ts +0 -1
- package/src/routes/api/discover.ts +95 -0
- package/src/routes/api/export.ts +3 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +159 -0
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +23 -6
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/uploads.ts +1 -2
- package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
- package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
- package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
- package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
- package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
- package/src/routes/auth/__tests__/setup.test.ts +278 -26
- package/src/routes/auth/__tests__/signin.test.ts +135 -0
- package/src/routes/auth/setup.tsx +934 -186
- package/src/routes/auth/signin.tsx +10 -8
- package/src/routes/compose.tsx +5 -0
- package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
- package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
- package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
- package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
- package/src/routes/dash/custom-urls.tsx +1 -37
- package/src/routes/dash/settings.tsx +502 -41
- package/src/routes/discover-announce.ts +96 -0
- package/src/routes/feed/__tests__/feed.test.ts +165 -2
- package/src/routes/feed/feed.ts +69 -40
- package/src/routes/feed/sitemap.ts +95 -11
- package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
- package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
- package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
- package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
- package/src/routes/pages/__tests__/collections.test.ts +9 -3
- package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
- package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
- package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
- package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
- package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
- package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
- package/src/routes/pages/archive.tsx +471 -369
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +32 -13
- package/src/routes/pages/home.tsx +25 -6
- package/src/routes/pages/language.tsx +113 -0
- package/src/routes/pages/latest.tsx +26 -16
- package/src/routes/pages/new.tsx +3 -0
- package/src/routes/pages/page.tsx +178 -17
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +18 -3
- package/src/routes/pages/smart-collection.tsx +340 -0
- package/src/routes/pages/subscribe.tsx +182 -0
- package/src/runtime/__tests__/index.test.ts +19 -0
- package/src/runtime/__tests__/node.test.ts +129 -4
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +18 -1
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +180 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post.test.ts +46 -9
- package/src/services/__tests__/settings.test.ts +228 -17
- package/src/services/__tests__/site-admin.test.ts +5 -5
- package/src/services/__tests__/smart-collection.test.ts +651 -0
- package/src/services/about-page.ts +22 -1
- package/src/services/auth.ts +51 -49
- package/src/services/bootstrap.ts +188 -38
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +27 -22
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +123 -123
- package/src/services/export-theme/layouts/_default/rss.xml +77 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
- package/src/services/export-theme/layouts/partials/pagination.html +14 -13
- package/src/services/export-theme/layouts/partials/post-card.html +8 -4
- package/src/services/export-theme/layouts/partials/reply.html +2 -1
- package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
- package/src/services/export-theme/styles/main.css +13 -1
- package/src/services/export.ts +278 -19
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +3 -1
- package/src/services/media.ts +14 -6
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +221 -6
- package/src/services/post.ts +1021 -152
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +24 -7
- package/src/services/site.ts +77 -1
- package/src/services/smart-collection.ts +636 -0
- package/src/services/telegram.ts +20 -22
- package/src/services/upload-session.ts +1 -3
- package/src/style-author.css +27 -0
- package/src/styles/components-author.css +1622 -0
- package/src/styles/components.css +24 -1571
- package/src/styles/site-media.css +16 -2
- package/src/styles/tokens.css +82 -12
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1087 -7219
- package/src/types/app-context.ts +7 -0
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +94 -15
- package/src/types/constants.ts +188 -16
- package/src/types/entities.ts +87 -1
- package/src/types/operations.ts +64 -2
- package/src/types/props.ts +157 -31
- package/src/types/views.ts +55 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +201 -7
- package/src/ui/compose/ComposePrompt.tsx +1 -5
- package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
- package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
- package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
- package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
- package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
- package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
- package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
- package/src/ui/dash/settings/GeneralContent.tsx +275 -113
- package/src/ui/dash/settings/LanguageContent.tsx +409 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
- package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
- package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
- package/src/ui/dash/settings/discover-copy.ts +116 -0
- package/src/ui/feed/LinkCard.tsx +55 -45
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +90 -43
- package/src/ui/feed/QuoteCard.tsx +55 -30
- package/src/ui/feed/ThreadPreview.tsx +6 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
- package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
- package/src/ui/feed/thread-preview-state.ts +20 -6
- package/src/ui/font-themes.ts +39 -29
- package/src/ui/layouts/BaseLayout.tsx +155 -10
- package/src/ui/layouts/SiteLayout.tsx +192 -34
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
- package/src/ui/pages/ArchivePage.tsx +282 -289
- package/src/ui/pages/CollectionPage.tsx +86 -76
- package/src/ui/pages/CollectionsPage.tsx +5 -0
- package/src/ui/pages/ComposePage.tsx +8 -0
- package/src/ui/pages/PostPage.tsx +47 -0
- package/src/ui/pages/SearchPage.tsx +3 -3
- package/src/ui/pages/SmartCollectionPage.tsx +434 -0
- package/src/ui/pages/SubscribePage.tsx +236 -0
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
- package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
- package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
- package/src/ui/shared/CollectionDirectory.tsx +178 -3
- package/src/ui/shared/CollectionsManager.tsx +70 -17
- package/src/ui/shared/CopyField.tsx +127 -0
- package/src/ui/shared/EmptyPostContent.tsx +23 -0
- package/src/ui/shared/MediaGallery.tsx +125 -46
- package/src/ui/shared/Pagination.tsx +20 -5
- package/src/ui/shared/PostFooter.tsx +20 -11
- package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
- package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
- package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
- package/src/ui/shared/__tests__/pagination.test.ts +38 -24
- package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
- package/src/ui/shared/archive-labels.ts +98 -0
- package/src/ui/shared/collection-management-labels.ts +94 -16
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +2 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-C75Zc8dD.js +0 -6
- package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
- package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
- package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
- package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
- package/dist/client/_assets/client-D3VZyG4L.css +0 -2
- package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
- package/dist/env-BPYViDJJ.js +0 -281
- package/dist/github-api-BNF35Lkx.js +0 -176
- package/dist/github-app-DdiD-bC4.js +0 -275
- package/dist/github-sync-Bqg62IvV.js +0 -4
- package/dist/github-sync-HdK2EgvJ.js +0 -431
- package/dist/url-CbLAtlZe.js +0 -900
- package/src/client/__tests__/collection-created-notice.test.ts +0 -36
- package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
- package/src/client/__tests__/post-form-bridge.test.ts +0 -82
- package/src/client/collection-created-notice.ts +0 -66
- package/src/client/collection-form-bridge.ts +0 -191
- package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
- package/src/client/components/jant-post-form.ts +0 -422
- package/src/client/components/post-form-template.ts +0 -352
- package/src/client/components/post-form-types.ts +0 -96
- package/src/client/media-metadata.ts +0 -247
- package/src/client/post-form-bridge.ts +0 -193
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
package/src/types/operations.ts
CHANGED
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
|
+
ArchiveLayout,
|
|
6
7
|
Format,
|
|
7
8
|
Status,
|
|
8
9
|
Visibility,
|
|
9
10
|
CollectionSortOrder,
|
|
11
|
+
SmartCollectionSortOrder,
|
|
10
12
|
SystemNavKey,
|
|
11
13
|
NavItemPlacement,
|
|
12
14
|
TextAttachmentContentFormat,
|
|
13
15
|
} from "./constants.js";
|
|
16
|
+
import type { PostFilterSelection } from "../lib/filter-dimensions.js";
|
|
14
17
|
|
|
15
18
|
export type PostAttachmentInput =
|
|
16
19
|
| {
|
|
@@ -93,6 +96,18 @@ export interface CreatePost {
|
|
|
93
96
|
collectionEntries?: ThreadCollectionEntry[];
|
|
94
97
|
replyToId?: string;
|
|
95
98
|
quietReply?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* BCP 47 content language. Ignored for replies, which always inherit the
|
|
101
|
+
* Thread root's language so a language filter stays a plain column predicate.
|
|
102
|
+
* Omitted on single-language sites, where the column stays NULL.
|
|
103
|
+
*/
|
|
104
|
+
language?: string | null;
|
|
105
|
+
/**
|
|
106
|
+
* Thread root ID this Post is a translation of. The service joins both into
|
|
107
|
+
* one translation group when the Post is created, minting the group on the
|
|
108
|
+
* source Post if it does not have one yet. Roots only.
|
|
109
|
+
*/
|
|
110
|
+
translationOfId?: string;
|
|
96
111
|
publishedAt?: number;
|
|
97
112
|
attachments?: PostAttachmentInput[];
|
|
98
113
|
}
|
|
@@ -119,6 +134,13 @@ export interface UpdatePost {
|
|
|
119
134
|
collectionIds?: string[];
|
|
120
135
|
collectionEntries?: ThreadCollectionEntry[];
|
|
121
136
|
publishedAt?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Content language. Applied to the whole Thread, like
|
|
139
|
+
* `PostService.setThreadLanguage` — a Thread is written in one language, and
|
|
140
|
+
* that is what keeps every language filter a plain column predicate. Ignored
|
|
141
|
+
* on replies, which follow their root.
|
|
142
|
+
*/
|
|
143
|
+
language?: string | null;
|
|
122
144
|
attachments?: PostAttachmentInput[];
|
|
123
145
|
}
|
|
124
146
|
|
|
@@ -137,10 +159,20 @@ export type CreateNavItem =
|
|
|
137
159
|
position?: string;
|
|
138
160
|
}
|
|
139
161
|
| {
|
|
162
|
+
// The URL is derived from the collection's slug and is not accepted
|
|
163
|
+
// here; an omitted label means the item follows the collection's title.
|
|
140
164
|
type: "collection";
|
|
141
165
|
collectionId: string;
|
|
142
|
-
label
|
|
143
|
-
|
|
166
|
+
label?: string;
|
|
167
|
+
placement?: NavItemPlacement;
|
|
168
|
+
position?: string;
|
|
169
|
+
}
|
|
170
|
+
| {
|
|
171
|
+
// Same shape as `collection`: the URL comes from the smart collection's
|
|
172
|
+
// slug, and an omitted label follows its title.
|
|
173
|
+
type: "smart_collection";
|
|
174
|
+
smartCollectionId: string;
|
|
175
|
+
label?: string;
|
|
144
176
|
placement?: NavItemPlacement;
|
|
145
177
|
position?: string;
|
|
146
178
|
}
|
|
@@ -173,11 +205,41 @@ export interface UpdateCollection {
|
|
|
173
205
|
sortOrder?: CollectionSortOrder;
|
|
174
206
|
}
|
|
175
207
|
|
|
208
|
+
/**
|
|
209
|
+
* A smart collection's editable fields.
|
|
210
|
+
*
|
|
211
|
+
* `title` is required and stays required: the dialog asks for one, so no
|
|
212
|
+
* surface ever has to invent a name for an untitled smart collection in a page
|
|
213
|
+
* heading, a browser tab, a feed, or the directory.
|
|
214
|
+
*/
|
|
215
|
+
export interface CreateSmartCollection {
|
|
216
|
+
slug: string;
|
|
217
|
+
title: string;
|
|
218
|
+
description?: string | null;
|
|
219
|
+
/** The conditions. Omitted or empty means every post. */
|
|
220
|
+
selection?: PostFilterSelection;
|
|
221
|
+
sort?: SmartCollectionSortOrder;
|
|
222
|
+
layout?: ArchiveLayout | null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface UpdateSmartCollection {
|
|
226
|
+
slug?: string;
|
|
227
|
+
title?: string;
|
|
228
|
+
description?: string | null;
|
|
229
|
+
selection?: PostFilterSelection;
|
|
230
|
+
sort?: SmartCollectionSortOrder;
|
|
231
|
+
layout?: ArchiveLayout | null;
|
|
232
|
+
}
|
|
233
|
+
|
|
176
234
|
export type CreateCollectionDirectoryEntry =
|
|
177
235
|
| {
|
|
178
236
|
type: "collection";
|
|
179
237
|
collectionId: string;
|
|
180
238
|
}
|
|
239
|
+
| {
|
|
240
|
+
type: "smart_collection";
|
|
241
|
+
smartCollectionId: string;
|
|
242
|
+
}
|
|
181
243
|
| {
|
|
182
244
|
type: "divider";
|
|
183
245
|
label?: string | null;
|
package/src/types/props.ts
CHANGED
|
@@ -2,15 +2,28 @@
|
|
|
2
2
|
* Page-Level Props & Feed Data Types
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
6
|
-
|
|
5
|
+
import type {
|
|
6
|
+
ArchiveLayout,
|
|
7
|
+
ArchiveVisibility,
|
|
8
|
+
CollectionSortOrder,
|
|
9
|
+
SmartCollectionSortOrder,
|
|
10
|
+
} from "./constants.js";
|
|
11
|
+
import type { PostFilterSelection } from "../lib/filter-dimensions.js";
|
|
12
|
+
import type {
|
|
13
|
+
Collection,
|
|
14
|
+
CollectionDirectoryItem,
|
|
15
|
+
SmartCollection,
|
|
16
|
+
} from "./entities.js";
|
|
7
17
|
import type {
|
|
8
18
|
PostView,
|
|
9
19
|
FeedPostView,
|
|
20
|
+
LanguageAlternate,
|
|
21
|
+
LanguageSwitcherOption,
|
|
10
22
|
TimelineItemView,
|
|
11
23
|
SearchResultView,
|
|
12
24
|
ArchiveGroup,
|
|
13
25
|
} from "./views.js";
|
|
26
|
+
import type { DiscoverMode } from "../lib/discover.js";
|
|
14
27
|
|
|
15
28
|
// =============================================================================
|
|
16
29
|
// Page-Level Props
|
|
@@ -26,12 +39,27 @@ export interface HomePageProps {
|
|
|
26
39
|
signinUrl: string;
|
|
27
40
|
}
|
|
28
41
|
|
|
42
|
+
/** A link to another language's version of a post. */
|
|
43
|
+
export interface PostTranslationLinkView {
|
|
44
|
+
href: string;
|
|
45
|
+
/** The language's own name for itself, e.g. "日本語". */
|
|
46
|
+
label: string;
|
|
47
|
+
/** Canonical BCP 47 tag. */
|
|
48
|
+
lang: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
/** Props for the single post page component */
|
|
30
52
|
export interface PostPageProps {
|
|
31
53
|
post: PostView;
|
|
32
54
|
threadPosts?: PostView[];
|
|
33
55
|
/** Hide owner-only actions while preserving the public post rendering. */
|
|
34
56
|
isPreview?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Other-language versions of this post. Rendered as one quiet sentence
|
|
59
|
+
* after the post — a reader who cannot read this one needs a way out that
|
|
60
|
+
* does not involve guessing at the language switcher.
|
|
61
|
+
*/
|
|
62
|
+
translations?: PostTranslationLinkView[];
|
|
35
63
|
}
|
|
36
64
|
|
|
37
65
|
/** Props for the featured page component */
|
|
@@ -42,15 +70,7 @@ export interface FeaturedPageProps {
|
|
|
42
70
|
baseUrl: string;
|
|
43
71
|
}
|
|
44
72
|
|
|
45
|
-
|
|
46
|
-
export type ArchiveVisibility =
|
|
47
|
-
| "public"
|
|
48
|
-
| "latest_hidden"
|
|
49
|
-
| "private"
|
|
50
|
-
| "featured";
|
|
51
|
-
|
|
52
|
-
/** View mode for the archive page. */
|
|
53
|
-
export type ArchiveView = "grid" | "list";
|
|
73
|
+
export type { ArchiveLayout, ArchiveVisibility };
|
|
54
74
|
|
|
55
75
|
/**
|
|
56
76
|
* Time axis for the archive page.
|
|
@@ -62,19 +82,19 @@ export type ArchiveView = "grid" | "list";
|
|
|
62
82
|
*/
|
|
63
83
|
export type ArchiveSort = "published" | "updated";
|
|
64
84
|
|
|
65
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* What the archive page is currently showing.
|
|
87
|
+
*
|
|
88
|
+
* The selection itself is the shared dimension vocabulary — the chip bar builds
|
|
89
|
+
* its links by handing an edited copy back to the same serializer the route
|
|
90
|
+
* parses with, so a chip can never spell a filter the route cannot read.
|
|
91
|
+
* `layout` and `sort` sit beside it because they shape the rendering, not the
|
|
92
|
+
* result set.
|
|
93
|
+
*/
|
|
66
94
|
export interface ArchiveFilters {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
format?: Format;
|
|
71
|
-
mediaKinds?: MediaKind[];
|
|
72
|
-
hasMedia?: boolean;
|
|
73
|
-
hasTitle?: boolean;
|
|
74
|
-
/** true = threads (roots with replies), false = single posts (no replies) */
|
|
75
|
-
hasReplies?: boolean;
|
|
76
|
-
visibility?: ArchiveVisibility;
|
|
77
|
-
view?: ArchiveView;
|
|
95
|
+
selection: PostFilterSelection;
|
|
96
|
+
/** Omitted when the site's configured default layout is active */
|
|
97
|
+
layout?: ArchiveLayout;
|
|
78
98
|
/** Omitted when the default `published` axis is active */
|
|
79
99
|
sort?: ArchiveSort;
|
|
80
100
|
}
|
|
@@ -85,14 +105,30 @@ export interface ArchivePageProps {
|
|
|
85
105
|
groups: ArchiveGroup[];
|
|
86
106
|
/** Flat timeline items used by list view (skips month grouping) */
|
|
87
107
|
items?: TimelineItemView[];
|
|
108
|
+
/**
|
|
109
|
+
* Layout to use when the URL names none. Comes from the site's
|
|
110
|
+
* `ARCHIVE_DEFAULT_LAYOUT` setting.
|
|
111
|
+
*/
|
|
112
|
+
defaultLayout?: ArchiveLayout;
|
|
88
113
|
totalCount: number;
|
|
114
|
+
/**
|
|
115
|
+
* Matches the same view with every filter cleared. Lets the count say how
|
|
116
|
+
* much the active filter removed; omitted when nothing is filtered.
|
|
117
|
+
*/
|
|
118
|
+
baselineCount?: number;
|
|
89
119
|
currentPage: number;
|
|
90
120
|
totalPages: number;
|
|
91
121
|
filters: ArchiveFilters;
|
|
92
122
|
availableYears: number[];
|
|
93
|
-
availableCollections: { slug: string; title: string }[];
|
|
123
|
+
availableCollections: { id: string; slug: string; title: string }[];
|
|
94
124
|
isAuthenticated: boolean;
|
|
95
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Public path this page's own URLs are built from — the deployment prefix
|
|
127
|
+
* plus, in a language view, that language's prefix (`/blog/en`). Every link
|
|
128
|
+
* the archive generates is a link back to itself with different filters, so
|
|
129
|
+
* this is the only prefix it needs.
|
|
130
|
+
*/
|
|
131
|
+
basePath?: string;
|
|
96
132
|
timeZone?: string;
|
|
97
133
|
/** Href for the RSS feed matching current filters */
|
|
98
134
|
feedHref?: string;
|
|
@@ -105,7 +141,8 @@ export interface SearchPageProps {
|
|
|
105
141
|
error?: string;
|
|
106
142
|
hasMore: boolean;
|
|
107
143
|
page: number;
|
|
108
|
-
|
|
144
|
+
/** Public path this page's own URLs are built from. See `ArchivePageProps`. */
|
|
145
|
+
basePath?: string;
|
|
109
146
|
isAuthenticated?: boolean;
|
|
110
147
|
}
|
|
111
148
|
|
|
@@ -123,25 +160,80 @@ export interface CollectionPageProps {
|
|
|
123
160
|
showRatingSort: boolean;
|
|
124
161
|
isAuthenticated: boolean;
|
|
125
162
|
isInNavigation?: boolean;
|
|
163
|
+
/** Deployment path prefix. Used for links to admin surfaces. */
|
|
126
164
|
sitePathPrefix?: string;
|
|
165
|
+
/**
|
|
166
|
+
* Public path prefix for reader-facing links, which in a language view also
|
|
167
|
+
* carries that language's prefix. Defaults to `sitePathPrefix`.
|
|
168
|
+
*/
|
|
169
|
+
basePath?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Set when the collection is empty in this language but not in the others.
|
|
172
|
+
* The empty state then says which language is missing and offers the ones
|
|
173
|
+
* that have something — an empty page with no way out is a dead end.
|
|
174
|
+
*/
|
|
175
|
+
emptyInLanguage?: {
|
|
176
|
+
/** The current language's own name for itself. */
|
|
177
|
+
languageLabel: string;
|
|
178
|
+
/** This collection in each of the site's other languages. */
|
|
179
|
+
alternatives: LanguageSwitcherOption[];
|
|
180
|
+
};
|
|
127
181
|
/** Href for this collection selection's Atom feed when feeds are enabled. */
|
|
128
182
|
feedHref?: string;
|
|
129
183
|
}
|
|
130
184
|
|
|
131
|
-
/** Props for the collection
|
|
132
|
-
export interface
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
185
|
+
/** Props for the smart collection page component */
|
|
186
|
+
export interface SmartCollectionPageProps {
|
|
187
|
+
smartCollection: SmartCollection;
|
|
188
|
+
items: TimelineItemView[];
|
|
189
|
+
totalThreadCount: number;
|
|
190
|
+
currentPage: number;
|
|
191
|
+
totalPages: number;
|
|
192
|
+
pagePath: string;
|
|
193
|
+
baseUrl: string;
|
|
194
|
+
currentSort: SmartCollectionSortOrder;
|
|
195
|
+
defaultSort: SmartCollectionSortOrder;
|
|
196
|
+
showRatingSort: boolean;
|
|
197
|
+
/** One sentence naming what the conditions gather. Shown to every reader. */
|
|
198
|
+
conditionSummary: string;
|
|
199
|
+
/** Archive URL showing the same posts, for a reader who wants to narrow. */
|
|
200
|
+
conditionHref: string;
|
|
201
|
+
isAuthenticated: boolean;
|
|
202
|
+
isInNavigation?: boolean;
|
|
203
|
+
/** Deployment path prefix. Used for links to admin surfaces. */
|
|
136
204
|
sitePathPrefix?: string;
|
|
205
|
+
/**
|
|
206
|
+
* Public path prefix for reader-facing links, which in a language view also
|
|
207
|
+
* carries that language's prefix. Defaults to `sitePathPrefix`.
|
|
208
|
+
*/
|
|
209
|
+
basePath?: string;
|
|
210
|
+
/** Href for this smart collection's Atom feed when feeds are enabled. */
|
|
211
|
+
feedHref?: string;
|
|
137
212
|
}
|
|
138
213
|
|
|
139
214
|
/** Props for the collections list page component */
|
|
140
215
|
export interface CollectionsPageProps {
|
|
141
216
|
items: CollectionDirectoryItem[];
|
|
142
217
|
isAuthenticated: boolean;
|
|
218
|
+
/**
|
|
219
|
+
* TypeIDs of the collections and smart collections already in the site
|
|
220
|
+
* navigation. Both kinds share one list: the directory offers them the
|
|
221
|
+
* same action, and their ID prefixes keep them apart.
|
|
222
|
+
*/
|
|
143
223
|
navigationCollectionIds?: string[];
|
|
224
|
+
/**
|
|
225
|
+
* Whether each collection offers its feed. `/subscribe` lists only the
|
|
226
|
+
* site-wide feeds, so the directory is where a reader is handed a
|
|
227
|
+
* collection's — beside the collection itself.
|
|
228
|
+
*/
|
|
229
|
+
feedsEnabled?: boolean;
|
|
230
|
+
/** Deployment path prefix. Used for links to admin surfaces. */
|
|
144
231
|
sitePathPrefix?: string;
|
|
232
|
+
/**
|
|
233
|
+
* Public path prefix for reader-facing links, which in a language view also
|
|
234
|
+
* carries that language's prefix. Defaults to `sitePathPrefix`.
|
|
235
|
+
*/
|
|
236
|
+
basePath?: string;
|
|
145
237
|
siteOrigin?: string;
|
|
146
238
|
}
|
|
147
239
|
|
|
@@ -159,6 +251,38 @@ export interface FeedData {
|
|
|
159
251
|
title?: string;
|
|
160
252
|
selfUrl: string;
|
|
161
253
|
posts: FeedPostView[];
|
|
254
|
+
/**
|
|
255
|
+
* The site's avatar, for `atom:icon`. Readers show it beside the feed, and
|
|
256
|
+
* a directory listing the site has no other machine-readable place to find
|
|
257
|
+
* it. Omitted when the site has no avatar; Atom has no empty-icon form.
|
|
258
|
+
*/
|
|
259
|
+
siteIconUrl?: string;
|
|
260
|
+
/**
|
|
261
|
+
* Effective Jant Discover mode, declared in the feed header. Omitted means
|
|
262
|
+
* the feed says nothing about Discover — which a crawler reads as "this
|
|
263
|
+
* site predates the protocol", not as `none`.
|
|
264
|
+
*/
|
|
265
|
+
discover?: DiscoverMode;
|
|
266
|
+
/** Absolute URL of the feed a Discover crawler should poll, if any. */
|
|
267
|
+
discoverFeedUrl?: string | null;
|
|
268
|
+
/**
|
|
269
|
+
* Absolute URL of the site's featured feed, named beside `discoverFeedUrl`
|
|
270
|
+
* under `latest` so a directory can show featured posts on one list and
|
|
271
|
+
* everything on another. Null under `featured`, where `discoverFeedUrl`
|
|
272
|
+
* already is that feed, and under `none`.
|
|
273
|
+
*/
|
|
274
|
+
discoverFeaturedFeedUrl?: string | null;
|
|
275
|
+
/**
|
|
276
|
+
* Absolute URL of the endpoint a directory asks whether posts it holds are
|
|
277
|
+
* still in these feeds, scoped to this feed's language view. Null under
|
|
278
|
+
* `none`.
|
|
279
|
+
*/
|
|
280
|
+
discoverStatusUrl?: string | null;
|
|
281
|
+
/**
|
|
282
|
+
* The same feed in the site's other languages. Empty on a single-language
|
|
283
|
+
* site.
|
|
284
|
+
*/
|
|
285
|
+
languageAlternates?: LanguageAlternate[];
|
|
162
286
|
}
|
|
163
287
|
|
|
164
288
|
// =============================================================================
|
|
@@ -200,6 +324,8 @@ export interface ThreadPreviewProps {
|
|
|
200
324
|
leadingReplies: PostView[];
|
|
201
325
|
trailingReplies: PostView[];
|
|
202
326
|
latestReply: PostView;
|
|
327
|
+
/** Where the gap link points: the first post the fold hides. */
|
|
328
|
+
gapHref?: string;
|
|
203
329
|
totalReplyCount: number;
|
|
204
330
|
}
|
|
205
331
|
|
package/src/types/views.ts
CHANGED
|
@@ -68,6 +68,11 @@ export interface PostView {
|
|
|
68
68
|
/** 24-hour featured time, e.g. "23:05" */
|
|
69
69
|
featuredAtTime?: string;
|
|
70
70
|
rating?: number;
|
|
71
|
+
/**
|
|
72
|
+
* BCP 47 content language, on Thread roots of a multilingual site. Absent on
|
|
73
|
+
* replies (they follow the root) and on sites that publish one language.
|
|
74
|
+
*/
|
|
75
|
+
language?: string;
|
|
71
76
|
|
|
72
77
|
// Link preview
|
|
73
78
|
/** Preview kind: "video", "image", etc. */
|
|
@@ -163,9 +168,13 @@ export interface NavItemView {
|
|
|
163
168
|
type: NavItemType;
|
|
164
169
|
systemKey?: SystemNavKey;
|
|
165
170
|
collectionId?: string;
|
|
171
|
+
smartCollectionId?: string;
|
|
166
172
|
postId?: string;
|
|
173
|
+
/** Author's override, or `""` when the item follows its target's title */
|
|
167
174
|
label: string;
|
|
168
175
|
url: string;
|
|
176
|
+
/** Target's current title — the display label when `label` is empty */
|
|
177
|
+
targetTitle?: string;
|
|
169
178
|
/** "header" = visible in nav bar, "more" = under More dropdown */
|
|
170
179
|
placement: NavItemPlacement;
|
|
171
180
|
/** Pre-computed based on currentPath */
|
|
@@ -234,6 +243,8 @@ export interface TimelineItemView {
|
|
|
234
243
|
leadingReplies: PostView[];
|
|
235
244
|
trailingReplies: PostView[];
|
|
236
245
|
latestReply: PostView;
|
|
246
|
+
/** Where the gap link points: the first post the fold hides. */
|
|
247
|
+
gapHref?: string;
|
|
237
248
|
totalReplyCount: number;
|
|
238
249
|
};
|
|
239
250
|
curatedThread?: {
|
|
@@ -267,14 +278,45 @@ export interface ArchiveGroup {
|
|
|
267
278
|
/**
|
|
268
279
|
* Site Layout Props
|
|
269
280
|
*/
|
|
281
|
+
/** One `<link rel="alternate" hreflang>` target. */
|
|
282
|
+
export interface LanguageAlternate {
|
|
283
|
+
/** BCP 47 tag, or `x-default` for the entry point. */
|
|
284
|
+
hreflang: string;
|
|
285
|
+
/** Absolute URL. */
|
|
286
|
+
href: string;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** One entry in the site's language switcher. */
|
|
290
|
+
export interface LanguageSwitcherOption {
|
|
291
|
+
/** Canonical BCP 47 tag. */
|
|
292
|
+
lang: string;
|
|
293
|
+
/** The language's own name for itself, e.g. "日本語". */
|
|
294
|
+
label: string;
|
|
295
|
+
/** Where switching to this language takes the reader. */
|
|
296
|
+
href: string;
|
|
297
|
+
/** Whether this is the language currently on screen. */
|
|
298
|
+
isCurrent: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* Whether this is the site's primary language — the one served from the
|
|
301
|
+
* root. The switcher's trigger stays silent on it and names every other
|
|
302
|
+
* language, so a reader always knows when they are off the default.
|
|
303
|
+
*/
|
|
304
|
+
isPrimary: boolean;
|
|
305
|
+
}
|
|
306
|
+
|
|
270
307
|
export interface SiteLayoutProps {
|
|
271
308
|
siteName: string;
|
|
272
309
|
links: NavItemView[];
|
|
273
310
|
currentPath: string;
|
|
274
311
|
sitePathPrefix?: string;
|
|
312
|
+
/**
|
|
313
|
+
* Public base for language-scoped chrome links (logo, search, home
|
|
314
|
+
* detection). `sitePathPrefix` plus the language-view prefix, when any.
|
|
315
|
+
*/
|
|
316
|
+
basePath?: string;
|
|
275
317
|
isAuthenticated?: boolean;
|
|
276
318
|
collections?: Collection[];
|
|
277
|
-
|
|
319
|
+
siteAvatarThumbUrl?: string;
|
|
278
320
|
showHeaderAvatar?: boolean;
|
|
279
321
|
siteDescriptionHtml?: string;
|
|
280
322
|
siteFooterHtml?: string;
|
|
@@ -287,4 +329,16 @@ export interface SiteLayoutProps {
|
|
|
287
329
|
slashCommandDiscovered?: boolean;
|
|
288
330
|
/** When set, the mobile compose FAB pre-selects this collection. */
|
|
289
331
|
composeCollectionId?: string;
|
|
332
|
+
/**
|
|
333
|
+
* Languages this site publishes in, for the header's language switcher.
|
|
334
|
+
* Empty on a single-language site, which renders no switcher at all.
|
|
335
|
+
*/
|
|
336
|
+
languageSwitcher?: LanguageSwitcherOption[];
|
|
337
|
+
/** Languages offered in the composer. Empty on a single-language site. */
|
|
338
|
+
composeLanguages?: Array<{ tag: string; label: string }>;
|
|
339
|
+
/**
|
|
340
|
+
* Content language of the page the composer opens from. The composer's
|
|
341
|
+
* automatic language resolves to this unless detection disagrees.
|
|
342
|
+
*/
|
|
343
|
+
composeContextLanguage?: string | null;
|
|
290
344
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
1
3
|
import { describe, it, expect } from "vitest";
|
|
2
4
|
import {
|
|
3
5
|
BUILTIN_FONT_THEMES,
|
|
@@ -93,7 +95,7 @@ describe("BUILTIN_FONT_THEMES", () => {
|
|
|
93
95
|
});
|
|
94
96
|
|
|
95
97
|
it("routes zh-Hans content to simplified serif and sans fallbacks", () => {
|
|
96
|
-
const variables = getCjkFontCssVariables("zh-Hans"
|
|
98
|
+
const variables = getCjkFontCssVariables("zh-Hans");
|
|
97
99
|
const serif = variables["--font-cjk-serif-fallback"];
|
|
98
100
|
const sans = variables["--font-cjk-sans-fallback"];
|
|
99
101
|
|
|
@@ -105,7 +107,7 @@ describe("BUILTIN_FONT_THEMES", () => {
|
|
|
105
107
|
});
|
|
106
108
|
|
|
107
109
|
it("routes zh-Hant content to traditional serif and sans fallbacks", () => {
|
|
108
|
-
const variables = getCjkFontCssVariables("zh-Hant"
|
|
110
|
+
const variables = getCjkFontCssVariables("zh-Hant");
|
|
109
111
|
const serif = variables["--font-cjk-serif-fallback"];
|
|
110
112
|
const sans = variables["--font-cjk-sans-fallback"];
|
|
111
113
|
|
|
@@ -117,31 +119,69 @@ describe("BUILTIN_FONT_THEMES", () => {
|
|
|
117
119
|
});
|
|
118
120
|
|
|
119
121
|
it("routes Japanese content to the Japanese font profile", () => {
|
|
120
|
-
const variables = getCjkFontCssVariables("ja"
|
|
122
|
+
const variables = getCjkFontCssVariables("ja");
|
|
121
123
|
|
|
122
124
|
expect(variables["--font-cjk-serif-fallback"]).toContain('"Noto Serif JP"');
|
|
123
125
|
expect(variables["--font-cjk-sans-fallback"]).toContain('"Noto Sans JP"');
|
|
124
126
|
});
|
|
125
127
|
|
|
126
128
|
it("routes Korean content to the Korean font profile", () => {
|
|
127
|
-
const variables = getCjkFontCssVariables("ko"
|
|
129
|
+
const variables = getCjkFontCssVariables("ko");
|
|
128
130
|
|
|
129
131
|
expect(variables["--font-cjk-serif-fallback"]).toContain('"Noto Serif KR"');
|
|
130
132
|
expect(variables["--font-cjk-sans-fallback"]).toContain('"Noto Sans KR"');
|
|
131
133
|
});
|
|
132
134
|
|
|
133
|
-
it("
|
|
134
|
-
expect(resolveCjkFontProfile("
|
|
135
|
+
it("derives the profile from the language alone", () => {
|
|
136
|
+
expect(resolveCjkFontProfile("zh-Hant")).toBe("zh-Hant");
|
|
137
|
+
expect(resolveCjkFontProfile("zh-CN")).toBe("zh-Hans");
|
|
138
|
+
expect(resolveCjkFontProfile("ja-JP")).toBe("ja");
|
|
135
139
|
});
|
|
136
140
|
|
|
137
|
-
it("
|
|
138
|
-
expect(resolveCjkFontProfile("
|
|
141
|
+
it("has no profile for a language without CJK typography", () => {
|
|
142
|
+
expect(resolveCjkFontProfile("en")).toBeUndefined();
|
|
143
|
+
expect(resolveCjkFontProfile()).toBeUndefined();
|
|
139
144
|
});
|
|
140
145
|
|
|
141
|
-
it("
|
|
142
|
-
expect(getCjkFontCssVariables("en"
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
it("leaves a language without a profile on the token defaults", () => {
|
|
147
|
+
expect(getCjkFontCssVariables("en")).toEqual({});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("keeps the default stacks real, script-neutral and generic-free", () => {
|
|
151
|
+
for (const stack of [
|
|
152
|
+
DEFAULT_FONT_CJK_SERIF_FALLBACK,
|
|
153
|
+
DEFAULT_FONT_CJK_SANS_FALLBACK,
|
|
154
|
+
]) {
|
|
155
|
+
// A placeholder family name is skipped by the browser, which drops CJK
|
|
156
|
+
// text to the OS last-resort font — the whole point of these constants.
|
|
157
|
+
expect(stack).not.toContain("Jant Language Fallback");
|
|
158
|
+
// The generics belong to the theme stack, after this slot.
|
|
159
|
+
expect(stack).not.toMatch(/\b(?:ui-)?(?:serif|sans-serif)\b/);
|
|
160
|
+
// Every script the profiles cover is reachable from the default too.
|
|
161
|
+
for (const family of ["SC", "TC", "JP", "KR"]) {
|
|
162
|
+
expect(stack).toContain(family);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
expect(DEFAULT_FONT_CJK_SERIF_FALLBACK).toMatch(/^"Songti SC",/);
|
|
166
|
+
expect(DEFAULT_FONT_CJK_SANS_FALLBACK).toMatch(/^"PingFang SC",/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("ships the same defaults in tokens.css", () => {
|
|
170
|
+
const tokens = readFileSync(
|
|
171
|
+
fileURLToPath(new URL("../../styles/tokens.css", import.meta.url)),
|
|
172
|
+
"utf8",
|
|
173
|
+
);
|
|
174
|
+
const declared = (name: string) => {
|
|
175
|
+
const match = tokens.match(new RegExp(`--${name}:([^;]*);`));
|
|
176
|
+
expect(match, `--${name} is missing from tokens.css`).not.toBeNull();
|
|
177
|
+
return match![1].trim().replace(/\s+/g, " ");
|
|
178
|
+
};
|
|
179
|
+
expect(declared("font-cjk-serif-fallback")).toBe(
|
|
180
|
+
DEFAULT_FONT_CJK_SERIF_FALLBACK,
|
|
181
|
+
);
|
|
182
|
+
expect(declared("font-cjk-sans-fallback")).toBe(
|
|
183
|
+
DEFAULT_FONT_CJK_SANS_FALLBACK,
|
|
184
|
+
);
|
|
145
185
|
});
|
|
146
186
|
|
|
147
187
|
it("puts language slots into every serif and sans theme stack", () => {
|