@jant/core 0.3.36 → 0.3.38

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 (271) hide show
  1. package/bin/commands/export.js +1 -1
  2. package/bin/commands/import-site.js +529 -0
  3. package/bin/commands/reset-password.js +3 -2
  4. package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
  5. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  6. package/dist/client/client.css +1 -1
  7. package/dist/client/client.js +4012 -3276
  8. package/dist/index.js +10285 -5809
  9. package/package.json +11 -3
  10. package/src/__tests__/helpers/app.ts +9 -9
  11. package/src/__tests__/helpers/db.ts +91 -93
  12. package/src/app.tsx +157 -27
  13. package/src/auth.ts +20 -2
  14. package/src/client/archive-nav.js +187 -0
  15. package/src/client/audio-player.ts +478 -0
  16. package/src/client/audio-processor.ts +84 -0
  17. package/src/client/avatar-upload.ts +3 -2
  18. package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
  19. package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
  20. package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
  21. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  22. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  23. package/src/client/components/collection-sidebar-types.ts +7 -9
  24. package/src/client/components/compose-types.ts +101 -4
  25. package/src/client/components/jant-collection-form.ts +43 -7
  26. package/src/client/components/jant-collection-sidebar.ts +88 -84
  27. package/src/client/components/jant-compose-dialog.ts +1655 -219
  28. package/src/client/components/jant-compose-editor.ts +732 -168
  29. package/src/client/components/jant-compose-fullscreen.ts +23 -78
  30. package/src/client/components/jant-media-lightbox.ts +2 -0
  31. package/src/client/components/jant-nav-manager.ts +24 -284
  32. package/src/client/components/jant-post-form.ts +89 -9
  33. package/src/client/components/jant-post-menu.ts +1019 -0
  34. package/src/client/components/jant-settings-avatar.ts +3 -3
  35. package/src/client/components/jant-settings-general.ts +38 -4
  36. package/src/client/components/jant-text-preview.ts +232 -0
  37. package/src/client/components/nav-manager-types.ts +4 -19
  38. package/src/client/components/post-form-template.ts +107 -12
  39. package/src/client/components/post-form-types.ts +11 -4
  40. package/src/client/compose-bridge.ts +410 -109
  41. package/src/client/image-processor.ts +26 -8
  42. package/src/client/media-metadata.ts +247 -0
  43. package/src/client/multipart-upload.ts +160 -0
  44. package/src/client/post-form-bridge.ts +52 -1
  45. package/src/client/settings-bridge.ts +0 -12
  46. package/src/client/thread-context.ts +140 -0
  47. package/src/client/tiptap/create-editor.ts +46 -0
  48. package/src/client/tiptap/extensions.ts +5 -0
  49. package/src/client/tiptap/image-node.ts +2 -8
  50. package/src/client/tiptap/paste-image.ts +2 -13
  51. package/src/client/tiptap/slash-commands.ts +173 -63
  52. package/src/client/toast.ts +101 -3
  53. package/src/client/types/sortablejs.d.ts +15 -0
  54. package/src/client/upload-with-metadata.ts +54 -0
  55. package/src/client/video-processor.ts +207 -0
  56. package/src/client.ts +5 -2
  57. package/src/db/__tests__/migrations.test.ts +118 -0
  58. package/src/db/index.ts +52 -0
  59. package/src/db/migrations/0000_baseline.sql +269 -0
  60. package/src/db/migrations/0001_fts_setup.sql +31 -0
  61. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  62. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  63. package/src/db/migrations/meta/_journal.json +4 -39
  64. package/src/db/schema.ts +409 -145
  65. package/src/i18n/__tests__/detect.test.ts +115 -0
  66. package/src/i18n/context.tsx +2 -2
  67. package/src/i18n/detect.ts +85 -1
  68. package/src/i18n/i18n.ts +1 -1
  69. package/src/i18n/index.ts +2 -1
  70. package/src/i18n/locales/en.po +487 -1217
  71. package/src/i18n/locales/en.ts +1 -1
  72. package/src/i18n/locales/zh-Hans.po +613 -996
  73. package/src/i18n/locales/zh-Hans.ts +1 -1
  74. package/src/i18n/locales/zh-Hant.po +624 -1007
  75. package/src/i18n/locales/zh-Hant.ts +1 -1
  76. package/src/i18n/middleware.ts +6 -0
  77. package/src/index.ts +5 -7
  78. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  79. package/src/lib/__tests__/constants.test.ts +0 -1
  80. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  81. package/src/lib/__tests__/nanoid.test.ts +26 -0
  82. package/src/lib/__tests__/schemas.test.ts +181 -63
  83. package/src/lib/__tests__/slug.test.ts +126 -0
  84. package/src/lib/__tests__/sse.test.ts +6 -6
  85. package/src/lib/__tests__/summary.test.ts +264 -0
  86. package/src/lib/__tests__/theme.test.ts +1 -1
  87. package/src/lib/__tests__/timeline.test.ts +33 -30
  88. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  89. package/src/lib/__tests__/view.test.ts +141 -66
  90. package/src/lib/blurhash-placeholder.ts +102 -0
  91. package/src/lib/constants.ts +3 -1
  92. package/src/lib/emoji-catalog.ts +885 -68
  93. package/src/lib/errors.ts +11 -8
  94. package/src/lib/feed.ts +78 -32
  95. package/src/lib/html.ts +2 -1
  96. package/src/lib/icon-catalog.ts +5033 -1
  97. package/src/lib/icons.ts +3 -2
  98. package/src/lib/index.ts +0 -1
  99. package/src/lib/markdown-to-tiptap.ts +286 -0
  100. package/src/lib/media-helpers.ts +12 -3
  101. package/src/lib/nanoid.ts +29 -0
  102. package/src/lib/navigation.ts +1 -1
  103. package/src/lib/render.tsx +20 -2
  104. package/src/lib/resolve-config.ts +6 -2
  105. package/src/lib/schemas.ts +224 -55
  106. package/src/lib/search-snippet.ts +34 -0
  107. package/src/lib/slug.ts +96 -0
  108. package/src/lib/sse.ts +6 -6
  109. package/src/lib/storage.ts +115 -7
  110. package/src/lib/summary.ts +66 -0
  111. package/src/lib/theme.ts +11 -8
  112. package/src/lib/timeline.ts +74 -34
  113. package/src/lib/tiptap-render.ts +5 -10
  114. package/src/lib/tiptap-to-markdown.ts +305 -0
  115. package/src/lib/upload.ts +190 -29
  116. package/src/lib/url.ts +31 -0
  117. package/src/lib/view.ts +204 -37
  118. package/src/middleware/__tests__/auth.test.ts +191 -11
  119. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  120. package/src/middleware/auth.ts +63 -9
  121. package/src/middleware/onboarding.ts +1 -1
  122. package/src/middleware/secure-headers.ts +40 -0
  123. package/src/preset.css +45 -2
  124. package/src/routes/__tests__/compose.test.ts +17 -24
  125. package/src/routes/api/__tests__/collections.test.ts +109 -61
  126. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  127. package/src/routes/api/__tests__/posts.test.ts +132 -68
  128. package/src/routes/api/__tests__/search.test.ts +15 -2
  129. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  130. package/src/routes/api/collections.ts +51 -42
  131. package/src/routes/api/custom-urls.ts +80 -0
  132. package/src/routes/api/export.ts +31 -0
  133. package/src/routes/api/nav-items.ts +23 -19
  134. package/src/routes/api/posts.ts +43 -39
  135. package/src/routes/api/search.ts +3 -4
  136. package/src/routes/api/upload-multipart.ts +245 -0
  137. package/src/routes/api/upload.ts +85 -19
  138. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  139. package/src/routes/auth/setup.tsx +26 -33
  140. package/src/routes/auth/signin.tsx +3 -7
  141. package/src/routes/compose.tsx +10 -55
  142. package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
  143. package/src/routes/dash/custom-urls.tsx +414 -0
  144. package/src/routes/dash/settings.tsx +304 -232
  145. package/src/routes/feed/__tests__/rss.test.ts +27 -28
  146. package/src/routes/feed/rss.ts +6 -4
  147. package/src/routes/feed/sitemap.ts +2 -12
  148. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  149. package/src/routes/pages/__tests__/featured.test.ts +41 -22
  150. package/src/routes/pages/archive.tsx +175 -39
  151. package/src/routes/pages/collection.tsx +22 -10
  152. package/src/routes/pages/collections.tsx +3 -3
  153. package/src/routes/pages/featured.tsx +28 -4
  154. package/src/routes/pages/home.tsx +16 -15
  155. package/src/routes/pages/latest.tsx +1 -11
  156. package/src/routes/pages/new.tsx +39 -0
  157. package/src/routes/pages/page.tsx +95 -49
  158. package/src/routes/pages/search.tsx +1 -1
  159. package/src/services/__tests__/api-token.test.ts +135 -0
  160. package/src/services/__tests__/collection.test.ts +275 -227
  161. package/src/services/__tests__/custom-url.test.ts +213 -0
  162. package/src/services/__tests__/media.test.ts +162 -22
  163. package/src/services/__tests__/navigation.test.ts +109 -68
  164. package/src/services/__tests__/post-timeline.test.ts +205 -32
  165. package/src/services/__tests__/post.test.ts +713 -234
  166. package/src/services/__tests__/search.test.ts +67 -10
  167. package/src/services/api-token.ts +166 -0
  168. package/src/services/auth.ts +17 -2
  169. package/src/services/collection.ts +397 -131
  170. package/src/services/custom-url.ts +188 -0
  171. package/src/services/export.ts +802 -0
  172. package/src/services/index.ts +26 -19
  173. package/src/services/media.ts +100 -22
  174. package/src/services/navigation.ts +158 -47
  175. package/src/services/path.ts +339 -0
  176. package/src/services/post.ts +687 -154
  177. package/src/services/search.ts +160 -75
  178. package/src/styles/components.css +58 -7
  179. package/src/styles/tokens.css +84 -6
  180. package/src/styles/ui.css +2964 -457
  181. package/src/types/bindings.ts +4 -1
  182. package/src/types/config.ts +12 -4
  183. package/src/types/constants.ts +15 -3
  184. package/src/types/entities.ts +74 -35
  185. package/src/types/operations.ts +11 -24
  186. package/src/types/props.ts +51 -16
  187. package/src/types/views.ts +45 -22
  188. package/src/ui/color-themes.ts +133 -23
  189. package/src/ui/compose/ComposeDialog.tsx +239 -17
  190. package/src/ui/compose/ComposePrompt.tsx +1 -1
  191. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  192. package/src/ui/dash/ListItemRow.tsx +1 -1
  193. package/src/ui/dash/StatusBadge.tsx +3 -1
  194. package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
  195. package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
  196. package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
  197. package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
  198. package/src/ui/dash/index.ts +0 -3
  199. package/src/ui/dash/settings/AccountContent.tsx +3 -57
  200. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  201. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  202. package/src/ui/dash/settings/AvatarContent.tsx +8 -0
  203. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  204. package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
  205. package/src/ui/feed/LinkCard.tsx +89 -40
  206. package/src/ui/feed/NoteCard.tsx +39 -25
  207. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  208. package/src/ui/feed/QuoteCard.tsx +38 -23
  209. package/src/ui/feed/ThreadPreview.tsx +90 -26
  210. package/src/ui/feed/TimelineFeed.tsx +3 -2
  211. package/src/ui/feed/TimelineItem.tsx +15 -6
  212. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  213. package/src/ui/feed/thread-preview-state.ts +61 -0
  214. package/src/ui/font-themes.ts +2 -2
  215. package/src/ui/layouts/BaseLayout.tsx +2 -2
  216. package/src/ui/layouts/SiteLayout.tsx +105 -92
  217. package/src/ui/pages/ArchivePage.tsx +923 -98
  218. package/src/ui/pages/ComposePage.tsx +54 -0
  219. package/src/ui/pages/PostPage.tsx +30 -45
  220. package/src/ui/pages/SearchPage.tsx +181 -37
  221. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  222. package/src/ui/shared/CollectionsSidebar.tsx +47 -37
  223. package/src/ui/shared/MediaGallery.tsx +445 -149
  224. package/src/ui/shared/PostFooter.tsx +204 -0
  225. package/src/ui/shared/StarRating.tsx +27 -0
  226. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  227. package/src/ui/shared/index.ts +0 -1
  228. package/dist/client/assets/url-8Dj-5CLW.js +0 -1
  229. package/src/client/media-upload.ts +0 -161
  230. package/src/client/page-slug-bridge.ts +0 -42
  231. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  232. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  233. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  234. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  235. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  236. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  237. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  238. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  239. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  240. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  241. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  242. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  243. package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
  244. package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
  245. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  246. package/src/lib/__tests__/sqid.test.ts +0 -65
  247. package/src/lib/sqid.ts +0 -79
  248. package/src/routes/api/__tests__/pages.test.ts +0 -218
  249. package/src/routes/api/pages.ts +0 -73
  250. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  251. package/src/routes/dash/index.tsx +0 -109
  252. package/src/routes/dash/media.tsx +0 -135
  253. package/src/routes/dash/pages.tsx +0 -245
  254. package/src/routes/dash/posts.tsx +0 -338
  255. package/src/routes/dash/redirects.tsx +0 -263
  256. package/src/routes/pages/post.tsx +0 -59
  257. package/src/services/__tests__/page.test.ts +0 -298
  258. package/src/services/__tests__/path-registry.test.ts +0 -165
  259. package/src/services/__tests__/redirect.test.ts +0 -159
  260. package/src/services/page.ts +0 -216
  261. package/src/services/path-registry.ts +0 -160
  262. package/src/services/redirect.ts +0 -97
  263. package/src/ui/dash/PageForm.tsx +0 -187
  264. package/src/ui/dash/PostList.tsx +0 -95
  265. package/src/ui/dash/media/MediaListContent.tsx +0 -206
  266. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  267. package/src/ui/dash/pages/PagesContent.tsx +0 -75
  268. package/src/ui/dash/posts/PostForm.tsx +0 -260
  269. package/src/ui/layouts/DashLayout.tsx +0 -247
  270. package/src/ui/pages/SinglePage.tsx +0 -23
  271. package/src/ui/shared/ThreadView.tsx +0 -136
@@ -1,109 +0,0 @@
1
- /**
2
- * Dashboard Index Route
3
- *
4
- * Example of using @lingui/react/macro with Hono JSX!
5
- */
6
-
7
- import { Hono } from "hono";
8
- import { Trans, useLingui } from "@lingui/react/macro";
9
- import type { Bindings } from "../../types.js";
10
- import type { AppVariables } from "../../types/app-context.js";
11
- import { DashLayout } from "../../ui/layouts/DashLayout.js";
12
-
13
- type Env = { Bindings: Bindings; Variables: AppVariables };
14
-
15
- export const dashIndexRoutes = new Hono<Env>();
16
-
17
- /**
18
- * Dashboard content component
19
- * Uses useLingui() from @lingui/react/macro - works with Hono JSX!
20
- */
21
- function DashboardContent({
22
- publishedCount,
23
- draftCount,
24
- }: {
25
- publishedCount: number;
26
- draftCount: number;
27
- }) {
28
- // 🎉 Single layer! Just like React!
29
- const { t } = useLingui();
30
-
31
- return (
32
- <>
33
- <h1 class="text-2xl font-semibold mb-6">
34
- {t({
35
- message: "Dashboard",
36
- comment: "@context: Dashboard main heading",
37
- })}
38
- </h1>
39
-
40
- <div class="grid gap-4 md:grid-cols-3 mb-6">
41
- <div class="p-4 border rounded">
42
- <p class="text-sm text-muted-foreground">
43
- {t({
44
- message: "Published",
45
- comment: "@context: Post status label",
46
- })}
47
- </p>
48
- <p class="text-3xl font-bold">{publishedCount}</p>
49
- </div>
50
-
51
- <div class="p-4 border rounded">
52
- <p class="text-sm text-muted-foreground">
53
- {t({ message: "Drafts", comment: "@context: Post status label" })}
54
- </p>
55
- <p class="text-3xl font-bold">{draftCount}</p>
56
- </div>
57
-
58
- <div class="p-4 border rounded">
59
- <p class="text-sm text-muted-foreground mb-2">
60
- {t({
61
- message: "Quick Actions",
62
- comment: "@context: Dashboard section title",
63
- })}
64
- </p>
65
- <a href="/dash/posts/new" class="btn-primary w-full">
66
- {t({
67
- message: "New Post",
68
- comment: "@context: Button to create new post",
69
- })}
70
- </a>
71
- </div>
72
- </div>
73
-
74
- <p>
75
- <Trans comment="@context: Help text with link">
76
- Need help? Visit the{" "}
77
- <a href="/docs" class="underline">
78
- documentation
79
- </a>
80
- </Trans>
81
- </p>
82
- </>
83
- );
84
- }
85
-
86
- dashIndexRoutes.get("/", async (c) => {
87
- const siteName = c.var.appConfig.siteName;
88
-
89
- // Get stats via service-level counting (avoids loading all posts into memory)
90
- const [publishedCount, draftCount] = await Promise.all([
91
- c.var.services.posts.count({ status: "published" }),
92
- c.var.services.posts.count({ status: "draft" }),
93
- ]);
94
-
95
- return c.html(
96
- <DashLayout
97
- c={c}
98
- title="Dashboard"
99
- siteName={siteName}
100
- siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
101
- currentPath="/dash"
102
- >
103
- <DashboardContent
104
- publishedCount={publishedCount}
105
- draftCount={draftCount}
106
- />
107
- </DashLayout>,
108
- );
109
- });
@@ -1,135 +0,0 @@
1
- /**
2
- * Dashboard Media Routes
3
- */
4
-
5
- import { Hono } from "hono";
6
- import type { Bindings } from "../../types.js";
7
- import type { AppVariables } from "../../types/app-context.js";
8
- import { DashLayout } from "../../ui/layouts/DashLayout.js";
9
- import { dsRedirect } from "../../lib/sse.js";
10
- import {
11
- getMediaUrl,
12
- getImageUrl,
13
- getPublicUrlForProvider,
14
- } from "../../lib/image.js";
15
- import { MediaListContent } from "../../ui/dash/media/MediaListContent.js";
16
- import { ViewMediaContent } from "../../ui/dash/media/ViewMediaContent.js";
17
-
18
- type Env = { Bindings: Bindings; Variables: AppVariables };
19
-
20
- export const mediaRoutes = new Hono<Env>();
21
-
22
- // List media
23
- mediaRoutes.get("/", async (c) => {
24
- const mediaList = await c.var.services.media.list({ limit: 100 });
25
- const siteName = c.var.appConfig.siteName;
26
-
27
- return c.html(
28
- <DashLayout
29
- c={c}
30
- title="Media"
31
- siteName={siteName}
32
- siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
33
- currentPath="/dash/media"
34
- >
35
- <MediaListContent
36
- mediaList={mediaList}
37
- r2PublicUrl={c.var.appConfig.r2PublicUrl}
38
- imageTransformUrl={c.var.appConfig.imageTransformUrl}
39
- s3PublicUrl={c.var.appConfig.s3PublicUrl}
40
- uploadMaxFileSize={c.var.appConfig.uploadMaxFileSize}
41
- />
42
- </DashLayout>,
43
- );
44
- });
45
-
46
- // Media picker (returns HTML fragment for PostForm dialog)
47
- // Must be defined before /:id to avoid "picker" matching as an ID
48
- mediaRoutes.get("/picker", async (c) => {
49
- const mediaList = await c.var.services.media.list({
50
- limit: 100,
51
- mimePrefix: "image/",
52
- });
53
- const r2PublicUrl = c.var.appConfig.r2PublicUrl;
54
- const imageTransformUrl = c.var.appConfig.imageTransformUrl;
55
- const s3PublicUrl = c.var.appConfig.s3PublicUrl;
56
-
57
- if (mediaList.length === 0) {
58
- return c.html(
59
- <p class="text-muted-foreground text-sm col-span-4">
60
- No media uploaded yet. Upload media from the Media page first.
61
- </p>,
62
- );
63
- }
64
-
65
- return c.html(
66
- <>
67
- {mediaList.map((m) => {
68
- const pUrl = getPublicUrlForProvider(
69
- m.provider,
70
- r2PublicUrl,
71
- s3PublicUrl,
72
- );
73
- const url = getMediaUrl(m.storageKey, pUrl);
74
- const thumbUrl = getImageUrl(url, imageTransformUrl, {
75
- width: 150,
76
- quality: 80,
77
- format: "auto",
78
- fit: "cover",
79
- });
80
- return (
81
- <button
82
- key={m.id}
83
- type="button"
84
- class="aspect-square rounded-lg overflow-hidden border-2 hover:border-primary cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
85
- data-media-id={m.id}
86
- >
87
- <img
88
- src={thumbUrl}
89
- alt={m.alt || m.originalName}
90
- class="w-full h-full object-cover"
91
- loading="lazy"
92
- />
93
- </button>
94
- );
95
- })}
96
- </>,
97
- );
98
- });
99
-
100
- // View single media
101
- mediaRoutes.get("/:id", async (c) => {
102
- const id = c.req.param("id");
103
- const media = await c.var.services.media.getById(id);
104
- if (!media) return c.notFound();
105
-
106
- const siteName = c.var.appConfig.siteName;
107
-
108
- return c.html(
109
- <DashLayout
110
- c={c}
111
- title={media.originalName}
112
- siteName={siteName}
113
- siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
114
- currentPath="/dash/media"
115
- >
116
- <ViewMediaContent
117
- media={media}
118
- r2PublicUrl={c.var.appConfig.r2PublicUrl}
119
- imageTransformUrl={c.var.appConfig.imageTransformUrl}
120
- s3PublicUrl={c.var.appConfig.s3PublicUrl}
121
- />
122
- </DashLayout>,
123
- );
124
- });
125
-
126
- // Delete media
127
- mediaRoutes.post("/:id/delete", async (c) => {
128
- const id = c.req.param("id");
129
- const media = await c.var.services.media.getById(id);
130
- if (!media) return c.notFound();
131
-
132
- await c.var.services.media.delete(id, c.var.storage);
133
-
134
- return dsRedirect("/dash/media");
135
- });
@@ -1,245 +0,0 @@
1
- /**
2
- * Dashboard Pages Routes
3
- *
4
- * Page CRUD management.
5
- */
6
-
7
- import { Hono } from "hono";
8
- import { msg } from "@lingui/core/macro";
9
- import { useLingui } from "@lingui/react/macro";
10
- import type { Bindings, Page } from "../../types.js";
11
- import type { AppVariables } from "../../types/app-context.js";
12
- import { DashLayout } from "../../ui/layouts/DashLayout.js";
13
- import { PageForm, ActionButtons, DangerZone } from "../../ui/dash/index.js";
14
- import { dsRedirect, dsToast } from "../../lib/sse.js";
15
- import { CreatePageSchema } from "../../lib/schemas.js";
16
- import { PagesContent } from "../../ui/dash/pages/PagesContent.js";
17
- import { getI18n } from "../../i18n/index.js";
18
-
19
- type Env = { Bindings: Bindings; Variables: AppVariables };
20
-
21
- export const pagesRoutes = new Hono<Env>();
22
-
23
- // =============================================================================
24
- // Inline components (small, tightly coupled to route params)
25
- // =============================================================================
26
-
27
- function NewPageContent() {
28
- const { t } = useLingui();
29
- return (
30
- <>
31
- <h1 class="text-2xl font-semibold mb-6">
32
- {t({ message: "New Page", comment: "@context: New page main heading" })}
33
- </h1>
34
- <PageForm action="/dash/pages" />
35
- </>
36
- );
37
- }
38
-
39
- function ViewPageContent({ page }: { page: Page }) {
40
- const { t } = useLingui();
41
- return (
42
- <>
43
- <div class="flex items-center justify-between mb-6">
44
- <div>
45
- <h1 class="text-2xl font-semibold">
46
- {page.title ||
47
- t({
48
- message: "Page",
49
- comment: "@context: Default page heading when untitled",
50
- })}
51
- </h1>
52
- <p class="text-muted-foreground mt-1">/{page.slug}</p>
53
- </div>
54
- <ActionButtons
55
- editHref={`/dash/pages/${page.id}/edit`}
56
- editLabel={t({
57
- message: "Edit",
58
- comment: "@context: Button to edit page",
59
- })}
60
- viewHref={page.status !== "draft" ? `/${page.slug}` : undefined}
61
- viewLabel={t({
62
- message: "View",
63
- comment: "@context: Button to view page on public site",
64
- })}
65
- />
66
- </div>
67
-
68
- <div class="card">
69
- <section>
70
- <div
71
- class="prose"
72
- dangerouslySetInnerHTML={{ __html: page.bodyHtml || "" }}
73
- />
74
- </section>
75
- </div>
76
-
77
- <DangerZone
78
- actionLabel={t({
79
- message: "Delete Page",
80
- comment: "@context: Button to delete page",
81
- })}
82
- formAction={`/dash/pages/${page.id}/delete`}
83
- confirmMessage="Are you sure you want to delete this page?"
84
- />
85
- </>
86
- );
87
- }
88
-
89
- function EditPageContent({ page }: { page: Page }) {
90
- const { t } = useLingui();
91
- return (
92
- <>
93
- <h1 class="text-2xl font-semibold mb-6">
94
- {t({
95
- message: "Edit Page",
96
- comment: "@context: Edit page main heading",
97
- })}
98
- </h1>
99
- <PageForm page={page} action={`/dash/pages/${page.id}`} />
100
- </>
101
- );
102
- }
103
-
104
- // =============================================================================
105
- // Route handlers
106
- // =============================================================================
107
-
108
- pagesRoutes.get("/", async (c) => {
109
- const pages = await c.var.services.pages.list();
110
- const siteName = c.var.appConfig.siteName;
111
-
112
- return c.html(
113
- <DashLayout
114
- c={c}
115
- title="Pages"
116
- siteName={siteName}
117
- siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
118
- currentPath="/dash/pages"
119
- >
120
- <PagesContent pages={pages} />
121
- </DashLayout>,
122
- );
123
- });
124
-
125
- pagesRoutes.get("/new", async (c) => {
126
- const siteName = c.var.appConfig.siteName;
127
- return c.html(
128
- <DashLayout
129
- c={c}
130
- title="New Page"
131
- siteName={siteName}
132
- siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
133
- currentPath="/dash/pages"
134
- >
135
- <NewPageContent />
136
- </DashLayout>,
137
- );
138
- });
139
-
140
- pagesRoutes.post("/", async (c) => {
141
- const i18n = getI18n(c);
142
- const raw = await c.req.json();
143
- const parsed = CreatePageSchema.safeParse(raw);
144
- if (!parsed.success) {
145
- const errorMsg =
146
- parsed.error.issues[0]?.message ??
147
- i18n._(
148
- msg({
149
- message:
150
- "Something doesn't look right. Check the form and try again.",
151
- comment: "@context: Fallback validation error for page form",
152
- }),
153
- );
154
- return dsToast(errorMsg, "error");
155
- }
156
-
157
- const page = await c.var.services.pages.create({
158
- title: parsed.data.title,
159
- body: parsed.data.body,
160
- status: parsed.data.status,
161
- slug: parsed.data.slug,
162
- });
163
-
164
- return dsRedirect(`/dash/pages/${page.id}`);
165
- });
166
-
167
- pagesRoutes.get("/:id", async (c) => {
168
- const id = parseInt(c.req.param("id"), 10);
169
- if (isNaN(id)) return c.notFound();
170
-
171
- const page = await c.var.services.pages.getById(id);
172
- if (!page) return c.notFound();
173
-
174
- const siteName = c.var.appConfig.siteName;
175
- return c.html(
176
- <DashLayout
177
- c={c}
178
- title={page.title || "Page"}
179
- siteName={siteName}
180
- siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
181
- currentPath="/dash/pages"
182
- >
183
- <ViewPageContent page={page} />
184
- </DashLayout>,
185
- );
186
- });
187
-
188
- pagesRoutes.get("/:id/edit", async (c) => {
189
- const id = parseInt(c.req.param("id"), 10);
190
- if (isNaN(id)) return c.notFound();
191
-
192
- const page = await c.var.services.pages.getById(id);
193
- if (!page) return c.notFound();
194
-
195
- const siteName = c.var.appConfig.siteName;
196
- return c.html(
197
- <DashLayout
198
- c={c}
199
- title={`Edit: ${page.title || "Page"}`}
200
- siteName={siteName}
201
- siteAvatarUrl={c.var.appConfig.siteAvatarUrl}
202
- currentPath="/dash/pages"
203
- >
204
- <EditPageContent page={page} />
205
- </DashLayout>,
206
- );
207
- });
208
-
209
- pagesRoutes.post("/:id", async (c) => {
210
- const i18n = getI18n(c);
211
- const id = parseInt(c.req.param("id"), 10);
212
- if (isNaN(id)) return c.notFound();
213
-
214
- const raw = await c.req.json();
215
- const parsed = CreatePageSchema.safeParse(raw);
216
- if (!parsed.success) {
217
- const errorMsg =
218
- parsed.error.issues[0]?.message ??
219
- i18n._(
220
- msg({
221
- message:
222
- "Something doesn't look right. Check the form and try again.",
223
- comment: "@context: Fallback validation error for page form",
224
- }),
225
- );
226
- return dsToast(errorMsg, "error");
227
- }
228
-
229
- await c.var.services.pages.update(id, {
230
- title: parsed.data.title,
231
- body: parsed.data.body,
232
- status: parsed.data.status,
233
- slug: parsed.data.slug,
234
- });
235
-
236
- return dsRedirect(`/dash/pages/${id}`);
237
- });
238
-
239
- pagesRoutes.post("/:id/delete", async (c) => {
240
- const id = parseInt(c.req.param("id"), 10);
241
- if (isNaN(id)) return c.notFound();
242
-
243
- await c.var.services.pages.delete(id);
244
- return dsRedirect("/dash/pages");
245
- });