@jant/core 0.3.23 → 0.3.25

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 (248) hide show
  1. package/dist/app.js +50 -26
  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 -11
  7. package/dist/lib/constants.js +2 -4
  8. package/dist/lib/excerpt.js +76 -0
  9. package/dist/lib/feed.js +18 -7
  10. package/dist/lib/nav-reorder.js +1 -1
  11. package/dist/lib/navigation.js +30 -6
  12. package/dist/lib/pagination.js +44 -0
  13. package/dist/lib/render.js +7 -11
  14. package/dist/lib/schemas.js +80 -38
  15. package/dist/lib/theme.js +4 -4
  16. package/dist/lib/time.js +56 -1
  17. package/dist/lib/timeline.js +95 -0
  18. package/dist/lib/view.js +61 -72
  19. package/dist/routes/api/collections.js +124 -0
  20. package/dist/routes/api/nav-items.js +104 -0
  21. package/dist/routes/api/pages.js +91 -0
  22. package/dist/routes/api/posts.js +27 -33
  23. package/dist/routes/api/search.js +4 -5
  24. package/dist/routes/api/settings.js +68 -0
  25. package/dist/routes/api/upload.js +13 -13
  26. package/dist/routes/compose.js +48 -0
  27. package/dist/routes/dash/collections.js +24 -42
  28. package/dist/routes/dash/index.js +3 -3
  29. package/dist/routes/dash/media.js +2 -2
  30. package/dist/routes/dash/pages.js +440 -106
  31. package/dist/routes/dash/posts.js +27 -37
  32. package/dist/routes/dash/redirects.js +2 -2
  33. package/dist/routes/dash/settings.js +79 -5
  34. package/dist/routes/feed/rss.js +4 -6
  35. package/dist/routes/feed/sitemap.js +11 -8
  36. package/dist/routes/pages/archive.js +13 -15
  37. package/dist/routes/pages/collection.js +12 -9
  38. package/dist/routes/pages/collections.js +28 -0
  39. package/dist/routes/pages/featured.js +32 -0
  40. package/dist/routes/pages/home.js +19 -68
  41. package/dist/routes/pages/page.js +57 -29
  42. package/dist/routes/pages/post.js +7 -17
  43. package/dist/routes/pages/search.js +5 -9
  44. package/dist/services/collection.js +52 -64
  45. package/dist/services/index.js +5 -3
  46. package/dist/services/navigation.js +29 -53
  47. package/dist/services/page.js +84 -0
  48. package/dist/services/post.js +102 -69
  49. package/dist/services/search.js +24 -18
  50. package/dist/types.js +24 -40
  51. package/dist/ui/compose/ComposeDialog.js +452 -0
  52. package/dist/ui/compose/ComposePrompt.js +55 -0
  53. package/dist/{theme/components/TypeBadge.js → ui/dash/FormatBadge.js} +3 -15
  54. package/dist/{theme/components → ui/dash}/PageForm.js +15 -15
  55. package/dist/{theme/components → ui/dash}/PostForm.js +117 -137
  56. package/dist/{theme/components → ui/dash}/PostList.js +18 -13
  57. package/dist/ui/dash/StatusBadge.js +46 -0
  58. package/dist/{theme/components → ui/dash}/index.js +3 -6
  59. package/dist/ui/feed/LinkCard.js +72 -0
  60. package/dist/ui/feed/NoteCard.js +58 -0
  61. package/dist/{themes/minimal/timeline → ui/feed}/QuoteCard.js +29 -14
  62. package/dist/{themes/minimal/timeline → ui/feed}/ThreadPreview.js +20 -18
  63. package/dist/ui/feed/TimelineFeed.js +41 -0
  64. package/dist/ui/feed/TimelineItem.js +27 -0
  65. package/dist/{theme → ui}/layouts/BaseLayout.js +10 -0
  66. package/dist/{theme → ui}/layouts/DashLayout.js +0 -8
  67. package/dist/ui/layouts/SiteLayout.js +141 -0
  68. package/dist/{themes/minimal → ui}/pages/ArchivePage.js +37 -50
  69. package/dist/ui/pages/CollectionPage.js +70 -0
  70. package/dist/ui/pages/CollectionsPage.js +76 -0
  71. package/dist/ui/pages/FeaturedPage.js +24 -0
  72. package/dist/ui/pages/HomePage.js +24 -0
  73. package/dist/{themes/minimal → ui}/pages/PostPage.js +20 -12
  74. package/dist/{themes/minimal → ui}/pages/SearchPage.js +19 -18
  75. package/dist/{themes/minimal → ui}/pages/SinglePage.js +5 -4
  76. package/dist/ui/shared/MediaGallery.js +35 -0
  77. package/dist/{theme/components → ui/shared}/Pagination.js +41 -2
  78. package/dist/{theme/components → ui/shared}/ThreadView.js +3 -3
  79. package/dist/ui/shared/index.js +5 -0
  80. package/package.json +2 -9
  81. package/src/__tests__/helpers/app.ts +4 -0
  82. package/src/__tests__/helpers/db.ts +53 -73
  83. package/src/app.tsx +56 -28
  84. package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
  85. package/src/db/migrations/0006_rename_slug_to_path.sql +5 -0
  86. package/src/db/migrations/meta/_journal.json +14 -0
  87. package/src/db/schema.ts +63 -46
  88. package/src/i18n/locales/en.po +443 -240
  89. package/src/i18n/locales/en.ts +1 -1
  90. package/src/i18n/locales/zh-Hans.po +443 -240
  91. package/src/i18n/locales/zh-Hans.ts +1 -1
  92. package/src/i18n/locales/zh-Hant.po +443 -240
  93. package/src/i18n/locales/zh-Hant.ts +1 -1
  94. package/src/index.ts +29 -42
  95. package/src/lib/__tests__/excerpt.test.ts +125 -0
  96. package/src/lib/__tests__/schemas.test.ts +201 -99
  97. package/src/lib/__tests__/time.test.ts +62 -0
  98. package/src/{routes/api → lib}/__tests__/timeline.test.ts +81 -75
  99. package/src/lib/__tests__/view.test.ts +204 -50
  100. package/src/lib/constants.ts +2 -4
  101. package/src/lib/excerpt.ts +87 -0
  102. package/src/lib/feed.ts +22 -7
  103. package/src/lib/nav-reorder.ts +1 -1
  104. package/src/lib/navigation.ts +45 -8
  105. package/src/lib/pagination.ts +50 -0
  106. package/src/lib/render.tsx +7 -14
  107. package/src/lib/schemas.ts +119 -51
  108. package/src/lib/theme.ts +5 -5
  109. package/src/lib/time.ts +64 -0
  110. package/src/lib/timeline.ts +141 -0
  111. package/src/lib/view.ts +80 -82
  112. package/src/preset.css +46 -0
  113. package/src/routes/__tests__/compose.test.ts +199 -0
  114. package/src/routes/api/__tests__/collections.test.ts +249 -0
  115. package/src/routes/api/__tests__/nav-items.test.ts +222 -0
  116. package/src/routes/api/__tests__/pages.test.ts +218 -0
  117. package/src/routes/api/__tests__/posts.test.ts +50 -108
  118. package/src/routes/api/__tests__/search.test.ts +2 -3
  119. package/src/routes/api/__tests__/settings.test.ts +132 -0
  120. package/src/routes/api/collections.ts +143 -0
  121. package/src/routes/api/nav-items.ts +115 -0
  122. package/src/routes/api/pages.ts +101 -0
  123. package/src/routes/api/posts.ts +28 -28
  124. package/src/routes/api/search.ts +3 -3
  125. package/src/routes/api/settings.ts +91 -0
  126. package/src/routes/api/upload.ts +16 -6
  127. package/src/routes/compose.ts +63 -0
  128. package/src/routes/dash/__tests__/pages.test.ts +225 -0
  129. package/src/routes/dash/collections.tsx +20 -42
  130. package/src/routes/dash/index.tsx +3 -3
  131. package/src/routes/dash/media.tsx +2 -2
  132. package/src/routes/dash/pages.tsx +480 -122
  133. package/src/routes/dash/posts.tsx +42 -54
  134. package/src/routes/dash/redirects.tsx +2 -2
  135. package/src/routes/dash/settings.tsx +83 -5
  136. package/src/routes/feed/rss.ts +4 -3
  137. package/src/routes/feed/sitemap.ts +15 -5
  138. package/src/routes/pages/__tests__/collections.test.ts +94 -0
  139. package/src/routes/pages/__tests__/featured.test.ts +94 -0
  140. package/src/routes/pages/archive.tsx +15 -15
  141. package/src/routes/pages/collection.tsx +16 -9
  142. package/src/routes/pages/collections.tsx +36 -0
  143. package/src/routes/pages/featured.tsx +38 -0
  144. package/src/routes/pages/home.tsx +21 -92
  145. package/src/routes/pages/page.tsx +62 -27
  146. package/src/routes/pages/post.tsx +6 -18
  147. package/src/routes/pages/search.tsx +3 -7
  148. package/src/services/__tests__/collection.test.ts +257 -158
  149. package/src/services/__tests__/media.test.ts +18 -18
  150. package/src/services/__tests__/navigation.test.ts +161 -87
  151. package/src/services/__tests__/page.test.ts +106 -0
  152. package/src/services/__tests__/post-timeline.test.ts +92 -88
  153. package/src/services/__tests__/post.test.ts +432 -197
  154. package/src/services/__tests__/search.test.ts +19 -25
  155. package/src/services/collection.ts +71 -113
  156. package/src/services/index.ts +9 -8
  157. package/src/services/navigation.ts +38 -71
  158. package/src/services/page.ts +136 -0
  159. package/src/services/post.ts +141 -101
  160. package/src/services/search.ts +38 -27
  161. package/src/styles/tokens.css +47 -0
  162. package/src/styles/ui.css +491 -0
  163. package/src/types.ts +212 -198
  164. package/src/ui/compose/ComposeDialog.tsx +395 -0
  165. package/src/ui/compose/ComposePrompt.tsx +55 -0
  166. package/src/ui/dash/FormatBadge.tsx +28 -0
  167. package/src/{theme/components → ui/dash}/PageForm.tsx +21 -21
  168. package/src/{theme/components → ui/dash}/PostForm.tsx +110 -131
  169. package/src/ui/dash/PostList.tsx +101 -0
  170. package/src/ui/dash/StatusBadge.tsx +61 -0
  171. package/src/ui/dash/index.ts +10 -0
  172. package/src/ui/feed/LinkCard.tsx +72 -0
  173. package/src/ui/feed/NoteCard.tsx +63 -0
  174. package/src/ui/feed/QuoteCard.tsx +68 -0
  175. package/src/ui/feed/ThreadPreview.tsx +48 -0
  176. package/src/ui/feed/TimelineFeed.tsx +49 -0
  177. package/src/ui/feed/TimelineItem.tsx +45 -0
  178. package/src/{theme → ui}/layouts/BaseLayout.tsx +11 -1
  179. package/src/{theme → ui}/layouts/DashLayout.tsx +0 -10
  180. package/src/ui/layouts/SiteLayout.tsx +150 -0
  181. package/src/ui/pages/ArchivePage.tsx +162 -0
  182. package/src/ui/pages/CollectionPage.tsx +70 -0
  183. package/src/ui/pages/CollectionsPage.tsx +73 -0
  184. package/src/ui/pages/FeaturedPage.tsx +31 -0
  185. package/src/ui/pages/HomePage.tsx +37 -0
  186. package/src/ui/pages/PostPage.tsx +56 -0
  187. package/src/{themes/minimal → ui}/pages/SearchPage.tsx +24 -20
  188. package/src/{themes/minimal → ui}/pages/SinglePage.tsx +5 -5
  189. package/src/ui/shared/MediaGallery.tsx +59 -0
  190. package/src/{theme/components → ui/shared}/Pagination.tsx +67 -4
  191. package/src/{theme/components → ui/shared}/ThreadView.tsx +6 -3
  192. package/src/ui/shared/__tests__/pagination.test.ts +46 -0
  193. package/src/ui/shared/index.ts +12 -0
  194. package/bin/jant.js +0 -185
  195. package/dist/lib/theme-components.js +0 -49
  196. package/dist/routes/api/timeline.js +0 -120
  197. package/dist/routes/dash/navigation.js +0 -288
  198. package/dist/theme/components/MediaGallery.js +0 -107
  199. package/dist/theme/components/VisibilityBadge.js +0 -37
  200. package/dist/theme/index.js +0 -18
  201. package/dist/theme/layouts/index.js +0 -2
  202. package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
  203. package/dist/themes/minimal/index.js +0 -65
  204. package/dist/themes/minimal/pages/CollectionPage.js +0 -65
  205. package/dist/themes/minimal/pages/HomePage.js +0 -25
  206. package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
  207. package/dist/themes/minimal/timeline/ImageCard.js +0 -67
  208. package/dist/themes/minimal/timeline/LinkCard.js +0 -47
  209. package/dist/themes/minimal/timeline/NoteCard.js +0 -34
  210. package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
  211. package/dist/themes/minimal/timeline/TimelineItem.js +0 -44
  212. package/src/lib/__tests__/theme-components.test.ts +0 -126
  213. package/src/lib/theme-components.ts +0 -68
  214. package/src/routes/api/timeline.tsx +0 -159
  215. package/src/routes/dash/navigation.tsx +0 -316
  216. package/src/theme/components/MediaGallery.tsx +0 -128
  217. package/src/theme/components/PostList.tsx +0 -92
  218. package/src/theme/components/TypeBadge.tsx +0 -37
  219. package/src/theme/components/VisibilityBadge.tsx +0 -45
  220. package/src/theme/components/index.ts +0 -23
  221. package/src/theme/index.ts +0 -22
  222. package/src/theme/layouts/index.ts +0 -7
  223. package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
  224. package/src/themes/minimal/index.ts +0 -83
  225. package/src/themes/minimal/pages/ArchivePage.tsx +0 -157
  226. package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
  227. package/src/themes/minimal/pages/HomePage.tsx +0 -41
  228. package/src/themes/minimal/pages/PostPage.tsx +0 -43
  229. package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
  230. package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
  231. package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
  232. package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
  233. package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
  234. package/src/themes/minimal/timeline/ThreadPreview.tsx +0 -47
  235. package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
  236. package/src/themes/minimal/timeline/TimelineItem.tsx +0 -75
  237. /package/dist/{theme → ui}/color-themes.js +0 -0
  238. /package/dist/{theme/components → ui/dash}/ActionButtons.js +0 -0
  239. /package/dist/{theme/components → ui/dash}/CrudPageHeader.js +0 -0
  240. /package/dist/{theme/components → ui/dash}/DangerZone.js +0 -0
  241. /package/dist/{theme/components → ui/dash}/ListItemRow.js +0 -0
  242. /package/dist/{theme/components → ui/shared}/EmptyState.js +0 -0
  243. /package/src/{theme → ui}/color-themes.ts +0 -0
  244. /package/src/{theme/components → ui/dash}/ActionButtons.tsx +0 -0
  245. /package/src/{theme/components → ui/dash}/CrudPageHeader.tsx +0 -0
  246. /package/src/{theme/components → ui/dash}/DangerZone.tsx +0 -0
  247. /package/src/{theme/components → ui/dash}/ListItemRow.tsx +0 -0
  248. /package/src/{theme/components → ui/shared}/EmptyState.tsx +0 -0
@@ -1,100 +0,0 @@
1
- /**
2
- * Minimal Theme - Site Layout
3
- *
4
- * Single-column, centered layout with horizontal nav.
5
- * Inspired by Tufte CSS and Manton.org.
6
- */
7
-
8
- import type { FC, PropsWithChildren } from "hono/jsx";
9
- import type { NavLinkView, SiteLayoutProps } from "../../types.js";
10
-
11
- function NavLinks({ links }: { links: NavLinkView[] }) {
12
- return (
13
- <>
14
- {links.map((link) => (
15
- <a
16
- key={link.id}
17
- href={link.url}
18
- class={`text-sm ${
19
- link.isActive
20
- ? "text-foreground font-medium"
21
- : "text-muted-foreground hover:text-foreground"
22
- }`}
23
- {...(link.isExternal
24
- ? { target: "_blank", rel: "noopener noreferrer" }
25
- : {})}
26
- >
27
- {link.label}
28
- {link.isExternal && (
29
- <span class="ml-0.5 text-xs opacity-50">&#8599;</span>
30
- )}
31
- </a>
32
- ))}
33
- </>
34
- );
35
- }
36
-
37
- export const SiteLayout: FC<PropsWithChildren<SiteLayoutProps>> = ({
38
- siteName,
39
- links,
40
- children,
41
- }) => {
42
- return (
43
- <div
44
- class="max-w-2xl mx-auto px-4 py-8"
45
- data-signals={JSON.stringify({ _menuOpen: false })}
46
- >
47
- {/* Header */}
48
- <header class="mb-12">
49
- <div class="flex items-center justify-between">
50
- <a href="/" class="text-xl font-semibold">
51
- {siteName}
52
- </a>
53
-
54
- {/* Mobile menu toggle */}
55
- {links.length > 0 && (
56
- <button
57
- data-on:click="$_menuOpen = !$_menuOpen"
58
- class="p-2 -mr-2 text-muted-foreground hover:text-foreground sm:hidden"
59
- aria-label="Toggle menu"
60
- >
61
- <svg
62
- class="size-5"
63
- fill="none"
64
- viewBox="0 0 24 24"
65
- stroke-width="1.5"
66
- stroke="currentColor"
67
- >
68
- <path
69
- stroke-linecap="round"
70
- stroke-linejoin="round"
71
- d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
72
- />
73
- </svg>
74
- </button>
75
- )}
76
- </div>
77
-
78
- {/* Desktop nav (inline) */}
79
- {links.length > 0 && (
80
- <nav class="hidden sm:flex flex-wrap gap-x-4 gap-y-1 mt-3">
81
- <NavLinks links={links} />
82
- </nav>
83
- )}
84
-
85
- {/* Mobile nav (collapsible) */}
86
- {links.length > 0 && (
87
- <nav
88
- class="sm:hidden flex flex-col gap-1 mt-3 overflow-hidden"
89
- data-show="$_menuOpen"
90
- >
91
- <NavLinks links={links} />
92
- </nav>
93
- )}
94
- </header>
95
-
96
- {/* Main content */}
97
- <main>{children}</main>
98
- </div>
99
- );
100
- };
@@ -1,83 +0,0 @@
1
- /**
2
- * Minimal Theme
3
- *
4
- * A content-first, borderless theme inspired by Tufte CSS and Manton.org.
5
- * Single-column layout with serif-friendly typography and generous whitespace.
6
- *
7
- * This is the default theme for Jant.
8
- */
9
-
10
- import type { JantTheme, ThemeComponents } from "../../types.js";
11
- import type { ColorTheme } from "../../theme/color-themes.js";
12
-
13
- // Layout
14
- import { SiteLayout } from "./MinimalSiteLayout.js";
15
-
16
- // Pages
17
- import { HomePage } from "./pages/HomePage.js";
18
- import { PostPage } from "./pages/PostPage.js";
19
- import { SinglePage } from "./pages/SinglePage.js";
20
- import { ArchivePage } from "./pages/ArchivePage.js";
21
- import { SearchPage } from "./pages/SearchPage.js";
22
- import { CollectionPage } from "./pages/CollectionPage.js";
23
-
24
- // Timeline
25
- import { NoteCard } from "./timeline/NoteCard.js";
26
- import { ArticleCard } from "./timeline/ArticleCard.js";
27
- import { LinkCard } from "./timeline/LinkCard.js";
28
- import { QuoteCard } from "./timeline/QuoteCard.js";
29
- import { ImageCard } from "./timeline/ImageCard.js";
30
- import { ThreadPreview } from "./timeline/ThreadPreview.js";
31
- import { TimelineFeed } from "./timeline/TimelineFeed.js";
32
-
33
- export interface ThemeOptions {
34
- /** Override individual components */
35
- components?: Partial<ThemeComponents>;
36
- /** CSS variable overrides */
37
- cssVariables?: Record<string, string>;
38
- /** Custom color themes */
39
- colorThemes?: ColorTheme[];
40
- }
41
-
42
- /**
43
- * Create the minimal theme configuration.
44
- *
45
- * @param options - Optional overrides for components, CSS variables, or color themes
46
- * @returns A JantTheme configuration object
47
- *
48
- * @example
49
- * ```typescript
50
- * import { createApp } from "@jant/core";
51
- * import { minimalTheme } from "@jant/core";
52
- *
53
- * export default createApp({
54
- * theme: minimalTheme(), // re-exported as minimalTheme from @jant/core
55
- * });
56
- * ```
57
- */
58
- export function theme(options?: ThemeOptions): JantTheme {
59
- return {
60
- name: "minimal",
61
- components: {
62
- SiteLayout,
63
- HomePage,
64
- PostPage,
65
- SinglePage,
66
- ArchivePage,
67
- SearchPage,
68
- CollectionPage,
69
- NoteCard,
70
- ArticleCard,
71
- LinkCard,
72
- QuoteCard,
73
- ImageCard,
74
- ThreadPreview,
75
- TimelineFeed,
76
- ...options?.components,
77
- },
78
- cssVariables: {
79
- ...options?.cssVariables,
80
- },
81
- colorThemes: options?.colorThemes,
82
- };
83
- }
@@ -1,157 +0,0 @@
1
- /**
2
- * Minimal Theme - Archive Page
3
- *
4
- * Date-first list with type filter and cursor pagination.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import { useLingui } from "@lingui/react/macro";
9
- import type { ArchivePageProps } from "../../../types.js";
10
- import { POST_TYPES } from "../../../types.js";
11
- import { Pagination as DefaultPagination } from "../../../theme/components/Pagination.js";
12
-
13
- function getTypeLabel(type: string): string {
14
- const { t } = useLingui();
15
- const labels: Record<string, string> = {
16
- note: t({ message: "Note", comment: "@context: Post type label - note" }),
17
- article: t({
18
- message: "Article",
19
- comment: "@context: Post type label - article",
20
- }),
21
- link: t({ message: "Link", comment: "@context: Post type label - link" }),
22
- quote: t({
23
- message: "Quote",
24
- comment: "@context: Post type label - quote",
25
- }),
26
- image: t({
27
- message: "Image",
28
- comment: "@context: Post type label - image",
29
- }),
30
- page: t({ message: "Page", comment: "@context: Post type label - page" }),
31
- };
32
- return labels[type] ?? type;
33
- }
34
-
35
- function getTypeLabelPlural(type: string): string {
36
- const { t } = useLingui();
37
- const labels: Record<string, string> = {
38
- note: t({
39
- message: "Notes",
40
- comment: "@context: Post type label plural - notes",
41
- }),
42
- article: t({
43
- message: "Articles",
44
- comment: "@context: Post type label plural - articles",
45
- }),
46
- link: t({
47
- message: "Links",
48
- comment: "@context: Post type label plural - links",
49
- }),
50
- quote: t({
51
- message: "Quotes",
52
- comment: "@context: Post type label plural - quotes",
53
- }),
54
- image: t({
55
- message: "Images",
56
- comment: "@context: Post type label plural - images",
57
- }),
58
- page: t({
59
- message: "Pages",
60
- comment: "@context: Post type label plural - pages",
61
- }),
62
- };
63
- return labels[type] ?? `${type}s`;
64
- }
65
-
66
- export const ArchivePage: FC<ArchivePageProps> = ({
67
- groups,
68
- hasMore,
69
- nextCursor,
70
- type,
71
- theme,
72
- }) => {
73
- const { t } = useLingui();
74
- const title = type
75
- ? getTypeLabelPlural(type)
76
- : t({ message: "Archive", comment: "@context: Archive page title" });
77
-
78
- const PaginationComponent = theme?.Pagination ?? DefaultPagination;
79
-
80
- return (
81
- <div>
82
- <header class="mb-8">
83
- <h1 class="text-2xl font-semibold">{title}</h1>
84
-
85
- <nav class="flex flex-wrap gap-2 mt-4">
86
- <a
87
- href="/archive"
88
- class={`text-sm ${!type ? "font-medium text-foreground" : "text-muted-foreground hover:text-foreground"}`}
89
- >
90
- {t({
91
- message: "All",
92
- comment: "@context: Archive filter - all types",
93
- })}
94
- </a>
95
- {POST_TYPES.filter((t) => t !== "page").map((typeKey) => (
96
- <a
97
- key={typeKey}
98
- href={`/archive?type=${typeKey}`}
99
- class={`text-sm ${type === typeKey ? "font-medium text-foreground" : "text-muted-foreground hover:text-foreground"}`}
100
- >
101
- {getTypeLabelPlural(typeKey)}
102
- </a>
103
- ))}
104
- </nav>
105
- </header>
106
-
107
- <main>
108
- {groups.length === 0 ? (
109
- <p class="text-muted-foreground">
110
- {t({
111
- message: "No posts found.",
112
- comment: "@context: Archive empty state",
113
- })}
114
- </p>
115
- ) : (
116
- groups.map((group) => (
117
- <section key={`${group.year}-${group.month}`} class="mb-8">
118
- <h2 class="text-lg font-medium mb-4 text-muted-foreground">
119
- {group.label}
120
- </h2>
121
- <div class="flex flex-col gap-3">
122
- {group.posts.map((post) => (
123
- <article key={post.id} class="flex items-baseline gap-4">
124
- <time
125
- class="text-sm text-muted-foreground w-12 shrink-0"
126
- datetime={post.publishedAt}
127
- >
128
- {new Date(post.publishedAt).getUTCDate()}
129
- </time>
130
- <div class="flex-1 min-w-0">
131
- <a href={post.permalink} class="hover:underline">
132
- {post.title ||
133
- post.content?.slice(0, 80) ||
134
- `Post #${post.id}`}
135
- </a>
136
- {!type && (
137
- <span class="ml-2 text-xs text-muted-foreground">
138
- {getTypeLabel(post.type)}
139
- </span>
140
- )}
141
- </div>
142
- </article>
143
- ))}
144
- </div>
145
- </section>
146
- ))
147
- )}
148
- </main>
149
-
150
- <PaginationComponent
151
- baseUrl={type ? `/archive?type=${type}` : "/archive"}
152
- hasMore={hasMore}
153
- nextCursor={nextCursor}
154
- />
155
- </div>
156
- );
157
- };
@@ -1,60 +0,0 @@
1
- /**
2
- * Minimal Theme - Collection Page
3
- *
4
- * Simple list of posts in a collection.
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>
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 class="flex flex-col">
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
- posts.map((post, i) => (
36
- <article key={post.id} class="h-entry">
37
- {i > 0 && <hr class="my-6 border-border" />}
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.contentHtml || "" }}
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
- )}
57
- </main>
58
- </div>
59
- );
60
- };
@@ -1,41 +0,0 @@
1
- /**
2
- * Minimal Theme - Home Page
3
- *
4
- * Renders the timeline feed with thread previews.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import { useLingui } from "@lingui/react/macro";
9
- import type { HomePageProps } from "../../../types.js";
10
- import { TimelineFeed as DefaultTimelineFeed } from "../timeline/TimelineFeed.js";
11
-
12
- export const HomePage: FC<HomePageProps> = ({
13
- items,
14
- hasMore,
15
- nextCursor,
16
- theme,
17
- }) => {
18
- const { t } = useLingui();
19
-
20
- const Feed = theme?.TimelineFeed ?? DefaultTimelineFeed;
21
-
22
- return (
23
- <>
24
- {items.length === 0 ? (
25
- <p class="text-muted-foreground">
26
- {t({
27
- message: "No posts yet.",
28
- comment: "@context: Empty state message on home page",
29
- })}
30
- </p>
31
- ) : (
32
- <Feed
33
- items={items}
34
- hasMore={hasMore}
35
- nextCursor={nextCursor}
36
- theme={theme}
37
- />
38
- )}
39
- </>
40
- );
41
- };
@@ -1,43 +0,0 @@
1
- /**
2
- * Minimal Theme - Post Page
3
- *
4
- * Clean article layout for a single post.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import { useLingui } from "@lingui/react/macro";
9
- import type { PostPageProps } from "../../../types.js";
10
- import { MediaGallery as DefaultMediaGallery } from "../../../theme/components/MediaGallery.js";
11
-
12
- export const PostPage: FC<PostPageProps> = ({ post, theme }) => {
13
- const { t } = useLingui();
14
-
15
- const Gallery = theme?.MediaGallery ?? DefaultMediaGallery;
16
-
17
- return (
18
- <article class="h-entry">
19
- {post.title && (
20
- <h1 class="p-name text-2xl font-semibold mb-4">{post.title}</h1>
21
- )}
22
-
23
- <div
24
- class="e-content prose"
25
- dangerouslySetInnerHTML={{ __html: post.contentHtml || "" }}
26
- />
27
-
28
- {post.media.length > 0 && <Gallery attachments={post.media} />}
29
-
30
- <footer class="mt-8 pt-4 border-t border-border text-sm text-muted-foreground">
31
- <time class="dt-published" datetime={post.publishedAt}>
32
- {post.publishedAtFormatted}
33
- </time>
34
- <a href={post.permalink} class="u-url ml-4 hover:underline">
35
- {t({
36
- message: "Permalink",
37
- comment: "@context: Link to permanent URL of post",
38
- })}
39
- </a>
40
- </footer>
41
- </article>
42
- );
43
- };
@@ -1,37 +0,0 @@
1
- /**
2
- * Minimal Theme - Article Card
3
- *
4
- * Title + excerpt, borderless, for type="article" posts.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import type { TimelineCardProps } from "../../../types.js";
9
-
10
- export const ArticleCard: FC<TimelineCardProps> = ({ post, compact }) => {
11
- return (
12
- <article class={`h-entry${compact ? " text-sm" : ""}`}>
13
- {post.title && (
14
- <h2 class={`p-name font-semibold ${compact ? "text-sm" : "text-lg"}`}>
15
- <a href={post.permalink} class="u-url hover:underline">
16
- {post.title}
17
- </a>
18
- </h2>
19
- )}
20
- {!compact && post.excerpt && (
21
- <p class="e-content text-muted-foreground mt-1 line-clamp-3">
22
- {post.excerpt}
23
- </p>
24
- )}
25
- <footer class="mt-2">
26
- <a
27
- href={post.permalink}
28
- class="u-url text-xs text-muted-foreground hover:text-foreground"
29
- >
30
- <time class="dt-published" datetime={post.publishedAt}>
31
- {post.publishedAtFormatted}
32
- </time>
33
- </a>
34
- </footer>
35
- </article>
36
- );
37
- };
@@ -1,63 +0,0 @@
1
- /**
2
- * Minimal Theme - Image Card
3
- *
4
- * Inline images with no card frame for type="image" posts.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import type { TimelineCardProps } from "../../../types.js";
9
- import { MediaGallery } from "../../../theme/components/MediaGallery.js";
10
-
11
- export const ImageCard: FC<TimelineCardProps> = ({ post, compact }) => {
12
- if (compact) {
13
- return (
14
- <article class="h-entry text-sm">
15
- {post.title && (
16
- <h2 class="p-name font-medium text-sm">
17
- <a href={post.permalink} class="u-url hover:underline">
18
- {post.title}
19
- </a>
20
- </h2>
21
- )}
22
- {post.contentHtml && (
23
- <div
24
- class="e-content prose prose-sm text-muted-foreground"
25
- dangerouslySetInnerHTML={{ __html: post.contentHtml }}
26
- />
27
- )}
28
- <footer class="mt-1">
29
- <a
30
- href={post.permalink}
31
- class="u-url text-xs text-muted-foreground hover:text-foreground"
32
- >
33
- <time class="dt-published" datetime={post.publishedAt}>
34
- {post.publishedAtFormatted}
35
- </time>
36
- </a>
37
- </footer>
38
- </article>
39
- );
40
- }
41
-
42
- return (
43
- <article class="h-entry">
44
- {post.contentHtml && (
45
- <div
46
- class="e-content prose prose-sm"
47
- dangerouslySetInnerHTML={{ __html: post.contentHtml }}
48
- />
49
- )}
50
- {post.media.length > 0 && <MediaGallery attachments={post.media} />}
51
- <footer class="mt-2">
52
- <a
53
- href={post.permalink}
54
- class="u-url text-xs text-muted-foreground hover:text-foreground"
55
- >
56
- <time class="dt-published" datetime={post.publishedAt}>
57
- {post.publishedAtFormatted}
58
- </time>
59
- </a>
60
- </footer>
61
- </article>
62
- );
63
- };
@@ -1,48 +0,0 @@
1
- /**
2
- * Minimal Theme - Link Card
3
- *
4
- * Subtle external link indicator for type="link" posts.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import type { TimelineCardProps } from "../../../types.js";
9
-
10
- export const LinkCard: FC<TimelineCardProps> = ({ post, compact }) => {
11
- return (
12
- <article class={`h-entry${compact ? " text-sm" : ""}`}>
13
- {post.title && (
14
- <h2 class={`p-name font-semibold ${compact ? "text-sm" : "text-base"}`}>
15
- <a
16
- href={post.sourceUrl || post.permalink}
17
- class="u-url hover:underline"
18
- target={post.sourceUrl ? "_blank" : undefined}
19
- rel={post.sourceUrl ? "noopener noreferrer" : undefined}
20
- >
21
- {post.title}
22
- </a>
23
- </h2>
24
- )}
25
- {post.sourceDomain && (
26
- <div class="text-xs text-muted-foreground mt-0.5">
27
- &#8599; {post.sourceDomain}
28
- </div>
29
- )}
30
- {!compact && post.contentHtml && (
31
- <div
32
- class="e-content prose prose-sm text-muted-foreground mt-1"
33
- dangerouslySetInnerHTML={{ __html: post.contentHtml }}
34
- />
35
- )}
36
- <footer class="mt-2">
37
- <a
38
- href={post.permalink}
39
- class="text-xs text-muted-foreground hover:text-foreground"
40
- >
41
- <time class="dt-published" datetime={post.publishedAt}>
42
- {post.publishedAtFormatted}
43
- </time>
44
- </a>
45
- </footer>
46
- </article>
47
- );
48
- };
@@ -1,35 +0,0 @@
1
- /**
2
- * Minimal Theme - Note Card
3
- *
4
- * Borderless, content-first card for type="note" posts.
5
- */
6
-
7
- import type { FC } from "hono/jsx";
8
- import type { TimelineCardProps } from "../../../types.js";
9
- import { MediaGallery } from "../../../theme/components/MediaGallery.js";
10
-
11
- export const NoteCard: FC<TimelineCardProps> = ({ post, compact }) => {
12
- return (
13
- <article class={`h-entry${compact ? " text-sm" : ""}`}>
14
- {post.contentHtml && (
15
- <div
16
- class={`e-content prose ${compact ? "prose-sm" : ""}`}
17
- dangerouslySetInnerHTML={{ __html: post.contentHtml }}
18
- />
19
- )}
20
- {!compact && post.media.length > 0 && (
21
- <MediaGallery attachments={post.media} />
22
- )}
23
- <footer class="mt-2">
24
- <a
25
- href={post.permalink}
26
- class="u-url text-xs text-muted-foreground hover:text-foreground"
27
- >
28
- <time class="dt-published" datetime={post.publishedAt}>
29
- {post.publishedAtFormatted}
30
- </time>
31
- </a>
32
- </footer>
33
- </article>
34
- );
35
- };