@jant/core 0.3.20 → 0.3.22
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 +60 -17
- package/dist/index.js +8 -0
- package/dist/lib/feed.js +112 -0
- package/dist/lib/navigation.js +9 -9
- package/dist/lib/render.js +48 -0
- package/dist/lib/theme-components.js +18 -18
- package/dist/lib/view.js +228 -0
- package/dist/routes/api/timeline.js +20 -16
- package/dist/routes/dash/collections.js +38 -10
- package/dist/routes/dash/navigation.js +22 -8
- package/dist/routes/dash/redirects.js +19 -5
- package/dist/routes/dash/settings.js +57 -15
- package/dist/routes/feed/rss.js +34 -78
- package/dist/routes/feed/sitemap.js +11 -26
- package/dist/routes/pages/archive.js +18 -195
- package/dist/routes/pages/collection.js +16 -70
- package/dist/routes/pages/home.js +25 -47
- package/dist/routes/pages/page.js +15 -27
- package/dist/routes/pages/post.js +25 -79
- package/dist/routes/pages/search.js +20 -130
- package/dist/theme/components/MediaGallery.js +10 -10
- package/dist/theme/components/PageForm.js +22 -8
- package/dist/theme/components/PostForm.js +22 -8
- package/dist/theme/components/index.js +1 -1
- package/dist/theme/components/timeline/ArticleCard.js +7 -11
- package/dist/theme/components/timeline/ImageCard.js +10 -13
- package/dist/theme/components/timeline/LinkCard.js +4 -7
- package/dist/theme/components/timeline/NoteCard.js +5 -8
- package/dist/theme/components/timeline/QuoteCard.js +3 -6
- package/dist/theme/components/timeline/ThreadPreview.js +9 -10
- package/dist/theme/components/timeline/TimelineFeed.js +8 -5
- package/dist/theme/components/timeline/TimelineItem.js +22 -2
- package/dist/theme/components/timeline/index.js +1 -1
- package/dist/theme/index.js +6 -3
- package/dist/theme/layouts/SiteLayout.js +10 -39
- package/dist/theme/pages/ArchivePage.js +157 -0
- package/dist/theme/pages/CollectionPage.js +63 -0
- package/dist/theme/pages/HomePage.js +26 -0
- package/dist/theme/pages/PostPage.js +48 -0
- package/dist/theme/pages/SearchPage.js +120 -0
- package/dist/theme/pages/SinglePage.js +23 -0
- package/dist/theme/pages/index.js +11 -0
- package/package.json +2 -1
- package/src/app.tsx +48 -17
- package/src/i18n/locales/en.po +171 -147
- package/src/i18n/locales/zh-Hans.po +171 -147
- package/src/i18n/locales/zh-Hant.po +171 -147
- package/src/index.ts +51 -2
- package/src/lib/__tests__/theme-components.test.ts +33 -14
- package/src/lib/__tests__/view.test.ts +375 -0
- package/src/lib/feed.ts +148 -0
- package/src/lib/navigation.ts +11 -11
- package/src/lib/render.tsx +67 -0
- package/src/lib/theme-components.ts +27 -35
- package/src/lib/view.ts +318 -0
- package/src/routes/api/__tests__/timeline.test.ts +3 -3
- package/src/routes/api/timeline.tsx +32 -25
- package/src/routes/dash/collections.tsx +30 -10
- package/src/routes/dash/navigation.tsx +20 -10
- package/src/routes/dash/redirects.tsx +15 -5
- package/src/routes/dash/settings.tsx +53 -15
- package/src/routes/feed/rss.ts +47 -94
- package/src/routes/feed/sitemap.ts +8 -30
- package/src/routes/pages/archive.tsx +24 -209
- package/src/routes/pages/collection.tsx +19 -75
- package/src/routes/pages/home.tsx +42 -76
- package/src/routes/pages/page.tsx +17 -28
- package/src/routes/pages/post.tsx +28 -86
- package/src/routes/pages/search.tsx +29 -151
- package/src/services/search.ts +2 -8
- package/src/theme/components/MediaGallery.tsx +12 -12
- package/src/theme/components/PageForm.tsx +20 -10
- package/src/theme/components/PostForm.tsx +20 -10
- package/src/theme/components/index.ts +1 -0
- package/src/theme/components/timeline/ArticleCard.tsx +7 -19
- package/src/theme/components/timeline/ImageCard.tsx +10 -20
- package/src/theme/components/timeline/LinkCard.tsx +4 -11
- package/src/theme/components/timeline/NoteCard.tsx +5 -12
- package/src/theme/components/timeline/QuoteCard.tsx +3 -10
- package/src/theme/components/timeline/ThreadPreview.tsx +5 -5
- package/src/theme/components/timeline/TimelineFeed.tsx +7 -3
- package/src/theme/components/timeline/TimelineItem.tsx +43 -4
- package/src/theme/components/timeline/index.ts +1 -1
- package/src/theme/index.ts +7 -3
- package/src/theme/layouts/SiteLayout.tsx +25 -77
- package/src/theme/layouts/index.ts +2 -1
- package/src/theme/pages/ArchivePage.tsx +160 -0
- package/src/theme/pages/CollectionPage.tsx +60 -0
- package/src/theme/pages/HomePage.tsx +42 -0
- package/src/theme/pages/PostPage.tsx +44 -0
- package/src/theme/pages/SearchPage.tsx +128 -0
- package/src/theme/pages/SinglePage.tsx +24 -0
- package/src/theme/pages/index.ts +13 -0
- package/src/types.ts +262 -38
|
@@ -1,191 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Archive Page Route
|
|
4
4
|
*
|
|
5
5
|
* Shows all posts, optionally filtered by type
|
|
6
6
|
*/ import { Hono } from "hono";
|
|
7
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
-
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
9
|
-
import { Pagination } from "../../theme/components/index.js";
|
|
10
7
|
import { POST_TYPES } from "../../types.js";
|
|
11
|
-
import
|
|
12
|
-
import * as time from "../../lib/time.js";
|
|
8
|
+
import { ArchivePage as DefaultArchivePage } from "../../theme/pages/ArchivePage.js";
|
|
13
9
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
10
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
11
|
+
import { createMediaContext, toArchiveGroups } from "../../lib/view.js";
|
|
14
12
|
const PAGE_SIZE = 50;
|
|
15
13
|
export const archiveRoutes = new Hono();
|
|
16
|
-
function getTypeLabel(type) {
|
|
17
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
18
|
-
const labels = {
|
|
19
|
-
note: $__i18n._({
|
|
20
|
-
id: "KiJn9B",
|
|
21
|
-
message: "Note"
|
|
22
|
-
}),
|
|
23
|
-
article: $__i18n._({
|
|
24
|
-
id: "f6e0Ry",
|
|
25
|
-
message: "Article"
|
|
26
|
-
}),
|
|
27
|
-
link: $__i18n._({
|
|
28
|
-
id: "yzF66j",
|
|
29
|
-
message: "Link"
|
|
30
|
-
}),
|
|
31
|
-
quote: $__i18n._({
|
|
32
|
-
id: "ZhhOwV",
|
|
33
|
-
message: "Quote"
|
|
34
|
-
}),
|
|
35
|
-
image: $__i18n._({
|
|
36
|
-
id: "hG89Ed",
|
|
37
|
-
message: "Image"
|
|
38
|
-
}),
|
|
39
|
-
page: $__i18n._({
|
|
40
|
-
id: "6WdDG7",
|
|
41
|
-
message: "Page"
|
|
42
|
-
})
|
|
43
|
-
};
|
|
44
|
-
return labels[type] ?? type;
|
|
45
|
-
}
|
|
46
|
-
function getTypeLabelPlural(type) {
|
|
47
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
48
|
-
const labels = {
|
|
49
|
-
note: $__i18n._({
|
|
50
|
-
id: "1DBGsz",
|
|
51
|
-
message: "Notes"
|
|
52
|
-
}),
|
|
53
|
-
article: $__i18n._({
|
|
54
|
-
id: "Tt5T6+",
|
|
55
|
-
message: "Articles"
|
|
56
|
-
}),
|
|
57
|
-
link: $__i18n._({
|
|
58
|
-
id: "Rj01Fz",
|
|
59
|
-
message: "Links"
|
|
60
|
-
}),
|
|
61
|
-
quote: $__i18n._({
|
|
62
|
-
id: "eWLklq",
|
|
63
|
-
message: "Quotes"
|
|
64
|
-
}),
|
|
65
|
-
image: $__i18n._({
|
|
66
|
-
id: "an5hVd",
|
|
67
|
-
message: "Images"
|
|
68
|
-
}),
|
|
69
|
-
page: $__i18n._({
|
|
70
|
-
id: "wRR604",
|
|
71
|
-
message: "Pages"
|
|
72
|
-
})
|
|
73
|
-
};
|
|
74
|
-
return labels[type] ?? `${type}s`;
|
|
75
|
-
}
|
|
76
|
-
function formatYearMonth(yearMonth) {
|
|
77
|
-
const [year, month] = yearMonth.split("-");
|
|
78
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- yearMonth format YYYY-MM guarantees both year and month exist
|
|
79
|
-
const date = new Date(parseInt(year, 10), parseInt(month, 10) - 1);
|
|
80
|
-
return date.toLocaleDateString("en-US", {
|
|
81
|
-
year: "numeric",
|
|
82
|
-
month: "long"
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
function ArchiveContent({ displayPosts, hasMore, nextCursor, type, grouped, replyCounts }) {
|
|
86
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
87
|
-
const title = type ? getTypeLabelPlural(type) : $__i18n._({
|
|
88
|
-
id: "B495Gs",
|
|
89
|
-
message: "Archive"
|
|
90
|
-
});
|
|
91
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
92
|
-
children: [
|
|
93
|
-
/*#__PURE__*/ _jsxs("header", {
|
|
94
|
-
class: "mb-8",
|
|
95
|
-
children: [
|
|
96
|
-
/*#__PURE__*/ _jsx("h1", {
|
|
97
|
-
class: "text-2xl font-semibold",
|
|
98
|
-
children: title
|
|
99
|
-
}),
|
|
100
|
-
/*#__PURE__*/ _jsxs("nav", {
|
|
101
|
-
class: "flex flex-wrap gap-2 mt-4",
|
|
102
|
-
children: [
|
|
103
|
-
/*#__PURE__*/ _jsx("a", {
|
|
104
|
-
href: "/archive",
|
|
105
|
-
class: `badge ${!type ? "badge-primary" : "badge-outline"}`,
|
|
106
|
-
children: $__i18n._({
|
|
107
|
-
id: "N40H+G",
|
|
108
|
-
message: "All"
|
|
109
|
-
})
|
|
110
|
-
}),
|
|
111
|
-
POST_TYPES.filter((t)=>t !== "page").map((typeKey)=>/*#__PURE__*/ _jsx("a", {
|
|
112
|
-
href: `/archive?type=${typeKey}`,
|
|
113
|
-
class: `badge ${type === typeKey ? "badge-primary" : "badge-outline"}`,
|
|
114
|
-
children: getTypeLabelPlural(typeKey)
|
|
115
|
-
}, typeKey))
|
|
116
|
-
]
|
|
117
|
-
})
|
|
118
|
-
]
|
|
119
|
-
}),
|
|
120
|
-
/*#__PURE__*/ _jsx("main", {
|
|
121
|
-
children: displayPosts.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
122
|
-
class: "text-muted-foreground",
|
|
123
|
-
children: $__i18n._({
|
|
124
|
-
id: "Hzi9AA",
|
|
125
|
-
message: "No posts found."
|
|
126
|
-
})
|
|
127
|
-
}) : Array.from(grouped.entries()).map(([yearMonth, monthPosts])=>/*#__PURE__*/ _jsxs("section", {
|
|
128
|
-
class: "mb-8",
|
|
129
|
-
children: [
|
|
130
|
-
/*#__PURE__*/ _jsx("h2", {
|
|
131
|
-
class: "text-lg font-medium mb-4 text-muted-foreground",
|
|
132
|
-
children: formatYearMonth(yearMonth)
|
|
133
|
-
}),
|
|
134
|
-
/*#__PURE__*/ _jsx("div", {
|
|
135
|
-
class: "flex flex-col gap-3",
|
|
136
|
-
children: monthPosts.map((post)=>{
|
|
137
|
-
const replyCount = replyCounts.get(post.id);
|
|
138
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
139
|
-
class: "flex items-baseline gap-4",
|
|
140
|
-
children: [
|
|
141
|
-
/*#__PURE__*/ _jsx("time", {
|
|
142
|
-
class: "text-sm text-muted-foreground w-12 shrink-0",
|
|
143
|
-
datetime: time.toISOString(post.publishedAt),
|
|
144
|
-
children: new Date(post.publishedAt * 1000).getDate()
|
|
145
|
-
}),
|
|
146
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
147
|
-
class: "flex-1 min-w-0",
|
|
148
|
-
children: [
|
|
149
|
-
/*#__PURE__*/ _jsx("a", {
|
|
150
|
-
href: `/p/${sqid.encode(post.id)}`,
|
|
151
|
-
class: "hover:underline",
|
|
152
|
-
children: post.title || post.content?.slice(0, 80) || `Post #${post.id}`
|
|
153
|
-
}),
|
|
154
|
-
!type && /*#__PURE__*/ _jsx("span", {
|
|
155
|
-
class: "ml-2 badge-outline text-xs",
|
|
156
|
-
children: getTypeLabel(post.type)
|
|
157
|
-
}),
|
|
158
|
-
replyCount && replyCount > 0 && /*#__PURE__*/ _jsxs("span", {
|
|
159
|
-
class: "ml-2 text-xs text-muted-foreground",
|
|
160
|
-
children: [
|
|
161
|
-
"(",
|
|
162
|
-
replyCount === 1 ? $__i18n._({
|
|
163
|
-
id: "TxE+Mj",
|
|
164
|
-
message: "1 reply"
|
|
165
|
-
}) : $__i18n._({
|
|
166
|
-
id: "90Luob",
|
|
167
|
-
message: "{count} replies"
|
|
168
|
-
}),
|
|
169
|
-
")"
|
|
170
|
-
]
|
|
171
|
-
})
|
|
172
|
-
]
|
|
173
|
-
})
|
|
174
|
-
]
|
|
175
|
-
}, post.id);
|
|
176
|
-
})
|
|
177
|
-
})
|
|
178
|
-
]
|
|
179
|
-
}, yearMonth))
|
|
180
|
-
}),
|
|
181
|
-
/*#__PURE__*/ _jsx(Pagination, {
|
|
182
|
-
baseUrl: type ? `/archive?type=${type}` : "/archive",
|
|
183
|
-
hasMore: hasMore,
|
|
184
|
-
nextCursor: nextCursor
|
|
185
|
-
})
|
|
186
|
-
]
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
14
|
// Archive page - all posts
|
|
190
15
|
archiveRoutes.get("/", async (c)=>{
|
|
191
16
|
const typeParam = c.req.query("type");
|
|
@@ -207,9 +32,6 @@ archiveRoutes.get("/", async (c)=>{
|
|
|
207
32
|
});
|
|
208
33
|
const hasMore = posts.length > PAGE_SIZE;
|
|
209
34
|
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
210
|
-
// Get reply counts for thread indicators
|
|
211
|
-
const postIds = displayPosts.map((p)=>p.id);
|
|
212
|
-
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
213
35
|
// Get next cursor
|
|
214
36
|
const nextCursor = hasMore && displayPosts.length > 0 ? displayPosts[displayPosts.length - 1].id : undefined;
|
|
215
37
|
// Group posts by year-month
|
|
@@ -223,19 +45,20 @@ archiveRoutes.get("/", async (c)=>{
|
|
|
223
45
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Map.set() above guarantees key exists
|
|
224
46
|
grouped.get(key).push(post);
|
|
225
47
|
}
|
|
226
|
-
|
|
48
|
+
// Transform to View Models
|
|
49
|
+
const mediaCtx = createMediaContext(c);
|
|
50
|
+
const groups = toArchiveGroups(grouped, mediaCtx);
|
|
51
|
+
const components = c.var.config.theme?.components;
|
|
52
|
+
const Page = components?.ArchivePage ?? DefaultArchivePage;
|
|
53
|
+
return renderPublicPage(c, {
|
|
227
54
|
title: `Archive - ${navData.siteName}`,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
type: type,
|
|
236
|
-
grouped: grouped,
|
|
237
|
-
replyCounts: replyCounts
|
|
238
|
-
})
|
|
55
|
+
navData,
|
|
56
|
+
content: /*#__PURE__*/ _jsx(Page, {
|
|
57
|
+
groups: groups,
|
|
58
|
+
hasMore: hasMore,
|
|
59
|
+
nextCursor: nextCursor,
|
|
60
|
+
type: type,
|
|
61
|
+
theme: components
|
|
239
62
|
})
|
|
240
|
-
})
|
|
63
|
+
});
|
|
241
64
|
});
|
|
@@ -1,85 +1,31 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Collection Page Route
|
|
4
4
|
*/ import { Hono } from "hono";
|
|
5
|
-
import {
|
|
6
|
-
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
7
|
-
import * as sqid from "../../lib/sqid.js";
|
|
8
|
-
import * as time from "../../lib/time.js";
|
|
5
|
+
import { CollectionPage as DefaultCollectionPage } from "../../theme/pages/CollectionPage.js";
|
|
9
6
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
7
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
8
|
+
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
10
9
|
export const collectionRoutes = new Hono();
|
|
11
|
-
function CollectionContent({ collection, posts }) {
|
|
12
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
13
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
14
|
-
children: [
|
|
15
|
-
/*#__PURE__*/ _jsxs("header", {
|
|
16
|
-
class: "mb-8",
|
|
17
|
-
children: [
|
|
18
|
-
/*#__PURE__*/ _jsx("h1", {
|
|
19
|
-
class: "text-2xl font-semibold",
|
|
20
|
-
children: collection.title
|
|
21
|
-
}),
|
|
22
|
-
collection.description && /*#__PURE__*/ _jsx("p", {
|
|
23
|
-
class: "text-muted-foreground mt-2",
|
|
24
|
-
children: collection.description
|
|
25
|
-
})
|
|
26
|
-
]
|
|
27
|
-
}),
|
|
28
|
-
/*#__PURE__*/ _jsx("main", {
|
|
29
|
-
class: "flex flex-col gap-6",
|
|
30
|
-
children: posts.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
31
|
-
class: "text-muted-foreground",
|
|
32
|
-
children: $__i18n._({
|
|
33
|
-
id: "J4FNfC",
|
|
34
|
-
message: "No posts in this collection."
|
|
35
|
-
})
|
|
36
|
-
}) : posts.map((post)=>/*#__PURE__*/ _jsxs("article", {
|
|
37
|
-
class: "h-entry",
|
|
38
|
-
children: [
|
|
39
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
40
|
-
class: "p-name text-lg font-medium mb-2",
|
|
41
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
42
|
-
href: `/p/${sqid.encode(post.id)}`,
|
|
43
|
-
class: "u-url hover:underline",
|
|
44
|
-
children: post.title
|
|
45
|
-
})
|
|
46
|
-
}),
|
|
47
|
-
/*#__PURE__*/ _jsx("div", {
|
|
48
|
-
class: "e-content prose prose-sm",
|
|
49
|
-
dangerouslySetInnerHTML: {
|
|
50
|
-
__html: post.contentHtml || ""
|
|
51
|
-
}
|
|
52
|
-
}),
|
|
53
|
-
/*#__PURE__*/ _jsx("footer", {
|
|
54
|
-
class: "mt-2 text-sm text-muted-foreground",
|
|
55
|
-
children: /*#__PURE__*/ _jsx("time", {
|
|
56
|
-
class: "dt-published",
|
|
57
|
-
datetime: time.toISOString(post.publishedAt),
|
|
58
|
-
children: time.formatDate(post.publishedAt)
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
]
|
|
62
|
-
}, post.id))
|
|
63
|
-
})
|
|
64
|
-
]
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
10
|
collectionRoutes.get("/:path", async (c)=>{
|
|
68
11
|
const path = c.req.param("path");
|
|
69
12
|
const collection = await c.var.services.collections.getByPath(path);
|
|
70
13
|
if (!collection) return c.notFound();
|
|
71
14
|
const posts = await c.var.services.collections.getPosts(collection.id);
|
|
72
15
|
const navData = await getNavigationData(c);
|
|
73
|
-
|
|
16
|
+
// Transform to View Models
|
|
17
|
+
const mediaCtx = createMediaContext(c);
|
|
18
|
+
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
19
|
+
const components = c.var.config.theme?.components;
|
|
20
|
+
const Page = components?.CollectionPage ?? DefaultCollectionPage;
|
|
21
|
+
return renderPublicPage(c, {
|
|
74
22
|
title: `${collection.title} - ${navData.siteName}`,
|
|
75
23
|
description: collection.description ?? undefined,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
posts: posts
|
|
82
|
-
})
|
|
24
|
+
navData,
|
|
25
|
+
content: /*#__PURE__*/ _jsx(Page, {
|
|
26
|
+
collection: collection,
|
|
27
|
+
posts: postViews,
|
|
28
|
+
theme: components
|
|
83
29
|
})
|
|
84
|
-
})
|
|
30
|
+
});
|
|
85
31
|
});
|
|
@@ -1,31 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Home Page Route
|
|
4
4
|
*
|
|
5
5
|
* Timeline feed with per-type card components and thread previews.
|
|
6
6
|
*/ import { Hono } from "hono";
|
|
7
|
-
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
-
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
9
7
|
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
10
|
-
import { resolveTimelineFeed } from "../../lib/theme-components.js";
|
|
11
|
-
import { TimelineFeed as DefaultTimelineFeed } from "../../theme/components/timeline/TimelineFeed.js";
|
|
12
8
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
9
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
10
|
+
import { HomePage as DefaultHomePage } from "../../theme/pages/HomePage.js";
|
|
11
|
+
import { createMediaContext, toPostView, toPostViews } from "../../lib/view.js";
|
|
13
12
|
const PAGE_SIZE = 20;
|
|
14
13
|
export const homeRoutes = new Hono();
|
|
15
|
-
function HomeContent({ FeedComponent, feedProps }) {
|
|
16
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
17
|
-
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
18
|
-
children: feedProps.items.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
19
|
-
class: "text-muted-foreground",
|
|
20
|
-
children: $__i18n._({
|
|
21
|
-
id: "ODiSoW",
|
|
22
|
-
message: "No posts yet."
|
|
23
|
-
})
|
|
24
|
-
}) : /*#__PURE__*/ _jsx(FeedComponent, {
|
|
25
|
-
...feedProps
|
|
26
|
-
})
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
14
|
homeRoutes.get("/", async (c)=>{
|
|
30
15
|
const navData = await getNavigationData(c);
|
|
31
16
|
// Fetch one extra to determine if there are more
|
|
@@ -45,10 +30,8 @@ homeRoutes.get("/", async (c)=>{
|
|
|
45
30
|
// Batch load media attachments
|
|
46
31
|
const postIds = displayPosts.map((p)=>p.id);
|
|
47
32
|
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const s3PublicUrl = c.env.S3_PUBLIC_URL;
|
|
51
|
-
const mediaMap = buildMediaMap(rawMediaMap, r2PublicUrl, imageTransformUrl, s3PublicUrl);
|
|
33
|
+
const mediaCtx = createMediaContext(c);
|
|
34
|
+
const mediaMap = buildMediaMap(rawMediaMap, mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl);
|
|
52
35
|
// Get reply counts to identify thread roots
|
|
53
36
|
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
54
37
|
const threadRootIds = postIds.filter((id)=>(replyCounts.get(id) ?? 0) > 0);
|
|
@@ -61,50 +44,45 @@ homeRoutes.get("/", async (c)=>{
|
|
|
61
44
|
previewReplyIds.push(reply.id);
|
|
62
45
|
}
|
|
63
46
|
}
|
|
64
|
-
const previewMediaMap = previewReplyIds.length > 0 ? buildMediaMap(await c.var.services.media.getByPostIds(previewReplyIds), r2PublicUrl, imageTransformUrl, s3PublicUrl) : new Map();
|
|
65
|
-
// Assemble timeline items
|
|
47
|
+
const previewMediaMap = previewReplyIds.length > 0 ? buildMediaMap(await c.var.services.media.getByPostIds(previewReplyIds), mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl) : new Map();
|
|
48
|
+
// Assemble timeline items with View Models
|
|
66
49
|
const items = displayPosts.map((post)=>{
|
|
67
|
-
const
|
|
50
|
+
const postView = toPostView({
|
|
68
51
|
...post,
|
|
69
52
|
mediaAttachments: mediaMap.get(post.id) ?? []
|
|
70
|
-
};
|
|
53
|
+
}, mediaCtx);
|
|
71
54
|
const replyCount = replyCounts.get(post.id) ?? 0;
|
|
72
55
|
const previewReplies = threadPreviews.get(post.id);
|
|
73
56
|
if (replyCount > 0 && previewReplies) {
|
|
74
57
|
return {
|
|
75
|
-
post:
|
|
58
|
+
post: postView,
|
|
76
59
|
threadPreview: {
|
|
77
|
-
replies: previewReplies.map((r)=>({
|
|
60
|
+
replies: toPostViews(previewReplies.map((r)=>({
|
|
78
61
|
...r,
|
|
79
62
|
mediaAttachments: previewMediaMap.get(r.id) ?? []
|
|
80
|
-
})),
|
|
63
|
+
})), mediaCtx),
|
|
81
64
|
totalReplyCount: replyCount
|
|
82
65
|
}
|
|
83
66
|
};
|
|
84
67
|
}
|
|
85
68
|
return {
|
|
86
|
-
post:
|
|
69
|
+
post: postView
|
|
87
70
|
};
|
|
88
71
|
});
|
|
89
72
|
// Determine next cursor
|
|
90
73
|
const lastPost = displayPosts[displayPosts.length - 1];
|
|
91
74
|
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
92
|
-
// Resolve
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
hasMore,
|
|
97
|
-
nextCursor
|
|
98
|
-
};
|
|
99
|
-
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
75
|
+
// Resolve page component
|
|
76
|
+
const components = c.var.config.theme?.components;
|
|
77
|
+
const Page = components?.HomePage ?? DefaultHomePage;
|
|
78
|
+
return renderPublicPage(c, {
|
|
100
79
|
title: navData.siteName,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
})
|
|
80
|
+
navData,
|
|
81
|
+
content: /*#__PURE__*/ _jsx(Page, {
|
|
82
|
+
items: items,
|
|
83
|
+
hasMore: hasMore,
|
|
84
|
+
nextCursor: nextCursor,
|
|
85
|
+
theme: components
|
|
108
86
|
})
|
|
109
|
-
})
|
|
87
|
+
});
|
|
110
88
|
});
|
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Custom Page Route
|
|
4
4
|
*
|
|
5
5
|
* Catch-all route for custom pages accessible via their path field
|
|
6
6
|
*/ import { Hono } from "hono";
|
|
7
|
-
import {
|
|
7
|
+
import { SinglePage as DefaultSinglePage } from "../../theme/pages/SinglePage.js";
|
|
8
8
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
9
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
10
|
+
import { createMediaContext, toPostViewFromPost } from "../../lib/view.js";
|
|
9
11
|
export const pageRoutes = new Hono();
|
|
10
|
-
function PageContent({ page }) {
|
|
11
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
12
|
-
class: "h-entry",
|
|
13
|
-
children: [
|
|
14
|
-
page.title && /*#__PURE__*/ _jsx("h1", {
|
|
15
|
-
class: "p-name text-3xl font-semibold mb-6",
|
|
16
|
-
children: page.title
|
|
17
|
-
}),
|
|
18
|
-
/*#__PURE__*/ _jsx("div", {
|
|
19
|
-
class: "e-content prose",
|
|
20
|
-
dangerouslySetInnerHTML: {
|
|
21
|
-
__html: page.contentHtml || ""
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
]
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
12
|
// Catch-all for custom page paths
|
|
28
13
|
pageRoutes.get("/:path", async (c)=>{
|
|
29
14
|
const path = c.req.param("path");
|
|
@@ -38,15 +23,18 @@ pageRoutes.get("/:path", async (c)=>{
|
|
|
38
23
|
return c.notFound();
|
|
39
24
|
}
|
|
40
25
|
const navData = await getNavigationData(c);
|
|
41
|
-
|
|
26
|
+
// Transform to View Model
|
|
27
|
+
const mediaCtx = createMediaContext(c);
|
|
28
|
+
const pageView = toPostViewFromPost(page, mediaCtx);
|
|
29
|
+
const components = c.var.config.theme?.components;
|
|
30
|
+
const Page = components?.SinglePage ?? DefaultSinglePage;
|
|
31
|
+
return renderPublicPage(c, {
|
|
42
32
|
title: `${page.title} - ${navData.siteName}`,
|
|
43
33
|
description: page.content?.slice(0, 160),
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
page: page
|
|
49
|
-
})
|
|
34
|
+
navData,
|
|
35
|
+
content: /*#__PURE__*/ _jsx(Page, {
|
|
36
|
+
page: pageView,
|
|
37
|
+
theme: components
|
|
50
38
|
})
|
|
51
|
-
})
|
|
39
|
+
});
|
|
52
40
|
});
|
|
@@ -1,54 +1,14 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Single Post Page Route
|
|
4
4
|
*/ import { Hono } from "hono";
|
|
5
|
-
import {
|
|
6
|
-
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
7
|
-
import { MediaGallery } from "../../theme/components/index.js";
|
|
5
|
+
import { PostPage as DefaultPostPage } from "../../theme/pages/PostPage.js";
|
|
8
6
|
import * as sqid from "../../lib/sqid.js";
|
|
9
|
-
import * as time from "../../lib/time.js";
|
|
10
|
-
import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "../../lib/image.js";
|
|
11
7
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
8
|
+
import { renderPublicPage } from "../../lib/render.js";
|
|
9
|
+
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
10
|
+
import { createMediaContext, toPostView } from "../../lib/view.js";
|
|
12
11
|
export const postRoutes = new Hono();
|
|
13
|
-
function PostContent({ post, mediaAttachments }) {
|
|
14
|
-
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
15
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
16
|
-
class: "h-entry",
|
|
17
|
-
children: [
|
|
18
|
-
post.title && /*#__PURE__*/ _jsx("h1", {
|
|
19
|
-
class: "p-name text-2xl font-semibold mb-4",
|
|
20
|
-
children: post.title
|
|
21
|
-
}),
|
|
22
|
-
/*#__PURE__*/ _jsx("div", {
|
|
23
|
-
class: "e-content prose",
|
|
24
|
-
dangerouslySetInnerHTML: {
|
|
25
|
-
__html: post.contentHtml || ""
|
|
26
|
-
}
|
|
27
|
-
}),
|
|
28
|
-
mediaAttachments.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
|
|
29
|
-
attachments: mediaAttachments
|
|
30
|
-
}),
|
|
31
|
-
/*#__PURE__*/ _jsxs("footer", {
|
|
32
|
-
class: "mt-6 pt-4 border-t text-sm text-muted-foreground",
|
|
33
|
-
children: [
|
|
34
|
-
/*#__PURE__*/ _jsx("time", {
|
|
35
|
-
class: "dt-published",
|
|
36
|
-
datetime: time.toISOString(post.publishedAt),
|
|
37
|
-
children: time.formatDate(post.publishedAt)
|
|
38
|
-
}),
|
|
39
|
-
/*#__PURE__*/ _jsx("a", {
|
|
40
|
-
href: `/p/${sqid.encode(post.id)}`,
|
|
41
|
-
class: "u-url ml-4",
|
|
42
|
-
children: $__i18n._({
|
|
43
|
-
id: "D9Oea+",
|
|
44
|
-
message: "Permalink"
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
]
|
|
48
|
-
})
|
|
49
|
-
]
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
12
|
postRoutes.get("/:id", async (c)=>{
|
|
53
13
|
const paramId = c.req.param("id");
|
|
54
14
|
// Try to decode as sqid first
|
|
@@ -67,42 +27,28 @@ postRoutes.get("/:id", async (c)=>{
|
|
|
67
27
|
if (post.visibility === "draft") {
|
|
68
28
|
return c.notFound();
|
|
69
29
|
}
|
|
70
|
-
//
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
width: 400,
|
|
82
|
-
quality: 80,
|
|
83
|
-
format: "auto",
|
|
84
|
-
fit: "cover"
|
|
85
|
-
}),
|
|
86
|
-
alt: m.alt,
|
|
87
|
-
blurhash: m.blurhash,
|
|
88
|
-
width: m.width,
|
|
89
|
-
height: m.height,
|
|
90
|
-
position: m.position,
|
|
91
|
-
mimeType: m.mimeType
|
|
92
|
-
};
|
|
93
|
-
});
|
|
30
|
+
// Batch load media attachments
|
|
31
|
+
const rawMediaMap = await c.var.services.media.getByPostIds([
|
|
32
|
+
post.id
|
|
33
|
+
]);
|
|
34
|
+
const mediaCtx = createMediaContext(c);
|
|
35
|
+
const mediaMap = buildMediaMap(rawMediaMap, mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl);
|
|
36
|
+
// Transform to View Model
|
|
37
|
+
const postView = toPostView({
|
|
38
|
+
...post,
|
|
39
|
+
mediaAttachments: mediaMap.get(post.id) ?? []
|
|
40
|
+
}, mediaCtx);
|
|
94
41
|
const navData = await getNavigationData(c);
|
|
95
42
|
const title = post.title || navData.siteName;
|
|
96
|
-
|
|
97
|
-
|
|
43
|
+
const components = c.var.config.theme?.components;
|
|
44
|
+
const Page = components?.PostPage ?? DefaultPostPage;
|
|
45
|
+
return renderPublicPage(c, {
|
|
46
|
+
title,
|
|
98
47
|
description: post.content?.slice(0, 160),
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
post: post,
|
|
104
|
-
mediaAttachments: mediaAttachments
|
|
105
|
-
})
|
|
48
|
+
navData,
|
|
49
|
+
content: /*#__PURE__*/ _jsx(Page, {
|
|
50
|
+
post: postView,
|
|
51
|
+
theme: components
|
|
106
52
|
})
|
|
107
|
-
})
|
|
53
|
+
});
|
|
108
54
|
});
|