@jant/core 0.3.7 → 0.3.9
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 -4
- package/dist/client.js +1 -0
- package/dist/db/schema.js +15 -1
- 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/lib/image.js +39 -15
- package/dist/lib/media-helpers.js +49 -0
- package/dist/lib/nav-reorder.js +27 -0
- package/dist/lib/navigation.js +35 -0
- package/dist/lib/storage.js +164 -0
- package/dist/lib/theme-components.js +49 -0
- package/dist/routes/api/posts.js +12 -7
- package/dist/routes/api/timeline.js +116 -0
- package/dist/routes/api/upload.js +35 -24
- package/dist/routes/dash/media.js +24 -14
- package/dist/routes/dash/navigation.js +274 -0
- package/dist/routes/dash/posts.js +4 -1
- package/dist/routes/feed/rss.js +3 -2
- package/dist/routes/pages/archive.js +14 -27
- package/dist/routes/pages/collection.js +10 -19
- package/dist/routes/pages/home.js +84 -126
- package/dist/routes/pages/page.js +19 -38
- package/dist/routes/pages/post.js +47 -56
- package/dist/routes/pages/search.js +13 -26
- package/dist/services/index.js +3 -1
- package/dist/services/media.js +8 -6
- package/dist/services/navigation.js +115 -0
- package/dist/services/post.js +26 -1
- package/dist/theme/components/PostForm.js +4 -3
- package/dist/theme/components/PostList.js +5 -0
- package/dist/theme/components/index.js +2 -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 +32 -0
- package/package.json +4 -2
- package/src/__tests__/helpers/app.ts +1 -0
- package/src/__tests__/helpers/db.ts +20 -0
- package/src/app.tsx +12 -7
- package/src/client.ts +1 -0
- package/src/db/migrations/0003_add_navigation_links.sql +8 -0
- package/src/db/migrations/0004_add_storage_provider.sql +3 -0
- package/src/db/migrations/meta/0003_snapshot.json +821 -0
- package/src/db/migrations/meta/_journal.json +21 -0
- package/src/db/schema.ts +15 -1
- package/src/i18n/locales/en.po +148 -80
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +150 -103
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +150 -103
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +5 -0
- package/src/lib/__tests__/image.test.ts +96 -0
- package/src/lib/__tests__/storage.test.ts +162 -0
- package/src/lib/__tests__/theme-components.test.ts +107 -0
- package/src/lib/image.ts +46 -16
- package/src/lib/media-helpers.ts +65 -0
- package/src/lib/nav-reorder.ts +26 -0
- package/src/lib/navigation.ts +46 -0
- package/src/lib/storage.ts +236 -0
- package/src/lib/theme-components.ts +76 -0
- package/src/routes/api/__tests__/posts.test.ts +8 -8
- package/src/routes/api/__tests__/timeline.test.ts +242 -0
- package/src/routes/api/posts.ts +20 -6
- package/src/routes/api/timeline.tsx +152 -0
- package/src/routes/api/upload.ts +52 -25
- package/src/routes/dash/media.tsx +40 -8
- package/src/routes/dash/navigation.tsx +306 -0
- package/src/routes/dash/posts.tsx +5 -0
- package/src/routes/feed/rss.ts +3 -2
- package/src/routes/pages/archive.tsx +15 -23
- package/src/routes/pages/collection.tsx +8 -15
- package/src/routes/pages/home.tsx +118 -122
- package/src/routes/pages/page.tsx +17 -30
- package/src/routes/pages/post.tsx +63 -60
- package/src/routes/pages/search.tsx +18 -22
- package/src/services/__tests__/media.test.ts +73 -28
- package/src/services/__tests__/navigation.test.ts +213 -0
- package/src/services/__tests__/post-timeline.test.ts +220 -0
- package/src/services/index.ts +7 -0
- package/src/services/media.ts +12 -8
- 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/PostForm.tsx +13 -2
- package/src/theme/components/PostList.tsx +7 -0
- package/src/theme/components/index.ts +12 -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 +102 -1
- 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 -1543
- 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 -130
- 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 -32
- 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 -34
- 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/MediaGallery.d.ts +0 -13
- package/dist/theme/components/MediaGallery.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 -16
- 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 -14
- 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 -237
- package/dist/types.d.ts.map +0 -1
|
@@ -1,161 +1,157 @@
|
|
|
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
|
|
13
|
-
import {
|
|
14
|
-
import { getMediaUrl, getImageUrl } from "../../lib/image.js";
|
|
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";
|
|
15
22
|
|
|
16
23
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
17
24
|
|
|
25
|
+
const PAGE_SIZE = 20;
|
|
26
|
+
|
|
18
27
|
export const homeRoutes = new Hono<Env>();
|
|
19
28
|
|
|
20
29
|
function HomeContent({
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
mediaMap,
|
|
30
|
+
FeedComponent,
|
|
31
|
+
feedProps,
|
|
24
32
|
}: {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
mediaMap: Map<number, MediaAttachment[]>;
|
|
33
|
+
FeedComponent: FC<TimelineFeedProps>;
|
|
34
|
+
feedProps: TimelineFeedProps;
|
|
28
35
|
}) {
|
|
29
36
|
const { t } = useLingui();
|
|
30
37
|
|
|
31
38
|
return (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
comment: "@context: Navigation link to archive page",
|
|
43
|
-
})}
|
|
44
|
-
</a>
|
|
45
|
-
<a href="/feed" class="text-muted-foreground hover:text-foreground">
|
|
46
|
-
RSS
|
|
47
|
-
</a>
|
|
48
|
-
</nav>
|
|
49
|
-
</header>
|
|
50
|
-
|
|
51
|
-
<main class="flex flex-col gap-6">
|
|
52
|
-
{posts.length === 0 ? (
|
|
53
|
-
<p class="text-muted-foreground">
|
|
54
|
-
{t({
|
|
55
|
-
message: "No posts yet.",
|
|
56
|
-
comment: "@context: Empty state message on home page",
|
|
57
|
-
})}
|
|
58
|
-
</p>
|
|
59
|
-
) : (
|
|
60
|
-
posts.map((post) => {
|
|
61
|
-
const attachments = mediaMap.get(post.id) ?? [];
|
|
62
|
-
return (
|
|
63
|
-
<article key={post.id} class="h-entry">
|
|
64
|
-
{post.title && (
|
|
65
|
-
<h2 class="p-name text-lg font-medium mb-2">
|
|
66
|
-
<a
|
|
67
|
-
href={`/p/${sqid.encode(post.id)}`}
|
|
68
|
-
class="u-url hover:underline"
|
|
69
|
-
>
|
|
70
|
-
{post.title}
|
|
71
|
-
</a>
|
|
72
|
-
</h2>
|
|
73
|
-
)}
|
|
74
|
-
<div
|
|
75
|
-
class="e-content prose prose-sm"
|
|
76
|
-
dangerouslySetInnerHTML={{ __html: post.contentHtml || "" }}
|
|
77
|
-
/>
|
|
78
|
-
{attachments.length > 0 && (
|
|
79
|
-
<MediaGallery attachments={attachments} />
|
|
80
|
-
)}
|
|
81
|
-
<footer class="mt-2 text-sm text-muted-foreground">
|
|
82
|
-
<time
|
|
83
|
-
class="dt-published"
|
|
84
|
-
datetime={time.toISOString(post.publishedAt)}
|
|
85
|
-
>
|
|
86
|
-
{time.formatDate(post.publishedAt)}
|
|
87
|
-
</time>
|
|
88
|
-
{post.visibility === "featured" && (
|
|
89
|
-
<span class="ml-2 text-xs">
|
|
90
|
-
{t({
|
|
91
|
-
message: "Featured",
|
|
92
|
-
comment: "@context: Post visibility badge",
|
|
93
|
-
})}
|
|
94
|
-
</span>
|
|
95
|
-
)}
|
|
96
|
-
</footer>
|
|
97
|
-
</article>
|
|
98
|
-
);
|
|
99
|
-
})
|
|
100
|
-
)}
|
|
101
|
-
</main>
|
|
102
|
-
|
|
103
|
-
{posts.length >= 20 && (
|
|
104
|
-
<nav class="mt-8 text-center">
|
|
105
|
-
<a
|
|
106
|
-
href="/archive"
|
|
107
|
-
class="text-sm text-muted-foreground hover:text-foreground"
|
|
108
|
-
>
|
|
109
|
-
{t({
|
|
110
|
-
message: "View all posts →",
|
|
111
|
-
comment: "@context: Link to view all posts on archive page",
|
|
112
|
-
})}
|
|
113
|
-
</a>
|
|
114
|
-
</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} />
|
|
115
49
|
)}
|
|
116
|
-
|
|
50
|
+
</>
|
|
117
51
|
);
|
|
118
52
|
}
|
|
119
53
|
|
|
120
54
|
homeRoutes.get("/", async (c) => {
|
|
121
|
-
const
|
|
55
|
+
const navData = await getNavigationData(c);
|
|
122
56
|
|
|
57
|
+
// Fetch one extra to determine if there are more
|
|
123
58
|
const posts = await c.var.services.posts.list({
|
|
124
59
|
visibility: ["featured", "quiet"],
|
|
125
|
-
|
|
60
|
+
excludeReplies: true,
|
|
61
|
+
excludeTypes: ["page"],
|
|
62
|
+
limit: PAGE_SIZE + 1,
|
|
126
63
|
});
|
|
127
64
|
|
|
65
|
+
const hasMore = posts.length > PAGE_SIZE;
|
|
66
|
+
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
67
|
+
|
|
128
68
|
// Batch load media attachments
|
|
129
|
-
const postIds =
|
|
69
|
+
const postIds = displayPosts.map((p) => p.id);
|
|
130
70
|
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
131
71
|
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
132
72
|
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
73
|
+
const s3PublicUrl = c.env.S3_PUBLIC_URL;
|
|
74
|
+
const mediaMap = buildMediaMap(
|
|
75
|
+
rawMediaMap,
|
|
76
|
+
r2PublicUrl,
|
|
77
|
+
imageTransformUrl,
|
|
78
|
+
s3PublicUrl,
|
|
79
|
+
);
|
|
133
80
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
position: m.position,
|
|
151
|
-
mimeType: m.mimeType,
|
|
152
|
-
})),
|
|
153
|
-
);
|
|
81
|
+
// Get reply counts to identify thread roots
|
|
82
|
+
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
83
|
+
const threadRootIds = postIds.filter((id) => (replyCounts.get(id) ?? 0) > 0);
|
|
84
|
+
|
|
85
|
+
// Batch load thread previews
|
|
86
|
+
const threadPreviews = await c.var.services.posts.getThreadPreviews(
|
|
87
|
+
threadRootIds,
|
|
88
|
+
3,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Batch load media for preview replies
|
|
92
|
+
const previewReplyIds: number[] = [];
|
|
93
|
+
for (const replies of threadPreviews.values()) {
|
|
94
|
+
for (const reply of replies) {
|
|
95
|
+
previewReplyIds.push(reply.id);
|
|
96
|
+
}
|
|
154
97
|
}
|
|
98
|
+
const previewMediaMap =
|
|
99
|
+
previewReplyIds.length > 0
|
|
100
|
+
? buildMediaMap(
|
|
101
|
+
await c.var.services.media.getByPostIds(previewReplyIds),
|
|
102
|
+
r2PublicUrl,
|
|
103
|
+
imageTransformUrl,
|
|
104
|
+
s3PublicUrl,
|
|
105
|
+
)
|
|
106
|
+
: new Map();
|
|
107
|
+
|
|
108
|
+
// Assemble timeline items
|
|
109
|
+
const items: TimelineItemData[] = displayPosts.map((post) => {
|
|
110
|
+
const postWithMedia: PostWithMedia = {
|
|
111
|
+
...post,
|
|
112
|
+
mediaAttachments: mediaMap.get(post.id) ?? [],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const replyCount = replyCounts.get(post.id) ?? 0;
|
|
116
|
+
const previewReplies = threadPreviews.get(post.id);
|
|
117
|
+
|
|
118
|
+
if (replyCount > 0 && previewReplies) {
|
|
119
|
+
return {
|
|
120
|
+
post: postWithMedia,
|
|
121
|
+
threadPreview: {
|
|
122
|
+
replies: previewReplies.map((r) => ({
|
|
123
|
+
...r,
|
|
124
|
+
mediaAttachments: previewMediaMap.get(r.id) ?? [],
|
|
125
|
+
})),
|
|
126
|
+
totalReplyCount: replyCount,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { post: postWithMedia };
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Determine next cursor
|
|
135
|
+
const lastPost = displayPosts[displayPosts.length - 1];
|
|
136
|
+
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
137
|
+
|
|
138
|
+
// Resolve theme components
|
|
139
|
+
const Feed = resolveTimelineFeed(
|
|
140
|
+
DefaultTimelineFeed,
|
|
141
|
+
c.var.config.theme?.components,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const feedProps: TimelineFeedProps = {
|
|
145
|
+
items,
|
|
146
|
+
hasMore,
|
|
147
|
+
nextCursor,
|
|
148
|
+
};
|
|
155
149
|
|
|
156
150
|
return c.html(
|
|
157
|
-
<BaseLayout title={siteName} c={c}>
|
|
158
|
-
<
|
|
151
|
+
<BaseLayout title={navData.siteName} c={c}>
|
|
152
|
+
<SiteLayout {...navData}>
|
|
153
|
+
<HomeContent FeedComponent={Feed} feedProps={feedProps} />
|
|
154
|
+
</SiteLayout>
|
|
159
155
|
</BaseLayout>,
|
|
160
156
|
);
|
|
161
157
|
});
|
|
@@ -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
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getSiteName } from "../../lib/config.js";
|
|
2
1
|
/**
|
|
3
2
|
* Single Post Page Route
|
|
4
3
|
*/
|
|
@@ -7,11 +6,16 @@ import { Hono } from "hono";
|
|
|
7
6
|
import { useLingui } from "@lingui/react/macro";
|
|
8
7
|
import type { Bindings, Post, MediaAttachment } 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 { MediaGallery } from "../../theme/components/index.js";
|
|
12
11
|
import * as sqid from "../../lib/sqid.js";
|
|
13
12
|
import * as time from "../../lib/time.js";
|
|
14
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getMediaUrl,
|
|
15
|
+
getImageUrl,
|
|
16
|
+
getPublicUrlForProvider,
|
|
17
|
+
} from "../../lib/image.js";
|
|
18
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
15
19
|
|
|
16
20
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
17
21
|
|
|
@@ -27,46 +31,35 @@ function PostContent({
|
|
|
27
31
|
const { t } = useLingui();
|
|
28
32
|
|
|
29
33
|
return (
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
{post.title
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{mediaAttachments
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<a href={`/p/${sqid.encode(post.id)}`} class="u-url ml-4">
|
|
53
|
-
{t({
|
|
54
|
-
message: "Permalink",
|
|
55
|
-
comment: "@context: Link to permanent URL of post",
|
|
56
|
-
})}
|
|
57
|
-
</a>
|
|
58
|
-
</footer>
|
|
59
|
-
</article>
|
|
60
|
-
|
|
61
|
-
<nav class="mt-8">
|
|
62
|
-
<a href="/" class="text-sm hover:underline">
|
|
34
|
+
<article class="h-entry">
|
|
35
|
+
{post.title && (
|
|
36
|
+
<h1 class="p-name text-2xl font-semibold mb-4">{post.title}</h1>
|
|
37
|
+
)}
|
|
38
|
+
|
|
39
|
+
<div
|
|
40
|
+
class="e-content prose"
|
|
41
|
+
dangerouslySetInnerHTML={{ __html: post.contentHtml || "" }}
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
{mediaAttachments.length > 0 && (
|
|
45
|
+
<MediaGallery attachments={mediaAttachments} />
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
<footer class="mt-6 pt-4 border-t text-sm text-muted-foreground">
|
|
49
|
+
<time
|
|
50
|
+
class="dt-published"
|
|
51
|
+
datetime={time.toISOString(post.publishedAt)}
|
|
52
|
+
>
|
|
53
|
+
{time.formatDate(post.publishedAt)}
|
|
54
|
+
</time>
|
|
55
|
+
<a href={`/p/${sqid.encode(post.id)}`} class="u-url ml-4">
|
|
63
56
|
{t({
|
|
64
|
-
message: "
|
|
65
|
-
comment: "@context:
|
|
57
|
+
message: "Permalink",
|
|
58
|
+
comment: "@context: Link to permanent URL of post",
|
|
66
59
|
})}
|
|
67
60
|
</a>
|
|
68
|
-
</
|
|
69
|
-
</
|
|
61
|
+
</footer>
|
|
62
|
+
</article>
|
|
70
63
|
);
|
|
71
64
|
}
|
|
72
65
|
|
|
@@ -98,29 +91,39 @@ postRoutes.get("/:id", async (c) => {
|
|
|
98
91
|
const rawMedia = await c.var.services.media.getByPostId(post.id);
|
|
99
92
|
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
100
93
|
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
94
|
+
const s3PublicUrl = c.env.S3_PUBLIC_URL;
|
|
95
|
+
|
|
96
|
+
const mediaAttachments: MediaAttachment[] = rawMedia.map((m) => {
|
|
97
|
+
const publicUrl = getPublicUrlForProvider(
|
|
98
|
+
m.provider,
|
|
99
|
+
r2PublicUrl,
|
|
100
|
+
s3PublicUrl,
|
|
101
|
+
);
|
|
102
|
+
return {
|
|
103
|
+
id: m.id,
|
|
104
|
+
url: getMediaUrl(m.id, m.storageKey, publicUrl),
|
|
105
|
+
previewUrl: getImageUrl(
|
|
106
|
+
getMediaUrl(m.id, m.storageKey, publicUrl),
|
|
107
|
+
imageTransformUrl,
|
|
108
|
+
{ width: 400, quality: 80, format: "auto", fit: "cover" },
|
|
109
|
+
),
|
|
110
|
+
alt: m.alt,
|
|
111
|
+
blurhash: m.blurhash,
|
|
112
|
+
width: m.width,
|
|
113
|
+
height: m.height,
|
|
114
|
+
position: m.position,
|
|
115
|
+
mimeType: m.mimeType,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const navData = await getNavigationData(c);
|
|
120
|
+
const title = post.title || navData.siteName;
|
|
120
121
|
|
|
121
122
|
return c.html(
|
|
122
123
|
<BaseLayout title={title} description={post.content?.slice(0, 160)} c={c}>
|
|
123
|
-
<
|
|
124
|
+
<SiteLayout {...navData}>
|
|
125
|
+
<PostContent post={post} mediaAttachments={mediaAttachments} />
|
|
126
|
+
</SiteLayout>
|
|
124
127
|
</BaseLayout>,
|
|
125
128
|
);
|
|
126
129
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getSiteName } from "../../lib/config.js";
|
|
2
1
|
/**
|
|
3
2
|
* Search Page Route
|
|
4
3
|
*/
|
|
@@ -8,10 +7,11 @@ import { useLingui } from "@lingui/react/macro";
|
|
|
8
7
|
import type { Bindings } from "../../types.js";
|
|
9
8
|
import type { AppVariables } from "../../app.js";
|
|
10
9
|
import type { SearchResult } from "../../services/search.js";
|
|
11
|
-
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
10
|
+
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
12
11
|
import { PagePagination } from "../../theme/components/index.js";
|
|
13
12
|
import * as sqid from "../../lib/sqid.js";
|
|
14
13
|
import * as time from "../../lib/time.js";
|
|
14
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
15
15
|
|
|
16
16
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
17
17
|
|
|
@@ -39,7 +39,7 @@ function SearchContent({
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
|
-
<div
|
|
42
|
+
<div>
|
|
43
43
|
<h1 class="text-2xl font-semibold mb-6">{searchTitle}</h1>
|
|
44
44
|
|
|
45
45
|
{/* Search form */}
|
|
@@ -137,16 +137,6 @@ function SearchContent({
|
|
|
137
137
|
)}
|
|
138
138
|
</div>
|
|
139
139
|
)}
|
|
140
|
-
|
|
141
|
-
<nav class="mt-8 pt-6 border-t">
|
|
142
|
-
<a href="/" class="text-sm hover:underline">
|
|
143
|
-
←{" "}
|
|
144
|
-
{t({
|
|
145
|
-
message: "Back to home",
|
|
146
|
-
comment: "@context: Navigation link back to home page",
|
|
147
|
-
})}
|
|
148
|
-
</a>
|
|
149
|
-
</nav>
|
|
150
140
|
</div>
|
|
151
141
|
);
|
|
152
142
|
}
|
|
@@ -156,7 +146,7 @@ searchRoutes.get("/", async (c) => {
|
|
|
156
146
|
const pageParam = c.req.query("page");
|
|
157
147
|
const page = pageParam ? Math.max(1, parseInt(pageParam, 10) || 1) : 1;
|
|
158
148
|
|
|
159
|
-
const
|
|
149
|
+
const navData = await getNavigationData(c);
|
|
160
150
|
|
|
161
151
|
// Only search if there's a query
|
|
162
152
|
let results: Awaited<ReturnType<typeof c.var.services.search.search>> = [];
|
|
@@ -185,16 +175,22 @@ searchRoutes.get("/", async (c) => {
|
|
|
185
175
|
|
|
186
176
|
return c.html(
|
|
187
177
|
<BaseLayout
|
|
188
|
-
title={
|
|
178
|
+
title={
|
|
179
|
+
query
|
|
180
|
+
? `Search: ${query} - ${navData.siteName}`
|
|
181
|
+
: `Search - ${navData.siteName}`
|
|
182
|
+
}
|
|
189
183
|
c={c}
|
|
190
184
|
>
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
185
|
+
<SiteLayout {...navData}>
|
|
186
|
+
<SearchContent
|
|
187
|
+
query={query}
|
|
188
|
+
results={results}
|
|
189
|
+
error={error}
|
|
190
|
+
hasMore={hasMore}
|
|
191
|
+
page={page}
|
|
192
|
+
/>
|
|
193
|
+
</SiteLayout>
|
|
198
194
|
</BaseLayout>,
|
|
199
195
|
);
|
|
200
196
|
});
|