@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
|
@@ -18,7 +18,15 @@ import {
|
|
|
18
18
|
import { showConfirmDialog } from "../confirm.js";
|
|
19
19
|
import { refreshArticleView } from "../post-refresh.js";
|
|
20
20
|
import { showToast } from "../toast.js";
|
|
21
|
+
import { readErrorMessage } from "../json.js";
|
|
21
22
|
import { publicPath } from "../runtime-paths.js";
|
|
23
|
+
import { looksLikeAddress } from "../../lib/url.js";
|
|
24
|
+
import { pickPost } from "../post-picker.js";
|
|
25
|
+
import {
|
|
26
|
+
ensureComposeDialog,
|
|
27
|
+
readComposeDialogLabels,
|
|
28
|
+
} from "../compose-launch.js";
|
|
29
|
+
import type { PostPickerResult } from "./jant-post-picker.js";
|
|
22
30
|
import {
|
|
23
31
|
applyItemOrder,
|
|
24
32
|
filterCollectionsBySearch,
|
|
@@ -35,6 +43,51 @@ interface PostMenuData {
|
|
|
35
43
|
visibility: string;
|
|
36
44
|
isReply: boolean;
|
|
37
45
|
isDraft: boolean;
|
|
46
|
+
/** Content language of the Thread, when the site publishes more than one. */
|
|
47
|
+
language: string | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** One language the site publishes. */
|
|
51
|
+
interface MenuLanguage {
|
|
52
|
+
tag: string;
|
|
53
|
+
label: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* What the server made of an address pasted into the translation picker: the
|
|
58
|
+
* Thread it names, or why that Thread cannot be linked to this one.
|
|
59
|
+
*/
|
|
60
|
+
type TranslationAddressResolution =
|
|
61
|
+
| {
|
|
62
|
+
kind: "ok";
|
|
63
|
+
address: string;
|
|
64
|
+
candidate: { id: string; label: string; language: string | null };
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
kind:
|
|
68
|
+
| "external"
|
|
69
|
+
| "not_found"
|
|
70
|
+
| "not_a_post"
|
|
71
|
+
| "same_thread"
|
|
72
|
+
| "unpublished"
|
|
73
|
+
| "no_language"
|
|
74
|
+
| "same_language"
|
|
75
|
+
| "group_conflict";
|
|
76
|
+
address: string;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
kind: "language_taken" | "group_language_taken";
|
|
80
|
+
address: string;
|
|
81
|
+
language: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** A Thread already linked to this one as a translation. */
|
|
85
|
+
interface TranslationItem {
|
|
86
|
+
id: string;
|
|
87
|
+
slug: string;
|
|
88
|
+
/** What to call it — its title, or something derived when it has none. */
|
|
89
|
+
label: string;
|
|
90
|
+
language: string | null;
|
|
38
91
|
}
|
|
39
92
|
|
|
40
93
|
interface CollectionItem {
|
|
@@ -51,7 +104,16 @@ interface ThreadCollectionsResponse {
|
|
|
51
104
|
collectionIds?: string[];
|
|
52
105
|
}
|
|
53
106
|
|
|
54
|
-
type PostMenuView =
|
|
107
|
+
type PostMenuView =
|
|
108
|
+
"menu" | "collections" | "visibility" | "language" | "language-switch";
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Where the language panel puts focus, in document order rather than in the
|
|
112
|
+
* order written here — `querySelector` picks the first match on the page, so
|
|
113
|
+
* this reads as "the topmost real action in the panel", whichever it is.
|
|
114
|
+
*/
|
|
115
|
+
const LANGUAGE_PANEL_FOCUS =
|
|
116
|
+
"[data-post-menu-translation] a, [data-post-menu-translation-first], [data-post-menu-open-language-switch]";
|
|
55
117
|
|
|
56
118
|
interface MenuTriggerRect {
|
|
57
119
|
top: number;
|
|
@@ -107,11 +169,14 @@ function readPostMenuData(article: HTMLElement): PostMenuData | null {
|
|
|
107
169
|
visibility: article.dataset.postVisibility ?? "public",
|
|
108
170
|
isReply: threadId !== id || article.hasAttribute("data-post-reply"),
|
|
109
171
|
isDraft: article.hasAttribute("data-post-draft"),
|
|
172
|
+
// Carried on the Thread root, so a reply reads it from there.
|
|
173
|
+
language: threadRootArticle.dataset.postLanguage ?? null,
|
|
110
174
|
};
|
|
111
175
|
}
|
|
112
176
|
|
|
113
177
|
export class JantPostMenu extends LitElement {
|
|
114
178
|
static properties = {
|
|
179
|
+
languages: { type: Array },
|
|
115
180
|
_open: { state: true },
|
|
116
181
|
_data: { state: true },
|
|
117
182
|
_x: { state: true },
|
|
@@ -123,8 +188,12 @@ export class JantPostMenu extends LitElement {
|
|
|
123
188
|
_collectionSearch: { state: true },
|
|
124
189
|
_threadCollectionIds: { state: true },
|
|
125
190
|
_addCollectionPanelOpen: { state: true },
|
|
191
|
+
_translations: { state: true },
|
|
192
|
+
_translationsLoading: { state: true },
|
|
193
|
+
_translationBusy: { state: true },
|
|
126
194
|
};
|
|
127
195
|
|
|
196
|
+
declare languages: MenuLanguage[];
|
|
128
197
|
declare _open: boolean;
|
|
129
198
|
declare _data: PostMenuData | null;
|
|
130
199
|
declare _x: number;
|
|
@@ -136,6 +205,9 @@ export class JantPostMenu extends LitElement {
|
|
|
136
205
|
declare _collectionSearch: string;
|
|
137
206
|
declare _threadCollectionIds: string[];
|
|
138
207
|
declare _addCollectionPanelOpen: boolean;
|
|
208
|
+
declare _translations: TranslationItem[] | null;
|
|
209
|
+
declare _translationsLoading: boolean;
|
|
210
|
+
declare _translationBusy: boolean;
|
|
139
211
|
declare _triggerEl: HTMLElement | null;
|
|
140
212
|
|
|
141
213
|
/** Whether collections were modified during this session (triggers page reload on close) */
|
|
@@ -150,6 +222,7 @@ export class JantPostMenu extends LitElement {
|
|
|
150
222
|
|
|
151
223
|
constructor() {
|
|
152
224
|
super();
|
|
225
|
+
this.languages = [];
|
|
153
226
|
this._open = false;
|
|
154
227
|
this._data = null;
|
|
155
228
|
this._x = 0;
|
|
@@ -161,6 +234,9 @@ export class JantPostMenu extends LitElement {
|
|
|
161
234
|
this._collectionSearch = "";
|
|
162
235
|
this._threadCollectionIds = [];
|
|
163
236
|
this._addCollectionPanelOpen = false;
|
|
237
|
+
this._translations = null;
|
|
238
|
+
this._translationsLoading = false;
|
|
239
|
+
this._translationBusy = false;
|
|
164
240
|
this._triggerEl = null;
|
|
165
241
|
}
|
|
166
242
|
|
|
@@ -218,6 +294,12 @@ export class JantPostMenu extends LitElement {
|
|
|
218
294
|
return;
|
|
219
295
|
}
|
|
220
296
|
if (this._open) {
|
|
297
|
+
// One step back per press, not one step out: the language picker is a
|
|
298
|
+
// level deeper than the panel that opens it.
|
|
299
|
+
if (this._view === "language-switch") {
|
|
300
|
+
this.#showLanguagePanel();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
221
303
|
if (this._view !== "menu") {
|
|
222
304
|
this.#showMainMenu();
|
|
223
305
|
return;
|
|
@@ -300,11 +382,27 @@ export class JantPostMenu extends LitElement {
|
|
|
300
382
|
return;
|
|
301
383
|
}
|
|
302
384
|
|
|
303
|
-
// Clicking inside the dropdown — don't close (menu or
|
|
385
|
+
// Clicking inside the dropdown — don't close (menu or any of its panels).
|
|
386
|
+
//
|
|
387
|
+
// `.post-menu-view` — the root of whichever panel is on screen — and not
|
|
388
|
+
// the two obvious alternatives, for two different reasons.
|
|
389
|
+
//
|
|
390
|
+
// Not `[role="menu"]`: that is on the *list*, and a panel's header sits
|
|
391
|
+
// outside it, so every back button read as a click outside the menu and
|
|
392
|
+
// closed the whole thing.
|
|
393
|
+
//
|
|
394
|
+
// Not `.post-menu-panel`, the container the views render into, even though
|
|
395
|
+
// it reads as the more honest "anywhere in the dropdown". On a *real*
|
|
396
|
+
// click the browser runs a microtask checkpoint between event listeners,
|
|
397
|
+
// so a menu item that switches panels has already re-rendered by the time
|
|
398
|
+
// this handler runs: `target` is a detached node whose subtree ends at the
|
|
399
|
+
// view root, and `closest` can no longer reach the container. Clicking
|
|
400
|
+
// Language would open the panel and close the menu in the same event.
|
|
401
|
+
// (`el.click()` from a script never shows this — the stack never empties,
|
|
402
|
+
// so nothing re-renders mid-dispatch. Hence the `performUpdate()` in the
|
|
403
|
+
// test that covers it.)
|
|
304
404
|
if (this._open) {
|
|
305
|
-
const inside = target.closest?.(
|
|
306
|
-
"[role='menu'], [data-collection-picker], [data-visibility-panel]",
|
|
307
|
-
);
|
|
405
|
+
const inside = target.closest?.(".post-menu-view, .post-menu-panel");
|
|
308
406
|
if (inside) return;
|
|
309
407
|
}
|
|
310
408
|
|
|
@@ -465,6 +563,402 @@ export class JantPostMenu extends LitElement {
|
|
|
465
563
|
);
|
|
466
564
|
}
|
|
467
565
|
|
|
566
|
+
// --- Language and translations ---
|
|
567
|
+
|
|
568
|
+
/** Whether this site publishes enough languages for any of this to matter. */
|
|
569
|
+
get #multilingual(): boolean {
|
|
570
|
+
return this.languages.length > 1;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
#languageLabel(tag: string | null): string {
|
|
574
|
+
if (!tag) return "Not set";
|
|
575
|
+
return this.languages.find((l) => l.tag === tag)?.label ?? tag;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async #openLanguagePanel() {
|
|
579
|
+
if (this._data?.isReply) return;
|
|
580
|
+
this._view = "language";
|
|
581
|
+
this.#focusAfterUpdate(LANGUAGE_PANEL_FOCUS);
|
|
582
|
+
// The panel is mostly about them, and the picker one level down needs them
|
|
583
|
+
// too: a language is unavailable exactly when another post in the group
|
|
584
|
+
// already holds it.
|
|
585
|
+
await this.#loadTranslations();
|
|
586
|
+
// A Thread whose every other language is already spoken for has nothing to
|
|
587
|
+
// put on screen until this resolves, so the first attempt found nothing to
|
|
588
|
+
// land on. Anywhere else, focus is already inside and must stay put.
|
|
589
|
+
if (!this.contains(document.activeElement)) {
|
|
590
|
+
this.#focusAfterUpdate(LANGUAGE_PANEL_FOCUS);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
#showLanguagePanel(focusSelector = "[data-post-menu-open-language-switch]") {
|
|
595
|
+
this._view = "language";
|
|
596
|
+
this.#focusAfterUpdate(focusSelector);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* The one place the language picker lives.
|
|
601
|
+
*
|
|
602
|
+
* Behind a row of its own rather than at the top of the language panel:
|
|
603
|
+
* switching a Thread's language is a correction, made once if ever, while
|
|
604
|
+
* reading and adding other versions is the daily work. Left inline it also
|
|
605
|
+
* degenerated — on a two-language site whose other version is already linked,
|
|
606
|
+
* the radio group rendered a single unclickable row for the current language.
|
|
607
|
+
*/
|
|
608
|
+
#openLanguageSwitch() {
|
|
609
|
+
if (this._data?.isReply) return;
|
|
610
|
+
this._view = "language-switch";
|
|
611
|
+
this.#focusAfterUpdate(
|
|
612
|
+
"[data-post-menu-language-current='true'], [data-post-menu-language-option]",
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Split the site's languages by what this Thread can do with each.
|
|
618
|
+
*
|
|
619
|
+
* A language another version already holds is neither selectable nor free:
|
|
620
|
+
* the group allows one post per language, so it is spoken for either way.
|
|
621
|
+
*/
|
|
622
|
+
#languageChoices() {
|
|
623
|
+
const current = this._data?.language ?? null;
|
|
624
|
+
const linked = this._translations ?? [];
|
|
625
|
+
const held = new Set(
|
|
626
|
+
linked.flatMap((translation) =>
|
|
627
|
+
translation.language ? [translation.language] : [],
|
|
628
|
+
),
|
|
629
|
+
);
|
|
630
|
+
return {
|
|
631
|
+
current,
|
|
632
|
+
linked,
|
|
633
|
+
selectable: this.languages.filter(
|
|
634
|
+
(language) => language.tag === current || !held.has(language.tag),
|
|
635
|
+
),
|
|
636
|
+
free: this.languages.filter(
|
|
637
|
+
(language) => language.tag !== current && !held.has(language.tag),
|
|
638
|
+
),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Set the language of this whole Thread.
|
|
644
|
+
*
|
|
645
|
+
* Not optimistic: unlike visibility, this can be refused server-side — the
|
|
646
|
+
* Thread's translation group may already hold that language — and showing the
|
|
647
|
+
* new language before the server agrees would be a lie the author acts on.
|
|
648
|
+
*/
|
|
649
|
+
async #setLanguage(tag: string) {
|
|
650
|
+
const data = this._data;
|
|
651
|
+
if (!data || this._translationBusy) return;
|
|
652
|
+
this._translationBusy = true;
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
const response = await fetch(
|
|
656
|
+
publicPath(`/api/posts/${data.threadId}/language`),
|
|
657
|
+
{
|
|
658
|
+
method: "PUT",
|
|
659
|
+
headers: { "Content-Type": "application/json" },
|
|
660
|
+
credentials: "same-origin",
|
|
661
|
+
body: JSON.stringify({ language: tag }),
|
|
662
|
+
},
|
|
663
|
+
);
|
|
664
|
+
if (!response.ok) {
|
|
665
|
+
showToast(
|
|
666
|
+
await readErrorMessage(
|
|
667
|
+
response,
|
|
668
|
+
"Could not change the language. Try again.",
|
|
669
|
+
),
|
|
670
|
+
"error",
|
|
671
|
+
);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const article = document.querySelector<HTMLElement>(
|
|
676
|
+
`article[data-post-id="${data.threadId}"]`,
|
|
677
|
+
);
|
|
678
|
+
if (article) article.dataset.postLanguage = tag;
|
|
679
|
+
this._data = { ...data, language: tag };
|
|
680
|
+
showToast(`Thread is now in ${this.#languageLabel(tag)}.`);
|
|
681
|
+
this.#close();
|
|
682
|
+
} catch {
|
|
683
|
+
showToast("Could not change the language. Try again.", "error");
|
|
684
|
+
} finally {
|
|
685
|
+
this._translationBusy = false;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async #loadTranslations() {
|
|
690
|
+
const data = this._data;
|
|
691
|
+
if (!data) return;
|
|
692
|
+
this._translationsLoading = true;
|
|
693
|
+
try {
|
|
694
|
+
const response = await fetch(
|
|
695
|
+
publicPath(`/api/posts/${data.threadId}/translations`),
|
|
696
|
+
{ credentials: "same-origin" },
|
|
697
|
+
);
|
|
698
|
+
if (!response.ok) return;
|
|
699
|
+
const body = (await response.json()) as {
|
|
700
|
+
translations?: TranslationItem[];
|
|
701
|
+
};
|
|
702
|
+
this._translations = body.translations ?? [];
|
|
703
|
+
} catch {
|
|
704
|
+
this._translations = [];
|
|
705
|
+
} finally {
|
|
706
|
+
this._translationsLoading = false;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Open the composer on a new post that translates this one.
|
|
712
|
+
*
|
|
713
|
+
* Prefers the dialog, so the author writes the translation without leaving
|
|
714
|
+
* the post they are translating — navigating away to a blank page loses that
|
|
715
|
+
* context exactly when it is most useful. Pages that render no dialog (the
|
|
716
|
+
* standalone composer among them) fall back to the URL, which carries the
|
|
717
|
+
* same two values.
|
|
718
|
+
*/
|
|
719
|
+
async #writeTranslation(tag: string) {
|
|
720
|
+
const data = this._data;
|
|
721
|
+
if (!data) return;
|
|
722
|
+
this.#close({ restoreFocus: false });
|
|
723
|
+
|
|
724
|
+
const composeEl = await ensureComposeDialog();
|
|
725
|
+
if (composeEl) {
|
|
726
|
+
await composeEl.openTranslation(data.threadId, tag);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const params = new URLSearchParams({
|
|
731
|
+
translationOf: data.threadId,
|
|
732
|
+
lang: tag,
|
|
733
|
+
});
|
|
734
|
+
window.location.assign(publicPath(`/new?${params.toString()}`));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Ask the author which post to link, in a dialog rather than in this popover.
|
|
739
|
+
*
|
|
740
|
+
* The menu is the wrong shape for a search: results need room for a real
|
|
741
|
+
* title and a keyboard. The picker is generic — the eligibility rules stay on
|
|
742
|
+
* the server, and this only supplies the copy and the request.
|
|
743
|
+
*/
|
|
744
|
+
async #openLinkPicker() {
|
|
745
|
+
const data = this._data;
|
|
746
|
+
if (!data) return;
|
|
747
|
+
this.#close({ restoreFocus: false });
|
|
748
|
+
|
|
749
|
+
const picked = await pickPost({
|
|
750
|
+
heading: "Link a translation",
|
|
751
|
+
hint: "Only posts you could actually link are listed: published, and in a language this one's group does not already have.",
|
|
752
|
+
placeholder: "Search your posts, or paste a URL…",
|
|
753
|
+
emptyHint: "Nothing matched that you could link.",
|
|
754
|
+
search: async (query) => {
|
|
755
|
+
// An author writing a translation has the other post open in a tab, so
|
|
756
|
+
// the URL is closer to hand than the title. Resolving it also lets the
|
|
757
|
+
// answer be a reason rather than an empty list.
|
|
758
|
+
if (looksLikeAddress(query)) {
|
|
759
|
+
return this.#resolveTranslationAddress(data.threadId, query);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const response = await fetch(
|
|
763
|
+
publicPath(
|
|
764
|
+
`/api/posts/${data.threadId}/translations/candidates?q=${encodeURIComponent(query)}`,
|
|
765
|
+
),
|
|
766
|
+
{ credentials: "same-origin" },
|
|
767
|
+
);
|
|
768
|
+
if (!response.ok) return { items: [] };
|
|
769
|
+
const body = (await response.json()) as {
|
|
770
|
+
candidates?: Array<{
|
|
771
|
+
id: string;
|
|
772
|
+
label: string;
|
|
773
|
+
language: string | null;
|
|
774
|
+
}>;
|
|
775
|
+
};
|
|
776
|
+
return {
|
|
777
|
+
items: (body.candidates ?? []).map((candidate) => ({
|
|
778
|
+
id: candidate.id,
|
|
779
|
+
label: candidate.label,
|
|
780
|
+
meta: this.#languageLabel(candidate.language),
|
|
781
|
+
})),
|
|
782
|
+
};
|
|
783
|
+
},
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
if (!picked) return;
|
|
787
|
+
this._data = data;
|
|
788
|
+
await this.#linkTranslation(picked);
|
|
789
|
+
// The page renders the link too ("Also available in …"), so it has to catch
|
|
790
|
+
// up with the change the author just made.
|
|
791
|
+
window.location.reload();
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Look up the post at a pasted address, and say what is wrong with it when
|
|
796
|
+
* it cannot be linked. The rules are the server's; this only puts the answer
|
|
797
|
+
* into words.
|
|
798
|
+
*/
|
|
799
|
+
async #resolveTranslationAddress(
|
|
800
|
+
threadId: string,
|
|
801
|
+
address: string,
|
|
802
|
+
): Promise<PostPickerResult> {
|
|
803
|
+
let resolution: TranslationAddressResolution | undefined;
|
|
804
|
+
try {
|
|
805
|
+
const response = await fetch(
|
|
806
|
+
publicPath(
|
|
807
|
+
`/api/posts/${threadId}/translations/resolve?url=${encodeURIComponent(address)}`,
|
|
808
|
+
),
|
|
809
|
+
{ credentials: "same-origin" },
|
|
810
|
+
);
|
|
811
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
812
|
+
({ resolution } = (await response.json()) as {
|
|
813
|
+
resolution?: TranslationAddressResolution;
|
|
814
|
+
});
|
|
815
|
+
} catch {
|
|
816
|
+
return {
|
|
817
|
+
items: [],
|
|
818
|
+
note: "Could not check that address. Try again in a moment.",
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (!resolution) return { items: [] };
|
|
823
|
+
if (resolution.kind === "ok") {
|
|
824
|
+
return {
|
|
825
|
+
items: [
|
|
826
|
+
{
|
|
827
|
+
id: resolution.candidate.id,
|
|
828
|
+
label: resolution.candidate.label,
|
|
829
|
+
meta: this.#languageLabel(resolution.candidate.language),
|
|
830
|
+
},
|
|
831
|
+
],
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
return { items: [], note: this.#translationAddressNote(resolution) };
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
#translationAddressNote(resolution: TranslationAddressResolution): string {
|
|
839
|
+
const language = this.#languageLabel(
|
|
840
|
+
"language" in resolution ? resolution.language : null,
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
switch (resolution.kind) {
|
|
844
|
+
case "external":
|
|
845
|
+
return "That address is somewhere else. Only posts on this site can be linked.";
|
|
846
|
+
case "not_found":
|
|
847
|
+
return `Nothing at ${resolution.address}. Check the address, or search by title.`;
|
|
848
|
+
case "not_a_post":
|
|
849
|
+
return "That address is not a post. Translations link posts to posts.";
|
|
850
|
+
case "same_thread":
|
|
851
|
+
return "That is this post.";
|
|
852
|
+
case "unpublished":
|
|
853
|
+
return "That post is a draft. Publish it, then link it.";
|
|
854
|
+
case "no_language":
|
|
855
|
+
return "That post has no language yet. Set one on it first.";
|
|
856
|
+
case "same_language":
|
|
857
|
+
return `That post is written in ${this.#languageLabel(
|
|
858
|
+
this._data?.language ?? null,
|
|
859
|
+
)} too. A translation has to be in another language.`;
|
|
860
|
+
case "language_taken":
|
|
861
|
+
return `A ${language} version is already linked here. Unlink that one first.`;
|
|
862
|
+
case "group_conflict":
|
|
863
|
+
return "That post belongs to another translation group. Unlink it first.";
|
|
864
|
+
case "group_language_taken":
|
|
865
|
+
return `Its translation group already has a ${language} version.`;
|
|
866
|
+
default:
|
|
867
|
+
return "That post cannot be linked.";
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async #linkTranslation(otherId: string) {
|
|
872
|
+
const data = this._data;
|
|
873
|
+
if (!data || this._translationBusy) return;
|
|
874
|
+
this._translationBusy = true;
|
|
875
|
+
|
|
876
|
+
try {
|
|
877
|
+
const response = await fetch(
|
|
878
|
+
publicPath(`/api/posts/${data.threadId}/translations`),
|
|
879
|
+
{
|
|
880
|
+
method: "POST",
|
|
881
|
+
headers: { "Content-Type": "application/json" },
|
|
882
|
+
credentials: "same-origin",
|
|
883
|
+
body: JSON.stringify({ postId: otherId }),
|
|
884
|
+
},
|
|
885
|
+
);
|
|
886
|
+
if (!response.ok) {
|
|
887
|
+
showToast(
|
|
888
|
+
await readErrorMessage(
|
|
889
|
+
response,
|
|
890
|
+
"Could not link those posts. Try again.",
|
|
891
|
+
),
|
|
892
|
+
"error",
|
|
893
|
+
);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
await this.#loadTranslations();
|
|
897
|
+
showToast("Posts linked as translations.");
|
|
898
|
+
} catch {
|
|
899
|
+
showToast("Could not link those posts. Try again.", "error");
|
|
900
|
+
} finally {
|
|
901
|
+
this._translationBusy = false;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Take one other version out of this Thread's translation group.
|
|
907
|
+
*
|
|
908
|
+
* Unlinking is per-version rather than "leave the group": from the author's
|
|
909
|
+
* seat the group *is* the list of other versions, so removing the row they
|
|
910
|
+
* are looking at is the action they mean. A group left with a single member
|
|
911
|
+
* collapses server-side, which makes the two-post case do the obvious thing.
|
|
912
|
+
*
|
|
913
|
+
* The confirm runs after the menu closes, the way deleting a post does — a
|
|
914
|
+
* modal opened underneath a popover fights it for focus and for the backdrop
|
|
915
|
+
* click.
|
|
916
|
+
*/
|
|
917
|
+
async #unlinkTranslation(target: TranslationItem) {
|
|
918
|
+
const data = this._data;
|
|
919
|
+
if (!data || this._translationBusy) return;
|
|
920
|
+
|
|
921
|
+
const trigger = this._triggerEl;
|
|
922
|
+
this.#close({ restoreFocus: false });
|
|
923
|
+
|
|
924
|
+
const languageLabel = this.#languageLabel(target.language);
|
|
925
|
+
const confirmed = await showConfirmDialog({
|
|
926
|
+
message: `Unlink the ${languageLabel} version, “${target.label}”? Both posts stay published — they just stop pointing at each other.`,
|
|
927
|
+
confirmLabel: "Unlink",
|
|
928
|
+
cancelLabel: "Cancel",
|
|
929
|
+
tone: "danger",
|
|
930
|
+
});
|
|
931
|
+
if (!confirmed) {
|
|
932
|
+
trigger?.focus();
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
this._translationBusy = true;
|
|
937
|
+
try {
|
|
938
|
+
const response = await fetch(
|
|
939
|
+
publicPath(`/api/posts/${target.id}/translations`),
|
|
940
|
+
{ method: "DELETE", credentials: "same-origin" },
|
|
941
|
+
);
|
|
942
|
+
if (!response.ok) {
|
|
943
|
+
showToast(
|
|
944
|
+
await readErrorMessage(response, "Could not unlink. Try again."),
|
|
945
|
+
"error",
|
|
946
|
+
);
|
|
947
|
+
trigger?.focus();
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
showToast("Translation link removed.");
|
|
951
|
+
// The page renders the link too ("Also available in …"), so it has to
|
|
952
|
+
// catch up with the change the author just made.
|
|
953
|
+
window.location.reload();
|
|
954
|
+
} catch {
|
|
955
|
+
showToast("Could not unlink. Try again.", "error");
|
|
956
|
+
trigger?.focus();
|
|
957
|
+
} finally {
|
|
958
|
+
this._translationBusy = false;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
468
962
|
#close(options: { restoreFocus?: boolean } = {}) {
|
|
469
963
|
const restoreFocus =
|
|
470
964
|
options.restoreFocus ?? this.#restoreTriggerFocusOnClose;
|
|
@@ -476,6 +970,7 @@ export class JantPostMenu extends LitElement {
|
|
|
476
970
|
this._view = "menu";
|
|
477
971
|
this._addCollectionPanelOpen = false;
|
|
478
972
|
this._collectionSearch = "";
|
|
973
|
+
this._translations = null;
|
|
479
974
|
|
|
480
975
|
if (this.#collectionsDirty) {
|
|
481
976
|
this.#collectionsDirty = false;
|
|
@@ -521,12 +1016,7 @@ export class JantPostMenu extends LitElement {
|
|
|
521
1016
|
const postId = this._data.id;
|
|
522
1017
|
this.#close({ restoreFocus: false });
|
|
523
1018
|
|
|
524
|
-
const
|
|
525
|
-
"compose-dialog",
|
|
526
|
-
) as HTMLDialogElement | null;
|
|
527
|
-
const composeEl = dialog?.querySelector("jant-compose-dialog") as
|
|
528
|
-
| import("./jant-compose-dialog.js").JantComposeDialog
|
|
529
|
-
| null;
|
|
1019
|
+
const composeEl = await ensureComposeDialog();
|
|
530
1020
|
if (composeEl) {
|
|
531
1021
|
await composeEl.openEdit(postId);
|
|
532
1022
|
}
|
|
@@ -931,8 +1421,7 @@ export class JantPostMenu extends LitElement {
|
|
|
931
1421
|
if (!detail) return;
|
|
932
1422
|
|
|
933
1423
|
const formEl = this.querySelector("jant-collection-form") as
|
|
934
|
-
|
|
935
|
-
| null;
|
|
1424
|
+
(HTMLElement & { loading: boolean }) | null;
|
|
936
1425
|
if (formEl) formEl.loading = true;
|
|
937
1426
|
|
|
938
1427
|
try {
|
|
@@ -992,26 +1481,23 @@ export class JantPostMenu extends LitElement {
|
|
|
992
1481
|
}
|
|
993
1482
|
}
|
|
994
1483
|
|
|
995
|
-
/**
|
|
1484
|
+
/** Collection form labels the server rendered onto the compose dialog. */
|
|
996
1485
|
#getCollectionFormLabels() {
|
|
997
|
-
|
|
998
|
-
| import("./jant-compose-dialog.js").JantComposeDialog
|
|
999
|
-
| null;
|
|
1000
|
-
return composeEl?.labels?.collectionFormLabels ?? null;
|
|
1486
|
+
return readComposeDialogLabels()?.collectionFormLabels ?? null;
|
|
1001
1487
|
}
|
|
1002
1488
|
|
|
1003
1489
|
#getAddCollectionLabel() {
|
|
1004
|
-
|
|
1005
|
-
| import("./jant-compose-dialog.js").JantComposeDialog
|
|
1006
|
-
| null;
|
|
1007
|
-
return composeEl?.labels?.addCollection ?? "Add Collection";
|
|
1490
|
+
return readComposeDialogLabels()?.addCollection ?? "Add Collection";
|
|
1008
1491
|
}
|
|
1009
1492
|
|
|
1493
|
+
/**
|
|
1494
|
+
* The composer reads its collections from server-rendered markup, so a
|
|
1495
|
+
* collection created here would be missing from it until the next page
|
|
1496
|
+
* load. Loading the composer to refresh it is the price of staying right.
|
|
1497
|
+
*/
|
|
1010
1498
|
async #refreshComposeCollections() {
|
|
1011
|
-
const composeEl =
|
|
1012
|
-
|
|
1013
|
-
} | null;
|
|
1014
|
-
await composeEl?.refreshCollections?.();
|
|
1499
|
+
const composeEl = await ensureComposeDialog();
|
|
1500
|
+
await composeEl?.refreshCollections();
|
|
1015
1501
|
}
|
|
1016
1502
|
|
|
1017
1503
|
#getVisibilityLabel(visibility: string) {
|
|
@@ -1181,6 +1667,22 @@ export class JantPostMenu extends LitElement {
|
|
|
1181
1667
|
</svg>`;
|
|
1182
1668
|
}
|
|
1183
1669
|
|
|
1670
|
+
#iconExternal() {
|
|
1671
|
+
return html`<svg
|
|
1672
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1673
|
+
viewBox="0 0 24 24"
|
|
1674
|
+
fill="none"
|
|
1675
|
+
stroke="currentColor"
|
|
1676
|
+
stroke-width="1.9"
|
|
1677
|
+
stroke-linecap="round"
|
|
1678
|
+
stroke-linejoin="round"
|
|
1679
|
+
>
|
|
1680
|
+
<path d="M14 4h6v6" />
|
|
1681
|
+
<path d="M20 4 11 13" />
|
|
1682
|
+
<path d="M18 14v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4" />
|
|
1683
|
+
</svg>`;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1184
1686
|
// --- Render ---
|
|
1185
1687
|
|
|
1186
1688
|
#renderCollectionPicker() {
|
|
@@ -1214,116 +1716,126 @@ export class JantPostMenu extends LitElement {
|
|
|
1214
1716
|
<span>Collections</span>
|
|
1215
1717
|
</div>
|
|
1216
1718
|
</div>
|
|
1217
|
-
${
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1719
|
+
${
|
|
1720
|
+
collections.length > 0
|
|
1721
|
+
? html`<div class="post-menu-picker-search">
|
|
1722
|
+
<svg
|
|
1723
|
+
width="14"
|
|
1724
|
+
height="14"
|
|
1725
|
+
viewBox="0 0 24 24"
|
|
1726
|
+
fill="none"
|
|
1727
|
+
stroke="currentColor"
|
|
1728
|
+
stroke-width="2"
|
|
1729
|
+
stroke-linecap="round"
|
|
1730
|
+
stroke-linejoin="round"
|
|
1731
|
+
>
|
|
1732
|
+
<circle cx="11" cy="11" r="8" />
|
|
1733
|
+
<path d="m21 21-4.3-4.3" />
|
|
1734
|
+
</svg>
|
|
1735
|
+
<input
|
|
1736
|
+
type="text"
|
|
1737
|
+
placeholder="Search collections..."
|
|
1738
|
+
autocomplete="off"
|
|
1739
|
+
autocorrect="off"
|
|
1740
|
+
spellcheck="false"
|
|
1741
|
+
.value=${this._collectionSearch}
|
|
1742
|
+
@keydown=${this.#handleCollectionSearchKeydown}
|
|
1743
|
+
@input=${(e: Event) => {
|
|
1744
|
+
this._collectionSearch = (
|
|
1745
|
+
e.target as HTMLInputElement
|
|
1746
|
+
).value;
|
|
1747
|
+
}}
|
|
1748
|
+
/>
|
|
1749
|
+
</div>`
|
|
1750
|
+
: nothing
|
|
1751
|
+
}
|
|
1246
1752
|
<div
|
|
1247
1753
|
class="post-menu-picker-list"
|
|
1248
1754
|
role="listbox"
|
|
1249
1755
|
aria-multiselectable="true"
|
|
1250
1756
|
>
|
|
1251
|
-
${
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1757
|
+
${
|
|
1758
|
+
this._collectionsLoading
|
|
1759
|
+
? html`<div class="post-menu-picker-empty">Loading...</div>`
|
|
1760
|
+
: filtered.length > 0
|
|
1761
|
+
? filtered.map((c) => {
|
|
1762
|
+
const selected = this._threadCollectionIds.includes(c.id);
|
|
1763
|
+
return html`
|
|
1764
|
+
<button
|
|
1765
|
+
type="button"
|
|
1766
|
+
role="option"
|
|
1767
|
+
aria-selected=${selected ? "true" : "false"}
|
|
1768
|
+
class=${`post-menu-picker-option${
|
|
1769
|
+
selected ? " post-menu-picker-option-selected" : ""
|
|
1770
|
+
}`}
|
|
1771
|
+
@keydown=${(event: globalThis.KeyboardEvent) =>
|
|
1772
|
+
this.#handleCollectionOptionKeydown(event, c.id)}
|
|
1773
|
+
@click=${(event: globalThis.MouseEvent) =>
|
|
1774
|
+
this.#handleCollectionOptionClick(event, c.id)}
|
|
1775
|
+
>
|
|
1776
|
+
<span class="post-menu-picker-title">${c.title}</span>
|
|
1777
|
+
${
|
|
1778
|
+
selected
|
|
1779
|
+
? html`<span
|
|
1780
|
+
class="post-menu-picker-marker post-menu-picker-marker-selected"
|
|
1781
|
+
>
|
|
1782
|
+
<svg
|
|
1783
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1784
|
+
viewBox="0 0 24 24"
|
|
1785
|
+
fill="none"
|
|
1786
|
+
aria-hidden="true"
|
|
1787
|
+
>
|
|
1788
|
+
<circle
|
|
1789
|
+
cx="12"
|
|
1790
|
+
cy="12"
|
|
1791
|
+
r="10"
|
|
1792
|
+
fill="currentColor"
|
|
1793
|
+
/>
|
|
1794
|
+
<path
|
|
1795
|
+
d="M8 12.5 10.7 15.2 16.4 9.5"
|
|
1796
|
+
stroke="var(--site-page-bg)"
|
|
1797
|
+
stroke-width="2.3"
|
|
1798
|
+
stroke-linecap="round"
|
|
1799
|
+
stroke-linejoin="round"
|
|
1800
|
+
/>
|
|
1801
|
+
</svg>
|
|
1802
|
+
</span>`
|
|
1803
|
+
: html`<span
|
|
1804
|
+
class="post-menu-picker-marker post-menu-picker-marker-add"
|
|
1805
|
+
>
|
|
1806
|
+
<svg
|
|
1807
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1808
|
+
viewBox="0 0 24 24"
|
|
1809
|
+
fill="none"
|
|
1810
|
+
aria-hidden="true"
|
|
1811
|
+
>
|
|
1812
|
+
<circle
|
|
1813
|
+
cx="12"
|
|
1814
|
+
cy="12"
|
|
1815
|
+
r="9"
|
|
1816
|
+
stroke="currentColor"
|
|
1817
|
+
stroke-width="1.8"
|
|
1818
|
+
/>
|
|
1819
|
+
<path
|
|
1820
|
+
d="M12 8v8M8 12h8"
|
|
1821
|
+
stroke="currentColor"
|
|
1822
|
+
stroke-width="1.9"
|
|
1823
|
+
stroke-linecap="round"
|
|
1824
|
+
/>
|
|
1825
|
+
</svg>
|
|
1826
|
+
</span>`
|
|
1827
|
+
}
|
|
1828
|
+
</button>
|
|
1829
|
+
`;
|
|
1830
|
+
})
|
|
1831
|
+
: html`<div class="post-menu-picker-empty">
|
|
1832
|
+
${
|
|
1833
|
+
hasSearch
|
|
1834
|
+
? "No matching collections"
|
|
1835
|
+
: "No collections yet"
|
|
1836
|
+
}
|
|
1837
|
+
</div>`
|
|
1838
|
+
}
|
|
1327
1839
|
</div>
|
|
1328
1840
|
<div class="post-menu-picker-footer">
|
|
1329
1841
|
<button
|
|
@@ -1352,6 +1864,236 @@ export class JantPostMenu extends LitElement {
|
|
|
1352
1864
|
`;
|
|
1353
1865
|
}
|
|
1354
1866
|
|
|
1867
|
+
/**
|
|
1868
|
+
* Everything about this Thread's language, in one place.
|
|
1869
|
+
*
|
|
1870
|
+
* The picker and the translation list belong together: a language is
|
|
1871
|
+
* unavailable here precisely because another post in the group already holds
|
|
1872
|
+
* it, and that reason is only legible next to the list of those posts. Two
|
|
1873
|
+
* top-level menu entries for one subject was one too many.
|
|
1874
|
+
*
|
|
1875
|
+
* Top to bottom, the panel answers three questions in the order they get
|
|
1876
|
+
* asked. *What language is this?* — the entry that opened this panel already
|
|
1877
|
+
* said "Language · 简体中文", so the panel opens by confirming it, and the
|
|
1878
|
+
* picker itself is one level down (see `#openLanguageSwitch`) because
|
|
1879
|
+
* changing it is a correction, made once if ever. *What can I do?* — writing
|
|
1880
|
+
* or linking a version, the recurring work, and self-describing enough to
|
|
1881
|
+
* need no section label over it. *What is already there?* — the other
|
|
1882
|
+
* versions, last, because they arrive with a fetch: any earlier and the
|
|
1883
|
+
* panel would shove itself downward under the author's cursor a moment
|
|
1884
|
+
* after opening.
|
|
1885
|
+
*/
|
|
1886
|
+
#renderLanguagePanel() {
|
|
1887
|
+
if (!this._data || this._data.isReply) return nothing;
|
|
1888
|
+
|
|
1889
|
+
const { current, linked, free } = this.#languageChoices();
|
|
1890
|
+
return html`
|
|
1891
|
+
<div class="post-menu-view post-menu-language-panel">
|
|
1892
|
+
<div class="post-menu-panel-header">
|
|
1893
|
+
<button
|
|
1894
|
+
type="button"
|
|
1895
|
+
class="post-menu-panel-back"
|
|
1896
|
+
aria-label="Back"
|
|
1897
|
+
@click=${() => this.#showMainMenu("[data-post-menu-open-language]")}
|
|
1898
|
+
>
|
|
1899
|
+
${this.#iconChevronLeft()}
|
|
1900
|
+
</button>
|
|
1901
|
+
<div class="post-menu-panel-heading">
|
|
1902
|
+
<span>Language</span>
|
|
1903
|
+
</div>
|
|
1904
|
+
</div>
|
|
1905
|
+
<div role="menu" class="post-menu-list">
|
|
1906
|
+
<!-- Both sections stand or fall together: with every other language
|
|
1907
|
+
spoken for there is nothing to switch to and nothing to add. -->
|
|
1908
|
+
${
|
|
1909
|
+
free.length > 0
|
|
1910
|
+
? html`
|
|
1911
|
+
<div class="post-menu-section">
|
|
1912
|
+
<button
|
|
1913
|
+
type="button"
|
|
1914
|
+
role="menuitem"
|
|
1915
|
+
class="post-menu-item"
|
|
1916
|
+
data-post-menu-open-language-switch
|
|
1917
|
+
?disabled=${this._translationBusy}
|
|
1918
|
+
@click=${() => this.#openLanguageSwitch()}
|
|
1919
|
+
>
|
|
1920
|
+
<span class="post-menu-item-label">Change language</span>
|
|
1921
|
+
<span class="post-menu-item-meta"
|
|
1922
|
+
>${this.#languageLabel(current)}</span
|
|
1923
|
+
>
|
|
1924
|
+
<span
|
|
1925
|
+
class="post-menu-item-trailing post-menu-item-chevron"
|
|
1926
|
+
>${this.#iconChevronRight()}</span
|
|
1927
|
+
>
|
|
1928
|
+
</button>
|
|
1929
|
+
</div>
|
|
1930
|
+
<div class="post-menu-section">
|
|
1931
|
+
${free.map(
|
|
1932
|
+
(language, index) => html`
|
|
1933
|
+
<button
|
|
1934
|
+
type="button"
|
|
1935
|
+
role="menuitem"
|
|
1936
|
+
class="post-menu-item"
|
|
1937
|
+
?data-post-menu-translation-first=${index === 0}
|
|
1938
|
+
@click=${() => this.#writeTranslation(language.tag)}
|
|
1939
|
+
>
|
|
1940
|
+
<span class="post-menu-item-label"
|
|
1941
|
+
>Write the ${language.label} version</span
|
|
1942
|
+
>
|
|
1943
|
+
<span
|
|
1944
|
+
class="post-menu-item-trailing post-menu-item-chevron"
|
|
1945
|
+
>${this.#iconChevronRight()}</span
|
|
1946
|
+
>
|
|
1947
|
+
</button>
|
|
1948
|
+
`,
|
|
1949
|
+
)}
|
|
1950
|
+
<button
|
|
1951
|
+
type="button"
|
|
1952
|
+
role="menuitem"
|
|
1953
|
+
class="post-menu-item"
|
|
1954
|
+
@click=${() => this.#openLinkPicker()}
|
|
1955
|
+
>
|
|
1956
|
+
<span class="post-menu-item-label"
|
|
1957
|
+
>Link a version you already wrote</span
|
|
1958
|
+
>
|
|
1959
|
+
<span
|
|
1960
|
+
class="post-menu-item-trailing post-menu-item-chevron"
|
|
1961
|
+
>${this.#iconChevronRight()}</span
|
|
1962
|
+
>
|
|
1963
|
+
</button>
|
|
1964
|
+
</div>
|
|
1965
|
+
`
|
|
1966
|
+
: nothing
|
|
1967
|
+
}
|
|
1968
|
+
${
|
|
1969
|
+
linked.length > 0
|
|
1970
|
+
? html`
|
|
1971
|
+
<div class="post-menu-section">
|
|
1972
|
+
<p class="post-menu-section-label">Other versions</p>
|
|
1973
|
+
${linked.map((translation) => {
|
|
1974
|
+
const languageLabel = this.#languageLabel(
|
|
1975
|
+
translation.language,
|
|
1976
|
+
);
|
|
1977
|
+
return html`
|
|
1978
|
+
<div class="post-menu-row" data-post-menu-translation>
|
|
1979
|
+
<a
|
|
1980
|
+
role="menuitem"
|
|
1981
|
+
class="post-menu-item post-menu-row-main"
|
|
1982
|
+
href=${publicPath(`/${translation.slug}`)}
|
|
1983
|
+
target="_blank"
|
|
1984
|
+
rel="noopener noreferrer"
|
|
1985
|
+
title=${translation.label}
|
|
1986
|
+
aria-label=${`Open the ${languageLabel} version, ${translation.label}, in a new tab`}
|
|
1987
|
+
>
|
|
1988
|
+
<span
|
|
1989
|
+
class="post-menu-item-label"
|
|
1990
|
+
lang=${translation.language ?? nothing}
|
|
1991
|
+
>${languageLabel}</span
|
|
1992
|
+
>
|
|
1993
|
+
<span
|
|
1994
|
+
class="post-menu-item-trailing post-menu-item-chevron"
|
|
1995
|
+
>${this.#iconExternal()}</span
|
|
1996
|
+
>
|
|
1997
|
+
</a>
|
|
1998
|
+
<button
|
|
1999
|
+
type="button"
|
|
2000
|
+
role="menuitem"
|
|
2001
|
+
class="post-menu-row-action post-menu-row-action-danger"
|
|
2002
|
+
data-post-menu-translation-unlink
|
|
2003
|
+
?disabled=${this._translationBusy}
|
|
2004
|
+
aria-label=${`Unlink the ${languageLabel} version`}
|
|
2005
|
+
@click=${() => this.#unlinkTranslation(translation)}
|
|
2006
|
+
>
|
|
2007
|
+
Unlink
|
|
2008
|
+
</button>
|
|
2009
|
+
</div>
|
|
2010
|
+
`;
|
|
2011
|
+
})}
|
|
2012
|
+
</div>
|
|
2013
|
+
`
|
|
2014
|
+
: nothing
|
|
2015
|
+
}
|
|
2016
|
+
${
|
|
2017
|
+
this._translationsLoading
|
|
2018
|
+
? html`<p class="post-menu-hint">Loading…</p>`
|
|
2019
|
+
: nothing
|
|
2020
|
+
}
|
|
2021
|
+
</div>
|
|
2022
|
+
</div>
|
|
2023
|
+
`;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
/**
|
|
2027
|
+
* Which language this Thread is written in.
|
|
2028
|
+
*
|
|
2029
|
+
* A language another version already holds is left out entirely rather than
|
|
2030
|
+
* shown greyed out: the author cannot switch to it, and a dead row saying
|
|
2031
|
+
* "Taken" answers a question nobody asked. The version holding it is one
|
|
2032
|
+
* level up under *Other versions*, where reading it and unlinking it are one
|
|
2033
|
+
* click each.
|
|
2034
|
+
*/
|
|
2035
|
+
#renderLanguageSwitchPanel() {
|
|
2036
|
+
if (!this._data || this._data.isReply) return nothing;
|
|
2037
|
+
|
|
2038
|
+
const { current, selectable } = this.#languageChoices();
|
|
2039
|
+
return html`
|
|
2040
|
+
<div
|
|
2041
|
+
class="post-menu-view post-menu-language-panel post-menu-language-switch-panel"
|
|
2042
|
+
>
|
|
2043
|
+
<div class="post-menu-panel-header">
|
|
2044
|
+
<button
|
|
2045
|
+
type="button"
|
|
2046
|
+
class="post-menu-panel-back"
|
|
2047
|
+
aria-label="Back"
|
|
2048
|
+
@click=${() => this.#showLanguagePanel()}
|
|
2049
|
+
>
|
|
2050
|
+
${this.#iconChevronLeft()}
|
|
2051
|
+
</button>
|
|
2052
|
+
<div class="post-menu-panel-heading">
|
|
2053
|
+
<span>Change language</span>
|
|
2054
|
+
</div>
|
|
2055
|
+
</div>
|
|
2056
|
+
<div role="menu" class="post-menu-list">
|
|
2057
|
+
<div
|
|
2058
|
+
class="post-menu-section"
|
|
2059
|
+
role="radiogroup"
|
|
2060
|
+
aria-label="Language"
|
|
2061
|
+
>
|
|
2062
|
+
${selectable.map((language) => {
|
|
2063
|
+
const selected = language.tag === current;
|
|
2064
|
+
return html`
|
|
2065
|
+
<button
|
|
2066
|
+
type="button"
|
|
2067
|
+
role="menuitemradio"
|
|
2068
|
+
lang=${language.tag}
|
|
2069
|
+
aria-checked=${selected ? "true" : "false"}
|
|
2070
|
+
?disabled=${this._translationBusy}
|
|
2071
|
+
data-post-menu-language-option
|
|
2072
|
+
data-post-menu-language-current=${selected ? "true" : "false"}
|
|
2073
|
+
class=${`post-menu-item${selected ? " post-menu-item-active" : ""}`}
|
|
2074
|
+
@click=${() =>
|
|
2075
|
+
selected
|
|
2076
|
+
? this.#showLanguagePanel()
|
|
2077
|
+
: this.#setLanguage(language.tag)}
|
|
2078
|
+
>
|
|
2079
|
+
<span class="post-menu-item-label">${language.label}</span>
|
|
2080
|
+
${
|
|
2081
|
+
selected
|
|
2082
|
+
? html`<span
|
|
2083
|
+
class="post-menu-item-trailing post-menu-item-check"
|
|
2084
|
+
>${this.#iconCheck()}</span
|
|
2085
|
+
>`
|
|
2086
|
+
: nothing
|
|
2087
|
+
}
|
|
2088
|
+
</button>
|
|
2089
|
+
`;
|
|
2090
|
+
})}
|
|
2091
|
+
</div>
|
|
2092
|
+
</div>
|
|
2093
|
+
</div>
|
|
2094
|
+
`;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
1355
2097
|
#renderVisibilityPanel() {
|
|
1356
2098
|
if (!this._data || this._data.isReply) return nothing;
|
|
1357
2099
|
|
|
@@ -1382,9 +2124,9 @@ export class JantPostMenu extends LitElement {
|
|
|
1382
2124
|
role="menuitemradio"
|
|
1383
2125
|
aria-checked=${visibility === "public" ? "true" : "false"}
|
|
1384
2126
|
data-post-menu-visibility-option
|
|
1385
|
-
data-post-menu-visibility-current=${
|
|
1386
|
-
? "true"
|
|
1387
|
-
|
|
2127
|
+
data-post-menu-visibility-current=${
|
|
2128
|
+
visibility === "public" ? "true" : "false"
|
|
2129
|
+
}
|
|
1388
2130
|
class=${`post-menu-item${
|
|
1389
2131
|
visibility === "public" ? " post-menu-item-active" : ""
|
|
1390
2132
|
}`}
|
|
@@ -1394,12 +2136,14 @@ export class JantPostMenu extends LitElement {
|
|
|
1394
2136
|
: this.#setVisibility("public")}
|
|
1395
2137
|
>
|
|
1396
2138
|
<span class="post-menu-item-label">Public</span>
|
|
1397
|
-
${
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
2139
|
+
${
|
|
2140
|
+
visibility === "public"
|
|
2141
|
+
? html`<span
|
|
2142
|
+
class="post-menu-item-trailing post-menu-item-check"
|
|
2143
|
+
>${this.#iconCheck()}</span
|
|
2144
|
+
>`
|
|
2145
|
+
: nothing
|
|
2146
|
+
}
|
|
1403
2147
|
</button>
|
|
1404
2148
|
|
|
1405
2149
|
<button
|
|
@@ -1407,9 +2151,9 @@ export class JantPostMenu extends LitElement {
|
|
|
1407
2151
|
role="menuitemradio"
|
|
1408
2152
|
aria-checked=${visibility === "latest_hidden" ? "true" : "false"}
|
|
1409
2153
|
data-post-menu-visibility-option
|
|
1410
|
-
data-post-menu-visibility-current=${
|
|
1411
|
-
? "true"
|
|
1412
|
-
|
|
2154
|
+
data-post-menu-visibility-current=${
|
|
2155
|
+
visibility === "latest_hidden" ? "true" : "false"
|
|
2156
|
+
}
|
|
1413
2157
|
class=${`post-menu-item${
|
|
1414
2158
|
visibility === "latest_hidden" ? " post-menu-item-active" : ""
|
|
1415
2159
|
}`}
|
|
@@ -1419,12 +2163,14 @@ export class JantPostMenu extends LitElement {
|
|
|
1419
2163
|
: this.#setVisibility("latest_hidden")}
|
|
1420
2164
|
>
|
|
1421
2165
|
<span class="post-menu-item-label">Hidden from Latest</span>
|
|
1422
|
-
${
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2166
|
+
${
|
|
2167
|
+
visibility === "latest_hidden"
|
|
2168
|
+
? html`<span
|
|
2169
|
+
class="post-menu-item-trailing post-menu-item-check"
|
|
2170
|
+
>${this.#iconCheck()}</span
|
|
2171
|
+
>`
|
|
2172
|
+
: nothing
|
|
2173
|
+
}
|
|
1428
2174
|
</button>
|
|
1429
2175
|
|
|
1430
2176
|
<button
|
|
@@ -1432,9 +2178,9 @@ export class JantPostMenu extends LitElement {
|
|
|
1432
2178
|
role="menuitemradio"
|
|
1433
2179
|
aria-checked=${visibility === "private" ? "true" : "false"}
|
|
1434
2180
|
data-post-menu-visibility-option
|
|
1435
|
-
data-post-menu-visibility-current=${
|
|
1436
|
-
? "true"
|
|
1437
|
-
|
|
2181
|
+
data-post-menu-visibility-current=${
|
|
2182
|
+
visibility === "private" ? "true" : "false"
|
|
2183
|
+
}
|
|
1438
2184
|
class=${`post-menu-item${
|
|
1439
2185
|
visibility === "private" ? " post-menu-item-active" : ""
|
|
1440
2186
|
}`}
|
|
@@ -1444,12 +2190,14 @@ export class JantPostMenu extends LitElement {
|
|
|
1444
2190
|
: this.#setVisibility("private")}
|
|
1445
2191
|
>
|
|
1446
2192
|
<span class="post-menu-item-label">Private</span>
|
|
1447
|
-
${
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
2193
|
+
${
|
|
2194
|
+
visibility === "private"
|
|
2195
|
+
? html`<span
|
|
2196
|
+
class="post-menu-item-trailing post-menu-item-check"
|
|
2197
|
+
>${this.#iconCheck()}</span
|
|
2198
|
+
>`
|
|
2199
|
+
: nothing
|
|
2200
|
+
}
|
|
1453
2201
|
</button>
|
|
1454
2202
|
</div>
|
|
1455
2203
|
</div>
|
|
@@ -1551,118 +2299,164 @@ export class JantPostMenu extends LitElement {
|
|
|
1551
2299
|
<span class="post-menu-item-trailing">${this.#iconEdit()}</span>
|
|
1552
2300
|
</button>
|
|
1553
2301
|
|
|
1554
|
-
${
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
<span class="post-menu-item-label">Publish</span>
|
|
1563
|
-
<span class="post-menu-item-trailing"
|
|
1564
|
-
>${this.#iconPublish()}</span
|
|
2302
|
+
${
|
|
2303
|
+
this._data.isDraft
|
|
2304
|
+
? html`
|
|
2305
|
+
<button
|
|
2306
|
+
type="button"
|
|
2307
|
+
role="menuitem"
|
|
2308
|
+
class="post-menu-item"
|
|
2309
|
+
@click=${() => this.#publish()}
|
|
1565
2310
|
>
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
2311
|
+
<span class="post-menu-item-label">Publish</span>
|
|
2312
|
+
<span class="post-menu-item-trailing"
|
|
2313
|
+
>${this.#iconPublish()}</span
|
|
2314
|
+
>
|
|
2315
|
+
</button>
|
|
2316
|
+
`
|
|
2317
|
+
: nothing
|
|
2318
|
+
}
|
|
2319
|
+
${
|
|
2320
|
+
this._data.isReply
|
|
2321
|
+
? nothing
|
|
2322
|
+
: html`
|
|
2323
|
+
<button
|
|
2324
|
+
type="button"
|
|
2325
|
+
role="menuitem"
|
|
2326
|
+
class="post-menu-item"
|
|
2327
|
+
data-post-menu-open-collections
|
|
2328
|
+
@click=${() => this.#openCollectionPicker()}
|
|
1582
2329
|
>
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
2330
|
+
<span class="post-menu-item-label"
|
|
2331
|
+
>Add to collection</span
|
|
2332
|
+
>
|
|
2333
|
+
<span
|
|
2334
|
+
class="post-menu-item-trailing post-menu-item-chevron"
|
|
2335
|
+
>${this.#iconChevronRight()}</span
|
|
2336
|
+
>
|
|
2337
|
+
</button>
|
|
2338
|
+
`
|
|
2339
|
+
}
|
|
2340
|
+
${
|
|
2341
|
+
this._data.isReply
|
|
2342
|
+
? nothing
|
|
2343
|
+
: html`
|
|
2344
|
+
<button
|
|
2345
|
+
type="button"
|
|
2346
|
+
role="menuitem"
|
|
2347
|
+
class="post-menu-item"
|
|
2348
|
+
data-post-menu-open-visibility
|
|
2349
|
+
@click=${() => this.#openVisibilityPanel()}
|
|
1598
2350
|
>
|
|
1599
|
-
|
|
1600
|
-
|
|
2351
|
+
<span class="post-menu-item-label">Visibility</span>
|
|
2352
|
+
<span class="post-menu-item-meta"
|
|
2353
|
+
>${this.#getVisibilityLabel(visibility)}</span
|
|
2354
|
+
>
|
|
2355
|
+
<span
|
|
2356
|
+
class="post-menu-item-trailing post-menu-item-chevron"
|
|
2357
|
+
>${this.#iconChevronRight()}</span
|
|
2358
|
+
>
|
|
2359
|
+
</button>
|
|
2360
|
+
`
|
|
2361
|
+
}
|
|
2362
|
+
${
|
|
2363
|
+
this._data.isReply || !this.#multilingual
|
|
2364
|
+
? nothing
|
|
2365
|
+
: html`
|
|
2366
|
+
<button
|
|
2367
|
+
type="button"
|
|
2368
|
+
role="menuitem"
|
|
2369
|
+
class="post-menu-item"
|
|
2370
|
+
data-post-menu-open-language
|
|
2371
|
+
@click=${() => this.#openLanguagePanel()}
|
|
1601
2372
|
>
|
|
1602
|
-
|
|
1603
|
-
|
|
2373
|
+
<span class="post-menu-item-label">Language</span>
|
|
2374
|
+
<span class="post-menu-item-meta"
|
|
2375
|
+
>${this.#languageLabel(this._data.language)}</span
|
|
2376
|
+
>
|
|
2377
|
+
<span
|
|
2378
|
+
class="post-menu-item-trailing post-menu-item-chevron"
|
|
2379
|
+
>${this.#iconChevronRight()}</span
|
|
2380
|
+
>
|
|
2381
|
+
</button>
|
|
2382
|
+
`
|
|
2383
|
+
}
|
|
1604
2384
|
</div>
|
|
1605
2385
|
|
|
1606
2386
|
<div class="post-menu-section">
|
|
1607
|
-
${
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
<span class="post-menu-item-label"
|
|
1617
|
-
>${isFeatured
|
|
1618
|
-
? "Remove from Featured"
|
|
1619
|
-
: "Add to Featured"}</span
|
|
1620
|
-
>
|
|
1621
|
-
<span class="post-menu-item-trailing"
|
|
1622
|
-
>${isFeatured
|
|
1623
|
-
? this.#iconFeaturedOff()
|
|
1624
|
-
: this.#iconFeatured()}</span
|
|
1625
|
-
>
|
|
1626
|
-
</button>
|
|
1627
|
-
`}
|
|
1628
|
-
${this._data.isReply
|
|
1629
|
-
? nothing
|
|
1630
|
-
: html`
|
|
1631
|
-
<button
|
|
1632
|
-
type="button"
|
|
1633
|
-
role="menuitem"
|
|
1634
|
-
class="post-menu-item"
|
|
1635
|
-
@click=${() => this.#togglePin()}
|
|
1636
|
-
>
|
|
1637
|
-
<span class="post-menu-item-label"
|
|
1638
|
-
>${isPinned ? "Unpin" : "Pin this post"}</span
|
|
1639
|
-
>
|
|
1640
|
-
<span class="post-menu-item-trailing"
|
|
1641
|
-
>${isPinned ? this.#iconPinOff() : this.#iconPin()}</span
|
|
2387
|
+
${
|
|
2388
|
+
this._data.isDraft
|
|
2389
|
+
? nothing
|
|
2390
|
+
: html`
|
|
2391
|
+
<button
|
|
2392
|
+
type="button"
|
|
2393
|
+
role="menuitem"
|
|
2394
|
+
class="post-menu-item"
|
|
2395
|
+
@click=${() => this.#setFeatured(!isFeatured)}
|
|
1642
2396
|
>
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
2397
|
+
<span class="post-menu-item-label"
|
|
2398
|
+
>${
|
|
2399
|
+
isFeatured
|
|
2400
|
+
? "Remove from Featured"
|
|
2401
|
+
: "Add to Featured"
|
|
2402
|
+
}</span
|
|
2403
|
+
>
|
|
2404
|
+
<span class="post-menu-item-trailing"
|
|
2405
|
+
>${
|
|
2406
|
+
isFeatured
|
|
2407
|
+
? this.#iconFeaturedOff()
|
|
2408
|
+
: this.#iconFeatured()
|
|
2409
|
+
}</span
|
|
2410
|
+
>
|
|
2411
|
+
</button>
|
|
2412
|
+
`
|
|
2413
|
+
}
|
|
2414
|
+
${
|
|
2415
|
+
this._data.isReply
|
|
2416
|
+
? nothing
|
|
2417
|
+
: html`
|
|
2418
|
+
<button
|
|
2419
|
+
type="button"
|
|
2420
|
+
role="menuitem"
|
|
2421
|
+
class="post-menu-item"
|
|
2422
|
+
@click=${() => this.#togglePin()}
|
|
1657
2423
|
>
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
2424
|
+
<span class="post-menu-item-label"
|
|
2425
|
+
>${isPinned ? "Unpin" : "Pin this post"}</span
|
|
2426
|
+
>
|
|
2427
|
+
<span class="post-menu-item-trailing"
|
|
2428
|
+
>${isPinned ? this.#iconPinOff() : this.#iconPin()}</span
|
|
2429
|
+
>
|
|
2430
|
+
</button>
|
|
2431
|
+
`
|
|
2432
|
+
}
|
|
2433
|
+
${
|
|
2434
|
+
collectionId && !this._data.isReply
|
|
2435
|
+
? html`
|
|
2436
|
+
<button
|
|
2437
|
+
type="button"
|
|
2438
|
+
role="menuitem"
|
|
2439
|
+
class="post-menu-item"
|
|
2440
|
+
@click=${() => this.#toggleCollectionPin()}
|
|
1662
2441
|
>
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
2442
|
+
<span class="post-menu-item-label"
|
|
2443
|
+
>${
|
|
2444
|
+
isPinnedInCollection
|
|
2445
|
+
? "Unpin from collection"
|
|
2446
|
+
: "Pin in collection"
|
|
2447
|
+
}</span
|
|
2448
|
+
>
|
|
2449
|
+
<span class="post-menu-item-trailing"
|
|
2450
|
+
>${
|
|
2451
|
+
isPinnedInCollection
|
|
2452
|
+
? this.#iconPinOff()
|
|
2453
|
+
: this.#iconPin()
|
|
2454
|
+
}</span
|
|
2455
|
+
>
|
|
2456
|
+
</button>
|
|
2457
|
+
`
|
|
2458
|
+
: nothing
|
|
2459
|
+
}
|
|
1666
2460
|
</div>
|
|
1667
2461
|
|
|
1668
2462
|
<div class="post-menu-section post-menu-section-danger">
|
|
@@ -1693,30 +2487,40 @@ export class JantPostMenu extends LitElement {
|
|
|
1693
2487
|
const showMenuSurface = !this._addCollectionPanelOpen;
|
|
1694
2488
|
|
|
1695
2489
|
return html`
|
|
1696
|
-
${
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
class="post-menu-backdrop"
|
|
1700
|
-
@click=${() => this.#close({ restoreFocus: false })}
|
|
1701
|
-
></div>
|
|
1702
|
-
<div class="dropdown-menu" style=${wrapperStyle}>
|
|
2490
|
+
${
|
|
2491
|
+
showMenuSurface
|
|
2492
|
+
? html`
|
|
1703
2493
|
<div
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
>
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2494
|
+
class="post-menu-backdrop"
|
|
2495
|
+
@click=${() => this.#close({ restoreFocus: false })}
|
|
2496
|
+
></div>
|
|
2497
|
+
<div class="dropdown-menu" style=${wrapperStyle}>
|
|
2498
|
+
<div
|
|
2499
|
+
data-popover
|
|
2500
|
+
aria-hidden="false"
|
|
2501
|
+
class="!static post-menu-panel"
|
|
2502
|
+
>
|
|
2503
|
+
${
|
|
2504
|
+
this._view === "collections"
|
|
2505
|
+
? this.#renderCollectionPicker()
|
|
2506
|
+
: this._view === "visibility"
|
|
2507
|
+
? this.#renderVisibilityPanel()
|
|
2508
|
+
: this._view === "language"
|
|
2509
|
+
? this.#renderLanguagePanel()
|
|
2510
|
+
: this._view === "language-switch"
|
|
2511
|
+
? this.#renderLanguageSwitchPanel()
|
|
2512
|
+
: this.#renderMenu()
|
|
2513
|
+
}
|
|
2514
|
+
</div>
|
|
1713
2515
|
</div>
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
${
|
|
1718
|
-
|
|
1719
|
-
|
|
2516
|
+
`
|
|
2517
|
+
: nothing
|
|
2518
|
+
}
|
|
2519
|
+
${
|
|
2520
|
+
this._addCollectionPanelOpen
|
|
2521
|
+
? this.#renderAddCollectionPanel()
|
|
2522
|
+
: nothing
|
|
2523
|
+
}
|
|
1720
2524
|
`;
|
|
1721
2525
|
}
|
|
1722
2526
|
}
|