@jant/core 0.2.12 → 0.2.13

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 (146) hide show
  1. package/bin/jant.js +3 -1
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +112 -85
  4. package/dist/auth.d.ts +1 -0
  5. package/dist/auth.d.ts.map +1 -1
  6. package/dist/auth.js +2 -1
  7. package/dist/client.js +1 -1
  8. package/dist/db/schema.d.ts.map +1 -1
  9. package/dist/i18n/context.d.ts.map +1 -1
  10. package/dist/i18n/context.js +0 -3
  11. package/dist/i18n/detect.d.ts +0 -11
  12. package/dist/i18n/detect.d.ts.map +1 -1
  13. package/dist/i18n/detect.js +1 -52
  14. package/dist/i18n/i18n.d.ts +4 -14
  15. package/dist/i18n/i18n.d.ts.map +1 -1
  16. package/dist/i18n/i18n.js +19 -25
  17. package/dist/i18n/index.d.ts +1 -1
  18. package/dist/i18n/index.d.ts.map +1 -1
  19. package/dist/i18n/index.js +1 -1
  20. package/dist/i18n/middleware.d.ts +2 -5
  21. package/dist/i18n/middleware.d.ts.map +1 -1
  22. package/dist/i18n/middleware.js +12 -23
  23. package/dist/lib/constants.d.ts.map +1 -1
  24. package/dist/lib/image.d.ts.map +1 -1
  25. package/dist/lib/schemas.d.ts.map +1 -1
  26. package/dist/lib/sse.d.ts +45 -17
  27. package/dist/lib/sse.d.ts.map +1 -1
  28. package/dist/lib/sse.js +77 -37
  29. package/dist/middleware/auth.d.ts.map +1 -1
  30. package/dist/routes/api/posts.js +0 -1
  31. package/dist/routes/api/upload.js +3 -1
  32. package/dist/routes/dash/collections.d.ts.map +1 -1
  33. package/dist/routes/dash/collections.js +134 -142
  34. package/dist/routes/dash/index.js +25 -26
  35. package/dist/routes/dash/media.d.ts.map +1 -1
  36. package/dist/routes/dash/media.js +60 -56
  37. package/dist/routes/dash/pages.js +64 -66
  38. package/dist/routes/dash/posts.d.ts.map +1 -1
  39. package/dist/routes/dash/posts.js +50 -59
  40. package/dist/routes/dash/redirects.d.ts.map +1 -1
  41. package/dist/routes/dash/redirects.js +63 -60
  42. package/dist/routes/dash/settings.d.ts.map +1 -1
  43. package/dist/routes/dash/settings.js +249 -93
  44. package/dist/routes/feed/rss.js +6 -4
  45. package/dist/routes/pages/archive.js +60 -62
  46. package/dist/routes/pages/collection.js +8 -8
  47. package/dist/routes/pages/home.js +14 -14
  48. package/dist/routes/pages/page.js +7 -6
  49. package/dist/routes/pages/post.js +8 -8
  50. package/dist/routes/pages/search.js +25 -27
  51. package/dist/services/collection.d.ts.map +1 -1
  52. package/dist/services/index.d.ts.map +1 -1
  53. package/dist/services/media.d.ts.map +1 -1
  54. package/dist/services/post.d.ts.map +1 -1
  55. package/dist/services/redirect.d.ts.map +1 -1
  56. package/dist/services/settings.d.ts.map +1 -1
  57. package/dist/theme/components/ActionButtons.d.ts +1 -1
  58. package/dist/theme/components/ActionButtons.d.ts.map +1 -1
  59. package/dist/theme/components/ActionButtons.js +17 -21
  60. package/dist/theme/components/CrudPageHeader.d.ts.map +1 -1
  61. package/dist/theme/components/DangerZone.d.ts.map +1 -1
  62. package/dist/theme/components/DangerZone.js +12 -15
  63. package/dist/theme/components/EmptyState.d.ts.map +1 -1
  64. package/dist/theme/components/PageForm.d.ts.map +1 -1
  65. package/dist/theme/components/PageForm.js +58 -56
  66. package/dist/theme/components/Pagination.d.ts.map +1 -1
  67. package/dist/theme/components/Pagination.js +22 -25
  68. package/dist/theme/components/PostForm.d.ts +0 -1
  69. package/dist/theme/components/PostForm.d.ts.map +1 -1
  70. package/dist/theme/components/PostForm.js +85 -77
  71. package/dist/theme/components/PostList.d.ts.map +1 -1
  72. package/dist/theme/components/PostList.js +17 -17
  73. package/dist/theme/components/ThreadView.d.ts.map +1 -1
  74. package/dist/theme/components/ThreadView.js +15 -18
  75. package/dist/theme/components/TypeBadge.d.ts.map +1 -1
  76. package/dist/theme/components/TypeBadge.js +20 -20
  77. package/dist/theme/components/VisibilityBadge.d.ts.map +1 -1
  78. package/dist/theme/components/VisibilityBadge.js +14 -14
  79. package/dist/theme/components/index.d.ts +1 -1
  80. package/dist/theme/components/index.d.ts.map +1 -1
  81. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
  82. package/dist/theme/layouts/BaseLayout.js +4 -2
  83. package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
  84. package/dist/theme/layouts/DashLayout.js +29 -29
  85. package/dist/types/lingui-react-macro.d.js +9 -0
  86. package/dist/types.d.ts +2 -0
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/vendor/datastar.js +1606 -0
  89. package/package.json +5 -2
  90. package/src/app.tsx +175 -56
  91. package/src/auth.ts +5 -1
  92. package/src/client.ts +1 -1
  93. package/src/db/schema.ts +22 -7
  94. package/src/i18n/EXAMPLES.md +34 -14
  95. package/src/i18n/README.md +19 -9
  96. package/src/i18n/context.tsx +1 -4
  97. package/src/i18n/detect.ts +1 -67
  98. package/src/i18n/i18n.ts +15 -19
  99. package/src/i18n/index.ts +0 -3
  100. package/src/i18n/middleware.ts +12 -24
  101. package/src/lib/constants.ts +2 -1
  102. package/src/lib/image-processor.ts +23 -7
  103. package/src/lib/image.ts +6 -2
  104. package/src/lib/schemas.ts +6 -2
  105. package/src/lib/sse.ts +138 -50
  106. package/src/middleware/auth.ts +6 -2
  107. package/src/routes/api/posts.ts +14 -5
  108. package/src/routes/api/upload.ts +25 -7
  109. package/src/routes/dash/collections.tsx +162 -70
  110. package/src/routes/dash/index.tsx +22 -7
  111. package/src/routes/dash/media.tsx +59 -16
  112. package/src/routes/dash/pages.tsx +102 -44
  113. package/src/routes/dash/posts.tsx +87 -54
  114. package/src/routes/dash/redirects.tsx +74 -26
  115. package/src/routes/dash/settings.tsx +250 -57
  116. package/src/routes/feed/rss.ts +6 -4
  117. package/src/routes/pages/archive.tsx +71 -21
  118. package/src/routes/pages/collection.tsx +21 -6
  119. package/src/routes/pages/home.tsx +30 -9
  120. package/src/routes/pages/page.tsx +14 -5
  121. package/src/routes/pages/post.tsx +21 -7
  122. package/src/routes/pages/search.tsx +42 -11
  123. package/src/services/collection.ts +34 -9
  124. package/src/services/index.ts +4 -1
  125. package/src/services/media.ts +15 -3
  126. package/src/services/post.ts +39 -10
  127. package/src/services/redirect.ts +4 -1
  128. package/src/services/settings.ts +14 -3
  129. package/src/theme/components/ActionButtons.tsx +26 -14
  130. package/src/theme/components/CrudPageHeader.tsx +6 -1
  131. package/src/theme/components/DangerZone.tsx +19 -13
  132. package/src/theme/components/EmptyState.tsx +6 -1
  133. package/src/theme/components/PageForm.tsx +71 -24
  134. package/src/theme/components/Pagination.tsx +26 -8
  135. package/src/theme/components/PostForm.tsx +72 -25
  136. package/src/theme/components/PostList.tsx +16 -5
  137. package/src/theme/components/ThreadView.tsx +25 -7
  138. package/src/theme/components/TypeBadge.tsx +13 -4
  139. package/src/theme/components/VisibilityBadge.tsx +17 -5
  140. package/src/theme/components/index.ts +4 -1
  141. package/src/theme/layouts/BaseLayout.tsx +5 -2
  142. package/src/theme/layouts/DashLayout.tsx +41 -12
  143. package/src/types/lingui-react-macro.d.ts +34 -0
  144. package/src/types.ts +16 -2
  145. package/src/vendor/datastar.js +9 -0
  146. package/src/vendor/datastar.js.map +7 -0
package/src/lib/sse.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Server-Sent Events (SSE) utilities for Datastar
2
+ * Server-Sent Events (SSE) utilities for Datastar v1.0.0-RC.7
3
3
  *
4
- * Provides helpers for streaming SSE responses that Datastar can consume.
5
- * Datastar uses SSE for real-time UI updates without page reloads.
4
+ * Generates SSE events compatible with the Datastar client's expected format.
6
5
  *
7
6
  * @see https://data-star.dev/
8
7
  *
@@ -11,7 +10,8 @@
11
10
  * app.post("/api/example", (c) => {
12
11
  * return sse(c, async (stream) => {
13
12
  * await stream.patchSignals({ loading: false });
14
- * await stream.patchElements("#result", "<div>Done!</div>");
13
+ * await stream.patchElements('<div id="result">Done!</div>');
14
+ * await stream.redirect("/success");
15
15
  * });
16
16
  * });
17
17
  * ```
@@ -20,9 +20,19 @@
20
20
  import type { Context } from "hono";
21
21
 
22
22
  /**
23
- * Patch modes for DOM updates
23
+ * Patch modes for DOM element updates
24
+ *
25
+ * @see https://data-star.dev/reference/action_plugins/backend/sse
24
26
  */
25
- export type PatchMode = "morph" | "inner" | "outer" | "append" | "prepend" | "remove";
27
+ export type PatchMode =
28
+ | "outer"
29
+ | "inner"
30
+ | "replace"
31
+ | "prepend"
32
+ | "append"
33
+ | "before"
34
+ | "after"
35
+ | "remove";
26
36
 
27
37
  /**
28
38
  * SSE stream writer for Datastar events
@@ -32,23 +42,27 @@ export interface SSEStream {
32
42
  * Update reactive signals on the client
33
43
  *
34
44
  * @param signals - Object containing signal values to update
45
+ * @param options - Optional settings (e.g. onlyIfMissing)
35
46
  *
36
47
  * @example
37
48
  * ```ts
38
49
  * await stream.patchSignals({ count: 42, loading: false });
39
50
  * ```
40
51
  */
41
- patchSignals(signals: Record<string, unknown>): Promise<void>;
52
+ patchSignals(
53
+ signals: Record<string, unknown>,
54
+ options?: { onlyIfMissing?: boolean },
55
+ ): void;
42
56
 
43
57
  /**
44
- * Update DOM elements
58
+ * Update DOM elements via patching
45
59
  *
46
60
  * @param html - HTML content (must include element with id for targeting)
47
- * @param options - Optional mode and selector
61
+ * @param options - Optional patch mode, selector, and view transition
48
62
  *
49
63
  * @example
50
64
  * ```ts
51
- * // Replace element with matching id (default: morph)
65
+ * // Outer patch element with matching id (default)
52
66
  * await stream.patchElements('<div id="content">New content</div>');
53
67
  *
54
68
  * // Append to a container
@@ -58,19 +72,56 @@ export interface SSEStream {
58
72
  * });
59
73
  * ```
60
74
  */
61
- patchElements(html: string, options?: { mode?: PatchMode; selector?: string }): Promise<void>;
75
+ patchElements(
76
+ html: string,
77
+ options?: {
78
+ mode?: PatchMode;
79
+ selector?: string;
80
+ useViewTransition?: boolean;
81
+ },
82
+ ): void;
62
83
 
63
84
  /**
64
- * Execute JavaScript on the client
85
+ * Redirect the client to a new URL
65
86
  *
66
- * @param script - JavaScript code to execute
87
+ * Uses patchElements internally to inject a script that navigates the client.
88
+ *
89
+ * @param url - The URL to redirect to
67
90
  *
68
91
  * @example
69
92
  * ```ts
70
- * await stream.executeScript('console.log("Hello from server")');
93
+ * await stream.redirect('/dash/posts');
71
94
  * ```
72
95
  */
73
- executeScript(script: string): Promise<void>;
96
+ redirect(url: string): void;
97
+
98
+ /**
99
+ * Remove elements matching a CSS selector
100
+ *
101
+ * @param selector - CSS selector for elements to remove
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * await stream.remove('#placeholder');
106
+ * ```
107
+ */
108
+ remove(selector: string): void;
109
+ }
110
+
111
+ /**
112
+ * Format a single SSE event string
113
+ *
114
+ * @param eventType - The Datastar event type (e.g. "datastar-patch-elements")
115
+ * @param dataLines - Array of "key value" data lines
116
+ * @returns Formatted SSE event string
117
+ */
118
+ function formatEvent(eventType: string, dataLines: readonly string[]): string {
119
+ let event = `event: ${eventType}\n`;
120
+ for (const line of dataLines) {
121
+ event += `data: ${line}\n`;
122
+ }
123
+ event += "\n";
124
+ return event;
74
125
  }
75
126
 
76
127
  /**
@@ -78,13 +129,13 @@ export interface SSEStream {
78
129
  *
79
130
  * @param c - Hono context
80
131
  * @param handler - Async function that writes to the SSE stream
132
+ * @param options - Optional response options (e.g. headers for cookie forwarding)
81
133
  * @returns Response with SSE content-type
82
134
  *
83
135
  * @example
84
136
  * ```ts
85
137
  * app.post("/api/upload", (c) => {
86
138
  * return sse(c, async (stream) => {
87
- * // Process upload...
88
139
  * await stream.patchSignals({ uploading: false });
89
140
  * await stream.patchElements('<div id="new-item">...</div>', {
90
141
  * mode: 'append',
@@ -92,55 +143,92 @@ export interface SSEStream {
92
143
  * });
93
144
  * });
94
145
  * });
146
+ *
147
+ * // With cookie forwarding (for auth)
148
+ * app.post("/signin", (c) => {
149
+ * return sse(c, async (stream) => {
150
+ * await stream.redirect('/dash');
151
+ * }, { headers: { 'Set-Cookie': cookieValue } });
152
+ * });
95
153
  * ```
96
154
  */
97
- export function sse(c: Context, handler: (stream: SSEStream) => Promise<void>): Response {
155
+ export function sse(
156
+ c: Context,
157
+ handler: (stream: SSEStream) => Promise<void>,
158
+ options?: { headers?: Record<string, string> },
159
+ ): Response {
98
160
  const encoder = new TextEncoder();
99
161
 
100
- const stream = new ReadableStream({
162
+ const body = new ReadableStream({
101
163
  async start(controller) {
102
- const write = (data: string) => {
103
- controller.enqueue(encoder.encode(data));
104
- };
105
-
106
- const sseStream: SSEStream = {
107
- async patchSignals(signals) {
108
- write(`event: datastar-patch-signals\n`);
109
- write(`data: signals ${JSON.stringify(signals)}\n\n`);
164
+ const stream: SSEStream = {
165
+ patchSignals(signals, opts) {
166
+ const dataLines: string[] = [`signals ${JSON.stringify(signals)}`];
167
+ if (opts?.onlyIfMissing) {
168
+ dataLines.push("onlyIfMissing true");
169
+ }
170
+ controller.enqueue(
171
+ encoder.encode(formatEvent("datastar-patch-signals", dataLines)),
172
+ );
110
173
  },
111
174
 
112
- async patchElements(html, options = {}) {
113
- write(`event: datastar-patch-elements\n`);
114
- if (options.mode) {
115
- write(`data: mode ${options.mode}\n`);
175
+ patchElements(html, opts) {
176
+ const dataLines: string[] = [];
177
+ // Each line of HTML gets its own "elements <line>" data line
178
+ for (const line of html.split("\n")) {
179
+ dataLines.push(`elements ${line}`);
116
180
  }
117
- if (options.selector) {
118
- write(`data: selector ${options.selector}\n`);
181
+ if (opts?.mode) {
182
+ dataLines.push(`mode ${opts.mode}`);
119
183
  }
120
- // Escape newlines in HTML for SSE format
121
- const escapedHtml = html.replace(/\n/g, "\ndata: ");
122
- write(`data: elements ${escapedHtml}\n\n`);
184
+ if (opts?.selector) {
185
+ dataLines.push(`selector ${opts.selector}`);
186
+ }
187
+ if (opts?.useViewTransition) {
188
+ dataLines.push("useViewTransition true");
189
+ }
190
+ controller.enqueue(
191
+ encoder.encode(formatEvent("datastar-patch-elements", dataLines)),
192
+ );
193
+ },
194
+
195
+ redirect(url) {
196
+ const escapedUrl = url.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
197
+ const script = `<script data-effect="el.remove()">window.location.href='${escapedUrl}'</script>`;
198
+ const dataLines: string[] = [
199
+ `elements ${script}`,
200
+ "mode append",
201
+ "selector body",
202
+ ];
203
+ controller.enqueue(
204
+ encoder.encode(formatEvent("datastar-patch-elements", dataLines)),
205
+ );
123
206
  },
124
207
 
125
- async executeScript(script) {
126
- write(`event: datastar-execute-script\n`);
127
- write(`data: script ${script}\n\n`);
208
+ remove(selector) {
209
+ controller.enqueue(
210
+ encoder.encode(
211
+ formatEvent("datastar-patch-elements", [
212
+ "elements ",
213
+ `mode remove`,
214
+ `selector ${selector}`,
215
+ ]),
216
+ ),
217
+ );
128
218
  },
129
219
  };
130
220
 
131
- try {
132
- await handler(sseStream);
133
- } finally {
134
- controller.close();
135
- }
221
+ await handler(stream);
222
+ controller.close();
136
223
  },
137
224
  });
138
225
 
139
- return new Response(stream, {
140
- headers: {
141
- "Content-Type": "text/event-stream",
142
- "Cache-Control": "no-cache",
143
- Connection: "keep-alive",
144
- },
145
- });
226
+ const headers: Record<string, string> = {
227
+ "Content-Type": "text/event-stream",
228
+ "Cache-Control": "no-cache",
229
+ Connection: "keep-alive",
230
+ ...options?.headers,
231
+ };
232
+
233
+ return new Response(body, { headers });
146
234
  }
@@ -21,7 +21,9 @@ export function requireAuth(redirectTo = "/signin"): MiddlewareHandler<Env> {
21
21
  }
22
22
 
23
23
  try {
24
- const session = await c.var.auth.api.getSession({ headers: c.req.raw.headers });
24
+ const session = await c.var.auth.api.getSession({
25
+ headers: c.req.raw.headers,
26
+ });
25
27
 
26
28
  if (!session?.user) {
27
29
  return c.redirect(redirectTo);
@@ -45,7 +47,9 @@ export function requireAuthApi(): MiddlewareHandler<Env> {
45
47
  }
46
48
 
47
49
  try {
48
- const session = await c.var.auth.api.getSession({ headers: c.req.raw.headers });
50
+ const session = await c.var.auth.api.getSession({
51
+ headers: c.req.raw.headers,
52
+ });
49
53
 
50
54
  if (!session?.user) {
51
55
  return c.json({ error: "Unauthorized" }, 401);
@@ -32,8 +32,9 @@ postsApiRoutes.get("/", async (c) => {
32
32
  ...p,
33
33
  sqid: sqid.encode(p.id),
34
34
  })),
35
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Array length check guarantees element exists
36
- nextCursor: posts.length === limit ? sqid.encode(posts[posts.length - 1]!.id) : null,
35
+
36
+ nextCursor:
37
+ posts.length === limit ? sqid.encode(posts[posts.length - 1]!.id) : null,
37
38
  });
38
39
  });
39
40
 
@@ -55,7 +56,10 @@ postsApiRoutes.post("/", requireAuthApi(), async (c) => {
55
56
  // Validate request body
56
57
  const parseResult = CreatePostSchema.safeParse(rawBody);
57
58
  if (!parseResult.success) {
58
- return c.json({ error: "Validation failed", details: parseResult.error.flatten() }, 400);
59
+ return c.json(
60
+ { error: "Validation failed", details: parseResult.error.flatten() },
61
+ 400,
62
+ );
59
63
  }
60
64
 
61
65
  const body = parseResult.data;
@@ -68,7 +72,9 @@ postsApiRoutes.post("/", requireAuthApi(), async (c) => {
68
72
  sourceUrl: body.sourceUrl || undefined,
69
73
  sourceName: body.sourceName,
70
74
  path: body.path || undefined,
71
- replyToId: body.replyToId ? (sqid.decode(body.replyToId) ?? undefined) : undefined,
75
+ replyToId: body.replyToId
76
+ ? (sqid.decode(body.replyToId) ?? undefined)
77
+ : undefined,
72
78
  publishedAt: body.publishedAt,
73
79
  });
74
80
 
@@ -85,7 +91,10 @@ postsApiRoutes.put("/:id", requireAuthApi(), async (c) => {
85
91
  // Validate request body
86
92
  const parseResult = UpdatePostSchema.safeParse(rawBody);
87
93
  if (!parseResult.success) {
88
- return c.json({ error: "Validation failed", details: parseResult.error.flatten() }, 400);
94
+ return c.json(
95
+ { error: "Validation failed", details: parseResult.error.flatten() },
96
+ 400,
97
+ );
89
98
  }
90
99
 
91
100
  const body = parseResult.data;
@@ -33,7 +33,7 @@ function renderMediaCard(
33
33
  size: number;
34
34
  },
35
35
  r2PublicUrl?: string,
36
- imageTransformUrl?: string
36
+ imageTransformUrl?: string,
37
37
  ): string {
38
38
  const fullUrl = getMediaUrl(media.id, media.r2Key, r2PublicUrl);
39
39
  const thumbnailUrl = getImageUrl(fullUrl, imageTransformUrl, {
@@ -79,7 +79,9 @@ function renderMediaCard(
79
79
  href="/dash/media/${media.id}"
80
80
  class="block aspect-square bg-muted rounded-lg overflow-hidden border hover:border-primary"
81
81
  >
82
- <div class="w-full h-full flex items-center justify-center text-muted-foreground">
82
+ <div
83
+ class="w-full h-full flex items-center justify-center text-muted-foreground"
84
+ >
83
85
  <span class="text-xs">${media.mimeType}</span>
84
86
  </div>
85
87
  </a>
@@ -104,7 +106,9 @@ function formatSize(bytes: number): string {
104
106
  /**
105
107
  * Check if request wants SSE response (from Datastar)
106
108
  */
107
- function wantsSSE(c: { req: { header: (name: string) => string | undefined } }): boolean {
109
+ function wantsSSE(c: {
110
+ req: { header: (name: string) => string | undefined };
111
+ }): boolean {
108
112
  const accept = c.req.header("accept") || "";
109
113
  return accept.includes("text/event-stream");
110
114
  }
@@ -114,7 +118,9 @@ uploadApiRoutes.post("/", async (c) => {
114
118
  if (!c.env.R2) {
115
119
  if (wantsSSE(c)) {
116
120
  return sse(c, async (stream) => {
117
- await stream.patchSignals({ _uploadError: "R2 storage not configured" });
121
+ await stream.patchSignals({
122
+ _uploadError: "R2 storage not configured",
123
+ });
118
124
  });
119
125
  }
120
126
  return c.json({ error: "R2 storage not configured" }, 500);
@@ -133,7 +139,13 @@ uploadApiRoutes.post("/", async (c) => {
133
139
  }
134
140
 
135
141
  // Validate file type
136
- const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"];
142
+ const allowedTypes = [
143
+ "image/jpeg",
144
+ "image/png",
145
+ "image/gif",
146
+ "image/webp",
147
+ "image/svg+xml",
148
+ ];
137
149
  if (!allowedTypes.includes(file.type)) {
138
150
  if (wantsSSE(c)) {
139
151
  return sse(c, async (stream) => {
@@ -148,7 +160,9 @@ uploadApiRoutes.post("/", async (c) => {
148
160
  if (file.size > maxSize) {
149
161
  if (wantsSSE(c)) {
150
162
  return sse(c, async (stream) => {
151
- await stream.patchSignals({ _uploadError: "File too large (max 10MB)" });
163
+ await stream.patchSignals({
164
+ _uploadError: "File too large (max 10MB)",
165
+ });
152
166
  });
153
167
  }
154
168
  return c.json({ error: "File too large (max 10MB)" }, 400);
@@ -180,7 +194,11 @@ uploadApiRoutes.post("/", async (c) => {
180
194
 
181
195
  // SSE response for Datastar
182
196
  if (wantsSSE(c)) {
183
- const cardHtml = renderMediaCard(media, c.env.R2_PUBLIC_URL, c.env.IMAGE_TRANSFORM_URL);
197
+ const cardHtml = renderMediaCard(
198
+ media,
199
+ c.env.R2_PUBLIC_URL,
200
+ c.env.IMAGE_TRANSFORM_URL,
201
+ );
184
202
 
185
203
  return sse(c, async (stream) => {
186
204
  // Replace placeholder with real media card