@jant/core 0.6.16 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/bin/commands/assets/upload.js +192 -15
- package/bin/commands/import-site.js +60 -4
- package/bin/commands/reset-password.js +21 -8
- package/bin/commands/site/snapshot/export.js +1 -1
- package/bin/commands/site/snapshot/import.js +1 -1
- package/bin/lib/d1-query.js +5 -16
- package/bin/lib/node-env.js +40 -4
- package/bin/lib/site-selection.js +85 -29
- package/bin/lib/wrangler-cli.js +28 -2
- package/bin/lib/wrangler-json.js +35 -0
- package/dist/{app-C7_4oab9.js → app-K_Aa1MMn.js} +52476 -13330
- package/dist/client/.vite/manifest.json +255 -90
- package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
- package/dist/client/_assets/chunks/create-editor-CD3FhrOB.js +257 -0
- package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
- package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
- package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
- package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
- package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
- package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
- package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
- package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
- package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
- package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
- package/dist/client/_assets/chunks/sortable-list-CgaL2jCs.js +2 -0
- package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
- package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
- package/dist/client/_assets/chunks/unsafe-svg-0QCkP0vZ.js +2 -0
- package/dist/client/_assets/client-GiYENVw8.css +2 -0
- package/dist/client/_assets/client-VnFxJN7G.js +295 -0
- package/dist/client/_assets/client-auth-k8gJ6QJj.js +828 -0
- package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
- package/dist/client/_assets/client-compose-B_kDtWkW.js +2004 -0
- package/dist/client/_assets/client-manage-M90aSTOk.js +2170 -0
- package/dist/client/_assets/client-settings-DfYs9n1F.js +1112 -0
- package/dist/{export-Dl5KCiEs.js → github-sync-BPAvT999.js} +2254 -107
- package/dist/index.js +2 -5
- package/dist/node.js +5 -7
- package/package.json +3 -3
- package/src/__tests__/auth-session-freshness.test.ts +57 -0
- package/src/__tests__/basecoat-variants.test.ts +121 -0
- package/src/__tests__/bin/assets-upload.test.ts +68 -0
- package/src/__tests__/export-hugo-build.test.ts +260 -0
- package/src/__tests__/export-service.test.ts +231 -0
- package/src/__tests__/helpers/component-css.ts +91 -0
- package/src/__tests__/helpers/db.ts +33 -0
- package/src/__tests__/helpers/export-fixtures.ts +3 -0
- package/src/__tests__/stylesheet-audience.test.ts +159 -0
- package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
- package/src/app.tsx +71 -16
- package/src/auth.ts +18 -1
- package/src/client/__tests__/collection-page-actions.test.ts +51 -2
- package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
- package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
- package/src/client/__tests__/compose-bridge.test.ts +272 -0
- package/src/client/__tests__/compose-launch.test.ts +62 -1
- package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
- package/src/client/__tests__/compose-triggers.test.ts +90 -0
- package/src/client/__tests__/copy-field.test.ts +162 -0
- package/src/client/__tests__/custom-url-menu.test.ts +127 -6
- package/src/client/__tests__/image-processor.test.ts +324 -2
- package/src/client/__tests__/lazy-entries.test.ts +92 -0
- package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
- package/src/client/__tests__/runtime-paths.test.ts +84 -0
- package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
- package/src/client/__tests__/toast.test.ts +23 -0
- package/src/client/__tests__/video-processor-probe.test.ts +175 -0
- package/src/client/__tests__/video-processor.test.ts +181 -0
- package/src/client/audio-processor.ts +24 -15
- package/src/client/audio-waveform.ts +49 -0
- package/src/client/collection-dialog-host.ts +72 -0
- package/src/client/collection-navigation.ts +51 -16
- package/src/client/collection-page-actions.ts +26 -0
- package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
- package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
- package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
- package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
- package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
- package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
- package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
- package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
- package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
- package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
- package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
- package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
- package/src/client/components/collection-dialog-types.ts +49 -0
- package/src/client/components/collection-manager-types.ts +32 -1
- package/src/client/components/collection-types.ts +1 -15
- package/src/client/components/compose-types.ts +75 -1
- package/src/client/components/jant-collection-dialog.ts +722 -0
- package/src/client/components/jant-collection-directory.ts +567 -182
- package/src/client/components/jant-collection-form.ts +53 -232
- package/src/client/components/jant-command-palette.ts +100 -49
- package/src/client/components/jant-compose-dialog.ts +1486 -707
- package/src/client/components/jant-compose-editor.ts +673 -336
- package/src/client/components/jant-compose-fullscreen.ts +51 -43
- package/src/client/components/jant-config-editor.ts +65 -53
- package/src/client/components/jant-confirm-dialog.ts +13 -11
- package/src/client/components/jant-locale-picker.ts +418 -0
- package/src/client/components/jant-media-lightbox.ts +146 -136
- package/src/client/components/jant-nav-manager.ts +690 -366
- package/src/client/components/jant-post-menu.ts +1086 -282
- package/src/client/components/jant-post-picker.ts +324 -0
- package/src/client/components/jant-repo-picker.ts +138 -106
- package/src/client/components/jant-settings-avatar.ts +31 -27
- package/src/client/components/jant-settings-general.ts +617 -543
- package/src/client/components/jant-settings-language.ts +1087 -0
- package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
- package/src/client/components/jant-text-preview.ts +94 -86
- package/src/client/components/nav-manager-types.ts +62 -1
- package/src/client/components/settings-types.ts +14 -29
- package/src/client/components/smart-collection-conditions.ts +285 -0
- package/src/client/components/smart-collection-dialog-types.ts +68 -0
- package/src/client/compose-bridge.ts +214 -141
- package/src/client/compose-launch.ts +76 -2
- package/src/client/compose-shortcuts.ts +6 -8
- package/src/client/compose-triggers.ts +50 -0
- package/src/client/copy-field.ts +108 -0
- package/src/client/custom-url-menu.ts +60 -4
- package/src/client/icons.ts +14 -0
- package/src/client/image-plan.ts +165 -0
- package/src/client/image-processor.ts +195 -117
- package/src/client/image-worker-client.ts +45 -0
- package/src/client/image-worker.ts +135 -0
- package/src/client/lazy-entries.ts +95 -0
- package/src/client/lazy-slugify.ts +2 -2
- package/src/client/media-scroll-hint.ts +43 -2
- package/src/client/mediabunny.ts +29 -0
- package/src/client/post-picker.ts +41 -0
- package/src/client/runtime-paths.ts +74 -1
- package/src/client/settings-bridge.ts +4 -10
- package/src/client/site-header-nav.js +18 -6
- package/src/client/smart-collection-dialog-host.ts +116 -0
- package/src/client/smart-collection-page-actions.ts +208 -0
- package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
- package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
- package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
- package/src/client/tiptap/bubble-menu.ts +47 -16
- package/src/client/tiptap/floating-position.ts +216 -2
- package/src/client/tiptap/inline-image-upload.ts +7 -3
- package/src/client/tiptap/link-toolbar.ts +22 -9
- package/src/client/toast.ts +13 -0
- package/src/client/types/vite-worker.d.ts +12 -0
- package/src/client/upload-with-metadata.ts +3 -13
- package/src/client/video-processor.ts +351 -107
- package/src/client-auth.ts +14 -31
- package/src/client-compose.ts +19 -0
- package/src/client-manage.ts +13 -0
- package/src/client-settings.ts +14 -0
- package/src/client-site.ts +10 -3
- package/src/client.ts +4 -0
- package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
- package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
- package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
- package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
- package/src/db/migrations/0032_married_tomas.sql +3 -0
- package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
- package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
- package/src/db/migrations/meta/0032_snapshot.json +2582 -0
- package/src/db/migrations/meta/0033_snapshot.json +2816 -0
- package/src/db/migrations/meta/0034_snapshot.json +2816 -0
- package/src/db/migrations/meta/_journal.json +21 -0
- package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
- package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
- package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
- package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
- package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
- package/src/db/migrations/pg/meta/_journal.json +21 -0
- package/src/db/pg/schema.ts +195 -27
- package/src/db/post-visibility.ts +117 -0
- package/src/db/schema.ts +222 -27
- package/src/i18n/__tests__/detect.test.ts +26 -81
- package/src/i18n/__tests__/supported-locales.test.ts +83 -0
- package/src/i18n/context.tsx +12 -2
- package/src/i18n/detect.ts +7 -66
- package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
- package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
- package/src/i18n/locales/public/en.po +490 -158
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +487 -155
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +487 -155
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +551 -69
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +597 -115
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +610 -128
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/locales.ts +94 -0
- package/src/i18n/middleware.ts +12 -2
- package/src/i18n/supported-locales.ts +80 -0
- package/src/lib/__tests__/api-settings.test.ts +11 -2
- package/src/lib/__tests__/asset-path.test.ts +92 -0
- package/src/lib/__tests__/deferred.test.ts +90 -0
- package/src/lib/__tests__/discover-ping.test.ts +89 -0
- package/src/lib/__tests__/discover.test.ts +410 -0
- package/src/lib/__tests__/feed-policy.test.ts +26 -0
- package/src/lib/__tests__/feed.test.ts +1526 -15
- package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
- package/src/lib/__tests__/hosted-signin.test.ts +1 -1
- package/src/lib/__tests__/http-cache.test.ts +162 -0
- package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
- package/src/lib/__tests__/jant-docs.test.ts +23 -0
- package/src/lib/__tests__/lang-detect.test.ts +231 -0
- package/src/lib/__tests__/media-helpers.test.ts +81 -0
- package/src/lib/__tests__/navigation.test.ts +135 -1
- package/src/lib/__tests__/resolve-config.test.ts +64 -3
- package/src/lib/__tests__/schemas.test.ts +42 -4
- package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
- package/src/lib/__tests__/slug.test.ts +70 -0
- package/src/lib/__tests__/slugify.test.ts +85 -0
- package/src/lib/__tests__/storage.test.ts +32 -0
- package/src/lib/__tests__/thread-fold.test.ts +113 -0
- package/src/lib/__tests__/upload-sideload.test.ts +14 -0
- package/src/lib/__tests__/url-fetch.test.ts +1 -2
- package/src/lib/__tests__/url.test.ts +78 -72
- package/src/lib/__tests__/view-language.test.ts +413 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
- package/src/lib/address.ts +57 -0
- package/src/lib/api-posts.ts +12 -0
- package/src/lib/asset-path.ts +98 -1
- package/src/lib/build-env.ts +16 -0
- package/src/lib/collection-groups.ts +5 -1
- package/src/lib/collection-paths.ts +3 -10
- package/src/lib/constants.ts +28 -3
- package/src/lib/copy-field.ts +36 -0
- package/src/lib/crypto.ts +40 -0
- package/src/lib/csp-builder.ts +2 -1
- package/src/lib/deferred.ts +79 -0
- package/src/lib/discover-ping.ts +141 -0
- package/src/lib/discover.ts +437 -0
- package/src/lib/env.ts +134 -0
- package/src/lib/errors.ts +33 -0
- package/src/lib/feed-policy.ts +236 -2
- package/src/lib/feed.ts +926 -73
- package/src/lib/filter-dimensions.ts +1349 -0
- package/src/lib/github-sync-site-config.ts +3 -0
- package/src/lib/hosted-signin.ts +1 -1
- package/src/lib/http-cache.ts +148 -0
- package/src/lib/hugo-markdown.ts +16 -0
- package/src/lib/icons.ts +9 -2
- package/src/lib/ids.ts +3 -0
- package/src/lib/jant-docs.ts +42 -0
- package/src/lib/lang-detect.ts +308 -0
- package/src/lib/media-helpers.ts +13 -1
- package/src/lib/navigation.ts +78 -11
- package/src/lib/pagination.ts +28 -24
- package/src/lib/per-language-surfaces.ts +67 -0
- package/src/lib/post-meta.ts +19 -0
- package/src/lib/render.tsx +55 -7
- package/src/lib/resolve-config.ts +53 -5
- package/src/lib/schemas.ts +201 -10
- package/src/lib/site-header-fragment.tsx +2 -1
- package/src/lib/site-unavailable-page.ts +47 -0
- package/src/lib/slug.ts +51 -7
- package/src/lib/slugify.ts +113 -0
- package/src/lib/startup-config.ts +4 -6
- package/src/lib/storage.ts +7 -0
- package/src/lib/summary.ts +50 -0
- package/src/lib/thread-fold.ts +161 -0
- package/src/lib/time.ts +32 -0
- package/src/lib/timeline.ts +42 -1
- package/src/lib/tiptap-render.ts +1 -2
- package/src/lib/upload.ts +16 -13
- package/src/lib/url.ts +139 -86
- package/src/lib/version.ts +10 -8
- package/src/lib/view-language.ts +436 -0
- package/src/lib/view.ts +51 -23
- package/src/lib/viewer-context.ts +62 -0
- package/src/middleware/__tests__/auth.test.ts +70 -2
- package/src/middleware/__tests__/cache-control.test.ts +71 -3
- package/src/middleware/__tests__/onboarding.test.ts +76 -5
- package/src/middleware/__tests__/secure-headers.test.ts +1 -1
- package/src/middleware/__tests__/session.test.ts +181 -33
- package/src/middleware/auth.ts +50 -31
- package/src/middleware/cache-control.ts +75 -10
- package/src/middleware/config.ts +6 -6
- package/src/middleware/error-handler.ts +12 -0
- package/src/middleware/onboarding.ts +32 -10
- package/src/middleware/secure-headers.ts +1 -1
- package/src/middleware/session.ts +96 -8
- package/src/node/__tests__/cli-migrate.test.ts +21 -19
- package/src/node/__tests__/cli-reset-password.test.ts +81 -8
- package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
- package/src/node/__tests__/cli-site-selection.test.ts +128 -38
- package/src/node/__tests__/runtime.test.ts +37 -0
- package/src/node/index.ts +1 -0
- package/src/preset.css +76 -22
- package/src/routes/__tests__/feed-validator.test.ts +156 -0
- package/src/routes/api/__tests__/collections.test.ts +114 -0
- package/src/routes/api/__tests__/discover.test.ts +222 -0
- package/src/routes/api/__tests__/nav-items.test.ts +212 -2
- package/src/routes/api/__tests__/post-translations.test.ts +829 -0
- package/src/routes/api/__tests__/settings.test.ts +24 -3
- package/src/routes/api/__tests__/telegram.test.ts +6 -5
- package/src/routes/api/collections.ts +49 -6
- package/src/routes/api/custom-urls.ts +0 -1
- package/src/routes/api/discover.ts +95 -0
- package/src/routes/api/export.ts +3 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
- package/src/routes/api/nav-items.ts +55 -8
- package/src/routes/api/posts.ts +159 -0
- package/src/routes/api/public/__tests__/archive.test.ts +111 -0
- package/src/routes/api/public/archive.ts +77 -81
- package/src/routes/api/public/posts.ts +23 -6
- package/src/routes/api/smart-collections.ts +132 -0
- package/src/routes/api/telegram.ts +2 -40
- package/src/routes/api/uploads.ts +1 -2
- package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
- package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
- package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
- package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
- package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
- package/src/routes/auth/__tests__/setup.test.ts +278 -26
- package/src/routes/auth/__tests__/signin.test.ts +135 -0
- package/src/routes/auth/setup.tsx +934 -186
- package/src/routes/auth/signin.tsx +10 -8
- package/src/routes/compose.tsx +5 -0
- package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
- package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
- package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
- package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
- package/src/routes/dash/custom-urls.tsx +1 -37
- package/src/routes/dash/settings.tsx +502 -41
- package/src/routes/discover-announce.ts +96 -0
- package/src/routes/feed/__tests__/feed.test.ts +165 -2
- package/src/routes/feed/feed.ts +69 -40
- package/src/routes/feed/sitemap.ts +95 -11
- package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
- package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
- package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
- package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
- package/src/routes/pages/__tests__/collections.test.ts +9 -3
- package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
- package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
- package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
- package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
- package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
- package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
- package/src/routes/pages/archive.tsx +471 -369
- package/src/routes/pages/collection.tsx +94 -96
- package/src/routes/pages/collections.tsx +38 -50
- package/src/routes/pages/featured.tsx +32 -13
- package/src/routes/pages/home.tsx +25 -6
- package/src/routes/pages/language.tsx +113 -0
- package/src/routes/pages/latest.tsx +26 -16
- package/src/routes/pages/new.tsx +3 -0
- package/src/routes/pages/page.tsx +178 -17
- package/src/routes/pages/partials.tsx +41 -0
- package/src/routes/pages/search.tsx +18 -3
- package/src/routes/pages/smart-collection.tsx +340 -0
- package/src/routes/pages/subscribe.tsx +182 -0
- package/src/runtime/__tests__/index.test.ts +19 -0
- package/src/runtime/__tests__/node.test.ts +129 -4
- package/src/runtime/cloudflare.ts +21 -2
- package/src/runtime/index.ts +27 -1
- package/src/runtime/node.ts +18 -1
- package/src/runtime/site.ts +95 -24
- package/src/services/__tests__/auth.test.ts +116 -7
- package/src/services/__tests__/collection.test.ts +108 -4
- package/src/services/__tests__/language.test.ts +495 -0
- package/src/services/__tests__/navigation.test.ts +213 -10
- package/src/services/__tests__/path.test.ts +180 -0
- package/src/services/__tests__/post-language.test.ts +830 -0
- package/src/services/__tests__/post.test.ts +46 -9
- package/src/services/__tests__/settings.test.ts +228 -17
- package/src/services/__tests__/site-admin.test.ts +5 -5
- package/src/services/__tests__/smart-collection.test.ts +651 -0
- package/src/services/about-page.ts +22 -1
- package/src/services/auth.ts +51 -49
- package/src/services/bootstrap.ts +188 -38
- package/src/services/collection-directory-position.ts +76 -0
- package/src/services/collection.ts +331 -46
- package/src/services/custom-url.ts +27 -22
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +123 -123
- package/src/services/export-theme/layouts/_default/rss.xml +77 -1
- package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
- package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
- package/src/services/export-theme/layouts/partials/pagination.html +14 -13
- package/src/services/export-theme/layouts/partials/post-card.html +8 -4
- package/src/services/export-theme/layouts/partials/reply.html +2 -1
- package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
- package/src/services/export-theme/styles/main.css +13 -1
- package/src/services/export.ts +278 -19
- package/src/services/index.ts +34 -3
- package/src/services/language.ts +412 -0
- package/src/services/mcp.ts +3 -1
- package/src/services/media.ts +14 -6
- package/src/services/navigation.ts +429 -67
- package/src/services/path.ts +221 -6
- package/src/services/post.ts +1021 -152
- package/src/services/search.ts +39 -2
- package/src/services/settings.ts +238 -43
- package/src/services/site-admin.ts +24 -7
- package/src/services/site.ts +77 -1
- package/src/services/smart-collection.ts +636 -0
- package/src/services/telegram.ts +20 -22
- package/src/services/upload-session.ts +1 -3
- package/src/style-author.css +27 -0
- package/src/styles/components-author.css +1622 -0
- package/src/styles/components.css +24 -1571
- package/src/styles/site-media.css +16 -2
- package/src/styles/tokens.css +82 -12
- package/src/styles/ui-author.css +7602 -0
- package/src/styles/ui.css +1087 -7219
- package/src/types/app-context.ts +7 -0
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +94 -15
- package/src/types/constants.ts +188 -16
- package/src/types/entities.ts +87 -1
- package/src/types/operations.ts +64 -2
- package/src/types/props.ts +157 -31
- package/src/types/views.ts +55 -1
- package/src/ui/__tests__/font-themes.test.ts +52 -12
- package/src/ui/compose/ComposeDialog.tsx +201 -7
- package/src/ui/compose/ComposePrompt.tsx +1 -5
- package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
- package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
- package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
- package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
- package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
- package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
- package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
- package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
- package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
- package/src/ui/dash/settings/GeneralContent.tsx +275 -113
- package/src/ui/dash/settings/LanguageContent.tsx +409 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
- package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
- package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
- package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
- package/src/ui/dash/settings/discover-copy.ts +116 -0
- package/src/ui/feed/LinkCard.tsx +55 -45
- package/src/ui/feed/NoteCard.tsx +9 -4
- package/src/ui/feed/PostStatusBadges.tsx +90 -43
- package/src/ui/feed/QuoteCard.tsx +55 -30
- package/src/ui/feed/ThreadPreview.tsx +6 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
- package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
- package/src/ui/feed/thread-preview-state.ts +20 -6
- package/src/ui/font-themes.ts +39 -29
- package/src/ui/layouts/BaseLayout.tsx +155 -10
- package/src/ui/layouts/SiteLayout.tsx +192 -34
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
- package/src/ui/pages/ArchivePage.tsx +282 -289
- package/src/ui/pages/CollectionPage.tsx +86 -76
- package/src/ui/pages/CollectionsPage.tsx +5 -0
- package/src/ui/pages/ComposePage.tsx +8 -0
- package/src/ui/pages/PostPage.tsx +47 -0
- package/src/ui/pages/SearchPage.tsx +3 -3
- package/src/ui/pages/SmartCollectionPage.tsx +434 -0
- package/src/ui/pages/SubscribePage.tsx +236 -0
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
- package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
- package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
- package/src/ui/shared/CollectionDirectory.tsx +178 -3
- package/src/ui/shared/CollectionsManager.tsx +70 -17
- package/src/ui/shared/CopyField.tsx +127 -0
- package/src/ui/shared/EmptyPostContent.tsx +23 -0
- package/src/ui/shared/MediaGallery.tsx +125 -46
- package/src/ui/shared/Pagination.tsx +20 -5
- package/src/ui/shared/PostFooter.tsx +20 -11
- package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
- package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
- package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
- package/src/ui/shared/__tests__/pagination.test.ts +38 -24
- package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
- package/src/ui/shared/archive-labels.ts +98 -0
- package/src/ui/shared/collection-management-labels.ts +94 -16
- package/src/ui/shared/navigation-labels.ts +45 -71
- package/src/ui/shared/post-article-attributes.ts +2 -0
- package/src/ui/shared/smart-collection-labels.ts +343 -0
- package/dist/app-C75Zc8dD.js +0 -6
- package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
- package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
- package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
- package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
- package/dist/client/_assets/client-D3VZyG4L.css +0 -2
- package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
- package/dist/env-BPYViDJJ.js +0 -281
- package/dist/github-api-BNF35Lkx.js +0 -176
- package/dist/github-app-DdiD-bC4.js +0 -275
- package/dist/github-sync-Bqg62IvV.js +0 -4
- package/dist/github-sync-HdK2EgvJ.js +0 -431
- package/dist/url-CbLAtlZe.js +0 -900
- package/src/client/__tests__/collection-created-notice.test.ts +0 -36
- package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
- package/src/client/__tests__/post-form-bridge.test.ts +0 -82
- package/src/client/collection-created-notice.ts +0 -66
- package/src/client/collection-form-bridge.ts +0 -191
- package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
- package/src/client/components/jant-post-form.ts +0 -422
- package/src/client/components/post-form-template.ts +0 -352
- package/src/client/components/post-form-types.ts +0 -96
- package/src/client/media-metadata.ts +0 -247
- package/src/client/post-form-bridge.ts +0 -193
- package/src/ui/pages/CollectionEditorPage.tsx +0 -175
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import type { Context } from "hono";
|
|
3
3
|
import { renderToString } from "hono/jsx/dom/server";
|
|
4
4
|
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { readComponentCss } from "../../../__tests__/helpers/component-css.js";
|
|
5
6
|
import type { MediaView, PostView } from "../../../types.js";
|
|
6
7
|
import { I18nProvider } from "../../../i18n/context.js";
|
|
7
8
|
import { createI18n } from "../../../i18n/i18n.js";
|
|
@@ -50,11 +51,13 @@ function renderWithI18n(
|
|
|
50
51
|
| ReturnType<typeof NoteCard>
|
|
51
52
|
| ReturnType<typeof LinkCard>
|
|
52
53
|
| ReturnType<typeof QuoteCard>,
|
|
54
|
+
isAuthenticated = true,
|
|
53
55
|
) {
|
|
54
56
|
const i18n = createI18n("en");
|
|
55
57
|
const c = {
|
|
56
58
|
get(key: string) {
|
|
57
59
|
if (key === "i18n") return i18n;
|
|
60
|
+
if (key === "isAuthenticated") return isAuthenticated;
|
|
58
61
|
return undefined;
|
|
59
62
|
},
|
|
60
63
|
} as unknown as Context;
|
|
@@ -91,6 +94,274 @@ describe("timeline cards", () => {
|
|
|
91
94
|
expect(detailHtml).not.toContain("feed-card");
|
|
92
95
|
});
|
|
93
96
|
|
|
97
|
+
it("keeps the link preview above the commentary in every mode", () => {
|
|
98
|
+
const post = createPostView({
|
|
99
|
+
format: "link",
|
|
100
|
+
previewImageUrl: "https://example.com/preview.jpg",
|
|
101
|
+
previewKind: "video",
|
|
102
|
+
previewProvider: "youtube",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
for (const mode of ["feed", "detail"] as const) {
|
|
106
|
+
const html = renderWithI18n(LinkCard({ post, mode }));
|
|
107
|
+
|
|
108
|
+
// The preview is part of the reference, so commentary follows it —
|
|
109
|
+
// detail used to invert this and read as a note about nothing.
|
|
110
|
+
expect(html.indexOf('class="link-preview"')).toBeGreaterThan(-1);
|
|
111
|
+
expect(html.indexOf('class="link-preview"')).toBeLessThan(
|
|
112
|
+
html.indexOf("data-post-body"),
|
|
113
|
+
);
|
|
114
|
+
expect(html.indexOf("feed-link-title")).toBeLessThan(
|
|
115
|
+
html.indexOf('class="link-preview"'),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("marks a link post's referenced article as u-bookmark-of, never as the entry's own u-url", () => {
|
|
121
|
+
const post = createPostView({ format: "link" });
|
|
122
|
+
|
|
123
|
+
const feedHtml = renderWithI18n(LinkCard({ post, mode: "feed" }));
|
|
124
|
+
const compactHtml = renderWithI18n(LinkCard({ post, mode: "compact" }));
|
|
125
|
+
const detailHtml = renderWithI18n(LinkCard({ post, mode: "detail" }));
|
|
126
|
+
|
|
127
|
+
for (const html of [feedHtml, compactHtml, detailHtml]) {
|
|
128
|
+
expect(html).toContain(
|
|
129
|
+
'<a href="https://example.com/article" class="u-bookmark-of feed-link-title-link"',
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The entry's own URL is the permalink, and it is emitted exactly once —
|
|
134
|
+
// a second `u-url` on the referenced article would make parsers read that
|
|
135
|
+
// article as this post.
|
|
136
|
+
expect(feedHtml.match(/u-url/g)).toHaveLength(1);
|
|
137
|
+
expect(detailHtml.match(/u-url/g)).toHaveLength(1);
|
|
138
|
+
expect(feedHtml).toContain('class="u-url post-footer-link"');
|
|
139
|
+
expect(detailHtml).toContain('class="u-url post-footer-link"');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("omits u-bookmark-of when a link post has no referenced article", () => {
|
|
143
|
+
const post = createPostView({ format: "link", url: undefined });
|
|
144
|
+
|
|
145
|
+
const feedHtml = renderWithI18n(LinkCard({ post, mode: "feed" }));
|
|
146
|
+
const detailHtml = renderWithI18n(LinkCard({ post, mode: "detail" }));
|
|
147
|
+
|
|
148
|
+
// The title falls back to the permalink; a post does not bookmark itself.
|
|
149
|
+
for (const html of [feedHtml, detailHtml]) {
|
|
150
|
+
expect(html).toContain('<a href="/post-1" class="feed-link-title-link"');
|
|
151
|
+
expect(html).not.toContain("u-bookmark-of");
|
|
152
|
+
expect(html.match(/u-url/g)).toHaveLength(1);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("scopes a quote post's citation to an h-cite and keeps e-content on the commentary", () => {
|
|
157
|
+
const post = createPostView({ format: "quote" });
|
|
158
|
+
|
|
159
|
+
const feedHtml = renderWithI18n(QuoteCard({ post, mode: "feed" }));
|
|
160
|
+
const detailHtml = renderWithI18n(QuoteCard({ post, mode: "detail" }));
|
|
161
|
+
|
|
162
|
+
for (const html of [feedHtml, detailHtml]) {
|
|
163
|
+
expect(html).toContain('<figure class="feed-quote-cite h-cite">');
|
|
164
|
+
// The quotation is the citation's content, not the entry's.
|
|
165
|
+
expect(html).toContain('class="p-content feed-quote-content');
|
|
166
|
+
expect(html).not.toContain("e-content feed-quote-content");
|
|
167
|
+
expect(html).toContain('class="e-content feed-quote-commentary prose');
|
|
168
|
+
|
|
169
|
+
// Source URL and attribution belong to the cited work, so they have to
|
|
170
|
+
// sit inside the h-cite — outside it they would land on the h-entry.
|
|
171
|
+
const cite = html.slice(
|
|
172
|
+
html.indexOf('<figure class="feed-quote-cite h-cite">'),
|
|
173
|
+
html.indexOf("</figure>"),
|
|
174
|
+
);
|
|
175
|
+
expect(cite).toContain(
|
|
176
|
+
'<a href="https://example.com/article" class="u-url p-name feed-quote-source"',
|
|
177
|
+
);
|
|
178
|
+
expect(cite).toContain('<figcaption class="feed-quote-attribution">');
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("keeps a quote attribution without a source link addressable as p-name", () => {
|
|
183
|
+
const post = createPostView({ format: "quote", url: undefined });
|
|
184
|
+
|
|
185
|
+
const feedHtml = renderWithI18n(QuoteCard({ post, mode: "feed" }));
|
|
186
|
+
|
|
187
|
+
expect(feedHtml).toContain('<span class="p-name">Card title</span>');
|
|
188
|
+
expect(feedHtml.match(/u-url/g)).toHaveLength(1);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("leaves note card microformats untouched", () => {
|
|
192
|
+
const post = createPostView({ format: "note" });
|
|
193
|
+
|
|
194
|
+
const feedHtml = renderWithI18n(NoteCard({ post, mode: "feed" }));
|
|
195
|
+
const detailHtml = renderWithI18n(NoteCard({ post, mode: "detail" }));
|
|
196
|
+
|
|
197
|
+
expect(feedHtml).toContain('<h2 class="p-name feed-note-title">');
|
|
198
|
+
expect(feedHtml).toContain('<a href="/post-1" class="hover:underline"');
|
|
199
|
+
expect(feedHtml).toContain('class="e-content prose');
|
|
200
|
+
expect(feedHtml).not.toContain("u-bookmark-of");
|
|
201
|
+
expect(feedHtml).not.toContain("h-cite");
|
|
202
|
+
|
|
203
|
+
expect(detailHtml).toContain('<h1 class="p-name post-detail-title">');
|
|
204
|
+
expect(detailHtml).toContain('class="u-url post-header-meta-link"');
|
|
205
|
+
expect(detailHtml).toContain('class="e-content prose post-detail-body"');
|
|
206
|
+
expect(detailHtml).not.toContain("u-bookmark-of");
|
|
207
|
+
expect(detailHtml).not.toContain("h-cite");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("gives every card exactly one u-url, pointing at its permalink", () => {
|
|
211
|
+
// A titled note's heading used to link the permalink as a second `u-url`.
|
|
212
|
+
// Same value as the footer's, so no parser read the wrong post — but the
|
|
213
|
+
// entry's URL is one fact, and the footer is where all three cards state it.
|
|
214
|
+
const cards = [
|
|
215
|
+
NoteCard({ post: createPostView({ format: "note" }), mode: "feed" }),
|
|
216
|
+
NoteCard({ post: createPostView({ format: "note" }), mode: "detail" }),
|
|
217
|
+
NoteCard({
|
|
218
|
+
post: createPostView({ format: "note", title: undefined }),
|
|
219
|
+
mode: "feed",
|
|
220
|
+
}),
|
|
221
|
+
LinkCard({ post: createPostView({ format: "link" }), mode: "feed" }),
|
|
222
|
+
LinkCard({ post: createPostView({ format: "link" }), mode: "detail" }),
|
|
223
|
+
QuoteCard({ post: createPostView({ format: "quote" }), mode: "feed" }),
|
|
224
|
+
QuoteCard({ post: createPostView({ format: "quote" }), mode: "detail" }),
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
for (const card of cards) {
|
|
228
|
+
// A quote's source link is a `u-url` too, but it belongs to the nested
|
|
229
|
+
// h-cite. Drop the citation before counting what the entry claims.
|
|
230
|
+
const entry = renderWithI18n(card).replace(
|
|
231
|
+
/<figure class="feed-quote-cite h-cite">[\s\S]*?<\/figure>/,
|
|
232
|
+
"",
|
|
233
|
+
);
|
|
234
|
+
expect(entry.match(/u-url/g)).toHaveLength(1);
|
|
235
|
+
expect(entry).toMatch(/<a href="\/post-1" class="u-url /);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("states empty content outright when a card has no name and nothing to say", () => {
|
|
240
|
+
// The author's card carries every status badge in the DOM, hidden with
|
|
241
|
+
// CSS, which a microformats parser does not run. With no `p-name` and no
|
|
242
|
+
// `e-content` to go on, it names the entry after whatever text it finds —
|
|
243
|
+
// the badge labels. An explicit empty `e-content` says what is true and
|
|
244
|
+
// stops that.
|
|
245
|
+
const empty = { title: undefined, bodyHtml: undefined, media: [] };
|
|
246
|
+
|
|
247
|
+
for (const mode of ["feed", "detail", "compact"] as const) {
|
|
248
|
+
const cards = {
|
|
249
|
+
note: NoteCard({
|
|
250
|
+
post: createPostView({ format: "note", ...empty }),
|
|
251
|
+
mode,
|
|
252
|
+
}),
|
|
253
|
+
link: LinkCard({
|
|
254
|
+
post: createPostView({ format: "link", ...empty }),
|
|
255
|
+
mode,
|
|
256
|
+
}),
|
|
257
|
+
quote: QuoteCard({
|
|
258
|
+
post: createPostView({
|
|
259
|
+
format: "quote",
|
|
260
|
+
...empty,
|
|
261
|
+
quoteText: undefined,
|
|
262
|
+
url: undefined,
|
|
263
|
+
}),
|
|
264
|
+
mode,
|
|
265
|
+
}),
|
|
266
|
+
};
|
|
267
|
+
for (const [format, card] of Object.entries(cards)) {
|
|
268
|
+
const html = renderWithI18n(card);
|
|
269
|
+
expect(`${format}/${mode}: ${html}`).toContain(
|
|
270
|
+
'<div class="e-content"></div>',
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("keeps stray h-* utilities out of post markup", () => {
|
|
277
|
+
// A microformats2 parser reads every `h-`-prefixed class as a microformat
|
|
278
|
+
// root, so a Tailwind height utility inside a card becomes a phantom child
|
|
279
|
+
// microformat on the entry. Only real roots may appear.
|
|
280
|
+
const withMedia = createPostView({
|
|
281
|
+
format: "note",
|
|
282
|
+
media: [createMediaView(), createMediaView({ id: "media-2" })],
|
|
283
|
+
});
|
|
284
|
+
const cards = {
|
|
285
|
+
"note with one image": NoteCard({
|
|
286
|
+
post: { ...withMedia, media: [createMediaView()] },
|
|
287
|
+
mode: "feed",
|
|
288
|
+
}),
|
|
289
|
+
"note with a gallery": NoteCard({ post: withMedia, mode: "feed" }),
|
|
290
|
+
"link post": LinkCard({
|
|
291
|
+
post: createPostView({ format: "link" }),
|
|
292
|
+
mode: "detail",
|
|
293
|
+
}),
|
|
294
|
+
"quote post": QuoteCard({
|
|
295
|
+
post: createPostView({ format: "quote" }),
|
|
296
|
+
mode: "feed",
|
|
297
|
+
}),
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
for (const [label, card] of Object.entries(cards)) {
|
|
301
|
+
const html = renderWithI18n(card);
|
|
302
|
+
const roots = [...html.matchAll(/class="([^"]*)"/g)]
|
|
303
|
+
.flatMap((m) => m[1].split(/\s+/))
|
|
304
|
+
.filter((c) => /^h-/.test(c));
|
|
305
|
+
const unexpected = roots.filter((c) => c !== "h-entry" && c !== "h-cite");
|
|
306
|
+
expect(`${label}: ${unexpected.join(", ")}`).toBe(`${label}: `);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("omits the empty content marker when the card carries a name or content", () => {
|
|
311
|
+
const cards = {
|
|
312
|
+
"titled note": NoteCard({
|
|
313
|
+
post: createPostView({ format: "note", bodyHtml: undefined }),
|
|
314
|
+
mode: "feed",
|
|
315
|
+
}),
|
|
316
|
+
"untitled note with a body": NoteCard({
|
|
317
|
+
post: createPostView({ format: "note", title: undefined }),
|
|
318
|
+
mode: "feed",
|
|
319
|
+
}),
|
|
320
|
+
"titled link": LinkCard({
|
|
321
|
+
post: createPostView({ format: "link", bodyHtml: undefined }),
|
|
322
|
+
mode: "feed",
|
|
323
|
+
}),
|
|
324
|
+
// A quotation is a nested h-cite, which is enough on its own.
|
|
325
|
+
"quote without commentary": QuoteCard({
|
|
326
|
+
post: createPostView({ format: "quote", bodyHtml: undefined }),
|
|
327
|
+
mode: "feed",
|
|
328
|
+
}),
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
for (const [label, card] of Object.entries(cards)) {
|
|
332
|
+
const html = renderWithI18n(card);
|
|
333
|
+
expect(`${label}: ${html}`).not.toContain(
|
|
334
|
+
'<div class="e-content"></div>',
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("styles post titles by their own classes, never by their microformats class", () => {
|
|
340
|
+
const css = readComponentCss();
|
|
341
|
+
|
|
342
|
+
// `.p-name` is parsed API; styling it would silently retypeset any element
|
|
343
|
+
// that later needs the property — the quote attribution, for one.
|
|
344
|
+
expect(css).not.toMatch(/^\s*\.p-name\s*\{/m);
|
|
345
|
+
expect(css).toMatch(
|
|
346
|
+
/\.post-detail-title,\s*\.feed-compact-title\s*\{[^}]*font-family:\s*var\(--font-heading\);/,
|
|
347
|
+
);
|
|
348
|
+
// Compact note titles have no other class carrying the heading face.
|
|
349
|
+
const compactHtml = renderWithI18n(
|
|
350
|
+
NoteCard({ post: createPostView({ format: "note" }), mode: "compact" }),
|
|
351
|
+
);
|
|
352
|
+
expect(compactHtml).toContain("feed-compact-title");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("keeps embedded quotes on the host panel's type scale", () => {
|
|
356
|
+
const css = readComponentCss();
|
|
357
|
+
|
|
358
|
+
// The quote body dropped `.e-content`, which is how this rule used to
|
|
359
|
+
// reach it; without the explicit class it would out-size the panel.
|
|
360
|
+
expect(css).toMatch(
|
|
361
|
+
/\.compose-reply-context-body\s*:is\(\s*\.prose,\s*\.e-content,\s*\.feed-quote,\s*\.feed-quote-content\s*\)\s*\{[^}]*font-size:\s*inherit;/,
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
|
|
94
365
|
it("renders quote attachments in feed and detail modes", () => {
|
|
95
366
|
const post = createPostView({ format: "quote" });
|
|
96
367
|
|
|
@@ -108,10 +379,7 @@ describe("timeline cards", () => {
|
|
|
108
379
|
new URL("../../../styles/tokens.css", import.meta.url),
|
|
109
380
|
"utf8",
|
|
110
381
|
);
|
|
111
|
-
const css =
|
|
112
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
113
|
-
"utf8",
|
|
114
|
-
);
|
|
382
|
+
const css = readComponentCss();
|
|
115
383
|
const exportCss = readFileSync(
|
|
116
384
|
new URL(
|
|
117
385
|
"../../../services/export-theme/styles/main.css",
|
|
@@ -171,10 +439,7 @@ describe("timeline cards", () => {
|
|
|
171
439
|
new URL("../../../preset.css", import.meta.url),
|
|
172
440
|
"utf8",
|
|
173
441
|
);
|
|
174
|
-
const uiCss =
|
|
175
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
176
|
-
"utf8",
|
|
177
|
-
);
|
|
442
|
+
const uiCss = readComponentCss();
|
|
178
443
|
const exportCss = readFileSync(
|
|
179
444
|
new URL(
|
|
180
445
|
"../../../services/export-theme/styles/main.css",
|
|
@@ -291,10 +556,7 @@ describe("timeline cards", () => {
|
|
|
291
556
|
new URL("../../../preset.css", import.meta.url),
|
|
292
557
|
"utf8",
|
|
293
558
|
);
|
|
294
|
-
const uiCss =
|
|
295
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
296
|
-
"utf8",
|
|
297
|
-
);
|
|
559
|
+
const uiCss = readComponentCss();
|
|
298
560
|
const exportCss = readFileSync(
|
|
299
561
|
new URL(
|
|
300
562
|
"../../../services/export-theme/styles/main.css",
|
|
@@ -346,10 +608,7 @@ describe("timeline cards", () => {
|
|
|
346
608
|
});
|
|
347
609
|
|
|
348
610
|
it("keeps quote footers on the shared card spacing baseline", () => {
|
|
349
|
-
const css =
|
|
350
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
351
|
-
"utf8",
|
|
352
|
-
);
|
|
611
|
+
const css = readComponentCss();
|
|
353
612
|
|
|
354
613
|
expect(css).toContain(".feed-quote-post [data-post-meta] {");
|
|
355
614
|
expect(css).not.toContain(
|
|
@@ -358,10 +617,7 @@ describe("timeline cards", () => {
|
|
|
358
617
|
});
|
|
359
618
|
|
|
360
619
|
it("uses inset-note styling for compose editor blockquotes", () => {
|
|
361
|
-
const css =
|
|
362
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
363
|
-
"utf8",
|
|
364
|
-
);
|
|
620
|
+
const css = readComponentCss();
|
|
365
621
|
|
|
366
622
|
expect(css).toMatch(
|
|
367
623
|
/\.compose-tiptap-body\s+\.tiptap\s+blockquote\s*\{[\s\S]*background:\s*linear-gradient\(/,
|
|
@@ -378,10 +634,7 @@ describe("timeline cards", () => {
|
|
|
378
634
|
});
|
|
379
635
|
|
|
380
636
|
it("keeps quote compose aligned with quote-card typography", () => {
|
|
381
|
-
const css =
|
|
382
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
383
|
-
"utf8",
|
|
384
|
-
);
|
|
637
|
+
const css = readComponentCss();
|
|
385
638
|
|
|
386
639
|
expect(css).toMatch(
|
|
387
640
|
/\.compose-quote-wrap\s*\{[\s\S]*background-color:\s*color-mix\(/,
|
|
@@ -393,10 +646,7 @@ describe("timeline cards", () => {
|
|
|
393
646
|
});
|
|
394
647
|
|
|
395
648
|
it("keeps reply context previews on thread-context type tokens", () => {
|
|
396
|
-
const uiCss =
|
|
397
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
398
|
-
"utf8",
|
|
399
|
-
);
|
|
649
|
+
const uiCss = readComponentCss();
|
|
400
650
|
const tokenCss = readFileSync(
|
|
401
651
|
new URL("../../../styles/tokens.css", import.meta.url),
|
|
402
652
|
"utf8",
|
|
@@ -416,10 +666,7 @@ describe("timeline cards", () => {
|
|
|
416
666
|
});
|
|
417
667
|
|
|
418
668
|
it("resets cloned reply previews away from feed/detail width constraints", () => {
|
|
419
|
-
const uiCss =
|
|
420
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
421
|
-
"utf8",
|
|
422
|
-
);
|
|
669
|
+
const uiCss = readComponentCss();
|
|
423
670
|
const unlayeredCssStart = uiCss.indexOf(" * Home description");
|
|
424
671
|
const resetRule = uiCss.match(
|
|
425
672
|
/\.compose-reply-context-body\s*:is\([^{}]*\[data-post-body\]\.prose[^{}]*\)\s*\{[^{}]*width:\s*100%;[^{}]*max-width:\s*none;[^{}]*\}/,
|
|
@@ -487,10 +734,7 @@ describe("timeline cards", () => {
|
|
|
487
734
|
});
|
|
488
735
|
|
|
489
736
|
it("keeps titled detail headers spaced as one reading group", () => {
|
|
490
|
-
const css =
|
|
491
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
492
|
-
"utf8",
|
|
493
|
-
);
|
|
737
|
+
const css = readComponentCss();
|
|
494
738
|
|
|
495
739
|
expect(css).toMatch(
|
|
496
740
|
/\.post-header-block\s*\{[\s\S]*margin-bottom:\s*1\.7rem;/,
|
|
@@ -501,10 +745,7 @@ describe("timeline cards", () => {
|
|
|
501
745
|
});
|
|
502
746
|
|
|
503
747
|
it("keeps detail dates quiet while preserving standalone footer spacing", () => {
|
|
504
|
-
const css =
|
|
505
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
506
|
-
"utf8",
|
|
507
|
-
);
|
|
748
|
+
const css = readComponentCss();
|
|
508
749
|
|
|
509
750
|
expect(css).toMatch(
|
|
510
751
|
/\.post-header-meta-link\s*\{[^}]*font-size:\s*var\(--type-ui-hint\);/,
|
|
@@ -518,13 +759,10 @@ describe("timeline cards", () => {
|
|
|
518
759
|
});
|
|
519
760
|
|
|
520
761
|
it("adds breathing room when a quote source leads directly into its footer", () => {
|
|
521
|
-
const css =
|
|
522
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
523
|
-
"utf8",
|
|
524
|
-
);
|
|
762
|
+
const css = readComponentCss();
|
|
525
763
|
|
|
526
764
|
expect(css).toMatch(
|
|
527
|
-
/\.feed-quote-
|
|
765
|
+
/\.feed-quote-cite:has\(\+ \.post-menu-footer\) \.feed-quote-attribution\s*\{[^}]*margin-bottom:\s*0\.45rem;/,
|
|
528
766
|
);
|
|
529
767
|
});
|
|
530
768
|
|
|
@@ -542,10 +780,7 @@ describe("timeline cards", () => {
|
|
|
542
780
|
});
|
|
543
781
|
|
|
544
782
|
it("progressively enhances canonical endnotes on wide detail and timeline posts", () => {
|
|
545
|
-
const css =
|
|
546
|
-
new URL("../../../styles/ui.css", import.meta.url),
|
|
547
|
-
"utf8",
|
|
548
|
-
);
|
|
783
|
+
const css = readComponentCss();
|
|
549
784
|
const tokens = readFileSync(
|
|
550
785
|
new URL("../../../styles/tokens.css", import.meta.url),
|
|
551
786
|
"utf8",
|
|
@@ -764,4 +999,34 @@ describe("timeline cards", () => {
|
|
|
764
999
|
expect(html).not.toContain("data-reply-trigger");
|
|
765
1000
|
expect(html).toContain("data-post-menu-trigger");
|
|
766
1001
|
});
|
|
1002
|
+
|
|
1003
|
+
it("ships every status badge to the author so the menu can reveal one", () => {
|
|
1004
|
+
const html = renderWithI18n(
|
|
1005
|
+
NoteCard({ post: createPostView({ format: "note" }), mode: "feed" }),
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
expect(html).toContain("post-status-pinned");
|
|
1009
|
+
expect(html).toContain("post-status-private");
|
|
1010
|
+
expect(html).toContain("post-status-draft");
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it("gives a reader only the badges that apply", () => {
|
|
1014
|
+
const plain = renderWithI18n(
|
|
1015
|
+
NoteCard({ post: createPostView({ format: "note" }), mode: "feed" }),
|
|
1016
|
+
false,
|
|
1017
|
+
);
|
|
1018
|
+
// Nothing to announce, so not even the wrapper.
|
|
1019
|
+
expect(plain).not.toContain("post-status-badges");
|
|
1020
|
+
|
|
1021
|
+
const pinned = renderWithI18n(
|
|
1022
|
+
NoteCard({
|
|
1023
|
+
post: createPostView({ format: "note", pinned: true }),
|
|
1024
|
+
mode: "feed",
|
|
1025
|
+
}),
|
|
1026
|
+
false,
|
|
1027
|
+
);
|
|
1028
|
+
expect(pinned).toContain("post-status-pinned");
|
|
1029
|
+
expect(pinned).not.toContain("post-status-private");
|
|
1030
|
+
expect(pinned).not.toContain("post-status-draft");
|
|
1031
|
+
});
|
|
767
1032
|
});
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { PostView } from "../../types.js";
|
|
2
|
+
import { getThreadHiddenCount } from "../../lib/thread-fold.js";
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* What the preview needs to know about its own fold.
|
|
6
|
+
*
|
|
7
|
+
* The count itself lives in `lib/thread-fold.ts`, where the feed reads it too —
|
|
8
|
+
* a card and a feed entry that disagreed about how many posts are hidden would
|
|
9
|
+
* be describing different threads.
|
|
10
|
+
*
|
|
11
|
+
* @param selection - The replies this preview shows, and the thread's total
|
|
12
|
+
* @returns The hidden-post count the gap link announces
|
|
13
|
+
* @example
|
|
14
|
+
* getThreadPreviewState({ leadingReplies, trailingReplies, latestReply,
|
|
15
|
+
* totalReplyCount: 8 }); // { hiddenCount: 3 }
|
|
16
|
+
*/
|
|
3
17
|
export function getThreadPreviewState({
|
|
4
18
|
leadingReplies,
|
|
5
19
|
trailingReplies,
|
|
@@ -11,13 +25,13 @@ export function getThreadPreviewState({
|
|
|
11
25
|
latestReply: PostView;
|
|
12
26
|
totalReplyCount: number;
|
|
13
27
|
}) {
|
|
14
|
-
const visibleReplyIds = new Set(
|
|
15
|
-
[...leadingReplies, ...trailingReplies, latestReply].map((post) => post.id),
|
|
16
|
-
);
|
|
17
|
-
const hiddenCount = Math.max(0, totalReplyCount - visibleReplyIds.size);
|
|
18
|
-
|
|
19
28
|
return {
|
|
20
|
-
hiddenCount
|
|
29
|
+
hiddenCount: getThreadHiddenCount({
|
|
30
|
+
leadingReplies,
|
|
31
|
+
trailingReplies,
|
|
32
|
+
latestReply,
|
|
33
|
+
totalReplyCount,
|
|
34
|
+
}),
|
|
21
35
|
};
|
|
22
36
|
}
|
|
23
37
|
|
package/src/ui/font-themes.ts
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
import type { MessageDescriptor } from "@lingui/core";
|
|
13
13
|
import {
|
|
14
14
|
getCjkFontFromLanguageTag,
|
|
15
|
-
|
|
16
|
-
type CjkSerifFont,
|
|
15
|
+
type CjkFontProfile,
|
|
17
16
|
} from "../i18n/detect.js";
|
|
18
17
|
|
|
19
18
|
/**
|
|
@@ -34,8 +33,6 @@ export interface FontTheme {
|
|
|
34
33
|
description: MessageDescriptor;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
const DEFAULT_CJK_SERIF_FALLBACK = '"Jant Language Fallback"';
|
|
38
|
-
const DEFAULT_CJK_SANS_FALLBACK = '"Jant Language Fallback"';
|
|
39
36
|
const HANS_CJK_SERIF_FALLBACK =
|
|
40
37
|
'"Songti SC", STSong, SimSun, "Noto Serif SC", "Noto Serif CJK SC", "Songti TC", PMingLiU, MingLiU, "Noto Serif TC", "Noto Serif CJK TC"';
|
|
41
38
|
const HANS_CJK_SANS_FALLBACK =
|
|
@@ -52,6 +49,20 @@ const KO_CJK_SERIF_FALLBACK =
|
|
|
52
49
|
'Batang, "Noto Serif KR", "Noto Serif CJK KR", NanumMyeongjo';
|
|
53
50
|
const KO_CJK_SANS_FALLBACK =
|
|
54
51
|
'"Apple SD Gothic Neo", "Noto Sans KR", "Noto Sans CJK KR", "Malgun Gothic"';
|
|
52
|
+
/*
|
|
53
|
+
* Default stacks for a page whose language names no CJK profile.
|
|
54
|
+
*
|
|
55
|
+
* A font stack cannot express "no opinion": every Latin family in a theme stack
|
|
56
|
+
* lacks Han coverage, and `ui-serif` / `serif` resolve to Times or New York,
|
|
57
|
+
* which lack it too — so a placeholder name leaves Chinese, Japanese and Korean
|
|
58
|
+
* text to the OS last-resort font (PingFang SC on Apple platforms), rendering a
|
|
59
|
+
* serif page in sans. The default therefore has to be real. Simplified comes
|
|
60
|
+
* first because it is the most common CJK content here; a page that knows its
|
|
61
|
+
* language gets the profile stacks above instead, which put the right glyph
|
|
62
|
+
* shapes first.
|
|
63
|
+
*/
|
|
64
|
+
const DEFAULT_CJK_SERIF_FALLBACK = `${HANS_CJK_SERIF_FALLBACK}, ${JP_CJK_SERIF_FALLBACK}, ${KO_CJK_SERIF_FALLBACK}`;
|
|
65
|
+
const DEFAULT_CJK_SANS_FALLBACK = `${HANS_CJK_SANS_FALLBACK}, ${JP_CJK_SANS_FALLBACK}, ${KO_CJK_SANS_FALLBACK}`;
|
|
55
66
|
const CJK_SERIF_FALLBACK_VAR = "var(--font-cjk-serif-fallback)";
|
|
56
67
|
const CJK_SANS_FALLBACK_VAR = "var(--font-cjk-sans-fallback)";
|
|
57
68
|
|
|
@@ -117,46 +128,43 @@ export function getFontThemeCssVariables(
|
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
/**
|
|
120
|
-
* Resolve the
|
|
131
|
+
* Resolve the CJK font profile for a content language.
|
|
121
132
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
133
|
+
* Purely derived from the language — Simplified, Traditional, Japanese and
|
|
134
|
+
* Korean want different Han glyph shapes, and the language tag is the only
|
|
135
|
+
* thing that knows which. The page passes the language it is actually
|
|
136
|
+
* rendering: a post's own language on a post page, the view language on a
|
|
137
|
+
* language-filtered list, the site language otherwise.
|
|
124
138
|
*
|
|
125
|
-
* @param
|
|
126
|
-
* @
|
|
127
|
-
* @returns Resolved CJK font profile, or `undefined` for the default stack
|
|
139
|
+
* @param language - BCP 47 content language tag
|
|
140
|
+
* @returns Resolved CJK font profile, or `undefined` to keep the token default
|
|
128
141
|
* @example
|
|
129
|
-
* resolveCjkFontProfile("zh-TW"
|
|
142
|
+
* resolveCjkFontProfile("zh-TW") // "zh-Hant"
|
|
143
|
+
* resolveCjkFontProfile("en") // undefined
|
|
130
144
|
*/
|
|
131
145
|
export function resolveCjkFontProfile(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const contentProfile = siteLanguage
|
|
136
|
-
? getCjkFontFromLanguageTag(siteLanguage)
|
|
137
|
-
: undefined;
|
|
138
|
-
if (contentProfile) return contentProfile;
|
|
139
|
-
|
|
140
|
-
return isCjkSerifFont(cjkFallback) && cjkFallback !== "off"
|
|
141
|
-
? cjkFallback
|
|
142
|
-
: undefined;
|
|
146
|
+
language?: string,
|
|
147
|
+
): CjkFontProfile | undefined {
|
|
148
|
+
return language ? getCjkFontFromLanguageTag(language) : undefined;
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
/**
|
|
146
152
|
* Build serif and sans fallback variables for the resolved language profile.
|
|
147
153
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
154
|
+
* Empty for a language with no profile: `tokens.css` already carries a real
|
|
155
|
+
* script-neutral stack in both variables, so there is nothing to override.
|
|
156
|
+
*
|
|
157
|
+
* @param language - BCP 47 content language tag
|
|
158
|
+
* @returns CSS variables consumed by every font theme, empty when the token
|
|
159
|
+
* defaults already apply
|
|
151
160
|
* @example
|
|
152
|
-
* getCjkFontCssVariables("
|
|
161
|
+
* getCjkFontCssVariables("zh-Hans")
|
|
153
162
|
* // => { "--font-cjk-serif-fallback": '"Songti SC", ...', ... }
|
|
154
163
|
*/
|
|
155
164
|
export function getCjkFontCssVariables(
|
|
156
|
-
|
|
157
|
-
cjkFallback?: string,
|
|
165
|
+
language?: string,
|
|
158
166
|
): Record<string, string> {
|
|
159
|
-
switch (resolveCjkFontProfile(
|
|
167
|
+
switch (resolveCjkFontProfile(language)) {
|
|
160
168
|
case "zh-Hans":
|
|
161
169
|
return {
|
|
162
170
|
"--font-cjk-serif-fallback": HANS_CJK_SERIF_FALLBACK,
|
|
@@ -182,7 +190,9 @@ export function getCjkFontCssVariables(
|
|
|
182
190
|
}
|
|
183
191
|
}
|
|
184
192
|
|
|
193
|
+
/** The `--font-cjk-serif-fallback` value `tokens.css` ships as its default. */
|
|
185
194
|
export const DEFAULT_FONT_CJK_SERIF_FALLBACK = DEFAULT_CJK_SERIF_FALLBACK;
|
|
195
|
+
/** The `--font-cjk-sans-fallback` value `tokens.css` ships as its default. */
|
|
186
196
|
export const DEFAULT_FONT_CJK_SANS_FALLBACK = DEFAULT_CJK_SANS_FALLBACK;
|
|
187
197
|
|
|
188
198
|
export const BUILTIN_FONT_THEMES: FontTheme[] = [
|