@jant/core 0.3.23 → 0.3.24
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.js +4 -5
- package/dist/db/schema.js +72 -47
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.js +3 -3
- package/dist/lib/constants.js +1 -4
- package/dist/lib/excerpt.js +76 -0
- package/dist/lib/feed.js +18 -7
- package/dist/lib/navigation.js +4 -5
- package/dist/lib/render.js +1 -1
- package/dist/lib/schemas.js +80 -38
- package/dist/lib/theme-components.js +8 -11
- package/dist/lib/time.js +56 -1
- package/dist/lib/timeline.js +119 -0
- package/dist/lib/view.js +61 -72
- package/dist/routes/api/posts.js +29 -35
- package/dist/routes/api/search.js +5 -6
- package/dist/routes/api/upload.js +13 -13
- package/dist/routes/dash/collections.js +22 -40
- package/dist/routes/dash/index.js +2 -2
- package/dist/routes/dash/navigation.js +25 -24
- package/dist/routes/dash/pages.js +42 -57
- package/dist/routes/dash/posts.js +27 -35
- package/dist/routes/feed/rss.js +2 -4
- package/dist/routes/feed/sitemap.js +10 -7
- package/dist/routes/pages/archive.js +12 -11
- package/dist/routes/pages/collection.js +11 -5
- package/dist/routes/pages/home.js +53 -61
- package/dist/routes/pages/page.js +60 -29
- package/dist/routes/pages/post.js +5 -12
- package/dist/routes/pages/search.js +3 -4
- package/dist/services/collection.js +52 -64
- package/dist/services/index.js +5 -3
- package/dist/services/navigation.js +29 -53
- package/dist/services/page.js +80 -0
- package/dist/services/post.js +68 -69
- package/dist/services/search.js +24 -18
- package/dist/theme/components/MediaGallery.js +19 -91
- package/dist/theme/components/PageForm.js +15 -15
- package/dist/theme/components/PostForm.js +136 -129
- package/dist/theme/components/PostList.js +13 -8
- package/dist/theme/components/ThreadView.js +3 -3
- package/dist/theme/components/TypeBadge.js +3 -14
- package/dist/theme/components/VisibilityBadge.js +33 -23
- package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
- package/dist/themes/threads/index.js +81 -0
- package/dist/themes/{minimal → threads}/pages/ArchivePage.js +32 -47
- package/dist/themes/threads/pages/CollectionPage.js +65 -0
- package/dist/themes/{minimal → threads}/pages/HomePage.js +3 -3
- package/dist/themes/{minimal → threads}/pages/PostPage.js +12 -9
- package/dist/themes/{minimal → threads}/pages/SearchPage.js +13 -14
- package/dist/themes/{minimal → threads}/pages/SinglePage.js +4 -4
- package/dist/themes/threads/timeline/LinkCard.js +68 -0
- package/dist/themes/threads/timeline/NoteCard.js +53 -0
- package/dist/themes/threads/timeline/QuoteCard.js +59 -0
- package/dist/themes/{minimal → threads}/timeline/ThreadPreview.js +17 -13
- package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
- package/dist/themes/{minimal → threads}/timeline/TimelineItem.js +8 -16
- package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
- package/dist/themes/threads/timeline/groupByDate.js +22 -0
- package/dist/themes/threads/timeline/timelineMore.js +107 -0
- package/dist/types.js +24 -40
- package/package.json +2 -1
- package/src/__tests__/helpers/app.ts +4 -0
- package/src/__tests__/helpers/db.ts +51 -74
- package/src/app.tsx +4 -6
- package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/schema.ts +63 -46
- package/src/i18n/locales/en.po +216 -164
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +216 -164
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +216 -164
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +28 -12
- package/src/lib/__tests__/excerpt.test.ts +125 -0
- package/src/lib/__tests__/schemas.test.ts +166 -105
- package/src/lib/__tests__/theme-components.test.ts +4 -25
- package/src/lib/__tests__/time.test.ts +62 -0
- package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
- package/src/lib/__tests__/view.test.ts +199 -51
- package/src/lib/constants.ts +1 -4
- package/src/lib/excerpt.ts +87 -0
- package/src/lib/feed.ts +22 -7
- package/src/lib/navigation.ts +6 -7
- package/src/lib/render.tsx +1 -1
- package/src/lib/schemas.ts +118 -52
- package/src/lib/theme-components.ts +10 -13
- package/src/lib/time.ts +64 -0
- package/src/lib/timeline.ts +170 -0
- package/src/lib/view.ts +80 -82
- package/src/preset.css +45 -0
- package/src/routes/api/__tests__/posts.test.ts +50 -108
- package/src/routes/api/__tests__/search.test.ts +2 -3
- package/src/routes/api/posts.ts +30 -30
- package/src/routes/api/search.ts +4 -4
- package/src/routes/api/upload.ts +16 -6
- package/src/routes/dash/collections.tsx +18 -40
- package/src/routes/dash/index.tsx +2 -2
- package/src/routes/dash/navigation.tsx +27 -26
- package/src/routes/dash/pages.tsx +45 -60
- package/src/routes/dash/posts.tsx +44 -52
- package/src/routes/feed/rss.ts +2 -1
- package/src/routes/feed/sitemap.ts +14 -4
- package/src/routes/pages/archive.tsx +14 -10
- package/src/routes/pages/collection.tsx +17 -6
- package/src/routes/pages/home.tsx +56 -81
- package/src/routes/pages/page.tsx +64 -27
- package/src/routes/pages/post.tsx +5 -14
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +257 -158
- package/src/services/__tests__/media.test.ts +18 -18
- package/src/services/__tests__/navigation.test.ts +161 -87
- package/src/services/__tests__/post-timeline.test.ts +92 -88
- package/src/services/__tests__/post.test.ts +342 -206
- package/src/services/__tests__/search.test.ts +19 -25
- package/src/services/collection.ts +71 -113
- package/src/services/index.ts +9 -8
- package/src/services/navigation.ts +38 -71
- package/src/services/page.ts +124 -0
- package/src/services/post.ts +93 -103
- package/src/services/search.ts +38 -27
- package/src/theme/components/MediaGallery.tsx +27 -96
- package/src/theme/components/PageForm.tsx +21 -21
- package/src/theme/components/PostForm.tsx +122 -118
- package/src/theme/components/PostList.tsx +58 -49
- package/src/theme/components/ThreadView.tsx +6 -3
- package/src/theme/components/TypeBadge.tsx +9 -17
- package/src/theme/components/VisibilityBadge.tsx +40 -23
- package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
- package/src/themes/{minimal → threads}/index.ts +30 -13
- package/src/themes/{minimal → threads}/pages/ArchivePage.tsx +53 -53
- package/src/themes/threads/pages/CollectionPage.tsx +61 -0
- package/src/themes/{minimal → threads}/pages/HomePage.tsx +3 -3
- package/src/themes/{minimal → threads}/pages/PostPage.tsx +12 -8
- package/src/themes/{minimal → threads}/pages/SearchPage.tsx +15 -13
- package/src/themes/{minimal → threads}/pages/SinglePage.tsx +4 -4
- package/src/themes/threads/style.css +336 -0
- package/src/themes/threads/timeline/LinkCard.tsx +67 -0
- package/src/themes/threads/timeline/NoteCard.tsx +58 -0
- package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
- package/src/themes/{minimal → threads}/timeline/ThreadPreview.tsx +15 -13
- package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
- package/src/themes/{minimal → threads}/timeline/TimelineItem.tsx +9 -17
- package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
- package/src/themes/threads/timeline/groupByDate.ts +30 -0
- package/src/themes/threads/timeline/timelineMore.tsx +130 -0
- package/src/types.ts +242 -98
- package/dist/routes/api/timeline.js +0 -120
- package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
- package/dist/themes/minimal/index.js +0 -65
- package/dist/themes/minimal/pages/CollectionPage.js +0 -65
- package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
- package/dist/themes/minimal/timeline/ImageCard.js +0 -67
- package/dist/themes/minimal/timeline/LinkCard.js +0 -47
- package/dist/themes/minimal/timeline/NoteCard.js +0 -34
- package/dist/themes/minimal/timeline/QuoteCard.js +0 -48
- package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
- package/src/routes/api/timeline.tsx +0 -159
- package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
- package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
- package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
- package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
- package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
- package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
- package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
- package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
package/dist/routes/api/posts.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Posts API Routes
|
|
3
3
|
*/ import { Hono } from "hono";
|
|
4
4
|
import * as sqid from "../../lib/sqid.js";
|
|
5
|
-
import { CreatePostSchema, UpdatePostSchema,
|
|
5
|
+
import { CreatePostSchema, UpdatePostSchema, validateMediaCount } from "../../lib/schemas.js";
|
|
6
6
|
import { requireAuthApi } from "../../middleware/auth.js";
|
|
7
7
|
import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "../../lib/image.js";
|
|
8
8
|
export const postsApiRoutes = new Hono();
|
|
@@ -31,18 +31,13 @@ export const postsApiRoutes = new Hono();
|
|
|
31
31
|
}
|
|
32
32
|
// List posts
|
|
33
33
|
postsApiRoutes.get("/", async (c)=>{
|
|
34
|
-
const
|
|
35
|
-
const
|
|
34
|
+
const format = c.req.query("format");
|
|
35
|
+
const status = c.req.query("status");
|
|
36
36
|
const cursor = c.req.query("cursor");
|
|
37
37
|
const limit = parseInt(c.req.query("limit") ?? "100", 10);
|
|
38
38
|
const posts = await c.var.services.posts.list({
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
visibility
|
|
42
|
-
] : [
|
|
43
|
-
"featured",
|
|
44
|
-
"quiet"
|
|
45
|
-
],
|
|
39
|
+
format,
|
|
40
|
+
status: status ?? "published",
|
|
46
41
|
cursor: cursor ? sqid.decode(cursor) ?? undefined : undefined,
|
|
47
42
|
limit
|
|
48
43
|
});
|
|
@@ -93,9 +88,9 @@ postsApiRoutes.post("/", requireAuthApi(), async (c)=>{
|
|
|
93
88
|
}, 400);
|
|
94
89
|
}
|
|
95
90
|
const body = parseResult.data;
|
|
96
|
-
// Validate media
|
|
91
|
+
// Validate media count
|
|
97
92
|
if (body.mediaIds) {
|
|
98
|
-
const mediaError =
|
|
93
|
+
const mediaError = validateMediaCount(body.mediaIds);
|
|
99
94
|
if (mediaError) {
|
|
100
95
|
return c.json({
|
|
101
96
|
error: mediaError
|
|
@@ -112,13 +107,17 @@ postsApiRoutes.post("/", requireAuthApi(), async (c)=>{
|
|
|
112
107
|
}
|
|
113
108
|
}
|
|
114
109
|
const post = await c.var.services.posts.create({
|
|
115
|
-
|
|
110
|
+
format: body.format,
|
|
116
111
|
title: body.title,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
body: body.body,
|
|
113
|
+
slug: body.slug || undefined,
|
|
114
|
+
status: body.status,
|
|
115
|
+
featured: body.featured,
|
|
116
|
+
pinned: body.pinned,
|
|
117
|
+
url: body.url || undefined,
|
|
118
|
+
quoteText: body.quoteText,
|
|
119
|
+
rating: body.rating || undefined,
|
|
120
|
+
collectionId: body.collectionId || undefined,
|
|
122
121
|
replyToId: body.replyToId ? sqid.decode(body.replyToId) ?? undefined : undefined,
|
|
123
122
|
publishedAt: body.publishedAt
|
|
124
123
|
});
|
|
@@ -152,18 +151,9 @@ postsApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
|
|
|
152
151
|
}, 400);
|
|
153
152
|
}
|
|
154
153
|
const body = parseResult.data;
|
|
155
|
-
// Validate media
|
|
154
|
+
// Validate media count if mediaIds is provided
|
|
156
155
|
if (body.mediaIds !== undefined) {
|
|
157
|
-
|
|
158
|
-
let postType = body.type;
|
|
159
|
-
if (!postType) {
|
|
160
|
-
const existing = await c.var.services.posts.getById(id);
|
|
161
|
-
if (!existing) return c.json({
|
|
162
|
-
error: "Not found"
|
|
163
|
-
}, 404);
|
|
164
|
-
postType = existing.type;
|
|
165
|
-
}
|
|
166
|
-
const mediaError = validateMediaForPostType(postType, body.mediaIds);
|
|
156
|
+
const mediaError = validateMediaCount(body.mediaIds);
|
|
167
157
|
if (mediaError) {
|
|
168
158
|
return c.json({
|
|
169
159
|
error: mediaError
|
|
@@ -180,13 +170,17 @@ postsApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
|
|
|
180
170
|
}
|
|
181
171
|
}
|
|
182
172
|
const post = await c.var.services.posts.update(id, {
|
|
183
|
-
|
|
173
|
+
format: body.format,
|
|
184
174
|
title: body.title,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
175
|
+
body: body.body,
|
|
176
|
+
slug: body.slug,
|
|
177
|
+
status: body.status,
|
|
178
|
+
featured: body.featured,
|
|
179
|
+
pinned: body.pinned,
|
|
180
|
+
url: body.url,
|
|
181
|
+
quoteText: body.quoteText,
|
|
182
|
+
rating: body.rating || undefined,
|
|
183
|
+
collectionId: body.collectionId || undefined,
|
|
190
184
|
publishedAt: body.publishedAt
|
|
191
185
|
});
|
|
192
186
|
if (!post) return c.json({
|
|
@@ -21,21 +21,20 @@ searchApiRoutes.get("/", async (c)=>{
|
|
|
21
21
|
try {
|
|
22
22
|
const results = await c.var.services.search.search(query, {
|
|
23
23
|
limit,
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
"quiet"
|
|
24
|
+
status: [
|
|
25
|
+
"published"
|
|
27
26
|
]
|
|
28
27
|
});
|
|
29
28
|
return c.json({
|
|
30
29
|
query,
|
|
31
30
|
results: results.map((r)=>({
|
|
32
31
|
id: sqid.encode(r.post.id),
|
|
33
|
-
|
|
32
|
+
format: r.post.format,
|
|
34
33
|
title: r.post.title,
|
|
35
|
-
|
|
34
|
+
slug: r.post.slug,
|
|
36
35
|
snippet: r.snippet,
|
|
37
36
|
publishedAt: r.post.publishedAt,
|
|
38
|
-
url: `/p/${sqid.encode(r.post.id)}`
|
|
37
|
+
url: r.post.slug ? `/${r.post.slug}` : `/p/${sqid.encode(r.post.id)}`
|
|
39
38
|
})),
|
|
40
39
|
count: results.length
|
|
41
40
|
});
|
|
@@ -8,7 +8,7 @@ import { html } from "hono/html";
|
|
|
8
8
|
import { uuidv7 } from "uuidv7";
|
|
9
9
|
import { requireAuthApi } from "../../middleware/auth.js";
|
|
10
10
|
import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "../../lib/image.js";
|
|
11
|
-
import { sse
|
|
11
|
+
import { sse } from "../../lib/sse.js";
|
|
12
12
|
export const uploadApiRoutes = new Hono();
|
|
13
13
|
// Require auth for all upload routes
|
|
14
14
|
uploadApiRoutes.use("*", requireAuthApi());
|
|
@@ -85,14 +85,20 @@ function formatSize(bytes) {
|
|
|
85
85
|
const accept = c.req.header("accept") || "";
|
|
86
86
|
return accept.includes("text/event-stream");
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Return an SSE error response that removes the upload placeholder and shows a toast
|
|
90
|
+
*/ function sseUploadError(c, message) {
|
|
91
|
+
return sse(c, async (stream)=>{
|
|
92
|
+
await stream.remove("#upload-placeholder");
|
|
93
|
+
await stream.toast(message, "error");
|
|
94
|
+
});
|
|
95
|
+
}
|
|
88
96
|
// Upload a file
|
|
89
97
|
uploadApiRoutes.post("/", async (c)=>{
|
|
90
98
|
const storage = c.var.storage;
|
|
91
99
|
if (!storage) {
|
|
92
100
|
if (wantsSSE(c)) {
|
|
93
|
-
return
|
|
94
|
-
_uploadError: "Storage not configured"
|
|
95
|
-
});
|
|
101
|
+
return sseUploadError(c, "Storage not configured");
|
|
96
102
|
}
|
|
97
103
|
return c.json({
|
|
98
104
|
error: "Storage not configured"
|
|
@@ -102,9 +108,7 @@ uploadApiRoutes.post("/", async (c)=>{
|
|
|
102
108
|
const file = formData.get("file");
|
|
103
109
|
if (!file) {
|
|
104
110
|
if (wantsSSE(c)) {
|
|
105
|
-
return
|
|
106
|
-
_uploadError: "No file provided"
|
|
107
|
-
});
|
|
111
|
+
return sseUploadError(c, "No file provided");
|
|
108
112
|
}
|
|
109
113
|
return c.json({
|
|
110
114
|
error: "No file provided"
|
|
@@ -120,9 +124,7 @@ uploadApiRoutes.post("/", async (c)=>{
|
|
|
120
124
|
];
|
|
121
125
|
if (!allowedTypes.includes(file.type)) {
|
|
122
126
|
if (wantsSSE(c)) {
|
|
123
|
-
return
|
|
124
|
-
_uploadError: "File type not allowed"
|
|
125
|
-
});
|
|
127
|
+
return sseUploadError(c, "File type not allowed");
|
|
126
128
|
}
|
|
127
129
|
return c.json({
|
|
128
130
|
error: "File type not allowed"
|
|
@@ -132,9 +134,7 @@ uploadApiRoutes.post("/", async (c)=>{
|
|
|
132
134
|
const maxSize = 10 * 1024 * 1024;
|
|
133
135
|
if (file.size > maxSize) {
|
|
134
136
|
if (wantsSSE(c)) {
|
|
135
|
-
return
|
|
136
|
-
_uploadError: "File too large (max 10MB)"
|
|
137
|
-
});
|
|
137
|
+
return sseUploadError(c, "File too large (max 10MB)");
|
|
138
138
|
}
|
|
139
139
|
return c.json({
|
|
140
140
|
error: "File too large (max 10MB)"
|
|
@@ -43,7 +43,7 @@ function CollectionsListContent({ collections }) {
|
|
|
43
43
|
id: "ePK91l",
|
|
44
44
|
message: "Edit"
|
|
45
45
|
}),
|
|
46
|
-
viewHref: `/c/${col.
|
|
46
|
+
viewHref: `/c/${col.slug}`,
|
|
47
47
|
viewLabel: $__i18n._({
|
|
48
48
|
id: "jpctdh",
|
|
49
49
|
message: "View"
|
|
@@ -59,7 +59,7 @@ function CollectionsListContent({ collections }) {
|
|
|
59
59
|
class: "text-sm text-muted-foreground",
|
|
60
60
|
children: [
|
|
61
61
|
"/",
|
|
62
|
-
col.
|
|
62
|
+
col.slug
|
|
63
63
|
]
|
|
64
64
|
}),
|
|
65
65
|
col.description && /*#__PURE__*/ _jsx("p", {
|
|
@@ -84,7 +84,7 @@ function NewCollectionContent() {
|
|
|
84
84
|
})
|
|
85
85
|
}),
|
|
86
86
|
/*#__PURE__*/ _jsxs("form", {
|
|
87
|
-
"data-signals": "{title: '',
|
|
87
|
+
"data-signals": "{title: '', slug: '', description: ''}",
|
|
88
88
|
"data-on:submit__prevent": "@post('/dash/collections')",
|
|
89
89
|
"data-indicator": "_loading",
|
|
90
90
|
class: "flex flex-col gap-4 max-w-lg",
|
|
@@ -123,7 +123,7 @@ function NewCollectionContent() {
|
|
|
123
123
|
}),
|
|
124
124
|
/*#__PURE__*/ _jsx("input", {
|
|
125
125
|
type: "text",
|
|
126
|
-
"data-bind": "
|
|
126
|
+
"data-bind": "slug",
|
|
127
127
|
class: "input",
|
|
128
128
|
required: true,
|
|
129
129
|
placeholder: "my-collection",
|
|
@@ -219,7 +219,7 @@ function ViewCollectionContent({ collection, posts }) {
|
|
|
219
219
|
class: "text-sm text-muted-foreground",
|
|
220
220
|
children: [
|
|
221
221
|
"/",
|
|
222
|
-
collection.
|
|
222
|
+
collection.slug
|
|
223
223
|
]
|
|
224
224
|
})
|
|
225
225
|
]
|
|
@@ -230,7 +230,7 @@ function ViewCollectionContent({ collection, posts }) {
|
|
|
230
230
|
id: "ePK91l",
|
|
231
231
|
message: "Edit"
|
|
232
232
|
}),
|
|
233
|
-
viewHref: `/c/${collection.
|
|
233
|
+
viewHref: `/c/${collection.slug}`,
|
|
234
234
|
viewLabel: $__i18n._({
|
|
235
235
|
id: "jpctdh",
|
|
236
236
|
message: "View"
|
|
@@ -259,27 +259,16 @@ function ViewCollectionContent({ collection, posts }) {
|
|
|
259
259
|
})
|
|
260
260
|
}) : /*#__PURE__*/ _jsx("div", {
|
|
261
261
|
class: "flex flex-col divide-y",
|
|
262
|
-
children: posts.map((post)=>/*#__PURE__*/
|
|
262
|
+
children: posts.map((post)=>/*#__PURE__*/ _jsx("div", {
|
|
263
263
|
class: "py-3 flex items-center gap-4",
|
|
264
|
-
children:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
children: post.title || post.content?.slice(0, 50) || `Post #${post.id}`
|
|
271
|
-
})
|
|
272
|
-
}),
|
|
273
|
-
/*#__PURE__*/ _jsx("button", {
|
|
274
|
-
type: "button",
|
|
275
|
-
class: "btn-sm-ghost text-destructive",
|
|
276
|
-
"data-on:click__prevent": `@post('/dash/collections/${collection.id}/remove-post', {payload: {postId: ${post.id}}})`,
|
|
277
|
-
children: $__i18n._({
|
|
278
|
-
id: "t/YqKh",
|
|
279
|
-
message: "Remove"
|
|
280
|
-
})
|
|
264
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
265
|
+
class: "flex-1 min-w-0",
|
|
266
|
+
children: /*#__PURE__*/ _jsx("a", {
|
|
267
|
+
href: `/dash/posts/${sqid.encode(post.id)}`,
|
|
268
|
+
class: "font-medium hover:underline",
|
|
269
|
+
children: post.title || post.body?.slice(0, 50) || `Post #${post.id}`
|
|
281
270
|
})
|
|
282
|
-
|
|
271
|
+
})
|
|
283
272
|
}, post.id))
|
|
284
273
|
})
|
|
285
274
|
})
|
|
@@ -303,7 +292,7 @@ function EditCollectionContent({ collection }) {
|
|
|
303
292
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
304
293
|
const signals = JSON.stringify({
|
|
305
294
|
title: collection.title,
|
|
306
|
-
|
|
295
|
+
slug: collection.slug ?? "",
|
|
307
296
|
description: collection.description ?? ""
|
|
308
297
|
}).replace(/</g, "\\u003c");
|
|
309
298
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
@@ -351,7 +340,7 @@ function EditCollectionContent({ collection }) {
|
|
|
351
340
|
}),
|
|
352
341
|
/*#__PURE__*/ _jsx("input", {
|
|
353
342
|
type: "text",
|
|
354
|
-
"data-bind": "
|
|
343
|
+
"data-bind": "slug",
|
|
355
344
|
class: "input",
|
|
356
345
|
required: true,
|
|
357
346
|
pattern: "[a-z0-9-]+"
|
|
@@ -453,7 +442,7 @@ collectionsRoutes.post("/", async (c)=>{
|
|
|
453
442
|
const body = await c.req.json();
|
|
454
443
|
const collection = await c.var.services.collections.create({
|
|
455
444
|
title: body.title,
|
|
456
|
-
|
|
445
|
+
slug: body.slug,
|
|
457
446
|
description: body.description || undefined
|
|
458
447
|
});
|
|
459
448
|
return dsRedirect(`/dash/collections/${collection.id}`);
|
|
@@ -464,7 +453,10 @@ collectionsRoutes.get("/:id", async (c)=>{
|
|
|
464
453
|
if (isNaN(id)) return c.notFound();
|
|
465
454
|
const collection = await c.var.services.collections.getById(id);
|
|
466
455
|
if (!collection) return c.notFound();
|
|
467
|
-
|
|
456
|
+
// Fetch posts in this collection via post service
|
|
457
|
+
const posts = await c.var.services.posts.list({
|
|
458
|
+
collectionId: id
|
|
459
|
+
});
|
|
468
460
|
const siteName = await getSiteName(c);
|
|
469
461
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
470
462
|
c: c,
|
|
@@ -501,7 +493,7 @@ collectionsRoutes.post("/:id", async (c)=>{
|
|
|
501
493
|
const body = await c.req.json();
|
|
502
494
|
await c.var.services.collections.update(id, {
|
|
503
495
|
title: body.title,
|
|
504
|
-
|
|
496
|
+
slug: body.slug,
|
|
505
497
|
description: body.description || undefined
|
|
506
498
|
});
|
|
507
499
|
return dsRedirect(`/dash/collections/${id}`);
|
|
@@ -513,13 +505,3 @@ collectionsRoutes.post("/:id/delete", async (c)=>{
|
|
|
513
505
|
await c.var.services.collections.delete(id);
|
|
514
506
|
return dsRedirect("/dash/collections");
|
|
515
507
|
});
|
|
516
|
-
// Remove post from collection
|
|
517
|
-
collectionsRoutes.post("/:id/remove-post", async (c)=>{
|
|
518
|
-
const id = parseInt(c.req.param("id"), 10);
|
|
519
|
-
if (isNaN(id)) return c.notFound();
|
|
520
|
-
const body = await c.req.json();
|
|
521
|
-
if (body.postId) {
|
|
522
|
-
await c.var.services.collections.removePost(id, body.postId);
|
|
523
|
-
}
|
|
524
|
-
return dsRedirect(`/dash/collections/${id}`);
|
|
525
|
-
});
|
|
@@ -103,8 +103,8 @@ dashIndexRoutes.get("/", async (c)=>{
|
|
|
103
103
|
const allPosts = await c.var.services.posts.list({
|
|
104
104
|
limit: 1000
|
|
105
105
|
});
|
|
106
|
-
const publishedPosts = allPosts.filter((p)=>p.
|
|
107
|
-
const draftPosts = allPosts.filter((p)=>p.
|
|
106
|
+
const publishedPosts = allPosts.filter((p)=>p.status !== "draft");
|
|
107
|
+
const draftPosts = allPosts.filter((p)=>p.status === "draft");
|
|
108
108
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
109
109
|
c: c,
|
|
110
110
|
title: "Dashboard",
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
2
2
|
import { getSiteName } from "../../lib/config.js";
|
|
3
3
|
/**
|
|
4
|
-
* Dashboard Navigation
|
|
4
|
+
* Dashboard Navigation Items Routes
|
|
5
5
|
*/ import { Hono } from "hono";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
7
|
import { DashLayout } from "../../theme/layouts/index.js";
|
|
8
8
|
import { EmptyState, ListItemRow, ActionButtons, CrudPageHeader } from "../../theme/components/index.js";
|
|
9
9
|
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
10
10
|
export const navigationRoutes = new Hono();
|
|
11
|
-
function NavigationListContent({
|
|
11
|
+
function NavigationListContent({ items }) {
|
|
12
12
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
13
13
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
14
14
|
children: [
|
|
@@ -23,7 +23,7 @@ function NavigationListContent({ links }) {
|
|
|
23
23
|
}),
|
|
24
24
|
ctaHref: "/dash/navigation/new"
|
|
25
25
|
}),
|
|
26
|
-
|
|
26
|
+
items.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
|
|
27
27
|
message: $__i18n._({
|
|
28
28
|
id: "wdGjkd",
|
|
29
29
|
message: "No navigation links configured."
|
|
@@ -37,14 +37,14 @@ function NavigationListContent({ links }) {
|
|
|
37
37
|
children: /*#__PURE__*/ _jsx("div", {
|
|
38
38
|
id: "nav-links-list",
|
|
39
39
|
class: "flex flex-col divide-y",
|
|
40
|
-
children:
|
|
40
|
+
children: items.map((item)=>/*#__PURE__*/ _jsx(ListItemRow, {
|
|
41
41
|
actions: /*#__PURE__*/ _jsx(ActionButtons, {
|
|
42
|
-
editHref: `/dash/navigation/${
|
|
42
|
+
editHref: `/dash/navigation/${item.id}/edit`,
|
|
43
43
|
editLabel: $__i18n._({
|
|
44
44
|
id: "ePK91l",
|
|
45
45
|
message: "Edit"
|
|
46
46
|
}),
|
|
47
|
-
deleteAction: `/dash/navigation/${
|
|
47
|
+
deleteAction: `/dash/navigation/${item.id}/delete`,
|
|
48
48
|
deleteLabel: $__i18n._({
|
|
49
49
|
id: "cnGeoo",
|
|
50
50
|
message: "Delete"
|
|
@@ -52,7 +52,7 @@ function NavigationListContent({ links }) {
|
|
|
52
52
|
}),
|
|
53
53
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
54
54
|
class: "flex items-center gap-3 cursor-grab",
|
|
55
|
-
"data-id":
|
|
55
|
+
"data-id": item.id,
|
|
56
56
|
children: [
|
|
57
57
|
/*#__PURE__*/ _jsx("span", {
|
|
58
58
|
class: "text-muted-foreground select-none",
|
|
@@ -63,23 +63,23 @@ function NavigationListContent({ links }) {
|
|
|
63
63
|
children: [
|
|
64
64
|
/*#__PURE__*/ _jsx("span", {
|
|
65
65
|
class: "font-medium",
|
|
66
|
-
children:
|
|
66
|
+
children: item.label
|
|
67
67
|
}),
|
|
68
68
|
/*#__PURE__*/ _jsx("code", {
|
|
69
69
|
class: "text-sm text-muted-foreground bg-muted px-1 rounded",
|
|
70
|
-
children:
|
|
70
|
+
children: item.url
|
|
71
71
|
})
|
|
72
72
|
]
|
|
73
73
|
})
|
|
74
74
|
]
|
|
75
75
|
})
|
|
76
|
-
},
|
|
76
|
+
}, item.id))
|
|
77
77
|
})
|
|
78
78
|
})
|
|
79
79
|
]
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
function NavigationFormContent({
|
|
82
|
+
function NavigationFormContent({ item, isEdit }) {
|
|
83
83
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
84
84
|
const title = isEdit ? $__i18n._({
|
|
85
85
|
id: "gDx5MG",
|
|
@@ -89,10 +89,10 @@ function NavigationFormContent({ link, isEdit }) {
|
|
|
89
89
|
message: "New Link"
|
|
90
90
|
});
|
|
91
91
|
const signals = JSON.stringify({
|
|
92
|
-
label:
|
|
93
|
-
url:
|
|
92
|
+
label: item?.label ?? "",
|
|
93
|
+
url: item?.url ?? ""
|
|
94
94
|
}).replace(/</g, "\\u003c");
|
|
95
|
-
const action = isEdit ? `/dash/navigation/${
|
|
95
|
+
const action = isEdit ? `/dash/navigation/${item?.id}` : "/dash/navigation";
|
|
96
96
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
97
97
|
children: [
|
|
98
98
|
/*#__PURE__*/ _jsx("h1", {
|
|
@@ -199,17 +199,17 @@ function NavigationFormContent({ link, isEdit }) {
|
|
|
199
199
|
]
|
|
200
200
|
});
|
|
201
201
|
}
|
|
202
|
-
// List navigation
|
|
202
|
+
// List navigation items
|
|
203
203
|
navigationRoutes.get("/", async (c)=>{
|
|
204
204
|
const siteName = await getSiteName(c);
|
|
205
|
-
const
|
|
205
|
+
const items = await c.var.services.navItems.list();
|
|
206
206
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
207
207
|
c: c,
|
|
208
208
|
title: "Navigation",
|
|
209
209
|
siteName: siteName,
|
|
210
210
|
currentPath: "/dash/navigation",
|
|
211
211
|
children: /*#__PURE__*/ _jsx(NavigationListContent, {
|
|
212
|
-
|
|
212
|
+
items: items
|
|
213
213
|
})
|
|
214
214
|
}));
|
|
215
215
|
});
|
|
@@ -230,7 +230,8 @@ navigationRoutes.post("/", async (c)=>{
|
|
|
230
230
|
if (!body.label || !body.url) {
|
|
231
231
|
return dsToast("Label and URL are required", "error");
|
|
232
232
|
}
|
|
233
|
-
await c.var.services.
|
|
233
|
+
await c.var.services.navItems.create({
|
|
234
|
+
type: "link",
|
|
234
235
|
label: body.label,
|
|
235
236
|
url: body.url
|
|
236
237
|
});
|
|
@@ -242,15 +243,15 @@ navigationRoutes.post("/reorder", async (c)=>{
|
|
|
242
243
|
if (!Array.isArray(body.ids)) {
|
|
243
244
|
return dsToast("Invalid request", "error");
|
|
244
245
|
}
|
|
245
|
-
await c.var.services.
|
|
246
|
+
await c.var.services.navItems.reorder(body.ids);
|
|
246
247
|
return dsToast("Order saved");
|
|
247
248
|
});
|
|
248
249
|
// Edit link form
|
|
249
250
|
navigationRoutes.get("/:id/edit", async (c)=>{
|
|
250
251
|
const id = parseInt(c.req.param("id"), 10);
|
|
251
252
|
if (isNaN(id)) return c.notFound();
|
|
252
|
-
const
|
|
253
|
-
if (!
|
|
253
|
+
const item = await c.var.services.navItems.getById(id);
|
|
254
|
+
if (!item) return c.notFound();
|
|
254
255
|
const siteName = await getSiteName(c);
|
|
255
256
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
256
257
|
c: c,
|
|
@@ -258,7 +259,7 @@ navigationRoutes.get("/:id/edit", async (c)=>{
|
|
|
258
259
|
siteName: siteName,
|
|
259
260
|
currentPath: "/dash/navigation",
|
|
260
261
|
children: /*#__PURE__*/ _jsx(NavigationFormContent, {
|
|
261
|
-
|
|
262
|
+
item: item,
|
|
262
263
|
isEdit: true
|
|
263
264
|
})
|
|
264
265
|
}));
|
|
@@ -271,7 +272,7 @@ navigationRoutes.post("/:id", async (c)=>{
|
|
|
271
272
|
if (!body.label || !body.url) {
|
|
272
273
|
return dsToast("Label and URL are required", "error");
|
|
273
274
|
}
|
|
274
|
-
const updated = await c.var.services.
|
|
275
|
+
const updated = await c.var.services.navItems.update(id, {
|
|
275
276
|
label: body.label,
|
|
276
277
|
url: body.url
|
|
277
278
|
});
|
|
@@ -282,7 +283,7 @@ navigationRoutes.post("/:id", async (c)=>{
|
|
|
282
283
|
navigationRoutes.post("/:id/delete", async (c)=>{
|
|
283
284
|
const id = parseInt(c.req.param("id"), 10);
|
|
284
285
|
if (!isNaN(id)) {
|
|
285
|
-
await c.var.services.
|
|
286
|
+
await c.var.services.navItems.delete(id);
|
|
286
287
|
}
|
|
287
288
|
return dsRedirect("/dash/navigation");
|
|
288
289
|
});
|