@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,101 +1,27 @@
1
- import { getSiteName } from "../../lib/config.js";
2
1
  /**
3
- * Dashboard Pages Routes
2
+ * Dashboard Pages & Navigation Routes
4
3
  *
5
- * Management for standalone pages (about, now, etc.)
4
+ * Unified management for pages and navigation items.
6
5
  */
7
6
 
8
7
  import { Hono } from "hono";
9
8
  import { useLingui } from "@lingui/react/macro";
10
9
  import type { Bindings, Page } from "../../types.js";
11
10
  import type { AppVariables } from "../../app.js";
12
- import { DashLayout } from "../../theme/layouts/index.js";
13
- import {
14
- PageForm,
15
- EmptyState,
16
- ListItemRow,
17
- ActionButtons,
18
- CrudPageHeader,
19
- DangerZone,
20
- } from "../../theme/components/index.js";
21
- import * as time from "../../lib/time.js";
22
- import { dsRedirect } from "../../lib/sse.js";
11
+ import { DashLayout } from "../../ui/layouts/DashLayout.js";
12
+ import { PageForm, ActionButtons, DangerZone } from "../../ui/dash/index.js";
13
+ import { dsRedirect, dsToast } from "../../lib/sse.js";
14
+ import { getSiteName } from "../../lib/config.js";
15
+ import { UnifiedPagesContent } from "../../ui/dash/pages/UnifiedPagesContent.js";
16
+ import { LinkFormContent } from "../../ui/dash/pages/LinkFormContent.js";
23
17
 
24
18
  type Env = { Bindings: Bindings; Variables: AppVariables };
25
19
 
26
20
  export const pagesRoutes = new Hono<Env>();
27
21
 
28
- function PagesListContent({ pages }: { pages: Page[] }) {
29
- const { t } = useLingui();
30
-
31
- return (
32
- <>
33
- <CrudPageHeader
34
- title={t({ message: "Pages", comment: "@context: Pages main heading" })}
35
- ctaLabel={t({
36
- message: "New Page",
37
- comment: "@context: Button to create new page",
38
- })}
39
- ctaHref="/dash/pages/new"
40
- />
41
-
42
- {pages.length === 0 ? (
43
- <EmptyState
44
- message={t({
45
- message: "No pages yet.",
46
- comment: "@context: Empty state message when no pages exist",
47
- })}
48
- ctaText={t({
49
- message: "Create your first page",
50
- comment: "@context: Button in empty state to create first page",
51
- })}
52
- ctaHref="/dash/pages/new"
53
- />
54
- ) : (
55
- <div class="flex flex-col divide-y">
56
- {pages.map((page) => (
57
- <ListItemRow
58
- key={page.id}
59
- actions={
60
- <ActionButtons
61
- editHref={`/dash/pages/${page.id}/edit`}
62
- editLabel={t({
63
- message: "Edit",
64
- comment: "@context: Button to edit page",
65
- })}
66
- viewHref={
67
- page.status !== "draft" ? `/${page.slug}` : undefined
68
- }
69
- viewLabel={t({
70
- message: "View",
71
- comment: "@context: Button to view page on public site",
72
- })}
73
- />
74
- }
75
- >
76
- <div class="flex items-center gap-2 mb-1">
77
- <span class="text-xs text-muted-foreground">
78
- {time.formatDate(page.updatedAt)}
79
- </span>
80
- </div>
81
- <a
82
- href={`/dash/pages/${page.id}`}
83
- class="font-medium hover:underline"
84
- >
85
- {page.title ||
86
- t({
87
- message: "Untitled",
88
- comment: "@context: Default title for untitled page",
89
- })}
90
- </a>
91
- <p class="text-sm text-muted-foreground mt-1">/{page.slug}</p>
92
- </ListItemRow>
93
- ))}
94
- </div>
95
- )}
96
- </>
97
- );
98
- }
22
+ // =============================================================================
23
+ // Inline components (small, tightly coupled to route params)
24
+ // =============================================================================
99
25
 
100
26
  function NewPageContent() {
101
27
  const { t } = useLingui();
@@ -174,9 +100,15 @@ function EditPageContent({ page }: { page: Page }) {
174
100
  );
175
101
  }
176
102
 
177
- // List pages
103
+ // =============================================================================
104
+ // Route handlers
105
+ // =============================================================================
106
+
178
107
  pagesRoutes.get("/", async (c) => {
179
- const pages = await c.var.services.pages.list();
108
+ const [navItems, otherPages] = await Promise.all([
109
+ c.var.services.navItems.list(),
110
+ c.var.services.pages.listNotInNav(),
111
+ ]);
180
112
  const siteName = await getSiteName(c);
181
113
 
182
114
  return c.html(
@@ -186,15 +118,13 @@ pagesRoutes.get("/", async (c) => {
186
118
  siteName={siteName}
187
119
  currentPath="/dash/pages"
188
120
  >
189
- <PagesListContent pages={pages} />
121
+ <UnifiedPagesContent navItems={navItems} otherPages={otherPages} />
190
122
  </DashLayout>,
191
123
  );
192
124
  });
193
125
 
194
- // New page form
195
126
  pagesRoutes.get("/new", async (c) => {
196
127
  const siteName = await getSiteName(c);
197
-
198
128
  return c.html(
199
129
  <DashLayout
200
130
  c={c}
@@ -207,7 +137,89 @@ pagesRoutes.get("/new", async (c) => {
207
137
  );
208
138
  });
209
139
 
210
- // Create page
140
+ pagesRoutes.get("/links/new", async (c) => {
141
+ const siteName = await getSiteName(c);
142
+ return c.html(
143
+ <DashLayout
144
+ c={c}
145
+ title="New Link"
146
+ siteName={siteName}
147
+ currentPath="/dash/pages"
148
+ >
149
+ <LinkFormContent />
150
+ </DashLayout>,
151
+ );
152
+ });
153
+
154
+ pagesRoutes.post("/links", async (c) => {
155
+ const body = await c.req.json<{ label: string; url: string }>();
156
+ if (!body.label || !body.url) {
157
+ return dsToast("Label and URL are required", "error");
158
+ }
159
+
160
+ await c.var.services.navItems.create({
161
+ type: "link",
162
+ label: body.label,
163
+ url: body.url,
164
+ });
165
+ return dsRedirect("/dash/pages");
166
+ });
167
+
168
+ pagesRoutes.post("/reorder", async (c) => {
169
+ const body = await c.req.json<{ ids: number[] }>();
170
+ if (!Array.isArray(body.ids)) {
171
+ return dsToast("Invalid request", "error");
172
+ }
173
+ await c.var.services.navItems.reorder(body.ids);
174
+ return dsToast("Order saved");
175
+ });
176
+
177
+ pagesRoutes.get("/links/:id/edit", async (c) => {
178
+ const id = parseInt(c.req.param("id"), 10);
179
+ if (isNaN(id)) return c.notFound();
180
+
181
+ const item = await c.var.services.navItems.getById(id);
182
+ if (!item) return c.notFound();
183
+
184
+ const siteName = await getSiteName(c);
185
+ return c.html(
186
+ <DashLayout
187
+ c={c}
188
+ title="Edit Link"
189
+ siteName={siteName}
190
+ currentPath="/dash/pages"
191
+ >
192
+ <LinkFormContent item={item} isEdit />
193
+ </DashLayout>,
194
+ );
195
+ });
196
+
197
+ pagesRoutes.post("/links/:id", async (c) => {
198
+ const id = parseInt(c.req.param("id"), 10);
199
+ if (isNaN(id)) return c.notFound();
200
+
201
+ const body = await c.req.json<{ label: string; url: string }>();
202
+ if (!body.label || !body.url) {
203
+ return dsToast("Label and URL are required", "error");
204
+ }
205
+
206
+ const updated = await c.var.services.navItems.update(id, {
207
+ label: body.label,
208
+ url: body.url,
209
+ });
210
+ if (!updated) return c.notFound();
211
+
212
+ return dsRedirect("/dash/pages");
213
+ });
214
+
215
+ pagesRoutes.post("/links/:id/delete", async (c) => {
216
+ const id = parseInt(c.req.param("id"), 10);
217
+ if (!isNaN(id)) {
218
+ await c.var.services.navItems.delete(id);
219
+ }
220
+ return dsRedirect("/dash/pages");
221
+ });
222
+
211
223
  pagesRoutes.post("/", async (c) => {
212
224
  const body = await c.req.json<{
213
225
  title: string;
@@ -226,7 +238,34 @@ pagesRoutes.post("/", async (c) => {
226
238
  return dsRedirect(`/dash/pages/${page.id}`);
227
239
  });
228
240
 
229
- // View single page
241
+ pagesRoutes.post("/:id/add-to-nav", async (c) => {
242
+ const id = parseInt(c.req.param("id"), 10);
243
+ if (isNaN(id)) return c.notFound();
244
+
245
+ const page = await c.var.services.pages.getById(id);
246
+ if (!page) return c.notFound();
247
+
248
+ await c.var.services.navItems.create({
249
+ type: "page",
250
+ label: page.title || page.slug,
251
+ url: `/${page.slug}`,
252
+ pageId: page.id,
253
+ });
254
+ return dsRedirect("/dash/pages");
255
+ });
256
+
257
+ pagesRoutes.post("/:id/remove-from-nav", async (c) => {
258
+ const pageId = parseInt(c.req.param("id"), 10);
259
+ if (isNaN(pageId)) return c.notFound();
260
+
261
+ const navItems = await c.var.services.navItems.list();
262
+ const navItem = navItems.find((item) => item.pageId === pageId);
263
+ if (navItem) {
264
+ await c.var.services.navItems.delete(navItem.id);
265
+ }
266
+ return dsRedirect("/dash/pages");
267
+ });
268
+
230
269
  pagesRoutes.get("/:id", async (c) => {
231
270
  const id = parseInt(c.req.param("id"), 10);
232
271
  if (isNaN(id)) return c.notFound();
@@ -235,7 +274,6 @@ pagesRoutes.get("/:id", async (c) => {
235
274
  if (!page) return c.notFound();
236
275
 
237
276
  const siteName = await getSiteName(c);
238
-
239
277
  return c.html(
240
278
  <DashLayout
241
279
  c={c}
@@ -248,7 +286,6 @@ pagesRoutes.get("/:id", async (c) => {
248
286
  );
249
287
  });
250
288
 
251
- // Edit page form
252
289
  pagesRoutes.get("/:id/edit", async (c) => {
253
290
  const id = parseInt(c.req.param("id"), 10);
254
291
  if (isNaN(id)) return c.notFound();
@@ -257,7 +294,6 @@ pagesRoutes.get("/:id/edit", async (c) => {
257
294
  if (!page) return c.notFound();
258
295
 
259
296
  const siteName = await getSiteName(c);
260
-
261
297
  return c.html(
262
298
  <DashLayout
263
299
  c={c}
@@ -270,7 +306,6 @@ pagesRoutes.get("/:id/edit", async (c) => {
270
306
  );
271
307
  });
272
308
 
273
- // Update page
274
309
  pagesRoutes.post("/:id", async (c) => {
275
310
  const id = parseInt(c.req.param("id"), 10);
276
311
  if (isNaN(id)) return c.notFound();
@@ -292,12 +327,10 @@ pagesRoutes.post("/:id", async (c) => {
292
327
  return dsRedirect(`/dash/pages/${id}`);
293
328
  });
294
329
 
295
- // Delete page
296
330
  pagesRoutes.post("/:id/delete", async (c) => {
297
331
  const id = parseInt(c.req.param("id"), 10);
298
332
  if (isNaN(id)) return c.notFound();
299
333
 
300
334
  await c.var.services.pages.delete(id);
301
-
302
335
  return dsRedirect("/dash/pages");
303
336
  });
@@ -7,13 +7,13 @@ import { Hono } from "hono";
7
7
  import { useLingui } from "@lingui/react/macro";
8
8
  import type { Bindings, Post, Media, Collection } 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
  PostForm,
13
13
  PostList,
14
14
  CrudPageHeader,
15
15
  ActionButtons,
16
- } from "../../theme/components/index.js";
16
+ } from "../../ui/dash/index.js";
17
17
  import * as sqid from "../../lib/sqid.js";
18
18
  import { dsRedirect } from "../../lib/sse.js";
19
19
 
@@ -95,7 +95,6 @@ postsRoutes.post("/", async (c) => {
95
95
  status: string;
96
96
  featured?: boolean;
97
97
  pinned?: boolean;
98
- slug?: string;
99
98
  url?: string;
100
99
  quoteText?: string;
101
100
  rating?: number;
@@ -110,7 +109,6 @@ postsRoutes.post("/", async (c) => {
110
109
  status: body.status as Post["status"],
111
110
  featured: body.featured,
112
111
  pinned: body.pinned,
113
- slug: body.slug || undefined,
114
112
  url: body.url || undefined,
115
113
  quoteText: body.quoteText || undefined,
116
114
  rating: body.rating || undefined,
@@ -131,7 +129,7 @@ function ViewPostContent({ post }: { post: Post }) {
131
129
  message: "Post",
132
130
  comment: "@context: Default post title",
133
131
  });
134
- const permalink = post.slug ? `/${post.slug}` : `/p/${sqid.encode(post.id)}`;
132
+ const permalink = post.path ? `/${post.path}` : `/p/${sqid.encode(post.id)}`;
135
133
 
136
134
  return (
137
135
  <>
@@ -266,7 +264,6 @@ postsRoutes.post("/:id", async (c) => {
266
264
  status: string;
267
265
  featured?: boolean;
268
266
  pinned?: boolean;
269
- slug?: string;
270
267
  url?: string;
271
268
  quoteText?: string;
272
269
  rating?: number;
@@ -281,7 +278,6 @@ postsRoutes.post("/:id", async (c) => {
281
278
  status: body.status as Post["status"],
282
279
  featured: body.featured,
283
280
  pinned: body.pinned,
284
- slug: body.slug || null,
285
281
  url: body.url || null,
286
282
  quoteText: body.quoteText || null,
287
283
  rating: body.rating || null,
@@ -7,13 +7,13 @@ import { Hono } from "hono";
7
7
  import { useLingui } from "@lingui/react/macro";
8
8
  import type { Bindings, Redirect } 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
- } from "../../theme/components/index.js";
16
+ } from "../../ui/dash/index.js";
17
17
  import { dsRedirect } from "../../lib/sse.js";
18
18
 
19
19
  type Env = { Bindings: Bindings; Variables: AppVariables };
@@ -158,20 +158,26 @@ function NewRedirectContent() {
158
158
  </div>
159
159
 
160
160
  <div class="flex gap-2">
161
- <button type="submit" class="btn" data-attr-disabled="$_loading">
162
- <span data-show="!$_loading">
163
- {t({
164
- message: "Create Redirect",
165
- comment: "@context: Button to save new redirect",
166
- })}
167
- </span>
168
- <span data-show="$_loading">
169
- {t({
170
- message: "Processing...",
171
- comment:
172
- "@context: Loading text shown on submit button while request is in progress",
173
- })}
174
- </span>
161
+ <button type="submit" class="btn" data-attr:disabled="$_loading">
162
+ <svg
163
+ data-show="$_loading"
164
+ style="display:none"
165
+ class="animate-spin size-4"
166
+ xmlns="http://www.w3.org/2000/svg"
167
+ viewBox="0 0 24 24"
168
+ fill="none"
169
+ stroke="currentColor"
170
+ stroke-width="2"
171
+ stroke-linecap="round"
172
+ stroke-linejoin="round"
173
+ role="status"
174
+ >
175
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
176
+ </svg>
177
+ {t({
178
+ message: "Create Redirect",
179
+ comment: "@context: Button to save new redirect",
180
+ })}
175
181
  </button>
176
182
  <a href="/dash/redirects" class="btn-outline">
177
183
  {t({