@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
|
@@ -3,12 +3,11 @@ import { getSiteName } from "../../lib/config.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Dashboard Pages Routes
|
|
5
5
|
*
|
|
6
|
-
* Management for
|
|
6
|
+
* Management for standalone pages (about, now, etc.)
|
|
7
7
|
*/ import { Hono } from "hono";
|
|
8
8
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
9
9
|
import { DashLayout } from "../../theme/layouts/index.js";
|
|
10
|
-
import { PageForm,
|
|
11
|
-
import * as sqid from "../../lib/sqid.js";
|
|
10
|
+
import { PageForm, EmptyState, ListItemRow, ActionButtons, CrudPageHeader, DangerZone } from "../../theme/components/index.js";
|
|
12
11
|
import * as time from "../../lib/time.js";
|
|
13
12
|
import { dsRedirect } from "../../lib/sse.js";
|
|
14
13
|
export const pagesRoutes = new Hono();
|
|
@@ -41,32 +40,27 @@ function PagesListContent({ pages }) {
|
|
|
41
40
|
class: "flex flex-col divide-y",
|
|
42
41
|
children: pages.map((page)=>/*#__PURE__*/ _jsxs(ListItemRow, {
|
|
43
42
|
actions: /*#__PURE__*/ _jsx(ActionButtons, {
|
|
44
|
-
editHref: `/dash/pages/${
|
|
43
|
+
editHref: `/dash/pages/${page.id}/edit`,
|
|
45
44
|
editLabel: $__i18n._({
|
|
46
45
|
id: "ePK91l",
|
|
47
46
|
message: "Edit"
|
|
48
47
|
}),
|
|
49
|
-
viewHref: page.
|
|
48
|
+
viewHref: page.status !== "draft" ? `/${page.slug}` : undefined,
|
|
50
49
|
viewLabel: $__i18n._({
|
|
51
50
|
id: "jpctdh",
|
|
52
51
|
message: "View"
|
|
53
52
|
})
|
|
54
53
|
}),
|
|
55
54
|
children: [
|
|
56
|
-
/*#__PURE__*/
|
|
55
|
+
/*#__PURE__*/ _jsx("div", {
|
|
57
56
|
class: "flex items-center gap-2 mb-1",
|
|
58
|
-
children:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/*#__PURE__*/ _jsx("span", {
|
|
63
|
-
class: "text-xs text-muted-foreground",
|
|
64
|
-
children: time.formatDate(page.updatedAt)
|
|
65
|
-
})
|
|
66
|
-
]
|
|
57
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
58
|
+
class: "text-xs text-muted-foreground",
|
|
59
|
+
children: time.formatDate(page.updatedAt)
|
|
60
|
+
})
|
|
67
61
|
}),
|
|
68
62
|
/*#__PURE__*/ _jsx("a", {
|
|
69
|
-
href: `/dash/pages/${
|
|
63
|
+
href: `/dash/pages/${page.id}`,
|
|
70
64
|
class: "font-medium hover:underline",
|
|
71
65
|
children: page.title || $__i18n._({
|
|
72
66
|
id: "wja8aL",
|
|
@@ -77,7 +71,7 @@ function PagesListContent({ pages }) {
|
|
|
77
71
|
class: "text-sm text-muted-foreground mt-1",
|
|
78
72
|
children: [
|
|
79
73
|
"/",
|
|
80
|
-
page.
|
|
74
|
+
page.slug
|
|
81
75
|
]
|
|
82
76
|
})
|
|
83
77
|
]
|
|
@@ -119,22 +113,22 @@ function ViewPageContent({ page }) {
|
|
|
119
113
|
message: "Page"
|
|
120
114
|
})
|
|
121
115
|
}),
|
|
122
|
-
|
|
116
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
123
117
|
class: "text-muted-foreground mt-1",
|
|
124
118
|
children: [
|
|
125
119
|
"/",
|
|
126
|
-
page.
|
|
120
|
+
page.slug
|
|
127
121
|
]
|
|
128
122
|
})
|
|
129
123
|
]
|
|
130
124
|
}),
|
|
131
125
|
/*#__PURE__*/ _jsx(ActionButtons, {
|
|
132
|
-
editHref: `/dash/pages/${
|
|
126
|
+
editHref: `/dash/pages/${page.id}/edit`,
|
|
133
127
|
editLabel: $__i18n._({
|
|
134
128
|
id: "ePK91l",
|
|
135
129
|
message: "Edit"
|
|
136
130
|
}),
|
|
137
|
-
viewHref: page.
|
|
131
|
+
viewHref: page.status !== "draft" ? `/${page.slug}` : undefined,
|
|
138
132
|
viewLabel: $__i18n._({
|
|
139
133
|
id: "jpctdh",
|
|
140
134
|
message: "View"
|
|
@@ -148,7 +142,7 @@ function ViewPageContent({ page }) {
|
|
|
148
142
|
children: /*#__PURE__*/ _jsx("div", {
|
|
149
143
|
class: "prose",
|
|
150
144
|
dangerouslySetInnerHTML: {
|
|
151
|
-
__html: page.
|
|
145
|
+
__html: page.bodyHtml || ""
|
|
152
146
|
}
|
|
153
147
|
})
|
|
154
148
|
})
|
|
@@ -158,7 +152,7 @@ function ViewPageContent({ page }) {
|
|
|
158
152
|
id: "4KzVT6",
|
|
159
153
|
message: "Delete Page"
|
|
160
154
|
}),
|
|
161
|
-
formAction: `/dash/pages/${
|
|
155
|
+
formAction: `/dash/pages/${page.id}/delete`,
|
|
162
156
|
confirmMessage: "Are you sure you want to delete this page?"
|
|
163
157
|
})
|
|
164
158
|
]
|
|
@@ -177,21 +171,14 @@ function EditPageContent({ page }) {
|
|
|
177
171
|
}),
|
|
178
172
|
/*#__PURE__*/ _jsx(PageForm, {
|
|
179
173
|
page: page,
|
|
180
|
-
action: `/dash/pages/${
|
|
174
|
+
action: `/dash/pages/${page.id}`
|
|
181
175
|
})
|
|
182
176
|
]
|
|
183
177
|
});
|
|
184
178
|
}
|
|
185
179
|
// List pages
|
|
186
180
|
pagesRoutes.get("/", async (c)=>{
|
|
187
|
-
const pages = await c.var.services.
|
|
188
|
-
type: "page",
|
|
189
|
-
visibility: [
|
|
190
|
-
"unlisted",
|
|
191
|
-
"draft"
|
|
192
|
-
],
|
|
193
|
-
limit: 100
|
|
194
|
-
});
|
|
181
|
+
const pages = await c.var.services.pages.list();
|
|
195
182
|
const siteName = await getSiteName(c);
|
|
196
183
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
197
184
|
c: c,
|
|
@@ -217,21 +204,20 @@ pagesRoutes.get("/new", async (c)=>{
|
|
|
217
204
|
// Create page
|
|
218
205
|
pagesRoutes.post("/", async (c)=>{
|
|
219
206
|
const body = await c.req.json();
|
|
220
|
-
const page = await c.var.services.
|
|
221
|
-
type: "page",
|
|
207
|
+
const page = await c.var.services.pages.create({
|
|
222
208
|
title: body.title,
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
209
|
+
body: body.body,
|
|
210
|
+
status: body.status,
|
|
211
|
+
slug: body.slug.toLowerCase().replace(/[^a-z0-9-]/g, "-")
|
|
226
212
|
});
|
|
227
|
-
return dsRedirect(`/dash/pages/${
|
|
213
|
+
return dsRedirect(`/dash/pages/${page.id}`);
|
|
228
214
|
});
|
|
229
215
|
// View single page
|
|
230
216
|
pagesRoutes.get("/:id", async (c)=>{
|
|
231
|
-
const id =
|
|
232
|
-
if (
|
|
233
|
-
const page = await c.var.services.
|
|
234
|
-
if (!page
|
|
217
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
218
|
+
if (isNaN(id)) return c.notFound();
|
|
219
|
+
const page = await c.var.services.pages.getById(id);
|
|
220
|
+
if (!page) return c.notFound();
|
|
235
221
|
const siteName = await getSiteName(c);
|
|
236
222
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
237
223
|
c: c,
|
|
@@ -245,10 +231,10 @@ pagesRoutes.get("/:id", async (c)=>{
|
|
|
245
231
|
});
|
|
246
232
|
// Edit page form
|
|
247
233
|
pagesRoutes.get("/:id/edit", async (c)=>{
|
|
248
|
-
const id =
|
|
249
|
-
if (
|
|
250
|
-
const page = await c.var.services.
|
|
251
|
-
if (!page
|
|
234
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
235
|
+
if (isNaN(id)) return c.notFound();
|
|
236
|
+
const page = await c.var.services.pages.getById(id);
|
|
237
|
+
if (!page) return c.notFound();
|
|
252
238
|
const siteName = await getSiteName(c);
|
|
253
239
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
254
240
|
c: c,
|
|
@@ -262,22 +248,21 @@ pagesRoutes.get("/:id/edit", async (c)=>{
|
|
|
262
248
|
});
|
|
263
249
|
// Update page
|
|
264
250
|
pagesRoutes.post("/:id", async (c)=>{
|
|
265
|
-
const id =
|
|
266
|
-
if (
|
|
251
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
252
|
+
if (isNaN(id)) return c.notFound();
|
|
267
253
|
const body = await c.req.json();
|
|
268
|
-
await c.var.services.
|
|
269
|
-
type: "page",
|
|
254
|
+
await c.var.services.pages.update(id, {
|
|
270
255
|
title: body.title,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
256
|
+
body: body.body,
|
|
257
|
+
status: body.status,
|
|
258
|
+
slug: body.slug.toLowerCase().replace(/[^a-z0-9-]/g, "-")
|
|
274
259
|
});
|
|
275
|
-
return dsRedirect(`/dash/pages/${
|
|
260
|
+
return dsRedirect(`/dash/pages/${id}`);
|
|
276
261
|
});
|
|
277
262
|
// Delete page
|
|
278
263
|
pagesRoutes.post("/:id/delete", async (c)=>{
|
|
279
|
-
const id =
|
|
280
|
-
if (
|
|
281
|
-
await c.var.services.
|
|
264
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
265
|
+
if (isNaN(id)) return c.notFound();
|
|
266
|
+
await c.var.services.pages.delete(id);
|
|
282
267
|
return dsRedirect("/dash/pages");
|
|
283
268
|
});
|
|
@@ -51,12 +51,7 @@ function NewPostContent({ collections }) {
|
|
|
51
51
|
// List posts
|
|
52
52
|
postsRoutes.get("/", async (c)=>{
|
|
53
53
|
const posts = await c.var.services.posts.list({
|
|
54
|
-
|
|
55
|
-
"featured",
|
|
56
|
-
"quiet",
|
|
57
|
-
"unlisted",
|
|
58
|
-
"draft"
|
|
59
|
-
]
|
|
54
|
+
excludeReplies: true
|
|
60
55
|
});
|
|
61
56
|
const siteName = await getSiteName(c);
|
|
62
57
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
@@ -87,22 +82,22 @@ postsRoutes.get("/new", async (c)=>{
|
|
|
87
82
|
postsRoutes.post("/", async (c)=>{
|
|
88
83
|
const body = await c.req.json();
|
|
89
84
|
const post = await c.var.services.posts.create({
|
|
90
|
-
|
|
85
|
+
format: body.format,
|
|
91
86
|
title: body.title || undefined,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
body: body.body,
|
|
88
|
+
status: body.status,
|
|
89
|
+
featured: body.featured,
|
|
90
|
+
pinned: body.pinned,
|
|
91
|
+
slug: body.slug || undefined,
|
|
92
|
+
url: body.url || undefined,
|
|
93
|
+
quoteText: body.quoteText || undefined,
|
|
94
|
+
rating: body.rating || undefined,
|
|
95
|
+
collectionId: body.collectionId || undefined
|
|
97
96
|
});
|
|
98
97
|
// Attach media if provided
|
|
99
98
|
if (body.mediaIds && body.mediaIds.length > 0) {
|
|
100
99
|
await c.var.services.media.attachToPost(post.id, body.mediaIds);
|
|
101
100
|
}
|
|
102
|
-
// Sync collection associations
|
|
103
|
-
if (body.collectionIds) {
|
|
104
|
-
await c.var.services.collections.syncPostCollections(post.id, body.collectionIds);
|
|
105
|
-
}
|
|
106
101
|
return dsRedirect(`/dash/posts/${sqid.encode(post.id)}`);
|
|
107
102
|
});
|
|
108
103
|
function ViewPostContent({ post }) {
|
|
@@ -111,6 +106,7 @@ function ViewPostContent({ post }) {
|
|
|
111
106
|
id: "y28hnO",
|
|
112
107
|
message: "Post"
|
|
113
108
|
});
|
|
109
|
+
const permalink = post.slug ? `/${post.slug}` : `/p/${sqid.encode(post.id)}`;
|
|
114
110
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
115
111
|
children: [
|
|
116
112
|
/*#__PURE__*/ _jsxs("div", {
|
|
@@ -126,7 +122,7 @@ function ViewPostContent({ post }) {
|
|
|
126
122
|
id: "ePK91l",
|
|
127
123
|
message: "Edit"
|
|
128
124
|
}),
|
|
129
|
-
viewHref:
|
|
125
|
+
viewHref: permalink,
|
|
130
126
|
viewLabel: $__i18n._({
|
|
131
127
|
id: "jpctdh",
|
|
132
128
|
message: "View"
|
|
@@ -140,7 +136,7 @@ function ViewPostContent({ post }) {
|
|
|
140
136
|
children: /*#__PURE__*/ _jsx("div", {
|
|
141
137
|
class: "prose",
|
|
142
138
|
dangerouslySetInnerHTML: {
|
|
143
|
-
__html: post.
|
|
139
|
+
__html: post.bodyHtml || ""
|
|
144
140
|
}
|
|
145
141
|
})
|
|
146
142
|
})
|
|
@@ -148,7 +144,7 @@ function ViewPostContent({ post }) {
|
|
|
148
144
|
]
|
|
149
145
|
});
|
|
150
146
|
}
|
|
151
|
-
function EditPostContent({ post, mediaAttachments, r2PublicUrl, imageTransformUrl, s3PublicUrl, collections
|
|
147
|
+
function EditPostContent({ post, mediaAttachments, r2PublicUrl, imageTransformUrl, s3PublicUrl, collections }) {
|
|
152
148
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
153
149
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
154
150
|
children: [
|
|
@@ -166,8 +162,7 @@ function EditPostContent({ post, mediaAttachments, r2PublicUrl, imageTransformUr
|
|
|
166
162
|
r2PublicUrl: r2PublicUrl,
|
|
167
163
|
imageTransformUrl: imageTransformUrl,
|
|
168
164
|
s3PublicUrl: s3PublicUrl,
|
|
169
|
-
collections: collections
|
|
170
|
-
postCollectionIds: postCollectionIds
|
|
165
|
+
collections: collections
|
|
171
166
|
})
|
|
172
167
|
]
|
|
173
168
|
});
|
|
@@ -202,8 +197,6 @@ postsRoutes.get("/:id/edit", async (c)=>{
|
|
|
202
197
|
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
203
198
|
const s3PublicUrl = c.env.S3_PUBLIC_URL;
|
|
204
199
|
const collections = await c.var.services.collections.list();
|
|
205
|
-
const postCollections = await c.var.services.collections.getCollectionsForPost(post.id);
|
|
206
|
-
const postCollectionIds = postCollections.map((col)=>col.id);
|
|
207
200
|
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
208
201
|
c: c,
|
|
209
202
|
title: `Edit: ${post.title || "Post"}`,
|
|
@@ -215,8 +208,7 @@ postsRoutes.get("/:id/edit", async (c)=>{
|
|
|
215
208
|
r2PublicUrl: r2PublicUrl,
|
|
216
209
|
imageTransformUrl: imageTransformUrl,
|
|
217
210
|
s3PublicUrl: s3PublicUrl,
|
|
218
|
-
collections: collections
|
|
219
|
-
postCollectionIds: postCollectionIds
|
|
211
|
+
collections: collections
|
|
220
212
|
})
|
|
221
213
|
}));
|
|
222
214
|
});
|
|
@@ -226,22 +218,22 @@ postsRoutes.post("/:id", async (c)=>{
|
|
|
226
218
|
if (!id) return c.notFound();
|
|
227
219
|
const body = await c.req.json();
|
|
228
220
|
await c.var.services.posts.update(id, {
|
|
229
|
-
|
|
221
|
+
format: body.format,
|
|
230
222
|
title: body.title || null,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
223
|
+
body: body.body || null,
|
|
224
|
+
status: body.status,
|
|
225
|
+
featured: body.featured,
|
|
226
|
+
pinned: body.pinned,
|
|
227
|
+
slug: body.slug || null,
|
|
228
|
+
url: body.url || null,
|
|
229
|
+
quoteText: body.quoteText || null,
|
|
230
|
+
rating: body.rating || null,
|
|
231
|
+
collectionId: body.collectionId || null
|
|
236
232
|
});
|
|
237
233
|
// Update media attachments if provided
|
|
238
234
|
if (body.mediaIds !== undefined) {
|
|
239
235
|
await c.var.services.media.attachToPost(id, body.mediaIds);
|
|
240
236
|
}
|
|
241
|
-
// Sync collection associations
|
|
242
|
-
if (body.collectionIds !== undefined) {
|
|
243
|
-
await c.var.services.collections.syncPostCollections(id, body.collectionIds);
|
|
244
|
-
}
|
|
245
237
|
return dsRedirect(`/dash/posts/${sqid.encode(id)}`);
|
|
246
238
|
});
|
|
247
239
|
// Delete post
|
package/dist/routes/feed/rss.js
CHANGED
|
@@ -15,10 +15,8 @@ export const rssRoutes = new Hono();
|
|
|
15
15
|
const siteUrl = c.env.SITE_URL;
|
|
16
16
|
const siteLanguage = await getSiteLanguage(c);
|
|
17
17
|
const posts = await c.var.services.posts.list({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"quiet"
|
|
21
|
-
],
|
|
18
|
+
status: "published",
|
|
19
|
+
excludeReplies: true,
|
|
22
20
|
limit: 50
|
|
23
21
|
});
|
|
24
22
|
// Batch load media for enclosures
|
|
@@ -2,25 +2,28 @@
|
|
|
2
2
|
* Sitemap Routes
|
|
3
3
|
*/ import { Hono } from "hono";
|
|
4
4
|
import { defaultSitemapRenderer } from "../../lib/feed.js";
|
|
5
|
-
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
5
|
+
import { createMediaContext, toPostViewsFromPosts, toPageView } from "../../lib/view.js";
|
|
6
6
|
export const sitemapRoutes = new Hono();
|
|
7
7
|
// XML Sitemap
|
|
8
8
|
sitemapRoutes.get("/sitemap.xml", async (c)=>{
|
|
9
9
|
const siteUrl = c.env.SITE_URL;
|
|
10
10
|
const posts = await c.var.services.posts.list({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"quiet"
|
|
14
|
-
],
|
|
11
|
+
status: "published",
|
|
12
|
+
excludeReplies: true,
|
|
15
13
|
limit: 1000
|
|
16
14
|
});
|
|
17
|
-
//
|
|
15
|
+
// Fetch published pages
|
|
16
|
+
const allPages = await c.var.services.pages.list();
|
|
17
|
+
const publishedPages = allPages.filter((p)=>p.status === "published");
|
|
18
|
+
// Transform to View Models
|
|
18
19
|
const mediaCtx = createMediaContext(c);
|
|
19
20
|
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
21
|
+
const pageViews = publishedPages.map(toPageView);
|
|
20
22
|
const renderer = c.var.config.theme?.feed?.sitemap ?? defaultSitemapRenderer;
|
|
21
23
|
const xml = renderer({
|
|
22
24
|
siteUrl,
|
|
23
|
-
posts: postViews
|
|
25
|
+
posts: postViews,
|
|
26
|
+
pages: pageViews
|
|
24
27
|
});
|
|
25
28
|
return new Response(xml, {
|
|
26
29
|
headers: {
|
|
@@ -2,10 +2,10 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* Archive Page Route
|
|
4
4
|
*
|
|
5
|
-
* Shows all posts, optionally filtered by
|
|
5
|
+
* Shows all posts, optionally filtered by format or featured status
|
|
6
6
|
*/ import { Hono } from "hono";
|
|
7
|
-
import {
|
|
8
|
-
import { ArchivePage as DefaultArchivePage } from "../../themes/
|
|
7
|
+
import { FORMATS } from "../../types.js";
|
|
8
|
+
import { ArchivePage as DefaultArchivePage } from "../../themes/threads/pages/ArchivePage.js";
|
|
9
9
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
10
10
|
import { renderPublicPage } from "../../lib/render.js";
|
|
11
11
|
import { createMediaContext, toArchiveGroups } from "../../lib/view.js";
|
|
@@ -13,19 +13,19 @@ const PAGE_SIZE = 50;
|
|
|
13
13
|
export const archiveRoutes = new Hono();
|
|
14
14
|
// Archive page - all posts
|
|
15
15
|
archiveRoutes.get("/", async (c)=>{
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const formatParam = c.req.query("format");
|
|
17
|
+
const format = formatParam && FORMATS.includes(formatParam) ? formatParam : undefined;
|
|
18
|
+
const featuredParam = c.req.query("featured");
|
|
19
|
+
const featured = featuredParam === "true" ? true : undefined;
|
|
18
20
|
// Parse cursor
|
|
19
21
|
const cursorParam = c.req.query("cursor");
|
|
20
22
|
const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
|
|
21
23
|
const navData = await getNavigationData(c);
|
|
22
24
|
// Fetch one extra to check for more
|
|
23
25
|
const posts = await c.var.services.posts.list({
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"quiet"
|
|
28
|
-
],
|
|
26
|
+
format,
|
|
27
|
+
status: "published",
|
|
28
|
+
featured,
|
|
29
29
|
excludeReplies: true,
|
|
30
30
|
cursor,
|
|
31
31
|
limit: PAGE_SIZE + 1
|
|
@@ -57,7 +57,8 @@ archiveRoutes.get("/", async (c)=>{
|
|
|
57
57
|
groups: groups,
|
|
58
58
|
hasMore: hasMore,
|
|
59
59
|
nextCursor: nextCursor,
|
|
60
|
-
|
|
60
|
+
format: format,
|
|
61
|
+
featured: featured,
|
|
61
62
|
theme: components
|
|
62
63
|
})
|
|
63
64
|
});
|
|
@@ -2,16 +2,21 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* Collection Page Route
|
|
4
4
|
*/ import { Hono } from "hono";
|
|
5
|
-
import { CollectionPage as DefaultCollectionPage } from "../../themes/
|
|
5
|
+
import { CollectionPage as DefaultCollectionPage } from "../../themes/threads/pages/CollectionPage.js";
|
|
6
6
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
7
7
|
import { renderPublicPage } from "../../lib/render.js";
|
|
8
8
|
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
9
9
|
export const collectionRoutes = new Hono();
|
|
10
|
-
collectionRoutes.get("/:
|
|
11
|
-
const
|
|
12
|
-
const collection = await c.var.services.collections.
|
|
10
|
+
collectionRoutes.get("/:slug", async (c)=>{
|
|
11
|
+
const slug = c.req.param("slug");
|
|
12
|
+
const collection = await c.var.services.collections.getBySlug(slug);
|
|
13
13
|
if (!collection) return c.notFound();
|
|
14
|
-
|
|
14
|
+
// Fetch posts in this collection
|
|
15
|
+
const posts = await c.var.services.posts.list({
|
|
16
|
+
collectionId: collection.id,
|
|
17
|
+
status: "published",
|
|
18
|
+
excludeReplies: true
|
|
19
|
+
});
|
|
15
20
|
const navData = await getNavigationData(c);
|
|
16
21
|
// Transform to View Models
|
|
17
22
|
const mediaCtx = createMediaContext(c);
|
|
@@ -25,6 +30,7 @@ collectionRoutes.get("/:path", async (c)=>{
|
|
|
25
30
|
content: /*#__PURE__*/ _jsx(Page, {
|
|
26
31
|
collection: collection,
|
|
27
32
|
posts: postViews,
|
|
33
|
+
hasMore: false,
|
|
28
34
|
theme: components
|
|
29
35
|
})
|
|
30
36
|
});
|
|
@@ -3,76 +3,67 @@ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
|
3
3
|
* Home Page Route
|
|
4
4
|
*
|
|
5
5
|
* Timeline feed with per-type card components and thread previews.
|
|
6
|
+
* Handles both full-page rendering and load-more SSE responses.
|
|
6
7
|
*/ import { Hono } from "hono";
|
|
7
|
-
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
8
8
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
9
9
|
import { renderPublicPage } from "../../lib/render.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import { assembleTimeline } from "../../lib/timeline.js";
|
|
11
|
+
import { sse } from "../../lib/sse.js";
|
|
12
|
+
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
13
|
+
import { HomePage as DefaultHomePage } from "../../themes/threads/pages/HomePage.js";
|
|
13
14
|
export const homeRoutes = new Hono();
|
|
14
15
|
homeRoutes.get("/", async (c)=>{
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"quiet"
|
|
21
|
-
],
|
|
22
|
-
excludeReplies: true,
|
|
23
|
-
excludeTypes: [
|
|
24
|
-
"page"
|
|
25
|
-
],
|
|
26
|
-
limit: PAGE_SIZE + 1
|
|
16
|
+
const cursorParam = c.req.query("cursor");
|
|
17
|
+
const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
|
|
18
|
+
const lastDate = c.req.query("lastDate");
|
|
19
|
+
const { items, hasMore, nextCursor } = await assembleTimeline(c, {
|
|
20
|
+
cursor: cursor && !isNaN(cursor) ? cursor : undefined
|
|
27
21
|
});
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const mediaMap = buildMediaMap(rawMediaMap, mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl);
|
|
35
|
-
// Get reply counts to identify thread roots
|
|
36
|
-
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
37
|
-
const threadRootIds = postIds.filter((id)=>(replyCounts.get(id) ?? 0) > 0);
|
|
38
|
-
// Batch load thread previews
|
|
39
|
-
const threadPreviews = await c.var.services.posts.getThreadPreviews(threadRootIds, 3);
|
|
40
|
-
// Batch load media for preview replies
|
|
41
|
-
const previewReplyIds = [];
|
|
42
|
-
for (const replies of threadPreviews.values()){
|
|
43
|
-
for (const reply of replies){
|
|
44
|
-
previewReplyIds.push(reply.id);
|
|
22
|
+
// SSE load-more response
|
|
23
|
+
if (cursor && !isNaN(cursor)) {
|
|
24
|
+
if (items.length === 0) {
|
|
25
|
+
return sse(c, async (stream)=>{
|
|
26
|
+
stream.remove("#load-more-container");
|
|
27
|
+
});
|
|
45
28
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}, mediaCtx);
|
|
54
|
-
const replyCount = replyCounts.get(post.id) ?? 0;
|
|
55
|
-
const previewReplies = threadPreviews.get(post.id);
|
|
56
|
-
if (replyCount > 0 && previewReplies) {
|
|
57
|
-
return {
|
|
58
|
-
post: postView,
|
|
59
|
-
threadPreview: {
|
|
60
|
-
replies: toPostViews(previewReplies.map((r)=>({
|
|
61
|
-
...r,
|
|
62
|
-
mediaAttachments: previewMediaMap.get(r.id) ?? []
|
|
63
|
-
})), mediaCtx),
|
|
64
|
-
totalReplyCount: replyCount
|
|
65
|
-
}
|
|
66
|
-
};
|
|
29
|
+
const themeConfig = c.var.config.theme;
|
|
30
|
+
const renderMore = themeConfig?.timelineMore;
|
|
31
|
+
if (!renderMore) {
|
|
32
|
+
// Should never happen — default theme always provides timelineMore
|
|
33
|
+
return sse(c, async (stream)=>{
|
|
34
|
+
stream.remove("#load-more-container");
|
|
35
|
+
});
|
|
67
36
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
37
|
+
const patches = renderMore({
|
|
38
|
+
items,
|
|
39
|
+
lastDate: lastDate ?? undefined,
|
|
40
|
+
hasMore,
|
|
41
|
+
nextCursor,
|
|
42
|
+
theme: themeConfig?.components
|
|
43
|
+
});
|
|
44
|
+
return sse(c, async (stream)=>{
|
|
45
|
+
for (const patch of patches){
|
|
46
|
+
if (patch.mode === "remove") {
|
|
47
|
+
stream.remove(patch.selector);
|
|
48
|
+
} else {
|
|
49
|
+
stream.patchElements(patch.content, {
|
|
50
|
+
mode: patch.mode,
|
|
51
|
+
selector: patch.selector
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Full page render
|
|
58
|
+
const navData = await getNavigationData(c);
|
|
59
|
+
// Fetch pinned posts
|
|
60
|
+
const pinnedPosts = await c.var.services.posts.list({
|
|
61
|
+
pinned: true,
|
|
62
|
+
status: "published",
|
|
63
|
+
excludeReplies: true
|
|
71
64
|
});
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
75
|
-
// Resolve page component
|
|
65
|
+
const mediaCtx = createMediaContext(c);
|
|
66
|
+
const pinnedItems = toPostViewsFromPosts(pinnedPosts, mediaCtx);
|
|
76
67
|
const components = c.var.config.theme?.components;
|
|
77
68
|
const Page = components?.HomePage ?? DefaultHomePage;
|
|
78
69
|
return renderPublicPage(c, {
|
|
@@ -80,6 +71,7 @@ homeRoutes.get("/", async (c)=>{
|
|
|
80
71
|
navData,
|
|
81
72
|
content: /*#__PURE__*/ _jsx(Page, {
|
|
82
73
|
items: items,
|
|
74
|
+
pinnedItems: pinnedItems,
|
|
83
75
|
hasMore: hasMore,
|
|
84
76
|
nextCursor: nextCursor,
|
|
85
77
|
theme: components
|