@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
|
@@ -166,6 +166,22 @@ describe("Collection Routing", () => {
|
|
|
166
166
|
expect(xml).toContain("Film log");
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
+
it("leads the collection feed title with the site name", async () => {
|
|
170
|
+
// A reader's sidebar sorts by feed title, so every Jant feed leads with
|
|
171
|
+
// the site name — otherwise one site's feeds scatter across the alphabet.
|
|
172
|
+
const { app, services } = createCollectionRoutingTestApp();
|
|
173
|
+
await services.settings.set("SITE_NAME", "Jant");
|
|
174
|
+
await createCollectionWithPost(services, {
|
|
175
|
+
slug: "reading",
|
|
176
|
+
title: "Reading",
|
|
177
|
+
postTitle: "Book log",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const xml = await (await app.request("/reading/feed")).text();
|
|
181
|
+
|
|
182
|
+
expect(xml).toContain("<title>Jant - Reading</title>");
|
|
183
|
+
});
|
|
184
|
+
|
|
169
185
|
it("applies the RSS publication delay to Collection feeds", async () => {
|
|
170
186
|
const currentTime = 2_000_000;
|
|
171
187
|
vi.spyOn(Date, "now").mockReturnValue(currentTime * 1000);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection feeds in the directory.
|
|
3
|
+
*
|
|
4
|
+
* `/subscribe` lists only the site-wide feeds, so this is the one place a
|
|
5
|
+
* reader is handed a collection's feed. If these icons go, collection feeds
|
|
6
|
+
* become undiscoverable outside the collection's own page.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
11
|
+
import { i18nMiddleware } from "../../../i18n/index.js";
|
|
12
|
+
import { collectionsPageRoutes } from "../collections.js";
|
|
13
|
+
|
|
14
|
+
function createDirectoryTestApp() {
|
|
15
|
+
const testApp = createTestApp();
|
|
16
|
+
const { app } = testApp;
|
|
17
|
+
|
|
18
|
+
app.use("*", async (c, next) => {
|
|
19
|
+
c.set("publicPath", c.req.path);
|
|
20
|
+
c.set("publicRequestUrl", c.req.url);
|
|
21
|
+
await next();
|
|
22
|
+
});
|
|
23
|
+
app.use("*", i18nMiddleware());
|
|
24
|
+
app.route("/collections", collectionsPageRoutes);
|
|
25
|
+
|
|
26
|
+
return testApp;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Every feed address the directory offers, in document order. */
|
|
30
|
+
function feedHrefs(html: string): string[] {
|
|
31
|
+
return [...html.matchAll(/<a href="([^"]*)" class="feed-link"/g)].map(
|
|
32
|
+
(match) => match[1] as string,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function seedCollection(
|
|
37
|
+
services: ReturnType<typeof createTestApp>["services"],
|
|
38
|
+
slug: string,
|
|
39
|
+
) {
|
|
40
|
+
const collection = await services.collections.create({
|
|
41
|
+
slug,
|
|
42
|
+
title: slug,
|
|
43
|
+
});
|
|
44
|
+
const post = await services.posts.create({
|
|
45
|
+
format: "note",
|
|
46
|
+
bodyMarkdown: `A post in ${slug}`,
|
|
47
|
+
status: "published",
|
|
48
|
+
});
|
|
49
|
+
await services.collections.addThread(collection.id, post.id);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("collections directory feed links", () => {
|
|
53
|
+
it("offers each collection's feed beside it", async () => {
|
|
54
|
+
const { app, services } = createDirectoryTestApp();
|
|
55
|
+
await seedCollection(services, "reading");
|
|
56
|
+
await seedCollection(services, "cooking");
|
|
57
|
+
|
|
58
|
+
const html = await (await app.request("/collections")).text();
|
|
59
|
+
|
|
60
|
+
expect(feedHrefs(html).sort()).toEqual(["/cooking/feed", "/reading/feed"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("offers nothing when feeds are switched off site-wide", async () => {
|
|
64
|
+
const { app, services } = createDirectoryTestApp();
|
|
65
|
+
await seedCollection(services, "reading");
|
|
66
|
+
await services.settings.set("RSS_FEEDS_ENABLED", "false");
|
|
67
|
+
|
|
68
|
+
const html = await (await app.request("/collections")).text();
|
|
69
|
+
|
|
70
|
+
expect(feedHrefs(html)).toEqual([]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -52,7 +52,9 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
52
52
|
await collectionService.addThread(recipes.id, p1.id);
|
|
53
53
|
await collectionService.addThread(recipes.id, p2.id);
|
|
54
54
|
|
|
55
|
-
const directory = await collectionService.listDirectoryData(
|
|
55
|
+
const directory = await collectionService.listDirectoryData({
|
|
56
|
+
isAuthenticated: false,
|
|
57
|
+
});
|
|
56
58
|
|
|
57
59
|
expect(directory.collections).toHaveLength(2);
|
|
58
60
|
const recipesResult = directory.collections.find(
|
|
@@ -66,7 +68,9 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
it("returns empty list when no collections exist", async () => {
|
|
69
|
-
const directory = await collectionService.listDirectoryData(
|
|
71
|
+
const directory = await collectionService.listDirectoryData({
|
|
72
|
+
isAuthenticated: false,
|
|
73
|
+
});
|
|
70
74
|
expect(directory.collections).toHaveLength(0);
|
|
71
75
|
expect(directory.items).toHaveLength(0);
|
|
72
76
|
});
|
|
@@ -91,7 +95,9 @@ describe("Collections Listing Page - Data Logic", () => {
|
|
|
91
95
|
|
|
92
96
|
await postService.delete(post.id);
|
|
93
97
|
|
|
94
|
-
const directory = await collectionService.listDirectoryData(
|
|
98
|
+
const directory = await collectionService.listDirectoryData({
|
|
99
|
+
isAuthenticated: false,
|
|
100
|
+
});
|
|
95
101
|
expect(directory.collections[0]?.threadCount).toBe(1);
|
|
96
102
|
expect(directory.collections[0]?.recentActivityAt).toBe(
|
|
97
103
|
post2.lastActivityAt,
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage for archive queries stored in the path registry.
|
|
3
|
+
*
|
|
4
|
+
* A custom URL of kind `archive` hands `renderArchivePage` a query the author
|
|
5
|
+
* wrote, not one the reader assembled. That distinction decides what a
|
|
6
|
+
* signed-out reader gets when the query names a set only the author can see:
|
|
7
|
+
* the /archive route rewrites such a URL, but here the path *is* the name, so
|
|
8
|
+
* there is nothing to rewrite. Rendering the query with the clause quietly
|
|
9
|
+
* dropped would serve a different set under a name the author chose for
|
|
10
|
+
* another one.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, expect, it } from "vitest";
|
|
14
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
15
|
+
import { pageRoutes } from "../page.js";
|
|
16
|
+
|
|
17
|
+
function createPageTestApp(authenticated = false) {
|
|
18
|
+
const testApp = createTestApp({ authenticated });
|
|
19
|
+
const { app } = testApp;
|
|
20
|
+
|
|
21
|
+
app.use("*", async (c, next) => {
|
|
22
|
+
c.set("publicPath", c.req.path);
|
|
23
|
+
c.set("publicRequestUrl", c.req.url);
|
|
24
|
+
await next();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
app.route("/", pageRoutes);
|
|
28
|
+
|
|
29
|
+
return testApp;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function seed(services: {
|
|
33
|
+
posts: { create: (input: Record<string, unknown>) => Promise<unknown> };
|
|
34
|
+
customUrls: {
|
|
35
|
+
create: (input: Record<string, unknown>) => Promise<unknown>;
|
|
36
|
+
};
|
|
37
|
+
}) {
|
|
38
|
+
await services.posts.create({
|
|
39
|
+
format: "note",
|
|
40
|
+
bodyMarkdown: "body on the stream",
|
|
41
|
+
status: "published",
|
|
42
|
+
});
|
|
43
|
+
await services.posts.create({
|
|
44
|
+
format: "note",
|
|
45
|
+
bodyMarkdown: "body kept back",
|
|
46
|
+
status: "published",
|
|
47
|
+
visibility: "private",
|
|
48
|
+
});
|
|
49
|
+
await services.paths.create({
|
|
50
|
+
path: "/private-notes",
|
|
51
|
+
kind: "archive",
|
|
52
|
+
archiveQuery: "visibility=private",
|
|
53
|
+
});
|
|
54
|
+
await services.paths.create({
|
|
55
|
+
path: "/every-note",
|
|
56
|
+
kind: "archive",
|
|
57
|
+
archiveQuery: "format=note",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe("custom archive URLs", () => {
|
|
62
|
+
it("renders a stored query the reader is allowed to see", async () => {
|
|
63
|
+
const { app, services } = createPageTestApp();
|
|
64
|
+
await seed(services);
|
|
65
|
+
|
|
66
|
+
const res = await app.request("/every-note");
|
|
67
|
+
expect(res.status).toBe(200);
|
|
68
|
+
|
|
69
|
+
const html = await res.text();
|
|
70
|
+
expect(html).toContain("body on the stream");
|
|
71
|
+
expect(html).not.toContain("body kept back");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("404s a stored query naming a set only the author can see", async () => {
|
|
75
|
+
const { app, services } = createPageTestApp();
|
|
76
|
+
await seed(services);
|
|
77
|
+
|
|
78
|
+
const res = await app.request("/private-notes");
|
|
79
|
+
expect(res.status).toBe(404);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// The bug this covers: the clause used to evaporate for signed-out readers,
|
|
83
|
+
// so /private-notes served the whole public archive under that name.
|
|
84
|
+
it("never widens a stored private query into the public archive", async () => {
|
|
85
|
+
const { app, services } = createPageTestApp();
|
|
86
|
+
await seed(services);
|
|
87
|
+
|
|
88
|
+
const res = await app.request("/private-notes");
|
|
89
|
+
expect(await res.text()).not.toContain("body on the stream");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("renders the same stored query for the author", async () => {
|
|
93
|
+
const { app, services } = createPageTestApp(true);
|
|
94
|
+
await seed(services);
|
|
95
|
+
|
|
96
|
+
const res = await app.request("/private-notes");
|
|
97
|
+
expect(res.status).toBe(200);
|
|
98
|
+
|
|
99
|
+
const html = await res.text();
|
|
100
|
+
expect(html).toContain("body kept back");
|
|
101
|
+
expect(html).not.toContain("body on the stream");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Same rule, reached through a stored query: a collection that was deleted
|
|
105
|
+
// after the path was saved leaves the path naming nothing, and the honest
|
|
106
|
+
// answer is that it no longer resolves — not the whole archive.
|
|
107
|
+
it("404s a stored query naming a collection that is gone", async () => {
|
|
108
|
+
const { app, services } = createPageTestApp();
|
|
109
|
+
await seed(services);
|
|
110
|
+
await services.paths.create({
|
|
111
|
+
path: "/gone",
|
|
112
|
+
kind: "archive",
|
|
113
|
+
archiveQuery: "collection=deleted-collection",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const res = await app.request("/gone");
|
|
117
|
+
expect(res.status).toBe(404);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -39,12 +39,16 @@ describe("Featured Page - Data Logic", () => {
|
|
|
39
39
|
bodyMarkdown: "Normal post",
|
|
40
40
|
status: "published",
|
|
41
41
|
});
|
|
42
|
-
|
|
42
|
+
// Featuring an unpublished post is refused outright, so the only way a
|
|
43
|
+
// draft carries the flag is by being unpublished after the fact — the
|
|
44
|
+
// case this filter still has to catch.
|
|
45
|
+
const unpublished = await postService.create({
|
|
43
46
|
format: "note",
|
|
44
47
|
bodyMarkdown: "Draft featured",
|
|
45
48
|
featured: true,
|
|
46
|
-
status: "
|
|
49
|
+
status: "published",
|
|
47
50
|
});
|
|
51
|
+
await postService.update(unpublished.id, { status: "draft" });
|
|
48
52
|
|
|
49
53
|
const posts = await postService.list({
|
|
50
54
|
featured: true,
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feed autodiscovery in `<head>`.
|
|
3
|
+
*
|
|
4
|
+
* A page that publishes its own feed must advertise it, so a reader's
|
|
5
|
+
* extension subscribing from `/reading` gets the Reading collection — not
|
|
6
|
+
* only the site-wide feeds every page carries.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { createTestApp } from "../../../__tests__/helpers/app.js";
|
|
11
|
+
import { i18nMiddleware } from "../../../i18n/index.js";
|
|
12
|
+
import { archiveRoutes } from "../archive.js";
|
|
13
|
+
import { collectionRoutes } from "../collection.js";
|
|
14
|
+
import { collectionsPageRoutes } from "../collections.js";
|
|
15
|
+
import { languageRoutes } from "../language.js";
|
|
16
|
+
import { pageRoutes } from "../page.js";
|
|
17
|
+
|
|
18
|
+
function createFeedLinkTestApp() {
|
|
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("/archive", archiveRoutes);
|
|
30
|
+
app.route("/collections", collectionsPageRoutes);
|
|
31
|
+
app.route("/collections", collectionRoutes);
|
|
32
|
+
app.route("/:lang", languageRoutes);
|
|
33
|
+
app.route("/", pageRoutes);
|
|
34
|
+
|
|
35
|
+
return testApp;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Every `<link rel="alternate" type="application/atom+xml">` in document order. */
|
|
39
|
+
function feedLinks(html: string): { title?: string; href: string }[] {
|
|
40
|
+
const matches = html.matchAll(
|
|
41
|
+
/<link rel="alternate" type="application\/atom\+xml"(?: title="([^"]*)")? href="([^"]*)"\/?>/g,
|
|
42
|
+
);
|
|
43
|
+
return [...matches].map((match) => ({
|
|
44
|
+
title: match[1],
|
|
45
|
+
href: match[2] as string,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function createCollectionWithPost(
|
|
50
|
+
services: ReturnType<typeof createTestApp>["services"],
|
|
51
|
+
{
|
|
52
|
+
slug,
|
|
53
|
+
title,
|
|
54
|
+
postTitle,
|
|
55
|
+
}: { slug: string; title: string; postTitle: string },
|
|
56
|
+
) {
|
|
57
|
+
const collection = await services.collections.create({ slug, title });
|
|
58
|
+
const post = await services.posts.create({
|
|
59
|
+
format: "note",
|
|
60
|
+
title: postTitle,
|
|
61
|
+
bodyMarkdown: postTitle,
|
|
62
|
+
status: "published",
|
|
63
|
+
});
|
|
64
|
+
await services.collections.addThread(collection.id, post.id);
|
|
65
|
+
return collection;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("feed autodiscovery links", () => {
|
|
69
|
+
it("advertises a collection's own feed ahead of the site feeds", async () => {
|
|
70
|
+
const { app, services } = createFeedLinkTestApp();
|
|
71
|
+
await createCollectionWithPost(services, {
|
|
72
|
+
slug: "reading",
|
|
73
|
+
title: "Reading",
|
|
74
|
+
postTitle: "Book log",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const html = await (await app.request("/reading")).text();
|
|
78
|
+
|
|
79
|
+
expect(feedLinks(html)[0]).toEqual({
|
|
80
|
+
title: "Reading",
|
|
81
|
+
href: "/reading/feed",
|
|
82
|
+
});
|
|
83
|
+
expect(html).toContain('href="/feed"');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("advertises the combined feed on an aggregate collection page", async () => {
|
|
87
|
+
const { app, services } = createFeedLinkTestApp();
|
|
88
|
+
await createCollectionWithPost(services, {
|
|
89
|
+
slug: "reading",
|
|
90
|
+
title: "Reading",
|
|
91
|
+
postTitle: "Book log",
|
|
92
|
+
});
|
|
93
|
+
await createCollectionWithPost(services, {
|
|
94
|
+
slug: "movies",
|
|
95
|
+
title: "Movies",
|
|
96
|
+
postTitle: "Film log",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const html = await (
|
|
100
|
+
await app.request("/collections/reading+movies")
|
|
101
|
+
).text();
|
|
102
|
+
|
|
103
|
+
expect(feedLinks(html)[0]?.href).toBe("/collections/reading+movies/feed");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("advertises the archive feed, carrying the active filters", async () => {
|
|
107
|
+
const { app } = createFeedLinkTestApp();
|
|
108
|
+
|
|
109
|
+
const plain = await (await app.request("/archive")).text();
|
|
110
|
+
expect(feedLinks(plain)[0]).toEqual({
|
|
111
|
+
title: "Archive",
|
|
112
|
+
href: "/archive/feed",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const filtered = await (await app.request("/archive?format=note")).text();
|
|
116
|
+
expect(feedLinks(filtered)[0]).toEqual({
|
|
117
|
+
title: "Archive: Notes",
|
|
118
|
+
href: "/archive/feed?format=note",
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("points at the language view's feed inside a language view", async () => {
|
|
123
|
+
const { app, services } = createFeedLinkTestApp();
|
|
124
|
+
await services.settings.set("SITE_LANGUAGE", "zh-Hans");
|
|
125
|
+
await services.settings.set("ADDITIONAL_LANGUAGES", "en");
|
|
126
|
+
await services.settings.set("MULTILINGUAL_ENABLED", "true");
|
|
127
|
+
|
|
128
|
+
const html = await (await app.request("/en/archive")).text();
|
|
129
|
+
|
|
130
|
+
expect(feedLinks(html)[0]?.href).toBe("/en/archive/feed");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("drops the page feed link when feed publishing is off", async () => {
|
|
134
|
+
const { app, services } = createFeedLinkTestApp();
|
|
135
|
+
await services.settings.set("RSS_FEEDS_ENABLED", "false");
|
|
136
|
+
await createCollectionWithPost(services, {
|
|
137
|
+
slug: "reading",
|
|
138
|
+
title: "Reading",
|
|
139
|
+
postTitle: "Book log",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const collectionHtml = await (await app.request("/reading")).text();
|
|
143
|
+
const archiveHtml = await (await app.request("/archive")).text();
|
|
144
|
+
|
|
145
|
+
expect(feedLinks(collectionHtml)).toHaveLength(0);
|
|
146
|
+
expect(feedLinks(archiveHtml)).toHaveLength(0);
|
|
147
|
+
});
|
|
148
|
+
});
|