@jant/core 0.3.22 → 0.3.24

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 (178) hide show
  1. package/dist/app.js +23 -5
  2. package/dist/db/schema.js +72 -47
  3. package/dist/i18n/locales/en.js +1 -1
  4. package/dist/i18n/locales/zh-Hans.js +1 -1
  5. package/dist/i18n/locales/zh-Hant.js +1 -1
  6. package/dist/index.js +5 -6
  7. package/dist/lib/constants.js +1 -4
  8. package/dist/lib/excerpt.js +76 -0
  9. package/dist/lib/feed.js +18 -7
  10. package/dist/lib/navigation.js +4 -5
  11. package/dist/lib/render.js +1 -1
  12. package/dist/lib/schemas.js +80 -38
  13. package/dist/lib/theme-components.js +8 -11
  14. package/dist/lib/time.js +56 -1
  15. package/dist/lib/timeline.js +119 -0
  16. package/dist/lib/view.js +62 -73
  17. package/dist/routes/api/posts.js +29 -35
  18. package/dist/routes/api/search.js +5 -6
  19. package/dist/routes/api/upload.js +13 -13
  20. package/dist/routes/dash/collections.js +22 -40
  21. package/dist/routes/dash/index.js +2 -2
  22. package/dist/routes/dash/navigation.js +25 -24
  23. package/dist/routes/dash/pages.js +42 -57
  24. package/dist/routes/dash/posts.js +27 -35
  25. package/dist/routes/feed/rss.js +2 -4
  26. package/dist/routes/feed/sitemap.js +10 -7
  27. package/dist/routes/pages/archive.js +12 -11
  28. package/dist/routes/pages/collection.js +11 -5
  29. package/dist/routes/pages/home.js +53 -61
  30. package/dist/routes/pages/page.js +60 -29
  31. package/dist/routes/pages/post.js +5 -12
  32. package/dist/routes/pages/search.js +3 -4
  33. package/dist/services/collection.js +52 -64
  34. package/dist/services/index.js +5 -3
  35. package/dist/services/navigation.js +29 -53
  36. package/dist/services/page.js +80 -0
  37. package/dist/services/post.js +68 -69
  38. package/dist/services/search.js +24 -18
  39. package/dist/theme/components/MediaGallery.js +19 -91
  40. package/dist/theme/components/PageForm.js +15 -15
  41. package/dist/theme/components/PostForm.js +136 -129
  42. package/dist/theme/components/PostList.js +13 -8
  43. package/dist/theme/components/ThreadView.js +3 -3
  44. package/dist/theme/components/TypeBadge.js +3 -14
  45. package/dist/theme/components/VisibilityBadge.js +33 -23
  46. package/dist/theme/components/index.js +0 -2
  47. package/dist/theme/index.js +10 -16
  48. package/dist/theme/layouts/index.js +0 -1
  49. package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
  50. package/dist/themes/threads/index.js +81 -0
  51. package/dist/{theme → themes/threads}/pages/ArchivePage.js +31 -47
  52. package/dist/themes/threads/pages/CollectionPage.js +65 -0
  53. package/dist/{theme → themes/threads}/pages/HomePage.js +4 -5
  54. package/dist/{theme → themes/threads}/pages/PostPage.js +10 -8
  55. package/dist/{theme → themes/threads}/pages/SearchPage.js +8 -8
  56. package/dist/{theme → themes/threads}/pages/SinglePage.js +5 -6
  57. package/dist/{theme/components → themes/threads}/timeline/LinkCard.js +20 -11
  58. package/dist/themes/threads/timeline/NoteCard.js +53 -0
  59. package/dist/themes/threads/timeline/QuoteCard.js +59 -0
  60. package/dist/{theme/components → themes/threads}/timeline/ThreadPreview.js +5 -6
  61. package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
  62. package/dist/{theme/components → themes/threads}/timeline/TimelineItem.js +8 -17
  63. package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
  64. package/dist/themes/threads/timeline/groupByDate.js +22 -0
  65. package/dist/themes/threads/timeline/timelineMore.js +107 -0
  66. package/dist/types.js +24 -40
  67. package/package.json +2 -1
  68. package/src/__tests__/helpers/app.ts +4 -0
  69. package/src/__tests__/helpers/db.ts +51 -74
  70. package/src/app.tsx +27 -6
  71. package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
  72. package/src/db/migrations/meta/_journal.json +7 -0
  73. package/src/db/schema.ts +63 -46
  74. package/src/i18n/locales/en.po +216 -164
  75. package/src/i18n/locales/en.ts +1 -1
  76. package/src/i18n/locales/zh-Hans.po +216 -164
  77. package/src/i18n/locales/zh-Hans.ts +1 -1
  78. package/src/i18n/locales/zh-Hant.po +216 -164
  79. package/src/i18n/locales/zh-Hant.ts +1 -1
  80. package/src/index.ts +30 -15
  81. package/src/lib/__tests__/excerpt.test.ts +125 -0
  82. package/src/lib/__tests__/schemas.test.ts +166 -105
  83. package/src/lib/__tests__/theme-components.test.ts +4 -25
  84. package/src/lib/__tests__/time.test.ts +62 -0
  85. package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
  86. package/src/lib/__tests__/view.test.ts +217 -67
  87. package/src/lib/constants.ts +1 -4
  88. package/src/lib/excerpt.ts +87 -0
  89. package/src/lib/feed.ts +22 -7
  90. package/src/lib/navigation.ts +6 -7
  91. package/src/lib/render.tsx +1 -1
  92. package/src/lib/schemas.ts +118 -52
  93. package/src/lib/theme-components.ts +10 -13
  94. package/src/lib/time.ts +64 -0
  95. package/src/lib/timeline.ts +170 -0
  96. package/src/lib/view.ts +81 -83
  97. package/src/preset.css +45 -0
  98. package/src/routes/api/__tests__/posts.test.ts +50 -108
  99. package/src/routes/api/__tests__/search.test.ts +2 -3
  100. package/src/routes/api/posts.ts +30 -30
  101. package/src/routes/api/search.ts +4 -4
  102. package/src/routes/api/upload.ts +16 -6
  103. package/src/routes/dash/collections.tsx +18 -40
  104. package/src/routes/dash/index.tsx +2 -2
  105. package/src/routes/dash/navigation.tsx +27 -26
  106. package/src/routes/dash/pages.tsx +45 -60
  107. package/src/routes/dash/posts.tsx +44 -52
  108. package/src/routes/feed/rss.ts +2 -1
  109. package/src/routes/feed/sitemap.ts +14 -4
  110. package/src/routes/pages/archive.tsx +14 -10
  111. package/src/routes/pages/collection.tsx +17 -6
  112. package/src/routes/pages/home.tsx +56 -81
  113. package/src/routes/pages/page.tsx +64 -27
  114. package/src/routes/pages/post.tsx +5 -14
  115. package/src/routes/pages/search.tsx +2 -2
  116. package/src/services/__tests__/collection.test.ts +257 -158
  117. package/src/services/__tests__/media.test.ts +18 -18
  118. package/src/services/__tests__/navigation.test.ts +161 -87
  119. package/src/services/__tests__/post-timeline.test.ts +92 -88
  120. package/src/services/__tests__/post.test.ts +342 -206
  121. package/src/services/__tests__/search.test.ts +19 -25
  122. package/src/services/collection.ts +71 -113
  123. package/src/services/index.ts +9 -8
  124. package/src/services/navigation.ts +38 -71
  125. package/src/services/page.ts +124 -0
  126. package/src/services/post.ts +93 -103
  127. package/src/services/search.ts +38 -27
  128. package/src/styles/components.css +0 -54
  129. package/src/theme/components/MediaGallery.tsx +27 -96
  130. package/src/theme/components/PageForm.tsx +21 -21
  131. package/src/theme/components/PostForm.tsx +122 -118
  132. package/src/theme/components/PostList.tsx +58 -49
  133. package/src/theme/components/ThreadView.tsx +6 -3
  134. package/src/theme/components/TypeBadge.tsx +9 -17
  135. package/src/theme/components/VisibilityBadge.tsx +40 -23
  136. package/src/theme/components/index.ts +0 -13
  137. package/src/theme/index.ts +10 -16
  138. package/src/theme/layouts/index.ts +0 -1
  139. package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
  140. package/src/themes/threads/index.ts +100 -0
  141. package/src/{theme → themes/threads}/pages/ArchivePage.tsx +52 -55
  142. package/src/themes/threads/pages/CollectionPage.tsx +61 -0
  143. package/src/{theme → themes/threads}/pages/HomePage.tsx +5 -6
  144. package/src/{theme → themes/threads}/pages/PostPage.tsx +11 -8
  145. package/src/{theme → themes/threads}/pages/SearchPage.tsx +9 -13
  146. package/src/themes/threads/pages/SinglePage.tsx +23 -0
  147. package/src/themes/threads/style.css +336 -0
  148. package/src/{theme/components → themes/threads}/timeline/LinkCard.tsx +21 -13
  149. package/src/themes/threads/timeline/NoteCard.tsx +58 -0
  150. package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
  151. package/src/{theme/components → themes/threads}/timeline/ThreadPreview.tsx +6 -6
  152. package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
  153. package/src/{theme/components → themes/threads}/timeline/TimelineItem.tsx +9 -20
  154. package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
  155. package/src/themes/threads/timeline/groupByDate.ts +30 -0
  156. package/src/themes/threads/timeline/timelineMore.tsx +130 -0
  157. package/src/types.ts +242 -98
  158. package/dist/routes/api/timeline.js +0 -120
  159. package/dist/theme/components/timeline/ArticleCard.js +0 -46
  160. package/dist/theme/components/timeline/ImageCard.js +0 -83
  161. package/dist/theme/components/timeline/NoteCard.js +0 -34
  162. package/dist/theme/components/timeline/QuoteCard.js +0 -48
  163. package/dist/theme/components/timeline/TimelineFeed.js +0 -46
  164. package/dist/theme/components/timeline/index.js +0 -8
  165. package/dist/theme/layouts/SiteLayout.js +0 -131
  166. package/dist/theme/pages/CollectionPage.js +0 -63
  167. package/dist/theme/pages/index.js +0 -11
  168. package/src/routes/api/timeline.tsx +0 -159
  169. package/src/theme/components/timeline/ArticleCard.tsx +0 -45
  170. package/src/theme/components/timeline/ImageCard.tsx +0 -70
  171. package/src/theme/components/timeline/NoteCard.tsx +0 -34
  172. package/src/theme/components/timeline/QuoteCard.tsx +0 -48
  173. package/src/theme/components/timeline/TimelineFeed.tsx +0 -56
  174. package/src/theme/components/timeline/index.ts +0 -8
  175. package/src/theme/layouts/SiteLayout.tsx +0 -132
  176. package/src/theme/pages/CollectionPage.tsx +0 -60
  177. package/src/theme/pages/SinglePage.tsx +0 -24
  178. package/src/theme/pages/index.ts +0 -13
@@ -1,108 +1,96 @@
1
1
  /**
2
- * Default Archive Page Component
2
+ * Threads Theme - Archive Page
3
3
  *
4
- * Renders posts grouped by year-month with type filter and cursor pagination.
5
- * Theme authors can replace this entirely via ThemeComponents.ArchivePage.
4
+ * Posts grouped by year-month with format filter and cursor pagination.
6
5
  */
7
6
 
8
7
  import type { FC } from "hono/jsx";
9
8
  import { useLingui } from "@lingui/react/macro";
10
- import type { ArchivePageProps } from "../../types.js";
11
- import { POST_TYPES } from "../../types.js";
12
- import { Pagination as DefaultPagination } from "../components/Pagination.js";
9
+ import type { ArchivePageProps } from "../../../types.js";
10
+ import { FORMATS } from "../../../types.js";
11
+ import { Pagination as DefaultPagination } from "../../../theme/index.js";
13
12
 
14
- function getTypeLabel(type: string): string {
13
+ function getFormatLabel(format: string): string {
15
14
  const { t } = useLingui();
16
15
  const labels: Record<string, string> = {
17
- note: t({ message: "Note", comment: "@context: Post type label - note" }),
18
- article: t({
19
- message: "Article",
20
- comment: "@context: Post type label - article",
21
- }),
22
- link: t({ message: "Link", comment: "@context: Post type label - link" }),
16
+ note: t({ message: "Note", comment: "@context: Post format label - note" }),
17
+ link: t({ message: "Link", comment: "@context: Post format label - link" }),
23
18
  quote: t({
24
19
  message: "Quote",
25
- comment: "@context: Post type label - quote",
26
- }),
27
- image: t({
28
- message: "Image",
29
- comment: "@context: Post type label - image",
20
+ comment: "@context: Post format label - quote",
30
21
  }),
31
- page: t({ message: "Page", comment: "@context: Post type label - page" }),
32
22
  };
33
- return labels[type] ?? type;
23
+ return labels[format] ?? format;
34
24
  }
35
25
 
36
- function getTypeLabelPlural(type: string): string {
26
+ function getFormatLabelPlural(format: string): string {
37
27
  const { t } = useLingui();
38
28
  const labels: Record<string, string> = {
39
29
  note: t({
40
30
  message: "Notes",
41
- comment: "@context: Post type label plural - notes",
42
- }),
43
- article: t({
44
- message: "Articles",
45
- comment: "@context: Post type label plural - articles",
31
+ comment: "@context: Post format label plural - notes",
46
32
  }),
47
33
  link: t({
48
34
  message: "Links",
49
- comment: "@context: Post type label plural - links",
35
+ comment: "@context: Post format label plural - links",
50
36
  }),
51
37
  quote: t({
52
38
  message: "Quotes",
53
- comment: "@context: Post type label plural - quotes",
54
- }),
55
- image: t({
56
- message: "Images",
57
- comment: "@context: Post type label plural - images",
58
- }),
59
- page: t({
60
- message: "Pages",
61
- comment: "@context: Post type label plural - pages",
39
+ comment: "@context: Post format label plural - quotes",
62
40
  }),
63
41
  };
64
- return labels[type] ?? `${type}s`;
42
+ return labels[format] ?? `${format}s`;
65
43
  }
66
44
 
67
45
  export const ArchivePage: FC<ArchivePageProps> = ({
68
46
  groups,
69
47
  hasMore,
70
48
  nextCursor,
71
- type,
49
+ format,
50
+ featured,
72
51
  theme,
73
52
  }) => {
74
53
  const { t } = useLingui();
75
- const title = type
76
- ? getTypeLabelPlural(type)
54
+ const title = format
55
+ ? getFormatLabelPlural(format)
77
56
  : t({ message: "Archive", comment: "@context: Archive page title" });
78
57
 
79
58
  const PaginationComponent = theme?.Pagination ?? DefaultPagination;
80
59
 
81
60
  return (
82
- <div>
61
+ <div class="py-6">
83
62
  <header class="mb-8">
84
63
  <h1 class="text-2xl font-semibold">{title}</h1>
85
64
 
86
- {/* Type filter */}
65
+ {/* Format filter */}
87
66
  <nav class="flex flex-wrap gap-2 mt-4">
88
67
  <a
89
68
  href="/archive"
90
- class={`badge ${!type ? "badge-primary" : "badge-outline"}`}
69
+ class={`badge ${!format && !featured ? "badge-primary" : "badge-outline"}`}
91
70
  >
92
71
  {t({
93
72
  message: "All",
94
- comment: "@context: Archive filter - all types",
73
+ comment: "@context: Archive filter - all formats",
95
74
  })}
96
75
  </a>
97
- {POST_TYPES.filter((t) => t !== "page").map((typeKey) => (
76
+ {FORMATS.map((formatKey) => (
98
77
  <a
99
- key={typeKey}
100
- href={`/archive?type=${typeKey}`}
101
- class={`badge ${type === typeKey ? "badge-primary" : "badge-outline"}`}
78
+ key={formatKey}
79
+ href={`/archive?format=${formatKey}`}
80
+ class={`badge ${format === formatKey ? "badge-primary" : "badge-outline"}`}
102
81
  >
103
- {getTypeLabelPlural(typeKey)}
82
+ {getFormatLabelPlural(formatKey)}
104
83
  </a>
105
84
  ))}
85
+ <a
86
+ href="/archive?featured=true"
87
+ class={`badge ${featured ? "badge-primary" : "badge-outline"}`}
88
+ >
89
+ {t({
90
+ message: "Featured",
91
+ comment: "@context: Archive filter - featured posts",
92
+ })}
93
+ </a>
106
94
  </nav>
107
95
  </header>
108
96
 
@@ -120,9 +108,12 @@ export const ArchivePage: FC<ArchivePageProps> = ({
120
108
  <h2 class="text-lg font-medium mb-4 text-muted-foreground">
121
109
  {group.label}
122
110
  </h2>
123
- <div class="flex flex-col gap-3">
111
+ <div class="divide-y divide-border">
124
112
  {group.posts.map((post) => (
125
- <article key={post.id} class="flex items-baseline gap-4">
113
+ <article
114
+ key={post.id}
115
+ class="flex items-baseline gap-4 py-2.5"
116
+ >
126
117
  <time
127
118
  class="text-sm text-muted-foreground w-12 shrink-0"
128
119
  datetime={post.publishedAt}
@@ -132,12 +123,12 @@ export const ArchivePage: FC<ArchivePageProps> = ({
132
123
  <div class="flex-1 min-w-0">
133
124
  <a href={post.permalink} class="hover:underline">
134
125
  {post.title ||
135
- post.content?.slice(0, 80) ||
126
+ post.excerpt?.slice(0, 80) ||
136
127
  `Post #${post.id}`}
137
128
  </a>
138
- {!type && (
129
+ {!format && (
139
130
  <span class="ml-2 badge-outline text-xs">
140
- {getTypeLabel(post.type)}
131
+ {getFormatLabel(post.format)}
141
132
  </span>
142
133
  )}
143
134
  </div>
@@ -151,7 +142,13 @@ export const ArchivePage: FC<ArchivePageProps> = ({
151
142
 
152
143
  {/* Pagination */}
153
144
  <PaginationComponent
154
- baseUrl={type ? `/archive?type=${type}` : "/archive"}
145
+ baseUrl={
146
+ format
147
+ ? `/archive?format=${format}`
148
+ : featured
149
+ ? "/archive?featured=true"
150
+ : "/archive"
151
+ }
155
152
  hasMore={hasMore}
156
153
  nextCursor={nextCursor}
157
154
  />
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Threads Theme - Collection Page
3
+ *
4
+ * Collection header with divider-separated post list.
5
+ */
6
+
7
+ import type { FC } from "hono/jsx";
8
+ import { useLingui } from "@lingui/react/macro";
9
+ import type { CollectionPageProps } from "../../../types.js";
10
+
11
+ export const CollectionPage: FC<CollectionPageProps> = ({
12
+ collection,
13
+ posts,
14
+ }) => {
15
+ const { t } = useLingui();
16
+
17
+ return (
18
+ <div class="py-6">
19
+ <header class="mb-8">
20
+ <h1 class="text-2xl font-semibold">{collection.title}</h1>
21
+ {collection.description && (
22
+ <p class="text-muted-foreground mt-2">{collection.description}</p>
23
+ )}
24
+ </header>
25
+
26
+ <main>
27
+ {posts.length === 0 ? (
28
+ <p class="text-muted-foreground">
29
+ {t({
30
+ message: "No posts in this collection.",
31
+ comment: "@context: Empty state message",
32
+ })}
33
+ </p>
34
+ ) : (
35
+ <div class="divide-y divide-border">
36
+ {posts.map((post) => (
37
+ <article key={post.id} class="h-entry py-4">
38
+ {post.title && (
39
+ <h2 class="p-name text-lg font-medium mb-2">
40
+ <a href={post.permalink} class="u-url hover:underline">
41
+ {post.title}
42
+ </a>
43
+ </h2>
44
+ )}
45
+ <div
46
+ class="e-content prose prose-sm"
47
+ dangerouslySetInnerHTML={{ __html: post.bodyHtml || "" }}
48
+ />
49
+ <footer class="mt-2 text-sm text-muted-foreground">
50
+ <time class="dt-published" datetime={post.publishedAt}>
51
+ {post.publishedAtFormatted}
52
+ </time>
53
+ </footer>
54
+ </article>
55
+ ))}
56
+ </div>
57
+ )}
58
+ </main>
59
+ </div>
60
+ );
61
+ };
@@ -1,14 +1,13 @@
1
1
  /**
2
- * Default Home Page Component
2
+ * Threads Theme - Home Page
3
3
  *
4
- * Renders the timeline feed with thread previews.
5
- * Theme authors can replace this entirely via ThemeComponents.HomePage.
4
+ * Clean feed of posts separated by dividers.
6
5
  */
7
6
 
8
7
  import type { FC } from "hono/jsx";
9
8
  import { useLingui } from "@lingui/react/macro";
10
- import type { HomePageProps } from "../../types.js";
11
- import { TimelineFeed as DefaultTimelineFeed } from "../components/timeline/TimelineFeed.js";
9
+ import type { HomePageProps } from "../../../types.js";
10
+ import { TimelineFeed as DefaultTimelineFeed } from "../timeline/TimelineFeed.js";
12
11
 
13
12
  export const HomePage: FC<HomePageProps> = ({
14
13
  items,
@@ -23,7 +22,7 @@ export const HomePage: FC<HomePageProps> = ({
23
22
  return (
24
23
  <>
25
24
  {items.length === 0 ? (
26
- <p class="text-muted-foreground">
25
+ <p class="py-12 text-center text-muted-foreground">
27
26
  {t({
28
27
  message: "No posts yet.",
29
28
  comment: "@context: Empty state message on home page",
@@ -1,14 +1,13 @@
1
1
  /**
2
- * Default Post Page Component
2
+ * Threads Theme - Post Page
3
3
  *
4
- * Renders a single post with media gallery.
5
- * Theme authors can replace this entirely via ThemeComponents.PostPage.
4
+ * Single post view clean, no card border, with divider footer.
6
5
  */
7
6
 
8
7
  import type { FC } from "hono/jsx";
9
8
  import { useLingui } from "@lingui/react/macro";
10
- import type { PostPageProps } from "../../types.js";
11
- import { MediaGallery as DefaultMediaGallery } from "../components/MediaGallery.js";
9
+ import type { PostPageProps } from "../../../types.js";
10
+ import { MediaGallery as DefaultMediaGallery } from "../../../theme/index.js";
12
11
 
13
12
  export const PostPage: FC<PostPageProps> = ({ post, theme }) => {
14
13
  const { t } = useLingui();
@@ -16,17 +15,21 @@ export const PostPage: FC<PostPageProps> = ({ post, theme }) => {
16
15
  const Gallery = theme?.MediaGallery ?? DefaultMediaGallery;
17
16
 
18
17
  return (
19
- <article class="h-entry">
18
+ <article class="h-entry py-6">
20
19
  {post.title && (
21
20
  <h1 class="p-name text-2xl font-semibold mb-4">{post.title}</h1>
22
21
  )}
23
22
 
24
23
  <div
25
24
  class="e-content prose"
26
- dangerouslySetInnerHTML={{ __html: post.contentHtml || "" }}
25
+ dangerouslySetInnerHTML={{ __html: post.bodyHtml || "" }}
27
26
  />
28
27
 
29
- {post.media.length > 0 && <Gallery attachments={post.media} />}
28
+ {post.media.length > 0 && (
29
+ <div class="threads-media mt-4">
30
+ <Gallery attachments={post.media} />
31
+ </div>
32
+ )}
30
33
 
31
34
  <footer class="mt-6 pt-4 border-t text-sm text-muted-foreground">
32
35
  <time class="dt-published" datetime={post.publishedAt}>
@@ -1,14 +1,13 @@
1
1
  /**
2
- * Default Search Page Component
2
+ * Threads Theme - Search Page
3
3
  *
4
- * Renders search form and results with page-based pagination.
5
- * Theme authors can replace this entirely via ThemeComponents.SearchPage.
4
+ * Search form and results divider-separated instead of bordered cards.
6
5
  */
7
6
 
8
7
  import type { FC } from "hono/jsx";
9
8
  import { useLingui } from "@lingui/react/macro";
10
- import type { SearchPageProps } from "../../types.js";
11
- import { PagePagination as DefaultPagePagination } from "../components/Pagination.js";
9
+ import type { SearchPageProps } from "../../../types.js";
10
+ import { PagePagination as DefaultPagePagination } from "../../../theme/index.js";
12
11
 
13
12
  export const SearchPage: FC<SearchPageProps> = ({
14
13
  query,
@@ -27,7 +26,7 @@ export const SearchPage: FC<SearchPageProps> = ({
27
26
  const PaginationComponent = theme?.PagePagination ?? DefaultPagePagination;
28
27
 
29
28
  return (
30
- <div>
29
+ <div class="py-6">
31
30
  <h1 class="text-2xl font-semibold mb-6">{searchTitle}</h1>
32
31
 
33
32
  {/* Search form */}
@@ -83,16 +82,13 @@ export const SearchPage: FC<SearchPageProps> = ({
83
82
 
84
83
  {results.length > 0 && (
85
84
  <>
86
- <div class="flex flex-col gap-4">
85
+ <div class="divide-y divide-border">
87
86
  {results.map((result) => (
88
- <article
89
- key={result.post.id}
90
- class="p-4 rounded-lg border hover:border-primary"
91
- >
87
+ <article key={result.post.id} class="py-4">
92
88
  <a href={result.post.permalink} class="block">
93
89
  <h2 class="font-medium hover:underline">
94
90
  {result.post.title ||
95
- result.post.content?.slice(0, 60) ||
91
+ result.post.excerpt?.slice(0, 60) ||
96
92
  `Post #${result.post.id}`}
97
93
  </h2>
98
94
 
@@ -104,7 +100,7 @@ export const SearchPage: FC<SearchPageProps> = ({
104
100
  )}
105
101
 
106
102
  <footer class="flex items-center gap-2 mt-2 text-xs text-muted-foreground">
107
- <span class="badge-outline">{result.post.type}</span>
103
+ <span class="badge-outline">{result.post.format}</span>
108
104
  <time datetime={result.post.publishedAt}>
109
105
  {result.post.publishedAtFormatted}
110
106
  </time>
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Threads Theme - Single Page
3
+ *
4
+ * Custom page (type "page") view — clean centered content.
5
+ */
6
+
7
+ import type { FC } from "hono/jsx";
8
+ import type { SinglePageProps } from "../../../types.js";
9
+
10
+ export const SinglePage: FC<SinglePageProps> = ({ page }) => {
11
+ return (
12
+ <article class="h-entry py-6">
13
+ {page.title && (
14
+ <h1 class="p-name text-2xl font-semibold mb-6">{page.title}</h1>
15
+ )}
16
+
17
+ <div
18
+ class="e-content prose"
19
+ dangerouslySetInnerHTML={{ __html: page.bodyHtml || "" }}
20
+ />
21
+ </article>
22
+ );
23
+ };