@jant/core 0.3.6 → 0.3.8
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 +11 -21
- package/dist/client.js +1 -0
- package/dist/db/schema.js +15 -0
- 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 +1 -1
- package/dist/lib/image.js +3 -3
- package/dist/lib/media-helpers.js +43 -0
- package/dist/lib/nav-reorder.js +27 -0
- package/dist/lib/navigation.js +35 -0
- package/dist/lib/schemas.js +32 -2
- package/dist/lib/sse.js +7 -8
- package/dist/lib/theme-components.js +49 -0
- package/dist/routes/api/posts.js +101 -5
- package/dist/routes/api/timeline.js +115 -0
- package/dist/routes/api/upload.js +9 -5
- package/dist/routes/dash/media.js +38 -0
- package/dist/routes/dash/navigation.js +274 -0
- package/dist/routes/dash/posts.js +45 -6
- package/dist/routes/feed/rss.js +10 -1
- package/dist/routes/pages/archive.js +14 -27
- package/dist/routes/pages/collection.js +10 -19
- package/dist/routes/pages/home.js +88 -98
- package/dist/routes/pages/page.js +19 -38
- package/dist/routes/pages/post.js +61 -48
- package/dist/routes/pages/search.js +13 -26
- package/dist/services/collection.js +13 -0
- package/dist/services/index.js +3 -1
- package/dist/services/media.js +55 -2
- package/dist/services/navigation.js +115 -0
- package/dist/services/post.js +26 -1
- package/dist/theme/components/MediaGallery.js +107 -0
- package/dist/theme/components/PostForm.js +158 -2
- package/dist/theme/components/PostList.js +5 -0
- package/dist/theme/components/index.js +3 -0
- package/dist/theme/components/timeline/ArticleCard.js +50 -0
- package/dist/theme/components/timeline/ImageCard.js +86 -0
- package/dist/theme/components/timeline/LinkCard.js +62 -0
- package/dist/theme/components/timeline/NoteCard.js +37 -0
- package/dist/theme/components/timeline/QuoteCard.js +51 -0
- package/dist/theme/components/timeline/ThreadPreview.js +52 -0
- package/dist/theme/components/timeline/TimelineFeed.js +43 -0
- package/dist/theme/components/timeline/TimelineItem.js +25 -0
- package/dist/theme/components/timeline/index.js +8 -0
- package/dist/theme/layouts/DashLayout.js +8 -0
- package/dist/theme/layouts/SiteLayout.js +160 -0
- package/dist/theme/layouts/index.js +1 -0
- package/dist/types/sortablejs.d.js +5 -0
- package/dist/types.js +27 -0
- package/package.json +3 -2
- package/src/__tests__/helpers/app.ts +6 -1
- package/src/__tests__/helpers/db.ts +20 -0
- package/src/app.tsx +11 -25
- package/src/client.ts +1 -0
- package/src/db/migrations/0002_add_media_attachments.sql +3 -0
- package/src/db/migrations/0003_add_navigation_links.sql +8 -0
- package/src/db/migrations/meta/0003_snapshot.json +821 -0
- package/src/db/migrations/meta/_journal.json +14 -0
- package/src/db/schema.ts +15 -0
- package/src/i18n/locales/en.po +170 -58
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +162 -71
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +162 -71
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +13 -1
- package/src/lib/__tests__/schemas.test.ts +89 -1
- package/src/lib/__tests__/sse.test.ts +13 -1
- package/src/lib/__tests__/theme-components.test.ts +107 -0
- package/src/lib/image.ts +3 -3
- package/src/lib/media-helpers.ts +54 -0
- package/src/lib/nav-reorder.ts +26 -0
- package/src/lib/navigation.ts +46 -0
- package/src/lib/schemas.ts +47 -1
- package/src/lib/sse.ts +10 -11
- package/src/lib/theme-components.ts +76 -0
- package/src/routes/api/__tests__/posts.test.ts +239 -0
- package/src/routes/api/__tests__/timeline.test.ts +242 -0
- package/src/routes/api/posts.ts +134 -5
- package/src/routes/api/timeline.tsx +145 -0
- package/src/routes/api/upload.ts +9 -5
- package/src/routes/dash/media.tsx +50 -0
- package/src/routes/dash/navigation.tsx +306 -0
- package/src/routes/dash/posts.tsx +79 -7
- package/src/routes/feed/rss.ts +14 -1
- package/src/routes/pages/archive.tsx +15 -23
- package/src/routes/pages/collection.tsx +8 -15
- package/src/routes/pages/home.tsx +121 -88
- package/src/routes/pages/page.tsx +17 -30
- package/src/routes/pages/post.tsx +64 -40
- package/src/routes/pages/search.tsx +18 -22
- package/src/services/__tests__/collection.test.ts +102 -0
- package/src/services/__tests__/media.test.ts +282 -7
- package/src/services/__tests__/navigation.test.ts +213 -0
- package/src/services/__tests__/post-timeline.test.ts +220 -0
- package/src/services/collection.ts +19 -0
- package/src/services/index.ts +7 -0
- package/src/services/media.ts +78 -2
- package/src/services/navigation.ts +165 -0
- package/src/services/post.ts +48 -1
- package/src/styles/components.css +59 -0
- package/src/theme/components/MediaGallery.tsx +128 -0
- package/src/theme/components/PostForm.tsx +170 -2
- package/src/theme/components/PostList.tsx +7 -0
- package/src/theme/components/index.ts +13 -0
- package/src/theme/components/timeline/ArticleCard.tsx +57 -0
- package/src/theme/components/timeline/ImageCard.tsx +80 -0
- package/src/theme/components/timeline/LinkCard.tsx +66 -0
- package/src/theme/components/timeline/NoteCard.tsx +41 -0
- package/src/theme/components/timeline/QuoteCard.tsx +55 -0
- package/src/theme/components/timeline/ThreadPreview.tsx +49 -0
- package/src/theme/components/timeline/TimelineFeed.tsx +52 -0
- package/src/theme/components/timeline/TimelineItem.tsx +39 -0
- package/src/theme/components/timeline/index.ts +8 -0
- package/src/theme/layouts/DashLayout.tsx +10 -0
- package/src/theme/layouts/SiteLayout.tsx +184 -0
- package/src/theme/layouts/index.ts +1 -0
- package/src/types/sortablejs.d.ts +23 -0
- package/src/types.ts +97 -0
- package/dist/app.d.ts +0 -38
- package/dist/app.d.ts.map +0 -1
- package/dist/auth.d.ts +0 -25
- package/dist/auth.d.ts.map +0 -1
- package/dist/db/index.d.ts +0 -10
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/schema.d.ts +0 -1507
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/i18n/Trans.d.ts +0 -25
- package/dist/i18n/Trans.d.ts.map +0 -1
- package/dist/i18n/context.d.ts +0 -69
- package/dist/i18n/context.d.ts.map +0 -1
- package/dist/i18n/detect.d.ts +0 -20
- package/dist/i18n/detect.d.ts.map +0 -1
- package/dist/i18n/i18n.d.ts +0 -32
- package/dist/i18n/i18n.d.ts.map +0 -1
- package/dist/i18n/index.d.ts +0 -41
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/i18n/locales/en.d.ts +0 -3
- package/dist/i18n/locales/en.d.ts.map +0 -1
- package/dist/i18n/locales/zh-Hans.d.ts +0 -3
- package/dist/i18n/locales/zh-Hans.d.ts.map +0 -1
- package/dist/i18n/locales/zh-Hant.d.ts +0 -3
- package/dist/i18n/locales/zh-Hant.d.ts.map +0 -1
- package/dist/i18n/locales.d.ts +0 -11
- package/dist/i18n/locales.d.ts.map +0 -1
- package/dist/i18n/middleware.d.ts +0 -21
- package/dist/i18n/middleware.d.ts.map +0 -1
- package/dist/index.d.ts +0 -16
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/config.d.ts +0 -83
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/constants.d.ts +0 -37
- package/dist/lib/constants.d.ts.map +0 -1
- package/dist/lib/image.d.ts +0 -73
- package/dist/lib/image.d.ts.map +0 -1
- package/dist/lib/index.d.ts +0 -9
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/markdown.d.ts +0 -60
- package/dist/lib/markdown.d.ts.map +0 -1
- package/dist/lib/schemas.d.ts +0 -113
- package/dist/lib/schemas.d.ts.map +0 -1
- package/dist/lib/sqid.d.ts +0 -60
- package/dist/lib/sqid.d.ts.map +0 -1
- package/dist/lib/sse.d.ts +0 -192
- package/dist/lib/sse.d.ts.map +0 -1
- package/dist/lib/theme.d.ts +0 -44
- package/dist/lib/theme.d.ts.map +0 -1
- package/dist/lib/time.d.ts +0 -90
- package/dist/lib/time.d.ts.map +0 -1
- package/dist/lib/url.d.ts +0 -82
- package/dist/lib/url.d.ts.map +0 -1
- package/dist/middleware/auth.d.ts +0 -24
- package/dist/middleware/auth.d.ts.map +0 -1
- package/dist/middleware/onboarding.d.ts +0 -26
- package/dist/middleware/onboarding.d.ts.map +0 -1
- package/dist/routes/api/posts.d.ts +0 -13
- package/dist/routes/api/posts.d.ts.map +0 -1
- package/dist/routes/api/search.d.ts +0 -13
- package/dist/routes/api/search.d.ts.map +0 -1
- package/dist/routes/api/upload.d.ts +0 -16
- package/dist/routes/api/upload.d.ts.map +0 -1
- package/dist/routes/dash/collections.d.ts +0 -13
- package/dist/routes/dash/collections.d.ts.map +0 -1
- package/dist/routes/dash/index.d.ts +0 -15
- package/dist/routes/dash/index.d.ts.map +0 -1
- package/dist/routes/dash/media.d.ts +0 -16
- package/dist/routes/dash/media.d.ts.map +0 -1
- package/dist/routes/dash/pages.d.ts +0 -15
- package/dist/routes/dash/pages.d.ts.map +0 -1
- package/dist/routes/dash/posts.d.ts +0 -13
- package/dist/routes/dash/posts.d.ts.map +0 -1
- package/dist/routes/dash/redirects.d.ts +0 -13
- package/dist/routes/dash/redirects.d.ts.map +0 -1
- package/dist/routes/dash/settings.d.ts +0 -15
- package/dist/routes/dash/settings.d.ts.map +0 -1
- package/dist/routes/feed/rss.d.ts +0 -13
- package/dist/routes/feed/rss.d.ts.map +0 -1
- package/dist/routes/feed/sitemap.d.ts +0 -13
- package/dist/routes/feed/sitemap.d.ts.map +0 -1
- package/dist/routes/pages/archive.d.ts +0 -15
- package/dist/routes/pages/archive.d.ts.map +0 -1
- package/dist/routes/pages/collection.d.ts +0 -13
- package/dist/routes/pages/collection.d.ts.map +0 -1
- package/dist/routes/pages/home.d.ts +0 -13
- package/dist/routes/pages/home.d.ts.map +0 -1
- package/dist/routes/pages/page.d.ts +0 -15
- package/dist/routes/pages/page.d.ts.map +0 -1
- package/dist/routes/pages/post.d.ts +0 -13
- package/dist/routes/pages/post.d.ts.map +0 -1
- package/dist/routes/pages/search.d.ts +0 -13
- package/dist/routes/pages/search.d.ts.map +0 -1
- package/dist/services/collection.d.ts +0 -31
- package/dist/services/collection.d.ts.map +0 -1
- package/dist/services/index.d.ts +0 -28
- package/dist/services/index.d.ts.map +0 -1
- package/dist/services/media.d.ts +0 -27
- package/dist/services/media.d.ts.map +0 -1
- package/dist/services/post.d.ts +0 -31
- package/dist/services/post.d.ts.map +0 -1
- package/dist/services/redirect.d.ts +0 -15
- package/dist/services/redirect.d.ts.map +0 -1
- package/dist/services/search.d.ts +0 -26
- package/dist/services/search.d.ts.map +0 -1
- package/dist/services/settings.d.ts +0 -18
- package/dist/services/settings.d.ts.map +0 -1
- package/dist/theme/color-themes.d.ts +0 -30
- package/dist/theme/color-themes.d.ts.map +0 -1
- package/dist/theme/components/ActionButtons.d.ts +0 -43
- package/dist/theme/components/ActionButtons.d.ts.map +0 -1
- package/dist/theme/components/CrudPageHeader.d.ts +0 -23
- package/dist/theme/components/CrudPageHeader.d.ts.map +0 -1
- package/dist/theme/components/DangerZone.d.ts +0 -36
- package/dist/theme/components/DangerZone.d.ts.map +0 -1
- package/dist/theme/components/EmptyState.d.ts +0 -27
- package/dist/theme/components/EmptyState.d.ts.map +0 -1
- package/dist/theme/components/ListItemRow.d.ts +0 -15
- package/dist/theme/components/ListItemRow.d.ts.map +0 -1
- package/dist/theme/components/PageForm.d.ts +0 -14
- package/dist/theme/components/PageForm.d.ts.map +0 -1
- package/dist/theme/components/Pagination.d.ts +0 -46
- package/dist/theme/components/Pagination.d.ts.map +0 -1
- package/dist/theme/components/PostForm.d.ts +0 -11
- package/dist/theme/components/PostForm.d.ts.map +0 -1
- package/dist/theme/components/PostList.d.ts +0 -10
- package/dist/theme/components/PostList.d.ts.map +0 -1
- package/dist/theme/components/ThreadView.d.ts +0 -15
- package/dist/theme/components/ThreadView.d.ts.map +0 -1
- package/dist/theme/components/TypeBadge.d.ts +0 -12
- package/dist/theme/components/TypeBadge.d.ts.map +0 -1
- package/dist/theme/components/VisibilityBadge.d.ts +0 -12
- package/dist/theme/components/VisibilityBadge.d.ts.map +0 -1
- package/dist/theme/components/index.d.ts +0 -13
- package/dist/theme/components/index.d.ts.map +0 -1
- package/dist/theme/index.d.ts +0 -21
- package/dist/theme/index.d.ts.map +0 -1
- package/dist/theme/layouts/BaseLayout.d.ts +0 -23
- package/dist/theme/layouts/BaseLayout.d.ts.map +0 -1
- package/dist/theme/layouts/DashLayout.d.ts +0 -17
- package/dist/theme/layouts/DashLayout.d.ts.map +0 -1
- package/dist/theme/layouts/index.d.ts +0 -3
- package/dist/theme/layouts/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -213
- package/dist/types.d.ts.map +0 -1
|
@@ -5,7 +5,7 @@ import { getSiteName } from "../../lib/config.js";
|
|
|
5
5
|
|
|
6
6
|
import { Hono } from "hono";
|
|
7
7
|
import { useLingui } from "@lingui/react/macro";
|
|
8
|
-
import type { Bindings, Post } from "../../types.js";
|
|
8
|
+
import type { Bindings, Post, Media, Collection } from "../../types.js";
|
|
9
9
|
import type { AppVariables } from "../../app.js";
|
|
10
10
|
import { DashLayout } from "../../theme/layouts/index.js";
|
|
11
11
|
import {
|
|
@@ -38,14 +38,14 @@ function PostsListContent({ posts }: { posts: Post[] }) {
|
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function NewPostContent() {
|
|
41
|
+
function NewPostContent({ collections }: { collections: Collection[] }) {
|
|
42
42
|
const { t } = useLingui();
|
|
43
43
|
return (
|
|
44
44
|
<>
|
|
45
45
|
<h1 class="text-2xl font-semibold mb-6">
|
|
46
46
|
{t({ message: "New Post", comment: "@context: Page heading" })}
|
|
47
47
|
</h1>
|
|
48
|
-
<PostForm action="/dash/posts" />
|
|
48
|
+
<PostForm action="/dash/posts" collections={collections} />
|
|
49
49
|
</>
|
|
50
50
|
);
|
|
51
51
|
}
|
|
@@ -72,6 +72,7 @@ postsRoutes.get("/", async (c) => {
|
|
|
72
72
|
// New post form
|
|
73
73
|
postsRoutes.get("/new", async (c) => {
|
|
74
74
|
const siteName = await getSiteName(c);
|
|
75
|
+
const collections = await c.var.services.collections.list();
|
|
75
76
|
|
|
76
77
|
return c.html(
|
|
77
78
|
<DashLayout
|
|
@@ -80,7 +81,7 @@ postsRoutes.get("/new", async (c) => {
|
|
|
80
81
|
siteName={siteName}
|
|
81
82
|
currentPath="/dash/posts"
|
|
82
83
|
>
|
|
83
|
-
<NewPostContent />
|
|
84
|
+
<NewPostContent collections={collections} />
|
|
84
85
|
</DashLayout>,
|
|
85
86
|
);
|
|
86
87
|
});
|
|
@@ -93,7 +94,10 @@ postsRoutes.post("/", async (c) => {
|
|
|
93
94
|
content: string;
|
|
94
95
|
visibility: string;
|
|
95
96
|
sourceUrl?: string;
|
|
97
|
+
sourceName?: string;
|
|
96
98
|
path?: string;
|
|
99
|
+
mediaIds?: string[];
|
|
100
|
+
collectionIds?: number[];
|
|
97
101
|
}>();
|
|
98
102
|
|
|
99
103
|
const post = await c.var.services.posts.create({
|
|
@@ -102,9 +106,23 @@ postsRoutes.post("/", async (c) => {
|
|
|
102
106
|
content: body.content,
|
|
103
107
|
visibility: body.visibility as Post["visibility"],
|
|
104
108
|
sourceUrl: body.sourceUrl || undefined,
|
|
109
|
+
sourceName: body.sourceName || undefined,
|
|
105
110
|
path: body.path || undefined,
|
|
106
111
|
});
|
|
107
112
|
|
|
113
|
+
// Attach media if provided
|
|
114
|
+
if (body.mediaIds && body.mediaIds.length > 0) {
|
|
115
|
+
await c.var.services.media.attachToPost(post.id, body.mediaIds);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Sync collection associations
|
|
119
|
+
if (body.collectionIds) {
|
|
120
|
+
await c.var.services.collections.syncPostCollections(
|
|
121
|
+
post.id,
|
|
122
|
+
body.collectionIds,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
108
126
|
return dsRedirect(`/dash/posts/${sqid.encode(post.id)}`);
|
|
109
127
|
});
|
|
110
128
|
|
|
@@ -145,14 +163,36 @@ function ViewPostContent({ post }: { post: Post }) {
|
|
|
145
163
|
);
|
|
146
164
|
}
|
|
147
165
|
|
|
148
|
-
function EditPostContent({
|
|
166
|
+
function EditPostContent({
|
|
167
|
+
post,
|
|
168
|
+
mediaAttachments,
|
|
169
|
+
r2PublicUrl,
|
|
170
|
+
imageTransformUrl,
|
|
171
|
+
collections,
|
|
172
|
+
postCollectionIds,
|
|
173
|
+
}: {
|
|
174
|
+
post: Post;
|
|
175
|
+
mediaAttachments: Media[];
|
|
176
|
+
r2PublicUrl?: string;
|
|
177
|
+
imageTransformUrl?: string;
|
|
178
|
+
collections: Collection[];
|
|
179
|
+
postCollectionIds: number[];
|
|
180
|
+
}) {
|
|
149
181
|
const { t } = useLingui();
|
|
150
182
|
return (
|
|
151
183
|
<>
|
|
152
184
|
<h1 class="text-2xl font-semibold mb-6">
|
|
153
185
|
{t({ message: "Edit Post", comment: "@context: Page heading" })}
|
|
154
186
|
</h1>
|
|
155
|
-
<PostForm
|
|
187
|
+
<PostForm
|
|
188
|
+
post={post}
|
|
189
|
+
action={`/dash/posts/${sqid.encode(post.id)}`}
|
|
190
|
+
mediaAttachments={mediaAttachments}
|
|
191
|
+
r2PublicUrl={r2PublicUrl}
|
|
192
|
+
imageTransformUrl={imageTransformUrl}
|
|
193
|
+
collections={collections}
|
|
194
|
+
postCollectionIds={postCollectionIds}
|
|
195
|
+
/>
|
|
156
196
|
</>
|
|
157
197
|
);
|
|
158
198
|
}
|
|
@@ -189,6 +229,13 @@ postsRoutes.get("/:id/edit", async (c) => {
|
|
|
189
229
|
if (!post) return c.notFound();
|
|
190
230
|
|
|
191
231
|
const siteName = await getSiteName(c);
|
|
232
|
+
const mediaAttachments = await c.var.services.media.getByPostId(post.id);
|
|
233
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
234
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
235
|
+
const collections = await c.var.services.collections.list();
|
|
236
|
+
const postCollections =
|
|
237
|
+
await c.var.services.collections.getCollectionsForPost(post.id);
|
|
238
|
+
const postCollectionIds = postCollections.map((col) => col.id);
|
|
192
239
|
|
|
193
240
|
return c.html(
|
|
194
241
|
<DashLayout
|
|
@@ -197,7 +244,14 @@ postsRoutes.get("/:id/edit", async (c) => {
|
|
|
197
244
|
siteName={siteName}
|
|
198
245
|
currentPath="/dash/posts"
|
|
199
246
|
>
|
|
200
|
-
<EditPostContent
|
|
247
|
+
<EditPostContent
|
|
248
|
+
post={post}
|
|
249
|
+
mediaAttachments={mediaAttachments}
|
|
250
|
+
r2PublicUrl={r2PublicUrl}
|
|
251
|
+
imageTransformUrl={imageTransformUrl}
|
|
252
|
+
collections={collections}
|
|
253
|
+
postCollectionIds={postCollectionIds}
|
|
254
|
+
/>
|
|
201
255
|
</DashLayout>,
|
|
202
256
|
);
|
|
203
257
|
});
|
|
@@ -213,7 +267,10 @@ postsRoutes.post("/:id", async (c) => {
|
|
|
213
267
|
content?: string;
|
|
214
268
|
visibility: string;
|
|
215
269
|
sourceUrl?: string;
|
|
270
|
+
sourceName?: string;
|
|
216
271
|
path?: string;
|
|
272
|
+
mediaIds?: string[];
|
|
273
|
+
collectionIds?: number[];
|
|
217
274
|
}>();
|
|
218
275
|
|
|
219
276
|
await c.var.services.posts.update(id, {
|
|
@@ -222,9 +279,23 @@ postsRoutes.post("/:id", async (c) => {
|
|
|
222
279
|
content: body.content || null,
|
|
223
280
|
visibility: body.visibility as Post["visibility"],
|
|
224
281
|
sourceUrl: body.sourceUrl || null,
|
|
282
|
+
sourceName: body.sourceName || null,
|
|
225
283
|
path: body.path || null,
|
|
226
284
|
});
|
|
227
285
|
|
|
286
|
+
// Update media attachments if provided
|
|
287
|
+
if (body.mediaIds !== undefined) {
|
|
288
|
+
await c.var.services.media.attachToPost(id, body.mediaIds);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Sync collection associations
|
|
292
|
+
if (body.collectionIds !== undefined) {
|
|
293
|
+
await c.var.services.collections.syncPostCollections(
|
|
294
|
+
id,
|
|
295
|
+
body.collectionIds,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
228
299
|
return dsRedirect(`/dash/posts/${sqid.encode(id)}`);
|
|
229
300
|
});
|
|
230
301
|
|
|
@@ -233,6 +304,7 @@ postsRoutes.post("/:id/delete", async (c) => {
|
|
|
233
304
|
const id = sqid.decode(c.req.param("id"));
|
|
234
305
|
if (!id) return c.notFound();
|
|
235
306
|
|
|
307
|
+
await c.var.services.media.detachFromPost(id);
|
|
236
308
|
await c.var.services.posts.delete(id);
|
|
237
309
|
|
|
238
310
|
return dsRedirect("/dash/posts");
|
package/src/routes/feed/rss.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { Bindings } from "../../types.js";
|
|
|
7
7
|
import type { AppVariables } from "../../app.js";
|
|
8
8
|
import * as sqid from "../../lib/sqid.js";
|
|
9
9
|
import * as time from "../../lib/time.js";
|
|
10
|
+
import { getMediaUrl } from "../../lib/image.js";
|
|
10
11
|
|
|
11
12
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
12
13
|
|
|
@@ -18,25 +19,37 @@ rssRoutes.get("/", async (c) => {
|
|
|
18
19
|
const siteName = all["SITE_NAME"] ?? "Jant";
|
|
19
20
|
const siteDescription = all["SITE_DESCRIPTION"] ?? "";
|
|
20
21
|
const siteUrl = c.env.SITE_URL;
|
|
22
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
21
23
|
|
|
22
24
|
const posts = await c.var.services.posts.list({
|
|
23
25
|
visibility: ["featured", "quiet"],
|
|
24
26
|
limit: 50,
|
|
25
27
|
});
|
|
26
28
|
|
|
29
|
+
// Batch load media for enclosures
|
|
30
|
+
const postIds = posts.map((p) => p.id);
|
|
31
|
+
const mediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
32
|
+
|
|
27
33
|
const items = posts
|
|
28
34
|
.map((post) => {
|
|
29
35
|
const link = `${siteUrl}/p/${sqid.encode(post.id)}`;
|
|
30
36
|
const title = post.title || `Post #${post.id}`;
|
|
31
37
|
const pubDate = new Date(post.publishedAt * 1000).toUTCString();
|
|
32
38
|
|
|
39
|
+
// Add enclosure for first media attachment
|
|
40
|
+
const postMedia = mediaMap.get(post.id);
|
|
41
|
+
const firstMedia = postMedia?.[0];
|
|
42
|
+
const enclosure = firstMedia
|
|
43
|
+
? `\n <enclosure url="${getMediaUrl(firstMedia.id, firstMedia.r2Key, r2PublicUrl)}" length="${firstMedia.size}" type="${firstMedia.mimeType}"/>`
|
|
44
|
+
: "";
|
|
45
|
+
|
|
33
46
|
return `
|
|
34
47
|
<item>
|
|
35
48
|
<title><![CDATA[${escapeXml(title)}]]></title>
|
|
36
49
|
<link>${link}</link>
|
|
37
50
|
<guid isPermaLink="true">${link}</guid>
|
|
38
51
|
<pubDate>${pubDate}</pubDate>
|
|
39
|
-
<description><![CDATA[${post.contentHtml || ""}]]></description
|
|
52
|
+
<description><![CDATA[${post.contentHtml || ""}]]></description>${enclosure}
|
|
40
53
|
</item>`;
|
|
41
54
|
})
|
|
42
55
|
.join("");
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getSiteName } from "../../lib/config.js";
|
|
2
1
|
/**
|
|
3
2
|
* Archive Page Route
|
|
4
3
|
*
|
|
@@ -9,11 +8,12 @@ import { Hono } from "hono";
|
|
|
9
8
|
import { useLingui } from "@lingui/react/macro";
|
|
10
9
|
import type { Bindings, Post, PostType } from "../../types.js";
|
|
11
10
|
import type { AppVariables } from "../../app.js";
|
|
12
|
-
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
11
|
+
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
13
12
|
import { Pagination } from "../../theme/components/index.js";
|
|
14
13
|
import { POST_TYPES } from "../../types.js";
|
|
15
14
|
import * as sqid from "../../lib/sqid.js";
|
|
16
15
|
import * as time from "../../lib/time.js";
|
|
16
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
17
17
|
|
|
18
18
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
19
19
|
|
|
@@ -102,7 +102,7 @@ function ArchiveContent({
|
|
|
102
102
|
: t({ message: "Archive", comment: "@context: Archive page title" });
|
|
103
103
|
|
|
104
104
|
return (
|
|
105
|
-
<div
|
|
105
|
+
<div>
|
|
106
106
|
<header class="mb-8">
|
|
107
107
|
<h1 class="text-2xl font-semibold">{title}</h1>
|
|
108
108
|
|
|
@@ -202,16 +202,6 @@ function ArchiveContent({
|
|
|
202
202
|
hasMore={hasMore}
|
|
203
203
|
nextCursor={nextCursor}
|
|
204
204
|
/>
|
|
205
|
-
|
|
206
|
-
<nav class="mt-4">
|
|
207
|
-
<a href="/" class="text-sm hover:underline">
|
|
208
|
-
←{" "}
|
|
209
|
-
{t({
|
|
210
|
-
message: "Back to home",
|
|
211
|
-
comment: "@context: Navigation link back to home page",
|
|
212
|
-
})}
|
|
213
|
-
</a>
|
|
214
|
-
</nav>
|
|
215
205
|
</div>
|
|
216
206
|
);
|
|
217
207
|
}
|
|
@@ -226,7 +216,7 @@ archiveRoutes.get("/", async (c) => {
|
|
|
226
216
|
const cursorParam = c.req.query("cursor");
|
|
227
217
|
const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
|
|
228
218
|
|
|
229
|
-
const
|
|
219
|
+
const navData = await getNavigationData(c);
|
|
230
220
|
|
|
231
221
|
// Fetch one extra to check for more
|
|
232
222
|
const posts = await c.var.services.posts.list({
|
|
@@ -264,15 +254,17 @@ archiveRoutes.get("/", async (c) => {
|
|
|
264
254
|
}
|
|
265
255
|
|
|
266
256
|
return c.html(
|
|
267
|
-
<BaseLayout title={`Archive - ${siteName}`} c={c}>
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
257
|
+
<BaseLayout title={`Archive - ${navData.siteName}`} c={c}>
|
|
258
|
+
<SiteLayout {...navData}>
|
|
259
|
+
<ArchiveContent
|
|
260
|
+
displayPosts={displayPosts}
|
|
261
|
+
hasMore={hasMore}
|
|
262
|
+
nextCursor={nextCursor}
|
|
263
|
+
type={type}
|
|
264
|
+
grouped={grouped}
|
|
265
|
+
replyCounts={replyCounts}
|
|
266
|
+
/>
|
|
267
|
+
</SiteLayout>
|
|
276
268
|
</BaseLayout>,
|
|
277
269
|
);
|
|
278
270
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getSiteName } from "../../lib/config.js";
|
|
2
1
|
/**
|
|
3
2
|
* Collection Page Route
|
|
4
3
|
*/
|
|
@@ -7,9 +6,10 @@ import { Hono } from "hono";
|
|
|
7
6
|
import { useLingui } from "@lingui/react/macro";
|
|
8
7
|
import type { Bindings, Collection, Post } from "../../types.js";
|
|
9
8
|
import type { AppVariables } from "../../app.js";
|
|
10
|
-
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
9
|
+
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
11
10
|
import * as sqid from "../../lib/sqid.js";
|
|
12
11
|
import * as time from "../../lib/time.js";
|
|
12
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
13
13
|
|
|
14
14
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ function CollectionContent({
|
|
|
25
25
|
const { t } = useLingui();
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
|
-
<div
|
|
28
|
+
<div>
|
|
29
29
|
<header class="mb-8">
|
|
30
30
|
<h1 class="text-2xl font-semibold">{collection.title}</h1>
|
|
31
31
|
{collection.description && (
|
|
@@ -70,15 +70,6 @@ function CollectionContent({
|
|
|
70
70
|
))
|
|
71
71
|
)}
|
|
72
72
|
</main>
|
|
73
|
-
|
|
74
|
-
<nav class="mt-8">
|
|
75
|
-
<a href="/" class="text-sm hover:underline">
|
|
76
|
-
{t({
|
|
77
|
-
message: "← Back to home",
|
|
78
|
-
comment: "@context: Navigation link",
|
|
79
|
-
})}
|
|
80
|
-
</a>
|
|
81
|
-
</nav>
|
|
82
73
|
</div>
|
|
83
74
|
);
|
|
84
75
|
}
|
|
@@ -90,15 +81,17 @@ collectionRoutes.get("/:path", async (c) => {
|
|
|
90
81
|
if (!collection) return c.notFound();
|
|
91
82
|
|
|
92
83
|
const posts = await c.var.services.collections.getPosts(collection.id);
|
|
93
|
-
const
|
|
84
|
+
const navData = await getNavigationData(c);
|
|
94
85
|
|
|
95
86
|
return c.html(
|
|
96
87
|
<BaseLayout
|
|
97
|
-
title={`${collection.title} - ${siteName}`}
|
|
88
|
+
title={`${collection.title} - ${navData.siteName}`}
|
|
98
89
|
description={collection.description ?? undefined}
|
|
99
90
|
c={c}
|
|
100
91
|
>
|
|
101
|
-
<
|
|
92
|
+
<SiteLayout {...navData}>
|
|
93
|
+
<CollectionContent collection={collection} posts={posts} />
|
|
94
|
+
</SiteLayout>
|
|
102
95
|
</BaseLayout>,
|
|
103
96
|
);
|
|
104
97
|
});
|
|
@@ -1,117 +1,150 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Home Page Route
|
|
3
|
+
*
|
|
4
|
+
* Timeline feed with per-type card components and thread previews.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import { Hono } from "hono";
|
|
6
8
|
import { useLingui } from "@lingui/react/macro";
|
|
7
|
-
import type {
|
|
9
|
+
import type { FC } from "hono/jsx";
|
|
10
|
+
import type {
|
|
11
|
+
Bindings,
|
|
12
|
+
PostWithMedia,
|
|
13
|
+
TimelineItemData,
|
|
14
|
+
TimelineFeedProps,
|
|
15
|
+
} from "../../types.js";
|
|
8
16
|
import type { AppVariables } from "../../app.js";
|
|
9
|
-
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
17
|
+
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
18
|
+
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
19
|
+
import { resolveTimelineFeed } from "../../lib/theme-components.js";
|
|
20
|
+
import { TimelineFeed as DefaultTimelineFeed } from "../../theme/components/timeline/TimelineFeed.js";
|
|
21
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
13
22
|
|
|
14
23
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
15
24
|
|
|
25
|
+
const PAGE_SIZE = 20;
|
|
26
|
+
|
|
16
27
|
export const homeRoutes = new Hono<Env>();
|
|
17
28
|
|
|
18
|
-
function HomeContent({
|
|
29
|
+
function HomeContent({
|
|
30
|
+
FeedComponent,
|
|
31
|
+
feedProps,
|
|
32
|
+
}: {
|
|
33
|
+
FeedComponent: FC<TimelineFeedProps>;
|
|
34
|
+
feedProps: TimelineFeedProps;
|
|
35
|
+
}) {
|
|
19
36
|
const { t } = useLingui();
|
|
20
37
|
|
|
21
38
|
return (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
comment: "@context: Navigation link to archive page",
|
|
33
|
-
})}
|
|
34
|
-
</a>
|
|
35
|
-
<a href="/feed" class="text-muted-foreground hover:text-foreground">
|
|
36
|
-
RSS
|
|
37
|
-
</a>
|
|
38
|
-
</nav>
|
|
39
|
-
</header>
|
|
40
|
-
|
|
41
|
-
<main class="flex flex-col gap-6">
|
|
42
|
-
{posts.length === 0 ? (
|
|
43
|
-
<p class="text-muted-foreground">
|
|
44
|
-
{t({
|
|
45
|
-
message: "No posts yet.",
|
|
46
|
-
comment: "@context: Empty state message on home page",
|
|
47
|
-
})}
|
|
48
|
-
</p>
|
|
49
|
-
) : (
|
|
50
|
-
posts.map((post) => (
|
|
51
|
-
<article key={post.id} class="h-entry">
|
|
52
|
-
{post.title && (
|
|
53
|
-
<h2 class="p-name text-lg font-medium mb-2">
|
|
54
|
-
<a
|
|
55
|
-
href={`/p/${sqid.encode(post.id)}`}
|
|
56
|
-
class="u-url hover:underline"
|
|
57
|
-
>
|
|
58
|
-
{post.title}
|
|
59
|
-
</a>
|
|
60
|
-
</h2>
|
|
61
|
-
)}
|
|
62
|
-
<div
|
|
63
|
-
class="e-content prose prose-sm"
|
|
64
|
-
dangerouslySetInnerHTML={{ __html: post.contentHtml || "" }}
|
|
65
|
-
/>
|
|
66
|
-
<footer class="mt-2 text-sm text-muted-foreground">
|
|
67
|
-
<time
|
|
68
|
-
class="dt-published"
|
|
69
|
-
datetime={time.toISOString(post.publishedAt)}
|
|
70
|
-
>
|
|
71
|
-
{time.formatDate(post.publishedAt)}
|
|
72
|
-
</time>
|
|
73
|
-
{post.visibility === "featured" && (
|
|
74
|
-
<span class="ml-2 text-xs">
|
|
75
|
-
{t({
|
|
76
|
-
message: "Featured",
|
|
77
|
-
comment: "@context: Post visibility badge",
|
|
78
|
-
})}
|
|
79
|
-
</span>
|
|
80
|
-
)}
|
|
81
|
-
</footer>
|
|
82
|
-
</article>
|
|
83
|
-
))
|
|
84
|
-
)}
|
|
85
|
-
</main>
|
|
86
|
-
|
|
87
|
-
{posts.length >= 20 && (
|
|
88
|
-
<nav class="mt-8 text-center">
|
|
89
|
-
<a
|
|
90
|
-
href="/archive"
|
|
91
|
-
class="text-sm text-muted-foreground hover:text-foreground"
|
|
92
|
-
>
|
|
93
|
-
{t({
|
|
94
|
-
message: "View all posts →",
|
|
95
|
-
comment: "@context: Link to view all posts on archive page",
|
|
96
|
-
})}
|
|
97
|
-
</a>
|
|
98
|
-
</nav>
|
|
39
|
+
<>
|
|
40
|
+
{feedProps.items.length === 0 ? (
|
|
41
|
+
<p class="text-muted-foreground">
|
|
42
|
+
{t({
|
|
43
|
+
message: "No posts yet.",
|
|
44
|
+
comment: "@context: Empty state message on home page",
|
|
45
|
+
})}
|
|
46
|
+
</p>
|
|
47
|
+
) : (
|
|
48
|
+
<FeedComponent {...feedProps} />
|
|
99
49
|
)}
|
|
100
|
-
|
|
50
|
+
</>
|
|
101
51
|
);
|
|
102
52
|
}
|
|
103
53
|
|
|
104
54
|
homeRoutes.get("/", async (c) => {
|
|
105
|
-
const
|
|
55
|
+
const navData = await getNavigationData(c);
|
|
106
56
|
|
|
57
|
+
// Fetch one extra to determine if there are more
|
|
107
58
|
const posts = await c.var.services.posts.list({
|
|
108
59
|
visibility: ["featured", "quiet"],
|
|
109
|
-
|
|
60
|
+
excludeReplies: true,
|
|
61
|
+
excludeTypes: ["page"],
|
|
62
|
+
limit: PAGE_SIZE + 1,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const hasMore = posts.length > PAGE_SIZE;
|
|
66
|
+
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
67
|
+
|
|
68
|
+
// Batch load media attachments
|
|
69
|
+
const postIds = displayPosts.map((p) => p.id);
|
|
70
|
+
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
71
|
+
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
72
|
+
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
73
|
+
const mediaMap = buildMediaMap(rawMediaMap, r2PublicUrl, imageTransformUrl);
|
|
74
|
+
|
|
75
|
+
// Get reply counts to identify thread roots
|
|
76
|
+
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
77
|
+
const threadRootIds = postIds.filter((id) => (replyCounts.get(id) ?? 0) > 0);
|
|
78
|
+
|
|
79
|
+
// Batch load thread previews
|
|
80
|
+
const threadPreviews = await c.var.services.posts.getThreadPreviews(
|
|
81
|
+
threadRootIds,
|
|
82
|
+
3,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Batch load media for preview replies
|
|
86
|
+
const previewReplyIds: number[] = [];
|
|
87
|
+
for (const replies of threadPreviews.values()) {
|
|
88
|
+
for (const reply of replies) {
|
|
89
|
+
previewReplyIds.push(reply.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const previewMediaMap =
|
|
93
|
+
previewReplyIds.length > 0
|
|
94
|
+
? buildMediaMap(
|
|
95
|
+
await c.var.services.media.getByPostIds(previewReplyIds),
|
|
96
|
+
r2PublicUrl,
|
|
97
|
+
imageTransformUrl,
|
|
98
|
+
)
|
|
99
|
+
: new Map();
|
|
100
|
+
|
|
101
|
+
// Assemble timeline items
|
|
102
|
+
const items: TimelineItemData[] = displayPosts.map((post) => {
|
|
103
|
+
const postWithMedia: PostWithMedia = {
|
|
104
|
+
...post,
|
|
105
|
+
mediaAttachments: mediaMap.get(post.id) ?? [],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const replyCount = replyCounts.get(post.id) ?? 0;
|
|
109
|
+
const previewReplies = threadPreviews.get(post.id);
|
|
110
|
+
|
|
111
|
+
if (replyCount > 0 && previewReplies) {
|
|
112
|
+
return {
|
|
113
|
+
post: postWithMedia,
|
|
114
|
+
threadPreview: {
|
|
115
|
+
replies: previewReplies.map((r) => ({
|
|
116
|
+
...r,
|
|
117
|
+
mediaAttachments: previewMediaMap.get(r.id) ?? [],
|
|
118
|
+
})),
|
|
119
|
+
totalReplyCount: replyCount,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { post: postWithMedia };
|
|
110
125
|
});
|
|
111
126
|
|
|
127
|
+
// Determine next cursor
|
|
128
|
+
const lastPost = displayPosts[displayPosts.length - 1];
|
|
129
|
+
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
130
|
+
|
|
131
|
+
// Resolve theme components
|
|
132
|
+
const Feed = resolveTimelineFeed(
|
|
133
|
+
DefaultTimelineFeed,
|
|
134
|
+
c.var.config.theme?.components,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const feedProps: TimelineFeedProps = {
|
|
138
|
+
items,
|
|
139
|
+
hasMore,
|
|
140
|
+
nextCursor,
|
|
141
|
+
};
|
|
142
|
+
|
|
112
143
|
return c.html(
|
|
113
|
-
<BaseLayout title={siteName} c={c}>
|
|
114
|
-
<
|
|
144
|
+
<BaseLayout title={navData.siteName} c={c}>
|
|
145
|
+
<SiteLayout {...navData}>
|
|
146
|
+
<HomeContent FeedComponent={Feed} feedProps={feedProps} />
|
|
147
|
+
</SiteLayout>
|
|
115
148
|
</BaseLayout>,
|
|
116
149
|
);
|
|
117
150
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getSiteName } from "../../lib/config.js";
|
|
2
1
|
/**
|
|
3
2
|
* Custom Page Route
|
|
4
3
|
*
|
|
@@ -6,41 +5,27 @@ import { getSiteName } from "../../lib/config.js";
|
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import { Hono } from "hono";
|
|
9
|
-
import { useLingui } from "@lingui/react/macro";
|
|
10
8
|
import type { Bindings, Post } from "../../types.js";
|
|
11
9
|
import type { AppVariables } from "../../app.js";
|
|
12
|
-
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
10
|
+
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
11
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
13
12
|
|
|
14
13
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
15
14
|
|
|
16
15
|
export const pageRoutes = new Hono<Env>();
|
|
17
16
|
|
|
18
17
|
function PageContent({ page }: { page: Post }) {
|
|
19
|
-
const { t } = useLingui();
|
|
20
|
-
|
|
21
18
|
return (
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
{page.title
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
</article>
|
|
33
|
-
|
|
34
|
-
<nav class="mt-8 pt-6 border-t">
|
|
35
|
-
<a href="/" class="text-sm hover:underline">
|
|
36
|
-
←{" "}
|
|
37
|
-
{t({
|
|
38
|
-
message: "Back to home",
|
|
39
|
-
comment: "@context: Navigation link back to home page",
|
|
40
|
-
})}
|
|
41
|
-
</a>
|
|
42
|
-
</nav>
|
|
43
|
-
</div>
|
|
19
|
+
<article class="h-entry">
|
|
20
|
+
{page.title && (
|
|
21
|
+
<h1 class="p-name text-3xl font-semibold mb-6">{page.title}</h1>
|
|
22
|
+
)}
|
|
23
|
+
|
|
24
|
+
<div
|
|
25
|
+
class="e-content prose"
|
|
26
|
+
dangerouslySetInnerHTML={{ __html: page.contentHtml || "" }}
|
|
27
|
+
/>
|
|
28
|
+
</article>
|
|
44
29
|
);
|
|
45
30
|
}
|
|
46
31
|
|
|
@@ -61,15 +46,17 @@ pageRoutes.get("/:path", async (c) => {
|
|
|
61
46
|
return c.notFound();
|
|
62
47
|
}
|
|
63
48
|
|
|
64
|
-
const
|
|
49
|
+
const navData = await getNavigationData(c);
|
|
65
50
|
|
|
66
51
|
return c.html(
|
|
67
52
|
<BaseLayout
|
|
68
|
-
title={`${page.title} - ${siteName}`}
|
|
53
|
+
title={`${page.title} - ${navData.siteName}`}
|
|
69
54
|
description={page.content?.slice(0, 160)}
|
|
70
55
|
c={c}
|
|
71
56
|
>
|
|
72
|
-
<
|
|
57
|
+
<SiteLayout {...navData}>
|
|
58
|
+
<PageContent page={page} />
|
|
59
|
+
</SiteLayout>
|
|
73
60
|
</BaseLayout>,
|
|
74
61
|
);
|
|
75
62
|
});
|