@jant/core 0.3.22 → 0.3.24

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 (178) hide show
  1. package/dist/app.js +23 -5
  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 -6
  7. package/dist/lib/constants.js +1 -4
  8. package/dist/lib/excerpt.js +76 -0
  9. package/dist/lib/feed.js +18 -7
  10. package/dist/lib/navigation.js +4 -5
  11. package/dist/lib/render.js +1 -1
  12. package/dist/lib/schemas.js +80 -38
  13. package/dist/lib/theme-components.js +8 -11
  14. package/dist/lib/time.js +56 -1
  15. package/dist/lib/timeline.js +119 -0
  16. package/dist/lib/view.js +62 -73
  17. package/dist/routes/api/posts.js +29 -35
  18. package/dist/routes/api/search.js +5 -6
  19. package/dist/routes/api/upload.js +13 -13
  20. package/dist/routes/dash/collections.js +22 -40
  21. package/dist/routes/dash/index.js +2 -2
  22. package/dist/routes/dash/navigation.js +25 -24
  23. package/dist/routes/dash/pages.js +42 -57
  24. package/dist/routes/dash/posts.js +27 -35
  25. package/dist/routes/feed/rss.js +2 -4
  26. package/dist/routes/feed/sitemap.js +10 -7
  27. package/dist/routes/pages/archive.js +12 -11
  28. package/dist/routes/pages/collection.js +11 -5
  29. package/dist/routes/pages/home.js +53 -61
  30. package/dist/routes/pages/page.js +60 -29
  31. package/dist/routes/pages/post.js +5 -12
  32. package/dist/routes/pages/search.js +3 -4
  33. package/dist/services/collection.js +52 -64
  34. package/dist/services/index.js +5 -3
  35. package/dist/services/navigation.js +29 -53
  36. package/dist/services/page.js +80 -0
  37. package/dist/services/post.js +68 -69
  38. package/dist/services/search.js +24 -18
  39. package/dist/theme/components/MediaGallery.js +19 -91
  40. package/dist/theme/components/PageForm.js +15 -15
  41. package/dist/theme/components/PostForm.js +136 -129
  42. package/dist/theme/components/PostList.js +13 -8
  43. package/dist/theme/components/ThreadView.js +3 -3
  44. package/dist/theme/components/TypeBadge.js +3 -14
  45. package/dist/theme/components/VisibilityBadge.js +33 -23
  46. package/dist/theme/components/index.js +0 -2
  47. package/dist/theme/index.js +10 -16
  48. package/dist/theme/layouts/index.js +0 -1
  49. package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
  50. package/dist/themes/threads/index.js +81 -0
  51. package/dist/{theme → themes/threads}/pages/ArchivePage.js +31 -47
  52. package/dist/themes/threads/pages/CollectionPage.js +65 -0
  53. package/dist/{theme → themes/threads}/pages/HomePage.js +4 -5
  54. package/dist/{theme → themes/threads}/pages/PostPage.js +10 -8
  55. package/dist/{theme → themes/threads}/pages/SearchPage.js +8 -8
  56. package/dist/{theme → themes/threads}/pages/SinglePage.js +5 -6
  57. package/dist/{theme/components → themes/threads}/timeline/LinkCard.js +20 -11
  58. package/dist/themes/threads/timeline/NoteCard.js +53 -0
  59. package/dist/themes/threads/timeline/QuoteCard.js +59 -0
  60. package/dist/{theme/components → themes/threads}/timeline/ThreadPreview.js +5 -6
  61. package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
  62. package/dist/{theme/components → themes/threads}/timeline/TimelineItem.js +8 -17
  63. package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
  64. package/dist/themes/threads/timeline/groupByDate.js +22 -0
  65. package/dist/themes/threads/timeline/timelineMore.js +107 -0
  66. package/dist/types.js +24 -40
  67. package/package.json +2 -1
  68. package/src/__tests__/helpers/app.ts +4 -0
  69. package/src/__tests__/helpers/db.ts +51 -74
  70. package/src/app.tsx +27 -6
  71. package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
  72. package/src/db/migrations/meta/_journal.json +7 -0
  73. package/src/db/schema.ts +63 -46
  74. package/src/i18n/locales/en.po +216 -164
  75. package/src/i18n/locales/en.ts +1 -1
  76. package/src/i18n/locales/zh-Hans.po +216 -164
  77. package/src/i18n/locales/zh-Hans.ts +1 -1
  78. package/src/i18n/locales/zh-Hant.po +216 -164
  79. package/src/i18n/locales/zh-Hant.ts +1 -1
  80. package/src/index.ts +30 -15
  81. package/src/lib/__tests__/excerpt.test.ts +125 -0
  82. package/src/lib/__tests__/schemas.test.ts +166 -105
  83. package/src/lib/__tests__/theme-components.test.ts +4 -25
  84. package/src/lib/__tests__/time.test.ts +62 -0
  85. package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
  86. package/src/lib/__tests__/view.test.ts +217 -67
  87. package/src/lib/constants.ts +1 -4
  88. package/src/lib/excerpt.ts +87 -0
  89. package/src/lib/feed.ts +22 -7
  90. package/src/lib/navigation.ts +6 -7
  91. package/src/lib/render.tsx +1 -1
  92. package/src/lib/schemas.ts +118 -52
  93. package/src/lib/theme-components.ts +10 -13
  94. package/src/lib/time.ts +64 -0
  95. package/src/lib/timeline.ts +170 -0
  96. package/src/lib/view.ts +81 -83
  97. package/src/preset.css +45 -0
  98. package/src/routes/api/__tests__/posts.test.ts +50 -108
  99. package/src/routes/api/__tests__/search.test.ts +2 -3
  100. package/src/routes/api/posts.ts +30 -30
  101. package/src/routes/api/search.ts +4 -4
  102. package/src/routes/api/upload.ts +16 -6
  103. package/src/routes/dash/collections.tsx +18 -40
  104. package/src/routes/dash/index.tsx +2 -2
  105. package/src/routes/dash/navigation.tsx +27 -26
  106. package/src/routes/dash/pages.tsx +45 -60
  107. package/src/routes/dash/posts.tsx +44 -52
  108. package/src/routes/feed/rss.ts +2 -1
  109. package/src/routes/feed/sitemap.ts +14 -4
  110. package/src/routes/pages/archive.tsx +14 -10
  111. package/src/routes/pages/collection.tsx +17 -6
  112. package/src/routes/pages/home.tsx +56 -81
  113. package/src/routes/pages/page.tsx +64 -27
  114. package/src/routes/pages/post.tsx +5 -14
  115. package/src/routes/pages/search.tsx +2 -2
  116. package/src/services/__tests__/collection.test.ts +257 -158
  117. package/src/services/__tests__/media.test.ts +18 -18
  118. package/src/services/__tests__/navigation.test.ts +161 -87
  119. package/src/services/__tests__/post-timeline.test.ts +92 -88
  120. package/src/services/__tests__/post.test.ts +342 -206
  121. package/src/services/__tests__/search.test.ts +19 -25
  122. package/src/services/collection.ts +71 -113
  123. package/src/services/index.ts +9 -8
  124. package/src/services/navigation.ts +38 -71
  125. package/src/services/page.ts +124 -0
  126. package/src/services/post.ts +93 -103
  127. package/src/services/search.ts +38 -27
  128. package/src/styles/components.css +0 -54
  129. package/src/theme/components/MediaGallery.tsx +27 -96
  130. package/src/theme/components/PageForm.tsx +21 -21
  131. package/src/theme/components/PostForm.tsx +122 -118
  132. package/src/theme/components/PostList.tsx +58 -49
  133. package/src/theme/components/ThreadView.tsx +6 -3
  134. package/src/theme/components/TypeBadge.tsx +9 -17
  135. package/src/theme/components/VisibilityBadge.tsx +40 -23
  136. package/src/theme/components/index.ts +0 -13
  137. package/src/theme/index.ts +10 -16
  138. package/src/theme/layouts/index.ts +0 -1
  139. package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
  140. package/src/themes/threads/index.ts +100 -0
  141. package/src/{theme → themes/threads}/pages/ArchivePage.tsx +52 -55
  142. package/src/themes/threads/pages/CollectionPage.tsx +61 -0
  143. package/src/{theme → themes/threads}/pages/HomePage.tsx +5 -6
  144. package/src/{theme → themes/threads}/pages/PostPage.tsx +11 -8
  145. package/src/{theme → themes/threads}/pages/SearchPage.tsx +9 -13
  146. package/src/themes/threads/pages/SinglePage.tsx +23 -0
  147. package/src/themes/threads/style.css +336 -0
  148. package/src/{theme/components → themes/threads}/timeline/LinkCard.tsx +21 -13
  149. package/src/themes/threads/timeline/NoteCard.tsx +58 -0
  150. package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
  151. package/src/{theme/components → themes/threads}/timeline/ThreadPreview.tsx +6 -6
  152. package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
  153. package/src/{theme/components → themes/threads}/timeline/TimelineItem.tsx +9 -20
  154. package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
  155. package/src/themes/threads/timeline/groupByDate.ts +30 -0
  156. package/src/themes/threads/timeline/timelineMore.tsx +130 -0
  157. package/src/types.ts +242 -98
  158. package/dist/routes/api/timeline.js +0 -120
  159. package/dist/theme/components/timeline/ArticleCard.js +0 -46
  160. package/dist/theme/components/timeline/ImageCard.js +0 -83
  161. package/dist/theme/components/timeline/NoteCard.js +0 -34
  162. package/dist/theme/components/timeline/QuoteCard.js +0 -48
  163. package/dist/theme/components/timeline/TimelineFeed.js +0 -46
  164. package/dist/theme/components/timeline/index.js +0 -8
  165. package/dist/theme/layouts/SiteLayout.js +0 -131
  166. package/dist/theme/pages/CollectionPage.js +0 -63
  167. package/dist/theme/pages/index.js +0 -11
  168. package/src/routes/api/timeline.tsx +0 -159
  169. package/src/theme/components/timeline/ArticleCard.tsx +0 -45
  170. package/src/theme/components/timeline/ImageCard.tsx +0 -70
  171. package/src/theme/components/timeline/NoteCard.tsx +0 -34
  172. package/src/theme/components/timeline/QuoteCard.tsx +0 -48
  173. package/src/theme/components/timeline/TimelineFeed.tsx +0 -56
  174. package/src/theme/components/timeline/index.ts +0 -8
  175. package/src/theme/layouts/SiteLayout.tsx +0 -132
  176. package/src/theme/pages/CollectionPage.tsx +0 -60
  177. package/src/theme/pages/SinglePage.tsx +0 -24
  178. package/src/theme/pages/index.ts +0 -13
@@ -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
- });
@@ -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">
@@ -1,11 +1,11 @@
1
1
  import { getSiteName } from "../../lib/config.js";
2
2
  /**
3
- * Dashboard Navigation Links Routes
3
+ * Dashboard Navigation Items Routes
4
4
  */
5
5
 
6
6
  import { Hono } from "hono";
7
7
  import { useLingui } from "@lingui/react/macro";
8
- import type { Bindings, NavigationLink } from "../../types.js";
8
+ import type { Bindings, NavItem } from "../../types.js";
9
9
  import type { AppVariables } from "../../app.js";
10
10
  import { DashLayout } from "../../theme/layouts/index.js";
11
11
  import {
@@ -20,7 +20,7 @@ type Env = { Bindings: Bindings; Variables: AppVariables };
20
20
 
21
21
  export const navigationRoutes = new Hono<Env>();
22
22
 
23
- function NavigationListContent({ links }: { links: NavigationLink[] }) {
23
+ function NavigationListContent({ items }: { items: NavItem[] }) {
24
24
  const { t } = useLingui();
25
25
 
26
26
  return (
@@ -37,7 +37,7 @@ function NavigationListContent({ links }: { links: NavigationLink[] }) {
37
37
  ctaHref="/dash/navigation/new"
38
38
  />
39
39
 
40
- {links.length === 0 ? (
40
+ {items.length === 0 ? (
41
41
  <EmptyState
42
42
  message={t({
43
43
  message: "No navigation links configured.",
@@ -52,17 +52,17 @@ function NavigationListContent({ links }: { links: NavigationLink[] }) {
52
52
  ) : (
53
53
  <>
54
54
  <div id="nav-links-list" class="flex flex-col divide-y">
55
- {links.map((link) => (
55
+ {items.map((item) => (
56
56
  <ListItemRow
57
- key={link.id}
57
+ key={item.id}
58
58
  actions={
59
59
  <ActionButtons
60
- editHref={`/dash/navigation/${link.id}/edit`}
60
+ editHref={`/dash/navigation/${item.id}/edit`}
61
61
  editLabel={t({
62
62
  message: "Edit",
63
63
  comment: "@context: Button to edit navigation link",
64
64
  })}
65
- deleteAction={`/dash/navigation/${link.id}/delete`}
65
+ deleteAction={`/dash/navigation/${item.id}/delete`}
66
66
  deleteLabel={t({
67
67
  message: "Delete",
68
68
  comment: "@context: Button to delete navigation link",
@@ -72,13 +72,13 @@ function NavigationListContent({ links }: { links: NavigationLink[] }) {
72
72
  >
73
73
  <div
74
74
  class="flex items-center gap-3 cursor-grab"
75
- data-id={link.id}
75
+ data-id={item.id}
76
76
  >
77
77
  <span class="text-muted-foreground select-none">⠿</span>
78
78
  <div class="flex items-center gap-2">
79
- <span class="font-medium">{link.label}</span>
79
+ <span class="font-medium">{item.label}</span>
80
80
  <code class="text-sm text-muted-foreground bg-muted px-1 rounded">
81
- {link.url}
81
+ {item.url}
82
82
  </code>
83
83
  </div>
84
84
  </div>
@@ -94,10 +94,10 @@ function NavigationListContent({ links }: { links: NavigationLink[] }) {
94
94
  }
95
95
 
96
96
  function NavigationFormContent({
97
- link,
97
+ item,
98
98
  isEdit,
99
99
  }: {
100
- link?: NavigationLink;
100
+ item?: NavItem;
101
101
  isEdit?: boolean;
102
102
  }) {
103
103
  const { t } = useLingui();
@@ -106,11 +106,11 @@ function NavigationFormContent({
106
106
  : t({ message: "New Link", comment: "@context: Page heading" });
107
107
 
108
108
  const signals = JSON.stringify({
109
- label: link?.label ?? "",
110
- url: link?.url ?? "",
109
+ label: item?.label ?? "",
110
+ url: item?.url ?? "",
111
111
  }).replace(/</g, "\\u003c");
112
112
 
113
- const action = isEdit ? `/dash/navigation/${link?.id}` : "/dash/navigation";
113
+ const action = isEdit ? `/dash/navigation/${item?.id}` : "/dash/navigation";
114
114
 
115
115
  return (
116
116
  <>
@@ -200,10 +200,10 @@ function NavigationFormContent({
200
200
  );
201
201
  }
202
202
 
203
- // List navigation links
203
+ // List navigation items
204
204
  navigationRoutes.get("/", async (c) => {
205
205
  const siteName = await getSiteName(c);
206
- const links = await c.var.services.navigationLinks.list();
206
+ const items = await c.var.services.navItems.list();
207
207
 
208
208
  return c.html(
209
209
  <DashLayout
@@ -212,7 +212,7 @@ navigationRoutes.get("/", async (c) => {
212
212
  siteName={siteName}
213
213
  currentPath="/dash/navigation"
214
214
  >
215
- <NavigationListContent links={links} />
215
+ <NavigationListContent items={items} />
216
216
  </DashLayout>,
217
217
  );
218
218
  });
@@ -241,7 +241,8 @@ navigationRoutes.post("/", async (c) => {
241
241
  return dsToast("Label and URL are required", "error");
242
242
  }
243
243
 
244
- await c.var.services.navigationLinks.create({
244
+ await c.var.services.navItems.create({
245
+ type: "link",
245
246
  label: body.label,
246
247
  url: body.url,
247
248
  });
@@ -257,7 +258,7 @@ navigationRoutes.post("/reorder", async (c) => {
257
258
  return dsToast("Invalid request", "error");
258
259
  }
259
260
 
260
- await c.var.services.navigationLinks.reorder(body.ids);
261
+ await c.var.services.navItems.reorder(body.ids);
261
262
 
262
263
  return dsToast("Order saved");
263
264
  });
@@ -267,8 +268,8 @@ navigationRoutes.get("/:id/edit", async (c) => {
267
268
  const id = parseInt(c.req.param("id"), 10);
268
269
  if (isNaN(id)) return c.notFound();
269
270
 
270
- const link = await c.var.services.navigationLinks.getById(id);
271
- if (!link) return c.notFound();
271
+ const item = await c.var.services.navItems.getById(id);
272
+ if (!item) return c.notFound();
272
273
 
273
274
  const siteName = await getSiteName(c);
274
275
 
@@ -279,7 +280,7 @@ navigationRoutes.get("/:id/edit", async (c) => {
279
280
  siteName={siteName}
280
281
  currentPath="/dash/navigation"
281
282
  >
282
- <NavigationFormContent link={link} isEdit />
283
+ <NavigationFormContent item={item} isEdit />
283
284
  </DashLayout>,
284
285
  );
285
286
  });
@@ -295,7 +296,7 @@ navigationRoutes.post("/:id", async (c) => {
295
296
  return dsToast("Label and URL are required", "error");
296
297
  }
297
298
 
298
- const updated = await c.var.services.navigationLinks.update(id, {
299
+ const updated = await c.var.services.navItems.update(id, {
299
300
  label: body.label,
300
301
  url: body.url,
301
302
  });
@@ -309,7 +310,7 @@ navigationRoutes.post("/:id", async (c) => {
309
310
  navigationRoutes.post("/:id/delete", async (c) => {
310
311
  const id = parseInt(c.req.param("id"), 10);
311
312
  if (!isNaN(id)) {
312
- await c.var.services.navigationLinks.delete(id);
313
+ await c.var.services.navItems.delete(id);
313
314
  }
314
315
 
315
316
  return dsRedirect("/dash/navigation");
@@ -2,24 +2,22 @@ import { getSiteName } from "../../lib/config.js";
2
2
  /**
3
3
  * Dashboard Pages Routes
4
4
  *
5
- * Management for custom pages (posts with type="page")
5
+ * Management for standalone pages (about, now, etc.)
6
6
  */
7
7
 
8
8
  import { Hono } from "hono";
9
9
  import { useLingui } from "@lingui/react/macro";
10
- import type { Bindings, Post } from "../../types.js";
10
+ import type { Bindings, Page } from "../../types.js";
11
11
  import type { AppVariables } from "../../app.js";
12
12
  import { DashLayout } from "../../theme/layouts/index.js";
13
13
  import {
14
14
  PageForm,
15
- VisibilityBadge,
16
15
  EmptyState,
17
16
  ListItemRow,
18
17
  ActionButtons,
19
18
  CrudPageHeader,
20
19
  DangerZone,
21
20
  } from "../../theme/components/index.js";
22
- import * as sqid from "../../lib/sqid.js";
23
21
  import * as time from "../../lib/time.js";
24
22
  import { dsRedirect } from "../../lib/sse.js";
25
23
 
@@ -27,7 +25,7 @@ type Env = { Bindings: Bindings; Variables: AppVariables };
27
25
 
28
26
  export const pagesRoutes = new Hono<Env>();
29
27
 
30
- function PagesListContent({ pages }: { pages: Post[] }) {
28
+ function PagesListContent({ pages }: { pages: Page[] }) {
31
29
  const { t } = useLingui();
32
30
 
33
31
  return (
@@ -60,15 +58,13 @@ function PagesListContent({ pages }: { pages: Post[] }) {
60
58
  key={page.id}
61
59
  actions={
62
60
  <ActionButtons
63
- editHref={`/dash/pages/${sqid.encode(page.id)}/edit`}
61
+ editHref={`/dash/pages/${page.id}/edit`}
64
62
  editLabel={t({
65
63
  message: "Edit",
66
64
  comment: "@context: Button to edit page",
67
65
  })}
68
66
  viewHref={
69
- page.visibility !== "draft" && page.path
70
- ? `/${page.path}`
71
- : undefined
67
+ page.status !== "draft" ? `/${page.slug}` : undefined
72
68
  }
73
69
  viewLabel={t({
74
70
  message: "View",
@@ -78,13 +74,12 @@ function PagesListContent({ pages }: { pages: Post[] }) {
78
74
  }
79
75
  >
80
76
  <div class="flex items-center gap-2 mb-1">
81
- <VisibilityBadge visibility={page.visibility} />
82
77
  <span class="text-xs text-muted-foreground">
83
78
  {time.formatDate(page.updatedAt)}
84
79
  </span>
85
80
  </div>
86
81
  <a
87
- href={`/dash/pages/${sqid.encode(page.id)}`}
82
+ href={`/dash/pages/${page.id}`}
88
83
  class="font-medium hover:underline"
89
84
  >
90
85
  {page.title ||
@@ -93,7 +88,7 @@ function PagesListContent({ pages }: { pages: Post[] }) {
93
88
  comment: "@context: Default title for untitled page",
94
89
  })}
95
90
  </a>
96
- <p class="text-sm text-muted-foreground mt-1">/{page.path}</p>
91
+ <p class="text-sm text-muted-foreground mt-1">/{page.slug}</p>
97
92
  </ListItemRow>
98
93
  ))}
99
94
  </div>
@@ -114,7 +109,7 @@ function NewPageContent() {
114
109
  );
115
110
  }
116
111
 
117
- function ViewPageContent({ page }: { page: Post }) {
112
+ function ViewPageContent({ page }: { page: Page }) {
118
113
  const { t } = useLingui();
119
114
  return (
120
115
  <>
@@ -127,19 +122,15 @@ function ViewPageContent({ page }: { page: Post }) {
127
122
  comment: "@context: Default page heading when untitled",
128
123
  })}
129
124
  </h1>
130
- {page.path && <p class="text-muted-foreground mt-1">/{page.path}</p>}
125
+ <p class="text-muted-foreground mt-1">/{page.slug}</p>
131
126
  </div>
132
127
  <ActionButtons
133
- editHref={`/dash/pages/${sqid.encode(page.id)}/edit`}
128
+ editHref={`/dash/pages/${page.id}/edit`}
134
129
  editLabel={t({
135
130
  message: "Edit",
136
131
  comment: "@context: Button to edit page",
137
132
  })}
138
- viewHref={
139
- page.visibility !== "draft" && page.path
140
- ? `/${page.path}`
141
- : undefined
142
- }
133
+ viewHref={page.status !== "draft" ? `/${page.slug}` : undefined}
143
134
  viewLabel={t({
144
135
  message: "View",
145
136
  comment: "@context: Button to view page on public site",
@@ -151,7 +142,7 @@ function ViewPageContent({ page }: { page: Post }) {
151
142
  <section>
152
143
  <div
153
144
  class="prose"
154
- dangerouslySetInnerHTML={{ __html: page.contentHtml || "" }}
145
+ dangerouslySetInnerHTML={{ __html: page.bodyHtml || "" }}
155
146
  />
156
147
  </section>
157
148
  </div>
@@ -161,14 +152,14 @@ function ViewPageContent({ page }: { page: Post }) {
161
152
  message: "Delete Page",
162
153
  comment: "@context: Button to delete page",
163
154
  })}
164
- formAction={`/dash/pages/${sqid.encode(page.id)}/delete`}
155
+ formAction={`/dash/pages/${page.id}/delete`}
165
156
  confirmMessage="Are you sure you want to delete this page?"
166
157
  />
167
158
  </>
168
159
  );
169
160
  }
170
161
 
171
- function EditPageContent({ page }: { page: Post }) {
162
+ function EditPageContent({ page }: { page: Page }) {
172
163
  const { t } = useLingui();
173
164
  return (
174
165
  <>
@@ -178,18 +169,14 @@ function EditPageContent({ page }: { page: Post }) {
178
169
  comment: "@context: Edit page main heading",
179
170
  })}
180
171
  </h1>
181
- <PageForm page={page} action={`/dash/pages/${sqid.encode(page.id)}`} />
172
+ <PageForm page={page} action={`/dash/pages/${page.id}`} />
182
173
  </>
183
174
  );
184
175
  }
185
176
 
186
177
  // List pages
187
178
  pagesRoutes.get("/", async (c) => {
188
- const pages = await c.var.services.posts.list({
189
- type: "page",
190
- visibility: ["unlisted", "draft"],
191
- limit: 100,
192
- });
179
+ const pages = await c.var.services.pages.list();
193
180
  const siteName = await getSiteName(c);
194
181
 
195
182
  return c.html(
@@ -224,29 +211,28 @@ pagesRoutes.get("/new", async (c) => {
224
211
  pagesRoutes.post("/", async (c) => {
225
212
  const body = await c.req.json<{
226
213
  title: string;
227
- content: string;
228
- visibility: string;
229
- path: string;
214
+ body: string;
215
+ status: string;
216
+ slug: string;
230
217
  }>();
231
218
 
232
- const page = await c.var.services.posts.create({
233
- type: "page",
219
+ const page = await c.var.services.pages.create({
234
220
  title: body.title,
235
- content: body.content,
236
- visibility: body.visibility as Post["visibility"],
237
- path: body.path.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
221
+ body: body.body,
222
+ status: body.status as Page["status"],
223
+ slug: body.slug.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
238
224
  });
239
225
 
240
- return dsRedirect(`/dash/pages/${sqid.encode(page.id)}`);
226
+ return dsRedirect(`/dash/pages/${page.id}`);
241
227
  });
242
228
 
243
229
  // View single page
244
230
  pagesRoutes.get("/:id", async (c) => {
245
- const id = sqid.decode(c.req.param("id"));
246
- if (!id) return c.notFound();
231
+ const id = parseInt(c.req.param("id"), 10);
232
+ if (isNaN(id)) return c.notFound();
247
233
 
248
- const page = await c.var.services.posts.getById(id);
249
- if (!page || page.type !== "page") return c.notFound();
234
+ const page = await c.var.services.pages.getById(id);
235
+ if (!page) return c.notFound();
250
236
 
251
237
  const siteName = await getSiteName(c);
252
238
 
@@ -264,11 +250,11 @@ pagesRoutes.get("/:id", async (c) => {
264
250
 
265
251
  // Edit page form
266
252
  pagesRoutes.get("/:id/edit", async (c) => {
267
- const id = sqid.decode(c.req.param("id"));
268
- if (!id) return c.notFound();
253
+ const id = parseInt(c.req.param("id"), 10);
254
+ if (isNaN(id)) return c.notFound();
269
255
 
270
- const page = await c.var.services.posts.getById(id);
271
- if (!page || page.type !== "page") return c.notFound();
256
+ const page = await c.var.services.pages.getById(id);
257
+ if (!page) return c.notFound();
272
258
 
273
259
  const siteName = await getSiteName(c);
274
260
 
@@ -286,33 +272,32 @@ pagesRoutes.get("/:id/edit", async (c) => {
286
272
 
287
273
  // Update page
288
274
  pagesRoutes.post("/:id", async (c) => {
289
- const id = sqid.decode(c.req.param("id"));
290
- if (!id) return c.notFound();
275
+ const id = parseInt(c.req.param("id"), 10);
276
+ if (isNaN(id)) return c.notFound();
291
277
 
292
278
  const body = await c.req.json<{
293
279
  title: string;
294
- content: string;
295
- visibility: string;
296
- path: string;
280
+ body: string;
281
+ status: string;
282
+ slug: string;
297
283
  }>();
298
284
 
299
- await c.var.services.posts.update(id, {
300
- type: "page",
285
+ await c.var.services.pages.update(id, {
301
286
  title: body.title,
302
- content: body.content,
303
- visibility: body.visibility as Post["visibility"],
304
- path: body.path.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
287
+ body: body.body,
288
+ status: body.status as Page["status"],
289
+ slug: body.slug.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
305
290
  });
306
291
 
307
- return dsRedirect(`/dash/pages/${sqid.encode(id)}`);
292
+ return dsRedirect(`/dash/pages/${id}`);
308
293
  });
309
294
 
310
295
  // Delete page
311
296
  pagesRoutes.post("/:id/delete", async (c) => {
312
- const id = sqid.decode(c.req.param("id"));
313
- if (!id) return c.notFound();
297
+ const id = parseInt(c.req.param("id"), 10);
298
+ if (isNaN(id)) return c.notFound();
314
299
 
315
- await c.var.services.posts.delete(id);
300
+ await c.var.services.pages.delete(id);
316
301
 
317
302
  return dsRedirect("/dash/pages");
318
303
  });