@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
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { LitElement, html, nothing } from "lit";
|
|
12
|
+
import type { TemplateResult } from "lit";
|
|
12
13
|
import { classMap } from "lit/directives/class-map.js";
|
|
13
14
|
import { ifDefined } from "lit/directives/if-defined.js";
|
|
14
15
|
import { unsafeSVG } from "lit/directives/unsafe-svg.js";
|
|
@@ -36,11 +37,7 @@ import {
|
|
|
36
37
|
promoteLeadingH1Title as getLeadingH1TitlePromotion,
|
|
37
38
|
type LeadingH1TitlePromotion,
|
|
38
39
|
} from "./compose-title-from-heading.js";
|
|
39
|
-
import {
|
|
40
|
-
UPLOAD_ACCEPT,
|
|
41
|
-
getMediaCategory,
|
|
42
|
-
validateUploadFile,
|
|
43
|
-
} from "../../lib/upload.js";
|
|
40
|
+
import { getMediaCategory, validateUploadFile } from "../../lib/upload.js";
|
|
44
41
|
import type { MediaCategory } from "../../lib/upload.js";
|
|
45
42
|
import {
|
|
46
43
|
DECORATIVE_QUOTE_MARK_SVG_CONTENT,
|
|
@@ -70,6 +67,13 @@ interface ComposeFilePickerCloseDetail {
|
|
|
70
67
|
cancelled: boolean;
|
|
71
68
|
}
|
|
72
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Which control a reordered attachment hands focus back to. The strip renders
|
|
72
|
+
* positionally, so without this the focused DOM slot silently retargets a
|
|
73
|
+
* different attachment after every move.
|
|
74
|
+
*/
|
|
75
|
+
type AttachmentFocusTarget = "handle" | "move";
|
|
76
|
+
|
|
73
77
|
const COMPOSE_TOOLBAR_ICONS = {
|
|
74
78
|
media: `
|
|
75
79
|
<rect x="2.75" y="3" width="12.5" height="11.25" rx="3" />
|
|
@@ -281,12 +285,24 @@ export class JantComposeEditor extends LitElement {
|
|
|
281
285
|
* to run this cycle.
|
|
282
286
|
*/
|
|
283
287
|
private _suppressContentChangedOnce = false;
|
|
288
|
+
/**
|
|
289
|
+
* Set when the title toggle arrives with the post rather than from the
|
|
290
|
+
* owner's default — a handed-over editor keeps the answer it was given
|
|
291
|
+
* instead of having the first update reseed it from `titleByDefault`.
|
|
292
|
+
*/
|
|
293
|
+
#showTitleHandedOver = false;
|
|
284
294
|
#inlineImageUploadGeneration = 0;
|
|
285
295
|
#inlineImageUploadPromises = new Set<Promise<void>>();
|
|
286
296
|
#rehostFailureCount = 0;
|
|
287
297
|
#rehostFailureNoticeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
288
298
|
#sortable: { destroy(): void } | null = null;
|
|
289
299
|
#revertNextSibling: globalThis.Node | null = null;
|
|
300
|
+
#focusAfterMove: {
|
|
301
|
+
clientId: string;
|
|
302
|
+
direction: -1 | 1;
|
|
303
|
+
focusTarget: AttachmentFocusTarget;
|
|
304
|
+
} | null = null;
|
|
305
|
+
#coarsePointerQuery: globalThis.MediaQueryList | null | undefined;
|
|
290
306
|
|
|
291
307
|
createRenderRoot() {
|
|
292
308
|
return this;
|
|
@@ -338,6 +354,10 @@ export class JantComposeEditor extends LitElement {
|
|
|
338
354
|
this.addEventListener("dragover", this._onDragOver, true);
|
|
339
355
|
this.addEventListener("dragleave", this._onDragLeave);
|
|
340
356
|
this.addEventListener("drop", this._onDrop);
|
|
357
|
+
this.#coarsePointer()?.addEventListener(
|
|
358
|
+
"change",
|
|
359
|
+
this._onPointerTypeChange,
|
|
360
|
+
);
|
|
341
361
|
}
|
|
342
362
|
|
|
343
363
|
disconnectedCallback() {
|
|
@@ -362,6 +382,10 @@ export class JantComposeEditor extends LitElement {
|
|
|
362
382
|
this._emojiPickerEl = null;
|
|
363
383
|
this._filePickerCleanup?.();
|
|
364
384
|
this._filePickerCleanup = null;
|
|
385
|
+
this.#coarsePointer()?.removeEventListener(
|
|
386
|
+
"change",
|
|
387
|
+
this._onPointerTypeChange,
|
|
388
|
+
);
|
|
365
389
|
}
|
|
366
390
|
|
|
367
391
|
// Tracks dragenter/dragleave nesting so the highlight only clears when the
|
|
@@ -1025,20 +1049,6 @@ export class JantComposeEditor extends LitElement {
|
|
|
1025
1049
|
return this._title.trim() ? null : this.labels.linkTitleRequired;
|
|
1026
1050
|
}
|
|
1027
1051
|
|
|
1028
|
-
private _getInlineUrlValidationMessage(): string | null {
|
|
1029
|
-
if (this.format === "link" && !this._url.trim()) {
|
|
1030
|
-
return null;
|
|
1031
|
-
}
|
|
1032
|
-
return this.getUrlValidationMessage();
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
private _getInlineLinkTitleValidationMessage(): string | null {
|
|
1036
|
-
if (this.format !== "link" || !this._title.trim()) {
|
|
1037
|
-
return null;
|
|
1038
|
-
}
|
|
1039
|
-
return this.getLinkTitleValidationMessage();
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
1052
|
revealUrlValidation(): string | null {
|
|
1043
1053
|
this._showUrlValidation = true;
|
|
1044
1054
|
return this.getUrlValidationMessage();
|
|
@@ -1122,6 +1132,12 @@ export class JantComposeEditor extends LitElement {
|
|
|
1122
1132
|
tableControlLabels: this.labels.tableControls,
|
|
1123
1133
|
});
|
|
1124
1134
|
this._lastEditorSelection = this._readEditorSelection();
|
|
1135
|
+
// An editor built from existing content — after a format switch, or on a
|
|
1136
|
+
// replacement element that took over a post — inherits the uploads still
|
|
1137
|
+
// working on that content. Without this the `blob:` placeholder is left
|
|
1138
|
+
// with nothing watching it: the finished upload has no editor to write
|
|
1139
|
+
// into, and publishing puts the blob URL in the post.
|
|
1140
|
+
this.adoptPendingUploads();
|
|
1125
1141
|
}
|
|
1126
1142
|
|
|
1127
1143
|
private _destroyEditor() {
|
|
@@ -1150,7 +1166,7 @@ export class JantComposeEditor extends LitElement {
|
|
|
1150
1166
|
// `titleByDefault` is an owner-supplied prop, so it is only known once the
|
|
1151
1167
|
// first update runs. Seed the toggle from it there and leave it alone after
|
|
1152
1168
|
// that — from then on the value belongs to the user (and to `reset()`).
|
|
1153
|
-
if (!this.hasUpdated) {
|
|
1169
|
+
if (!this.hasUpdated && !this.#showTitleHandedOver) {
|
|
1154
1170
|
this._showTitle = this.titleByDefault;
|
|
1155
1171
|
}
|
|
1156
1172
|
}
|
|
@@ -1190,6 +1206,7 @@ export class JantComposeEditor extends LitElement {
|
|
|
1190
1206
|
this.#sortable?.destroy();
|
|
1191
1207
|
this.#sortable = null;
|
|
1192
1208
|
}
|
|
1209
|
+
this.#restoreAttachmentFocus();
|
|
1193
1210
|
}
|
|
1194
1211
|
|
|
1195
1212
|
// Every field that can change this row's answer is reactive, so `updated()`
|
|
@@ -1257,6 +1274,63 @@ export class JantComposeEditor extends LitElement {
|
|
|
1257
1274
|
this._bodyJson = fields.bodyJson;
|
|
1258
1275
|
}
|
|
1259
1276
|
|
|
1277
|
+
/**
|
|
1278
|
+
* Take over everything another editor holds for its post.
|
|
1279
|
+
*
|
|
1280
|
+
* Entering or leaving thread mode renders a different editor element for the
|
|
1281
|
+
* same post, and the element being replaced has the only copy of what was
|
|
1282
|
+
* written. Every field that belongs to the post moves here, in this one
|
|
1283
|
+
* place: a caller picking fields out by hand drops whichever one it forgets,
|
|
1284
|
+
* which is how a quote's source link used to vanish on "Add to thread".
|
|
1285
|
+
* Fields hidden by the current format move too, so switching back still
|
|
1286
|
+
* finds them.
|
|
1287
|
+
*
|
|
1288
|
+
* Safe before or after this editor's first render, and on a `previous` that
|
|
1289
|
+
* has already left the DOM — its TipTap instance is gone by then, but
|
|
1290
|
+
* `_bodyJson` tracks every edit.
|
|
1291
|
+
*
|
|
1292
|
+
* @param previous - The editor this one replaces
|
|
1293
|
+
* @example
|
|
1294
|
+
* await dialog.updateComplete;
|
|
1295
|
+
* threadRootEditor.takeOver(singlePostEditor);
|
|
1296
|
+
*/
|
|
1297
|
+
takeOver(previous: JantComposeEditor) {
|
|
1298
|
+
this._title = previous._title;
|
|
1299
|
+
this._showTitle = previous._showTitle;
|
|
1300
|
+
this.#showTitleHandedOver = true;
|
|
1301
|
+
this._bodyJson = previous._editor?.getJSON() ?? previous._bodyJson;
|
|
1302
|
+
this._url = previous._url;
|
|
1303
|
+
this._quoteText = previous._quoteText;
|
|
1304
|
+
this._quoteAuthor = previous._quoteAuthor;
|
|
1305
|
+
this._rating = previous._rating;
|
|
1306
|
+
this._showRating = previous._showRating;
|
|
1307
|
+
this._attachedTexts = [...previous._attachedTexts];
|
|
1308
|
+
this._attachments = [...previous._attachments];
|
|
1309
|
+
this._attachmentOrder = [...previous._attachmentOrder];
|
|
1310
|
+
this._lastEditorSelection = previous.getEditorSelection();
|
|
1311
|
+
// Before the first render there is no TipTap instance yet; `_initEditor`
|
|
1312
|
+
// builds it from `_bodyJson` and adopts the uploads then.
|
|
1313
|
+
if (this._editor) {
|
|
1314
|
+
this._editor.commands.setContent(
|
|
1315
|
+
this._bodyJson ?? { type: "doc", content: [{ type: "paragraph" }] },
|
|
1316
|
+
);
|
|
1317
|
+
this.adoptPendingUploads();
|
|
1318
|
+
}
|
|
1319
|
+
this.#notifyStatus();
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Whether this editor holds the attachment an upload is working on.
|
|
1324
|
+
*
|
|
1325
|
+
* @param clientId - The attachment's client-side id
|
|
1326
|
+
* @returns True when the attachment strip includes it
|
|
1327
|
+
* @example
|
|
1328
|
+
* editor.hasAttachment(clientId); // false once the post moved to another editor
|
|
1329
|
+
*/
|
|
1330
|
+
hasAttachment(clientId: string): boolean {
|
|
1331
|
+
return this._attachments.some((a) => a.clientId === clientId);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1260
1334
|
/** Pre-fill all fields for edit mode or draft restore */
|
|
1261
1335
|
populate(data: {
|
|
1262
1336
|
format: string;
|
|
@@ -1441,7 +1515,11 @@ export class JantComposeEditor extends LitElement {
|
|
|
1441
1515
|
);
|
|
1442
1516
|
}
|
|
1443
1517
|
|
|
1444
|
-
private _moveAttachment(
|
|
1518
|
+
private _moveAttachment(
|
|
1519
|
+
clientId: string,
|
|
1520
|
+
direction: -1 | 1,
|
|
1521
|
+
focusTarget: AttachmentFocusTarget = "handle",
|
|
1522
|
+
) {
|
|
1445
1523
|
const index = this._attachmentOrder.indexOf(clientId);
|
|
1446
1524
|
const nextIndex = index + direction;
|
|
1447
1525
|
if (
|
|
@@ -1457,9 +1535,50 @@ export class JantComposeEditor extends LitElement {
|
|
|
1457
1535
|
if (!item) return;
|
|
1458
1536
|
nextOrder.splice(nextIndex, 0, item);
|
|
1459
1537
|
this._attachmentOrder = nextOrder;
|
|
1538
|
+
this.#focusAfterMove = { clientId, direction, focusTarget };
|
|
1460
1539
|
this.#scrollAttachmentIntoView(clientId);
|
|
1461
1540
|
}
|
|
1462
1541
|
|
|
1542
|
+
/**
|
|
1543
|
+
* Returns focus to the attachment that just moved.
|
|
1544
|
+
*
|
|
1545
|
+
* Lit reuses the strip's DOM slots, so the element that held focus now
|
|
1546
|
+
* renders a different attachment. Called from `updated()`, once the new
|
|
1547
|
+
* order is in the DOM.
|
|
1548
|
+
*/
|
|
1549
|
+
#restoreAttachmentFocus() {
|
|
1550
|
+
const pending = this.#focusAfterMove;
|
|
1551
|
+
if (!pending) return;
|
|
1552
|
+
this.#focusAfterMove = null;
|
|
1553
|
+
|
|
1554
|
+
const item = this.querySelector<HTMLElement>(
|
|
1555
|
+
`[data-attachment-id="${pending.clientId}"]`,
|
|
1556
|
+
);
|
|
1557
|
+
if (!item) return;
|
|
1558
|
+
|
|
1559
|
+
if (pending.focusTarget === "handle") {
|
|
1560
|
+
item
|
|
1561
|
+
.querySelector<HTMLElement>("[data-attachment-sortable]")
|
|
1562
|
+
?.focus({ preventScroll: true });
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// The pressed button turns disabled at either end of the strip, and a
|
|
1567
|
+
// disabled button cannot hold focus — fall back to its twin so the
|
|
1568
|
+
// keyboard path never dead-ends.
|
|
1569
|
+
const target =
|
|
1570
|
+
this.#findMoveButton(item, pending.direction) ??
|
|
1571
|
+
this.#findMoveButton(item, pending.direction === 1 ? -1 : 1);
|
|
1572
|
+
target?.focus({ preventScroll: true });
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
#findMoveButton(item: HTMLElement, direction: -1 | 1) {
|
|
1576
|
+
const button = item.querySelector<HTMLButtonElement>(
|
|
1577
|
+
`[data-attachment-move="${direction === -1 ? "earlier" : "later"}"]`,
|
|
1578
|
+
);
|
|
1579
|
+
return button && !button.disabled ? button : null;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1463
1582
|
private _handleAttachmentKeydown(
|
|
1464
1583
|
clientId: string,
|
|
1465
1584
|
e: globalThis.KeyboardEvent,
|
|
@@ -1483,13 +1602,45 @@ export class JantComposeEditor extends LitElement {
|
|
|
1483
1602
|
}
|
|
1484
1603
|
}
|
|
1485
1604
|
|
|
1605
|
+
/**
|
|
1606
|
+
* Matches when the primary pointer is a finger.
|
|
1607
|
+
*
|
|
1608
|
+
* Cached because `matchMedia` allocates a new list each call, and `undefined`
|
|
1609
|
+
* distinguishes "not looked up yet" from "this environment has no
|
|
1610
|
+
* `matchMedia`".
|
|
1611
|
+
*/
|
|
1612
|
+
#coarsePointer(): globalThis.MediaQueryList | null {
|
|
1613
|
+
if (this.#coarsePointerQuery !== undefined) return this.#coarsePointerQuery;
|
|
1614
|
+
this.#coarsePointerQuery =
|
|
1615
|
+
typeof window.matchMedia === "function"
|
|
1616
|
+
? window.matchMedia("(pointer: coarse)")
|
|
1617
|
+
: null;
|
|
1618
|
+
return this.#coarsePointerQuery;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
private _onPointerTypeChange = () => {
|
|
1622
|
+
this.#sortable?.destroy();
|
|
1623
|
+
this.#sortable = null;
|
|
1624
|
+
this.#initSortable();
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1486
1627
|
#initSortable() {
|
|
1487
1628
|
const list = this.querySelector<HTMLElement>("[data-attachment-list]");
|
|
1488
1629
|
if (!list || this.#sortable || this._attachmentOrder.length <= 1) return;
|
|
1489
1630
|
|
|
1631
|
+
// Touch reorders through the move buttons, never by dragging. This strip
|
|
1632
|
+
// scrolls along the axis it would drag, and a near-full-width thumbnail's
|
|
1633
|
+
// drop target sits off-screen — so on a touch device the gesture is worth
|
|
1634
|
+
// more as a plain scroll than as a reorder.
|
|
1635
|
+
if (this.#coarsePointer()?.matches) return;
|
|
1636
|
+
|
|
1490
1637
|
this.#sortable = Sortable.create(list, {
|
|
1491
1638
|
...responsiveSortableOptions,
|
|
1492
1639
|
chosenClass: "compose-attachment-chosen",
|
|
1640
|
+
// Only reachable on a fine-pointer device that also has a touchscreen,
|
|
1641
|
+
// where the scroll-versus-drag conflict above still applies to touch.
|
|
1642
|
+
// Longer than the shared default so an ambiguous gesture scrolls.
|
|
1643
|
+
delay: 250,
|
|
1493
1644
|
direction: "horizontal",
|
|
1494
1645
|
dragClass: "compose-attachment-drag",
|
|
1495
1646
|
filter:
|
|
@@ -1637,6 +1788,24 @@ export class JantComposeEditor extends LitElement {
|
|
|
1637
1788
|
el.style.height = `${el.scrollHeight}px`;
|
|
1638
1789
|
}
|
|
1639
1790
|
|
|
1791
|
+
/**
|
|
1792
|
+
* Where the current pointer gesture over the body went down and came up. A
|
|
1793
|
+
* `click` reports the nearest common ancestor of the two, so these are the
|
|
1794
|
+
* only way to tell a press on blank paper from a drag that merely started or
|
|
1795
|
+
* ended there.
|
|
1796
|
+
*/
|
|
1797
|
+
#bodyPressTarget: globalThis.EventTarget | null = null;
|
|
1798
|
+
#bodyReleaseTarget: globalThis.EventTarget | null = null;
|
|
1799
|
+
|
|
1800
|
+
private _recordBodyPress = (e: MouseEvent) => {
|
|
1801
|
+
this.#bodyPressTarget = e.target;
|
|
1802
|
+
this.#bodyReleaseTarget = null;
|
|
1803
|
+
};
|
|
1804
|
+
|
|
1805
|
+
private _recordBodyRelease = (e: MouseEvent) => {
|
|
1806
|
+
this.#bodyReleaseTarget = e.target;
|
|
1807
|
+
};
|
|
1808
|
+
|
|
1640
1809
|
/**
|
|
1641
1810
|
* Puts the caret in the body when the click landed on blank section instead
|
|
1642
1811
|
* of on the editor. The surfaces that reserve height — the `/new` page, the
|
|
@@ -1644,8 +1813,20 @@ export class JantComposeEditor extends LitElement {
|
|
|
1644
1813
|
* editor, and it should behave like paper: click it, write there. Only the
|
|
1645
1814
|
* layout wrappers forward; a click that reached a real control belongs to
|
|
1646
1815
|
* that control.
|
|
1816
|
+
*
|
|
1817
|
+
* Selecting a line right-to-left normally lifts the pointer off the text and
|
|
1818
|
+
* over one of those wrappers, and the resulting click is retargeted to the
|
|
1819
|
+
* wrapper — indistinguishable from a click on blank paper by its target
|
|
1820
|
+
* alone. Requiring the press and the release to have landed on that same
|
|
1821
|
+
* blank space keeps a drag-selection from being thrown away.
|
|
1647
1822
|
*/
|
|
1648
1823
|
private _focusBodyFromBlankSpace = (e: MouseEvent) => {
|
|
1824
|
+
const pressTarget = this.#bodyPressTarget;
|
|
1825
|
+
const releaseTarget = this.#bodyReleaseTarget;
|
|
1826
|
+
this.#bodyPressTarget = null;
|
|
1827
|
+
this.#bodyReleaseTarget = null;
|
|
1828
|
+
if (pressTarget !== e.target || releaseTarget !== e.target) return;
|
|
1829
|
+
|
|
1649
1830
|
const target = e.target as HTMLElement | null;
|
|
1650
1831
|
if (!target) return;
|
|
1651
1832
|
const forwards =
|
|
@@ -1660,9 +1841,25 @@ export class JantComposeEditor extends LitElement {
|
|
|
1660
1841
|
this._rating = this._rating === star ? 0 : star;
|
|
1661
1842
|
}
|
|
1662
1843
|
|
|
1844
|
+
/**
|
|
1845
|
+
* Where a pasted or dropped image lands: inline in the body, or in the
|
|
1846
|
+
* attachment dock.
|
|
1847
|
+
*
|
|
1848
|
+
* Only a note has an article mode to fall into, and a visible, filled title
|
|
1849
|
+
* is what says the body is prose an image belongs inside. A link's body is
|
|
1850
|
+
* commentary beside a link card and a quote's is commentary beside the
|
|
1851
|
+
* quotation — images there are attachments, always. `_title` couldn't answer
|
|
1852
|
+
* this for them anyway: a link's title is required, so it is never empty
|
|
1853
|
+
* once the post is publishable, and a quote has no title field at all yet
|
|
1854
|
+
* can still be holding one carried over from a format switch.
|
|
1855
|
+
*
|
|
1856
|
+
* Only the default moves — the slash `/image` command inserts inline in
|
|
1857
|
+
* every format.
|
|
1858
|
+
*/
|
|
1663
1859
|
private _shouldPasteInlineImage(file: File): boolean {
|
|
1664
1860
|
if (!file.type.startsWith("image/")) return false;
|
|
1665
|
-
if (this.format
|
|
1861
|
+
if (this.format !== "note") return false;
|
|
1862
|
+
if (!this._showTitle) return false;
|
|
1666
1863
|
return this._title.trim().length > 0;
|
|
1667
1864
|
}
|
|
1668
1865
|
|
|
@@ -1670,7 +1867,9 @@ export class JantComposeEditor extends LitElement {
|
|
|
1670
1867
|
if (!this._fileInput) {
|
|
1671
1868
|
this._fileInput = document.createElement("input");
|
|
1672
1869
|
this._fileInput.type = "file";
|
|
1673
|
-
|
|
1870
|
+
// No `accept`: the composer takes any file, and leaving the attribute
|
|
1871
|
+
// off is how HTML says so. `*/*` is not a valid accept token, so every
|
|
1872
|
+
// browser had to work out what it meant before the dialog could open.
|
|
1674
1873
|
this._fileInput.multiple = true;
|
|
1675
1874
|
this._fileInput.style.display = "none";
|
|
1676
1875
|
this._fileInput.addEventListener("change", () =>
|
|
@@ -2088,15 +2287,17 @@ export class JantComposeEditor extends LitElement {
|
|
|
2088
2287
|
<span class="compose-attachment-preview-fallback-label">
|
|
2089
2288
|
${unavailableLabel}
|
|
2090
2289
|
</span>
|
|
2091
|
-
${
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
<
|
|
2095
|
-
<
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2290
|
+
${
|
|
2291
|
+
category === "video"
|
|
2292
|
+
? html`
|
|
2293
|
+
<div class="compose-attachment-play-icon">
|
|
2294
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="white">
|
|
2295
|
+
<path d="M8 5v14l11-7z" />
|
|
2296
|
+
</svg>
|
|
2297
|
+
</div>
|
|
2298
|
+
`
|
|
2299
|
+
: nothing
|
|
2300
|
+
}
|
|
2100
2301
|
</div>
|
|
2101
2302
|
`;
|
|
2102
2303
|
}
|
|
@@ -2106,17 +2307,19 @@ export class JantComposeEditor extends LitElement {
|
|
|
2106
2307
|
private _renderNoteFields() {
|
|
2107
2308
|
return html`
|
|
2108
2309
|
<div class="compose-field-enter">
|
|
2109
|
-
${
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2310
|
+
${
|
|
2311
|
+
this._showTitle
|
|
2312
|
+
? html`<input
|
|
2313
|
+
type="text"
|
|
2314
|
+
.value=${this._title}
|
|
2315
|
+
@input=${(e: Event) => this._onInput("_title", e)}
|
|
2316
|
+
@focus=${(e: Event) => this._onFieldFocus(e)}
|
|
2317
|
+
@keydown=${this._handleTitleKeydown}
|
|
2318
|
+
class="compose-input compose-note-title"
|
|
2319
|
+
placeholder=${this.labels.titlePlaceholder}
|
|
2320
|
+
/>`
|
|
2321
|
+
: nothing
|
|
2322
|
+
}
|
|
2120
2323
|
<div class="compose-tiptap-wrap">
|
|
2121
2324
|
<div class="compose-tiptap-body"></div>
|
|
2122
2325
|
<span class="compose-slash-discovery-hint" aria-hidden="true">
|
|
@@ -2129,10 +2332,10 @@ export class JantComposeEditor extends LitElement {
|
|
|
2129
2332
|
|
|
2130
2333
|
private _renderLinkFields() {
|
|
2131
2334
|
const urlError = this._showUrlValidation
|
|
2132
|
-
? this.
|
|
2335
|
+
? this.getUrlValidationMessage()
|
|
2133
2336
|
: null;
|
|
2134
2337
|
const titleError = this._showLinkTitleValidation
|
|
2135
|
-
? this.
|
|
2338
|
+
? this.getLinkTitleValidationMessage()
|
|
2136
2339
|
: null;
|
|
2137
2340
|
|
|
2138
2341
|
return html`
|
|
@@ -2150,7 +2353,10 @@ export class JantComposeEditor extends LitElement {
|
|
|
2150
2353
|
@input=${(e: Event) => this._onInput("_url", e)}
|
|
2151
2354
|
@focus=${(e: Event) => this._onFieldFocus(e)}
|
|
2152
2355
|
@blur=${() => {
|
|
2153
|
-
|
|
2356
|
+
// Leaving a field empty is not yet a mistake — it becomes one at
|
|
2357
|
+
// submit, where revealUrlValidation() opens this gate anyway.
|
|
2358
|
+
// Blur only speaks up about something already typed and wrong.
|
|
2359
|
+
if (this._url.trim()) this._showUrlValidation = true;
|
|
2154
2360
|
}}
|
|
2155
2361
|
class="compose-input compose-url-input"
|
|
2156
2362
|
placeholder=${this.labels.urlPlaceholder}
|
|
@@ -2160,15 +2366,17 @@ export class JantComposeEditor extends LitElement {
|
|
|
2160
2366
|
)}
|
|
2161
2367
|
/>
|
|
2162
2368
|
</div>
|
|
2163
|
-
${
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2369
|
+
${
|
|
2370
|
+
urlError
|
|
2371
|
+
? html`<p
|
|
2372
|
+
id=${this._urlStatusId}
|
|
2373
|
+
class="compose-url-status compose-url-status-error"
|
|
2374
|
+
data-compose-url-status="error"
|
|
2375
|
+
>
|
|
2376
|
+
${urlError}
|
|
2377
|
+
</p>`
|
|
2378
|
+
: nothing
|
|
2379
|
+
}
|
|
2172
2380
|
<input
|
|
2173
2381
|
type="text"
|
|
2174
2382
|
.value=${this._title}
|
|
@@ -2176,20 +2384,23 @@ export class JantComposeEditor extends LitElement {
|
|
|
2176
2384
|
@focus=${(e: Event) => this._onFieldFocus(e)}
|
|
2177
2385
|
@keydown=${this._handleTitleKeydown}
|
|
2178
2386
|
@blur=${() => {
|
|
2179
|
-
|
|
2387
|
+
// Same gate as the URL above: quiet while empty, loud on submit.
|
|
2388
|
+
if (this._title.trim()) this._showLinkTitleValidation = true;
|
|
2180
2389
|
}}
|
|
2181
2390
|
class="compose-input compose-link-title"
|
|
2182
2391
|
placeholder=${this.labels.linkTitlePlaceholder}
|
|
2183
2392
|
aria-invalid=${titleError ? "true" : "false"}
|
|
2184
2393
|
/>
|
|
2185
|
-
${
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2394
|
+
${
|
|
2395
|
+
titleError
|
|
2396
|
+
? html`<p
|
|
2397
|
+
class="compose-url-status compose-url-status-error"
|
|
2398
|
+
data-compose-link-title-error="error"
|
|
2399
|
+
>
|
|
2400
|
+
${titleError}
|
|
2401
|
+
</p>`
|
|
2402
|
+
: nothing
|
|
2403
|
+
}
|
|
2193
2404
|
<div class="compose-divider"></div>
|
|
2194
2405
|
<div class="compose-tiptap-wrap">
|
|
2195
2406
|
<div class="compose-tiptap-body compose-tiptap-link"></div>
|
|
@@ -2246,15 +2457,17 @@ export class JantComposeEditor extends LitElement {
|
|
|
2246
2457
|
urlError ? this._urlStatusId : undefined,
|
|
2247
2458
|
)}
|
|
2248
2459
|
/>
|
|
2249
|
-
${
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2460
|
+
${
|
|
2461
|
+
urlError
|
|
2462
|
+
? html`<p
|
|
2463
|
+
id=${this._urlStatusId}
|
|
2464
|
+
class="compose-url-status compose-url-status-error"
|
|
2465
|
+
data-compose-url-status="error"
|
|
2466
|
+
>
|
|
2467
|
+
${urlError}
|
|
2468
|
+
</p>`
|
|
2469
|
+
: nothing
|
|
2470
|
+
}
|
|
2258
2471
|
</div>
|
|
2259
2472
|
<div
|
|
2260
2473
|
class="compose-divider compose-divider-quote"
|
|
@@ -2289,9 +2502,11 @@ export class JantComposeEditor extends LitElement {
|
|
|
2289
2502
|
</button>
|
|
2290
2503
|
`,
|
|
2291
2504
|
)}
|
|
2292
|
-
${
|
|
2293
|
-
|
|
2294
|
-
|
|
2505
|
+
${
|
|
2506
|
+
this._rating > 0
|
|
2507
|
+
? html`<span class="compose-star-label">${this._rating}/5</span>`
|
|
2508
|
+
: nothing
|
|
2509
|
+
}
|
|
2295
2510
|
</div>
|
|
2296
2511
|
`;
|
|
2297
2512
|
}
|
|
@@ -2365,16 +2580,20 @@ export class JantComposeEditor extends LitElement {
|
|
|
2365
2580
|
${this._renderFileIcon(a.file.type, 20)}
|
|
2366
2581
|
</div>
|
|
2367
2582
|
<span class="compose-attachment-file-name">${a.file.name}</span>
|
|
2368
|
-
${
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2583
|
+
${
|
|
2584
|
+
a.summary
|
|
2585
|
+
? html`<span class="compose-attachment-text-summary"
|
|
2586
|
+
>${a.summary}</span
|
|
2587
|
+
>`
|
|
2588
|
+
: nothing
|
|
2589
|
+
}
|
|
2590
|
+
${
|
|
2591
|
+
typeof a.chars === "number" && a.chars > 0
|
|
2592
|
+
? html`<span class="compose-attachment-file-size"
|
|
2593
|
+
>${this._formatChars(a.chars)}</span
|
|
2594
|
+
>`
|
|
2595
|
+
: nothing
|
|
2596
|
+
}
|
|
2378
2597
|
</div>
|
|
2379
2598
|
`;
|
|
2380
2599
|
}
|
|
@@ -2404,46 +2623,52 @@ export class JantComposeEditor extends LitElement {
|
|
|
2404
2623
|
|
|
2405
2624
|
private _renderAttachmentOverlay(a: ComposeAttachment, index: number) {
|
|
2406
2625
|
return html`
|
|
2407
|
-
${
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
e
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
<
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2626
|
+
${
|
|
2627
|
+
a.status === "error"
|
|
2628
|
+
? html`
|
|
2629
|
+
<button
|
|
2630
|
+
type="button"
|
|
2631
|
+
class="compose-attachment-overlay compose-attachment-retry"
|
|
2632
|
+
@click=${(e: Event) => {
|
|
2633
|
+
e.stopPropagation();
|
|
2634
|
+
this._retryAllFailed();
|
|
2635
|
+
}}
|
|
2636
|
+
>
|
|
2637
|
+
<span class="compose-retry-content">
|
|
2638
|
+
<svg
|
|
2639
|
+
width="20"
|
|
2640
|
+
height="20"
|
|
2641
|
+
viewBox="0 0 24 24"
|
|
2642
|
+
fill="none"
|
|
2643
|
+
stroke="currentColor"
|
|
2644
|
+
stroke-width="2"
|
|
2645
|
+
stroke-linecap="round"
|
|
2646
|
+
stroke-linejoin="round"
|
|
2647
|
+
>
|
|
2648
|
+
<path
|
|
2649
|
+
d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"
|
|
2650
|
+
/>
|
|
2651
|
+
<path d="M3 3v5h5" />
|
|
2652
|
+
<path
|
|
2653
|
+
d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"
|
|
2654
|
+
/>
|
|
2655
|
+
<path d="M16 16h5v5" />
|
|
2656
|
+
</svg>
|
|
2657
|
+
<span class="compose-retry-label"
|
|
2658
|
+
>${this.labels.retryAll}</span
|
|
2659
|
+
>
|
|
2660
|
+
</span>
|
|
2661
|
+
${
|
|
2662
|
+
a.error
|
|
2663
|
+
? html`<span class="compose-attachment-error-msg"
|
|
2664
|
+
>${a.error}</span
|
|
2665
|
+
>`
|
|
2666
|
+
: nothing
|
|
2667
|
+
}
|
|
2668
|
+
</button>
|
|
2669
|
+
`
|
|
2670
|
+
: nothing
|
|
2671
|
+
}
|
|
2447
2672
|
${this._renderAttachmentRemoveButton((e: Event) => {
|
|
2448
2673
|
e.stopPropagation();
|
|
2449
2674
|
this._removeAttachment(index);
|
|
@@ -2475,6 +2700,87 @@ export class JantComposeEditor extends LitElement {
|
|
|
2475
2700
|
`;
|
|
2476
2701
|
}
|
|
2477
2702
|
|
|
2703
|
+
private _renderAttachmentMoveButton(
|
|
2704
|
+
clientId: string,
|
|
2705
|
+
direction: -1 | 1,
|
|
2706
|
+
disabled: boolean,
|
|
2707
|
+
) {
|
|
2708
|
+
const earlier = direction === -1;
|
|
2709
|
+
const label = earlier
|
|
2710
|
+
? this.labels.moveAttachmentEarlier
|
|
2711
|
+
: this.labels.moveAttachmentLater;
|
|
2712
|
+
|
|
2713
|
+
return html`
|
|
2714
|
+
<button
|
|
2715
|
+
type="button"
|
|
2716
|
+
class="compose-attachment-move"
|
|
2717
|
+
data-attachment-move=${earlier ? "earlier" : "later"}
|
|
2718
|
+
?disabled=${disabled}
|
|
2719
|
+
aria-label=${label}
|
|
2720
|
+
title=${label}
|
|
2721
|
+
@click=${(e: Event) => {
|
|
2722
|
+
e.stopPropagation();
|
|
2723
|
+
this._moveAttachment(clientId, direction, "move");
|
|
2724
|
+
}}
|
|
2725
|
+
>
|
|
2726
|
+
<svg
|
|
2727
|
+
width="14"
|
|
2728
|
+
height="14"
|
|
2729
|
+
viewBox="0 0 14 14"
|
|
2730
|
+
fill="none"
|
|
2731
|
+
stroke="currentColor"
|
|
2732
|
+
stroke-width="1.9"
|
|
2733
|
+
stroke-linecap="round"
|
|
2734
|
+
stroke-linejoin="round"
|
|
2735
|
+
>
|
|
2736
|
+
<path
|
|
2737
|
+
d=${
|
|
2738
|
+
earlier ? "M8.75 3.5 5.25 7l3.5 3.5" : "M5.25 3.5 8.75 7l-3.5 3.5"
|
|
2739
|
+
}
|
|
2740
|
+
/>
|
|
2741
|
+
</svg>
|
|
2742
|
+
</button>
|
|
2743
|
+
`;
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
/**
|
|
2747
|
+
* Renders the quiet control row under an attachment thumbnail.
|
|
2748
|
+
*
|
|
2749
|
+
* Reorder buttons are the touch-reliable path: dragging a thumbnail competes
|
|
2750
|
+
* with the strip's own horizontal scroll, and a near-full-width thumbnail's
|
|
2751
|
+
* drop target is off-screen anyway. They appear only once there is something
|
|
2752
|
+
* to reorder against.
|
|
2753
|
+
*/
|
|
2754
|
+
private _renderAttachmentActions(
|
|
2755
|
+
clientId: string,
|
|
2756
|
+
extra: TemplateResult | typeof nothing = nothing,
|
|
2757
|
+
) {
|
|
2758
|
+
const total = this._attachmentOrder.length;
|
|
2759
|
+
const index = this._attachmentOrder.indexOf(clientId);
|
|
2760
|
+
const canReorder = total > 1 && index !== -1;
|
|
2761
|
+
if (!canReorder && extra === nothing) return nothing;
|
|
2762
|
+
|
|
2763
|
+
return html`
|
|
2764
|
+
<div class="compose-attachment-actions">
|
|
2765
|
+
${
|
|
2766
|
+
canReorder
|
|
2767
|
+
? html`
|
|
2768
|
+
<div class="compose-attachment-move-group">
|
|
2769
|
+
${this._renderAttachmentMoveButton(clientId, -1, index === 0)}
|
|
2770
|
+
${this._renderAttachmentMoveButton(
|
|
2771
|
+
clientId,
|
|
2772
|
+
1,
|
|
2773
|
+
index === total - 1,
|
|
2774
|
+
)}
|
|
2775
|
+
</div>
|
|
2776
|
+
`
|
|
2777
|
+
: nothing
|
|
2778
|
+
}
|
|
2779
|
+
${extra}
|
|
2780
|
+
</div>
|
|
2781
|
+
`;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2478
2784
|
private _renderAttachedTextCard(item: AttachedTextItem, index: number) {
|
|
2479
2785
|
return html`
|
|
2480
2786
|
<div class="compose-attachment" data-attachment-id=${item.clientId}>
|
|
@@ -2493,19 +2799,22 @@ export class JantComposeEditor extends LitElement {
|
|
|
2493
2799
|
${this._renderFileIcon("text/html; charset=utf-8", 20)}
|
|
2494
2800
|
</div>
|
|
2495
2801
|
<span class="compose-attachment-text-summary">${item.summary}</span>
|
|
2496
|
-
${
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
this.
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2802
|
+
${
|
|
2803
|
+
item.bodyJson
|
|
2804
|
+
? html`<span class="compose-attachment-file-size"
|
|
2805
|
+
>${this._formatChars(
|
|
2806
|
+
this._extractPlainText(item.bodyJson).length,
|
|
2807
|
+
)}</span
|
|
2808
|
+
>`
|
|
2809
|
+
: nothing
|
|
2810
|
+
}
|
|
2503
2811
|
</div>
|
|
2504
2812
|
${this._renderAttachmentRemoveButton((e: Event) => {
|
|
2505
2813
|
e.stopPropagation();
|
|
2506
2814
|
this._removeAttachedText(index);
|
|
2507
2815
|
})}
|
|
2508
2816
|
</div>
|
|
2817
|
+
${this._renderAttachmentActions(item.clientId)}
|
|
2509
2818
|
</div>
|
|
2510
2819
|
`;
|
|
2511
2820
|
}
|
|
@@ -2518,115 +2827,122 @@ export class JantComposeEditor extends LitElement {
|
|
|
2518
2827
|
|
|
2519
2828
|
return html`
|
|
2520
2829
|
<div class="compose-attachment" data-attachment-id=${a.clientId}>
|
|
2521
|
-
${
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
<div
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2830
|
+
${
|
|
2831
|
+
isFileCard
|
|
2832
|
+
? html`
|
|
2833
|
+
<div class="compose-attachment-thumb">
|
|
2834
|
+
<div
|
|
2835
|
+
class="compose-attachment-sortable"
|
|
2836
|
+
data-attachment-sortable
|
|
2837
|
+
tabindex="0"
|
|
2838
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
2839
|
+
this._handleAttachmentKeydown(a.clientId, e)}
|
|
2840
|
+
>
|
|
2841
|
+
${this._renderAttachmentPreview(a)}
|
|
2842
|
+
</div>
|
|
2843
|
+
${this._renderAttachmentOverlay(a, i)}
|
|
2532
2844
|
</div>
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2845
|
+
`
|
|
2846
|
+
: html`
|
|
2847
|
+
<div class="compose-attachment-thumb">
|
|
2848
|
+
<div
|
|
2849
|
+
class="compose-attachment-sortable"
|
|
2850
|
+
data-attachment-sortable
|
|
2851
|
+
tabindex="0"
|
|
2852
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
2853
|
+
this._handleAttachmentKeydown(a.clientId, e)}
|
|
2854
|
+
>
|
|
2855
|
+
${
|
|
2856
|
+
previewFailed
|
|
2857
|
+
? this._renderAttachmentPreviewFallback(visualCategory)
|
|
2858
|
+
: category === "video"
|
|
2859
|
+
? html`
|
|
2860
|
+
<video
|
|
2861
|
+
src=${a.previewUrl}
|
|
2862
|
+
poster=${a.posterUrl ?? nothing}
|
|
2863
|
+
class="compose-attachment-img"
|
|
2864
|
+
preload="metadata"
|
|
2865
|
+
.playsInline=${true}
|
|
2866
|
+
.muted=${true}
|
|
2867
|
+
@loadeddata=${() =>
|
|
2868
|
+
this._setAttachmentPreviewFailure(
|
|
2869
|
+
a.clientId,
|
|
2870
|
+
false,
|
|
2871
|
+
)}
|
|
2872
|
+
@error=${() =>
|
|
2873
|
+
this._setAttachmentPreviewFailure(
|
|
2874
|
+
a.clientId,
|
|
2875
|
+
true,
|
|
2876
|
+
)}
|
|
2877
|
+
></video>
|
|
2878
|
+
<div class="compose-attachment-play-icon">
|
|
2879
|
+
<svg
|
|
2880
|
+
width="24"
|
|
2881
|
+
height="24"
|
|
2882
|
+
viewBox="0 0 24 24"
|
|
2883
|
+
fill="white"
|
|
2884
|
+
>
|
|
2885
|
+
<path d="M8 5v14l11-7z" />
|
|
2886
|
+
</svg>
|
|
2887
|
+
</div>
|
|
2888
|
+
`
|
|
2889
|
+
: a.status === "processing"
|
|
2890
|
+
? html`
|
|
2891
|
+
<div class="compose-attachment-processing">
|
|
2892
|
+
<svg
|
|
2893
|
+
class="animate-spin size-5"
|
|
2894
|
+
viewBox="0 0 24 24"
|
|
2895
|
+
fill="none"
|
|
2896
|
+
stroke="currentColor"
|
|
2897
|
+
stroke-width="2"
|
|
2898
|
+
>
|
|
2899
|
+
<path
|
|
2900
|
+
d="M12 2a10 10 0 1 0 10 10"
|
|
2901
|
+
stroke-linecap="round"
|
|
2902
|
+
/>
|
|
2903
|
+
</svg>
|
|
2904
|
+
</div>
|
|
2905
|
+
`
|
|
2906
|
+
: html`
|
|
2907
|
+
<img
|
|
2908
|
+
src=${a.previewUrl}
|
|
2909
|
+
alt=""
|
|
2910
|
+
class="compose-attachment-img"
|
|
2911
|
+
@load=${() =>
|
|
2912
|
+
this._setAttachmentPreviewFailure(
|
|
2913
|
+
a.clientId,
|
|
2914
|
+
false,
|
|
2915
|
+
)}
|
|
2916
|
+
@error=${() =>
|
|
2917
|
+
this._setAttachmentPreviewFailure(
|
|
2918
|
+
a.clientId,
|
|
2919
|
+
true,
|
|
2920
|
+
)}
|
|
2591
2921
|
/>
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
<img
|
|
2597
|
-
src=${a.previewUrl}
|
|
2598
|
-
alt=""
|
|
2599
|
-
class="compose-attachment-img"
|
|
2600
|
-
@load=${() =>
|
|
2601
|
-
this._setAttachmentPreviewFailure(
|
|
2602
|
-
a.clientId,
|
|
2603
|
-
false,
|
|
2604
|
-
)}
|
|
2605
|
-
@error=${() =>
|
|
2606
|
-
this._setAttachmentPreviewFailure(
|
|
2607
|
-
a.clientId,
|
|
2608
|
-
true,
|
|
2609
|
-
)}
|
|
2610
|
-
/>
|
|
2611
|
-
`}
|
|
2922
|
+
`
|
|
2923
|
+
}
|
|
2924
|
+
</div>
|
|
2925
|
+
${this._renderAttachmentOverlay(a, i)}
|
|
2612
2926
|
</div>
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2927
|
+
`
|
|
2928
|
+
}
|
|
2929
|
+
${this._renderAttachmentActions(
|
|
2930
|
+
a.clientId,
|
|
2931
|
+
category === "image"
|
|
2932
|
+
? html`
|
|
2933
|
+
<button
|
|
2934
|
+
type="button"
|
|
2935
|
+
class=${classMap({
|
|
2936
|
+
"compose-attachment-alt": true,
|
|
2937
|
+
"compose-attachment-alt-set": a.alt.length > 0,
|
|
2938
|
+
})}
|
|
2939
|
+
@click=${() => this._openAltPanel(i)}
|
|
2940
|
+
>
|
|
2941
|
+
${a.alt.length > 0 ? "ALT" : "+ ALT"}
|
|
2942
|
+
</button>
|
|
2943
|
+
`
|
|
2944
|
+
: nothing,
|
|
2945
|
+
)}
|
|
2630
2946
|
</div>
|
|
2631
2947
|
`;
|
|
2632
2948
|
}
|
|
@@ -2685,11 +3001,13 @@ export class JantComposeEditor extends LitElement {
|
|
|
2685
3001
|
@click=${() => this._openFilePicker()}
|
|
2686
3002
|
>
|
|
2687
3003
|
${renderComposeToolbarIcon(COMPOSE_TOOLBAR_ICONS.media)}
|
|
2688
|
-
${
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
3004
|
+
${
|
|
3005
|
+
this._attachments.length > 0
|
|
3006
|
+
? html`<span class="compose-tool-label"
|
|
3007
|
+
>${this.labels.addMore}</span
|
|
3008
|
+
>`
|
|
3009
|
+
: nothing
|
|
3010
|
+
}
|
|
2693
3011
|
</button>
|
|
2694
3012
|
|
|
2695
3013
|
<button
|
|
@@ -2702,11 +3020,13 @@ export class JantComposeEditor extends LitElement {
|
|
|
2702
3020
|
@click=${() => this._openAttachedText()}
|
|
2703
3021
|
>
|
|
2704
3022
|
${renderComposeToolbarIcon(COMPOSE_TOOLBAR_ICONS.attachedText)}
|
|
2705
|
-
${
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
3023
|
+
${
|
|
3024
|
+
hasAttached
|
|
3025
|
+
? html`<span class="compose-tool-label"
|
|
3026
|
+
>${this.labels.addMore}</span
|
|
3027
|
+
>`
|
|
3028
|
+
: nothing
|
|
3029
|
+
}
|
|
2710
3030
|
</button>
|
|
2711
3031
|
|
|
2712
3032
|
<button
|
|
@@ -2748,57 +3068,61 @@ export class JantComposeEditor extends LitElement {
|
|
|
2748
3068
|
${renderComposeToolbarIcon(COMPOSE_TOOLBAR_ICONS.rate)}
|
|
2749
3069
|
</button>
|
|
2750
3070
|
|
|
2751
|
-
${
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
type="button"
|
|
2755
|
-
class=${classMap({
|
|
2756
|
-
"compose-tool-btn": true,
|
|
2757
|
-
"compose-tool-btn-active": this._showTitle,
|
|
2758
|
-
})}
|
|
2759
|
-
title=${this.labels.title}
|
|
2760
|
-
aria-pressed=${this._showTitle ? "true" : "false"}
|
|
2761
|
-
@click=${() => {
|
|
2762
|
-
const willShow = !this._showTitle;
|
|
2763
|
-
this._showTitle = willShow;
|
|
2764
|
-
// Only this deliberate click is worth remembering. The other
|
|
2765
|
-
// writers of `_showTitle` — H1 promotion, format conversion,
|
|
2766
|
-
// fullscreen reveal — are the program acting on one post, not
|
|
2767
|
-
// the author stating a habit, so they stay silent.
|
|
2768
|
-
this.dispatchEvent(
|
|
2769
|
-
new CustomEvent("jant:title-toggle", {
|
|
2770
|
-
bubbles: true,
|
|
2771
|
-
detail: { showTitle: willShow },
|
|
2772
|
-
}),
|
|
2773
|
-
);
|
|
2774
|
-
if (willShow) {
|
|
2775
|
-
this.updateComplete.then(() => {
|
|
2776
|
-
this.querySelector<HTMLInputElement>(
|
|
2777
|
-
".compose-note-title",
|
|
2778
|
-
)?.focus();
|
|
2779
|
-
});
|
|
2780
|
-
}
|
|
2781
|
-
}}
|
|
2782
|
-
>
|
|
2783
|
-
${renderComposeToolbarIcon(COMPOSE_TOOLBAR_ICONS.title)}
|
|
2784
|
-
</button>
|
|
2785
|
-
`
|
|
2786
|
-
: nothing}
|
|
2787
|
-
${this.format === "note"
|
|
2788
|
-
? html`
|
|
2789
|
-
<div class="compose-tool-view-group">
|
|
3071
|
+
${
|
|
3072
|
+
this.format === "note"
|
|
3073
|
+
? html`
|
|
2790
3074
|
<button
|
|
2791
3075
|
type="button"
|
|
2792
|
-
class
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
3076
|
+
class=${classMap({
|
|
3077
|
+
"compose-tool-btn": true,
|
|
3078
|
+
"compose-tool-btn-active": this._showTitle,
|
|
3079
|
+
})}
|
|
3080
|
+
title=${this.labels.title}
|
|
3081
|
+
aria-pressed=${this._showTitle ? "true" : "false"}
|
|
3082
|
+
@click=${() => {
|
|
3083
|
+
const willShow = !this._showTitle;
|
|
3084
|
+
this._showTitle = willShow;
|
|
3085
|
+
// Only this deliberate click is worth remembering. The other
|
|
3086
|
+
// writers of `_showTitle` — H1 promotion, format conversion,
|
|
3087
|
+
// fullscreen reveal — are the program acting on one post, not
|
|
3088
|
+
// the author stating a habit, so they stay silent.
|
|
3089
|
+
this.dispatchEvent(
|
|
3090
|
+
new CustomEvent("jant:title-toggle", {
|
|
3091
|
+
bubbles: true,
|
|
3092
|
+
detail: { showTitle: willShow },
|
|
3093
|
+
}),
|
|
3094
|
+
);
|
|
3095
|
+
if (willShow) {
|
|
3096
|
+
this.updateComplete.then(() => {
|
|
3097
|
+
this.querySelector<HTMLInputElement>(
|
|
3098
|
+
".compose-note-title",
|
|
3099
|
+
)?.focus();
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
}}
|
|
2796
3103
|
>
|
|
2797
|
-
${renderComposeToolbarIcon(COMPOSE_TOOLBAR_ICONS.
|
|
3104
|
+
${renderComposeToolbarIcon(COMPOSE_TOOLBAR_ICONS.title)}
|
|
2798
3105
|
</button>
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
3106
|
+
`
|
|
3107
|
+
: nothing
|
|
3108
|
+
}
|
|
3109
|
+
${
|
|
3110
|
+
this.format === "note"
|
|
3111
|
+
? html`
|
|
3112
|
+
<div class="compose-tool-view-group">
|
|
3113
|
+
<button
|
|
3114
|
+
type="button"
|
|
3115
|
+
class="compose-tool-btn compose-tool-btn-view"
|
|
3116
|
+
title=${this.labels.fullscreen}
|
|
3117
|
+
aria-label=${this.labels.fullscreen}
|
|
3118
|
+
@click=${() => this.openFullscreen()}
|
|
3119
|
+
>
|
|
3120
|
+
${renderComposeToolbarIcon(COMPOSE_TOOLBAR_ICONS.fullscreen)}
|
|
3121
|
+
</button>
|
|
3122
|
+
</div>
|
|
3123
|
+
`
|
|
3124
|
+
: nothing
|
|
3125
|
+
}
|
|
2802
3126
|
</div>
|
|
2803
3127
|
`;
|
|
2804
3128
|
}
|
|
@@ -2842,42 +3166,48 @@ export class JantComposeEditor extends LitElement {
|
|
|
2842
3166
|
`,
|
|
2843
3167
|
)}
|
|
2844
3168
|
</div>
|
|
2845
|
-
${
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
3169
|
+
${
|
|
3170
|
+
this.positionLabel
|
|
3171
|
+
? html`<span class="compose-post-position"
|
|
3172
|
+
>${this.positionLabel}</span
|
|
3173
|
+
>`
|
|
3174
|
+
: nothing
|
|
3175
|
+
}
|
|
3176
|
+
${
|
|
3177
|
+
this.badgeLabel
|
|
3178
|
+
? html`<span class="compose-post-position compose-post-badge"
|
|
3179
|
+
>${this.badgeLabel}</span
|
|
3180
|
+
>`
|
|
3181
|
+
: nothing
|
|
3182
|
+
}
|
|
2855
3183
|
${this.headerExtra ?? nothing}
|
|
2856
|
-
${
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
<svg
|
|
2868
|
-
width="14"
|
|
2869
|
-
height="14"
|
|
2870
|
-
viewBox="0 0 16 16"
|
|
2871
|
-
fill="none"
|
|
2872
|
-
stroke="currentColor"
|
|
2873
|
-
stroke-width="1.8"
|
|
2874
|
-
stroke-linecap="round"
|
|
2875
|
-
aria-hidden="true"
|
|
3184
|
+
${
|
|
3185
|
+
this.removable
|
|
3186
|
+
? html`<button
|
|
3187
|
+
type="button"
|
|
3188
|
+
class="compose-thread-post-remove"
|
|
3189
|
+
title="Remove post"
|
|
3190
|
+
@click=${() => {
|
|
3191
|
+
this.dispatchEvent(
|
|
3192
|
+
new CustomEvent("jant:thread-remove", { bubbles: true }),
|
|
3193
|
+
);
|
|
3194
|
+
}}
|
|
2876
3195
|
>
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
3196
|
+
<svg
|
|
3197
|
+
width="14"
|
|
3198
|
+
height="14"
|
|
3199
|
+
viewBox="0 0 16 16"
|
|
3200
|
+
fill="none"
|
|
3201
|
+
stroke="currentColor"
|
|
3202
|
+
stroke-width="1.8"
|
|
3203
|
+
stroke-linecap="round"
|
|
3204
|
+
aria-hidden="true"
|
|
3205
|
+
>
|
|
3206
|
+
${unsafeSVG(CLOSE_ICON)}
|
|
3207
|
+
</svg>
|
|
3208
|
+
</button>`
|
|
3209
|
+
: nothing
|
|
3210
|
+
}
|
|
2881
3211
|
</div>
|
|
2882
3212
|
`;
|
|
2883
3213
|
}
|
|
@@ -2898,12 +3228,19 @@ export class JantComposeEditor extends LitElement {
|
|
|
2898
3228
|
// thread item alike — so there is exactly one place it can be.
|
|
2899
3229
|
return html`
|
|
2900
3230
|
${this._renderFormatHeader()}
|
|
2901
|
-
<section
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3231
|
+
<section
|
|
3232
|
+
class="compose-body"
|
|
3233
|
+
@mousedown=${this._recordBodyPress}
|
|
3234
|
+
@mouseup=${this._recordBodyRelease}
|
|
3235
|
+
@click=${this._focusBodyFromBlankSpace}
|
|
3236
|
+
>
|
|
3237
|
+
${
|
|
3238
|
+
this.format === "note"
|
|
3239
|
+
? this._renderNoteFields()
|
|
3240
|
+
: this.format === "link"
|
|
3241
|
+
? this._renderLinkFields()
|
|
3242
|
+
: this._renderQuoteFields()
|
|
3243
|
+
}
|
|
2907
3244
|
</section>
|
|
2908
3245
|
${this._renderAttachmentDock()} ${this._renderStarRating()}
|
|
2909
3246
|
${this._renderToolsRow()}
|