@jant/core 0.3.24 → 0.3.25

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 (206) hide show
  1. package/dist/app.js +50 -25
  2. package/dist/db/schema.js +1 -1
  3. package/dist/i18n/locales/en.js +1 -1
  4. package/dist/i18n/locales/zh-Hans.js +1 -1
  5. package/dist/i18n/locales/zh-Hant.js +1 -1
  6. package/dist/index.js +3 -9
  7. package/dist/lib/constants.js +1 -0
  8. package/dist/lib/nav-reorder.js +1 -1
  9. package/dist/lib/navigation.js +26 -1
  10. package/dist/lib/pagination.js +44 -0
  11. package/dist/lib/render.js +7 -11
  12. package/dist/lib/schemas.js +3 -3
  13. package/dist/lib/theme.js +4 -4
  14. package/dist/lib/timeline.js +24 -48
  15. package/dist/lib/view.js +2 -2
  16. package/dist/routes/api/collections.js +124 -0
  17. package/dist/routes/api/nav-items.js +104 -0
  18. package/dist/routes/api/pages.js +91 -0
  19. package/dist/routes/api/posts.js +2 -2
  20. package/dist/routes/api/search.js +2 -2
  21. package/dist/routes/api/settings.js +68 -0
  22. package/dist/routes/compose.js +48 -0
  23. package/dist/routes/dash/collections.js +2 -2
  24. package/dist/routes/dash/index.js +1 -1
  25. package/dist/routes/dash/media.js +2 -2
  26. package/dist/routes/dash/pages.js +411 -62
  27. package/dist/routes/dash/posts.js +3 -5
  28. package/dist/routes/dash/redirects.js +2 -2
  29. package/dist/routes/dash/settings.js +79 -5
  30. package/dist/routes/feed/rss.js +2 -2
  31. package/dist/routes/feed/sitemap.js +1 -1
  32. package/dist/routes/pages/archive.js +3 -6
  33. package/dist/routes/pages/collection.js +3 -6
  34. package/dist/routes/pages/collections.js +28 -0
  35. package/dist/routes/pages/featured.js +32 -0
  36. package/dist/routes/pages/home.js +9 -50
  37. package/dist/routes/pages/page.js +29 -32
  38. package/dist/routes/pages/post.js +3 -6
  39. package/dist/routes/pages/search.js +3 -6
  40. package/dist/services/page.js +5 -1
  41. package/dist/services/post.js +40 -6
  42. package/dist/services/search.js +1 -1
  43. package/dist/ui/compose/ComposeDialog.js +452 -0
  44. package/dist/ui/compose/ComposePrompt.js +55 -0
  45. package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +1 -2
  46. package/dist/{theme/components → ui/dash}/PostForm.js +0 -27
  47. package/dist/{theme/components → ui/dash}/PostList.js +6 -6
  48. package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
  49. package/dist/{theme/components → ui/dash}/index.js +3 -6
  50. package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
  51. package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
  52. package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
  53. package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
  54. package/dist/ui/feed/TimelineFeed.js +41 -0
  55. package/dist/ui/feed/TimelineItem.js +27 -0
  56. package/dist/{theme → ui}/layouts/BaseLayout.js +10 -0
  57. package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
  58. package/dist/ui/layouts/SiteLayout.js +141 -0
  59. package/dist/{themes/threads → ui}/pages/ArchivePage.js +16 -14
  60. package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
  61. package/dist/ui/pages/CollectionsPage.js +76 -0
  62. package/dist/ui/pages/FeaturedPage.js +24 -0
  63. package/dist/ui/pages/HomePage.js +24 -0
  64. package/dist/{themes/threads → ui}/pages/PostPage.js +13 -8
  65. package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
  66. package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
  67. package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
  68. package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
  69. package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
  70. package/dist/ui/shared/index.js +5 -0
  71. package/package.json +1 -9
  72. package/src/__tests__/helpers/db.ts +3 -0
  73. package/src/app.tsx +57 -27
  74. package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
  75. package/src/db/migrations/meta/_journal.json +7 -0
  76. package/src/db/schema.ts +1 -1
  77. package/src/i18n/locales/en.po +332 -181
  78. package/src/i18n/locales/en.ts +1 -1
  79. package/src/i18n/locales/zh-Hans.po +332 -181
  80. package/src/i18n/locales/zh-Hans.ts +1 -1
  81. package/src/i18n/locales/zh-Hant.po +332 -181
  82. package/src/i18n/locales/zh-Hant.ts +1 -1
  83. package/src/index.ts +7 -36
  84. package/src/lib/__tests__/schemas.test.ts +60 -19
  85. package/src/lib/__tests__/timeline.test.ts +45 -81
  86. package/src/lib/__tests__/view.test.ts +13 -7
  87. package/src/lib/constants.ts +1 -0
  88. package/src/lib/nav-reorder.ts +1 -1
  89. package/src/lib/navigation.ts +40 -2
  90. package/src/lib/pagination.ts +50 -0
  91. package/src/lib/render.tsx +7 -14
  92. package/src/lib/schemas.ts +8 -6
  93. package/src/lib/theme.ts +5 -5
  94. package/src/lib/timeline.ts +28 -57
  95. package/src/lib/view.ts +2 -2
  96. package/src/preset.css +2 -1
  97. package/src/routes/__tests__/compose.test.ts +199 -0
  98. package/src/routes/api/__tests__/collections.test.ts +249 -0
  99. package/src/routes/api/__tests__/nav-items.test.ts +222 -0
  100. package/src/routes/api/__tests__/pages.test.ts +218 -0
  101. package/src/routes/api/__tests__/settings.test.ts +132 -0
  102. package/src/routes/api/collections.ts +143 -0
  103. package/src/routes/api/nav-items.ts +115 -0
  104. package/src/routes/api/pages.ts +101 -0
  105. package/src/routes/api/posts.ts +2 -2
  106. package/src/routes/api/search.ts +2 -2
  107. package/src/routes/api/settings.ts +91 -0
  108. package/src/routes/compose.ts +63 -0
  109. package/src/routes/dash/__tests__/pages.test.ts +225 -0
  110. package/src/routes/dash/collections.tsx +2 -2
  111. package/src/routes/dash/index.tsx +1 -1
  112. package/src/routes/dash/media.tsx +2 -2
  113. package/src/routes/dash/pages.tsx +443 -70
  114. package/src/routes/dash/posts.tsx +3 -7
  115. package/src/routes/dash/redirects.tsx +2 -2
  116. package/src/routes/dash/settings.tsx +83 -5
  117. package/src/routes/feed/rss.ts +2 -2
  118. package/src/routes/feed/sitemap.ts +1 -1
  119. package/src/routes/pages/__tests__/collections.test.ts +94 -0
  120. package/src/routes/pages/__tests__/featured.test.ts +94 -0
  121. package/src/routes/pages/archive.tsx +2 -6
  122. package/src/routes/pages/collection.tsx +2 -6
  123. package/src/routes/pages/collections.tsx +36 -0
  124. package/src/routes/pages/featured.tsx +38 -0
  125. package/src/routes/pages/home.tsx +9 -55
  126. package/src/routes/pages/page.tsx +28 -30
  127. package/src/routes/pages/post.tsx +2 -5
  128. package/src/routes/pages/search.tsx +2 -6
  129. package/src/services/__tests__/page.test.ts +106 -0
  130. package/src/services/__tests__/post.test.ts +114 -15
  131. package/src/services/page.ts +13 -1
  132. package/src/services/post.ts +57 -7
  133. package/src/services/search.ts +2 -2
  134. package/src/styles/tokens.css +47 -0
  135. package/src/styles/ui.css +491 -0
  136. package/src/types.ts +29 -159
  137. package/src/ui/compose/ComposeDialog.tsx +395 -0
  138. package/src/ui/compose/ComposePrompt.tsx +55 -0
  139. package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
  140. package/src/{theme/components → ui/dash}/PostForm.tsx +0 -25
  141. package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
  142. package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
  143. package/src/ui/dash/index.ts +10 -0
  144. package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
  145. package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
  146. package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
  147. package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
  148. package/src/ui/feed/TimelineFeed.tsx +49 -0
  149. package/src/ui/feed/TimelineItem.tsx +45 -0
  150. package/src/{theme → ui}/layouts/BaseLayout.tsx +11 -1
  151. package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
  152. package/src/ui/layouts/SiteLayout.tsx +150 -0
  153. package/src/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
  154. package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
  155. package/src/ui/pages/CollectionsPage.tsx +73 -0
  156. package/src/ui/pages/FeaturedPage.tsx +31 -0
  157. package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
  158. package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
  159. package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
  160. package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
  161. package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
  162. package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
  163. package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
  164. package/src/ui/shared/__tests__/pagination.test.ts +46 -0
  165. package/src/ui/shared/index.ts +12 -0
  166. package/bin/jant.js +0 -185
  167. package/dist/lib/theme-components.js +0 -46
  168. package/dist/routes/dash/navigation.js +0 -289
  169. package/dist/theme/index.js +0 -18
  170. package/dist/theme/layouts/index.js +0 -2
  171. package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
  172. package/dist/themes/threads/index.js +0 -81
  173. package/dist/themes/threads/pages/HomePage.js +0 -25
  174. package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
  175. package/dist/themes/threads/timeline/TimelineItem.js +0 -36
  176. package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
  177. package/dist/themes/threads/timeline/groupByDate.js +0 -22
  178. package/dist/themes/threads/timeline/timelineMore.js +0 -107
  179. package/src/lib/__tests__/theme-components.test.ts +0 -105
  180. package/src/lib/theme-components.ts +0 -65
  181. package/src/routes/dash/navigation.tsx +0 -317
  182. package/src/theme/components/index.ts +0 -23
  183. package/src/theme/index.ts +0 -22
  184. package/src/theme/layouts/index.ts +0 -7
  185. package/src/themes/threads/ThreadsSiteLayout.tsx +0 -194
  186. package/src/themes/threads/index.ts +0 -100
  187. package/src/themes/threads/style.css +0 -336
  188. package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
  189. package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
  190. package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
  191. package/src/themes/threads/timeline/groupByDate.ts +0 -30
  192. package/src/themes/threads/timeline/timelineMore.tsx +0 -130
  193. /package/dist/{theme → ui}/color-themes.js +0 -0
  194. /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
  195. /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
  196. /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
  197. /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
  198. /package/dist/{theme/components → ui/dash}/PageForm.js +0 -0
  199. /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
  200. /package/src/{theme → ui}/color-themes.ts +0 -0
  201. /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
  202. /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
  203. /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
  204. /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
  205. /package/src/{theme/components → ui/dash}/PageForm.tsx +0 -0
  206. /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
@@ -1,172 +0,0 @@
1
- /**
2
- * Threads Theme - Site Layout
3
- *
4
- * Left icon sidebar (76px) on desktop, bottom tab bar (60px) on mobile.
5
- * Gray page background (#fafafa) with white rounded content container.
6
- * All dimensions match threads.com's --barcelona-* design tokens.
7
- */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
8
- /** Map known URL paths to SVG icons. Size 26x26 matching Threads' nav icons. */ function NavIcon({ url, isActive }) {
9
- const stroke = "currentColor";
10
- const sw = isActive ? "2.25" : "1.75";
11
- const cls = "size-[26px]";
12
- // Home
13
- if (url === "/") {
14
- return /*#__PURE__*/ _jsx("svg", {
15
- class: cls,
16
- fill: "none",
17
- viewBox: "0 0 24 24",
18
- "stroke-width": sw,
19
- stroke: stroke,
20
- children: /*#__PURE__*/ _jsx("path", {
21
- "stroke-linecap": "round",
22
- "stroke-linejoin": "round",
23
- d: "m2.25 12 8.954-8.955a1.126 1.126 0 0 1 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
24
- })
25
- });
26
- }
27
- // Search
28
- if (url === "/search") {
29
- return /*#__PURE__*/ _jsx("svg", {
30
- class: cls,
31
- fill: "none",
32
- viewBox: "0 0 24 24",
33
- "stroke-width": sw,
34
- stroke: stroke,
35
- children: /*#__PURE__*/ _jsx("path", {
36
- "stroke-linecap": "round",
37
- "stroke-linejoin": "round",
38
- d: "m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
39
- })
40
- });
41
- }
42
- // Archive
43
- if (url === "/archive") {
44
- return /*#__PURE__*/ _jsx("svg", {
45
- class: cls,
46
- fill: "none",
47
- viewBox: "0 0 24 24",
48
- "stroke-width": sw,
49
- stroke: stroke,
50
- children: /*#__PURE__*/ _jsx("path", {
51
- "stroke-linecap": "round",
52
- "stroke-linejoin": "round",
53
- d: "M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"
54
- })
55
- });
56
- }
57
- // RSS — common for /feed, /rss, /atom
58
- if (url.match(/\/(feed|rss|atom)/)) {
59
- return /*#__PURE__*/ _jsx("svg", {
60
- class: cls,
61
- fill: "none",
62
- viewBox: "0 0 24 24",
63
- "stroke-width": sw,
64
- stroke: stroke,
65
- children: /*#__PURE__*/ _jsx("path", {
66
- "stroke-linecap": "round",
67
- "stroke-linejoin": "round",
68
- d: "M12.75 19.5v-.75a7.5 7.5 0 0 0-7.5-7.5H4.5m0-6.75h.75c7.87 0 14.25 6.38 14.25 14.25v.75M4.5 19.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
69
- })
70
- });
71
- }
72
- // External link
73
- if (url.startsWith("http")) {
74
- return /*#__PURE__*/ _jsx("svg", {
75
- class: cls,
76
- fill: "none",
77
- viewBox: "0 0 24 24",
78
- "stroke-width": sw,
79
- stroke: stroke,
80
- children: /*#__PURE__*/ _jsx("path", {
81
- "stroke-linecap": "round",
82
- "stroke-linejoin": "round",
83
- d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"
84
- })
85
- });
86
- }
87
- // Default: generic page icon
88
- return /*#__PURE__*/ _jsx("svg", {
89
- class: cls,
90
- fill: "none",
91
- viewBox: "0 0 24 24",
92
- "stroke-width": sw,
93
- stroke: stroke,
94
- children: /*#__PURE__*/ _jsx("path", {
95
- "stroke-linecap": "round",
96
- "stroke-linejoin": "round",
97
- d: "M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
98
- })
99
- });
100
- }
101
- function SidebarLink({ link }) {
102
- return /*#__PURE__*/ _jsx("a", {
103
- href: link.url,
104
- class: `threads-sidebar-link ${link.isActive ? "threads-sidebar-link-active" : ""}`,
105
- title: link.label,
106
- ...link.isExternal ? {
107
- target: "_blank",
108
- rel: "noopener noreferrer"
109
- } : {},
110
- children: /*#__PURE__*/ _jsx(NavIcon, {
111
- url: link.url,
112
- isActive: link.isActive
113
- })
114
- });
115
- }
116
- function MobileTabLink({ link }) {
117
- return /*#__PURE__*/ _jsx("a", {
118
- href: link.url,
119
- class: `threads-mobile-tab ${link.isActive ? "threads-mobile-tab-active" : ""}`,
120
- ...link.isExternal ? {
121
- target: "_blank",
122
- rel: "noopener noreferrer"
123
- } : {},
124
- children: /*#__PURE__*/ _jsx(NavIcon, {
125
- url: link.url,
126
- isActive: link.isActive
127
- })
128
- });
129
- }
130
- export const ThreadsSiteLayout = ({ siteName, links, children })=>{
131
- return /*#__PURE__*/ _jsxs("div", {
132
- class: "threads-page",
133
- children: [
134
- /*#__PURE__*/ _jsxs("aside", {
135
- class: "threads-sidebar",
136
- children: [
137
- /*#__PURE__*/ _jsx("a", {
138
- href: "/",
139
- class: "threads-logo",
140
- title: siteName,
141
- children: /*#__PURE__*/ _jsx("span", {
142
- class: "text-2xl font-black leading-none",
143
- children: "@"
144
- })
145
- }),
146
- /*#__PURE__*/ _jsx("nav", {
147
- class: "flex flex-1 flex-col items-center gap-1",
148
- children: links.map((link)=>/*#__PURE__*/ _jsx(SidebarLink, {
149
- link: link
150
- }, link.id))
151
- })
152
- ]
153
- }),
154
- /*#__PURE__*/ _jsx("main", {
155
- class: "threads-main",
156
- children: /*#__PURE__*/ _jsx("div", {
157
- class: "threads-container",
158
- children: /*#__PURE__*/ _jsx("div", {
159
- class: "threads-content",
160
- children: children
161
- })
162
- })
163
- }),
164
- /*#__PURE__*/ _jsx("nav", {
165
- class: "threads-mobile-tabs",
166
- children: links.map((link)=>/*#__PURE__*/ _jsx(MobileTabLink, {
167
- link: link
168
- }, link.id))
169
- })
170
- ]
171
- });
172
- };
@@ -1,81 +0,0 @@
1
- /**
2
- * Threads Theme
3
- *
4
- * A clean, centered timeline theme inspired by Threads.net.
5
- * Posts separated by thin dividers, no cards, with thread connector lines.
6
- *
7
- * This is the default theme for Jant.
8
- */ // Layout
9
- import { ThreadsSiteLayout } from "./ThreadsSiteLayout.js";
10
- // Pages
11
- import { HomePage } from "./pages/HomePage.js";
12
- import { PostPage } from "./pages/PostPage.js";
13
- import { SinglePage } from "./pages/SinglePage.js";
14
- import { ArchivePage } from "./pages/ArchivePage.js";
15
- import { SearchPage } from "./pages/SearchPage.js";
16
- import { CollectionPage } from "./pages/CollectionPage.js";
17
- // Timeline
18
- import { NoteCard } from "./timeline/NoteCard.js";
19
- import { LinkCard } from "./timeline/LinkCard.js";
20
- import { QuoteCard } from "./timeline/QuoteCard.js";
21
- import { ThreadPreview } from "./timeline/ThreadPreview.js";
22
- import { TimelineFeed } from "./timeline/TimelineFeed.js";
23
- import { TimelineLoadMore } from "./timeline/TimelineLoadMore.js";
24
- import { timelineMore } from "./timeline/timelineMore.js";
25
- /**
26
- * Create the threads theme configuration.
27
- *
28
- * @param options - Optional overrides for components, CSS variables, or color themes
29
- * @returns A JantTheme configuration object
30
- *
31
- * @example
32
- * ```typescript
33
- * import { createApp } from "@jant/core";
34
- * import { threadsTheme } from "@jant/core";
35
- *
36
- * export default createApp({
37
- * theme: threadsTheme(),
38
- * });
39
- * ```
40
- */ export function theme(options) {
41
- return {
42
- name: "threads",
43
- components: {
44
- SiteLayout: ThreadsSiteLayout,
45
- HomePage,
46
- PostPage,
47
- SinglePage,
48
- ArchivePage,
49
- SearchPage,
50
- CollectionPage,
51
- NoteCard,
52
- LinkCard,
53
- QuoteCard,
54
- ThreadPreview,
55
- TimelineFeed,
56
- TimelineLoadMore,
57
- ...options?.components
58
- },
59
- timelineMore,
60
- cssVariables: {
61
- ...options?.cssVariables
62
- },
63
- colorThemes: options?.colorThemes
64
- };
65
- }
66
- // Re-export individual components for wrapping/extending
67
- export { ThreadsSiteLayout } from "./ThreadsSiteLayout.js";
68
- export { HomePage } from "./pages/HomePage.js";
69
- export { PostPage } from "./pages/PostPage.js";
70
- export { SinglePage } from "./pages/SinglePage.js";
71
- export { ArchivePage } from "./pages/ArchivePage.js";
72
- export { SearchPage } from "./pages/SearchPage.js";
73
- export { CollectionPage } from "./pages/CollectionPage.js";
74
- export { NoteCard } from "./timeline/NoteCard.js";
75
- export { LinkCard } from "./timeline/LinkCard.js";
76
- export { QuoteCard } from "./timeline/QuoteCard.js";
77
- export { ThreadPreview } from "./timeline/ThreadPreview.js";
78
- export { TimelineFeed } from "./timeline/TimelineFeed.js";
79
- export { TimelineLoadMore } from "./timeline/TimelineLoadMore.js";
80
- export { TimelineItem, TimelineItemFromPost } from "./timeline/TimelineItem.js";
81
- export { timelineMore } from "./timeline/timelineMore.js";
@@ -1,25 +0,0 @@
1
- /**
2
- * Threads Theme - Home Page
3
- *
4
- * Clean feed of posts separated by dividers.
5
- */ import { jsx as _jsx, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
6
- import { useLingui as $_useLingui } from "@jant/core/i18n";
7
- import { TimelineFeed as DefaultTimelineFeed } from "../timeline/TimelineFeed.js";
8
- export const HomePage = ({ items, hasMore, nextCursor, theme })=>{
9
- const { i18n: $__i18n, _: $__ } = $_useLingui();
10
- const Feed = theme?.TimelineFeed ?? DefaultTimelineFeed;
11
- return /*#__PURE__*/ _jsx(_Fragment, {
12
- children: items.length === 0 ? /*#__PURE__*/ _jsx("p", {
13
- class: "py-12 text-center text-muted-foreground",
14
- children: $__i18n._({
15
- id: "ODiSoW",
16
- message: "No posts yet."
17
- })
18
- }) : /*#__PURE__*/ _jsx(Feed, {
19
- items: items,
20
- hasMore: hasMore,
21
- nextCursor: nextCursor,
22
- theme: theme
23
- })
24
- });
25
- };
@@ -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
- });