@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,68 @@
1
+ /**
2
+ * Settings API Routes
3
+ */ import { Hono } from "hono";
4
+ import { requireAuthApi } from "../../middleware/auth.js";
5
+ import { CONFIG_FIELDS } from "../../types.js";
6
+ import { z } from "zod";
7
+ export const settingsApiRoutes = new Hono();
8
+ /** Config keys that can be modified via the settings API */ const editableKeys = Object.entries(CONFIG_FIELDS).filter(([, field])=>!field.envOnly).map(([key])=>key);
9
+ const UpdateSettingsSchema = z.record(z.string(), z.string());
10
+ // Get all settings (requires auth)
11
+ settingsApiRoutes.get("/", requireAuthApi(), async (c)=>{
12
+ const allSettings = await c.var.services.settings.getAll();
13
+ // Include default values for editable keys not yet stored in DB
14
+ const result = {};
15
+ for (const key of editableKeys){
16
+ result[key] = allSettings[key] ?? CONFIG_FIELDS[key].defaultValue;
17
+ }
18
+ return c.json({
19
+ settings: result
20
+ });
21
+ });
22
+ // Update settings (requires auth)
23
+ settingsApiRoutes.put("/", requireAuthApi(), async (c)=>{
24
+ const rawBody = await c.req.json();
25
+ const parseResult = UpdateSettingsSchema.safeParse(rawBody);
26
+ if (!parseResult.success) {
27
+ return c.json({
28
+ error: "Validation failed",
29
+ details: parseResult.error.flatten()
30
+ }, 400);
31
+ }
32
+ const updates = parseResult.data;
33
+ // Filter to only editable keys
34
+ const filteredUpdates = {};
35
+ const rejectedKeys = [];
36
+ for (const [key, value] of Object.entries(updates)){
37
+ if (editableKeys.includes(key)) {
38
+ filteredUpdates[key] = value;
39
+ } else {
40
+ rejectedKeys.push(key);
41
+ }
42
+ }
43
+ if (rejectedKeys.length > 0 && Object.keys(filteredUpdates).length === 0) {
44
+ return c.json({
45
+ error: "None of the provided keys are editable",
46
+ rejectedKeys
47
+ }, 400);
48
+ }
49
+ if (Object.keys(filteredUpdates).length > 0) {
50
+ // Settings service expects SettingsKey, but our ConfigKeys that are
51
+ // editable (SITE_NAME, SITE_DESCRIPTION, SITE_LANGUAGE) are valid SettingsKeys
52
+ for (const [key, value] of Object.entries(filteredUpdates)){
53
+ await c.var.services.settings.set(key, value);
54
+ }
55
+ }
56
+ // Return updated state
57
+ const allSettings = await c.var.services.settings.getAll();
58
+ const result = {};
59
+ for (const key of editableKeys){
60
+ result[key] = allSettings[key] ?? CONFIG_FIELDS[key].defaultValue;
61
+ }
62
+ return c.json({
63
+ settings: result,
64
+ ...rejectedKeys.length > 0 && {
65
+ rejectedKeys
66
+ }
67
+ });
68
+ });
@@ -8,7 +8,7 @@ import { html } from "hono/html";
8
8
  import { uuidv7 } from "uuidv7";
9
9
  import { requireAuthApi } from "../../middleware/auth.js";
10
10
  import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "../../lib/image.js";
11
- import { sse, dsSignals } from "../../lib/sse.js";
11
+ import { sse } from "../../lib/sse.js";
12
12
  export const uploadApiRoutes = new Hono();
13
13
  // Require auth for all upload routes
14
14
  uploadApiRoutes.use("*", requireAuthApi());
@@ -85,14 +85,20 @@ function formatSize(bytes) {
85
85
  const accept = c.req.header("accept") || "";
86
86
  return accept.includes("text/event-stream");
87
87
  }
88
+ /**
89
+ * Return an SSE error response that removes the upload placeholder and shows a toast
90
+ */ function sseUploadError(c, message) {
91
+ return sse(c, async (stream)=>{
92
+ await stream.remove("#upload-placeholder");
93
+ await stream.toast(message, "error");
94
+ });
95
+ }
88
96
  // Upload a file
89
97
  uploadApiRoutes.post("/", async (c)=>{
90
98
  const storage = c.var.storage;
91
99
  if (!storage) {
92
100
  if (wantsSSE(c)) {
93
- return dsSignals({
94
- _uploadError: "Storage not configured"
95
- });
101
+ return sseUploadError(c, "Storage not configured");
96
102
  }
97
103
  return c.json({
98
104
  error: "Storage not configured"
@@ -102,9 +108,7 @@ uploadApiRoutes.post("/", async (c)=>{
102
108
  const file = formData.get("file");
103
109
  if (!file) {
104
110
  if (wantsSSE(c)) {
105
- return dsSignals({
106
- _uploadError: "No file provided"
107
- });
111
+ return sseUploadError(c, "No file provided");
108
112
  }
109
113
  return c.json({
110
114
  error: "No file provided"
@@ -120,9 +124,7 @@ uploadApiRoutes.post("/", async (c)=>{
120
124
  ];
121
125
  if (!allowedTypes.includes(file.type)) {
122
126
  if (wantsSSE(c)) {
123
- return dsSignals({
124
- _uploadError: "File type not allowed"
125
- });
127
+ return sseUploadError(c, "File type not allowed");
126
128
  }
127
129
  return c.json({
128
130
  error: "File type not allowed"
@@ -132,9 +134,7 @@ uploadApiRoutes.post("/", async (c)=>{
132
134
  const maxSize = 10 * 1024 * 1024;
133
135
  if (file.size > maxSize) {
134
136
  if (wantsSSE(c)) {
135
- return dsSignals({
136
- _uploadError: "File too large (max 10MB)"
137
- });
137
+ return sseUploadError(c, "File too large (max 10MB)");
138
138
  }
139
139
  return c.json({
140
140
  error: "File too large (max 10MB)"
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Compose Route
3
+ *
4
+ * Handles post creation from the public-site compose dialog.
5
+ * Returns dsRedirect to the new post's permalink (Datastar form pattern).
6
+ */ import { Hono } from "hono";
7
+ import { requireAuth } from "../middleware/auth.js";
8
+ import { CreatePostSchema, validateMediaCount } from "../lib/schemas.js";
9
+ import * as sqid from "../lib/sqid.js";
10
+ import { dsRedirect, dsToast } from "../lib/sse.js";
11
+ export const composeRoutes = new Hono();
12
+ // All compose routes require authentication
13
+ composeRoutes.use("*", requireAuth());
14
+ composeRoutes.post("/", async (c)=>{
15
+ const raw = await c.req.json();
16
+ const result = CreatePostSchema.safeParse(raw);
17
+ if (!result.success) {
18
+ const firstError = result.error.issues[0]?.message ?? "Invalid input";
19
+ return dsToast(firstError, "error");
20
+ }
21
+ const data = result.data;
22
+ // Validate media count
23
+ if (data.mediaIds) {
24
+ const mediaError = validateMediaCount(data.mediaIds);
25
+ if (mediaError) {
26
+ return dsToast(mediaError, "error");
27
+ }
28
+ }
29
+ const post = await c.var.services.posts.create({
30
+ format: data.format,
31
+ title: data.title || undefined,
32
+ body: data.body || undefined,
33
+ status: data.status ?? "published",
34
+ featured: data.featured,
35
+ pinned: data.pinned,
36
+ url: data.url || undefined,
37
+ quoteText: data.quoteText || undefined,
38
+ rating: data.rating || undefined,
39
+ collectionId: data.collectionId || undefined
40
+ });
41
+ // Attach media if provided
42
+ if (data.mediaIds && data.mediaIds.length > 0) {
43
+ await c.var.services.media.attachToPost(post.id, data.mediaIds);
44
+ }
45
+ // Redirect to the new post's permalink
46
+ const permalink = post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`;
47
+ return dsRedirect(permalink);
48
+ });
@@ -4,8 +4,8 @@ import { getSiteName } from "../../lib/config.js";
4
4
  * Dashboard Collections Routes
5
5
  */ import { Hono } from "hono";
6
6
  import { useLingui as $_useLingui } from "@jant/core/i18n";
7
- import { DashLayout } from "../../theme/layouts/index.js";
8
- import { EmptyState, ListItemRow, ActionButtons, CrudPageHeader, DangerZone } from "../../theme/components/index.js";
7
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
8
+ import { EmptyState, ListItemRow, ActionButtons, CrudPageHeader, DangerZone } from "../../ui/dash/index.js";
9
9
  import * as sqid from "../../lib/sqid.js";
10
10
  import { dsRedirect } from "../../lib/sse.js";
11
11
  export const collectionsRoutes = new Hono();
@@ -43,7 +43,7 @@ function CollectionsListContent({ collections }) {
43
43
  id: "ePK91l",
44
44
  message: "Edit"
45
45
  }),
46
- viewHref: `/c/${col.path}`,
46
+ viewHref: `/c/${col.slug}`,
47
47
  viewLabel: $__i18n._({
48
48
  id: "jpctdh",
49
49
  message: "View"
@@ -59,7 +59,7 @@ function CollectionsListContent({ collections }) {
59
59
  class: "text-sm text-muted-foreground",
60
60
  children: [
61
61
  "/",
62
- col.path
62
+ col.slug
63
63
  ]
64
64
  }),
65
65
  col.description && /*#__PURE__*/ _jsx("p", {
@@ -84,7 +84,7 @@ function NewCollectionContent() {
84
84
  })
85
85
  }),
86
86
  /*#__PURE__*/ _jsxs("form", {
87
- "data-signals": "{title: '', path: '', description: ''}",
87
+ "data-signals": "{title: '', slug: '', description: ''}",
88
88
  "data-on:submit__prevent": "@post('/dash/collections')",
89
89
  "data-indicator": "_loading",
90
90
  class: "flex flex-col gap-4 max-w-lg",
@@ -123,7 +123,7 @@ function NewCollectionContent() {
123
123
  }),
124
124
  /*#__PURE__*/ _jsx("input", {
125
125
  type: "text",
126
- "data-bind": "path",
126
+ "data-bind": "slug",
127
127
  class: "input",
128
128
  required: true,
129
129
  placeholder: "my-collection",
@@ -219,7 +219,7 @@ function ViewCollectionContent({ collection, posts }) {
219
219
  class: "text-sm text-muted-foreground",
220
220
  children: [
221
221
  "/",
222
- collection.path
222
+ collection.slug
223
223
  ]
224
224
  })
225
225
  ]
@@ -230,7 +230,7 @@ function ViewCollectionContent({ collection, posts }) {
230
230
  id: "ePK91l",
231
231
  message: "Edit"
232
232
  }),
233
- viewHref: `/c/${collection.path}`,
233
+ viewHref: `/c/${collection.slug}`,
234
234
  viewLabel: $__i18n._({
235
235
  id: "jpctdh",
236
236
  message: "View"
@@ -259,27 +259,16 @@ function ViewCollectionContent({ collection, posts }) {
259
259
  })
260
260
  }) : /*#__PURE__*/ _jsx("div", {
261
261
  class: "flex flex-col divide-y",
262
- children: posts.map((post)=>/*#__PURE__*/ _jsxs("div", {
262
+ children: posts.map((post)=>/*#__PURE__*/ _jsx("div", {
263
263
  class: "py-3 flex items-center gap-4",
264
- children: [
265
- /*#__PURE__*/ _jsx("div", {
266
- class: "flex-1 min-w-0",
267
- children: /*#__PURE__*/ _jsx("a", {
268
- href: `/dash/posts/${sqid.encode(post.id)}`,
269
- class: "font-medium hover:underline",
270
- children: post.title || post.content?.slice(0, 50) || `Post #${post.id}`
271
- })
272
- }),
273
- /*#__PURE__*/ _jsx("button", {
274
- type: "button",
275
- class: "btn-sm-ghost text-destructive",
276
- "data-on:click__prevent": `@post('/dash/collections/${collection.id}/remove-post', {payload: {postId: ${post.id}}})`,
277
- children: $__i18n._({
278
- id: "t/YqKh",
279
- message: "Remove"
280
- })
264
+ children: /*#__PURE__*/ _jsx("div", {
265
+ class: "flex-1 min-w-0",
266
+ children: /*#__PURE__*/ _jsx("a", {
267
+ href: `/dash/posts/${sqid.encode(post.id)}`,
268
+ class: "font-medium hover:underline",
269
+ children: post.title || post.body?.slice(0, 50) || `Post #${post.id}`
281
270
  })
282
- ]
271
+ })
283
272
  }, post.id))
284
273
  })
285
274
  })
@@ -303,7 +292,7 @@ function EditCollectionContent({ collection }) {
303
292
  const { i18n: $__i18n, _: $__ } = $_useLingui();
304
293
  const signals = JSON.stringify({
305
294
  title: collection.title,
306
- path: collection.path ?? "",
295
+ slug: collection.slug ?? "",
307
296
  description: collection.description ?? ""
308
297
  }).replace(/</g, "\\u003c");
309
298
  return /*#__PURE__*/ _jsxs(_Fragment, {
@@ -351,7 +340,7 @@ function EditCollectionContent({ collection }) {
351
340
  }),
352
341
  /*#__PURE__*/ _jsx("input", {
353
342
  type: "text",
354
- "data-bind": "path",
343
+ "data-bind": "slug",
355
344
  class: "input",
356
345
  required: true,
357
346
  pattern: "[a-z0-9-]+"
@@ -453,7 +442,7 @@ collectionsRoutes.post("/", async (c)=>{
453
442
  const body = await c.req.json();
454
443
  const collection = await c.var.services.collections.create({
455
444
  title: body.title,
456
- path: body.path,
445
+ slug: body.slug,
457
446
  description: body.description || undefined
458
447
  });
459
448
  return dsRedirect(`/dash/collections/${collection.id}`);
@@ -464,7 +453,10 @@ collectionsRoutes.get("/:id", async (c)=>{
464
453
  if (isNaN(id)) return c.notFound();
465
454
  const collection = await c.var.services.collections.getById(id);
466
455
  if (!collection) return c.notFound();
467
- const posts = await c.var.services.collections.getPosts(id);
456
+ // Fetch posts in this collection via post service
457
+ const posts = await c.var.services.posts.list({
458
+ collectionId: id
459
+ });
468
460
  const siteName = await getSiteName(c);
469
461
  return c.html(/*#__PURE__*/ _jsx(DashLayout, {
470
462
  c: c,
@@ -501,7 +493,7 @@ collectionsRoutes.post("/:id", async (c)=>{
501
493
  const body = await c.req.json();
502
494
  await c.var.services.collections.update(id, {
503
495
  title: body.title,
504
- path: body.path,
496
+ slug: body.slug,
505
497
  description: body.description || undefined
506
498
  });
507
499
  return dsRedirect(`/dash/collections/${id}`);
@@ -513,13 +505,3 @@ collectionsRoutes.post("/:id/delete", async (c)=>{
513
505
  await c.var.services.collections.delete(id);
514
506
  return dsRedirect("/dash/collections");
515
507
  });
516
- // Remove post from collection
517
- collectionsRoutes.post("/:id/remove-post", async (c)=>{
518
- const id = parseInt(c.req.param("id"), 10);
519
- if (isNaN(id)) return c.notFound();
520
- const body = await c.req.json();
521
- if (body.postId) {
522
- await c.var.services.collections.removePost(id, body.postId);
523
- }
524
- return dsRedirect(`/dash/collections/${id}`);
525
- });
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
6
  */ import { Hono } from "hono";
7
7
  import { useLingui as $_useLingui } from "@jant/core/i18n";
8
8
  import { Trans as Trans_ } from "@jant/core/i18n";
9
- import { DashLayout } from "../../theme/layouts/index.js";
9
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
10
10
  import { getSiteName } from "../../lib/config.js";
11
11
  export const dashIndexRoutes = new Hono();
12
12
  /**
@@ -103,8 +103,8 @@ dashIndexRoutes.get("/", async (c)=>{
103
103
  const allPosts = await c.var.services.posts.list({
104
104
  limit: 1000
105
105
  });
106
- const publishedPosts = allPosts.filter((p)=>p.visibility !== "draft");
107
- const draftPosts = allPosts.filter((p)=>p.visibility === "draft");
106
+ const publishedPosts = allPosts.filter((p)=>p.status !== "draft");
107
+ const draftPosts = allPosts.filter((p)=>p.status === "draft");
108
108
  return c.html(/*#__PURE__*/ _jsx(DashLayout, {
109
109
  c: c,
110
110
  title: "Dashboard",
@@ -7,8 +7,8 @@ import { getSiteName } from "../../lib/config.js";
7
7
  * Uses SSE for real-time UI updates without page reloads.
8
8
  */ import { Hono } from "hono";
9
9
  import { useLingui as $_useLingui } from "@jant/core/i18n";
10
- import { DashLayout } from "../../theme/layouts/index.js";
11
- import { EmptyState, DangerZone } from "../../theme/components/index.js";
10
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
11
+ import { EmptyState, DangerZone } from "../../ui/dash/index.js";
12
12
  import * as time from "../../lib/time.js";
13
13
  import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "../../lib/image.js";
14
14
  import { dsRedirect } from "../../lib/sse.js";