@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,336 +0,0 @@
1
- /**
2
- * Threads theme styles
3
- *
4
- * Design tokens extracted from threads.com source CSS.
5
- * Key variables from Threads' --barcelona-* system:
6
- * --barcelona-side-navigation-width: 76px
7
- * --barcelona-column-layout-max-width: 640px
8
- * --barcelona-columns-item-horizontal-padding: 24px
9
- * --barcelona-primary-column-outline: rgb(213,213,213) / rgb(45,45,45)
10
- * --barcelona-threadline: rgb(229,229,229) / rgb(51,54,56)
11
- * --barcelona-navigation-icon: rgb(77,77,77) / rgb(184,184,184)
12
- * --barcelona-navigation-item-hover-background: rgba(0,0,0,0.035)
13
- */
14
-
15
- /* Tell Tailwind to scan threads theme source files for class usage */
16
- @source "./";
17
-
18
- /* =========================================================================
19
- * Threads Design Tokens (as CSS custom properties)
20
- * ========================================================================= */
21
-
22
- :root {
23
- --threads-sidebar-width: 76px;
24
- --threads-column-max-width: 640px;
25
- --threads-column-padding: 24px;
26
- --threads-column-outline: rgb(213, 213, 213);
27
- --threads-threadline: rgb(229, 229, 229);
28
- --threads-page-bg: rgb(250, 250, 250);
29
- --threads-elevated-bg: rgb(255, 255, 255);
30
- --threads-nav-icon: rgb(77, 77, 77);
31
- --threads-nav-icon-active: rgb(0, 0, 0);
32
- --threads-nav-hover-bg: rgba(0, 0, 0, 0.035);
33
- --threads-text-primary: rgb(0, 0, 0);
34
- --threads-text-secondary: rgb(119, 119, 119);
35
- --threads-media-outline: rgba(0, 0, 0, 0.15);
36
- --threads-divider: rgba(0, 0, 0, 0.15);
37
- --threads-container-radius: 24px;
38
- --threads-mobile-header-height: 60px;
39
- }
40
-
41
- @media (prefers-color-scheme: dark) {
42
- :root {
43
- --threads-column-outline: rgb(45, 45, 45);
44
- --threads-threadline: rgb(51, 54, 56);
45
- --threads-page-bg: rgb(10, 10, 10);
46
- --threads-elevated-bg: rgb(24, 24, 24);
47
- --threads-nav-icon: rgb(184, 184, 184);
48
- --threads-nav-icon-active: rgb(243, 245, 247);
49
- --threads-nav-hover-bg: rgba(255, 255, 255, 0.08);
50
- --threads-text-primary: rgb(243, 245, 247);
51
- --threads-text-secondary: rgb(153, 153, 153);
52
- --threads-media-outline: rgba(243, 245, 247, 0.2);
53
- --threads-divider: rgba(255, 255, 255, 0.15);
54
- }
55
- }
56
-
57
- .dark {
58
- --threads-column-outline: rgb(45, 45, 45);
59
- --threads-threadline: rgb(51, 54, 56);
60
- --threads-page-bg: rgb(10, 10, 10);
61
- --threads-elevated-bg: rgb(24, 24, 24);
62
- --threads-nav-icon: rgb(184, 184, 184);
63
- --threads-nav-icon-active: rgb(243, 245, 247);
64
- --threads-nav-hover-bg: rgba(255, 255, 255, 0.08);
65
- --threads-text-primary: rgb(243, 245, 247);
66
- --threads-text-secondary: rgb(153, 153, 153);
67
- --threads-media-outline: rgba(243, 245, 247, 0.2);
68
- --threads-divider: rgba(255, 255, 255, 0.15);
69
- }
70
-
71
- @layer components {
72
- /* =========================================================================
73
- * Page Layout
74
- * ========================================================================= */
75
-
76
- .threads-page {
77
- min-height: 100vh;
78
- min-height: 100dvh;
79
- background-color: var(--threads-page-bg);
80
- }
81
-
82
- /* --- Left sidebar (desktop only) ---------------------------------------- */
83
-
84
- .threads-sidebar {
85
- position: fixed;
86
- inset: 0 auto 0 0;
87
- z-index: 30;
88
- display: none;
89
- flex-direction: column;
90
- align-items: center;
91
- width: var(--threads-sidebar-width);
92
- padding: 12px 0;
93
- background-color: var(--threads-page-bg);
94
- }
95
-
96
- @media (min-width: 700px) {
97
- .threads-sidebar {
98
- display: flex;
99
- }
100
- }
101
-
102
- .threads-logo {
103
- display: flex;
104
- align-items: center;
105
- justify-content: center;
106
- width: 48px;
107
- height: 48px;
108
- margin-bottom: 4px;
109
- color: var(--threads-nav-icon-active);
110
- }
111
-
112
- .threads-sidebar-link {
113
- display: flex;
114
- align-items: center;
115
- justify-content: center;
116
- width: 48px;
117
- height: 48px;
118
- border-radius: 10px;
119
- color: var(--threads-nav-icon);
120
- transition:
121
- background-color 0.15s,
122
- color 0.15s;
123
- }
124
-
125
- .threads-sidebar-link:hover {
126
- color: var(--threads-nav-icon-active);
127
- background-color: var(--threads-nav-hover-bg);
128
- }
129
-
130
- .threads-sidebar-link-active {
131
- color: var(--threads-nav-icon-active);
132
- }
133
-
134
- /* --- Main content area -------------------------------------------------- */
135
-
136
- .threads-main {
137
- padding-bottom: var(--threads-mobile-header-height);
138
- }
139
-
140
- @media (min-width: 700px) {
141
- .threads-main {
142
- padding-bottom: 0;
143
- padding-left: var(--threads-sidebar-width);
144
- }
145
- }
146
-
147
- .threads-container {
148
- max-width: var(--threads-column-max-width);
149
- margin: 0 auto;
150
- }
151
-
152
- /* Mobile: full-bleed white, no border-radius */
153
- .threads-content {
154
- background-color: var(--threads-elevated-bg);
155
- padding: 0 var(--threads-column-padding);
156
- }
157
-
158
- /* Desktop: rounded white card on gray background */
159
- @media (min-width: 700px) {
160
- .threads-container {
161
- padding: 16px 24px 0;
162
- }
163
-
164
- .threads-content {
165
- border-radius: var(--threads-container-radius);
166
- border: 0.5px solid var(--threads-column-outline);
167
- padding: 0 var(--threads-column-padding);
168
- min-height: calc(100vh - 32px);
169
- }
170
- }
171
-
172
- /* --- Home page: each date group as its own card ------------------------ */
173
-
174
- .threads-content:has(#timeline-feed) {
175
- background-color: transparent;
176
- padding: 0;
177
- }
178
-
179
- .threads-content #timeline-feed {
180
- display: flex;
181
- flex-direction: column;
182
- gap: 4px;
183
- }
184
-
185
- .threads-content #timeline-feed > div {
186
- background-color: var(--threads-elevated-bg);
187
- padding: 0 var(--threads-column-padding) 16px;
188
- }
189
-
190
- @media (min-width: 700px) {
191
- .threads-content:has(#timeline-feed) {
192
- border: none;
193
- border-radius: 0;
194
- min-height: auto;
195
- }
196
-
197
- .threads-content #timeline-feed {
198
- gap: 16px;
199
- }
200
-
201
- .threads-content #timeline-feed > div {
202
- border-radius: var(--threads-container-radius);
203
- border: 0.5px solid var(--threads-column-outline);
204
- }
205
- }
206
-
207
- /* --- Mobile bottom tab bar ---------------------------------------------- */
208
-
209
- .threads-mobile-tabs {
210
- position: fixed;
211
- inset: auto 0 0 0;
212
- z-index: 30;
213
- display: flex;
214
- height: var(--threads-mobile-header-height);
215
- background-color: var(--threads-elevated-bg);
216
- border-top: 0.5px solid var(--threads-column-outline);
217
- }
218
-
219
- @media (min-width: 700px) {
220
- .threads-mobile-tabs {
221
- display: none;
222
- }
223
- }
224
-
225
- .threads-mobile-tab {
226
- display: flex;
227
- align-items: center;
228
- justify-content: center;
229
- flex: 1;
230
- color: var(--threads-nav-icon);
231
- transition: color 0.15s;
232
- }
233
-
234
- .threads-mobile-tab-active {
235
- color: var(--threads-nav-icon-active);
236
- }
237
-
238
- /* =========================================================================
239
- * Timeline & Post Components
240
- * ========================================================================= */
241
-
242
- /* Date group header — centered, muted label at top of each card */
243
- .threads-date-header {
244
- padding: 16px 0 8px;
245
- text-align: center;
246
- font-size: 13px;
247
- color: var(--threads-text-secondary);
248
- }
249
-
250
- /* Post divider — full-width 0.5px line matching Threads.
251
- * Targets the <hr class="border-border my-5"> used by both
252
- * TimelineFeed (initial render) and the SSE load-more handler. */
253
- .threads-content hr.border-border {
254
- border: none;
255
- border-top: 0.5px solid var(--threads-divider);
256
- margin-left: calc(-1 * var(--threads-column-padding));
257
- margin-right: calc(-1 * var(--threads-column-padding));
258
- }
259
-
260
- /* Thread reply container with vertical connector line */
261
- .threads-replies {
262
- position: relative;
263
- margin-left: 16px;
264
- padding-left: 16px;
265
- margin-top: 8px;
266
- }
267
-
268
- .threads-replies::before {
269
- content: "";
270
- position: absolute;
271
- left: 0;
272
- top: 0;
273
- bottom: 0;
274
- width: 2px;
275
- background-color: var(--threads-threadline);
276
- }
277
-
278
- /* Individual reply in thread */
279
- .threads-reply {
280
- padding: 12px 0;
281
- }
282
-
283
- .threads-reply + .threads-reply {
284
- border-top: 0.5px solid var(--threads-divider);
285
- }
286
-
287
- /* Compact text for thread replies */
288
- .threads-compact {
289
- @apply text-sm;
290
- }
291
-
292
- /* Media — rounded corners + subtle outline like Threads */
293
- .threads-media img {
294
- border-radius: 8px;
295
- outline: 1px solid var(--threads-media-outline);
296
- outline-offset: -1px;
297
- }
298
-
299
- /* Image carousel — horizontal scroll like Threads.net */
300
- .threads-carousel-track {
301
- display: flex;
302
- overflow-x: auto;
303
- gap: 4px;
304
- margin-inline: calc(-1 * var(--threads-column-padding));
305
- padding-inline: var(--threads-column-padding);
306
- scrollbar-width: none; /* Firefox */
307
- -ms-overflow-style: none; /* IE/Edge */
308
- }
309
- .threads-carousel-track::-webkit-scrollbar {
310
- display: none; /* Chrome/Safari */
311
- }
312
- .threads-carousel-item {
313
- flex: 0 0 auto;
314
- width: 224px;
315
- height: 280px;
316
- }
317
- .threads-carousel-img {
318
- width: 100%;
319
- height: 100%;
320
- object-fit: cover;
321
- }
322
-
323
- /* Link preview styling */
324
- .threads-link-preview {
325
- border-radius: 12px;
326
- border: 0.5px solid var(--threads-column-outline);
327
- padding: 12px 16px;
328
- margin-top: 8px;
329
- }
330
-
331
- /* Quote block styling */
332
- .threads-quote {
333
- padding-left: 12px;
334
- border-left: 2px solid var(--threads-text-secondary);
335
- }
336
- }
@@ -1,62 +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
- */
7
-
8
- import type { FC } from "hono/jsx";
9
- import type { TimelineFeedProps } from "../../../types.js";
10
- import { TimelineItem } from "./TimelineItem.js";
11
- import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
12
- import { TimelineLoadMore as DefaultTimelineLoadMore } from "./TimelineLoadMore.js";
13
- import { groupByDate } from "./groupByDate.js";
14
-
15
- export const TimelineFeed: FC<TimelineFeedProps> = ({
16
- items,
17
- hasMore,
18
- nextCursor,
19
- theme,
20
- }) => {
21
- const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
22
- const ResolvedLoadMore = theme?.TimelineLoadMore ?? DefaultTimelineLoadMore;
23
- const groups = groupByDate(items);
24
-
25
- return (
26
- <div>
27
- <div id="timeline-feed">
28
- {groups.map((group) => (
29
- <div key={group.dateKey} class="threads-card">
30
- <div class="threads-date-header">
31
- <span>{group.label}</span>
32
- </div>
33
- <div id={`date-items-${group.dateKey}`} class="flex flex-col">
34
- {group.items.map((item, i) => (
35
- <div key={item.post.id}>
36
- {i > 0 && <hr class="border-border my-5" />}
37
- {item.threadPreview ? (
38
- <ResolvedThreadPreview
39
- rootPost={item.post}
40
- previewReplies={item.threadPreview.replies}
41
- totalReplyCount={item.threadPreview.totalReplyCount}
42
- theme={theme}
43
- />
44
- ) : (
45
- <TimelineItem item={item} theme={theme} />
46
- )}
47
- </div>
48
- ))}
49
- </div>
50
- </div>
51
- ))}
52
- </div>
53
- {hasMore && nextCursor && (
54
- <ResolvedLoadMore
55
- nextCursor={nextCursor}
56
- lastDate={groups.at(-1)?.dateKey}
57
- theme={theme}
58
- />
59
- )}
60
- </div>
61
- );
62
- };
@@ -1,67 +0,0 @@
1
- /**
2
- * Threads Theme - Timeline Item
3
- *
4
- * Dispatches to the correct card component based on post format.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import type {
9
- TimelineItemView,
10
- TimelineCardProps,
11
- ThemeComponents,
12
- PostView,
13
- Format,
14
- } from "../../../types.js";
15
- import { NoteCard } from "./NoteCard.js";
16
- import { LinkCard } from "./LinkCard.js";
17
- import { QuoteCard } from "./QuoteCard.js";
18
-
19
- const CARD_MAP: Record<Format, FC<TimelineCardProps>> = {
20
- note: NoteCard,
21
- link: LinkCard,
22
- quote: QuoteCard,
23
- };
24
-
25
- const THEME_KEY_MAP: Record<Format, keyof ThemeComponents> = {
26
- note: "NoteCard",
27
- link: "LinkCard",
28
- quote: "QuoteCard",
29
- };
30
-
31
- interface TimelineItemProps {
32
- item: TimelineItemView;
33
- compact?: boolean;
34
- cardOverride?: FC<TimelineCardProps>;
35
- theme?: ThemeComponents;
36
- }
37
-
38
- interface TimelineItemFromPostProps {
39
- post: PostView;
40
- compact?: boolean;
41
- cardOverride?: FC<TimelineCardProps>;
42
- theme?: ThemeComponents;
43
- }
44
-
45
- export const TimelineItem: FC<TimelineItemProps> = ({
46
- item,
47
- compact,
48
- cardOverride,
49
- theme,
50
- }) => {
51
- const themeKey = THEME_KEY_MAP[item.post.format];
52
- const themeCard = theme?.[themeKey] as FC<TimelineCardProps> | undefined;
53
- const Card = cardOverride ?? themeCard ?? CARD_MAP[item.post.format];
54
- return <Card post={item.post} compact={compact} />;
55
- };
56
-
57
- export const TimelineItemFromPost: FC<TimelineItemFromPostProps> = ({
58
- post,
59
- compact,
60
- cardOverride,
61
- theme,
62
- }) => {
63
- const themeKey = THEME_KEY_MAP[post.format];
64
- const themeCard = theme?.[themeKey] as FC<TimelineCardProps> | undefined;
65
- const Card = cardOverride ?? themeCard ?? CARD_MAP[post.format];
66
- return <Card post={post} compact={compact} />;
67
- };
@@ -1,35 +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
- */
7
-
8
- import type { FC } from "hono/jsx";
9
- import { useLingui } from "@lingui/react/macro";
10
- import type { TimelineLoadMoreProps } from "../../../types.js";
11
-
12
- export const TimelineLoadMore: FC<TimelineLoadMoreProps> = ({
13
- nextCursor,
14
- lastDate,
15
- }) => {
16
- const { t } = useLingui();
17
- const url = lastDate
18
- ? `/?cursor=${nextCursor}&lastDate=${lastDate}`
19
- : `/?cursor=${nextCursor}`;
20
-
21
- return (
22
- <div
23
- id="load-more-container"
24
- class="py-6 text-center"
25
- data-on-intersect__once={`@get('${url}')`}
26
- >
27
- <span class="text-sm text-muted-foreground">
28
- {t({
29
- message: "Loading...",
30
- comment: "@context: Loading indicator while fetching more posts",
31
- })}
32
- </span>
33
- </div>
34
- );
35
- };
@@ -1,30 +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
- */
7
-
8
- import type { TimelineItemView } from "../../../types.js";
9
-
10
- export interface DateGroup {
11
- dateKey: string;
12
- label: string;
13
- items: TimelineItemView[];
14
- }
15
-
16
- export function groupByDate(items: TimelineItemView[]): DateGroup[] {
17
- const groups: DateGroup[] = [];
18
- let current: DateGroup | null = null;
19
-
20
- for (const item of items) {
21
- const dateKey = item.post.publishedAt.slice(0, 10);
22
- if (!current || current.dateKey !== dateKey) {
23
- current = { dateKey, label: item.post.publishedAtFormatted, items: [] };
24
- groups.push(current);
25
- }
26
- current.items.push(item);
27
- }
28
-
29
- return groups;
30
- }
@@ -1,130 +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
- */
8
-
9
- import type {
10
- TimelineMoreProps,
11
- TimelinePatch,
12
- TimelineItemView,
13
- } from "../../../types.js";
14
- import { groupByDate } from "./groupByDate.js";
15
- import { TimelineItem } from "./TimelineItem.js";
16
- import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
17
- import { TimelineLoadMore as DefaultTimelineLoadMore } from "./TimelineLoadMore.js";
18
-
19
- function renderItem(item: TimelineItemView, props: TimelineMoreProps): string {
20
- const theme = props.theme;
21
- const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
22
-
23
- if (item.threadPreview) {
24
- return (
25
- <ResolvedThreadPreview
26
- rootPost={item.post}
27
- previewReplies={item.threadPreview.replies}
28
- totalReplyCount={item.threadPreview.totalReplyCount}
29
- theme={theme}
30
- />
31
- ).toString();
32
- }
33
- return (<TimelineItem item={item} theme={theme} />).toString();
34
- }
35
-
36
- /**
37
- * Renders SSE patches for the threads theme's load-more response.
38
- *
39
- * @param props - Timeline more props with items, pagination, and theme
40
- * @returns Array of DOM patch instructions for the SSE stream
41
- */
42
- export function timelineMore(props: TimelineMoreProps): TimelinePatch[] {
43
- const { items, lastDate, hasMore, nextCursor, theme } = props;
44
- const patches: TimelinePatch[] = [];
45
- const groups = groupByDate(items);
46
-
47
- if (groups.length === 0) return patches;
48
-
49
- const firstGroup = groups[0]!;
50
- const isContinuation = lastDate === firstGroup.dateKey;
51
-
52
- // Continuation items: append into the existing date group's container
53
- if (isContinuation) {
54
- const continuationHtml = firstGroup.items
55
- .map((item) => {
56
- const content = renderItem(item, props);
57
- return `<div><hr class="border-border my-5"/>${content}</div>`;
58
- })
59
- .join("");
60
-
61
- if (continuationHtml) {
62
- patches.push({
63
- selector: `#date-items-${lastDate}`,
64
- content: continuationHtml,
65
- mode: "append",
66
- });
67
- }
68
- }
69
-
70
- // New date groups: append to #timeline-feed
71
- const newGroups = isContinuation ? groups.slice(1) : groups;
72
- if (newGroups.length > 0) {
73
- const newGroupsHtml = newGroups
74
- .map((group) => {
75
- const itemsHtml = group.items
76
- .map((item, i) => {
77
- const content = renderItem(item, props);
78
- return i > 0
79
- ? `<div><hr class="border-border my-5"/>${content}</div>`
80
- : `<div>${content}</div>`;
81
- })
82
- .join("");
83
-
84
- return (
85
- <div class="threads-card">
86
- <div class="threads-date-header">
87
- <span>{group.label}</span>
88
- </div>
89
- <div
90
- id={`date-items-${group.dateKey}`}
91
- class="flex flex-col"
92
- dangerouslySetInnerHTML={{ __html: itemsHtml }}
93
- />
94
- </div>
95
- ).toString();
96
- })
97
- .join("");
98
-
99
- patches.push({
100
- selector: "#timeline-feed",
101
- content: newGroupsHtml,
102
- mode: "append",
103
- });
104
- }
105
-
106
- // Load-more button
107
- const ResolvedLoadMore = theme?.TimelineLoadMore ?? DefaultTimelineLoadMore;
108
- const lastGroupDate = groups.at(-1)?.dateKey;
109
-
110
- if (hasMore && nextCursor) {
111
- patches.push({
112
- selector: "#load-more-container",
113
- content: (
114
- <ResolvedLoadMore
115
- nextCursor={nextCursor}
116
- lastDate={lastGroupDate}
117
- theme={theme}
118
- />
119
- ).toString(),
120
- });
121
- } else {
122
- patches.push({
123
- selector: "#load-more-container",
124
- content: "",
125
- mode: "remove",
126
- });
127
- }
128
-
129
- return patches;
130
- }