@jant/core 0.1.2 → 0.2.0

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 (288) hide show
  1. package/dist/app.d.ts +34 -0
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +474 -0
  4. package/dist/assets/datastar.min.js +1775 -0
  5. package/dist/auth.d.ts +23 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +34 -0
  8. package/{src/client.ts → dist/client.d.ts} +1 -1
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +4 -0
  11. package/dist/db/index.d.ts +10 -0
  12. package/dist/db/index.d.ts.map +1 -0
  13. package/dist/db/index.js +10 -0
  14. package/dist/db/schema.d.ts +1507 -0
  15. package/dist/db/schema.d.ts.map +1 -0
  16. package/dist/db/schema.js +183 -0
  17. package/{src/i18n/Trans.tsx → dist/i18n/Trans.d.ts} +4 -10
  18. package/dist/i18n/Trans.d.ts.map +1 -0
  19. package/dist/i18n/Trans.js +24 -0
  20. package/dist/i18n/context.d.ts +69 -0
  21. package/dist/i18n/context.d.ts.map +1 -0
  22. package/dist/i18n/context.js +61 -0
  23. package/dist/i18n/detect.d.ts +31 -0
  24. package/dist/i18n/detect.d.ts.map +1 -0
  25. package/dist/i18n/detect.js +77 -0
  26. package/{src/i18n/i18n.ts → dist/i18n/i18n.d.ts} +5 -25
  27. package/dist/i18n/i18n.d.ts.map +1 -0
  28. package/dist/i18n/i18n.js +55 -0
  29. package/dist/i18n/index.d.ts +41 -0
  30. package/dist/i18n/index.d.ts.map +1 -0
  31. package/{src/i18n/index.ts → dist/i18n/index.js} +3 -24
  32. package/dist/i18n/locales/en.d.ts +3 -0
  33. package/dist/i18n/locales/en.d.ts.map +1 -0
  34. package/dist/i18n/locales/en.js +1 -0
  35. package/dist/i18n/locales/zh-Hans.d.ts +3 -0
  36. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -0
  37. package/dist/i18n/locales/zh-Hans.js +1 -0
  38. package/dist/i18n/locales/zh-Hant.d.ts +3 -0
  39. package/dist/i18n/locales/zh-Hant.d.ts.map +1 -0
  40. package/dist/i18n/locales/zh-Hant.js +1 -0
  41. package/dist/i18n/locales.d.ts +11 -0
  42. package/dist/i18n/locales.d.ts.map +1 -0
  43. package/dist/i18n/locales.js +13 -0
  44. package/dist/i18n/middleware.d.ts +24 -0
  45. package/dist/i18n/middleware.d.ts.map +1 -0
  46. package/dist/i18n/middleware.js +41 -0
  47. package/dist/index.d.ts +16 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/{src/index.ts → dist/index.js} +1 -28
  50. package/dist/lib/assets.d.ts +19 -0
  51. package/dist/lib/assets.d.ts.map +1 -0
  52. package/dist/lib/assets.js +33 -0
  53. package/dist/lib/constants.d.ts +36 -0
  54. package/dist/lib/constants.d.ts.map +1 -0
  55. package/dist/lib/constants.js +50 -0
  56. package/{src/lib/image.ts → dist/lib/image.d.ts} +13 -47
  57. package/dist/lib/image.d.ts.map +1 -0
  58. package/dist/lib/image.js +77 -0
  59. package/{src/lib/index.ts → dist/lib/index.d.ts} +1 -1
  60. package/dist/lib/index.d.ts.map +1 -0
  61. package/dist/lib/index.js +7 -0
  62. package/dist/lib/markdown.d.ts +60 -0
  63. package/dist/lib/markdown.d.ts.map +1 -0
  64. package/{src/lib/markdown.ts → dist/lib/markdown.js} +16 -26
  65. package/dist/lib/schemas.d.ts +113 -0
  66. package/dist/lib/schemas.d.ts.map +1 -0
  67. package/dist/lib/schemas.js +71 -0
  68. package/dist/lib/sqid.d.ts +60 -0
  69. package/dist/lib/sqid.d.ts.map +1 -0
  70. package/{src/lib/sqid.ts → dist/lib/sqid.js} +15 -22
  71. package/dist/lib/sse.d.ts +95 -0
  72. package/dist/lib/sse.d.ts.map +1 -0
  73. package/dist/lib/sse.js +81 -0
  74. package/dist/lib/time.d.ts +90 -0
  75. package/dist/lib/time.d.ts.map +1 -0
  76. package/{src/lib/time.ts → dist/lib/time.js} +20 -33
  77. package/{src/lib/url.ts → dist/lib/url.d.ts} +5 -30
  78. package/dist/lib/url.d.ts.map +1 -0
  79. package/dist/lib/url.js +89 -0
  80. package/dist/middleware/auth.d.ts +24 -0
  81. package/dist/middleware/auth.d.ts.map +1 -0
  82. package/dist/middleware/auth.js +52 -0
  83. package/dist/routes/api/posts.d.ts +13 -0
  84. package/dist/routes/api/posts.d.ts.map +1 -0
  85. package/dist/routes/api/posts.js +124 -0
  86. package/dist/routes/api/search.d.ts +13 -0
  87. package/dist/routes/api/search.d.ts.map +1 -0
  88. package/dist/routes/api/search.js +49 -0
  89. package/dist/routes/api/upload.d.ts +16 -0
  90. package/dist/routes/api/upload.d.ts.map +1 -0
  91. package/dist/routes/api/upload.js +227 -0
  92. package/dist/routes/dash/collections.d.ts +13 -0
  93. package/dist/routes/dash/collections.d.ts.map +1 -0
  94. package/dist/routes/dash/collections.js +512 -0
  95. package/dist/routes/dash/index.d.ts +15 -0
  96. package/dist/routes/dash/index.d.ts.map +1 -0
  97. package/dist/routes/dash/index.js +117 -0
  98. package/dist/routes/dash/media.d.ts +16 -0
  99. package/dist/routes/dash/media.d.ts.map +1 -0
  100. package/dist/routes/dash/media.js +589 -0
  101. package/dist/routes/dash/pages.d.ts +15 -0
  102. package/dist/routes/dash/pages.d.ts.map +1 -0
  103. package/dist/routes/dash/pages.js +290 -0
  104. package/dist/routes/dash/posts.d.ts +13 -0
  105. package/dist/routes/dash/posts.d.ts.map +1 -0
  106. package/dist/routes/dash/posts.js +226 -0
  107. package/dist/routes/dash/redirects.d.ts +13 -0
  108. package/dist/routes/dash/redirects.d.ts.map +1 -0
  109. package/dist/routes/dash/redirects.js +237 -0
  110. package/dist/routes/dash/settings.d.ts +13 -0
  111. package/dist/routes/dash/settings.d.ts.map +1 -0
  112. package/dist/routes/dash/settings.js +154 -0
  113. package/dist/routes/feed/rss.d.ts +13 -0
  114. package/dist/routes/feed/rss.d.ts.map +1 -0
  115. package/dist/routes/feed/rss.js +95 -0
  116. package/dist/routes/feed/sitemap.d.ts +13 -0
  117. package/dist/routes/feed/sitemap.d.ts.map +1 -0
  118. package/dist/routes/feed/sitemap.js +59 -0
  119. package/dist/routes/pages/archive.d.ts +15 -0
  120. package/dist/routes/pages/archive.d.ts.map +1 -0
  121. package/dist/routes/pages/archive.js +255 -0
  122. package/dist/routes/pages/collection.d.ts +13 -0
  123. package/dist/routes/pages/collection.d.ts.map +1 -0
  124. package/dist/routes/pages/collection.js +93 -0
  125. package/dist/routes/pages/home.d.ts +13 -0
  126. package/dist/routes/pages/home.d.ts.map +1 -0
  127. package/dist/routes/pages/home.js +122 -0
  128. package/dist/routes/pages/page.d.ts +15 -0
  129. package/dist/routes/pages/page.d.ts.map +1 -0
  130. package/dist/routes/pages/page.js +69 -0
  131. package/dist/routes/pages/post.d.ts +13 -0
  132. package/dist/routes/pages/post.d.ts.map +1 -0
  133. package/dist/routes/pages/post.js +90 -0
  134. package/dist/routes/pages/search.d.ts +13 -0
  135. package/dist/routes/pages/search.d.ts.map +1 -0
  136. package/dist/routes/pages/search.js +180 -0
  137. package/dist/services/collection.d.ts +31 -0
  138. package/dist/services/collection.d.ts.map +1 -0
  139. package/dist/services/collection.js +108 -0
  140. package/dist/services/index.d.ts +28 -0
  141. package/dist/services/index.d.ts.map +1 -0
  142. package/dist/services/index.js +20 -0
  143. package/dist/services/media.d.ts +27 -0
  144. package/dist/services/media.d.ts.map +1 -0
  145. package/dist/services/media.js +62 -0
  146. package/dist/services/post.d.ts +31 -0
  147. package/dist/services/post.d.ts.map +1 -0
  148. package/dist/services/post.js +191 -0
  149. package/dist/services/redirect.d.ts +15 -0
  150. package/dist/services/redirect.d.ts.map +1 -0
  151. package/dist/services/redirect.js +48 -0
  152. package/dist/services/search.d.ts +26 -0
  153. package/dist/services/search.d.ts.map +1 -0
  154. package/dist/services/search.js +61 -0
  155. package/dist/services/settings.d.ts +17 -0
  156. package/dist/services/settings.d.ts.map +1 -0
  157. package/dist/services/settings.js +65 -0
  158. package/dist/theme/components/ActionButtons.d.ts +43 -0
  159. package/dist/theme/components/ActionButtons.d.ts.map +1 -0
  160. package/dist/theme/components/ActionButtons.js +50 -0
  161. package/dist/theme/components/CrudPageHeader.d.ts +23 -0
  162. package/dist/theme/components/CrudPageHeader.d.ts.map +1 -0
  163. package/dist/theme/components/CrudPageHeader.js +22 -0
  164. package/dist/theme/components/DangerZone.d.ts +36 -0
  165. package/dist/theme/components/DangerZone.d.ts.map +1 -0
  166. package/dist/theme/components/DangerZone.js +39 -0
  167. package/dist/theme/components/EmptyState.d.ts +27 -0
  168. package/dist/theme/components/EmptyState.d.ts.map +1 -0
  169. package/dist/theme/components/EmptyState.js +27 -0
  170. package/dist/theme/components/ListItemRow.d.ts +15 -0
  171. package/dist/theme/components/ListItemRow.d.ts.map +1 -0
  172. package/dist/theme/components/ListItemRow.js +21 -0
  173. package/dist/theme/components/PageForm.d.ts +14 -0
  174. package/dist/theme/components/PageForm.d.ts.map +1 -0
  175. package/dist/theme/components/PageForm.js +173 -0
  176. package/dist/theme/components/Pagination.d.ts +46 -0
  177. package/dist/theme/components/Pagination.d.ts.map +1 -0
  178. package/dist/theme/components/Pagination.js +159 -0
  179. package/dist/theme/components/PostForm.d.ts +12 -0
  180. package/dist/theme/components/PostForm.d.ts.map +1 -0
  181. package/dist/theme/components/PostForm.js +230 -0
  182. package/dist/theme/components/PostList.d.ts +10 -0
  183. package/dist/theme/components/PostList.d.ts.map +1 -0
  184. package/dist/theme/components/PostList.js +73 -0
  185. package/dist/theme/components/ThreadView.d.ts +15 -0
  186. package/dist/theme/components/ThreadView.d.ts.map +1 -0
  187. package/dist/theme/components/ThreadView.js +111 -0
  188. package/dist/theme/components/TypeBadge.d.ts +12 -0
  189. package/dist/theme/components/TypeBadge.d.ts.map +1 -0
  190. package/dist/theme/components/TypeBadge.js +39 -0
  191. package/dist/theme/components/VisibilityBadge.d.ts +12 -0
  192. package/dist/theme/components/VisibilityBadge.d.ts.map +1 -0
  193. package/dist/theme/components/VisibilityBadge.js +37 -0
  194. package/{src/theme/components/index.ts → dist/theme/components/index.d.ts} +1 -0
  195. package/dist/theme/components/index.d.ts.map +1 -0
  196. package/dist/theme/components/index.js +12 -0
  197. package/dist/theme/index.d.ts +21 -0
  198. package/dist/theme/index.d.ts.map +1 -0
  199. package/{src/theme/index.ts → dist/theme/index.js} +1 -4
  200. package/dist/theme/layouts/BaseLayout.d.ts +16 -0
  201. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -0
  202. package/dist/theme/layouts/BaseLayout.js +58 -0
  203. package/dist/theme/layouts/DashLayout.d.ts +15 -0
  204. package/dist/theme/layouts/DashLayout.d.ts.map +1 -0
  205. package/dist/theme/layouts/DashLayout.js +139 -0
  206. package/{src/theme/layouts/index.ts → dist/theme/layouts/index.d.ts} +1 -0
  207. package/dist/theme/layouts/index.d.ts.map +1 -0
  208. package/dist/theme/layouts/index.js +2 -0
  209. package/dist/theme/styles/main.css +2 -0
  210. package/dist/types.d.ts +179 -0
  211. package/dist/types.d.ts.map +1 -0
  212. package/dist/types.js +19 -0
  213. package/package.json +26 -26
  214. package/drizzle.config.ts +0 -10
  215. package/lingui.config.ts +0 -16
  216. package/src/app.tsx +0 -377
  217. package/src/assets/datastar.min.js +0 -8
  218. package/src/auth.ts +0 -38
  219. package/src/db/index.ts +0 -14
  220. package/src/db/migrations/0000_solid_moon_knight.sql +0 -118
  221. package/src/db/migrations/0001_add_search_fts.sql +0 -40
  222. package/src/db/migrations/0002_collection_path.sql +0 -2
  223. package/src/db/migrations/0003_collection_path_nullable.sql +0 -21
  224. package/src/db/migrations/0004_media_uuid.sql +0 -35
  225. package/src/db/migrations/meta/0000_snapshot.json +0 -784
  226. package/src/db/migrations/meta/_journal.json +0 -41
  227. package/src/db/schema.ts +0 -159
  228. package/src/i18n/EXAMPLES.md +0 -235
  229. package/src/i18n/README.md +0 -296
  230. package/src/i18n/context.tsx +0 -101
  231. package/src/i18n/detect.ts +0 -100
  232. package/src/i18n/locales/en.po +0 -875
  233. package/src/i18n/locales/en.ts +0 -1
  234. package/src/i18n/locales/zh-Hans.po +0 -875
  235. package/src/i18n/locales/zh-Hans.ts +0 -1
  236. package/src/i18n/locales/zh-Hant.po +0 -875
  237. package/src/i18n/locales/zh-Hant.ts +0 -1
  238. package/src/i18n/locales.ts +0 -14
  239. package/src/i18n/middleware.ts +0 -59
  240. package/src/lib/assets.ts +0 -47
  241. package/src/lib/constants.ts +0 -67
  242. package/src/lib/schemas.ts +0 -92
  243. package/src/lib/sse.ts +0 -152
  244. package/src/middleware/auth.ts +0 -59
  245. package/src/routes/api/posts.ts +0 -127
  246. package/src/routes/api/search.ts +0 -53
  247. package/src/routes/api/upload.ts +0 -240
  248. package/src/routes/dash/collections.tsx +0 -341
  249. package/src/routes/dash/index.tsx +0 -89
  250. package/src/routes/dash/media.tsx +0 -551
  251. package/src/routes/dash/pages.tsx +0 -245
  252. package/src/routes/dash/posts.tsx +0 -202
  253. package/src/routes/dash/redirects.tsx +0 -155
  254. package/src/routes/dash/settings.tsx +0 -93
  255. package/src/routes/feed/rss.ts +0 -119
  256. package/src/routes/feed/sitemap.ts +0 -75
  257. package/src/routes/pages/archive.tsx +0 -223
  258. package/src/routes/pages/collection.tsx +0 -79
  259. package/src/routes/pages/home.tsx +0 -93
  260. package/src/routes/pages/page.tsx +0 -64
  261. package/src/routes/pages/post.tsx +0 -81
  262. package/src/routes/pages/search.tsx +0 -162
  263. package/src/services/collection.ts +0 -180
  264. package/src/services/index.ts +0 -40
  265. package/src/services/media.ts +0 -97
  266. package/src/services/post.ts +0 -279
  267. package/src/services/redirect.ts +0 -74
  268. package/src/services/search.ts +0 -117
  269. package/src/services/settings.ts +0 -76
  270. package/src/theme/components/ActionButtons.tsx +0 -98
  271. package/src/theme/components/CrudPageHeader.tsx +0 -48
  272. package/src/theme/components/DangerZone.tsx +0 -77
  273. package/src/theme/components/EmptyState.tsx +0 -56
  274. package/src/theme/components/ListItemRow.tsx +0 -24
  275. package/src/theme/components/PageForm.tsx +0 -114
  276. package/src/theme/components/Pagination.tsx +0 -196
  277. package/src/theme/components/PostForm.tsx +0 -122
  278. package/src/theme/components/PostList.tsx +0 -68
  279. package/src/theme/components/ThreadView.tsx +0 -118
  280. package/src/theme/components/TypeBadge.tsx +0 -28
  281. package/src/theme/components/VisibilityBadge.tsx +0 -33
  282. package/src/theme/layouts/BaseLayout.tsx +0 -49
  283. package/src/theme/layouts/DashLayout.tsx +0 -108
  284. package/src/theme/styles/main.css +0 -52
  285. package/src/types.ts +0 -222
  286. package/tsconfig.json +0 -16
  287. package/vite.config.ts +0 -82
  288. package/wrangler.toml +0 -21
@@ -0,0 +1,589 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
2
+ /**
3
+ * Dashboard Media Routes
4
+ *
5
+ * Media management with Datastar-powered uploads.
6
+ * Uses SSE for real-time UI updates without page reloads.
7
+ */ import { Hono } from "hono";
8
+ import { useLingui } from "../../i18n/index.js";
9
+ import { DashLayout } from "../../theme/layouts/index.js";
10
+ import { EmptyState, DangerZone } from "../../theme/components/index.js";
11
+ import * as time from "../../lib/time.js";
12
+ import { getMediaUrl, getImageUrl } from "../../lib/image.js";
13
+ import { getAssets } from "../../lib/assets.js";
14
+ export const mediaRoutes = new Hono();
15
+ /**
16
+ * Format file size for display
17
+ */ function formatSize(bytes) {
18
+ if (bytes < 1024) return `${bytes} B`;
19
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
20
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
21
+ }
22
+ /**
23
+ * Media card component for the grid
24
+ */ function MediaCard({ media, r2PublicUrl, imageTransformUrl }) {
25
+ const fullUrl = getMediaUrl(media.id, media.r2Key, r2PublicUrl);
26
+ const thumbnailUrl = getImageUrl(fullUrl, imageTransformUrl, {
27
+ width: 300,
28
+ quality: 80,
29
+ format: "auto",
30
+ fit: "cover"
31
+ });
32
+ const isImage = media.mimeType.startsWith("image/");
33
+ return /*#__PURE__*/ _jsxs("div", {
34
+ class: "group relative",
35
+ "data-media-id": media.id,
36
+ children: [
37
+ isImage ? /*#__PURE__*/ _jsx("button", {
38
+ type: "button",
39
+ class: "block w-full aspect-square bg-muted rounded-lg overflow-hidden border hover:border-primary cursor-pointer",
40
+ onclick: `document.getElementById('lightbox-img').src = '${fullUrl}'; document.getElementById('lightbox').showModal()`,
41
+ children: /*#__PURE__*/ _jsx("img", {
42
+ src: thumbnailUrl,
43
+ alt: media.alt || media.originalName,
44
+ class: "w-full h-full object-cover",
45
+ loading: "lazy"
46
+ })
47
+ }) : /*#__PURE__*/ _jsx("a", {
48
+ href: `/dash/media/${media.id}`,
49
+ class: "block aspect-square bg-muted rounded-lg overflow-hidden border hover:border-primary",
50
+ children: /*#__PURE__*/ _jsx("div", {
51
+ class: "w-full h-full flex items-center justify-center text-muted-foreground",
52
+ children: /*#__PURE__*/ _jsx("span", {
53
+ class: "text-xs",
54
+ children: media.mimeType
55
+ })
56
+ })
57
+ }),
58
+ /*#__PURE__*/ _jsx("a", {
59
+ href: `/dash/media/${media.id}`,
60
+ class: "block mt-2 text-xs truncate hover:underline",
61
+ title: media.originalName,
62
+ children: media.originalName
63
+ }),
64
+ /*#__PURE__*/ _jsx("div", {
65
+ class: "text-xs text-muted-foreground",
66
+ children: formatSize(media.size)
67
+ })
68
+ ]
69
+ });
70
+ }
71
+ /**
72
+ * Media list page content
73
+ *
74
+ * Uses plain JavaScript for upload state management (more reliable than Datastar signals
75
+ * for complex async flows like file uploads with SSE responses).
76
+ */ function MediaListContent({ mediaList, r2PublicUrl, imageTransformUrl, imageProcessorUrl }) {
77
+ const { t } = useLingui();
78
+ const processingText = t({
79
+ message: "Processing...",
80
+ comment: "@context: Upload status - processing"
81
+ });
82
+ const uploadingText = t({
83
+ message: "Uploading...",
84
+ comment: "@context: Upload status - uploading"
85
+ });
86
+ const uploadText = t({
87
+ message: "Upload",
88
+ comment: "@context: Button to upload media file"
89
+ });
90
+ const errorText = t({
91
+ message: "Upload failed. Please try again.",
92
+ comment: "@context: Upload error message"
93
+ });
94
+ // Plain JavaScript upload handler - shows progress in the list
95
+ const uploadScript = `
96
+ async function handleMediaUpload(input) {
97
+ if (!input.files || !input.files[0]) return;
98
+
99
+ const file = input.files[0];
100
+ const errorBox = document.getElementById('upload-error');
101
+ errorBox.classList.add('hidden');
102
+
103
+ // Ensure grid exists (remove empty state if needed)
104
+ let grid = document.getElementById('media-grid');
105
+ if (!grid) {
106
+ document.getElementById('empty-state')?.remove();
107
+ grid = document.createElement('div');
108
+ grid.id = 'media-grid';
109
+ grid.className = 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4';
110
+ document.getElementById('media-content').appendChild(grid);
111
+ }
112
+
113
+ // Create placeholder card showing progress
114
+ const placeholder = document.createElement('div');
115
+ placeholder.id = 'upload-placeholder';
116
+ placeholder.className = 'group relative';
117
+ placeholder.innerHTML = \`
118
+ <div class="aspect-square bg-muted rounded-lg overflow-hidden border flex items-center justify-center">
119
+ <div class="text-center px-2">
120
+ <svg class="animate-spin h-6 w-6 text-muted-foreground mx-auto mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
121
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
122
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
123
+ </svg>
124
+ <span id="upload-status" class="text-xs text-muted-foreground">${processingText}</span>
125
+ </div>
126
+ </div>
127
+ <div class="mt-2 text-xs truncate" title="\${file.name}">\${file.name}</div>
128
+ <div class="text-xs text-muted-foreground">\${formatFileSize(file.size)}</div>
129
+ \`;
130
+ grid.prepend(placeholder);
131
+
132
+ try {
133
+ if (typeof ImageProcessor === 'undefined') {
134
+ throw new Error('ImageProcessor not loaded');
135
+ }
136
+
137
+ // Process image client-side
138
+ const processed = await ImageProcessor.processToFile(file);
139
+ document.getElementById('upload-status').textContent = '${uploadingText}';
140
+
141
+ // Upload with SSE response
142
+ const fd = new FormData();
143
+ fd.append('file', processed);
144
+
145
+ const response = await fetch('/api/upload', {
146
+ method: 'POST',
147
+ body: fd,
148
+ headers: { 'Accept': 'text/event-stream' }
149
+ });
150
+
151
+ if (!response.ok) throw new Error('Upload failed: ' + response.status);
152
+
153
+ // Parse SSE stream - will replace placeholder with real card
154
+ const reader = response.body.getReader();
155
+ const decoder = new TextDecoder();
156
+ let buffer = '';
157
+
158
+ while (true) {
159
+ const { done, value } = await reader.read();
160
+ if (done) break;
161
+
162
+ buffer += decoder.decode(value, { stream: true });
163
+ const events = buffer.split('\\n\\n');
164
+ buffer = events.pop() || '';
165
+
166
+ for (const event of events) {
167
+ if (!event.trim()) continue;
168
+ processSSEEvent(event);
169
+ }
170
+ }
171
+
172
+ } catch (err) {
173
+ console.error('Upload error:', err);
174
+ // Show error in placeholder
175
+ placeholder.innerHTML = \`
176
+ <div class="aspect-square bg-destructive/10 rounded-lg overflow-hidden border border-destructive flex items-center justify-center">
177
+ <div class="text-center px-2">
178
+ <span class="text-xs text-destructive">\${err.message || '${errorText}'}</span>
179
+ </div>
180
+ </div>
181
+ <div class="mt-2 text-xs truncate text-destructive">\${file.name}</div>
182
+ <button type="button" class="text-xs text-muted-foreground hover:underline" onclick="this.closest('.group').remove()">Remove</button>
183
+ \`;
184
+ }
185
+
186
+ input.value = '';
187
+ }
188
+
189
+ function formatFileSize(bytes) {
190
+ if (bytes < 1024) return bytes + ' B';
191
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
192
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
193
+ }
194
+
195
+ function processSSEEvent(event) {
196
+ const lines = event.split('\\n');
197
+ let eventType = '';
198
+ const data = {};
199
+ let elementsLines = [];
200
+ let inElements = false;
201
+
202
+ for (const line of lines) {
203
+ if (line.startsWith('event: ')) {
204
+ eventType = line.slice(7);
205
+ } else if (line.startsWith('data: ')) {
206
+ const content = line.slice(6);
207
+ if (content.startsWith('mode ')) {
208
+ data.mode = content.slice(5);
209
+ inElements = false;
210
+ } else if (content.startsWith('selector ')) {
211
+ data.selector = content.slice(9);
212
+ inElements = false;
213
+ } else if (content.startsWith('elements ')) {
214
+ elementsLines = [content.slice(9)];
215
+ inElements = true;
216
+ } else if (inElements) {
217
+ // Continuation of elements content
218
+ elementsLines.push(content);
219
+ }
220
+ }
221
+ }
222
+
223
+ if (elementsLines.length > 0) {
224
+ data.elements = elementsLines.join('\\n');
225
+ }
226
+
227
+ if (eventType === 'datastar-patch-elements') {
228
+ if (data.mode === 'remove' && data.selector) {
229
+ document.querySelector(data.selector)?.remove();
230
+ } else if (data.mode === 'outer' && data.selector && data.elements) {
231
+ // Replace element entirely (used for placeholder -> real card)
232
+ const target = document.querySelector(data.selector);
233
+ if (target) {
234
+ const temp = document.createElement('div');
235
+ temp.innerHTML = data.elements;
236
+ const newElement = temp.firstElementChild;
237
+ if (newElement) {
238
+ target.replaceWith(newElement);
239
+ if (window.Datastar) Datastar.apply(newElement);
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }
245
+ `.trim();
246
+ return /*#__PURE__*/ _jsxs(_Fragment, {
247
+ children: [
248
+ /*#__PURE__*/ _jsx("script", {
249
+ src: imageProcessorUrl
250
+ }),
251
+ /*#__PURE__*/ _jsx("script", {
252
+ dangerouslySetInnerHTML: {
253
+ __html: uploadScript
254
+ }
255
+ }),
256
+ /*#__PURE__*/ _jsxs("div", {
257
+ class: "flex items-center justify-between mb-6",
258
+ children: [
259
+ /*#__PURE__*/ _jsx("h1", {
260
+ class: "text-2xl font-semibold",
261
+ children: t({
262
+ message: "Media",
263
+ comment: "@context: Media main heading"
264
+ })
265
+ }),
266
+ /*#__PURE__*/ _jsxs("label", {
267
+ class: "btn cursor-pointer",
268
+ children: [
269
+ /*#__PURE__*/ _jsx("span", {
270
+ children: uploadText
271
+ }),
272
+ /*#__PURE__*/ _jsx("input", {
273
+ type: "file",
274
+ class: "hidden",
275
+ accept: "image/*",
276
+ onchange: "handleMediaUpload(this)"
277
+ })
278
+ ]
279
+ })
280
+ ]
281
+ }),
282
+ /*#__PURE__*/ _jsx("div", {
283
+ id: "upload-error",
284
+ class: "hidden"
285
+ }),
286
+ /*#__PURE__*/ _jsx("div", {
287
+ class: "card mb-6",
288
+ children: /*#__PURE__*/ _jsx("section", {
289
+ class: "text-sm text-muted-foreground",
290
+ children: /*#__PURE__*/ _jsx("p", {
291
+ children: t({
292
+ message: "Images are automatically optimized: resized to max 1920px, converted to WebP, and metadata stripped.",
293
+ comment: "@context: Media upload instructions - auto optimization"
294
+ })
295
+ })
296
+ })
297
+ }),
298
+ /*#__PURE__*/ _jsx("div", {
299
+ id: "media-content",
300
+ children: mediaList.length === 0 ? /*#__PURE__*/ _jsx("div", {
301
+ id: "empty-state",
302
+ children: /*#__PURE__*/ _jsx(EmptyState, {
303
+ message: t({
304
+ message: "No media uploaded yet.",
305
+ comment: "@context: Empty state message when no media exists"
306
+ })
307
+ })
308
+ }) : /*#__PURE__*/ _jsx("div", {
309
+ id: "media-grid",
310
+ class: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4",
311
+ children: mediaList.map((m)=>/*#__PURE__*/ _jsx(MediaCard, {
312
+ media: m,
313
+ r2PublicUrl: r2PublicUrl,
314
+ imageTransformUrl: imageTransformUrl
315
+ }, m.id))
316
+ })
317
+ }),
318
+ /*#__PURE__*/ _jsx("dialog", {
319
+ id: "lightbox",
320
+ class: "p-0 m-auto bg-transparent backdrop:bg-black/80",
321
+ onclick: "event.target === this && this.close()",
322
+ children: /*#__PURE__*/ _jsx("img", {
323
+ id: "lightbox-img",
324
+ src: "",
325
+ alt: "",
326
+ class: "max-w-[90vw] max-h-[90vh] object-contain rounded-lg"
327
+ })
328
+ })
329
+ ]
330
+ });
331
+ }
332
+ /**
333
+ * View single media content
334
+ */ function ViewMediaContent({ media, r2PublicUrl, imageTransformUrl }) {
335
+ const { t } = useLingui();
336
+ const url = getMediaUrl(media.id, media.r2Key, r2PublicUrl);
337
+ const thumbnailUrl = getImageUrl(url, imageTransformUrl, {
338
+ width: 600,
339
+ quality: 85,
340
+ format: "auto"
341
+ });
342
+ const isImage = media.mimeType.startsWith("image/");
343
+ return /*#__PURE__*/ _jsxs(_Fragment, {
344
+ children: [
345
+ /*#__PURE__*/ _jsxs("div", {
346
+ class: "flex items-center justify-between mb-6",
347
+ children: [
348
+ /*#__PURE__*/ _jsxs("div", {
349
+ children: [
350
+ /*#__PURE__*/ _jsx("h1", {
351
+ class: "text-2xl font-semibold",
352
+ children: media.originalName
353
+ }),
354
+ /*#__PURE__*/ _jsxs("p", {
355
+ class: "text-muted-foreground mt-1",
356
+ children: [
357
+ formatSize(media.size),
358
+ " · ",
359
+ media.mimeType,
360
+ " · ",
361
+ time.formatDate(media.createdAt)
362
+ ]
363
+ })
364
+ ]
365
+ }),
366
+ /*#__PURE__*/ _jsx("a", {
367
+ href: "/dash/media",
368
+ class: "btn-outline",
369
+ children: t({
370
+ message: "Back",
371
+ comment: "@context: Button to go back to media list"
372
+ })
373
+ })
374
+ ]
375
+ }),
376
+ /*#__PURE__*/ _jsxs("div", {
377
+ class: "grid gap-6 md:grid-cols-2",
378
+ children: [
379
+ /*#__PURE__*/ _jsxs("div", {
380
+ class: "card",
381
+ children: [
382
+ /*#__PURE__*/ _jsx("header", {
383
+ children: /*#__PURE__*/ _jsx("h2", {
384
+ children: t({
385
+ message: "Preview",
386
+ comment: "@context: Media detail section - preview"
387
+ })
388
+ })
389
+ }),
390
+ /*#__PURE__*/ _jsx("section", {
391
+ children: isImage ? /*#__PURE__*/ _jsxs(_Fragment, {
392
+ children: [
393
+ /*#__PURE__*/ _jsx("button", {
394
+ type: "button",
395
+ class: "cursor-pointer",
396
+ onclick: `document.getElementById('lightbox-img').src = '${url}'; document.getElementById('lightbox').showModal()`,
397
+ children: /*#__PURE__*/ _jsx("img", {
398
+ src: thumbnailUrl,
399
+ alt: media.alt || media.originalName,
400
+ class: "max-w-full rounded-lg hover:opacity-90 transition-opacity"
401
+ })
402
+ }),
403
+ /*#__PURE__*/ _jsx("p", {
404
+ class: "text-xs text-muted-foreground mt-2",
405
+ children: t({
406
+ message: "Click image to view full size",
407
+ comment: "@context: Hint to click image for lightbox"
408
+ })
409
+ })
410
+ ]
411
+ }) : /*#__PURE__*/ _jsx("div", {
412
+ class: "aspect-video bg-muted rounded-lg flex items-center justify-center text-muted-foreground",
413
+ children: /*#__PURE__*/ _jsx("span", {
414
+ children: media.mimeType
415
+ })
416
+ })
417
+ })
418
+ ]
419
+ }),
420
+ /*#__PURE__*/ _jsxs("div", {
421
+ class: "space-y-6",
422
+ children: [
423
+ /*#__PURE__*/ _jsxs("div", {
424
+ class: "card",
425
+ children: [
426
+ /*#__PURE__*/ _jsx("header", {
427
+ children: /*#__PURE__*/ _jsx("h2", {
428
+ children: t({
429
+ message: "URL",
430
+ comment: "@context: Media detail section - URL"
431
+ })
432
+ })
433
+ }),
434
+ /*#__PURE__*/ _jsxs("section", {
435
+ children: [
436
+ /*#__PURE__*/ _jsxs("div", {
437
+ class: "flex items-center gap-2",
438
+ children: [
439
+ /*#__PURE__*/ _jsx("input", {
440
+ type: "text",
441
+ class: "input flex-1 font-mono text-sm",
442
+ value: url,
443
+ readonly: true
444
+ }),
445
+ /*#__PURE__*/ _jsx("button", {
446
+ type: "button",
447
+ class: "btn-outline",
448
+ onclick: `navigator.clipboard.writeText('${url}')`,
449
+ children: t({
450
+ message: "Copy",
451
+ comment: "@context: Button to copy URL to clipboard"
452
+ })
453
+ })
454
+ ]
455
+ }),
456
+ /*#__PURE__*/ _jsx("p", {
457
+ class: "text-xs text-muted-foreground mt-2",
458
+ children: t({
459
+ message: "Use this URL to embed the media in your posts.",
460
+ comment: "@context: Media URL helper text"
461
+ })
462
+ })
463
+ ]
464
+ })
465
+ ]
466
+ }),
467
+ /*#__PURE__*/ _jsxs("div", {
468
+ class: "card",
469
+ children: [
470
+ /*#__PURE__*/ _jsx("header", {
471
+ children: /*#__PURE__*/ _jsx("h2", {
472
+ children: t({
473
+ message: "Markdown",
474
+ comment: "@context: Media detail section - Markdown snippet"
475
+ })
476
+ })
477
+ }),
478
+ /*#__PURE__*/ _jsx("section", {
479
+ children: /*#__PURE__*/ _jsxs("div", {
480
+ class: "flex items-center gap-2",
481
+ children: [
482
+ /*#__PURE__*/ _jsx("input", {
483
+ type: "text",
484
+ class: "input flex-1 font-mono text-sm",
485
+ value: `![${media.alt || media.originalName}](${url})`,
486
+ readonly: true
487
+ }),
488
+ /*#__PURE__*/ _jsx("button", {
489
+ type: "button",
490
+ class: "btn-outline",
491
+ onclick: `navigator.clipboard.writeText('![${media.alt || media.originalName}](${url})')`,
492
+ children: t({
493
+ message: "Copy",
494
+ comment: "@context: Button to copy Markdown to clipboard"
495
+ })
496
+ })
497
+ ]
498
+ })
499
+ })
500
+ ]
501
+ }),
502
+ /*#__PURE__*/ _jsx(DangerZone, {
503
+ actionLabel: t({
504
+ message: "Delete Media",
505
+ comment: "@context: Button to delete media"
506
+ }),
507
+ formAction: `/dash/media/${media.id}/delete`,
508
+ confirmMessage: "Are you sure you want to delete this media?",
509
+ description: t({
510
+ message: "Deleting this media will remove it permanently from storage.",
511
+ comment: "@context: Warning message before deleting media"
512
+ })
513
+ })
514
+ ]
515
+ })
516
+ ]
517
+ }),
518
+ isImage && /*#__PURE__*/ _jsx("dialog", {
519
+ id: "lightbox",
520
+ class: "p-0 m-auto bg-transparent backdrop:bg-black/80",
521
+ onclick: "event.target === this && this.close()",
522
+ children: /*#__PURE__*/ _jsx("img", {
523
+ id: "lightbox-img",
524
+ src: "",
525
+ alt: "",
526
+ class: "max-w-[90vw] max-h-[90vh] object-contain rounded-lg"
527
+ })
528
+ })
529
+ ]
530
+ });
531
+ }
532
+ // List media
533
+ mediaRoutes.get("/", async (c)=>{
534
+ const mediaList = await c.var.services.media.list(100);
535
+ const siteName = await c.var.services.settings.get("SITE_NAME") ?? "Jant";
536
+ const r2PublicUrl = c.env.R2_PUBLIC_URL;
537
+ const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
538
+ const assets = getAssets();
539
+ return c.html(/*#__PURE__*/ _jsx(DashLayout, {
540
+ c: c,
541
+ title: "Media",
542
+ siteName: siteName,
543
+ currentPath: "/dash/media",
544
+ children: /*#__PURE__*/ _jsx(MediaListContent, {
545
+ mediaList: mediaList,
546
+ r2PublicUrl: r2PublicUrl,
547
+ imageTransformUrl: imageTransformUrl,
548
+ imageProcessorUrl: assets.imageProcessor
549
+ })
550
+ }));
551
+ });
552
+ // View single media
553
+ mediaRoutes.get("/:id", async (c)=>{
554
+ const id = c.req.param("id");
555
+ const media = await c.var.services.media.getById(id);
556
+ if (!media) return c.notFound();
557
+ const siteName = await c.var.services.settings.get("SITE_NAME") ?? "Jant";
558
+ const r2PublicUrl = c.env.R2_PUBLIC_URL;
559
+ const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
560
+ return c.html(/*#__PURE__*/ _jsx(DashLayout, {
561
+ c: c,
562
+ title: media.originalName,
563
+ siteName: siteName,
564
+ currentPath: "/dash/media",
565
+ children: /*#__PURE__*/ _jsx(ViewMediaContent, {
566
+ media: media,
567
+ r2PublicUrl: r2PublicUrl,
568
+ imageTransformUrl: imageTransformUrl
569
+ })
570
+ }));
571
+ });
572
+ // Delete media
573
+ mediaRoutes.post("/:id/delete", async (c)=>{
574
+ const id = c.req.param("id");
575
+ const media = await c.var.services.media.getById(id);
576
+ if (!media) return c.notFound();
577
+ // Delete from R2
578
+ if (c.env.R2) {
579
+ try {
580
+ await c.env.R2.delete(media.r2Key);
581
+ } catch (err) {
582
+ // eslint-disable-next-line no-console -- Error logging is intentional
583
+ console.error("R2 delete error:", err);
584
+ }
585
+ }
586
+ // Delete from database
587
+ await c.var.services.media.delete(id);
588
+ return c.redirect("/dash/media");
589
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Dashboard Pages Routes
3
+ *
4
+ * Management for custom pages (posts with type="page")
5
+ */
6
+ import { Hono } from "hono";
7
+ import type { Bindings } from "../../types.js";
8
+ import type { AppVariables } from "../../app.js";
9
+ type Env = {
10
+ Bindings: Bindings;
11
+ Variables: AppVariables;
12
+ };
13
+ export declare const pagesRoutes: Hono<Env, import("hono/types").BlankSchema, "/">;
14
+ export {};
15
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../../src/routes/dash/pages.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAQ,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAOjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,WAAW,kDAAkB,CAAC"}