@jant/core 0.3.6 → 0.3.7
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 +7 -21
- package/dist/db/schema.d.ts +36 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +2 -0
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.d.ts.map +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/schemas.d.ts +17 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +32 -2
- package/dist/lib/sse.d.ts +3 -3
- package/dist/lib/sse.d.ts.map +1 -1
- package/dist/lib/sse.js +7 -8
- package/dist/routes/api/posts.d.ts.map +1 -1
- package/dist/routes/api/posts.js +101 -5
- package/dist/routes/dash/media.js +38 -0
- package/dist/routes/dash/posts.d.ts.map +1 -1
- package/dist/routes/dash/posts.js +45 -6
- package/dist/routes/feed/rss.d.ts.map +1 -1
- package/dist/routes/feed/rss.js +10 -1
- package/dist/routes/pages/home.d.ts.map +1 -1
- package/dist/routes/pages/home.js +37 -4
- package/dist/routes/pages/post.d.ts.map +1 -1
- package/dist/routes/pages/post.js +28 -2
- package/dist/services/collection.d.ts +1 -0
- package/dist/services/collection.d.ts.map +1 -1
- package/dist/services/collection.js +13 -0
- package/dist/services/media.d.ts +7 -0
- package/dist/services/media.d.ts.map +1 -1
- package/dist/services/media.js +54 -1
- package/dist/theme/components/MediaGallery.d.ts +13 -0
- package/dist/theme/components/MediaGallery.d.ts.map +1 -0
- package/dist/theme/components/MediaGallery.js +107 -0
- package/dist/theme/components/PostForm.d.ts +6 -1
- package/dist/theme/components/PostForm.d.ts.map +1 -1
- package/dist/theme/components/PostForm.js +158 -2
- package/dist/theme/components/index.d.ts +1 -0
- package/dist/theme/components/index.d.ts.map +1 -1
- package/dist/theme/components/index.js +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +27 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/app.ts +6 -1
- package/src/__tests__/helpers/db.ts +10 -0
- package/src/app.tsx +7 -25
- package/src/db/migrations/0002_add_media_attachments.sql +3 -0
- package/src/db/schema.ts +2 -0
- package/src/i18n/locales/en.po +81 -37
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +81 -37
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +81 -37
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +8 -1
- package/src/lib/__tests__/schemas.test.ts +89 -1
- package/src/lib/__tests__/sse.test.ts +13 -1
- package/src/lib/schemas.ts +47 -1
- package/src/lib/sse.ts +10 -11
- package/src/routes/api/__tests__/posts.test.ts +239 -0
- package/src/routes/api/posts.ts +134 -5
- package/src/routes/dash/media.tsx +50 -0
- package/src/routes/dash/posts.tsx +79 -7
- package/src/routes/feed/rss.ts +14 -1
- package/src/routes/pages/home.tsx +80 -36
- package/src/routes/pages/post.tsx +36 -3
- package/src/services/__tests__/collection.test.ts +102 -0
- package/src/services/__tests__/media.test.ts +248 -0
- package/src/services/collection.ts +19 -0
- package/src/services/media.ts +76 -1
- package/src/theme/components/MediaGallery.tsx +128 -0
- package/src/theme/components/PostForm.tsx +170 -2
- package/src/theme/components/index.ts +1 -0
- package/src/types.ts +36 -0
package/dist/routes/api/posts.js
CHANGED
|
@@ -2,9 +2,32 @@
|
|
|
2
2
|
* Posts API Routes
|
|
3
3
|
*/ import { Hono } from "hono";
|
|
4
4
|
import * as sqid from "../../lib/sqid.js";
|
|
5
|
-
import { CreatePostSchema, UpdatePostSchema } from "../../lib/schemas.js";
|
|
5
|
+
import { CreatePostSchema, UpdatePostSchema, validateMediaForPostType } from "../../lib/schemas.js";
|
|
6
6
|
import { requireAuthApi } from "../../middleware/auth.js";
|
|
7
|
+
import { getMediaUrl, getImageUrl } from "../../lib/image.js";
|
|
7
8
|
export const postsApiRoutes = new Hono();
|
|
9
|
+
/**
|
|
10
|
+
* Converts a Media record to a MediaAttachment API response shape.
|
|
11
|
+
*/ function toMediaAttachment(m, r2PublicUrl, imageTransformUrl) {
|
|
12
|
+
const url = getMediaUrl(m.id, m.r2Key, r2PublicUrl);
|
|
13
|
+
const previewUrl = getImageUrl(url, imageTransformUrl, {
|
|
14
|
+
width: 400,
|
|
15
|
+
quality: 80,
|
|
16
|
+
format: "auto",
|
|
17
|
+
fit: "cover"
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
id: m.id,
|
|
21
|
+
url,
|
|
22
|
+
previewUrl,
|
|
23
|
+
alt: m.alt,
|
|
24
|
+
blurhash: m.blurhash,
|
|
25
|
+
width: m.width,
|
|
26
|
+
height: m.height,
|
|
27
|
+
position: m.position,
|
|
28
|
+
mimeType: m.mimeType
|
|
29
|
+
};
|
|
30
|
+
}
|
|
8
31
|
// List posts
|
|
9
32
|
postsApiRoutes.get("/", async (c)=>{
|
|
10
33
|
const type = c.req.query("type");
|
|
@@ -22,10 +45,16 @@ postsApiRoutes.get("/", async (c)=>{
|
|
|
22
45
|
cursor: cursor ? sqid.decode(cursor) ?? undefined : undefined,
|
|
23
46
|
limit
|
|
24
47
|
});
|
|
48
|
+
// Batch load media for all posts
|
|
49
|
+
const postIds = posts.map((p)=>p.id);
|
|
50
|
+
const mediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
51
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
52
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
25
53
|
return c.json({
|
|
26
54
|
posts: posts.map((p)=>({
|
|
27
55
|
...p,
|
|
28
|
-
sqid: sqid.encode(p.id)
|
|
56
|
+
sqid: sqid.encode(p.id),
|
|
57
|
+
mediaAttachments: (mediaMap.get(p.id) ?? []).map((m)=>toMediaAttachment(m, r2PublicUrl, imageTransformUrl))
|
|
29
58
|
})),
|
|
30
59
|
nextCursor: posts.length === limit ? sqid.encode(posts[posts.length - 1]?.id ?? 0) : null
|
|
31
60
|
});
|
|
@@ -40,9 +69,13 @@ postsApiRoutes.get("/:id", async (c)=>{
|
|
|
40
69
|
if (!post) return c.json({
|
|
41
70
|
error: "Not found"
|
|
42
71
|
}, 404);
|
|
72
|
+
const mediaList = await c.var.services.media.getByPostId(post.id);
|
|
73
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
74
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
43
75
|
return c.json({
|
|
44
76
|
...post,
|
|
45
|
-
sqid: sqid.encode(post.id)
|
|
77
|
+
sqid: sqid.encode(post.id),
|
|
78
|
+
mediaAttachments: mediaList.map((m)=>toMediaAttachment(m, r2PublicUrl, imageTransformUrl))
|
|
46
79
|
});
|
|
47
80
|
});
|
|
48
81
|
// Create post (requires auth)
|
|
@@ -57,6 +90,24 @@ postsApiRoutes.post("/", requireAuthApi(), async (c)=>{
|
|
|
57
90
|
}, 400);
|
|
58
91
|
}
|
|
59
92
|
const body = parseResult.data;
|
|
93
|
+
// Validate media for post type
|
|
94
|
+
if (body.mediaIds) {
|
|
95
|
+
const mediaError = validateMediaForPostType(body.type, body.mediaIds);
|
|
96
|
+
if (mediaError) {
|
|
97
|
+
return c.json({
|
|
98
|
+
error: mediaError
|
|
99
|
+
}, 400);
|
|
100
|
+
}
|
|
101
|
+
// Verify all media IDs exist
|
|
102
|
+
if (body.mediaIds.length > 0) {
|
|
103
|
+
const existing = await c.var.services.media.getByIds(body.mediaIds);
|
|
104
|
+
if (existing.length !== body.mediaIds.length) {
|
|
105
|
+
return c.json({
|
|
106
|
+
error: "One or more media IDs are invalid"
|
|
107
|
+
}, 400);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
60
111
|
const post = await c.var.services.posts.create({
|
|
61
112
|
type: body.type,
|
|
62
113
|
title: body.title,
|
|
@@ -68,9 +119,17 @@ postsApiRoutes.post("/", requireAuthApi(), async (c)=>{
|
|
|
68
119
|
replyToId: body.replyToId ? sqid.decode(body.replyToId) ?? undefined : undefined,
|
|
69
120
|
publishedAt: body.publishedAt
|
|
70
121
|
});
|
|
122
|
+
// Attach media
|
|
123
|
+
if (body.mediaIds && body.mediaIds.length > 0) {
|
|
124
|
+
await c.var.services.media.attachToPost(post.id, body.mediaIds);
|
|
125
|
+
}
|
|
126
|
+
const mediaList = await c.var.services.media.getByPostId(post.id);
|
|
127
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
128
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
71
129
|
return c.json({
|
|
72
130
|
...post,
|
|
73
|
-
sqid: sqid.encode(post.id)
|
|
131
|
+
sqid: sqid.encode(post.id),
|
|
132
|
+
mediaAttachments: mediaList.map((m)=>toMediaAttachment(m, r2PublicUrl, imageTransformUrl))
|
|
74
133
|
}, 201);
|
|
75
134
|
});
|
|
76
135
|
// Update post (requires auth)
|
|
@@ -89,6 +148,33 @@ postsApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
|
|
|
89
148
|
}, 400);
|
|
90
149
|
}
|
|
91
150
|
const body = parseResult.data;
|
|
151
|
+
// Validate media for post type if mediaIds is provided
|
|
152
|
+
if (body.mediaIds !== undefined) {
|
|
153
|
+
// Need the post type — use the new type if provided, else fetch existing
|
|
154
|
+
let postType = body.type;
|
|
155
|
+
if (!postType) {
|
|
156
|
+
const existing = await c.var.services.posts.getById(id);
|
|
157
|
+
if (!existing) return c.json({
|
|
158
|
+
error: "Not found"
|
|
159
|
+
}, 404);
|
|
160
|
+
postType = existing.type;
|
|
161
|
+
}
|
|
162
|
+
const mediaError = validateMediaForPostType(postType, body.mediaIds);
|
|
163
|
+
if (mediaError) {
|
|
164
|
+
return c.json({
|
|
165
|
+
error: mediaError
|
|
166
|
+
}, 400);
|
|
167
|
+
}
|
|
168
|
+
// Verify all media IDs exist
|
|
169
|
+
if (body.mediaIds.length > 0) {
|
|
170
|
+
const existing = await c.var.services.media.getByIds(body.mediaIds);
|
|
171
|
+
if (existing.length !== body.mediaIds.length) {
|
|
172
|
+
return c.json({
|
|
173
|
+
error: "One or more media IDs are invalid"
|
|
174
|
+
}, 400);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
92
178
|
const post = await c.var.services.posts.update(id, {
|
|
93
179
|
type: body.type,
|
|
94
180
|
title: body.title,
|
|
@@ -102,9 +188,17 @@ postsApiRoutes.put("/:id", requireAuthApi(), async (c)=>{
|
|
|
102
188
|
if (!post) return c.json({
|
|
103
189
|
error: "Not found"
|
|
104
190
|
}, 404);
|
|
191
|
+
// Update media attachments if provided (including empty array to clear)
|
|
192
|
+
if (body.mediaIds !== undefined) {
|
|
193
|
+
await c.var.services.media.attachToPost(post.id, body.mediaIds);
|
|
194
|
+
}
|
|
195
|
+
const mediaList = await c.var.services.media.getByPostId(post.id);
|
|
196
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
197
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
105
198
|
return c.json({
|
|
106
199
|
...post,
|
|
107
|
-
sqid: sqid.encode(post.id)
|
|
200
|
+
sqid: sqid.encode(post.id),
|
|
201
|
+
mediaAttachments: mediaList.map((m)=>toMediaAttachment(m, r2PublicUrl, imageTransformUrl))
|
|
108
202
|
});
|
|
109
203
|
});
|
|
110
204
|
// Delete post (requires auth)
|
|
@@ -113,6 +207,8 @@ postsApiRoutes.delete("/:id", requireAuthApi(), async (c)=>{
|
|
|
113
207
|
if (!id) return c.json({
|
|
114
208
|
error: "Invalid ID"
|
|
115
209
|
}, 400);
|
|
210
|
+
// Detach media before deleting
|
|
211
|
+
await c.var.services.media.detachFromPost(id);
|
|
116
212
|
const success = await c.var.services.posts.delete(id);
|
|
117
213
|
if (!success) return c.json({
|
|
118
214
|
error: "Not found"
|
|
@@ -398,6 +398,44 @@ mediaRoutes.get("/", async (c)=>{
|
|
|
398
398
|
})
|
|
399
399
|
}));
|
|
400
400
|
});
|
|
401
|
+
// Media picker (returns HTML fragment for PostForm dialog)
|
|
402
|
+
// Must be defined before /:id to avoid "picker" matching as an ID
|
|
403
|
+
mediaRoutes.get("/picker", async (c)=>{
|
|
404
|
+
const mediaList = await c.var.services.media.list(100);
|
|
405
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
406
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
407
|
+
if (mediaList.length === 0) {
|
|
408
|
+
return c.html(/*#__PURE__*/ _jsx("p", {
|
|
409
|
+
class: "text-muted-foreground text-sm col-span-4",
|
|
410
|
+
children: "No media uploaded yet. Upload media from the Media page first."
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
413
|
+
return c.html(/*#__PURE__*/ _jsx(_Fragment, {
|
|
414
|
+
children: mediaList.filter((m)=>m.mimeType.startsWith("image/")).map((m)=>{
|
|
415
|
+
const url = getMediaUrl(m.id, m.r2Key, r2PublicUrl);
|
|
416
|
+
const thumbUrl = getImageUrl(url, imageTransformUrl, {
|
|
417
|
+
width: 150,
|
|
418
|
+
quality: 80,
|
|
419
|
+
format: "auto",
|
|
420
|
+
fit: "cover"
|
|
421
|
+
});
|
|
422
|
+
return /*#__PURE__*/ _jsx("button", {
|
|
423
|
+
type: "button",
|
|
424
|
+
class: "aspect-square rounded-lg overflow-hidden border-2 hover:border-primary cursor-pointer transition-colors",
|
|
425
|
+
"data-on:click": `$mediaIds.includes('${m.id}') ? ($mediaIds = $mediaIds.filter(id => id !== '${m.id}')) : ($mediaIds = [...$mediaIds, '${m.id}'])`,
|
|
426
|
+
"data-class:border-primary": `$mediaIds.includes('${m.id}')`,
|
|
427
|
+
"data-class:ring-2": `$mediaIds.includes('${m.id}')`,
|
|
428
|
+
"data-class:ring-primary": `$mediaIds.includes('${m.id}')`,
|
|
429
|
+
children: /*#__PURE__*/ _jsx("img", {
|
|
430
|
+
src: thumbUrl,
|
|
431
|
+
alt: m.alt || m.originalName,
|
|
432
|
+
class: "w-full h-full object-cover",
|
|
433
|
+
loading: "lazy"
|
|
434
|
+
})
|
|
435
|
+
}, m.id);
|
|
436
|
+
})
|
|
437
|
+
}));
|
|
438
|
+
});
|
|
401
439
|
// View single media
|
|
402
440
|
mediaRoutes.get("/:id", async (c)=>{
|
|
403
441
|
const id = c.req.param("id");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posts.d.ts","sourceRoot":"","sources":["../../../src/routes/dash/posts.tsx"],"names":[],"mappings":"AACA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"posts.d.ts","sourceRoot":"","sources":["../../../src/routes/dash/posts.tsx"],"names":[],"mappings":"AACA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAA2B,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAWjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,WAAW,kDAAkB,CAAC"}
|
|
@@ -30,7 +30,7 @@ function PostsListContent({ posts }) {
|
|
|
30
30
|
]
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
function NewPostContent() {
|
|
33
|
+
function NewPostContent({ collections }) {
|
|
34
34
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
35
35
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
36
36
|
children: [
|
|
@@ -42,7 +42,8 @@ function NewPostContent() {
|
|
|
42
42
|
})
|
|
43
43
|
}),
|
|
44
44
|
/*#__PURE__*/ _jsx(PostForm, {
|
|
45
|
-
action: "/dash/posts"
|
|
45
|
+
action: "/dash/posts",
|
|
46
|
+
collections: collections
|
|
46
47
|
})
|
|
47
48
|
]
|
|
48
49
|
});
|
|
@@ -71,12 +72,15 @@ postsRoutes.get("/", async (c)=>{
|
|
|
71
72
|
// New post form
|
|
72
73
|
postsRoutes.get("/new", async (c)=>{
|
|
73
74
|
const siteName = await getSiteName(c);
|
|
75
|
+
const collections = await c.var.services.collections.list();
|
|
74
76
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
75
77
|
c: c,
|
|
76
78
|
title: "New Post",
|
|
77
79
|
siteName: siteName,
|
|
78
80
|
currentPath: "/dash/posts",
|
|
79
|
-
children: /*#__PURE__*/ _jsx(NewPostContent, {
|
|
81
|
+
children: /*#__PURE__*/ _jsx(NewPostContent, {
|
|
82
|
+
collections: collections
|
|
83
|
+
})
|
|
80
84
|
}));
|
|
81
85
|
});
|
|
82
86
|
// Create post
|
|
@@ -88,8 +92,17 @@ postsRoutes.post("/", async (c)=>{
|
|
|
88
92
|
content: body.content,
|
|
89
93
|
visibility: body.visibility,
|
|
90
94
|
sourceUrl: body.sourceUrl || undefined,
|
|
95
|
+
sourceName: body.sourceName || undefined,
|
|
91
96
|
path: body.path || undefined
|
|
92
97
|
});
|
|
98
|
+
// Attach media if provided
|
|
99
|
+
if (body.mediaIds && body.mediaIds.length > 0) {
|
|
100
|
+
await c.var.services.media.attachToPost(post.id, body.mediaIds);
|
|
101
|
+
}
|
|
102
|
+
// Sync collection associations
|
|
103
|
+
if (body.collectionIds) {
|
|
104
|
+
await c.var.services.collections.syncPostCollections(post.id, body.collectionIds);
|
|
105
|
+
}
|
|
93
106
|
return dsRedirect(`/dash/posts/${sqid.encode(post.id)}`);
|
|
94
107
|
});
|
|
95
108
|
function ViewPostContent({ post }) {
|
|
@@ -135,7 +148,7 @@ function ViewPostContent({ post }) {
|
|
|
135
148
|
]
|
|
136
149
|
});
|
|
137
150
|
}
|
|
138
|
-
function EditPostContent({ post }) {
|
|
151
|
+
function EditPostContent({ post, mediaAttachments, r2PublicUrl, imageTransformUrl, collections, postCollectionIds }) {
|
|
139
152
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
140
153
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
141
154
|
children: [
|
|
@@ -148,7 +161,12 @@ function EditPostContent({ post }) {
|
|
|
148
161
|
}),
|
|
149
162
|
/*#__PURE__*/ _jsx(PostForm, {
|
|
150
163
|
post: post,
|
|
151
|
-
action: `/dash/posts/${sqid.encode(post.id)}
|
|
164
|
+
action: `/dash/posts/${sqid.encode(post.id)}`,
|
|
165
|
+
mediaAttachments: mediaAttachments,
|
|
166
|
+
r2PublicUrl: r2PublicUrl,
|
|
167
|
+
imageTransformUrl: imageTransformUrl,
|
|
168
|
+
collections: collections,
|
|
169
|
+
postCollectionIds: postCollectionIds
|
|
152
170
|
})
|
|
153
171
|
]
|
|
154
172
|
});
|
|
@@ -178,13 +196,24 @@ postsRoutes.get("/:id/edit", async (c)=>{
|
|
|
178
196
|
const post = await c.var.services.posts.getById(id);
|
|
179
197
|
if (!post) return c.notFound();
|
|
180
198
|
const siteName = await getSiteName(c);
|
|
199
|
+
const mediaAttachments = await c.var.services.media.getByPostId(post.id);
|
|
200
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
201
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
202
|
+
const collections = await c.var.services.collections.list();
|
|
203
|
+
const postCollections = await c.var.services.collections.getCollectionsForPost(post.id);
|
|
204
|
+
const postCollectionIds = postCollections.map((col)=>col.id);
|
|
181
205
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
182
206
|
c: c,
|
|
183
207
|
title: `Edit: ${post.title || "Post"}`,
|
|
184
208
|
siteName: siteName,
|
|
185
209
|
currentPath: "/dash/posts",
|
|
186
210
|
children: /*#__PURE__*/ _jsx(EditPostContent, {
|
|
187
|
-
post: post
|
|
211
|
+
post: post,
|
|
212
|
+
mediaAttachments: mediaAttachments,
|
|
213
|
+
r2PublicUrl: r2PublicUrl,
|
|
214
|
+
imageTransformUrl: imageTransformUrl,
|
|
215
|
+
collections: collections,
|
|
216
|
+
postCollectionIds: postCollectionIds
|
|
188
217
|
})
|
|
189
218
|
}));
|
|
190
219
|
});
|
|
@@ -199,14 +228,24 @@ postsRoutes.post("/:id", async (c)=>{
|
|
|
199
228
|
content: body.content || null,
|
|
200
229
|
visibility: body.visibility,
|
|
201
230
|
sourceUrl: body.sourceUrl || null,
|
|
231
|
+
sourceName: body.sourceName || null,
|
|
202
232
|
path: body.path || null
|
|
203
233
|
});
|
|
234
|
+
// Update media attachments if provided
|
|
235
|
+
if (body.mediaIds !== undefined) {
|
|
236
|
+
await c.var.services.media.attachToPost(id, body.mediaIds);
|
|
237
|
+
}
|
|
238
|
+
// Sync collection associations
|
|
239
|
+
if (body.collectionIds !== undefined) {
|
|
240
|
+
await c.var.services.collections.syncPostCollections(id, body.collectionIds);
|
|
241
|
+
}
|
|
204
242
|
return dsRedirect(`/dash/posts/${sqid.encode(id)}`);
|
|
205
243
|
});
|
|
206
244
|
// Delete post
|
|
207
245
|
postsRoutes.post("/:id/delete", async (c)=>{
|
|
208
246
|
const id = sqid.decode(c.req.param("id"));
|
|
209
247
|
if (!id) return c.notFound();
|
|
248
|
+
await c.var.services.media.detachFromPost(id);
|
|
210
249
|
await c.var.services.posts.delete(id);
|
|
211
250
|
return dsRedirect("/dash/posts");
|
|
212
251
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rss.d.ts","sourceRoot":"","sources":["../../../src/routes/feed/rss.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"rss.d.ts","sourceRoot":"","sources":["../../../src/routes/feed/rss.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,SAAS,kDAAkB,CAAC"}
|
package/dist/routes/feed/rss.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/ import { Hono } from "hono";
|
|
4
4
|
import * as sqid from "../../lib/sqid.js";
|
|
5
5
|
import * as time from "../../lib/time.js";
|
|
6
|
+
import { getMediaUrl } from "../../lib/image.js";
|
|
6
7
|
export const rssRoutes = new Hono();
|
|
7
8
|
// RSS 2.0 Feed - main feed at /feed
|
|
8
9
|
rssRoutes.get("/", async (c)=>{
|
|
@@ -10,6 +11,7 @@ rssRoutes.get("/", async (c)=>{
|
|
|
10
11
|
const siteName = all["SITE_NAME"] ?? "Jant";
|
|
11
12
|
const siteDescription = all["SITE_DESCRIPTION"] ?? "";
|
|
12
13
|
const siteUrl = c.env.SITE_URL;
|
|
14
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
13
15
|
const posts = await c.var.services.posts.list({
|
|
14
16
|
visibility: [
|
|
15
17
|
"featured",
|
|
@@ -17,17 +19,24 @@ rssRoutes.get("/", async (c)=>{
|
|
|
17
19
|
],
|
|
18
20
|
limit: 50
|
|
19
21
|
});
|
|
22
|
+
// Batch load media for enclosures
|
|
23
|
+
const postIds = posts.map((p)=>p.id);
|
|
24
|
+
const mediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
20
25
|
const items = posts.map((post)=>{
|
|
21
26
|
const link = `${siteUrl}/p/${sqid.encode(post.id)}`;
|
|
22
27
|
const title = post.title || `Post #${post.id}`;
|
|
23
28
|
const pubDate = new Date(post.publishedAt * 1000).toUTCString();
|
|
29
|
+
// Add enclosure for first media attachment
|
|
30
|
+
const postMedia = mediaMap.get(post.id);
|
|
31
|
+
const firstMedia = postMedia?.[0];
|
|
32
|
+
const enclosure = firstMedia ? `\n <enclosure url="${getMediaUrl(firstMedia.id, firstMedia.r2Key, r2PublicUrl)}" length="${firstMedia.size}" type="${firstMedia.mimeType}"/>` : "";
|
|
24
33
|
return `
|
|
25
34
|
<item>
|
|
26
35
|
<title><![CDATA[${escapeXml(title)}]]></title>
|
|
27
36
|
<link>${link}</link>
|
|
28
37
|
<guid isPermaLink="true">${link}</guid>
|
|
29
38
|
<pubDate>${pubDate}</pubDate>
|
|
30
|
-
<description><![CDATA[${post.contentHtml || ""}]]></description
|
|
39
|
+
<description><![CDATA[${post.contentHtml || ""}]]></description>${enclosure}
|
|
31
40
|
</item>`;
|
|
32
41
|
}).join("");
|
|
33
42
|
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/home.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/home.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAyB,MAAM,gBAAgB,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAQjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,UAAU,kDAAkB,CAAC"}
|
|
@@ -4,11 +4,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
|
4
4
|
*/ import { Hono } from "hono";
|
|
5
5
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
6
6
|
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
7
|
+
import { MediaGallery } from "../../theme/components/index.js";
|
|
7
8
|
import * as sqid from "../../lib/sqid.js";
|
|
8
9
|
import * as time from "../../lib/time.js";
|
|
9
10
|
import { getSiteName } from "../../lib/config.js";
|
|
11
|
+
import { getMediaUrl, getImageUrl } from "../../lib/image.js";
|
|
10
12
|
export const homeRoutes = new Hono();
|
|
11
|
-
function HomeContent({ siteName, posts }) {
|
|
13
|
+
function HomeContent({ siteName, posts, mediaMap }) {
|
|
12
14
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
13
15
|
return /*#__PURE__*/ _jsxs("div", {
|
|
14
16
|
class: "container py-8",
|
|
@@ -48,7 +50,9 @@ function HomeContent({ siteName, posts }) {
|
|
|
48
50
|
id: "ODiSoW",
|
|
49
51
|
message: "No posts yet."
|
|
50
52
|
})
|
|
51
|
-
}) : posts.map((post)
|
|
53
|
+
}) : posts.map((post)=>{
|
|
54
|
+
const attachments = mediaMap.get(post.id) ?? [];
|
|
55
|
+
return /*#__PURE__*/ _jsxs("article", {
|
|
52
56
|
class: "h-entry",
|
|
53
57
|
children: [
|
|
54
58
|
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
@@ -65,6 +69,9 @@ function HomeContent({ siteName, posts }) {
|
|
|
65
69
|
__html: post.contentHtml || ""
|
|
66
70
|
}
|
|
67
71
|
}),
|
|
72
|
+
attachments.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
|
|
73
|
+
attachments: attachments
|
|
74
|
+
}),
|
|
68
75
|
/*#__PURE__*/ _jsxs("footer", {
|
|
69
76
|
class: "mt-2 text-sm text-muted-foreground",
|
|
70
77
|
children: [
|
|
@@ -83,7 +90,8 @@ function HomeContent({ siteName, posts }) {
|
|
|
83
90
|
]
|
|
84
91
|
})
|
|
85
92
|
]
|
|
86
|
-
}, post.id)
|
|
93
|
+
}, post.id);
|
|
94
|
+
})
|
|
87
95
|
}),
|
|
88
96
|
posts.length >= 20 && /*#__PURE__*/ _jsx("nav", {
|
|
89
97
|
class: "mt-8 text-center",
|
|
@@ -108,12 +116,37 @@ homeRoutes.get("/", async (c)=>{
|
|
|
108
116
|
],
|
|
109
117
|
limit: 20
|
|
110
118
|
});
|
|
119
|
+
// Batch load media attachments
|
|
120
|
+
const postIds = posts.map((p)=>p.id);
|
|
121
|
+
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
122
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
123
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
124
|
+
const mediaMap = new Map();
|
|
125
|
+
for (const [postId, mediaList] of rawMediaMap){
|
|
126
|
+
mediaMap.set(postId, mediaList.map((m)=>({
|
|
127
|
+
id: m.id,
|
|
128
|
+
url: getMediaUrl(m.id, m.r2Key, r2PublicUrl),
|
|
129
|
+
previewUrl: getImageUrl(getMediaUrl(m.id, m.r2Key, r2PublicUrl), imageTransformUrl, {
|
|
130
|
+
width: 400,
|
|
131
|
+
quality: 80,
|
|
132
|
+
format: "auto",
|
|
133
|
+
fit: "cover"
|
|
134
|
+
}),
|
|
135
|
+
alt: m.alt,
|
|
136
|
+
blurhash: m.blurhash,
|
|
137
|
+
width: m.width,
|
|
138
|
+
height: m.height,
|
|
139
|
+
position: m.position,
|
|
140
|
+
mimeType: m.mimeType
|
|
141
|
+
})));
|
|
142
|
+
}
|
|
111
143
|
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
112
144
|
title: siteName,
|
|
113
145
|
c: c,
|
|
114
146
|
children: /*#__PURE__*/ _jsx(HomeContent, {
|
|
115
147
|
siteName: siteName,
|
|
116
|
-
posts: posts
|
|
148
|
+
posts: posts,
|
|
149
|
+
mediaMap: mediaMap
|
|
117
150
|
})
|
|
118
151
|
}));
|
|
119
152
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/post.tsx"],"names":[],"mappings":"AACA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"post.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/post.tsx"],"names":[],"mappings":"AACA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,QAAQ,EAAyB,MAAM,gBAAgB,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAOjD,KAAK,GAAG,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D,eAAO,MAAM,UAAU,kDAAkB,CAAC"}
|
|
@@ -5,10 +5,12 @@ import { getSiteName } from "../../lib/config.js";
|
|
|
5
5
|
*/ import { Hono } from "hono";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
7
|
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
8
|
+
import { MediaGallery } from "../../theme/components/index.js";
|
|
8
9
|
import * as sqid from "../../lib/sqid.js";
|
|
9
10
|
import * as time from "../../lib/time.js";
|
|
11
|
+
import { getMediaUrl, getImageUrl } from "../../lib/image.js";
|
|
10
12
|
export const postRoutes = new Hono();
|
|
11
|
-
function PostContent({ post }) {
|
|
13
|
+
function PostContent({ post, mediaAttachments }) {
|
|
12
14
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
13
15
|
return /*#__PURE__*/ _jsxs("div", {
|
|
14
16
|
class: "container py-8",
|
|
@@ -26,6 +28,9 @@ function PostContent({ post }) {
|
|
|
26
28
|
__html: post.contentHtml || ""
|
|
27
29
|
}
|
|
28
30
|
}),
|
|
31
|
+
mediaAttachments.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
|
|
32
|
+
attachments: mediaAttachments
|
|
33
|
+
}),
|
|
29
34
|
/*#__PURE__*/ _jsxs("footer", {
|
|
30
35
|
class: "mt-6 pt-4 border-t text-sm text-muted-foreground",
|
|
31
36
|
children: [
|
|
@@ -78,6 +83,26 @@ postRoutes.get("/:id", async (c)=>{
|
|
|
78
83
|
if (post.visibility === "draft") {
|
|
79
84
|
return c.notFound();
|
|
80
85
|
}
|
|
86
|
+
// Load media attachments
|
|
87
|
+
const rawMedia = await c.var.services.media.getByPostId(post.id);
|
|
88
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
89
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
90
|
+
const mediaAttachments = rawMedia.map((m)=>({
|
|
91
|
+
id: m.id,
|
|
92
|
+
url: getMediaUrl(m.id, m.r2Key, r2PublicUrl),
|
|
93
|
+
previewUrl: getImageUrl(getMediaUrl(m.id, m.r2Key, r2PublicUrl), imageTransformUrl, {
|
|
94
|
+
width: 400,
|
|
95
|
+
quality: 80,
|
|
96
|
+
format: "auto",
|
|
97
|
+
fit: "cover"
|
|
98
|
+
}),
|
|
99
|
+
alt: m.alt,
|
|
100
|
+
blurhash: m.blurhash,
|
|
101
|
+
width: m.width,
|
|
102
|
+
height: m.height,
|
|
103
|
+
position: m.position,
|
|
104
|
+
mimeType: m.mimeType
|
|
105
|
+
}));
|
|
81
106
|
const siteName = await getSiteName(c);
|
|
82
107
|
const title = post.title || siteName;
|
|
83
108
|
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
@@ -85,7 +110,8 @@ postRoutes.get("/:id", async (c)=>{
|
|
|
85
110
|
description: post.content?.slice(0, 160),
|
|
86
111
|
c: c,
|
|
87
112
|
children: /*#__PURE__*/ _jsx(PostContent, {
|
|
88
|
-
post: post
|
|
113
|
+
post: post,
|
|
114
|
+
mediaAttachments: mediaAttachments
|
|
89
115
|
})
|
|
90
116
|
}));
|
|
91
117
|
});
|
|
@@ -16,6 +16,7 @@ export interface CollectionService {
|
|
|
16
16
|
removePost(collectionId: number, postId: number): Promise<void>;
|
|
17
17
|
getPosts(collectionId: number): Promise<Post[]>;
|
|
18
18
|
getCollectionsForPost(postId: number): Promise<Collection[]>;
|
|
19
|
+
syncPostCollections(postId: number, collectionIds: number[]): Promise<void>;
|
|
19
20
|
}
|
|
20
21
|
export interface CreateCollectionData {
|
|
21
22
|
title: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/services/collection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAChD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACpD,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC9B,MAAM,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC3E,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/services/collection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAChD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACpD,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC9B,MAAM,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC3E,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC7D,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7E;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,QAAQ,GAAG,iBAAiB,CAqLvE"}
|
|
@@ -103,6 +103,19 @@ export function createCollectionService(db) {
|
|
|
103
103
|
collection: collections
|
|
104
104
|
}).from(postCollections).innerJoin(collections, eq(postCollections.collectionId, collections.id)).where(eq(postCollections.postId, postId));
|
|
105
105
|
return rows.map((r)=>toCollection(r.collection));
|
|
106
|
+
},
|
|
107
|
+
async syncPostCollections (postId, collectionIds) {
|
|
108
|
+
const current = await this.getCollectionsForPost(postId);
|
|
109
|
+
const currentIds = new Set(current.map((c)=>c.id));
|
|
110
|
+
const desiredIds = new Set(collectionIds);
|
|
111
|
+
const toAdd = collectionIds.filter((id)=>!currentIds.has(id));
|
|
112
|
+
const toRemove = current.map((c)=>c.id).filter((id)=>!desiredIds.has(id));
|
|
113
|
+
for (const collectionId of toAdd){
|
|
114
|
+
await this.addPost(collectionId, postId);
|
|
115
|
+
}
|
|
116
|
+
for (const collectionId of toRemove){
|
|
117
|
+
await this.removePost(collectionId, postId);
|
|
118
|
+
}
|
|
106
119
|
}
|
|
107
120
|
};
|
|
108
121
|
}
|
package/dist/services/media.d.ts
CHANGED
|
@@ -7,10 +7,15 @@ import type { Database } from "../db/index.js";
|
|
|
7
7
|
import type { Media } from "../types.js";
|
|
8
8
|
export interface MediaService {
|
|
9
9
|
getById(id: string): Promise<Media | null>;
|
|
10
|
+
getByIds(ids: string[]): Promise<Media[]>;
|
|
11
|
+
getByPostId(postId: number): Promise<Media[]>;
|
|
12
|
+
getByPostIds(postIds: number[]): Promise<Map<number, Media[]>>;
|
|
10
13
|
list(limit?: number): Promise<Media[]>;
|
|
11
14
|
create(data: CreateMediaData): Promise<Media>;
|
|
12
15
|
delete(id: string): Promise<boolean>;
|
|
13
16
|
getByR2Key(r2Key: string): Promise<Media | null>;
|
|
17
|
+
attachToPost(postId: number, mediaIds: string[]): Promise<void>;
|
|
18
|
+
detachFromPost(postId: number): Promise<void>;
|
|
14
19
|
}
|
|
15
20
|
export interface CreateMediaData {
|
|
16
21
|
postId?: number;
|
|
@@ -22,6 +27,8 @@ export interface CreateMediaData {
|
|
|
22
27
|
width?: number;
|
|
23
28
|
height?: number;
|
|
24
29
|
alt?: string;
|
|
30
|
+
position?: number;
|
|
31
|
+
blurhash?: string;
|
|
25
32
|
}
|
|
26
33
|
export declare function createMediaService(db: Database): MediaService;
|
|
27
34
|
//# sourceMappingURL=media.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/services/media.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACvC,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/services/media.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC3C,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1C,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9C,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/D,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACvC,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACjD,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,GAAG,YAAY,CA+I7D"}
|