@jant/core 0.3.35 → 0.3.37

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.
Files changed (307) hide show
  1. package/bin/commands/export.js +1 -1
  2. package/bin/commands/import-site.js +529 -0
  3. package/bin/commands/reset-password.js +3 -2
  4. package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
  5. package/dist/client/assets/module-RjUF93sV.js +716 -0
  6. package/dist/client/assets/native-48B9X9Wg.js +1 -0
  7. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  8. package/dist/client/client.css +1 -1
  9. package/dist/client/client.js +4564 -3013
  10. package/dist/index.js +12885 -8161
  11. package/package.json +23 -6
  12. package/src/__tests__/helpers/app.ts +10 -10
  13. package/src/__tests__/helpers/db.ts +91 -87
  14. package/src/app.tsx +157 -31
  15. package/src/auth.ts +20 -2
  16. package/src/client/archive-nav.js +187 -0
  17. package/src/client/audio-player.ts +478 -0
  18. package/src/client/audio-processor.ts +84 -0
  19. package/src/{lib → client}/avatar-upload.ts +4 -3
  20. package/src/{lib → client}/collection-form-bridge.ts +2 -2
  21. package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
  22. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
  23. package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
  24. package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
  25. package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
  26. package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
  27. package/src/client/components/collection-sidebar-types.ts +43 -0
  28. package/src/{ui → client}/components/collection-types.ts +3 -4
  29. package/src/client/components/compose-types.ts +174 -0
  30. package/src/client/components/jant-collection-form.ts +667 -0
  31. package/src/client/components/jant-collection-sidebar.ts +805 -0
  32. package/src/client/components/jant-compose-dialog.ts +2161 -0
  33. package/src/client/components/jant-compose-editor.ts +1813 -0
  34. package/src/client/components/jant-compose-fullscreen.ts +283 -0
  35. package/src/client/components/jant-media-lightbox.ts +259 -0
  36. package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
  37. package/src/{ui → client}/components/jant-post-form.ts +141 -12
  38. package/src/client/components/jant-post-menu.ts +1019 -0
  39. package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
  40. package/src/{ui → client}/components/jant-settings-general.ts +38 -4
  41. package/src/client/components/jant-text-preview.ts +232 -0
  42. package/src/{ui → client}/components/nav-manager-types.ts +6 -18
  43. package/src/{ui → client}/components/post-form-template.ts +137 -38
  44. package/src/{ui → client}/components/post-form-types.ts +15 -4
  45. package/src/client/compose-bridge.ts +583 -0
  46. package/src/{lib → client}/image-processor.ts +26 -8
  47. package/src/client/lazy-slugify.ts +51 -0
  48. package/src/client/media-metadata.ts +247 -0
  49. package/src/client/multipart-upload.ts +160 -0
  50. package/src/{lib → client}/nav-manager-bridge.ts +1 -1
  51. package/src/{lib → client}/post-form-bridge.ts +53 -2
  52. package/src/{lib → client}/settings-bridge.ts +3 -15
  53. package/src/client/thread-context.ts +140 -0
  54. package/src/client/tiptap/bubble-menu.ts +205 -0
  55. package/src/client/tiptap/create-editor.ts +86 -0
  56. package/src/client/tiptap/exitable-marks.ts +73 -0
  57. package/src/client/tiptap/extensions.ts +65 -0
  58. package/src/client/tiptap/image-node.ts +482 -0
  59. package/src/client/tiptap/link-toolbar.ts +371 -0
  60. package/src/client/tiptap/more-break.ts +50 -0
  61. package/src/client/tiptap/paste-image.ts +129 -0
  62. package/src/client/tiptap/slash-commands.ts +438 -0
  63. package/src/{lib → client}/toast.ts +101 -3
  64. package/src/client/types/sortablejs.d.ts +44 -0
  65. package/src/client/upload-with-metadata.ts +54 -0
  66. package/src/client/video-processor.ts +207 -0
  67. package/src/client.ts +27 -17
  68. package/src/db/__tests__/migrations.test.ts +118 -0
  69. package/src/db/index.ts +52 -0
  70. package/src/db/migrations/0000_baseline.sql +269 -0
  71. package/src/db/migrations/0001_fts_setup.sql +31 -0
  72. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  73. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  74. package/src/db/migrations/meta/_journal.json +4 -39
  75. package/src/db/schema.ts +409 -140
  76. package/src/i18n/__tests__/detect.test.ts +115 -0
  77. package/src/i18n/context.tsx +2 -2
  78. package/src/i18n/detect.ts +85 -1
  79. package/src/i18n/i18n.ts +1 -1
  80. package/src/i18n/index.ts +2 -1
  81. package/src/i18n/locales/en.po +783 -1087
  82. package/src/i18n/locales/en.ts +1 -1
  83. package/src/i18n/locales/zh-Hans.po +867 -812
  84. package/src/i18n/locales/zh-Hans.ts +1 -1
  85. package/src/i18n/locales/zh-Hant.po +878 -823
  86. package/src/i18n/locales/zh-Hant.ts +1 -1
  87. package/src/i18n/middleware.ts +6 -0
  88. package/src/index.ts +5 -7
  89. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  90. package/src/lib/__tests__/constants.test.ts +0 -1
  91. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  92. package/src/lib/__tests__/nanoid.test.ts +26 -0
  93. package/src/lib/__tests__/resolve-config.test.ts +2 -2
  94. package/src/lib/__tests__/schemas.test.ts +186 -65
  95. package/src/lib/__tests__/slug.test.ts +126 -0
  96. package/src/lib/__tests__/sse.test.ts +6 -6
  97. package/src/lib/__tests__/summary.test.ts +264 -0
  98. package/src/lib/__tests__/theme.test.ts +1 -1
  99. package/src/lib/__tests__/timeline.test.ts +33 -30
  100. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  101. package/src/lib/__tests__/url.test.ts +2 -2
  102. package/src/lib/__tests__/view.test.ts +140 -65
  103. package/src/lib/blurhash-placeholder.ts +102 -0
  104. package/src/lib/constants.ts +3 -1
  105. package/src/lib/emoji-catalog.ts +963 -0
  106. package/src/lib/errors.ts +11 -8
  107. package/src/lib/feed.ts +77 -31
  108. package/src/lib/html.ts +2 -1
  109. package/src/lib/icon-catalog.ts +5033 -1
  110. package/src/lib/icons.ts +3 -2
  111. package/src/lib/index.ts +0 -1
  112. package/src/lib/markdown-to-tiptap.ts +286 -0
  113. package/src/lib/media-helpers.ts +22 -12
  114. package/src/lib/nanoid.ts +29 -0
  115. package/src/lib/navigation.ts +1 -1
  116. package/src/lib/render.tsx +24 -5
  117. package/src/lib/resolve-config.ts +13 -2
  118. package/src/lib/schemas.ts +226 -58
  119. package/src/lib/search-snippet.ts +34 -0
  120. package/src/lib/slug.ts +96 -0
  121. package/src/lib/sse.ts +6 -6
  122. package/src/lib/storage.ts +115 -7
  123. package/src/lib/summary.ts +158 -0
  124. package/src/lib/theme.ts +11 -8
  125. package/src/lib/timeline.ts +76 -34
  126. package/src/lib/tiptap-render.ts +191 -0
  127. package/src/lib/tiptap-to-markdown.ts +305 -0
  128. package/src/lib/upload.ts +263 -14
  129. package/src/lib/url.ts +37 -22
  130. package/src/lib/view.ts +236 -55
  131. package/src/middleware/__tests__/auth.test.ts +191 -11
  132. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  133. package/src/middleware/auth.ts +63 -9
  134. package/src/middleware/error-handler.ts +3 -3
  135. package/src/middleware/onboarding.ts +1 -1
  136. package/src/middleware/secure-headers.ts +40 -0
  137. package/src/preset.css +83 -2
  138. package/src/routes/__tests__/compose.test.ts +17 -24
  139. package/src/routes/api/__tests__/collections.test.ts +109 -61
  140. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  141. package/src/routes/api/__tests__/posts.test.ts +132 -68
  142. package/src/routes/api/__tests__/search.test.ts +15 -2
  143. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  144. package/src/routes/api/collections.ts +57 -31
  145. package/src/routes/api/custom-urls.ts +80 -0
  146. package/src/routes/api/export.ts +31 -0
  147. package/src/routes/api/nav-items.ts +23 -19
  148. package/src/routes/api/posts.ts +81 -62
  149. package/src/routes/api/search.ts +3 -4
  150. package/src/routes/api/upload-multipart.ts +245 -0
  151. package/src/routes/api/upload.ts +92 -24
  152. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  153. package/src/routes/auth/reset.tsx +5 -4
  154. package/src/routes/auth/setup.tsx +39 -31
  155. package/src/routes/auth/signin.tsx +13 -14
  156. package/src/routes/compose.tsx +27 -63
  157. package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
  158. package/src/routes/dash/custom-urls.tsx +414 -0
  159. package/src/routes/dash/settings.tsx +475 -99
  160. package/src/routes/feed/__tests__/rss.test.ts +22 -23
  161. package/src/routes/feed/rss.ts +6 -2
  162. package/src/routes/feed/sitemap.ts +2 -12
  163. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  164. package/src/routes/pages/__tests__/featured.test.ts +36 -18
  165. package/src/routes/pages/archive.tsx +177 -37
  166. package/src/routes/pages/collection.tsx +43 -14
  167. package/src/routes/pages/collections.tsx +11 -2
  168. package/src/routes/pages/featured.tsx +27 -3
  169. package/src/routes/pages/home.tsx +15 -14
  170. package/src/routes/pages/latest.tsx +1 -11
  171. package/src/routes/pages/new.tsx +39 -0
  172. package/src/routes/pages/page.tsx +95 -49
  173. package/src/routes/pages/search.tsx +1 -1
  174. package/src/services/__tests__/api-token.test.ts +135 -0
  175. package/src/services/__tests__/collection.test.ts +275 -227
  176. package/src/services/__tests__/custom-url.test.ts +213 -0
  177. package/src/services/__tests__/media.test.ts +162 -22
  178. package/src/services/__tests__/navigation.test.ts +109 -68
  179. package/src/services/__tests__/post-timeline.test.ts +205 -32
  180. package/src/services/__tests__/post.test.ts +800 -230
  181. package/src/services/__tests__/search.test.ts +67 -10
  182. package/src/services/__tests__/settings.test.ts +3 -3
  183. package/src/services/api-token.ts +166 -0
  184. package/src/services/auth.ts +17 -2
  185. package/src/services/collection.ts +397 -131
  186. package/src/services/custom-url.ts +188 -0
  187. package/src/services/export.ts +802 -0
  188. package/src/services/index.ts +26 -19
  189. package/src/services/media.ts +100 -22
  190. package/src/services/navigation.ts +158 -47
  191. package/src/services/path.ts +339 -0
  192. package/src/services/post.ts +764 -172
  193. package/src/services/search.ts +161 -74
  194. package/src/services/settings.ts +6 -2
  195. package/src/styles/components.css +293 -62
  196. package/src/styles/tokens.css +93 -5
  197. package/src/styles/ui.css +4349 -766
  198. package/src/types/bindings.ts +8 -0
  199. package/src/types/config.ts +34 -4
  200. package/src/types/constants.ts +17 -2
  201. package/src/types/entities.ts +83 -37
  202. package/src/types/operations.ts +20 -27
  203. package/src/types/props.ts +52 -17
  204. package/src/types/views.ts +48 -24
  205. package/src/ui/color-themes.ts +133 -23
  206. package/src/ui/compose/ComposeDialog.tsx +255 -16
  207. package/src/ui/compose/ComposePrompt.tsx +1 -1
  208. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  209. package/src/ui/dash/ListItemRow.tsx +1 -1
  210. package/src/ui/dash/StatusBadge.tsx +12 -2
  211. package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
  212. package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
  213. package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
  214. package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
  215. package/src/ui/dash/index.ts +0 -3
  216. package/src/ui/dash/settings/AccountContent.tsx +87 -146
  217. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  218. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  219. package/src/ui/dash/settings/AvatarContent.tsx +78 -0
  220. package/src/ui/dash/settings/GeneralContent.tsx +3 -62
  221. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  222. package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
  223. package/src/ui/feed/LinkCard.tsx +89 -40
  224. package/src/ui/feed/NoteCard.tsx +39 -25
  225. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  226. package/src/ui/feed/QuoteCard.tsx +38 -23
  227. package/src/ui/feed/ThreadPreview.tsx +90 -26
  228. package/src/ui/feed/TimelineFeed.tsx +3 -2
  229. package/src/ui/feed/TimelineItem.tsx +15 -6
  230. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  231. package/src/ui/feed/thread-preview-state.ts +61 -0
  232. package/src/ui/font-themes.ts +2 -2
  233. package/src/ui/layouts/BaseLayout.tsx +2 -2
  234. package/src/ui/layouts/SiteLayout.tsx +116 -103
  235. package/src/ui/pages/ArchivePage.tsx +923 -95
  236. package/src/ui/pages/CollectionPage.tsx +6 -35
  237. package/src/ui/pages/CollectionsPage.tsx +2 -1
  238. package/src/ui/pages/ComposePage.tsx +54 -0
  239. package/src/ui/pages/FeaturedPage.tsx +2 -1
  240. package/src/ui/pages/HomePage.tsx +1 -1
  241. package/src/ui/pages/PostPage.tsx +30 -45
  242. package/src/ui/pages/SearchPage.tsx +182 -38
  243. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  244. package/src/ui/shared/CollectionsSidebar.tsx +239 -4
  245. package/src/ui/shared/MediaGallery.tsx +475 -41
  246. package/src/ui/shared/PostFooter.tsx +204 -0
  247. package/src/ui/shared/StarRating.tsx +27 -0
  248. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  249. package/src/ui/shared/index.ts +0 -1
  250. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  251. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  252. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  253. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  254. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  255. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  256. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  257. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  258. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  259. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  260. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  261. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  262. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  263. package/src/lib/__tests__/sqid.test.ts +0 -65
  264. package/src/lib/collections-reorder.ts +0 -28
  265. package/src/lib/compose-bridge.ts +0 -280
  266. package/src/lib/media-upload.ts +0 -148
  267. package/src/lib/sqid.ts +0 -79
  268. package/src/routes/api/__tests__/pages.test.ts +0 -218
  269. package/src/routes/api/pages.ts +0 -73
  270. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  271. package/src/routes/dash/appearance.tsx +0 -240
  272. package/src/routes/dash/collections.tsx +0 -211
  273. package/src/routes/dash/index.tsx +0 -103
  274. package/src/routes/dash/media.tsx +0 -132
  275. package/src/routes/dash/pages.tsx +0 -239
  276. package/src/routes/dash/posts.tsx +0 -334
  277. package/src/routes/dash/redirects.tsx +0 -257
  278. package/src/routes/pages/post.tsx +0 -59
  279. package/src/services/__tests__/page.test.ts +0 -298
  280. package/src/services/__tests__/path-registry.test.ts +0 -165
  281. package/src/services/__tests__/redirect.test.ts +0 -159
  282. package/src/services/page.ts +0 -203
  283. package/src/services/path-registry.ts +0 -160
  284. package/src/services/redirect.ts +0 -97
  285. package/src/types/sortablejs.d.ts +0 -29
  286. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
  287. package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
  288. package/src/ui/components/compose-types.ts +0 -75
  289. package/src/ui/components/jant-collection-form.ts +0 -512
  290. package/src/ui/components/jant-compose-dialog.ts +0 -495
  291. package/src/ui/components/jant-compose-editor.ts +0 -814
  292. package/src/ui/dash/PageForm.tsx +0 -185
  293. package/src/ui/dash/PostList.tsx +0 -95
  294. package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
  295. package/src/ui/dash/collections/CollectionForm.tsx +0 -166
  296. package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
  297. package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
  298. package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
  299. package/src/ui/dash/media/MediaListContent.tsx +0 -201
  300. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  301. package/src/ui/dash/pages/PagesContent.tsx +0 -74
  302. package/src/ui/dash/posts/PostForm.tsx +0 -248
  303. package/src/ui/dash/settings/SettingsNav.tsx +0 -52
  304. package/src/ui/layouts/DashLayout.tsx +0 -165
  305. package/src/ui/pages/SinglePage.tsx +0 -23
  306. package/src/ui/shared/ThreadView.tsx +0 -136
  307. /package/src/{ui → client}/components/settings-types.ts +0 -0
@@ -0,0 +1,107 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { PostView } from "../../../types.js";
3
+ import {
4
+ getThreadPreviewState,
5
+ isThreadContextLikelyOverflow,
6
+ } from "../thread-preview-state.js";
7
+
8
+ function createPostView(overrides: Partial<PostView> = {}): PostView {
9
+ return {
10
+ id: "post-1",
11
+ permalink: "/post-1",
12
+ slug: "post-1",
13
+ format: "note",
14
+ status: "published",
15
+ visibility: "public",
16
+ pinned: false,
17
+ featured: false,
18
+ publishedAt: "2026-03-14T00:00:00.000Z",
19
+ publishedAtFormatted: "Mar 14, 2026",
20
+ publishedAtTime: "00:00",
21
+ publishedAtRelative: "now",
22
+ updatedAt: "2026-03-14T00:00:00.000Z",
23
+ media: [],
24
+ collections: [],
25
+ isLastInThread: false,
26
+ ...overrides,
27
+ };
28
+ }
29
+
30
+ describe("getThreadPreviewState", () => {
31
+ it("has no hidden ancestors for a 2-post thread", () => {
32
+ expect(
33
+ getThreadPreviewState({
34
+ hasParentReply: false,
35
+ totalReplyCount: 1,
36
+ }),
37
+ ).toEqual({
38
+ hiddenCount: 0,
39
+ });
40
+ });
41
+
42
+ it("has no hidden ancestors for a 3-post thread with parent context", () => {
43
+ expect(
44
+ getThreadPreviewState({
45
+ hasParentReply: true,
46
+ totalReplyCount: 2,
47
+ }),
48
+ ).toEqual({
49
+ hiddenCount: 0,
50
+ });
51
+ });
52
+
53
+ it("counts hidden ancestors for longer threads", () => {
54
+ expect(
55
+ getThreadPreviewState({
56
+ hasParentReply: true,
57
+ totalReplyCount: 5,
58
+ }),
59
+ ).toEqual({
60
+ hiddenCount: 3,
61
+ });
62
+ });
63
+
64
+ it("treats hidden ancestors as likely overflow", () => {
65
+ expect(
66
+ isThreadContextLikelyOverflow({
67
+ rootPost: createPostView(),
68
+ hiddenCount: 1,
69
+ }),
70
+ ).toBe(true);
71
+ });
72
+
73
+ it("treats media-heavy context as likely overflow", () => {
74
+ expect(
75
+ isThreadContextLikelyOverflow({
76
+ rootPost: createPostView({
77
+ media: [
78
+ {
79
+ id: "media-1",
80
+ url: "/image.jpg",
81
+ thumbnailUrl: "/image-thumb.jpg",
82
+ mimeType: "image/jpeg",
83
+ },
84
+ ],
85
+ }),
86
+ hiddenCount: 0,
87
+ }),
88
+ ).toBe(true);
89
+ });
90
+
91
+ it("keeps very short context collapsed without affordances", () => {
92
+ expect(
93
+ isThreadContextLikelyOverflow({
94
+ rootPost: createPostView({
95
+ bodyHtml: "<p>Short note.</p>",
96
+ }),
97
+ parentReply: createPostView({
98
+ id: "post-2",
99
+ permalink: "/post-2",
100
+ slug: "post-2",
101
+ bodyHtml: "<p>Tiny reply.</p>",
102
+ }),
103
+ hiddenCount: 0,
104
+ }),
105
+ ).toBe(false);
106
+ });
107
+ });
@@ -0,0 +1,61 @@
1
+ import type { PostView } from "../../types.js";
2
+ import { stripHtml } from "../../lib/excerpt.js";
3
+
4
+ export function getThreadPreviewState({
5
+ hasParentReply,
6
+ totalReplyCount,
7
+ }: {
8
+ hasParentReply: boolean;
9
+ totalReplyCount: number;
10
+ }) {
11
+ const hiddenCount = hasParentReply
12
+ ? totalReplyCount - 2 // exclude latest + parent
13
+ : totalReplyCount - 1; // exclude latest only
14
+
15
+ return {
16
+ hiddenCount,
17
+ };
18
+ }
19
+
20
+ function getRenderedTextLength(post?: PostView): number {
21
+ if (!post) return 0;
22
+
23
+ const htmlText = stripHtml(post.summaryHtml ?? post.bodyHtml ?? "");
24
+ return (
25
+ (post.title?.length ?? 0) +
26
+ (post.quoteText?.length ?? 0) +
27
+ (post.excerpt?.length ?? 0) +
28
+ htmlText.length
29
+ );
30
+ }
31
+
32
+ export function isThreadContextLikelyOverflow({
33
+ rootPost,
34
+ parentReply,
35
+ hiddenCount,
36
+ }: {
37
+ rootPost: PostView;
38
+ parentReply?: PostView;
39
+ hiddenCount: number;
40
+ }): boolean {
41
+ if (hiddenCount > 0) return true;
42
+
43
+ const contextPosts = [rootPost, parentReply].filter(
44
+ (post): post is PostView => post !== undefined,
45
+ );
46
+
47
+ if (
48
+ contextPosts.some(
49
+ (post) => post.media.length > 0 || post.summaryHasMore === true,
50
+ )
51
+ ) {
52
+ return true;
53
+ }
54
+
55
+ const renderedTextLength = contextPosts.reduce(
56
+ (sum, post) => sum + getRenderedTextLength(post),
57
+ 0,
58
+ );
59
+
60
+ return renderedTextLength > 220;
61
+ }
@@ -29,7 +29,7 @@ export interface FontTheme {
29
29
 
30
30
  /** System sans-serif stack */
31
31
  const SANS =
32
- 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif';
32
+ 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif';
33
33
 
34
34
  /**
35
35
  * Editorial serif stack
@@ -38,7 +38,7 @@ const SANS =
38
38
  * Charter (macOS); Cambria / Sitka Text (Windows); Georgia (universal)
39
39
  */
40
40
  const EDITORIAL_SERIF =
41
- 'ui-serif, "Iowan Old Style", Charter, "Bitstream Charter", Cambria, "Sitka Text", Georgia, "Songti SC", "Noto Serif CJK SC", "STSong", "SimSun", serif';
41
+ 'ui-serif, "New York Small", "New York", "Iowan Old Style", Charter, "Bitstream Charter", Cambria, "Sitka Text", Georgia, "Songti SC", "Noto Serif CJK SC", "STSong", "SimSun", serif';
42
42
 
43
43
  /**
44
44
  * Classical serif stack
@@ -113,11 +113,11 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
113
113
  {...(isAuthenticated ? { "data-authenticated": true } : {})}
114
114
  >
115
115
  {content}
116
- <div id="toast-container" class="toast-container">
116
+ <div id="toast-container" class="toast-container" popover="manual">
117
117
  {toast && (
118
118
  <div
119
119
  class={`toast ${toast.type === "error" ? "toast-error" : "toast-success"}`}
120
- data-init="history.replaceState({}, '', location.pathname); setTimeout(() => { el.classList.add('toast-out'); el.addEventListener('animationend', () => el.remove()) }, 3000)"
120
+ data-init="el.closest('[popover]').showPopover(); history.replaceState({}, '', location.pathname); setTimeout(() => { el.classList.add('toast-out'); el.addEventListener('animationend', () => el.remove()) }, 3000)"
121
121
  >
122
122
  {toast.type === "error" ? (
123
123
  <svg
@@ -37,10 +37,15 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
37
37
  showHeaderAvatar,
38
38
  siteFooterHtml,
39
39
  sidebar,
40
+ uploadMaxFileSize,
41
+ showComposeDialog = true,
42
+ showHeader = true,
40
43
  children,
41
44
  }) => {
42
45
  const { t } = useLingui();
43
46
  const maxVisible = headerNavMaxVisible ?? 3;
47
+ const overflowLinks = links.slice(maxVisible);
48
+ const hasActiveOverflow = overflowLinks.some((l) => l.isActive);
44
49
 
45
50
  const latestHref = homeDefaultView === "featured" ? "/latest" : "/";
46
51
  const featuredHref = homeDefaultView === "featured" ? "/" : "/featured";
@@ -78,115 +83,118 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
78
83
 
79
84
  return (
80
85
  <div class="site-page">
81
- <header class={`site-header ${sidebar ? "site-header-sidebar" : ""}`}>
82
- <div class="site-header-inner">
83
- <div class="site-header-top site-header-top-bordered">
84
- <a href="/" class="site-logo">
85
- {showHeaderAvatar && siteAvatarUrl && (
86
- <img src={siteAvatarUrl} class="site-logo-avatar" alt="" />
87
- )}
88
- {siteName}
89
- </a>
90
- <div class="site-header-right">
91
- {links.length > 0 && (
92
- <nav class="site-header-nav">
93
- {links.slice(0, maxVisible).map((link) => (
94
- <HeaderLink key={link.id} link={link} />
95
- ))}
96
- {links.length > maxVisible && (
97
- <div class="dropdown-menu site-header-more">
98
- <button
99
- type="button"
100
- id="site-nav-more-trigger"
101
- class="site-header-more-btn"
102
- aria-haspopup="menu"
103
- aria-controls="site-nav-more-menu"
104
- aria-expanded="false"
105
- aria-label={t({
106
- message: "More links",
107
- comment:
108
- "@context: Button to show overflow nav links",
109
- })}
110
- >
111
- <svg
112
- xmlns="http://www.w3.org/2000/svg"
113
- width="16"
114
- height="16"
115
- viewBox="0 0 24 24"
116
- fill="currentColor"
86
+ {showHeader && (
87
+ <header class="site-header">
88
+ <div class="site-header-inner">
89
+ <div class="site-header-top site-header-top-bordered">
90
+ <a href="/" class="site-logo">
91
+ {showHeaderAvatar && siteAvatarUrl && (
92
+ <img src={siteAvatarUrl} class="site-logo-avatar" alt="" />
93
+ )}
94
+ {siteName}
95
+ </a>
96
+ <div class="site-header-right">
97
+ {links.length > 0 && (
98
+ <nav class="site-header-nav">
99
+ {links.slice(0, maxVisible).map((link) => (
100
+ <HeaderLink key={link.id} link={link} />
101
+ ))}
102
+ {overflowLinks.length > 0 && (
103
+ <div class="dropdown-menu site-header-more">
104
+ <button
105
+ type="button"
106
+ id="site-nav-more-trigger"
107
+ class={`site-header-more-btn ${hasActiveOverflow ? "site-header-more-btn-active" : ""}`}
108
+ aria-haspopup="menu"
109
+ aria-controls="site-nav-more-menu"
110
+ aria-expanded="false"
111
+ aria-label={t({
112
+ message: "More links",
113
+ comment:
114
+ "@context: Button to show overflow nav links",
115
+ })}
117
116
  >
118
- <circle cx="5" cy="12" r="2" />
119
- <circle cx="12" cy="12" r="2" />
120
- <circle cx="19" cy="12" r="2" />
121
- </svg>
122
- </button>
123
- <div
124
- id="site-nav-more-popover"
125
- data-popover
126
- data-align="end"
127
- aria-hidden="true"
128
- >
117
+ <svg
118
+ xmlns="http://www.w3.org/2000/svg"
119
+ width="16"
120
+ height="16"
121
+ viewBox="0 0 24 24"
122
+ fill="currentColor"
123
+ >
124
+ <circle cx="5" cy="12" r="2" />
125
+ <circle cx="12" cy="12" r="2" />
126
+ <circle cx="19" cy="12" r="2" />
127
+ </svg>
128
+ </button>
129
129
  <div
130
- role="menu"
131
- id="site-nav-more-menu"
132
- aria-labelledby="site-nav-more-trigger"
130
+ id="site-nav-more-popover"
131
+ data-popover
132
+ data-align="end"
133
+ aria-hidden="true"
133
134
  >
134
- {links.slice(maxVisible).map((link) => (
135
- <a
136
- key={link.id}
137
- href={link.url}
138
- role="menuitem"
139
- {...(link.isExternal
140
- ? {
141
- target: "_blank",
142
- rel: "noopener noreferrer",
143
- }
144
- : {})}
145
- >
146
- {link.label}
147
- </a>
148
- ))}
135
+ <div
136
+ role="menu"
137
+ id="site-nav-more-menu"
138
+ aria-labelledby="site-nav-more-trigger"
139
+ >
140
+ {overflowLinks.map((link) => (
141
+ <a
142
+ key={link.id}
143
+ href={link.url}
144
+ role="menuitem"
145
+ class={
146
+ link.isActive
147
+ ? "site-header-menuitem-active"
148
+ : undefined
149
+ }
150
+ {...(link.isExternal
151
+ ? {
152
+ target: "_blank",
153
+ rel: "noopener noreferrer",
154
+ }
155
+ : {})}
156
+ >
157
+ {link.label}
158
+ </a>
159
+ ))}
160
+ </div>
149
161
  </div>
150
162
  </div>
151
- </div>
152
- )}
153
- </nav>
154
- )}
155
- <a
156
- href="/search"
157
- class={`site-header-search ${currentPath === "/search" ? "site-header-search-active" : ""}`}
158
- aria-label={searchLabel}
159
- title={searchLabel}
160
- >
161
- <svg
162
- xmlns="http://www.w3.org/2000/svg"
163
- width="16"
164
- height="16"
165
- viewBox="0 0 24 24"
166
- fill="none"
167
- stroke="currentColor"
168
- stroke-width="2"
169
- stroke-linecap="round"
170
- stroke-linejoin="round"
163
+ )}
164
+ </nav>
165
+ )}
166
+ <a
167
+ href="/search"
168
+ class={`site-header-search ${currentPath === "/search" ? "site-header-search-active" : ""}`}
169
+ aria-label={searchLabel}
170
+ title={searchLabel}
171
171
  >
172
- <circle cx="11" cy="11" r="8" />
173
- <path d="m21 21-4.35-4.35" />
174
- </svg>
175
- </a>
172
+ <svg
173
+ xmlns="http://www.w3.org/2000/svg"
174
+ width="16"
175
+ height="16"
176
+ viewBox="0 0 24 24"
177
+ fill="none"
178
+ stroke="currentColor"
179
+ stroke-width="2"
180
+ stroke-linecap="round"
181
+ stroke-linejoin="round"
182
+ >
183
+ <circle cx="11" cy="11" r="8" />
184
+ <path d="m21 21-4.35-4.35" />
185
+ </svg>
186
+ </a>
187
+ </div>
176
188
  </div>
177
189
  </div>
178
- </div>
179
- </header>
190
+ </header>
191
+ )}
180
192
 
181
193
  <main class="site-main">
182
194
  {sidebar ? (
183
- <div class="container-sidebar">
184
- <div class="sidebar-layout">
185
- <aside class="sidebar-nav">{sidebar}</aside>
186
- <div class="sidebar-main">
187
- <div class="site-content">{children}</div>
188
- </div>
189
- </div>
195
+ <div class="site-container site-container-sidebar">
196
+ <aside class="sidebar-nav">{sidebar}</aside>
197
+ <div class="site-content">{children}</div>
190
198
  </div>
191
199
  ) : (
192
200
  <div class="site-container">
@@ -221,10 +229,7 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
221
229
  </main>
222
230
 
223
231
  {siteFooterHtml && (
224
- <footer
225
- class={`site-footer ${sidebar ? "site-footer-sidebar" : ""}`}
226
- data-footer
227
- >
232
+ <footer class="site-footer" data-footer>
228
233
  <div class="site-container">
229
234
  <div
230
235
  class="prose"
@@ -234,7 +239,15 @@ export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
234
239
  </footer>
235
240
  )}
236
241
 
237
- {isAuthenticated && <ComposeDialog collections={collections} />}
242
+ <jant-media-lightbox />
243
+ <jant-text-preview />
244
+ {isAuthenticated && <jant-post-menu />}
245
+ {isAuthenticated && showComposeDialog && (
246
+ <ComposeDialog
247
+ collections={collections}
248
+ uploadMaxFileSize={uploadMaxFileSize}
249
+ />
250
+ )}
238
251
  </div>
239
252
  );
240
253
  };