@jant/core 0.6.16 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/bin/commands/assets/upload.js +192 -15
- package/bin/commands/export.js +3 -1
- package/bin/commands/import-site.js +749 -220
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/setup.js +136 -0
- package/bin/commands/site/export.js +71 -34
- package/bin/commands/site/pull-media.js +2 -6
- package/bin/commands/site/snapshot/export.js +19 -61
- package/bin/commands/site/snapshot/import.js +23 -24
- package/bin/lib/d1-query.js +92 -18
- package/bin/lib/hugo-markdown.js +4 -0
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-pull-media.js +21 -28
- package/bin/lib/site-selection.js +94 -29
- package/bin/lib/site-snapshot.js +338 -3
- package/bin/lib/sql-export.js +68 -5
- package/bin/lib/wrangler-cli.js +37 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/bin/lib/zip-archive.js +187 -0
- package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
- package/dist/client/.vite/manifest.json +255 -90
- package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
- package/dist/client/_assets/chunks/create-editor-B-m7X7S5.js +261 -0
- package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
- package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
- package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
- package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
- package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
- package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
- package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
- package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
- package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
- package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
- package/dist/client/_assets/chunks/sortable-list-BJyd-LXE.js +2 -0
- package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
- package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
- package/dist/client/_assets/chunks/unsafe-svg-BkscJx69.js +2 -0
- package/dist/client/_assets/client-DAhoqPdr.css +2 -0
- package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
- package/dist/client/_assets/client-knSEyUJO.js +295 -0
- package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
- package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
- package/dist/github-sync-Gw4orAHk.js +8398 -0
- package/dist/index.js +2 -5
- package/dist/node.js +11 -9
- package/package.json +15 -5
- package/src/__tests__/auth-session-freshness.test.ts +57 -0
- package/src/__tests__/basecoat-variants.test.ts +121 -0
- package/src/__tests__/bin/assets-upload.test.ts +68 -0
- package/src/__tests__/dev-scripts.test.ts +203 -0
- package/src/__tests__/export-collection-order.test.ts +276 -0
- package/src/__tests__/export-feed-ids.test.ts +184 -0
- package/src/__tests__/export-feed-order.test.ts +294 -0
- package/src/__tests__/export-hugo-build.test.ts +390 -0
- package/src/__tests__/export-import-roundtrip.test.ts +94 -0
- package/src/__tests__/export-service.test.ts +842 -28
- package/src/__tests__/export-smart-collection.test.ts +329 -0
- package/src/__tests__/helpers/component-css.ts +91 -0
- package/src/__tests__/helpers/db.ts +33 -0
- package/src/__tests__/helpers/export-fixtures.ts +3 -0
- package/src/__tests__/helpers/hugo-site.ts +146 -0
- package/src/__tests__/import-site-command.test.ts +487 -1
- package/src/__tests__/mise-config.test.ts +75 -4
- package/src/__tests__/node-dev-tasks.test.ts +264 -0
- package/src/__tests__/site-export-canonical-import.test.ts +149 -0
- package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
- package/src/__tests__/snapshot-settings.test.ts +97 -0
- package/src/__tests__/snapshot-tables.test.ts +219 -0
- package/src/__tests__/sql-export.test.ts +173 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/__tests__/zip-archive.test.ts +106 -0
- package/src/app.tsx +86 -22
- package/src/auth.ts +18 -1
- package/src/client/__tests__/collection-page-actions.test.ts +51 -2
- package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
- package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
- package/src/client/__tests__/compose-bridge.test.ts +272 -0
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
- package/src/client/__tests__/compose-triggers.test.ts +90 -0
- package/src/client/__tests__/copy-field.test.ts +162 -0
- package/src/client/__tests__/custom-url-menu.test.ts +127 -6
- package/src/client/__tests__/image-processor.test.ts +324 -2
- package/src/client/__tests__/lazy-entries.test.ts +92 -0
- package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
- package/src/client/__tests__/runtime-paths.test.ts +84 -0
- package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
- package/src/client/__tests__/toast.test.ts +23 -0
- package/src/client/__tests__/video-processor-probe.test.ts +175 -0
- package/src/client/__tests__/video-processor.test.ts +181 -0
- package/src/client/audio-processor.ts +24 -15
- package/src/client/audio-waveform.ts +49 -0
- package/src/client/collection-dialog-host.ts +72 -0
- package/src/client/collection-navigation.ts +51 -16
- package/src/client/collection-page-actions.ts +26 -0
- package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
- package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
- package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
- package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
- package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
- package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
- package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
- package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
- package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
- package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
- package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
- package/src/client/components/collection-dialog-types.ts +49 -0
- package/src/client/components/collection-manager-types.ts +32 -1
- package/src/client/components/collection-types.ts +1 -15
- package/src/client/components/compose-types.ts +75 -1
- package/src/client/components/jant-collection-dialog.ts +722 -0
- package/src/client/components/jant-collection-directory.ts +567 -182
- package/src/client/components/jant-collection-form.ts +53 -232
- package/src/client/components/jant-command-palette.ts +100 -49
- package/src/client/components/jant-compose-dialog.ts +1486 -707
- package/src/client/components/jant-compose-editor.ts +673 -336
- package/src/client/components/jant-compose-fullscreen.ts +51 -43
- package/src/client/components/jant-config-editor.ts +65 -53
- package/src/client/components/jant-confirm-dialog.ts +13 -11
- package/src/client/components/jant-locale-picker.ts +418 -0
- package/src/client/components/jant-media-lightbox.ts +146 -136
- package/src/client/components/jant-nav-manager.ts +690 -366
- package/src/client/components/jant-post-menu.ts +1086 -282
- package/src/client/components/jant-post-picker.ts +324 -0
- package/src/client/components/jant-repo-picker-types.ts +6 -1
- package/src/client/components/jant-repo-picker.ts +137 -108
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +622 -543
- package/src/client/components/jant-settings-language.ts +1087 -0
- package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
- package/src/client/components/jant-text-preview.ts +94 -86
- package/src/client/components/nav-manager-types.ts +62 -1
- package/src/client/components/settings-types.ts +14 -29
- package/src/client/components/smart-collection-conditions.ts +285 -0
- package/src/client/components/smart-collection-dialog-types.ts +68 -0
- package/src/client/compose-bridge.ts +214 -141
- package/src/client/compose-launch.ts +76 -2
- package/src/client/compose-shortcuts.ts +6 -8
- package/src/client/compose-triggers.ts +50 -0
- package/src/client/copy-field.ts +108 -0
- package/src/client/custom-url-menu.ts +60 -4
- package/src/client/icons.ts +14 -0
- package/src/client/image-plan.ts +165 -0
- package/src/client/image-processor.ts +195 -117
- package/src/client/image-worker-client.ts +45 -0
- package/src/client/image-worker.ts +135 -0
- package/src/client/lazy-entries.ts +95 -0
- package/src/client/lazy-slugify.ts +2 -2
- package/src/client/media-scroll-hint.ts +43 -2
- package/src/client/mediabunny.ts +29 -0
- package/src/client/post-picker.ts +41 -0
- package/src/client/runtime-paths.ts +74 -1
- package/src/client/settings-bridge.ts +4 -10
- package/src/client/site-header-nav.js +18 -6
- package/src/client/smart-collection-dialog-host.ts +116 -0
- package/src/client/smart-collection-page-actions.ts +208 -0
- package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
- package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
- package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
- package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/extensions.ts +0 -3
- package/src/client/tiptap/floating-position.ts +216 -2
- package/src/client/tiptap/inline-image-upload.ts +7 -3
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/tiptap/structural-keymap.ts +158 -47
- package/src/client/toast.ts +13 -0
- package/src/client/types/vite-worker.d.ts +12 -0
- package/src/client/upload-with-metadata.ts +3 -13
- package/src/client/video-processor.ts +351 -107
- package/src/client-auth.ts +14 -31
- package/src/client-compose.ts +19 -0
- package/src/client-manage.ts +13 -0
- package/src/client-settings.ts +14 -0
- package/src/client-site.ts +10 -3
- package/src/client.ts +4 -0
- package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
- package/src/db/__tests__/d1-query.test.ts +56 -1
- package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
- package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0032_married_tomas.sql +3 -0
- package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
- package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
- package/src/db/migrations/meta/0032_snapshot.json +2582 -0
- package/src/db/migrations/meta/0033_snapshot.json +2816 -0
- package/src/db/migrations/meta/0034_snapshot.json +2816 -0
- package/src/db/migrations/meta/_journal.json +21 -0
- package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
- package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
- package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
- package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
- package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/_journal.json +21 -0
- package/src/db/pg/schema.ts +195 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +222 -27
- package/src/i18n/__tests__/detect.test.ts +26 -81
- package/src/i18n/__tests__/supported-locales.test.ts +83 -0
- package/src/i18n/context.tsx +12 -2
- package/src/i18n/detect.ts +7 -66
- package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
- package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
- package/src/i18n/locales/public/en.po +490 -158
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +487 -155
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +487 -155
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +555 -73
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +601 -119
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +614 -132
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +94 -0
- package/src/i18n/middleware.ts +12 -2
- package/src/i18n/supported-locales.ts +80 -0
- package/src/lib/__tests__/api-settings.test.ts +11 -2
- package/src/lib/__tests__/asset-path.test.ts +92 -0
- package/src/lib/__tests__/deferred.test.ts +90 -0
- package/src/lib/__tests__/discover-ping.test.ts +89 -0
- package/src/lib/__tests__/discover.test.ts +410 -0
- package/src/lib/__tests__/feed-policy.test.ts +26 -0
- package/src/lib/__tests__/feed.test.ts +1526 -15
- package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
- package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
- package/src/lib/__tests__/hosted-signin.test.ts +1 -1
- package/src/lib/__tests__/http-cache.test.ts +162 -0
- package/src/lib/__tests__/image.test.ts +27 -1
- package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
- package/src/lib/__tests__/jant-docs.test.ts +23 -0
- package/src/lib/__tests__/lang-detect.test.ts +231 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
- package/src/lib/__tests__/markdown.test.ts +10 -0
- package/src/lib/__tests__/media-helpers.test.ts +81 -0
- package/src/lib/__tests__/navigation.test.ts +135 -1
- package/src/lib/__tests__/resolve-config.test.ts +131 -3
- package/src/lib/__tests__/schemas.test.ts +69 -5
- package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
- package/src/lib/__tests__/slug.test.ts +70 -0
- package/src/lib/__tests__/slugify.test.ts +85 -0
- package/src/lib/__tests__/storage.test.ts +32 -0
- package/src/lib/__tests__/thread-fold.test.ts +113 -0
- package/src/lib/__tests__/timeline.test.ts +87 -0
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
- package/src/lib/__tests__/upload-sideload.test.ts +14 -0
- package/src/lib/__tests__/url-fetch.test.ts +1 -2
- package/src/lib/__tests__/url.test.ts +78 -72
- package/src/lib/__tests__/view-language.test.ts +413 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
- package/src/lib/address.ts +57 -0
- package/src/lib/api-posts.ts +12 -0
- package/src/lib/asset-path.ts +98 -1
- package/src/lib/build-env.ts +16 -0
- package/src/lib/collection-groups.ts +5 -1
- package/src/lib/collection-paths.ts +3 -10
- package/src/lib/constants.ts +28 -3
- package/src/lib/copy-field.ts +36 -0
- package/src/lib/crypto.ts +40 -0
- package/src/lib/csp-builder.ts +2 -1
- package/src/lib/deferred.ts +79 -0
- package/src/lib/discover-ping.ts +141 -0
- package/src/lib/discover.ts +439 -0
- package/src/lib/env.ts +134 -0
- package/src/lib/errors.ts +33 -0
- package/src/lib/feed-policy.ts +236 -2
- package/src/lib/feed.ts +926 -73
- package/src/lib/filter-dimensions.ts +1349 -0
- package/src/lib/github-sync-repo-name.ts +45 -0
- package/src/lib/github-sync-site-config.ts +3 -0
- package/src/lib/hosted-signin.ts +1 -1
- package/src/lib/http-cache.ts +148 -0
- package/src/lib/hugo-markdown.ts +62 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/image.ts +17 -4
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/markdown-manager.ts +392 -2
- package/src/lib/media-helpers.ts +13 -1
- package/src/lib/navigation.ts +78 -11
- package/src/lib/pagination.ts +28 -24
- package/src/lib/per-language-surfaces.ts +67 -0
- package/src/lib/post-body-html.ts +11 -4
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +111 -6
- package/src/lib/schemas.ts +251 -15
- package/src/lib/site-header-fragment.tsx +2 -1
- package/src/lib/site-unavailable-page.ts +47 -0
- package/src/lib/slug.ts +51 -7
- package/src/lib/slugify.ts +113 -0
- package/src/lib/startup-config.ts +4 -6
- package/src/lib/storage.ts +7 -0
- package/src/lib/summary.ts +50 -0
- package/src/lib/thread-fold.ts +161 -0
- package/src/lib/time.ts +32 -0
- package/src/lib/timeline.ts +43 -2
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/tiptap-to-markdown.ts +13 -7
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +159 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +53 -25
- package/src/lib/viewer-context.ts +62 -0
- package/src/middleware/__tests__/auth.test.ts +70 -2
- package/src/middleware/__tests__/cache-control.test.ts +71 -3
- package/src/middleware/__tests__/onboarding.test.ts +76 -5
- package/src/middleware/__tests__/secure-headers.test.ts +1 -1
- package/src/middleware/__tests__/session.test.ts +181 -33
- package/src/middleware/auth.ts +50 -31
- package/src/middleware/cache-control.ts +75 -10
- package/src/middleware/config.ts +6 -6
- package/src/middleware/error-handler.ts +12 -0
- package/src/middleware/onboarding.ts +32 -10
- package/src/middleware/secure-headers.ts +1 -1
- package/src/middleware/session.ts +96 -8
- package/src/node/__tests__/cli-migrate.test.ts +21 -19
- package/src/node/__tests__/cli-reset-password.test.ts +81 -8
- package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
- package/src/node/__tests__/cli-setup.test.ts +163 -0
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
- package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
- package/src/node/__tests__/runtime.test.ts +75 -0
- package/src/node/index.ts +3 -0
- package/src/node/request-handler.ts +3 -1
- package/src/preset.css +76 -22
- package/src/routes/__tests__/feed-validator.test.ts +156 -0
- package/src/routes/api/__tests__/collections.test.ts +114 -0
- package/src/routes/api/__tests__/discover.test.ts +222 -0
- package/src/routes/api/__tests__/nav-items.test.ts +212 -2
- package/src/routes/api/__tests__/post-translations.test.ts +829 -0
- package/src/routes/api/__tests__/posts.test.ts +24 -0
- package/src/routes/api/__tests__/settings.test.ts +24 -3
- package/src/routes/api/__tests__/telegram.test.ts +6 -5
- package/src/routes/api/__tests__/upload.test.ts +34 -0
- package/src/routes/api/collections.ts +49 -6
- package/src/routes/api/custom-urls.ts +0 -1
- package/src/routes/api/discover.ts +95 -0
- package/src/routes/api/export.ts +6 -3
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/internal/sites.ts +0 -1
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +161 -0
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +44 -7
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/upload.ts +10 -3
- package/src/routes/api/uploads.ts +1 -2
- package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
- package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
- package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
- package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
- package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
- package/src/routes/auth/__tests__/setup.test.ts +278 -26
- package/src/routes/auth/__tests__/signin.test.ts +135 -0
- package/src/routes/auth/setup.tsx +934 -186
- package/src/routes/auth/signin.tsx +10 -8
- package/src/routes/compose.tsx +9 -0
- package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
- package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
- package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
- package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
- package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
- package/src/routes/dash/custom-urls.tsx +1 -37
- package/src/routes/dash/settings.tsx +714 -111
- package/src/routes/discover-announce.ts +96 -0
- package/src/routes/feed/__tests__/feed.test.ts +165 -2
- package/src/routes/feed/feed.ts +69 -40
- package/src/routes/feed/sitemap.ts +95 -11
- package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
- package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
- package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
- package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
- package/src/routes/pages/__tests__/collections.test.ts +9 -3
- package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
- package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
- package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
- package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
- package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
- package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
- package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
- package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
- package/src/routes/pages/archive.tsx +487 -379
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +43 -18
- package/src/routes/pages/home.tsx +25 -6
- package/src/routes/pages/language.tsx +113 -0
- package/src/routes/pages/latest.tsx +26 -16
- package/src/routes/pages/new.tsx +3 -0
- package/src/routes/pages/page.tsx +228 -45
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +22 -5
- package/src/routes/pages/smart-collection.tsx +340 -0
- package/src/routes/pages/subscribe.tsx +182 -0
- package/src/runtime/__tests__/index.test.ts +19 -0
- package/src/runtime/__tests__/node.test.ts +129 -4
- package/src/runtime/__tests__/readiness.test.ts +23 -0
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +67 -1
- package/src/runtime/readiness.ts +15 -0
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/custom-url.test.ts +34 -0
- package/src/services/__tests__/github-app-installations.test.ts +153 -0
- package/src/services/__tests__/github-sync-push.test.ts +46 -0
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/media.test.ts +31 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +236 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post-timeline.test.ts +127 -0
- package/src/services/__tests__/post.test.ts +120 -9
- package/src/services/__tests__/settings.test.ts +228 -17
- package/src/services/__tests__/site-admin.test.ts +5 -5
- package/src/services/__tests__/smart-collection.test.ts +651 -0
- package/src/services/about-page.ts +22 -1
- package/src/services/auth.ts +51 -49
- package/src/services/bootstrap.ts +408 -39
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +29 -24
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +123 -123
- package/src/services/export-theme/layouts/_default/alias.html +27 -1
- package/src/services/export-theme/layouts/_default/list.html +2 -63
- package/src/services/export-theme/layouts/_default/rss.xml +103 -38
- package/src/services/export-theme/layouts/collections/list.html +3 -3
- package/src/services/export-theme/layouts/featured/list.html +1 -4
- package/src/services/export-theme/layouts/index.html +40 -26
- package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
- package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
- package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
- package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/head.html +1 -1
- package/src/services/export-theme/layouts/partials/header.html +5 -3
- package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
- package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
- package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
- package/src/services/export-theme/layouts/partials/pagination.html +14 -13
- package/src/services/export-theme/layouts/partials/post-card.html +8 -4
- package/src/services/export-theme/layouts/partials/reply.html +2 -1
- package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
- package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
- package/src/services/export-theme/layouts/post/list.html +1 -1
- package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
- package/src/services/export-theme/styles/main.css +13 -2
- package/src/services/export-theme/theme.toml +1 -1
- package/src/services/export.ts +880 -72
- package/src/services/github-app-installations.ts +171 -4
- package/src/services/github-sync.ts +69 -15
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +22 -2
- package/src/services/media.ts +22 -7
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +254 -6
- package/src/services/post.ts +1234 -190
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +27 -14
- package/src/services/site.ts +160 -32
- package/src/services/smart-collection.ts +636 -0
- package/src/services/telegram.ts +20 -22
- package/src/services/upload-session.ts +1 -3
- package/src/style-author.css +27 -0
- package/src/styles/components-author.css +1622 -0
- package/src/styles/components.css +24 -1571
- package/src/styles/site-media.css +16 -2
- package/src/styles/tokens.css +82 -12
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1090 -7216
- package/src/types/app-context.ts +23 -0
- package/src/types/bindings.ts +8 -0
- package/src/types/config.ts +94 -15
- package/src/types/constants.ts +188 -16
- package/src/types/entities.ts +87 -1
- package/src/types/operations.ts +74 -2
- package/src/types/props.ts +157 -31
- package/src/types/views.ts +55 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +201 -7
- package/src/ui/compose/ComposePrompt.tsx +1 -5
- package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
- package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
- package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
- package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
- package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
- package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
- package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
- package/src/ui/dash/settings/GeneralContent.tsx +277 -113
- package/src/ui/dash/settings/LanguageContent.tsx +409 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
- package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
- package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
- package/src/ui/dash/settings/discover-copy.ts +116 -0
- package/src/ui/feed/LinkCard.tsx +55 -45
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +90 -43
- package/src/ui/feed/QuoteCard.tsx +55 -30
- package/src/ui/feed/ThreadPreview.tsx +6 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
- package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
- package/src/ui/feed/thread-preview-state.ts +20 -6
- package/src/ui/font-themes.ts +39 -29
- package/src/ui/layouts/BaseLayout.tsx +155 -10
- package/src/ui/layouts/SiteLayout.tsx +192 -34
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
- package/src/ui/pages/ArchivePage.tsx +282 -289
- package/src/ui/pages/CollectionPage.tsx +86 -76
- package/src/ui/pages/CollectionsPage.tsx +5 -0
- package/src/ui/pages/ComposePage.tsx +8 -0
- package/src/ui/pages/PostPage.tsx +47 -0
- package/src/ui/pages/SearchPage.tsx +3 -3
- package/src/ui/pages/SmartCollectionPage.tsx +434 -0
- package/src/ui/pages/SubscribePage.tsx +236 -0
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
- package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
- package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
- package/src/ui/shared/CollectionDirectory.tsx +178 -3
- package/src/ui/shared/CollectionsManager.tsx +70 -17
- package/src/ui/shared/CopyField.tsx +127 -0
- package/src/ui/shared/EmptyPostContent.tsx +23 -0
- package/src/ui/shared/MediaGallery.tsx +125 -46
- package/src/ui/shared/Pagination.tsx +20 -5
- package/src/ui/shared/PostFooter.tsx +20 -11
- package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
- package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
- package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
- package/src/ui/shared/__tests__/pagination.test.ts +38 -24
- package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
- package/src/ui/shared/archive-labels.ts +98 -0
- package/src/ui/shared/collection-management-labels.ts +94 -16
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +2 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-C75Zc8dD.js +0 -6
- package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
- package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
- package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
- package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
- package/dist/client/_assets/client-D3VZyG4L.css +0 -2
- package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
- package/dist/env-BPYViDJJ.js +0 -281
- package/dist/export-Dl5KCiEs.js +0 -5562
- package/dist/github-api-BNF35Lkx.js +0 -176
- package/dist/github-app-DdiD-bC4.js +0 -275
- package/dist/github-sync-Bqg62IvV.js +0 -4
- package/dist/github-sync-HdK2EgvJ.js +0 -431
- package/dist/url-CbLAtlZe.js +0 -900
- package/src/client/__tests__/collection-created-notice.test.ts +0 -36
- package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
- package/src/client/__tests__/post-form-bridge.test.ts +0 -82
- package/src/client/collection-created-notice.ts +0 -66
- package/src/client/collection-form-bridge.ts +0 -191
- package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
- package/src/client/components/jant-post-form.ts +0 -422
- package/src/client/components/post-form-template.ts +0 -352
- package/src/client/components/post-form-types.ts +0 -96
- package/src/client/media-metadata.ts +0 -247
- package/src/client/post-form-bridge.ts +0 -193
- package/src/client/tiptap/exitable-marks.ts +0 -73
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* Client-side Video Processor
|
|
3
3
|
*
|
|
4
4
|
* Processes videos before upload using mediabunny:
|
|
5
|
-
* -
|
|
5
|
+
* - Remuxes to MP4, transcoding to H.264/AAC only when the source is not
|
|
6
|
+
* already in those codecs or does not fit the size caps. A source that
|
|
7
|
+
* needs nothing has its encoded packets copied straight across, so it
|
|
8
|
+
* reaches the server without a generation of re-encoding loss
|
|
6
9
|
* - Resizes to max 1920px long edge / 1080px short edge
|
|
7
10
|
* - Strips spurious rotation metadata from the output (mediabunny may
|
|
8
11
|
* bake rotation into pixels AND write a display matrix, causing the
|
|
@@ -14,17 +17,8 @@
|
|
|
14
17
|
* Requires WebCodecs API support — check `isSupported()` before use.
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
Output,
|
|
20
|
-
Mp4OutputFormat,
|
|
21
|
-
BufferTarget,
|
|
22
|
-
BlobSource,
|
|
23
|
-
CanvasSink,
|
|
24
|
-
Conversion,
|
|
25
|
-
QUALITY_HIGH,
|
|
26
|
-
ALL_FORMATS,
|
|
27
|
-
} from "mediabunny";
|
|
20
|
+
import type { VideoCodec } from "mediabunny";
|
|
21
|
+
import { loadMediabunny } from "./mediabunny.js";
|
|
28
22
|
import { encode } from "blurhash";
|
|
29
23
|
import { normalizeDurationSeconds } from "../lib/video-playback.js";
|
|
30
24
|
import { zeroTrackAlternateGroups } from "../lib/mp4-track-flags.js";
|
|
@@ -36,6 +30,66 @@ const MAX_SHORT_EDGE = 1080;
|
|
|
36
30
|
const POSTER_WIDTH = 640;
|
|
37
31
|
const BLURHASH_SIZE = 32;
|
|
38
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Bitrate targeted for a 1080p H.264 re-encode; every other size scales from
|
|
35
|
+
* here. Around 8 Mbps — enough to hold up against the source on a phone or a
|
|
36
|
+
* laptop, low enough that a clip stays servable over the web.
|
|
37
|
+
*
|
|
38
|
+
* This is expressed as a bitrate on purpose. Given a quality *level*,
|
|
39
|
+
* mediabunny prefers quantizer-based encoding, which has no size ceiling at
|
|
40
|
+
* all: a frugally-encoded source gets faithfully reproduced — its own
|
|
41
|
+
* compression artifacts included — at several times its original size. A
|
|
42
|
+
* bitrate keeps the output predictable.
|
|
43
|
+
*/
|
|
44
|
+
const REFERENCE_BITRATE = 7_900_000;
|
|
45
|
+
const REFERENCE_PIXELS = 1920 * 1080;
|
|
46
|
+
|
|
47
|
+
/** Bitrate scales slightly sub-linearly with pixel count. */
|
|
48
|
+
const PIXEL_SCALE_EXPONENT = 0.95;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Bits H.264 needs to carry one bit of the source codec at the same quality.
|
|
52
|
+
* H.264 is the baseline; HEVC and VP9 fit roughly 40% more picture into a
|
|
53
|
+
* bit, AV1 around 60%. Used to cap the target at what the source content
|
|
54
|
+
* actually carries — spending more than that reproduces the source's
|
|
55
|
+
* artifacts in high fidelity and buys nothing, because the detail was never
|
|
56
|
+
* there to begin with.
|
|
57
|
+
*/
|
|
58
|
+
const CODEC_BITRATE_RATIO: Partial<Record<VideoCodec, number>> = {
|
|
59
|
+
avc: 1,
|
|
60
|
+
hevc: 1 / 0.6,
|
|
61
|
+
vp9: 1 / 0.6,
|
|
62
|
+
av1: 1 / 0.4,
|
|
63
|
+
vp8: 1 / 1.2,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Pick the bitrate for a re-encode: the size-appropriate target, but never
|
|
68
|
+
* more than the source content itself warrants.
|
|
69
|
+
*/
|
|
70
|
+
function reencodeBitrate(
|
|
71
|
+
source: {
|
|
72
|
+
pixels: number;
|
|
73
|
+
bitrate: number | undefined;
|
|
74
|
+
codec: VideoCodec | null | undefined;
|
|
75
|
+
},
|
|
76
|
+
targetPixels: number,
|
|
77
|
+
): number {
|
|
78
|
+
const target =
|
|
79
|
+
REFERENCE_BITRATE *
|
|
80
|
+
(targetPixels / REFERENCE_PIXELS) ** PIXEL_SCALE_EXPONENT;
|
|
81
|
+
|
|
82
|
+
if (!source.bitrate || !source.pixels) return Math.round(target);
|
|
83
|
+
|
|
84
|
+
const ratio = (source.codec && CODEC_BITRATE_RATIO[source.codec]) ?? 1;
|
|
85
|
+
const sourceCap =
|
|
86
|
+
source.bitrate *
|
|
87
|
+
ratio *
|
|
88
|
+
(targetPixels / source.pixels) ** PIXEL_SCALE_EXPONENT;
|
|
89
|
+
|
|
90
|
+
return Math.round(Math.min(target, sourceCap));
|
|
91
|
+
}
|
|
92
|
+
|
|
39
93
|
export interface VideoProcessResult {
|
|
40
94
|
file: File;
|
|
41
95
|
width: number;
|
|
@@ -54,102 +108,281 @@ function isSupported(): boolean {
|
|
|
54
108
|
return typeof VideoEncoder !== "undefined";
|
|
55
109
|
}
|
|
56
110
|
|
|
57
|
-
|
|
58
|
-
* Extract a poster frame, blurhash, and source dimensions from a video file.
|
|
59
|
-
* Seeks to `min(duration × 0.1, 1s)` and captures the frame.
|
|
60
|
-
* Also returns the original video dimensions so the caller can compute
|
|
61
|
-
* the correct output size without opening a second Input instance.
|
|
62
|
-
*
|
|
63
|
-
* @param file - Source video file
|
|
64
|
-
* @returns Poster blob (640px-wide WebP), blurhash string, and source dimensions
|
|
65
|
-
*/
|
|
66
|
-
async function extractPoster(file: File): Promise<{
|
|
111
|
+
interface SourceProbe {
|
|
67
112
|
poster?: Blob;
|
|
68
113
|
blurhash?: string;
|
|
69
114
|
sourceWidth?: number;
|
|
70
115
|
sourceHeight?: number;
|
|
71
116
|
rotation?: number;
|
|
72
117
|
durationSeconds?: number;
|
|
73
|
-
|
|
118
|
+
/** Source video codec, or `null` when it could not be determined. */
|
|
119
|
+
videoCodec?: VideoCodec | null;
|
|
120
|
+
/** Average bitrate of the source video track, in bits per second. */
|
|
121
|
+
videoBitrate?: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Report a probe step that failed. Every step here is optional — the upload
|
|
126
|
+
* proceeds without it — so the failure has to be visible somewhere, or a
|
|
127
|
+
* broken decoder reads as a video that merely has no thumbnail.
|
|
128
|
+
*/
|
|
129
|
+
function warnProbeFailure(step: string, error: unknown): void {
|
|
130
|
+
// eslint-disable-next-line no-console -- an optional step failing is silent by design; say so
|
|
131
|
+
console.warn(`[jant] video probe: ${step} failed`, error);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Run one optional probe step, returning `undefined` when it fails.
|
|
136
|
+
*
|
|
137
|
+
* Each step feeds a different part of the result, and none of them is worth
|
|
138
|
+
* the others: an unreadable bitrate should not cost the poster, and an
|
|
139
|
+
* undecodable frame should not cost the codec that decides whether the file
|
|
140
|
+
* needs re-encoding at all.
|
|
141
|
+
*/
|
|
142
|
+
async function optional<T>(
|
|
143
|
+
step: string,
|
|
144
|
+
run: () => Promise<T>,
|
|
145
|
+
): Promise<T | undefined> {
|
|
146
|
+
try {
|
|
147
|
+
return await run();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
warnProbeFailure(step, error);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Draw the captured frame into a canvas of the given size.
|
|
156
|
+
* Returns null when the browser refuses a 2D context.
|
|
157
|
+
*/
|
|
158
|
+
function drawFrame(
|
|
159
|
+
source: HTMLCanvasElement,
|
|
160
|
+
width: number,
|
|
161
|
+
height: number,
|
|
162
|
+
): { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D } | null {
|
|
163
|
+
const canvas = document.createElement("canvas");
|
|
164
|
+
canvas.width = width;
|
|
165
|
+
canvas.height = height;
|
|
166
|
+
const ctx = canvas.getContext("2d");
|
|
167
|
+
if (!ctx) return null;
|
|
168
|
+
ctx.drawImage(source, 0, 0, width, height);
|
|
169
|
+
return { canvas, ctx };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Probe a video file and extract its poster frame and blurhash in one pass.
|
|
174
|
+
* Seeks to `min(duration × 0.1, 1s)` and captures the frame.
|
|
175
|
+
*
|
|
176
|
+
* Returns the source dimensions, codec, and bitrate alongside, so the caller
|
|
177
|
+
* can decide whether a re-encode is needed at all — and at what bitrate —
|
|
178
|
+
* without opening a second Input instance.
|
|
179
|
+
*
|
|
180
|
+
* Every field is optional and probed independently. A file whose frames will
|
|
181
|
+
* not decode still reports its codec and duration; one whose bitrate cannot be
|
|
182
|
+
* read still yields a poster. Only the container being unreadable — no video
|
|
183
|
+
* track at all — returns nothing, because then there is nothing to report.
|
|
184
|
+
*
|
|
185
|
+
* @param file - Source video file
|
|
186
|
+
* @returns Poster blob (640px-wide WebP), blurhash, dimensions, codec, bitrate
|
|
187
|
+
*/
|
|
188
|
+
async function probeSource(file: File): Promise<SourceProbe> {
|
|
189
|
+
const { Input, BlobSource, ALL_FORMATS, CanvasSink } = await loadMediabunny();
|
|
74
190
|
const input = new Input({
|
|
75
191
|
source: new BlobSource(file),
|
|
76
192
|
formats: ALL_FORMATS,
|
|
77
193
|
});
|
|
78
194
|
try {
|
|
79
|
-
const videoTrack = await
|
|
195
|
+
const videoTrack = await optional("open video track", () =>
|
|
196
|
+
input.getPrimaryVideoTrack(),
|
|
197
|
+
);
|
|
80
198
|
if (!videoTrack) return {};
|
|
81
199
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
200
|
+
const videoCodec = await optional("read codec", () =>
|
|
201
|
+
videoTrack.getCodec(),
|
|
202
|
+
);
|
|
203
|
+
// Metadata-only scan of the sample table — milliseconds even on a large
|
|
204
|
+
// file, and exact, unlike dividing file size by duration. Only an input
|
|
205
|
+
// to the re-encode bitrate, so `undefined` degrades to the size-based
|
|
206
|
+
// target rather than failing the probe.
|
|
207
|
+
const videoBitrate = await optional(
|
|
208
|
+
"compute bitrate",
|
|
209
|
+
async () => (await videoTrack.computePacketStats()).averageBitrate,
|
|
210
|
+
);
|
|
211
|
+
const duration = await optional("compute duration", () =>
|
|
212
|
+
input.computeDuration(),
|
|
213
|
+
);
|
|
85
214
|
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
215
|
+
// Everything the caller needs to plan the conversion.
|
|
216
|
+
const base: SourceProbe = {
|
|
217
|
+
sourceWidth: videoTrack.displayWidth,
|
|
218
|
+
sourceHeight: videoTrack.displayHeight,
|
|
219
|
+
rotation: videoTrack.rotation,
|
|
220
|
+
durationSeconds:
|
|
221
|
+
duration === undefined ? undefined : normalizeDurationSeconds(duration),
|
|
222
|
+
videoCodec,
|
|
223
|
+
videoBitrate,
|
|
224
|
+
};
|
|
89
225
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
226
|
+
// A tenth of the way in, so the still is not the fade-from-black most
|
|
227
|
+
// clips open on. With no duration to take a tenth of, the opening frame
|
|
228
|
+
// is the only one we can name.
|
|
229
|
+
const seekTime = duration === undefined ? 0 : Math.min(duration * 0.1, 1);
|
|
230
|
+
const wrapped = await optional("capture frame", () =>
|
|
231
|
+
new CanvasSink(videoTrack).getCanvas(seekTime),
|
|
232
|
+
);
|
|
233
|
+
if (!wrapped) return base;
|
|
95
234
|
|
|
96
235
|
const canvas = wrapped.canvas as HTMLCanvasElement;
|
|
97
|
-
|
|
98
|
-
// Poster: 640px wide WebP
|
|
99
236
|
const srcW = canvas.width;
|
|
100
237
|
const srcH = canvas.height;
|
|
238
|
+
|
|
239
|
+
// Poster: 640px wide WebP
|
|
101
240
|
const posterScale = Math.min(POSTER_WIDTH / srcW, 1);
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
241
|
+
const posterFrame = drawFrame(
|
|
242
|
+
canvas,
|
|
243
|
+
Math.round(srcW * posterScale),
|
|
244
|
+
Math.round(srcH * posterScale),
|
|
245
|
+
);
|
|
246
|
+
const poster = posterFrame
|
|
247
|
+
? await optional(
|
|
248
|
+
"encode poster",
|
|
249
|
+
() =>
|
|
250
|
+
new Promise<Blob | undefined>((resolve) => {
|
|
251
|
+
posterFrame.canvas.toBlob(
|
|
252
|
+
(blob) => resolve(blob ?? undefined),
|
|
253
|
+
"image/webp",
|
|
254
|
+
0.8,
|
|
255
|
+
);
|
|
256
|
+
}),
|
|
257
|
+
)
|
|
258
|
+
: undefined;
|
|
119
259
|
|
|
120
260
|
// Blurhash: 32px canvas, 4×3 components
|
|
121
261
|
const bhScale = Math.min(BLURHASH_SIZE / srcW, BLURHASH_SIZE / srcH, 1);
|
|
122
262
|
const bw = Math.max(Math.round(srcW * bhScale), 1);
|
|
123
263
|
const bh = Math.max(Math.round(srcH * bhScale), 1);
|
|
264
|
+
const bhFrame = drawFrame(canvas, bw, bh);
|
|
265
|
+
const blurhash = bhFrame
|
|
266
|
+
? await optional("encode blurhash", async () =>
|
|
267
|
+
encode(bhFrame.ctx.getImageData(0, 0, bw, bh).data, bw, bh, 4, 3),
|
|
268
|
+
)
|
|
269
|
+
: undefined;
|
|
270
|
+
|
|
271
|
+
return { ...base, poster, blurhash };
|
|
272
|
+
} finally {
|
|
273
|
+
input.dispose();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
124
276
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
277
|
+
/** What a given source needs done to it before upload. */
|
|
278
|
+
export interface VideoProcessPlan {
|
|
279
|
+
/** True when the video must be scaled down to fit the size caps. */
|
|
280
|
+
needsResize: boolean;
|
|
281
|
+
/** Target dimensions — equal to the source dimensions when not resizing. */
|
|
282
|
+
width: number;
|
|
283
|
+
height: number;
|
|
284
|
+
/** True when the video track must be decoded and re-encoded. */
|
|
285
|
+
videoNeedsReencode: boolean;
|
|
286
|
+
/**
|
|
287
|
+
* Target video bitrate in bits per second, or `undefined` when the track
|
|
288
|
+
* is copied rather than re-encoded.
|
|
289
|
+
*/
|
|
290
|
+
videoBitrate?: number;
|
|
291
|
+
}
|
|
131
292
|
|
|
132
|
-
|
|
133
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Decide what a source video needs: a resize, a re-encode, or nothing at all.
|
|
295
|
+
*
|
|
296
|
+
* Asking for a size or a quality is what disables mediabunny's copy fast
|
|
297
|
+
* path, so anything already within the caps and already in H.264 must come
|
|
298
|
+
* back with `videoNeedsReencode` false — its encoded packets are then copied
|
|
299
|
+
* across untouched, sparing it a generation of quality. An unknown codec
|
|
300
|
+
* (`null`) counts as needing a re-encode, since we can't vouch for it playing
|
|
301
|
+
* everywhere.
|
|
302
|
+
*
|
|
303
|
+
* When a re-encode is unavoidable, the bitrate is the smaller of what the
|
|
304
|
+
* output size warrants and what the source content actually carries. The
|
|
305
|
+
* second half matters: an efficiently-encoded source re-encoded at constant
|
|
306
|
+
* quality can come out several times larger than it went in, all of those
|
|
307
|
+
* bits spent preserving its own compression artifacts.
|
|
308
|
+
*
|
|
309
|
+
* Dimensions are the source's *display* dimensions, i.e. post-rotation, so
|
|
310
|
+
* the caps apply orientation-agnostically.
|
|
311
|
+
*
|
|
312
|
+
* @param source - Display dimensions, codec, and bitrate of the source
|
|
313
|
+
* @param options - `maxLongEdge` and `maxShortEdge` caps
|
|
314
|
+
* @returns The processing plan
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* // Already H.264 and within the caps — copied, not re-encoded
|
|
319
|
+
* planVideoProcessing(
|
|
320
|
+
* { width: 1280, height: 720, videoCodec: "avc" },
|
|
321
|
+
* { maxLongEdge: 1920, maxShortEdge: 1080 },
|
|
322
|
+
* );
|
|
323
|
+
* // { needsResize: false, width: 1280, height: 720,
|
|
324
|
+
* // videoNeedsReencode: false, videoBitrate: undefined }
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
export function planVideoProcessing(
|
|
328
|
+
source: {
|
|
329
|
+
width?: number;
|
|
330
|
+
height?: number;
|
|
331
|
+
videoCodec?: VideoCodec | null;
|
|
332
|
+
videoBitrate?: number;
|
|
333
|
+
},
|
|
334
|
+
options: { maxLongEdge: number; maxShortEdge: number },
|
|
335
|
+
): VideoProcessPlan {
|
|
336
|
+
const { width: sourceWidth, height: sourceHeight } = source;
|
|
337
|
+
|
|
338
|
+
// Fall back to the caps when the probe could not read the dimensions;
|
|
339
|
+
// without a source size there is nothing to scale against.
|
|
340
|
+
let width = sourceWidth || options.maxLongEdge;
|
|
341
|
+
let height = sourceHeight || options.maxShortEdge;
|
|
342
|
+
let needsResize = false;
|
|
134
343
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
344
|
+
if (sourceWidth && sourceHeight) {
|
|
345
|
+
const longSide = Math.max(sourceWidth, sourceHeight);
|
|
346
|
+
const shortSide = Math.min(sourceWidth, sourceHeight);
|
|
347
|
+
const scale = Math.min(
|
|
348
|
+
options.maxLongEdge / longSide,
|
|
349
|
+
options.maxShortEdge / shortSide,
|
|
350
|
+
1,
|
|
351
|
+
);
|
|
352
|
+
needsResize = scale < 1;
|
|
353
|
+
if (needsResize) {
|
|
354
|
+
width = Math.round(sourceWidth * scale);
|
|
355
|
+
height = Math.round(sourceHeight * scale);
|
|
356
|
+
// H.264 requires even dimensions
|
|
357
|
+
width += width % 2;
|
|
358
|
+
height += height % 2;
|
|
359
|
+
}
|
|
147
360
|
}
|
|
361
|
+
|
|
362
|
+
const videoNeedsReencode = needsResize || source.videoCodec !== "avc";
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
needsResize,
|
|
366
|
+
width,
|
|
367
|
+
height,
|
|
368
|
+
videoNeedsReencode,
|
|
369
|
+
videoBitrate: videoNeedsReencode
|
|
370
|
+
? reencodeBitrate(
|
|
371
|
+
{
|
|
372
|
+
pixels: (sourceWidth ?? 0) * (sourceHeight ?? 0),
|
|
373
|
+
bitrate: source.videoBitrate,
|
|
374
|
+
codec: source.videoCodec,
|
|
375
|
+
},
|
|
376
|
+
width * height,
|
|
377
|
+
)
|
|
378
|
+
: undefined,
|
|
379
|
+
};
|
|
148
380
|
}
|
|
149
381
|
|
|
150
382
|
/**
|
|
151
|
-
* Process a video file:
|
|
152
|
-
* 1920×1080, and extract poster frame
|
|
383
|
+
* Process a video file: remux to MP4, re-encoding to H.264/AAC only when the
|
|
384
|
+
* source needs it, resize to fit within 1920×1080, and extract poster frame
|
|
385
|
+
* + blurhash.
|
|
153
386
|
*
|
|
154
387
|
* @param file - Source video file
|
|
155
388
|
* @param onProgress - Optional callback receiving progress from 0 to 1
|
|
@@ -159,8 +392,8 @@ async function processToFile(
|
|
|
159
392
|
file: File,
|
|
160
393
|
onProgress?: (progress: number) => void,
|
|
161
394
|
): Promise<VideoProcessResult> {
|
|
162
|
-
//
|
|
163
|
-
// so the
|
|
395
|
+
// Probe the source and grab poster + blurhash (separate Input instance,
|
|
396
|
+
// so the conversion Input below starts with clean demuxer state).
|
|
164
397
|
const {
|
|
165
398
|
poster,
|
|
166
399
|
blurhash,
|
|
@@ -168,28 +401,36 @@ async function processToFile(
|
|
|
168
401
|
sourceHeight,
|
|
169
402
|
rotation,
|
|
170
403
|
durationSeconds,
|
|
171
|
-
|
|
404
|
+
videoCodec,
|
|
405
|
+
videoBitrate,
|
|
406
|
+
} = await probeSource(file);
|
|
172
407
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
408
|
+
const {
|
|
409
|
+
needsResize,
|
|
410
|
+
width: targetW,
|
|
411
|
+
height: targetH,
|
|
412
|
+
videoBitrate: targetBitrate,
|
|
413
|
+
} = planVideoProcessing(
|
|
414
|
+
{
|
|
415
|
+
width: sourceWidth,
|
|
416
|
+
height: sourceHeight,
|
|
417
|
+
videoCodec,
|
|
418
|
+
videoBitrate,
|
|
419
|
+
},
|
|
420
|
+
{ maxLongEdge: MAX_LONG_EDGE, maxShortEdge: MAX_SHORT_EDGE },
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// Convert to MP4 (fresh Input — not shared with probeSource)
|
|
424
|
+
const {
|
|
425
|
+
Input,
|
|
426
|
+
Output,
|
|
427
|
+
Mp4OutputFormat,
|
|
428
|
+
BufferTarget,
|
|
429
|
+
BlobSource,
|
|
430
|
+
Conversion,
|
|
431
|
+
Quality,
|
|
432
|
+
ALL_FORMATS,
|
|
433
|
+
} = await loadMediabunny();
|
|
193
434
|
const input = new Input({
|
|
194
435
|
source: new BlobSource(file),
|
|
195
436
|
formats: ALL_FORMATS,
|
|
@@ -206,14 +447,16 @@ async function processToFile(
|
|
|
206
447
|
output,
|
|
207
448
|
video: {
|
|
208
449
|
codec: "avc",
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
codec: "aac",
|
|
450
|
+
...(needsResize
|
|
451
|
+
? { width: targetW, height: targetH, fit: "contain" as const }
|
|
452
|
+
: {}),
|
|
453
|
+
...(targetBitrate !== undefined
|
|
454
|
+
? { quality: new Quality({ bitrate: targetBitrate }) }
|
|
455
|
+
: {}),
|
|
216
456
|
},
|
|
457
|
+
// No quality set: an AAC track is copied, and anything else falls back
|
|
458
|
+
// to mediabunny's default, which for AAC lands on 192 kbps either way.
|
|
459
|
+
audio: { codec: "aac" },
|
|
217
460
|
});
|
|
218
461
|
|
|
219
462
|
if (onProgress) {
|
|
@@ -237,7 +480,8 @@ async function processToFile(
|
|
|
237
480
|
// Safari's WebCodecs does NOT bake rotation, so the matrix is needed.
|
|
238
481
|
// Strategy: probe the output as-is; if the dimensions already match
|
|
239
482
|
// the expected display size, leave the file alone. Otherwise strip
|
|
240
|
-
// the matrix and re-probe.
|
|
483
|
+
// the matrix and re-probe. A copied (not re-encoded) track never hits
|
|
484
|
+
// this — no encoder touched the pixels — so it falls out via dimsMatch.
|
|
241
485
|
const originalName = file.name.replace(/\.[^.]+$/, "");
|
|
242
486
|
let mp4File = new File([buffer], `${originalName}.mp4`, {
|
|
243
487
|
type: "video/mp4",
|
package/src/client-auth.ts
CHANGED
|
@@ -1,44 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Authenticated client entry point.
|
|
2
|
+
* Authenticated client entry point: the shell every signed-in page loads.
|
|
3
3
|
*
|
|
4
|
-
* Extends the public bundle with
|
|
5
|
-
*
|
|
4
|
+
* Extends the public bundle with what an author uses on any page — the post
|
|
5
|
+
* menu, the command palette, keyboard shortcuts, the buttons that open the
|
|
6
|
+
* composer — and leaves the rest to `client/lazy-entries.ts`, which loads the
|
|
7
|
+
* editor, settings, and management entries by what the page contains.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import "./client.js";
|
|
9
|
-
import "./client/image-processor.js";
|
|
10
|
-
import "./client/avatar-upload.js";
|
|
11
11
|
import { ensureConfirmDialog } from "./client/confirm.js";
|
|
12
|
-
import "./client/components/jant-compose-dialog.js";
|
|
13
|
-
import "./client/components/jant-compose-editor.js";
|
|
14
|
-
import "./client/components/jant-compose-fullscreen.js";
|
|
15
|
-
import "./client/compose-bridge.js";
|
|
16
|
-
import "./client/compose-discovery-bridge.js";
|
|
17
|
-
import "./client/slash-discovery-bridge.js";
|
|
18
|
-
import "./client/compose-shortcuts.js";
|
|
19
|
-
import "./client/components/jant-settings-general.js";
|
|
20
|
-
import "./client/components/jant-settings-avatar.js";
|
|
21
|
-
import "./client/components/jant-config-editor.js";
|
|
22
|
-
import "./client/settings-bridge.js";
|
|
23
|
-
import "./client/components/jant-repo-picker.js";
|
|
24
|
-
import "./client/components/jant-collection-form.js";
|
|
25
|
-
import "./client/components/jant-collection-directory.js";
|
|
26
|
-
import "./client/collection-form-bridge.js";
|
|
27
|
-
import "./client/components/jant-post-form.js";
|
|
28
|
-
import "./client/post-form-bridge.js";
|
|
29
|
-
import "./client/components/jant-nav-manager.js";
|
|
30
12
|
import "./client/components/jant-post-menu.js";
|
|
31
|
-
|
|
32
|
-
|
|
13
|
+
// The post menu renders the collection form inline when a post needs a new
|
|
14
|
+
// collection, so the form's definition travels with the menu.
|
|
15
|
+
import "./client/components/jant-collection-form.js";
|
|
33
16
|
import "./client/components/jant-command-palette.js";
|
|
34
17
|
import "./client/palette-shortcuts.js";
|
|
35
18
|
import "./client/palette-search-trigger.js";
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
document.body.appendChild(document.createElement("jant-compose-fullscreen"));
|
|
41
|
-
}
|
|
19
|
+
import "./client/compose-triggers.js";
|
|
20
|
+
import "./client/compose-shortcuts.js";
|
|
21
|
+
import "./client/compose-discovery-bridge.js";
|
|
22
|
+
import { loadEntriesForPage } from "./client/lazy-entries.js";
|
|
42
23
|
|
|
43
24
|
ensureConfirmDialog();
|
|
44
25
|
|
|
@@ -46,3 +27,5 @@ ensureConfirmDialog();
|
|
|
46
27
|
if (!document.querySelector("jant-command-palette")) {
|
|
47
28
|
document.body.appendChild(document.createElement("jant-command-palette"));
|
|
48
29
|
}
|
|
30
|
+
|
|
31
|
+
loadEntriesForPage();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer entry: the editor and everything that only runs while writing.
|
|
3
|
+
*
|
|
4
|
+
* Loaded by `client/lazy-entries.ts` — on first open through
|
|
5
|
+
* `compose-launch.ts`, at once on the compose page — and preloaded by the
|
|
6
|
+
* layout on every signed-in page. Never referenced from a script tag.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import "./client/components/jant-compose-dialog.js";
|
|
10
|
+
import "./client/components/jant-compose-editor.js";
|
|
11
|
+
import "./client/components/jant-compose-fullscreen.js";
|
|
12
|
+
import "./client/compose-bridge.js";
|
|
13
|
+
import "./client/slash-discovery-bridge.js";
|
|
14
|
+
|
|
15
|
+
// Mount the fullscreen overlay at body level to escape the dialog's containing
|
|
16
|
+
// block (dialog animation creates a containing block that traps fixed children).
|
|
17
|
+
if (!document.querySelector("jant-compose-fullscreen")) {
|
|
18
|
+
document.body.appendChild(document.createElement("jant-compose-fullscreen"));
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Management entry: navigation manager, collection directory, and the menus
|
|
3
|
+
* on collection and custom URL pages.
|
|
4
|
+
*
|
|
5
|
+
* Loaded by `client/lazy-entries.ts` when the page carries one of their
|
|
6
|
+
* elements or hooks. Never referenced from a script tag.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import "./client/components/jant-nav-manager.js";
|
|
10
|
+
import "./client/components/jant-collection-directory.js";
|
|
11
|
+
import "./client/collection-page-actions.js";
|
|
12
|
+
import "./client/smart-collection-page-actions.js";
|
|
13
|
+
import "./client/custom-url-menu.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings entry: the components behind the settings pages.
|
|
3
|
+
*
|
|
4
|
+
* Loaded by `client/lazy-entries.ts` when one of these elements is on the
|
|
5
|
+
* page. Never referenced from a script tag.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import "./client/avatar-upload.js";
|
|
9
|
+
import "./client/components/jant-settings-general.js";
|
|
10
|
+
import "./client/components/jant-settings-language.js";
|
|
11
|
+
import "./client/components/jant-settings-avatar.js";
|
|
12
|
+
import "./client/components/jant-config-editor.js";
|
|
13
|
+
import "./client/components/jant-repo-picker.js";
|
|
14
|
+
import "./client/settings-bridge.js";
|