@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/lib/summary.ts
CHANGED
|
@@ -22,6 +22,21 @@ interface TiptapNode {
|
|
|
22
22
|
attrs?: Record<string, unknown>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Summary limits for titled, article-style posts — the excerpt is a teaser
|
|
27
|
+
* and the reader continues on the post's own page.
|
|
28
|
+
*/
|
|
29
|
+
export const ARTICLE_SUMMARY_MAX_BLOCKS = 5;
|
|
30
|
+
export const ARTICLE_SUMMARY_MAX_CHARS = 500;
|
|
31
|
+
/**
|
|
32
|
+
* Larger limits for untitled notes — the body itself is the content, so the
|
|
33
|
+
* preview only exists to cap runaway length.
|
|
34
|
+
*/
|
|
35
|
+
export const NOTE_SUMMARY_MAX_BLOCKS = 10;
|
|
36
|
+
export const NOTE_SUMMARY_MAX_CHARS = 1500;
|
|
37
|
+
/** Don't truncate an untitled note just to hide a tail under this many chars. */
|
|
38
|
+
export const NOTE_SUMMARY_MIN_HIDDEN_CHARS = 200;
|
|
39
|
+
|
|
25
40
|
/**
|
|
26
41
|
* Block node types that carry user-visible content for summary extraction.
|
|
27
42
|
* Structural nodes (horizontalRule, moreBreak, image) are excluded.
|
|
@@ -348,3 +363,38 @@ export function extractSummaryHtml(
|
|
|
348
363
|
breakAtIndex: lastSelectedIdx + 1,
|
|
349
364
|
};
|
|
350
365
|
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Extract the summary the site's timeline shows for a post, at the boundary
|
|
369
|
+
* the timeline uses.
|
|
370
|
+
*
|
|
371
|
+
* Wraps {@link extractSummaryHtml} with the limits that decide where a card
|
|
372
|
+
* stops: a titled post is an article whose excerpt is a teaser, an untitled one
|
|
373
|
+
* is a note whose body is the content and only needs a cap. Callers that need
|
|
374
|
+
* that boundary — the feed renderer, the theme exporter — share this rather
|
|
375
|
+
* than repeating five numbers each.
|
|
376
|
+
*
|
|
377
|
+
* @param bodyJson - TipTap JSON body, or null/undefined for a post without one
|
|
378
|
+
* @param hasTitle - Whether the post has a title, which makes it an article
|
|
379
|
+
* @param renderOptions - Renderer options, notably the footnote namespace
|
|
380
|
+
* @returns The truncated HTML and whether content continues, or null when
|
|
381
|
+
* there is no TipTap document to truncate
|
|
382
|
+
* @example
|
|
383
|
+
* extractTimelineSummary(post.body, !!post.title, { namespace: post.id });
|
|
384
|
+
* // { html: "<p>Intro</p>", hasMore: true }
|
|
385
|
+
*/
|
|
386
|
+
export function extractTimelineSummary(
|
|
387
|
+
bodyJson: string | null | undefined,
|
|
388
|
+
hasTitle: boolean,
|
|
389
|
+
renderOptions: TiptapRenderOptions = {},
|
|
390
|
+
): { html: string; hasMore: boolean } | null {
|
|
391
|
+
if (!bodyJson) return null;
|
|
392
|
+
const result = extractSummaryHtml(
|
|
393
|
+
bodyJson,
|
|
394
|
+
hasTitle ? ARTICLE_SUMMARY_MAX_BLOCKS : NOTE_SUMMARY_MAX_BLOCKS,
|
|
395
|
+
hasTitle ? ARTICLE_SUMMARY_MAX_CHARS : NOTE_SUMMARY_MAX_CHARS,
|
|
396
|
+
hasTitle ? 0 : NOTE_SUMMARY_MIN_HIDDEN_CHARS,
|
|
397
|
+
renderOptions,
|
|
398
|
+
);
|
|
399
|
+
return result ? { html: result.html, hasMore: result.hasMore } : null;
|
|
400
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread Fold
|
|
3
|
+
*
|
|
4
|
+
* How much of a thread the timeline shows, and how much it folds away.
|
|
5
|
+
*
|
|
6
|
+
* A thread appears in two places that cannot select its posts the same way.
|
|
7
|
+
* The site paints a page of timeline items and must not load whole threads to
|
|
8
|
+
* do it, so `getThreadTimelineContext` ranks replies in SQL and hands the
|
|
9
|
+
* buckets down. The feed already holds every reply — `<content>` carries the
|
|
10
|
+
* whole chain — so it slices the array it has. Two access paths, one rule.
|
|
11
|
+
*
|
|
12
|
+
* This module owns what they share: the thresholds, and the arithmetic that
|
|
13
|
+
* turns a selection into a hidden count. Whoever changes the shape of the fold
|
|
14
|
+
* changes it here, and both surfaces move together.
|
|
15
|
+
*
|
|
16
|
+
* ## The fold
|
|
17
|
+
*
|
|
18
|
+
* ```
|
|
19
|
+
* root · reply 1 · reply 2 · [N more posts] · 3rd-newest · 2nd-newest · newest
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Six posts at most. A thread that fits arrives whole, with no gap at all —
|
|
23
|
+
* the gap appears only once something is actually left out.
|
|
24
|
+
*
|
|
25
|
+
* ## Where the gap points
|
|
26
|
+
*
|
|
27
|
+
* **At the first post it hides.** The link stands for a run of posts, and it
|
|
28
|
+
* opens the earliest of them, so a reader following "3 more posts" lands on
|
|
29
|
+
* the first of those three.
|
|
30
|
+
*
|
|
31
|
+
* It used to open the last *visible* post instead, on the reasoning that the
|
|
32
|
+
* detail page would then start just above the missing stretch, like a
|
|
33
|
+
* bookmark. That was a deliberate choice and it reads well on the site — but
|
|
34
|
+
* it makes the link mean something other than what its label says, and the
|
|
35
|
+
* feed cannot follow it: `<jant:thread>`'s `gap` attribute is documented as
|
|
36
|
+
* the first hidden post, and a consumer joining it against the `<jant:post>`
|
|
37
|
+
* rows would be handed a post that is on screen. One rule beats two, so both
|
|
38
|
+
* surfaces open the first hidden post.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type { Post, PostView } from "../types.js";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Replies kept from the start of a thread, as context for the newest post.
|
|
45
|
+
*
|
|
46
|
+
* Also fixes where the SQL path's rank cut-off falls, so the two selections
|
|
47
|
+
* stay the same fold.
|
|
48
|
+
*/
|
|
49
|
+
export const THREAD_LEADING_REPLIES = 2;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Replies kept from the end of a thread, the newest one included — it is the
|
|
53
|
+
* hero, and the two before it are its immediate context.
|
|
54
|
+
*/
|
|
55
|
+
export const THREAD_TRAILING_REPLIES = 3;
|
|
56
|
+
|
|
57
|
+
/** Anything with an id, which is all the dedupe needs. */
|
|
58
|
+
type Identified = Pick<Post, "id"> | Pick<PostView, "id">;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* How many replies a fold leaves unshown.
|
|
62
|
+
*
|
|
63
|
+
* Deduplicated, because on a short thread the buckets overlap: with four
|
|
64
|
+
* replies the first is also the third-newest, and counting it twice would
|
|
65
|
+
* report posts hidden that are on screen.
|
|
66
|
+
*
|
|
67
|
+
* @param selection - The replies the fold shows, and the thread's total
|
|
68
|
+
* @returns Replies the fold hides, never negative
|
|
69
|
+
* @example
|
|
70
|
+
* getThreadHiddenCount({ leadingReplies: [r1, r2], trailingReplies: [r6, r7],
|
|
71
|
+
* latestReply: r8, totalReplyCount: 8 }); // 3
|
|
72
|
+
*/
|
|
73
|
+
export function getThreadHiddenCount({
|
|
74
|
+
leadingReplies,
|
|
75
|
+
trailingReplies,
|
|
76
|
+
latestReply,
|
|
77
|
+
totalReplyCount,
|
|
78
|
+
}: {
|
|
79
|
+
leadingReplies: Identified[];
|
|
80
|
+
trailingReplies: Identified[];
|
|
81
|
+
latestReply: Identified;
|
|
82
|
+
totalReplyCount: number;
|
|
83
|
+
}): number {
|
|
84
|
+
const shown = new Set(
|
|
85
|
+
[...leadingReplies, ...trailingReplies, latestReply].map((post) => post.id),
|
|
86
|
+
);
|
|
87
|
+
return Math.max(0, totalReplyCount - shown.size);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** What the fold shows of a thread, and what it hides. */
|
|
91
|
+
export interface ThreadFold<T> {
|
|
92
|
+
/** Earliest replies, kept as context above the gap. */
|
|
93
|
+
leadingReplies: T[];
|
|
94
|
+
/** Replies just before the newest, kept as its immediate context. */
|
|
95
|
+
trailingReplies: T[];
|
|
96
|
+
/** The newest reply — the hero the timeline builds the card around. */
|
|
97
|
+
latestReply: T;
|
|
98
|
+
/** First reply the fold hides, which is where the gap link points. */
|
|
99
|
+
firstHiddenReply: T | null;
|
|
100
|
+
/** How many replies the fold hides. */
|
|
101
|
+
hiddenCount: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Fold a thread whose replies are already in hand.
|
|
106
|
+
*
|
|
107
|
+
* Takes the same slices the SQL path ranks for: the first
|
|
108
|
+
* `THREAD_LEADING_REPLIES`, and the last `THREAD_TRAILING_REPLIES` with the
|
|
109
|
+
* newest as the hero. `getPublishedThreads` orders replies by
|
|
110
|
+
* `(threadId, createdAt, id)` ascending, which is the window function's own
|
|
111
|
+
* `ORDER BY`, so position in this array *is* the rank — no approximation.
|
|
112
|
+
*
|
|
113
|
+
* @param replies - Every published reply, oldest first
|
|
114
|
+
* @returns The fold, or null when the thread has no replies
|
|
115
|
+
* @example
|
|
116
|
+
* foldThreadReplies([r1, r2, r3, r4, r5, r6, r7, r8]);
|
|
117
|
+
* // leading [r1, r2], trailing [r6, r7], latest r8, firstHidden r3, hidden 3
|
|
118
|
+
*/
|
|
119
|
+
export function foldThreadReplies<T extends Identified>(
|
|
120
|
+
replies: T[],
|
|
121
|
+
): ThreadFold<T> | null {
|
|
122
|
+
const latestReply = replies.at(-1);
|
|
123
|
+
if (!latestReply) return null;
|
|
124
|
+
|
|
125
|
+
// A short thread's windows overlap, and the hero is inside both of them on
|
|
126
|
+
// the shortest ones: with a single reply, that reply is the newest *and* the
|
|
127
|
+
// whole leading window. Each list therefore drops whatever an earlier one
|
|
128
|
+
// already shows, hero first — the same order `ThreadPreview` dedupes in when
|
|
129
|
+
// it walks the buckets the SQL path hands it.
|
|
130
|
+
const leadingReplies = replies
|
|
131
|
+
.slice(0, THREAD_LEADING_REPLIES)
|
|
132
|
+
.filter((reply) => reply.id !== latestReply.id);
|
|
133
|
+
|
|
134
|
+
const leadingIds = new Set(leadingReplies.map((reply) => reply.id));
|
|
135
|
+
const trailingReplies = replies
|
|
136
|
+
.slice(-THREAD_TRAILING_REPLIES, -1)
|
|
137
|
+
.filter(
|
|
138
|
+
(reply) => reply.id !== latestReply.id && !leadingIds.has(reply.id),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const shown = new Set([
|
|
142
|
+
...leadingIds,
|
|
143
|
+
...trailingReplies.map((reply) => reply.id),
|
|
144
|
+
latestReply.id,
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
leadingReplies,
|
|
149
|
+
trailingReplies,
|
|
150
|
+
latestReply,
|
|
151
|
+
// In thread order, so the gap link lands on the earliest post it stands
|
|
152
|
+
// for rather than somewhere in the middle of the run it hides.
|
|
153
|
+
firstHiddenReply: replies.find((reply) => !shown.has(reply.id)) ?? null,
|
|
154
|
+
hiddenCount: getThreadHiddenCount({
|
|
155
|
+
leadingReplies,
|
|
156
|
+
trailingReplies,
|
|
157
|
+
latestReply,
|
|
158
|
+
totalReplyCount: replies.length,
|
|
159
|
+
}),
|
|
160
|
+
};
|
|
161
|
+
}
|
package/src/lib/time.ts
CHANGED
|
@@ -223,3 +223,35 @@ export function formatYearMonth(timestamp: number, timeZone = "UTC"): string {
|
|
|
223
223
|
const month = parts.find((part) => part.type === "month")?.value ?? "01";
|
|
224
224
|
return `${year}-${month}`;
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Formats a "YYYY-MM" grouping key as a readable month label.
|
|
229
|
+
*
|
|
230
|
+
* The label is always en-US, matching the archive's other month copy — the
|
|
231
|
+
* grid header and the list marker read from this one function so their labels
|
|
232
|
+
* cannot drift apart.
|
|
233
|
+
*
|
|
234
|
+
* @param yearMonth - Year-month string in "YYYY-MM" format
|
|
235
|
+
* @returns Month label such as "February 2024", or null when the key is malformed
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* const label = formatYearMonthLabel("2024-02");
|
|
240
|
+
* // Returns: "February 2024"
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
export function formatYearMonthLabel(yearMonth: string): string | null {
|
|
244
|
+
const [year, month] = yearMonth.split("-");
|
|
245
|
+
if (!year || !month) return null;
|
|
246
|
+
|
|
247
|
+
const yearNumber = parseInt(year, 10);
|
|
248
|
+
const monthNumber = parseInt(month, 10);
|
|
249
|
+
if (!Number.isInteger(yearNumber) || !Number.isInteger(monthNumber)) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return new Date(yearNumber, monthNumber - 1).toLocaleDateString("en-US", {
|
|
254
|
+
year: "numeric",
|
|
255
|
+
month: "long",
|
|
256
|
+
});
|
|
257
|
+
}
|
package/src/lib/timeline.ts
CHANGED
|
@@ -15,9 +15,24 @@ import type {
|
|
|
15
15
|
import type { AppVariables } from "../types/app-context.js";
|
|
16
16
|
import { buildMediaMap } from "./media-helpers.js";
|
|
17
17
|
import { createMediaContext, resolveDraftTailId, toPostView } from "./view.js";
|
|
18
|
+
import { getViewLang } from "./view-language.js";
|
|
19
|
+
import { toPublicPath } from "./url.js";
|
|
18
20
|
|
|
19
21
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Language filter for the view the request is being served in.
|
|
25
|
+
*
|
|
26
|
+
* Read from the context rather than passed in, so a caller cannot forget it
|
|
27
|
+
* and quietly leak another language's posts into a language view.
|
|
28
|
+
*
|
|
29
|
+
* @param c - Hono context
|
|
30
|
+
* @returns Canonical tag to filter on, or undefined on the root view
|
|
31
|
+
*/
|
|
32
|
+
function viewLangFilter(c: Context<Env>): string | undefined {
|
|
33
|
+
return getViewLang(c) ?? undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
/**
|
|
22
37
|
* Result from assembling a timeline page.
|
|
23
38
|
*/
|
|
@@ -88,11 +103,15 @@ async function buildTimelineItems(
|
|
|
88
103
|
|
|
89
104
|
// Batch load media for the bounded leading/trailing thread context.
|
|
90
105
|
const contextPostIds = new Set<string>();
|
|
106
|
+
const gapPostIds = new Set<string>();
|
|
91
107
|
for (const ctx of threadContexts.values()) {
|
|
92
108
|
contextPostIds.add(ctx.latestReply.id);
|
|
93
109
|
for (const reply of [...ctx.leadingReplies, ...ctx.trailingReplies]) {
|
|
94
110
|
contextPostIds.add(reply.id);
|
|
95
111
|
}
|
|
112
|
+
// The gap target is never rendered, so it needs no media or collections —
|
|
113
|
+
// only the alias that decides its address.
|
|
114
|
+
if (ctx.firstHiddenReply) gapPostIds.add(ctx.firstHiddenReply.id);
|
|
96
115
|
}
|
|
97
116
|
const [contextMediaMap, contextCollectionsMap, contextAliasesMap] =
|
|
98
117
|
contextPostIds.size > 0
|
|
@@ -112,7 +131,12 @@ async function buildTimelineItems(
|
|
|
112
131
|
c.var.services.collections.getCollectionsByPostIds([
|
|
113
132
|
...contextPostIds,
|
|
114
133
|
]),
|
|
115
|
-
|
|
134
|
+
// Gap targets ride along here and nowhere else: their address is
|
|
135
|
+
// all the gap link needs.
|
|
136
|
+
c.var.services.paths.getPostAliases([
|
|
137
|
+
...contextPostIds,
|
|
138
|
+
...gapPostIds,
|
|
139
|
+
]),
|
|
116
140
|
])
|
|
117
141
|
: [new Map(), new Map(), new Map<string, string[]>()];
|
|
118
142
|
|
|
@@ -178,6 +202,12 @@ async function buildTimelineItems(
|
|
|
178
202
|
leadingReplies: leadingReplyViews,
|
|
179
203
|
trailingReplies: trailingReplyViews,
|
|
180
204
|
latestReply: latestReplyView,
|
|
205
|
+
gapHref: threadCtx.firstHiddenReply
|
|
206
|
+
? toPublicPath(
|
|
207
|
+
`/${firstContextAlias(threadCtx.firstHiddenReply.id) ?? threadCtx.firstHiddenReply.slug}`,
|
|
208
|
+
mediaCtx.sitePathPrefix,
|
|
209
|
+
)
|
|
210
|
+
: undefined,
|
|
181
211
|
totalReplyCount: threadCtx.totalReplyCount,
|
|
182
212
|
},
|
|
183
213
|
};
|
|
@@ -344,18 +374,22 @@ export async function assembleTimeline(
|
|
|
344
374
|
const excludePrivate = !(options?.isAuthenticated ?? false);
|
|
345
375
|
|
|
346
376
|
// Count + list are independent — run in parallel
|
|
377
|
+
const lang = viewLangFilter(c);
|
|
378
|
+
|
|
347
379
|
const [totalCount, posts] = await Promise.all([
|
|
348
380
|
c.var.services.posts.count({
|
|
349
381
|
status: "published",
|
|
350
382
|
excludeReplies: true,
|
|
351
383
|
excludeLatestHidden: true,
|
|
352
384
|
excludePrivate,
|
|
385
|
+
lang,
|
|
353
386
|
}),
|
|
354
387
|
c.var.services.posts.list({
|
|
355
388
|
status: "published",
|
|
356
389
|
excludeReplies: true,
|
|
357
390
|
excludeLatestHidden: true,
|
|
358
391
|
excludePrivate,
|
|
392
|
+
lang,
|
|
359
393
|
limit: pageSize,
|
|
360
394
|
offset,
|
|
361
395
|
}),
|
|
@@ -420,14 +454,18 @@ export async function assembleFeaturedTimeline(
|
|
|
420
454
|
const offset = (page - 1) * pageSize;
|
|
421
455
|
const excludePrivate = !(options?.isAuthenticated ?? false);
|
|
422
456
|
|
|
457
|
+
const lang = viewLangFilter(c);
|
|
458
|
+
|
|
423
459
|
const [totalCount, rootIds] = await Promise.all([
|
|
424
460
|
c.var.services.posts.countFeaturedThreadRoots({
|
|
425
461
|
status: "published",
|
|
426
462
|
excludePrivate,
|
|
463
|
+
lang,
|
|
427
464
|
}),
|
|
428
465
|
c.var.services.posts.listFeaturedThreadRootIds({
|
|
429
466
|
status: "published",
|
|
430
467
|
excludePrivate,
|
|
468
|
+
lang,
|
|
431
469
|
limit: pageSize,
|
|
432
470
|
offset,
|
|
433
471
|
}),
|
|
@@ -482,6 +520,7 @@ export async function assembleCollectionTimeline(
|
|
|
482
520
|
const page = Math.max(1, options.page ?? 1);
|
|
483
521
|
const offset = (page - 1) * pageSize;
|
|
484
522
|
const excludePrivate = !(options.isAuthenticated ?? false);
|
|
523
|
+
const lang = viewLangFilter(c);
|
|
485
524
|
|
|
486
525
|
const [totalCount, rootIds] = await Promise.all([
|
|
487
526
|
c.var.services.posts.countCollectionThreadRootsForCollections(
|
|
@@ -489,6 +528,7 @@ export async function assembleCollectionTimeline(
|
|
|
489
528
|
{
|
|
490
529
|
status: "published",
|
|
491
530
|
excludePrivate,
|
|
531
|
+
lang,
|
|
492
532
|
},
|
|
493
533
|
),
|
|
494
534
|
c.var.services.posts.listCollectionThreadRootIdsForCollections(
|
|
@@ -496,6 +536,7 @@ export async function assembleCollectionTimeline(
|
|
|
496
536
|
{
|
|
497
537
|
status: "published",
|
|
498
538
|
excludePrivate,
|
|
539
|
+
lang,
|
|
499
540
|
sortOrder: options.sortOrder,
|
|
500
541
|
limit: pageSize,
|
|
501
542
|
offset,
|
package/src/lib/tiptap-render.ts
CHANGED
|
@@ -48,8 +48,7 @@ export interface TiptapRenderOptions {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export type TiptapRenderResult =
|
|
51
|
-
|
|
52
|
-
| { ok: false; error: string };
|
|
51
|
+
{ ok: true; html: string } | { ok: false; error: string };
|
|
53
52
|
|
|
54
53
|
interface FootnoteReferencePlan {
|
|
55
54
|
definitionId: string;
|
package/src/lib/upload.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Shared file validation and storage key generation for upload routes.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { MediaKind } from "../types/constants.js";
|
|
7
|
+
import type { ContentDisposition, MediaKind } from "../types/constants.js";
|
|
8
8
|
import { createEntityId } from "./ids.js";
|
|
9
9
|
|
|
10
10
|
const MEDIA_ROOT_PREFIX = "media";
|
|
@@ -161,16 +161,6 @@ const MIME_CATEGORY_MAP = new Map<string, MediaCategory>([
|
|
|
161
161
|
...CODE_MIME_TYPES.map((t) => [t, "code" as const] as const),
|
|
162
162
|
]);
|
|
163
163
|
|
|
164
|
-
/**
|
|
165
|
-
* Accept string for file inputs. Accepts all file types.
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* ```ts
|
|
169
|
-
* <input type="file" accept={UPLOAD_ACCEPT} />
|
|
170
|
-
* ```
|
|
171
|
-
*/
|
|
172
|
-
export const UPLOAD_ACCEPT = "*/*";
|
|
173
|
-
|
|
174
164
|
export type MediaCategory =
|
|
175
165
|
| "image"
|
|
176
166
|
| "video"
|
|
@@ -183,7 +173,7 @@ export type MediaCategory =
|
|
|
183
173
|
| "3d"
|
|
184
174
|
| "code";
|
|
185
175
|
|
|
186
|
-
export type UploadContentDisposition =
|
|
176
|
+
export type UploadContentDisposition = ContentDisposition;
|
|
187
177
|
|
|
188
178
|
export interface StoredUploadPolicy {
|
|
189
179
|
contentDisposition: UploadContentDisposition;
|
|
@@ -326,10 +316,22 @@ export function imageExtensionForMimeType(contentType: string): string | null {
|
|
|
326
316
|
return IMAGE_MIME_EXTENSIONS[contentType] ?? null;
|
|
327
317
|
}
|
|
328
318
|
|
|
319
|
+
/** ISO BMFF `ftyp` major brands that mean a HEIC/HEIF still image. */
|
|
320
|
+
const HEIC_FTYP_BRANDS = new Set([
|
|
321
|
+
"heic",
|
|
322
|
+
"heix",
|
|
323
|
+
"hevc",
|
|
324
|
+
"hevx",
|
|
325
|
+
"mif1",
|
|
326
|
+
"msf1",
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
329
|
/**
|
|
330
330
|
* Identify an image format from its leading bytes (magic numbers), so a remote
|
|
331
331
|
* sideload can trust the actual content rather than the server's content-type
|
|
332
|
-
* header. Recognizes every {@link isAllowedSideloadImageType} format
|
|
332
|
+
* header. Recognizes every {@link isAllowedSideloadImageType} format, plus
|
|
333
|
+
* HEIC/HEIF — which no browser decodes natively and no sideload accepts, but
|
|
334
|
+
* which the composer converts before upload once it knows what it holds.
|
|
333
335
|
*
|
|
334
336
|
* @param bytes - The leading bytes of the file (≥ ~256 bytes recommended)
|
|
335
337
|
* @returns The detected image MIME type, or null if unrecognized
|
|
@@ -367,6 +369,7 @@ export function sniffImageMimeType(bytes: Uint8Array): string | null {
|
|
|
367
369
|
if (bytes.length >= 12 && readAscii(bytes, 4, 4) === "ftyp") {
|
|
368
370
|
const brand = readAscii(bytes, 8, 4);
|
|
369
371
|
if (brand === "avif" || brand === "avis") return "image/avif";
|
|
372
|
+
if (HEIC_FTYP_BRANDS.has(brand)) return "image/heic";
|
|
370
373
|
}
|
|
371
374
|
if (bytes.length >= 2 && bytes[0] === 0x42 && bytes[1] === 0x4d) {
|
|
372
375
|
return "image/bmp";
|