@jant/core 0.3.24 → 0.3.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/dist/app.js +101 -571
  2. package/dist/client.js +1 -0
  3. package/dist/db/schema.js +1 -1
  4. package/dist/i18n/locales/en.js +1 -1
  5. package/dist/i18n/locales/zh-Hans.js +1 -1
  6. package/dist/i18n/locales/zh-Hant.js +1 -1
  7. package/dist/index.js +3 -9
  8. package/dist/lib/avatar-upload.js +134 -0
  9. package/dist/lib/config.js +39 -0
  10. package/dist/lib/constants.js +10 -9
  11. package/dist/lib/favicon.js +102 -0
  12. package/dist/lib/image.js +13 -17
  13. package/dist/lib/media-helpers.js +2 -2
  14. package/dist/lib/nav-reorder.js +1 -1
  15. package/dist/lib/navigation.js +48 -3
  16. package/dist/lib/pagination.js +44 -0
  17. package/dist/lib/render.js +16 -11
  18. package/dist/lib/schemas.js +34 -3
  19. package/dist/lib/theme.js +4 -4
  20. package/dist/lib/timeline.js +24 -48
  21. package/dist/lib/timezones.js +388 -0
  22. package/dist/lib/view.js +3 -3
  23. package/dist/routes/api/collections.js +124 -0
  24. package/dist/routes/api/nav-items.js +104 -0
  25. package/dist/routes/api/pages.js +91 -0
  26. package/dist/routes/api/posts.js +3 -3
  27. package/dist/routes/api/search.js +2 -2
  28. package/dist/routes/api/settings.js +68 -0
  29. package/dist/routes/api/upload.js +3 -3
  30. package/dist/routes/auth/reset.js +221 -0
  31. package/dist/routes/auth/setup.js +194 -0
  32. package/dist/routes/auth/signin.js +176 -0
  33. package/dist/routes/compose.js +48 -0
  34. package/dist/routes/dash/collections.js +24 -416
  35. package/dist/routes/dash/index.js +1 -1
  36. package/dist/routes/dash/media.js +13 -393
  37. package/dist/routes/dash/pages.js +112 -86
  38. package/dist/routes/dash/posts.js +3 -5
  39. package/dist/routes/dash/redirects.js +20 -14
  40. package/dist/routes/dash/settings.js +213 -518
  41. package/dist/routes/feed/rss.js +4 -3
  42. package/dist/routes/feed/sitemap.js +5 -3
  43. package/dist/routes/pages/archive.js +3 -6
  44. package/dist/routes/pages/collection.js +3 -6
  45. package/dist/routes/pages/collections.js +28 -0
  46. package/dist/routes/pages/featured.js +36 -0
  47. package/dist/routes/pages/home.js +33 -49
  48. package/dist/routes/pages/latest.js +45 -0
  49. package/dist/routes/pages/page.js +29 -32
  50. package/dist/routes/pages/post.js +3 -6
  51. package/dist/routes/pages/search.js +3 -6
  52. package/dist/services/page.js +5 -1
  53. package/dist/services/post.js +45 -31
  54. package/dist/services/search.js +1 -1
  55. package/dist/types/bindings.js +3 -0
  56. package/dist/types/config.js +147 -0
  57. package/dist/types/constants.js +27 -0
  58. package/dist/types/entities.js +3 -0
  59. package/dist/types/operations.js +3 -0
  60. package/dist/types/props.js +3 -0
  61. package/dist/types/views.js +5 -0
  62. package/dist/types.js +8 -111
  63. package/dist/{theme → ui}/color-themes.js +33 -33
  64. package/dist/ui/compose/ComposeDialog.js +467 -0
  65. package/dist/ui/compose/ComposePrompt.js +55 -0
  66. package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +1 -2
  67. package/dist/{theme/components → ui/dash}/PageForm.js +21 -15
  68. package/dist/{theme/components → ui/dash}/PostForm.js +22 -43
  69. package/dist/{theme/components → ui/dash}/PostList.js +6 -6
  70. package/dist/{theme/components/VisibilityBadge.js → ui/dash/StatusBadge.js} +1 -2
  71. package/dist/ui/dash/collections/CollectionForm.js +152 -0
  72. package/dist/ui/dash/collections/CollectionsListContent.js +68 -0
  73. package/dist/ui/dash/collections/ViewCollectionContent.js +96 -0
  74. package/dist/{theme/components → ui/dash}/index.js +3 -6
  75. package/dist/ui/dash/media/MediaListContent.js +166 -0
  76. package/dist/ui/dash/media/ViewMediaContent.js +212 -0
  77. package/dist/ui/dash/pages/LinkFormContent.js +130 -0
  78. package/dist/ui/dash/pages/UnifiedPagesContent.js +193 -0
  79. package/dist/ui/dash/settings/AccountContent.js +209 -0
  80. package/dist/ui/dash/settings/AppearanceContent.js +259 -0
  81. package/dist/ui/dash/settings/GeneralContent.js +536 -0
  82. package/dist/ui/dash/settings/SettingsNav.js +41 -0
  83. package/dist/{themes/threads/timeline → ui/feed}/LinkCard.js +6 -2
  84. package/dist/{themes/threads/timeline → ui/feed}/NoteCard.js +11 -6
  85. package/dist/{themes/threads/timeline → ui/feed}/QuoteCard.js +10 -6
  86. package/dist/{themes/threads/timeline → ui/feed}/ThreadPreview.js +7 -9
  87. package/dist/ui/feed/TimelineFeed.js +41 -0
  88. package/dist/ui/feed/TimelineItem.js +27 -0
  89. package/dist/ui/font-themes.js +36 -0
  90. package/dist/{theme → ui}/layouts/BaseLayout.js +34 -2
  91. package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
  92. package/dist/ui/layouts/SiteLayout.js +169 -0
  93. package/dist/{themes/threads → ui}/pages/ArchivePage.js +16 -14
  94. package/dist/{themes/threads → ui}/pages/CollectionPage.js +6 -1
  95. package/dist/ui/pages/CollectionsPage.js +76 -0
  96. package/dist/ui/pages/FeaturedPage.js +24 -0
  97. package/dist/ui/pages/HomePage.js +24 -0
  98. package/dist/{themes/threads → ui}/pages/PostPage.js +13 -8
  99. package/dist/{themes/threads → ui}/pages/SearchPage.js +9 -7
  100. package/dist/{themes/threads → ui}/pages/SinglePage.js +3 -2
  101. package/dist/{theme/components → ui/shared}/MediaGallery.js +1 -1
  102. package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
  103. package/dist/{theme/components → ui/shared}/ThreadView.js +2 -2
  104. package/dist/ui/shared/index.js +5 -0
  105. package/package.json +1 -9
  106. package/src/__tests__/helpers/db.ts +3 -0
  107. package/src/app.tsx +131 -561
  108. package/src/client.ts +1 -0
  109. package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
  110. package/src/db/migrations/meta/_journal.json +7 -0
  111. package/src/db/schema.ts +1 -1
  112. package/src/i18n/locales/en.po +477 -261
  113. package/src/i18n/locales/en.ts +1 -1
  114. package/src/i18n/locales/zh-Hans.po +477 -261
  115. package/src/i18n/locales/zh-Hans.ts +1 -1
  116. package/src/i18n/locales/zh-Hant.po +477 -261
  117. package/src/i18n/locales/zh-Hant.ts +1 -1
  118. package/src/index.ts +7 -36
  119. package/src/lib/__tests__/config.test.ts +192 -0
  120. package/src/lib/__tests__/favicon.test.ts +151 -0
  121. package/src/lib/__tests__/image.test.ts +2 -6
  122. package/src/lib/__tests__/schemas.test.ts +60 -19
  123. package/src/lib/__tests__/timeline.test.ts +45 -81
  124. package/src/lib/__tests__/timezones.test.ts +61 -0
  125. package/src/lib/__tests__/view.test.ts +15 -9
  126. package/src/lib/avatar-upload.ts +165 -0
  127. package/src/lib/config.ts +47 -0
  128. package/src/lib/constants.ts +19 -10
  129. package/src/lib/favicon.ts +115 -0
  130. package/src/lib/image.ts +13 -21
  131. package/src/lib/media-helpers.ts +2 -2
  132. package/src/lib/nav-reorder.ts +1 -1
  133. package/src/lib/navigation.ts +73 -4
  134. package/src/lib/pagination.ts +50 -0
  135. package/src/lib/render.tsx +22 -15
  136. package/src/lib/schemas.ts +47 -6
  137. package/src/lib/theme.ts +5 -5
  138. package/src/lib/timeline.ts +28 -57
  139. package/src/lib/timezones.ts +325 -0
  140. package/src/lib/view.ts +3 -3
  141. package/src/preset.css +2 -1
  142. package/src/routes/__tests__/compose.test.ts +199 -0
  143. package/src/routes/api/__tests__/collections.test.ts +249 -0
  144. package/src/routes/api/__tests__/nav-items.test.ts +222 -0
  145. package/src/routes/api/__tests__/pages.test.ts +218 -0
  146. package/src/routes/api/__tests__/settings.test.ts +132 -0
  147. package/src/routes/api/collections.ts +143 -0
  148. package/src/routes/api/nav-items.ts +115 -0
  149. package/src/routes/api/pages.ts +101 -0
  150. package/src/routes/api/posts.ts +3 -3
  151. package/src/routes/api/search.ts +2 -2
  152. package/src/routes/api/settings.ts +91 -0
  153. package/src/routes/api/upload.ts +2 -3
  154. package/src/routes/auth/reset.tsx +239 -0
  155. package/src/routes/auth/setup.tsx +189 -0
  156. package/src/routes/auth/signin.tsx +163 -0
  157. package/src/routes/compose.ts +63 -0
  158. package/src/routes/dash/__tests__/pages.test.ts +225 -0
  159. package/src/routes/dash/__tests__/settings-avatar.test.ts +89 -0
  160. package/src/routes/dash/collections.tsx +18 -367
  161. package/src/routes/dash/index.tsx +1 -1
  162. package/src/routes/dash/media.tsx +13 -415
  163. package/src/routes/dash/pages.tsx +131 -98
  164. package/src/routes/dash/posts.tsx +3 -7
  165. package/src/routes/dash/redirects.tsx +22 -16
  166. package/src/routes/dash/settings.tsx +265 -478
  167. package/src/routes/feed/__tests__/rss.test.ts +141 -0
  168. package/src/routes/feed/rss.ts +5 -3
  169. package/src/routes/feed/sitemap.ts +5 -3
  170. package/src/routes/pages/__tests__/collections.test.ts +94 -0
  171. package/src/routes/pages/__tests__/featured.test.ts +94 -0
  172. package/src/routes/pages/archive.tsx +2 -6
  173. package/src/routes/pages/collection.tsx +2 -6
  174. package/src/routes/pages/collections.tsx +36 -0
  175. package/src/routes/pages/featured.tsx +44 -0
  176. package/src/routes/pages/home.tsx +30 -53
  177. package/src/routes/pages/latest.tsx +59 -0
  178. package/src/routes/pages/page.tsx +28 -30
  179. package/src/routes/pages/post.tsx +2 -5
  180. package/src/routes/pages/search.tsx +2 -6
  181. package/src/services/__tests__/page.test.ts +106 -0
  182. package/src/services/__tests__/post.test.ts +114 -15
  183. package/src/services/page.ts +13 -1
  184. package/src/services/post.ts +58 -40
  185. package/src/services/search.ts +2 -2
  186. package/src/styles/components.css +0 -65
  187. package/src/styles/tokens.css +47 -0
  188. package/src/styles/ui.css +475 -0
  189. package/src/types/bindings.ts +30 -0
  190. package/src/types/config.ts +183 -0
  191. package/src/types/constants.ts +26 -0
  192. package/src/types/entities.ts +109 -0
  193. package/src/types/operations.ts +88 -0
  194. package/src/types/props.ts +115 -0
  195. package/src/types/views.ts +172 -0
  196. package/src/types.ts +8 -774
  197. package/src/ui/__tests__/font-themes.test.ts +34 -0
  198. package/src/{theme → ui}/color-themes.ts +34 -34
  199. package/src/ui/compose/ComposeDialog.tsx +414 -0
  200. package/src/ui/compose/ComposePrompt.tsx +55 -0
  201. package/src/{theme/components/TypeBadge.tsx → ui/dash/FormatBadge.tsx} +2 -3
  202. package/src/{theme/components → ui/dash}/PageForm.tsx +25 -19
  203. package/src/{theme/components → ui/dash}/PostForm.tsx +26 -45
  204. package/src/{theme/components → ui/dash}/PostList.tsx +7 -7
  205. package/src/{theme/components/VisibilityBadge.tsx → ui/dash/StatusBadge.tsx} +2 -3
  206. package/src/ui/dash/collections/CollectionForm.tsx +153 -0
  207. package/src/ui/dash/collections/CollectionsListContent.tsx +85 -0
  208. package/src/ui/dash/collections/ViewCollectionContent.tsx +92 -0
  209. package/src/ui/dash/index.ts +10 -0
  210. package/src/ui/dash/media/MediaListContent.tsx +201 -0
  211. package/src/ui/dash/media/ViewMediaContent.tsx +208 -0
  212. package/src/ui/dash/pages/LinkFormContent.tsx +119 -0
  213. package/src/ui/dash/pages/UnifiedPagesContent.tsx +203 -0
  214. package/src/ui/dash/settings/AccountContent.tsx +176 -0
  215. package/src/ui/dash/settings/AppearanceContent.tsx +254 -0
  216. package/src/ui/dash/settings/GeneralContent.tsx +533 -0
  217. package/src/ui/dash/settings/SettingsNav.tsx +56 -0
  218. package/src/{themes/threads/timeline → ui/feed}/LinkCard.tsx +9 -4
  219. package/src/{themes/threads/timeline → ui/feed}/NoteCard.tsx +13 -8
  220. package/src/{themes/threads/timeline → ui/feed}/QuoteCard.tsx +13 -8
  221. package/src/{themes/threads/timeline → ui/feed}/ThreadPreview.tsx +7 -8
  222. package/src/ui/feed/TimelineFeed.tsx +49 -0
  223. package/src/ui/feed/TimelineItem.tsx +45 -0
  224. package/src/ui/font-themes.ts +54 -0
  225. package/src/{theme → ui}/layouts/BaseLayout.tsx +28 -1
  226. package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
  227. package/src/ui/layouts/SiteLayout.tsx +164 -0
  228. package/src/{themes/threads → ui}/pages/ArchivePage.tsx +22 -17
  229. package/src/{themes/threads → ui}/pages/CollectionPage.tsx +14 -5
  230. package/src/ui/pages/CollectionsPage.tsx +73 -0
  231. package/src/ui/pages/FeaturedPage.tsx +31 -0
  232. package/src/{themes/threads → ui}/pages/HomePage.tsx +11 -15
  233. package/src/{themes/threads → ui}/pages/PostPage.tsx +23 -14
  234. package/src/{themes/threads → ui}/pages/SearchPage.tsx +13 -11
  235. package/src/{themes/threads → ui}/pages/SinglePage.tsx +4 -4
  236. package/src/{theme/components → ui/shared}/MediaGallery.tsx +1 -1
  237. package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
  238. package/src/{theme/components → ui/shared}/ThreadView.tsx +2 -2
  239. package/src/ui/shared/__tests__/pagination.test.ts +46 -0
  240. package/src/ui/shared/index.ts +12 -0
  241. package/bin/jant.js +0 -185
  242. package/dist/lib/theme-components.js +0 -46
  243. package/dist/routes/dash/navigation.js +0 -289
  244. package/dist/theme/index.js +0 -18
  245. package/dist/theme/layouts/index.js +0 -2
  246. package/dist/themes/threads/ThreadsSiteLayout.js +0 -172
  247. package/dist/themes/threads/index.js +0 -81
  248. package/dist/themes/threads/pages/HomePage.js +0 -25
  249. package/dist/themes/threads/timeline/TimelineFeed.js +0 -58
  250. package/dist/themes/threads/timeline/TimelineItem.js +0 -36
  251. package/dist/themes/threads/timeline/TimelineLoadMore.js +0 -23
  252. package/dist/themes/threads/timeline/groupByDate.js +0 -22
  253. package/dist/themes/threads/timeline/timelineMore.js +0 -107
  254. package/src/lib/__tests__/theme-components.test.ts +0 -105
  255. package/src/lib/theme-components.ts +0 -65
  256. package/src/routes/dash/navigation.tsx +0 -317
  257. package/src/theme/components/index.ts +0 -23
  258. package/src/theme/index.ts +0 -22
  259. package/src/theme/layouts/index.ts +0 -7
  260. package/src/themes/threads/ThreadsSiteLayout.tsx +0 -194
  261. package/src/themes/threads/index.ts +0 -100
  262. package/src/themes/threads/style.css +0 -336
  263. package/src/themes/threads/timeline/TimelineFeed.tsx +0 -62
  264. package/src/themes/threads/timeline/TimelineItem.tsx +0 -67
  265. package/src/themes/threads/timeline/TimelineLoadMore.tsx +0 -35
  266. package/src/themes/threads/timeline/groupByDate.ts +0 -30
  267. package/src/themes/threads/timeline/timelineMore.tsx +0 -130
  268. /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
  269. /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
  270. /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
  271. /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
  272. /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
  273. /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
  274. /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
  275. /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
  276. /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
  277. /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
@@ -1,376 +1,23 @@
1
- import { getSiteName } from "../../lib/config.js";
2
1
  /**
3
2
  * Dashboard Collections Routes
4
3
  */
5
4
 
6
5
  import { Hono } from "hono";
7
- import { useLingui } from "@lingui/react/macro";
8
- import type { Bindings, Collection, Post } from "../../types.js";
6
+ import type { Bindings } from "../../types.js";
9
7
  import type { AppVariables } from "../../app.js";
10
- import { DashLayout } from "../../theme/layouts/index.js";
11
- import {
12
- EmptyState,
13
- ListItemRow,
14
- ActionButtons,
15
- CrudPageHeader,
16
- DangerZone,
17
- } from "../../theme/components/index.js";
18
- import * as sqid from "../../lib/sqid.js";
8
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
9
+ import { DangerZone } from "../../ui/dash/index.js";
19
10
  import { dsRedirect } from "../../lib/sse.js";
11
+ import { getSiteName } from "../../lib/config.js";
12
+ import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
13
+ import { CollectionsListContent } from "../../ui/dash/collections/CollectionsListContent.js";
14
+ import { CollectionForm } from "../../ui/dash/collections/CollectionForm.js";
15
+ import { ViewCollectionContent } from "../../ui/dash/collections/ViewCollectionContent.js";
20
16
 
21
17
  type Env = { Bindings: Bindings; Variables: AppVariables };
22
18
 
23
19
  export const collectionsRoutes = new Hono<Env>();
24
20
 
25
- function CollectionsListContent({
26
- collections,
27
- }: {
28
- collections: Collection[];
29
- }) {
30
- const { t } = useLingui();
31
-
32
- return (
33
- <>
34
- <CrudPageHeader
35
- title={t({
36
- message: "Collections",
37
- comment: "@context: Dashboard heading",
38
- })}
39
- ctaLabel={t({
40
- message: "New Collection",
41
- comment: "@context: Button to create new collection",
42
- })}
43
- ctaHref="/dash/collections/new"
44
- />
45
-
46
- {collections.length === 0 ? (
47
- <EmptyState
48
- message={t({
49
- message: "No collections yet.",
50
- comment: "@context: Empty state message",
51
- })}
52
- ctaText={t({
53
- message: "New Collection",
54
- comment: "@context: Button to create new collection",
55
- })}
56
- ctaHref="/dash/collections/new"
57
- />
58
- ) : (
59
- <div class="flex flex-col divide-y">
60
- {collections.map((col) => (
61
- <ListItemRow
62
- key={col.id}
63
- actions={
64
- <ActionButtons
65
- editHref={`/dash/collections/${col.id}/edit`}
66
- editLabel={t({
67
- message: "Edit",
68
- comment: "@context: Button to edit collection",
69
- })}
70
- viewHref={`/c/${col.slug}`}
71
- viewLabel={t({
72
- message: "View",
73
- comment: "@context: Button to view collection",
74
- })}
75
- />
76
- }
77
- >
78
- <a
79
- href={`/dash/collections/${col.id}`}
80
- class="font-medium hover:underline"
81
- >
82
- {col.title}
83
- </a>
84
- <p class="text-sm text-muted-foreground">/{col.slug}</p>
85
- {col.description && (
86
- <p class="text-sm text-muted-foreground mt-1">
87
- {col.description}
88
- </p>
89
- )}
90
- </ListItemRow>
91
- ))}
92
- </div>
93
- )}
94
- </>
95
- );
96
- }
97
-
98
- function NewCollectionContent() {
99
- const { t } = useLingui();
100
- return (
101
- <>
102
- <h1 class="text-2xl font-semibold mb-6">
103
- {t({ message: "New Collection", comment: "@context: Page heading" })}
104
- </h1>
105
-
106
- <form
107
- data-signals="{title: '', slug: '', description: ''}"
108
- data-on:submit__prevent="@post('/dash/collections')"
109
- data-indicator="_loading"
110
- class="flex flex-col gap-4 max-w-lg"
111
- >
112
- <div class="field">
113
- <label class="label">
114
- {t({
115
- message: "Title",
116
- comment: "@context: Collection form field",
117
- })}
118
- </label>
119
- <input
120
- type="text"
121
- data-bind="title"
122
- class="input"
123
- required
124
- placeholder={t({
125
- message: "My Collection",
126
- comment: "@context: Collection title placeholder",
127
- })}
128
- />
129
- </div>
130
-
131
- <div class="field">
132
- <label class="label">
133
- {t({ message: "Slug", comment: "@context: Collection form field" })}
134
- </label>
135
- <input
136
- type="text"
137
- data-bind="slug"
138
- class="input"
139
- required
140
- placeholder="my-collection"
141
- pattern="[a-z0-9-]+"
142
- />
143
- <p class="text-xs text-muted-foreground mt-1">
144
- {t({
145
- message: "URL-safe identifier (lowercase, numbers, hyphens)",
146
- comment: "@context: Collection path help text",
147
- })}
148
- </p>
149
- </div>
150
-
151
- <div class="field">
152
- <label class="label">
153
- {t({
154
- message: "Description (optional)",
155
- comment: "@context: Collection form field",
156
- })}
157
- </label>
158
- <textarea
159
- data-bind="description"
160
- class="textarea"
161
- rows={3}
162
- placeholder={t({
163
- message: "What's this collection about?",
164
- comment: "@context: Collection description placeholder",
165
- })}
166
- />
167
- </div>
168
-
169
- <div class="flex gap-2">
170
- <button type="submit" class="btn" data-attr-disabled="$_loading">
171
- <span data-show="!$_loading">
172
- {t({
173
- message: "Create Collection",
174
- comment: "@context: Button to save new collection",
175
- })}
176
- </span>
177
- <span data-show="$_loading">
178
- {t({
179
- message: "Processing...",
180
- comment:
181
- "@context: Loading text shown on submit button while request is in progress",
182
- })}
183
- </span>
184
- </button>
185
- <a href="/dash/collections" class="btn-outline">
186
- {t({
187
- message: "Cancel",
188
- comment: "@context: Button to cancel form",
189
- })}
190
- </a>
191
- </div>
192
- </form>
193
- </>
194
- );
195
- }
196
-
197
- function ViewCollectionContent({
198
- collection,
199
- posts,
200
- }: {
201
- collection: Collection;
202
- posts: Post[];
203
- }) {
204
- const { t } = useLingui();
205
- const postsHeader = t({
206
- message: "Posts in Collection ({count})",
207
- comment: "@context: Collection posts section heading",
208
- values: { count: String(posts.length) },
209
- });
210
-
211
- return (
212
- <>
213
- <div class="flex items-center justify-between mb-6">
214
- <div>
215
- <h1 class="text-2xl font-semibold">{collection.title}</h1>
216
- <p class="text-sm text-muted-foreground">/{collection.slug}</p>
217
- </div>
218
- <ActionButtons
219
- editHref={`/dash/collections/${collection.id}/edit`}
220
- editLabel={t({
221
- message: "Edit",
222
- comment: "@context: Button to edit collection",
223
- })}
224
- viewHref={`/c/${collection.slug}`}
225
- viewLabel={t({
226
- message: "View",
227
- comment: "@context: Button to view collection",
228
- })}
229
- />
230
- </div>
231
-
232
- {collection.description && (
233
- <p class="text-muted-foreground mb-6">{collection.description}</p>
234
- )}
235
-
236
- <div class="card">
237
- <header>
238
- <h2>{postsHeader}</h2>
239
- </header>
240
- <section>
241
- {posts.length === 0 ? (
242
- <p class="text-muted-foreground">
243
- {t({
244
- message: "No posts in this collection.",
245
- comment: "@context: Empty state message",
246
- })}
247
- </p>
248
- ) : (
249
- <div class="flex flex-col divide-y">
250
- {posts.map((post) => (
251
- <div key={post.id} class="py-3 flex items-center gap-4">
252
- <div class="flex-1 min-w-0">
253
- <a
254
- href={`/dash/posts/${sqid.encode(post.id)}`}
255
- class="font-medium hover:underline"
256
- >
257
- {post.title ||
258
- post.body?.slice(0, 50) ||
259
- `Post #${post.id}`}
260
- </a>
261
- </div>
262
- </div>
263
- ))}
264
- </div>
265
- )}
266
- </section>
267
- </div>
268
-
269
- <div class="mt-6">
270
- <a href="/dash/collections" class="text-sm hover:underline">
271
- {t({
272
- message: "\u2190 Back to Collections",
273
- comment: "@context: Navigation link",
274
- })}
275
- </a>
276
- </div>
277
- </>
278
- );
279
- }
280
-
281
- function EditCollectionContent({ collection }: { collection: Collection }) {
282
- const { t } = useLingui();
283
-
284
- const signals = JSON.stringify({
285
- title: collection.title,
286
- slug: collection.slug ?? "",
287
- description: collection.description ?? "",
288
- }).replace(/</g, "\\u003c");
289
-
290
- return (
291
- <>
292
- <h1 class="text-2xl font-semibold mb-6">
293
- {t({ message: "Edit Collection", comment: "@context: Page heading" })}
294
- </h1>
295
-
296
- <form
297
- data-signals={signals}
298
- data-on:submit__prevent={`@post('/dash/collections/${collection.id}')`}
299
- data-indicator="_loading"
300
- class="flex flex-col gap-4 max-w-lg"
301
- >
302
- <div class="field">
303
- <label class="label">
304
- {t({
305
- message: "Title",
306
- comment: "@context: Collection form field",
307
- })}
308
- </label>
309
- <input type="text" data-bind="title" class="input" required />
310
- </div>
311
-
312
- <div class="field">
313
- <label class="label">
314
- {t({ message: "Slug", comment: "@context: Collection form field" })}
315
- </label>
316
- <input
317
- type="text"
318
- data-bind="slug"
319
- class="input"
320
- required
321
- pattern="[a-z0-9-]+"
322
- />
323
- </div>
324
-
325
- <div class="field">
326
- <label class="label">
327
- {t({
328
- message: "Description (optional)",
329
- comment: "@context: Collection form field",
330
- })}
331
- </label>
332
- <textarea data-bind="description" class="textarea" rows={3}>
333
- {collection.description ?? ""}
334
- </textarea>
335
- </div>
336
-
337
- <div class="flex gap-2">
338
- <button type="submit" class="btn" data-attr-disabled="$_loading">
339
- <span data-show="!$_loading">
340
- {t({
341
- message: "Update Collection",
342
- comment: "@context: Button to save collection changes",
343
- })}
344
- </span>
345
- <span data-show="$_loading">
346
- {t({
347
- message: "Processing...",
348
- comment:
349
- "@context: Loading text shown on submit button while request is in progress",
350
- })}
351
- </span>
352
- </button>
353
- <a href={`/dash/collections/${collection.id}`} class="btn-outline">
354
- {t({
355
- message: "Cancel",
356
- comment: "@context: Button to cancel form",
357
- })}
358
- </a>
359
- </div>
360
- </form>
361
-
362
- <DangerZone
363
- actionLabel={t({
364
- message: "Delete Collection",
365
- comment: "@context: Button to delete collection",
366
- })}
367
- formAction={`/dash/collections/${collection.id}/delete`}
368
- confirmMessage="Are you sure you want to delete this collection?"
369
- />
370
- </>
371
- );
372
- }
373
-
374
21
  // List collections
375
22
  collectionsRoutes.get("/", async (c) => {
376
23
  const siteName = await getSiteName(c);
@@ -399,7 +46,7 @@ collectionsRoutes.get("/new", async (c) => {
399
46
  siteName={siteName}
400
47
  currentPath="/dash/collections"
401
48
  >
402
- <NewCollectionContent />
49
+ <CollectionForm />
403
50
  </DashLayout>,
404
51
  );
405
52
  });
@@ -429,10 +76,9 @@ collectionsRoutes.get("/:id", async (c) => {
429
76
  const collection = await c.var.services.collections.getById(id);
430
77
  if (!collection) return c.notFound();
431
78
 
432
- // Fetch posts in this collection via post service
433
- const posts = await c.var.services.posts.list({
434
- collectionId: id,
435
- });
79
+ const rawPosts = await c.var.services.posts.list({ collectionId: id });
80
+ const ctx = createMediaContext(c);
81
+ const posts = toPostViewsFromPosts(rawPosts, ctx);
436
82
  const siteName = await getSiteName(c);
437
83
 
438
84
  return c.html(
@@ -464,7 +110,12 @@ collectionsRoutes.get("/:id/edit", async (c) => {
464
110
  siteName={siteName}
465
111
  currentPath="/dash/collections"
466
112
  >
467
- <EditCollectionContent collection={collection} />
113
+ <CollectionForm collection={collection} isEdit />
114
+ <DangerZone
115
+ actionLabel="Delete Collection"
116
+ formAction={`/dash/collections/${collection.id}/delete`}
117
+ confirmMessage="Are you sure you want to delete this collection?"
118
+ />
468
119
  </DashLayout>,
469
120
  );
470
121
  });
@@ -8,7 +8,7 @@ import { Hono } from "hono";
8
8
  import { Trans, useLingui } from "@lingui/react/macro";
9
9
  import type { Bindings } from "../../types.js";
10
10
  import type { AppVariables } from "../../app.js";
11
- import { DashLayout } from "../../theme/layouts/index.js";
11
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
12
12
  import { getSiteName } from "../../lib/config.js";
13
13
 
14
14
  type Env = { Bindings: Bindings; Variables: AppVariables };