@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.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +112 -85
- package/dist/auth.d.ts +1 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -1
- package/dist/client.js +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/i18n/context.d.ts.map +1 -1
- package/dist/i18n/context.js +0 -3
- package/dist/i18n/detect.d.ts +0 -11
- package/dist/i18n/detect.d.ts.map +1 -1
- package/dist/i18n/detect.js +1 -52
- package/dist/i18n/i18n.d.ts +4 -14
- package/dist/i18n/i18n.d.ts.map +1 -1
- package/dist/i18n/i18n.js +19 -25
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/middleware.d.ts +2 -5
- package/dist/i18n/middleware.d.ts.map +1 -1
- package/dist/i18n/middleware.js +12 -23
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/sse.d.ts +45 -17
- package/dist/lib/sse.d.ts.map +1 -1
- package/dist/lib/sse.js +77 -37
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/routes/api/posts.js +0 -1
- package/dist/routes/api/upload.js +13 -3
- package/dist/routes/dash/collections.d.ts.map +1 -1
- package/dist/routes/dash/collections.js +134 -142
- package/dist/routes/dash/index.js +25 -25
- package/dist/routes/dash/media.d.ts.map +1 -1
- package/dist/routes/dash/media.js +60 -56
- package/dist/routes/dash/pages.d.ts.map +1 -1
- package/dist/routes/dash/pages.js +64 -66
- package/dist/routes/dash/posts.d.ts.map +1 -1
- package/dist/routes/dash/posts.js +50 -59
- package/dist/routes/dash/redirects.d.ts.map +1 -1
- package/dist/routes/dash/redirects.js +63 -60
- package/dist/routes/dash/settings.d.ts.map +1 -1
- package/dist/routes/dash/settings.js +249 -93
- package/dist/routes/feed/rss.js +6 -4
- package/dist/routes/pages/archive.js +60 -62
- package/dist/routes/pages/collection.js +8 -8
- package/dist/routes/pages/home.js +14 -14
- package/dist/routes/pages/page.js +7 -6
- package/dist/routes/pages/post.js +8 -8
- package/dist/routes/pages/search.js +25 -27
- package/dist/services/collection.d.ts.map +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/media.d.ts.map +1 -1
- package/dist/services/post.d.ts.map +1 -1
- package/dist/services/redirect.d.ts.map +1 -1
- package/dist/services/settings.d.ts.map +1 -1
- package/dist/theme/components/ActionButtons.d.ts +1 -1
- package/dist/theme/components/ActionButtons.d.ts.map +1 -1
- package/dist/theme/components/ActionButtons.js +17 -21
- package/dist/theme/components/CrudPageHeader.d.ts.map +1 -1
- package/dist/theme/components/DangerZone.d.ts.map +1 -1
- package/dist/theme/components/DangerZone.js +12 -15
- package/dist/theme/components/EmptyState.d.ts.map +1 -1
- package/dist/theme/components/PageForm.d.ts.map +1 -1
- package/dist/theme/components/PageForm.js +58 -56
- package/dist/theme/components/Pagination.d.ts.map +1 -1
- package/dist/theme/components/Pagination.js +22 -25
- package/dist/theme/components/PostForm.d.ts +0 -1
- package/dist/theme/components/PostForm.d.ts.map +1 -1
- package/dist/theme/components/PostForm.js +85 -77
- package/dist/theme/components/PostList.d.ts.map +1 -1
- package/dist/theme/components/PostList.js +17 -17
- package/dist/theme/components/ThreadView.d.ts.map +1 -1
- package/dist/theme/components/ThreadView.js +15 -18
- package/dist/theme/components/TypeBadge.d.ts.map +1 -1
- package/dist/theme/components/TypeBadge.js +20 -20
- package/dist/theme/components/VisibilityBadge.d.ts.map +1 -1
- package/dist/theme/components/VisibilityBadge.js +14 -14
- package/dist/theme/components/index.d.ts +2 -2
- package/dist/theme/components/index.d.ts.map +1 -1
- package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
- package/dist/theme/layouts/BaseLayout.js +4 -2
- package/dist/theme/layouts/DashLayout.d.ts.map +1 -1
- package/dist/theme/layouts/DashLayout.js +29 -29
- package/dist/types/lingui-react-macro.d.js +9 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vendor/datastar.js +1606 -0
- package/package.json +7 -15
- package/src/app.tsx +222 -59
- package/src/auth.ts +5 -1
- package/src/client.ts +1 -1
- package/src/db/migrations/meta/0000_snapshot.json +16 -47
- package/src/db/migrations/meta/_journal.json +1 -1
- package/src/db/schema.ts +22 -7
- package/src/i18n/EXAMPLES.md +45 -23
- package/src/i18n/README.md +39 -25
- package/src/i18n/context.tsx +1 -4
- package/src/i18n/detect.ts +1 -67
- package/src/i18n/i18n.ts +15 -19
- package/src/i18n/index.ts +0 -3
- package/src/i18n/middleware.ts +12 -24
- package/src/lib/constants.ts +2 -1
- package/src/lib/image-processor.ts +14 -6
- package/src/lib/image.ts +2 -2
- package/src/lib/schemas.ts +7 -3
- package/src/lib/sse.ts +133 -51
- package/src/middleware/auth.ts +6 -2
- package/src/routes/api/posts.ts +9 -9
- package/src/routes/api/upload.ts +39 -10
- package/src/routes/dash/collections.tsx +249 -81
- package/src/routes/dash/index.tsx +22 -7
- package/src/routes/dash/media.tsx +94 -24
- package/src/routes/dash/pages.tsx +132 -54
- package/src/routes/dash/posts.tsx +99 -57
- package/src/routes/dash/redirects.tsx +117 -36
- package/src/routes/dash/settings.tsx +268 -55
- package/src/routes/feed/rss.ts +6 -4
- package/src/routes/pages/archive.tsx +78 -24
- package/src/routes/pages/collection.tsx +32 -8
- package/src/routes/pages/home.tsx +38 -10
- package/src/routes/pages/page.tsx +15 -5
- package/src/routes/pages/post.tsx +17 -6
- package/src/routes/pages/search.tsx +50 -13
- package/src/services/collection.ts +29 -8
- package/src/services/index.ts +4 -1
- package/src/services/media.ts +15 -3
- package/src/services/post.ts +37 -10
- package/src/services/redirect.ts +4 -1
- package/src/services/settings.ts +14 -3
- package/src/theme/components/ActionButtons.tsx +31 -15
- package/src/theme/components/CrudPageHeader.tsx +3 -4
- package/src/theme/components/DangerZone.tsx +19 -13
- package/src/theme/components/EmptyState.tsx +1 -5
- package/src/theme/components/PageForm.tsx +80 -25
- package/src/theme/components/Pagination.tsx +34 -31
- package/src/theme/components/PostForm.tsx +91 -27
- package/src/theme/components/PostList.tsx +23 -6
- package/src/theme/components/ThreadView.tsx +25 -10
- package/src/theme/components/TypeBadge.tsx +13 -4
- package/src/theme/components/VisibilityBadge.tsx +17 -5
- package/src/theme/components/index.ts +12 -2
- package/src/theme/layouts/BaseLayout.tsx +6 -5
- package/src/theme/layouts/DashLayout.tsx +71 -18
- package/src/types/lingui-react-macro.d.ts +34 -0
- package/src/types.ts +16 -4
- package/src/vendor/datastar.js +9 -0
- package/src/vendor/datastar.js.map +7 -0
- package/dist/plugin.d.ts +0 -3
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -20
- package/dist/tailwind.d.ts +0 -12
- package/dist/tailwind.d.ts.map +0 -1
- package/dist/tailwind.js +0 -15
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Hono } from "hono";
|
|
9
|
-
import { useLingui } from "
|
|
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({
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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:
|
|
293
|
-
|
|
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
|
|
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} ·
|
|
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({
|
|
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>
|
|
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({
|
|
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>
|
|
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({
|
|
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({
|
|
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>
|
|
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('')`}
|
|
450
498
|
>
|
|
451
|
-
{t({
|
|
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({
|
|
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({
|
|
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
|
|
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
|
|
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
|
|
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 "
|
|
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 {
|
|
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 {
|
|
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({
|
|
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({
|
|
35
|
-
|
|
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({
|
|
47
|
-
|
|
48
|
-
|
|
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 ||
|
|
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">
|
|
94
|
-
|
|
95
|
-
|
|
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({
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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({
|
|
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({
|
|
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
|
|
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
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
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
|
|
320
|
+
return sse(c, async (stream) => {
|
|
321
|
+
await stream.redirect("/dash/pages");
|
|
322
|
+
});
|
|
245
323
|
});
|