@jant/core 0.3.24 → 0.3.26

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 (277) hide show
  1. package/dist/app.js +101 -571
  2. package/dist/client.js +1 -0
  3. package/dist/db/schema.js +1 -1
  4. package/dist/i18n/locales/en.js +1 -1
  5. package/dist/i18n/locales/zh-Hans.js +1 -1
  6. package/dist/i18n/locales/zh-Hant.js +1 -1
  7. package/dist/index.js +3 -9
  8. package/dist/lib/avatar-upload.js +134 -0
  9. package/dist/lib/config.js +39 -0
  10. package/dist/lib/constants.js +10 -9
  11. package/dist/lib/favicon.js +102 -0
  12. package/dist/lib/image.js +13 -17
  13. package/dist/lib/media-helpers.js +2 -2
  14. package/dist/lib/nav-reorder.js +1 -1
  15. package/dist/lib/navigation.js +48 -3
  16. package/dist/lib/pagination.js +44 -0
  17. package/dist/lib/render.js +16 -11
  18. package/dist/lib/schemas.js +34 -3
  19. package/dist/lib/theme.js +4 -4
  20. package/dist/lib/timeline.js +24 -48
  21. package/dist/lib/timezones.js +388 -0
  22. package/dist/lib/view.js +3 -3
  23. package/dist/routes/api/collections.js +124 -0
  24. package/dist/routes/api/nav-items.js +104 -0
  25. package/dist/routes/api/pages.js +91 -0
  26. package/dist/routes/api/posts.js +3 -3
  27. package/dist/routes/api/search.js +2 -2
  28. package/dist/routes/api/settings.js +68 -0
  29. package/dist/routes/api/upload.js +3 -3
  30. package/dist/routes/auth/reset.js +221 -0
  31. package/dist/routes/auth/setup.js +194 -0
  32. package/dist/routes/auth/signin.js +176 -0
  33. package/dist/routes/compose.js +48 -0
  34. package/dist/routes/dash/collections.js +24 -416
  35. package/dist/routes/dash/index.js +1 -1
  36. package/dist/routes/dash/media.js +13 -393
  37. package/dist/routes/dash/pages.js +112 -86
  38. package/dist/routes/dash/posts.js +3 -5
  39. package/dist/routes/dash/redirects.js +20 -14
  40. package/dist/routes/dash/settings.js +213 -518
  41. package/dist/routes/feed/rss.js +4 -3
  42. package/dist/routes/feed/sitemap.js +5 -3
  43. package/dist/routes/pages/archive.js +3 -6
  44. package/dist/routes/pages/collection.js +3 -6
  45. package/dist/routes/pages/collections.js +28 -0
  46. package/dist/routes/pages/featured.js +36 -0
  47. package/dist/routes/pages/home.js +33 -49
  48. package/dist/routes/pages/latest.js +45 -0
  49. package/dist/routes/pages/page.js +29 -32
  50. package/dist/routes/pages/post.js +3 -6
  51. package/dist/routes/pages/search.js +3 -6
  52. package/dist/services/page.js +5 -1
  53. package/dist/services/post.js +45 -31
  54. package/dist/services/search.js +1 -1
  55. package/dist/types/bindings.js +3 -0
  56. package/dist/types/config.js +147 -0
  57. package/dist/types/constants.js +27 -0
  58. package/dist/types/entities.js +3 -0
  59. package/dist/types/operations.js +3 -0
  60. package/dist/types/props.js +3 -0
  61. package/dist/types/views.js +5 -0
  62. package/dist/types.js +8 -111
  63. package/dist/{theme → ui}/color-themes.js +33 -33
  64. package/dist/ui/compose/ComposeDialog.js +467 -0
  65. package/dist/ui/compose/ComposePrompt.js +55 -0
  66. package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +1 -2
  67. package/dist/{theme/components → ui/dash}/PageForm.js +21 -15
  68. package/dist/{theme/components → ui/dash}/PostForm.js +22 -43
  69. package/dist/{theme/components → ui/dash}/PostList.js +6 -6
  70. package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
  71. package/dist/ui/dash/collections/CollectionForm.js +152 -0
  72. package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
  73. package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
  74. package/dist/{theme/components → ui/dash}/index.js +3 -6
  75. package/dist/ui/dash/media/MediaListContent.js +166 -0
  76. package/dist/ui/dash/media/ViewMediaContent.js +212 -0
  77. package/dist/ui/dash/pages/LinkFormContent.js +130 -0
  78. package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
  79. package/dist/ui/dash/settings/AccountContent.js +209 -0
  80. package/dist/ui/dash/settings/AppearanceContent.js +259 -0
  81. package/dist/ui/dash/settings/GeneralContent.js +536 -0
  82. package/dist/ui/dash/settings/SettingsNav.js +41 -0
  83. package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
  84. package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
  85. package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
  86. package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
  87. package/dist/ui/feed/TimelineFeed.js +41 -0
  88. package/dist/ui/feed/TimelineItem.js +27 -0
  89. package/dist/ui/font-themes.js +36 -0
  90. package/dist/{theme → ui}/layouts/BaseLayout.js +34 -2
  91. package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
  92. package/dist/ui/layouts/SiteLayout.js +169 -0
  93. package/dist/{themes/threads → ui}/pages/ArchivePage.js +16 -14
  94. package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
  95. package/dist/ui/pages/CollectionsPage.js +76 -0
  96. package/dist/ui/pages/FeaturedPage.js +24 -0
  97. package/dist/ui/pages/HomePage.js +24 -0
  98. package/dist/{themes/threads → ui}/pages/PostPage.js +13 -8
  99. package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
  100. package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
  101. package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
  102. package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
  103. package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
  104. package/dist/ui/shared/index.js +5 -0
  105. package/package.json +1 -9
  106. package/src/__tests__/helpers/db.ts +3 -0
  107. package/src/app.tsx +131 -561
  108. package/src/client.ts +1 -0
  109. package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
  110. package/src/db/migrations/meta/_journal.json +7 -0
  111. package/src/db/schema.ts +1 -1
  112. package/src/i18n/locales/en.po +477 -261
  113. package/src/i18n/locales/en.ts +1 -1
  114. package/src/i18n/locales/zh-Hans.po +477 -261
  115. package/src/i18n/locales/zh-Hans.ts +1 -1
  116. package/src/i18n/locales/zh-Hant.po +477 -261
  117. package/src/i18n/locales/zh-Hant.ts +1 -1
  118. package/src/index.ts +7 -36
  119. package/src/lib/__tests__/config.test.ts +192 -0
  120. package/src/lib/__tests__/favicon.test.ts +151 -0
  121. package/src/lib/__tests__/image.test.ts +2 -6
  122. package/src/lib/__tests__/schemas.test.ts +60 -19
  123. package/src/lib/__tests__/timeline.test.ts +45 -81
  124. package/src/lib/__tests__/timezones.test.ts +61 -0
  125. package/src/lib/__tests__/view.test.ts +15 -9
  126. package/src/lib/avatar-upload.ts +165 -0
  127. package/src/lib/config.ts +47 -0
  128. package/src/lib/constants.ts +19 -10
  129. package/src/lib/favicon.ts +115 -0
  130. package/src/lib/image.ts +13 -21
  131. package/src/lib/media-helpers.ts +2 -2
  132. package/src/lib/nav-reorder.ts +1 -1
  133. package/src/lib/navigation.ts +73 -4
  134. package/src/lib/pagination.ts +50 -0
  135. package/src/lib/render.tsx +22 -15
  136. package/src/lib/schemas.ts +47 -6
  137. package/src/lib/theme.ts +5 -5
  138. package/src/lib/timeline.ts +28 -57
  139. package/src/lib/timezones.ts +325 -0
  140. package/src/lib/view.ts +3 -3
  141. package/src/preset.css +2 -1
  142. package/src/routes/__tests__/compose.test.ts +199 -0
  143. package/src/routes/api/__tests__/collections.test.ts +249 -0
  144. package/src/routes/api/__tests__/nav-items.test.ts +222 -0
  145. package/src/routes/api/__tests__/pages.test.ts +218 -0
  146. package/src/routes/api/__tests__/settings.test.ts +132 -0
  147. package/src/routes/api/collections.ts +143 -0
  148. package/src/routes/api/nav-items.ts +115 -0
  149. package/src/routes/api/pages.ts +101 -0
  150. package/src/routes/api/posts.ts +3 -3
  151. package/src/routes/api/search.ts +2 -2
  152. package/src/routes/api/settings.ts +91 -0
  153. package/src/routes/api/upload.ts +2 -3
  154. package/src/routes/auth/reset.tsx +239 -0
  155. package/src/routes/auth/setup.tsx +189 -0
  156. package/src/routes/auth/signin.tsx +163 -0
  157. package/src/routes/compose.ts +63 -0
  158. package/src/routes/dash/__tests__/pages.test.ts +225 -0
  159. package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
  160. package/src/routes/dash/collections.tsx +18 -367
  161. package/src/routes/dash/index.tsx +1 -1
  162. package/src/routes/dash/media.tsx +13 -415
  163. package/src/routes/dash/pages.tsx +131 -98
  164. package/src/routes/dash/posts.tsx +3 -7
  165. package/src/routes/dash/redirects.tsx +22 -16
  166. package/src/routes/dash/settings.tsx +265 -478
  167. package/src/routes/feed/__tests__/rss.test.ts +141 -0
  168. package/src/routes/feed/rss.ts +5 -3
  169. package/src/routes/feed/sitemap.ts +5 -3
  170. package/src/routes/pages/__tests__/collections.test.ts +94 -0
  171. package/src/routes/pages/__tests__/featured.test.ts +94 -0
  172. package/src/routes/pages/archive.tsx +2 -6
  173. package/src/routes/pages/collection.tsx +2 -6
  174. package/src/routes/pages/collections.tsx +36 -0
  175. package/src/routes/pages/featured.tsx +44 -0
  176. package/src/routes/pages/home.tsx +30 -53
  177. package/src/routes/pages/latest.tsx +59 -0
  178. package/src/routes/pages/page.tsx +28 -30
  179. package/src/routes/pages/post.tsx +2 -5
  180. package/src/routes/pages/search.tsx +2 -6
  181. package/src/services/__tests__/page.test.ts +106 -0
  182. package/src/services/__tests__/post.test.ts +114 -15
  183. package/src/services/page.ts +13 -1
  184. package/src/services/post.ts +58 -40
  185. package/src/services/search.ts +2 -2
  186. package/src/styles/components.css +0 -65
  187. package/src/styles/tokens.css +47 -0
  188. package/src/styles/ui.css +475 -0
  189. package/src/types/bindings.ts +30 -0
  190. package/src/types/config.ts +183 -0
  191. package/src/types/constants.ts +26 -0
  192. package/src/types/entities.ts +109 -0
  193. package/src/types/operations.ts +88 -0
  194. package/src/types/props.ts +115 -0
  195. package/src/types/views.ts +172 -0
  196. package/src/types.ts +8 -774
  197. package/src/ui/__tests__/font-themes.test.ts +34 -0
  198. package/src/{theme → ui}/color-themes.ts +34 -34
  199. package/src/ui/compose/ComposeDialog.tsx +414 -0
  200. package/src/ui/compose/ComposePrompt.tsx +55 -0
  201. package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
  202. package/src/{theme/components → ui/dash}/PageForm.tsx +25 -19
  203. package/src/{theme/components → ui/dash}/PostForm.tsx +26 -45
  204. package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
  205. package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
  206. package/src/ui/dash/collections/CollectionForm.tsx +153 -0
  207. package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
  208. package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
  209. package/src/ui/dash/index.ts +10 -0
  210. package/src/ui/dash/media/MediaListContent.tsx +201 -0
  211. package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
  212. package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
  213. package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
  214. package/src/ui/dash/settings/AccountContent.tsx +176 -0
  215. package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
  216. package/src/ui/dash/settings/GeneralContent.tsx +533 -0
  217. package/src/ui/dash/settings/SettingsNav.tsx +56 -0
  218. package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
  219. package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
  220. package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
  221. package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
  222. package/src/ui/feed/TimelineFeed.tsx +49 -0
  223. package/src/ui/feed/TimelineItem.tsx +45 -0
  224. package/src/ui/font-themes.ts +54 -0
  225. package/src/{theme → ui}/layouts/BaseLayout.tsx +28 -1
  226. package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
  227. package/src/ui/layouts/SiteLayout.tsx +164 -0
  228. package/src/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
  229. package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
  230. package/src/ui/pages/CollectionsPage.tsx +73 -0
  231. package/src/ui/pages/FeaturedPage.tsx +31 -0
  232. package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
  233. package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
  234. package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
  235. package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
  236. package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
  237. package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
  238. package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
  239. package/src/ui/shared/__tests__/pagination.test.ts +46 -0
  240. package/src/ui/shared/index.ts +12 -0
  241. package/bin/jant.js +0 -185
  242. package/dist/lib/theme-components.js +0 -46
  243. package/dist/routes/dash/navigation.js +0 -289
  244. package/dist/theme/index.js +0 -18
  245. package/dist/theme/layouts/index.js +0 -2
  246. package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
  247. package/dist/themes/threads/index.js +0 -81
  248. package/dist/themes/threads/pages/HomePage.js +0 -25
  249. package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
  250. package/dist/themes/threads/timeline/TimelineItem.js +0 -36
  251. package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
  252. package/dist/themes/threads/timeline/groupByDate.js +0 -22
  253. package/dist/themes/threads/timeline/timelineMore.js +0 -107
  254. package/src/lib/__tests__/theme-components.test.ts +0 -105
  255. package/src/lib/theme-components.ts +0 -65
  256. package/src/routes/dash/navigation.tsx +0 -317
  257. package/src/theme/components/index.ts +0 -23
  258. package/src/theme/index.ts +0 -22
  259. package/src/theme/layouts/index.ts +0 -7
  260. package/src/themes/threads/ThreadsSiteLayout.tsx +0 -194
  261. package/src/themes/threads/index.ts +0 -100
  262. package/src/themes/threads/style.css +0 -336
  263. package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
  264. package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
  265. package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
  266. package/src/themes/threads/timeline/groupByDate.ts +0 -30
  267. package/src/themes/threads/timeline/timelineMore.tsx +0 -130
  268. /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
  269. /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
  270. /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
  271. /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
  272. /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
  273. /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
  274. /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
  275. /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
  276. /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
  277. /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
@@ -1,58 +0,0 @@
1
- /**
2
- * Threads Theme - Timeline Feed
3
- *
4
- * Date-grouped posts separated by thin dividers.
5
- * A centered date header appears above each group.
6
- */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
7
- import { TimelineItem } from "./TimelineItem.js";
8
- import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
9
- import { TimelineLoadMore as DefaultTimelineLoadMore } from "./TimelineLoadMore.js";
10
- import { groupByDate } from "./groupByDate.js";
11
- export const TimelineFeed = ({ items, hasMore, nextCursor, theme })=>{
12
- const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
13
- const ResolvedLoadMore = theme?.TimelineLoadMore ?? DefaultTimelineLoadMore;
14
- const groups = groupByDate(items);
15
- return /*#__PURE__*/ _jsxs("div", {
16
- children: [
17
- /*#__PURE__*/ _jsx("div", {
18
- id: "timeline-feed",
19
- children: groups.map((group)=>/*#__PURE__*/ _jsxs("div", {
20
- class: "threads-card",
21
- children: [
22
- /*#__PURE__*/ _jsx("div", {
23
- class: "threads-date-header",
24
- children: /*#__PURE__*/ _jsx("span", {
25
- children: group.label
26
- })
27
- }),
28
- /*#__PURE__*/ _jsx("div", {
29
- id: `date-items-${group.dateKey}`,
30
- class: "flex flex-col",
31
- children: group.items.map((item, i)=>/*#__PURE__*/ _jsxs("div", {
32
- children: [
33
- i > 0 && /*#__PURE__*/ _jsx("hr", {
34
- class: "border-border my-5"
35
- }),
36
- item.threadPreview ? /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
37
- rootPost: item.post,
38
- previewReplies: item.threadPreview.replies,
39
- totalReplyCount: item.threadPreview.totalReplyCount,
40
- theme: theme
41
- }) : /*#__PURE__*/ _jsx(TimelineItem, {
42
- item: item,
43
- theme: theme
44
- })
45
- ]
46
- }, item.post.id))
47
- })
48
- ]
49
- }, group.dateKey))
50
- }),
51
- hasMore && nextCursor && /*#__PURE__*/ _jsx(ResolvedLoadMore, {
52
- nextCursor: nextCursor,
53
- lastDate: groups.at(-1)?.dateKey,
54
- theme: theme
55
- })
56
- ]
57
- });
58
- };
@@ -1,36 +0,0 @@
1
- /**
2
- * Threads Theme - Timeline Item
3
- *
4
- * Dispatches to the correct card component based on post format.
5
- */ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
6
- import { NoteCard } from "./NoteCard.js";
7
- import { LinkCard } from "./LinkCard.js";
8
- import { QuoteCard } from "./QuoteCard.js";
9
- const CARD_MAP = {
10
- note: NoteCard,
11
- link: LinkCard,
12
- quote: QuoteCard
13
- };
14
- const THEME_KEY_MAP = {
15
- note: "NoteCard",
16
- link: "LinkCard",
17
- quote: "QuoteCard"
18
- };
19
- export const TimelineItem = ({ item, compact, cardOverride, theme })=>{
20
- const themeKey = THEME_KEY_MAP[item.post.format];
21
- const themeCard = theme?.[themeKey];
22
- const Card = cardOverride ?? themeCard ?? CARD_MAP[item.post.format];
23
- return /*#__PURE__*/ _jsx(Card, {
24
- post: item.post,
25
- compact: compact
26
- });
27
- };
28
- export const TimelineItemFromPost = ({ post, compact, cardOverride, theme })=>{
29
- const themeKey = THEME_KEY_MAP[post.format];
30
- const themeCard = theme?.[themeKey];
31
- const Card = cardOverride ?? themeCard ?? CARD_MAP[post.format];
32
- return /*#__PURE__*/ _jsx(Card, {
33
- post: post,
34
- compact: compact
35
- });
36
- };
@@ -1,23 +0,0 @@
1
- /**
2
- * Threads Theme - Timeline Load More
3
- *
4
- * Auto-loads more posts when scrolled into view.
5
- * Passes `lastDate` to the server so it can merge date groups across pages.
6
- */ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
7
- import { useLingui as $_useLingui } from "@jant/core/i18n";
8
- export const TimelineLoadMore = ({ nextCursor, lastDate })=>{
9
- const { i18n: $__i18n, _: $__ } = $_useLingui();
10
- const url = lastDate ? `/?cursor=${nextCursor}&lastDate=${lastDate}` : `/?cursor=${nextCursor}`;
11
- return /*#__PURE__*/ _jsx("div", {
12
- id: "load-more-container",
13
- class: "py-6 text-center",
14
- "data-on-intersect__once": `@get('${url}')`,
15
- children: /*#__PURE__*/ _jsx("span", {
16
- class: "text-sm text-muted-foreground",
17
- children: $__i18n._({
18
- id: "Z3FXyt",
19
- message: "Loading..."
20
- })
21
- })
22
- });
23
- };
@@ -1,22 +0,0 @@
1
- /**
2
- * Groups timeline items by their publication date (YYYY-MM-DD).
3
- *
4
- * Shared between TimelineFeed (initial render) and timelineMore (SSE patches)
5
- * so that both produce identical date group structure.
6
- */ export function groupByDate(items) {
7
- const groups = [];
8
- let current = null;
9
- for (const item of items){
10
- const dateKey = item.post.publishedAt.slice(0, 10);
11
- if (!current || current.dateKey !== dateKey) {
12
- current = {
13
- dateKey,
14
- label: item.post.publishedAtFormatted,
15
- items: []
16
- };
17
- groups.push(current);
18
- }
19
- current.items.push(item);
20
- }
21
- return groups;
22
- }
@@ -1,107 +0,0 @@
1
- /**
2
- * Threads Theme - Timeline Load-More SSE Renderer
3
- *
4
- * Produces SSE DOM patches for incremental timeline loading.
5
- * Uses date-grouped layout with threads-specific HTML (threads-card, threads-date-header)
6
- * matching TimelineFeed's initial render exactly.
7
- */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
8
- import { groupByDate } from "./groupByDate.js";
9
- import { TimelineItem } from "./TimelineItem.js";
10
- import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
11
- import { TimelineLoadMore as DefaultTimelineLoadMore } from "./TimelineLoadMore.js";
12
- function renderItem(item, props) {
13
- const theme = props.theme;
14
- const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
15
- if (item.threadPreview) {
16
- return /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
17
- rootPost: item.post,
18
- previewReplies: item.threadPreview.replies,
19
- totalReplyCount: item.threadPreview.totalReplyCount,
20
- theme: theme
21
- }).toString();
22
- }
23
- return /*#__PURE__*/ _jsx(TimelineItem, {
24
- item: item,
25
- theme: theme
26
- }).toString();
27
- }
28
- /**
29
- * Renders SSE patches for the threads theme's load-more response.
30
- *
31
- * @param props - Timeline more props with items, pagination, and theme
32
- * @returns Array of DOM patch instructions for the SSE stream
33
- */ export function timelineMore(props) {
34
- const { items, lastDate, hasMore, nextCursor, theme } = props;
35
- const patches = [];
36
- const groups = groupByDate(items);
37
- if (groups.length === 0) return patches;
38
- const firstGroup = groups[0];
39
- const isContinuation = lastDate === firstGroup.dateKey;
40
- // Continuation items: append into the existing date group's container
41
- if (isContinuation) {
42
- const continuationHtml = firstGroup.items.map((item)=>{
43
- const content = renderItem(item, props);
44
- return `<div><hr class="border-border my-5"/>${content}</div>`;
45
- }).join("");
46
- if (continuationHtml) {
47
- patches.push({
48
- selector: `#date-items-${lastDate}`,
49
- content: continuationHtml,
50
- mode: "append"
51
- });
52
- }
53
- }
54
- // New date groups: append to #timeline-feed
55
- const newGroups = isContinuation ? groups.slice(1) : groups;
56
- if (newGroups.length > 0) {
57
- const newGroupsHtml = newGroups.map((group)=>{
58
- const itemsHtml = group.items.map((item, i)=>{
59
- const content = renderItem(item, props);
60
- return i > 0 ? `<div><hr class="border-border my-5"/>${content}</div>` : `<div>${content}</div>`;
61
- }).join("");
62
- return /*#__PURE__*/ _jsxs("div", {
63
- class: "threads-card",
64
- children: [
65
- /*#__PURE__*/ _jsx("div", {
66
- class: "threads-date-header",
67
- children: /*#__PURE__*/ _jsx("span", {
68
- children: group.label
69
- })
70
- }),
71
- /*#__PURE__*/ _jsx("div", {
72
- id: `date-items-${group.dateKey}`,
73
- class: "flex flex-col",
74
- dangerouslySetInnerHTML: {
75
- __html: itemsHtml
76
- }
77
- })
78
- ]
79
- }).toString();
80
- }).join("");
81
- patches.push({
82
- selector: "#timeline-feed",
83
- content: newGroupsHtml,
84
- mode: "append"
85
- });
86
- }
87
- // Load-more button
88
- const ResolvedLoadMore = theme?.TimelineLoadMore ?? DefaultTimelineLoadMore;
89
- const lastGroupDate = groups.at(-1)?.dateKey;
90
- if (hasMore && nextCursor) {
91
- patches.push({
92
- selector: "#load-more-container",
93
- content: /*#__PURE__*/ _jsx(ResolvedLoadMore, {
94
- nextCursor: nextCursor,
95
- lastDate: lastGroupDate,
96
- theme: theme
97
- }).toString()
98
- });
99
- } else {
100
- patches.push({
101
- selector: "#load-more-container",
102
- content: "",
103
- mode: "remove"
104
- });
105
- }
106
- return patches;
107
- }
@@ -1,105 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { resolveCardComponent, resolveComponent } from "../theme-components.js";
3
- import type {
4
- ThemeComponents,
5
- TimelineCardProps,
6
- ThreadPreviewProps,
7
- TimelineFeedProps,
8
- Format,
9
- HomePageProps,
10
- } from "../../types.js";
11
- import type { FC } from "hono/jsx";
12
-
13
- // Create simple mock components for testing (avoids importing .tsx files with i18n)
14
- const MockNoteCard: FC<TimelineCardProps> = () => null;
15
- const MockLinkCard: FC<TimelineCardProps> = () => null;
16
- const MockQuoteCard: FC<TimelineCardProps> = () => null;
17
- const MockThreadPreview: FC<ThreadPreviewProps> = () => null;
18
- const MockTimelineFeed: FC<TimelineFeedProps> = () => null;
19
- const MockHomePage: FC<HomePageProps> = () => null;
20
-
21
- const DEFAULT_CARD_MAP: Record<Format, FC<TimelineCardProps>> = {
22
- note: MockNoteCard,
23
- link: MockLinkCard,
24
- quote: MockQuoteCard,
25
- };
26
-
27
- describe("theme-components", () => {
28
- describe("resolveCardComponent", () => {
29
- it("returns default NoteCard for note type", () => {
30
- expect(resolveCardComponent("note", DEFAULT_CARD_MAP)).toBe(MockNoteCard);
31
- });
32
-
33
- it("returns default LinkCard for link type", () => {
34
- expect(resolveCardComponent("link", DEFAULT_CARD_MAP)).toBe(MockLinkCard);
35
- });
36
-
37
- it("returns default QuoteCard for quote type", () => {
38
- expect(resolveCardComponent("quote", DEFAULT_CARD_MAP)).toBe(
39
- MockQuoteCard,
40
- );
41
- });
42
-
43
- it("returns theme override when provided", () => {
44
- const CustomNote: FC<TimelineCardProps> = () => null;
45
- const overrides: ThemeComponents = { NoteCard: CustomNote };
46
- expect(resolveCardComponent("note", DEFAULT_CARD_MAP, overrides)).toBe(
47
- CustomNote,
48
- );
49
- });
50
-
51
- it("returns default when theme has no override for type", () => {
52
- const overrides: ThemeComponents = {};
53
- expect(resolveCardComponent("link", DEFAULT_CARD_MAP, overrides)).toBe(
54
- MockLinkCard,
55
- );
56
- });
57
- });
58
-
59
- describe("resolveComponent", () => {
60
- it("returns default ThreadPreview when no override", () => {
61
- expect(resolveComponent("ThreadPreview", MockThreadPreview)).toBe(
62
- MockThreadPreview,
63
- );
64
- });
65
-
66
- it("returns theme override for ThreadPreview when provided", () => {
67
- const Custom: FC<ThreadPreviewProps> = () => null;
68
- expect(
69
- resolveComponent("ThreadPreview", MockThreadPreview, {
70
- ThreadPreview: Custom,
71
- }),
72
- ).toBe(Custom);
73
- });
74
-
75
- it("returns default TimelineFeed when no override", () => {
76
- expect(resolveComponent("TimelineFeed", MockTimelineFeed)).toBe(
77
- MockTimelineFeed,
78
- );
79
- });
80
-
81
- it("returns theme override for TimelineFeed when provided", () => {
82
- const Custom: FC<TimelineFeedProps> = () => null;
83
- expect(
84
- resolveComponent("TimelineFeed", MockTimelineFeed, {
85
- TimelineFeed: Custom,
86
- }),
87
- ).toBe(Custom);
88
- });
89
-
90
- it("returns default HomePage when no override", () => {
91
- expect(resolveComponent("HomePage", MockHomePage)).toBe(MockHomePage);
92
- });
93
-
94
- it("returns theme override for HomePage when provided", () => {
95
- const Custom: FC<HomePageProps> = () => null;
96
- expect(
97
- resolveComponent("HomePage", MockHomePage, { HomePage: Custom }),
98
- ).toBe(Custom);
99
- });
100
-
101
- it("returns default when theme has empty overrides", () => {
102
- expect(resolveComponent("HomePage", MockHomePage, {})).toBe(MockHomePage);
103
- });
104
- });
105
- });
@@ -1,65 +0,0 @@
1
- /**
2
- * Theme Component Resolution
3
- *
4
- * Resolves theme-overridable components, falling back to defaults.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import type { Format, ThemeComponents, TimelineCardProps } from "../types.js";
9
-
10
- const THEME_KEY_MAP: Record<Format, keyof ThemeComponents> = {
11
- note: "NoteCard",
12
- link: "LinkCard",
13
- quote: "QuoteCard",
14
- };
15
-
16
- /**
17
- * Generic component resolver.
18
- *
19
- * Looks up a component by key in `ThemeComponents` and falls back to the
20
- * provided default component.
21
- *
22
- * @param key - ThemeComponents key to look up
23
- * @param defaultComponent - Fallback component
24
- * @param themeComponents - Optional theme component overrides
25
- * @returns The resolved component
26
- *
27
- * @example
28
- * ```ts
29
- * const Gallery = resolveComponent("MediaGallery", DefaultMediaGallery, theme);
30
- * ```
31
- */
32
- export function resolveComponent<K extends keyof ThemeComponents>(
33
- key: K,
34
- defaultComponent: NonNullable<ThemeComponents[K]>,
35
- themeComponents?: ThemeComponents,
36
- ): NonNullable<ThemeComponents[K]> {
37
- return (themeComponents?.[key] ?? defaultComponent) as NonNullable<
38
- ThemeComponents[K]
39
- >;
40
- }
41
-
42
- /**
43
- * Resolves the card component for a given post format.
44
- *
45
- * Checks theme overrides first, then falls back to the provided default card component.
46
- *
47
- * @param format - The post format to resolve a card for
48
- * @param defaults - Map of format to default card component
49
- * @param themeComponents - Optional theme component overrides
50
- * @returns The resolved card component
51
- *
52
- * @example
53
- * ```ts
54
- * const Card = resolveCardComponent("note", DEFAULT_CARD_MAP, c.var.config.theme?.components);
55
- * ```
56
- */
57
- export function resolveCardComponent(
58
- format: Format,
59
- defaults: Record<Format, FC<TimelineCardProps>>,
60
- themeComponents?: ThemeComponents,
61
- ): FC<TimelineCardProps> {
62
- const key = THEME_KEY_MAP[format];
63
- const override = themeComponents?.[key] as FC<TimelineCardProps> | undefined;
64
- return override ?? defaults[format];
65
- }