@jant/core 0.3.20 → 0.3.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/app.js +60 -17
  2. package/dist/index.js +8 -0
  3. package/dist/lib/feed.js +112 -0
  4. package/dist/lib/navigation.js +9 -9
  5. package/dist/lib/render.js +48 -0
  6. package/dist/lib/theme-components.js +18 -18
  7. package/dist/lib/view.js +228 -0
  8. package/dist/routes/api/timeline.js +20 -16
  9. package/dist/routes/dash/collections.js +38 -10
  10. package/dist/routes/dash/navigation.js +22 -8
  11. package/dist/routes/dash/redirects.js +19 -5
  12. package/dist/routes/dash/settings.js +57 -15
  13. package/dist/routes/feed/rss.js +34 -78
  14. package/dist/routes/feed/sitemap.js +11 -26
  15. package/dist/routes/pages/archive.js +18 -195
  16. package/dist/routes/pages/collection.js +16 -70
  17. package/dist/routes/pages/home.js +25 -47
  18. package/dist/routes/pages/page.js +15 -27
  19. package/dist/routes/pages/post.js +25 -79
  20. package/dist/routes/pages/search.js +20 -130
  21. package/dist/theme/components/MediaGallery.js +10 -10
  22. package/dist/theme/components/PageForm.js +22 -8
  23. package/dist/theme/components/PostForm.js +22 -8
  24. package/dist/theme/components/index.js +1 -1
  25. package/dist/theme/components/timeline/ArticleCard.js +7 -11
  26. package/dist/theme/components/timeline/ImageCard.js +10 -13
  27. package/dist/theme/components/timeline/LinkCard.js +4 -7
  28. package/dist/theme/components/timeline/NoteCard.js +5 -8
  29. package/dist/theme/components/timeline/QuoteCard.js +3 -6
  30. package/dist/theme/components/timeline/ThreadPreview.js +9 -10
  31. package/dist/theme/components/timeline/TimelineFeed.js +8 -5
  32. package/dist/theme/components/timeline/TimelineItem.js +22 -2
  33. package/dist/theme/components/timeline/index.js +1 -1
  34. package/dist/theme/index.js +6 -3
  35. package/dist/theme/layouts/SiteLayout.js +10 -39
  36. package/dist/theme/pages/ArchivePage.js +157 -0
  37. package/dist/theme/pages/CollectionPage.js +63 -0
  38. package/dist/theme/pages/HomePage.js +26 -0
  39. package/dist/theme/pages/PostPage.js +48 -0
  40. package/dist/theme/pages/SearchPage.js +120 -0
  41. package/dist/theme/pages/SinglePage.js +23 -0
  42. package/dist/theme/pages/index.js +11 -0
  43. package/package.json +2 -1
  44. package/src/app.tsx +48 -17
  45. package/src/i18n/locales/en.po +171 -147
  46. package/src/i18n/locales/zh-Hans.po +171 -147
  47. package/src/i18n/locales/zh-Hant.po +171 -147
  48. package/src/index.ts +51 -2
  49. package/src/lib/__tests__/theme-components.test.ts +33 -14
  50. package/src/lib/__tests__/view.test.ts +375 -0
  51. package/src/lib/feed.ts +148 -0
  52. package/src/lib/navigation.ts +11 -11
  53. package/src/lib/render.tsx +67 -0
  54. package/src/lib/theme-components.ts +27 -35
  55. package/src/lib/view.ts +318 -0
  56. package/src/routes/api/__tests__/timeline.test.ts +3 -3
  57. package/src/routes/api/timeline.tsx +32 -25
  58. package/src/routes/dash/collections.tsx +30 -10
  59. package/src/routes/dash/navigation.tsx +20 -10
  60. package/src/routes/dash/redirects.tsx +15 -5
  61. package/src/routes/dash/settings.tsx +53 -15
  62. package/src/routes/feed/rss.ts +47 -94
  63. package/src/routes/feed/sitemap.ts +8 -30
  64. package/src/routes/pages/archive.tsx +24 -209
  65. package/src/routes/pages/collection.tsx +19 -75
  66. package/src/routes/pages/home.tsx +42 -76
  67. package/src/routes/pages/page.tsx +17 -28
  68. package/src/routes/pages/post.tsx +28 -86
  69. package/src/routes/pages/search.tsx +29 -151
  70. package/src/services/search.ts +2 -8
  71. package/src/theme/components/MediaGallery.tsx +12 -12
  72. package/src/theme/components/PageForm.tsx +20 -10
  73. package/src/theme/components/PostForm.tsx +20 -10
  74. package/src/theme/components/index.ts +1 -0
  75. package/src/theme/components/timeline/ArticleCard.tsx +7 -19
  76. package/src/theme/components/timeline/ImageCard.tsx +10 -20
  77. package/src/theme/components/timeline/LinkCard.tsx +4 -11
  78. package/src/theme/components/timeline/NoteCard.tsx +5 -12
  79. package/src/theme/components/timeline/QuoteCard.tsx +3 -10
  80. package/src/theme/components/timeline/ThreadPreview.tsx +5 -5
  81. package/src/theme/components/timeline/TimelineFeed.tsx +7 -3
  82. package/src/theme/components/timeline/TimelineItem.tsx +43 -4
  83. package/src/theme/components/timeline/index.ts +1 -1
  84. package/src/theme/index.ts +7 -3
  85. package/src/theme/layouts/SiteLayout.tsx +25 -77
  86. package/src/theme/layouts/index.ts +2 -1
  87. package/src/theme/pages/ArchivePage.tsx +160 -0
  88. package/src/theme/pages/CollectionPage.tsx +60 -0
  89. package/src/theme/pages/HomePage.tsx +42 -0
  90. package/src/theme/pages/PostPage.tsx +44 -0
  91. package/src/theme/pages/SearchPage.tsx +128 -0
  92. package/src/theme/pages/SinglePage.tsx +24 -0
  93. package/src/theme/pages/index.ts +13 -0
  94. package/src/types.ts +262 -38
@@ -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
- export const TimelineItem = ({ item, compact, cardOverride })=>{
20
- const Card = cardOverride ?? CARD_MAP[item.post.type];
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";
@@ -5,12 +5,13 @@
5
5
  *
6
6
  * @example
7
7
  * ```typescript
8
- * import { PostCard } from "@jant/core/theme";
8
+ * import { PostPage } from "@jant/core/theme";
9
+ * import type { PostPageProps } from "@jant/core";
9
10
  *
10
- * export function MyPostCard(props) {
11
+ * export function MyPostPage(props: PostPageProps) {
11
12
  * return (
12
13
  * <div class="my-wrapper">
13
- * <PostCard {...props} />
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({ navigationLinks, currentPath }) {
9
+ */ function NavLinks({ links }) {
33
10
  return /*#__PURE__*/ _jsx(_Fragment, {
34
- children: navigationLinks.map((link)=>{
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 ${active ? "text-primary font-medium" : "text-muted-foreground hover:text-foreground"}`,
40
- ...external ? {
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 ${active ? "bg-primary" : "bg-transparent"}`
20
+ class: `size-1.5 rounded-full shrink-0 ${link.isActive ? "bg-primary" : "bg-transparent"}`
47
21
  }),
48
22
  link.label,
49
- external && /*#__PURE__*/ _jsx("span", {
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, navigationLinks, currentPath, children })=>{
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
- navigationLinks: navigationLinks,
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
- navigationLinks: navigationLinks,
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
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Default Single Page Component
3
+ *
4
+ * Renders a custom page (type "page").
5
+ * Theme authors can replace this entirely via ThemeComponents.SinglePage.
6
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
7
+ export const SinglePage = ({ page })=>{
8
+ return /*#__PURE__*/ _jsxs("article", {
9
+ class: "h-entry",
10
+ children: [
11
+ page.title && /*#__PURE__*/ _jsx("h1", {
12
+ class: "p-name text-3xl font-semibold mb-6",
13
+ children: page.title
14
+ }),
15
+ /*#__PURE__*/ _jsx("div", {
16
+ class: "e-content prose",
17
+ dangerouslySetInnerHTML: {
18
+ __html: page.contentHtml || ""
19
+ }
20
+ })
21
+ ]
22
+ });
23
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Default Page Components
3
+ *
4
+ * These are the built-in page components that render each public page.
5
+ * Theme authors can import these to wrap/extend them.
6
+ */ export { HomePage } from "./HomePage.js";
7
+ export { PostPage } from "./PostPage.js";
8
+ export { SinglePage } from "./SinglePage.js";
9
+ export { ArchivePage } from "./ArchivePage.js";
10
+ export { SearchPage } from "./SearchPage.js";
11
+ export { CollectionPage } from "./CollectionPage.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jant/core",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "description": "A modern, open-source microblogging platform built on Cloudflare Workers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,6 +45,7 @@
45
45
  "zod": "^4.3.6"
46
46
  },
47
47
  "peerDependencies": {
48
+ "hono": "^4.0.0",
48
49
  "tailwindcss": "^4.0.0"
49
50
  },
50
51
  "devDependencies": {