@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.
- package/bin/jant.js +3 -1
- 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/image.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 +3 -1
- 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 -26
- package/dist/routes/dash/media.d.ts.map +1 -1
- package/dist/routes/dash/media.js +60 -56
- 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 +1 -1
- 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 +5 -2
- package/src/app.tsx +175 -56
- package/src/auth.ts +5 -1
- package/src/client.ts +1 -1
- package/src/db/schema.ts +22 -7
- package/src/i18n/EXAMPLES.md +34 -14
- package/src/i18n/README.md +19 -9
- 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 +23 -7
- package/src/lib/image.ts +6 -2
- package/src/lib/schemas.ts +6 -2
- package/src/lib/sse.ts +138 -50
- package/src/middleware/auth.ts +6 -2
- package/src/routes/api/posts.ts +14 -5
- package/src/routes/api/upload.ts +25 -7
- package/src/routes/dash/collections.tsx +162 -70
- package/src/routes/dash/index.tsx +22 -7
- package/src/routes/dash/media.tsx +59 -16
- package/src/routes/dash/pages.tsx +102 -44
- package/src/routes/dash/posts.tsx +87 -54
- package/src/routes/dash/redirects.tsx +74 -26
- package/src/routes/dash/settings.tsx +250 -57
- package/src/routes/feed/rss.ts +6 -4
- package/src/routes/pages/archive.tsx +71 -21
- package/src/routes/pages/collection.tsx +21 -6
- package/src/routes/pages/home.tsx +30 -9
- package/src/routes/pages/page.tsx +14 -5
- package/src/routes/pages/post.tsx +21 -7
- package/src/routes/pages/search.tsx +42 -11
- package/src/services/collection.ts +34 -9
- package/src/services/index.ts +4 -1
- package/src/services/media.ts +15 -3
- package/src/services/post.ts +39 -10
- package/src/services/redirect.ts +4 -1
- package/src/services/settings.ts +14 -3
- package/src/theme/components/ActionButtons.tsx +26 -14
- package/src/theme/components/CrudPageHeader.tsx +6 -1
- package/src/theme/components/DangerZone.tsx +19 -13
- package/src/theme/components/EmptyState.tsx +6 -1
- package/src/theme/components/PageForm.tsx +71 -24
- package/src/theme/components/Pagination.tsx +26 -8
- package/src/theme/components/PostForm.tsx +72 -25
- package/src/theme/components/PostList.tsx +16 -5
- package/src/theme/components/ThreadView.tsx +25 -7
- package/src/theme/components/TypeBadge.tsx +13 -4
- package/src/theme/components/VisibilityBadge.tsx +17 -5
- package/src/theme/components/index.ts +4 -1
- package/src/theme/layouts/BaseLayout.tsx +5 -2
- package/src/theme/layouts/DashLayout.tsx +41 -12
- package/src/types/lingui-react-macro.d.ts +34 -0
- package/src/types.ts +16 -2
- package/src/vendor/datastar.js +9 -0
- package/src/vendor/datastar.js.map +7 -0
|
@@ -5,7 +5,7 @@
|
|
|
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";
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from "../../theme/components/index.js";
|
|
21
21
|
import * as sqid from "../../lib/sqid.js";
|
|
22
22
|
import * as time from "../../lib/time.js";
|
|
23
|
-
import {
|
|
23
|
+
import { sse } from "../../lib/sse.js";
|
|
24
24
|
|
|
25
25
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
26
26
|
|
|
@@ -33,7 +33,10 @@ function PagesListContent({ pages }: { pages: Post[] }) {
|
|
|
33
33
|
<>
|
|
34
34
|
<CrudPageHeader
|
|
35
35
|
title={t({ message: "Pages", comment: "@context: Pages main heading" })}
|
|
36
|
-
ctaLabel={t({
|
|
36
|
+
ctaLabel={t({
|
|
37
|
+
message: "New Page",
|
|
38
|
+
comment: "@context: Button to create new page",
|
|
39
|
+
})}
|
|
37
40
|
ctaHref="/dash/pages/new"
|
|
38
41
|
/>
|
|
39
42
|
|
|
@@ -57,8 +60,15 @@ function PagesListContent({ pages }: { pages: Post[] }) {
|
|
|
57
60
|
actions={
|
|
58
61
|
<ActionButtons
|
|
59
62
|
editHref={`/dash/pages/${sqid.encode(page.id)}/edit`}
|
|
60
|
-
editLabel={t({
|
|
61
|
-
|
|
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
|
+
}
|
|
62
72
|
viewLabel={t({
|
|
63
73
|
message: "View",
|
|
64
74
|
comment: "@context: Button to view page on public site",
|
|
@@ -68,11 +78,19 @@ function PagesListContent({ pages }: { pages: Post[] }) {
|
|
|
68
78
|
>
|
|
69
79
|
<div class="flex items-center gap-2 mb-1">
|
|
70
80
|
<VisibilityBadge visibility={page.visibility} />
|
|
71
|
-
<span class="text-xs text-muted-foreground">
|
|
81
|
+
<span class="text-xs text-muted-foreground">
|
|
82
|
+
{time.formatDate(page.updatedAt)}
|
|
83
|
+
</span>
|
|
72
84
|
</div>
|
|
73
|
-
<a
|
|
85
|
+
<a
|
|
86
|
+
href={`/dash/pages/${sqid.encode(page.id)}`}
|
|
87
|
+
class="font-medium hover:underline"
|
|
88
|
+
>
|
|
74
89
|
{page.title ||
|
|
75
|
-
t({
|
|
90
|
+
t({
|
|
91
|
+
message: "Untitled",
|
|
92
|
+
comment: "@context: Default title for untitled page",
|
|
93
|
+
})}
|
|
76
94
|
</a>
|
|
77
95
|
<p class="text-sm text-muted-foreground mt-1">/{page.path}</p>
|
|
78
96
|
</ListItemRow>
|
|
@@ -103,14 +121,24 @@ function ViewPageContent({ page }: { page: Post }) {
|
|
|
103
121
|
<div>
|
|
104
122
|
<h1 class="text-2xl font-semibold">
|
|
105
123
|
{page.title ||
|
|
106
|
-
t({
|
|
124
|
+
t({
|
|
125
|
+
message: "Page",
|
|
126
|
+
comment: "@context: Default page heading when untitled",
|
|
127
|
+
})}
|
|
107
128
|
</h1>
|
|
108
129
|
{page.path && <p class="text-muted-foreground mt-1">/{page.path}</p>}
|
|
109
130
|
</div>
|
|
110
131
|
<ActionButtons
|
|
111
132
|
editHref={`/dash/pages/${sqid.encode(page.id)}/edit`}
|
|
112
|
-
editLabel={t({
|
|
113
|
-
|
|
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
|
+
}
|
|
114
142
|
viewLabel={t({
|
|
115
143
|
message: "View",
|
|
116
144
|
comment: "@context: Button to view page on public site",
|
|
@@ -120,12 +148,18 @@ function ViewPageContent({ page }: { page: Post }) {
|
|
|
120
148
|
|
|
121
149
|
<div class="card">
|
|
122
150
|
<section>
|
|
123
|
-
<div
|
|
151
|
+
<div
|
|
152
|
+
class="prose"
|
|
153
|
+
dangerouslySetInnerHTML={{ __html: page.contentHtml || "" }}
|
|
154
|
+
/>
|
|
124
155
|
</section>
|
|
125
156
|
</div>
|
|
126
157
|
|
|
127
158
|
<DangerZone
|
|
128
|
-
actionLabel={t({
|
|
159
|
+
actionLabel={t({
|
|
160
|
+
message: "Delete Page",
|
|
161
|
+
comment: "@context: Button to delete page",
|
|
162
|
+
})}
|
|
129
163
|
formAction={`/dash/pages/${sqid.encode(page.id)}/delete`}
|
|
130
164
|
confirmMessage="Are you sure you want to delete this page?"
|
|
131
165
|
/>
|
|
@@ -138,7 +172,10 @@ function EditPageContent({ page }: { page: Post }) {
|
|
|
138
172
|
return (
|
|
139
173
|
<>
|
|
140
174
|
<h1 class="text-2xl font-semibold mb-6">
|
|
141
|
-
{t({
|
|
175
|
+
{t({
|
|
176
|
+
message: "Edit Page",
|
|
177
|
+
comment: "@context: Edit page main heading",
|
|
178
|
+
})}
|
|
142
179
|
</h1>
|
|
143
180
|
<PageForm page={page} action={`/dash/pages/${sqid.encode(page.id)}`} />
|
|
144
181
|
</>
|
|
@@ -155,9 +192,14 @@ pagesRoutes.get("/", async (c) => {
|
|
|
155
192
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
156
193
|
|
|
157
194
|
return c.html(
|
|
158
|
-
<DashLayout
|
|
195
|
+
<DashLayout
|
|
196
|
+
c={c}
|
|
197
|
+
title="Pages"
|
|
198
|
+
siteName={siteName}
|
|
199
|
+
currentPath="/dash/pages"
|
|
200
|
+
>
|
|
159
201
|
<PagesListContent pages={pages} />
|
|
160
|
-
</DashLayout
|
|
202
|
+
</DashLayout>,
|
|
161
203
|
);
|
|
162
204
|
});
|
|
163
205
|
|
|
@@ -166,30 +208,37 @@ pagesRoutes.get("/new", async (c) => {
|
|
|
166
208
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
167
209
|
|
|
168
210
|
return c.html(
|
|
169
|
-
<DashLayout
|
|
211
|
+
<DashLayout
|
|
212
|
+
c={c}
|
|
213
|
+
title="New Page"
|
|
214
|
+
siteName={siteName}
|
|
215
|
+
currentPath="/dash/pages"
|
|
216
|
+
>
|
|
170
217
|
<NewPageContent />
|
|
171
|
-
</DashLayout
|
|
218
|
+
</DashLayout>,
|
|
172
219
|
);
|
|
173
220
|
});
|
|
174
221
|
|
|
175
222
|
// Create page
|
|
176
223
|
pagesRoutes.post("/", async (c) => {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
224
|
+
const body = await c.req.json<{
|
|
225
|
+
title: string;
|
|
226
|
+
content: string;
|
|
227
|
+
visibility: string;
|
|
228
|
+
path: string;
|
|
229
|
+
}>();
|
|
183
230
|
|
|
184
231
|
const page = await c.var.services.posts.create({
|
|
185
232
|
type: "page",
|
|
186
|
-
title,
|
|
187
|
-
content,
|
|
188
|
-
visibility,
|
|
189
|
-
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, "-"),
|
|
190
237
|
});
|
|
191
238
|
|
|
192
|
-
return c
|
|
239
|
+
return sse(c, async (stream) => {
|
|
240
|
+
await stream.redirect(`/dash/pages/${sqid.encode(page.id)}`);
|
|
241
|
+
});
|
|
193
242
|
});
|
|
194
243
|
|
|
195
244
|
// View single page
|
|
@@ -203,9 +252,14 @@ pagesRoutes.get("/:id", async (c) => {
|
|
|
203
252
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
204
253
|
|
|
205
254
|
return c.html(
|
|
206
|
-
<DashLayout
|
|
255
|
+
<DashLayout
|
|
256
|
+
c={c}
|
|
257
|
+
title={page.title || "Page"}
|
|
258
|
+
siteName={siteName}
|
|
259
|
+
currentPath="/dash/pages"
|
|
260
|
+
>
|
|
207
261
|
<ViewPageContent page={page} />
|
|
208
|
-
</DashLayout
|
|
262
|
+
</DashLayout>,
|
|
209
263
|
);
|
|
210
264
|
});
|
|
211
265
|
|
|
@@ -227,7 +281,7 @@ pagesRoutes.get("/:id/edit", async (c) => {
|
|
|
227
281
|
currentPath="/dash/pages"
|
|
228
282
|
>
|
|
229
283
|
<EditPageContent page={page} />
|
|
230
|
-
</DashLayout
|
|
284
|
+
</DashLayout>,
|
|
231
285
|
);
|
|
232
286
|
});
|
|
233
287
|
|
|
@@ -236,22 +290,24 @@ pagesRoutes.post("/:id", async (c) => {
|
|
|
236
290
|
const id = sqid.decode(c.req.param("id"));
|
|
237
291
|
if (!id) return c.notFound();
|
|
238
292
|
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
293
|
+
const body = await c.req.json<{
|
|
294
|
+
title: string;
|
|
295
|
+
content: string;
|
|
296
|
+
visibility: string;
|
|
297
|
+
path: string;
|
|
298
|
+
}>();
|
|
245
299
|
|
|
246
300
|
await c.var.services.posts.update(id, {
|
|
247
301
|
type: "page",
|
|
248
|
-
title,
|
|
249
|
-
content,
|
|
250
|
-
visibility,
|
|
251
|
-
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, "-"),
|
|
252
306
|
});
|
|
253
307
|
|
|
254
|
-
return c
|
|
308
|
+
return sse(c, async (stream) => {
|
|
309
|
+
await stream.redirect(`/dash/pages/${sqid.encode(id)}`);
|
|
310
|
+
});
|
|
255
311
|
});
|
|
256
312
|
|
|
257
313
|
// Delete page
|
|
@@ -261,5 +317,7 @@ pagesRoutes.post("/:id/delete", async (c) => {
|
|
|
261
317
|
|
|
262
318
|
await c.var.services.posts.delete(id);
|
|
263
319
|
|
|
264
|
-
return c
|
|
320
|
+
return sse(c, async (stream) => {
|
|
321
|
+
await stream.redirect("/dash/pages");
|
|
322
|
+
});
|
|
265
323
|
});
|
|
@@ -3,19 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
|
-
import {
|
|
7
|
-
import { useLingui } from "../../i18n/index.js";
|
|
6
|
+
import { useLingui } from "@lingui/react/macro";
|
|
8
7
|
import type { Bindings, Post } from "../../types.js";
|
|
9
8
|
import type { AppVariables } from "../../app.js";
|
|
10
9
|
import { DashLayout } from "../../theme/layouts/index.js";
|
|
11
|
-
import { PostForm, PostList, CrudPageHeader, ActionButtons } from "../../theme/components/index.js";
|
|
12
|
-
import * as sqid from "../../lib/sqid.js";
|
|
13
10
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "../../
|
|
11
|
+
PostForm,
|
|
12
|
+
PostList,
|
|
13
|
+
CrudPageHeader,
|
|
14
|
+
ActionButtons,
|
|
15
|
+
} from "../../theme/components/index.js";
|
|
16
|
+
import * as sqid from "../../lib/sqid.js";
|
|
17
|
+
import { sse } from "../../lib/sse.js";
|
|
19
18
|
|
|
20
19
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
21
20
|
|
|
@@ -27,7 +26,10 @@ function PostsListContent({ posts }: { posts: Post[] }) {
|
|
|
27
26
|
<>
|
|
28
27
|
<CrudPageHeader
|
|
29
28
|
title={t({ message: "Posts", comment: "@context: Dashboard heading" })}
|
|
30
|
-
ctaLabel={t({
|
|
29
|
+
ctaLabel={t({
|
|
30
|
+
message: "New Post",
|
|
31
|
+
comment: "@context: Button to create new post",
|
|
32
|
+
})}
|
|
31
33
|
ctaHref="/dash/posts/new"
|
|
32
34
|
/>
|
|
33
35
|
<PostList posts={posts} />
|
|
@@ -55,9 +57,14 @@ postsRoutes.get("/", async (c) => {
|
|
|
55
57
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
56
58
|
|
|
57
59
|
return c.html(
|
|
58
|
-
<DashLayout
|
|
60
|
+
<DashLayout
|
|
61
|
+
c={c}
|
|
62
|
+
title="Posts"
|
|
63
|
+
siteName={siteName}
|
|
64
|
+
currentPath="/dash/posts"
|
|
65
|
+
>
|
|
59
66
|
<PostsListContent posts={posts} />
|
|
60
|
-
</DashLayout
|
|
67
|
+
</DashLayout>,
|
|
61
68
|
);
|
|
62
69
|
});
|
|
63
70
|
|
|
@@ -66,39 +73,48 @@ postsRoutes.get("/new", async (c) => {
|
|
|
66
73
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
67
74
|
|
|
68
75
|
return c.html(
|
|
69
|
-
<DashLayout
|
|
76
|
+
<DashLayout
|
|
77
|
+
c={c}
|
|
78
|
+
title="New Post"
|
|
79
|
+
siteName={siteName}
|
|
80
|
+
currentPath="/dash/posts"
|
|
81
|
+
>
|
|
70
82
|
<NewPostContent />
|
|
71
|
-
</DashLayout
|
|
83
|
+
</DashLayout>,
|
|
72
84
|
);
|
|
73
85
|
});
|
|
74
86
|
|
|
75
87
|
// Create post
|
|
76
88
|
postsRoutes.post("/", async (c) => {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const path = parseFormDataOptional(formData, "path", z.string());
|
|
89
|
+
const body = await c.req.json<{
|
|
90
|
+
type: string;
|
|
91
|
+
title?: string;
|
|
92
|
+
content: string;
|
|
93
|
+
visibility: string;
|
|
94
|
+
sourceUrl?: string;
|
|
95
|
+
path?: string;
|
|
96
|
+
}>();
|
|
86
97
|
|
|
87
98
|
const post = await c.var.services.posts.create({
|
|
88
|
-
type,
|
|
89
|
-
title,
|
|
90
|
-
content,
|
|
91
|
-
visibility,
|
|
92
|
-
sourceUrl,
|
|
93
|
-
path,
|
|
99
|
+
type: body.type as Post["type"],
|
|
100
|
+
title: body.title || undefined,
|
|
101
|
+
content: body.content,
|
|
102
|
+
visibility: body.visibility as Post["visibility"],
|
|
103
|
+
sourceUrl: body.sourceUrl || undefined,
|
|
104
|
+
path: body.path || undefined,
|
|
94
105
|
});
|
|
95
106
|
|
|
96
|
-
return c
|
|
107
|
+
return sse(c, async (stream) => {
|
|
108
|
+
await stream.redirect(`/dash/posts/${sqid.encode(post.id)}`);
|
|
109
|
+
});
|
|
97
110
|
});
|
|
98
111
|
|
|
99
112
|
function ViewPostContent({ post }: { post: Post }) {
|
|
100
113
|
const { t } = useLingui();
|
|
101
|
-
const defaultTitle = t({
|
|
114
|
+
const defaultTitle = t({
|
|
115
|
+
message: "Post",
|
|
116
|
+
comment: "@context: Default post title",
|
|
117
|
+
});
|
|
102
118
|
|
|
103
119
|
return (
|
|
104
120
|
<>
|
|
@@ -106,15 +122,24 @@ function ViewPostContent({ post }: { post: Post }) {
|
|
|
106
122
|
<h1 class="text-2xl font-semibold">{post.title || defaultTitle}</h1>
|
|
107
123
|
<ActionButtons
|
|
108
124
|
editHref={`/dash/posts/${sqid.encode(post.id)}/edit`}
|
|
109
|
-
editLabel={t({
|
|
125
|
+
editLabel={t({
|
|
126
|
+
message: "Edit",
|
|
127
|
+
comment: "@context: Button to edit post",
|
|
128
|
+
})}
|
|
110
129
|
viewHref={`/p/${sqid.encode(post.id)}`}
|
|
111
|
-
viewLabel={t({
|
|
130
|
+
viewLabel={t({
|
|
131
|
+
message: "View",
|
|
132
|
+
comment: "@context: Button to view post",
|
|
133
|
+
})}
|
|
112
134
|
/>
|
|
113
135
|
</div>
|
|
114
136
|
|
|
115
137
|
<div class="card">
|
|
116
138
|
<section>
|
|
117
|
-
<div
|
|
139
|
+
<div
|
|
140
|
+
class="prose"
|
|
141
|
+
dangerouslySetInnerHTML={{ __html: post.contentHtml || "" }}
|
|
142
|
+
/>
|
|
118
143
|
</section>
|
|
119
144
|
</div>
|
|
120
145
|
</>
|
|
@@ -145,9 +170,14 @@ postsRoutes.get("/:id", async (c) => {
|
|
|
145
170
|
const pageTitle = post.title || "Post";
|
|
146
171
|
|
|
147
172
|
return c.html(
|
|
148
|
-
<DashLayout
|
|
173
|
+
<DashLayout
|
|
174
|
+
c={c}
|
|
175
|
+
title={pageTitle}
|
|
176
|
+
siteName={siteName}
|
|
177
|
+
currentPath="/dash/posts"
|
|
178
|
+
>
|
|
149
179
|
<ViewPostContent post={post} />
|
|
150
|
-
</DashLayout
|
|
180
|
+
</DashLayout>,
|
|
151
181
|
);
|
|
152
182
|
});
|
|
153
183
|
|
|
@@ -169,7 +199,7 @@ postsRoutes.get("/:id/edit", async (c) => {
|
|
|
169
199
|
currentPath="/dash/posts"
|
|
170
200
|
>
|
|
171
201
|
<EditPostContent post={post} />
|
|
172
|
-
</DashLayout
|
|
202
|
+
</DashLayout>,
|
|
173
203
|
);
|
|
174
204
|
});
|
|
175
205
|
|
|
@@ -178,26 +208,27 @@ postsRoutes.post("/:id", async (c) => {
|
|
|
178
208
|
const id = sqid.decode(c.req.param("id"));
|
|
179
209
|
if (!id) return c.notFound();
|
|
180
210
|
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const path = parseFormDataOptional(formData, "path", z.string()) || null;
|
|
211
|
+
const body = await c.req.json<{
|
|
212
|
+
type: string;
|
|
213
|
+
title?: string;
|
|
214
|
+
content?: string;
|
|
215
|
+
visibility: string;
|
|
216
|
+
sourceUrl?: string;
|
|
217
|
+
path?: string;
|
|
218
|
+
}>();
|
|
190
219
|
|
|
191
220
|
await c.var.services.posts.update(id, {
|
|
192
|
-
type,
|
|
193
|
-
title,
|
|
194
|
-
content,
|
|
195
|
-
visibility,
|
|
196
|
-
sourceUrl,
|
|
197
|
-
path,
|
|
221
|
+
type: body.type as Post["type"],
|
|
222
|
+
title: body.title || null,
|
|
223
|
+
content: body.content || null,
|
|
224
|
+
visibility: body.visibility as Post["visibility"],
|
|
225
|
+
sourceUrl: body.sourceUrl || null,
|
|
226
|
+
path: body.path || null,
|
|
198
227
|
});
|
|
199
228
|
|
|
200
|
-
return c
|
|
229
|
+
return sse(c, async (stream) => {
|
|
230
|
+
await stream.redirect(`/dash/posts/${sqid.encode(id)}`);
|
|
231
|
+
});
|
|
201
232
|
});
|
|
202
233
|
|
|
203
234
|
// Delete post
|
|
@@ -207,5 +238,7 @@ postsRoutes.post("/:id/delete", async (c) => {
|
|
|
207
238
|
|
|
208
239
|
await c.var.services.posts.delete(id);
|
|
209
240
|
|
|
210
|
-
return c
|
|
241
|
+
return sse(c, async (stream) => {
|
|
242
|
+
await stream.redirect("/dash/posts");
|
|
243
|
+
});
|
|
211
244
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
|
-
import { useLingui } from "
|
|
6
|
+
import { useLingui } from "@lingui/react/macro";
|
|
7
7
|
import type { Bindings, Redirect } from "../../types.js";
|
|
8
8
|
import type { AppVariables } from "../../app.js";
|
|
9
9
|
import { DashLayout } from "../../theme/layouts/index.js";
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ActionButtons,
|
|
14
14
|
CrudPageHeader,
|
|
15
15
|
} from "../../theme/components/index.js";
|
|
16
|
+
import { sse } from "../../lib/sse.js";
|
|
16
17
|
|
|
17
18
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
18
19
|
|
|
@@ -24,7 +25,10 @@ function RedirectsListContent({ redirects }: { redirects: Redirect[] }) {
|
|
|
24
25
|
return (
|
|
25
26
|
<>
|
|
26
27
|
<CrudPageHeader
|
|
27
|
-
title={t({
|
|
28
|
+
title={t({
|
|
29
|
+
message: "Redirects",
|
|
30
|
+
comment: "@context: Dashboard heading",
|
|
31
|
+
})}
|
|
28
32
|
ctaLabel={t({
|
|
29
33
|
message: "New Redirect",
|
|
30
34
|
comment: "@context: Button to create new redirect",
|
|
@@ -82,12 +86,25 @@ function NewRedirectContent() {
|
|
|
82
86
|
{t({ message: "New Redirect", comment: "@context: Page heading" })}
|
|
83
87
|
</h1>
|
|
84
88
|
|
|
85
|
-
<form
|
|
89
|
+
<form
|
|
90
|
+
data-signals="{fromPath: '', toPath: '', type: '301'}"
|
|
91
|
+
data-on:submit__prevent="@post('/dash/redirects')"
|
|
92
|
+
class="flex flex-col gap-4 max-w-lg"
|
|
93
|
+
>
|
|
86
94
|
<div class="field">
|
|
87
95
|
<label class="label">
|
|
88
|
-
{t({
|
|
96
|
+
{t({
|
|
97
|
+
message: "From Path",
|
|
98
|
+
comment: "@context: Redirect form field",
|
|
99
|
+
})}
|
|
89
100
|
</label>
|
|
90
|
-
<input
|
|
101
|
+
<input
|
|
102
|
+
type="text"
|
|
103
|
+
data-bind="fromPath"
|
|
104
|
+
class="input"
|
|
105
|
+
placeholder="/old-path"
|
|
106
|
+
required
|
|
107
|
+
/>
|
|
91
108
|
<p class="text-xs text-muted-foreground mt-1">
|
|
92
109
|
{t({
|
|
93
110
|
message: "The path to redirect from",
|
|
@@ -98,11 +115,14 @@ function NewRedirectContent() {
|
|
|
98
115
|
|
|
99
116
|
<div class="field">
|
|
100
117
|
<label class="label">
|
|
101
|
-
{t({
|
|
118
|
+
{t({
|
|
119
|
+
message: "To Path",
|
|
120
|
+
comment: "@context: Redirect form field",
|
|
121
|
+
})}
|
|
102
122
|
</label>
|
|
103
123
|
<input
|
|
104
124
|
type="text"
|
|
105
|
-
|
|
125
|
+
data-bind="toPath"
|
|
106
126
|
class="input"
|
|
107
127
|
placeholder="/new-path or https://..."
|
|
108
128
|
required
|
|
@@ -119,22 +139,34 @@ function NewRedirectContent() {
|
|
|
119
139
|
<label class="label">
|
|
120
140
|
{t({ message: "Type", comment: "@context: Redirect form field" })}
|
|
121
141
|
</label>
|
|
122
|
-
<select
|
|
142
|
+
<select data-bind="type" class="select">
|
|
123
143
|
<option value="301">
|
|
124
|
-
{t({
|
|
144
|
+
{t({
|
|
145
|
+
message: "301 (Permanent)",
|
|
146
|
+
comment: "@context: Redirect type option",
|
|
147
|
+
})}
|
|
125
148
|
</option>
|
|
126
149
|
<option value="302">
|
|
127
|
-
{t({
|
|
150
|
+
{t({
|
|
151
|
+
message: "302 (Temporary)",
|
|
152
|
+
comment: "@context: Redirect type option",
|
|
153
|
+
})}
|
|
128
154
|
</option>
|
|
129
155
|
</select>
|
|
130
156
|
</div>
|
|
131
157
|
|
|
132
158
|
<div class="flex gap-2">
|
|
133
159
|
<button type="submit" class="btn">
|
|
134
|
-
{t({
|
|
160
|
+
{t({
|
|
161
|
+
message: "Create Redirect",
|
|
162
|
+
comment: "@context: Button to save new redirect",
|
|
163
|
+
})}
|
|
135
164
|
</button>
|
|
136
165
|
<a href="/dash/redirects" class="btn-outline">
|
|
137
|
-
{t({
|
|
166
|
+
{t({
|
|
167
|
+
message: "Cancel",
|
|
168
|
+
comment: "@context: Button to cancel form",
|
|
169
|
+
})}
|
|
138
170
|
</a>
|
|
139
171
|
</div>
|
|
140
172
|
</form>
|
|
@@ -148,9 +180,14 @@ redirectsRoutes.get("/", async (c) => {
|
|
|
148
180
|
const redirects = await c.var.services.redirects.list();
|
|
149
181
|
|
|
150
182
|
return c.html(
|
|
151
|
-
<DashLayout
|
|
183
|
+
<DashLayout
|
|
184
|
+
c={c}
|
|
185
|
+
title="Redirects"
|
|
186
|
+
siteName={siteName}
|
|
187
|
+
currentPath="/dash/redirects"
|
|
188
|
+
>
|
|
152
189
|
<RedirectsListContent redirects={redirects} />
|
|
153
|
-
</DashLayout
|
|
190
|
+
</DashLayout>,
|
|
154
191
|
);
|
|
155
192
|
});
|
|
156
193
|
|
|
@@ -159,23 +196,31 @@ redirectsRoutes.get("/new", async (c) => {
|
|
|
159
196
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
160
197
|
|
|
161
198
|
return c.html(
|
|
162
|
-
<DashLayout
|
|
199
|
+
<DashLayout
|
|
200
|
+
c={c}
|
|
201
|
+
title="New Redirect"
|
|
202
|
+
siteName={siteName}
|
|
203
|
+
currentPath="/dash/redirects"
|
|
204
|
+
>
|
|
163
205
|
<NewRedirectContent />
|
|
164
|
-
</DashLayout
|
|
206
|
+
</DashLayout>,
|
|
165
207
|
);
|
|
166
208
|
});
|
|
167
209
|
|
|
168
210
|
// Create redirect
|
|
169
211
|
redirectsRoutes.post("/", async (c) => {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
212
|
+
const body = await c.req.json<{
|
|
213
|
+
fromPath: string;
|
|
214
|
+
toPath: string;
|
|
215
|
+
type: string;
|
|
216
|
+
}>();
|
|
217
|
+
|
|
218
|
+
const type = parseInt(body.type, 10) as 301 | 302;
|
|
219
|
+
await c.var.services.redirects.create(body.fromPath, body.toPath, type);
|
|
220
|
+
|
|
221
|
+
return sse(c, async (stream) => {
|
|
222
|
+
await stream.redirect("/dash/redirects");
|
|
223
|
+
});
|
|
179
224
|
});
|
|
180
225
|
|
|
181
226
|
// Delete redirect
|
|
@@ -184,5 +229,8 @@ redirectsRoutes.post("/:id/delete", async (c) => {
|
|
|
184
229
|
if (!isNaN(id)) {
|
|
185
230
|
await c.var.services.redirects.delete(id);
|
|
186
231
|
}
|
|
187
|
-
|
|
232
|
+
|
|
233
|
+
return sse(c, async (stream) => {
|
|
234
|
+
await stream.redirect("/dash/redirects");
|
|
235
|
+
});
|
|
188
236
|
});
|