@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,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content language detection.
|
|
3
|
+
*
|
|
4
|
+
* The detector is a suggestion engine, so these tests care about two things:
|
|
5
|
+
* that it is right on the cases it claims to be reliable on (Hangul, kana,
|
|
6
|
+
* Simplified vs Traditional over a sentence), and that it refuses rather than
|
|
7
|
+
* guesses everywhere else — a wrong confident answer costs the author an edit
|
|
8
|
+
* they may never notice they need.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import {
|
|
13
|
+
detectContentLanguage,
|
|
14
|
+
detectScript,
|
|
15
|
+
readContentLanguage,
|
|
16
|
+
suggestPostLanguage,
|
|
17
|
+
} from "../lang-detect.js";
|
|
18
|
+
|
|
19
|
+
describe("detectScript", () => {
|
|
20
|
+
it("reads Hangul as Korean", () => {
|
|
21
|
+
expect(detectScript("안녕하세요, 반갑습니다")).toBe("ko");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("reads Korean mixed with hanja as Korean", () => {
|
|
25
|
+
// Korean borrows Han characters but never kana, so Hangul settles it.
|
|
26
|
+
expect(detectScript("韓國語 문법 정리")).toBe("ko");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("reads kana as Japanese", () => {
|
|
30
|
+
expect(detectScript("これはテストです")).toBe("ja");
|
|
31
|
+
expect(detectScript("日本語の文章を書く")).toBe("ja");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("reads Simplified-only characters as Simplified", () => {
|
|
35
|
+
expect(detectScript("国学说这时会对后")).toBe("zh-Hans");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("reads Traditional-only characters as Traditional", () => {
|
|
39
|
+
expect(detectScript("國學說這時會對後")).toBe("zh-Hant");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("decides a mixed passage by majority", () => {
|
|
43
|
+
// A Traditional post that quotes a short Simplified phrase.
|
|
44
|
+
expect(detectScript("這篇文章談的是國學說法,裡頭引了一句「这个」")).toBe(
|
|
45
|
+
"zh-Hant",
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("names the script but not the variant when the two sides tie", () => {
|
|
50
|
+
// Three distinctive characters each. Which variant is a coin toss; that
|
|
51
|
+
// the text is Chinese is not in doubt.
|
|
52
|
+
expect(detectScript("這個說这个说")).toBe("han");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("names the script but not the variant for characters shared by both", () => {
|
|
56
|
+
// "我今天很好" is identical in Simplified and Traditional.
|
|
57
|
+
expect(detectScript("我今天很好")).toBe("han");
|
|
58
|
+
// Repetition across paragraphs adds length, never a distinguishing
|
|
59
|
+
// character — the case that first showed this up.
|
|
60
|
+
expect(detectScript("哈哈哈哈哈哈哈啊哈哈哈\n\n".repeat(5))).toBe("han");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("reads Latin script as Latin, accents included", () => {
|
|
64
|
+
expect(detectScript("Hello there")).toBe("latin");
|
|
65
|
+
expect(detectScript("Café crème à Paris")).toBe("latin");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("refuses text with no letters at all", () => {
|
|
69
|
+
expect(detectScript("123 — !!! 🙂")).toBeNull();
|
|
70
|
+
expect(detectScript("")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("refuses a fragment, whichever script it is in", () => {
|
|
74
|
+
// The first few characters of a post decide nothing — an answer here would
|
|
75
|
+
// change on the next keystroke, which is worse than no answer.
|
|
76
|
+
expect(detectScript("H")).toBeNull();
|
|
77
|
+
expect(detectScript("Hello")).toBeNull();
|
|
78
|
+
expect(detectScript("这会")).toBeNull();
|
|
79
|
+
expect(detectScript("こん")).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("answers once there are a couple of words", () => {
|
|
83
|
+
expect(detectScript("Hello there")).toBe("latin");
|
|
84
|
+
expect(detectScript("这是中文")).toBe("zh-Hans");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("reads a passage by what it is mostly in", () => {
|
|
88
|
+
// A Han character used to speak for the whole post no matter how much
|
|
89
|
+
// English surrounded it.
|
|
90
|
+
expect(
|
|
91
|
+
detectScript(
|
|
92
|
+
"The word 说 comes up in every other sentence of this chapter.",
|
|
93
|
+
),
|
|
94
|
+
).toBe("latin");
|
|
95
|
+
// ...and the reverse still holds: a Chinese passage quoting a English
|
|
96
|
+
// word is Chinese.
|
|
97
|
+
expect(detectScript("这篇讲的是国学时间,里头引了 React")).toBe("zh-Hans");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("detectContentLanguage", () => {
|
|
102
|
+
const zhEn = { languages: ["zh-Hans", "en"], fallback: "zh-Hans" };
|
|
103
|
+
|
|
104
|
+
it("picks the language matching the detected script", () => {
|
|
105
|
+
expect(detectContentLanguage("这是中文", zhEn)).toBe("zh-Hans");
|
|
106
|
+
expect(detectContentLanguage("This is English", zhEn)).toBe("en");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("matches Simplified and Traditional as distinct languages", () => {
|
|
110
|
+
const both = {
|
|
111
|
+
languages: ["zh-Hans", "zh-Hant", "en"],
|
|
112
|
+
fallback: "zh-Hans",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
expect(detectContentLanguage("國學說這時會對後", both)).toBe("zh-Hant");
|
|
116
|
+
expect(detectContentLanguage("国学说这时会对后", both)).toBe("zh-Hans");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("matches a language tag that only implies its script", () => {
|
|
120
|
+
// The site configured zh-TW, not zh-Hant.
|
|
121
|
+
expect(
|
|
122
|
+
detectContentLanguage("國學說這時", {
|
|
123
|
+
languages: ["en", "zh-TW"],
|
|
124
|
+
fallback: "en",
|
|
125
|
+
}),
|
|
126
|
+
).toBe("zh-TW");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("will not choose between two non-CJK languages", () => {
|
|
130
|
+
// The Latin alphabet does not distinguish English from French.
|
|
131
|
+
expect(
|
|
132
|
+
detectContentLanguage("Bonjour tout le monde", {
|
|
133
|
+
languages: ["en", "fr", "ja"],
|
|
134
|
+
fallback: "en",
|
|
135
|
+
}),
|
|
136
|
+
).toBe("en");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("reads shared Han as the site's one Chinese language", () => {
|
|
140
|
+
// Text written the same way in both variants. With one of the two
|
|
141
|
+
// published there is nothing left to be ambiguous about.
|
|
142
|
+
expect(detectContentLanguage("哈哈哈哈哈哈哈啊哈哈哈", zhEn)).toBe(
|
|
143
|
+
"zh-Hans",
|
|
144
|
+
);
|
|
145
|
+
expect(
|
|
146
|
+
detectContentLanguage("我今天很好", {
|
|
147
|
+
languages: ["zh-TW", "en"],
|
|
148
|
+
fallback: "en",
|
|
149
|
+
}),
|
|
150
|
+
).toBe("zh-TW");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("reads shared Han as Simplified when that is the only language", () => {
|
|
154
|
+
// Nothing to weigh it against: every candidate the site has is Simplified
|
|
155
|
+
// Chinese, so text that is certainly Chinese is certainly that.
|
|
156
|
+
expect(
|
|
157
|
+
readContentLanguage("哈哈哈哈哈哈哈啊哈哈哈", {
|
|
158
|
+
languages: ["zh-Hans"],
|
|
159
|
+
}),
|
|
160
|
+
).toBe("zh-Hans");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("will not choose between Simplified and Traditional", () => {
|
|
164
|
+
// Both published, and the text distinguishes neither: the fallback is the
|
|
165
|
+
// only honest answer.
|
|
166
|
+
expect(
|
|
167
|
+
readContentLanguage("我今天很好", {
|
|
168
|
+
languages: ["zh-Hans", "zh-Hant", "en"],
|
|
169
|
+
}),
|
|
170
|
+
).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("falls back when the site does not publish the detected script", () => {
|
|
174
|
+
expect(
|
|
175
|
+
detectContentLanguage("これは日本語です", {
|
|
176
|
+
languages: ["zh-Hans", "en"],
|
|
177
|
+
fallback: "zh-Hans",
|
|
178
|
+
}),
|
|
179
|
+
).toBe("zh-Hans");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("never returns a language outside the configured set", () => {
|
|
183
|
+
const samples = [
|
|
184
|
+
"안녕하세요",
|
|
185
|
+
"これはテスト",
|
|
186
|
+
"國學說這時",
|
|
187
|
+
"国学说这时",
|
|
188
|
+
"Hello",
|
|
189
|
+
"12345",
|
|
190
|
+
"",
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
for (const sample of samples) {
|
|
194
|
+
expect(zhEn.languages, sample).toContain(
|
|
195
|
+
detectContentLanguage(sample, zhEn),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("suggestPostLanguage", () => {
|
|
202
|
+
const site = { languages: ["zh-Hans", "en"], primary: "zh-Hans" };
|
|
203
|
+
|
|
204
|
+
it("reads the language out of the text", () => {
|
|
205
|
+
expect(suggestPostLanguage({ ...site, text: "Hello there" })).toBe("en");
|
|
206
|
+
expect(suggestPostLanguage({ ...site, text: "这是中文" })).toBe("zh-Hans");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("falls back to the primary language for empty text", () => {
|
|
210
|
+
expect(suggestPostLanguage({ ...site, text: " " })).toBe("zh-Hans");
|
|
211
|
+
expect(suggestPostLanguage(site)).toBe("zh-Hans");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("keeps the primary language until the text says otherwise", () => {
|
|
215
|
+
// Half a word in: the composer shows the language of the page the author
|
|
216
|
+
// is writing from, and stays there until there is something to read.
|
|
217
|
+
expect(suggestPostLanguage({ ...site, text: "Hel" })).toBe("zh-Hans");
|
|
218
|
+
expect(suggestPostLanguage({ ...site, text: "Hello there" })).toBe("en");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("returns null on a site that is not multilingual", () => {
|
|
222
|
+
// Nothing is stamped before the author turns the feature on.
|
|
223
|
+
expect(
|
|
224
|
+
suggestPostLanguage({
|
|
225
|
+
languages: [],
|
|
226
|
+
primary: "zh-Hans",
|
|
227
|
+
text: "Hello there",
|
|
228
|
+
}),
|
|
229
|
+
).toBeNull();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -90,6 +90,111 @@ describe("markdownToTiptapJson", () => {
|
|
|
90
90
|
expect(doc.content[0].content).toHaveLength(3);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
// Tiptap's own tokenizer gave an empty item no content at all, which the
|
|
94
|
+
// schema rejects and the Markdown serializer threw on.
|
|
95
|
+
it("gives an empty list item the paragraph the schema requires", () => {
|
|
96
|
+
const doc = parse("1. First\n2.\n3. Third");
|
|
97
|
+
expect(doc.content[0].type).toBe("orderedList");
|
|
98
|
+
expect(doc.content[0].content).toHaveLength(3);
|
|
99
|
+
expect(doc.content[0].content[1].type).toBe("listItem");
|
|
100
|
+
expect(doc.content[0].content[1].content).toMatchObject([
|
|
101
|
+
{ type: "paragraph" },
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it.each([
|
|
106
|
+
["<strong>粗</strong>", "bold"],
|
|
107
|
+
["<b>粗</b>", "bold"],
|
|
108
|
+
["<em>斜</em>", "italic"],
|
|
109
|
+
["<i>斜</i>", "italic"],
|
|
110
|
+
["<s>删</s>", "strike"],
|
|
111
|
+
["<del>删</del>", "strike"],
|
|
112
|
+
])("reads %s as a %s mark", (html, mark) => {
|
|
113
|
+
const doc = parse(`前${html}后`);
|
|
114
|
+
const marked = doc.content[0].content.find(
|
|
115
|
+
(node: TiptapNode) => node.marks?.length,
|
|
116
|
+
);
|
|
117
|
+
expect(marked.marks).toEqual([{ type: mark }]);
|
|
118
|
+
expect(
|
|
119
|
+
doc.content[0].content.map((n: TiptapNode) => n.text).join(""),
|
|
120
|
+
).toBe(`前${html.replace(/<[^>]+>/g, "")}后`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("parses Markdown inside an HTML emphasis tag", () => {
|
|
124
|
+
const doc = parse("<strong>[链接](https://example.com)。</strong>后");
|
|
125
|
+
const first = doc.content[0].content[0];
|
|
126
|
+
expect(first.text).toBe("链接");
|
|
127
|
+
expect(first.marks.map((mark: TiptapMark) => mark.type).sort()).toEqual([
|
|
128
|
+
"bold",
|
|
129
|
+
"link",
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it.each([
|
|
134
|
+
'<strong onclick="alert(1)">x</strong>',
|
|
135
|
+
"<span>x</span>",
|
|
136
|
+
"<script>x</script>",
|
|
137
|
+
])("keeps %j as text", (html) => {
|
|
138
|
+
const doc = parse(html);
|
|
139
|
+
expect(doc.content[0].content).toEqual([{ type: "text", text: html }]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("keeps an ordered list's start number", () => {
|
|
143
|
+
const doc = parse("3. Third\n4. Fourth");
|
|
144
|
+
expect(doc.content[0].type).toBe("orderedList");
|
|
145
|
+
expect(doc.content[0].attrs).toEqual({ start: 3 });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// CommonMark list markers are digits. Tiptap's own tokenizer also takes
|
|
149
|
+
// letters and roman numerals, which ate the first word of these lines.
|
|
150
|
+
it.each([
|
|
151
|
+
"Ps. 今天发现苹果尼日利亚区的终身会员",
|
|
152
|
+
"PS. 补充一句",
|
|
153
|
+
"Mr. Smith went to Washington.",
|
|
154
|
+
"No. 5 is here.",
|
|
155
|
+
"OK. Fine.",
|
|
156
|
+
"I. Introduction",
|
|
157
|
+
"a. first",
|
|
158
|
+
"mix. of words",
|
|
159
|
+
])("keeps %j a paragraph", (line) => {
|
|
160
|
+
const doc = parse(line);
|
|
161
|
+
expect(doc.content).toEqual([
|
|
162
|
+
{ type: "paragraph", content: [{ type: "text", text: line }] },
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("keeps a code block inside a list item exactly as written", () => {
|
|
167
|
+
const doc = parse(
|
|
168
|
+
[
|
|
169
|
+
"1. Create the project:",
|
|
170
|
+
" ```bash",
|
|
171
|
+
" ├── .github",
|
|
172
|
+
" │ └── workflows",
|
|
173
|
+
" └── package.json",
|
|
174
|
+
" ```",
|
|
175
|
+
"2. Clone it",
|
|
176
|
+
].join("\n"),
|
|
177
|
+
);
|
|
178
|
+
const list = doc.content[0];
|
|
179
|
+
expect(list.type).toBe("orderedList");
|
|
180
|
+
expect(list.content).toHaveLength(2);
|
|
181
|
+
const codeBlock = list.content[0].content.find(
|
|
182
|
+
(node: TiptapNode) => node.type === "codeBlock",
|
|
183
|
+
);
|
|
184
|
+
expect(codeBlock.content[0].text).toBe(
|
|
185
|
+
"├── .github\n│ └── workflows\n└── package.json",
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("keeps a wide marker's continuation lines intact", () => {
|
|
190
|
+
const doc = parse("10. Run:\n\n ```sh\n ls -la\n ```");
|
|
191
|
+
const codeBlock = doc.content[0].content[0].content.find(
|
|
192
|
+
(node: TiptapNode) => node.type === "codeBlock",
|
|
193
|
+
);
|
|
194
|
+
expect(doc.content[0].attrs).toEqual({ start: 10 });
|
|
195
|
+
expect(codeBlock.content[0].text).toBe("ls -la");
|
|
196
|
+
});
|
|
197
|
+
|
|
93
198
|
it("converts horizontal rules", () => {
|
|
94
199
|
const doc = parse("Above\n\n---\n\nBelow");
|
|
95
200
|
const hr = doc.content.find(
|
|
@@ -2,6 +2,16 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import { render, toPlainText, extractTitle } from "../markdown.js";
|
|
3
3
|
|
|
4
4
|
describe("render", () => {
|
|
5
|
+
it("renders the HTML emphasis tags the serializer writes for CJK text", () => {
|
|
6
|
+
expect(render("而是:<strong>固定时间。</strong>来的人")).toBe(
|
|
7
|
+
"<p>而是:<strong>固定时间。</strong>来的人</p>",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("escapes HTML tags outside the emphasis allowlist", () => {
|
|
12
|
+
expect(render("<span>x</span>")).toBe("<p><span>x</span></p>");
|
|
13
|
+
});
|
|
14
|
+
|
|
5
15
|
it("renders a heading", () => {
|
|
6
16
|
const html = render("# Hello");
|
|
7
17
|
expect(html).toContain("<h1>");
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { Media } from "../../types.js";
|
|
3
|
+
import { buildMediaMap } from "../media-helpers.js";
|
|
4
|
+
|
|
5
|
+
const R2_PUBLIC_URL = "https://cdn.example.com";
|
|
6
|
+
const IMAGE_TRANSFORM_URL = "https://example.com/cdn-cgi/image";
|
|
7
|
+
|
|
8
|
+
function makeMedia(overrides: Partial<Media> = {}): Media {
|
|
9
|
+
return {
|
|
10
|
+
id: "med_01j0000000000000000000000",
|
|
11
|
+
siteId: "sit_test00000000000000000000000",
|
|
12
|
+
postId: "pst_01j0000000000000000000000",
|
|
13
|
+
filename: "clip.mp4",
|
|
14
|
+
originalName: "clip.mp4",
|
|
15
|
+
mimeType: "video/mp4",
|
|
16
|
+
size: 4_000_000,
|
|
17
|
+
storageKey: "media/clip.mp4",
|
|
18
|
+
provider: "r2",
|
|
19
|
+
width: 1920,
|
|
20
|
+
height: 1080,
|
|
21
|
+
durationSeconds: 12,
|
|
22
|
+
alt: null,
|
|
23
|
+
position: "a0",
|
|
24
|
+
blurhash: null,
|
|
25
|
+
waveform: null,
|
|
26
|
+
posterKey: "posters/clip-poster.png",
|
|
27
|
+
summary: null,
|
|
28
|
+
chars: null,
|
|
29
|
+
mediaKind: "video",
|
|
30
|
+
createdAt: 1706745600,
|
|
31
|
+
updatedAt: 1706745600,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function firstAttachment(media: Media) {
|
|
37
|
+
const map = buildMediaMap(
|
|
38
|
+
new Map([["pst_01j0000000000000000000000", [media]]]),
|
|
39
|
+
R2_PUBLIC_URL,
|
|
40
|
+
IMAGE_TRANSFORM_URL,
|
|
41
|
+
);
|
|
42
|
+
const list = map.get("pst_01j0000000000000000000000");
|
|
43
|
+
expect(list).toHaveLength(1);
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length checked above
|
|
45
|
+
return list![0]!;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("buildMediaMap", () => {
|
|
49
|
+
it("sizes a video poster through the image transform", () => {
|
|
50
|
+
// A poster is a still frame at the source video's resolution, so an
|
|
51
|
+
// untransformed one puts the full-size original in the timeline. This is
|
|
52
|
+
// the same treatment `toMediaView` gives the field.
|
|
53
|
+
expect(firstAttachment(makeMedia()).posterUrl).toBe(
|
|
54
|
+
"https://example.com/cdn-cgi/image/width=640,quality=80,format=auto,fit=scale-down/https://cdn.example.com/posters/clip-poster.png",
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("leaves the poster URL untransformed when no transform is configured", () => {
|
|
59
|
+
const map = buildMediaMap(
|
|
60
|
+
new Map([["pst_01j0000000000000000000000", [makeMedia()]]]),
|
|
61
|
+
R2_PUBLIC_URL,
|
|
62
|
+
);
|
|
63
|
+
expect(map.get("pst_01j0000000000000000000000")?.[0]?.posterUrl).toBe(
|
|
64
|
+
"https://cdn.example.com/posters/clip-poster.png",
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("has no poster URL without a poster key", () => {
|
|
69
|
+
expect(
|
|
70
|
+
firstAttachment(makeMedia({ posterKey: null })).posterUrl,
|
|
71
|
+
).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("leaves a video's own URL alone", () => {
|
|
75
|
+
// Only images go through the transform; asking it to resize an mp4
|
|
76
|
+
// returns nothing useful.
|
|
77
|
+
expect(firstAttachment(makeMedia()).previewUrl).toBe(
|
|
78
|
+
"https://cdn.example.com/media/clip.mp4",
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Context } from "hono";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
|
-
import {
|
|
3
|
+
import type { NavItemView } from "../../types.js";
|
|
4
|
+
import {
|
|
5
|
+
collectNavigationCollectionIds,
|
|
6
|
+
getNavigationData,
|
|
7
|
+
} from "../navigation.js";
|
|
4
8
|
|
|
5
9
|
describe("getNavigationData", () => {
|
|
6
10
|
it("renders site footer markdown through the shared pipeline", async () => {
|
|
@@ -99,6 +103,61 @@ describe("getNavigationData", () => {
|
|
|
99
103
|
expect(result.links[1]?.url).toBe("/");
|
|
100
104
|
});
|
|
101
105
|
|
|
106
|
+
it("hides the built-in Subscribe item when feeds are disabled", async () => {
|
|
107
|
+
const context = {
|
|
108
|
+
var: {
|
|
109
|
+
publicPath: "/",
|
|
110
|
+
appConfig: {
|
|
111
|
+
siteName: "Jant",
|
|
112
|
+
sitePathPrefix: "",
|
|
113
|
+
siteOrigin: "https://example.com",
|
|
114
|
+
siteDescription: "",
|
|
115
|
+
siteDescriptionExplicit: false,
|
|
116
|
+
siteAvatarUrl: "",
|
|
117
|
+
showHeaderAvatar: false,
|
|
118
|
+
siteFooter: "",
|
|
119
|
+
rssFeedsEnabled: false,
|
|
120
|
+
},
|
|
121
|
+
services: {
|
|
122
|
+
navItems: {
|
|
123
|
+
list: async () => [
|
|
124
|
+
{
|
|
125
|
+
id: "nav_subscribe",
|
|
126
|
+
type: "system",
|
|
127
|
+
systemKey: "subscribe",
|
|
128
|
+
label: "Subscribe",
|
|
129
|
+
url: "/subscribe",
|
|
130
|
+
placement: "header",
|
|
131
|
+
position: "a0",
|
|
132
|
+
createdAt: 1,
|
|
133
|
+
updatedAt: 1,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "nav_archive",
|
|
137
|
+
type: "system",
|
|
138
|
+
systemKey: "archive",
|
|
139
|
+
label: "All",
|
|
140
|
+
url: "/archive",
|
|
141
|
+
placement: "header",
|
|
142
|
+
position: "a1",
|
|
143
|
+
createdAt: 1,
|
|
144
|
+
updatedAt: 1,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
collections: { list: async () => [] },
|
|
149
|
+
},
|
|
150
|
+
isAuthenticated: false,
|
|
151
|
+
session: null,
|
|
152
|
+
},
|
|
153
|
+
} as unknown as Context;
|
|
154
|
+
|
|
155
|
+
const result = await getNavigationData(context);
|
|
156
|
+
|
|
157
|
+
// The page it addresses 404s without feeds, so the entry has to go too.
|
|
158
|
+
expect(result.links.map((link) => link.url)).toEqual(["/archive"]);
|
|
159
|
+
});
|
|
160
|
+
|
|
102
161
|
it("hides only the built-in RSS item when feeds are disabled", async () => {
|
|
103
162
|
const context = {
|
|
104
163
|
var: {
|
|
@@ -153,4 +212,79 @@ describe("getNavigationData", () => {
|
|
|
153
212
|
|
|
154
213
|
expect(result.links.map((link) => link.id)).toEqual(["nav_custom"]);
|
|
155
214
|
});
|
|
215
|
+
|
|
216
|
+
// A post page's URL carries no language prefix, so its chrome is scoped by
|
|
217
|
+
// the post's language instead of the request path.
|
|
218
|
+
it("scopes the base path to an explicit language", async () => {
|
|
219
|
+
const makeContext = (languageScope: string) =>
|
|
220
|
+
({
|
|
221
|
+
var: {
|
|
222
|
+
publicPath: "/some-post",
|
|
223
|
+
appConfig: {
|
|
224
|
+
siteName: "Jant",
|
|
225
|
+
sitePathPrefix: "",
|
|
226
|
+
siteDescription: "",
|
|
227
|
+
siteDescriptionExplicit: false,
|
|
228
|
+
siteAvatarUrl: "",
|
|
229
|
+
showHeaderAvatar: false,
|
|
230
|
+
siteFooter: "",
|
|
231
|
+
siteLanguage: "zh-Hans",
|
|
232
|
+
additionalLanguages: ["ja"],
|
|
233
|
+
multilingualEnabled: true,
|
|
234
|
+
rssFeedsEnabled: true,
|
|
235
|
+
},
|
|
236
|
+
services: {
|
|
237
|
+
navItems: {
|
|
238
|
+
list: async () => [
|
|
239
|
+
{
|
|
240
|
+
id: "nav_latest",
|
|
241
|
+
type: "system",
|
|
242
|
+
systemKey: "latest",
|
|
243
|
+
label: "Latest",
|
|
244
|
+
url: "/",
|
|
245
|
+
placement: "header",
|
|
246
|
+
position: "a0",
|
|
247
|
+
createdAt: 1,
|
|
248
|
+
updatedAt: 1,
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
collections: {
|
|
253
|
+
listByRecentActivity: async () => [],
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
isAuthenticated: false,
|
|
257
|
+
session: null,
|
|
258
|
+
},
|
|
259
|
+
}) as unknown as Context;
|
|
260
|
+
|
|
261
|
+
const japanese = await getNavigationData(makeContext("ja"), {
|
|
262
|
+
languageScope: "ja",
|
|
263
|
+
});
|
|
264
|
+
expect(japanese.basePath).toBe("/ja");
|
|
265
|
+
expect(japanese.links[0]?.url).toBe("/ja");
|
|
266
|
+
|
|
267
|
+
const primary = await getNavigationData(makeContext("zh-Hans"), {
|
|
268
|
+
languageScope: "zh-Hans",
|
|
269
|
+
});
|
|
270
|
+
expect(primary.basePath).toBe("");
|
|
271
|
+
expect(primary.links[0]?.url).toBe("/");
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("collectNavigationCollectionIds", () => {
|
|
276
|
+
// The directory used to read only `collection` items, so a smart collection
|
|
277
|
+
// already in the navigation still offered "Add to Navigation".
|
|
278
|
+
it("names both kinds of collection and ignores everything else", () => {
|
|
279
|
+
const links = [
|
|
280
|
+
{ id: "nav_1", type: "system", systemKey: "home" },
|
|
281
|
+
{ id: "nav_2", type: "collection", collectionId: "col_1" },
|
|
282
|
+
{ id: "nav_3", type: "smart_collection", smartCollectionId: "smc_1" },
|
|
283
|
+
{ id: "nav_4", type: "link", url: "https://example.com" },
|
|
284
|
+
{ id: "nav_5", type: "collection" },
|
|
285
|
+
{ id: "nav_6", type: "smart_collection" },
|
|
286
|
+
] as unknown as NavItemView[];
|
|
287
|
+
|
|
288
|
+
expect(collectNavigationCollectionIds(links)).toEqual(["col_1", "smc_1"]);
|
|
289
|
+
});
|
|
156
290
|
});
|