@jant/core 0.3.22 → 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 (78) hide show
  1. package/dist/app.js +22 -3
  2. package/dist/index.js +3 -4
  3. package/dist/lib/render.js +1 -1
  4. package/dist/lib/view.js +1 -1
  5. package/dist/routes/api/timeline.js +3 -3
  6. package/dist/routes/pages/archive.js +1 -1
  7. package/dist/routes/pages/collection.js +1 -1
  8. package/dist/routes/pages/home.js +1 -1
  9. package/dist/routes/pages/page.js +1 -1
  10. package/dist/routes/pages/post.js +1 -1
  11. package/dist/routes/pages/search.js +1 -1
  12. package/dist/theme/components/index.js +0 -2
  13. package/dist/theme/index.js +10 -16
  14. package/dist/theme/layouts/index.js +0 -1
  15. package/dist/themes/minimal/MinimalSiteLayout.js +83 -0
  16. package/dist/themes/minimal/index.js +65 -0
  17. package/dist/{theme → themes/minimal}/pages/ArchivePage.js +7 -8
  18. package/dist/{theme → themes/minimal}/pages/CollectionPage.js +7 -5
  19. package/dist/{theme → themes/minimal}/pages/HomePage.js +2 -3
  20. package/dist/{theme → themes/minimal}/pages/PostPage.js +5 -6
  21. package/dist/{theme → themes/minimal}/pages/SearchPage.js +11 -10
  22. package/dist/{theme → themes/minimal}/pages/SinglePage.js +3 -4
  23. package/dist/themes/minimal/timeline/ArticleCard.js +36 -0
  24. package/dist/themes/minimal/timeline/ImageCard.js +67 -0
  25. package/dist/{theme/components → themes/minimal}/timeline/LinkCard.js +14 -26
  26. package/dist/{theme/components → themes/minimal}/timeline/NoteCard.js +7 -7
  27. package/dist/{theme/components → themes/minimal}/timeline/QuoteCard.js +6 -6
  28. package/dist/{theme/components → themes/minimal}/timeline/ThreadPreview.js +13 -18
  29. package/dist/themes/minimal/timeline/TimelineFeed.js +48 -0
  30. package/dist/{theme/components → themes/minimal}/timeline/TimelineItem.js +1 -2
  31. package/package.json +1 -1
  32. package/src/app.tsx +26 -3
  33. package/src/i18n/locales/en.po +47 -47
  34. package/src/i18n/locales/zh-Hans.po +47 -47
  35. package/src/i18n/locales/zh-Hant.po +47 -47
  36. package/src/index.ts +4 -5
  37. package/src/lib/__tests__/view.test.ts +18 -16
  38. package/src/lib/render.tsx +1 -1
  39. package/src/lib/view.ts +1 -1
  40. package/src/routes/api/timeline.tsx +3 -3
  41. package/src/routes/pages/archive.tsx +1 -1
  42. package/src/routes/pages/collection.tsx +1 -1
  43. package/src/routes/pages/home.tsx +1 -1
  44. package/src/routes/pages/page.tsx +1 -1
  45. package/src/routes/pages/post.tsx +1 -1
  46. package/src/routes/pages/search.tsx +1 -1
  47. package/src/styles/components.css +0 -54
  48. package/src/theme/components/index.ts +0 -13
  49. package/src/theme/index.ts +10 -16
  50. package/src/theme/layouts/index.ts +0 -1
  51. package/src/themes/minimal/MinimalSiteLayout.tsx +100 -0
  52. package/src/themes/minimal/index.ts +83 -0
  53. package/src/{theme → themes/minimal}/pages/ArchivePage.tsx +8 -11
  54. package/src/{theme → themes/minimal}/pages/CollectionPage.tsx +6 -6
  55. package/src/{theme → themes/minimal}/pages/HomePage.tsx +3 -4
  56. package/src/{theme → themes/minimal}/pages/PostPage.tsx +6 -7
  57. package/src/{theme → themes/minimal}/pages/SearchPage.tsx +11 -17
  58. package/src/{theme → themes/minimal}/pages/SinglePage.tsx +4 -5
  59. package/src/themes/minimal/timeline/ArticleCard.tsx +37 -0
  60. package/src/themes/minimal/timeline/ImageCard.tsx +63 -0
  61. package/src/themes/minimal/timeline/LinkCard.tsx +48 -0
  62. package/src/{theme/components → themes/minimal}/timeline/NoteCard.tsx +10 -9
  63. package/src/{theme/components → themes/minimal}/timeline/QuoteCard.tsx +9 -8
  64. package/src/{theme/components → themes/minimal}/timeline/ThreadPreview.tsx +14 -16
  65. package/src/{theme/components → themes/minimal}/timeline/TimelineFeed.tsx +14 -13
  66. package/src/{theme/components → themes/minimal}/timeline/TimelineItem.tsx +1 -4
  67. package/dist/theme/components/timeline/ArticleCard.js +0 -46
  68. package/dist/theme/components/timeline/ImageCard.js +0 -83
  69. package/dist/theme/components/timeline/TimelineFeed.js +0 -46
  70. package/dist/theme/components/timeline/index.js +0 -8
  71. package/dist/theme/layouts/SiteLayout.js +0 -131
  72. package/dist/theme/pages/index.js +0 -11
  73. package/src/theme/components/timeline/ArticleCard.tsx +0 -45
  74. package/src/theme/components/timeline/ImageCard.tsx +0 -70
  75. package/src/theme/components/timeline/LinkCard.tsx +0 -59
  76. package/src/theme/components/timeline/index.ts +0 -8
  77. package/src/theme/layouts/SiteLayout.tsx +0 -132
  78. package/src/theme/pages/index.ts +0 -13
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Minimal Theme - Image Card
3
+ *
4
+ * Inline images with no card frame for type="image" posts.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import { MediaGallery } from "../../../theme/components/MediaGallery.js";
7
+ export const ImageCard = ({ post, compact })=>{
8
+ if (compact) {
9
+ return /*#__PURE__*/ _jsxs("article", {
10
+ class: "h-entry text-sm",
11
+ children: [
12
+ post.title && /*#__PURE__*/ _jsx("h2", {
13
+ class: "p-name font-medium text-sm",
14
+ children: /*#__PURE__*/ _jsx("a", {
15
+ href: post.permalink,
16
+ class: "u-url hover:underline",
17
+ children: post.title
18
+ })
19
+ }),
20
+ post.contentHtml && /*#__PURE__*/ _jsx("div", {
21
+ class: "e-content prose prose-sm text-muted-foreground",
22
+ dangerouslySetInnerHTML: {
23
+ __html: post.contentHtml
24
+ }
25
+ }),
26
+ /*#__PURE__*/ _jsx("footer", {
27
+ class: "mt-1",
28
+ children: /*#__PURE__*/ _jsx("a", {
29
+ href: post.permalink,
30
+ class: "u-url text-xs text-muted-foreground hover:text-foreground",
31
+ children: /*#__PURE__*/ _jsx("time", {
32
+ class: "dt-published",
33
+ datetime: post.publishedAt,
34
+ children: post.publishedAtFormatted
35
+ })
36
+ })
37
+ })
38
+ ]
39
+ });
40
+ }
41
+ return /*#__PURE__*/ _jsxs("article", {
42
+ class: "h-entry",
43
+ children: [
44
+ post.contentHtml && /*#__PURE__*/ _jsx("div", {
45
+ class: "e-content prose prose-sm",
46
+ dangerouslySetInnerHTML: {
47
+ __html: post.contentHtml
48
+ }
49
+ }),
50
+ post.media.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
51
+ attachments: post.media
52
+ }),
53
+ /*#__PURE__*/ _jsx("footer", {
54
+ class: "mt-2",
55
+ children: /*#__PURE__*/ _jsx("a", {
56
+ href: post.permalink,
57
+ class: "u-url text-xs text-muted-foreground hover:text-foreground",
58
+ children: /*#__PURE__*/ _jsx("time", {
59
+ class: "dt-published",
60
+ datetime: post.publishedAt,
61
+ children: post.publishedAtFormatted
62
+ })
63
+ })
64
+ })
65
+ ]
66
+ });
67
+ };
@@ -1,33 +1,14 @@
1
1
  /**
2
- * Link Card Component
2
+ * Minimal Theme - Link Card
3
3
  *
4
- * External link emphasis for type="link" posts.
4
+ * Subtle external link indicator for type="link" posts.
5
5
  */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
6
  export const LinkCard = ({ post, compact })=>{
7
7
  return /*#__PURE__*/ _jsxs("article", {
8
- class: `h-entry timeline-card timeline-card-link${compact ? " timeline-card-compact" : ""}`,
8
+ class: `h-entry${compact ? " text-sm" : ""}`,
9
9
  children: [
10
- post.sourceDomain && /*#__PURE__*/ _jsxs("div", {
11
- class: "text-xs text-muted-foreground mb-1 flex items-center gap-1",
12
- children: [
13
- /*#__PURE__*/ _jsx("svg", {
14
- class: "size-3",
15
- xmlns: "http://www.w3.org/2000/svg",
16
- fill: "none",
17
- viewBox: "0 0 24 24",
18
- "stroke-width": "2",
19
- stroke: "currentColor",
20
- children: /*#__PURE__*/ _jsx("path", {
21
- 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"
22
- })
23
- }),
24
- /*#__PURE__*/ _jsx("span", {
25
- children: post.sourceDomain
26
- })
27
- ]
28
- }),
29
10
  post.title && /*#__PURE__*/ _jsx("h2", {
30
- class: `p-name font-semibold ${compact ? "text-sm" : "text-base"} mb-1`,
11
+ class: `p-name font-semibold ${compact ? "text-sm" : "text-base"}`,
31
12
  children: /*#__PURE__*/ _jsx("a", {
32
13
  href: post.sourceUrl || post.permalink,
33
14
  class: "u-url hover:underline",
@@ -36,17 +17,24 @@ export const LinkCard = ({ post, compact })=>{
36
17
  children: post.title
37
18
  })
38
19
  }),
20
+ post.sourceDomain && /*#__PURE__*/ _jsxs("div", {
21
+ class: "text-xs text-muted-foreground mt-0.5",
22
+ children: [
23
+ "↗ ",
24
+ post.sourceDomain
25
+ ]
26
+ }),
39
27
  !compact && post.contentHtml && /*#__PURE__*/ _jsx("div", {
40
- class: "e-content prose prose-sm text-muted-foreground",
28
+ class: "e-content prose prose-sm text-muted-foreground mt-1",
41
29
  dangerouslySetInnerHTML: {
42
30
  __html: post.contentHtml
43
31
  }
44
32
  }),
45
33
  /*#__PURE__*/ _jsx("footer", {
46
- class: "mt-2 text-xs text-muted-foreground",
34
+ class: "mt-2",
47
35
  children: /*#__PURE__*/ _jsx("a", {
48
36
  href: post.permalink,
49
- class: "hover:underline",
37
+ class: "text-xs text-muted-foreground hover:text-foreground",
50
38
  children: /*#__PURE__*/ _jsx("time", {
51
39
  class: "dt-published",
52
40
  datetime: post.publishedAt,
@@ -1,15 +1,15 @@
1
1
  /**
2
- * Note Card Component
2
+ * Minimal Theme - Note Card
3
3
  *
4
- * Text-first, minimal card for type="note" posts.
4
+ * Borderless, content-first card for type="note" posts.
5
5
  */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
- import { MediaGallery } from "../MediaGallery.js";
6
+ import { MediaGallery } from "../../../theme/components/MediaGallery.js";
7
7
  export const NoteCard = ({ post, compact })=>{
8
8
  return /*#__PURE__*/ _jsxs("article", {
9
- class: `h-entry timeline-card${compact ? " timeline-card-compact" : ""}`,
9
+ class: `h-entry${compact ? " text-sm" : ""}`,
10
10
  children: [
11
11
  post.contentHtml && /*#__PURE__*/ _jsx("div", {
12
- class: `e-content prose ${compact ? "prose-sm" : "prose-sm"}`,
12
+ class: `e-content prose ${compact ? "prose-sm" : ""}`,
13
13
  dangerouslySetInnerHTML: {
14
14
  __html: post.contentHtml
15
15
  }
@@ -18,10 +18,10 @@ export const NoteCard = ({ post, compact })=>{
18
18
  attachments: post.media
19
19
  }),
20
20
  /*#__PURE__*/ _jsx("footer", {
21
- class: "mt-2 text-xs text-muted-foreground",
21
+ class: "mt-2",
22
22
  children: /*#__PURE__*/ _jsx("a", {
23
23
  href: post.permalink,
24
- class: "u-url hover:underline",
24
+ class: "u-url text-xs text-muted-foreground hover:text-foreground",
25
25
  children: /*#__PURE__*/ _jsx("time", {
26
26
  class: "dt-published",
27
27
  datetime: post.publishedAt,
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Quote Card Component
2
+ * Minimal Theme - Quote Card
3
3
  *
4
- * Blockquote + attribution for type="quote" posts.
4
+ * Subtle blockquote with left border for type="quote" posts.
5
5
  */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
6
  export const QuoteCard = ({ post, compact })=>{
7
7
  return /*#__PURE__*/ _jsxs("article", {
8
- class: `h-entry timeline-card timeline-card-quote${compact ? " timeline-card-compact" : ""}`,
8
+ class: `h-entry${compact ? " text-sm" : ""}`,
9
9
  children: [
10
10
  post.contentHtml && /*#__PURE__*/ _jsx("blockquote", {
11
- class: `e-content italic ${compact ? "text-sm" : "text-base"} leading-relaxed`,
11
+ class: `e-content border-l-2 border-muted-foreground/30 pl-4 italic ${compact ? "text-sm" : ""} leading-relaxed`,
12
12
  children: /*#__PURE__*/ _jsx("div", {
13
13
  dangerouslySetInnerHTML: {
14
14
  __html: post.contentHtml
@@ -32,10 +32,10 @@ export const QuoteCard = ({ post, compact })=>{
32
32
  ]
33
33
  }),
34
34
  /*#__PURE__*/ _jsx("footer", {
35
- class: "mt-2 text-xs text-muted-foreground",
35
+ class: "mt-2",
36
36
  children: /*#__PURE__*/ _jsx("a", {
37
37
  href: post.permalink,
38
- class: "u-url hover:underline",
38
+ class: "u-url text-xs text-muted-foreground hover:text-foreground",
39
39
  children: /*#__PURE__*/ _jsx("time", {
40
40
  class: "dt-published",
41
41
  datetime: post.publishedAt,
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Thread Preview Component
2
+ * Minimal Theme - Thread Preview
3
3
  *
4
- * Inline thread preview: root card + compact replies + "show more" link.
4
+ * Minimal thread indicator: root post + compact replies + "show more" link.
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
7
  import { TimelineItem } from "./TimelineItem.js";
@@ -10,7 +10,6 @@ export const ThreadPreview = ({ rootPost, previewReplies, totalReplyCount, theme
10
10
  const { i18n: $__i18n, _: $__ } = $_useLingui();
11
11
  const remainingCount = totalReplyCount - previewReplies.length;
12
12
  return /*#__PURE__*/ _jsxs("div", {
13
- class: "timeline-thread",
14
13
  children: [
15
14
  /*#__PURE__*/ _jsx(TimelineItem, {
16
15
  item: {
@@ -19,29 +18,25 @@ export const ThreadPreview = ({ rootPost, previewReplies, totalReplyCount, theme
19
18
  theme: theme
20
19
  }),
21
20
  previewReplies.length > 0 && /*#__PURE__*/ _jsxs("div", {
22
- class: "timeline-thread-replies",
21
+ class: "ml-4 mt-2 border-l border-border pl-4 flex flex-col gap-3",
23
22
  children: [
24
23
  previewReplies.map((reply)=>/*#__PURE__*/ _jsx("div", {
25
- class: "timeline-thread-reply",
26
24
  children: /*#__PURE__*/ _jsx(TimelineItemFromPost, {
27
25
  post: reply,
28
26
  compact: true,
29
27
  theme: theme
30
28
  })
31
29
  }, reply.id)),
32
- remainingCount > 0 && /*#__PURE__*/ _jsx("div", {
33
- class: "timeline-thread-reply",
34
- children: /*#__PURE__*/ _jsx("a", {
35
- href: rootPost.permalink,
36
- class: "text-sm text-muted-foreground hover:text-foreground hover:underline",
37
- children: $__i18n._({
38
- id: "smzF8S",
39
- message: "Show {remainingCount} more {0}",
40
- values: {
41
- remainingCount: remainingCount,
42
- 0: remainingCount === 1 ? "reply" : "replies"
43
- }
44
- })
30
+ remainingCount > 0 && /*#__PURE__*/ _jsx("a", {
31
+ href: rootPost.permalink,
32
+ class: "text-sm text-muted-foreground hover:text-foreground hover:underline",
33
+ children: $__i18n._({
34
+ id: "smzF8S",
35
+ message: "Show {remainingCount} more {0}",
36
+ values: {
37
+ remainingCount: remainingCount,
38
+ 0: remainingCount === 1 ? "reply" : "replies"
39
+ }
45
40
  })
46
41
  })
47
42
  ]
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Minimal Theme - Timeline Feed
3
+ *
4
+ * Divider-separated stream of posts with load-more button.
5
+ */ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
6
+ import { useLingui as $_useLingui } from "@jant/core/i18n";
7
+ import { TimelineItem } from "./TimelineItem.js";
8
+ import { ThreadPreview as DefaultThreadPreview } from "./ThreadPreview.js";
9
+ export const TimelineFeed = ({ items, hasMore, nextCursor, theme })=>{
10
+ const { i18n: $__i18n, _: $__ } = $_useLingui();
11
+ const ResolvedThreadPreview = theme?.ThreadPreview ?? DefaultThreadPreview;
12
+ return /*#__PURE__*/ _jsxs("div", {
13
+ children: [
14
+ /*#__PURE__*/ _jsx("div", {
15
+ id: "timeline-feed",
16
+ class: "flex flex-col",
17
+ children: items.map((item, i)=>/*#__PURE__*/ _jsxs("div", {
18
+ children: [
19
+ i > 0 && /*#__PURE__*/ _jsx("hr", {
20
+ class: "my-6 border-border"
21
+ }),
22
+ item.threadPreview ? /*#__PURE__*/ _jsx(ResolvedThreadPreview, {
23
+ rootPost: item.post,
24
+ previewReplies: item.threadPreview.replies,
25
+ totalReplyCount: item.threadPreview.totalReplyCount,
26
+ theme: theme
27
+ }) : /*#__PURE__*/ _jsx(TimelineItem, {
28
+ item: item,
29
+ theme: theme
30
+ })
31
+ ]
32
+ }, item.post.id))
33
+ }),
34
+ hasMore && nextCursor && /*#__PURE__*/ _jsx("div", {
35
+ id: "load-more-container",
36
+ class: "mt-8 text-center",
37
+ children: /*#__PURE__*/ _jsx("button", {
38
+ class: "text-sm text-muted-foreground hover:text-foreground hover:underline",
39
+ "data-on:click": `@get('/api/timeline?cursor=${nextCursor}')`,
40
+ children: $__i18n._({
41
+ id: "yQ2kGp",
42
+ message: "Load more"
43
+ })
44
+ })
45
+ })
46
+ ]
47
+ });
48
+ };
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Timeline Item Component
2
+ * Minimal Theme - Timeline Item
3
3
  *
4
4
  * Dispatches to the correct card component based on post type.
5
- * Resolves card overrides from theme components if provided.
6
5
  */ import { jsx as _jsx } from "hono/jsx/jsx-runtime";
7
6
  import { NoteCard } from "./NoteCard.js";
8
7
  import { ArticleCard } from "./ArticleCard.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jant/core",
3
- "version": "0.3.22",
3
+ "version": "0.3.23",
4
4
  "description": "A modern, open-source microblogging platform built on Cloudflare Workers",
5
5
  "type": "module",
6
6
  "bin": {
package/src/app.tsx CHANGED
@@ -11,6 +11,7 @@ import { i18nMiddleware } from "./i18n/index.js";
11
11
  import { useLingui } from "@lingui/react/macro";
12
12
  import type { Bindings, JantConfig } from "./types.js";
13
13
  import { SETTINGS_KEYS } from "./lib/constants.js";
14
+ import { theme as minimalTheme } from "./themes/minimal/index.js";
14
15
  import { hashPassword } from "better-auth/crypto";
15
16
 
16
17
  // Routes - Pages
@@ -82,6 +83,25 @@ export type App = Hono<{ Bindings: Bindings; Variables: AppVariables }>;
82
83
  * ```
83
84
  */
84
85
  export function createApp(config: JantConfig = {}): App {
86
+ // Merge with default minimal theme
87
+ const defaultTheme = minimalTheme();
88
+ const resolvedConfig: JantConfig = {
89
+ ...config,
90
+ theme: {
91
+ name: config.theme?.name ?? defaultTheme.name,
92
+ components: {
93
+ ...defaultTheme.components,
94
+ ...config.theme?.components,
95
+ },
96
+ cssVariables: {
97
+ ...defaultTheme.cssVariables,
98
+ ...config.theme?.cssVariables,
99
+ },
100
+ colorThemes: config.theme?.colorThemes ?? defaultTheme.colorThemes,
101
+ feed: config.theme?.feed,
102
+ },
103
+ };
104
+
85
105
  const app = new Hono<{ Bindings: Bindings; Variables: AppVariables }>();
86
106
 
87
107
  // Initialize services, auth, and config middleware
@@ -96,7 +116,7 @@ export function createApp(config: JantConfig = {}): App {
96
116
  const db = createDatabase(session as unknown as D1Database);
97
117
  const services = createServices(db, session as unknown as D1Database);
98
118
  c.set("services", services);
99
- c.set("config", config);
119
+ c.set("config", resolvedConfig);
100
120
  c.set("storage", createStorageDriver(c.env));
101
121
 
102
122
  if (c.env.AUTH_SECRET) {
@@ -117,11 +137,14 @@ export function createApp(config: JantConfig = {}): App {
117
137
  // Theme middleware - resolve active color theme and build CSS
118
138
  app.use("*", async (c, next) => {
119
139
  const themeId = await c.var.services.settings.get(SETTINGS_KEYS.THEME);
120
- const themes = getAvailableThemes(config);
140
+ const themes = getAvailableThemes(resolvedConfig);
121
141
  const activeTheme = themeId
122
142
  ? themes.find((t) => t.id === themeId)
123
143
  : undefined;
124
- const themeStyle = buildThemeStyle(activeTheme, config.theme?.cssVariables);
144
+ const themeStyle = buildThemeStyle(
145
+ activeTheme,
146
+ resolvedConfig.theme?.cssVariables,
147
+ );
125
148
  c.set("themeStyle", themeStyle);
126
149
  await next();
127
150
  });