@jant/core 0.6.16 → 0.7.1
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/export.js +3 -1
- package/bin/commands/import-site.js +749 -220
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/setup.js +136 -0
- package/bin/commands/site/export.js +71 -34
- package/bin/commands/site/pull-media.js +2 -6
- package/bin/commands/site/snapshot/export.js +19 -61
- package/bin/commands/site/snapshot/import.js +23 -24
- package/bin/lib/d1-query.js +92 -18
- package/bin/lib/hugo-markdown.js +4 -0
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-pull-media.js +21 -28
- package/bin/lib/site-selection.js +94 -29
- package/bin/lib/site-snapshot.js +338 -3
- package/bin/lib/sql-export.js +68 -5
- package/bin/lib/wrangler-cli.js +37 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/bin/lib/zip-archive.js +187 -0
- package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
- 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-B-m7X7S5.js +261 -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-BJyd-LXE.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-BkscJx69.js +2 -0
- package/dist/client/_assets/client-DAhoqPdr.css +2 -0
- package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
- package/dist/client/_assets/client-knSEyUJO.js +295 -0
- package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
- package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
- package/dist/github-sync-Gw4orAHk.js +8398 -0
- package/dist/index.js +2 -5
- package/dist/node.js +11 -9
- package/package.json +15 -5
- 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__/dev-scripts.test.ts +203 -0
- package/src/__tests__/export-collection-order.test.ts +276 -0
- package/src/__tests__/export-feed-ids.test.ts +184 -0
- package/src/__tests__/export-feed-order.test.ts +294 -0
- package/src/__tests__/export-hugo-build.test.ts +390 -0
- package/src/__tests__/export-import-roundtrip.test.ts +94 -0
- package/src/__tests__/export-service.test.ts +842 -28
- package/src/__tests__/export-smart-collection.test.ts +329 -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__/helpers/hugo-site.ts +146 -0
- package/src/__tests__/import-site-command.test.ts +487 -1
- package/src/__tests__/mise-config.test.ts +75 -4
- package/src/__tests__/node-dev-tasks.test.ts +264 -0
- package/src/__tests__/site-export-canonical-import.test.ts +149 -0
- package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
- package/src/__tests__/snapshot-settings.test.ts +97 -0
- package/src/__tests__/snapshot-tables.test.ts +219 -0
- package/src/__tests__/sql-export.test.ts +173 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/__tests__/zip-archive.test.ts +106 -0
- package/src/app.tsx +86 -22
- 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 +561 -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-types.ts +6 -1
- package/src/client/components/jant-repo-picker.ts +137 -108
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +622 -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/__tests__/list-editing.test.ts +224 -86
- package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/extensions.ts +0 -3
- 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/tiptap/structural-keymap.ts +158 -47
- 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__/d1-query.test.ts +56 -1
- 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 +555 -73
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +601 -119
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +614 -132
- 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__/github-sync-repo-name.test.ts +40 -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__/image.test.ts +27 -1
- 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-to-tiptap.test.ts +105 -0
- package/src/lib/__tests__/markdown.test.ts +10 -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 +131 -3
- package/src/lib/__tests__/schemas.test.ts +69 -5
- 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__/timeline.test.ts +87 -0
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
- 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 +439 -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-repo-name.ts +45 -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 +62 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/image.ts +17 -4
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/markdown-manager.ts +392 -2
- 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-body-html.ts +11 -4
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +111 -6
- package/src/lib/schemas.ts +251 -15
- 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 +43 -2
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/tiptap-to-markdown.ts +13 -7
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +159 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +53 -25
- 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-setup.test.ts +163 -0
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
- package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
- package/src/node/__tests__/runtime.test.ts +75 -0
- package/src/node/index.ts +3 -0
- package/src/node/request-handler.ts +3 -1
- 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__/posts.test.ts +24 -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/__tests__/upload.test.ts +34 -0
- 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 +6 -3
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/internal/sites.ts +0 -1
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +161 -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 +44 -7
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/upload.ts +10 -3
- 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 +9 -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__/github-sync-app.test.ts +365 -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 +714 -111
- 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-page-round-trips.test.ts +211 -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/__tests__/thread-order.test.ts +179 -0
- package/src/routes/pages/archive.tsx +487 -379
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +43 -18
- 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 +228 -45
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +22 -5
- 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/__tests__/readiness.test.ts +23 -0
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +67 -1
- package/src/runtime/readiness.ts +15 -0
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/custom-url.test.ts +34 -0
- package/src/services/__tests__/github-app-installations.test.ts +153 -0
- package/src/services/__tests__/github-sync-push.test.ts +46 -0
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/media.test.ts +31 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +236 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post-timeline.test.ts +127 -0
- package/src/services/__tests__/post.test.ts +120 -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 +408 -39
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +29 -24
- 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/alias.html +27 -1
- package/src/services/export-theme/layouts/_default/list.html +2 -63
- package/src/services/export-theme/layouts/_default/rss.xml +103 -38
- package/src/services/export-theme/layouts/collections/list.html +3 -3
- package/src/services/export-theme/layouts/featured/list.html +1 -4
- package/src/services/export-theme/layouts/index.html +40 -26
- package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
- package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
- package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
- package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/head.html +1 -1
- package/src/services/export-theme/layouts/partials/header.html +5 -3
- package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
- package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
- 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/smart-collection-members.html +129 -0
- package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
- package/src/services/export-theme/layouts/post/list.html +1 -1
- package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
- package/src/services/export-theme/styles/main.css +13 -2
- package/src/services/export-theme/theme.toml +1 -1
- package/src/services/export.ts +880 -72
- package/src/services/github-app-installations.ts +171 -4
- package/src/services/github-sync.ts +69 -15
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +22 -2
- package/src/services/media.ts +22 -7
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +254 -6
- package/src/services/post.ts +1234 -190
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +27 -14
- package/src/services/site.ts +160 -32
- 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 +1090 -7216
- package/src/types/app-context.ts +23 -0
- package/src/types/bindings.ts +8 -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 +74 -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 +277 -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/export-Dl5KCiEs.js +0 -5562
- 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/client/tiptap/exitable-marks.ts +0 -73
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subscribe page.
|
|
3
|
+
*
|
|
4
|
+
* What is under test is mostly the page's contract with readers: three feeds on
|
|
5
|
+
* every site, whole addresses that survive being pasted into a reader, and the
|
|
6
|
+
* right pair for whichever end `/feed` currently returns. Getting the pairing
|
|
7
|
+
* wrong would list the same feed twice under two names — the confusion this
|
|
8
|
+
* page exists to end.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
13
|
+
import { i18nMiddleware } from "../../../i18n/index.js";
|
|
14
|
+
import { languageRoutes } from "../language.js";
|
|
15
|
+
import { pageRoutes } from "../page.js";
|
|
16
|
+
import { subscribeRoutes } from "../subscribe.js";
|
|
17
|
+
|
|
18
|
+
function createSubscribeTestApp() {
|
|
19
|
+
const testApp = createTestApp();
|
|
20
|
+
const { app } = testApp;
|
|
21
|
+
|
|
22
|
+
app.use("*", async (c, next) => {
|
|
23
|
+
c.set("publicPath", c.req.path);
|
|
24
|
+
c.set("publicRequestUrl", c.req.url);
|
|
25
|
+
await next();
|
|
26
|
+
});
|
|
27
|
+
app.use("*", i18nMiddleware());
|
|
28
|
+
|
|
29
|
+
app.route("/subscribe", subscribeRoutes);
|
|
30
|
+
app.route("/:lang", languageRoutes);
|
|
31
|
+
app.route("/", pageRoutes);
|
|
32
|
+
|
|
33
|
+
return testApp;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Every address the page offers for copying, in document order. */
|
|
37
|
+
function feedAddresses(html: string): string[] {
|
|
38
|
+
return [...html.matchAll(/<input[^>]*>/g)]
|
|
39
|
+
.filter((match) => match[0].includes("data-copy-field-value"))
|
|
40
|
+
.map((match) => /value="([^"]*)"/.exec(match[0])?.[1] ?? "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The addresses as paths, for asserting which feeds are offered without
|
|
45
|
+
* pinning the test to the host the test app happens to run under.
|
|
46
|
+
*/
|
|
47
|
+
function feedPaths(html: string): string[] {
|
|
48
|
+
return feedAddresses(html).map((address) => new URL(address).pathname);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Where each feed's name links, in document order. */
|
|
52
|
+
function labelLinks(html: string): string[] {
|
|
53
|
+
return [
|
|
54
|
+
...html.matchAll(/<a href="([^"]*)" title="Open the page[^"]*"/g),
|
|
55
|
+
].map((match) => match[1] ?? "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("subscribe page", () => {
|
|
59
|
+
it("offers the main feed, the other end of the list, and the archive", async () => {
|
|
60
|
+
const { app, services } = createSubscribeTestApp();
|
|
61
|
+
await services.settings.set("MAIN_RSS_FEED", "latest");
|
|
62
|
+
|
|
63
|
+
const res = await app.request("/subscribe");
|
|
64
|
+
const html = await res.text();
|
|
65
|
+
|
|
66
|
+
expect(res.status).toBe(200);
|
|
67
|
+
expect(feedPaths(html)).toEqual([
|
|
68
|
+
"/feed",
|
|
69
|
+
"/featured/feed",
|
|
70
|
+
"/archive/feed",
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// `/feed` already returns the featured posts here, so offering
|
|
75
|
+
// `/featured/feed` beside it would be one feed under two names.
|
|
76
|
+
it("offers the latest feed when the main feed is the featured one", async () => {
|
|
77
|
+
const { app, services } = createSubscribeTestApp();
|
|
78
|
+
await services.settings.set("MAIN_RSS_FEED", "featured");
|
|
79
|
+
|
|
80
|
+
const html = await (await app.request("/subscribe")).text();
|
|
81
|
+
|
|
82
|
+
expect(feedPaths(html)).toEqual(["/feed", "/latest/feed", "/archive/feed"]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("names the main feed by what it currently carries", async () => {
|
|
86
|
+
const { app, services } = createSubscribeTestApp();
|
|
87
|
+
await services.settings.set("MAIN_RSS_FEED", "latest");
|
|
88
|
+
const latest = await (await app.request("/subscribe")).text();
|
|
89
|
+
|
|
90
|
+
await services.settings.set("MAIN_RSS_FEED", "featured");
|
|
91
|
+
const featured = await (await app.request("/subscribe")).text();
|
|
92
|
+
|
|
93
|
+
expect(latest).toContain("The same posts as the home page.");
|
|
94
|
+
expect(featured).toContain("Only the posts marked as featured.");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Each feed is the feed of a page, and the page is the only honest preview
|
|
98
|
+
// of what subscribing gets you — a sentence can only approximate it.
|
|
99
|
+
it("links each feed to the page whose posts it carries", async () => {
|
|
100
|
+
const { app, services } = createSubscribeTestApp();
|
|
101
|
+
await services.settings.set("MAIN_RSS_FEED", "latest");
|
|
102
|
+
|
|
103
|
+
const html = await (await app.request("/subscribe")).text();
|
|
104
|
+
|
|
105
|
+
// `/latest` only redirects to `/`, so the latest feed links straight at
|
|
106
|
+
// the home page rather than sending readers through a 302.
|
|
107
|
+
expect(labelLinks(html)).toEqual(["/", "/featured", "/archive"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// The main feed is one of the other two under a different name. If its
|
|
111
|
+
// description and its page link were built separately they could drift, and
|
|
112
|
+
// the page would be describing one feed while linking at another.
|
|
113
|
+
it("gives the main feed the same description and page as the feed it is", async () => {
|
|
114
|
+
const { app, services } = createSubscribeTestApp();
|
|
115
|
+
|
|
116
|
+
await services.settings.set("MAIN_RSS_FEED", "featured");
|
|
117
|
+
const featured = await (await app.request("/subscribe")).text();
|
|
118
|
+
// Main feed first, then the opposite end (`latest`), then the archive.
|
|
119
|
+
expect(labelLinks(featured)).toEqual(["/featured", "/", "/archive"]);
|
|
120
|
+
expect([
|
|
121
|
+
...featured.matchAll(/Only the posts marked as featured\./g),
|
|
122
|
+
]).toHaveLength(1);
|
|
123
|
+
|
|
124
|
+
await services.settings.set("MAIN_RSS_FEED", "latest");
|
|
125
|
+
const latest = await (await app.request("/subscribe")).text();
|
|
126
|
+
expect(labelLinks(latest)).toEqual(["/", "/featured", "/archive"]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// A reader who wants more than these two paragraphs needs somewhere to go,
|
|
130
|
+
// and it must not be a page about Jant.
|
|
131
|
+
it("points at an outside introduction, opened safely", async () => {
|
|
132
|
+
const { app } = createSubscribeTestApp();
|
|
133
|
+
|
|
134
|
+
const html = await (await app.request("/subscribe")).text();
|
|
135
|
+
|
|
136
|
+
const link = /<a[^>]*href="https:\/\/aboutfeeds\.com\/"[^>]*>/.exec(
|
|
137
|
+
html,
|
|
138
|
+
)?.[0];
|
|
139
|
+
expect(link).toBeDefined();
|
|
140
|
+
expect(link).toContain('rel="noopener noreferrer"');
|
|
141
|
+
expect(link).toContain('target="_blank"');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// The description has to say what the archive feed adds, not sound like the
|
|
145
|
+
// default choice.
|
|
146
|
+
it("says what the archive feed carries beyond the others", async () => {
|
|
147
|
+
const { app } = createSubscribeTestApp();
|
|
148
|
+
|
|
149
|
+
const html = await (await app.request("/subscribe")).text();
|
|
150
|
+
|
|
151
|
+
expect(html).toContain(
|
|
152
|
+
"Every published post, including those kept off the home page.",
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// The addresses are the whole content of this page. A reader with scripts off
|
|
157
|
+
// must still be able to select and copy them, so the server renders the field
|
|
158
|
+
// and ships the button hidden rather than leaving a dead control.
|
|
159
|
+
it("renders every address without waiting for JavaScript", async () => {
|
|
160
|
+
const { app } = createSubscribeTestApp();
|
|
161
|
+
|
|
162
|
+
const html = await (await app.request("/subscribe")).text();
|
|
163
|
+
|
|
164
|
+
const addresses = feedAddresses(html);
|
|
165
|
+
expect(addresses).toHaveLength(3);
|
|
166
|
+
// Whole addresses: a path pasted into a feed reader resolves nowhere.
|
|
167
|
+
for (const address of addresses) {
|
|
168
|
+
expect(address).toMatch(/^https?:\/\/[^/]+\//);
|
|
169
|
+
}
|
|
170
|
+
expect(html).toContain("data-copy-field-root");
|
|
171
|
+
expect(/<button[^>]*hidden[^>]*data-copy-field=/.test(html)).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// The page is about feeds and used to carry no feed mark at all, while its
|
|
175
|
+
// closing note tells the reader to look for that glyph on other pages. The
|
|
176
|
+
// note only works once the glyph has been shown here.
|
|
177
|
+
it("shows the feed glyph it tells readers to look for", async () => {
|
|
178
|
+
const { app } = createSubscribeTestApp();
|
|
179
|
+
|
|
180
|
+
const html = await (await app.request("/subscribe")).text();
|
|
181
|
+
|
|
182
|
+
// Once beside the page title, once as the specimen the closing note
|
|
183
|
+
// points at.
|
|
184
|
+
expect([...html.matchAll(/lucide-rss/g)]).toHaveLength(2);
|
|
185
|
+
expect(html).toContain("Look for this icon on those pages.");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// A reader who has never used a feed reader is handed three addresses and no
|
|
189
|
+
// idea what to do with them — the same dead end this page exists to end, one
|
|
190
|
+
// step later. No product names: the list would be identical on every Jant
|
|
191
|
+
// site, and it would rot.
|
|
192
|
+
it("explains what a feed reader is without naming one", async () => {
|
|
193
|
+
const { app } = createSubscribeTestApp();
|
|
194
|
+
|
|
195
|
+
const html = await (await app.request("/subscribe")).text();
|
|
196
|
+
|
|
197
|
+
expect(html).toContain("What a feed reader does");
|
|
198
|
+
expect(html).toContain(
|
|
199
|
+
"A feed reader checks these addresses for new posts",
|
|
200
|
+
);
|
|
201
|
+
expect(html).toContain("Subscribing creates no account here.");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("404s when feeds are switched off site-wide", async () => {
|
|
205
|
+
const { app, services } = createSubscribeTestApp();
|
|
206
|
+
await services.settings.set("RSS_FEEDS_ENABLED", "false");
|
|
207
|
+
|
|
208
|
+
const res = await app.request("/subscribe");
|
|
209
|
+
|
|
210
|
+
expect(res.status).toBe(404);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("serves a language view with that language's addresses", async () => {
|
|
214
|
+
const { app, services } = createSubscribeTestApp();
|
|
215
|
+
await services.settings.set("SITE_LANGUAGE", "en");
|
|
216
|
+
await services.settings.set("ADDITIONAL_LANGUAGES", "ja");
|
|
217
|
+
await services.settings.set("MULTILINGUAL_ENABLED", "true");
|
|
218
|
+
await services.settings.set("MAIN_RSS_FEED", "latest");
|
|
219
|
+
|
|
220
|
+
const res = await app.request("/ja/subscribe");
|
|
221
|
+
const html = await res.text();
|
|
222
|
+
|
|
223
|
+
expect(res.status).toBe(200);
|
|
224
|
+
expect(feedPaths(html)).toEqual([
|
|
225
|
+
"/ja/feed",
|
|
226
|
+
"/ja/featured/feed",
|
|
227
|
+
"/ja/archive/feed",
|
|
228
|
+
]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// A slug may not shadow the page: `subscribe` is in RESERVED_PATHS, so this
|
|
232
|
+
// never becomes a post's address in the first place.
|
|
233
|
+
it("keeps the path for the page rather than a post", async () => {
|
|
234
|
+
const { app, services } = createSubscribeTestApp();
|
|
235
|
+
|
|
236
|
+
await expect(
|
|
237
|
+
services.posts.create({
|
|
238
|
+
format: "note",
|
|
239
|
+
title: "Subscribe",
|
|
240
|
+
slug: "subscribe",
|
|
241
|
+
bodyMarkdown: "Trying to take the path",
|
|
242
|
+
status: "published",
|
|
243
|
+
}),
|
|
244
|
+
).rejects.toThrow();
|
|
245
|
+
|
|
246
|
+
expect((await app.request("/subscribe")).status).toBe(200);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread order on the public surfaces when a reply is older than its root.
|
|
3
|
+
*
|
|
4
|
+
* A post moved into a Thread keeps its own creation time, and an export
|
|
5
|
+
* without `created` dates every post by its publication, so a Thread's replies
|
|
6
|
+
* can carry a `createdAt` earlier than the root's. Thread order still opens on
|
|
7
|
+
* the root: every page and feed that reads the first post as the root has to
|
|
8
|
+
* find it there, or the Thread drops out of Featured and Collections and its
|
|
9
|
+
* page canonicalizes to a reply.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { eq } from "drizzle-orm";
|
|
13
|
+
import { describe, expect, it } from "vitest";
|
|
14
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
15
|
+
import { posts as postTable } from "../../../db/schema.js";
|
|
16
|
+
import { toISOString } from "../../../lib/time.js";
|
|
17
|
+
import { collectionRoutes } from "../collection.js";
|
|
18
|
+
import { collectionsPageRoutes } from "../collections.js";
|
|
19
|
+
import { featuredRoutes } from "../featured.js";
|
|
20
|
+
import { pageRoutes } from "../page.js";
|
|
21
|
+
|
|
22
|
+
const ROOT_TEXT = "Root body text";
|
|
23
|
+
const FIRST_TEXT = "First reply text";
|
|
24
|
+
const SECOND_TEXT = "Second reply text";
|
|
25
|
+
|
|
26
|
+
function createThreadOrderTestApp(options: { authenticated?: boolean } = {}) {
|
|
27
|
+
const testApp = createTestApp(options);
|
|
28
|
+
const { app } = testApp;
|
|
29
|
+
|
|
30
|
+
app.use("*", async (c, next) => {
|
|
31
|
+
c.set("publicPath", c.req.path);
|
|
32
|
+
c.set("publicRequestUrl", c.req.url);
|
|
33
|
+
await next();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
app.route("/featured", featuredRoutes);
|
|
37
|
+
app.route("/collections", collectionsPageRoutes);
|
|
38
|
+
app.route("/collections", collectionRoutes);
|
|
39
|
+
app.route("/", pageRoutes);
|
|
40
|
+
|
|
41
|
+
return testApp;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A featured root published after its two replies, with creation times to
|
|
46
|
+
* match — the shape a site rebuilt from a pre-`created` export holds.
|
|
47
|
+
*/
|
|
48
|
+
async function createThreadWithOlderReplies(
|
|
49
|
+
testApp: ReturnType<typeof createThreadOrderTestApp>,
|
|
50
|
+
) {
|
|
51
|
+
const { services, db } = testApp;
|
|
52
|
+
const root = await services.posts.create({
|
|
53
|
+
format: "note",
|
|
54
|
+
title: "Thread root",
|
|
55
|
+
bodyMarkdown: ROOT_TEXT,
|
|
56
|
+
featured: true,
|
|
57
|
+
publishedAt: 3000,
|
|
58
|
+
});
|
|
59
|
+
const first = await services.posts.create({
|
|
60
|
+
format: "note",
|
|
61
|
+
bodyMarkdown: FIRST_TEXT,
|
|
62
|
+
replyToId: root.id,
|
|
63
|
+
publishedAt: 1000,
|
|
64
|
+
});
|
|
65
|
+
const second = await services.posts.create({
|
|
66
|
+
format: "note",
|
|
67
|
+
bodyMarkdown: SECOND_TEXT,
|
|
68
|
+
replyToId: first.id,
|
|
69
|
+
publishedAt: 2000,
|
|
70
|
+
});
|
|
71
|
+
for (const [id, createdAt] of [
|
|
72
|
+
[root.id, 3000],
|
|
73
|
+
[first.id, 1000],
|
|
74
|
+
[second.id, 2000],
|
|
75
|
+
] as const) {
|
|
76
|
+
await db.update(postTable).set({ createdAt }).where(eq(postTable.id, id));
|
|
77
|
+
}
|
|
78
|
+
return { root, first, second };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Assert the three posts appear root first, in Thread order. A page's `<head>`
|
|
83
|
+
* repeats the visited post's text, so only the body counts.
|
|
84
|
+
*/
|
|
85
|
+
function expectRootFirst(markup: string) {
|
|
86
|
+
const bodyStart = markup.indexOf("<body");
|
|
87
|
+
const body = bodyStart === -1 ? markup : markup.slice(bodyStart);
|
|
88
|
+
const root = body.indexOf(ROOT_TEXT);
|
|
89
|
+
const first = body.indexOf(FIRST_TEXT);
|
|
90
|
+
const second = body.indexOf(SECOND_TEXT);
|
|
91
|
+
expect(root).toBeGreaterThan(-1);
|
|
92
|
+
expect(first).toBeGreaterThan(root);
|
|
93
|
+
expect(second).toBeGreaterThan(first);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The post whose `<article>` holds the markup at `index`. */
|
|
97
|
+
function postIdAt(html: string, index: number): string | undefined {
|
|
98
|
+
const articles = html
|
|
99
|
+
.slice(0, index)
|
|
100
|
+
.matchAll(/<article\b[^>]*\bdata-post-id="([^"]+)"/g);
|
|
101
|
+
return [...articles].at(-1)?.[1];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractCanonicalHref(html: string): string | null {
|
|
105
|
+
const match = html.match(/<link\s+rel="canonical"\s+href="([^"]+)"\s*\/?>/i);
|
|
106
|
+
return match?.[1] ?? null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
describe("Thread order with replies older than the root", () => {
|
|
110
|
+
it("opens the Thread page on the root and points its canonical there", async () => {
|
|
111
|
+
const testApp = createThreadOrderTestApp();
|
|
112
|
+
const { root, second } = await createThreadWithOlderReplies(testApp);
|
|
113
|
+
|
|
114
|
+
const res = await testApp.app.request(`/${second.slug}`);
|
|
115
|
+
expect(res.status).toBe(200);
|
|
116
|
+
const html = await res.text();
|
|
117
|
+
|
|
118
|
+
expectRootFirst(html);
|
|
119
|
+
expect(extractCanonicalHref(html)).toMatch(new RegExp(`/${root.slug}$`));
|
|
120
|
+
// The page describes the Thread as one article, dated by its root.
|
|
121
|
+
expect(html).toContain(
|
|
122
|
+
`<meta property="article:published_time" content="${toISOString(3000)}"/>`,
|
|
123
|
+
);
|
|
124
|
+
expect(html).toContain(`"datePublished":"${toISOString(3000)}"`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("offers the author a reply on the newest reply, not the root", async () => {
|
|
128
|
+
const testApp = createThreadOrderTestApp({ authenticated: true });
|
|
129
|
+
const { root, second } = await createThreadWithOlderReplies(testApp);
|
|
130
|
+
|
|
131
|
+
const html = await (await testApp.app.request(`/${root.slug}`)).text();
|
|
132
|
+
|
|
133
|
+
expect(html.match(/data-reply-trigger/g)).toHaveLength(1);
|
|
134
|
+
expect(postIdAt(html, html.indexOf("data-reply-trigger"))).toBe(second.id);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("keeps the Thread on the Featured page", async () => {
|
|
138
|
+
const testApp = createThreadOrderTestApp();
|
|
139
|
+
await createThreadWithOlderReplies(testApp);
|
|
140
|
+
|
|
141
|
+
const res = await testApp.app.request("/featured");
|
|
142
|
+
expect(res.status).toBe(200);
|
|
143
|
+
const html = await res.text();
|
|
144
|
+
|
|
145
|
+
// The curated view shows the root and the Thread's final post, with the
|
|
146
|
+
// reply between them folded into a gap.
|
|
147
|
+
expect(html).toContain(ROOT_TEXT);
|
|
148
|
+
expect(html).not.toContain(FIRST_TEXT);
|
|
149
|
+
expect(html.indexOf(SECOND_TEXT)).toBeGreaterThan(html.indexOf(ROOT_TEXT));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("keeps the Thread in the Featured feed", async () => {
|
|
153
|
+
const testApp = createThreadOrderTestApp();
|
|
154
|
+
const { root } = await createThreadWithOlderReplies(testApp);
|
|
155
|
+
|
|
156
|
+
const res = await testApp.app.request("/featured/feed");
|
|
157
|
+
expect(res.status).toBe(200);
|
|
158
|
+
const xml = await res.text();
|
|
159
|
+
|
|
160
|
+
expect(xml.match(/<entry>/g)).toHaveLength(1);
|
|
161
|
+
expect(xml).toContain(`/${root.slug}`);
|
|
162
|
+
expectRootFirst(xml);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("opens the Thread on its root on a Collection page", async () => {
|
|
166
|
+
const testApp = createThreadOrderTestApp();
|
|
167
|
+
const collection = await testApp.services.collections.create({
|
|
168
|
+
slug: "reading",
|
|
169
|
+
title: "Reading",
|
|
170
|
+
});
|
|
171
|
+
const { root } = await createThreadWithOlderReplies(testApp);
|
|
172
|
+
await testApp.services.collections.addThread(collection.id, root.id);
|
|
173
|
+
|
|
174
|
+
const res = await testApp.app.request(`/${collection.slug}`);
|
|
175
|
+
expect(res.status).toBe(200);
|
|
176
|
+
|
|
177
|
+
expectRootFirst(await res.text());
|
|
178
|
+
});
|
|
179
|
+
});
|