@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
|
@@ -4,12 +4,11 @@ import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
|
4
4
|
import type {
|
|
5
5
|
SettingsLabels,
|
|
6
6
|
SettingsTimezone,
|
|
7
|
-
SettingsCjkFont,
|
|
8
|
-
SettingsDashboardLanguage,
|
|
9
7
|
SettingsSaveDetail,
|
|
10
8
|
SettingsAboutPageStatus,
|
|
11
9
|
} from "../settings-types.js";
|
|
12
10
|
import { MAX_SITE_NAME_LENGTH } from "../../../types.js";
|
|
11
|
+
import type { DiscoverPageUrls } from "../../../lib/discover.js";
|
|
13
12
|
import "../jant-settings-general.js";
|
|
14
13
|
import type { JantSettingsGeneral } from "../jant-settings-general.js";
|
|
15
14
|
|
|
@@ -43,13 +42,18 @@ function findRadioByValue(
|
|
|
43
42
|
);
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Matched exactly: "Site" is a prefix of "Site visibility", so a substring
|
|
47
|
+
* match would find whichever of the two comes first on the page.
|
|
48
|
+
*/
|
|
46
49
|
function findSectionByHeading(
|
|
47
50
|
el: HTMLElement,
|
|
48
51
|
headingText: string,
|
|
49
52
|
): HTMLElement | null {
|
|
50
53
|
return (
|
|
51
|
-
Array.from(el.querySelectorAll<HTMLElement>("section")).find(
|
|
52
|
-
section
|
|
54
|
+
Array.from(el.querySelectorAll<HTMLElement>("section")).find(
|
|
55
|
+
(section) =>
|
|
56
|
+
section.querySelector("h3")?.textContent?.trim() === headingText,
|
|
53
57
|
) ?? null
|
|
54
58
|
);
|
|
55
59
|
}
|
|
@@ -83,22 +87,12 @@ const labels: SettingsLabels = {
|
|
|
83
87
|
"/about is already used. Rename that item before creating an About page.",
|
|
84
88
|
createAboutPage: "Create About page",
|
|
85
89
|
editAboutPage: "Edit About page",
|
|
86
|
-
|
|
90
|
+
timeSection: "Time",
|
|
87
91
|
home: "Home",
|
|
88
|
-
|
|
92
|
+
siteVisibility: "Site visibility",
|
|
89
93
|
siteName: "Site Name",
|
|
90
94
|
aboutBlog: "About this blog",
|
|
91
95
|
aboutBlogHelp: "Displayed above your blog posts.",
|
|
92
|
-
siteLanguage: "Content language",
|
|
93
|
-
siteLanguageHelp: "The language your posts are written in.",
|
|
94
|
-
siteLanguageSearchPlaceholder: "Search…",
|
|
95
|
-
siteLanguageNoMatches: "No matches.",
|
|
96
|
-
contentLanguagePreview: "Readers and search engines see",
|
|
97
|
-
dashboardLanguage: "Dashboard language",
|
|
98
|
-
dashboardLanguageHelp: "The language this admin dashboard shows in.",
|
|
99
|
-
cjkFont: "CJK fallback",
|
|
100
|
-
cjkFontHelp:
|
|
101
|
-
"Used when the content language has no built-in font profile. Your font theme still controls serif and sans styling.",
|
|
102
96
|
timeZone: "Time Zone",
|
|
103
97
|
feeds: "Feeds",
|
|
104
98
|
mainRssFeed: "Main RSS feed",
|
|
@@ -124,11 +118,24 @@ const labels: SettingsLabels = {
|
|
|
124
118
|
markdownSupported: "Markdown supported",
|
|
125
119
|
allowIndexing: "Allow search engines to index my site",
|
|
126
120
|
demoSeoLocked: "Demo sites always stay hidden from search engines.",
|
|
121
|
+
discoverName: "Jant Discover",
|
|
122
|
+
discoverRules: "Discover community rules",
|
|
123
|
+
discoverEnabled: "Allow Jant Discover to list my site",
|
|
124
|
+
discoverIntro:
|
|
125
|
+
"Jant Discover is a directory of Jant blogs, curated by hand by the Jant community to help people find new Jant blogs and posts. A post you mark Featured appears on the Discover home page 24 hours later, and link and quote posts appear on the Links and Quotes lists 24 hours after they are published. You can keep editing them in the meantime. See the Discover community rules.",
|
|
126
|
+
discoverDemoLocked: "Demo sites are never listed in Discover.",
|
|
127
|
+
discoverFeedsOffLocked:
|
|
128
|
+
"Discover reads your Atom feed, so it needs feeds turned on.",
|
|
129
|
+
discoverSearchOff:
|
|
130
|
+
"Search engine indexing is off, so this site is not listed by default. Ticking the box above lists it anyway.",
|
|
131
|
+
discoverAnnounce: "Announce my site",
|
|
132
|
+
discoverAnnounceManual: "Or submit your address by hand",
|
|
127
133
|
save: "Save",
|
|
128
134
|
cancel: "Cancel",
|
|
129
135
|
copy: "Copy",
|
|
130
136
|
copyFailed: "Could not copy. Try again.",
|
|
131
137
|
feedUrlCopied: "Feed URL copied.",
|
|
138
|
+
feedsDocs: "All feed addresses",
|
|
132
139
|
};
|
|
133
140
|
|
|
134
141
|
const timezones: SettingsTimezone[] = [
|
|
@@ -136,28 +143,22 @@ const timezones: SettingsTimezone[] = [
|
|
|
136
143
|
{ value: "America/New_York", label: "(UTC-05:00) Eastern Time" },
|
|
137
144
|
];
|
|
138
145
|
|
|
139
|
-
const cjkFonts: SettingsCjkFont[] = [
|
|
140
|
-
{ value: "off", label: "Follow content language" },
|
|
141
|
-
{ value: "zh-Hans", label: "简体中文 (Simplified Chinese)" },
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
const dashboardLanguages: SettingsDashboardLanguage[] = [
|
|
145
|
-
{ value: "en", label: "English" },
|
|
146
|
-
{ value: "zh-Hans", label: "简体中文" },
|
|
147
|
-
{ value: "zh-Hant", label: "繁體中文" },
|
|
148
|
-
];
|
|
149
|
-
|
|
150
146
|
const initialData = {
|
|
151
147
|
siteName: "My Blog",
|
|
152
148
|
siteDescription: "A test blog",
|
|
153
|
-
siteLanguage: "en",
|
|
154
|
-
dashboardLanguage: "en",
|
|
155
|
-
cjkSerifFont: "off",
|
|
156
149
|
timeZone: "UTC",
|
|
157
150
|
mainRssFeed: "featured",
|
|
158
151
|
siteFooter: "Footer text",
|
|
159
152
|
showJantBrandingOnHome: false,
|
|
160
153
|
noindex: false,
|
|
154
|
+
discover: "",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const discoverPages: DiscoverPageUrls = {
|
|
158
|
+
home: "https://jant.me/discover",
|
|
159
|
+
links: "https://jant.me/links",
|
|
160
|
+
quotes: "https://jant.me/quotes",
|
|
161
|
+
rules: "https://jant.me/discover/about",
|
|
161
162
|
};
|
|
162
163
|
|
|
163
164
|
function findCheckboxByLabel(
|
|
@@ -171,10 +172,27 @@ function findCheckboxByLabel(
|
|
|
171
172
|
);
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
/**
|
|
176
|
+
* The Discover help line, found by its text: it is one of several muted
|
|
177
|
+
* paragraphs in the section, and only this one carries the directory's links.
|
|
178
|
+
*/
|
|
179
|
+
function findIntroParagraph(el: HTMLElement): HTMLElement | null {
|
|
180
|
+
return (
|
|
181
|
+
Array.from(el.querySelectorAll<HTMLElement>("p")).find(
|
|
182
|
+
(paragraph) => paragraph.textContent?.trim() === labels.discoverIntro,
|
|
183
|
+
) ?? null
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
async function createElement(
|
|
175
188
|
opts: {
|
|
176
189
|
demoMode?: boolean;
|
|
190
|
+
feedsEnabled?: boolean;
|
|
177
191
|
aboutPage?: SettingsAboutPageStatus;
|
|
192
|
+
/** The deployment's own answer: "" when it has none. */
|
|
193
|
+
discoverDefault?: string;
|
|
194
|
+
/** Serialized status view, as the server hands it to the component. */
|
|
195
|
+
discoverStatus?: string;
|
|
178
196
|
} = {},
|
|
179
197
|
): Promise<JantSettingsGeneral> {
|
|
180
198
|
const el = document.createElement(
|
|
@@ -182,8 +200,6 @@ async function createElement(
|
|
|
182
200
|
) as JantSettingsGeneral;
|
|
183
201
|
el.labels = labels;
|
|
184
202
|
el.timezones = timezones;
|
|
185
|
-
el.cjkFonts = cjkFonts;
|
|
186
|
-
el.dashboardLanguages = dashboardLanguages;
|
|
187
203
|
el.siteNameFallback = "Fallback Name";
|
|
188
204
|
el.siteDescriptionFallback = "Fallback Description";
|
|
189
205
|
el.mainFeedUrl = "/feed";
|
|
@@ -199,6 +215,10 @@ async function createElement(
|
|
|
199
215
|
el.aboutEditUrl = "/about?edit=1";
|
|
200
216
|
el.aboutCreateUrl = "/settings/general/about-page";
|
|
201
217
|
el.demoMode = opts.demoMode ?? false;
|
|
218
|
+
el.discoverPages = discoverPages;
|
|
219
|
+
el.discoverDefault = opts.discoverDefault ?? "";
|
|
220
|
+
el.discoverStatus = opts.discoverStatus ?? "";
|
|
221
|
+
el.feedsEnabled = opts.feedsEnabled ?? true;
|
|
202
222
|
document.body.appendChild(el);
|
|
203
223
|
await el.updateComplete;
|
|
204
224
|
el.initData(initialData);
|
|
@@ -220,10 +240,10 @@ describe("JantSettingsGeneral", () => {
|
|
|
220
240
|
expect(el.querySelector("h2")?.textContent).toBe("General");
|
|
221
241
|
expect(groupTitles).toEqual([
|
|
222
242
|
labels.site,
|
|
223
|
-
labels.
|
|
243
|
+
labels.siteVisibility,
|
|
224
244
|
labels.feeds,
|
|
245
|
+
labels.timeSection,
|
|
225
246
|
labels.home,
|
|
226
|
-
labels.search,
|
|
227
247
|
]);
|
|
228
248
|
|
|
229
249
|
const siteSection = requireElement(
|
|
@@ -309,121 +329,6 @@ describe("JantSettingsGeneral", () => {
|
|
|
309
329
|
expect(options?.[0]?.value).toBe("UTC");
|
|
310
330
|
});
|
|
311
331
|
|
|
312
|
-
it("opens the locale combobox and filters options as the user searches", async () => {
|
|
313
|
-
const el = await createElement();
|
|
314
|
-
|
|
315
|
-
const trigger = requireElement(
|
|
316
|
-
el.querySelector<HTMLButtonElement>(
|
|
317
|
-
'button[aria-haspopup="listbox"][aria-labelledby="site-language-label"]',
|
|
318
|
-
),
|
|
319
|
-
"expected locale picker trigger",
|
|
320
|
-
);
|
|
321
|
-
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
322
|
-
|
|
323
|
-
trigger.click();
|
|
324
|
-
await el.updateComplete;
|
|
325
|
-
|
|
326
|
-
expect(trigger.getAttribute("aria-expanded")).toBe("true");
|
|
327
|
-
|
|
328
|
-
const options = el.querySelectorAll<HTMLButtonElement>('[role="option"]');
|
|
329
|
-
// The content-language picker lists the full BCP 47 catalog so any public
|
|
330
|
-
// content language is reachable. Coverage / raw tags are not shown here.
|
|
331
|
-
expect(options.length).toBeGreaterThanOrEqual(20);
|
|
332
|
-
for (const option of options) {
|
|
333
|
-
expect(option.textContent).not.toMatch(/% translated/);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const search = requireElement(
|
|
337
|
-
el.querySelector<HTMLInputElement>("[data-locale-search]"),
|
|
338
|
-
"expected search input",
|
|
339
|
-
);
|
|
340
|
-
search.value = "fin";
|
|
341
|
-
search.dispatchEvent(new Event("input", { bubbles: true }));
|
|
342
|
-
await el.updateComplete;
|
|
343
|
-
|
|
344
|
-
const filtered = el.querySelectorAll<HTMLButtonElement>('[role="option"]');
|
|
345
|
-
expect(filtered.length).toBe(1);
|
|
346
|
-
expect(filtered[0]?.textContent).toMatch(/Suomi|Finnish/);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it("selects a non-catalog content language and shows its native name", async () => {
|
|
350
|
-
const el = await createElement();
|
|
351
|
-
const trigger = requireElement(
|
|
352
|
-
el.querySelector<HTMLButtonElement>(
|
|
353
|
-
'button[aria-labelledby="site-language-label"]',
|
|
354
|
-
),
|
|
355
|
-
"expected trigger",
|
|
356
|
-
);
|
|
357
|
-
trigger.click();
|
|
358
|
-
await el.updateComplete;
|
|
359
|
-
|
|
360
|
-
const search = requireElement(
|
|
361
|
-
el.querySelector<HTMLInputElement>("[data-locale-search]"),
|
|
362
|
-
"expected search input",
|
|
363
|
-
);
|
|
364
|
-
search.value = "fi";
|
|
365
|
-
search.dispatchEvent(new Event("input", { bubbles: true }));
|
|
366
|
-
await el.updateComplete;
|
|
367
|
-
|
|
368
|
-
const finnishOption = Array.from(
|
|
369
|
-
el.querySelectorAll<HTMLButtonElement>('[role="option"]'),
|
|
370
|
-
).find((opt) => /Suomi|Finnish/.test(opt.textContent ?? ""));
|
|
371
|
-
finnishOption?.click();
|
|
372
|
-
await el.updateComplete;
|
|
373
|
-
|
|
374
|
-
// Picker closes after selection.
|
|
375
|
-
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
376
|
-
// Trigger shows the selected language's native name only — no raw BCP 47
|
|
377
|
-
// tag, no coverage metric.
|
|
378
|
-
expect(trigger.textContent).toMatch(/suomi|finnish/i);
|
|
379
|
-
expect(trigger.textContent).not.toMatch(/% translated/);
|
|
380
|
-
expect(trigger.textContent).not.toMatch(/\bfi\b/);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("renders dashboard language options and saves the selection", async () => {
|
|
384
|
-
const el = await createElement();
|
|
385
|
-
const select = requireElement(
|
|
386
|
-
el.querySelector(
|
|
387
|
-
'select[aria-labelledby="dashboard-language-label"]',
|
|
388
|
-
) as globalThis.HTMLSelectElement | null,
|
|
389
|
-
"expected dashboard language select",
|
|
390
|
-
);
|
|
391
|
-
const values = Array.from(select.options).map((o) => o.value);
|
|
392
|
-
expect(values).toEqual(["en", "zh-Hans", "zh-Hant"]);
|
|
393
|
-
expect(select.value).toBe("en");
|
|
394
|
-
|
|
395
|
-
const saves: SettingsSaveDetail[] = [];
|
|
396
|
-
el.addEventListener("jant:settings-save", (e) => {
|
|
397
|
-
saves.push((e as CustomEvent<SettingsSaveDetail>).detail);
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
select.value = "zh-Hant";
|
|
401
|
-
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
402
|
-
await el.updateComplete;
|
|
403
|
-
|
|
404
|
-
const saveButton = requireElement(
|
|
405
|
-
findSaveButtonByHeading(el, labels.languageAndTime),
|
|
406
|
-
"expected language & time save button",
|
|
407
|
-
);
|
|
408
|
-
saveButton.click();
|
|
409
|
-
|
|
410
|
-
expect(saves).toHaveLength(1);
|
|
411
|
-
expect(saves[0]?.endpoint).toBe("/settings/general/language-time");
|
|
412
|
-
expect(saves[0]?.data.dashboardLanguage).toBe("zh-Hant");
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it("renders CJK font options", async () => {
|
|
416
|
-
const el = await createElement();
|
|
417
|
-
const cjkSelect = requireElement(
|
|
418
|
-
findSelectByLabel(el, labels.cjkFont),
|
|
419
|
-
"expected CJK font select",
|
|
420
|
-
);
|
|
421
|
-
const options = cjkSelect?.querySelectorAll("option");
|
|
422
|
-
expect(options?.length).toBe(2);
|
|
423
|
-
expect(options?.[0]?.value).toBe("off");
|
|
424
|
-
expect(options?.[1]?.value).toBe("zh-Hans");
|
|
425
|
-
});
|
|
426
|
-
|
|
427
332
|
it("renders main RSS feed controls and fixed feed URLs", async () => {
|
|
428
333
|
const el = await createElement();
|
|
429
334
|
const featuredRadio = requireElement(
|
|
@@ -451,26 +356,37 @@ describe("JantSettingsGeneral", () => {
|
|
|
451
356
|
]);
|
|
452
357
|
});
|
|
453
358
|
|
|
454
|
-
it("
|
|
455
|
-
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
456
|
-
Object.defineProperty(globalThis.navigator, "clipboard", {
|
|
457
|
-
value: { writeText },
|
|
458
|
-
configurable: true,
|
|
459
|
-
});
|
|
460
|
-
|
|
359
|
+
it("marks up each feed URL for the copy-field enhancer", async () => {
|
|
461
360
|
const el = await createElement();
|
|
462
361
|
const feedSection = requireElement(
|
|
463
362
|
findSectionByHeading(el, labels.feeds),
|
|
464
363
|
"expected feeds section",
|
|
465
364
|
);
|
|
466
|
-
const
|
|
467
|
-
"button[data-copy-feed-url]",
|
|
468
|
-
);
|
|
365
|
+
const fields = feedSection.querySelectorAll("[data-copy-field-root]");
|
|
469
366
|
|
|
470
|
-
|
|
471
|
-
|
|
367
|
+
expect(fields).toHaveLength(4);
|
|
368
|
+
|
|
369
|
+
const firstField = requireElement(
|
|
370
|
+
fields[0],
|
|
371
|
+
"expected the main feed field",
|
|
372
|
+
);
|
|
373
|
+
const input = requireElement(
|
|
374
|
+
firstField.querySelector<HTMLInputElement>(
|
|
375
|
+
"input[data-copy-field-value]",
|
|
376
|
+
),
|
|
377
|
+
"expected the address input",
|
|
378
|
+
);
|
|
379
|
+
const button = requireElement(
|
|
380
|
+
firstField.querySelector<HTMLButtonElement>("button[data-copy-field]"),
|
|
381
|
+
"expected the copy button",
|
|
382
|
+
);
|
|
472
383
|
|
|
473
|
-
expect(
|
|
384
|
+
expect(input.value).toBe("/feed");
|
|
385
|
+
expect(button.getAttribute("data-copy-field")).toBe(labels.feedUrlCopied);
|
|
386
|
+
expect(button.getAttribute("data-copy-field-failed")).toBe(
|
|
387
|
+
labels.copyFailed,
|
|
388
|
+
);
|
|
389
|
+
expect(button.textContent).toContain(labels.copy);
|
|
474
390
|
});
|
|
475
391
|
|
|
476
392
|
it("tracks site group dirty state on input", async () => {
|
|
@@ -534,48 +450,69 @@ describe("JantSettingsGeneral", () => {
|
|
|
534
450
|
detail = (event as CustomEvent<SettingsSaveDetail>).detail;
|
|
535
451
|
});
|
|
536
452
|
|
|
537
|
-
const saveBtn = findSaveButtonByHeading(el, labels.
|
|
453
|
+
const saveBtn = findSaveButtonByHeading(el, labels.timeSection);
|
|
538
454
|
saveBtn?.click();
|
|
539
455
|
await el.updateComplete;
|
|
540
456
|
|
|
541
457
|
expect(detail).not.toBeNull();
|
|
542
458
|
expect((detail as unknown as SettingsSaveDetail).endpoint).toBe(
|
|
543
|
-
"/settings/general/
|
|
544
|
-
);
|
|
545
|
-
expect((detail as unknown as SettingsSaveDetail).section).toBe(
|
|
546
|
-
"language-time",
|
|
459
|
+
"/settings/general/time",
|
|
547
460
|
);
|
|
461
|
+
expect((detail as unknown as SettingsSaveDetail).section).toBe("time");
|
|
548
462
|
expect((detail as unknown as SettingsSaveDetail).data.timeZone).toBe(
|
|
549
463
|
"America/New_York",
|
|
550
464
|
);
|
|
551
465
|
});
|
|
552
466
|
|
|
553
|
-
it("
|
|
467
|
+
it("auto-saves the main RSS feed choice without a save button", async () => {
|
|
554
468
|
const el = await createElement();
|
|
555
469
|
const latestRadio = requireElement(
|
|
556
470
|
findRadioByValue(el, "main-rss-feed", "latest"),
|
|
557
471
|
"expected latest radio option",
|
|
558
472
|
);
|
|
559
473
|
|
|
560
|
-
|
|
561
|
-
await el.updateComplete;
|
|
474
|
+
expect(findSaveButtonByHeading(el, labels.feeds)).toBeNull();
|
|
562
475
|
|
|
563
476
|
let detail: SettingsSaveDetail | null = null;
|
|
564
477
|
el.addEventListener("jant:settings-save", (event) => {
|
|
565
478
|
detail = (event as CustomEvent<SettingsSaveDetail>).detail;
|
|
566
479
|
});
|
|
567
480
|
|
|
568
|
-
|
|
569
|
-
saveBtn?.click();
|
|
481
|
+
latestRadio.click();
|
|
570
482
|
await el.updateComplete;
|
|
571
483
|
|
|
572
484
|
expect(detail).not.toBeNull();
|
|
573
485
|
expect((detail as unknown as SettingsSaveDetail).endpoint).toBe(
|
|
574
486
|
"/settings/general/feeds",
|
|
575
487
|
);
|
|
488
|
+
expect((detail as unknown as SettingsSaveDetail).section).toBe("feeds");
|
|
576
489
|
expect((detail as unknown as SettingsSaveDetail).data.mainRssFeed).toBe(
|
|
577
490
|
"latest",
|
|
578
491
|
);
|
|
492
|
+
expect(latestRadio.checked).toBe(true);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("sectionError for the feed choice restores the saved value", async () => {
|
|
496
|
+
const el = await createElement();
|
|
497
|
+
const latestRadio = requireElement(
|
|
498
|
+
findRadioByValue(el, "main-rss-feed", "latest"),
|
|
499
|
+
"expected latest radio option",
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
latestRadio.click();
|
|
503
|
+
await el.updateComplete;
|
|
504
|
+
expect(latestRadio.checked).toBe(true);
|
|
505
|
+
|
|
506
|
+
el.sectionError("feeds");
|
|
507
|
+
await el.updateComplete;
|
|
508
|
+
|
|
509
|
+
expect(latestRadio.checked).toBe(false);
|
|
510
|
+
expect(
|
|
511
|
+
requireElement(
|
|
512
|
+
findRadioByValue(el, "main-rss-feed", "featured"),
|
|
513
|
+
"expected featured radio option",
|
|
514
|
+
).checked,
|
|
515
|
+
).toBe(true);
|
|
579
516
|
});
|
|
580
517
|
|
|
581
518
|
it("sectionSaved resets site dirty state and updates originals", async () => {
|
|
@@ -735,4 +672,436 @@ describe("JantSettingsGeneral", () => {
|
|
|
735
672
|
const spinner = saveBtn?.querySelector("svg.animate-spin");
|
|
736
673
|
expect(spinner).not.toBeNull();
|
|
737
674
|
});
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Every control here saves on change, like the indexing checkbox beside it:
|
|
678
|
+
* ticking the box stores the default mode, and a mode is stored the moment
|
|
679
|
+
* it is picked. Ticking the box is also how a self-hosted site opts in, and
|
|
680
|
+
* that first save is what announces it to the directory.
|
|
681
|
+
*/
|
|
682
|
+
describe("Discover", () => {
|
|
683
|
+
it("renders the section under Site visibility", async () => {
|
|
684
|
+
const el = await createElement();
|
|
685
|
+
|
|
686
|
+
expect(findSectionByHeading(el, labels.siteVisibility)).not.toBeNull();
|
|
687
|
+
expect(el.textContent).toContain(labels.discoverEnabled);
|
|
688
|
+
expect(el.textContent).toContain(labels.discoverIntro);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
// Opt-in: a self-hosted site nobody configured shows the box unticked.
|
|
692
|
+
it("starts off for a site with no deployment default", async () => {
|
|
693
|
+
const el = await createElement();
|
|
694
|
+
const toggle = requireElement(
|
|
695
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
696
|
+
"expected the Discover checkbox",
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
expect(toggle.checked).toBe(false);
|
|
700
|
+
|
|
701
|
+
toggle.click();
|
|
702
|
+
await el.updateComplete;
|
|
703
|
+
|
|
704
|
+
expect(toggle.checked).toBe(true);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// Hosted Jant sets `DISCOVER=latest`, and an owner who has never opened
|
|
708
|
+
// this page must not be shown "off" while their feeds say otherwise.
|
|
709
|
+
it("starts on when the deployment lists its blogs", async () => {
|
|
710
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
711
|
+
|
|
712
|
+
expect(
|
|
713
|
+
requireElement(
|
|
714
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
715
|
+
"expected the Discover checkbox",
|
|
716
|
+
).checked,
|
|
717
|
+
).toBe(true);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
// The section carries no Save button of its own: the only button it can
|
|
721
|
+
// render is the announcement retry, which needs a failed announcement.
|
|
722
|
+
it("offers no Save button", async () => {
|
|
723
|
+
const el = await createElement();
|
|
724
|
+
const section = requireElement(
|
|
725
|
+
findSectionByHeading(el, labels.siteVisibility),
|
|
726
|
+
"expected the Site visibility section",
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
expect(section.querySelectorAll("button").length).toBe(0);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("sends latest when the box is ticked", async () => {
|
|
733
|
+
const el = await createElement();
|
|
734
|
+
let detail: SettingsSaveDetail | null = null;
|
|
735
|
+
el.addEventListener("jant:settings-save", (event) => {
|
|
736
|
+
detail = (event as CustomEvent<SettingsSaveDetail>).detail;
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
requireElement(
|
|
740
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
741
|
+
"expected the Discover checkbox",
|
|
742
|
+
).click();
|
|
743
|
+
await el.updateComplete;
|
|
744
|
+
|
|
745
|
+
const d = detail as unknown as SettingsSaveDetail;
|
|
746
|
+
expect(d.endpoint).toBe("/settings/general/discover");
|
|
747
|
+
expect(d.section).toBe("discover");
|
|
748
|
+
expect(d.data.discover).toBe("latest");
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it("sends off when the box is unticked", async () => {
|
|
752
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
753
|
+
let detail: SettingsSaveDetail | null = null;
|
|
754
|
+
el.addEventListener("jant:settings-save", (event) => {
|
|
755
|
+
detail = (event as CustomEvent<SettingsSaveDetail>).detail;
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
requireElement(
|
|
759
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
760
|
+
"expected the Discover checkbox",
|
|
761
|
+
).click();
|
|
762
|
+
await el.updateComplete;
|
|
763
|
+
|
|
764
|
+
expect((detail as unknown as SettingsSaveDetail).data.discover).toBe(
|
|
765
|
+
"off",
|
|
766
|
+
);
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// A site that chose the old featured-only mode reads as on, and the box
|
|
770
|
+
// widens it only when the owner ticks it again — never on its own.
|
|
771
|
+
it("keeps a stored featured choice ticked, and writes latest on re-tick", async () => {
|
|
772
|
+
const el = await createElement();
|
|
773
|
+
el.initData({ ...initialData, discover: "featured" });
|
|
774
|
+
await el.updateComplete;
|
|
775
|
+
const details: SettingsSaveDetail[] = [];
|
|
776
|
+
el.addEventListener("jant:settings-save", (event) => {
|
|
777
|
+
details.push((event as CustomEvent<SettingsSaveDetail>).detail);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
const toggle = requireElement(
|
|
781
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
782
|
+
"expected the Discover checkbox",
|
|
783
|
+
);
|
|
784
|
+
expect(toggle.checked).toBe(true);
|
|
785
|
+
expect(details).toHaveLength(0);
|
|
786
|
+
|
|
787
|
+
toggle.click();
|
|
788
|
+
await el.updateComplete;
|
|
789
|
+
expect(details[0]?.data.discover).toBe("off");
|
|
790
|
+
|
|
791
|
+
el.sectionSaved("discover");
|
|
792
|
+
await el.updateComplete;
|
|
793
|
+
requireElement(
|
|
794
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
795
|
+
"expected the Discover checkbox",
|
|
796
|
+
).click();
|
|
797
|
+
await el.updateComplete;
|
|
798
|
+
expect(details[1]?.data.discover).toBe("latest");
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// The controls stay disabled until the save answers, so a second click
|
|
802
|
+
// cannot race the first.
|
|
803
|
+
it("disables the controls while a save is in flight", async () => {
|
|
804
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
805
|
+
const toggle = requireElement(
|
|
806
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
807
|
+
"expected the Discover checkbox",
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
toggle.click();
|
|
811
|
+
await el.updateComplete;
|
|
812
|
+
|
|
813
|
+
expect(toggle.disabled).toBe(true);
|
|
814
|
+
|
|
815
|
+
el.sectionSaved("discover");
|
|
816
|
+
await el.updateComplete;
|
|
817
|
+
|
|
818
|
+
expect(
|
|
819
|
+
requireElement(
|
|
820
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
821
|
+
"expected the Discover checkbox",
|
|
822
|
+
).disabled,
|
|
823
|
+
).toBe(false);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
// A failed save leaves the page describing the site as it still is.
|
|
827
|
+
it("restores the stored choice when the save fails", async () => {
|
|
828
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
829
|
+
const toggle = requireElement(
|
|
830
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
831
|
+
"expected the Discover checkbox",
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
toggle.click();
|
|
835
|
+
await el.updateComplete;
|
|
836
|
+
el.sectionError("discover");
|
|
837
|
+
await el.updateComplete;
|
|
838
|
+
|
|
839
|
+
expect(
|
|
840
|
+
requireElement(
|
|
841
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
842
|
+
"expected the Discover checkbox",
|
|
843
|
+
).checked,
|
|
844
|
+
).toBe(true);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it("locks the control off for a demo site", async () => {
|
|
848
|
+
const el = await createElement({ demoMode: true });
|
|
849
|
+
const toggle = requireElement(
|
|
850
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
851
|
+
"expected the Discover checkbox",
|
|
852
|
+
);
|
|
853
|
+
|
|
854
|
+
expect(toggle.disabled).toBe(true);
|
|
855
|
+
expect(toggle.checked).toBe(false);
|
|
856
|
+
expect(el.textContent).toContain(labels.discoverDemoLocked);
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
// Discover reads the Atom feed, so with feeds off there is nothing to read.
|
|
860
|
+
it("locks the control off when the site publishes no feeds", async () => {
|
|
861
|
+
const el = await createElement({ feedsEnabled: false });
|
|
862
|
+
const toggle = requireElement(
|
|
863
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
864
|
+
"expected the Discover checkbox",
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
expect(toggle.disabled).toBe(true);
|
|
868
|
+
expect(el.textContent).toContain(labels.discoverFeedsOffLocked);
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
// What Discover is, is best answered by the list itself, so the sentence
|
|
872
|
+
// explaining the list is the way into it: the name goes to the home, the
|
|
873
|
+
// list names to the lists, and the rules to the page of rules.
|
|
874
|
+
it("links each page the help line names to that page", async () => {
|
|
875
|
+
const el = await createElement();
|
|
876
|
+
const intro = requireElement(
|
|
877
|
+
findIntroParagraph(el),
|
|
878
|
+
"expected the Discover help line",
|
|
879
|
+
);
|
|
880
|
+
const links = Array.from(intro.querySelectorAll<HTMLAnchorElement>("a"));
|
|
881
|
+
|
|
882
|
+
expect(
|
|
883
|
+
links.map((link) => [link.textContent, link.getAttribute("href")]),
|
|
884
|
+
).toEqual([
|
|
885
|
+
[labels.discoverName, discoverPages.home],
|
|
886
|
+
["Links", discoverPages.links],
|
|
887
|
+
["Quotes", discoverPages.quotes],
|
|
888
|
+
[labels.discoverRules, discoverPages.rules],
|
|
889
|
+
]);
|
|
890
|
+
for (const link of links) {
|
|
891
|
+
expect(link.getAttribute("target")).toBe("_blank");
|
|
892
|
+
expect(link.getAttribute("rel")).toBe("noopener noreferrer");
|
|
893
|
+
expect(link.className).toContain("underline");
|
|
894
|
+
}
|
|
895
|
+
// The whole line still reads as one sentence.
|
|
896
|
+
expect(intro.textContent?.trim()).toBe(labels.discoverIntro);
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// A `<label>` forwards a click on any descendant to its control, so a link
|
|
900
|
+
// inside it would open the directory and flip the setting on the way out.
|
|
901
|
+
it("keeps the directory link out of the checkbox label", async () => {
|
|
902
|
+
const el = await createElement();
|
|
903
|
+
const label = requireElement(
|
|
904
|
+
findCheckboxByLabel(el, labels.discoverEnabled)?.closest("label"),
|
|
905
|
+
"expected the Discover checkbox label",
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
expect(label.querySelector("a")).toBeNull();
|
|
909
|
+
expect(label.textContent).toContain(labels.discoverEnabled);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
// A directory this deployment does not announce to has no address to
|
|
913
|
+
// link, and a sentence with a dead link in it is worse than a plain one.
|
|
914
|
+
it("renders the help line as plain text with no directory configured", async () => {
|
|
915
|
+
const el = await createElement();
|
|
916
|
+
el.discoverPages = null;
|
|
917
|
+
await el.updateComplete;
|
|
918
|
+
|
|
919
|
+
const intro = requireElement(
|
|
920
|
+
findIntroParagraph(el),
|
|
921
|
+
"expected the Discover help line",
|
|
922
|
+
);
|
|
923
|
+
|
|
924
|
+
expect(intro.querySelector("a")).toBeNull();
|
|
925
|
+
expect(intro.textContent?.trim()).toBe(labels.discoverIntro);
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
// The two checkboxes are one rule read twice. Search indexing gates the
|
|
929
|
+
// deployment default, so a page that answers the second from a value the
|
|
930
|
+
// server resolved at load time goes on claiming a listing the feed has
|
|
931
|
+
// already stopped declaring — right until the owner reloads and finds out.
|
|
932
|
+
it("unticks Discover when search indexing is turned off", async () => {
|
|
933
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
934
|
+
const discover = requireElement(
|
|
935
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
936
|
+
"expected the Discover checkbox",
|
|
937
|
+
);
|
|
938
|
+
const indexing = requireElement(
|
|
939
|
+
findCheckboxByLabel(el, labels.allowIndexing) ?? null,
|
|
940
|
+
"expected the indexing checkbox",
|
|
941
|
+
);
|
|
942
|
+
expect(discover.checked).toBe(true);
|
|
943
|
+
|
|
944
|
+
indexing.click();
|
|
945
|
+
await el.updateComplete;
|
|
946
|
+
|
|
947
|
+
expect(discover.checked).toBe(false);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
// A control that moves on its own has to say why it moved.
|
|
951
|
+
it("says why the box unticked itself", async () => {
|
|
952
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
953
|
+
expect(el.textContent).not.toContain(labels.discoverSearchOff);
|
|
954
|
+
|
|
955
|
+
requireElement(
|
|
956
|
+
findCheckboxByLabel(el, labels.allowIndexing) ?? null,
|
|
957
|
+
"expected the indexing checkbox",
|
|
958
|
+
).click();
|
|
959
|
+
await el.updateComplete;
|
|
960
|
+
|
|
961
|
+
expect(el.textContent).toContain(labels.discoverSearchOff);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// Nothing was holding this site back: the deployment lists nothing by
|
|
965
|
+
// default, so an unticked box is the opt-in it has always been.
|
|
966
|
+
it("says nothing about search on a site no default would list", async () => {
|
|
967
|
+
const el = await createElement();
|
|
968
|
+
|
|
969
|
+
requireElement(
|
|
970
|
+
findCheckboxByLabel(el, labels.allowIndexing) ?? null,
|
|
971
|
+
"expected the indexing checkbox",
|
|
972
|
+
).click();
|
|
973
|
+
await el.updateComplete;
|
|
974
|
+
|
|
975
|
+
expect(el.textContent).not.toContain(labels.discoverSearchOff);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
// An owner who answered Discover is not being held back by anything: the
|
|
979
|
+
// stored choice is read first, whichever way it went.
|
|
980
|
+
it("says nothing about search once the owner has answered", async () => {
|
|
981
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
982
|
+
const discover = requireElement(
|
|
983
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
984
|
+
"expected the Discover checkbox",
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
discover.click();
|
|
988
|
+
await el.updateComplete;
|
|
989
|
+
el.sectionSaved("discover");
|
|
990
|
+
await el.updateComplete;
|
|
991
|
+
|
|
992
|
+
requireElement(
|
|
993
|
+
findCheckboxByLabel(el, labels.allowIndexing) ?? null,
|
|
994
|
+
"expected the indexing checkbox",
|
|
995
|
+
).click();
|
|
996
|
+
await el.updateComplete;
|
|
997
|
+
|
|
998
|
+
expect(discover.checked).toBe(false);
|
|
999
|
+
expect(el.textContent).not.toContain(labels.discoverSearchOff);
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
it("ticks Discover again when search indexing comes back", async () => {
|
|
1003
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
1004
|
+
const indexing = requireElement(
|
|
1005
|
+
findCheckboxByLabel(el, labels.allowIndexing) ?? null,
|
|
1006
|
+
"expected the indexing checkbox",
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
indexing.click();
|
|
1010
|
+
await el.updateComplete;
|
|
1011
|
+
el.sectionSaved("search");
|
|
1012
|
+
await el.updateComplete;
|
|
1013
|
+
indexing.click();
|
|
1014
|
+
await el.updateComplete;
|
|
1015
|
+
el.sectionSaved("search");
|
|
1016
|
+
await el.updateComplete;
|
|
1017
|
+
|
|
1018
|
+
expect(
|
|
1019
|
+
requireElement(
|
|
1020
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
1021
|
+
"expected the Discover checkbox",
|
|
1022
|
+
).checked,
|
|
1023
|
+
).toBe(true);
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
// An owner who ticked the box meant it, and turning off search indexing
|
|
1027
|
+
// does not quietly undo it — the server reads it the same way.
|
|
1028
|
+
it("leaves a stored choice alone when search indexing is turned off", async () => {
|
|
1029
|
+
const el = await createElement({ discoverDefault: "latest" });
|
|
1030
|
+
const discover = requireElement(
|
|
1031
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
1032
|
+
"expected the Discover checkbox",
|
|
1033
|
+
);
|
|
1034
|
+
|
|
1035
|
+
discover.click();
|
|
1036
|
+
await el.updateComplete;
|
|
1037
|
+
el.sectionSaved("discover");
|
|
1038
|
+
await el.updateComplete;
|
|
1039
|
+
discover.click();
|
|
1040
|
+
await el.updateComplete;
|
|
1041
|
+
el.sectionSaved("discover");
|
|
1042
|
+
await el.updateComplete;
|
|
1043
|
+
expect(discover.checked).toBe(true);
|
|
1044
|
+
|
|
1045
|
+
requireElement(
|
|
1046
|
+
findCheckboxByLabel(el, labels.allowIndexing) ?? null,
|
|
1047
|
+
"expected the indexing checkbox",
|
|
1048
|
+
).click();
|
|
1049
|
+
await el.updateComplete;
|
|
1050
|
+
|
|
1051
|
+
expect(discover.checked).toBe(true);
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
// The status is what the server sent with the page, and it sends a line
|
|
1055
|
+
// only when there is something to say. A site whose setting is doing what
|
|
1056
|
+
// it says is handed nothing, and no heading is left behind to announce an
|
|
1057
|
+
// empty block. Ticking the box does not conjure one either: the lines are
|
|
1058
|
+
// recomputed on the next page load.
|
|
1059
|
+
it("drops the status block when the server sends no lines", async () => {
|
|
1060
|
+
const el = await createElement({
|
|
1061
|
+
discoverStatus: JSON.stringify({
|
|
1062
|
+
lines: [],
|
|
1063
|
+
showAnnounce: false,
|
|
1064
|
+
submitUrl: null,
|
|
1065
|
+
}),
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
expect(el.querySelector(".border-t.pt-3")).toBeNull();
|
|
1069
|
+
|
|
1070
|
+
requireElement(
|
|
1071
|
+
findCheckboxByLabel(el, labels.discoverEnabled) ?? null,
|
|
1072
|
+
"expected the Discover checkbox",
|
|
1073
|
+
).click();
|
|
1074
|
+
await el.updateComplete;
|
|
1075
|
+
|
|
1076
|
+
expect(el.querySelector(".border-t.pt-3")).toBeNull();
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
it("shows the status the server sent for a listed site", async () => {
|
|
1080
|
+
const el = await createElement({
|
|
1081
|
+
discoverDefault: "latest",
|
|
1082
|
+
discoverStatus: JSON.stringify({
|
|
1083
|
+
lines: ["Not announced yet."],
|
|
1084
|
+
showAnnounce: false,
|
|
1085
|
+
submitUrl: null,
|
|
1086
|
+
}),
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
expect(el.textContent).toContain("Not announced yet.");
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
// The lines and the button are separate halves of the same block, so the
|
|
1093
|
+
// block cannot key its existence on the lines alone.
|
|
1094
|
+
it("keeps the block for an announce button with no lines", async () => {
|
|
1095
|
+
const el = await createElement({
|
|
1096
|
+
discoverDefault: "latest",
|
|
1097
|
+
discoverStatus: JSON.stringify({
|
|
1098
|
+
lines: [],
|
|
1099
|
+
showAnnounce: true,
|
|
1100
|
+
submitUrl: null,
|
|
1101
|
+
}),
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
expect(el.textContent).toContain(labels.discoverAnnounce);
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
738
1107
|
});
|