@jant/core 0.2.11 → 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 (153) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +112 -85
  3. package/dist/auth.d.ts +1 -0
  4. package/dist/auth.d.ts.map +1 -1
  5. package/dist/auth.js +2 -1
  6. package/dist/client.js +1 -1
  7. package/dist/db/schema.d.ts.map +1 -1
  8. package/dist/i18n/context.d.ts.map +1 -1
  9. package/dist/i18n/context.js +0 -3
  10. package/dist/i18n/detect.d.ts +0 -11
  11. package/dist/i18n/detect.d.ts.map +1 -1
  12. package/dist/i18n/detect.js +1 -52
  13. package/dist/i18n/i18n.d.ts +4 -14
  14. package/dist/i18n/i18n.d.ts.map +1 -1
  15. package/dist/i18n/i18n.js +19 -25
  16. package/dist/i18n/index.d.ts +1 -1
  17. package/dist/i18n/index.d.ts.map +1 -1
  18. package/dist/i18n/index.js +1 -1
  19. package/dist/i18n/middleware.d.ts +2 -5
  20. package/dist/i18n/middleware.d.ts.map +1 -1
  21. package/dist/i18n/middleware.js +12 -23
  22. package/dist/lib/constants.d.ts.map +1 -1
  23. package/dist/lib/schemas.d.ts.map +1 -1
  24. package/dist/lib/sse.d.ts +45 -17
  25. package/dist/lib/sse.d.ts.map +1 -1
  26. package/dist/lib/sse.js +77 -37
  27. package/dist/middleware/auth.d.ts.map +1 -1
  28. package/dist/routes/api/posts.js +0 -1
  29. package/dist/routes/api/upload.js +13 -3
  30. package/dist/routes/dash/collections.d.ts.map +1 -1
  31. package/dist/routes/dash/collections.js +134 -142
  32. package/dist/routes/dash/index.js +25 -25
  33. package/dist/routes/dash/media.d.ts.map +1 -1
  34. package/dist/routes/dash/media.js +60 -56
  35. package/dist/routes/dash/pages.d.ts.map +1 -1
  36. package/dist/routes/dash/pages.js +64 -66
  37. package/dist/routes/dash/posts.d.ts.map +1 -1
  38. package/dist/routes/dash/posts.js +50 -59
  39. package/dist/routes/dash/redirects.d.ts.map +1 -1
  40. package/dist/routes/dash/redirects.js +63 -60
  41. package/dist/routes/dash/settings.d.ts.map +1 -1
  42. package/dist/routes/dash/settings.js +249 -93
  43. package/dist/routes/feed/rss.js +6 -4
  44. package/dist/routes/pages/archive.js +60 -62
  45. package/dist/routes/pages/collection.js +8 -8
  46. package/dist/routes/pages/home.js +14 -14
  47. package/dist/routes/pages/page.js +7 -6
  48. package/dist/routes/pages/post.js +8 -8
  49. package/dist/routes/pages/search.js +25 -27
  50. package/dist/services/collection.d.ts.map +1 -1
  51. package/dist/services/index.d.ts.map +1 -1
  52. package/dist/services/media.d.ts.map +1 -1
  53. package/dist/services/post.d.ts.map +1 -1
  54. package/dist/services/redirect.d.ts.map +1 -1
  55. package/dist/services/settings.d.ts.map +1 -1
  56. package/dist/theme/components/ActionButtons.d.ts +1 -1
  57. package/dist/theme/components/ActionButtons.d.ts.map +1 -1
  58. package/dist/theme/components/ActionButtons.js +17 -21
  59. package/dist/theme/components/CrudPageHeader.d.ts.map +1 -1
  60. package/dist/theme/components/DangerZone.d.ts.map +1 -1
  61. package/dist/theme/components/DangerZone.js +12 -15
  62. package/dist/theme/components/EmptyState.d.ts.map +1 -1
  63. package/dist/theme/components/PageForm.d.ts.map +1 -1
  64. package/dist/theme/components/PageForm.js +58 -56
  65. package/dist/theme/components/Pagination.d.ts.map +1 -1
  66. package/dist/theme/components/Pagination.js +22 -25
  67. package/dist/theme/components/PostForm.d.ts +0 -1
  68. package/dist/theme/components/PostForm.d.ts.map +1 -1
  69. package/dist/theme/components/PostForm.js +85 -77
  70. package/dist/theme/components/PostList.d.ts.map +1 -1
  71. package/dist/theme/components/PostList.js +17 -17
  72. package/dist/theme/components/ThreadView.d.ts.map +1 -1
  73. package/dist/theme/components/ThreadView.js +15 -18
  74. package/dist/theme/components/TypeBadge.d.ts.map +1 -1
  75. package/dist/theme/components/TypeBadge.js +20 -20
  76. package/dist/theme/components/VisibilityBadge.d.ts.map +1 -1
  77. package/dist/theme/components/VisibilityBadge.js +14 -14
  78. package/dist/theme/components/index.d.ts +2 -2
  79. package/dist/theme/components/index.d.ts.map +1 -1
  80. package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
  81. package/dist/theme/layouts/BaseLayout.js +4 -2
  82. package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
  83. package/dist/theme/layouts/DashLayout.js +29 -29
  84. package/dist/types/lingui-react-macro.d.js +9 -0
  85. package/dist/types.d.ts +2 -0
  86. package/dist/types.d.ts.map +1 -1
  87. package/dist/vendor/datastar.js +1606 -0
  88. package/package.json +7 -15
  89. package/src/app.tsx +222 -59
  90. package/src/auth.ts +5 -1
  91. package/src/client.ts +1 -1
  92. package/src/db/migrations/meta/0000_snapshot.json +16 -47
  93. package/src/db/migrations/meta/_journal.json +1 -1
  94. package/src/db/schema.ts +22 -7
  95. package/src/i18n/EXAMPLES.md +45 -23
  96. package/src/i18n/README.md +39 -25
  97. package/src/i18n/context.tsx +1 -4
  98. package/src/i18n/detect.ts +1 -67
  99. package/src/i18n/i18n.ts +15 -19
  100. package/src/i18n/index.ts +0 -3
  101. package/src/i18n/middleware.ts +12 -24
  102. package/src/lib/constants.ts +2 -1
  103. package/src/lib/image-processor.ts +14 -6
  104. package/src/lib/image.ts +2 -2
  105. package/src/lib/schemas.ts +7 -3
  106. package/src/lib/sse.ts +133 -51
  107. package/src/middleware/auth.ts +6 -2
  108. package/src/routes/api/posts.ts +9 -9
  109. package/src/routes/api/upload.ts +39 -10
  110. package/src/routes/dash/collections.tsx +249 -81
  111. package/src/routes/dash/index.tsx +22 -7
  112. package/src/routes/dash/media.tsx +94 -24
  113. package/src/routes/dash/pages.tsx +132 -54
  114. package/src/routes/dash/posts.tsx +99 -57
  115. package/src/routes/dash/redirects.tsx +117 -36
  116. package/src/routes/dash/settings.tsx +268 -55
  117. package/src/routes/feed/rss.ts +6 -4
  118. package/src/routes/pages/archive.tsx +78 -24
  119. package/src/routes/pages/collection.tsx +32 -8
  120. package/src/routes/pages/home.tsx +38 -10
  121. package/src/routes/pages/page.tsx +15 -5
  122. package/src/routes/pages/post.tsx +17 -6
  123. package/src/routes/pages/search.tsx +50 -13
  124. package/src/services/collection.ts +29 -8
  125. package/src/services/index.ts +4 -1
  126. package/src/services/media.ts +15 -3
  127. package/src/services/post.ts +37 -10
  128. package/src/services/redirect.ts +4 -1
  129. package/src/services/settings.ts +14 -3
  130. package/src/theme/components/ActionButtons.tsx +31 -15
  131. package/src/theme/components/CrudPageHeader.tsx +3 -4
  132. package/src/theme/components/DangerZone.tsx +19 -13
  133. package/src/theme/components/EmptyState.tsx +1 -5
  134. package/src/theme/components/PageForm.tsx +80 -25
  135. package/src/theme/components/Pagination.tsx +34 -31
  136. package/src/theme/components/PostForm.tsx +91 -27
  137. package/src/theme/components/PostList.tsx +23 -6
  138. package/src/theme/components/ThreadView.tsx +25 -10
  139. package/src/theme/components/TypeBadge.tsx +13 -4
  140. package/src/theme/components/VisibilityBadge.tsx +17 -5
  141. package/src/theme/components/index.ts +12 -2
  142. package/src/theme/layouts/BaseLayout.tsx +6 -5
  143. package/src/theme/layouts/DashLayout.tsx +71 -18
  144. package/src/types/lingui-react-macro.d.ts +34 -0
  145. package/src/types.ts +16 -4
  146. package/src/vendor/datastar.js +9 -0
  147. package/src/vendor/datastar.js.map +7 -0
  148. package/dist/plugin.d.ts +0 -3
  149. package/dist/plugin.d.ts.map +0 -1
  150. package/dist/plugin.js +0 -20
  151. package/dist/tailwind.d.ts +0 -12
  152. package/dist/tailwind.d.ts.map +0 -1
  153. package/dist/tailwind.js +0 -15
@@ -6,13 +6,14 @@
6
6
  */
7
7
 
8
8
  import { Hono } from "hono";
9
- import { useLingui } from "../../i18n/index.js";
9
+ import { useLingui } from "@lingui/react/macro";
10
10
  import type { Bindings, Media } from "../../types.js";
11
11
  import type { AppVariables } from "../../app.js";
12
12
  import { DashLayout } from "../../theme/layouts/index.js";
13
13
  import { EmptyState, DangerZone } from "../../theme/components/index.js";
14
14
  import * as time from "../../lib/time.js";
15
15
  import { getMediaUrl, getImageUrl } from "../../lib/image.js";
16
+ import { sse } from "../../lib/sse.js";
16
17
 
17
18
  type Env = { Bindings: Bindings; Variables: AppVariables };
18
19
 
@@ -102,10 +103,22 @@ function MediaListContent({
102
103
  }) {
103
104
  const { t } = useLingui();
104
105
 
105
- const processingText = t({ message: "Processing...", comment: "@context: Upload status - processing" });
106
- const uploadingText = t({ message: "Uploading...", comment: "@context: Upload status - uploading" });
107
- const uploadText = t({ message: "Upload", comment: "@context: Button to upload media file" });
108
- const errorText = t({ message: "Upload failed. Please try again.", comment: "@context: Upload error message" });
106
+ const processingText = t({
107
+ message: "Processing...",
108
+ comment: "@context: Upload status - processing",
109
+ });
110
+ const uploadingText = t({
111
+ message: "Uploading...",
112
+ comment: "@context: Upload status - uploading",
113
+ });
114
+ const uploadText = t({
115
+ message: "Upload",
116
+ comment: "@context: Button to upload media file",
117
+ });
118
+ const errorText = t({
119
+ message: "Upload failed. Please try again.",
120
+ comment: "@context: Upload error message",
121
+ });
109
122
 
110
123
  // Plain JavaScript upload handler - shows progress in the list
111
124
  const uploadScript = `
@@ -289,8 +302,10 @@ function processSSEEvent(event) {
289
302
  <section class="text-sm text-muted-foreground">
290
303
  <p>
291
304
  {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",
305
+ message:
306
+ "Images are automatically optimized: resized to max 1920px, converted to WebP, and metadata stripped.",
307
+ comment:
308
+ "@context: Media upload instructions - auto optimization",
294
309
  })}
295
310
  </p>
296
311
  </section>
@@ -330,7 +345,12 @@ function processSSEEvent(event) {
330
345
  class="p-0 m-auto bg-transparent backdrop:bg-black/80"
331
346
  onclick="event.target === this && this.close()"
332
347
  >
333
- <img id="lightbox-img" src="" alt="" class="max-w-[90vw] max-h-[90vh] object-contain rounded-lg" />
348
+ <img
349
+ id="lightbox-img"
350
+ src=""
351
+ alt=""
352
+ class="max-w-[90vw] max-h-[90vh] object-contain rounded-lg"
353
+ />
334
354
  </dialog>
335
355
  </>
336
356
  );
@@ -363,11 +383,15 @@ function ViewMediaContent({
363
383
  <div>
364
384
  <h1 class="text-2xl font-semibold">{media.originalName}</h1>
365
385
  <p class="text-muted-foreground mt-1">
366
- {formatSize(media.size)} · {media.mimeType} · {time.formatDate(media.createdAt)}
386
+ {formatSize(media.size)} · {media.mimeType} ·{" "}
387
+ {time.formatDate(media.createdAt)}
367
388
  </p>
368
389
  </div>
369
390
  <a href="/dash/media" class="btn-outline">
370
- {t({ message: "Back", comment: "@context: Button to go back to media list" })}
391
+ {t({
392
+ message: "Back",
393
+ comment: "@context: Button to go back to media list",
394
+ })}
371
395
  </a>
372
396
  </div>
373
397
 
@@ -375,7 +399,12 @@ function ViewMediaContent({
375
399
  {/* Preview */}
376
400
  <div class="card">
377
401
  <header>
378
- <h2>{t({ message: "Preview", comment: "@context: Media detail section - preview" })}</h2>
402
+ <h2>
403
+ {t({
404
+ message: "Preview",
405
+ comment: "@context: Media detail section - preview",
406
+ })}
407
+ </h2>
379
408
  </header>
380
409
  <section>
381
410
  {isImage ? (
@@ -392,7 +421,10 @@ function ViewMediaContent({
392
421
  />
393
422
  </button>
394
423
  <p class="text-xs text-muted-foreground mt-2">
395
- {t({ message: "Click image to view full size", comment: "@context: Hint to click image for lightbox" })}
424
+ {t({
425
+ message: "Click image to view full size",
426
+ comment: "@context: Hint to click image for lightbox",
427
+ })}
396
428
  </p>
397
429
  </>
398
430
  ) : (
@@ -407,7 +439,12 @@ function ViewMediaContent({
407
439
  <div class="space-y-6">
408
440
  <div class="card">
409
441
  <header>
410
- <h2>{t({ message: "URL", comment: "@context: Media detail section - URL" })}</h2>
442
+ <h2>
443
+ {t({
444
+ message: "URL",
445
+ comment: "@context: Media detail section - URL",
446
+ })}
447
+ </h2>
411
448
  </header>
412
449
  <section>
413
450
  <div class="flex items-center gap-2">
@@ -422,18 +459,29 @@ function ViewMediaContent({
422
459
  class="btn-outline"
423
460
  onclick={`navigator.clipboard.writeText('${url}')`}
424
461
  >
425
- {t({ message: "Copy", comment: "@context: Button to copy URL to clipboard" })}
462
+ {t({
463
+ message: "Copy",
464
+ comment: "@context: Button to copy URL to clipboard",
465
+ })}
426
466
  </button>
427
467
  </div>
428
468
  <p class="text-xs text-muted-foreground mt-2">
429
- {t({ message: "Use this URL to embed the media in your posts.", comment: "@context: Media URL helper text" })}
469
+ {t({
470
+ message: "Use this URL to embed the media in your posts.",
471
+ comment: "@context: Media URL helper text",
472
+ })}
430
473
  </p>
431
474
  </section>
432
475
  </div>
433
476
 
434
477
  <div class="card">
435
478
  <header>
436
- <h2>{t({ message: "Markdown", comment: "@context: Media detail section - Markdown snippet" })}</h2>
479
+ <h2>
480
+ {t({
481
+ message: "Markdown",
482
+ comment: "@context: Media detail section - Markdown snippet",
483
+ })}
484
+ </h2>
437
485
  </header>
438
486
  <section>
439
487
  <div class="flex items-center gap-2">
@@ -448,7 +496,10 @@ function ViewMediaContent({
448
496
  class="btn-outline"
449
497
  onclick={`navigator.clipboard.writeText('![${media.alt || media.originalName}](${url})')`}
450
498
  >
451
- {t({ message: "Copy", comment: "@context: Button to copy Markdown to clipboard" })}
499
+ {t({
500
+ message: "Copy",
501
+ comment: "@context: Button to copy Markdown to clipboard",
502
+ })}
452
503
  </button>
453
504
  </div>
454
505
  </section>
@@ -456,10 +507,17 @@ function ViewMediaContent({
456
507
 
457
508
  {/* Delete */}
458
509
  <DangerZone
459
- actionLabel={t({ message: "Delete Media", comment: "@context: Button to delete media" })}
510
+ actionLabel={t({
511
+ message: "Delete Media",
512
+ comment: "@context: Button to delete media",
513
+ })}
460
514
  formAction={`/dash/media/${media.id}/delete`}
461
515
  confirmMessage="Are you sure you want to delete this media?"
462
- description={t({ message: "Deleting this media will remove it permanently from storage.", comment: "@context: Warning message before deleting media" })}
516
+ description={t({
517
+ message:
518
+ "Deleting this media will remove it permanently from storage.",
519
+ comment: "@context: Warning message before deleting media",
520
+ })}
463
521
  />
464
522
  </div>
465
523
  </div>
@@ -491,13 +549,18 @@ mediaRoutes.get("/", async (c) => {
491
549
  const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
492
550
 
493
551
  return c.html(
494
- <DashLayout c={c} title="Media" siteName={siteName} currentPath="/dash/media">
552
+ <DashLayout
553
+ c={c}
554
+ title="Media"
555
+ siteName={siteName}
556
+ currentPath="/dash/media"
557
+ >
495
558
  <MediaListContent
496
559
  mediaList={mediaList}
497
560
  r2PublicUrl={r2PublicUrl}
498
561
  imageTransformUrl={imageTransformUrl}
499
562
  />
500
- </DashLayout>
563
+ </DashLayout>,
501
564
  );
502
565
  });
503
566
 
@@ -512,13 +575,18 @@ mediaRoutes.get("/:id", async (c) => {
512
575
  const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
513
576
 
514
577
  return c.html(
515
- <DashLayout c={c} title={media.originalName} siteName={siteName} currentPath="/dash/media">
578
+ <DashLayout
579
+ c={c}
580
+ title={media.originalName}
581
+ siteName={siteName}
582
+ currentPath="/dash/media"
583
+ >
516
584
  <ViewMediaContent
517
585
  media={media}
518
586
  r2PublicUrl={r2PublicUrl}
519
587
  imageTransformUrl={imageTransformUrl}
520
588
  />
521
- </DashLayout>
589
+ </DashLayout>,
522
590
  );
523
591
  });
524
592
 
@@ -541,5 +609,7 @@ mediaRoutes.post("/:id/delete", async (c) => {
541
609
  // Delete from database
542
610
  await c.var.services.media.delete(id);
543
611
 
544
- return c.redirect("/dash/media");
612
+ return sse(c, async (stream) => {
613
+ await stream.redirect("/dash/media");
614
+ });
545
615
  });
@@ -5,14 +5,22 @@
5
5
  */
6
6
 
7
7
  import { Hono } from "hono";
8
- import { useLingui } from "../../i18n/index.js";
8
+ import { useLingui } from "@lingui/react/macro";
9
9
  import type { Bindings, Post } from "../../types.js";
10
10
  import type { AppVariables } from "../../app.js";
11
11
  import { DashLayout } from "../../theme/layouts/index.js";
12
- import { PageForm, VisibilityBadge, EmptyState, ListItemRow, ActionButtons, CrudPageHeader, DangerZone } from "../../theme/components/index.js";
12
+ import {
13
+ PageForm,
14
+ VisibilityBadge,
15
+ EmptyState,
16
+ ListItemRow,
17
+ ActionButtons,
18
+ CrudPageHeader,
19
+ DangerZone,
20
+ } from "../../theme/components/index.js";
13
21
  import * as sqid from "../../lib/sqid.js";
14
22
  import * as time from "../../lib/time.js";
15
- import { VisibilitySchema, parseFormData } from "../../lib/schemas.js";
23
+ import { sse } from "../../lib/sse.js";
16
24
 
17
25
  type Env = { Bindings: Bindings; Variables: AppVariables };
18
26
 
@@ -25,14 +33,23 @@ function PagesListContent({ pages }: { pages: Post[] }) {
25
33
  <>
26
34
  <CrudPageHeader
27
35
  title={t({ message: "Pages", comment: "@context: Pages main heading" })}
28
- ctaLabel={t({ message: "New Page", comment: "@context: Button to create new page" })}
36
+ ctaLabel={t({
37
+ message: "New Page",
38
+ comment: "@context: Button to create new page",
39
+ })}
29
40
  ctaHref="/dash/pages/new"
30
41
  />
31
42
 
32
43
  {pages.length === 0 ? (
33
44
  <EmptyState
34
- message={t({ message: "No pages yet.", comment: "@context: Empty state message when no pages exist" })}
35
- ctaText={t({ message: "Create your first page", comment: "@context: Button in empty state to create first page" })}
45
+ message={t({
46
+ message: "No pages yet.",
47
+ comment: "@context: Empty state message when no pages exist",
48
+ })}
49
+ ctaText={t({
50
+ message: "Create your first page",
51
+ comment: "@context: Button in empty state to create first page",
52
+ })}
36
53
  ctaHref="/dash/pages/new"
37
54
  />
38
55
  ) : (
@@ -43,9 +60,19 @@ function PagesListContent({ pages }: { pages: Post[] }) {
43
60
  actions={
44
61
  <ActionButtons
45
62
  editHref={`/dash/pages/${sqid.encode(page.id)}/edit`}
46
- editLabel={t({ message: "Edit", comment: "@context: Button to edit page" })}
47
- viewHref={page.visibility !== "draft" && page.path ? `/${page.path}` : undefined}
48
- viewLabel={t({ message: "View", comment: "@context: Button to view page on public site" })}
63
+ editLabel={t({
64
+ message: "Edit",
65
+ comment: "@context: Button to edit page",
66
+ })}
67
+ viewHref={
68
+ page.visibility !== "draft" && page.path
69
+ ? `/${page.path}`
70
+ : undefined
71
+ }
72
+ viewLabel={t({
73
+ message: "View",
74
+ comment: "@context: Button to view page on public site",
75
+ })}
49
76
  />
50
77
  }
51
78
  >
@@ -59,11 +86,13 @@ function PagesListContent({ pages }: { pages: Post[] }) {
59
86
  href={`/dash/pages/${sqid.encode(page.id)}`}
60
87
  class="font-medium hover:underline"
61
88
  >
62
- {page.title || t({ message: "Untitled", comment: "@context: Default title for untitled page" })}
89
+ {page.title ||
90
+ t({
91
+ message: "Untitled",
92
+ comment: "@context: Default title for untitled page",
93
+ })}
63
94
  </a>
64
- <p class="text-sm text-muted-foreground mt-1">
65
- /{page.path}
66
- </p>
95
+ <p class="text-sm text-muted-foreground mt-1">/{page.path}</p>
67
96
  </ListItemRow>
68
97
  ))}
69
98
  </div>
@@ -90,27 +119,47 @@ function ViewPageContent({ page }: { page: Post }) {
90
119
  <>
91
120
  <div class="flex items-center justify-between mb-6">
92
121
  <div>
93
- <h1 class="text-2xl font-semibold">{page.title || t({ message: "Page", comment: "@context: Default page heading when untitled" })}</h1>
94
- {page.path && (
95
- <p class="text-muted-foreground mt-1">/{page.path}</p>
96
- )}
122
+ <h1 class="text-2xl font-semibold">
123
+ {page.title ||
124
+ t({
125
+ message: "Page",
126
+ comment: "@context: Default page heading when untitled",
127
+ })}
128
+ </h1>
129
+ {page.path && <p class="text-muted-foreground mt-1">/{page.path}</p>}
97
130
  </div>
98
131
  <ActionButtons
99
132
  editHref={`/dash/pages/${sqid.encode(page.id)}/edit`}
100
- editLabel={t({ message: "Edit", comment: "@context: Button to edit page" })}
101
- viewHref={page.visibility !== "draft" && page.path ? `/${page.path}` : undefined}
102
- viewLabel={t({ message: "View", comment: "@context: Button to view page on public site" })}
133
+ editLabel={t({
134
+ message: "Edit",
135
+ comment: "@context: Button to edit page",
136
+ })}
137
+ viewHref={
138
+ page.visibility !== "draft" && page.path
139
+ ? `/${page.path}`
140
+ : undefined
141
+ }
142
+ viewLabel={t({
143
+ message: "View",
144
+ comment: "@context: Button to view page on public site",
145
+ })}
103
146
  />
104
147
  </div>
105
148
 
106
149
  <div class="card">
107
150
  <section>
108
- <div class="prose" dangerouslySetInnerHTML={{ __html: page.contentHtml || "" }} />
151
+ <div
152
+ class="prose"
153
+ dangerouslySetInnerHTML={{ __html: page.contentHtml || "" }}
154
+ />
109
155
  </section>
110
156
  </div>
111
157
 
112
158
  <DangerZone
113
- actionLabel={t({ message: "Delete Page", comment: "@context: Button to delete page" })}
159
+ actionLabel={t({
160
+ message: "Delete Page",
161
+ comment: "@context: Button to delete page",
162
+ })}
114
163
  formAction={`/dash/pages/${sqid.encode(page.id)}/delete`}
115
164
  confirmMessage="Are you sure you want to delete this page?"
116
165
  />
@@ -123,7 +172,10 @@ function EditPageContent({ page }: { page: Post }) {
123
172
  return (
124
173
  <>
125
174
  <h1 class="text-2xl font-semibold mb-6">
126
- {t({ message: "Edit Page", comment: "@context: Edit page main heading" })}
175
+ {t({
176
+ message: "Edit Page",
177
+ comment: "@context: Edit page main heading",
178
+ })}
127
179
  </h1>
128
180
  <PageForm page={page} action={`/dash/pages/${sqid.encode(page.id)}`} />
129
181
  </>
@@ -140,9 +192,14 @@ pagesRoutes.get("/", async (c) => {
140
192
  const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
141
193
 
142
194
  return c.html(
143
- <DashLayout c={c} title="Pages" siteName={siteName} currentPath="/dash/pages">
195
+ <DashLayout
196
+ c={c}
197
+ title="Pages"
198
+ siteName={siteName}
199
+ currentPath="/dash/pages"
200
+ >
144
201
  <PagesListContent pages={pages} />
145
- </DashLayout>
202
+ </DashLayout>,
146
203
  );
147
204
  });
148
205
 
@@ -151,30 +208,37 @@ pagesRoutes.get("/new", async (c) => {
151
208
  const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
152
209
 
153
210
  return c.html(
154
- <DashLayout c={c} title="New Page" siteName={siteName} currentPath="/dash/pages">
211
+ <DashLayout
212
+ c={c}
213
+ title="New Page"
214
+ siteName={siteName}
215
+ currentPath="/dash/pages"
216
+ >
155
217
  <NewPageContent />
156
- </DashLayout>
218
+ </DashLayout>,
157
219
  );
158
220
  });
159
221
 
160
222
  // Create page
161
223
  pagesRoutes.post("/", async (c) => {
162
- const formData = await c.req.formData();
163
-
164
- const title = formData.get("title") as string;
165
- const content = formData.get("content") as string;
166
- const visibility = parseFormData(formData, "visibility", VisibilitySchema);
167
- const path = formData.get("path") as string;
224
+ const body = await c.req.json<{
225
+ title: string;
226
+ content: string;
227
+ visibility: string;
228
+ path: string;
229
+ }>();
168
230
 
169
231
  const page = await c.var.services.posts.create({
170
232
  type: "page",
171
- title,
172
- content,
173
- visibility,
174
- path: path.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
233
+ title: body.title,
234
+ content: body.content,
235
+ visibility: body.visibility as Post["visibility"],
236
+ path: body.path.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
175
237
  });
176
238
 
177
- return c.redirect(`/dash/pages/${sqid.encode(page.id)}`);
239
+ return sse(c, async (stream) => {
240
+ await stream.redirect(`/dash/pages/${sqid.encode(page.id)}`);
241
+ });
178
242
  });
179
243
 
180
244
  // View single page
@@ -188,9 +252,14 @@ pagesRoutes.get("/:id", async (c) => {
188
252
  const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
189
253
 
190
254
  return c.html(
191
- <DashLayout c={c} title={page.title || "Page"} siteName={siteName} currentPath="/dash/pages">
255
+ <DashLayout
256
+ c={c}
257
+ title={page.title || "Page"}
258
+ siteName={siteName}
259
+ currentPath="/dash/pages"
260
+ >
192
261
  <ViewPageContent page={page} />
193
- </DashLayout>
262
+ </DashLayout>,
194
263
  );
195
264
  });
196
265
 
@@ -205,9 +274,14 @@ pagesRoutes.get("/:id/edit", async (c) => {
205
274
  const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
206
275
 
207
276
  return c.html(
208
- <DashLayout c={c} title={`Edit: ${page.title || "Page"}`} siteName={siteName} currentPath="/dash/pages">
277
+ <DashLayout
278
+ c={c}
279
+ title={`Edit: ${page.title || "Page"}`}
280
+ siteName={siteName}
281
+ currentPath="/dash/pages"
282
+ >
209
283
  <EditPageContent page={page} />
210
- </DashLayout>
284
+ </DashLayout>,
211
285
  );
212
286
  });
213
287
 
@@ -216,22 +290,24 @@ pagesRoutes.post("/:id", async (c) => {
216
290
  const id = sqid.decode(c.req.param("id"));
217
291
  if (!id) return c.notFound();
218
292
 
219
- const formData = await c.req.formData();
220
-
221
- const title = formData.get("title") as string;
222
- const content = formData.get("content") as string;
223
- const visibility = parseFormData(formData, "visibility", VisibilitySchema);
224
- const path = formData.get("path") as string;
293
+ const body = await c.req.json<{
294
+ title: string;
295
+ content: string;
296
+ visibility: string;
297
+ path: string;
298
+ }>();
225
299
 
226
300
  await c.var.services.posts.update(id, {
227
301
  type: "page",
228
- title,
229
- content,
230
- visibility,
231
- path: path.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
302
+ title: body.title,
303
+ content: body.content,
304
+ visibility: body.visibility as Post["visibility"],
305
+ path: body.path.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
232
306
  });
233
307
 
234
- return c.redirect(`/dash/pages/${sqid.encode(id)}`);
308
+ return sse(c, async (stream) => {
309
+ await stream.redirect(`/dash/pages/${sqid.encode(id)}`);
310
+ });
235
311
  });
236
312
 
237
313
  // Delete page
@@ -241,5 +317,7 @@ pagesRoutes.post("/:id/delete", async (c) => {
241
317
 
242
318
  await c.var.services.posts.delete(id);
243
319
 
244
- return c.redirect("/dash/pages");
320
+ return sse(c, async (stream) => {
321
+ await stream.redirect("/dash/pages");
322
+ });
245
323
  });