@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
@@ -0,0 +1,475 @@
1
+ /**
2
+ * UI Component Styles
3
+ *
4
+ * All styles reference design tokens from tokens.css.
5
+ * Class names are the stable public API — DO NOT rename without a major version bump.
6
+ */
7
+
8
+ /* Tell Tailwind to scan ui/ source files for class usage */
9
+ @source "../ui/";
10
+
11
+ @layer components {
12
+ /* =========================================================================
13
+ * Page Layout
14
+ * ========================================================================= */
15
+
16
+ .site-page {
17
+ min-height: 100vh;
18
+ min-height: 100dvh;
19
+ background-color: var(--site-page-bg);
20
+ }
21
+
22
+ /* --- Top header --------------------------------------------------------- */
23
+
24
+ .site-header {
25
+ max-width: var(--site-width);
26
+ margin: 0 auto;
27
+ padding: 20px var(--site-padding) 0;
28
+ }
29
+
30
+ @media (min-width: 700px) {
31
+ .site-header {
32
+ padding: 24px var(--site-padding) 0;
33
+ }
34
+ }
35
+
36
+ .site-header-inner {
37
+ display: flex;
38
+ flex-direction: column;
39
+ align-items: flex-start;
40
+ gap: 10px;
41
+ }
42
+
43
+ .site-header-top {
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ width: 100%;
48
+ }
49
+
50
+ .site-header-top-bordered {
51
+ padding-bottom: 12px;
52
+ border-bottom: 0.5px solid var(--site-divider);
53
+ }
54
+
55
+ .site-header-right {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 12px;
59
+ }
60
+
61
+ .site-header-search {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ width: 32px;
66
+ height: 32px;
67
+ border-radius: 999px;
68
+ color: var(--site-text-secondary);
69
+ transition:
70
+ color 0.15s,
71
+ background-color 0.15s;
72
+ }
73
+
74
+ .site-header-search:hover {
75
+ color: var(--site-text-primary);
76
+ background-color: var(--site-nav-hover-bg);
77
+ }
78
+
79
+ .site-header-search-active {
80
+ color: var(--site-text-primary);
81
+ background-color: var(--site-nav-hover-bg);
82
+ }
83
+
84
+ .site-logo {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 8px;
88
+ font-size: 1.125rem;
89
+ font-weight: 800;
90
+ font-family: var(--font-heading);
91
+ color: var(--site-text-primary);
92
+ text-decoration: none;
93
+ line-height: 1;
94
+ }
95
+
96
+ .site-logo-avatar {
97
+ width: var(--avatar-size);
98
+ height: var(--avatar-size);
99
+ border-radius: var(--avatar-radius);
100
+ object-fit: cover;
101
+ }
102
+
103
+ .site-header-nav {
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 12px;
107
+ }
108
+
109
+ .site-header-link {
110
+ font-size: 0.875rem;
111
+ color: var(--site-text-secondary);
112
+ text-decoration: underline transparent;
113
+ text-underline-offset: 3px;
114
+ text-decoration-thickness: 0.5px;
115
+ transition:
116
+ color 0.15s,
117
+ text-decoration-color 0.15s;
118
+ }
119
+
120
+ .site-header-link:hover {
121
+ color: var(--site-text-primary);
122
+ text-decoration-color: var(--site-text-secondary);
123
+ }
124
+
125
+ .site-header-link-active {
126
+ color: var(--site-text-primary);
127
+ }
128
+
129
+ .site-description {
130
+ font-size: var(--text-sm);
131
+ color: var(--site-text-secondary);
132
+ margin: 2px 0 0;
133
+ line-height: var(--leading);
134
+ }
135
+
136
+ /* --- Browse filter tabs ------------------------------------------------- */
137
+
138
+ .site-browse-nav {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 8px;
142
+ padding: 16px 0 var(--space-xl);
143
+ }
144
+
145
+ .site-browse-sep {
146
+ color: var(--site-divider);
147
+ font-size: var(--text-sm);
148
+ }
149
+
150
+ .site-browse-link {
151
+ font-size: var(--text-base);
152
+ color: var(--site-text-secondary);
153
+ text-decoration: none;
154
+ font-weight: 500;
155
+ transition: color 0.15s;
156
+ }
157
+
158
+ .site-browse-link:hover {
159
+ color: var(--site-text-primary);
160
+ }
161
+
162
+ .site-browse-link-active {
163
+ color: var(--site-text-primary);
164
+ font-weight: 700;
165
+ }
166
+
167
+ /* --- Main content area -------------------------------------------------- */
168
+
169
+ .site-container {
170
+ max-width: var(--site-width);
171
+ margin: 0 auto;
172
+ }
173
+
174
+ .site-content {
175
+ background-color: var(--site-elevated-bg);
176
+ padding: 0 var(--site-padding);
177
+ }
178
+
179
+ /* =========================================================================
180
+ * Timeline & Post Components
181
+ * ========================================================================= */
182
+
183
+ /* Post divider */
184
+ .site-content hr.feed-divider {
185
+ border: none;
186
+ height: 10px;
187
+ width: 32px;
188
+ margin: 2.5rem auto;
189
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 45 13'%3E%3Cpath fill='%23ccc' transform='translate(0,0) rotate(90 6 6.5)' d='M6.765.5.177 6.093l2.61 5.966 8.39-3.17L6.765.5Z'/%3E%3Cpath fill='%23ccc' transform='translate(16,0) rotate(100 6 6.5)' d='M6.765.5.177 6.093l2.61 5.966 8.39-3.17L6.765.5Z'/%3E%3Cpath fill='%23ccc' transform='translate(32,0) rotate(80 6 6.5)' d='M6.765.5.177 6.093l2.61 5.966 8.39-3.17L6.765.5Z'/%3E%3C/svg%3E");
190
+ background-repeat: no-repeat;
191
+ background-position: center;
192
+ }
193
+
194
+ /* Thread reply container with vertical connector line */
195
+ .feed-replies {
196
+ position: relative;
197
+ margin-left: 16px;
198
+ padding-left: 16px;
199
+ margin-top: 8px;
200
+ }
201
+
202
+ .feed-replies::before {
203
+ content: "";
204
+ position: absolute;
205
+ left: 0;
206
+ top: 0;
207
+ bottom: 0;
208
+ width: 2px;
209
+ background-color: var(--site-threadline);
210
+ }
211
+
212
+ /* Individual reply in thread */
213
+ .feed-reply {
214
+ padding: 12px 0;
215
+ }
216
+
217
+ .feed-reply + .feed-reply {
218
+ border-top: var(--card-border-width) solid var(--site-divider);
219
+ }
220
+
221
+ /* Compact text for thread replies */
222
+ .feed-compact {
223
+ @apply text-sm;
224
+ }
225
+
226
+ /* Quote block styling */
227
+ .feed-quote {
228
+ padding-left: 12px;
229
+ border-left: 2px solid var(--site-text-secondary);
230
+ }
231
+
232
+ /* =========================================================================
233
+ * Compose Prompt Bar ("What's new?" at top of content)
234
+ * ========================================================================= */
235
+
236
+ .compose-prompt {
237
+ display: flex;
238
+ align-items: center;
239
+ gap: 12px;
240
+ padding: 16px 0;
241
+ margin: -20px 0 12px;
242
+ }
243
+
244
+ .compose-prompt-trigger {
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 10px;
248
+ flex: 1;
249
+ min-width: 0;
250
+ padding: 0;
251
+ border: none;
252
+ background: none;
253
+ cursor: pointer;
254
+ text-align: left;
255
+ }
256
+
257
+ .compose-prompt-avatar {
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ width: var(--avatar-size);
262
+ height: var(--avatar-size);
263
+ flex-shrink: 0;
264
+ border-radius: var(--avatar-radius);
265
+ border: 1.5px solid var(--site-column-outline);
266
+ color: var(--site-text-secondary);
267
+ }
268
+
269
+ .compose-prompt-text {
270
+ font-size: var(--text-base);
271
+ color: var(--site-text-secondary);
272
+ }
273
+
274
+ .compose-prompt-post-btn {
275
+ flex-shrink: 0;
276
+ padding: 6px 16px;
277
+ border-radius: 10px;
278
+ border: 1px solid var(--site-column-outline);
279
+ background: none;
280
+ color: var(--site-text-secondary);
281
+ font-size: 0.875rem;
282
+ font-weight: 600;
283
+ cursor: pointer;
284
+ transition:
285
+ border-color 0.15s,
286
+ color 0.15s;
287
+ }
288
+
289
+ .compose-prompt-post-btn:hover {
290
+ color: var(--site-text-primary);
291
+ border-color: var(--site-text-secondary);
292
+ }
293
+
294
+ /* =========================================================================
295
+ * Compose Dialog
296
+ * ========================================================================= */
297
+
298
+ /* Dialog — positioned near top of viewport */
299
+ .compose-dialog {
300
+ padding: 0;
301
+ border: none;
302
+ border-radius: 16px;
303
+ max-width: 560px;
304
+ width: calc(100% - 32px);
305
+ max-height: calc(100dvh - 64px);
306
+ margin: max(80px, 10vh) auto auto;
307
+ background-color: var(--site-elevated-bg);
308
+ color: var(--site-text-primary);
309
+ box-shadow:
310
+ 0 25px 50px -12px rgba(0, 0, 0, 0.25),
311
+ 0 0 0 1px var(--site-column-outline);
312
+ }
313
+
314
+ .compose-dialog::backdrop {
315
+ background-color: rgba(0, 0, 0, 0.5);
316
+ }
317
+
318
+ .compose-dialog-inner {
319
+ display: flex;
320
+ flex-direction: column;
321
+ max-height: calc(100dvh - 64px);
322
+ }
323
+
324
+ .compose-dialog-header {
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: space-between;
328
+ padding: 16px 20px;
329
+ border-bottom: var(--card-border-width) solid var(--site-divider);
330
+ }
331
+
332
+ .compose-dialog-title {
333
+ font-size: 1rem;
334
+ font-weight: 600;
335
+ margin: 0;
336
+ }
337
+
338
+ .compose-dialog-close {
339
+ display: flex;
340
+ align-items: center;
341
+ justify-content: center;
342
+ width: 28px;
343
+ height: 28px;
344
+ border-radius: 50%;
345
+ border: none;
346
+ background: none;
347
+ color: var(--site-text-secondary);
348
+ cursor: pointer;
349
+ transition: background-color 0.15s;
350
+ }
351
+
352
+ .compose-dialog-close:hover {
353
+ background-color: var(--site-nav-hover-bg);
354
+ }
355
+
356
+ .compose-dialog-body {
357
+ padding: 16px 20px;
358
+ overflow-y: auto;
359
+ flex: 1;
360
+ }
361
+
362
+ .compose-dialog-footer {
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: space-between;
366
+ padding-top: 12px;
367
+ border-top: var(--card-border-width) solid var(--site-divider);
368
+ }
369
+
370
+ /* Format tabs — inline pill buttons */
371
+ .compose-format-tabs {
372
+ display: flex;
373
+ gap: 4px;
374
+ padding-bottom: 8px;
375
+ }
376
+
377
+ .compose-format-tab {
378
+ padding: 4px 12px;
379
+ border-radius: 999px;
380
+ border: 1px solid var(--site-column-outline);
381
+ background: none;
382
+ color: var(--site-text-secondary);
383
+ font-size: var(--text-sm);
384
+ cursor: pointer;
385
+ transition:
386
+ background-color 0.15s,
387
+ color 0.15s,
388
+ border-color 0.15s;
389
+ }
390
+
391
+ .compose-format-tab:hover {
392
+ color: var(--site-text-primary);
393
+ border-color: var(--site-text-secondary);
394
+ }
395
+
396
+ .compose-format-tab-active {
397
+ color: var(--site-text-primary);
398
+ background-color: var(--site-nav-hover-bg);
399
+ border-color: var(--site-text-primary);
400
+ font-weight: 500;
401
+ }
402
+
403
+ /* Borderless title input */
404
+ .compose-title-input {
405
+ width: 100%;
406
+ padding: 4px 0;
407
+ border: none;
408
+ outline: none;
409
+ background: transparent;
410
+ color: var(--site-text-primary);
411
+ font-size: var(--text-lg);
412
+ font-weight: 600;
413
+ }
414
+
415
+ .compose-title-input::placeholder {
416
+ color: var(--site-text-secondary);
417
+ font-weight: 400;
418
+ }
419
+
420
+ /* Borderless body textarea */
421
+ .compose-body-input {
422
+ width: 100%;
423
+ padding: 4px 0;
424
+ border: none;
425
+ outline: none;
426
+ background: transparent;
427
+ color: var(--site-text-primary);
428
+ font-size: var(--text-base);
429
+ line-height: var(--leading);
430
+ resize: vertical;
431
+ }
432
+
433
+ .compose-body-input::placeholder {
434
+ color: var(--site-text-secondary);
435
+ }
436
+
437
+ /* Bottom toolbar */
438
+ .compose-toolbar {
439
+ padding: 8px 0;
440
+ }
441
+
442
+ .compose-toolbar-btn {
443
+ display: flex;
444
+ align-items: center;
445
+ justify-content: center;
446
+ width: 32px;
447
+ height: 32px;
448
+ border-radius: 8px;
449
+ border: none;
450
+ background: none;
451
+ color: var(--site-text-secondary);
452
+ cursor: pointer;
453
+ transition:
454
+ background-color 0.15s,
455
+ color 0.15s;
456
+ }
457
+
458
+ .compose-toolbar-btn:hover {
459
+ color: var(--site-text-primary);
460
+ background-color: var(--site-nav-hover-bg);
461
+ }
462
+
463
+ /* =========================================================================
464
+ * Site Footer
465
+ * ========================================================================= */
466
+
467
+ .site-footer {
468
+ max-width: var(--site-width);
469
+ margin: var(--space-xl) auto 0;
470
+ padding: var(--content-gap) var(--site-padding) var(--space-xl);
471
+ border-top: 0.5px solid var(--site-divider);
472
+ color: var(--site-text-secondary);
473
+ font-size: var(--text-sm);
474
+ }
475
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Cloudflare Worker Bindings
3
+ */
4
+
5
+ export interface Bindings {
6
+ DB: D1Database;
7
+ R2?: R2Bucket;
8
+ SITE_URL: string;
9
+ AUTH_SECRET?: string;
10
+ R2_PUBLIC_URL?: string;
11
+ IMAGE_TRANSFORM_URL?: string;
12
+ DEMO_EMAIL?: string;
13
+ DEMO_PASSWORD?: string;
14
+ // Timeline
15
+ PAGE_SIZE?: string;
16
+ // Site configuration (optional - can be overridden in DB)
17
+ SITE_NAME?: string;
18
+ SITE_DESCRIPTION?: string;
19
+ SITE_LANGUAGE?: string;
20
+ // S3-compatible storage (alternative to R2)
21
+ STORAGE_DRIVER?: string;
22
+ S3_ENDPOINT?: string;
23
+ S3_BUCKET?: string;
24
+ S3_ACCESS_KEY_ID?: string;
25
+ S3_SECRET_ACCESS_KEY?: string;
26
+ S3_REGION?: string;
27
+ S3_PUBLIC_URL?: string;
28
+ // RSS feed
29
+ RSS_FEED_LIMIT?: string;
30
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Configuration System
3
+ *
4
+ * Single Source of Truth for all configuration fields.
5
+ */
6
+
7
+ import type { ColorTheme } from "../ui/color-themes.js";
8
+ import type { FeedData, SitemapData } from "./views.js";
9
+
10
+ /**
11
+ * Configuration Registry - Single Source of Truth
12
+ *
13
+ * All available configuration fields with their metadata.
14
+ * Add new fields here, and they'll automatically work everywhere.
15
+ *
16
+ * Priority logic:
17
+ * - envOnly: false -> User-configurable (DB > ENV > Default)
18
+ * - envOnly: true -> Environment-only (ENV > Default)
19
+ */
20
+ export const CONFIG_FIELDS = {
21
+ // User-configurable (can be modified in dashboard)
22
+ SITE_NAME: {
23
+ defaultValue: "Jant",
24
+ envOnly: false,
25
+ },
26
+ SITE_DESCRIPTION: {
27
+ defaultValue: "A microblog powered by Jant",
28
+ envOnly: false,
29
+ },
30
+ SITE_LANGUAGE: {
31
+ defaultValue: "en",
32
+ envOnly: false,
33
+ },
34
+ HOME_DEFAULT_VIEW: {
35
+ defaultValue: "latest",
36
+ envOnly: false,
37
+ },
38
+
39
+ // Environment-only (deployment/infrastructure config)
40
+ SITE_URL: {
41
+ defaultValue: "",
42
+ envOnly: true,
43
+ },
44
+ AUTH_SECRET: {
45
+ defaultValue: "",
46
+ envOnly: true,
47
+ },
48
+ R2_PUBLIC_URL: {
49
+ defaultValue: "",
50
+ envOnly: true,
51
+ },
52
+ IMAGE_TRANSFORM_URL: {
53
+ defaultValue: "",
54
+ envOnly: true,
55
+ },
56
+ DEMO_EMAIL: {
57
+ defaultValue: "",
58
+ envOnly: true,
59
+ },
60
+ DEMO_PASSWORD: {
61
+ defaultValue: "",
62
+ envOnly: true,
63
+ },
64
+ PAGE_SIZE: {
65
+ defaultValue: "20",
66
+ envOnly: true,
67
+ },
68
+ STORAGE_DRIVER: {
69
+ defaultValue: "r2",
70
+ envOnly: true,
71
+ },
72
+ S3_ENDPOINT: {
73
+ defaultValue: "",
74
+ envOnly: true,
75
+ },
76
+ S3_BUCKET: {
77
+ defaultValue: "",
78
+ envOnly: true,
79
+ },
80
+ S3_ACCESS_KEY_ID: {
81
+ defaultValue: "",
82
+ envOnly: true,
83
+ },
84
+ S3_SECRET_ACCESS_KEY: {
85
+ defaultValue: "",
86
+ envOnly: true,
87
+ },
88
+ S3_REGION: {
89
+ defaultValue: "auto",
90
+ envOnly: true,
91
+ },
92
+ S3_PUBLIC_URL: {
93
+ defaultValue: "",
94
+ envOnly: true,
95
+ },
96
+
97
+ // Internal settings (DB-only, not configurable via env or dashboard)
98
+ THEME: {
99
+ defaultValue: "",
100
+ envOnly: false,
101
+ internal: true,
102
+ },
103
+ CUSTOM_CSS: {
104
+ defaultValue: "",
105
+ envOnly: false,
106
+ internal: true,
107
+ },
108
+ SITE_AVATAR: {
109
+ defaultValue: "",
110
+ envOnly: false,
111
+ internal: true,
112
+ },
113
+ SHOW_HEADER_AVATAR: {
114
+ defaultValue: "",
115
+ envOnly: false,
116
+ internal: true,
117
+ },
118
+ SITE_FAVICON_ICO: {
119
+ defaultValue: "",
120
+ envOnly: false,
121
+ internal: true,
122
+ },
123
+ SITE_FAVICON_APPLE_TOUCH: {
124
+ defaultValue: "",
125
+ envOnly: false,
126
+ internal: true,
127
+ },
128
+ FONT_THEME: {
129
+ defaultValue: "",
130
+ envOnly: false,
131
+ internal: true,
132
+ },
133
+ TIME_ZONE: {
134
+ defaultValue: "UTC",
135
+ envOnly: false,
136
+ },
137
+ SITE_FOOTER: {
138
+ defaultValue: "",
139
+ envOnly: false,
140
+ },
141
+ NOINDEX: {
142
+ defaultValue: "",
143
+ envOnly: false,
144
+ },
145
+ ONBOARDING_STATUS: {
146
+ defaultValue: "pending",
147
+ envOnly: false,
148
+ internal: true,
149
+ },
150
+ PASSWORD_RESET_TOKEN: {
151
+ defaultValue: "",
152
+ envOnly: false,
153
+ internal: true,
154
+ },
155
+ } as const;
156
+
157
+ export type ConfigKey = keyof typeof CONFIG_FIELDS;
158
+
159
+ /**
160
+ * Main Jant configuration
161
+ *
162
+ * Configuration Philosophy:
163
+ * - Use environment variables for runtime config (API keys, feature flags, site settings)
164
+ * - Use code config (this object) for CSS customization and feed overrides
165
+ *
166
+ * Site-level settings (name, description, language) are configured via
167
+ * environment variables, not here. See lib/config.ts for details.
168
+ */
169
+ export interface JantConfig {
170
+ /** CSS variable overrides (highest priority after custom CSS) */
171
+ cssVariables?: Record<string, string>;
172
+ /** Replace built-in color themes with custom list */
173
+ colorThemes?: ColorTheme[];
174
+ /** Custom feed renderers */
175
+ feed?: {
176
+ /** Custom RSS 2.0 renderer -- returns XML string */
177
+ rss?: (data: FeedData) => string;
178
+ /** Custom Atom renderer -- returns XML string */
179
+ atom?: (data: FeedData) => string;
180
+ /** Custom Sitemap renderer -- returns XML string */
181
+ sitemap?: (data: SitemapData) => string;
182
+ };
183
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Content Type Constants
3
+ */
4
+
5
+ export const FORMATS = ["note", "link", "quote"] as const;
6
+ export type Format = (typeof FORMATS)[number];
7
+
8
+ export const STATUSES = ["draft", "published"] as const;
9
+ export type Status = (typeof STATUSES)[number];
10
+
11
+ export const SORT_ORDERS = [
12
+ "newest",
13
+ "oldest",
14
+ "rating_desc",
15
+ "rating_asc",
16
+ ] as const;
17
+ export type SortOrder = (typeof SORT_ORDERS)[number];
18
+
19
+ export const NAV_ITEM_TYPES = ["page", "link"] as const;
20
+ export type NavItemType = (typeof NAV_ITEM_TYPES)[number];
21
+
22
+ export const MAX_MEDIA_ATTACHMENTS = 20;
23
+ export const MAX_PINNED_POSTS = 3;
24
+
25
+ export const STORAGE_DRIVERS = ["r2", "s3"] as const;
26
+ export type StorageDriver = (typeof STORAGE_DRIVERS)[number];