@jant/core 0.3.23 → 0.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/dist/app.js +50 -26
  2. package/dist/db/schema.js +72 -47
  3. package/dist/i18n/locales/en.js +1 -1
  4. package/dist/i18n/locales/zh-Hans.js +1 -1
  5. package/dist/i18n/locales/zh-Hant.js +1 -1
  6. package/dist/index.js +5 -11
  7. package/dist/lib/constants.js +2 -4
  8. package/dist/lib/excerpt.js +76 -0
  9. package/dist/lib/feed.js +18 -7
  10. package/dist/lib/nav-reorder.js +1 -1
  11. package/dist/lib/navigation.js +30 -6
  12. package/dist/lib/pagination.js +44 -0
  13. package/dist/lib/render.js +7 -11
  14. package/dist/lib/schemas.js +80 -38
  15. package/dist/lib/theme.js +4 -4
  16. package/dist/lib/time.js +56 -1
  17. package/dist/lib/timeline.js +95 -0
  18. package/dist/lib/view.js +61 -72
  19. package/dist/routes/api/collections.js +124 -0
  20. package/dist/routes/api/nav-items.js +104 -0
  21. package/dist/routes/api/pages.js +91 -0
  22. package/dist/routes/api/posts.js +27 -33
  23. package/dist/routes/api/search.js +4 -5
  24. package/dist/routes/api/settings.js +68 -0
  25. package/dist/routes/api/upload.js +13 -13
  26. package/dist/routes/compose.js +48 -0
  27. package/dist/routes/dash/collections.js +24 -42
  28. package/dist/routes/dash/index.js +3 -3
  29. package/dist/routes/dash/media.js +2 -2
  30. package/dist/routes/dash/pages.js +440 -106
  31. package/dist/routes/dash/posts.js +27 -37
  32. package/dist/routes/dash/redirects.js +2 -2
  33. package/dist/routes/dash/settings.js +79 -5
  34. package/dist/routes/feed/rss.js +4 -6
  35. package/dist/routes/feed/sitemap.js +11 -8
  36. package/dist/routes/pages/archive.js +13 -15
  37. package/dist/routes/pages/collection.js +12 -9
  38. package/dist/routes/pages/collections.js +28 -0
  39. package/dist/routes/pages/featured.js +32 -0
  40. package/dist/routes/pages/home.js +19 -68
  41. package/dist/routes/pages/page.js +57 -29
  42. package/dist/routes/pages/post.js +7 -17
  43. package/dist/routes/pages/search.js +5 -9
  44. package/dist/services/collection.js +52 -64
  45. package/dist/services/index.js +5 -3
  46. package/dist/services/navigation.js +29 -53
  47. package/dist/services/page.js +84 -0
  48. package/dist/services/post.js +102 -69
  49. package/dist/services/search.js +24 -18
  50. package/dist/types.js +24 -40
  51. package/dist/ui/compose/ComposeDialog.js +452 -0
  52. package/dist/ui/compose/ComposePrompt.js +55 -0
  53. package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +3 -15
  54. package/dist/{theme/components → ui/dash}/PageForm.js +15 -15
  55. package/dist/{theme/components → ui/dash}/PostForm.js +117 -137
  56. package/dist/{theme/components → ui/dash}/PostList.js +18 -13
  57. package/dist/ui/dash/StatusBadge.js +46 -0
  58. package/dist/{theme/components → ui/dash}/index.js +3 -6
  59. package/dist/ui/feed/LinkCard.js +72 -0
  60. package/dist/ui/feed/NoteCard.js +58 -0
  61. package/dist/{themes/minimal/timeline → ui/feed}/QuoteCard.js +29 -14
  62. package/dist/{themes/minimal/timeline → ui/feed}/ThreadPreview.js +20 -18
  63. package/dist/ui/feed/TimelineFeed.js +41 -0
  64. package/dist/ui/feed/TimelineItem.js +27 -0
  65. package/dist/{theme → ui}/layouts/BaseLayout.js +10 -0
  66. package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
  67. package/dist/ui/layouts/SiteLayout.js +141 -0
  68. package/dist/{themes/minimal → ui}/pages/ArchivePage.js +37 -50
  69. package/dist/ui/pages/CollectionPage.js +70 -0
  70. package/dist/ui/pages/CollectionsPage.js +76 -0
  71. package/dist/ui/pages/FeaturedPage.js +24 -0
  72. package/dist/ui/pages/HomePage.js +24 -0
  73. package/dist/{themes/minimal → ui}/pages/PostPage.js +20 -12
  74. package/dist/{themes/minimal → ui}/pages/SearchPage.js +19 -18
  75. package/dist/{themes/minimal → ui}/pages/SinglePage.js +5 -4
  76. package/dist/ui/shared/MediaGallery.js +35 -0
  77. package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
  78. package/dist/{theme/components → ui/shared}/ThreadView.js +3 -3
  79. package/dist/ui/shared/index.js +5 -0
  80. package/package.json +2 -9
  81. package/src/__tests__/helpers/app.ts +4 -0
  82. package/src/__tests__/helpers/db.ts +53 -73
  83. package/src/app.tsx +56 -28
  84. package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
  85. package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
  86. package/src/db/migrations/meta/_journal.json +14 -0
  87. package/src/db/schema.ts +63 -46
  88. package/src/i18n/locales/en.po +443 -240
  89. package/src/i18n/locales/en.ts +1 -1
  90. package/src/i18n/locales/zh-Hans.po +443 -240
  91. package/src/i18n/locales/zh-Hans.ts +1 -1
  92. package/src/i18n/locales/zh-Hant.po +443 -240
  93. package/src/i18n/locales/zh-Hant.ts +1 -1
  94. package/src/index.ts +29 -42
  95. package/src/lib/__tests__/excerpt.test.ts +125 -0
  96. package/src/lib/__tests__/schemas.test.ts +201 -99
  97. package/src/lib/__tests__/time.test.ts +62 -0
  98. package/src/{routes/api → lib}/__tests__/timeline.test.ts +81 -75
  99. package/src/lib/__tests__/view.test.ts +204 -50
  100. package/src/lib/constants.ts +2 -4
  101. package/src/lib/excerpt.ts +87 -0
  102. package/src/lib/feed.ts +22 -7
  103. package/src/lib/nav-reorder.ts +1 -1
  104. package/src/lib/navigation.ts +45 -8
  105. package/src/lib/pagination.ts +50 -0
  106. package/src/lib/render.tsx +7 -14
  107. package/src/lib/schemas.ts +119 -51
  108. package/src/lib/theme.ts +5 -5
  109. package/src/lib/time.ts +64 -0
  110. package/src/lib/timeline.ts +141 -0
  111. package/src/lib/view.ts +80 -82
  112. package/src/preset.css +46 -0
  113. package/src/routes/__tests__/compose.test.ts +199 -0
  114. package/src/routes/api/__tests__/collections.test.ts +249 -0
  115. package/src/routes/api/__tests__/nav-items.test.ts +222 -0
  116. package/src/routes/api/__tests__/pages.test.ts +218 -0
  117. package/src/routes/api/__tests__/posts.test.ts +50 -108
  118. package/src/routes/api/__tests__/search.test.ts +2 -3
  119. package/src/routes/api/__tests__/settings.test.ts +132 -0
  120. package/src/routes/api/collections.ts +143 -0
  121. package/src/routes/api/nav-items.ts +115 -0
  122. package/src/routes/api/pages.ts +101 -0
  123. package/src/routes/api/posts.ts +28 -28
  124. package/src/routes/api/search.ts +3 -3
  125. package/src/routes/api/settings.ts +91 -0
  126. package/src/routes/api/upload.ts +16 -6
  127. package/src/routes/compose.ts +63 -0
  128. package/src/routes/dash/__tests__/pages.test.ts +225 -0
  129. package/src/routes/dash/collections.tsx +20 -42
  130. package/src/routes/dash/index.tsx +3 -3
  131. package/src/routes/dash/media.tsx +2 -2
  132. package/src/routes/dash/pages.tsx +480 -122
  133. package/src/routes/dash/posts.tsx +42 -54
  134. package/src/routes/dash/redirects.tsx +2 -2
  135. package/src/routes/dash/settings.tsx +83 -5
  136. package/src/routes/feed/rss.ts +4 -3
  137. package/src/routes/feed/sitemap.ts +15 -5
  138. package/src/routes/pages/__tests__/collections.test.ts +94 -0
  139. package/src/routes/pages/__tests__/featured.test.ts +94 -0
  140. package/src/routes/pages/archive.tsx +15 -15
  141. package/src/routes/pages/collection.tsx +16 -9
  142. package/src/routes/pages/collections.tsx +36 -0
  143. package/src/routes/pages/featured.tsx +38 -0
  144. package/src/routes/pages/home.tsx +21 -92
  145. package/src/routes/pages/page.tsx +62 -27
  146. package/src/routes/pages/post.tsx +6 -18
  147. package/src/routes/pages/search.tsx +3 -7
  148. package/src/services/__tests__/collection.test.ts +257 -158
  149. package/src/services/__tests__/media.test.ts +18 -18
  150. package/src/services/__tests__/navigation.test.ts +161 -87
  151. package/src/services/__tests__/page.test.ts +106 -0
  152. package/src/services/__tests__/post-timeline.test.ts +92 -88
  153. package/src/services/__tests__/post.test.ts +432 -197
  154. package/src/services/__tests__/search.test.ts +19 -25
  155. package/src/services/collection.ts +71 -113
  156. package/src/services/index.ts +9 -8
  157. package/src/services/navigation.ts +38 -71
  158. package/src/services/page.ts +136 -0
  159. package/src/services/post.ts +141 -101
  160. package/src/services/search.ts +38 -27
  161. package/src/styles/tokens.css +47 -0
  162. package/src/styles/ui.css +491 -0
  163. package/src/types.ts +212 -198
  164. package/src/ui/compose/ComposeDialog.tsx +395 -0
  165. package/src/ui/compose/ComposePrompt.tsx +55 -0
  166. package/src/ui/dash/FormatBadge.tsx +28 -0
  167. package/src/{theme/components → ui/dash}/PageForm.tsx +21 -21
  168. package/src/{theme/components → ui/dash}/PostForm.tsx +110 -131
  169. package/src/ui/dash/PostList.tsx +101 -0
  170. package/src/ui/dash/StatusBadge.tsx +61 -0
  171. package/src/ui/dash/index.ts +10 -0
  172. package/src/ui/feed/LinkCard.tsx +72 -0
  173. package/src/ui/feed/NoteCard.tsx +63 -0
  174. package/src/ui/feed/QuoteCard.tsx +68 -0
  175. package/src/ui/feed/ThreadPreview.tsx +48 -0
  176. package/src/ui/feed/TimelineFeed.tsx +49 -0
  177. package/src/ui/feed/TimelineItem.tsx +45 -0
  178. package/src/{theme → ui}/layouts/BaseLayout.tsx +11 -1
  179. package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
  180. package/src/ui/layouts/SiteLayout.tsx +150 -0
  181. package/src/ui/pages/ArchivePage.tsx +162 -0
  182. package/src/ui/pages/CollectionPage.tsx +70 -0
  183. package/src/ui/pages/CollectionsPage.tsx +73 -0
  184. package/src/ui/pages/FeaturedPage.tsx +31 -0
  185. package/src/ui/pages/HomePage.tsx +37 -0
  186. package/src/ui/pages/PostPage.tsx +56 -0
  187. package/src/{themes/minimal → ui}/pages/SearchPage.tsx +24 -20
  188. package/src/{themes/minimal → ui}/pages/SinglePage.tsx +5 -5
  189. package/src/ui/shared/MediaGallery.tsx +59 -0
  190. package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
  191. package/src/{theme/components → ui/shared}/ThreadView.tsx +6 -3
  192. package/src/ui/shared/__tests__/pagination.test.ts +46 -0
  193. package/src/ui/shared/index.ts +12 -0
  194. package/bin/jant.js +0 -185
  195. package/dist/lib/theme-components.js +0 -49
  196. package/dist/routes/api/timeline.js +0 -120
  197. package/dist/routes/dash/navigation.js +0 -288
  198. package/dist/theme/components/MediaGallery.js +0 -107
  199. package/dist/theme/components/VisibilityBadge.js +0 -37
  200. package/dist/theme/index.js +0 -18
  201. package/dist/theme/layouts/index.js +0 -2
  202. package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
  203. package/dist/themes/minimal/index.js +0 -65
  204. package/dist/themes/minimal/pages/CollectionPage.js +0 -65
  205. package/dist/themes/minimal/pages/HomePage.js +0 -25
  206. package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
  207. package/dist/themes/minimal/timeline/ImageCard.js +0 -67
  208. package/dist/themes/minimal/timeline/LinkCard.js +0 -47
  209. package/dist/themes/minimal/timeline/NoteCard.js +0 -34
  210. package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
  211. package/dist/themes/minimal/timeline/TimelineItem.js +0 -44
  212. package/src/lib/__tests__/theme-components.test.ts +0 -126
  213. package/src/lib/theme-components.ts +0 -68
  214. package/src/routes/api/timeline.tsx +0 -159
  215. package/src/routes/dash/navigation.tsx +0 -316
  216. package/src/theme/components/MediaGallery.tsx +0 -128
  217. package/src/theme/components/PostList.tsx +0 -92
  218. package/src/theme/components/TypeBadge.tsx +0 -37
  219. package/src/theme/components/VisibilityBadge.tsx +0 -45
  220. package/src/theme/components/index.ts +0 -23
  221. package/src/theme/index.ts +0 -22
  222. package/src/theme/layouts/index.ts +0 -7
  223. package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
  224. package/src/themes/minimal/index.ts +0 -83
  225. package/src/themes/minimal/pages/ArchivePage.tsx +0 -157
  226. package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
  227. package/src/themes/minimal/pages/HomePage.tsx +0 -41
  228. package/src/themes/minimal/pages/PostPage.tsx +0 -43
  229. package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
  230. package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
  231. package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
  232. package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
  233. package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
  234. package/src/themes/minimal/timeline/ThreadPreview.tsx +0 -47
  235. package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
  236. package/src/themes/minimal/timeline/TimelineItem.tsx +0 -75
  237. /package/dist/{theme → ui}/color-themes.js +0 -0
  238. /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
  239. /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
  240. /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
  241. /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
  242. /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
  243. /package/src/{theme → ui}/color-themes.ts +0 -0
  244. /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
  245. /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
  246. /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
  247. /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
  248. /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Tests for the page/nav management logic used by dashboard pages routes.
3
+ *
4
+ * Note: Route handler tests that import JSX components with @lingui/react/macro
5
+ * cannot run in vitest (requires SWC plugin). These tests verify the service
6
+ * layer operations that the routes orchestrate.
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach } from "vitest";
10
+ import { createTestDatabase } from "../../../__tests__/helpers/db.js";
11
+ import { createPageService } from "../../../services/page.js";
12
+ import { createNavItemService } from "../../../services/navigation.js";
13
+ import type { Database } from "../../../db/index.js";
14
+
15
+ describe("Dashboard Pages - Nav Management Logic", () => {
16
+ let db: Database;
17
+ let pageService: ReturnType<typeof createPageService>;
18
+ let navItemService: ReturnType<typeof createNavItemService>;
19
+
20
+ beforeEach(() => {
21
+ const testDb = createTestDatabase();
22
+ db = testDb.db as unknown as Database;
23
+ pageService = createPageService(db);
24
+ navItemService = createNavItemService(db);
25
+ });
26
+
27
+ describe("add page to nav", () => {
28
+ it("creates a page-type nav item for the page", async () => {
29
+ const page = await pageService.create({
30
+ slug: "about",
31
+ title: "About Us",
32
+ });
33
+
34
+ // Simulate what the route handler does
35
+ await navItemService.create({
36
+ type: "page",
37
+ label: page.title || page.slug,
38
+ url: `/${page.slug}`,
39
+ pageId: page.id,
40
+ });
41
+
42
+ const navItems = await navItemService.list();
43
+ expect(navItems).toHaveLength(1);
44
+ expect(navItems[0]?.type).toBe("page");
45
+ expect(navItems[0]?.label).toBe("About Us");
46
+ expect(navItems[0]?.url).toBe("/about");
47
+ expect(navItems[0]?.pageId).toBe(page.id);
48
+ });
49
+
50
+ it("uses slug as label when page has no title", async () => {
51
+ const page = await pageService.create({ slug: "about" });
52
+
53
+ await navItemService.create({
54
+ type: "page",
55
+ label: page.title || page.slug,
56
+ url: `/${page.slug}`,
57
+ pageId: page.id,
58
+ });
59
+
60
+ const navItems = await navItemService.list();
61
+ expect(navItems[0]?.label).toBe("about");
62
+ });
63
+
64
+ it("page appears in nav and not in listNotInNav after adding", async () => {
65
+ const page = await pageService.create({
66
+ slug: "about",
67
+ title: "About",
68
+ });
69
+
70
+ // Before adding to nav
71
+ let notInNav = await pageService.listNotInNav();
72
+ expect(notInNav).toHaveLength(1);
73
+
74
+ // Add to nav
75
+ await navItemService.create({
76
+ type: "page",
77
+ label: page.title || page.slug,
78
+ url: `/${page.slug}`,
79
+ pageId: page.id,
80
+ });
81
+
82
+ // After adding to nav
83
+ notInNav = await pageService.listNotInNav();
84
+ expect(notInNav).toHaveLength(0);
85
+
86
+ const navItems = await navItemService.list();
87
+ expect(navItems).toHaveLength(1);
88
+ });
89
+ });
90
+
91
+ describe("remove page from nav", () => {
92
+ it("removes the nav item but keeps the page", async () => {
93
+ const page = await pageService.create({
94
+ slug: "about",
95
+ title: "About",
96
+ });
97
+ const navItem = await navItemService.create({
98
+ type: "page",
99
+ label: "About",
100
+ url: "/about",
101
+ pageId: page.id,
102
+ });
103
+
104
+ // Simulate what the route handler does: find and delete nav item by pageId
105
+ const allNavItems = await navItemService.list();
106
+ const found = allNavItems.find((item) => item.pageId === page.id);
107
+ expect(found).toBeDefined();
108
+ await navItemService.delete(found!.id);
109
+
110
+ // Nav item should be gone
111
+ const navItems = await navItemService.list();
112
+ expect(navItems).toHaveLength(0);
113
+
114
+ // Page should still exist
115
+ const foundPage = await pageService.getById(page.id);
116
+ expect(foundPage).not.toBeNull();
117
+
118
+ // Page should appear in "not in nav" list
119
+ const notInNav = await pageService.listNotInNav();
120
+ expect(notInNav).toHaveLength(1);
121
+ expect(notInNav[0]?.slug).toBe("about");
122
+ });
123
+ });
124
+
125
+ describe("reorder nav items", () => {
126
+ it("reorders nav items by position", async () => {
127
+ const a = await navItemService.create({
128
+ type: "link",
129
+ label: "A",
130
+ url: "/a",
131
+ });
132
+ const b = await navItemService.create({
133
+ type: "link",
134
+ label: "B",
135
+ url: "/b",
136
+ });
137
+
138
+ // Reverse order
139
+ await navItemService.reorder([b.id, a.id]);
140
+
141
+ const items = await navItemService.list();
142
+ expect(items[0]?.label).toBe("B");
143
+ expect(items[1]?.label).toBe("A");
144
+ });
145
+ });
146
+
147
+ describe("link CRUD", () => {
148
+ it("creates a link nav item", async () => {
149
+ await navItemService.create({
150
+ type: "link",
151
+ label: "Blog",
152
+ url: "/blog",
153
+ });
154
+
155
+ const navItems = await navItemService.list();
156
+ expect(navItems).toHaveLength(1);
157
+ expect(navItems[0]?.type).toBe("link");
158
+ expect(navItems[0]?.label).toBe("Blog");
159
+ expect(navItems[0]?.url).toBe("/blog");
160
+ });
161
+
162
+ it("updates a link nav item", async () => {
163
+ const item = await navItemService.create({
164
+ type: "link",
165
+ label: "Blog",
166
+ url: "/blog",
167
+ });
168
+
169
+ await navItemService.update(item.id, {
170
+ label: "Posts",
171
+ url: "/posts",
172
+ });
173
+
174
+ const updated = await navItemService.getById(item.id);
175
+ expect(updated?.label).toBe("Posts");
176
+ expect(updated?.url).toBe("/posts");
177
+ });
178
+
179
+ it("deletes a link nav item", async () => {
180
+ const item = await navItemService.create({
181
+ type: "link",
182
+ label: "Blog",
183
+ url: "/blog",
184
+ });
185
+
186
+ await navItemService.delete(item.id);
187
+
188
+ const found = await navItemService.getById(item.id);
189
+ expect(found).toBeNull();
190
+ });
191
+ });
192
+
193
+ describe("unified page listing", () => {
194
+ it("separates pages into nav and non-nav groups", async () => {
195
+ const aboutPage = await pageService.create({
196
+ slug: "about",
197
+ title: "About",
198
+ });
199
+ await pageService.create({ slug: "contact", title: "Contact" });
200
+
201
+ // Add about to nav
202
+ await navItemService.create({
203
+ type: "page",
204
+ label: "About",
205
+ url: "/about",
206
+ pageId: aboutPage.id,
207
+ });
208
+
209
+ // Also add a link nav item
210
+ await navItemService.create({
211
+ type: "link",
212
+ label: "External",
213
+ url: "https://example.com",
214
+ });
215
+
216
+ // Simulate the unified page view data fetch
217
+ const navItems = await navItemService.list();
218
+ const otherPages = await pageService.listNotInNav();
219
+
220
+ expect(navItems).toHaveLength(2); // page + link
221
+ expect(otherPages).toHaveLength(1); // only contact
222
+ expect(otherPages[0]?.slug).toBe("contact");
223
+ });
224
+ });
225
+ });
@@ -7,14 +7,14 @@ import { Hono } from "hono";
7
7
  import { useLingui } from "@lingui/react/macro";
8
8
  import type { Bindings, Collection, Post } from "../../types.js";
9
9
  import type { AppVariables } from "../../app.js";
10
- import { DashLayout } from "../../theme/layouts/index.js";
10
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
11
11
  import {
12
12
  EmptyState,
13
13
  ListItemRow,
14
14
  ActionButtons,
15
15
  CrudPageHeader,
16
16
  DangerZone,
17
- } from "../../theme/components/index.js";
17
+ } from "../../ui/dash/index.js";
18
18
  import * as sqid from "../../lib/sqid.js";
19
19
  import { dsRedirect } from "../../lib/sse.js";
20
20
 
@@ -67,7 +67,7 @@ function CollectionsListContent({
67
67
  message: "Edit",
68
68
  comment: "@context: Button to edit collection",
69
69
  })}
70
- viewHref={`/c/${col.path}`}
70
+ viewHref={`/c/${col.slug}`}
71
71
  viewLabel={t({
72
72
  message: "View",
73
73
  comment: "@context: Button to view collection",
@@ -81,7 +81,7 @@ function CollectionsListContent({
81
81
  >
82
82
  {col.title}
83
83
  </a>
84
- <p class="text-sm text-muted-foreground">/{col.path}</p>
84
+ <p class="text-sm text-muted-foreground">/{col.slug}</p>
85
85
  {col.description && (
86
86
  <p class="text-sm text-muted-foreground mt-1">
87
87
  {col.description}
@@ -104,7 +104,7 @@ function NewCollectionContent() {
104
104
  </h1>
105
105
 
106
106
  <form
107
- data-signals="{title: '', path: '', description: ''}"
107
+ data-signals="{title: '', slug: '', description: ''}"
108
108
  data-on:submit__prevent="@post('/dash/collections')"
109
109
  data-indicator="_loading"
110
110
  class="flex flex-col gap-4 max-w-lg"
@@ -134,7 +134,7 @@ function NewCollectionContent() {
134
134
  </label>
135
135
  <input
136
136
  type="text"
137
- data-bind="path"
137
+ data-bind="slug"
138
138
  class="input"
139
139
  required
140
140
  placeholder="my-collection"
@@ -213,7 +213,7 @@ function ViewCollectionContent({
213
213
  <div class="flex items-center justify-between mb-6">
214
214
  <div>
215
215
  <h1 class="text-2xl font-semibold">{collection.title}</h1>
216
- <p class="text-sm text-muted-foreground">/{collection.path}</p>
216
+ <p class="text-sm text-muted-foreground">/{collection.slug}</p>
217
217
  </div>
218
218
  <ActionButtons
219
219
  editHref={`/dash/collections/${collection.id}/edit`}
@@ -221,7 +221,7 @@ function ViewCollectionContent({
221
221
  message: "Edit",
222
222
  comment: "@context: Button to edit collection",
223
223
  })}
224
- viewHref={`/c/${collection.path}`}
224
+ viewHref={`/c/${collection.slug}`}
225
225
  viewLabel={t({
226
226
  message: "View",
227
227
  comment: "@context: Button to view collection",
@@ -255,21 +255,10 @@ function ViewCollectionContent({
255
255
  class="font-medium hover:underline"
256
256
  >
257
257
  {post.title ||
258
- post.content?.slice(0, 50) ||
258
+ post.body?.slice(0, 50) ||
259
259
  `Post #${post.id}`}
260
260
  </a>
261
261
  </div>
262
- <button
263
- type="button"
264
- class="btn-sm-ghost text-destructive"
265
- data-on:click__prevent={`@post('/dash/collections/${collection.id}/remove-post', {payload: {postId: ${post.id}}})`}
266
- >
267
- {t({
268
- message: "Remove",
269
- comment:
270
- "@context: Button to remove post from collection",
271
- })}
272
- </button>
273
262
  </div>
274
263
  ))}
275
264
  </div>
@@ -280,7 +269,7 @@ function ViewCollectionContent({
280
269
  <div class="mt-6">
281
270
  <a href="/dash/collections" class="text-sm hover:underline">
282
271
  {t({
283
- message: " Back to Collections",
272
+ message: "\u2190 Back to Collections",
284
273
  comment: "@context: Navigation link",
285
274
  })}
286
275
  </a>
@@ -294,7 +283,7 @@ function EditCollectionContent({ collection }: { collection: Collection }) {
294
283
 
295
284
  const signals = JSON.stringify({
296
285
  title: collection.title,
297
- path: collection.path ?? "",
286
+ slug: collection.slug ?? "",
298
287
  description: collection.description ?? "",
299
288
  }).replace(/</g, "\\u003c");
300
289
 
@@ -326,7 +315,7 @@ function EditCollectionContent({ collection }: { collection: Collection }) {
326
315
  </label>
327
316
  <input
328
317
  type="text"
329
- data-bind="path"
318
+ data-bind="slug"
330
319
  class="input"
331
320
  required
332
321
  pattern="[a-z0-9-]+"
@@ -419,13 +408,13 @@ collectionsRoutes.get("/new", async (c) => {
419
408
  collectionsRoutes.post("/", async (c) => {
420
409
  const body = await c.req.json<{
421
410
  title: string;
422
- path: string;
411
+ slug: string;
423
412
  description?: string;
424
413
  }>();
425
414
 
426
415
  const collection = await c.var.services.collections.create({
427
416
  title: body.title,
428
- path: body.path,
417
+ slug: body.slug,
429
418
  description: body.description || undefined,
430
419
  });
431
420
 
@@ -440,7 +429,10 @@ collectionsRoutes.get("/:id", async (c) => {
440
429
  const collection = await c.var.services.collections.getById(id);
441
430
  if (!collection) return c.notFound();
442
431
 
443
- const posts = await c.var.services.collections.getPosts(id);
432
+ // Fetch posts in this collection via post service
433
+ const posts = await c.var.services.posts.list({
434
+ collectionId: id,
435
+ });
444
436
  const siteName = await getSiteName(c);
445
437
 
446
438
  return c.html(
@@ -484,13 +476,13 @@ collectionsRoutes.post("/:id", async (c) => {
484
476
 
485
477
  const body = await c.req.json<{
486
478
  title: string;
487
- path: string;
479
+ slug: string;
488
480
  description?: string;
489
481
  }>();
490
482
 
491
483
  await c.var.services.collections.update(id, {
492
484
  title: body.title,
493
- path: body.path,
485
+ slug: body.slug,
494
486
  description: body.description || undefined,
495
487
  });
496
488
 
@@ -506,17 +498,3 @@ collectionsRoutes.post("/:id/delete", async (c) => {
506
498
 
507
499
  return dsRedirect("/dash/collections");
508
500
  });
509
-
510
- // Remove post from collection
511
- collectionsRoutes.post("/:id/remove-post", async (c) => {
512
- const id = parseInt(c.req.param("id"), 10);
513
- if (isNaN(id)) return c.notFound();
514
-
515
- const body = await c.req.json<{ postId: number }>();
516
-
517
- if (body.postId) {
518
- await c.var.services.collections.removePost(id, body.postId);
519
- }
520
-
521
- return dsRedirect(`/dash/collections/${id}`);
522
- });
@@ -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 };
@@ -91,8 +91,8 @@ dashIndexRoutes.get("/", async (c) => {
91
91
 
92
92
  // Get some stats
93
93
  const allPosts = await c.var.services.posts.list({ limit: 1000 });
94
- const publishedPosts = allPosts.filter((p) => p.visibility !== "draft");
95
- const draftPosts = allPosts.filter((p) => p.visibility === "draft");
94
+ const publishedPosts = allPosts.filter((p) => p.status !== "draft");
95
+ const draftPosts = allPosts.filter((p) => p.status === "draft");
96
96
 
97
97
  return c.html(
98
98
  <DashLayout c={c} title="Dashboard" siteName={siteName} currentPath="/dash">
@@ -10,8 +10,8 @@ import { Hono } from "hono";
10
10
  import { useLingui } from "@lingui/react/macro";
11
11
  import type { Bindings, Media } from "../../types.js";
12
12
  import type { AppVariables } from "../../app.js";
13
- import { DashLayout } from "../../theme/layouts/index.js";
14
- import { EmptyState, DangerZone } from "../../theme/components/index.js";
13
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
14
+ import { EmptyState, DangerZone } from "../../ui/dash/index.js";
15
15
  import * as time from "../../lib/time.js";
16
16
  import {
17
17
  getMediaUrl,