@jant/core 0.6.14 → 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-2i7-WQHg.js → app-C-oi_t8W.js} +7408 -6343
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-B_eGKN5p.css +2 -0
- package/dist/client/_assets/{client-BvID5ucz.js → client-DxY5BFe8.js} +58 -37
- package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
- package/dist/{export-DWn149b7.js → export-CsFx6F_M.js} +1259 -125
- package/dist/{github-sync-Ckk0sJxK.js → github-sync-Cq1pzzOI.js} +1 -1
- package/dist/{github-sync-C0zyF6XS.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-compose-dialog.test.ts +370 -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 +2 -1
- package/src/client/components/jant-compose-dialog.ts +85 -21
- 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__/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/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 +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 +32 -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 +188 -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 +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 +829 -29
- 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 +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/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-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(
|
|
@@ -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
|
/>
|
|
@@ -12,10 +12,22 @@
|
|
|
12
12
|
import { msg } from "@lingui/core/macro";
|
|
13
13
|
import type { FC } from "hono/jsx";
|
|
14
14
|
import { useLingui } from "../../i18n/context.js";
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
PostPageProps,
|
|
17
|
+
PostView,
|
|
18
|
+
TimelineCardDisplayOptions,
|
|
19
|
+
} from "../../types.js";
|
|
16
20
|
import { TimelineItemFromPost } from "../feed/TimelineItem.js";
|
|
17
21
|
|
|
18
|
-
const
|
|
22
|
+
const PREVIEW_DISPLAY: TimelineCardDisplayOptions = {
|
|
23
|
+
footer: { hideActions: true, hideReply: true, hideTimestamp: false },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const renderThreadItem = (
|
|
27
|
+
tp: PostView,
|
|
28
|
+
currentId: string,
|
|
29
|
+
isPreview: boolean,
|
|
30
|
+
) => {
|
|
19
31
|
const isCurrent = tp.id === currentId;
|
|
20
32
|
return (
|
|
21
33
|
<div
|
|
@@ -27,16 +39,19 @@ const renderThreadItem = (tp: PostView, currentId: string) => {
|
|
|
27
39
|
<TimelineItemFromPost
|
|
28
40
|
post={tp}
|
|
29
41
|
mode="detail"
|
|
30
|
-
display={
|
|
42
|
+
display={
|
|
43
|
+
isPreview ? PREVIEW_DISPLAY : { footer: { hideTimestamp: false } }
|
|
44
|
+
}
|
|
31
45
|
/>
|
|
32
46
|
</div>
|
|
33
47
|
);
|
|
34
48
|
};
|
|
35
49
|
|
|
36
|
-
const ThreadDetail: FC<{
|
|
37
|
-
post
|
|
38
|
-
threadPosts
|
|
39
|
-
|
|
50
|
+
const ThreadDetail: FC<{
|
|
51
|
+
post: PostView;
|
|
52
|
+
threadPosts: PostView[];
|
|
53
|
+
isPreview: boolean;
|
|
54
|
+
}> = ({ post, threadPosts, isPreview }) => {
|
|
40
55
|
const { i18n } = useLingui();
|
|
41
56
|
const showMoreLabel = i18n._(
|
|
42
57
|
msg({
|
|
@@ -66,7 +81,7 @@ const ThreadDetail: FC<{ post: PostView; threadPosts: PostView[] }> = ({
|
|
|
66
81
|
data-thread-context
|
|
67
82
|
data-collapsed=""
|
|
68
83
|
>
|
|
69
|
-
{ancestors.map((tp) => renderThreadItem(tp, post.id))}
|
|
84
|
+
{ancestors.map((tp) => renderThreadItem(tp, post.id, isPreview))}
|
|
70
85
|
</div>
|
|
71
86
|
<button
|
|
72
87
|
type="button"
|
|
@@ -94,12 +109,16 @@ const ThreadDetail: FC<{ post: PostView; threadPosts: PostView[] }> = ({
|
|
|
94
109
|
</button>
|
|
95
110
|
</>
|
|
96
111
|
)}
|
|
97
|
-
{currentAndAfter.map((tp) => renderThreadItem(tp, post.id))}
|
|
112
|
+
{currentAndAfter.map((tp) => renderThreadItem(tp, post.id, isPreview))}
|
|
98
113
|
</div>
|
|
99
114
|
);
|
|
100
115
|
};
|
|
101
116
|
|
|
102
|
-
export const PostPage: FC<PostPageProps> = ({
|
|
117
|
+
export const PostPage: FC<PostPageProps> = ({
|
|
118
|
+
post,
|
|
119
|
+
threadPosts,
|
|
120
|
+
isPreview = false,
|
|
121
|
+
}) => {
|
|
103
122
|
return (
|
|
104
123
|
<div
|
|
105
124
|
data-post-view
|
|
@@ -107,9 +126,17 @@ export const PostPage: FC<PostPageProps> = ({ post, threadPosts }) => {
|
|
|
107
126
|
data-thread-root-id={post.threadRootId ?? post.id}
|
|
108
127
|
>
|
|
109
128
|
{threadPosts && threadPosts.length > 1 ? (
|
|
110
|
-
<ThreadDetail
|
|
129
|
+
<ThreadDetail
|
|
130
|
+
post={post}
|
|
131
|
+
threadPosts={threadPosts}
|
|
132
|
+
isPreview={isPreview}
|
|
133
|
+
/>
|
|
111
134
|
) : (
|
|
112
|
-
<TimelineItemFromPost
|
|
135
|
+
<TimelineItemFromPost
|
|
136
|
+
post={post}
|
|
137
|
+
mode="detail"
|
|
138
|
+
display={isPreview ? PREVIEW_DISPLAY : undefined}
|
|
139
|
+
/>
|
|
113
140
|
)}
|
|
114
141
|
{/* Public integration slot — code injection (giscus, Webmentions, etc.) appends here. */}
|
|
115
142
|
<div data-post-end />
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
CompactCollectionTags,
|
|
16
16
|
PostMenuTriggerButton,
|
|
17
17
|
} from "../shared/PostFooter.js";
|
|
18
|
+
import { getPostArticleAttributes } from "../shared/post-article-attributes.js";
|
|
18
19
|
|
|
19
20
|
// External link icon (shared by LinkCard)
|
|
20
21
|
const ExternalLinkIcon = () => (
|
|
@@ -46,20 +47,11 @@ const SearchResultCard: FC<{
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
const
|
|
50
|
+
const isChildPost = !!post.replyToId || !!post.threadRootId;
|
|
51
|
+
const visibleCollections = isChildPost ? [] : post.collections;
|
|
52
|
+
const hasCollections = visibleCollections.length > 0;
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
const postAttrs = {
|
|
53
|
-
"data-post": "",
|
|
54
|
-
"data-format": post.format,
|
|
55
|
-
"data-post-id": post.id,
|
|
56
|
-
"data-post-slug": post.slug,
|
|
57
|
-
"data-thread-root-id": post.threadRootId ?? post.id,
|
|
58
|
-
...(post.pinned ? { "data-post-pinned": "" } : {}),
|
|
59
|
-
...(post.featured ? { "data-post-featured": "" } : {}),
|
|
60
|
-
"data-post-visibility": post.visibility,
|
|
61
|
-
...(post.threadRootId ? { "data-post-reply": "" } : {}),
|
|
62
|
-
};
|
|
54
|
+
const postAttrs = getPostArticleAttributes(post);
|
|
63
55
|
|
|
64
56
|
const footer = (
|
|
65
57
|
<footer class="post-menu-footer mt-2">
|
|
@@ -69,7 +61,7 @@ const SearchResultCard: FC<{
|
|
|
69
61
|
<time datetime={post.publishedAt}>{post.publishedAtFormatted}</time>
|
|
70
62
|
</a>
|
|
71
63
|
<CompactCollectionTags
|
|
72
|
-
collections={
|
|
64
|
+
collections={visibleCollections}
|
|
73
65
|
showSeparator={hasCollections}
|
|
74
66
|
/>
|
|
75
67
|
</div>
|