@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,100 +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
- */
9
-
10
- import type { JantTheme, ThemeComponents } from "../../types.js";
11
- import type { ColorTheme } from "../../theme/color-themes.js";
12
-
13
- // Layout
14
- import { ThreadsSiteLayout } from "./ThreadsSiteLayout.js";
15
-
16
- // Pages
17
- import { HomePage } from "./pages/HomePage.js";
18
- import { PostPage } from "./pages/PostPage.js";
19
- import { SinglePage } from "./pages/SinglePage.js";
20
- import { ArchivePage } from "./pages/ArchivePage.js";
21
- import { SearchPage } from "./pages/SearchPage.js";
22
- import { CollectionPage } from "./pages/CollectionPage.js";
23
-
24
- // Timeline
25
- import { NoteCard } from "./timeline/NoteCard.js";
26
- import { LinkCard } from "./timeline/LinkCard.js";
27
- import { QuoteCard } from "./timeline/QuoteCard.js";
28
- import { ThreadPreview } from "./timeline/ThreadPreview.js";
29
- import { TimelineFeed } from "./timeline/TimelineFeed.js";
30
- import { TimelineLoadMore } from "./timeline/TimelineLoadMore.js";
31
- import { timelineMore } from "./timeline/timelineMore.js";
32
-
33
- export interface ThemeOptions {
34
- /** Override individual components */
35
- components?: Partial<ThemeComponents>;
36
- /** CSS variable overrides */
37
- cssVariables?: Record<string, string>;
38
- /** Custom color themes */
39
- colorThemes?: ColorTheme[];
40
- }
41
-
42
- /**
43
- * Create the threads theme configuration.
44
- *
45
- * @param options - Optional overrides for components, CSS variables, or color themes
46
- * @returns A JantTheme configuration object
47
- *
48
- * @example
49
- * ```typescript
50
- * import { createApp } from "@jant/core";
51
- * import { threadsTheme } from "@jant/core";
52
- *
53
- * export default createApp({
54
- * theme: threadsTheme(),
55
- * });
56
- * ```
57
- */
58
- export function theme(options?: ThemeOptions): JantTheme {
59
- return {
60
- name: "threads",
61
- components: {
62
- SiteLayout: ThreadsSiteLayout,
63
- HomePage,
64
- PostPage,
65
- SinglePage,
66
- ArchivePage,
67
- SearchPage,
68
- CollectionPage,
69
- NoteCard,
70
- LinkCard,
71
- QuoteCard,
72
- ThreadPreview,
73
- TimelineFeed,
74
- TimelineLoadMore,
75
- ...options?.components,
76
- },
77
- timelineMore,
78
- cssVariables: {
79
- ...options?.cssVariables,
80
- },
81
- colorThemes: options?.colorThemes,
82
- };
83
- }
84
-
85
- // Re-export individual components for wrapping/extending
86
- export { ThreadsSiteLayout } from "./ThreadsSiteLayout.js";
87
- export { HomePage } from "./pages/HomePage.js";
88
- export { PostPage } from "./pages/PostPage.js";
89
- export { SinglePage } from "./pages/SinglePage.js";
90
- export { ArchivePage } from "./pages/ArchivePage.js";
91
- export { SearchPage } from "./pages/SearchPage.js";
92
- export { CollectionPage } from "./pages/CollectionPage.js";
93
- export { NoteCard } from "./timeline/NoteCard.js";
94
- export { LinkCard } from "./timeline/LinkCard.js";
95
- export { QuoteCard } from "./timeline/QuoteCard.js";
96
- export { ThreadPreview } from "./timeline/ThreadPreview.js";
97
- export { TimelineFeed } from "./timeline/TimelineFeed.js";
98
- export { TimelineLoadMore } from "./timeline/TimelineLoadMore.js";
99
- export { TimelineItem, TimelineItemFromPost } from "./timeline/TimelineItem.js";
100
- export { timelineMore } from "./timeline/timelineMore.js";
@@ -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
- }