@jant/core 0.6.15 → 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 +67 -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-C-oi_t8W.js → app-K_Aa1MMn.js} +52623 -13193
- 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-CsFx6F_M.js → github-sync-BPAvT999.js} +2343 -110
- 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__/mise-config.test.ts +22 -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 +524 -2
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +57 -8
- 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 +88 -1
- 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__/compose-format-convert.test.ts +0 -19
- 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 +79 -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 +3283 -389
- package/src/client/components/__tests__/jant-compose-editor.test.ts +581 -86
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +34 -89
- 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-format-convert.ts +5 -1
- package/src/client/components/compose-types.ts +121 -3
- 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 +106 -49
- package/src/client/components/jant-compose-dialog.ts +2757 -1194
- package/src/client/components/jant-compose-editor.ts +866 -397
- package/src/client/components/jant-compose-fullscreen.ts +53 -47
- 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 +1142 -268
- 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 +336 -242
- package/src/client/compose-launch.ts +89 -2
- package/src/client/compose-shortcuts.ts +9 -7
- 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/post-refresh.ts +135 -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/__tests__/link-input-rules.test.ts +121 -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-input-rules.ts +4 -4
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/toast.ts +143 -38
- 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__/backfill-thread-activity.test.ts +234 -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/__tests__/thread-activity.test.ts +113 -0
- package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0031_many_ego.sql +3 -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/0031_snapshot.json +2562 -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 +28 -0
- package/src/db/migrations/pg/0029_rare_hairball.sql +3 -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/0029_snapshot.json +3289 -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 +28 -0
- package/src/db/pg/schema.ts +213 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +242 -27
- package/src/db/thread-activity.ts +101 -0
- 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 +568 -168
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +562 -162
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +562 -162
- 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 +1691 -12
- 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__/markdown.test.ts +16 -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 +88 -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__/translit.test.ts +87 -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 +93 -34
- 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 +1021 -66
- 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 +21 -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/link-preview.ts +25 -0
- package/src/lib/markdown-manager.ts +6 -1
- 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-display.ts +42 -9
- 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 +81 -10
- package/src/lib/tiptap-render.ts +5 -4
- package/src/lib/translit.ts +288 -0
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +188 -24
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +79 -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__/palette.test.ts +44 -0
- 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 +172 -9
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/__tests__/posts.test.ts +38 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +30 -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 +11 -2
- 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/__tests__/sitemap.test.ts +68 -1
- 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 +657 -1
- 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__/featured.test.ts +6 -2
- 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__/preview.test.ts +3 -1
- 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 +507 -357
- 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 +183 -19
- 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 +139 -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-timeline.test.ts +142 -11
- package/src/services/__tests__/post.test.ts +323 -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 +330 -47
- 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 +281 -24
- 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 +225 -7
- package/src/services/post.ts +1318 -252
- package/src/services/search.ts +48 -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 -1585
- package/src/styles/site-media.css +20 -6
- package/src/styles/tokens.css +144 -13
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1224 -7276
- 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 +92 -1
- package/src/types/operations.ts +64 -2
- package/src/types/props.ts +168 -30
- package/src/types/views.ts +61 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +215 -21
- 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/LinkPreview.tsx +2 -7
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +103 -17
- 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 +401 -301
- 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 +122 -3
- 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/DraftPreviewBar.tsx +25 -9
- 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 +55 -23
- 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/custom-icons.ts +5 -0
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +8 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-BxCOR3Uc.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-pLre2DM_.js +0 -1
- package/dist/client/_assets/client-B_eGKN5p.css +0 -2
- package/dist/client/_assets/client-DxY5BFe8.js +0 -296
- package/dist/client/_assets/client-auth-CakPESv9.js +0 -5533
- package/dist/env-OHRKGcMj.js +0 -281
- package/dist/github-api-BgSiE71w.js +0 -176
- package/dist/github-app-BbklkFmU.js +0 -275
- package/dist/github-sync-Cq1pzzOI.js +0 -431
- package/dist/github-sync-ppWXN3jb.js +0 -4
- package/dist/url-BMYO-Zlt.js +0 -395
- 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
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { Hono } from "hono";
|
|
18
|
-
import type { Bindings } from "../../types.js";
|
|
18
|
+
import type { Bindings, Post } from "../../types.js";
|
|
19
19
|
import type { AppVariables } from "../../types/app-context.js";
|
|
20
20
|
import {
|
|
21
21
|
renderSitemapIndex,
|
|
@@ -82,8 +82,12 @@ sitemapRoutes.get("/sitemap.xml", async (c) => {
|
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// The shard carries both kinds, so it is listed when either exists.
|
|
86
|
+
const [collections, smartCollections] = await Promise.all([
|
|
87
|
+
c.var.services.collections.list(),
|
|
88
|
+
c.var.services.smartCollections.list(),
|
|
89
|
+
]);
|
|
90
|
+
if (collections.length > 0 || smartCollections.length > 0) {
|
|
87
91
|
entries.push({
|
|
88
92
|
loc: absoluteUrl("/sitemap-collections.xml", siteUrl, sitePathPrefix),
|
|
89
93
|
});
|
|
@@ -125,16 +129,66 @@ sitemapRoutes.get("/:file{sitemap-posts-[0-9]+\\.xml}", async (c) => {
|
|
|
125
129
|
limit: SITEMAP_SHARD_SIZE,
|
|
126
130
|
});
|
|
127
131
|
|
|
132
|
+
// `entry.alias` already includes a leading "/" (see `paths.getPostAliases`);
|
|
133
|
+
// slugs are stored raw. Prepending "/" to an alias would create "//path",
|
|
134
|
+
// which `new URL()` reads as protocol-relative and hijacks the hostname.
|
|
135
|
+
const pathOf = (entry: (typeof shardEntries)[number]) =>
|
|
136
|
+
entry.alias ?? `/${entry.slug}`;
|
|
137
|
+
|
|
138
|
+
// A post's URL is language-neutral and listed once. Translations are
|
|
139
|
+
// announced with `xhtml:link` alternates instead, which is what the sitemap
|
|
140
|
+
// protocol asks for — and the only place a crawler can learn about a
|
|
141
|
+
// translation whose own URL lives in a different shard.
|
|
142
|
+
const translated = shardEntries.filter(
|
|
143
|
+
(entry) => entry.translationGroupId && entry.language,
|
|
144
|
+
);
|
|
145
|
+
const translationsMap =
|
|
146
|
+
translated.length > 0
|
|
147
|
+
? await c.var.services.posts.getTranslationsMap(
|
|
148
|
+
translated.map((entry) => entry.id),
|
|
149
|
+
)
|
|
150
|
+
: new Map<string, Post[]>();
|
|
151
|
+
const siblingIds = [...translationsMap.values()]
|
|
152
|
+
.flat()
|
|
153
|
+
.map((post) => post.id);
|
|
154
|
+
const siblingAliases =
|
|
155
|
+
siblingIds.length > 0
|
|
156
|
+
? await c.var.services.paths.getPostAliases(siblingIds)
|
|
157
|
+
: new Map<string, string[]>();
|
|
158
|
+
|
|
128
159
|
const urls: SitemapUrlEntry[] = shardEntries.map((entry) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
160
|
+
const path = pathOf(entry);
|
|
161
|
+
const siblings: Post[] = translationsMap.get(entry.id) ?? [];
|
|
162
|
+
const alternates =
|
|
163
|
+
entry.language && siblings.length > 0
|
|
164
|
+
? [
|
|
165
|
+
{
|
|
166
|
+
hreflang: entry.language,
|
|
167
|
+
href: absoluteUrl(path, siteUrl, sitePathPrefix),
|
|
168
|
+
},
|
|
169
|
+
...siblings.flatMap((sibling) =>
|
|
170
|
+
sibling.language
|
|
171
|
+
? [
|
|
172
|
+
{
|
|
173
|
+
hreflang: sibling.language,
|
|
174
|
+
href: absoluteUrl(
|
|
175
|
+
siblingAliases.get(sibling.id)?.[0] ??
|
|
176
|
+
`/${sibling.slug}`,
|
|
177
|
+
siteUrl,
|
|
178
|
+
sitePathPrefix,
|
|
179
|
+
),
|
|
180
|
+
},
|
|
181
|
+
]
|
|
182
|
+
: [],
|
|
183
|
+
),
|
|
184
|
+
]
|
|
185
|
+
: undefined;
|
|
186
|
+
|
|
134
187
|
return {
|
|
135
188
|
loc: absoluteUrl(path, siteUrl, sitePathPrefix),
|
|
136
189
|
lastmod: toIsoDate(entry.updatedAt),
|
|
137
190
|
priority: entry.featuredAt ? "0.8" : "0.6",
|
|
191
|
+
...(alternates ? { alternates } : {}),
|
|
138
192
|
};
|
|
139
193
|
});
|
|
140
194
|
|
|
@@ -156,7 +210,10 @@ sitemapRoutes.get("/sitemap-collections.xml", async (c) => {
|
|
|
156
210
|
const { appConfig } = c.var;
|
|
157
211
|
const { siteUrl, sitePathPrefix } = appConfig;
|
|
158
212
|
|
|
159
|
-
const collections = await
|
|
213
|
+
const [collections, smartCollections] = await Promise.all([
|
|
214
|
+
c.var.services.collections.list(),
|
|
215
|
+
c.var.services.smartCollections.list(),
|
|
216
|
+
]);
|
|
160
217
|
|
|
161
218
|
// Resolve each collection's canonical URL (alias if one exists, else slug).
|
|
162
219
|
// The `/collections` directory itself lives in `/sitemap-pages.xml`, since
|
|
@@ -176,6 +233,19 @@ sitemapRoutes.get("/sitemap-collections.xml", async (c) => {
|
|
|
176
233
|
}),
|
|
177
234
|
);
|
|
178
235
|
|
|
236
|
+
// Smart collections are here unconditionally. A page a reader assembled out
|
|
237
|
+
// of archive parameters is one facet of a combinatorial family and stays out
|
|
238
|
+
// of the index; a smart collection is a page the author declared and gave an
|
|
239
|
+
// address, so it belongs in the sitemap like any other — and unlike an
|
|
240
|
+
// archive URL, it can never be one only its author can see.
|
|
241
|
+
for (const smartCollection of smartCollections) {
|
|
242
|
+
urls.push({
|
|
243
|
+
loc: absoluteUrl(`/${smartCollection.slug}`, siteUrl, sitePathPrefix),
|
|
244
|
+
lastmod: toIsoDate(smartCollection.updatedAt),
|
|
245
|
+
priority: "0.7",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
179
249
|
return xmlResponse(renderSitemapUrlSet(urls), CACHE_SHORT);
|
|
180
250
|
});
|
|
181
251
|
|
|
@@ -206,12 +276,26 @@ sitemapRoutes.get("/sitemap-pages.xml", async (c) => {
|
|
|
206
276
|
changefreq: "daily",
|
|
207
277
|
});
|
|
208
278
|
|
|
279
|
+
// The subscribe page exists only while feeds do, and its content never
|
|
280
|
+
// changes on its own — it lists the same three addresses for the life of the
|
|
281
|
+
// site.
|
|
282
|
+
if (appConfig.rssFeedsEnabled) {
|
|
283
|
+
urls.push({
|
|
284
|
+
loc: absoluteUrl("/subscribe", siteUrl, sitePathPrefix),
|
|
285
|
+
priority: "0.3",
|
|
286
|
+
changefreq: "yearly",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
209
290
|
// Include the collections directory landing page when at least one
|
|
210
291
|
// collection exists. When there are no collections, `/collections` still
|
|
211
292
|
// renders (as an empty directory), but indexing an empty aggregate page
|
|
212
293
|
// adds no value.
|
|
213
|
-
const collections = await
|
|
214
|
-
|
|
294
|
+
const [collections, smartCollections] = await Promise.all([
|
|
295
|
+
c.var.services.collections.list(),
|
|
296
|
+
c.var.services.smartCollections.list(),
|
|
297
|
+
]);
|
|
298
|
+
if (collections.length > 0 || smartCollections.length > 0) {
|
|
215
299
|
urls.push({
|
|
216
300
|
loc: absoluteUrl("/collections", siteUrl, sitePathPrefix),
|
|
217
301
|
priority: "0.5",
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the archive tells crawlers about each of its URLs.
|
|
3
|
+
*
|
|
4
|
+
* The filter space has no ceiling — `media` alone is a comma-joined subset of
|
|
5
|
+
* kinds, multiplied by every other dimension — and the page sits in no cache,
|
|
6
|
+
* so each facet a crawler walks costs a Worker invocation and seven D1
|
|
7
|
+
* queries. None of those URLs is a page the site publishes. The ones that are
|
|
8
|
+
* — the bare path, a stored archive path, and the same page rendered
|
|
9
|
+
* differently — consolidate onto one canonical instead.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it } from "vitest";
|
|
13
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
14
|
+
import { archiveRoutes } from "../archive.js";
|
|
15
|
+
import { pageRoutes } from "../page.js";
|
|
16
|
+
|
|
17
|
+
const SITE_URL = "http://localhost:3000";
|
|
18
|
+
|
|
19
|
+
function setupApp() {
|
|
20
|
+
const { app, services } = createTestApp({ authenticated: false });
|
|
21
|
+
app.route("/archive", archiveRoutes);
|
|
22
|
+
return { app, services };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function setupPageApp() {
|
|
26
|
+
const testApp = createTestApp({ authenticated: false });
|
|
27
|
+
const { app } = testApp;
|
|
28
|
+
app.use("*", async (c, next) => {
|
|
29
|
+
c.set("publicPath", c.req.path);
|
|
30
|
+
c.set("publicRequestUrl", c.req.url);
|
|
31
|
+
await next();
|
|
32
|
+
});
|
|
33
|
+
app.route("/", pageRoutes);
|
|
34
|
+
return testApp;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readCanonical(html: string): string | null {
|
|
38
|
+
return html.match(/<link rel="canonical" href="([^"]+)"/)?.[1] ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readRobotsMeta(html: string): string | null {
|
|
42
|
+
return html.match(/<meta name="robots" content="([^"]+)"/)?.[1] ?? null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The `<link rel="alternate" hreflang>` set only. The language switcher in the
|
|
47
|
+
* nav also carries `hreflang`, on ordinary `<a>` links — that is navigation
|
|
48
|
+
* chrome, not the annotation search engines read.
|
|
49
|
+
*/
|
|
50
|
+
function readAlternates(html: string): string[] {
|
|
51
|
+
return [
|
|
52
|
+
...html.matchAll(
|
|
53
|
+
/<link rel="alternate" hreflang="([^"]+)" href="([^"]+)"/g,
|
|
54
|
+
),
|
|
55
|
+
].map((m) => `${m[1]} ${m[2]}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function seed(services: {
|
|
59
|
+
posts: { create: (input: Record<string, unknown>) => Promise<unknown> };
|
|
60
|
+
}) {
|
|
61
|
+
await services.posts.create({
|
|
62
|
+
format: "note",
|
|
63
|
+
bodyMarkdown: "a note in the archive",
|
|
64
|
+
status: "published",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("archive indexing", () => {
|
|
69
|
+
it("publishes the bare archive with a self-canonical", async () => {
|
|
70
|
+
const { app, services } = setupApp();
|
|
71
|
+
await seed(services);
|
|
72
|
+
|
|
73
|
+
const res = await app.request("/archive");
|
|
74
|
+
const html = await res.text();
|
|
75
|
+
|
|
76
|
+
expect(readRobotsMeta(html)).toBeNull();
|
|
77
|
+
expect(res.headers.get("X-Robots-Tag")).toBeNull();
|
|
78
|
+
expect(readCanonical(html)).toBe(`${SITE_URL}/archive`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// `layout` changes the markup, not the posts or their order, so the two
|
|
82
|
+
// renderings are one page with one canonical.
|
|
83
|
+
it("consolidates layout and tracking params onto the bare path", async () => {
|
|
84
|
+
const { app, services } = setupApp();
|
|
85
|
+
await seed(services);
|
|
86
|
+
|
|
87
|
+
for (const query of ["?layout=grid", "?utm_source=nl"]) {
|
|
88
|
+
const html = await (await app.request(`/archive${query}`)).text();
|
|
89
|
+
expect(readRobotsMeta(html)).toBeNull();
|
|
90
|
+
expect(readCanonical(html)).toBe(`${SITE_URL}/archive`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Pagination is genuinely different content, not a different rendering of
|
|
95
|
+
// the same content, so the canonical keeps it.
|
|
96
|
+
it("keeps the page number in the canonical", async () => {
|
|
97
|
+
const { app, services } = setupApp();
|
|
98
|
+
await seed(services);
|
|
99
|
+
|
|
100
|
+
const html = await (await app.request("/archive?page=2")).text();
|
|
101
|
+
expect(readCanonical(html)).toBe(`${SITE_URL}/archive?page=2`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("keeps a reader's facet out of the index while leaving its links crawlable", async () => {
|
|
105
|
+
const { app, services } = setupApp();
|
|
106
|
+
await seed(services);
|
|
107
|
+
|
|
108
|
+
const res = await app.request("/archive?format=note");
|
|
109
|
+
const html = await res.text();
|
|
110
|
+
|
|
111
|
+
expect(readRobotsMeta(html)).toBe("noindex, follow");
|
|
112
|
+
expect(res.headers.get("X-Robots-Tag")).toBe("noindex, follow");
|
|
113
|
+
expect(readCanonical(html)).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// `sort` is a filter, not a presentation toggle: the year filter follows the
|
|
117
|
+
// sort axis, and under pagination a different order is a different page one.
|
|
118
|
+
it("treats sort as a facet rather than a rendering", async () => {
|
|
119
|
+
const { app, services } = setupApp();
|
|
120
|
+
await seed(services);
|
|
121
|
+
|
|
122
|
+
const html = await (await app.request("/archive?sort=updated")).text();
|
|
123
|
+
expect(readRobotsMeta(html)).toBe("noindex, follow");
|
|
124
|
+
expect(readCanonical(html)).toBeNull();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// A site that asked not to be indexed at all has made the stricter
|
|
128
|
+
// statement, and a page policy may only narrow further. `follow` would
|
|
129
|
+
// otherwise reopen the site's links on exactly the URLs it never wanted
|
|
130
|
+
// crawled.
|
|
131
|
+
it("lets a site-wide noindex override the facet policy", async () => {
|
|
132
|
+
const { app, services } = createTestApp({
|
|
133
|
+
authenticated: false,
|
|
134
|
+
demoMode: true,
|
|
135
|
+
});
|
|
136
|
+
app.route("/archive", archiveRoutes);
|
|
137
|
+
await seed(services);
|
|
138
|
+
|
|
139
|
+
const facet = await (await app.request("/archive?format=note")).text();
|
|
140
|
+
expect(readRobotsMeta(facet)).toBe("noindex, nofollow");
|
|
141
|
+
|
|
142
|
+
const bare = await (await app.request("/archive")).text();
|
|
143
|
+
expect(readRobotsMeta(bare)).toBe("noindex, nofollow");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Every member of an hreflang set has to be a canonical URL. Left alone, the
|
|
147
|
+
// self-referential alternate would carry the very params the canonical
|
|
148
|
+
// strips, giving one page two identities.
|
|
149
|
+
it("builds the hreflang set from the canonical, not the raw URL", async () => {
|
|
150
|
+
const { app, services } = setupApp();
|
|
151
|
+
await services.settings.set("SITE_LANGUAGE", "zh-Hans");
|
|
152
|
+
await services.settings.set("ADDITIONAL_LANGUAGES", "en");
|
|
153
|
+
await services.settings.set("MULTILINGUAL_ENABLED", "true");
|
|
154
|
+
await seed(services);
|
|
155
|
+
|
|
156
|
+
const html = await (await app.request("/archive?utm_source=nl")).text();
|
|
157
|
+
|
|
158
|
+
expect(readCanonical(html)).toBe(`${SITE_URL}/archive`);
|
|
159
|
+
expect(readAlternates(html)).toEqual([
|
|
160
|
+
`zh-Hans ${SITE_URL}/archive`,
|
|
161
|
+
`en ${SITE_URL}/en/archive`,
|
|
162
|
+
`x-default ${SITE_URL}/archive`,
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// A facet has no canonical, so there is no set to build.
|
|
167
|
+
it("emits no hreflang set on a facet", async () => {
|
|
168
|
+
const { app, services } = setupApp();
|
|
169
|
+
await services.settings.set("SITE_LANGUAGE", "zh-Hans");
|
|
170
|
+
await services.settings.set("ADDITIONAL_LANGUAGES", "en");
|
|
171
|
+
await services.settings.set("MULTILINGUAL_ENABLED", "true");
|
|
172
|
+
await seed(services);
|
|
173
|
+
|
|
174
|
+
const facet = await (await app.request("/archive?format=note")).text();
|
|
175
|
+
expect(readAlternates(facet)).toEqual([]);
|
|
176
|
+
|
|
177
|
+
const bare = await (await app.request("/archive")).text();
|
|
178
|
+
expect(readAlternates(bare)).toContain(`en ${SITE_URL}/en/archive`);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// The author declared this path, so it is a page even though its stored
|
|
182
|
+
// query is a filter. Same seam the auth guard uses.
|
|
183
|
+
it("publishes a stored archive path despite its filter", async () => {
|
|
184
|
+
const { app, services } = setupPageApp();
|
|
185
|
+
await seed(services);
|
|
186
|
+
await services.paths.create({
|
|
187
|
+
path: "/every-note",
|
|
188
|
+
kind: "archive",
|
|
189
|
+
archiveQuery: "format=note&sort=updated",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const res = await app.request("/every-note");
|
|
193
|
+
expect(res.status).toBe(200);
|
|
194
|
+
|
|
195
|
+
const html = await res.text();
|
|
196
|
+
expect(readRobotsMeta(html)).toBeNull();
|
|
197
|
+
expect(res.headers.get("X-Robots-Tag")).toBeNull();
|
|
198
|
+
expect(readCanonical(html)).toBe(`${SITE_URL}/every-note`);
|
|
199
|
+
});
|
|
200
|
+
});
|