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