@jant/core 0.3.6 → 0.3.8

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 (264) hide show
  1. package/dist/app.js +11 -21
  2. package/dist/client.js +1 -0
  3. package/dist/db/schema.js +15 -0
  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 +1 -1
  8. package/dist/lib/image.js +3 -3
  9. package/dist/lib/media-helpers.js +43 -0
  10. package/dist/lib/nav-reorder.js +27 -0
  11. package/dist/lib/navigation.js +35 -0
  12. package/dist/lib/schemas.js +32 -2
  13. package/dist/lib/sse.js +7 -8
  14. package/dist/lib/theme-components.js +49 -0
  15. package/dist/routes/api/posts.js +101 -5
  16. package/dist/routes/api/timeline.js +115 -0
  17. package/dist/routes/api/upload.js +9 -5
  18. package/dist/routes/dash/media.js +38 -0
  19. package/dist/routes/dash/navigation.js +274 -0
  20. package/dist/routes/dash/posts.js +45 -6
  21. package/dist/routes/feed/rss.js +10 -1
  22. package/dist/routes/pages/archive.js +14 -27
  23. package/dist/routes/pages/collection.js +10 -19
  24. package/dist/routes/pages/home.js +88 -98
  25. package/dist/routes/pages/page.js +19 -38
  26. package/dist/routes/pages/post.js +61 -48
  27. package/dist/routes/pages/search.js +13 -26
  28. package/dist/services/collection.js +13 -0
  29. package/dist/services/index.js +3 -1
  30. package/dist/services/media.js +55 -2
  31. package/dist/services/navigation.js +115 -0
  32. package/dist/services/post.js +26 -1
  33. package/dist/theme/components/MediaGallery.js +107 -0
  34. package/dist/theme/components/PostForm.js +158 -2
  35. package/dist/theme/components/PostList.js +5 -0
  36. package/dist/theme/components/index.js +3 -0
  37. package/dist/theme/components/timeline/ArticleCard.js +50 -0
  38. package/dist/theme/components/timeline/ImageCard.js +86 -0
  39. package/dist/theme/components/timeline/LinkCard.js +62 -0
  40. package/dist/theme/components/timeline/NoteCard.js +37 -0
  41. package/dist/theme/components/timeline/QuoteCard.js +51 -0
  42. package/dist/theme/components/timeline/ThreadPreview.js +52 -0
  43. package/dist/theme/components/timeline/TimelineFeed.js +43 -0
  44. package/dist/theme/components/timeline/TimelineItem.js +25 -0
  45. package/dist/theme/components/timeline/index.js +8 -0
  46. package/dist/theme/layouts/DashLayout.js +8 -0
  47. package/dist/theme/layouts/SiteLayout.js +160 -0
  48. package/dist/theme/layouts/index.js +1 -0
  49. package/dist/types/sortablejs.d.js +5 -0
  50. package/dist/types.js +27 -0
  51. package/package.json +3 -2
  52. package/src/__tests__/helpers/app.ts +6 -1
  53. package/src/__tests__/helpers/db.ts +20 -0
  54. package/src/app.tsx +11 -25
  55. package/src/client.ts +1 -0
  56. package/src/db/migrations/0002_add_media_attachments.sql +3 -0
  57. package/src/db/migrations/0003_add_navigation_links.sql +8 -0
  58. package/src/db/migrations/meta/0003_snapshot.json +821 -0
  59. package/src/db/migrations/meta/_journal.json +14 -0
  60. package/src/db/schema.ts +15 -0
  61. package/src/i18n/locales/en.po +170 -58
  62. package/src/i18n/locales/en.ts +1 -1
  63. package/src/i18n/locales/zh-Hans.po +162 -71
  64. package/src/i18n/locales/zh-Hans.ts +1 -1
  65. package/src/i18n/locales/zh-Hant.po +162 -71
  66. package/src/i18n/locales/zh-Hant.ts +1 -1
  67. package/src/index.ts +13 -1
  68. package/src/lib/__tests__/schemas.test.ts +89 -1
  69. package/src/lib/__tests__/sse.test.ts +13 -1
  70. package/src/lib/__tests__/theme-components.test.ts +107 -0
  71. package/src/lib/image.ts +3 -3
  72. package/src/lib/media-helpers.ts +54 -0
  73. package/src/lib/nav-reorder.ts +26 -0
  74. package/src/lib/navigation.ts +46 -0
  75. package/src/lib/schemas.ts +47 -1
  76. package/src/lib/sse.ts +10 -11
  77. package/src/lib/theme-components.ts +76 -0
  78. package/src/routes/api/__tests__/posts.test.ts +239 -0
  79. package/src/routes/api/__tests__/timeline.test.ts +242 -0
  80. package/src/routes/api/posts.ts +134 -5
  81. package/src/routes/api/timeline.tsx +145 -0
  82. package/src/routes/api/upload.ts +9 -5
  83. package/src/routes/dash/media.tsx +50 -0
  84. package/src/routes/dash/navigation.tsx +306 -0
  85. package/src/routes/dash/posts.tsx +79 -7
  86. package/src/routes/feed/rss.ts +14 -1
  87. package/src/routes/pages/archive.tsx +15 -23
  88. package/src/routes/pages/collection.tsx +8 -15
  89. package/src/routes/pages/home.tsx +121 -88
  90. package/src/routes/pages/page.tsx +17 -30
  91. package/src/routes/pages/post.tsx +64 -40
  92. package/src/routes/pages/search.tsx +18 -22
  93. package/src/services/__tests__/collection.test.ts +102 -0
  94. package/src/services/__tests__/media.test.ts +282 -7
  95. package/src/services/__tests__/navigation.test.ts +213 -0
  96. package/src/services/__tests__/post-timeline.test.ts +220 -0
  97. package/src/services/collection.ts +19 -0
  98. package/src/services/index.ts +7 -0
  99. package/src/services/media.ts +78 -2
  100. package/src/services/navigation.ts +165 -0
  101. package/src/services/post.ts +48 -1
  102. package/src/styles/components.css +59 -0
  103. package/src/theme/components/MediaGallery.tsx +128 -0
  104. package/src/theme/components/PostForm.tsx +170 -2
  105. package/src/theme/components/PostList.tsx +7 -0
  106. package/src/theme/components/index.ts +13 -0
  107. package/src/theme/components/timeline/ArticleCard.tsx +57 -0
  108. package/src/theme/components/timeline/ImageCard.tsx +80 -0
  109. package/src/theme/components/timeline/LinkCard.tsx +66 -0
  110. package/src/theme/components/timeline/NoteCard.tsx +41 -0
  111. package/src/theme/components/timeline/QuoteCard.tsx +55 -0
  112. package/src/theme/components/timeline/ThreadPreview.tsx +49 -0
  113. package/src/theme/components/timeline/TimelineFeed.tsx +52 -0
  114. package/src/theme/components/timeline/TimelineItem.tsx +39 -0
  115. package/src/theme/components/timeline/index.ts +8 -0
  116. package/src/theme/layouts/DashLayout.tsx +10 -0
  117. package/src/theme/layouts/SiteLayout.tsx +184 -0
  118. package/src/theme/layouts/index.ts +1 -0
  119. package/src/types/sortablejs.d.ts +23 -0
  120. package/src/types.ts +97 -0
  121. package/dist/app.d.ts +0 -38
  122. package/dist/app.d.ts.map +0 -1
  123. package/dist/auth.d.ts +0 -25
  124. package/dist/auth.d.ts.map +0 -1
  125. package/dist/db/index.d.ts +0 -10
  126. package/dist/db/index.d.ts.map +0 -1
  127. package/dist/db/schema.d.ts +0 -1507
  128. package/dist/db/schema.d.ts.map +0 -1
  129. package/dist/i18n/Trans.d.ts +0 -25
  130. package/dist/i18n/Trans.d.ts.map +0 -1
  131. package/dist/i18n/context.d.ts +0 -69
  132. package/dist/i18n/context.d.ts.map +0 -1
  133. package/dist/i18n/detect.d.ts +0 -20
  134. package/dist/i18n/detect.d.ts.map +0 -1
  135. package/dist/i18n/i18n.d.ts +0 -32
  136. package/dist/i18n/i18n.d.ts.map +0 -1
  137. package/dist/i18n/index.d.ts +0 -41
  138. package/dist/i18n/index.d.ts.map +0 -1
  139. package/dist/i18n/locales/en.d.ts +0 -3
  140. package/dist/i18n/locales/en.d.ts.map +0 -1
  141. package/dist/i18n/locales/zh-Hans.d.ts +0 -3
  142. package/dist/i18n/locales/zh-Hans.d.ts.map +0 -1
  143. package/dist/i18n/locales/zh-Hant.d.ts +0 -3
  144. package/dist/i18n/locales/zh-Hant.d.ts.map +0 -1
  145. package/dist/i18n/locales.d.ts +0 -11
  146. package/dist/i18n/locales.d.ts.map +0 -1
  147. package/dist/i18n/middleware.d.ts +0 -21
  148. package/dist/i18n/middleware.d.ts.map +0 -1
  149. package/dist/index.d.ts +0 -16
  150. package/dist/index.d.ts.map +0 -1
  151. package/dist/lib/config.d.ts +0 -83
  152. package/dist/lib/config.d.ts.map +0 -1
  153. package/dist/lib/constants.d.ts +0 -37
  154. package/dist/lib/constants.d.ts.map +0 -1
  155. package/dist/lib/image.d.ts +0 -73
  156. package/dist/lib/image.d.ts.map +0 -1
  157. package/dist/lib/index.d.ts +0 -9
  158. package/dist/lib/index.d.ts.map +0 -1
  159. package/dist/lib/markdown.d.ts +0 -60
  160. package/dist/lib/markdown.d.ts.map +0 -1
  161. package/dist/lib/schemas.d.ts +0 -113
  162. package/dist/lib/schemas.d.ts.map +0 -1
  163. package/dist/lib/sqid.d.ts +0 -60
  164. package/dist/lib/sqid.d.ts.map +0 -1
  165. package/dist/lib/sse.d.ts +0 -192
  166. package/dist/lib/sse.d.ts.map +0 -1
  167. package/dist/lib/theme.d.ts +0 -44
  168. package/dist/lib/theme.d.ts.map +0 -1
  169. package/dist/lib/time.d.ts +0 -90
  170. package/dist/lib/time.d.ts.map +0 -1
  171. package/dist/lib/url.d.ts +0 -82
  172. package/dist/lib/url.d.ts.map +0 -1
  173. package/dist/middleware/auth.d.ts +0 -24
  174. package/dist/middleware/auth.d.ts.map +0 -1
  175. package/dist/middleware/onboarding.d.ts +0 -26
  176. package/dist/middleware/onboarding.d.ts.map +0 -1
  177. package/dist/routes/api/posts.d.ts +0 -13
  178. package/dist/routes/api/posts.d.ts.map +0 -1
  179. package/dist/routes/api/search.d.ts +0 -13
  180. package/dist/routes/api/search.d.ts.map +0 -1
  181. package/dist/routes/api/upload.d.ts +0 -16
  182. package/dist/routes/api/upload.d.ts.map +0 -1
  183. package/dist/routes/dash/collections.d.ts +0 -13
  184. package/dist/routes/dash/collections.d.ts.map +0 -1
  185. package/dist/routes/dash/index.d.ts +0 -15
  186. package/dist/routes/dash/index.d.ts.map +0 -1
  187. package/dist/routes/dash/media.d.ts +0 -16
  188. package/dist/routes/dash/media.d.ts.map +0 -1
  189. package/dist/routes/dash/pages.d.ts +0 -15
  190. package/dist/routes/dash/pages.d.ts.map +0 -1
  191. package/dist/routes/dash/posts.d.ts +0 -13
  192. package/dist/routes/dash/posts.d.ts.map +0 -1
  193. package/dist/routes/dash/redirects.d.ts +0 -13
  194. package/dist/routes/dash/redirects.d.ts.map +0 -1
  195. package/dist/routes/dash/settings.d.ts +0 -15
  196. package/dist/routes/dash/settings.d.ts.map +0 -1
  197. package/dist/routes/feed/rss.d.ts +0 -13
  198. package/dist/routes/feed/rss.d.ts.map +0 -1
  199. package/dist/routes/feed/sitemap.d.ts +0 -13
  200. package/dist/routes/feed/sitemap.d.ts.map +0 -1
  201. package/dist/routes/pages/archive.d.ts +0 -15
  202. package/dist/routes/pages/archive.d.ts.map +0 -1
  203. package/dist/routes/pages/collection.d.ts +0 -13
  204. package/dist/routes/pages/collection.d.ts.map +0 -1
  205. package/dist/routes/pages/home.d.ts +0 -13
  206. package/dist/routes/pages/home.d.ts.map +0 -1
  207. package/dist/routes/pages/page.d.ts +0 -15
  208. package/dist/routes/pages/page.d.ts.map +0 -1
  209. package/dist/routes/pages/post.d.ts +0 -13
  210. package/dist/routes/pages/post.d.ts.map +0 -1
  211. package/dist/routes/pages/search.d.ts +0 -13
  212. package/dist/routes/pages/search.d.ts.map +0 -1
  213. package/dist/services/collection.d.ts +0 -31
  214. package/dist/services/collection.d.ts.map +0 -1
  215. package/dist/services/index.d.ts +0 -28
  216. package/dist/services/index.d.ts.map +0 -1
  217. package/dist/services/media.d.ts +0 -27
  218. package/dist/services/media.d.ts.map +0 -1
  219. package/dist/services/post.d.ts +0 -31
  220. package/dist/services/post.d.ts.map +0 -1
  221. package/dist/services/redirect.d.ts +0 -15
  222. package/dist/services/redirect.d.ts.map +0 -1
  223. package/dist/services/search.d.ts +0 -26
  224. package/dist/services/search.d.ts.map +0 -1
  225. package/dist/services/settings.d.ts +0 -18
  226. package/dist/services/settings.d.ts.map +0 -1
  227. package/dist/theme/color-themes.d.ts +0 -30
  228. package/dist/theme/color-themes.d.ts.map +0 -1
  229. package/dist/theme/components/ActionButtons.d.ts +0 -43
  230. package/dist/theme/components/ActionButtons.d.ts.map +0 -1
  231. package/dist/theme/components/CrudPageHeader.d.ts +0 -23
  232. package/dist/theme/components/CrudPageHeader.d.ts.map +0 -1
  233. package/dist/theme/components/DangerZone.d.ts +0 -36
  234. package/dist/theme/components/DangerZone.d.ts.map +0 -1
  235. package/dist/theme/components/EmptyState.d.ts +0 -27
  236. package/dist/theme/components/EmptyState.d.ts.map +0 -1
  237. package/dist/theme/components/ListItemRow.d.ts +0 -15
  238. package/dist/theme/components/ListItemRow.d.ts.map +0 -1
  239. package/dist/theme/components/PageForm.d.ts +0 -14
  240. package/dist/theme/components/PageForm.d.ts.map +0 -1
  241. package/dist/theme/components/Pagination.d.ts +0 -46
  242. package/dist/theme/components/Pagination.d.ts.map +0 -1
  243. package/dist/theme/components/PostForm.d.ts +0 -11
  244. package/dist/theme/components/PostForm.d.ts.map +0 -1
  245. package/dist/theme/components/PostList.d.ts +0 -10
  246. package/dist/theme/components/PostList.d.ts.map +0 -1
  247. package/dist/theme/components/ThreadView.d.ts +0 -15
  248. package/dist/theme/components/ThreadView.d.ts.map +0 -1
  249. package/dist/theme/components/TypeBadge.d.ts +0 -12
  250. package/dist/theme/components/TypeBadge.d.ts.map +0 -1
  251. package/dist/theme/components/VisibilityBadge.d.ts +0 -12
  252. package/dist/theme/components/VisibilityBadge.d.ts.map +0 -1
  253. package/dist/theme/components/index.d.ts +0 -13
  254. package/dist/theme/components/index.d.ts.map +0 -1
  255. package/dist/theme/index.d.ts +0 -21
  256. package/dist/theme/index.d.ts.map +0 -1
  257. package/dist/theme/layouts/BaseLayout.d.ts +0 -23
  258. package/dist/theme/layouts/BaseLayout.d.ts.map +0 -1
  259. package/dist/theme/layouts/DashLayout.d.ts +0 -17
  260. package/dist/theme/layouts/DashLayout.d.ts.map +0 -1
  261. package/dist/theme/layouts/index.d.ts +0 -3
  262. package/dist/theme/layouts/index.d.ts.map +0 -1
  263. package/dist/types.d.ts +0 -213
  264. package/dist/types.d.ts.map +0 -1
@@ -2,16 +2,21 @@
2
2
  * Post Creation/Edit Form
3
3
  */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
4
4
  import { useLingui as $_useLingui } from "@jant/core/i18n";
5
- export const PostForm = ({ post, action })=>{
5
+ import { getMediaUrl, getImageUrl } from "../../lib/image.js";
6
+ export const PostForm = ({ post, action, mediaAttachments, r2PublicUrl, imageTransformUrl, collections, postCollectionIds })=>{
6
7
  const { i18n: $__i18n, _: $__ } = $_useLingui();
7
8
  const isEdit = !!post;
9
+ const existingMediaIds = (mediaAttachments ?? []).map((m)=>m.id);
8
10
  const signals = JSON.stringify({
9
11
  type: post?.type ?? "note",
10
12
  title: post?.title ?? "",
11
13
  content: post?.content ?? "",
12
14
  sourceUrl: post?.sourceUrl ?? "",
15
+ sourceName: post?.sourceName ?? "",
13
16
  visibility: post?.visibility ?? "quiet",
14
- path: post?.path ?? ""
17
+ path: post?.path ?? "",
18
+ mediaIds: existingMediaIds,
19
+ collectionIds: postCollectionIds ?? []
15
20
  }).replace(/</g, "\\u003c");
16
21
  return /*#__PURE__*/ _jsxs("form", {
17
22
  "data-signals": signals,
@@ -123,6 +128,70 @@ export const PostForm = ({ post, action })=>{
123
128
  })
124
129
  ]
125
130
  }),
131
+ /*#__PURE__*/ _jsxs("div", {
132
+ class: "field",
133
+ "data-show": "$type !== 'page'",
134
+ children: [
135
+ /*#__PURE__*/ _jsx("label", {
136
+ class: "label",
137
+ children: $__i18n._({
138
+ id: "xYilR2",
139
+ message: "Media"
140
+ })
141
+ }),
142
+ /*#__PURE__*/ _jsx("p", {
143
+ class: "text-xs text-muted-foreground mb-2",
144
+ "data-show": "$type === 'image'",
145
+ children: $__i18n._({
146
+ id: "I8hDlV",
147
+ message: "At least 1 image required for image posts."
148
+ })
149
+ }),
150
+ mediaAttachments && mediaAttachments.length > 0 && /*#__PURE__*/ _jsx("div", {
151
+ class: "grid grid-cols-4 sm:grid-cols-6 gap-2 mb-2",
152
+ children: mediaAttachments.map((m)=>{
153
+ const url = getMediaUrl(m.id, m.r2Key, r2PublicUrl);
154
+ const thumbUrl = getImageUrl(url, imageTransformUrl, {
155
+ width: 150,
156
+ quality: 80,
157
+ format: "auto",
158
+ fit: "cover"
159
+ });
160
+ return /*#__PURE__*/ _jsxs("div", {
161
+ class: "relative group aspect-square",
162
+ "data-show": `$mediaIds.includes('${m.id}')`,
163
+ children: [
164
+ /*#__PURE__*/ _jsx("img", {
165
+ src: thumbUrl,
166
+ alt: m.alt || m.originalName,
167
+ class: "w-full h-full object-cover rounded-lg border",
168
+ loading: "lazy"
169
+ }),
170
+ /*#__PURE__*/ _jsx("button", {
171
+ type: "button",
172
+ class: "absolute top-1 right-1 w-5 h-5 flex items-center justify-center bg-black/60 text-white rounded-full text-xs opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer",
173
+ "data-on:click": `$mediaIds = $mediaIds.filter(id => id !== '${m.id}')`,
174
+ title: $__i18n._({
175
+ id: "t/YqKh",
176
+ message: "Remove"
177
+ }),
178
+ children: "×"
179
+ })
180
+ ]
181
+ }, m.id);
182
+ })
183
+ }),
184
+ /*#__PURE__*/ _jsx("button", {
185
+ type: "button",
186
+ class: "btn-outline text-sm",
187
+ "data-on:click": "document.getElementById('media-picker-dialog').showModal(); fetch('/dash/media/picker').then(r => r.text()).then(html => document.getElementById('media-picker-grid').innerHTML = html)",
188
+ children: $__i18n._({
189
+ id: "qiXmlF",
190
+ message: "Add Media"
191
+ })
192
+ })
193
+ ]
194
+ }),
126
195
  /*#__PURE__*/ _jsxs("div", {
127
196
  class: "field",
128
197
  children: [
@@ -141,6 +210,27 @@ export const PostForm = ({ post, action })=>{
141
210
  })
142
211
  ]
143
212
  }),
213
+ /*#__PURE__*/ _jsxs("div", {
214
+ class: "field",
215
+ children: [
216
+ /*#__PURE__*/ _jsx("label", {
217
+ class: "label",
218
+ children: $__i18n._({
219
+ id: "oJFOZk",
220
+ message: "Source Name (optional)"
221
+ })
222
+ }),
223
+ /*#__PURE__*/ _jsx("input", {
224
+ type: "text",
225
+ "data-bind": "sourceName",
226
+ class: "input",
227
+ placeholder: $__i18n._({
228
+ id: "1o+wgo",
229
+ message: "e.g. The Verge, John Doe"
230
+ })
231
+ })
232
+ ]
233
+ }),
144
234
  /*#__PURE__*/ _jsxs("div", {
145
235
  class: "field",
146
236
  children: [
@@ -191,6 +281,33 @@ export const PostForm = ({ post, action })=>{
191
281
  })
192
282
  ]
193
283
  }),
284
+ collections && collections.length > 0 && /*#__PURE__*/ _jsxs("fieldset", {
285
+ class: "field",
286
+ children: [
287
+ /*#__PURE__*/ _jsx("legend", {
288
+ class: "label",
289
+ children: $__i18n._({
290
+ id: "MWBOxm",
291
+ message: "Collections (optional)"
292
+ })
293
+ }),
294
+ /*#__PURE__*/ _jsx("div", {
295
+ class: "flex flex-col gap-1",
296
+ children: collections.map((col)=>/*#__PURE__*/ _jsxs("label", {
297
+ class: "flex items-center gap-2 text-sm",
298
+ children: [
299
+ /*#__PURE__*/ _jsx("input", {
300
+ type: "checkbox",
301
+ class: "checkbox",
302
+ "data-attr:checked": `$collectionIds.includes(${col.id})`,
303
+ "data-on:change": `$collectionIds.includes(${col.id}) ? $collectionIds = $collectionIds.filter(id => id !== ${col.id}) : $collectionIds = [...$collectionIds, ${col.id}]`
304
+ }),
305
+ col.title
306
+ ]
307
+ }, col.id))
308
+ })
309
+ ]
310
+ }),
194
311
  /*#__PURE__*/ _jsxs("div", {
195
312
  class: "field",
196
313
  children: [
@@ -232,6 +349,45 @@ export const PostForm = ({ post, action })=>{
232
349
  })
233
350
  })
234
351
  ]
352
+ }),
353
+ /*#__PURE__*/ _jsxs("dialog", {
354
+ id: "media-picker-dialog",
355
+ class: "p-6 rounded-lg max-w-2xl w-full backdrop:bg-black/50",
356
+ onclick: "event.target === this && this.close()",
357
+ children: [
358
+ /*#__PURE__*/ _jsxs("div", {
359
+ class: "flex items-center justify-between mb-4",
360
+ children: [
361
+ /*#__PURE__*/ _jsx("h2", {
362
+ class: "text-lg font-semibold",
363
+ children: $__i18n._({
364
+ id: "2fUwEY",
365
+ message: "Select Media"
366
+ })
367
+ }),
368
+ /*#__PURE__*/ _jsx("button", {
369
+ type: "button",
370
+ class: "btn-outline text-sm",
371
+ onclick: "this.closest('dialog').close()",
372
+ children: $__i18n._({
373
+ id: "DPfwMq",
374
+ message: "Done"
375
+ })
376
+ })
377
+ ]
378
+ }),
379
+ /*#__PURE__*/ _jsx("div", {
380
+ id: "media-picker-grid",
381
+ class: "grid grid-cols-4 gap-2 max-h-96 overflow-y-auto",
382
+ children: /*#__PURE__*/ _jsx("p", {
383
+ class: "text-muted-foreground text-sm col-span-4",
384
+ children: $__i18n._({
385
+ id: "Z3FXyt",
386
+ message: "Loading..."
387
+ })
388
+ })
389
+ })
390
+ ]
235
391
  })
236
392
  ]
237
393
  });
@@ -37,6 +37,11 @@ export const PostList = ({ posts })=>{
37
37
  viewLabel: $__i18n._({
38
38
  id: "jpctdh",
39
39
  message: "View"
40
+ }),
41
+ deleteAction: `/dash/posts/${sqid.encode(post.id)}/delete`,
42
+ deleteConfirm: $__i18n._({
43
+ id: "KmGXnO",
44
+ message: "Are you sure you want to delete this post? This cannot be undone."
40
45
  })
41
46
  }),
42
47
  children: [
@@ -3,6 +3,7 @@ export { CrudPageHeader } from "./CrudPageHeader.js";
3
3
  export { DangerZone } from "./DangerZone.js";
4
4
  export { EmptyState } from "./EmptyState.js";
5
5
  export { ListItemRow } from "./ListItemRow.js";
6
+ export { MediaGallery } from "./MediaGallery.js";
6
7
  export { PageForm } from "./PageForm.js";
7
8
  export { Pagination, LoadMore, PagePagination } from "./Pagination.js";
8
9
  export { PostForm } from "./PostForm.js";
@@ -10,3 +11,5 @@ export { PostList } from "./PostList.js";
10
11
  export { ThreadView } from "./ThreadView.js";
11
12
  export { TypeBadge } from "./TypeBadge.js";
12
13
  export { VisibilityBadge } from "./VisibilityBadge.js";
14
+ // Timeline components
15
+ export { NoteCard, ArticleCard, LinkCard, QuoteCard, ImageCard, ThreadPreview, TimelineItem, TimelineFeed } from "./timeline/index.js";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Article Card Component
3
+ *
4
+ * Prominent title + excerpt for type="article" posts.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import * as sqid from "../../../lib/sqid.js";
7
+ import * as time from "../../../lib/time.js";
8
+ export const ArticleCard = ({ post, compact })=>{
9
+ const permalink = `/p/${sqid.encode(post.id)}`;
10
+ const excerpt = post.content ? post.content.length > 160 ? post.content.slice(0, 160) + "..." : post.content : null;
11
+ return /*#__PURE__*/ _jsxs("article", {
12
+ class: `h-entry timeline-card${compact ? " timeline-card-compact" : ""}`,
13
+ children: [
14
+ post.title && /*#__PURE__*/ _jsx("h2", {
15
+ class: `p-name font-semibold ${compact ? "text-sm" : "text-lg"} mb-1`,
16
+ children: /*#__PURE__*/ _jsx("a", {
17
+ href: permalink,
18
+ class: "u-url hover:underline",
19
+ children: post.title
20
+ })
21
+ }),
22
+ !compact && excerpt && /*#__PURE__*/ _jsx("p", {
23
+ class: "e-content text-sm text-muted-foreground line-clamp-3",
24
+ children: excerpt
25
+ }),
26
+ /*#__PURE__*/ _jsxs("footer", {
27
+ class: "mt-2 text-xs text-muted-foreground",
28
+ children: [
29
+ /*#__PURE__*/ _jsx("a", {
30
+ href: permalink,
31
+ class: "u-url hover:underline",
32
+ children: /*#__PURE__*/ _jsx("time", {
33
+ class: "dt-published",
34
+ datetime: time.toISOString(post.publishedAt),
35
+ children: time.formatDate(post.publishedAt)
36
+ })
37
+ }),
38
+ !compact && /*#__PURE__*/ _jsx("span", {
39
+ class: "ml-2",
40
+ children: /*#__PURE__*/ _jsx("a", {
41
+ href: permalink,
42
+ class: "hover:underline",
43
+ children: "Read more →"
44
+ })
45
+ })
46
+ ]
47
+ })
48
+ ]
49
+ });
50
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Image Card Component
3
+ *
4
+ * Image-first layout for type="image" posts.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import { MediaGallery } from "../MediaGallery.js";
7
+ import * as sqid from "../../../lib/sqid.js";
8
+ import * as time from "../../../lib/time.js";
9
+ export const ImageCard = ({ post, compact })=>{
10
+ const permalink = `/p/${sqid.encode(post.id)}`;
11
+ if (compact) {
12
+ return /*#__PURE__*/ _jsxs("article", {
13
+ class: "h-entry timeline-card timeline-card-compact",
14
+ children: [
15
+ post.title && /*#__PURE__*/ _jsx("h2", {
16
+ class: "p-name text-sm font-medium mb-1",
17
+ children: /*#__PURE__*/ _jsx("a", {
18
+ href: permalink,
19
+ class: "u-url hover:underline",
20
+ children: post.title
21
+ })
22
+ }),
23
+ post.contentHtml && /*#__PURE__*/ _jsx("div", {
24
+ class: "e-content prose prose-sm text-muted-foreground",
25
+ dangerouslySetInnerHTML: {
26
+ __html: post.contentHtml
27
+ }
28
+ }),
29
+ /*#__PURE__*/ _jsx("footer", {
30
+ class: "mt-1 text-xs text-muted-foreground",
31
+ children: /*#__PURE__*/ _jsx("a", {
32
+ href: permalink,
33
+ class: "u-url hover:underline",
34
+ children: /*#__PURE__*/ _jsx("time", {
35
+ class: "dt-published",
36
+ datetime: time.toISOString(post.publishedAt),
37
+ children: time.formatDate(post.publishedAt)
38
+ })
39
+ })
40
+ })
41
+ ]
42
+ });
43
+ }
44
+ return /*#__PURE__*/ _jsxs("article", {
45
+ class: "h-entry timeline-card timeline-card-image",
46
+ children: [
47
+ post.mediaAttachments.length > 0 && /*#__PURE__*/ _jsx("div", {
48
+ class: "timeline-card-image-gallery",
49
+ children: /*#__PURE__*/ _jsx(MediaGallery, {
50
+ attachments: post.mediaAttachments
51
+ })
52
+ }),
53
+ /*#__PURE__*/ _jsxs("div", {
54
+ class: "p-4",
55
+ children: [
56
+ post.title && /*#__PURE__*/ _jsx("h2", {
57
+ class: "p-name font-medium mb-1",
58
+ children: /*#__PURE__*/ _jsx("a", {
59
+ href: permalink,
60
+ class: "u-url hover:underline",
61
+ children: post.title
62
+ })
63
+ }),
64
+ post.contentHtml && /*#__PURE__*/ _jsx("div", {
65
+ class: "e-content prose prose-sm",
66
+ dangerouslySetInnerHTML: {
67
+ __html: post.contentHtml
68
+ }
69
+ }),
70
+ /*#__PURE__*/ _jsx("footer", {
71
+ class: "mt-2 text-xs text-muted-foreground",
72
+ children: /*#__PURE__*/ _jsx("a", {
73
+ href: permalink,
74
+ class: "u-url hover:underline",
75
+ children: /*#__PURE__*/ _jsx("time", {
76
+ class: "dt-published",
77
+ datetime: time.toISOString(post.publishedAt),
78
+ children: time.formatDate(post.publishedAt)
79
+ })
80
+ })
81
+ })
82
+ ]
83
+ })
84
+ ]
85
+ });
86
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Link Card Component
3
+ *
4
+ * External link emphasis for type="link" posts.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import * as sqid from "../../../lib/sqid.js";
7
+ import * as time from "../../../lib/time.js";
8
+ export const LinkCard = ({ post, compact })=>{
9
+ const permalink = `/p/${sqid.encode(post.id)}`;
10
+ return /*#__PURE__*/ _jsxs("article", {
11
+ class: `h-entry timeline-card timeline-card-link${compact ? " timeline-card-compact" : ""}`,
12
+ children: [
13
+ post.sourceDomain && /*#__PURE__*/ _jsxs("div", {
14
+ class: "text-xs text-muted-foreground mb-1 flex items-center gap-1",
15
+ children: [
16
+ /*#__PURE__*/ _jsx("svg", {
17
+ class: "size-3",
18
+ xmlns: "http://www.w3.org/2000/svg",
19
+ fill: "none",
20
+ viewBox: "0 0 24 24",
21
+ "stroke-width": "2",
22
+ stroke: "currentColor",
23
+ children: /*#__PURE__*/ _jsx("path", {
24
+ d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"
25
+ })
26
+ }),
27
+ /*#__PURE__*/ _jsx("span", {
28
+ children: post.sourceDomain
29
+ })
30
+ ]
31
+ }),
32
+ post.title && /*#__PURE__*/ _jsx("h2", {
33
+ class: `p-name font-semibold ${compact ? "text-sm" : "text-base"} mb-1`,
34
+ children: /*#__PURE__*/ _jsx("a", {
35
+ href: post.sourceUrl || permalink,
36
+ class: "u-url hover:underline",
37
+ target: post.sourceUrl ? "_blank" : undefined,
38
+ rel: post.sourceUrl ? "noopener noreferrer" : undefined,
39
+ children: post.title
40
+ })
41
+ }),
42
+ !compact && post.contentHtml && /*#__PURE__*/ _jsx("div", {
43
+ class: "e-content prose prose-sm text-muted-foreground",
44
+ dangerouslySetInnerHTML: {
45
+ __html: post.contentHtml
46
+ }
47
+ }),
48
+ /*#__PURE__*/ _jsx("footer", {
49
+ class: "mt-2 text-xs text-muted-foreground",
50
+ children: /*#__PURE__*/ _jsx("a", {
51
+ href: permalink,
52
+ class: "hover:underline",
53
+ children: /*#__PURE__*/ _jsx("time", {
54
+ class: "dt-published",
55
+ datetime: time.toISOString(post.publishedAt),
56
+ children: time.formatDate(post.publishedAt)
57
+ })
58
+ })
59
+ })
60
+ ]
61
+ });
62
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Note Card Component
3
+ *
4
+ * Text-first, minimal card for type="note" posts.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import { MediaGallery } from "../MediaGallery.js";
7
+ import * as sqid from "../../../lib/sqid.js";
8
+ import * as time from "../../../lib/time.js";
9
+ export const NoteCard = ({ post, compact })=>{
10
+ const permalink = `/p/${sqid.encode(post.id)}`;
11
+ return /*#__PURE__*/ _jsxs("article", {
12
+ class: `h-entry timeline-card${compact ? " timeline-card-compact" : ""}`,
13
+ children: [
14
+ post.contentHtml && /*#__PURE__*/ _jsx("div", {
15
+ class: `e-content prose ${compact ? "prose-sm" : "prose-sm"}`,
16
+ dangerouslySetInnerHTML: {
17
+ __html: post.contentHtml
18
+ }
19
+ }),
20
+ !compact && post.mediaAttachments.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
21
+ attachments: post.mediaAttachments
22
+ }),
23
+ /*#__PURE__*/ _jsx("footer", {
24
+ class: "mt-2 text-xs text-muted-foreground",
25
+ children: /*#__PURE__*/ _jsx("a", {
26
+ href: permalink,
27
+ class: "u-url hover:underline",
28
+ children: /*#__PURE__*/ _jsx("time", {
29
+ class: "dt-published",
30
+ datetime: time.toISOString(post.publishedAt),
31
+ children: time.formatDate(post.publishedAt)
32
+ })
33
+ })
34
+ })
35
+ ]
36
+ });
37
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Quote Card Component
3
+ *
4
+ * Blockquote + attribution for type="quote" posts.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import * as sqid from "../../../lib/sqid.js";
7
+ import * as time from "../../../lib/time.js";
8
+ export const QuoteCard = ({ post, compact })=>{
9
+ const permalink = `/p/${sqid.encode(post.id)}`;
10
+ return /*#__PURE__*/ _jsxs("article", {
11
+ class: `h-entry timeline-card timeline-card-quote${compact ? " timeline-card-compact" : ""}`,
12
+ children: [
13
+ post.contentHtml && /*#__PURE__*/ _jsx("blockquote", {
14
+ class: `e-content italic ${compact ? "text-sm" : "text-base"} leading-relaxed`,
15
+ children: /*#__PURE__*/ _jsx("div", {
16
+ dangerouslySetInnerHTML: {
17
+ __html: post.contentHtml
18
+ }
19
+ })
20
+ }),
21
+ !compact && (post.sourceName || post.sourceUrl) && /*#__PURE__*/ _jsxs("div", {
22
+ class: "mt-2 text-sm text-muted-foreground",
23
+ children: [
24
+ "—",
25
+ " ",
26
+ post.sourceUrl ? /*#__PURE__*/ _jsx("a", {
27
+ href: post.sourceUrl,
28
+ class: "hover:underline",
29
+ target: "_blank",
30
+ rel: "noopener noreferrer",
31
+ children: post.sourceName || post.sourceDomain || "Source"
32
+ }) : /*#__PURE__*/ _jsx("span", {
33
+ children: post.sourceName
34
+ })
35
+ ]
36
+ }),
37
+ /*#__PURE__*/ _jsx("footer", {
38
+ class: "mt-2 text-xs text-muted-foreground",
39
+ children: /*#__PURE__*/ _jsx("a", {
40
+ href: permalink,
41
+ class: "u-url hover:underline",
42
+ children: /*#__PURE__*/ _jsx("time", {
43
+ class: "dt-published",
44
+ datetime: time.toISOString(post.publishedAt),
45
+ children: time.formatDate(post.publishedAt)
46
+ })
47
+ })
48
+ })
49
+ ]
50
+ });
51
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Thread Preview Component
3
+ *
4
+ * Inline thread preview: root card + compact replies + "show more" link.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
7
+ import { TimelineItem } from "./TimelineItem.js";
8
+ import * as sqid from "../../../lib/sqid.js";
9
+ export const ThreadPreview = ({ rootPost, previewReplies, totalReplyCount })=>{
10
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
11
+ const permalink = `/p/${sqid.encode(rootPost.id)}`;
12
+ const remainingCount = totalReplyCount - previewReplies.length;
13
+ return /*#__PURE__*/ _jsxs("div", {
14
+ class: "timeline-thread",
15
+ children: [
16
+ /*#__PURE__*/ _jsx(TimelineItem, {
17
+ item: {
18
+ post: rootPost
19
+ }
20
+ }),
21
+ previewReplies.length > 0 && /*#__PURE__*/ _jsxs("div", {
22
+ class: "timeline-thread-replies",
23
+ children: [
24
+ previewReplies.map((reply)=>/*#__PURE__*/ _jsx("div", {
25
+ class: "timeline-thread-reply",
26
+ children: /*#__PURE__*/ _jsx(TimelineItem, {
27
+ item: {
28
+ post: reply
29
+ },
30
+ compact: true
31
+ })
32
+ }, reply.id)),
33
+ remainingCount > 0 && /*#__PURE__*/ _jsx("div", {
34
+ class: "timeline-thread-reply",
35
+ children: /*#__PURE__*/ _jsx("a", {
36
+ href: permalink,
37
+ class: "text-sm text-muted-foreground hover:text-foreground hover:underline",
38
+ children: $__i18n._({
39
+ id: "smzF8S",
40
+ message: "Show {remainingCount} more {0}",
41
+ values: {
42
+ remainingCount: remainingCount,
43
+ 0: remainingCount === 1 ? "reply" : "replies"
44
+ }
45
+ })
46
+ })
47
+ })
48
+ ]
49
+ })
50
+ ]
51
+ });
52
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Timeline Feed Component
3
+ *
4
+ * Main feed wrapper with load-more button.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
7
+ import { TimelineItem } from "./TimelineItem.js";
8
+ import { ThreadPreview } from "./ThreadPreview.js";
9
+ export const TimelineFeed = ({ items, hasMore, nextCursor })=>{
10
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
11
+ return /*#__PURE__*/ _jsxs("div", {
12
+ children: [
13
+ /*#__PURE__*/ _jsx("div", {
14
+ id: "timeline-feed",
15
+ class: "flex flex-col gap-4",
16
+ children: items.map((item)=>{
17
+ if (item.threadPreview) {
18
+ return /*#__PURE__*/ _jsx(ThreadPreview, {
19
+ rootPost: item.post,
20
+ previewReplies: item.threadPreview.replies,
21
+ totalReplyCount: item.threadPreview.totalReplyCount
22
+ }, item.post.id);
23
+ }
24
+ return /*#__PURE__*/ _jsx(TimelineItem, {
25
+ item: item
26
+ }, item.post.id);
27
+ })
28
+ }),
29
+ hasMore && nextCursor && /*#__PURE__*/ _jsx("div", {
30
+ id: "load-more-container",
31
+ class: "mt-6 text-center",
32
+ children: /*#__PURE__*/ _jsx("button", {
33
+ class: "btn btn-outline",
34
+ "data-on:click": `@get('/api/timeline?cursor=${nextCursor}')`,
35
+ children: $__i18n._({
36
+ id: "yQ2kGp",
37
+ message: "Load more"
38
+ })
39
+ })
40
+ })
41
+ ]
42
+ });
43
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Timeline Item Component
3
+ *
4
+ * Dispatches to the correct card component based on post type.
5
+ */ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
6
+ import { NoteCard } from "./NoteCard.js";
7
+ import { ArticleCard } from "./ArticleCard.js";
8
+ import { LinkCard } from "./LinkCard.js";
9
+ import { QuoteCard } from "./QuoteCard.js";
10
+ import { ImageCard } from "./ImageCard.js";
11
+ const CARD_MAP = {
12
+ note: NoteCard,
13
+ article: ArticleCard,
14
+ link: LinkCard,
15
+ quote: QuoteCard,
16
+ image: ImageCard,
17
+ page: NoteCard
18
+ };
19
+ export const TimelineItem = ({ item, compact, cardOverride })=>{
20
+ const Card = cardOverride ?? CARD_MAP[item.post.type];
21
+ return /*#__PURE__*/ _jsx(Card, {
22
+ post: item.post,
23
+ compact: compact
24
+ });
25
+ };
@@ -0,0 +1,8 @@
1
+ export { NoteCard } from "./NoteCard.js";
2
+ export { ArticleCard } from "./ArticleCard.js";
3
+ export { LinkCard } from "./LinkCard.js";
4
+ export { QuoteCard } from "./QuoteCard.js";
5
+ export { ImageCard } from "./ImageCard.js";
6
+ export { ThreadPreview } from "./ThreadPreview.js";
7
+ export { TimelineItem } from "./TimelineItem.js";
8
+ export { TimelineFeed } from "./TimelineFeed.js";