@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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the smart collection dialog.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FILTER_DIMENSION_KEYS } from "../../lib/filter-dimensions.js";
|
|
6
|
+
|
|
7
|
+
/** One condition row: which dimension, and the value it holds. */
|
|
8
|
+
export interface SmartConditionRow {
|
|
9
|
+
key: (typeof FILTER_DIMENSION_KEYS)[number];
|
|
10
|
+
/**
|
|
11
|
+
* The value in its URL spelling.
|
|
12
|
+
*
|
|
13
|
+
* The same string the query string carries, because the registry's parser is
|
|
14
|
+
* the only thing that turns a control's output into a stored value. A control
|
|
15
|
+
* that invented its own encoding would be a second vocabulary.
|
|
16
|
+
*/
|
|
17
|
+
value: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Everything the dialog can be opened with. */
|
|
21
|
+
export interface SmartCollectionDialogState {
|
|
22
|
+
title?: string;
|
|
23
|
+
description?: string | null;
|
|
24
|
+
selection?: Record<string, unknown>;
|
|
25
|
+
sort?: string;
|
|
26
|
+
layout?: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SmartCollectionDialogLabels {
|
|
30
|
+
createHeading: string;
|
|
31
|
+
editHeading: string;
|
|
32
|
+
/** What a smart collection is, shown once, when creating one. */
|
|
33
|
+
whatItIs: string;
|
|
34
|
+
title: string;
|
|
35
|
+
/** The collection link, in the collection form's own words. */
|
|
36
|
+
link: string;
|
|
37
|
+
linkHelp: string;
|
|
38
|
+
editLink: string;
|
|
39
|
+
resetLink: string;
|
|
40
|
+
linkTaken: string;
|
|
41
|
+
linkInvalid: string;
|
|
42
|
+
linkReserved: string;
|
|
43
|
+
linkTooLong: string;
|
|
44
|
+
linkMovesWarning: string;
|
|
45
|
+
description: string;
|
|
46
|
+
conditionsHeading: string;
|
|
47
|
+
matchAllHint: string;
|
|
48
|
+
noConditions: string;
|
|
49
|
+
addCondition: string;
|
|
50
|
+
removeCondition: string;
|
|
51
|
+
countSummary: string;
|
|
52
|
+
counting: string;
|
|
53
|
+
displayHeading: string;
|
|
54
|
+
orderBy: string;
|
|
55
|
+
layout: string;
|
|
56
|
+
cancel: string;
|
|
57
|
+
save: string;
|
|
58
|
+
saved: string;
|
|
59
|
+
saveFailed: string;
|
|
60
|
+
loadFailed: string;
|
|
61
|
+
titleAndLinkRequired: string;
|
|
62
|
+
/** Dimension names, keyed by dimension key. */
|
|
63
|
+
dimensions: Record<string, string>;
|
|
64
|
+
/** Value labels, keyed by `<dimension>.<value>`. */
|
|
65
|
+
values: Record<string, string>;
|
|
66
|
+
sortOptions: Record<string, string>;
|
|
67
|
+
layoutOptions: Record<string, string>;
|
|
68
|
+
}
|
|
@@ -15,10 +15,7 @@ import type { PostAttachmentInput } from "../types.js";
|
|
|
15
15
|
import { AudioProcessor } from "./audio-processor.js";
|
|
16
16
|
import { ImageProcessor } from "./image-processor.js";
|
|
17
17
|
import { VideoProcessor } from "./video-processor.js";
|
|
18
|
-
import {
|
|
19
|
-
extractMediaMetadata,
|
|
20
|
-
extractAudioWaveform,
|
|
21
|
-
} from "./media-metadata.js";
|
|
18
|
+
import { extractAudioWaveform } from "./audio-waveform.js";
|
|
22
19
|
import {
|
|
23
20
|
showToast,
|
|
24
21
|
showPersistentToast,
|
|
@@ -26,7 +23,6 @@ import {
|
|
|
26
23
|
replaceWithAutoClose,
|
|
27
24
|
queueToastForNextPage,
|
|
28
25
|
} from "./toast.js";
|
|
29
|
-
import { getComposeDialog, openReplyForArticle } from "./compose-launch.js";
|
|
30
26
|
import {
|
|
31
27
|
refreshPostCardView,
|
|
32
28
|
refreshPostPageView,
|
|
@@ -37,12 +33,79 @@ import { uploadViaSession } from "./upload-session.js";
|
|
|
37
33
|
import type { UploadSessionResult } from "./upload-session.js";
|
|
38
34
|
import { publicPath } from "./runtime-paths.js";
|
|
39
35
|
import { tiptapJsonToMarkdown } from "../lib/tiptap-to-markdown.js";
|
|
40
|
-
import { getMediaCategory } from "../lib/upload.js";
|
|
36
|
+
import { getMediaCategory, sniffImageMimeType } from "../lib/upload.js";
|
|
41
37
|
import {
|
|
42
38
|
resolveInlineImageUrls,
|
|
43
39
|
hasPendingInlineImagePlaceholders,
|
|
44
40
|
} from "./tiptap/inline-image-upload.js";
|
|
45
41
|
|
|
42
|
+
/**
|
|
43
|
+
* POST a compose payload, treating a dropped connection as a failed response.
|
|
44
|
+
*
|
|
45
|
+
* A publish that never reaches the server and a publish the server rejects are
|
|
46
|
+
* the same event to the author: the post did not go up. Letting `fetch` reject
|
|
47
|
+
* here would send only one of them down the recovery path, so the request that
|
|
48
|
+
* failed hardest — no connection at all — would be the one that skipped saving
|
|
49
|
+
* a draft.
|
|
50
|
+
*
|
|
51
|
+
* @param url - Compose endpoint to post to
|
|
52
|
+
* @param body - Request body, already serialized
|
|
53
|
+
* @param method - HTTP method, defaulting to POST
|
|
54
|
+
* @returns The response, or null when the request never completed
|
|
55
|
+
* @example
|
|
56
|
+
* const res = await postCompose("/compose/thread", JSON.stringify(threadBody));
|
|
57
|
+
* if (!res?.ok) await saveAsDraftInstead();
|
|
58
|
+
*/
|
|
59
|
+
async function postCompose(
|
|
60
|
+
url: string,
|
|
61
|
+
body: string,
|
|
62
|
+
method = "POST",
|
|
63
|
+
): Promise<globalThis.Response | null> {
|
|
64
|
+
try {
|
|
65
|
+
return await fetch(url, {
|
|
66
|
+
method,
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
Accept: "application/json",
|
|
70
|
+
},
|
|
71
|
+
body,
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* What to tell the author when a compose request did not land.
|
|
80
|
+
*
|
|
81
|
+
* Prefers the server's own words, because it knows what was wrong with the
|
|
82
|
+
* post. A request that never completed has no words of its own, so it says so
|
|
83
|
+
* — "no connection" and "the server refused this" call for different next
|
|
84
|
+
* steps from the author.
|
|
85
|
+
*
|
|
86
|
+
* @param res - The response, or null when the request never completed
|
|
87
|
+
* @param labels - Composer labels, absent before the element has upgraded
|
|
88
|
+
* @returns A message ready to show in a toast
|
|
89
|
+
* @example
|
|
90
|
+
* await handleSubmitError(await submitErrorMessage(res, labels));
|
|
91
|
+
*/
|
|
92
|
+
async function submitErrorMessage(
|
|
93
|
+
res: globalThis.Response | null,
|
|
94
|
+
labels: JantComposeDialog["labels"] | undefined,
|
|
95
|
+
): Promise<string> {
|
|
96
|
+
if (!res) {
|
|
97
|
+
return (
|
|
98
|
+
labels?.publishFailedOffline ?? "Couldn't reach the server. Try again."
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const data = await readJsonObject(res);
|
|
102
|
+
return (
|
|
103
|
+
getJsonString(data, "error") ??
|
|
104
|
+
labels?.publishFailed ??
|
|
105
|
+
"Couldn't publish. Try again."
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
46
109
|
/**
|
|
47
110
|
* Whether a serialized post body still references inline image placeholders
|
|
48
111
|
* pending upload or paste-rehost. Drives the "uploading" toast and whether to
|
|
@@ -143,19 +206,46 @@ function refreshUploadToast() {
|
|
|
143
206
|
/**
|
|
144
207
|
* Track completed upload results by clientId.
|
|
145
208
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* here as soon as any upload succeeds, and `buildRequestAttachments` reads it
|
|
152
|
-
* when `attachment.mediaId` is null and the clientId isn't in `uploadPromises`.
|
|
209
|
+
* By the time the user submits, a finished upload's promise has been deleted
|
|
210
|
+
* from `uploadPromises`, so the deferred handler can't look it up. This map
|
|
211
|
+
* provides a fallback: we record the result here as soon as any upload
|
|
212
|
+
* succeeds, and `buildRequestAttachments` reads it when `attachment.mediaId`
|
|
213
|
+
* is null and the clientId isn't in `uploadPromises`.
|
|
153
214
|
*
|
|
154
215
|
* The full result (not just the id) is kept so a failed publish can restore
|
|
155
216
|
* uploaded attachments into the reopened compose dialog.
|
|
156
217
|
*/
|
|
157
218
|
const completedUploads = new Map<string, UploadSessionResult>();
|
|
158
219
|
|
|
220
|
+
/**
|
|
221
|
+
* The editor holding an attachment now.
|
|
222
|
+
*
|
|
223
|
+
* Entering or leaving thread mode renders the post in a new editor element,
|
|
224
|
+
* which takes the attachments over from the one it replaced. An upload that
|
|
225
|
+
* outlives the swap has to follow the attachment: reporting to the editor it
|
|
226
|
+
* started in leaves the new one showing "uploading" forever, never showing an
|
|
227
|
+
* error, and keeping the finished file out of the local draft. Until a
|
|
228
|
+
* connected editor holds the attachment — the moment between the swap and the
|
|
229
|
+
* handover — the starting editor is still the one with the latest copy.
|
|
230
|
+
*
|
|
231
|
+
* @param started - The editor the upload was started from
|
|
232
|
+
* @param clientId - The attachment's client-side id
|
|
233
|
+
* @returns The editor to report progress and status to
|
|
234
|
+
*/
|
|
235
|
+
function attachmentOwner(
|
|
236
|
+
started: JantComposeEditor | null,
|
|
237
|
+
clientId: string,
|
|
238
|
+
): JantComposeEditor | null {
|
|
239
|
+
if (!started || started.isConnected) return started;
|
|
240
|
+
const editors = document.querySelectorAll<JantComposeEditor>(
|
|
241
|
+
"jant-compose-editor",
|
|
242
|
+
);
|
|
243
|
+
for (const editor of editors) {
|
|
244
|
+
if (editor.hasAttachment(clientId)) return editor;
|
|
245
|
+
}
|
|
246
|
+
return started;
|
|
247
|
+
}
|
|
248
|
+
|
|
159
249
|
/**
|
|
160
250
|
* Quickly grab the very first decoded frame of a video as a small poster.
|
|
161
251
|
* Uses a 3s timeout — returns null on any failure so callers don't stall.
|
|
@@ -225,6 +315,19 @@ function captureQuickPoster(file: File): Promise<Blob | null> {
|
|
|
225
315
|
});
|
|
226
316
|
}
|
|
227
317
|
|
|
318
|
+
/** Bytes that settle whether a file is HEIC: the `ftyp` box and its brand. */
|
|
319
|
+
const HEIC_SNIFF_BYTES = 12;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Whether the file's bytes say HEIC/HEIF, whatever its name or type claims.
|
|
323
|
+
* Reads the header only — the decoder's own check pulled the whole file into
|
|
324
|
+
* memory to look at four bytes of it.
|
|
325
|
+
*/
|
|
326
|
+
async function isHeicFile(file: File): Promise<boolean> {
|
|
327
|
+
const head = await file.slice(0, HEIC_SNIFF_BYTES).arrayBuffer();
|
|
328
|
+
return sniffImageMimeType(new Uint8Array(head)) === "image/heic";
|
|
329
|
+
}
|
|
330
|
+
|
|
228
331
|
/**
|
|
229
332
|
* Upload a single file: process locally, then send it through the upload
|
|
230
333
|
* session API so the backend can choose relay vs direct transport.
|
|
@@ -235,6 +338,7 @@ async function uploadFile(
|
|
|
235
338
|
clientId: string,
|
|
236
339
|
editor: JantComposeEditor | null,
|
|
237
340
|
): Promise<string | null> {
|
|
341
|
+
const owner = () => attachmentOwner(editor, clientId);
|
|
238
342
|
// Capture cheap metadata up-front so we can release `file` (the original
|
|
239
343
|
// potentially-huge blob) as soon as transcoding finishes. On iOS Safari
|
|
240
344
|
// holding a 300MB+ source blob alongside the transcoded output, upload
|
|
@@ -254,7 +358,7 @@ async function uploadFile(
|
|
|
254
358
|
if (fileType.startsWith("video/")) {
|
|
255
359
|
// Video: transcode with mediabunny (requires WebCodecs)
|
|
256
360
|
if (!VideoProcessor.isSupported()) {
|
|
257
|
-
|
|
361
|
+
owner()?.updateAttachmentStatus(
|
|
258
362
|
clientId,
|
|
259
363
|
"error",
|
|
260
364
|
null,
|
|
@@ -265,14 +369,19 @@ async function uploadFile(
|
|
|
265
369
|
|
|
266
370
|
// Capture the first frame quickly so Safari shows a preview while
|
|
267
371
|
// the heavy transcoding runs. Chrome shows it natively via
|
|
268
|
-
// <video preload="metadata">, Safari does not.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
372
|
+
// <video preload="metadata">, Safari does not. Kept, rather than shown
|
|
373
|
+
// and dropped: it is also the fallback for the upload below, so the
|
|
374
|
+
// card and the stored media can never disagree about having a poster.
|
|
375
|
+
const quickPoster = captureQuickPoster(file)
|
|
376
|
+
.then((blob) => {
|
|
377
|
+
if (blob) owner()?.updateAttachmentPoster(clientId, blob);
|
|
378
|
+
return blob;
|
|
379
|
+
})
|
|
380
|
+
.catch(() => null);
|
|
381
|
+
|
|
382
|
+
owner()?.updateAttachmentStatus(clientId, "processing", null, null);
|
|
274
383
|
const result = await VideoProcessor.processToFile(file, (progress) => {
|
|
275
|
-
|
|
384
|
+
owner()?.updateAttachmentProgress(clientId, progress);
|
|
276
385
|
});
|
|
277
386
|
toUpload = result.file;
|
|
278
387
|
// Drop the original blob ref now that we have the transcoded output.
|
|
@@ -281,14 +390,18 @@ async function uploadFile(
|
|
|
281
390
|
height = result.height;
|
|
282
391
|
durationSeconds = result.durationSeconds;
|
|
283
392
|
blurhash = result.blurhash;
|
|
284
|
-
poster
|
|
393
|
+
// The probe's poster is the better one — a frame from a tenth of the way
|
|
394
|
+
// in, at full decode quality. Falling back to the quick one costs a
|
|
395
|
+
// slightly worse still; not falling back costs a thumbnail entirely, on
|
|
396
|
+
// exactly the files whose frames the probe could not read.
|
|
397
|
+
poster = result.poster ?? (await quickPoster) ?? undefined;
|
|
285
398
|
if (poster) {
|
|
286
|
-
|
|
399
|
+
owner()?.updateAttachmentPoster(clientId, poster);
|
|
287
400
|
}
|
|
288
401
|
} else if (fileType.startsWith("audio/")) {
|
|
289
402
|
// Audio: transcode to AAC (.m4a) (requires WebCodecs)
|
|
290
403
|
if (!AudioProcessor.isSupported()) {
|
|
291
|
-
|
|
404
|
+
owner()?.updateAttachmentStatus(
|
|
292
405
|
clientId,
|
|
293
406
|
"error",
|
|
294
407
|
null,
|
|
@@ -304,9 +417,9 @@ async function uploadFile(
|
|
|
304
417
|
// Waveform extraction is best-effort
|
|
305
418
|
}
|
|
306
419
|
|
|
307
|
-
|
|
420
|
+
owner()?.updateAttachmentStatus(clientId, "processing", null, null);
|
|
308
421
|
const result = await AudioProcessor.processToFile(file, (progress) => {
|
|
309
|
-
|
|
422
|
+
owner()?.updateAttachmentProgress(clientId, progress);
|
|
310
423
|
});
|
|
311
424
|
toUpload = result.file;
|
|
312
425
|
file = null as unknown as File;
|
|
@@ -318,9 +431,11 @@ async function uploadFile(
|
|
|
318
431
|
// Image: convert HEIC/HEIF if needed, then resize + convert to WebP
|
|
319
432
|
let imageFile = file;
|
|
320
433
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
434
|
+
if (await isHeicFile(imageFile)) {
|
|
435
|
+
// heic-to carries libheif — a 3MB chunk — so it is fetched for a
|
|
436
|
+
// file whose bytes say HEIC, not for every photo on the off chance.
|
|
437
|
+
const { heicTo } = await import("heic-to");
|
|
438
|
+
owner()?.updateAttachmentStatus(clientId, "processing", null, null);
|
|
324
439
|
const blob = await heicTo({
|
|
325
440
|
blob: imageFile,
|
|
326
441
|
type: "image/jpeg",
|
|
@@ -329,16 +444,17 @@ async function uploadFile(
|
|
|
329
444
|
imageFile = new File([blob], fileName.replace(/\.heic$/i, ".jpg"), {
|
|
330
445
|
type: "image/jpeg",
|
|
331
446
|
});
|
|
332
|
-
|
|
447
|
+
owner()?.updateAttachmentPreview(clientId, imageFile);
|
|
333
448
|
}
|
|
334
449
|
const result = await ImageProcessor.processToFile(imageFile);
|
|
335
450
|
toUpload = result.file;
|
|
336
451
|
width = result.width;
|
|
337
452
|
height = result.height;
|
|
453
|
+
blurhash = result.blurhash;
|
|
338
454
|
file = null as unknown as File;
|
|
339
455
|
imageFile = null as unknown as File;
|
|
340
456
|
} catch {
|
|
341
|
-
|
|
457
|
+
owner()?.removeAttachment(clientId);
|
|
342
458
|
showToast("Image format not supported.", "error");
|
|
343
459
|
return null;
|
|
344
460
|
}
|
|
@@ -347,21 +463,7 @@ async function uploadFile(
|
|
|
347
463
|
}
|
|
348
464
|
|
|
349
465
|
// Update status to uploading
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// Extract metadata for non-video files (video metadata comes from VideoProcessor)
|
|
353
|
-
// Audio waveform is already extracted above (before AudioProcessor runs).
|
|
354
|
-
if (!fileType.startsWith("video/")) {
|
|
355
|
-
const meta = await extractMediaMetadata(toUpload);
|
|
356
|
-
width ??= meta.width;
|
|
357
|
-
height ??= meta.height;
|
|
358
|
-
blurhash ??= meta.blurhash;
|
|
359
|
-
waveform ??= meta.waveform;
|
|
360
|
-
if (!poster && meta.poster) {
|
|
361
|
-
poster = meta.poster;
|
|
362
|
-
editor?.updateAttachmentPoster(clientId, poster);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
466
|
+
owner()?.updateAttachmentStatus(clientId, "uploading", null, null);
|
|
365
467
|
|
|
366
468
|
// Text attachments keep summary/chars in the media record. This covers
|
|
367
469
|
// plain text-file uploads (.md, .txt, .csv). Jant-composed rich text
|
|
@@ -398,7 +500,7 @@ async function uploadFile(
|
|
|
398
500
|
chars,
|
|
399
501
|
},
|
|
400
502
|
(progress) => {
|
|
401
|
-
|
|
503
|
+
owner()?.updateAttachmentProgress(clientId, progress);
|
|
402
504
|
uploadProgress.set(clientId, progress);
|
|
403
505
|
refreshUploadToast();
|
|
404
506
|
},
|
|
@@ -406,7 +508,7 @@ async function uploadFile(
|
|
|
406
508
|
|
|
407
509
|
uploadProgress.set(clientId, 1);
|
|
408
510
|
refreshUploadToast();
|
|
409
|
-
|
|
511
|
+
owner()?.updateAttachmentStatus(
|
|
410
512
|
clientId,
|
|
411
513
|
"done",
|
|
412
514
|
result.id,
|
|
@@ -419,7 +521,7 @@ async function uploadFile(
|
|
|
419
521
|
uploadProgress.delete(clientId);
|
|
420
522
|
refreshUploadToast();
|
|
421
523
|
const message = error instanceof Error ? error.message : "Upload failed";
|
|
422
|
-
|
|
524
|
+
owner()?.updateAttachmentStatus(clientId, "error", null, message);
|
|
423
525
|
// Error is shown on the attachment thumbnail; only toast when there's no editor context.
|
|
424
526
|
if (!editor) showToast(message, "error");
|
|
425
527
|
return null;
|
|
@@ -469,38 +571,6 @@ document.addEventListener("jant:files-selected", (e: Event) => {
|
|
|
469
571
|
}
|
|
470
572
|
});
|
|
471
573
|
|
|
472
|
-
// ── Reply trigger handler ───────────────────────────────────────────
|
|
473
|
-
|
|
474
|
-
document.addEventListener("click", (e: MouseEvent) => {
|
|
475
|
-
const trigger = (e.target as HTMLElement).closest<HTMLButtonElement>(
|
|
476
|
-
"[data-reply-trigger]",
|
|
477
|
-
);
|
|
478
|
-
if (!trigger) return;
|
|
479
|
-
|
|
480
|
-
const article = trigger.closest<HTMLElement>("article[data-post]");
|
|
481
|
-
if (!article) return;
|
|
482
|
-
void openReplyForArticle(article);
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
// ── Draft badge → editor ────────────────────────────────────────────
|
|
486
|
-
//
|
|
487
|
-
// The badge on an inline draft doubles as its most likely next action. Publish
|
|
488
|
-
// and Delete live in the post menu with every other post action.
|
|
489
|
-
|
|
490
|
-
document.addEventListener("click", (e: MouseEvent) => {
|
|
491
|
-
const trigger = (e.target as HTMLElement).closest<HTMLElement>(
|
|
492
|
-
"[data-draft-continue]",
|
|
493
|
-
);
|
|
494
|
-
if (!trigger) return;
|
|
495
|
-
|
|
496
|
-
const postId =
|
|
497
|
-
trigger.dataset.postId ??
|
|
498
|
-
trigger.closest<HTMLElement>("article[data-post]")?.dataset.postId;
|
|
499
|
-
if (!postId) return;
|
|
500
|
-
|
|
501
|
-
void getComposeDialog()?.openEdit(postId);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
574
|
// ── Submit handler ──────────────────────────────────────────────────
|
|
505
575
|
|
|
506
576
|
/** Build the JSON body for both create and update requests */
|
|
@@ -564,6 +634,12 @@ function buildPostBody(
|
|
|
564
634
|
attachments: attachments.length > 0 ? attachments : undefined,
|
|
565
635
|
replyToId: detail.replyToId || undefined,
|
|
566
636
|
quietReply: detail.quietReply || undefined,
|
|
637
|
+
// Absent means the author left the language to detection; the server runs
|
|
638
|
+
// the same detector against the final text.
|
|
639
|
+
language: detail.language || undefined,
|
|
640
|
+
// A translation group is only ever minted on create — an edit that carried
|
|
641
|
+
// this would silently regroup an existing post.
|
|
642
|
+
translationOfId: isEdit ? undefined : detail.translationOfId || undefined,
|
|
567
643
|
};
|
|
568
644
|
}
|
|
569
645
|
|
|
@@ -635,32 +711,49 @@ document.addEventListener("jant:compose-submit-deferred", async (e: Event) => {
|
|
|
635
711
|
// Get labels for toast messages
|
|
636
712
|
const labels = composeEl?.labels;
|
|
637
713
|
const uploadingMsg = labels?.uploading ?? "Uploading...";
|
|
714
|
+
const publishingMsg = labels?.publishing ?? "Publishing...";
|
|
715
|
+
const savingMsg = labels?.saving ?? "Saving...";
|
|
638
716
|
const hasInlinePending = detail.threadPosts
|
|
639
717
|
? detail.threadPosts.some((p) => bodyHasPendingInline(p.body))
|
|
640
718
|
: bodyHasPendingInline(detail.body);
|
|
641
719
|
const hasPending = detail.pendingAttachments.length > 0 || hasInlinePending;
|
|
642
720
|
const publishedMsg = labels?.published ?? "Published!";
|
|
643
721
|
const viewLabel = labels?.view ?? "View";
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
722
|
+
const isEdit = !!detail.editPostId;
|
|
723
|
+
const isThread = !!(detail.threadPosts && detail.threadPosts.length >= 2);
|
|
724
|
+
// Publishing a draft goes through the edit endpoint but still ends published.
|
|
725
|
+
const willPublish =
|
|
726
|
+
detail.status === "published" && (!isEdit || !!detail.draftSourceId);
|
|
727
|
+
|
|
728
|
+
// The composer closes the moment it dispatches, so this toast is the only
|
|
729
|
+
// sign that work is still running. Uploads name themselves and grow a
|
|
730
|
+
// percentage; everything else says what it is doing — a Link post can sit on
|
|
731
|
+
// the server for seconds while its preview thumbnail is fetched and stored.
|
|
732
|
+
let progressToastOpen = true;
|
|
733
|
+
showPersistentToast(
|
|
734
|
+
"compose-deferred",
|
|
735
|
+
hasPending ? uploadingMsg : willPublish ? publishingMsg : savingMsg,
|
|
736
|
+
);
|
|
737
|
+
if (detail.pendingAttachments.length > 0) {
|
|
738
|
+
activeUploadToast = {
|
|
739
|
+
clientIds: detail.pendingAttachments.map((a) => a.clientId),
|
|
740
|
+
baseMsg: uploadingMsg,
|
|
741
|
+
};
|
|
742
|
+
refreshUploadToast();
|
|
655
743
|
}
|
|
656
744
|
|
|
657
|
-
/**
|
|
745
|
+
/**
|
|
746
|
+
* Show a result toast. The first one takes over the progress toast in place;
|
|
747
|
+
* a later message (a retry result, say) gets its own toast instead of
|
|
748
|
+
* overwriting one that has not been read yet.
|
|
749
|
+
*/
|
|
658
750
|
const toastMsg = (msg: string, type: "success" | "error" = "success") => {
|
|
659
751
|
if (activeUploadToast) {
|
|
660
752
|
for (const id of activeUploadToast.clientIds) uploadProgress.delete(id);
|
|
661
753
|
activeUploadToast = null;
|
|
662
754
|
}
|
|
663
|
-
if (
|
|
755
|
+
if (progressToastOpen) {
|
|
756
|
+
progressToastOpen = false;
|
|
664
757
|
replaceWithAutoClose("compose-deferred", msg, type);
|
|
665
758
|
} else {
|
|
666
759
|
showToast(msg, type);
|
|
@@ -783,8 +876,6 @@ document.addEventListener("jant:compose-submit-deferred", async (e: Event) => {
|
|
|
783
876
|
globalThis.location.assign(composeEl.closeHref || publicPath("/"));
|
|
784
877
|
return true;
|
|
785
878
|
};
|
|
786
|
-
const isEdit = !!detail.editPostId;
|
|
787
|
-
const isThread = !!(detail.threadPosts && detail.threadPosts.length >= 2);
|
|
788
879
|
let draftFallback: "upload" | "server" | null = null;
|
|
789
880
|
|
|
790
881
|
try {
|
|
@@ -854,30 +945,23 @@ document.addEventListener("jant:compose-submit-deferred", async (e: Event) => {
|
|
|
854
945
|
threadBody.replaceThreadId = detail.editPostId;
|
|
855
946
|
}
|
|
856
947
|
|
|
857
|
-
const res = await
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
Accept: "application/json",
|
|
862
|
-
},
|
|
863
|
-
body: JSON.stringify(threadBody),
|
|
864
|
-
});
|
|
948
|
+
const res = await postCompose(
|
|
949
|
+
"/compose/thread",
|
|
950
|
+
JSON.stringify(threadBody),
|
|
951
|
+
);
|
|
865
952
|
|
|
866
|
-
if (!res
|
|
867
|
-
//
|
|
953
|
+
if (!res?.ok) {
|
|
954
|
+
// The publish did not land — whether the server refused it or the
|
|
955
|
+
// request never arrived. Try to keep the work as a draft either way.
|
|
868
956
|
if (detail.status === "published" && !draftFallback) {
|
|
869
957
|
const retryPayload = {
|
|
870
958
|
posts: postsPayload.map((p) => ({ ...p, status: "draft" })),
|
|
871
959
|
};
|
|
872
|
-
const retryRes = await
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
},
|
|
878
|
-
body: JSON.stringify(retryPayload),
|
|
879
|
-
});
|
|
880
|
-
if (retryRes.ok) {
|
|
960
|
+
const retryRes = await postCompose(
|
|
961
|
+
"/compose/thread",
|
|
962
|
+
JSON.stringify(retryPayload),
|
|
963
|
+
);
|
|
964
|
+
if (retryRes?.ok) {
|
|
881
965
|
draftFallback = "server";
|
|
882
966
|
clearRecoveredLocalDraft();
|
|
883
967
|
const fallbackMsg =
|
|
@@ -888,10 +972,7 @@ document.addEventListener("jant:compose-submit-deferred", async (e: Event) => {
|
|
|
888
972
|
return;
|
|
889
973
|
}
|
|
890
974
|
}
|
|
891
|
-
|
|
892
|
-
await handleSubmitError(
|
|
893
|
-
getJsonString(data, "error") ?? "Something went wrong",
|
|
894
|
-
);
|
|
975
|
+
await handleSubmitError(await submitErrorMessage(res, labels));
|
|
895
976
|
return;
|
|
896
977
|
}
|
|
897
978
|
|
|
@@ -974,29 +1055,24 @@ document.addEventListener("jant:compose-submit-deferred", async (e: Event) => {
|
|
|
974
1055
|
requestAttachments,
|
|
975
1056
|
);
|
|
976
1057
|
|
|
977
|
-
const res = await
|
|
1058
|
+
const res = await postCompose(
|
|
1059
|
+
endpoint,
|
|
1060
|
+
JSON.stringify(bodyPayload),
|
|
978
1061
|
method,
|
|
979
|
-
|
|
980
|
-
"Content-Type": "application/json",
|
|
981
|
-
Accept: "application/json",
|
|
982
|
-
},
|
|
983
|
-
body: JSON.stringify(bodyPayload),
|
|
984
|
-
});
|
|
1062
|
+
);
|
|
985
1063
|
|
|
986
|
-
if (!res
|
|
987
|
-
//
|
|
1064
|
+
if (!res?.ok) {
|
|
1065
|
+
// As in thread mode: a refused request and a request that never arrived
|
|
1066
|
+
// both mean the post is not up, so both get the save-as-draft attempt.
|
|
988
1067
|
if (detail.status === "published" && !isEdit && !draftFallback) {
|
|
989
1068
|
const retryPayload = { ...bodyPayload, status: "draft" };
|
|
990
|
-
const retryRes = await
|
|
1069
|
+
const retryRes = await postCompose(
|
|
1070
|
+
endpoint,
|
|
1071
|
+
JSON.stringify(retryPayload),
|
|
991
1072
|
method,
|
|
992
|
-
|
|
993
|
-
"Content-Type": "application/json",
|
|
994
|
-
Accept: "application/json",
|
|
995
|
-
},
|
|
996
|
-
body: JSON.stringify(retryPayload),
|
|
997
|
-
});
|
|
1073
|
+
);
|
|
998
1074
|
|
|
999
|
-
if (retryRes
|
|
1075
|
+
if (retryRes?.ok) {
|
|
1000
1076
|
draftFallback = "server";
|
|
1001
1077
|
clearRecoveredLocalDraft();
|
|
1002
1078
|
const retryData = await readJsonObject(retryRes);
|
|
@@ -1013,10 +1089,7 @@ document.addEventListener("jant:compose-submit-deferred", async (e: Event) => {
|
|
|
1013
1089
|
}
|
|
1014
1090
|
}
|
|
1015
1091
|
|
|
1016
|
-
|
|
1017
|
-
await handleSubmitError(
|
|
1018
|
-
getJsonString(data, "error") ?? "Something went wrong",
|
|
1019
|
-
);
|
|
1092
|
+
await handleSubmitError(await submitErrorMessage(res, labels));
|
|
1020
1093
|
return;
|
|
1021
1094
|
}
|
|
1022
1095
|
|
|
@@ -1168,7 +1241,7 @@ document.addEventListener("jant:compose-submit-deferred", async (e: Event) => {
|
|
|
1168
1241
|
dispatchSubmitComplete(composeEl, "draft");
|
|
1169
1242
|
}
|
|
1170
1243
|
} catch {
|
|
1171
|
-
await handleSubmitError(
|
|
1244
|
+
await handleSubmitError(await submitErrorMessage(null, labels));
|
|
1172
1245
|
}
|
|
1173
1246
|
});
|
|
1174
1247
|
|