@jant/core 0.6.14 → 0.6.16
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/bin/commands/import-site.js +88 -13
- package/bin/commands/migrate.js +55 -0
- package/bin/commands/posts/rebuild-html.js +184 -0
- package/bin/commands/site/snapshot/import.js +14 -10
- package/bin/lib/d1-query.js +23 -5
- package/bin/lib/migration-runner.js +29 -7
- package/bin/lib/node-database.js +32 -0
- package/bin/lib/site-snapshot.js +347 -46
- package/bin/lib/sql-export.js +1 -1
- package/bin/lib/thread-collection-migration.js +326 -0
- package/dist/app-C75Zc8dD.js +6 -0
- package/dist/{app-2i7-WQHg.js → app-C7_4oab9.js} +7774 -6425
- package/dist/client/.vite/manifest.json +8 -8
- package/dist/client/_assets/chunks/{url-pLre2DM_.js → url-CU9pEbk5.js} +1 -1
- package/dist/client/_assets/{client-BvID5ucz.js → client-Bq0vre8Y.js} +59 -38
- package/dist/client/_assets/client-D3VZyG4L.css +2 -0
- package/dist/client/_assets/client-auth-F2rb9WfF.js +5571 -0
- package/dist/{env-OHRKGcMj.js → env-BPYViDJJ.js} +1 -1
- package/dist/{export-DWn149b7.js → export-Dl5KCiEs.js} +1348 -128
- package/dist/{github-api-BgSiE71w.js → github-api-BNF35Lkx.js} +1 -1
- package/dist/{github-app-BbklkFmU.js → github-app-DdiD-bC4.js} +1 -1
- package/dist/{github-sync-C0zyF6XS.js → github-sync-Bqg62IvV.js} +3 -3
- package/dist/{github-sync-Ckk0sJxK.js → github-sync-HdK2EgvJ.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/node.js +7 -7
- package/dist/{url-BMYO-Zlt.js → url-CbLAtlZe.js} +517 -12
- package/package.json +11 -10
- package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
- package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
- package/src/__tests__/export-hugo-build.test.ts +63 -4
- package/src/__tests__/export-import-roundtrip.test.ts +6 -4
- package/src/__tests__/export-service.test.ts +142 -9
- package/src/__tests__/helpers/app.ts +3 -0
- package/src/__tests__/helpers/export-fixtures.ts +2 -0
- package/src/__tests__/import-site-command.test.ts +65 -0
- package/src/__tests__/mise-config.test.ts +22 -0
- package/src/__tests__/vite-config.test.ts +45 -0
- package/src/app.tsx +16 -3
- package/src/client/__tests__/collection-created-notice.test.ts +36 -0
- package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
- package/src/client/__tests__/collection-page-actions.test.ts +71 -2
- package/src/client/__tests__/compose-bridge.test.ts +316 -2
- package/src/client/__tests__/compose-shortcuts.test.ts +20 -0
- package/src/client/__tests__/feed-video-player.test.ts +16 -1
- package/src/client/__tests__/footnote-rail.test.ts +472 -0
- package/src/client/__tests__/hydrate-partial.test.ts +3 -0
- package/src/client/__tests__/toast.test.ts +82 -1
- package/src/client/collection-created-notice.ts +66 -0
- package/src/client/collection-form-bridge.ts +17 -0
- package/src/client/collection-navigation.ts +44 -0
- package/src/client/collection-page-actions.ts +57 -1
- package/src/client/components/__tests__/compose-format-convert.test.ts +0 -19
- package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
- package/src/client/components/__tests__/jant-command-palette.test.ts +51 -0
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +2268 -206
- package/src/client/components/__tests__/jant-compose-editor.test.ts +147 -59
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +114 -64
- package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
- package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
- package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
- package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
- package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-manager-types.ts +9 -3
- package/src/client/components/compose-format-convert.ts +5 -1
- package/src/client/components/compose-types.ts +55 -3
- package/src/client/components/jant-collection-directory.ts +165 -30
- package/src/client/components/jant-command-palette.ts +14 -7
- package/src/client/components/jant-compose-dialog.ts +1542 -694
- package/src/client/components/jant-compose-editor.ts +237 -99
- package/src/client/components/jant-compose-fullscreen.ts +27 -9
- package/src/client/components/jant-config-editor.ts +722 -0
- package/src/client/components/jant-media-lightbox.ts +100 -18
- package/src/client/components/jant-nav-manager.ts +1283 -54
- package/src/client/components/jant-post-form.ts +3 -3
- package/src/client/components/jant-post-menu.ts +177 -95
- package/src/client/components/nav-manager-types.ts +51 -2
- package/src/client/components/post-form-types.ts +1 -1
- package/src/client/compose-bridge.ts +147 -123
- package/src/client/compose-launch.ts +13 -0
- package/src/client/compose-shortcuts.ts +5 -1
- package/src/client/feed-video-player.ts +62 -2
- package/src/client/footnote-rail.ts +478 -0
- package/src/client/hydrate-partial.ts +2 -0
- package/src/client/media-lightbox-events.ts +63 -0
- package/src/client/post-refresh.ts +135 -0
- package/src/client/site-header-fragment.ts +38 -0
- package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
- package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
- package/src/client/tiptap/__tests__/link-input-rules.test.ts +121 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
- package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
- package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
- package/src/client/tiptap/bubble-menu.ts +88 -10
- package/src/client/tiptap/continuous-lists.ts +102 -0
- package/src/client/tiptap/create-editor.ts +3 -0
- package/src/client/tiptap/extensions.ts +14 -5
- package/src/client/tiptap/link-input-rules.ts +4 -4
- package/src/client/tiptap/markdown-clipboard.ts +608 -11
- package/src/client/tiptap/slash-commands.ts +8 -1
- package/src/client/tiptap/structural-keymap.ts +211 -0
- package/src/client/tiptap/tab-indent.ts +8 -22
- package/src/client/tiptap/table-control-labels.ts +30 -0
- package/src/client/tiptap/table-controls.ts +537 -0
- package/src/client/tiptap/table-size-picker.ts +254 -0
- package/src/client/toast.ts +133 -39
- package/src/client-auth.ts +1 -0
- package/src/client.ts +1 -0
- package/src/db/__tests__/backfill-thread-activity.test.ts +234 -0
- package/src/db/__tests__/d1-query.test.ts +43 -1
- package/src/db/__tests__/migration-runner.test.ts +63 -0
- package/src/db/__tests__/migrations.test.ts +469 -0
- package/src/db/__tests__/thread-activity.test.ts +113 -0
- package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
- package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
- package/src/db/migrations/0027_old_blue_blade.sql +39 -0
- package/src/db/migrations/0028_superb_post.sql +2 -0
- package/src/db/migrations/0029_boring_magma.sql +1 -0
- package/src/db/migrations/0030_typical_vivisector.sql +50 -0
- package/src/db/migrations/0031_many_ego.sql +3 -0
- package/src/db/migrations/meta/0027_snapshot.json +2511 -0
- package/src/db/migrations/meta/0028_snapshot.json +2511 -0
- package/src/db/migrations/meta/0029_snapshot.json +2519 -0
- package/src/db/migrations/meta/0030_snapshot.json +2541 -0
- package/src/db/migrations/meta/0031_snapshot.json +2562 -0
- package/src/db/migrations/meta/_journal.json +35 -0
- package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
- package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
- package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
- package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
- package/src/db/migrations/pg/0029_rare_hairball.sql +3 -0
- package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
- package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
- package/src/db/migrations/pg/meta/0029_snapshot.json +3289 -0
- package/src/db/migrations/pg/meta/_journal.json +35 -0
- package/src/db/pg/schema.ts +46 -20
- package/src/db/rehearsal-fixtures/demo-current.json +39 -4
- package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
- package/src/db/schema.ts +48 -20
- package/src/db/thread-activity.ts +101 -0
- package/src/i18n/__tests__/detect.test.ts +8 -2
- package/src/i18n/detect.ts +14 -5
- package/src/i18n/locales/public/en.po +222 -51
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +216 -45
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +216 -45
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/i18n/locales/settings/en.po +513 -10
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +515 -12
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +515 -12
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/api-settings.test.ts +299 -0
- package/src/lib/__tests__/constants.test.ts +5 -0
- package/src/lib/__tests__/feed-policy.test.ts +25 -0
- package/src/lib/__tests__/feed.test.ts +168 -0
- package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
- package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
- package/src/lib/__tests__/markdown.test.ts +24 -6
- package/src/lib/__tests__/navigation.test.ts +59 -2
- package/src/lib/__tests__/post-body-html.test.ts +176 -0
- package/src/lib/__tests__/post-display.test.ts +64 -0
- package/src/lib/__tests__/resolve-config.test.ts +113 -1
- package/src/lib/__tests__/slug.test.ts +18 -0
- package/src/lib/__tests__/summary.test.ts +82 -0
- package/src/lib/__tests__/timeline.test.ts +109 -70
- package/src/lib/__tests__/timezones.test.ts +31 -3
- package/src/lib/__tests__/tiptap-render.test.ts +268 -9
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
- package/src/lib/__tests__/translit.test.ts +87 -0
- package/src/lib/__tests__/url.test.ts +53 -0
- package/src/lib/__tests__/view.test.ts +53 -0
- package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
- package/src/lib/api-settings.ts +239 -12
- package/src/lib/collection-sort.ts +7 -5
- package/src/lib/constants.ts +2 -0
- package/src/lib/feed-path.ts +25 -0
- package/src/lib/feed-policy.ts +64 -0
- package/src/lib/feed.ts +113 -11
- package/src/lib/footnotes.ts +264 -0
- package/src/lib/github-sync-site-config.ts +2 -0
- package/src/lib/hugo-markdown.ts +15 -3
- package/src/lib/jant-branding.ts +1 -0
- package/src/lib/link-preview.ts +25 -0
- package/src/lib/markdown-manager.ts +193 -5
- package/src/lib/markdown.ts +10 -3
- package/src/lib/navigation.ts +16 -3
- package/src/lib/post-body-html.ts +131 -0
- package/src/lib/post-display.ts +47 -8
- package/src/lib/render.tsx +9 -2
- package/src/lib/resolve-config.ts +100 -16
- package/src/lib/schemas.ts +133 -1
- package/src/lib/settings-paths.ts +5 -0
- package/src/lib/site-skill.ts +78 -0
- package/src/lib/summary.ts +16 -5
- package/src/lib/timeline.ts +137 -131
- package/src/lib/timezones.ts +54 -14
- package/src/lib/tiptap-render.ts +359 -83
- package/src/lib/translit.ts +288 -0
- package/src/lib/url.ts +123 -12
- package/src/lib/view.ts +58 -20
- package/src/lib/worker-response-cache.ts +11 -18
- package/src/middleware/__tests__/public-content-access.test.ts +91 -0
- package/src/middleware/config.ts +2 -2
- package/src/middleware/public-content-access.ts +87 -0
- package/src/node/__tests__/cli-export.test.ts +14 -0
- package/src/node/__tests__/cli-migrate.test.ts +6 -0
- package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
- package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
- package/src/node/index.ts +2 -1
- package/src/preset.css +16 -2
- package/src/routes/__tests__/compose.test.ts +51 -0
- package/src/routes/__tests__/site-skill.test.ts +112 -0
- package/src/routes/api/__tests__/collections.test.ts +91 -19
- package/src/routes/api/__tests__/mcp.test.ts +65 -1
- package/src/routes/api/__tests__/nav-items.test.ts +91 -0
- package/src/routes/api/__tests__/palette.test.ts +44 -0
- package/src/routes/api/__tests__/public-access.test.ts +81 -0
- package/src/routes/api/__tests__/settings.test.ts +217 -0
- package/src/routes/api/collections.ts +22 -26
- package/src/routes/api/export.ts +2 -0
- package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
- package/src/routes/api/internal/post-body-html.ts +43 -0
- package/src/routes/api/internal/sites.ts +24 -0
- package/src/routes/api/nav-items.ts +24 -1
- package/src/routes/api/posts.ts +14 -10
- package/src/routes/api/public/__tests__/archive.test.ts +35 -0
- package/src/routes/api/public/__tests__/posts.test.ts +246 -0
- package/src/routes/api/public/archive.ts +4 -0
- package/src/routes/api/public/posts.ts +48 -24
- package/src/routes/api/search.ts +3 -0
- package/src/routes/api/settings.ts +33 -0
- package/src/routes/auth/__tests__/setup.test.ts +7 -4
- package/src/routes/compose.tsx +8 -3
- package/src/routes/dash/settings.tsx +45 -2
- package/src/routes/feed/__tests__/feed.test.ts +210 -6
- package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
- package/src/routes/feed/feed.ts +23 -14
- package/src/routes/pages/__tests__/archive-params.test.ts +384 -3
- package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
- package/src/routes/pages/__tests__/collections.test.ts +9 -9
- package/src/routes/pages/__tests__/featured.test.ts +6 -2
- package/src/routes/pages/__tests__/preview.test.ts +176 -0
- package/src/routes/pages/__tests__/search.test.ts +69 -0
- package/src/routes/pages/archive.tsx +90 -22
- package/src/routes/pages/collection.tsx +32 -19
- package/src/routes/pages/collections.tsx +6 -1
- package/src/routes/pages/page.tsx +86 -11
- package/src/routes/site-skill.ts +18 -0
- package/src/services/__tests__/auth.test.ts +7 -5
- package/src/services/__tests__/collection.test.ts +190 -44
- package/src/services/__tests__/navigation.test.ts +229 -21
- package/src/services/__tests__/post-timeline.test.ts +249 -39
- package/src/services/__tests__/post.test.ts +844 -5
- package/src/services/__tests__/search.test.ts +48 -1
- package/src/services/__tests__/settings.test.ts +8 -0
- package/src/services/__tests__/site-admin.test.ts +121 -1
- package/src/services/auth.ts +2 -2
- package/src/services/bootstrap.ts +3 -2
- package/src/services/collection.ts +224 -167
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +46 -25
- package/src/services/export-theme/layouts/_default/list.html +18 -4
- package/src/services/export-theme/layouts/_default/rss.xml +8 -12
- package/src/services/export-theme/layouts/featured/list.html +5 -4
- package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
- package/src/services/export-theme/layouts/partials/footer.html +1 -1
- package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
- package/src/services/export-theme/styles/main.css +30 -2
- package/src/services/export.ts +94 -85
- package/src/services/mcp.ts +37 -27
- package/src/services/media.ts +1 -1
- package/src/services/navigation.ts +185 -46
- package/src/services/path.ts +4 -1
- package/src/services/post.ts +1142 -342
- package/src/services/search.ts +17 -2
- package/src/services/settings.ts +2 -10
- package/src/services/site-admin.ts +63 -43
- package/src/style-cjk-jp.css +1 -1
- package/src/style-cjk-kr.css +1 -1
- package/src/style-cjk-tc.css +1 -1
- package/src/style-cjk.css +1 -1
- package/src/styles/components.css +540 -14
- package/src/styles/site-media.css +111 -19
- package/src/styles/tokens.css +76 -10
- package/src/styles/ui.css +2227 -1347
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +243 -12
- package/src/types/constants.ts +22 -1
- package/src/types/entities.ts +14 -8
- package/src/types/operations.ts +18 -10
- package/src/types/props.ts +20 -2
- package/src/types/raw-assets.d.ts +5 -0
- package/src/types/views.ts +22 -4
- package/src/ui/__tests__/font-themes.test.ts +42 -30
- package/src/ui/compose/ComposeDialog.tsx +109 -19
- package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
- package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
- package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
- package/src/ui/dash/settings/GeneralContent.tsx +15 -5
- package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
- package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
- package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
- package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
- package/src/ui/feed/LinkCard.tsx +2 -12
- package/src/ui/feed/LinkPreview.tsx +2 -7
- package/src/ui/feed/NoteCard.tsx +10 -20
- package/src/ui/feed/PostStatusBadges.tsx +42 -3
- package/src/ui/feed/QuoteCard.tsx +2 -12
- package/src/ui/feed/ThreadPreview.tsx +26 -34
- package/src/ui/feed/TimelineFeed.tsx +2 -2
- package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
- package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
- package/src/ui/feed/thread-preview-state.ts +5 -7
- package/src/ui/font-themes.ts +74 -26
- package/src/ui/layouts/BaseLayout.tsx +22 -8
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
- package/src/ui/pages/ArchivePage.tsx +131 -24
- package/src/ui/pages/CollectionPage.tsx +101 -22
- package/src/ui/pages/CollectionsPage.tsx +3 -1
- package/src/ui/pages/PostPage.tsx +39 -12
- package/src/ui/pages/SearchPage.tsx +6 -14
- package/src/ui/pages/__tests__/ArchivePage.test.tsx +70 -0
- package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
- package/src/ui/shared/CollectionDirectory.tsx +13 -9
- package/src/ui/shared/CollectionsManager.tsx +17 -7
- package/src/ui/shared/DraftPreviewBar.tsx +49 -0
- package/src/ui/shared/HomePageBranding.tsx +2 -2
- package/src/ui/shared/MediaGallery.tsx +1 -0
- package/src/ui/shared/PostFooter.tsx +38 -13
- package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
- package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
- package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
- package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
- package/src/ui/shared/collection-management-labels.ts +33 -2
- package/src/ui/shared/custom-icons.ts +5 -0
- package/src/ui/shared/navigation-labels.ts +2 -4
- package/src/ui/shared/post-article-attributes.ts +52 -0
- package/dist/app-DZDlmh7C.js +0 -6
- package/dist/client/_assets/client-Cevfpm8H.css +0 -2
- package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
- package/src/client/tiptap/wrapping-input-rules.ts +0 -102
package/src/ui/font-themes.ts
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { MessageDescriptor } from "@lingui/core";
|
|
13
|
+
import {
|
|
14
|
+
getCjkFontFromLanguageTag,
|
|
15
|
+
isCjkSerifFont,
|
|
16
|
+
type CjkSerifFont,
|
|
17
|
+
} from "../i18n/detect.js";
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* A font theme definition with heading + body pairing.
|
|
@@ -29,29 +34,35 @@ export interface FontTheme {
|
|
|
29
34
|
description: MessageDescriptor;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
const DEFAULT_CJK_SERIF_FALLBACK =
|
|
33
|
-
|
|
37
|
+
const DEFAULT_CJK_SERIF_FALLBACK = '"Jant Language Fallback"';
|
|
38
|
+
const DEFAULT_CJK_SANS_FALLBACK = '"Jant Language Fallback"';
|
|
34
39
|
const HANS_CJK_SERIF_FALLBACK =
|
|
35
40
|
'"Songti SC", STSong, SimSun, "Noto Serif SC", "Noto Serif CJK SC", "Songti TC", PMingLiU, MingLiU, "Noto Serif TC", "Noto Serif CJK TC"';
|
|
41
|
+
const HANS_CJK_SANS_FALLBACK =
|
|
42
|
+
'"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", "Noto Sans CJK SC", "PingFang TC", "Hiragino Sans CNS", "Microsoft JhengHei", "Noto Sans TC", "Noto Sans CJK TC"';
|
|
36
43
|
const HANT_CJK_SERIF_FALLBACK =
|
|
37
44
|
'"Songti TC", PMingLiU, MingLiU, "Noto Serif TC", "Noto Serif CJK TC", "Songti SC", STSong, SimSun, "Noto Serif SC", "Noto Serif CJK SC"';
|
|
45
|
+
const HANT_CJK_SANS_FALLBACK =
|
|
46
|
+
'"PingFang TC", "Hiragino Sans CNS", "Microsoft JhengHei", "Noto Sans TC", "Noto Sans CJK TC", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", "Noto Sans CJK SC"';
|
|
38
47
|
const JP_CJK_SERIF_FALLBACK =
|
|
39
|
-
'"Hiragino Mincho ProN", "Hiragino Mincho Pro", "Yu Mincho", YuMincho, "Noto Serif JP", "Noto Serif CJK JP", "MS PMincho", "MS Mincho"
|
|
48
|
+
'"Hiragino Mincho ProN", "Hiragino Mincho Pro", "Yu Mincho", YuMincho, "Noto Serif JP", "Noto Serif CJK JP", "MS PMincho", "MS Mincho"';
|
|
49
|
+
const JP_CJK_SANS_FALLBACK =
|
|
50
|
+
'"Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic", YuGothic, "Noto Sans JP", "Noto Sans CJK JP", Meiryo';
|
|
40
51
|
const KO_CJK_SERIF_FALLBACK =
|
|
41
|
-
'
|
|
52
|
+
'Batang, "Noto Serif KR", "Noto Serif CJK KR", NanumMyeongjo';
|
|
53
|
+
const KO_CJK_SANS_FALLBACK =
|
|
54
|
+
'"Apple SD Gothic Neo", "Noto Sans KR", "Noto Sans CJK KR", "Malgun Gothic"';
|
|
42
55
|
const CJK_SERIF_FALLBACK_VAR = "var(--font-cjk-serif-fallback)";
|
|
56
|
+
const CJK_SANS_FALLBACK_VAR = "var(--font-cjk-sans-fallback)";
|
|
43
57
|
|
|
44
58
|
/** System sans-serif stack */
|
|
45
|
-
const SANS =
|
|
46
|
-
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "PingFang TC", "PingFang SC", "Hiragino Sans CNS", "Hiragino Sans GB", "Microsoft JhengHei", "Microsoft YaHei", "Noto Sans CJK TC", "Noto Sans CJK SC", sans-serif';
|
|
59
|
+
const SANS = `ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, ${CJK_SANS_FALLBACK_VAR}, sans-serif`;
|
|
47
60
|
|
|
48
61
|
/** Humanist sans stack with self-hosted Latin and system CJK fallback */
|
|
49
|
-
const HUMANIST_SANS =
|
|
50
|
-
'"Source Sans 3 Variable", "Source Sans 3", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "PingFang TC", "PingFang SC", "Hiragino Sans CNS", "Hiragino Sans GB", "Microsoft JhengHei", "Microsoft YaHei", "Noto Sans CJK TC", "Noto Sans CJK SC", sans-serif';
|
|
62
|
+
const HUMANIST_SANS = `"Source Sans 3 Variable", "Source Sans 3", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, ${CJK_SANS_FALLBACK_VAR}, sans-serif`;
|
|
51
63
|
|
|
52
64
|
/** Narrower newsroom sans for headlines and labels */
|
|
53
|
-
const NEWSROOM_SANS =
|
|
54
|
-
'"News Cycle", "Franklin Gothic Medium", "Arial Narrow", "Helvetica Neue", Helvetica, Arial, "PingFang TC", "PingFang SC", "Hiragino Sans CNS", "Hiragino Sans GB", "Microsoft JhengHei", "Microsoft YaHei", "Noto Sans CJK TC", "Noto Sans CJK SC", sans-serif';
|
|
65
|
+
const NEWSROOM_SANS = `"News Cycle", "Franklin Gothic Medium", "Arial Narrow", "Helvetica Neue", Helvetica, Arial, ${CJK_SANS_FALLBACK_VAR}, sans-serif`;
|
|
55
66
|
|
|
56
67
|
/**
|
|
57
68
|
* Editorial serif stack
|
|
@@ -81,8 +92,7 @@ const TUFTE_SERIF = `et-book, Palatino, "Palatino Linotype", "Palatino LT STD",
|
|
|
81
92
|
*
|
|
82
93
|
* Futura (macOS); Century Gothic (Windows); clean geometric proportions
|
|
83
94
|
*/
|
|
84
|
-
const GEOMETRIC_SANS =
|
|
85
|
-
'"Avenir Next", Avenir, Futura, "Century Gothic", Montserrat, "Noto Sans", "PingFang TC", "PingFang SC", "Hiragino Sans CNS", "Hiragino Sans GB", "Microsoft JhengHei", "Microsoft YaHei", "Noto Sans CJK TC", "Noto Sans CJK SC", sans-serif';
|
|
95
|
+
const GEOMETRIC_SANS = `"Avenir Next", Avenir, Futura, "Century Gothic", Montserrat, "Noto Sans", ${CJK_SANS_FALLBACK_VAR}, sans-serif`;
|
|
86
96
|
|
|
87
97
|
/**
|
|
88
98
|
* Resolve all CSS variables a font theme contributes.
|
|
@@ -107,35 +117,73 @@ export function getFontThemeCssVariables(
|
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
/**
|
|
110
|
-
* Resolve
|
|
120
|
+
* Resolve the language font profile for the current content language.
|
|
111
121
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
122
|
+
* An adapted content language always wins. The explicit CJK setting is a
|
|
123
|
+
* fallback for sites whose primary content language does not have a profile.
|
|
114
124
|
*
|
|
125
|
+
* @param siteLanguage - BCP 47 content language tag
|
|
126
|
+
* @param cjkFallback - Explicit CJK fallback setting
|
|
127
|
+
* @returns Resolved CJK font profile, or `undefined` for the default stack
|
|
115
128
|
* @example
|
|
116
|
-
*
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
* resolveCjkFontProfile("zh-TW", "zh-Hans") // "zh-Hant"
|
|
130
|
+
*/
|
|
131
|
+
export function resolveCjkFontProfile(
|
|
132
|
+
siteLanguage?: string,
|
|
133
|
+
cjkFallback?: string,
|
|
134
|
+
): Exclude<CjkSerifFont, "off"> | undefined {
|
|
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;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Build serif and sans fallback variables for the resolved language profile.
|
|
147
|
+
*
|
|
148
|
+
* @param siteLanguage - BCP 47 content language tag
|
|
149
|
+
* @param cjkFallback - Explicit CJK fallback setting
|
|
150
|
+
* @returns CSS variables consumed by every font theme
|
|
151
|
+
* @example
|
|
152
|
+
* getCjkFontCssVariables("en", "zh-Hans")
|
|
153
|
+
* // => { "--font-cjk-serif-fallback": '"Songti SC", ...', ... }
|
|
120
154
|
*/
|
|
121
|
-
export function
|
|
122
|
-
|
|
155
|
+
export function getCjkFontCssVariables(
|
|
156
|
+
siteLanguage?: string,
|
|
157
|
+
cjkFallback?: string,
|
|
123
158
|
): Record<string, string> {
|
|
124
|
-
switch (
|
|
159
|
+
switch (resolveCjkFontProfile(siteLanguage, cjkFallback)) {
|
|
125
160
|
case "zh-Hans":
|
|
126
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
"--font-cjk-serif-fallback": HANS_CJK_SERIF_FALLBACK,
|
|
163
|
+
"--font-cjk-sans-fallback": HANS_CJK_SANS_FALLBACK,
|
|
164
|
+
};
|
|
127
165
|
case "zh-Hant":
|
|
128
|
-
return {
|
|
166
|
+
return {
|
|
167
|
+
"--font-cjk-serif-fallback": HANT_CJK_SERIF_FALLBACK,
|
|
168
|
+
"--font-cjk-sans-fallback": HANT_CJK_SANS_FALLBACK,
|
|
169
|
+
};
|
|
129
170
|
case "ja":
|
|
130
|
-
return {
|
|
171
|
+
return {
|
|
172
|
+
"--font-cjk-serif-fallback": JP_CJK_SERIF_FALLBACK,
|
|
173
|
+
"--font-cjk-sans-fallback": JP_CJK_SANS_FALLBACK,
|
|
174
|
+
};
|
|
131
175
|
case "ko":
|
|
132
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
"--font-cjk-serif-fallback": KO_CJK_SERIF_FALLBACK,
|
|
178
|
+
"--font-cjk-sans-fallback": KO_CJK_SANS_FALLBACK,
|
|
179
|
+
};
|
|
133
180
|
default:
|
|
134
181
|
return {};
|
|
135
182
|
}
|
|
136
183
|
}
|
|
137
184
|
|
|
138
185
|
export const DEFAULT_FONT_CJK_SERIF_FALLBACK = DEFAULT_CJK_SERIF_FALLBACK;
|
|
186
|
+
export const DEFAULT_FONT_CJK_SANS_FALLBACK = DEFAULT_CJK_SANS_FALLBACK;
|
|
139
187
|
|
|
140
188
|
export const BUILTIN_FONT_THEMES: FontTheme[] = [
|
|
141
189
|
{
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
IS_VITE_DEV,
|
|
34
34
|
} from "../../lib/version.js";
|
|
35
35
|
import { I18nProvider } from "../../i18n/index.js";
|
|
36
|
+
import { resolveCjkFontProfile } from "../font-themes.js";
|
|
36
37
|
import { resetIconCollector } from "../shared/icon-collector.js";
|
|
37
38
|
import { Icon } from "../shared/Icon.js";
|
|
38
39
|
import { IconSprite } from "../shared/IconSprite.js";
|
|
@@ -177,20 +178,24 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
177
178
|
const resolvedClientBundle =
|
|
178
179
|
clientBundle ?? (isAuthenticated ? "full" : "public");
|
|
179
180
|
const cjkSerifFont = appConfig?.cjkSerifFont ?? "off";
|
|
181
|
+
const cjkFontProfile = resolveCjkFontProfile(
|
|
182
|
+
appConfig?.siteLanguage ?? resolvedLang,
|
|
183
|
+
cjkSerifFont,
|
|
184
|
+
);
|
|
180
185
|
const cjkStylesheetPath =
|
|
181
|
-
|
|
186
|
+
cjkFontProfile === "zh-Hans"
|
|
182
187
|
? IS_VITE_DEV
|
|
183
188
|
? assetPath("/src/style-cjk.css")
|
|
184
189
|
: toPublicAssetPath(CLIENT_CJK_CSS_FILE, assetBasePath)
|
|
185
|
-
:
|
|
190
|
+
: cjkFontProfile === "zh-Hant"
|
|
186
191
|
? IS_VITE_DEV
|
|
187
192
|
? assetPath("/src/style-cjk-tc.css")
|
|
188
193
|
: toPublicAssetPath(CLIENT_CJK_TC_CSS_FILE, assetBasePath)
|
|
189
|
-
:
|
|
194
|
+
: cjkFontProfile === "ja"
|
|
190
195
|
? IS_VITE_DEV
|
|
191
196
|
? assetPath("/src/style-cjk-jp.css")
|
|
192
197
|
: toPublicAssetPath(CLIENT_CJK_JP_CSS_FILE, assetBasePath)
|
|
193
|
-
:
|
|
198
|
+
: cjkFontProfile === "ko"
|
|
194
199
|
? IS_VITE_DEV
|
|
195
200
|
? assetPath("/src/style-cjk-kr.css")
|
|
196
201
|
: toPublicAssetPath(CLIENT_CJK_KR_CSS_FILE, assetBasePath)
|
|
@@ -248,11 +253,14 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
248
253
|
socialImageHeightValue !== undefined &&
|
|
249
254
|
socialImageWidthValue > socialImageHeightValue &&
|
|
250
255
|
socialImageWidthValue >= 300;
|
|
251
|
-
const
|
|
252
|
-
const
|
|
256
|
+
const feedsEnabled = appConfig?.rssFeedsEnabled === true;
|
|
257
|
+
const mainFeedHref = feedsEnabled
|
|
258
|
+
? toPublicPath("/feed", sitePathPrefix)
|
|
259
|
+
: null;
|
|
260
|
+
const latestFeedHref = feedsEnabled
|
|
253
261
|
? toPublicPath("/latest/feed", sitePathPrefix)
|
|
254
262
|
: null;
|
|
255
|
-
const featuredFeedHref =
|
|
263
|
+
const featuredFeedHref = feedsEnabled
|
|
256
264
|
? toPublicPath("/featured/feed", sitePathPrefix)
|
|
257
265
|
: null;
|
|
258
266
|
const mainFeedTitle =
|
|
@@ -451,7 +459,13 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
451
459
|
{...(isAuthenticated ? { "data-authenticated": true } : {})}
|
|
452
460
|
>
|
|
453
461
|
{content}
|
|
454
|
-
<div
|
|
462
|
+
<div
|
|
463
|
+
id="toast-container"
|
|
464
|
+
class="toast-container"
|
|
465
|
+
popover="manual"
|
|
466
|
+
aria-live="polite"
|
|
467
|
+
aria-relevant="additions text"
|
|
468
|
+
>
|
|
455
469
|
{toast && (
|
|
456
470
|
<div
|
|
457
471
|
class={`toast ${toast.type === "error" ? "toast-error" : "toast-success"}`}
|
|
@@ -16,15 +16,20 @@ function createContext(
|
|
|
16
16
|
themeMode?: "auto" | "light" | "dark";
|
|
17
17
|
themeId?: string;
|
|
18
18
|
defaultThemeId?: string;
|
|
19
|
+
siteLanguage?: string;
|
|
20
|
+
cjkSerifFont?: string;
|
|
21
|
+
rssFeedsEnabled?: boolean;
|
|
19
22
|
},
|
|
20
23
|
) {
|
|
21
24
|
const values = {
|
|
22
25
|
appConfig: {
|
|
23
26
|
mainRssFeed,
|
|
27
|
+
rssFeedsEnabled: overrides?.rssFeedsEnabled ?? true,
|
|
24
28
|
sitePathPrefix: overrides?.sitePathPrefix ?? "",
|
|
25
29
|
siteUrl: overrides?.siteUrl ?? "https://example.com",
|
|
26
30
|
siteAvatarUrl: overrides?.siteAvatarUrl,
|
|
27
|
-
siteLanguage: "en",
|
|
31
|
+
siteLanguage: overrides?.siteLanguage ?? "en",
|
|
32
|
+
cjkSerifFont: overrides?.cjkSerifFont ?? "off",
|
|
28
33
|
noindex: false,
|
|
29
34
|
customCSS: "",
|
|
30
35
|
themeMode: overrides?.themeMode ?? "auto",
|
|
@@ -301,6 +306,20 @@ describe("BaseLayout", () => {
|
|
|
301
306
|
expect(html).not.toContain('href="/latest/feed"');
|
|
302
307
|
});
|
|
303
308
|
|
|
309
|
+
it("omits feed autodiscovery when feed publishing is disabled", async () => {
|
|
310
|
+
const { BaseLayout } = await loadBaseLayout();
|
|
311
|
+
const html = renderToString(
|
|
312
|
+
BaseLayout({
|
|
313
|
+
title: "Jant",
|
|
314
|
+
c: createContext("featured", { rssFeedsEnabled: false }),
|
|
315
|
+
children: "Test",
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
expect(html).not.toContain('type="application/atom+xml"');
|
|
320
|
+
expect(html).not.toContain('href="/feed"');
|
|
321
|
+
});
|
|
322
|
+
|
|
304
323
|
it("uses the public asset base path from appConfig in production", async () => {
|
|
305
324
|
const { BaseLayout } = await loadBaseLayout();
|
|
306
325
|
const html = renderToString(
|
|
@@ -320,6 +339,50 @@ describe("BaseLayout", () => {
|
|
|
320
339
|
expect(html).toContain('data-asset-base-path="/blog/_assets"');
|
|
321
340
|
});
|
|
322
341
|
|
|
342
|
+
it("loads the content language font profile when the CJK fallback is off", async () => {
|
|
343
|
+
const { BaseLayout } = await loadBaseLayout();
|
|
344
|
+
const html = renderToString(
|
|
345
|
+
BaseLayout({
|
|
346
|
+
title: "Jant",
|
|
347
|
+
c: createContext("featured", {
|
|
348
|
+
siteLanguage: "zh-Hant",
|
|
349
|
+
cjkSerifFont: "off",
|
|
350
|
+
}),
|
|
351
|
+
children: "Test",
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
expect(html).toContain("client-cjk-tc.css");
|
|
356
|
+
expect(html).not.toContain("client-cjk.css");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("uses the manual CJK fallback only for an unadapted content language", async () => {
|
|
360
|
+
const { BaseLayout } = await loadBaseLayout();
|
|
361
|
+
const fallbackHtml = renderToString(
|
|
362
|
+
BaseLayout({
|
|
363
|
+
title: "Jant",
|
|
364
|
+
c: createContext("featured", {
|
|
365
|
+
siteLanguage: "en",
|
|
366
|
+
cjkSerifFont: "zh-Hans",
|
|
367
|
+
}),
|
|
368
|
+
children: "Test",
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
const contentLanguageHtml = renderToString(
|
|
372
|
+
BaseLayout({
|
|
373
|
+
title: "Jant",
|
|
374
|
+
c: createContext("featured", {
|
|
375
|
+
siteLanguage: "zh-Hant",
|
|
376
|
+
cjkSerifFont: "zh-Hans",
|
|
377
|
+
}),
|
|
378
|
+
children: "Test",
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
expect(fallbackHtml).toContain("client-cjk.css");
|
|
383
|
+
expect(contentLanguageHtml).toContain("client-cjk-tc.css");
|
|
384
|
+
});
|
|
385
|
+
|
|
323
386
|
it("exposes the active theme id on the root html element", async () => {
|
|
324
387
|
const { BaseLayout } = await loadBaseLayout();
|
|
325
388
|
const html = renderToString(
|
|
@@ -62,6 +62,7 @@ function buildFilterUrl(
|
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
if (merged.view && merged.view !== "grid") params.set("view", merged.view);
|
|
65
|
+
if (merged.sort === "updated") params.set("sort", "updated");
|
|
65
66
|
|
|
66
67
|
const qs = params.toString();
|
|
67
68
|
return qs
|
|
@@ -127,7 +128,7 @@ function getFormatLabelPlural(format: string): string {
|
|
|
127
128
|
const FORMAT_ICONS: Record<string, string> = {
|
|
128
129
|
note: "notepad-text",
|
|
129
130
|
link: "external-link",
|
|
130
|
-
quote: "
|
|
131
|
+
quote: "quote",
|
|
131
132
|
};
|
|
132
133
|
|
|
133
134
|
/** Icon name mapping for media kinds. */
|
|
@@ -463,34 +464,118 @@ const ChipMediaSelect: FC<{
|
|
|
463
464
|
// View Toggle
|
|
464
465
|
// =============================================================================
|
|
465
466
|
|
|
467
|
+
/**
|
|
468
|
+
* One option in a toolbar toggle. `label` is used for both the hover tooltip
|
|
469
|
+
* and the accessible name — an icon-only control has to explain itself to
|
|
470
|
+
* both audiences, and one string keeps them from drifting apart.
|
|
471
|
+
*/
|
|
472
|
+
const ToggleOption: FC<{
|
|
473
|
+
href: string;
|
|
474
|
+
icon: string;
|
|
475
|
+
label: string;
|
|
476
|
+
active: boolean;
|
|
477
|
+
}> = ({ href, icon, label, active }) => (
|
|
478
|
+
<a
|
|
479
|
+
href={href}
|
|
480
|
+
class={`archive-view-btn${active ? " archive-view-btn-active" : ""}`}
|
|
481
|
+
role="radio"
|
|
482
|
+
aria-checked={active ? "true" : "false"}
|
|
483
|
+
aria-label={label}
|
|
484
|
+
title={label}
|
|
485
|
+
>
|
|
486
|
+
<Icon name={icon} class="[&>svg]:size-4" />
|
|
487
|
+
</a>
|
|
488
|
+
);
|
|
489
|
+
|
|
466
490
|
const ViewToggle: FC<{
|
|
467
491
|
filters: ArchiveFilters;
|
|
468
492
|
sitePathPrefix?: string;
|
|
469
493
|
}> = ({ filters, sitePathPrefix = "" }) => {
|
|
494
|
+
const { i18n } = useLingui();
|
|
470
495
|
const currentView: ArchiveView = filters.view ?? "grid";
|
|
471
|
-
const gridUrl = buildFilterUrl(filters, { view: undefined }, sitePathPrefix);
|
|
472
|
-
const listUrl = buildFilterUrl(filters, { view: "list" }, sitePathPrefix);
|
|
473
496
|
|
|
474
497
|
return (
|
|
475
|
-
<div
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
<
|
|
486
|
-
href={
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
498
|
+
<div
|
|
499
|
+
class="archive-view-toggle"
|
|
500
|
+
role="radiogroup"
|
|
501
|
+
aria-label={i18n._(
|
|
502
|
+
msg({
|
|
503
|
+
message: "View mode",
|
|
504
|
+
comment: "@context: Archive grid/list toggle group label",
|
|
505
|
+
}),
|
|
506
|
+
)}
|
|
507
|
+
>
|
|
508
|
+
<ToggleOption
|
|
509
|
+
href={buildFilterUrl(filters, { view: undefined }, sitePathPrefix)}
|
|
510
|
+
icon="layout-grid"
|
|
511
|
+
active={currentView === "grid"}
|
|
512
|
+
label={i18n._(
|
|
513
|
+
msg({
|
|
514
|
+
message: "Show as a grid of tiles",
|
|
515
|
+
comment: "@context: Archive view option - grid",
|
|
516
|
+
}),
|
|
517
|
+
)}
|
|
518
|
+
/>
|
|
519
|
+
<ToggleOption
|
|
520
|
+
href={buildFilterUrl(filters, { view: "list" }, sitePathPrefix)}
|
|
521
|
+
icon="list"
|
|
522
|
+
active={currentView === "list"}
|
|
523
|
+
label={i18n._(
|
|
524
|
+
msg({
|
|
525
|
+
message: "Show as a list with full posts",
|
|
526
|
+
comment: "@context: Archive view option - list",
|
|
527
|
+
}),
|
|
528
|
+
)}
|
|
529
|
+
/>
|
|
530
|
+
</div>
|
|
531
|
+
);
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Time-axis toggle. Sits next to the view toggle because it changes how the
|
|
536
|
+
* page is arranged, not which threads it contains — unlike the filter chips.
|
|
537
|
+
*/
|
|
538
|
+
const SortToggle: FC<{
|
|
539
|
+
filters: ArchiveFilters;
|
|
540
|
+
sitePathPrefix?: string;
|
|
541
|
+
}> = ({ filters, sitePathPrefix = "" }) => {
|
|
542
|
+
const { i18n } = useLingui();
|
|
543
|
+
const sortsByActivity = filters.sort === "updated";
|
|
544
|
+
|
|
545
|
+
return (
|
|
546
|
+
<div
|
|
547
|
+
class="archive-view-toggle"
|
|
548
|
+
role="radiogroup"
|
|
549
|
+
aria-label={i18n._(
|
|
550
|
+
msg({
|
|
551
|
+
message: "Sort order",
|
|
552
|
+
comment: "@context: Archive sort toggle group label",
|
|
553
|
+
}),
|
|
554
|
+
)}
|
|
555
|
+
>
|
|
556
|
+
<ToggleOption
|
|
557
|
+
href={buildFilterUrl(filters, { sort: undefined }, sitePathPrefix)}
|
|
558
|
+
icon="clock"
|
|
559
|
+
active={!sortsByActivity}
|
|
560
|
+
label={i18n._(
|
|
561
|
+
msg({
|
|
562
|
+
message: "Sort by when each thread was published",
|
|
563
|
+
comment: "@context: Archive sort option - newest published first",
|
|
564
|
+
}),
|
|
565
|
+
)}
|
|
566
|
+
/>
|
|
567
|
+
<ToggleOption
|
|
568
|
+
href={buildFilterUrl(filters, { sort: "updated" }, sitePathPrefix)}
|
|
569
|
+
icon="history"
|
|
570
|
+
active={sortsByActivity}
|
|
571
|
+
label={i18n._(
|
|
572
|
+
msg({
|
|
573
|
+
message: "Sort by when each thread was last added to",
|
|
574
|
+
comment:
|
|
575
|
+
"@context: Archive sort option - threads that recently gained a post first",
|
|
576
|
+
}),
|
|
577
|
+
)}
|
|
578
|
+
/>
|
|
494
579
|
</div>
|
|
495
580
|
);
|
|
496
581
|
};
|
|
@@ -948,7 +1033,10 @@ const FilterBar: FC<{
|
|
|
948
1033
|
)}
|
|
949
1034
|
</div>
|
|
950
1035
|
|
|
951
|
-
<
|
|
1036
|
+
<div class="archive-toolbar-toggles">
|
|
1037
|
+
<SortToggle filters={filters} sitePathPrefix={sitePathPrefix} />
|
|
1038
|
+
<ViewToggle filters={filters} sitePathPrefix={sitePathPrefix} />
|
|
1039
|
+
</div>
|
|
952
1040
|
</div>
|
|
953
1041
|
);
|
|
954
1042
|
};
|
|
@@ -1258,6 +1346,7 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
1258
1346
|
}) => {
|
|
1259
1347
|
const { i18n } = useLingui();
|
|
1260
1348
|
const currentView: ArchiveView = filters.view ?? "grid";
|
|
1349
|
+
const sortsByActivity = filters.sort === "updated";
|
|
1261
1350
|
const paginationBaseUrl = buildFilterUrl(filters, {}, sitePathPrefix);
|
|
1262
1351
|
const totalCountUnit =
|
|
1263
1352
|
totalCount === 1
|
|
@@ -1295,6 +1384,18 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
1295
1384
|
>
|
|
1296
1385
|
<span class="archive-page-summary-count">{totalCount}</span>{" "}
|
|
1297
1386
|
{totalCountUnit}
|
|
1387
|
+
{sortsByActivity && (
|
|
1388
|
+
<>
|
|
1389
|
+
{" · "}
|
|
1390
|
+
{i18n._(
|
|
1391
|
+
msg({
|
|
1392
|
+
message: "newest changes first",
|
|
1393
|
+
comment:
|
|
1394
|
+
"@context: Archive page meta note explaining the active sort order",
|
|
1395
|
+
}),
|
|
1396
|
+
)}
|
|
1397
|
+
</>
|
|
1398
|
+
)}
|
|
1298
1399
|
{feedHref && (
|
|
1299
1400
|
<>
|
|
1300
1401
|
{" "}
|
|
@@ -1347,7 +1448,13 @@ export const ArchivePage: FC<ArchivePageProps> = ({
|
|
|
1347
1448
|
<div key={`grid-${group.year}-${group.month}`} class="contents">
|
|
1348
1449
|
<ArchiveMonthHeader
|
|
1349
1450
|
class={`archive-month-header${groupIndex > 0 ? " archive-month-header-spaced" : ""}`}
|
|
1350
|
-
|
|
1451
|
+
// Not translated, on purpose: `group.label` is built with
|
|
1452
|
+
// a hardcoded en-US month name, so a translated wrapper
|
|
1453
|
+
// would only produce "更新于 August 2026". Both halves
|
|
1454
|
+
// become translatable together, or neither does.
|
|
1455
|
+
label={
|
|
1456
|
+
sortsByActivity ? `Updated ${group.label}` : group.label
|
|
1457
|
+
}
|
|
1351
1458
|
count={group.totalCount}
|
|
1352
1459
|
/>
|
|
1353
1460
|
{group.posts.map((post) => (
|