@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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* General Settings Component
|
|
3
3
|
*
|
|
4
|
-
* Main container for the General settings page.
|
|
5
|
-
*
|
|
4
|
+
* Main container for the General settings page. Typed fields are grouped
|
|
5
|
+
* behind a Save button and track their dirty state independently; every
|
|
6
|
+
* control whose value is complete the moment it is set — checkbox, radio,
|
|
7
|
+
* select — saves on change.
|
|
6
8
|
*
|
|
7
9
|
* Light DOM only — BaseCoat and Tailwind classes apply directly.
|
|
8
10
|
*/
|
|
@@ -11,40 +13,55 @@ import { LitElement, html, nothing } from "lit";
|
|
|
11
13
|
import type { Editor } from "@tiptap/core";
|
|
12
14
|
import { MAX_SITE_NAME_LENGTH } from "../../types.js";
|
|
13
15
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type
|
|
17
|
-
} from "../../
|
|
16
|
+
discoverIntroRuns,
|
|
17
|
+
resolveDiscoverMode,
|
|
18
|
+
type DiscoverPageUrls,
|
|
19
|
+
} from "../../lib/discover.js";
|
|
18
20
|
import type {
|
|
19
21
|
SettingsInitialData,
|
|
20
22
|
SettingsLabels,
|
|
21
23
|
SettingsTimezone,
|
|
22
|
-
SettingsCjkFont,
|
|
23
|
-
SettingsDashboardLanguage,
|
|
24
24
|
SettingsAboutPageStatus,
|
|
25
25
|
} from "./settings-types.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
COPY_FIELD_BUTTON_CLASS,
|
|
28
|
+
COPY_FIELD_CLASS,
|
|
29
|
+
COPY_FIELD_CONTROL_CLASS,
|
|
30
|
+
COPY_FIELD_INPUT_CLASS,
|
|
31
|
+
} from "../../lib/copy-field.js";
|
|
27
32
|
import {
|
|
28
33
|
createSettingsEditor,
|
|
29
34
|
jsonToMarkdown,
|
|
30
35
|
} from "../tiptap/create-editor.js";
|
|
31
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Spacing every section shares, whatever its position.
|
|
39
|
+
*
|
|
40
|
+
* The container's `divide-y` draws the rule under each section but the last;
|
|
41
|
+
* this pads both sides of each rule and drops the padding at either end. The
|
|
42
|
+
* gap between a section's own children is left to the section.
|
|
43
|
+
*/
|
|
44
|
+
const SECTION_CLASS = "flex flex-col py-8 first:pt-0 last:pb-0";
|
|
45
|
+
|
|
32
46
|
export class JantSettingsGeneral extends LitElement {
|
|
33
47
|
static properties = {
|
|
34
48
|
labels: { type: Object },
|
|
35
49
|
timezones: { type: Array },
|
|
36
|
-
cjkFonts: { type: Array, attribute: "cjk-fonts" },
|
|
37
50
|
siteNameFallback: { type: String, attribute: "sitename-fallback" },
|
|
38
51
|
siteDescriptionFallback: {
|
|
39
52
|
type: String,
|
|
40
53
|
attribute: "sitedescription-fallback",
|
|
41
54
|
},
|
|
42
|
-
dashboardLanguages: { type: Array, attribute: "dashboard-languages" },
|
|
43
55
|
demoMode: { type: Boolean, attribute: "demo-mode" },
|
|
56
|
+
discoverDefault: { type: String, attribute: "discover-default" },
|
|
57
|
+
discoverPages: { type: Object, attribute: "discover-pages" },
|
|
58
|
+
discoverStatus: { type: String, attribute: "discover-status" },
|
|
59
|
+
feedsEnabled: { type: Boolean, attribute: "feeds-enabled" },
|
|
44
60
|
mainFeedUrl: { type: String, attribute: "main-feed-url" },
|
|
45
61
|
latestFeedUrl: { type: String, attribute: "latest-feed-url" },
|
|
46
62
|
featuredFeedUrl: { type: String, attribute: "featured-feed-url" },
|
|
47
63
|
archiveFeedUrl: { type: String, attribute: "archive-feed-url" },
|
|
64
|
+
feedsDocsUrl: { type: String, attribute: "feeds-docs-url" },
|
|
48
65
|
aboutPage: { type: Object, attribute: "about-page" },
|
|
49
66
|
aboutEditUrl: { type: String, attribute: "about-edit-url" },
|
|
50
67
|
aboutCreateUrl: {
|
|
@@ -60,12 +77,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
60
77
|
_siteDirty: { state: true },
|
|
61
78
|
_siteLoading: { state: true },
|
|
62
79
|
|
|
63
|
-
//
|
|
64
|
-
_siteLanguage: { state: true },
|
|
65
|
-
_dashboardLanguage: { state: true },
|
|
66
|
-
_localeOpen: { state: true },
|
|
67
|
-
_localeQuery: { state: true },
|
|
68
|
-
_cjkSerifFont: { state: true },
|
|
80
|
+
// Time group
|
|
69
81
|
_timeZone: { state: true },
|
|
70
82
|
_origLocale: { state: true },
|
|
71
83
|
_localeDirty: { state: true },
|
|
@@ -74,7 +86,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
74
86
|
// Feed group
|
|
75
87
|
_mainRssFeed: { state: true },
|
|
76
88
|
_origMainRssFeed: { state: true },
|
|
77
|
-
_feedDirty: { state: true },
|
|
78
89
|
_feedLoading: { state: true },
|
|
79
90
|
|
|
80
91
|
// Home auto-save
|
|
@@ -86,19 +97,40 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
86
97
|
_noindex: { state: true },
|
|
87
98
|
_origNoindex: { state: true },
|
|
88
99
|
_searchLoading: { state: true },
|
|
100
|
+
|
|
101
|
+
// Discover group
|
|
102
|
+
_discover: { state: true },
|
|
103
|
+
_origDiscover: { state: true },
|
|
104
|
+
_discoverLoading: { state: true },
|
|
89
105
|
};
|
|
90
106
|
|
|
91
107
|
declare labels: SettingsLabels;
|
|
92
108
|
declare timezones: SettingsTimezone[];
|
|
93
|
-
declare cjkFonts: SettingsCjkFont[];
|
|
94
|
-
declare dashboardLanguages: SettingsDashboardLanguage[];
|
|
95
109
|
declare siteNameFallback: string;
|
|
96
110
|
declare siteDescriptionFallback: string;
|
|
97
111
|
declare demoMode: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The deployment's own answer, unresolved: `""` when it has none.
|
|
114
|
+
*
|
|
115
|
+
* What the site actually declares is derived from this and from the controls
|
|
116
|
+
* around it — see `_effectiveDiscoverMode`.
|
|
117
|
+
*/
|
|
118
|
+
declare discoverDefault: string;
|
|
119
|
+
/** The directory's own pages, or `null` when none is configured. */
|
|
120
|
+
declare discoverPages: DiscoverPageUrls | null;
|
|
121
|
+
/**
|
|
122
|
+
* JSON status block, already translated by the server.
|
|
123
|
+
*
|
|
124
|
+
* The sentences carry runtime numbers, so they are built where the values
|
|
125
|
+
* are. This component only decides where they go.
|
|
126
|
+
*/
|
|
127
|
+
declare discoverStatus: string;
|
|
128
|
+
declare feedsEnabled: boolean;
|
|
98
129
|
declare mainFeedUrl: string;
|
|
99
130
|
declare latestFeedUrl: string;
|
|
100
131
|
declare featuredFeedUrl: string;
|
|
101
132
|
declare archiveFeedUrl: string;
|
|
133
|
+
declare feedsDocsUrl: string;
|
|
102
134
|
declare aboutPage: SettingsAboutPageStatus;
|
|
103
135
|
declare aboutEditUrl: string;
|
|
104
136
|
declare aboutCreateUrl: string;
|
|
@@ -115,20 +147,10 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
115
147
|
declare _siteDirty: boolean;
|
|
116
148
|
declare _siteLoading: boolean;
|
|
117
149
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
/** Admin dashboard UI locale (one of the translated catalog locales). */
|
|
121
|
-
declare _dashboardLanguage: string;
|
|
122
|
-
/** Whether the locale combobox dropdown is currently open. */
|
|
123
|
-
declare _localeOpen: boolean;
|
|
124
|
-
/** Search query inside the locale combobox. */
|
|
125
|
-
declare _localeQuery: string;
|
|
126
|
-
declare _cjkSerifFont: string;
|
|
150
|
+
// Time
|
|
151
|
+
/** IANA time zone name, such as `"America/New_York"`. */
|
|
127
152
|
declare _timeZone: string;
|
|
128
153
|
declare _origLocale: {
|
|
129
|
-
siteLanguage: string;
|
|
130
|
-
dashboardLanguage: string;
|
|
131
|
-
cjkSerifFont: string;
|
|
132
154
|
timeZone: string;
|
|
133
155
|
};
|
|
134
156
|
declare _localeDirty: boolean;
|
|
@@ -137,7 +159,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
137
159
|
// Feed
|
|
138
160
|
declare _mainRssFeed: string;
|
|
139
161
|
declare _origMainRssFeed: string;
|
|
140
|
-
declare _feedDirty: boolean;
|
|
141
162
|
declare _feedLoading: boolean;
|
|
142
163
|
|
|
143
164
|
// Home
|
|
@@ -150,6 +171,12 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
150
171
|
declare _origNoindex: boolean;
|
|
151
172
|
declare _searchLoading: boolean;
|
|
152
173
|
|
|
174
|
+
// Discover. "" means the owner has never used the control, which is not the
|
|
175
|
+
// same as "off" — an untouched site still follows the default.
|
|
176
|
+
declare _discover: string;
|
|
177
|
+
declare _origDiscover: string;
|
|
178
|
+
declare _discoverLoading: boolean;
|
|
179
|
+
|
|
153
180
|
// TipTap editor instances
|
|
154
181
|
private _descEditor: Editor | null = null;
|
|
155
182
|
private _footerEditor: Editor | null = null;
|
|
@@ -163,8 +190,6 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
163
190
|
super();
|
|
164
191
|
this.labels = {} as SettingsLabels;
|
|
165
192
|
this.timezones = [];
|
|
166
|
-
this.cjkFonts = [];
|
|
167
|
-
this.dashboardLanguages = [];
|
|
168
193
|
this.siteNameFallback = "";
|
|
169
194
|
this.siteDescriptionFallback = "";
|
|
170
195
|
this.demoMode = false;
|
|
@@ -172,6 +197,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
172
197
|
this.latestFeedUrl = "/latest/feed";
|
|
173
198
|
this.featuredFeedUrl = "/featured/feed";
|
|
174
199
|
this.archiveFeedUrl = "/archive/feed";
|
|
200
|
+
this.feedsDocsUrl = "";
|
|
175
201
|
this.aboutPage = {
|
|
176
202
|
state: "missing",
|
|
177
203
|
path: "/about",
|
|
@@ -190,25 +216,14 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
190
216
|
this._siteDirty = false;
|
|
191
217
|
this._siteLoading = false;
|
|
192
218
|
|
|
193
|
-
this._siteLanguage = "en";
|
|
194
|
-
this._dashboardLanguage = "en";
|
|
195
|
-
this._localeOpen = false;
|
|
196
|
-
this._localeQuery = "";
|
|
197
|
-
this._cjkSerifFont = "off";
|
|
198
219
|
this._timeZone = "UTC";
|
|
199
|
-
this._origLocale = {
|
|
200
|
-
siteLanguage: "en",
|
|
201
|
-
dashboardLanguage: "en",
|
|
202
|
-
cjkSerifFont: "off",
|
|
203
|
-
timeZone: "UTC",
|
|
204
|
-
};
|
|
220
|
+
this._origLocale = { timeZone: "UTC" };
|
|
205
221
|
|
|
206
222
|
this._localeDirty = false;
|
|
207
223
|
this._localeLoading = false;
|
|
208
224
|
|
|
209
225
|
this._mainRssFeed = "featured";
|
|
210
226
|
this._origMainRssFeed = "featured";
|
|
211
|
-
this._feedDirty = false;
|
|
212
227
|
this._feedLoading = false;
|
|
213
228
|
|
|
214
229
|
this._noindex = false;
|
|
@@ -217,18 +232,22 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
217
232
|
this._origShowJantBrandingOnHome = false;
|
|
218
233
|
this._homeLoading = false;
|
|
219
234
|
this._searchLoading = false;
|
|
235
|
+
|
|
236
|
+
this.discoverDefault = "";
|
|
237
|
+
this.discoverPages = null;
|
|
238
|
+
this.discoverStatus = "";
|
|
239
|
+
this.feedsEnabled = false;
|
|
240
|
+
this._discover = "";
|
|
241
|
+
this._origDiscover = "";
|
|
242
|
+
this._discoverLoading = false;
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
connectedCallback() {
|
|
223
246
|
super.connectedCallback();
|
|
224
|
-
document.addEventListener("click", this._onLocalePickerDocumentClick);
|
|
225
|
-
document.addEventListener("keydown", this._onLocalePickerKeydown);
|
|
226
247
|
}
|
|
227
248
|
|
|
228
249
|
disconnectedCallback() {
|
|
229
250
|
super.disconnectedCallback();
|
|
230
|
-
document.removeEventListener("click", this._onLocalePickerDocumentClick);
|
|
231
|
-
document.removeEventListener("keydown", this._onLocalePickerKeydown);
|
|
232
251
|
this._descEditor?.destroy();
|
|
233
252
|
this._descEditor = null;
|
|
234
253
|
this._footerEditor?.destroy();
|
|
@@ -241,16 +260,8 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
241
260
|
this._siteDescription = data.siteDescription;
|
|
242
261
|
this._siteFooter = data.siteFooter;
|
|
243
262
|
|
|
244
|
-
this._siteLanguage = data.siteLanguage;
|
|
245
|
-
this._dashboardLanguage = data.dashboardLanguage;
|
|
246
|
-
this._cjkSerifFont = data.cjkSerifFont;
|
|
247
263
|
this._timeZone = data.timeZone;
|
|
248
|
-
this._origLocale = {
|
|
249
|
-
siteLanguage: data.siteLanguage,
|
|
250
|
-
dashboardLanguage: data.dashboardLanguage,
|
|
251
|
-
cjkSerifFont: data.cjkSerifFont,
|
|
252
|
-
timeZone: data.timeZone,
|
|
253
|
-
};
|
|
264
|
+
this._origLocale = { timeZone: data.timeZone };
|
|
254
265
|
|
|
255
266
|
this._mainRssFeed = data.mainRssFeed;
|
|
256
267
|
this._origMainRssFeed = data.mainRssFeed;
|
|
@@ -261,6 +272,9 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
261
272
|
this._noindex = data.noindex;
|
|
262
273
|
this._origNoindex = data.noindex;
|
|
263
274
|
|
|
275
|
+
this._discover = data.discover;
|
|
276
|
+
this._origDiscover = data.discover;
|
|
277
|
+
|
|
264
278
|
// Defer editor init to after Lit renders the containers
|
|
265
279
|
this.updateComplete.then(() => {
|
|
266
280
|
this._initEditors();
|
|
@@ -283,18 +297,12 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
283
297
|
};
|
|
284
298
|
this._siteDirty = false;
|
|
285
299
|
this._siteLoading = false;
|
|
286
|
-
} else if (section === "
|
|
287
|
-
this._origLocale = {
|
|
288
|
-
siteLanguage: this._siteLanguage,
|
|
289
|
-
dashboardLanguage: this._dashboardLanguage,
|
|
290
|
-
cjkSerifFont: this._cjkSerifFont,
|
|
291
|
-
timeZone: this._timeZone,
|
|
292
|
-
};
|
|
300
|
+
} else if (section === "time") {
|
|
301
|
+
this._origLocale = { timeZone: this._timeZone };
|
|
293
302
|
this._localeDirty = false;
|
|
294
303
|
this._localeLoading = false;
|
|
295
304
|
} else if (section === "feeds") {
|
|
296
305
|
this._origMainRssFeed = this._mainRssFeed;
|
|
297
|
-
this._feedDirty = false;
|
|
298
306
|
this._feedLoading = false;
|
|
299
307
|
} else if (section === "home") {
|
|
300
308
|
this._origShowJantBrandingOnHome = this._showJantBrandingOnHome;
|
|
@@ -302,6 +310,9 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
302
310
|
} else if (section === "search") {
|
|
303
311
|
this._origNoindex = this._noindex;
|
|
304
312
|
this._searchLoading = false;
|
|
313
|
+
} else if (section === "discover") {
|
|
314
|
+
this._origDiscover = this._discover;
|
|
315
|
+
this._discoverLoading = false;
|
|
305
316
|
}
|
|
306
317
|
}
|
|
307
318
|
|
|
@@ -309,9 +320,10 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
309
320
|
sectionError(section: string) {
|
|
310
321
|
if (section === "site") {
|
|
311
322
|
this._siteLoading = false;
|
|
312
|
-
} else if (section === "
|
|
323
|
+
} else if (section === "time") {
|
|
313
324
|
this._localeLoading = false;
|
|
314
325
|
} else if (section === "feeds") {
|
|
326
|
+
this._mainRssFeed = this._origMainRssFeed;
|
|
315
327
|
this._feedLoading = false;
|
|
316
328
|
} else if (section === "home") {
|
|
317
329
|
this._showJantBrandingOnHome = this._origShowJantBrandingOnHome;
|
|
@@ -319,6 +331,9 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
319
331
|
} else if (section === "search") {
|
|
320
332
|
this._noindex = this._origNoindex;
|
|
321
333
|
this._searchLoading = false;
|
|
334
|
+
} else if (section === "discover") {
|
|
335
|
+
this._discover = this._origDiscover;
|
|
336
|
+
this._discoverLoading = false;
|
|
322
337
|
}
|
|
323
338
|
}
|
|
324
339
|
|
|
@@ -408,14 +423,10 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
408
423
|
);
|
|
409
424
|
}
|
|
410
425
|
|
|
411
|
-
// ──
|
|
426
|
+
// ── Time group helpers ────────────────────────────────────────────
|
|
412
427
|
|
|
413
428
|
private _syncLocaleDirty() {
|
|
414
|
-
this._localeDirty =
|
|
415
|
-
this._siteLanguage !== this._origLocale.siteLanguage ||
|
|
416
|
-
this._dashboardLanguage !== this._origLocale.dashboardLanguage ||
|
|
417
|
-
this._cjkSerifFont !== this._origLocale.cjkSerifFont ||
|
|
418
|
-
this._timeZone !== this._origLocale.timeZone;
|
|
429
|
+
this._localeDirty = this._timeZone !== this._origLocale.timeZone;
|
|
419
430
|
}
|
|
420
431
|
|
|
421
432
|
private _saveLocale() {
|
|
@@ -425,157 +436,19 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
425
436
|
new CustomEvent("jant:settings-save", {
|
|
426
437
|
bubbles: true,
|
|
427
438
|
detail: {
|
|
428
|
-
endpoint: "/settings/general/
|
|
429
|
-
data: {
|
|
430
|
-
|
|
431
|
-
dashboardLanguage: this._dashboardLanguage,
|
|
432
|
-
cjkSerifFont: this._cjkSerifFont,
|
|
433
|
-
timeZone: this._timeZone,
|
|
434
|
-
},
|
|
435
|
-
section: "language-time",
|
|
439
|
+
endpoint: "/settings/general/time",
|
|
440
|
+
data: { timeZone: this._timeZone },
|
|
441
|
+
section: "time",
|
|
436
442
|
},
|
|
437
443
|
}),
|
|
438
444
|
);
|
|
439
445
|
}
|
|
440
446
|
|
|
441
|
-
// ──
|
|
442
|
-
|
|
443
|
-
private _filteredLocaleEntries(): LocaleEntry[] {
|
|
444
|
-
const all = getSupportedLocaleEntries();
|
|
445
|
-
const query = this._localeQuery.trim().toLowerCase();
|
|
446
|
-
if (!query) return all;
|
|
447
|
-
return all.filter(
|
|
448
|
-
(e) =>
|
|
449
|
-
e.tag.toLowerCase().includes(query) ||
|
|
450
|
-
e.native.toLowerCase().includes(query) ||
|
|
451
|
-
e.english.toLowerCase().includes(query),
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
private _toggleLocalePicker = () => {
|
|
456
|
-
this._localeOpen = !this._localeOpen;
|
|
457
|
-
if (!this._localeOpen) {
|
|
458
|
-
this._localeQuery = "";
|
|
459
|
-
} else {
|
|
460
|
-
// Focus the search input on next paint.
|
|
461
|
-
this.updateComplete.then(() => {
|
|
462
|
-
const input = this.querySelector<HTMLInputElement>(
|
|
463
|
-
"[data-locale-search]",
|
|
464
|
-
);
|
|
465
|
-
input?.focus();
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
private _selectLocale(tag: string) {
|
|
471
|
-
this._siteLanguage = tag;
|
|
472
|
-
this._localeOpen = false;
|
|
473
|
-
this._localeQuery = "";
|
|
474
|
-
this._syncLocaleDirty();
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
private _onLocalePickerDocumentClick = (e: Event) => {
|
|
478
|
-
if (!this._localeOpen) return;
|
|
479
|
-
const target = e.target as Node | null;
|
|
480
|
-
const picker = this.querySelector("[data-locale-picker]");
|
|
481
|
-
if (picker && target && !picker.contains(target)) {
|
|
482
|
-
this._localeOpen = false;
|
|
483
|
-
}
|
|
484
|
-
};
|
|
447
|
+
// ── Feed auto-save helpers ────────────────────────────────────────
|
|
485
448
|
|
|
486
|
-
private
|
|
487
|
-
if (
|
|
488
|
-
|
|
489
|
-
this._localeOpen = false;
|
|
490
|
-
this._localeQuery = "";
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
private _renderLanguagePicker() {
|
|
495
|
-
const current = getOrBuildEntry(this._siteLanguage || "en");
|
|
496
|
-
const filtered = this._filteredLocaleEntries();
|
|
497
|
-
const searchPlaceholder =
|
|
498
|
-
this.labels.siteLanguageSearchPlaceholder || "Search…";
|
|
499
|
-
const noMatches = this.labels.siteLanguageNoMatches || "No matches.";
|
|
500
|
-
|
|
501
|
-
return html`
|
|
502
|
-
<div class="relative w-fit max-w-full" data-locale-picker>
|
|
503
|
-
<button
|
|
504
|
-
type="button"
|
|
505
|
-
class="flex h-9 w-full cursor-pointer items-center rounded-md border border-input bg-transparent bg-[image:var(--chevron-down-icon-50)] bg-position-[center_right_0.75rem] bg-size-[1rem] bg-no-repeat py-2 pl-3 pr-9 text-left text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
|
|
506
|
-
aria-expanded=${this._localeOpen ? "true" : "false"}
|
|
507
|
-
aria-haspopup="listbox"
|
|
508
|
-
aria-labelledby="site-language-label"
|
|
509
|
-
@click=${this._toggleLocalePicker}
|
|
510
|
-
>
|
|
511
|
-
<span class="min-w-0 truncate">${current.native}</span>
|
|
512
|
-
</button>
|
|
513
|
-
|
|
514
|
-
${this._localeOpen
|
|
515
|
-
? html`
|
|
516
|
-
<div
|
|
517
|
-
class="absolute left-0 top-full z-10 mt-1 w-80 min-w-full max-w-[calc(100vw-2rem)] max-h-72 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md"
|
|
518
|
-
>
|
|
519
|
-
<div class="border-b p-2">
|
|
520
|
-
<input
|
|
521
|
-
type="text"
|
|
522
|
-
class="input w-full"
|
|
523
|
-
data-locale-search
|
|
524
|
-
placeholder=${searchPlaceholder}
|
|
525
|
-
autocomplete="off"
|
|
526
|
-
spellcheck="false"
|
|
527
|
-
.value=${this._localeQuery}
|
|
528
|
-
@input=${(e: Event) => {
|
|
529
|
-
this._localeQuery = (e.target as HTMLInputElement).value;
|
|
530
|
-
}}
|
|
531
|
-
/>
|
|
532
|
-
</div>
|
|
533
|
-
<div role="listbox" class="max-h-56 overflow-auto py-1">
|
|
534
|
-
${filtered.length === 0
|
|
535
|
-
? html`
|
|
536
|
-
<div class="px-3 py-2 text-sm text-muted-foreground">
|
|
537
|
-
${noMatches}
|
|
538
|
-
</div>
|
|
539
|
-
`
|
|
540
|
-
: filtered.map(
|
|
541
|
-
(entry) => html`
|
|
542
|
-
<button
|
|
543
|
-
type="button"
|
|
544
|
-
role="option"
|
|
545
|
-
aria-selected=${entry.tag === this._siteLanguage
|
|
546
|
-
? "true"
|
|
547
|
-
: "false"}
|
|
548
|
-
class=${[
|
|
549
|
-
"flex w-full flex-col px-3 py-2 text-left text-sm hover:bg-accent",
|
|
550
|
-
entry.tag === this._siteLanguage
|
|
551
|
-
? "bg-accent/60"
|
|
552
|
-
: "",
|
|
553
|
-
].join(" ")}
|
|
554
|
-
@click=${() => this._selectLocale(entry.tag)}
|
|
555
|
-
>
|
|
556
|
-
<span>${entry.native}</span>
|
|
557
|
-
<span class="text-xs text-muted-foreground">
|
|
558
|
-
${entry.english}
|
|
559
|
-
</span>
|
|
560
|
-
</button>
|
|
561
|
-
`,
|
|
562
|
-
)}
|
|
563
|
-
</div>
|
|
564
|
-
</div>
|
|
565
|
-
`
|
|
566
|
-
: nothing}
|
|
567
|
-
</div>
|
|
568
|
-
`;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// ── Feed group helpers ────────────────────────────────────────────
|
|
572
|
-
|
|
573
|
-
private _syncFeedDirty() {
|
|
574
|
-
this._feedDirty = this._mainRssFeed !== this._origMainRssFeed;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
private _saveFeeds() {
|
|
578
|
-
if (this._feedLoading || !this._feedDirty) return;
|
|
449
|
+
private _saveMainRssFeed(value: string) {
|
|
450
|
+
if (this._feedLoading || value === this._mainRssFeed) return;
|
|
451
|
+
this._mainRssFeed = value;
|
|
579
452
|
this._feedLoading = true;
|
|
580
453
|
this.dispatchEvent(
|
|
581
454
|
new CustomEvent("jant:settings-save", {
|
|
@@ -583,7 +456,7 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
583
456
|
detail: {
|
|
584
457
|
endpoint: "/settings/general/feeds",
|
|
585
458
|
data: {
|
|
586
|
-
mainRssFeed:
|
|
459
|
+
mainRssFeed: value,
|
|
587
460
|
},
|
|
588
461
|
section: "feeds",
|
|
589
462
|
},
|
|
@@ -631,6 +504,65 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
631
504
|
);
|
|
632
505
|
}
|
|
633
506
|
|
|
507
|
+
// ── Discover helpers ──────────────────────────────────────────────
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* What this site's feeds declare, as of the controls on screen right now.
|
|
511
|
+
*
|
|
512
|
+
* The same `resolveDiscoverMode` the server runs, over the same inputs, so
|
|
513
|
+
* the checkbox cannot disagree with the feed. It has to be computed here
|
|
514
|
+
* rather than sent down resolved because two of its inputs — search
|
|
515
|
+
* indexing and the feed switch — are controls on this page: a value resolved
|
|
516
|
+
* on the server is correct until the first click and wrong from then until
|
|
517
|
+
* the next page load, which is exactly the state an owner reads it in.
|
|
518
|
+
*/
|
|
519
|
+
private _effectiveDiscoverMode(overrides: { noindex?: boolean } = {}) {
|
|
520
|
+
return resolveDiscoverMode({
|
|
521
|
+
// "" is never chosen, which is what lets the rules below decide.
|
|
522
|
+
storedValue: this._discover || null,
|
|
523
|
+
defaultValue: this.discoverDefault || null,
|
|
524
|
+
demoMode: this.demoMode,
|
|
525
|
+
noindex: overrides.noindex ?? this._noindex,
|
|
526
|
+
rssFeedsEnabled: this.feedsEnabled,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* On writes `latest`: the directory may read any public post, and decides
|
|
532
|
+
* itself which list each one reaches — featured posts on its home, links
|
|
533
|
+
* and quotes on their own lists.
|
|
534
|
+
*
|
|
535
|
+
* A site that stored `featured` under an older release still reads as
|
|
536
|
+
* ticked, and stays `featured` until its owner touches the box: widening
|
|
537
|
+
* what the directory may show is the owner's to do, not an upgrade's.
|
|
538
|
+
*/
|
|
539
|
+
private _onDiscoverToggle(enabled: boolean) {
|
|
540
|
+
this._saveDiscover(enabled ? "latest" : "off");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Store the choice the owner just made.
|
|
545
|
+
*
|
|
546
|
+
* The control is disabled while a save is in flight, so a second call
|
|
547
|
+
* cannot arrive before the first has answered; the guard covers the event
|
|
548
|
+
* that is already queued when that happens.
|
|
549
|
+
*/
|
|
550
|
+
private _saveDiscover(value: "latest" | "off") {
|
|
551
|
+
if (this._discoverLoading) return;
|
|
552
|
+
this._discover = value;
|
|
553
|
+
this._discoverLoading = true;
|
|
554
|
+
this.dispatchEvent(
|
|
555
|
+
new CustomEvent("jant:settings-save", {
|
|
556
|
+
bubbles: true,
|
|
557
|
+
detail: {
|
|
558
|
+
endpoint: "/settings/general/discover",
|
|
559
|
+
data: { discover: value },
|
|
560
|
+
section: "discover",
|
|
561
|
+
},
|
|
562
|
+
}),
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
634
566
|
/** Submit on Enter from non-textarea fields */
|
|
635
567
|
private _onKeydown(
|
|
636
568
|
e: globalThis.KeyboardEvent,
|
|
@@ -666,21 +598,23 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
666
598
|
?disabled=${loading || !dirty}
|
|
667
599
|
@click=${onSave}
|
|
668
600
|
>
|
|
669
|
-
${
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
601
|
+
${
|
|
602
|
+
loading
|
|
603
|
+
? html`<svg
|
|
604
|
+
class="animate-spin size-4"
|
|
605
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
606
|
+
viewBox="0 0 24 24"
|
|
607
|
+
fill="none"
|
|
608
|
+
stroke="currentColor"
|
|
609
|
+
stroke-width="2"
|
|
610
|
+
stroke-linecap="round"
|
|
611
|
+
stroke-linejoin="round"
|
|
612
|
+
role="status"
|
|
613
|
+
>
|
|
614
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
615
|
+
</svg>`
|
|
616
|
+
: nothing
|
|
617
|
+
}
|
|
684
618
|
${this.labels.save}
|
|
685
619
|
</button>
|
|
686
620
|
</div>
|
|
@@ -711,10 +645,8 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
711
645
|
value=${value}
|
|
712
646
|
class="mt-1"
|
|
713
647
|
.checked=${checked}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
this._syncFeedDirty();
|
|
717
|
-
}}
|
|
648
|
+
?disabled=${this._feedLoading}
|
|
649
|
+
@change=${() => this._saveMainRssFeed(value)}
|
|
718
650
|
/>
|
|
719
651
|
<div>
|
|
720
652
|
<div class="font-medium">${title}</div>
|
|
@@ -724,47 +656,39 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
724
656
|
`;
|
|
725
657
|
}
|
|
726
658
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
private _renderFeedInfoRow(
|
|
741
|
-
label: string,
|
|
742
|
-
value: string,
|
|
743
|
-
description?: string,
|
|
744
|
-
) {
|
|
659
|
+
/**
|
|
660
|
+
* One feed address with a copy button.
|
|
661
|
+
*
|
|
662
|
+
* The same field `ui/shared/CopyField.tsx` renders on the public subscribe
|
|
663
|
+
* page, driven by the same `client/copy-field.ts` enhancer — hence the shared
|
|
664
|
+
* classes. lit-html cannot interpolate attribute names, so the enhancer's
|
|
665
|
+
* hooks are spelled out; `jant-settings-general.test.ts` asserts they match.
|
|
666
|
+
* Unlike the public page the button is not rendered hidden: settings is
|
|
667
|
+
* behind auth, where the client bundle is always present.
|
|
668
|
+
*/
|
|
669
|
+
private _renderFeedUrl(label: string, value: string, description?: string) {
|
|
745
670
|
return html`
|
|
746
|
-
<div class
|
|
671
|
+
<div class=${COPY_FIELD_CLASS} data-copy-field-root>
|
|
747
672
|
<p class="text-sm font-medium">${label}</p>
|
|
748
|
-
${
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
673
|
+
${
|
|
674
|
+
description
|
|
675
|
+
? html`<p class="text-sm text-muted-foreground">${description}</p>`
|
|
676
|
+
: ""
|
|
677
|
+
}
|
|
678
|
+
<div class=${COPY_FIELD_CONTROL_CLASS}>
|
|
752
679
|
<input
|
|
753
680
|
type="text"
|
|
754
|
-
class
|
|
681
|
+
class=${COPY_FIELD_INPUT_CLASS}
|
|
755
682
|
.value=${value}
|
|
756
683
|
readonly
|
|
757
684
|
aria-label=${label}
|
|
758
|
-
|
|
759
|
-
(e.currentTarget as HTMLInputElement).select()}
|
|
760
|
-
@focus=${(e: Event) =>
|
|
761
|
-
(e.currentTarget as HTMLInputElement).select()}
|
|
685
|
+
data-copy-field-value
|
|
762
686
|
/>
|
|
763
687
|
<button
|
|
764
688
|
type="button"
|
|
765
|
-
class
|
|
766
|
-
data-copy-
|
|
767
|
-
|
|
689
|
+
class=${COPY_FIELD_BUTTON_CLASS}
|
|
690
|
+
data-copy-field=${this.labels.feedUrlCopied}
|
|
691
|
+
data-copy-field-failed=${this.labels.copyFailed}
|
|
768
692
|
>
|
|
769
693
|
${this.labels.copy}
|
|
770
694
|
</button>
|
|
@@ -779,319 +703,469 @@ export class JantSettingsGeneral extends LitElement {
|
|
|
779
703
|
return html`
|
|
780
704
|
<div class="mt-2 text-sm text-muted-foreground" data-about-page-row>
|
|
781
705
|
${this.labels.aboutPagePrompt}
|
|
782
|
-
${
|
|
783
|
-
|
|
784
|
-
<a
|
|
785
|
-
class="font-medium text-foreground underline-offset-4 hover:underline"
|
|
786
|
-
href=${this.aboutEditUrl}
|
|
787
|
-
>
|
|
788
|
-
${this.labels.editAboutPage}
|
|
789
|
-
</a>
|
|
790
|
-
`
|
|
791
|
-
: status.state === "missing"
|
|
706
|
+
${
|
|
707
|
+
status.state === "ready"
|
|
792
708
|
? html`
|
|
793
|
-
<
|
|
794
|
-
class="
|
|
795
|
-
|
|
796
|
-
action=${this.aboutCreateUrl}
|
|
709
|
+
<a
|
|
710
|
+
class="font-medium text-foreground underline-offset-4 hover:underline"
|
|
711
|
+
href=${this.aboutEditUrl}
|
|
797
712
|
>
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
class="inline cursor-pointer border-0 bg-transparent p-0 font-medium text-foreground underline-offset-4 hover:underline"
|
|
801
|
-
>
|
|
802
|
-
${this.labels.createAboutPage}
|
|
803
|
-
</button>
|
|
804
|
-
</form>
|
|
713
|
+
${this.labels.editAboutPage}
|
|
714
|
+
</a>
|
|
805
715
|
`
|
|
806
|
-
:
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
716
|
+
: status.state === "missing"
|
|
717
|
+
? html`
|
|
718
|
+
<form
|
|
719
|
+
class="inline"
|
|
720
|
+
method="post"
|
|
721
|
+
action=${this.aboutCreateUrl}
|
|
722
|
+
>
|
|
723
|
+
<button
|
|
724
|
+
type="submit"
|
|
725
|
+
class="inline cursor-pointer border-0 bg-transparent p-0 font-medium text-foreground underline-offset-4 hover:underline"
|
|
726
|
+
>
|
|
727
|
+
${this.labels.createAboutPage}
|
|
728
|
+
</button>
|
|
729
|
+
</form>
|
|
730
|
+
`
|
|
731
|
+
: html`
|
|
732
|
+
<span class="text-destructive"
|
|
733
|
+
>${this.labels.aboutPageConflict}</span
|
|
734
|
+
>
|
|
735
|
+
`
|
|
736
|
+
}
|
|
811
737
|
</div>
|
|
812
738
|
`;
|
|
813
739
|
}
|
|
814
740
|
|
|
815
|
-
|
|
741
|
+
// ── Sections, in the order `render()` lists them ──────────────────
|
|
742
|
+
//
|
|
743
|
+
// Each section carries only its own spacing (`SECTION_CLASS`); the rules
|
|
744
|
+
// between them are drawn by the container, so reordering is a change to
|
|
745
|
+
// `render()` alone.
|
|
746
|
+
|
|
747
|
+
private _renderSiteSection() {
|
|
816
748
|
return html`
|
|
817
|
-
<
|
|
818
|
-
|
|
819
|
-
|
|
749
|
+
<section
|
|
750
|
+
class="${SECTION_CLASS} gap-4"
|
|
751
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
752
|
+
this._onKeydown(
|
|
753
|
+
e,
|
|
754
|
+
() => this._saveSite(),
|
|
755
|
+
this._siteDirty,
|
|
756
|
+
this._siteLoading,
|
|
757
|
+
)}
|
|
758
|
+
>
|
|
759
|
+
${this._renderSectionTitle(this.labels.site)}
|
|
760
|
+
<div class="field">
|
|
761
|
+
<label class="label">${this.labels.siteName}</label>
|
|
762
|
+
<input
|
|
763
|
+
type="text"
|
|
764
|
+
class="input"
|
|
765
|
+
maxlength=${MAX_SITE_NAME_LENGTH}
|
|
766
|
+
.value=${this._siteName}
|
|
767
|
+
placeholder=${this.siteNameFallback}
|
|
768
|
+
@input=${(e: Event) => {
|
|
769
|
+
this._siteName = (e.target as HTMLInputElement).value;
|
|
770
|
+
this._syncSiteDirty();
|
|
771
|
+
}}
|
|
772
|
+
/>
|
|
820
773
|
</div>
|
|
821
774
|
|
|
822
|
-
<
|
|
823
|
-
class="
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
)}
|
|
831
|
-
>
|
|
832
|
-
${this._renderSectionTitle(this.labels.site)}
|
|
833
|
-
<div class="field">
|
|
834
|
-
<label class="label">${this.labels.siteName}</label>
|
|
835
|
-
<input
|
|
836
|
-
type="text"
|
|
837
|
-
class="input"
|
|
838
|
-
maxlength=${MAX_SITE_NAME_LENGTH}
|
|
839
|
-
.value=${this._siteName}
|
|
840
|
-
placeholder=${this.siteNameFallback}
|
|
841
|
-
@input=${(e: Event) => {
|
|
842
|
-
this._siteName = (e.target as HTMLInputElement).value;
|
|
843
|
-
this._syncSiteDirty();
|
|
844
|
-
}}
|
|
845
|
-
/>
|
|
846
|
-
</div>
|
|
775
|
+
<div class="field">
|
|
776
|
+
<label class="label">${this.labels.aboutBlog}</label>
|
|
777
|
+
<div class="settings-tiptap-editor" data-settings-desc-editor></div>
|
|
778
|
+
<p class="text-sm text-muted-foreground mt-1">
|
|
779
|
+
${this.labels.aboutBlogHelp}
|
|
780
|
+
</p>
|
|
781
|
+
${this._renderAboutPageRow()}
|
|
782
|
+
</div>
|
|
847
783
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
</div>
|
|
784
|
+
<div class="field">
|
|
785
|
+
<label class="label">${this.labels.siteFooter}</label>
|
|
786
|
+
<div class="settings-tiptap-editor" data-settings-footer-editor></div>
|
|
787
|
+
<p class="text-sm text-muted-foreground mt-1">
|
|
788
|
+
${this.labels.footerHelp}
|
|
789
|
+
</p>
|
|
790
|
+
</div>
|
|
856
791
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
<p class="text-sm text-muted-foreground mt-1">
|
|
864
|
-
${this.labels.footerHelp}
|
|
865
|
-
</p>
|
|
866
|
-
</div>
|
|
792
|
+
${this._renderSaveAction(this._siteLoading, this._siteDirty, () =>
|
|
793
|
+
this._saveSite(),
|
|
794
|
+
)}
|
|
795
|
+
</section>
|
|
796
|
+
`;
|
|
797
|
+
}
|
|
867
798
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
<
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
799
|
+
/**
|
|
800
|
+
* Search engine indexing and Jant Discover.
|
|
801
|
+
*
|
|
802
|
+
* The two groups sit further apart than a section's heading sits from its
|
|
803
|
+
* first control, so the Discover help text does not read as belonging to
|
|
804
|
+
* the indexing checkbox.
|
|
805
|
+
*/
|
|
806
|
+
private _renderVisibilitySection() {
|
|
807
|
+
return html`
|
|
808
|
+
<section class="${SECTION_CLASS} gap-6">
|
|
809
|
+
${this._renderSectionTitle(this.labels.siteVisibility)}
|
|
810
|
+
<div class="flex flex-col gap-2">
|
|
811
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
812
|
+
<input
|
|
813
|
+
type="checkbox"
|
|
814
|
+
class="checkbox"
|
|
815
|
+
.checked=${!this._noindex}
|
|
816
|
+
?disabled=${this.demoMode || this._searchLoading}
|
|
817
|
+
@change=${(e: Event) =>
|
|
818
|
+
this._saveSearchToggle((e.target as HTMLInputElement).checked)}
|
|
819
|
+
/>
|
|
820
|
+
<span>${this.labels.allowIndexing}</span>
|
|
821
|
+
</label>
|
|
822
|
+
${
|
|
823
|
+
this.demoMode
|
|
824
|
+
? html`<p class="text-sm text-muted-foreground">
|
|
825
|
+
${this.labels.demoSeoLocked}
|
|
826
|
+
</p>`
|
|
827
|
+
: nothing
|
|
828
|
+
}
|
|
829
|
+
</div>
|
|
830
|
+
${this._renderDiscoverForm()}
|
|
831
|
+
</section>
|
|
832
|
+
`;
|
|
833
|
+
}
|
|
899
834
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
`,
|
|
921
|
-
)}
|
|
922
|
-
</select>
|
|
923
|
-
<p class="text-sm text-muted-foreground mt-1">
|
|
924
|
-
${this.labels.dashboardLanguageHelp}
|
|
925
|
-
</p>
|
|
835
|
+
private _renderFeedsSection() {
|
|
836
|
+
return html`
|
|
837
|
+
<section class="${SECTION_CLASS} gap-4">
|
|
838
|
+
${this._renderSectionTitle(this.labels.feeds)}
|
|
839
|
+
<div class="field">
|
|
840
|
+
<p class="label">${this.labels.mainRssFeed}</p>
|
|
841
|
+
<p class="text-sm text-muted-foreground mt-1">
|
|
842
|
+
${this.labels.mainRssFeedHelp}
|
|
843
|
+
</p>
|
|
844
|
+
<div class="mt-3 flex flex-col gap-2">
|
|
845
|
+
${this._renderMainRssFeedOption(
|
|
846
|
+
"featured",
|
|
847
|
+
this.labels.featuredFeedOption,
|
|
848
|
+
this.labels.featuredFeedOptionDescription,
|
|
849
|
+
)}
|
|
850
|
+
${this._renderMainRssFeedOption(
|
|
851
|
+
"latest",
|
|
852
|
+
this.labels.latestFeedOption,
|
|
853
|
+
this.labels.latestFeedOptionDescription,
|
|
854
|
+
)}
|
|
926
855
|
</div>
|
|
856
|
+
<p class="text-sm text-muted-foreground mt-2">
|
|
857
|
+
${this.labels.mainRssFeedWarning}
|
|
858
|
+
</p>
|
|
859
|
+
</div>
|
|
927
860
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
<
|
|
931
|
-
class="
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
</
|
|
951
|
-
</div>
|
|
861
|
+
<div class="rounded-xl border border-border/70 bg-muted/30 p-4">
|
|
862
|
+
<div class="flex flex-col gap-4">
|
|
863
|
+
<div class="flex flex-col gap-1">
|
|
864
|
+
<p class="text-sm font-medium">
|
|
865
|
+
${this.labels.availableFeedUrls}
|
|
866
|
+
</p>
|
|
867
|
+
<p class="text-sm text-muted-foreground">
|
|
868
|
+
${this.labels.availableFeedUrlsHelp}
|
|
869
|
+
${
|
|
870
|
+
this.feedsDocsUrl
|
|
871
|
+
? html`
|
|
872
|
+
<a
|
|
873
|
+
href=${this.feedsDocsUrl}
|
|
874
|
+
target="_blank"
|
|
875
|
+
rel="noopener noreferrer"
|
|
876
|
+
class="underline hover:text-foreground transition-colors"
|
|
877
|
+
>${this.labels.feedsDocs}</a
|
|
878
|
+
>
|
|
879
|
+
`
|
|
880
|
+
: ""
|
|
881
|
+
}
|
|
882
|
+
</p>
|
|
883
|
+
</div>
|
|
952
884
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
>
|
|
968
|
-
${tz.label}
|
|
969
|
-
</option>
|
|
970
|
-
`,
|
|
971
|
-
)}
|
|
972
|
-
</select>
|
|
885
|
+
${this._renderFeedUrl(this.labels.mainFeedUrl, this.mainFeedUrl)}
|
|
886
|
+
${this._renderFeedUrl(
|
|
887
|
+
this.labels.latestFeedUrl,
|
|
888
|
+
this.latestFeedUrl,
|
|
889
|
+
)}
|
|
890
|
+
${this._renderFeedUrl(
|
|
891
|
+
this.labels.featuredFeedUrl,
|
|
892
|
+
this.featuredFeedUrl,
|
|
893
|
+
)}
|
|
894
|
+
${this._renderFeedUrl(
|
|
895
|
+
this.labels.archiveFeedUrl,
|
|
896
|
+
this.archiveFeedUrl,
|
|
897
|
+
this.labels.archiveFeedUrlHelp,
|
|
898
|
+
)}
|
|
973
899
|
</div>
|
|
900
|
+
</div>
|
|
901
|
+
</section>
|
|
902
|
+
`;
|
|
903
|
+
}
|
|
974
904
|
|
|
975
|
-
|
|
976
|
-
|
|
905
|
+
private _renderTimeSection() {
|
|
906
|
+
return html`
|
|
907
|
+
<section
|
|
908
|
+
class="${SECTION_CLASS} gap-4"
|
|
909
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
910
|
+
this._onKeydown(
|
|
911
|
+
e,
|
|
912
|
+
() => this._saveLocale(),
|
|
913
|
+
this._localeDirty,
|
|
914
|
+
this._localeLoading,
|
|
977
915
|
)}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
<
|
|
981
|
-
class="
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
(
|
|
986
|
-
this.
|
|
987
|
-
|
|
916
|
+
>
|
|
917
|
+
${this._renderSectionTitle(this.labels.timeSection)}
|
|
918
|
+
<div class="field">
|
|
919
|
+
<label class="label">${this.labels.timeZone}</label>
|
|
920
|
+
<select
|
|
921
|
+
class="select"
|
|
922
|
+
@change=${(e: Event) => {
|
|
923
|
+
this._timeZone = (e.target as HTMLSelectElement).value;
|
|
924
|
+
this._syncLocaleDirty();
|
|
925
|
+
}}
|
|
926
|
+
>
|
|
927
|
+
${this.timezones.map(
|
|
928
|
+
(tz) => html`
|
|
929
|
+
<option
|
|
930
|
+
value=${tz.value}
|
|
931
|
+
?selected=${this._timeZone === tz.value}
|
|
932
|
+
>
|
|
933
|
+
${tz.label}
|
|
934
|
+
</option>
|
|
935
|
+
`,
|
|
988
936
|
)}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
<div class="field">
|
|
992
|
-
<p class="label">${this.labels.mainRssFeed}</p>
|
|
993
|
-
<p class="text-sm text-muted-foreground mt-1">
|
|
994
|
-
${this.labels.mainRssFeedHelp}
|
|
995
|
-
</p>
|
|
996
|
-
<div class="mt-3 flex flex-col gap-2">
|
|
997
|
-
${this._renderMainRssFeedOption(
|
|
998
|
-
"featured",
|
|
999
|
-
this.labels.featuredFeedOption,
|
|
1000
|
-
this.labels.featuredFeedOptionDescription,
|
|
1001
|
-
)}
|
|
1002
|
-
${this._renderMainRssFeedOption(
|
|
1003
|
-
"latest",
|
|
1004
|
-
this.labels.latestFeedOption,
|
|
1005
|
-
this.labels.latestFeedOptionDescription,
|
|
1006
|
-
)}
|
|
1007
|
-
</div>
|
|
1008
|
-
<p class="text-sm text-muted-foreground mt-2">
|
|
1009
|
-
${this.labels.mainRssFeedWarning}
|
|
1010
|
-
</p>
|
|
1011
|
-
</div>
|
|
937
|
+
</select>
|
|
938
|
+
</div>
|
|
1012
939
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
<p class="text-sm text-muted-foreground">
|
|
1020
|
-
${this.labels.availableFeedUrlsHelp}
|
|
1021
|
-
</p>
|
|
1022
|
-
</div>
|
|
1023
|
-
|
|
1024
|
-
${this._renderFeedInfoRow(
|
|
1025
|
-
this.labels.mainFeedUrl,
|
|
1026
|
-
this.mainFeedUrl,
|
|
1027
|
-
)}
|
|
1028
|
-
${this._renderFeedInfoRow(
|
|
1029
|
-
this.labels.latestFeedUrl,
|
|
1030
|
-
this.latestFeedUrl,
|
|
1031
|
-
)}
|
|
1032
|
-
${this._renderFeedInfoRow(
|
|
1033
|
-
this.labels.featuredFeedUrl,
|
|
1034
|
-
this.featuredFeedUrl,
|
|
1035
|
-
)}
|
|
1036
|
-
${this._renderFeedInfoRow(
|
|
1037
|
-
this.labels.archiveFeedUrl,
|
|
1038
|
-
this.archiveFeedUrl,
|
|
1039
|
-
this.labels.archiveFeedUrlHelp,
|
|
1040
|
-
)}
|
|
1041
|
-
</div>
|
|
1042
|
-
</div>
|
|
940
|
+
${this._renderSaveAction(this._localeLoading, this._localeDirty, () =>
|
|
941
|
+
this._saveLocale(),
|
|
942
|
+
)}
|
|
943
|
+
</section>
|
|
944
|
+
`;
|
|
945
|
+
}
|
|
1043
946
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
947
|
+
private _renderHomeSection() {
|
|
948
|
+
return html`
|
|
949
|
+
<section class="${SECTION_CLASS} gap-4">
|
|
950
|
+
${this._renderSectionTitle(this.labels.home)}
|
|
951
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
952
|
+
<input
|
|
953
|
+
type="checkbox"
|
|
954
|
+
class="checkbox"
|
|
955
|
+
.checked=${this._showJantBrandingOnHome}
|
|
956
|
+
?disabled=${this._homeLoading}
|
|
957
|
+
@change=${(e: Event) =>
|
|
958
|
+
this._saveHomeToggle((e.target as HTMLInputElement).checked)}
|
|
959
|
+
/>
|
|
960
|
+
<span>${this.labels.showJantBrandingOnHome}</span>
|
|
961
|
+
</label>
|
|
962
|
+
</section>
|
|
963
|
+
`;
|
|
964
|
+
}
|
|
1048
965
|
|
|
1049
|
-
|
|
1050
|
-
|
|
966
|
+
/**
|
|
967
|
+
* Jant Discover.
|
|
968
|
+
*
|
|
969
|
+
* Saves on change, like the indexing checkbox next to it: the box is a
|
|
970
|
+
* complete answer on its own, so there is nothing a Save button would be
|
|
971
|
+
* waiting for. One box and no sub-choice — which of a blog's posts reach
|
|
972
|
+
* which list is the directory's own rule, stated on the directory.
|
|
973
|
+
*
|
|
974
|
+
* Ticking the box is also what announces a self-hosted site to the
|
|
975
|
+
* directory.
|
|
976
|
+
*/
|
|
977
|
+
private _renderDiscoverForm() {
|
|
978
|
+
const effective = this._effectiveDiscoverMode();
|
|
979
|
+
const enabled = effective !== "none";
|
|
980
|
+
const locked = this.demoMode || !this.feedsEnabled;
|
|
981
|
+
// The box unticks itself when search indexing goes off, and a control that
|
|
982
|
+
// moves on its own has to say why. Asked as a counterfactual rather than
|
|
983
|
+
// read off `noindex`, so the line appears only where it is the reason: a
|
|
984
|
+
// site the deployment would have listed, held back by that one setting.
|
|
985
|
+
// A site nobody would list either way is not being held back by anything,
|
|
986
|
+
// and an owner who ticked the box is not affected at all.
|
|
987
|
+
const heldBackBySearch =
|
|
988
|
+
!locked &&
|
|
989
|
+
!enabled &&
|
|
990
|
+
this._effectiveDiscoverMode({ noindex: false }) !== "none";
|
|
991
|
+
|
|
992
|
+
return html`
|
|
993
|
+
<div class="flex flex-col gap-3">
|
|
994
|
+
<div class="flex flex-col gap-2">
|
|
1051
995
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
1052
996
|
<input
|
|
1053
997
|
type="checkbox"
|
|
1054
998
|
class="checkbox"
|
|
1055
|
-
.checked=${
|
|
1056
|
-
?disabled=${this.
|
|
999
|
+
.checked=${enabled}
|
|
1000
|
+
?disabled=${locked || this._discoverLoading}
|
|
1057
1001
|
@change=${(e: Event) =>
|
|
1058
|
-
this.
|
|
1002
|
+
this._onDiscoverToggle((e.target as HTMLInputElement).checked)}
|
|
1059
1003
|
/>
|
|
1060
|
-
<span>${this.labels.
|
|
1004
|
+
<span>${this.labels.discoverEnabled}</span>
|
|
1061
1005
|
</label>
|
|
1062
|
-
|
|
1006
|
+
<p class="text-sm text-muted-foreground">
|
|
1007
|
+
${
|
|
1008
|
+
locked
|
|
1009
|
+
? this.demoMode
|
|
1010
|
+
? this.labels.discoverDemoLocked
|
|
1011
|
+
: this.labels.discoverFeedsOffLocked
|
|
1012
|
+
: this._renderDiscoverIntro()
|
|
1013
|
+
}
|
|
1014
|
+
</p>
|
|
1015
|
+
${
|
|
1016
|
+
heldBackBySearch
|
|
1017
|
+
? html`<p class="text-sm text-muted-foreground">
|
|
1018
|
+
${this.labels.discoverSearchOff}
|
|
1019
|
+
</p>`
|
|
1020
|
+
: nothing
|
|
1021
|
+
}
|
|
1022
|
+
</div>
|
|
1023
|
+
${locked ? nothing : this._renderDiscoverStatus()}
|
|
1063
1024
|
</div>
|
|
1064
1025
|
`;
|
|
1065
1026
|
}
|
|
1066
1027
|
|
|
1067
|
-
|
|
1028
|
+
/**
|
|
1029
|
+
* The help line, with each page it names linking to that page.
|
|
1030
|
+
*
|
|
1031
|
+
* Which words link where is `discoverIntroRuns`'s to decide, so the setup
|
|
1032
|
+
* screen, which shows the same line, links the same words.
|
|
1033
|
+
*/
|
|
1034
|
+
private _renderDiscoverIntro() {
|
|
1035
|
+
const runs = discoverIntroRuns(
|
|
1036
|
+
this.labels.discoverIntro ?? "",
|
|
1037
|
+
{
|
|
1038
|
+
name: this.labels.discoverName ?? "",
|
|
1039
|
+
rules: this.labels.discoverRules ?? "",
|
|
1040
|
+
},
|
|
1041
|
+
this.discoverPages,
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
return runs.map((run) =>
|
|
1045
|
+
run.href
|
|
1046
|
+
? html`<a
|
|
1047
|
+
href=${run.href}
|
|
1048
|
+
target="_blank"
|
|
1049
|
+
rel="noopener noreferrer"
|
|
1050
|
+
class="underline hover:text-foreground transition-colors"
|
|
1051
|
+
>${run.text}</a
|
|
1052
|
+
>`
|
|
1053
|
+
: run.text,
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* What is left to say once the controls above have spoken.
|
|
1059
|
+
*
|
|
1060
|
+
* Not a status report — a site whose Discover setting is doing exactly what
|
|
1061
|
+
* it says gets nothing here, and no heading announces a block that is
|
|
1062
|
+
* usually absent. The server sends a line only for a problem, a task, or an
|
|
1063
|
+
* answer still outstanding; deliberately all local evidence, because the
|
|
1064
|
+
* directory takes no status queries and cannot be asked whether a person has
|
|
1065
|
+
* moderated the site.
|
|
1066
|
+
*
|
|
1067
|
+
* The announce button appears when the directory has not heard from this
|
|
1068
|
+
* site; the manual form only when an announcement actually failed — beside a
|
|
1069
|
+
* working one it would read as a normal route in rather than the recovery it
|
|
1070
|
+
* is. The lines are what the server sent with the page, so ticking the box
|
|
1071
|
+
* does not rewrite them; the next load does.
|
|
1072
|
+
*/
|
|
1073
|
+
private _renderDiscoverStatus() {
|
|
1074
|
+
const status = this._parsedDiscoverStatus();
|
|
1075
|
+
if (!status) return nothing;
|
|
1076
|
+
// The server wrote these for the mode the page loaded with. A blog that
|
|
1077
|
+
// has just switched itself off is not the blog they describe.
|
|
1078
|
+
if (this._effectiveDiscoverMode() === "none") return nothing;
|
|
1079
|
+
if (status.lines.length === 0 && !status.showAnnounce) return nothing;
|
|
1080
|
+
|
|
1068
1081
|
return html`
|
|
1069
|
-
<
|
|
1070
|
-
${
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1082
|
+
<div class="flex flex-col gap-1 border-t pt-3">
|
|
1083
|
+
${status.lines.map(
|
|
1084
|
+
(line) => html`<p class="text-sm text-muted-foreground">${line}</p>`,
|
|
1085
|
+
)}
|
|
1086
|
+
${
|
|
1087
|
+
status.showAnnounce
|
|
1088
|
+
? html`
|
|
1089
|
+
<div class="flex flex-wrap items-center gap-3 mt-1">
|
|
1090
|
+
<button
|
|
1091
|
+
type="button"
|
|
1092
|
+
class="btn-outline"
|
|
1093
|
+
?disabled=${this._discoverLoading}
|
|
1094
|
+
@click=${() => this._announce()}
|
|
1095
|
+
>
|
|
1096
|
+
${this.labels.discoverAnnounce}
|
|
1097
|
+
</button>
|
|
1098
|
+
${
|
|
1099
|
+
status.submitUrl
|
|
1100
|
+
? html`<a
|
|
1101
|
+
class="text-sm underline hover:text-foreground transition-colors"
|
|
1102
|
+
href=${status.submitUrl}
|
|
1103
|
+
target="_blank"
|
|
1104
|
+
rel="noopener noreferrer"
|
|
1105
|
+
>${this.labels.discoverAnnounceManual}</a
|
|
1106
|
+
>`
|
|
1107
|
+
: nothing
|
|
1108
|
+
}
|
|
1109
|
+
</div>
|
|
1110
|
+
`
|
|
1111
|
+
: nothing
|
|
1112
|
+
}
|
|
1113
|
+
</div>
|
|
1088
1114
|
`;
|
|
1089
1115
|
}
|
|
1090
1116
|
|
|
1117
|
+
private _parsedDiscoverStatus(): {
|
|
1118
|
+
lines: string[];
|
|
1119
|
+
showAnnounce: boolean;
|
|
1120
|
+
submitUrl: string | null;
|
|
1121
|
+
} | null {
|
|
1122
|
+
if (!this.discoverStatus) return null;
|
|
1123
|
+
try {
|
|
1124
|
+
const parsed: unknown = JSON.parse(this.discoverStatus);
|
|
1125
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1126
|
+
const value = parsed as Record<string, unknown>;
|
|
1127
|
+
const lines = Array.isArray(value["lines"])
|
|
1128
|
+
? value["lines"].filter(
|
|
1129
|
+
(line): line is string => typeof line === "string",
|
|
1130
|
+
)
|
|
1131
|
+
: [];
|
|
1132
|
+
return {
|
|
1133
|
+
lines,
|
|
1134
|
+
showAnnounce: value["showAnnounce"] === true,
|
|
1135
|
+
submitUrl:
|
|
1136
|
+
typeof value["submitUrl"] === "string" ? value["submitUrl"] : null,
|
|
1137
|
+
};
|
|
1138
|
+
} catch {
|
|
1139
|
+
// A settings page that renders without its status block is far better
|
|
1140
|
+
// than one that does not render.
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
private _announce() {
|
|
1146
|
+
if (this._discoverLoading) return;
|
|
1147
|
+
this._discoverLoading = true;
|
|
1148
|
+
this.dispatchEvent(
|
|
1149
|
+
new CustomEvent("jant:settings-save", {
|
|
1150
|
+
bubbles: true,
|
|
1151
|
+
detail: {
|
|
1152
|
+
endpoint: "/settings/general/discover/announce",
|
|
1153
|
+
data: {},
|
|
1154
|
+
section: "discover",
|
|
1155
|
+
},
|
|
1156
|
+
}),
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1091
1160
|
render() {
|
|
1092
1161
|
return html`
|
|
1093
|
-
<div class="flex flex-col">
|
|
1094
|
-
|
|
1162
|
+
<div class="flex flex-col gap-8">
|
|
1163
|
+
<h2 class="text-lg font-semibold">${this.labels.general}</h2>
|
|
1164
|
+
<div class="flex flex-col divide-y">
|
|
1165
|
+
${this._renderSiteSection()} ${this._renderVisibilitySection()}
|
|
1166
|
+
${this._renderFeedsSection()} ${this._renderTimeSection()}
|
|
1167
|
+
${this._renderHomeSection()}
|
|
1168
|
+
</div>
|
|
1095
1169
|
</div>
|
|
1096
1170
|
`;
|
|
1097
1171
|
}
|