@jant/core 0.3.7 → 0.3.8

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 (241) hide show
  1. package/dist/app.js +4 -0
  2. package/dist/client.js +1 -0
  3. package/dist/db/schema.js +13 -0
  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/lib/image.js +3 -3
  8. package/dist/lib/media-helpers.js +43 -0
  9. package/dist/lib/nav-reorder.js +27 -0
  10. package/dist/lib/navigation.js +35 -0
  11. package/dist/lib/theme-components.js +49 -0
  12. package/dist/routes/api/timeline.js +115 -0
  13. package/dist/routes/api/upload.js +9 -5
  14. package/dist/routes/dash/navigation.js +274 -0
  15. package/dist/routes/pages/archive.js +14 -27
  16. package/dist/routes/pages/collection.js +10 -19
  17. package/dist/routes/pages/home.js +83 -126
  18. package/dist/routes/pages/page.js +19 -38
  19. package/dist/routes/pages/post.js +38 -51
  20. package/dist/routes/pages/search.js +13 -26
  21. package/dist/services/index.js +3 -1
  22. package/dist/services/media.js +1 -1
  23. package/dist/services/navigation.js +115 -0
  24. package/dist/services/post.js +26 -1
  25. package/dist/theme/components/PostList.js +5 -0
  26. package/dist/theme/components/index.js +2 -0
  27. package/dist/theme/components/timeline/ArticleCard.js +50 -0
  28. package/dist/theme/components/timeline/ImageCard.js +86 -0
  29. package/dist/theme/components/timeline/LinkCard.js +62 -0
  30. package/dist/theme/components/timeline/NoteCard.js +37 -0
  31. package/dist/theme/components/timeline/QuoteCard.js +51 -0
  32. package/dist/theme/components/timeline/ThreadPreview.js +52 -0
  33. package/dist/theme/components/timeline/TimelineFeed.js +43 -0
  34. package/dist/theme/components/timeline/TimelineItem.js +25 -0
  35. package/dist/theme/components/timeline/index.js +8 -0
  36. package/dist/theme/layouts/DashLayout.js +8 -0
  37. package/dist/theme/layouts/SiteLayout.js +160 -0
  38. package/dist/theme/layouts/index.js +1 -0
  39. package/dist/types/sortablejs.d.js +5 -0
  40. package/package.json +3 -2
  41. package/src/__tests__/helpers/db.ts +10 -0
  42. package/src/app.tsx +4 -0
  43. package/src/client.ts +1 -0
  44. package/src/db/migrations/0003_add_navigation_links.sql +8 -0
  45. package/src/db/migrations/meta/0003_snapshot.json +821 -0
  46. package/src/db/migrations/meta/_journal.json +14 -0
  47. package/src/db/schema.ts +13 -0
  48. package/src/i18n/locales/en.po +100 -32
  49. package/src/i18n/locales/en.ts +1 -1
  50. package/src/i18n/locales/zh-Hans.po +102 -55
  51. package/src/i18n/locales/zh-Hans.ts +1 -1
  52. package/src/i18n/locales/zh-Hant.po +102 -55
  53. package/src/i18n/locales/zh-Hant.ts +1 -1
  54. package/src/index.ts +5 -0
  55. package/src/lib/__tests__/theme-components.test.ts +107 -0
  56. package/src/lib/image.ts +3 -3
  57. package/src/lib/media-helpers.ts +54 -0
  58. package/src/lib/nav-reorder.ts +26 -0
  59. package/src/lib/navigation.ts +46 -0
  60. package/src/lib/theme-components.ts +76 -0
  61. package/src/routes/api/__tests__/posts.test.ts +8 -8
  62. package/src/routes/api/__tests__/timeline.test.ts +242 -0
  63. package/src/routes/api/timeline.tsx +145 -0
  64. package/src/routes/api/upload.ts +9 -5
  65. package/src/routes/dash/navigation.tsx +306 -0
  66. package/src/routes/pages/archive.tsx +15 -23
  67. package/src/routes/pages/collection.tsx +8 -15
  68. package/src/routes/pages/home.tsx +111 -122
  69. package/src/routes/pages/page.tsx +17 -30
  70. package/src/routes/pages/post.tsx +33 -42
  71. package/src/routes/pages/search.tsx +18 -22
  72. package/src/services/__tests__/media.test.ts +34 -7
  73. package/src/services/__tests__/navigation.test.ts +213 -0
  74. package/src/services/__tests__/post-timeline.test.ts +220 -0
  75. package/src/services/index.ts +7 -0
  76. package/src/services/media.ts +2 -1
  77. package/src/services/navigation.ts +165 -0
  78. package/src/services/post.ts +48 -1
  79. package/src/styles/components.css +59 -0
  80. package/src/theme/components/PostList.tsx +7 -0
  81. package/src/theme/components/index.ts +12 -0
  82. package/src/theme/components/timeline/ArticleCard.tsx +57 -0
  83. package/src/theme/components/timeline/ImageCard.tsx +80 -0
  84. package/src/theme/components/timeline/LinkCard.tsx +66 -0
  85. package/src/theme/components/timeline/NoteCard.tsx +41 -0
  86. package/src/theme/components/timeline/QuoteCard.tsx +55 -0
  87. package/src/theme/components/timeline/ThreadPreview.tsx +49 -0
  88. package/src/theme/components/timeline/TimelineFeed.tsx +52 -0
  89. package/src/theme/components/timeline/TimelineItem.tsx +39 -0
  90. package/src/theme/components/timeline/index.ts +8 -0
  91. package/src/theme/layouts/DashLayout.tsx +10 -0
  92. package/src/theme/layouts/SiteLayout.tsx +184 -0
  93. package/src/theme/layouts/index.ts +1 -0
  94. package/src/types/sortablejs.d.ts +23 -0
  95. package/src/types.ts +61 -0
  96. package/dist/app.d.ts +0 -38
  97. package/dist/app.d.ts.map +0 -1
  98. package/dist/auth.d.ts +0 -25
  99. package/dist/auth.d.ts.map +0 -1
  100. package/dist/db/index.d.ts +0 -10
  101. package/dist/db/index.d.ts.map +0 -1
  102. package/dist/db/schema.d.ts +0 -1543
  103. package/dist/db/schema.d.ts.map +0 -1
  104. package/dist/i18n/Trans.d.ts +0 -25
  105. package/dist/i18n/Trans.d.ts.map +0 -1
  106. package/dist/i18n/context.d.ts +0 -69
  107. package/dist/i18n/context.d.ts.map +0 -1
  108. package/dist/i18n/detect.d.ts +0 -20
  109. package/dist/i18n/detect.d.ts.map +0 -1
  110. package/dist/i18n/i18n.d.ts +0 -32
  111. package/dist/i18n/i18n.d.ts.map +0 -1
  112. package/dist/i18n/index.d.ts +0 -41
  113. package/dist/i18n/index.d.ts.map +0 -1
  114. package/dist/i18n/locales/en.d.ts +0 -3
  115. package/dist/i18n/locales/en.d.ts.map +0 -1
  116. package/dist/i18n/locales/zh-Hans.d.ts +0 -3
  117. package/dist/i18n/locales/zh-Hans.d.ts.map +0 -1
  118. package/dist/i18n/locales/zh-Hant.d.ts +0 -3
  119. package/dist/i18n/locales/zh-Hant.d.ts.map +0 -1
  120. package/dist/i18n/locales.d.ts +0 -11
  121. package/dist/i18n/locales.d.ts.map +0 -1
  122. package/dist/i18n/middleware.d.ts +0 -21
  123. package/dist/i18n/middleware.d.ts.map +0 -1
  124. package/dist/index.d.ts +0 -16
  125. package/dist/index.d.ts.map +0 -1
  126. package/dist/lib/config.d.ts +0 -83
  127. package/dist/lib/config.d.ts.map +0 -1
  128. package/dist/lib/constants.d.ts +0 -37
  129. package/dist/lib/constants.d.ts.map +0 -1
  130. package/dist/lib/image.d.ts +0 -73
  131. package/dist/lib/image.d.ts.map +0 -1
  132. package/dist/lib/index.d.ts +0 -9
  133. package/dist/lib/index.d.ts.map +0 -1
  134. package/dist/lib/markdown.d.ts +0 -60
  135. package/dist/lib/markdown.d.ts.map +0 -1
  136. package/dist/lib/schemas.d.ts +0 -130
  137. package/dist/lib/schemas.d.ts.map +0 -1
  138. package/dist/lib/sqid.d.ts +0 -60
  139. package/dist/lib/sqid.d.ts.map +0 -1
  140. package/dist/lib/sse.d.ts +0 -192
  141. package/dist/lib/sse.d.ts.map +0 -1
  142. package/dist/lib/theme.d.ts +0 -44
  143. package/dist/lib/theme.d.ts.map +0 -1
  144. package/dist/lib/time.d.ts +0 -90
  145. package/dist/lib/time.d.ts.map +0 -1
  146. package/dist/lib/url.d.ts +0 -82
  147. package/dist/lib/url.d.ts.map +0 -1
  148. package/dist/middleware/auth.d.ts +0 -24
  149. package/dist/middleware/auth.d.ts.map +0 -1
  150. package/dist/middleware/onboarding.d.ts +0 -26
  151. package/dist/middleware/onboarding.d.ts.map +0 -1
  152. package/dist/routes/api/posts.d.ts +0 -13
  153. package/dist/routes/api/posts.d.ts.map +0 -1
  154. package/dist/routes/api/search.d.ts +0 -13
  155. package/dist/routes/api/search.d.ts.map +0 -1
  156. package/dist/routes/api/upload.d.ts +0 -16
  157. package/dist/routes/api/upload.d.ts.map +0 -1
  158. package/dist/routes/dash/collections.d.ts +0 -13
  159. package/dist/routes/dash/collections.d.ts.map +0 -1
  160. package/dist/routes/dash/index.d.ts +0 -15
  161. package/dist/routes/dash/index.d.ts.map +0 -1
  162. package/dist/routes/dash/media.d.ts +0 -16
  163. package/dist/routes/dash/media.d.ts.map +0 -1
  164. package/dist/routes/dash/pages.d.ts +0 -15
  165. package/dist/routes/dash/pages.d.ts.map +0 -1
  166. package/dist/routes/dash/posts.d.ts +0 -13
  167. package/dist/routes/dash/posts.d.ts.map +0 -1
  168. package/dist/routes/dash/redirects.d.ts +0 -13
  169. package/dist/routes/dash/redirects.d.ts.map +0 -1
  170. package/dist/routes/dash/settings.d.ts +0 -15
  171. package/dist/routes/dash/settings.d.ts.map +0 -1
  172. package/dist/routes/feed/rss.d.ts +0 -13
  173. package/dist/routes/feed/rss.d.ts.map +0 -1
  174. package/dist/routes/feed/sitemap.d.ts +0 -13
  175. package/dist/routes/feed/sitemap.d.ts.map +0 -1
  176. package/dist/routes/pages/archive.d.ts +0 -15
  177. package/dist/routes/pages/archive.d.ts.map +0 -1
  178. package/dist/routes/pages/collection.d.ts +0 -13
  179. package/dist/routes/pages/collection.d.ts.map +0 -1
  180. package/dist/routes/pages/home.d.ts +0 -13
  181. package/dist/routes/pages/home.d.ts.map +0 -1
  182. package/dist/routes/pages/page.d.ts +0 -15
  183. package/dist/routes/pages/page.d.ts.map +0 -1
  184. package/dist/routes/pages/post.d.ts +0 -13
  185. package/dist/routes/pages/post.d.ts.map +0 -1
  186. package/dist/routes/pages/search.d.ts +0 -13
  187. package/dist/routes/pages/search.d.ts.map +0 -1
  188. package/dist/services/collection.d.ts +0 -32
  189. package/dist/services/collection.d.ts.map +0 -1
  190. package/dist/services/index.d.ts +0 -28
  191. package/dist/services/index.d.ts.map +0 -1
  192. package/dist/services/media.d.ts +0 -34
  193. package/dist/services/media.d.ts.map +0 -1
  194. package/dist/services/post.d.ts +0 -31
  195. package/dist/services/post.d.ts.map +0 -1
  196. package/dist/services/redirect.d.ts +0 -15
  197. package/dist/services/redirect.d.ts.map +0 -1
  198. package/dist/services/search.d.ts +0 -26
  199. package/dist/services/search.d.ts.map +0 -1
  200. package/dist/services/settings.d.ts +0 -18
  201. package/dist/services/settings.d.ts.map +0 -1
  202. package/dist/theme/color-themes.d.ts +0 -30
  203. package/dist/theme/color-themes.d.ts.map +0 -1
  204. package/dist/theme/components/ActionButtons.d.ts +0 -43
  205. package/dist/theme/components/ActionButtons.d.ts.map +0 -1
  206. package/dist/theme/components/CrudPageHeader.d.ts +0 -23
  207. package/dist/theme/components/CrudPageHeader.d.ts.map +0 -1
  208. package/dist/theme/components/DangerZone.d.ts +0 -36
  209. package/dist/theme/components/DangerZone.d.ts.map +0 -1
  210. package/dist/theme/components/EmptyState.d.ts +0 -27
  211. package/dist/theme/components/EmptyState.d.ts.map +0 -1
  212. package/dist/theme/components/ListItemRow.d.ts +0 -15
  213. package/dist/theme/components/ListItemRow.d.ts.map +0 -1
  214. package/dist/theme/components/MediaGallery.d.ts +0 -13
  215. package/dist/theme/components/MediaGallery.d.ts.map +0 -1
  216. package/dist/theme/components/PageForm.d.ts +0 -14
  217. package/dist/theme/components/PageForm.d.ts.map +0 -1
  218. package/dist/theme/components/Pagination.d.ts +0 -46
  219. package/dist/theme/components/Pagination.d.ts.map +0 -1
  220. package/dist/theme/components/PostForm.d.ts +0 -16
  221. package/dist/theme/components/PostForm.d.ts.map +0 -1
  222. package/dist/theme/components/PostList.d.ts +0 -10
  223. package/dist/theme/components/PostList.d.ts.map +0 -1
  224. package/dist/theme/components/ThreadView.d.ts +0 -15
  225. package/dist/theme/components/ThreadView.d.ts.map +0 -1
  226. package/dist/theme/components/TypeBadge.d.ts +0 -12
  227. package/dist/theme/components/TypeBadge.d.ts.map +0 -1
  228. package/dist/theme/components/VisibilityBadge.d.ts +0 -12
  229. package/dist/theme/components/VisibilityBadge.d.ts.map +0 -1
  230. package/dist/theme/components/index.d.ts +0 -14
  231. package/dist/theme/components/index.d.ts.map +0 -1
  232. package/dist/theme/index.d.ts +0 -21
  233. package/dist/theme/index.d.ts.map +0 -1
  234. package/dist/theme/layouts/BaseLayout.d.ts +0 -23
  235. package/dist/theme/layouts/BaseLayout.d.ts.map +0 -1
  236. package/dist/theme/layouts/DashLayout.d.ts +0 -17
  237. package/dist/theme/layouts/DashLayout.d.ts.map +0 -1
  238. package/dist/theme/layouts/index.d.ts +0 -3
  239. package/dist/theme/layouts/index.d.ts.map +0 -1
  240. package/dist/types.d.ts +0 -237
  241. package/dist/types.d.ts.map +0 -1
@@ -0,0 +1,274 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
2
+ import { getSiteName } from "../../lib/config.js";
3
+ /**
4
+ * Dashboard Navigation Links Routes
5
+ */ import { Hono } from "hono";
6
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
7
+ import { DashLayout } from "../../theme/layouts/index.js";
8
+ import { EmptyState, ListItemRow, ActionButtons, CrudPageHeader } from "../../theme/components/index.js";
9
+ import { dsRedirect, dsToast } from "../../lib/sse.js";
10
+ export const navigationRoutes = new Hono();
11
+ function NavigationListContent({ links }) {
12
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
13
+ return /*#__PURE__*/ _jsxs(_Fragment, {
14
+ children: [
15
+ /*#__PURE__*/ _jsx(CrudPageHeader, {
16
+ title: $__i18n._({
17
+ id: "UxKoFf",
18
+ message: "Navigation"
19
+ }),
20
+ ctaLabel: $__i18n._({
21
+ id: "aaGV/9",
22
+ message: "New Link"
23
+ }),
24
+ ctaHref: "/dash/navigation/new"
25
+ }),
26
+ links.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
27
+ message: $__i18n._({
28
+ id: "wdGjkd",
29
+ message: "No navigation links configured."
30
+ }),
31
+ ctaText: $__i18n._({
32
+ id: "aaGV/9",
33
+ message: "New Link"
34
+ }),
35
+ ctaHref: "/dash/navigation/new"
36
+ }) : /*#__PURE__*/ _jsx(_Fragment, {
37
+ children: /*#__PURE__*/ _jsx("div", {
38
+ id: "nav-links-list",
39
+ class: "flex flex-col divide-y",
40
+ children: links.map((link)=>/*#__PURE__*/ _jsx(ListItemRow, {
41
+ actions: /*#__PURE__*/ _jsx(ActionButtons, {
42
+ editHref: `/dash/navigation/${link.id}/edit`,
43
+ editLabel: $__i18n._({
44
+ id: "ePK91l",
45
+ message: "Edit"
46
+ }),
47
+ deleteAction: `/dash/navigation/${link.id}/delete`,
48
+ deleteLabel: $__i18n._({
49
+ id: "cnGeoo",
50
+ message: "Delete"
51
+ })
52
+ }),
53
+ children: /*#__PURE__*/ _jsxs("div", {
54
+ class: "flex items-center gap-3 cursor-grab",
55
+ "data-id": link.id,
56
+ children: [
57
+ /*#__PURE__*/ _jsx("span", {
58
+ class: "text-muted-foreground select-none",
59
+ children: "⠿"
60
+ }),
61
+ /*#__PURE__*/ _jsxs("div", {
62
+ class: "flex items-center gap-2",
63
+ children: [
64
+ /*#__PURE__*/ _jsx("span", {
65
+ class: "font-medium",
66
+ children: link.label
67
+ }),
68
+ /*#__PURE__*/ _jsx("code", {
69
+ class: "text-sm text-muted-foreground bg-muted px-1 rounded",
70
+ children: link.url
71
+ })
72
+ ]
73
+ })
74
+ ]
75
+ })
76
+ }, link.id))
77
+ })
78
+ })
79
+ ]
80
+ });
81
+ }
82
+ function NavigationFormContent({ link, isEdit }) {
83
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
84
+ const title = isEdit ? $__i18n._({
85
+ id: "gDx5MG",
86
+ message: "Edit Link"
87
+ }) : $__i18n._({
88
+ id: "aaGV/9",
89
+ message: "New Link"
90
+ });
91
+ const signals = JSON.stringify({
92
+ label: link?.label ?? "",
93
+ url: link?.url ?? ""
94
+ }).replace(/</g, "\\u003c");
95
+ const action = isEdit ? `/dash/navigation/${link?.id}` : "/dash/navigation";
96
+ return /*#__PURE__*/ _jsxs(_Fragment, {
97
+ children: [
98
+ /*#__PURE__*/ _jsx("h1", {
99
+ class: "text-2xl font-semibold mb-6",
100
+ children: title
101
+ }),
102
+ /*#__PURE__*/ _jsxs("form", {
103
+ "data-signals": signals,
104
+ "data-on:submit__prevent": `@post('${action}')`,
105
+ class: "flex flex-col gap-4 max-w-lg",
106
+ children: [
107
+ /*#__PURE__*/ _jsxs("div", {
108
+ class: "field",
109
+ children: [
110
+ /*#__PURE__*/ _jsx("label", {
111
+ class: "label",
112
+ children: $__i18n._({
113
+ id: "87a/t/",
114
+ message: "Label"
115
+ })
116
+ }),
117
+ /*#__PURE__*/ _jsx("input", {
118
+ type: "text",
119
+ "data-bind": "label",
120
+ class: "input",
121
+ placeholder: "Home",
122
+ required: true
123
+ }),
124
+ /*#__PURE__*/ _jsx("p", {
125
+ class: "text-xs text-muted-foreground mt-1",
126
+ children: $__i18n._({
127
+ id: "+bHzpy",
128
+ message: "Display text for the link"
129
+ })
130
+ })
131
+ ]
132
+ }),
133
+ /*#__PURE__*/ _jsxs("div", {
134
+ class: "field",
135
+ children: [
136
+ /*#__PURE__*/ _jsx("label", {
137
+ class: "label",
138
+ children: $__i18n._({
139
+ id: "IagCbF",
140
+ message: "URL"
141
+ })
142
+ }),
143
+ /*#__PURE__*/ _jsx("input", {
144
+ type: "text",
145
+ "data-bind": "url",
146
+ class: "input",
147
+ placeholder: "/archive or https://...",
148
+ required: true
149
+ }),
150
+ /*#__PURE__*/ _jsx("p", {
151
+ class: "text-xs text-muted-foreground mt-1",
152
+ children: $__i18n._({
153
+ id: "QEbNBb",
154
+ message: "Path (e.g. /archive) or full URL (e.g. https://example.com)"
155
+ })
156
+ })
157
+ ]
158
+ }),
159
+ /*#__PURE__*/ _jsxs("div", {
160
+ class: "flex gap-2",
161
+ children: [
162
+ /*#__PURE__*/ _jsx("button", {
163
+ type: "submit",
164
+ class: "btn",
165
+ children: isEdit ? $__i18n._({
166
+ id: "IUwGEM",
167
+ message: "Save Changes"
168
+ }) : $__i18n._({
169
+ id: "kd7eBB",
170
+ message: "Create Link"
171
+ })
172
+ }),
173
+ /*#__PURE__*/ _jsx("a", {
174
+ href: "/dash/navigation",
175
+ class: "btn-outline",
176
+ children: $__i18n._({
177
+ id: "dEgA5A",
178
+ message: "Cancel"
179
+ })
180
+ })
181
+ ]
182
+ })
183
+ ]
184
+ })
185
+ ]
186
+ });
187
+ }
188
+ // List navigation links
189
+ navigationRoutes.get("/", async (c)=>{
190
+ const siteName = await getSiteName(c);
191
+ const links = await c.var.services.navigationLinks.list();
192
+ return c.html(/*#__PURE__*/ _jsx(DashLayout, {
193
+ c: c,
194
+ title: "Navigation",
195
+ siteName: siteName,
196
+ currentPath: "/dash/navigation",
197
+ children: /*#__PURE__*/ _jsx(NavigationListContent, {
198
+ links: links
199
+ })
200
+ }));
201
+ });
202
+ // New link form
203
+ navigationRoutes.get("/new", async (c)=>{
204
+ const siteName = await getSiteName(c);
205
+ return c.html(/*#__PURE__*/ _jsx(DashLayout, {
206
+ c: c,
207
+ title: "New Link",
208
+ siteName: siteName,
209
+ currentPath: "/dash/navigation",
210
+ children: /*#__PURE__*/ _jsx(NavigationFormContent, {})
211
+ }));
212
+ });
213
+ // Create link
214
+ navigationRoutes.post("/", async (c)=>{
215
+ const body = await c.req.json();
216
+ if (!body.label || !body.url) {
217
+ return dsToast("Label and URL are required", "error");
218
+ }
219
+ await c.var.services.navigationLinks.create({
220
+ label: body.label,
221
+ url: body.url
222
+ });
223
+ return dsRedirect("/dash/navigation");
224
+ });
225
+ // Reorder links (must be before /:id to avoid "reorder" matching as :id)
226
+ navigationRoutes.post("/reorder", async (c)=>{
227
+ const body = await c.req.json();
228
+ if (!Array.isArray(body.ids)) {
229
+ return dsToast("Invalid request", "error");
230
+ }
231
+ await c.var.services.navigationLinks.reorder(body.ids);
232
+ return dsToast("Order saved");
233
+ });
234
+ // Edit link form
235
+ navigationRoutes.get("/:id/edit", async (c)=>{
236
+ const id = parseInt(c.req.param("id"), 10);
237
+ if (isNaN(id)) return c.notFound();
238
+ const link = await c.var.services.navigationLinks.getById(id);
239
+ if (!link) return c.notFound();
240
+ const siteName = await getSiteName(c);
241
+ return c.html(/*#__PURE__*/ _jsx(DashLayout, {
242
+ c: c,
243
+ title: "Edit Link",
244
+ siteName: siteName,
245
+ currentPath: "/dash/navigation",
246
+ children: /*#__PURE__*/ _jsx(NavigationFormContent, {
247
+ link: link,
248
+ isEdit: true
249
+ })
250
+ }));
251
+ });
252
+ // Update link
253
+ navigationRoutes.post("/:id", async (c)=>{
254
+ const id = parseInt(c.req.param("id"), 10);
255
+ if (isNaN(id)) return c.notFound();
256
+ const body = await c.req.json();
257
+ if (!body.label || !body.url) {
258
+ return dsToast("Label and URL are required", "error");
259
+ }
260
+ const updated = await c.var.services.navigationLinks.update(id, {
261
+ label: body.label,
262
+ url: body.url
263
+ });
264
+ if (!updated) return c.notFound();
265
+ return dsRedirect("/dash/navigation");
266
+ });
267
+ // Delete link
268
+ navigationRoutes.post("/:id/delete", async (c)=>{
269
+ const id = parseInt(c.req.param("id"), 10);
270
+ if (!isNaN(id)) {
271
+ await c.var.services.navigationLinks.delete(id);
272
+ }
273
+ return dsRedirect("/dash/navigation");
274
+ });
@@ -1,16 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
- import { getSiteName } from "../../lib/config.js";
3
2
  /**
4
3
  * Archive Page Route
5
4
  *
6
5
  * Shows all posts, optionally filtered by type
7
6
  */ import { Hono } from "hono";
8
7
  import { useLingui as $_useLingui } from "@jant/core/i18n";
9
- import { BaseLayout } from "../../theme/layouts/index.js";
8
+ import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
10
9
  import { Pagination } from "../../theme/components/index.js";
11
10
  import { POST_TYPES } from "../../types.js";
12
11
  import * as sqid from "../../lib/sqid.js";
13
12
  import * as time from "../../lib/time.js";
13
+ import { getNavigationData } from "../../lib/navigation.js";
14
14
  const PAGE_SIZE = 50;
15
15
  export const archiveRoutes = new Hono();
16
16
  function getTypeLabel(type) {
@@ -89,7 +89,6 @@ function ArchiveContent({ displayPosts, hasMore, nextCursor, type, grouped, repl
89
89
  message: "Archive"
90
90
  });
91
91
  return /*#__PURE__*/ _jsxs("div", {
92
- class: "container py-8",
93
92
  children: [
94
93
  /*#__PURE__*/ _jsxs("header", {
95
94
  class: "mb-8",
@@ -183,21 +182,6 @@ function ArchiveContent({ displayPosts, hasMore, nextCursor, type, grouped, repl
183
182
  baseUrl: type ? `/archive?type=${type}` : "/archive",
184
183
  hasMore: hasMore,
185
184
  nextCursor: nextCursor
186
- }),
187
- /*#__PURE__*/ _jsx("nav", {
188
- class: "mt-4",
189
- children: /*#__PURE__*/ _jsxs("a", {
190
- href: "/",
191
- class: "text-sm hover:underline",
192
- children: [
193
- "←",
194
- " ",
195
- $__i18n._({
196
- id: "x4RuFo",
197
- message: "Back to home"
198
- })
199
- ]
200
- })
201
185
  })
202
186
  ]
203
187
  });
@@ -209,7 +193,7 @@ archiveRoutes.get("/", async (c)=>{
209
193
  // Parse cursor
210
194
  const cursorParam = c.req.query("cursor");
211
195
  const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
212
- const siteName = await getSiteName(c);
196
+ const navData = await getNavigationData(c);
213
197
  // Fetch one extra to check for more
214
198
  const posts = await c.var.services.posts.list({
215
199
  type,
@@ -240,15 +224,18 @@ archiveRoutes.get("/", async (c)=>{
240
224
  grouped.get(key).push(post);
241
225
  }
242
226
  return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
243
- title: `Archive - ${siteName}`,
227
+ title: `Archive - ${navData.siteName}`,
244
228
  c: c,
245
- children: /*#__PURE__*/ _jsx(ArchiveContent, {
246
- displayPosts: displayPosts,
247
- hasMore: hasMore,
248
- nextCursor: nextCursor,
249
- type: type,
250
- grouped: grouped,
251
- replyCounts: replyCounts
229
+ children: /*#__PURE__*/ _jsx(SiteLayout, {
230
+ ...navData,
231
+ children: /*#__PURE__*/ _jsx(ArchiveContent, {
232
+ displayPosts: displayPosts,
233
+ hasMore: hasMore,
234
+ nextCursor: nextCursor,
235
+ type: type,
236
+ grouped: grouped,
237
+ replyCounts: replyCounts
238
+ })
252
239
  })
253
240
  }));
254
241
  });
@@ -1,17 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
- import { getSiteName } from "../../lib/config.js";
3
2
  /**
4
3
  * Collection Page Route
5
4
  */ import { Hono } from "hono";
6
5
  import { useLingui as $_useLingui } from "@jant/core/i18n";
7
- import { BaseLayout } from "../../theme/layouts/index.js";
6
+ import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
8
7
  import * as sqid from "../../lib/sqid.js";
9
8
  import * as time from "../../lib/time.js";
9
+ import { getNavigationData } from "../../lib/navigation.js";
10
10
  export const collectionRoutes = new Hono();
11
11
  function CollectionContent({ collection, posts }) {
12
12
  const { i18n: $__i18n, _: $__ } = $_useLingui();
13
13
  return /*#__PURE__*/ _jsxs("div", {
14
- class: "container py-8",
15
14
  children: [
16
15
  /*#__PURE__*/ _jsxs("header", {
17
16
  class: "mb-8",
@@ -61,17 +60,6 @@ function CollectionContent({ collection, posts }) {
61
60
  })
62
61
  ]
63
62
  }, post.id))
64
- }),
65
- /*#__PURE__*/ _jsx("nav", {
66
- class: "mt-8",
67
- children: /*#__PURE__*/ _jsx("a", {
68
- href: "/",
69
- class: "text-sm hover:underline",
70
- children: $__i18n._({
71
- id: "biOepV",
72
- message: "← Back to home"
73
- })
74
- })
75
63
  })
76
64
  ]
77
65
  });
@@ -81,14 +69,17 @@ collectionRoutes.get("/:path", async (c)=>{
81
69
  const collection = await c.var.services.collections.getByPath(path);
82
70
  if (!collection) return c.notFound();
83
71
  const posts = await c.var.services.collections.getPosts(collection.id);
84
- const siteName = await getSiteName(c);
72
+ const navData = await getNavigationData(c);
85
73
  return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
86
- title: `${collection.title} - ${siteName}`,
74
+ title: `${collection.title} - ${navData.siteName}`,
87
75
  description: collection.description ?? undefined,
88
76
  c: c,
89
- children: /*#__PURE__*/ _jsx(CollectionContent, {
90
- collection: collection,
91
- posts: posts
77
+ children: /*#__PURE__*/ _jsx(SiteLayout, {
78
+ ...navData,
79
+ children: /*#__PURE__*/ _jsx(CollectionContent, {
80
+ collection: collection,
81
+ posts: posts
82
+ })
92
83
  })
93
84
  }));
94
85
  });
@@ -1,152 +1,109 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
2
2
  /**
3
3
  * Home Page Route
4
+ *
5
+ * Timeline feed with per-type card components and thread previews.
4
6
  */ import { Hono } from "hono";
5
7
  import { useLingui as $_useLingui } from "@jant/core/i18n";
6
- import { BaseLayout } from "../../theme/layouts/index.js";
7
- import { MediaGallery } from "../../theme/components/index.js";
8
- import * as sqid from "../../lib/sqid.js";
9
- import * as time from "../../lib/time.js";
10
- import { getSiteName } from "../../lib/config.js";
11
- import { getMediaUrl, getImageUrl } from "../../lib/image.js";
8
+ import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
9
+ import { buildMediaMap } from "../../lib/media-helpers.js";
10
+ import { resolveTimelineFeed } from "../../lib/theme-components.js";
11
+ import { TimelineFeed as DefaultTimelineFeed } from "../../theme/components/timeline/TimelineFeed.js";
12
+ import { getNavigationData } from "../../lib/navigation.js";
13
+ const PAGE_SIZE = 20;
12
14
  export const homeRoutes = new Hono();
13
- function HomeContent({ siteName, posts, mediaMap }) {
15
+ function HomeContent({ FeedComponent, feedProps }) {
14
16
  const { i18n: $__i18n, _: $__ } = $_useLingui();
15
- return /*#__PURE__*/ _jsxs("div", {
16
- class: "container py-8",
17
- children: [
18
- /*#__PURE__*/ _jsxs("header", {
19
- class: "mb-8 flex items-center justify-between",
20
- children: [
21
- /*#__PURE__*/ _jsx("h1", {
22
- class: "text-2xl font-semibold",
23
- children: siteName
24
- }),
25
- /*#__PURE__*/ _jsxs("nav", {
26
- class: "flex items-center gap-4 text-sm",
27
- children: [
28
- /*#__PURE__*/ _jsx("a", {
29
- href: "/archive",
30
- class: "text-muted-foreground hover:text-foreground",
31
- children: $__i18n._({
32
- id: "B495Gs",
33
- message: "Archive"
34
- })
35
- }),
36
- /*#__PURE__*/ _jsx("a", {
37
- href: "/feed",
38
- class: "text-muted-foreground hover:text-foreground",
39
- children: "RSS"
40
- })
41
- ]
42
- })
43
- ]
44
- }),
45
- /*#__PURE__*/ _jsx("main", {
46
- class: "flex flex-col gap-6",
47
- children: posts.length === 0 ? /*#__PURE__*/ _jsx("p", {
48
- class: "text-muted-foreground",
49
- children: $__i18n._({
50
- id: "ODiSoW",
51
- message: "No posts yet."
52
- })
53
- }) : posts.map((post)=>{
54
- const attachments = mediaMap.get(post.id) ?? [];
55
- return /*#__PURE__*/ _jsxs("article", {
56
- class: "h-entry",
57
- children: [
58
- post.title && /*#__PURE__*/ _jsx("h2", {
59
- class: "p-name text-lg font-medium mb-2",
60
- children: /*#__PURE__*/ _jsx("a", {
61
- href: `/p/${sqid.encode(post.id)}`,
62
- class: "u-url hover:underline",
63
- children: post.title
64
- })
65
- }),
66
- /*#__PURE__*/ _jsx("div", {
67
- class: "e-content prose prose-sm",
68
- dangerouslySetInnerHTML: {
69
- __html: post.contentHtml || ""
70
- }
71
- }),
72
- attachments.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
73
- attachments: attachments
74
- }),
75
- /*#__PURE__*/ _jsxs("footer", {
76
- class: "mt-2 text-sm text-muted-foreground",
77
- children: [
78
- /*#__PURE__*/ _jsx("time", {
79
- class: "dt-published",
80
- datetime: time.toISOString(post.publishedAt),
81
- children: time.formatDate(post.publishedAt)
82
- }),
83
- post.visibility === "featured" && /*#__PURE__*/ _jsx("span", {
84
- class: "ml-2 text-xs",
85
- children: $__i18n._({
86
- id: "FkMol5",
87
- message: "Featured"
88
- })
89
- })
90
- ]
91
- })
92
- ]
93
- }, post.id);
94
- })
95
- }),
96
- posts.length >= 20 && /*#__PURE__*/ _jsx("nav", {
97
- class: "mt-8 text-center",
98
- children: /*#__PURE__*/ _jsx("a", {
99
- href: "/archive",
100
- class: "text-sm text-muted-foreground hover:text-foreground",
101
- children: $__i18n._({
102
- id: "mTOYla",
103
- message: "View all posts →"
104
- })
105
- })
17
+ return /*#__PURE__*/ _jsx(_Fragment, {
18
+ children: feedProps.items.length === 0 ? /*#__PURE__*/ _jsx("p", {
19
+ class: "text-muted-foreground",
20
+ children: $__i18n._({
21
+ id: "ODiSoW",
22
+ message: "No posts yet."
106
23
  })
107
- ]
24
+ }) : /*#__PURE__*/ _jsx(FeedComponent, {
25
+ ...feedProps
26
+ })
108
27
  });
109
28
  }
110
29
  homeRoutes.get("/", async (c)=>{
111
- const siteName = await getSiteName(c);
30
+ const navData = await getNavigationData(c);
31
+ // Fetch one extra to determine if there are more
112
32
  const posts = await c.var.services.posts.list({
113
33
  visibility: [
114
34
  "featured",
115
35
  "quiet"
116
36
  ],
117
- limit: 20
37
+ excludeReplies: true,
38
+ excludeTypes: [
39
+ "page"
40
+ ],
41
+ limit: PAGE_SIZE + 1
118
42
  });
43
+ const hasMore = posts.length > PAGE_SIZE;
44
+ const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
119
45
  // Batch load media attachments
120
- const postIds = posts.map((p)=>p.id);
46
+ const postIds = displayPosts.map((p)=>p.id);
121
47
  const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
122
48
  const r2PublicUrl = c.env.R2_PUBLIC_URL;
123
49
  const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
124
- const mediaMap = new Map();
125
- for (const [postId, mediaList] of rawMediaMap){
126
- mediaMap.set(postId, mediaList.map((m)=>({
127
- id: m.id,
128
- url: getMediaUrl(m.id, m.r2Key, r2PublicUrl),
129
- previewUrl: getImageUrl(getMediaUrl(m.id, m.r2Key, r2PublicUrl), imageTransformUrl, {
130
- width: 400,
131
- quality: 80,
132
- format: "auto",
133
- fit: "cover"
134
- }),
135
- alt: m.alt,
136
- blurhash: m.blurhash,
137
- width: m.width,
138
- height: m.height,
139
- position: m.position,
140
- mimeType: m.mimeType
141
- })));
50
+ const mediaMap = buildMediaMap(rawMediaMap, r2PublicUrl, imageTransformUrl);
51
+ // Get reply counts to identify thread roots
52
+ const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
53
+ const threadRootIds = postIds.filter((id)=>(replyCounts.get(id) ?? 0) > 0);
54
+ // Batch load thread previews
55
+ const threadPreviews = await c.var.services.posts.getThreadPreviews(threadRootIds, 3);
56
+ // Batch load media for preview replies
57
+ const previewReplyIds = [];
58
+ for (const replies of threadPreviews.values()){
59
+ for (const reply of replies){
60
+ previewReplyIds.push(reply.id);
61
+ }
142
62
  }
63
+ const previewMediaMap = previewReplyIds.length > 0 ? buildMediaMap(await c.var.services.media.getByPostIds(previewReplyIds), r2PublicUrl, imageTransformUrl) : new Map();
64
+ // Assemble timeline items
65
+ const items = displayPosts.map((post)=>{
66
+ const postWithMedia = {
67
+ ...post,
68
+ mediaAttachments: mediaMap.get(post.id) ?? []
69
+ };
70
+ const replyCount = replyCounts.get(post.id) ?? 0;
71
+ const previewReplies = threadPreviews.get(post.id);
72
+ if (replyCount > 0 && previewReplies) {
73
+ return {
74
+ post: postWithMedia,
75
+ threadPreview: {
76
+ replies: previewReplies.map((r)=>({
77
+ ...r,
78
+ mediaAttachments: previewMediaMap.get(r.id) ?? []
79
+ })),
80
+ totalReplyCount: replyCount
81
+ }
82
+ };
83
+ }
84
+ return {
85
+ post: postWithMedia
86
+ };
87
+ });
88
+ // Determine next cursor
89
+ const lastPost = displayPosts[displayPosts.length - 1];
90
+ const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
91
+ // Resolve theme components
92
+ const Feed = resolveTimelineFeed(DefaultTimelineFeed, c.var.config.theme?.components);
93
+ const feedProps = {
94
+ items,
95
+ hasMore,
96
+ nextCursor
97
+ };
143
98
  return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
144
- title: siteName,
99
+ title: navData.siteName,
145
100
  c: c,
146
- children: /*#__PURE__*/ _jsx(HomeContent, {
147
- siteName: siteName,
148
- posts: posts,
149
- mediaMap: mediaMap
101
+ children: /*#__PURE__*/ _jsx(SiteLayout, {
102
+ ...navData,
103
+ children: /*#__PURE__*/ _jsx(HomeContent, {
104
+ FeedComponent: Feed,
105
+ feedProps: feedProps
106
+ })
150
107
  })
151
108
  }));
152
109
  });