@jant/core 0.6.13 → 0.6.15
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 +81 -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-BxCOR3Uc.js +6 -0
- package/dist/{app-HnzmsaqU.js → app-C-oi_t8W.js} +7419 -6350
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-B_eGKN5p.css +2 -0
- package/dist/client/_assets/{client-3_OaxhTs.js → client-DxY5BFe8.js} +58 -37
- package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
- package/dist/{export-Dfbq9aot.js → export-CsFx6F_M.js} +1273 -131
- package/dist/{github-sync--PSoE-Ne.js → github-sync-Cq1pzzOI.js} +1 -1
- package/dist/{github-sync-BGTLx8Mf.js → github-sync-ppWXN3jb.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/node.js +5 -5
- 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__/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 +64 -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 +18 -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__/jant-collection-directory.test.ts +155 -6
- package/src/client/components/__tests__/jant-command-palette.test.ts +137 -10
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +373 -5
- package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
- package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
- 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-types.ts +8 -0
- package/src/client/components/jant-collection-directory.ts +165 -30
- package/src/client/components/jant-command-palette.ts +102 -71
- package/src/client/components/jant-compose-dialog.ts +86 -22
- package/src/client/components/jant-compose-editor.ts +13 -7
- package/src/client/components/jant-compose-fullscreen.ts +24 -4
- 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 +86 -74
- 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 +4 -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/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__/footnotes.test.ts +284 -0
- package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
- package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +872 -2
- 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 +91 -13
- 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/footnotes.ts +156 -0
- package/src/client/tiptap/markdown-clipboard.ts +614 -12
- 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 +8 -6
- package/src/client-auth.ts +1 -0
- package/src/client.ts +1 -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-collection-migration.test.ts +364 -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/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/_journal.json +28 -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/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/_journal.json +28 -0
- package/src/db/pg/schema.ts +28 -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 +28 -20
- package/src/i18n/__tests__/detect.test.ts +8 -2
- package/src/i18n/detect.ts +14 -5
- package/src/i18n/locales/public/en.po +133 -30
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +128 -25
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +128 -25
- 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__/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 +36 -4
- package/src/lib/__tests__/markdown.test.ts +8 -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__/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__/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/footnotes.ts +264 -0
- package/src/lib/github-sync-site-config.ts +2 -0
- package/src/lib/hugo-markdown.ts +10 -3
- package/src/lib/jant-branding.ts +1 -0
- package/src/lib/markdown-manager.ts +202 -10
- 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 +7 -1
- 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 +98 -122
- package/src/lib/timezones.ts +54 -14
- package/src/lib/tiptap-render.ts +355 -81
- package/src/lib/view.ts +30 -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__/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 +2 -2
- package/src/routes/api/public/__tests__/archive.test.ts +35 -0
- package/src/routes/api/public/__tests__/posts.test.ts +208 -0
- package/src/routes/api/public/archive.ts +4 -0
- package/src/routes/api/public/posts.ts +41 -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 +2 -1
- package/src/routes/dash/settings.tsx +45 -2
- package/src/routes/feed/__tests__/feed.test.ts +210 -6
- package/src/routes/feed/feed.ts +23 -14
- package/src/routes/pages/__tests__/archive-params.test.ts +92 -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__/preview.test.ts +174 -0
- package/src/routes/pages/__tests__/search.test.ts +69 -0
- package/src/routes/pages/archive.tsx +27 -7
- package/src/routes/pages/collection.tsx +32 -19
- package/src/routes/pages/collections.tsx +6 -1
- package/src/routes/pages/page.tsx +83 -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 +159 -44
- package/src/services/__tests__/navigation.test.ts +229 -21
- package/src/services/__tests__/post-timeline.test.ts +109 -30
- package/src/services/__tests__/post.test.ts +567 -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 +226 -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 +91 -80
- package/src/services/mcp.ts +37 -27
- package/src/services/media.ts +1 -1
- package/src/services/navigation.ts +185 -46
- package/src/services/post.ts +874 -271
- package/src/services/search.ts +8 -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 -0
- package/src/styles/site-media.css +111 -19
- package/src/styles/tokens.css +14 -9
- package/src/styles/ui.css +952 -64
- 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 +9 -8
- package/src/types/operations.ts +18 -10
- package/src/types/props.ts +8 -2
- package/src/types/raw-assets.d.ts +5 -0
- package/src/types/views.ts +16 -4
- package/src/ui/__tests__/font-themes.test.ts +42 -30
- package/src/ui/compose/ComposeDialog.tsx +92 -2
- 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/NoteCard.tsx +10 -20
- 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 +186 -6
- package/src/ui/feed/thread-preview-state.ts +5 -7
- package/src/ui/font-themes.ts +74 -26
- package/src/ui/layouts/BaseLayout.tsx +23 -9
- package/src/ui/layouts/SiteLayout.tsx +19 -13
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
- package/src/ui/layouts/__tests__/SiteLayout.test.tsx +48 -0
- 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 +7 -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 +33 -0
- package/src/ui/shared/HomePageBranding.tsx +2 -2
- package/src/ui/shared/MediaGallery.tsx +1 -0
- package/src/ui/shared/PostFooter.tsx +3 -1
- 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/navigation-labels.ts +2 -4
- package/src/ui/shared/post-article-attributes.ts +46 -0
- package/dist/app-CWJFPjuR.js +0 -6
- package/dist/client/_assets/client-DCrcHVJl.css +0 -2
- package/dist/client/_assets/client-auth-TsYq90xm.js +0 -4733
- 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 =
|
|
@@ -434,7 +442,7 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
434
442
|
responsive header layout before external CSS/JS loads */}
|
|
435
443
|
<style
|
|
436
444
|
dangerouslySetInnerHTML={{
|
|
437
|
-
__html: `.site-header-search-link,.site-header-hamburger,.site-header-more-responsive-only,.site-header-more-link-responsive,.site-header-more-divider-responsive{display:none!important}@media(max-width:1200px){.site-header-search-form{display:none!important}.site-header-search-link{display:inline-flex!important}}@media(max-width:960px){.site-header-link-collapse-lg{display:none!important}.site-header-more-responsive-only.site-header-more-tier-lg{display:inline-flex!important}.site-header-more-link-show-lg{display:flex!important}.site-header-more-divider-
|
|
445
|
+
__html: `.site-header-search-link,.site-header-hamburger,.site-header-more-responsive-only,.site-header-more-link-responsive,.site-header-more-divider-responsive{display:none!important}@media(max-width:1200px){.site-header-search-form{display:none!important}.site-header-search-link{display:inline-flex!important}}@media(max-width:960px){.site-header-link-collapse-lg{display:none!important}.site-header-more-responsive-only.site-header-more-tier-lg{display:inline-flex!important}.site-header-more-link-show-lg{display:flex!important}.site-header-more-divider-show-lg{display:block!important}}@media(max-width:780px){.site-header-link-collapse-md{display:none!important}.site-header-more-responsive-only.site-header-more-tier-md{display:inline-flex!important}.site-header-more-link-show-md{display:flex!important}.site-header-more-divider-show-md{display:block!important}}@media(max-width:580px){.site-header-link-collapse-sm{display:none!important}.site-header-more-responsive-only.site-header-more-tier-sm{display:inline-flex!important}.site-header-more-link-show-sm{display:flex!important}.site-header-more-divider-show-sm{display:block!important}}@media(max-width:480px){.site-header-nav,.site-header-more{display:none!important}.site-header-search-slot{display:flex!important}.site-header-hamburger{display:flex!important}.site-header-right{margin-left:.35rem}}`,
|
|
438
446
|
}}
|
|
439
447
|
/>
|
|
440
448
|
{themeStyle && (
|
|
@@ -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"}`}
|
|
@@ -134,6 +134,15 @@ function moreLinkRevealTier(idx: number): string {
|
|
|
134
134
|
return "site-header-more-link-show-lg";
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
function getResponsiveOverflowTier(
|
|
138
|
+
headerLinkCount: number,
|
|
139
|
+
): "sm" | "md" | "lg" | null {
|
|
140
|
+
if (headerLinkCount >= 5) return "lg";
|
|
141
|
+
if (headerLinkCount === 4) return "md";
|
|
142
|
+
if (headerLinkCount === 3) return "sm";
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
137
146
|
export const SiteHeader: FC<SiteHeaderProps> = ({
|
|
138
147
|
siteName,
|
|
139
148
|
links,
|
|
@@ -176,22 +185,17 @@ export const SiteHeader: FC<SiteHeaderProps> = ({
|
|
|
176
185
|
(l) => l.placement === "header" || !l.placement,
|
|
177
186
|
);
|
|
178
187
|
const moreLinks = linksWithLabels.filter((l) => l.placement === "more");
|
|
179
|
-
const
|
|
188
|
+
const responsiveOverflowTier = getResponsiveOverflowTier(headerLinks.length);
|
|
189
|
+
const hasResponsiveOverflow = responsiveOverflowTier !== null;
|
|
180
190
|
const hasSupplementalMoreLinks = moreLinks.length > 0;
|
|
181
191
|
const showMoreMenu = hasResponsiveOverflow || hasSupplementalMoreLinks;
|
|
182
192
|
|
|
183
193
|
// Decide the widest breakpoint at which the "More" button must appear
|
|
184
194
|
// when there are no supplemental links.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
} else if (headerLinks.length === 4) {
|
|
190
|
-
responsiveTierClass = "site-header-more-tier-md";
|
|
191
|
-
} else {
|
|
192
|
-
responsiveTierClass = "site-header-more-tier-sm";
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
+
const responsiveTierClass =
|
|
196
|
+
responsiveOverflowTier && !hasSupplementalMoreLinks
|
|
197
|
+
? `site-header-more-tier-${responsiveOverflowTier}`
|
|
198
|
+
: "";
|
|
195
199
|
const moreMenuClass = [
|
|
196
200
|
"site-header-more",
|
|
197
201
|
hasResponsiveOverflow && !hasSupplementalMoreLinks
|
|
@@ -297,8 +301,10 @@ export const SiteHeader: FC<SiteHeaderProps> = ({
|
|
|
297
301
|
</a>
|
|
298
302
|
);
|
|
299
303
|
})}
|
|
300
|
-
{
|
|
301
|
-
<div
|
|
304
|
+
{responsiveOverflowTier && hasSupplementalMoreLinks && (
|
|
305
|
+
<div
|
|
306
|
+
class={`site-header-more-divider site-header-more-divider-responsive site-header-more-divider-show-${responsiveOverflowTier}`}
|
|
307
|
+
/>
|
|
302
308
|
)}
|
|
303
309
|
{moreLinks.map((link) => (
|
|
304
310
|
<a
|
|
@@ -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(
|
|
@@ -3,8 +3,24 @@ import { renderToString } from "hono/jsx/dom/server";
|
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
4
|
import { I18nProvider } from "../../../i18n/context.js";
|
|
5
5
|
import { createI18n } from "../../../i18n/i18n.js";
|
|
6
|
+
import type { NavItemView } from "../../../types.js";
|
|
6
7
|
import { SiteLayout } from "../SiteLayout.js";
|
|
7
8
|
|
|
9
|
+
function createNavItem(
|
|
10
|
+
index: number,
|
|
11
|
+
placement: NavItemView["placement"],
|
|
12
|
+
): NavItemView {
|
|
13
|
+
return {
|
|
14
|
+
id: `nav_${index}`,
|
|
15
|
+
type: "link",
|
|
16
|
+
label: `Link ${index}`,
|
|
17
|
+
url: `/link-${index}`,
|
|
18
|
+
placement,
|
|
19
|
+
isActive: false,
|
|
20
|
+
isExternal: false,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
function renderSiteLayout(
|
|
9
25
|
props: Partial<Parameters<typeof SiteLayout>[0]> = {},
|
|
10
26
|
): string {
|
|
@@ -30,6 +46,38 @@ function renderSiteLayout(
|
|
|
30
46
|
}
|
|
31
47
|
|
|
32
48
|
describe("SiteLayout", () => {
|
|
49
|
+
it.each([
|
|
50
|
+
[3, "sm"],
|
|
51
|
+
[4, "md"],
|
|
52
|
+
[5, "lg"],
|
|
53
|
+
] as const)(
|
|
54
|
+
"reveals the More divider at the first breakpoint that overflows %i header links",
|
|
55
|
+
(headerLinkCount, tier) => {
|
|
56
|
+
const headerLinks = Array.from({ length: headerLinkCount }, (_, index) =>
|
|
57
|
+
createNavItem(index, "header"),
|
|
58
|
+
);
|
|
59
|
+
const html = renderSiteLayout({
|
|
60
|
+
links: [...headerLinks, createNavItem(headerLinkCount, "more")],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(html).toContain(
|
|
64
|
+
`class="site-header-more-divider site-header-more-divider-responsive site-header-more-divider-show-${tier}"`,
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
it("omits the More divider when no header link can overflow", () => {
|
|
70
|
+
const html = renderSiteLayout({
|
|
71
|
+
links: [
|
|
72
|
+
createNavItem(0, "header"),
|
|
73
|
+
createNavItem(1, "header"),
|
|
74
|
+
createNavItem(2, "more"),
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(html).not.toContain("site-header-more-divider");
|
|
79
|
+
});
|
|
80
|
+
|
|
33
81
|
it("renders the mobile compose FAB for authenticated timeline pages", () => {
|
|
34
82
|
const html = renderSiteLayout({
|
|
35
83
|
currentPath: "/latest",
|
|
@@ -19,6 +19,7 @@ import { toPublicPath } from "../../lib/url.js";
|
|
|
19
19
|
import { TimelineFeed } from "../feed/TimelineFeed.js";
|
|
20
20
|
import { getCollectionMutationLabels } from "../shared/collection-management-labels.js";
|
|
21
21
|
import { getIconSvg } from "../../lib/icons.js";
|
|
22
|
+
import { NAVIGATION_SETTINGS_PATH } from "../../lib/settings-paths.js";
|
|
22
23
|
|
|
23
24
|
const escapeJson = (data: unknown) =>
|
|
24
25
|
JSON.stringify(data).replace(/</g, "\\u003c");
|
|
@@ -35,7 +36,9 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
35
36
|
defaultSort,
|
|
36
37
|
showRatingSort,
|
|
37
38
|
isAuthenticated,
|
|
39
|
+
isInNavigation = false,
|
|
38
40
|
sitePathPrefix = "",
|
|
41
|
+
feedHref,
|
|
39
42
|
}) => {
|
|
40
43
|
const primaryCollection = collections[0];
|
|
41
44
|
if (!primaryCollection) return null;
|
|
@@ -58,6 +61,10 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
58
61
|
)}`,
|
|
59
62
|
sitePathPrefix,
|
|
60
63
|
);
|
|
64
|
+
const navigationSettingsUrl = toPublicPath(
|
|
65
|
+
NAVIGATION_SETTINGS_PATH,
|
|
66
|
+
sitePathPrefix,
|
|
67
|
+
);
|
|
61
68
|
const sortUiId = isAggregate
|
|
62
69
|
? collections.map((collection) => collection.slug).join("-")
|
|
63
70
|
: primaryCollection.id;
|
|
@@ -226,7 +233,9 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
226
233
|
<div
|
|
227
234
|
class="collection-page-description prose"
|
|
228
235
|
dangerouslySetInnerHTML={{
|
|
229
|
-
__html: renderMarkdown(primaryCollection.description
|
|
236
|
+
__html: renderMarkdown(primaryCollection.description, {
|
|
237
|
+
namespace: primaryCollection.id,
|
|
238
|
+
}),
|
|
230
239
|
}}
|
|
231
240
|
/>
|
|
232
241
|
) : null}
|
|
@@ -298,25 +307,30 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
298
307
|
"@context: Plural thread count label on collection detail page",
|
|
299
308
|
}),
|
|
300
309
|
)}
|
|
301
|
-
{pageLabel ? <span> / {pageLabel}</span> : null}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
310
|
+
{pageLabel ? <span> / {pageLabel}</span> : null}
|
|
311
|
+
{feedHref ? (
|
|
312
|
+
<>
|
|
313
|
+
{" "}
|
|
314
|
+
<a
|
|
315
|
+
href={toPublicPath(feedHref, sitePathPrefix)}
|
|
316
|
+
class="feed-link"
|
|
317
|
+
title={i18n._(
|
|
318
|
+
msg({
|
|
319
|
+
message: "RSS feed",
|
|
320
|
+
comment:
|
|
321
|
+
"@context: Tooltip for the RSS feed link on a collection page",
|
|
322
|
+
}),
|
|
323
|
+
)}
|
|
324
|
+
rel="noopener noreferrer"
|
|
325
|
+
>
|
|
326
|
+
<span
|
|
327
|
+
dangerouslySetInnerHTML={{
|
|
328
|
+
__html: getIconSvg("rss") ?? "",
|
|
329
|
+
}}
|
|
330
|
+
/>
|
|
331
|
+
</a>
|
|
332
|
+
</>
|
|
333
|
+
) : null}
|
|
320
334
|
</p>
|
|
321
335
|
<span class="collection-page-meta-divider" aria-hidden="true">
|
|
322
336
|
·
|
|
@@ -450,6 +464,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
450
464
|
data-collection-page-actions
|
|
451
465
|
data-collection-id={primaryCollection.id}
|
|
452
466
|
data-collection-page-labels={escapeJson(mutationLabels)}
|
|
467
|
+
data-collection-in-navigation={String(isInNavigation)}
|
|
453
468
|
data-collection-page-redirect-url={toPublicPath(
|
|
454
469
|
getCollectionsDirectoryPath(),
|
|
455
470
|
sitePathPrefix,
|
|
@@ -510,6 +525,70 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
510
525
|
{mutationLabels.edit}
|
|
511
526
|
</span>
|
|
512
527
|
</a>
|
|
528
|
+
<button
|
|
529
|
+
type="button"
|
|
530
|
+
class="collections-page-menu-item"
|
|
531
|
+
role="menuitem"
|
|
532
|
+
data-collection-page-action="add-to-navigation"
|
|
533
|
+
hidden={isInNavigation}
|
|
534
|
+
>
|
|
535
|
+
<span
|
|
536
|
+
class="collections-page-menu-item-icon"
|
|
537
|
+
aria-hidden="true"
|
|
538
|
+
>
|
|
539
|
+
<svg
|
|
540
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
541
|
+
width="16"
|
|
542
|
+
height="16"
|
|
543
|
+
viewBox="0 0 24 24"
|
|
544
|
+
fill="none"
|
|
545
|
+
stroke="currentColor"
|
|
546
|
+
stroke-width="2"
|
|
547
|
+
stroke-linecap="round"
|
|
548
|
+
stroke-linejoin="round"
|
|
549
|
+
>
|
|
550
|
+
<path d="M12 5v14" />
|
|
551
|
+
<path d="M5 12h14" />
|
|
552
|
+
</svg>
|
|
553
|
+
</span>
|
|
554
|
+
<span class="collections-page-menu-item-label">
|
|
555
|
+
{mutationLabels.addToNavigation}
|
|
556
|
+
</span>
|
|
557
|
+
</button>
|
|
558
|
+
<a
|
|
559
|
+
href={navigationSettingsUrl}
|
|
560
|
+
class="collections-page-menu-item"
|
|
561
|
+
role="menuitem"
|
|
562
|
+
data-collection-page-edit-navigation
|
|
563
|
+
hidden={!isInNavigation}
|
|
564
|
+
>
|
|
565
|
+
<span
|
|
566
|
+
class="collections-page-menu-item-icon"
|
|
567
|
+
aria-hidden="true"
|
|
568
|
+
>
|
|
569
|
+
<svg
|
|
570
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
571
|
+
width="16"
|
|
572
|
+
height="16"
|
|
573
|
+
viewBox="0 0 24 24"
|
|
574
|
+
fill="none"
|
|
575
|
+
stroke="currentColor"
|
|
576
|
+
stroke-width="2"
|
|
577
|
+
stroke-linecap="round"
|
|
578
|
+
stroke-linejoin="round"
|
|
579
|
+
>
|
|
580
|
+
<path d="M8 6h13" />
|
|
581
|
+
<path d="M8 12h13" />
|
|
582
|
+
<path d="M8 18h13" />
|
|
583
|
+
<path d="M3 6h.01" />
|
|
584
|
+
<path d="M3 12h.01" />
|
|
585
|
+
<path d="M3 18h.01" />
|
|
586
|
+
</svg>
|
|
587
|
+
</span>
|
|
588
|
+
<span class="collections-page-menu-item-label">
|
|
589
|
+
{mutationLabels.editNavigation}
|
|
590
|
+
</span>
|
|
591
|
+
</a>
|
|
513
592
|
<button
|
|
514
593
|
type="button"
|
|
515
594
|
class="collections-page-menu-item collections-page-menu-item-danger"
|
|
@@ -556,7 +635,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
556
635
|
? i18n._(
|
|
557
636
|
msg({
|
|
558
637
|
message:
|
|
559
|
-
"Nothing here yet. Add
|
|
638
|
+
"Nothing here yet. Add threads to one of these collections to fill this view.",
|
|
560
639
|
comment:
|
|
561
640
|
"@context: Empty state message on an aggregate collection page",
|
|
562
641
|
}),
|
|
@@ -564,7 +643,7 @@ export const CollectionPage: FC<CollectionPageProps> = ({
|
|
|
564
643
|
: i18n._(
|
|
565
644
|
msg({
|
|
566
645
|
message:
|
|
567
|
-
"This collection is empty. Add
|
|
646
|
+
"This collection is empty. Add threads from the editor.",
|
|
568
647
|
comment: "@context: Empty state message",
|
|
569
648
|
}),
|
|
570
649
|
)}
|
|
@@ -14,13 +14,14 @@ import { CollectionsManager } from "../shared/CollectionsManager.js";
|
|
|
14
14
|
export const CollectionsPage: FC<CollectionsPageProps> = ({
|
|
15
15
|
items,
|
|
16
16
|
isAuthenticated,
|
|
17
|
+
navigationCollectionIds = [],
|
|
17
18
|
sitePathPrefix = "",
|
|
18
19
|
siteOrigin = "",
|
|
19
20
|
}) => {
|
|
20
21
|
const { i18n } = useLingui();
|
|
21
22
|
const emptyMessage = i18n._(
|
|
22
23
|
msg({
|
|
23
|
-
message: "No collections yet. Start one to organize
|
|
24
|
+
message: "No collections yet. Start one to organize threads by topic.",
|
|
24
25
|
comment: "@context: Empty state message on collections page",
|
|
25
26
|
}),
|
|
26
27
|
);
|
|
@@ -30,6 +31,7 @@ export const CollectionsPage: FC<CollectionsPageProps> = ({
|
|
|
30
31
|
<div class="py-6" data-page="collections">
|
|
31
32
|
<CollectionsManager
|
|
32
33
|
items={items}
|
|
34
|
+
navigationCollectionIds={navigationCollectionIds}
|
|
33
35
|
sitePathPrefix={sitePathPrefix}
|
|
34
36
|
siteOrigin={siteOrigin}
|
|
35
37
|
/>
|