@jant/core 0.3.21 → 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 +1 -1
- 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/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/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 +1 -1
- package/src/i18n/locales/en.po +31 -31
- package/src/i18n/locales/zh-Hans.po +31 -31
- package/src/i18n/locales/zh-Hant.po +31 -31
- 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/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/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
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
7
|
import { TimelineItem } from "./TimelineItem.js";
|
|
8
|
-
import
|
|
9
|
-
export const ThreadPreview = ({ rootPost, previewReplies, totalReplyCount })=>{
|
|
8
|
+
import { TimelineItemFromPost } from "./TimelineItem.js";
|
|
9
|
+
export const ThreadPreview = ({ rootPost, previewReplies, totalReplyCount, theme })=>{
|
|
10
10
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
11
|
-
const permalink = `/p/${sqid.encode(rootPost.id)}`;
|
|
12
11
|
const remainingCount = totalReplyCount - previewReplies.length;
|
|
13
12
|
return /*#__PURE__*/ _jsxs("div", {
|
|
14
13
|
class: "timeline-thread",
|
|
@@ -16,24 +15,24 @@ export const ThreadPreview = ({ rootPost, previewReplies, totalReplyCount })=>{
|
|
|
16
15
|
/*#__PURE__*/ _jsx(TimelineItem, {
|
|
17
16
|
item: {
|
|
18
17
|
post: rootPost
|
|
19
|
-
}
|
|
18
|
+
},
|
|
19
|
+
theme: theme
|
|
20
20
|
}),
|
|
21
21
|
previewReplies.length > 0 && /*#__PURE__*/ _jsxs("div", {
|
|
22
22
|
class: "timeline-thread-replies",
|
|
23
23
|
children: [
|
|
24
24
|
previewReplies.map((reply)=>/*#__PURE__*/ _jsx("div", {
|
|
25
25
|
class: "timeline-thread-reply",
|
|
26
|
-
children: /*#__PURE__*/ _jsx(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
compact: true
|
|
26
|
+
children: /*#__PURE__*/ _jsx(TimelineItemFromPost, {
|
|
27
|
+
post: reply,
|
|
28
|
+
compact: true,
|
|
29
|
+
theme: theme
|
|
31
30
|
})
|
|
32
31
|
}, reply.id)),
|
|
33
32
|
remainingCount > 0 && /*#__PURE__*/ _jsx("div", {
|
|
34
33
|
class: "timeline-thread-reply",
|
|
35
34
|
children: /*#__PURE__*/ _jsx("a", {
|
|
36
|
-
href: permalink,
|
|
35
|
+
href: rootPost.permalink,
|
|
37
36
|
class: "text-sm text-muted-foreground hover:text-foreground hover:underline",
|
|
38
37
|
children: $__i18n._({
|
|
39
38
|
id: "smzF8S",
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
6
6
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
7
|
import { TimelineItem } from "./TimelineItem.js";
|
|
8
|
-
import { ThreadPreview } from "./ThreadPreview.js";
|
|
9
|
-
export const TimelineFeed = ({ items, hasMore, nextCursor })=>{
|
|
8
|
+
import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
|
|
9
|
+
export const TimelineFeed = ({ items, hasMore, nextCursor, theme })=>{
|
|
10
10
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
11
|
+
const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
|
|
11
12
|
return /*#__PURE__*/ _jsxs("div", {
|
|
12
13
|
children: [
|
|
13
14
|
/*#__PURE__*/ _jsx("div", {
|
|
@@ -15,14 +16,16 @@ export const TimelineFeed = ({ items, hasMore, nextCursor })=>{
|
|
|
15
16
|
class: "flex flex-col gap-4",
|
|
16
17
|
children: items.map((item)=>{
|
|
17
18
|
if (item.threadPreview) {
|
|
18
|
-
return /*#__PURE__*/ _jsx(
|
|
19
|
+
return /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
|
|
19
20
|
rootPost: item.post,
|
|
20
21
|
previewReplies: item.threadPreview.replies,
|
|
21
|
-
totalReplyCount: item.threadPreview.totalReplyCount
|
|
22
|
+
totalReplyCount: item.threadPreview.totalReplyCount,
|
|
23
|
+
theme: theme
|
|
22
24
|
}, item.post.id);
|
|
23
25
|
}
|
|
24
26
|
return /*#__PURE__*/ _jsx(TimelineItem, {
|
|
25
|
-
item: item
|
|
27
|
+
item: item,
|
|
28
|
+
theme: theme
|
|
26
29
|
}, item.post.id);
|
|
27
30
|
})
|
|
28
31
|
}),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Timeline Item Component
|
|
3
3
|
*
|
|
4
4
|
* Dispatches to the correct card component based on post type.
|
|
5
|
+
* Resolves card overrides from theme components if provided.
|
|
5
6
|
*/ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
6
7
|
import { NoteCard } from "./NoteCard.js";
|
|
7
8
|
import { ArticleCard } from "./ArticleCard.js";
|
|
@@ -16,10 +17,29 @@ const CARD_MAP = {
|
|
|
16
17
|
image: ImageCard,
|
|
17
18
|
page: NoteCard
|
|
18
19
|
};
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const THEME_KEY_MAP = {
|
|
21
|
+
note: "NoteCard",
|
|
22
|
+
article: "ArticleCard",
|
|
23
|
+
link: "LinkCard",
|
|
24
|
+
quote: "QuoteCard",
|
|
25
|
+
image: "ImageCard",
|
|
26
|
+
page: "NoteCard"
|
|
27
|
+
};
|
|
28
|
+
export const TimelineItem = ({ item, compact, cardOverride, theme })=>{
|
|
29
|
+
const themeKey = THEME_KEY_MAP[item.post.type];
|
|
30
|
+
const themeCard = theme?.[themeKey];
|
|
31
|
+
const Card = cardOverride ?? themeCard ?? CARD_MAP[item.post.type];
|
|
21
32
|
return /*#__PURE__*/ _jsx(Card, {
|
|
22
33
|
post: item.post,
|
|
23
34
|
compact: compact
|
|
24
35
|
});
|
|
25
36
|
};
|
|
37
|
+
export const TimelineItemFromPost = ({ post, compact, cardOverride, theme })=>{
|
|
38
|
+
const themeKey = THEME_KEY_MAP[post.type];
|
|
39
|
+
const themeCard = theme?.[themeKey];
|
|
40
|
+
const Card = cardOverride ?? themeCard ?? CARD_MAP[post.type];
|
|
41
|
+
return /*#__PURE__*/ _jsx(Card, {
|
|
42
|
+
post: post,
|
|
43
|
+
compact: compact
|
|
44
|
+
});
|
|
45
|
+
};
|
|
@@ -4,5 +4,5 @@ export { LinkCard } from "./LinkCard.js";
|
|
|
4
4
|
export { QuoteCard } from "./QuoteCard.js";
|
|
5
5
|
export { ImageCard } from "./ImageCard.js";
|
|
6
6
|
export { ThreadPreview } from "./ThreadPreview.js";
|
|
7
|
-
export { TimelineItem } from "./TimelineItem.js";
|
|
7
|
+
export { TimelineItem, TimelineItemFromPost } from "./TimelineItem.js";
|
|
8
8
|
export { TimelineFeed } from "./TimelineFeed.js";
|
package/dist/theme/index.js
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```typescript
|
|
8
|
-
* import {
|
|
8
|
+
* import { PostPage } from "@jant/core/theme";
|
|
9
|
+
* import type { PostPageProps } from "@jant/core";
|
|
9
10
|
*
|
|
10
|
-
* export function
|
|
11
|
+
* export function MyPostPage(props: PostPageProps) {
|
|
11
12
|
* return (
|
|
12
13
|
* <div class="my-wrapper">
|
|
13
|
-
* <
|
|
14
|
+
* <PostPage {...props} />
|
|
14
15
|
* </div>
|
|
15
16
|
* );
|
|
16
17
|
* }
|
|
@@ -19,3 +20,5 @@
|
|
|
19
20
|
export * from "./layouts/index.js";
|
|
20
21
|
// UI components
|
|
21
22
|
export * from "./components/index.js";
|
|
23
|
+
// Page components
|
|
24
|
+
export * from "./pages/index.js";
|
|
@@ -4,58 +4,31 @@
|
|
|
4
4
|
* Two-column layout for public pages with sidebar navigation.
|
|
5
5
|
* On mobile, uses a slide-out drawer menu.
|
|
6
6
|
*/ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
7
|
-
/**
|
|
8
|
-
* Determine if a navigation link is active based on the current path.
|
|
9
|
-
*
|
|
10
|
-
* @param linkUrl - The link's URL
|
|
11
|
-
* @param currentPath - The current page path
|
|
12
|
-
* @returns Whether the link should be shown as active
|
|
13
|
-
*/ function isLinkActive(linkUrl, currentPath) {
|
|
14
|
-
// External links are never active
|
|
15
|
-
if (linkUrl.startsWith("http://") || linkUrl.startsWith("https://")) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
// Exact match for home
|
|
19
|
-
if (linkUrl === "/") {
|
|
20
|
-
return currentPath === "/";
|
|
21
|
-
}
|
|
22
|
-
// Prefix match for other internal links
|
|
23
|
-
return currentPath === linkUrl || currentPath.startsWith(linkUrl + "/");
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Check if a URL is external
|
|
27
|
-
*/ function isExternalUrl(url) {
|
|
28
|
-
return url.startsWith("http://") || url.startsWith("https://");
|
|
29
|
-
}
|
|
30
7
|
/**
|
|
31
8
|
* Render navigation links with dot indicator for active state.
|
|
32
|
-
*/ function NavLinks({
|
|
9
|
+
*/ function NavLinks({ links }) {
|
|
33
10
|
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
34
|
-
children:
|
|
35
|
-
const active = isLinkActive(link.url, currentPath);
|
|
36
|
-
const external = isExternalUrl(link.url);
|
|
37
|
-
return /*#__PURE__*/ _jsxs("a", {
|
|
11
|
+
children: links.map((link)=>/*#__PURE__*/ _jsxs("a", {
|
|
38
12
|
href: link.url,
|
|
39
|
-
class: `text-sm flex items-center gap-2 py-0.5 ${
|
|
40
|
-
...
|
|
13
|
+
class: `text-sm flex items-center gap-2 py-0.5 ${link.isActive ? "text-primary font-medium" : "text-muted-foreground hover:text-foreground"}`,
|
|
14
|
+
...link.isExternal ? {
|
|
41
15
|
target: "_blank",
|
|
42
16
|
rel: "noopener noreferrer"
|
|
43
17
|
} : {},
|
|
44
18
|
children: [
|
|
45
19
|
/*#__PURE__*/ _jsx("span", {
|
|
46
|
-
class: `size-1.5 rounded-full shrink-0 ${
|
|
20
|
+
class: `size-1.5 rounded-full shrink-0 ${link.isActive ? "bg-primary" : "bg-transparent"}`
|
|
47
21
|
}),
|
|
48
22
|
link.label,
|
|
49
|
-
|
|
23
|
+
link.isExternal && /*#__PURE__*/ _jsx("span", {
|
|
50
24
|
class: "ml-1 text-xs opacity-50",
|
|
51
25
|
children: "↗"
|
|
52
26
|
})
|
|
53
27
|
]
|
|
54
|
-
}, link.id)
|
|
55
|
-
})
|
|
28
|
+
}, link.id))
|
|
56
29
|
});
|
|
57
30
|
}
|
|
58
|
-
export const SiteLayout = ({ siteName,
|
|
31
|
+
export const SiteLayout = ({ siteName, links, children })=>{
|
|
59
32
|
return /*#__PURE__*/ _jsxs("div", {
|
|
60
33
|
class: "container py-8 md:flex md:gap-12",
|
|
61
34
|
"data-signals": JSON.stringify({
|
|
@@ -128,8 +101,7 @@ export const SiteLayout = ({ siteName, navigationLinks, currentPath, children })
|
|
|
128
101
|
/*#__PURE__*/ _jsx("nav", {
|
|
129
102
|
class: "flex flex-col gap-0.5",
|
|
130
103
|
children: /*#__PURE__*/ _jsx(NavLinks, {
|
|
131
|
-
|
|
132
|
-
currentPath: currentPath
|
|
104
|
+
links: links
|
|
133
105
|
})
|
|
134
106
|
})
|
|
135
107
|
]
|
|
@@ -145,8 +117,7 @@ export const SiteLayout = ({ siteName, navigationLinks, currentPath, children })
|
|
|
145
117
|
/*#__PURE__*/ _jsx("nav", {
|
|
146
118
|
class: "flex flex-col gap-0.5",
|
|
147
119
|
children: /*#__PURE__*/ _jsx(NavLinks, {
|
|
148
|
-
|
|
149
|
-
currentPath: currentPath
|
|
120
|
+
links: links
|
|
150
121
|
})
|
|
151
122
|
})
|
|
152
123
|
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Archive Page Component
|
|
3
|
+
*
|
|
4
|
+
* Renders posts grouped by year-month with type filter and cursor pagination.
|
|
5
|
+
* Theme authors can replace this entirely via ThemeComponents.ArchivePage.
|
|
6
|
+
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
+
import { POST_TYPES } from "../../types.js";
|
|
9
|
+
import { Pagination as DefaultPagination } from "../components/Pagination.js";
|
|
10
|
+
function getTypeLabel(type) {
|
|
11
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
12
|
+
const labels = {
|
|
13
|
+
note: $__i18n._({
|
|
14
|
+
id: "KiJn9B",
|
|
15
|
+
message: "Note"
|
|
16
|
+
}),
|
|
17
|
+
article: $__i18n._({
|
|
18
|
+
id: "f6e0Ry",
|
|
19
|
+
message: "Article"
|
|
20
|
+
}),
|
|
21
|
+
link: $__i18n._({
|
|
22
|
+
id: "yzF66j",
|
|
23
|
+
message: "Link"
|
|
24
|
+
}),
|
|
25
|
+
quote: $__i18n._({
|
|
26
|
+
id: "ZhhOwV",
|
|
27
|
+
message: "Quote"
|
|
28
|
+
}),
|
|
29
|
+
image: $__i18n._({
|
|
30
|
+
id: "hG89Ed",
|
|
31
|
+
message: "Image"
|
|
32
|
+
}),
|
|
33
|
+
page: $__i18n._({
|
|
34
|
+
id: "6WdDG7",
|
|
35
|
+
message: "Page"
|
|
36
|
+
})
|
|
37
|
+
};
|
|
38
|
+
return labels[type] ?? type;
|
|
39
|
+
}
|
|
40
|
+
function getTypeLabelPlural(type) {
|
|
41
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
42
|
+
const labels = {
|
|
43
|
+
note: $__i18n._({
|
|
44
|
+
id: "1DBGsz",
|
|
45
|
+
message: "Notes"
|
|
46
|
+
}),
|
|
47
|
+
article: $__i18n._({
|
|
48
|
+
id: "Tt5T6+",
|
|
49
|
+
message: "Articles"
|
|
50
|
+
}),
|
|
51
|
+
link: $__i18n._({
|
|
52
|
+
id: "Rj01Fz",
|
|
53
|
+
message: "Links"
|
|
54
|
+
}),
|
|
55
|
+
quote: $__i18n._({
|
|
56
|
+
id: "eWLklq",
|
|
57
|
+
message: "Quotes"
|
|
58
|
+
}),
|
|
59
|
+
image: $__i18n._({
|
|
60
|
+
id: "an5hVd",
|
|
61
|
+
message: "Images"
|
|
62
|
+
}),
|
|
63
|
+
page: $__i18n._({
|
|
64
|
+
id: "wRR604",
|
|
65
|
+
message: "Pages"
|
|
66
|
+
})
|
|
67
|
+
};
|
|
68
|
+
return labels[type] ?? `${type}s`;
|
|
69
|
+
}
|
|
70
|
+
export const ArchivePage = ({ groups, hasMore, nextCursor, type, theme })=>{
|
|
71
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
72
|
+
const title = type ? getTypeLabelPlural(type) : $__i18n._({
|
|
73
|
+
id: "B495Gs",
|
|
74
|
+
message: "Archive"
|
|
75
|
+
});
|
|
76
|
+
const PaginationComponent = theme?.Pagination ?? DefaultPagination;
|
|
77
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
78
|
+
children: [
|
|
79
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
80
|
+
class: "mb-8",
|
|
81
|
+
children: [
|
|
82
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
83
|
+
class: "text-2xl font-semibold",
|
|
84
|
+
children: title
|
|
85
|
+
}),
|
|
86
|
+
/*#__PURE__*/ _jsxs("nav", {
|
|
87
|
+
class: "flex flex-wrap gap-2 mt-4",
|
|
88
|
+
children: [
|
|
89
|
+
/*#__PURE__*/ _jsx("a", {
|
|
90
|
+
href: "/archive",
|
|
91
|
+
class: `badge ${!type ? "badge-primary" : "badge-outline"}`,
|
|
92
|
+
children: $__i18n._({
|
|
93
|
+
id: "N40H+G",
|
|
94
|
+
message: "All"
|
|
95
|
+
})
|
|
96
|
+
}),
|
|
97
|
+
POST_TYPES.filter((t)=>t !== "page").map((typeKey)=>/*#__PURE__*/ _jsx("a", {
|
|
98
|
+
href: `/archive?type=${typeKey}`,
|
|
99
|
+
class: `badge ${type === typeKey ? "badge-primary" : "badge-outline"}`,
|
|
100
|
+
children: getTypeLabelPlural(typeKey)
|
|
101
|
+
}, typeKey))
|
|
102
|
+
]
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
}),
|
|
106
|
+
/*#__PURE__*/ _jsx("main", {
|
|
107
|
+
children: groups.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
108
|
+
class: "text-muted-foreground",
|
|
109
|
+
children: $__i18n._({
|
|
110
|
+
id: "Hzi9AA",
|
|
111
|
+
message: "No posts found."
|
|
112
|
+
})
|
|
113
|
+
}) : groups.map((group)=>/*#__PURE__*/ _jsxs("section", {
|
|
114
|
+
class: "mb-8",
|
|
115
|
+
children: [
|
|
116
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
117
|
+
class: "text-lg font-medium mb-4 text-muted-foreground",
|
|
118
|
+
children: group.label
|
|
119
|
+
}),
|
|
120
|
+
/*#__PURE__*/ _jsx("div", {
|
|
121
|
+
class: "flex flex-col gap-3",
|
|
122
|
+
children: group.posts.map((post)=>/*#__PURE__*/ _jsxs("article", {
|
|
123
|
+
class: "flex items-baseline gap-4",
|
|
124
|
+
children: [
|
|
125
|
+
/*#__PURE__*/ _jsx("time", {
|
|
126
|
+
class: "text-sm text-muted-foreground w-12 shrink-0",
|
|
127
|
+
datetime: post.publishedAt,
|
|
128
|
+
children: new Date(post.publishedAt).getUTCDate()
|
|
129
|
+
}),
|
|
130
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
131
|
+
class: "flex-1 min-w-0",
|
|
132
|
+
children: [
|
|
133
|
+
/*#__PURE__*/ _jsx("a", {
|
|
134
|
+
href: post.permalink,
|
|
135
|
+
class: "hover:underline",
|
|
136
|
+
children: post.title || post.content?.slice(0, 80) || `Post #${post.id}`
|
|
137
|
+
}),
|
|
138
|
+
!type && /*#__PURE__*/ _jsx("span", {
|
|
139
|
+
class: "ml-2 badge-outline text-xs",
|
|
140
|
+
children: getTypeLabel(post.type)
|
|
141
|
+
})
|
|
142
|
+
]
|
|
143
|
+
})
|
|
144
|
+
]
|
|
145
|
+
}, post.id))
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
}, `${group.year}-${group.month}`))
|
|
149
|
+
}),
|
|
150
|
+
/*#__PURE__*/ _jsx(PaginationComponent, {
|
|
151
|
+
baseUrl: type ? `/archive?type=${type}` : "/archive",
|
|
152
|
+
hasMore: hasMore,
|
|
153
|
+
nextCursor: nextCursor
|
|
154
|
+
})
|
|
155
|
+
]
|
|
156
|
+
});
|
|
157
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Collection Page Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a collection with its posts.
|
|
5
|
+
* Theme authors can replace this entirely via ThemeComponents.CollectionPage.
|
|
6
|
+
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
+
export const CollectionPage = ({ collection, posts })=>{
|
|
9
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
10
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
11
|
+
children: [
|
|
12
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
13
|
+
class: "mb-8",
|
|
14
|
+
children: [
|
|
15
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
16
|
+
class: "text-2xl font-semibold",
|
|
17
|
+
children: collection.title
|
|
18
|
+
}),
|
|
19
|
+
collection.description && /*#__PURE__*/ _jsx("p", {
|
|
20
|
+
class: "text-muted-foreground mt-2",
|
|
21
|
+
children: collection.description
|
|
22
|
+
})
|
|
23
|
+
]
|
|
24
|
+
}),
|
|
25
|
+
/*#__PURE__*/ _jsx("main", {
|
|
26
|
+
class: "flex flex-col gap-6",
|
|
27
|
+
children: posts.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
28
|
+
class: "text-muted-foreground",
|
|
29
|
+
children: $__i18n._({
|
|
30
|
+
id: "J4FNfC",
|
|
31
|
+
message: "No posts in this collection."
|
|
32
|
+
})
|
|
33
|
+
}) : posts.map((post)=>/*#__PURE__*/ _jsxs("article", {
|
|
34
|
+
class: "h-entry",
|
|
35
|
+
children: [
|
|
36
|
+
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
37
|
+
class: "p-name text-lg font-medium mb-2",
|
|
38
|
+
children: /*#__PURE__*/ _jsx("a", {
|
|
39
|
+
href: post.permalink,
|
|
40
|
+
class: "u-url hover:underline",
|
|
41
|
+
children: post.title
|
|
42
|
+
})
|
|
43
|
+
}),
|
|
44
|
+
/*#__PURE__*/ _jsx("div", {
|
|
45
|
+
class: "e-content prose prose-sm",
|
|
46
|
+
dangerouslySetInnerHTML: {
|
|
47
|
+
__html: post.contentHtml || ""
|
|
48
|
+
}
|
|
49
|
+
}),
|
|
50
|
+
/*#__PURE__*/ _jsx("footer", {
|
|
51
|
+
class: "mt-2 text-sm text-muted-foreground",
|
|
52
|
+
children: /*#__PURE__*/ _jsx("time", {
|
|
53
|
+
class: "dt-published",
|
|
54
|
+
datetime: post.publishedAt,
|
|
55
|
+
children: post.publishedAtFormatted
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
}, post.id))
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Home Page Component
|
|
3
|
+
*
|
|
4
|
+
* Renders the timeline feed with thread previews.
|
|
5
|
+
* Theme authors can replace this entirely via ThemeComponents.HomePage.
|
|
6
|
+
*/ import { jsx as _jsx, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
7
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
+
import { TimelineFeed as DefaultTimelineFeed } from "../components/timeline/TimelineFeed.js";
|
|
9
|
+
export const HomePage = ({ items, hasMore, nextCursor, theme })=>{
|
|
10
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
11
|
+
const Feed = theme?.TimelineFeed ?? DefaultTimelineFeed;
|
|
12
|
+
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
13
|
+
children: items.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
14
|
+
class: "text-muted-foreground",
|
|
15
|
+
children: $__i18n._({
|
|
16
|
+
id: "ODiSoW",
|
|
17
|
+
message: "No posts yet."
|
|
18
|
+
})
|
|
19
|
+
}) : /*#__PURE__*/ _jsx(Feed, {
|
|
20
|
+
items: items,
|
|
21
|
+
hasMore: hasMore,
|
|
22
|
+
nextCursor: nextCursor,
|
|
23
|
+
theme: theme
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Post Page Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a single post with media gallery.
|
|
5
|
+
* Theme authors can replace this entirely via ThemeComponents.PostPage.
|
|
6
|
+
*/ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
7
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
+
import { MediaGallery as DefaultMediaGallery } from "../components/MediaGallery.js";
|
|
9
|
+
export const PostPage = ({ post, theme })=>{
|
|
10
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
11
|
+
const Gallery = theme?.MediaGallery ?? DefaultMediaGallery;
|
|
12
|
+
return /*#__PURE__*/ _jsxs("article", {
|
|
13
|
+
class: "h-entry",
|
|
14
|
+
children: [
|
|
15
|
+
post.title && /*#__PURE__*/ _jsx("h1", {
|
|
16
|
+
class: "p-name text-2xl font-semibold mb-4",
|
|
17
|
+
children: post.title
|
|
18
|
+
}),
|
|
19
|
+
/*#__PURE__*/ _jsx("div", {
|
|
20
|
+
class: "e-content prose",
|
|
21
|
+
dangerouslySetInnerHTML: {
|
|
22
|
+
__html: post.contentHtml || ""
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
post.media.length > 0 && /*#__PURE__*/ _jsx(Gallery, {
|
|
26
|
+
attachments: post.media
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ _jsxs("footer", {
|
|
29
|
+
class: "mt-6 pt-4 border-t text-sm text-muted-foreground",
|
|
30
|
+
children: [
|
|
31
|
+
/*#__PURE__*/ _jsx("time", {
|
|
32
|
+
class: "dt-published",
|
|
33
|
+
datetime: post.publishedAt,
|
|
34
|
+
children: post.publishedAtFormatted
|
|
35
|
+
}),
|
|
36
|
+
/*#__PURE__*/ _jsx("a", {
|
|
37
|
+
href: post.permalink,
|
|
38
|
+
class: "u-url ml-4",
|
|
39
|
+
children: $__i18n._({
|
|
40
|
+
id: "D9Oea+",
|
|
41
|
+
message: "Permalink"
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
]
|
|
45
|
+
})
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Search Page Component
|
|
3
|
+
*
|
|
4
|
+
* Renders search form and results with page-based pagination.
|
|
5
|
+
* Theme authors can replace this entirely via ThemeComponents.SearchPage.
|
|
6
|
+
*/ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
7
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
8
|
+
import { PagePagination as DefaultPagePagination } from "../components/Pagination.js";
|
|
9
|
+
export const SearchPage = ({ query, results, error, hasMore, page, theme })=>{
|
|
10
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
11
|
+
const searchTitle = $__i18n._({
|
|
12
|
+
id: "A1taO8",
|
|
13
|
+
message: "Search"
|
|
14
|
+
});
|
|
15
|
+
const PaginationComponent = theme?.PagePagination ?? DefaultPagePagination;
|
|
16
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
17
|
+
children: [
|
|
18
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
19
|
+
class: "text-2xl font-semibold mb-6",
|
|
20
|
+
children: searchTitle
|
|
21
|
+
}),
|
|
22
|
+
/*#__PURE__*/ _jsx("form", {
|
|
23
|
+
method: "get",
|
|
24
|
+
action: "/search",
|
|
25
|
+
class: "mb-8",
|
|
26
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
27
|
+
class: "flex gap-2",
|
|
28
|
+
children: [
|
|
29
|
+
/*#__PURE__*/ _jsx("input", {
|
|
30
|
+
type: "search",
|
|
31
|
+
name: "q",
|
|
32
|
+
class: "input flex-1",
|
|
33
|
+
placeholder: $__i18n._({
|
|
34
|
+
id: "MqghUt",
|
|
35
|
+
message: "Search posts..."
|
|
36
|
+
}),
|
|
37
|
+
value: query,
|
|
38
|
+
autofocus: true
|
|
39
|
+
}),
|
|
40
|
+
/*#__PURE__*/ _jsx("button", {
|
|
41
|
+
type: "submit",
|
|
42
|
+
class: "btn",
|
|
43
|
+
children: $__i18n._({
|
|
44
|
+
id: "A1taO8",
|
|
45
|
+
message: "Search"
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
]
|
|
49
|
+
})
|
|
50
|
+
}),
|
|
51
|
+
error && /*#__PURE__*/ _jsx("div", {
|
|
52
|
+
class: "alert-destructive mb-6",
|
|
53
|
+
children: /*#__PURE__*/ _jsx("h2", {
|
|
54
|
+
children: error
|
|
55
|
+
})
|
|
56
|
+
}),
|
|
57
|
+
query && !error && /*#__PURE__*/ _jsxs("div", {
|
|
58
|
+
children: [
|
|
59
|
+
/*#__PURE__*/ _jsx("p", {
|
|
60
|
+
class: "text-sm text-muted-foreground mb-4",
|
|
61
|
+
children: results.length === 0 ? $__i18n._({
|
|
62
|
+
id: "MZbQHL",
|
|
63
|
+
message: "No results found."
|
|
64
|
+
}) : results.length === 1 ? $__i18n._({
|
|
65
|
+
id: "z8ajIE",
|
|
66
|
+
message: "Found 1 result"
|
|
67
|
+
}) : $__i18n._({
|
|
68
|
+
id: "zH6KqE",
|
|
69
|
+
message: "Found {count} results"
|
|
70
|
+
})
|
|
71
|
+
}),
|
|
72
|
+
results.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
73
|
+
children: [
|
|
74
|
+
/*#__PURE__*/ _jsx("div", {
|
|
75
|
+
class: "flex flex-col gap-4",
|
|
76
|
+
children: results.map((result)=>/*#__PURE__*/ _jsx("article", {
|
|
77
|
+
class: "p-4 rounded-lg border hover:border-primary",
|
|
78
|
+
children: /*#__PURE__*/ _jsxs("a", {
|
|
79
|
+
href: result.post.permalink,
|
|
80
|
+
class: "block",
|
|
81
|
+
children: [
|
|
82
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
83
|
+
class: "font-medium hover:underline",
|
|
84
|
+
children: result.post.title || result.post.content?.slice(0, 60) || `Post #${result.post.id}`
|
|
85
|
+
}),
|
|
86
|
+
result.snippet && /*#__PURE__*/ _jsx("p", {
|
|
87
|
+
class: "text-sm text-muted-foreground mt-2 line-clamp-2",
|
|
88
|
+
dangerouslySetInnerHTML: {
|
|
89
|
+
__html: result.snippet
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
/*#__PURE__*/ _jsxs("footer", {
|
|
93
|
+
class: "flex items-center gap-2 mt-2 text-xs text-muted-foreground",
|
|
94
|
+
children: [
|
|
95
|
+
/*#__PURE__*/ _jsx("span", {
|
|
96
|
+
class: "badge-outline",
|
|
97
|
+
children: result.post.type
|
|
98
|
+
}),
|
|
99
|
+
/*#__PURE__*/ _jsx("time", {
|
|
100
|
+
datetime: result.post.publishedAt,
|
|
101
|
+
children: result.post.publishedAtFormatted
|
|
102
|
+
})
|
|
103
|
+
]
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
}, result.post.id))
|
|
108
|
+
}),
|
|
109
|
+
/*#__PURE__*/ _jsx(PaginationComponent, {
|
|
110
|
+
baseUrl: `/search?q=${encodeURIComponent(query)}`,
|
|
111
|
+
currentPage: page,
|
|
112
|
+
hasMore: hasMore
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
})
|
|
116
|
+
]
|
|
117
|
+
})
|
|
118
|
+
]
|
|
119
|
+
});
|
|
120
|
+
};
|