@jant/core 0.3.21 → 0.3.23

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 (99) hide show
  1. package/dist/app.js +23 -4
  2. package/dist/index.js +11 -4
  3. package/dist/lib/feed.js +112 -0
  4. package/dist/lib/navigation.js +9 -9
  5. package/dist/lib/render.js +48 -0
  6. package/dist/lib/theme-components.js +18 -18
  7. package/dist/lib/view.js +228 -0
  8. package/dist/routes/api/timeline.js +22 -18
  9. package/dist/routes/feed/rss.js +34 -78
  10. package/dist/routes/feed/sitemap.js +11 -26
  11. package/dist/routes/pages/archive.js +18 -195
  12. package/dist/routes/pages/collection.js +16 -70
  13. package/dist/routes/pages/home.js +25 -47
  14. package/dist/routes/pages/page.js +15 -27
  15. package/dist/routes/pages/post.js +25 -79
  16. package/dist/routes/pages/search.js +20 -130
  17. package/dist/theme/components/MediaGallery.js +10 -10
  18. package/dist/theme/components/index.js +0 -2
  19. package/dist/theme/index.js +10 -13
  20. package/dist/theme/layouts/index.js +0 -1
  21. package/dist/themes/minimal/MinimalSiteLayout.js +83 -0
  22. package/dist/themes/minimal/index.js +65 -0
  23. package/dist/themes/minimal/pages/ArchivePage.js +156 -0
  24. package/dist/themes/minimal/pages/CollectionPage.js +65 -0
  25. package/dist/themes/minimal/pages/HomePage.js +25 -0
  26. package/dist/themes/minimal/pages/PostPage.js +47 -0
  27. package/dist/themes/minimal/pages/SearchPage.js +121 -0
  28. package/dist/themes/minimal/pages/SinglePage.js +22 -0
  29. package/dist/themes/minimal/timeline/ArticleCard.js +36 -0
  30. package/dist/themes/minimal/timeline/ImageCard.js +67 -0
  31. package/dist/themes/minimal/timeline/LinkCard.js +47 -0
  32. package/dist/themes/minimal/timeline/NoteCard.js +34 -0
  33. package/dist/{theme/components → themes/minimal}/timeline/QuoteCard.js +9 -12
  34. package/dist/themes/minimal/timeline/ThreadPreview.js +46 -0
  35. package/dist/themes/minimal/timeline/TimelineFeed.js +48 -0
  36. package/dist/themes/minimal/timeline/TimelineItem.js +44 -0
  37. package/package.json +2 -1
  38. package/src/app.tsx +27 -4
  39. package/src/i18n/locales/en.po +53 -53
  40. package/src/i18n/locales/zh-Hans.po +53 -53
  41. package/src/i18n/locales/zh-Hant.po +53 -53
  42. package/src/index.ts +54 -6
  43. package/src/lib/__tests__/theme-components.test.ts +33 -14
  44. package/src/lib/__tests__/view.test.ts +377 -0
  45. package/src/lib/feed.ts +148 -0
  46. package/src/lib/navigation.ts +11 -11
  47. package/src/lib/render.tsx +67 -0
  48. package/src/lib/theme-components.ts +27 -35
  49. package/src/lib/view.ts +318 -0
  50. package/src/routes/api/__tests__/timeline.test.ts +3 -3
  51. package/src/routes/api/timeline.tsx +34 -27
  52. package/src/routes/feed/rss.ts +47 -94
  53. package/src/routes/feed/sitemap.ts +8 -30
  54. package/src/routes/pages/archive.tsx +24 -209
  55. package/src/routes/pages/collection.tsx +19 -75
  56. package/src/routes/pages/home.tsx +42 -76
  57. package/src/routes/pages/page.tsx +17 -28
  58. package/src/routes/pages/post.tsx +28 -86
  59. package/src/routes/pages/search.tsx +29 -151
  60. package/src/services/search.ts +2 -8
  61. package/src/styles/components.css +0 -54
  62. package/src/theme/components/MediaGallery.tsx +12 -12
  63. package/src/theme/components/index.ts +0 -12
  64. package/src/theme/index.ts +11 -13
  65. package/src/theme/layouts/index.ts +1 -1
  66. package/src/themes/minimal/MinimalSiteLayout.tsx +100 -0
  67. package/src/themes/minimal/index.ts +83 -0
  68. package/src/themes/minimal/pages/ArchivePage.tsx +157 -0
  69. package/src/themes/minimal/pages/CollectionPage.tsx +60 -0
  70. package/src/themes/minimal/pages/HomePage.tsx +41 -0
  71. package/src/themes/minimal/pages/PostPage.tsx +43 -0
  72. package/src/themes/minimal/pages/SearchPage.tsx +122 -0
  73. package/src/themes/minimal/pages/SinglePage.tsx +23 -0
  74. package/src/themes/minimal/timeline/ArticleCard.tsx +37 -0
  75. package/src/themes/minimal/timeline/ImageCard.tsx +63 -0
  76. package/src/themes/minimal/timeline/LinkCard.tsx +48 -0
  77. package/src/themes/minimal/timeline/NoteCard.tsx +35 -0
  78. package/src/{theme/components → themes/minimal}/timeline/QuoteCard.tsx +11 -17
  79. package/src/themes/minimal/timeline/ThreadPreview.tsx +47 -0
  80. package/src/{theme/components → themes/minimal}/timeline/TimelineFeed.tsx +20 -15
  81. package/src/themes/minimal/timeline/TimelineItem.tsx +75 -0
  82. package/src/types.ts +262 -38
  83. package/dist/theme/components/timeline/ArticleCard.js +0 -50
  84. package/dist/theme/components/timeline/ImageCard.js +0 -86
  85. package/dist/theme/components/timeline/LinkCard.js +0 -62
  86. package/dist/theme/components/timeline/NoteCard.js +0 -37
  87. package/dist/theme/components/timeline/ThreadPreview.js +0 -52
  88. package/dist/theme/components/timeline/TimelineFeed.js +0 -43
  89. package/dist/theme/components/timeline/TimelineItem.js +0 -25
  90. package/dist/theme/components/timeline/index.js +0 -8
  91. package/dist/theme/layouts/SiteLayout.js +0 -160
  92. package/src/theme/components/timeline/ArticleCard.tsx +0 -57
  93. package/src/theme/components/timeline/ImageCard.tsx +0 -80
  94. package/src/theme/components/timeline/LinkCard.tsx +0 -66
  95. package/src/theme/components/timeline/NoteCard.tsx +0 -41
  96. package/src/theme/components/timeline/ThreadPreview.tsx +0 -49
  97. package/src/theme/components/timeline/TimelineItem.tsx +0 -39
  98. package/src/theme/components/timeline/index.ts +0 -8
  99. package/src/theme/layouts/SiteLayout.tsx +0 -184
@@ -1,29 +1,14 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
1
+ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
2
2
  /**
3
3
  * Custom Page Route
4
4
  *
5
5
  * Catch-all route for custom pages accessible via their path field
6
6
  */ import { Hono } from "hono";
7
- import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
7
+ import { SinglePage as DefaultSinglePage } from "../../themes/minimal/pages/SinglePage.js";
8
8
  import { getNavigationData } from "../../lib/navigation.js";
9
+ import { renderPublicPage } from "../../lib/render.js";
10
+ import { createMediaContext, toPostViewFromPost } from "../../lib/view.js";
9
11
  export const pageRoutes = new Hono();
10
- function PageContent({ page }) {
11
- return /*#__PURE__*/ _jsxs("article", {
12
- class: "h-entry",
13
- children: [
14
- page.title && /*#__PURE__*/ _jsx("h1", {
15
- class: "p-name text-3xl font-semibold mb-6",
16
- children: page.title
17
- }),
18
- /*#__PURE__*/ _jsx("div", {
19
- class: "e-content prose",
20
- dangerouslySetInnerHTML: {
21
- __html: page.contentHtml || ""
22
- }
23
- })
24
- ]
25
- });
26
- }
27
12
  // Catch-all for custom page paths
28
13
  pageRoutes.get("/:path", async (c)=>{
29
14
  const path = c.req.param("path");
@@ -38,15 +23,18 @@ pageRoutes.get("/:path", async (c)=>{
38
23
  return c.notFound();
39
24
  }
40
25
  const navData = await getNavigationData(c);
41
- return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
26
+ // Transform to View Model
27
+ const mediaCtx = createMediaContext(c);
28
+ const pageView = toPostViewFromPost(page, mediaCtx);
29
+ const components = c.var.config.theme?.components;
30
+ const Page = components?.SinglePage ?? DefaultSinglePage;
31
+ return renderPublicPage(c, {
42
32
  title: `${page.title} - ${navData.siteName}`,
43
33
  description: page.content?.slice(0, 160),
44
- c: c,
45
- children: /*#__PURE__*/ _jsx(SiteLayout, {
46
- ...navData,
47
- children: /*#__PURE__*/ _jsx(PageContent, {
48
- page: page
49
- })
34
+ navData,
35
+ content: /*#__PURE__*/ _jsx(Page, {
36
+ page: pageView,
37
+ theme: components
50
38
  })
51
- }));
39
+ });
52
40
  });
@@ -1,54 +1,14 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
1
+ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
2
2
  /**
3
3
  * Single Post Page Route
4
4
  */ import { Hono } from "hono";
5
- import { useLingui as $_useLingui } from "@jant/core/i18n";
6
- import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
7
- import { MediaGallery } from "../../theme/components/index.js";
5
+ import { PostPage as DefaultPostPage } from "../../themes/minimal/pages/PostPage.js";
8
6
  import * as sqid from "../../lib/sqid.js";
9
- import * as time from "../../lib/time.js";
10
- import { getMediaUrl, getImageUrl, getPublicUrlForProvider } from "../../lib/image.js";
11
7
  import { getNavigationData } from "../../lib/navigation.js";
8
+ import { renderPublicPage } from "../../lib/render.js";
9
+ import { buildMediaMap } from "../../lib/media-helpers.js";
10
+ import { createMediaContext, toPostView } from "../../lib/view.js";
12
11
  export const postRoutes = new Hono();
13
- function PostContent({ post, mediaAttachments }) {
14
- const { i18n: $__i18n, _: $__ } = $_useLingui();
15
- return /*#__PURE__*/ _jsxs("article", {
16
- class: "h-entry",
17
- children: [
18
- post.title && /*#__PURE__*/ _jsx("h1", {
19
- class: "p-name text-2xl font-semibold mb-4",
20
- children: post.title
21
- }),
22
- /*#__PURE__*/ _jsx("div", {
23
- class: "e-content prose",
24
- dangerouslySetInnerHTML: {
25
- __html: post.contentHtml || ""
26
- }
27
- }),
28
- mediaAttachments.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
29
- attachments: mediaAttachments
30
- }),
31
- /*#__PURE__*/ _jsxs("footer", {
32
- class: "mt-6 pt-4 border-t text-sm text-muted-foreground",
33
- children: [
34
- /*#__PURE__*/ _jsx("time", {
35
- class: "dt-published",
36
- datetime: time.toISOString(post.publishedAt),
37
- children: time.formatDate(post.publishedAt)
38
- }),
39
- /*#__PURE__*/ _jsx("a", {
40
- href: `/p/${sqid.encode(post.id)}`,
41
- class: "u-url ml-4",
42
- children: $__i18n._({
43
- id: "D9Oea+",
44
- message: "Permalink"
45
- })
46
- })
47
- ]
48
- })
49
- ]
50
- });
51
- }
52
12
  postRoutes.get("/:id", async (c)=>{
53
13
  const paramId = c.req.param("id");
54
14
  // Try to decode as sqid first
@@ -67,42 +27,28 @@ postRoutes.get("/:id", async (c)=>{
67
27
  if (post.visibility === "draft") {
68
28
  return c.notFound();
69
29
  }
70
- // Load media attachments
71
- const rawMedia = await c.var.services.media.getByPostId(post.id);
72
- const r2PublicUrl = c.env.R2_PUBLIC_URL;
73
- const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
74
- const s3PublicUrl = c.env.S3_PUBLIC_URL;
75
- const mediaAttachments = rawMedia.map((m)=>{
76
- const publicUrl = getPublicUrlForProvider(m.provider, r2PublicUrl, s3PublicUrl);
77
- return {
78
- id: m.id,
79
- url: getMediaUrl(m.id, m.storageKey, publicUrl),
80
- previewUrl: getImageUrl(getMediaUrl(m.id, m.storageKey, publicUrl), imageTransformUrl, {
81
- width: 400,
82
- quality: 80,
83
- format: "auto",
84
- fit: "cover"
85
- }),
86
- alt: m.alt,
87
- blurhash: m.blurhash,
88
- width: m.width,
89
- height: m.height,
90
- position: m.position,
91
- mimeType: m.mimeType
92
- };
93
- });
30
+ // Batch load media attachments
31
+ const rawMediaMap = await c.var.services.media.getByPostIds([
32
+ post.id
33
+ ]);
34
+ const mediaCtx = createMediaContext(c);
35
+ const mediaMap = buildMediaMap(rawMediaMap, mediaCtx.r2PublicUrl, mediaCtx.imageTransformUrl, mediaCtx.s3PublicUrl);
36
+ // Transform to View Model
37
+ const postView = toPostView({
38
+ ...post,
39
+ mediaAttachments: mediaMap.get(post.id) ?? []
40
+ }, mediaCtx);
94
41
  const navData = await getNavigationData(c);
95
42
  const title = post.title || navData.siteName;
96
- return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
97
- title: title,
43
+ const components = c.var.config.theme?.components;
44
+ const Page = components?.PostPage ?? DefaultPostPage;
45
+ return renderPublicPage(c, {
46
+ title,
98
47
  description: post.content?.slice(0, 160),
99
- c: c,
100
- children: /*#__PURE__*/ _jsx(SiteLayout, {
101
- ...navData,
102
- children: /*#__PURE__*/ _jsx(PostContent, {
103
- post: post,
104
- mediaAttachments: mediaAttachments
105
- })
48
+ navData,
49
+ content: /*#__PURE__*/ _jsx(Page, {
50
+ post: postView,
51
+ theme: components
106
52
  })
107
- }));
53
+ });
108
54
  });
@@ -1,126 +1,13 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
1
+ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
2
2
  /**
3
3
  * Search Page Route
4
4
  */ import { Hono } from "hono";
5
- import { useLingui as $_useLingui } from "@jant/core/i18n";
6
- import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
7
- import { PagePagination } from "../../theme/components/index.js";
8
- import * as sqid from "../../lib/sqid.js";
9
- import * as time from "../../lib/time.js";
5
+ import { SearchPage as DefaultSearchPage } from "../../themes/minimal/pages/SearchPage.js";
10
6
  import { getNavigationData } from "../../lib/navigation.js";
7
+ import { renderPublicPage } from "../../lib/render.js";
8
+ import { createMediaContext, toSearchResultViews } from "../../lib/view.js";
11
9
  const PAGE_SIZE = 10;
12
10
  export const searchRoutes = new Hono();
13
- function SearchContent({ query, results, error, hasMore, page }) {
14
- const { i18n: $__i18n, _: $__ } = $_useLingui();
15
- const searchTitle = $__i18n._({
16
- id: "A1taO8",
17
- message: "Search"
18
- });
19
- return /*#__PURE__*/ _jsxs("div", {
20
- children: [
21
- /*#__PURE__*/ _jsx("h1", {
22
- class: "text-2xl font-semibold mb-6",
23
- children: searchTitle
24
- }),
25
- /*#__PURE__*/ _jsx("form", {
26
- method: "get",
27
- action: "/search",
28
- class: "mb-8",
29
- children: /*#__PURE__*/ _jsxs("div", {
30
- class: "flex gap-2",
31
- children: [
32
- /*#__PURE__*/ _jsx("input", {
33
- type: "search",
34
- name: "q",
35
- class: "input flex-1",
36
- placeholder: $__i18n._({
37
- id: "MqghUt",
38
- message: "Search posts..."
39
- }),
40
- value: query,
41
- autofocus: true
42
- }),
43
- /*#__PURE__*/ _jsx("button", {
44
- type: "submit",
45
- class: "btn",
46
- children: $__i18n._({
47
- id: "A1taO8",
48
- message: "Search"
49
- })
50
- })
51
- ]
52
- })
53
- }),
54
- error && /*#__PURE__*/ _jsx("div", {
55
- class: "alert-destructive mb-6",
56
- children: /*#__PURE__*/ _jsx("h2", {
57
- children: error
58
- })
59
- }),
60
- query && !error && /*#__PURE__*/ _jsxs("div", {
61
- children: [
62
- /*#__PURE__*/ _jsx("p", {
63
- class: "text-sm text-muted-foreground mb-4",
64
- children: results.length === 0 ? $__i18n._({
65
- id: "MZbQHL",
66
- message: "No results found."
67
- }) : results.length === 1 ? $__i18n._({
68
- id: "z8ajIE",
69
- message: "Found 1 result"
70
- }) : $__i18n._({
71
- id: "zH6KqE",
72
- message: "Found {count} results"
73
- })
74
- }),
75
- results.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
76
- children: [
77
- /*#__PURE__*/ _jsx("div", {
78
- class: "flex flex-col gap-4",
79
- children: results.map((result)=>/*#__PURE__*/ _jsx("article", {
80
- class: "p-4 rounded-lg border hover:border-primary",
81
- children: /*#__PURE__*/ _jsxs("a", {
82
- href: `/p/${sqid.encode(result.post.id)}`,
83
- class: "block",
84
- children: [
85
- /*#__PURE__*/ _jsx("h2", {
86
- class: "font-medium hover:underline",
87
- children: result.post.title || result.post.content?.slice(0, 60) || `Post #${result.post.id}`
88
- }),
89
- result.snippet && /*#__PURE__*/ _jsx("p", {
90
- class: "text-sm text-muted-foreground mt-2 line-clamp-2",
91
- dangerouslySetInnerHTML: {
92
- __html: result.snippet
93
- }
94
- }),
95
- /*#__PURE__*/ _jsxs("footer", {
96
- class: "flex items-center gap-2 mt-2 text-xs text-muted-foreground",
97
- children: [
98
- /*#__PURE__*/ _jsx("span", {
99
- class: "badge-outline",
100
- children: result.post.type
101
- }),
102
- /*#__PURE__*/ _jsx("time", {
103
- datetime: time.toISOString(result.post.publishedAt),
104
- children: time.formatDate(result.post.publishedAt)
105
- })
106
- ]
107
- })
108
- ]
109
- })
110
- }, result.post.id))
111
- }),
112
- /*#__PURE__*/ _jsx(PagePagination, {
113
- baseUrl: `/search?q=${encodeURIComponent(query)}`,
114
- currentPage: page,
115
- hasMore: hasMore
116
- })
117
- ]
118
- })
119
- ]
120
- })
121
- ]
122
- });
123
- }
124
11
  searchRoutes.get("/", async (c)=>{
125
12
  const query = c.req.query("q") || "";
126
13
  const pageParam = c.req.query("page");
@@ -128,7 +15,7 @@ searchRoutes.get("/", async (c)=>{
128
15
  const navData = await getNavigationData(c);
129
16
  // Only search if there's a query
130
17
  let results = [];
131
- let error = null;
18
+ let error;
132
19
  let hasMore = false;
133
20
  if (query.trim()) {
134
21
  try {
@@ -151,18 +38,21 @@ searchRoutes.get("/", async (c)=>{
151
38
  error = "Search failed. Please try again.";
152
39
  }
153
40
  }
154
- return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
41
+ // Transform to View Models
42
+ const mediaCtx = createMediaContext(c);
43
+ const resultViews = toSearchResultViews(results, mediaCtx);
44
+ const components = c.var.config.theme?.components;
45
+ const Page = components?.SearchPage ?? DefaultSearchPage;
46
+ return renderPublicPage(c, {
155
47
  title: query ? `Search: ${query} - ${navData.siteName}` : `Search - ${navData.siteName}`,
156
- c: c,
157
- children: /*#__PURE__*/ _jsx(SiteLayout, {
158
- ...navData,
159
- children: /*#__PURE__*/ _jsx(SearchContent, {
160
- query: query,
161
- results: results,
162
- error: error,
163
- hasMore: hasMore,
164
- page: page
165
- })
48
+ navData,
49
+ content: /*#__PURE__*/ _jsx(Page, {
50
+ query: query,
51
+ results: resultViews,
52
+ error: error,
53
+ hasMore: hasMore,
54
+ page: page,
55
+ theme: components
166
56
  })
167
- }));
57
+ });
168
58
  });
@@ -17,8 +17,8 @@ export const MediaGallery = ({ attachments })=>{
17
17
  target: "_blank",
18
18
  rel: "noopener noreferrer",
19
19
  children: /*#__PURE__*/ _jsx("img", {
20
- src: img.previewUrl,
21
- alt: img.alt || "",
20
+ src: img.thumbnailUrl,
21
+ alt: img.altText || "",
22
22
  width: img.width ?? undefined,
23
23
  height: img.height ?? undefined,
24
24
  class: "rounded-lg max-w-full h-auto",
@@ -36,8 +36,8 @@ export const MediaGallery = ({ attachments })=>{
36
36
  rel: "noopener noreferrer",
37
37
  class: "aspect-square",
38
38
  children: /*#__PURE__*/ _jsx("img", {
39
- src: img.previewUrl,
40
- alt: img.alt || "",
39
+ src: img.thumbnailUrl,
40
+ alt: img.altText || "",
41
41
  class: "w-full h-full object-cover",
42
42
  loading: "lazy"
43
43
  })
@@ -56,8 +56,8 @@ export const MediaGallery = ({ attachments })=>{
56
56
  rel: "noopener noreferrer",
57
57
  class: "row-span-2",
58
58
  children: /*#__PURE__*/ _jsx("img", {
59
- src: first.previewUrl,
60
- alt: first.alt || "",
59
+ src: first.thumbnailUrl,
60
+ alt: first.altText || "",
61
61
  class: "w-full h-full object-cover",
62
62
  loading: "lazy"
63
63
  })
@@ -68,8 +68,8 @@ export const MediaGallery = ({ attachments })=>{
68
68
  rel: "noopener noreferrer",
69
69
  class: "aspect-square",
70
70
  children: /*#__PURE__*/ _jsx("img", {
71
- src: img.previewUrl,
72
- alt: img.alt || "",
71
+ src: img.thumbnailUrl,
72
+ alt: img.altText || "",
73
73
  class: "w-full h-full object-cover",
74
74
  loading: "lazy"
75
75
  })
@@ -89,8 +89,8 @@ export const MediaGallery = ({ attachments })=>{
89
89
  class: "relative aspect-square",
90
90
  children: [
91
91
  /*#__PURE__*/ _jsx("img", {
92
- src: img.previewUrl,
93
- alt: img.alt || "",
92
+ src: img.thumbnailUrl,
93
+ alt: img.altText || "",
94
94
  class: "w-full h-full object-cover",
95
95
  loading: "lazy"
96
96
  }),
@@ -11,5 +11,3 @@ export { PostList } from "./PostList.js";
11
11
  export { ThreadView } from "./ThreadView.js";
12
12
  export { TypeBadge } from "./TypeBadge.js";
13
13
  export { VisibilityBadge } from "./VisibilityBadge.js";
14
- // Timeline components
15
- export { NoteCard, ArticleCard, LinkCard, QuoteCard, ImageCard, ThreadPreview, TimelineItem, TimelineFeed } from "./timeline/index.js";
@@ -1,21 +1,18 @@
1
1
  /**
2
- * Jant Theme Components
2
+ * Jant Theme - Shared Infrastructure
3
3
  *
4
- * These components can be imported for wrapping/extending:
4
+ * Exports shared layouts, components, and color themes used by all themes.
5
+ * Individual theme packages (minimal, card, etc.) import from here.
5
6
  *
6
7
  * @example
7
8
  * ```typescript
8
- * import { PostCard } from "@jant/core/theme";
9
- *
10
- * export function MyPostCard(props) {
11
- * return (
12
- * <div class="my-wrapper">
13
- * <PostCard {...props} />
14
- * </div>
15
- * );
16
- * }
9
+ * // In a theme package:
10
+ * import { MediaGallery, Pagination } from "@jant/core/theme";
11
+ * import type { ColorTheme } from "@jant/core/theme";
17
12
  * ```
18
- */ // Layout components
13
+ */ // Layout components (BaseLayout, DashLayout)
19
14
  export * from "./layouts/index.js";
20
- // UI components
15
+ // Shared UI components (MediaGallery, Pagination, EmptyState, etc.)
21
16
  export * from "./components/index.js";
17
+ // Color themes
18
+ export * from "./color-themes.js";
@@ -1,3 +1,2 @@
1
1
  export { BaseLayout } from "./BaseLayout.js";
2
2
  export { DashLayout } from "./DashLayout.js";
3
- export { SiteLayout } from "./SiteLayout.js";
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Minimal Theme - Site Layout
3
+ *
4
+ * Single-column, centered layout with horizontal nav.
5
+ * Inspired by Tufte CSS and Manton.org.
6
+ */ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
7
+ function NavLinks({ links }) {
8
+ return /*#__PURE__*/ _jsx(_Fragment, {
9
+ children: links.map((link)=>/*#__PURE__*/ _jsxs("a", {
10
+ href: link.url,
11
+ class: `text-sm ${link.isActive ? "text-foreground font-medium" : "text-muted-foreground hover:text-foreground"}`,
12
+ ...link.isExternal ? {
13
+ target: "_blank",
14
+ rel: "noopener noreferrer"
15
+ } : {},
16
+ children: [
17
+ link.label,
18
+ link.isExternal && /*#__PURE__*/ _jsx("span", {
19
+ class: "ml-0.5 text-xs opacity-50",
20
+ children: "↗"
21
+ })
22
+ ]
23
+ }, link.id))
24
+ });
25
+ }
26
+ export const SiteLayout = ({ siteName, links, children })=>{
27
+ return /*#__PURE__*/ _jsxs("div", {
28
+ class: "max-w-2xl mx-auto px-4 py-8",
29
+ "data-signals": JSON.stringify({
30
+ _menuOpen: false
31
+ }),
32
+ children: [
33
+ /*#__PURE__*/ _jsxs("header", {
34
+ class: "mb-12",
35
+ children: [
36
+ /*#__PURE__*/ _jsxs("div", {
37
+ class: "flex items-center justify-between",
38
+ children: [
39
+ /*#__PURE__*/ _jsx("a", {
40
+ href: "/",
41
+ class: "text-xl font-semibold",
42
+ children: siteName
43
+ }),
44
+ links.length > 0 && /*#__PURE__*/ _jsx("button", {
45
+ "data-on:click": "$_menuOpen = !$_menuOpen",
46
+ class: "p-2 -mr-2 text-muted-foreground hover:text-foreground sm:hidden",
47
+ "aria-label": "Toggle menu",
48
+ children: /*#__PURE__*/ _jsx("svg", {
49
+ class: "size-5",
50
+ fill: "none",
51
+ viewBox: "0 0 24 24",
52
+ "stroke-width": "1.5",
53
+ stroke: "currentColor",
54
+ children: /*#__PURE__*/ _jsx("path", {
55
+ "stroke-linecap": "round",
56
+ "stroke-linejoin": "round",
57
+ d: "M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
58
+ })
59
+ })
60
+ })
61
+ ]
62
+ }),
63
+ links.length > 0 && /*#__PURE__*/ _jsx("nav", {
64
+ class: "hidden sm:flex flex-wrap gap-x-4 gap-y-1 mt-3",
65
+ children: /*#__PURE__*/ _jsx(NavLinks, {
66
+ links: links
67
+ })
68
+ }),
69
+ links.length > 0 && /*#__PURE__*/ _jsx("nav", {
70
+ class: "sm:hidden flex flex-col gap-1 mt-3 overflow-hidden",
71
+ "data-show": "$_menuOpen",
72
+ children: /*#__PURE__*/ _jsx(NavLinks, {
73
+ links: links
74
+ })
75
+ })
76
+ ]
77
+ }),
78
+ /*#__PURE__*/ _jsx("main", {
79
+ children: children
80
+ })
81
+ ]
82
+ });
83
+ };
@@ -0,0 +1,65 @@
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
+ */ // Layout
9
+ import { SiteLayout } from "./MinimalSiteLayout.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 { ArticleCard } from "./timeline/ArticleCard.js";
20
+ import { LinkCard } from "./timeline/LinkCard.js";
21
+ import { QuoteCard } from "./timeline/QuoteCard.js";
22
+ import { ImageCard } from "./timeline/ImageCard.js";
23
+ import { ThreadPreview } from "./timeline/ThreadPreview.js";
24
+ import { TimelineFeed } from "./timeline/TimelineFeed.js";
25
+ /**
26
+ * Create the minimal 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 { minimalTheme } from "@jant/core";
35
+ *
36
+ * export default createApp({
37
+ * theme: minimalTheme(), // re-exported as minimalTheme from @jant/core
38
+ * });
39
+ * ```
40
+ */ export function theme(options) {
41
+ return {
42
+ name: "minimal",
43
+ components: {
44
+ SiteLayout,
45
+ HomePage,
46
+ PostPage,
47
+ SinglePage,
48
+ ArchivePage,
49
+ SearchPage,
50
+ CollectionPage,
51
+ NoteCard,
52
+ ArticleCard,
53
+ LinkCard,
54
+ QuoteCard,
55
+ ImageCard,
56
+ ThreadPreview,
57
+ TimelineFeed,
58
+ ...options?.components
59
+ },
60
+ cssVariables: {
61
+ ...options?.cssVariables
62
+ },
63
+ colorThemes: options?.colorThemes
64
+ };
65
+ }