@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
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Threads Theme - Site Layout
3
+ *
4
+ * Left icon sidebar (76px) on desktop, bottom tab bar (60px) on mobile.
5
+ * Gray page background (#fafafa) with white rounded content container.
6
+ * All dimensions match threads.com's --barcelona-* design tokens.
7
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
8
+ /** Map known URL paths to SVG icons. Size 26x26 matching Threads' nav icons. */ function NavIcon({ url, isActive }) {
9
+ const stroke = "currentColor";
10
+ const sw = isActive ? "2.25" : "1.75";
11
+ const cls = "size-[26px]";
12
+ // Home
13
+ if (url === "/") {
14
+ return /*#__PURE__*/ _jsx("svg", {
15
+ class: cls,
16
+ fill: "none",
17
+ viewBox: "0 0 24 24",
18
+ "stroke-width": sw,
19
+ stroke: stroke,
20
+ children: /*#__PURE__*/ _jsx("path", {
21
+ "stroke-linecap": "round",
22
+ "stroke-linejoin": "round",
23
+ d: "m2.25 12 8.954-8.955a1.126 1.126 0 0 1 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
24
+ })
25
+ });
26
+ }
27
+ // Search
28
+ if (url === "/search") {
29
+ return /*#__PURE__*/ _jsx("svg", {
30
+ class: cls,
31
+ fill: "none",
32
+ viewBox: "0 0 24 24",
33
+ "stroke-width": sw,
34
+ stroke: stroke,
35
+ children: /*#__PURE__*/ _jsx("path", {
36
+ "stroke-linecap": "round",
37
+ "stroke-linejoin": "round",
38
+ d: "m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
39
+ })
40
+ });
41
+ }
42
+ // Archive
43
+ if (url === "/archive") {
44
+ return /*#__PURE__*/ _jsx("svg", {
45
+ class: cls,
46
+ fill: "none",
47
+ viewBox: "0 0 24 24",
48
+ "stroke-width": sw,
49
+ stroke: stroke,
50
+ children: /*#__PURE__*/ _jsx("path", {
51
+ "stroke-linecap": "round",
52
+ "stroke-linejoin": "round",
53
+ d: "M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"
54
+ })
55
+ });
56
+ }
57
+ // RSS — common for /feed, /rss, /atom
58
+ if (url.match(/\/(feed|rss|atom)/)) {
59
+ return /*#__PURE__*/ _jsx("svg", {
60
+ class: cls,
61
+ fill: "none",
62
+ viewBox: "0 0 24 24",
63
+ "stroke-width": sw,
64
+ stroke: stroke,
65
+ children: /*#__PURE__*/ _jsx("path", {
66
+ "stroke-linecap": "round",
67
+ "stroke-linejoin": "round",
68
+ d: "M12.75 19.5v-.75a7.5 7.5 0 0 0-7.5-7.5H4.5m0-6.75h.75c7.87 0 14.25 6.38 14.25 14.25v.75M4.5 19.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
69
+ })
70
+ });
71
+ }
72
+ // External link
73
+ if (url.startsWith("http")) {
74
+ return /*#__PURE__*/ _jsx("svg", {
75
+ class: cls,
76
+ fill: "none",
77
+ viewBox: "0 0 24 24",
78
+ "stroke-width": sw,
79
+ stroke: stroke,
80
+ children: /*#__PURE__*/ _jsx("path", {
81
+ "stroke-linecap": "round",
82
+ "stroke-linejoin": "round",
83
+ d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"
84
+ })
85
+ });
86
+ }
87
+ // Default: generic page icon
88
+ return /*#__PURE__*/ _jsx("svg", {
89
+ class: cls,
90
+ fill: "none",
91
+ viewBox: "0 0 24 24",
92
+ "stroke-width": sw,
93
+ stroke: stroke,
94
+ children: /*#__PURE__*/ _jsx("path", {
95
+ "stroke-linecap": "round",
96
+ "stroke-linejoin": "round",
97
+ d: "M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
98
+ })
99
+ });
100
+ }
101
+ function SidebarLink({ link }) {
102
+ return /*#__PURE__*/ _jsx("a", {
103
+ href: link.url,
104
+ class: `threads-sidebar-link ${link.isActive ? "threads-sidebar-link-active" : ""}`,
105
+ title: link.label,
106
+ ...link.isExternal ? {
107
+ target: "_blank",
108
+ rel: "noopener noreferrer"
109
+ } : {},
110
+ children: /*#__PURE__*/ _jsx(NavIcon, {
111
+ url: link.url,
112
+ isActive: link.isActive
113
+ })
114
+ });
115
+ }
116
+ function MobileTabLink({ link }) {
117
+ return /*#__PURE__*/ _jsx("a", {
118
+ href: link.url,
119
+ class: `threads-mobile-tab ${link.isActive ? "threads-mobile-tab-active" : ""}`,
120
+ ...link.isExternal ? {
121
+ target: "_blank",
122
+ rel: "noopener noreferrer"
123
+ } : {},
124
+ children: /*#__PURE__*/ _jsx(NavIcon, {
125
+ url: link.url,
126
+ isActive: link.isActive
127
+ })
128
+ });
129
+ }
130
+ export const ThreadsSiteLayout = ({ siteName, links, children })=>{
131
+ return /*#__PURE__*/ _jsxs("div", {
132
+ class: "threads-page",
133
+ children: [
134
+ /*#__PURE__*/ _jsxs("aside", {
135
+ class: "threads-sidebar",
136
+ children: [
137
+ /*#__PURE__*/ _jsx("a", {
138
+ href: "/",
139
+ class: "threads-logo",
140
+ title: siteName,
141
+ children: /*#__PURE__*/ _jsx("span", {
142
+ class: "text-2xl font-black leading-none",
143
+ children: "@"
144
+ })
145
+ }),
146
+ /*#__PURE__*/ _jsx("nav", {
147
+ class: "flex flex-1 flex-col items-center gap-1",
148
+ children: links.map((link)=>/*#__PURE__*/ _jsx(SidebarLink, {
149
+ link: link
150
+ }, link.id))
151
+ })
152
+ ]
153
+ }),
154
+ /*#__PURE__*/ _jsx("main", {
155
+ class: "threads-main",
156
+ children: /*#__PURE__*/ _jsx("div", {
157
+ class: "threads-container",
158
+ children: /*#__PURE__*/ _jsx("div", {
159
+ class: "threads-content",
160
+ children: children
161
+ })
162
+ })
163
+ }),
164
+ /*#__PURE__*/ _jsx("nav", {
165
+ class: "threads-mobile-tabs",
166
+ children: links.map((link)=>/*#__PURE__*/ _jsx(MobileTabLink, {
167
+ link: link
168
+ }, link.id))
169
+ })
170
+ ]
171
+ });
172
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Threads Theme
3
+ *
4
+ * A clean, centered timeline theme inspired by Threads.net.
5
+ * Posts separated by thin dividers, no cards, with thread connector lines.
6
+ *
7
+ * This is the default theme for Jant.
8
+ */ // Layout
9
+ import { ThreadsSiteLayout } from "./ThreadsSiteLayout.js";
10
+ // Pages
11
+ import { HomePage } from "./pages/HomePage.js";
12
+ import { PostPage } from "./pages/PostPage.js";
13
+ import { SinglePage } from "./pages/SinglePage.js";
14
+ import { ArchivePage } from "./pages/ArchivePage.js";
15
+ import { SearchPage } from "./pages/SearchPage.js";
16
+ import { CollectionPage } from "./pages/CollectionPage.js";
17
+ // Timeline
18
+ import { NoteCard } from "./timeline/NoteCard.js";
19
+ import { LinkCard } from "./timeline/LinkCard.js";
20
+ import { QuoteCard } from "./timeline/QuoteCard.js";
21
+ import { ThreadPreview } from "./timeline/ThreadPreview.js";
22
+ import { TimelineFeed } from "./timeline/TimelineFeed.js";
23
+ import { TimelineLoadMore } from "./timeline/TimelineLoadMore.js";
24
+ import { timelineMore } from "./timeline/timelineMore.js";
25
+ /**
26
+ * Create the threads theme configuration.
27
+ *
28
+ * @param options - Optional overrides for components, CSS variables, or color themes
29
+ * @returns A JantTheme configuration object
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { createApp } from "@jant/core";
34
+ * import { threadsTheme } from "@jant/core";
35
+ *
36
+ * export default createApp({
37
+ * theme: threadsTheme(),
38
+ * });
39
+ * ```
40
+ */ export function theme(options) {
41
+ return {
42
+ name: "threads",
43
+ components: {
44
+ SiteLayout: ThreadsSiteLayout,
45
+ HomePage,
46
+ PostPage,
47
+ SinglePage,
48
+ ArchivePage,
49
+ SearchPage,
50
+ CollectionPage,
51
+ NoteCard,
52
+ LinkCard,
53
+ QuoteCard,
54
+ ThreadPreview,
55
+ TimelineFeed,
56
+ TimelineLoadMore,
57
+ ...options?.components
58
+ },
59
+ timelineMore,
60
+ cssVariables: {
61
+ ...options?.cssVariables
62
+ },
63
+ colorThemes: options?.colorThemes
64
+ };
65
+ }
66
+ // Re-export individual components for wrapping/extending
67
+ export { ThreadsSiteLayout } from "./ThreadsSiteLayout.js";
68
+ export { HomePage } from "./pages/HomePage.js";
69
+ export { PostPage } from "./pages/PostPage.js";
70
+ export { SinglePage } from "./pages/SinglePage.js";
71
+ export { ArchivePage } from "./pages/ArchivePage.js";
72
+ export { SearchPage } from "./pages/SearchPage.js";
73
+ export { CollectionPage } from "./pages/CollectionPage.js";
74
+ export { NoteCard } from "./timeline/NoteCard.js";
75
+ export { LinkCard } from "./timeline/LinkCard.js";
76
+ export { QuoteCard } from "./timeline/QuoteCard.js";
77
+ export { ThreadPreview } from "./timeline/ThreadPreview.js";
78
+ export { TimelineFeed } from "./timeline/TimelineFeed.js";
79
+ export { TimelineLoadMore } from "./timeline/TimelineLoadMore.js";
80
+ export { TimelineItem, TimelineItemFromPost } from "./timeline/TimelineItem.js";
81
+ export { timelineMore } from "./timeline/timelineMore.js";
@@ -1,23 +1,18 @@
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
  */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
7
6
  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) {
7
+ import { FORMATS } from "../../../types.js";
8
+ import { Pagination as DefaultPagination } from "../../../theme/index.js";
9
+ function getFormatLabel(format) {
11
10
  const { i18n: $__i18n, _: $__ } = $_useLingui();
12
11
  const labels = {
13
12
  note: $__i18n._({
14
13
  id: "KiJn9B",
15
14
  message: "Note"
16
15
  }),
17
- article: $__i18n._({
18
- id: "f6e0Ry",
19
- message: "Article"
20
- }),
21
16
  link: $__i18n._({
22
17
  id: "yzF66j",
23
18
  message: "Link"
@@ -25,29 +20,17 @@ function getTypeLabel(type) {
25
20
  quote: $__i18n._({
26
21
  id: "ZhhOwV",
27
22
  message: "Quote"
28
- }),
29
- image: $__i18n._({
30
- id: "hG89Ed",
31
- message: "Image"
32
- }),
33
- page: $__i18n._({
34
- id: "6WdDG7",
35
- message: "Page"
36
23
  })
37
24
  };
38
- return labels[type] ?? type;
25
+ return labels[format] ?? format;
39
26
  }
40
- function getTypeLabelPlural(type) {
27
+ function getFormatLabelPlural(format) {
41
28
  const { i18n: $__i18n, _: $__ } = $_useLingui();
42
29
  const labels = {
43
30
  note: $__i18n._({
44
31
  id: "1DBGsz",
45
32
  message: "Notes"
46
33
  }),
47
- article: $__i18n._({
48
- id: "Tt5T6+",
49
- message: "Articles"
50
- }),
51
34
  link: $__i18n._({
52
35
  id: "Rj01Fz",
53
36
  message: "Links"
@@ -55,26 +38,19 @@ function getTypeLabelPlural(type) {
55
38
  quote: $__i18n._({
56
39
  id: "eWLklq",
57
40
  message: "Quotes"
58
- }),
59
- image: $__i18n._({
60
- id: "an5hVd",
61
- message: "Images"
62
- }),
63
- page: $__i18n._({
64
- id: "wRR604",
65
- message: "Pages"
66
41
  })
67
42
  };
68
- return labels[type] ?? `${type}s`;
43
+ return labels[format] ?? `${format}s`;
69
44
  }
70
- export const ArchivePage = ({ groups, hasMore, nextCursor, type, theme })=>{
45
+ export const ArchivePage = ({ groups, hasMore, nextCursor, format, featured, theme })=>{
71
46
  const { i18n: $__i18n, _: $__ } = $_useLingui();
72
- const title = type ? getTypeLabelPlural(type) : $__i18n._({
47
+ const title = format ? getFormatLabelPlural(format) : $__i18n._({
73
48
  id: "B495Gs",
74
49
  message: "Archive"
75
50
  });
76
51
  const PaginationComponent = theme?.Pagination ?? DefaultPagination;
77
52
  return /*#__PURE__*/ _jsxs("div", {
53
+ class: "py-6",
78
54
  children: [
79
55
  /*#__PURE__*/ _jsxs("header", {
80
56
  class: "mb-8",
@@ -88,17 +64,25 @@ export const ArchivePage = ({ groups, hasMore, nextCursor, type, theme })=>{
88
64
  children: [
89
65
  /*#__PURE__*/ _jsx("a", {
90
66
  href: "/archive",
91
- class: `badge ${!type ? "badge-primary" : "badge-outline"}`,
67
+ class: `badge ${!format && !featured ? "badge-primary" : "badge-outline"}`,
92
68
  children: $__i18n._({
93
69
  id: "N40H+G",
94
70
  message: "All"
95
71
  })
96
72
  }),
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))
73
+ FORMATS.map((formatKey)=>/*#__PURE__*/ _jsx("a", {
74
+ href: `/archive?format=${formatKey}`,
75
+ class: `badge ${format === formatKey ? "badge-primary" : "badge-outline"}`,
76
+ children: getFormatLabelPlural(formatKey)
77
+ }, formatKey)),
78
+ /*#__PURE__*/ _jsx("a", {
79
+ href: "/archive?featured=true",
80
+ class: `badge ${featured ? "badge-primary" : "badge-outline"}`,
81
+ children: $__i18n._({
82
+ id: "FkMol5",
83
+ message: "Featured"
84
+ })
85
+ })
102
86
  ]
103
87
  })
104
88
  ]
@@ -118,9 +102,9 @@ export const ArchivePage = ({ groups, hasMore, nextCursor, type, theme })=>{
118
102
  children: group.label
119
103
  }),
120
104
  /*#__PURE__*/ _jsx("div", {
121
- class: "flex flex-col gap-3",
105
+ class: "divide-y divide-border",
122
106
  children: group.posts.map((post)=>/*#__PURE__*/ _jsxs("article", {
123
- class: "flex items-baseline gap-4",
107
+ class: "flex items-baseline gap-4 py-2.5",
124
108
  children: [
125
109
  /*#__PURE__*/ _jsx("time", {
126
110
  class: "text-sm text-muted-foreground w-12 shrink-0",
@@ -133,11 +117,11 @@ export const ArchivePage = ({ groups, hasMore, nextCursor, type, theme })=>{
133
117
  /*#__PURE__*/ _jsx("a", {
134
118
  href: post.permalink,
135
119
  class: "hover:underline",
136
- children: post.title || post.content?.slice(0, 80) || `Post #${post.id}`
120
+ children: post.title || post.excerpt?.slice(0, 80) || `Post #${post.id}`
137
121
  }),
138
- !type && /*#__PURE__*/ _jsx("span", {
122
+ !format && /*#__PURE__*/ _jsx("span", {
139
123
  class: "ml-2 badge-outline text-xs",
140
- children: getTypeLabel(post.type)
124
+ children: getFormatLabel(post.format)
141
125
  })
142
126
  ]
143
127
  })
@@ -148,7 +132,7 @@ export const ArchivePage = ({ groups, hasMore, nextCursor, type, theme })=>{
148
132
  }, `${group.year}-${group.month}`))
149
133
  }),
150
134
  /*#__PURE__*/ _jsx(PaginationComponent, {
151
- baseUrl: type ? `/archive?type=${type}` : "/archive",
135
+ baseUrl: format ? `/archive?format=${format}` : featured ? "/archive?featured=true" : "/archive",
152
136
  hasMore: hasMore,
153
137
  nextCursor: nextCursor
154
138
  })
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Threads Theme - Collection Page
3
+ *
4
+ * Collection header with divider-separated post list.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
7
+ export const CollectionPage = ({ collection, posts })=>{
8
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
9
+ return /*#__PURE__*/ _jsxs("div", {
10
+ class: "py-6",
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
+ children: posts.length === 0 ? /*#__PURE__*/ _jsx("p", {
27
+ class: "text-muted-foreground",
28
+ children: $__i18n._({
29
+ id: "J4FNfC",
30
+ message: "No posts in this collection."
31
+ })
32
+ }) : /*#__PURE__*/ _jsx("div", {
33
+ class: "divide-y divide-border",
34
+ children: posts.map((post)=>/*#__PURE__*/ _jsxs("article", {
35
+ class: "h-entry py-4",
36
+ children: [
37
+ post.title && /*#__PURE__*/ _jsx("h2", {
38
+ class: "p-name text-lg font-medium mb-2",
39
+ children: /*#__PURE__*/ _jsx("a", {
40
+ href: post.permalink,
41
+ class: "u-url hover:underline",
42
+ children: post.title
43
+ })
44
+ }),
45
+ /*#__PURE__*/ _jsx("div", {
46
+ class: "e-content prose prose-sm",
47
+ dangerouslySetInnerHTML: {
48
+ __html: post.bodyHtml || ""
49
+ }
50
+ }),
51
+ /*#__PURE__*/ _jsx("footer", {
52
+ class: "mt-2 text-sm text-muted-foreground",
53
+ children: /*#__PURE__*/ _jsx("time", {
54
+ class: "dt-published",
55
+ datetime: post.publishedAt,
56
+ children: post.publishedAtFormatted
57
+ })
58
+ })
59
+ ]
60
+ }, post.id))
61
+ })
62
+ })
63
+ ]
64
+ });
65
+ };
@@ -1,17 +1,16 @@
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
  */ import { jsx as _jsx, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
7
6
  import { useLingui as $_useLingui } from "@jant/core/i18n";
8
- import { TimelineFeed as DefaultTimelineFeed } from "../components/timeline/TimelineFeed.js";
7
+ import { TimelineFeed as DefaultTimelineFeed } from "../timeline/TimelineFeed.js";
9
8
  export const HomePage = ({ items, hasMore, nextCursor, theme })=>{
10
9
  const { i18n: $__i18n, _: $__ } = $_useLingui();
11
10
  const Feed = theme?.TimelineFeed ?? DefaultTimelineFeed;
12
11
  return /*#__PURE__*/ _jsx(_Fragment, {
13
12
  children: items.length === 0 ? /*#__PURE__*/ _jsx("p", {
14
- class: "text-muted-foreground",
13
+ class: "py-12 text-center text-muted-foreground",
15
14
  children: $__i18n._({
16
15
  id: "ODiSoW",
17
16
  message: "No posts yet."
@@ -1,16 +1,15 @@
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
  */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
7
6
  import { useLingui as $_useLingui } from "@jant/core/i18n";
8
- import { MediaGallery as DefaultMediaGallery } from "../components/MediaGallery.js";
7
+ import { MediaGallery as DefaultMediaGallery } from "../../../theme/index.js";
9
8
  export const PostPage = ({ post, theme })=>{
10
9
  const { i18n: $__i18n, _: $__ } = $_useLingui();
11
10
  const Gallery = theme?.MediaGallery ?? DefaultMediaGallery;
12
11
  return /*#__PURE__*/ _jsxs("article", {
13
- class: "h-entry",
12
+ class: "h-entry py-6",
14
13
  children: [
15
14
  post.title && /*#__PURE__*/ _jsx("h1", {
16
15
  class: "p-name text-2xl font-semibold mb-4",
@@ -19,11 +18,14 @@ export const PostPage = ({ post, theme })=>{
19
18
  /*#__PURE__*/ _jsx("div", {
20
19
  class: "e-content prose",
21
20
  dangerouslySetInnerHTML: {
22
- __html: post.contentHtml || ""
21
+ __html: post.bodyHtml || ""
23
22
  }
24
23
  }),
25
- post.media.length > 0 && /*#__PURE__*/ _jsx(Gallery, {
26
- attachments: post.media
24
+ post.media.length > 0 && /*#__PURE__*/ _jsx("div", {
25
+ class: "threads-media mt-4",
26
+ children: /*#__PURE__*/ _jsx(Gallery, {
27
+ attachments: post.media
28
+ })
27
29
  }),
28
30
  /*#__PURE__*/ _jsxs("footer", {
29
31
  class: "mt-6 pt-4 border-t text-sm text-muted-foreground",
@@ -1,11 +1,10 @@
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
  */ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
7
6
  import { useLingui as $_useLingui } from "@jant/core/i18n";
8
- import { PagePagination as DefaultPagePagination } from "../components/Pagination.js";
7
+ import { PagePagination as DefaultPagePagination } from "../../../theme/index.js";
9
8
  export const SearchPage = ({ query, results, error, hasMore, page, theme })=>{
10
9
  const { i18n: $__i18n, _: $__ } = $_useLingui();
11
10
  const searchTitle = $__i18n._({
@@ -14,6 +13,7 @@ export const SearchPage = ({ query, results, error, hasMore, page, theme })=>{
14
13
  });
15
14
  const PaginationComponent = theme?.PagePagination ?? DefaultPagePagination;
16
15
  return /*#__PURE__*/ _jsxs("div", {
16
+ class: "py-6",
17
17
  children: [
18
18
  /*#__PURE__*/ _jsx("h1", {
19
19
  class: "text-2xl font-semibold mb-6",
@@ -72,16 +72,16 @@ export const SearchPage = ({ query, results, error, hasMore, page, theme })=>{
72
72
  results.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
73
73
  children: [
74
74
  /*#__PURE__*/ _jsx("div", {
75
- class: "flex flex-col gap-4",
75
+ class: "divide-y divide-border",
76
76
  children: results.map((result)=>/*#__PURE__*/ _jsx("article", {
77
- class: "p-4 rounded-lg border hover:border-primary",
77
+ class: "py-4",
78
78
  children: /*#__PURE__*/ _jsxs("a", {
79
79
  href: result.post.permalink,
80
80
  class: "block",
81
81
  children: [
82
82
  /*#__PURE__*/ _jsx("h2", {
83
83
  class: "font-medium hover:underline",
84
- children: result.post.title || result.post.content?.slice(0, 60) || `Post #${result.post.id}`
84
+ children: result.post.title || result.post.excerpt?.slice(0, 60) || `Post #${result.post.id}`
85
85
  }),
86
86
  result.snippet && /*#__PURE__*/ _jsx("p", {
87
87
  class: "text-sm text-muted-foreground mt-2 line-clamp-2",
@@ -94,7 +94,7 @@ export const SearchPage = ({ query, results, error, hasMore, page, theme })=>{
94
94
  children: [
95
95
  /*#__PURE__*/ _jsx("span", {
96
96
  class: "badge-outline",
97
- children: result.post.type
97
+ children: result.post.format
98
98
  }),
99
99
  /*#__PURE__*/ _jsx("time", {
100
100
  datetime: result.post.publishedAt,
@@ -1,21 +1,20 @@
1
1
  /**
2
- * Default Single Page Component
2
+ * Threads Theme - Single Page
3
3
  *
4
- * Renders a custom page (type "page").
5
- * Theme authors can replace this entirely via ThemeComponents.SinglePage.
4
+ * Custom page (type "page") view — clean centered content.
6
5
  */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
7
6
  export const SinglePage = ({ page })=>{
8
7
  return /*#__PURE__*/ _jsxs("article", {
9
- class: "h-entry",
8
+ class: "h-entry py-6",
10
9
  children: [
11
10
  page.title && /*#__PURE__*/ _jsx("h1", {
12
- class: "p-name text-3xl font-semibold mb-6",
11
+ class: "p-name text-2xl font-semibold mb-6",
13
12
  children: page.title
14
13
  }),
15
14
  /*#__PURE__*/ _jsx("div", {
16
15
  class: "e-content prose",
17
16
  dangerouslySetInnerHTML: {
18
- __html: page.contentHtml || ""
17
+ __html: page.bodyHtml || ""
19
18
  }
20
19
  })
21
20
  ]