@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
|
@@ -3,32 +3,55 @@
|
|
|
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, Collection, Post } from "../../types.js";
|
|
8
8
|
import type { AppVariables } from "../../app.js";
|
|
9
9
|
import { DashLayout } from "../../theme/layouts/index.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
EmptyState,
|
|
12
|
+
ListItemRow,
|
|
13
|
+
ActionButtons,
|
|
14
|
+
CrudPageHeader,
|
|
15
|
+
DangerZone,
|
|
16
|
+
} from "../../theme/components/index.js";
|
|
11
17
|
import * as sqid from "../../lib/sqid.js";
|
|
18
|
+
import { sse } from "../../lib/sse.js";
|
|
12
19
|
|
|
13
20
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
14
21
|
|
|
15
22
|
export const collectionsRoutes = new Hono<Env>();
|
|
16
23
|
|
|
17
|
-
function CollectionsListContent({
|
|
24
|
+
function CollectionsListContent({
|
|
25
|
+
collections,
|
|
26
|
+
}: {
|
|
27
|
+
collections: Collection[];
|
|
28
|
+
}) {
|
|
18
29
|
const { t } = useLingui();
|
|
19
30
|
|
|
20
31
|
return (
|
|
21
32
|
<>
|
|
22
33
|
<CrudPageHeader
|
|
23
|
-
title={t({
|
|
24
|
-
|
|
34
|
+
title={t({
|
|
35
|
+
message: "Collections",
|
|
36
|
+
comment: "@context: Dashboard heading",
|
|
37
|
+
})}
|
|
38
|
+
ctaLabel={t({
|
|
39
|
+
message: "New Collection",
|
|
40
|
+
comment: "@context: Button to create new collection",
|
|
41
|
+
})}
|
|
25
42
|
ctaHref="/dash/collections/new"
|
|
26
43
|
/>
|
|
27
44
|
|
|
28
45
|
{collections.length === 0 ? (
|
|
29
46
|
<EmptyState
|
|
30
|
-
message={t({
|
|
31
|
-
|
|
47
|
+
message={t({
|
|
48
|
+
message: "No collections yet.",
|
|
49
|
+
comment: "@context: Empty state message",
|
|
50
|
+
})}
|
|
51
|
+
ctaText={t({
|
|
52
|
+
message: "New Collection",
|
|
53
|
+
comment: "@context: Button to create new collection",
|
|
54
|
+
})}
|
|
32
55
|
ctaHref="/dash/collections/new"
|
|
33
56
|
/>
|
|
34
57
|
) : (
|
|
@@ -39,18 +62,29 @@ function CollectionsListContent({ collections }: { collections: Collection[] })
|
|
|
39
62
|
actions={
|
|
40
63
|
<ActionButtons
|
|
41
64
|
editHref={`/dash/collections/${col.id}/edit`}
|
|
42
|
-
editLabel={t({
|
|
65
|
+
editLabel={t({
|
|
66
|
+
message: "Edit",
|
|
67
|
+
comment: "@context: Button to edit collection",
|
|
68
|
+
})}
|
|
43
69
|
viewHref={`/c/${col.path}`}
|
|
44
|
-
viewLabel={t({
|
|
70
|
+
viewLabel={t({
|
|
71
|
+
message: "View",
|
|
72
|
+
comment: "@context: Button to view collection",
|
|
73
|
+
})}
|
|
45
74
|
/>
|
|
46
75
|
}
|
|
47
76
|
>
|
|
48
|
-
<a
|
|
77
|
+
<a
|
|
78
|
+
href={`/dash/collections/${col.id}`}
|
|
79
|
+
class="font-medium hover:underline"
|
|
80
|
+
>
|
|
49
81
|
{col.title}
|
|
50
82
|
</a>
|
|
51
83
|
<p class="text-sm text-muted-foreground">/{col.path}</p>
|
|
52
84
|
{col.description && (
|
|
53
|
-
<p class="text-sm text-muted-foreground mt-1">
|
|
85
|
+
<p class="text-sm text-muted-foreground mt-1">
|
|
86
|
+
{col.description}
|
|
87
|
+
</p>
|
|
54
88
|
)}
|
|
55
89
|
</ListItemRow>
|
|
56
90
|
))}
|
|
@@ -64,40 +98,84 @@ function NewCollectionContent() {
|
|
|
64
98
|
const { t } = useLingui();
|
|
65
99
|
return (
|
|
66
100
|
<>
|
|
67
|
-
<h1 class="text-2xl font-semibold mb-6">
|
|
68
|
-
|
|
69
|
-
|
|
101
|
+
<h1 class="text-2xl font-semibold mb-6">
|
|
102
|
+
{t({ message: "New Collection", comment: "@context: Page heading" })}
|
|
103
|
+
</h1>
|
|
104
|
+
|
|
105
|
+
<form
|
|
106
|
+
data-signals="{title: '', path: '', description: ''}"
|
|
107
|
+
data-on:submit__prevent="@post('/dash/collections')"
|
|
108
|
+
class="flex flex-col gap-4 max-w-lg"
|
|
109
|
+
>
|
|
70
110
|
<div class="field">
|
|
71
|
-
<label class="label">
|
|
72
|
-
|
|
111
|
+
<label class="label">
|
|
112
|
+
{t({
|
|
113
|
+
message: "Title",
|
|
114
|
+
comment: "@context: Collection form field",
|
|
115
|
+
})}
|
|
116
|
+
</label>
|
|
117
|
+
<input
|
|
118
|
+
type="text"
|
|
119
|
+
data-bind="title"
|
|
120
|
+
class="input"
|
|
121
|
+
required
|
|
122
|
+
placeholder={t({
|
|
123
|
+
message: "My Collection",
|
|
124
|
+
comment: "@context: Collection title placeholder",
|
|
125
|
+
})}
|
|
126
|
+
/>
|
|
73
127
|
</div>
|
|
74
128
|
|
|
75
129
|
<div class="field">
|
|
76
|
-
<label class="label">
|
|
130
|
+
<label class="label">
|
|
131
|
+
{t({ message: "Slug", comment: "@context: Collection form field" })}
|
|
132
|
+
</label>
|
|
77
133
|
<input
|
|
78
134
|
type="text"
|
|
79
|
-
|
|
135
|
+
data-bind="path"
|
|
80
136
|
class="input"
|
|
81
137
|
required
|
|
82
138
|
placeholder="my-collection"
|
|
83
139
|
pattern="[a-z0-9-]+"
|
|
84
140
|
/>
|
|
85
141
|
<p class="text-xs text-muted-foreground mt-1">
|
|
86
|
-
{t({
|
|
142
|
+
{t({
|
|
143
|
+
message: "URL-safe identifier (lowercase, numbers, hyphens)",
|
|
144
|
+
comment: "@context: Collection path help text",
|
|
145
|
+
})}
|
|
87
146
|
</p>
|
|
88
147
|
</div>
|
|
89
148
|
|
|
90
149
|
<div class="field">
|
|
91
|
-
<label class="label">
|
|
92
|
-
|
|
150
|
+
<label class="label">
|
|
151
|
+
{t({
|
|
152
|
+
message: "Description (optional)",
|
|
153
|
+
comment: "@context: Collection form field",
|
|
154
|
+
})}
|
|
155
|
+
</label>
|
|
156
|
+
<textarea
|
|
157
|
+
data-bind="description"
|
|
158
|
+
class="textarea"
|
|
159
|
+
rows={3}
|
|
160
|
+
placeholder={t({
|
|
161
|
+
message: "What's this collection about?",
|
|
162
|
+
comment: "@context: Collection description placeholder",
|
|
163
|
+
})}
|
|
164
|
+
/>
|
|
93
165
|
</div>
|
|
94
166
|
|
|
95
167
|
<div class="flex gap-2">
|
|
96
168
|
<button type="submit" class="btn">
|
|
97
|
-
{t({
|
|
169
|
+
{t({
|
|
170
|
+
message: "Create Collection",
|
|
171
|
+
comment: "@context: Button to save new collection",
|
|
172
|
+
})}
|
|
98
173
|
</button>
|
|
99
174
|
<a href="/dash/collections" class="btn-outline">
|
|
100
|
-
{t({
|
|
175
|
+
{t({
|
|
176
|
+
message: "Cancel",
|
|
177
|
+
comment: "@context: Button to cancel form",
|
|
178
|
+
})}
|
|
101
179
|
</a>
|
|
102
180
|
</div>
|
|
103
181
|
</form>
|
|
@@ -105,9 +183,19 @@ function NewCollectionContent() {
|
|
|
105
183
|
);
|
|
106
184
|
}
|
|
107
185
|
|
|
108
|
-
function ViewCollectionContent({
|
|
186
|
+
function ViewCollectionContent({
|
|
187
|
+
collection,
|
|
188
|
+
posts,
|
|
189
|
+
}: {
|
|
190
|
+
collection: Collection;
|
|
191
|
+
posts: Post[];
|
|
192
|
+
}) {
|
|
109
193
|
const { t } = useLingui();
|
|
110
|
-
const postsHeader = t({
|
|
194
|
+
const postsHeader = t({
|
|
195
|
+
message: "Posts in Collection ({count})",
|
|
196
|
+
comment: "@context: Collection posts section heading",
|
|
197
|
+
values: { count: String(posts.length) },
|
|
198
|
+
});
|
|
111
199
|
|
|
112
200
|
return (
|
|
113
201
|
<>
|
|
@@ -118,9 +206,15 @@ function ViewCollectionContent({ collection, posts }: { collection: Collection;
|
|
|
118
206
|
</div>
|
|
119
207
|
<ActionButtons
|
|
120
208
|
editHref={`/dash/collections/${collection.id}/edit`}
|
|
121
|
-
editLabel={t({
|
|
209
|
+
editLabel={t({
|
|
210
|
+
message: "Edit",
|
|
211
|
+
comment: "@context: Button to edit collection",
|
|
212
|
+
})}
|
|
122
213
|
viewHref={`/c/${collection.path}`}
|
|
123
|
-
viewLabel={t({
|
|
214
|
+
viewLabel={t({
|
|
215
|
+
message: "View",
|
|
216
|
+
comment: "@context: Button to view collection",
|
|
217
|
+
})}
|
|
124
218
|
/>
|
|
125
219
|
</div>
|
|
126
220
|
|
|
@@ -134,7 +228,12 @@ function ViewCollectionContent({ collection, posts }: { collection: Collection;
|
|
|
134
228
|
</header>
|
|
135
229
|
<section>
|
|
136
230
|
{posts.length === 0 ? (
|
|
137
|
-
<p class="text-muted-foreground">
|
|
231
|
+
<p class="text-muted-foreground">
|
|
232
|
+
{t({
|
|
233
|
+
message: "No posts in this collection.",
|
|
234
|
+
comment: "@context: Empty state message",
|
|
235
|
+
})}
|
|
236
|
+
</p>
|
|
138
237
|
) : (
|
|
139
238
|
<div class="flex flex-col divide-y">
|
|
140
239
|
{posts.map((post) => (
|
|
@@ -144,15 +243,22 @@ function ViewCollectionContent({ collection, posts }: { collection: Collection;
|
|
|
144
243
|
href={`/dash/posts/${sqid.encode(post.id)}`}
|
|
145
244
|
class="font-medium hover:underline"
|
|
146
245
|
>
|
|
147
|
-
{post.title ||
|
|
246
|
+
{post.title ||
|
|
247
|
+
post.content?.slice(0, 50) ||
|
|
248
|
+
`Post #${post.id}`}
|
|
148
249
|
</a>
|
|
149
250
|
</div>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
251
|
+
<button
|
|
252
|
+
type="button"
|
|
253
|
+
class="btn-sm-ghost text-destructive"
|
|
254
|
+
data-on:click__prevent={`@post('/dash/collections/${collection.id}/remove-post', {payload: {postId: ${post.id}}})`}
|
|
255
|
+
>
|
|
256
|
+
{t({
|
|
257
|
+
message: "Remove",
|
|
258
|
+
comment:
|
|
259
|
+
"@context: Button to remove post from collection",
|
|
260
|
+
})}
|
|
261
|
+
</button>
|
|
156
262
|
</div>
|
|
157
263
|
))}
|
|
158
264
|
</div>
|
|
@@ -162,7 +268,10 @@ function ViewCollectionContent({ collection, posts }: { collection: Collection;
|
|
|
162
268
|
|
|
163
269
|
<div class="mt-6">
|
|
164
270
|
<a href="/dash/collections" class="text-sm hover:underline">
|
|
165
|
-
{t({
|
|
271
|
+
{t({
|
|
272
|
+
message: "← Back to Collections",
|
|
273
|
+
comment: "@context: Navigation link",
|
|
274
|
+
})}
|
|
166
275
|
</a>
|
|
167
276
|
</div>
|
|
168
277
|
</>
|
|
@@ -172,47 +281,79 @@ function ViewCollectionContent({ collection, posts }: { collection: Collection;
|
|
|
172
281
|
function EditCollectionContent({ collection }: { collection: Collection }) {
|
|
173
282
|
const { t } = useLingui();
|
|
174
283
|
|
|
284
|
+
const signals = JSON.stringify({
|
|
285
|
+
title: collection.title,
|
|
286
|
+
path: collection.path ?? "",
|
|
287
|
+
description: collection.description ?? "",
|
|
288
|
+
}).replace(/</g, "\\u003c");
|
|
289
|
+
|
|
175
290
|
return (
|
|
176
291
|
<>
|
|
177
|
-
<h1 class="text-2xl font-semibold mb-6">
|
|
178
|
-
|
|
179
|
-
|
|
292
|
+
<h1 class="text-2xl font-semibold mb-6">
|
|
293
|
+
{t({ message: "Edit Collection", comment: "@context: Page heading" })}
|
|
294
|
+
</h1>
|
|
295
|
+
|
|
296
|
+
<form
|
|
297
|
+
data-signals={signals}
|
|
298
|
+
data-on:submit__prevent={`@post('/dash/collections/${collection.id}')`}
|
|
299
|
+
class="flex flex-col gap-4 max-w-lg"
|
|
300
|
+
>
|
|
180
301
|
<div class="field">
|
|
181
|
-
<label class="label">
|
|
182
|
-
|
|
302
|
+
<label class="label">
|
|
303
|
+
{t({
|
|
304
|
+
message: "Title",
|
|
305
|
+
comment: "@context: Collection form field",
|
|
306
|
+
})}
|
|
307
|
+
</label>
|
|
308
|
+
<input type="text" data-bind="title" class="input" required />
|
|
183
309
|
</div>
|
|
184
310
|
|
|
185
311
|
<div class="field">
|
|
186
|
-
<label class="label">
|
|
312
|
+
<label class="label">
|
|
313
|
+
{t({ message: "Slug", comment: "@context: Collection form field" })}
|
|
314
|
+
</label>
|
|
187
315
|
<input
|
|
188
316
|
type="text"
|
|
189
|
-
|
|
317
|
+
data-bind="path"
|
|
190
318
|
class="input"
|
|
191
319
|
required
|
|
192
|
-
value={collection.path ?? ""}
|
|
193
320
|
pattern="[a-z0-9-]+"
|
|
194
321
|
/>
|
|
195
322
|
</div>
|
|
196
323
|
|
|
197
324
|
<div class="field">
|
|
198
|
-
<label class="label">
|
|
199
|
-
|
|
325
|
+
<label class="label">
|
|
326
|
+
{t({
|
|
327
|
+
message: "Description (optional)",
|
|
328
|
+
comment: "@context: Collection form field",
|
|
329
|
+
})}
|
|
330
|
+
</label>
|
|
331
|
+
<textarea data-bind="description" class="textarea" rows={3}>
|
|
200
332
|
{collection.description ?? ""}
|
|
201
333
|
</textarea>
|
|
202
334
|
</div>
|
|
203
335
|
|
|
204
336
|
<div class="flex gap-2">
|
|
205
337
|
<button type="submit" class="btn">
|
|
206
|
-
{t({
|
|
338
|
+
{t({
|
|
339
|
+
message: "Update Collection",
|
|
340
|
+
comment: "@context: Button to save collection changes",
|
|
341
|
+
})}
|
|
207
342
|
</button>
|
|
208
343
|
<a href={`/dash/collections/${collection.id}`} class="btn-outline">
|
|
209
|
-
{t({
|
|
344
|
+
{t({
|
|
345
|
+
message: "Cancel",
|
|
346
|
+
comment: "@context: Button to cancel form",
|
|
347
|
+
})}
|
|
210
348
|
</a>
|
|
211
349
|
</div>
|
|
212
350
|
</form>
|
|
213
351
|
|
|
214
352
|
<DangerZone
|
|
215
|
-
actionLabel={t({
|
|
353
|
+
actionLabel={t({
|
|
354
|
+
message: "Delete Collection",
|
|
355
|
+
comment: "@context: Button to delete collection",
|
|
356
|
+
})}
|
|
216
357
|
formAction={`/dash/collections/${collection.id}/delete`}
|
|
217
358
|
confirmMessage="Are you sure you want to delete this collection?"
|
|
218
359
|
/>
|
|
@@ -226,9 +367,14 @@ collectionsRoutes.get("/", async (c) => {
|
|
|
226
367
|
const collections = await c.var.services.collections.list();
|
|
227
368
|
|
|
228
369
|
return c.html(
|
|
229
|
-
<DashLayout
|
|
370
|
+
<DashLayout
|
|
371
|
+
c={c}
|
|
372
|
+
title="Collections"
|
|
373
|
+
siteName={siteName}
|
|
374
|
+
currentPath="/dash/collections"
|
|
375
|
+
>
|
|
230
376
|
<CollectionsListContent collections={collections} />
|
|
231
|
-
</DashLayout
|
|
377
|
+
</DashLayout>,
|
|
232
378
|
);
|
|
233
379
|
});
|
|
234
380
|
|
|
@@ -237,27 +383,34 @@ collectionsRoutes.get("/new", async (c) => {
|
|
|
237
383
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
238
384
|
|
|
239
385
|
return c.html(
|
|
240
|
-
<DashLayout
|
|
386
|
+
<DashLayout
|
|
387
|
+
c={c}
|
|
388
|
+
title="New Collection"
|
|
389
|
+
siteName={siteName}
|
|
390
|
+
currentPath="/dash/collections"
|
|
391
|
+
>
|
|
241
392
|
<NewCollectionContent />
|
|
242
|
-
</DashLayout
|
|
393
|
+
</DashLayout>,
|
|
243
394
|
);
|
|
244
395
|
});
|
|
245
396
|
|
|
246
397
|
// Create collection
|
|
247
398
|
collectionsRoutes.post("/", async (c) => {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
399
|
+
const body = await c.req.json<{
|
|
400
|
+
title: string;
|
|
401
|
+
path: string;
|
|
402
|
+
description?: string;
|
|
403
|
+
}>();
|
|
253
404
|
|
|
254
405
|
const collection = await c.var.services.collections.create({
|
|
255
|
-
title,
|
|
256
|
-
path,
|
|
257
|
-
description: description || undefined,
|
|
406
|
+
title: body.title,
|
|
407
|
+
path: body.path,
|
|
408
|
+
description: body.description || undefined,
|
|
258
409
|
});
|
|
259
410
|
|
|
260
|
-
return c
|
|
411
|
+
return sse(c, async (stream) => {
|
|
412
|
+
await stream.redirect(`/dash/collections/${collection.id}`);
|
|
413
|
+
});
|
|
261
414
|
});
|
|
262
415
|
|
|
263
416
|
// View single collection
|
|
@@ -272,9 +425,14 @@ collectionsRoutes.get("/:id", async (c) => {
|
|
|
272
425
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
273
426
|
|
|
274
427
|
return c.html(
|
|
275
|
-
<DashLayout
|
|
428
|
+
<DashLayout
|
|
429
|
+
c={c}
|
|
430
|
+
title={collection.title}
|
|
431
|
+
siteName={siteName}
|
|
432
|
+
currentPath="/dash/collections"
|
|
433
|
+
>
|
|
276
434
|
<ViewCollectionContent collection={collection} posts={posts} />
|
|
277
|
-
</DashLayout
|
|
435
|
+
</DashLayout>,
|
|
278
436
|
);
|
|
279
437
|
});
|
|
280
438
|
|
|
@@ -289,9 +447,14 @@ collectionsRoutes.get("/:id/edit", async (c) => {
|
|
|
289
447
|
const siteName = (await c.var.services.settings.get("SITE_NAME")) ?? "Jant";
|
|
290
448
|
|
|
291
449
|
return c.html(
|
|
292
|
-
<DashLayout
|
|
450
|
+
<DashLayout
|
|
451
|
+
c={c}
|
|
452
|
+
title={`Edit: ${collection.title}`}
|
|
453
|
+
siteName={siteName}
|
|
454
|
+
currentPath="/dash/collections"
|
|
455
|
+
>
|
|
293
456
|
<EditCollectionContent collection={collection} />
|
|
294
|
-
</DashLayout
|
|
457
|
+
</DashLayout>,
|
|
295
458
|
);
|
|
296
459
|
});
|
|
297
460
|
|
|
@@ -300,19 +463,21 @@ collectionsRoutes.post("/:id", async (c) => {
|
|
|
300
463
|
const id = parseInt(c.req.param("id"), 10);
|
|
301
464
|
if (isNaN(id)) return c.notFound();
|
|
302
465
|
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
466
|
+
const body = await c.req.json<{
|
|
467
|
+
title: string;
|
|
468
|
+
path: string;
|
|
469
|
+
description?: string;
|
|
470
|
+
}>();
|
|
308
471
|
|
|
309
472
|
await c.var.services.collections.update(id, {
|
|
310
|
-
title,
|
|
311
|
-
path,
|
|
312
|
-
description: description || undefined,
|
|
473
|
+
title: body.title,
|
|
474
|
+
path: body.path,
|
|
475
|
+
description: body.description || undefined,
|
|
313
476
|
});
|
|
314
477
|
|
|
315
|
-
return c
|
|
478
|
+
return sse(c, async (stream) => {
|
|
479
|
+
await stream.redirect(`/dash/collections/${id}`);
|
|
480
|
+
});
|
|
316
481
|
});
|
|
317
482
|
|
|
318
483
|
// Delete collection
|
|
@@ -322,7 +487,9 @@ collectionsRoutes.post("/:id/delete", async (c) => {
|
|
|
322
487
|
|
|
323
488
|
await c.var.services.collections.delete(id);
|
|
324
489
|
|
|
325
|
-
return c
|
|
490
|
+
return sse(c, async (stream) => {
|
|
491
|
+
await stream.redirect("/dash/collections");
|
|
492
|
+
});
|
|
326
493
|
});
|
|
327
494
|
|
|
328
495
|
// Remove post from collection
|
|
@@ -330,12 +497,13 @@ collectionsRoutes.post("/:id/remove-post", async (c) => {
|
|
|
330
497
|
const id = parseInt(c.req.param("id"), 10);
|
|
331
498
|
if (isNaN(id)) return c.notFound();
|
|
332
499
|
|
|
333
|
-
const
|
|
334
|
-
const postId = parseInt(formData.get("postId") as string, 10);
|
|
500
|
+
const body = await c.req.json<{ postId: number }>();
|
|
335
501
|
|
|
336
|
-
if (
|
|
337
|
-
await c.var.services.collections.removePost(id, postId);
|
|
502
|
+
if (body.postId) {
|
|
503
|
+
await c.var.services.collections.removePost(id, body.postId);
|
|
338
504
|
}
|
|
339
505
|
|
|
340
|
-
return c
|
|
506
|
+
return sse(c, async (stream) => {
|
|
507
|
+
await stream.redirect(`/dash/collections/${id}`);
|
|
508
|
+
});
|
|
341
509
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Hono } from "hono";
|
|
8
|
-
import { Trans, useLingui } from "
|
|
8
|
+
import { Trans, useLingui } from "@lingui/react/macro";
|
|
9
9
|
import type { Bindings } from "../../types.js";
|
|
10
10
|
import type { AppVariables } from "../../app.js";
|
|
11
11
|
import { DashLayout } from "../../theme/layouts/index.js";
|
|
@@ -32,13 +32,19 @@ function DashboardContent({
|
|
|
32
32
|
<div class="container py-8">
|
|
33
33
|
<h1 class="text-2xl font-semibold mb-6">
|
|
34
34
|
{/* ✅ No more nesting! */}
|
|
35
|
-
{t({
|
|
35
|
+
{t({
|
|
36
|
+
message: "Dashboard",
|
|
37
|
+
comment: "@context: Dashboard main heading",
|
|
38
|
+
})}
|
|
36
39
|
</h1>
|
|
37
40
|
|
|
38
41
|
<div class="grid gap-4 md:grid-cols-3 mb-6">
|
|
39
42
|
<div class="p-4 border rounded">
|
|
40
43
|
<p class="text-sm text-muted-foreground">
|
|
41
|
-
{t({
|
|
44
|
+
{t({
|
|
45
|
+
message: "Published",
|
|
46
|
+
comment: "@context: Post status label",
|
|
47
|
+
})}
|
|
42
48
|
</p>
|
|
43
49
|
<p class="text-3xl font-bold">{publishedCount}</p>
|
|
44
50
|
</div>
|
|
@@ -52,10 +58,16 @@ function DashboardContent({
|
|
|
52
58
|
|
|
53
59
|
<div class="p-4 border rounded">
|
|
54
60
|
<p class="text-sm text-muted-foreground mb-2">
|
|
55
|
-
{t({
|
|
61
|
+
{t({
|
|
62
|
+
message: "Quick Actions",
|
|
63
|
+
comment: "@context: Dashboard section title",
|
|
64
|
+
})}
|
|
56
65
|
</p>
|
|
57
66
|
<a href="/dash/posts/new" class="btn btn-primary w-full">
|
|
58
|
-
{t({
|
|
67
|
+
{t({
|
|
68
|
+
message: "New Post",
|
|
69
|
+
comment: "@context: Button to create new post",
|
|
70
|
+
})}
|
|
59
71
|
</a>
|
|
60
72
|
</div>
|
|
61
73
|
</div>
|
|
@@ -63,7 +75,10 @@ function DashboardContent({
|
|
|
63
75
|
{/* ✅ Trans component with embedded JSX! */}
|
|
64
76
|
<p>
|
|
65
77
|
<Trans comment="@context: Help text with link">
|
|
66
|
-
Need help? Visit the
|
|
78
|
+
Need help? Visit the{" "}
|
|
79
|
+
<a href="/docs" class="underline">
|
|
80
|
+
documentation
|
|
81
|
+
</a>
|
|
67
82
|
</Trans>
|
|
68
83
|
</p>
|
|
69
84
|
</div>
|
|
@@ -84,6 +99,6 @@ dashIndexRoutes.get("/", async (c) => {
|
|
|
84
99
|
publishedCount={publishedPosts.length}
|
|
85
100
|
draftCount={draftPosts.length}
|
|
86
101
|
/>
|
|
87
|
-
</DashLayout
|
|
102
|
+
</DashLayout>,
|
|
88
103
|
);
|
|
89
104
|
});
|