@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
@@ -49,7 +49,7 @@ msgid "Add Media"
49
49
  msgstr "添加媒體"
50
50
 
51
51
  #. @context: Archive filter - all types
52
- #: src/theme/pages/ArchivePage.tsx:92
52
+ #: src/themes/minimal/pages/ArchivePage.tsx:90
53
53
  msgid "All"
54
54
  msgstr "所有"
55
55
 
@@ -59,7 +59,7 @@ msgid "Appearance"
59
59
  msgstr "外觀"
60
60
 
61
61
  #. @context: Archive page title
62
- #: src/theme/pages/ArchivePage.tsx:77
62
+ #: src/themes/minimal/pages/ArchivePage.tsx:76
63
63
  msgid "Archive"
64
64
  msgstr "檔案館"
65
65
 
@@ -73,12 +73,12 @@ msgstr ""
73
73
  #. @context: Post type option
74
74
  #: src/theme/components/PostForm.tsx:74
75
75
  #: src/theme/components/TypeBadge.tsx:20
76
- #: src/theme/pages/ArchivePage.tsx:18
76
+ #: src/themes/minimal/pages/ArchivePage.tsx:17
77
77
  msgid "Article"
78
78
  msgstr "文章"
79
79
 
80
80
  #. @context: Post type label plural - articles
81
- #: src/theme/pages/ArchivePage.tsx:43
81
+ #: src/themes/minimal/pages/ArchivePage.tsx:42
82
82
  msgid "Articles"
83
83
  msgstr "文章"
84
84
 
@@ -137,7 +137,7 @@ msgid "Color theme"
137
137
  msgstr "顏色主題"
138
138
 
139
139
  #. @context: Setup form submit button
140
- #: src/app.tsx:254
140
+ #: src/app.tsx:277
141
141
  msgid "Complete Setup"
142
142
  msgstr "完成設置"
143
143
 
@@ -147,7 +147,7 @@ msgid "Confirm New Password"
147
147
  msgstr "確認新密碼"
148
148
 
149
149
  #. @context: Password reset form field
150
- #: src/app.tsx:515
150
+ #: src/app.tsx:538
151
151
  msgid "Confirm Password"
152
152
  msgstr "確認密碼"
153
153
 
@@ -186,7 +186,7 @@ msgid "Create Redirect"
186
186
  msgstr "建立重定向"
187
187
 
188
188
  #. @context: Setup page description
189
- #: src/app.tsx:194
189
+ #: src/app.tsx:217
190
190
  msgid "Create your admin account."
191
191
  msgstr "建立您的管理員帳戶。"
192
192
 
@@ -252,7 +252,7 @@ msgid "Deleting this media will remove it permanently from storage."
252
252
  msgstr "刪除此媒體將永久從存儲中移除它。"
253
253
 
254
254
  #. @context: Hint shown on signin page when demo credentials are pre-filled
255
- #: src/app.tsx:353
255
+ #: src/app.tsx:376
256
256
  msgid "Demo account pre-filled. Just click Sign In."
257
257
  msgstr "示範帳戶已預填。只需點擊登入。"
258
258
 
@@ -333,13 +333,13 @@ msgstr "編輯文章"
333
333
 
334
334
  #. @context: Setup/signin form field - email
335
335
  #. @context: Setup/signin form field - email
336
- #: src/app.tsx:224
337
- #: src/app.tsx:368
336
+ #: src/app.tsx:247
337
+ #: src/app.tsx:391
338
338
  msgid "Email"
339
339
  msgstr "電子郵件"
340
340
 
341
341
  #. @context: Password reset page description
342
- #: src/app.tsx:484
342
+ #: src/app.tsx:507
343
343
  msgid "Enter your new password."
344
344
  msgstr "請輸入您的新密碼。"
345
345
 
@@ -351,12 +351,12 @@ msgid "Featured"
351
351
  msgstr "精選"
352
352
 
353
353
  #. @context: Search results count - multiple
354
- #: src/theme/pages/SearchPage.tsx:77
354
+ #: src/themes/minimal/pages/SearchPage.tsx:73
355
355
  msgid "Found {count} results"
356
356
  msgstr "找到 {count} 個結果"
357
357
 
358
358
  #. @context: Search results count - single
359
- #: src/theme/pages/SearchPage.tsx:73
359
+ #: src/themes/minimal/pages/SearchPage.tsx:69
360
360
  msgid "Found 1 result"
361
361
  msgstr "找到 1 個結果"
362
362
 
@@ -377,12 +377,12 @@ msgstr "一般設定"
377
377
  #. @context: Post type option
378
378
  #: src/theme/components/PostForm.tsx:83
379
379
  #: src/theme/components/TypeBadge.tsx:29
380
- #: src/theme/pages/ArchivePage.tsx:27
380
+ #: src/themes/minimal/pages/ArchivePage.tsx:26
381
381
  msgid "Image"
382
382
  msgstr "圖片"
383
383
 
384
384
  #. @context: Post type label plural - images
385
- #: src/theme/pages/ArchivePage.tsx:55
385
+ #: src/themes/minimal/pages/ArchivePage.tsx:54
386
386
  msgid "Images"
387
387
  msgstr "圖片"
388
388
 
@@ -392,7 +392,7 @@ msgid "Images are automatically optimized: resized to max 1920px, converted to W
392
392
  msgstr "圖片會自動優化:調整大小至最大 1920 像素,轉換為 WebP 格式,並去除元數據。"
393
393
 
394
394
  #. @context: Password reset error heading
395
- #: src/app.tsx:559
395
+ #: src/app.tsx:582
396
396
  msgid "Invalid or Expired Link"
397
397
  msgstr "無效或已過期的連結"
398
398
 
@@ -411,19 +411,19 @@ msgstr "語言"
411
411
  #. @context: Post type option
412
412
  #: src/theme/components/PostForm.tsx:77
413
413
  #: src/theme/components/TypeBadge.tsx:24
414
- #: src/theme/pages/ArchivePage.tsx:22
414
+ #: src/themes/minimal/pages/ArchivePage.tsx:21
415
415
  msgid "Link"
416
416
  msgstr "連結"
417
417
 
418
418
  #. @context: Post type label plural - links
419
- #: src/theme/pages/ArchivePage.tsx:47
419
+ #: src/themes/minimal/pages/ArchivePage.tsx:46
420
420
  msgid "Links"
421
421
  msgstr "連結"
422
422
 
423
423
  #. @context: Button to load more posts in timeline
424
424
  #. @context: Pagination button - load more items
425
425
  #: src/theme/components/Pagination.tsx:103
426
- #: src/theme/components/timeline/TimelineFeed.tsx:47
426
+ #: src/themes/minimal/timeline/TimelineFeed.tsx:48
427
427
  msgid "Load more"
428
428
  msgstr "載入更多"
429
429
 
@@ -500,7 +500,7 @@ msgstr "新頁面"
500
500
 
501
501
  #. @context: Password form field
502
502
  #. @context: Password reset form field
503
- #: src/app.tsx:499
503
+ #: src/app.tsx:522
504
504
  #: src/routes/dash/settings.tsx:442
505
505
  msgid "New Password"
506
506
  msgstr "新密碼"
@@ -551,21 +551,21 @@ msgid "No pages yet."
551
551
  msgstr "尚未有頁面。"
552
552
 
553
553
  #. @context: Archive empty state
554
- #: src/theme/pages/ArchivePage.tsx:112
554
+ #: src/themes/minimal/pages/ArchivePage.tsx:110
555
555
  msgid "No posts found."
556
556
  msgstr "未找到任何帖子。"
557
557
 
558
558
  #. @context: Empty state message
559
559
  #. @context: Empty state message
560
560
  #: src/routes/dash/collections.tsx:243
561
- #: src/theme/pages/CollectionPage.tsx:30
561
+ #: src/themes/minimal/pages/CollectionPage.tsx:29
562
562
  msgid "No posts in this collection."
563
563
  msgstr "此集合中沒有帖子。"
564
564
 
565
565
  #. @context: Empty state message on home page
566
566
  #. @context: Empty state message when no posts exist
567
567
  #: src/theme/components/PostList.tsx:25
568
- #: src/theme/pages/HomePage.tsx:27
568
+ #: src/themes/minimal/pages/HomePage.tsx:26
569
569
  msgid "No posts yet."
570
570
  msgstr "尚未有帖子。"
571
571
 
@@ -575,7 +575,7 @@ msgid "No redirects configured."
575
575
  msgstr "未配置任何重定向。"
576
576
 
577
577
  #. @context: Search empty results
578
- #: src/theme/pages/SearchPage.tsx:68
578
+ #: src/themes/minimal/pages/SearchPage.tsx:64
579
579
  msgid "No results found."
580
580
  msgstr "未找到結果。"
581
581
 
@@ -584,12 +584,12 @@ msgstr "未找到結果。"
584
584
  #. @context: Post type option
585
585
  #: src/theme/components/PostForm.tsx:71
586
586
  #: src/theme/components/TypeBadge.tsx:19
587
- #: src/theme/pages/ArchivePage.tsx:17
587
+ #: src/themes/minimal/pages/ArchivePage.tsx:16
588
588
  msgid "Note"
589
589
  msgstr "備註"
590
590
 
591
591
  #. @context: Post type label plural - notes
592
- #: src/theme/pages/ArchivePage.tsx:39
592
+ #: src/themes/minimal/pages/ArchivePage.tsx:38
593
593
  msgid "Notes"
594
594
  msgstr "筆記"
595
595
 
@@ -598,7 +598,7 @@ msgstr "筆記"
598
598
  #. @context: Post type label - page
599
599
  #: src/routes/dash/pages.tsx:125
600
600
  #: src/theme/components/TypeBadge.tsx:33
601
- #: src/theme/pages/ArchivePage.tsx:31
601
+ #: src/themes/minimal/pages/ArchivePage.tsx:30
602
602
  msgid "Page"
603
603
  msgstr "頁面"
604
604
 
@@ -622,14 +622,14 @@ msgstr "頁面標題..."
622
622
  #. @context: Post type label plural - pages
623
623
  #: src/routes/dash/pages.tsx:36
624
624
  #: src/theme/layouts/DashLayout.tsx:96
625
- #: src/theme/pages/ArchivePage.tsx:59
625
+ #: src/themes/minimal/pages/ArchivePage.tsx:58
626
626
  msgid "Pages"
627
627
  msgstr "頁面"
628
628
 
629
629
  #. @context: Setup/signin form field - password
630
630
  #. @context: Setup/signin form field - password
631
- #: src/app.tsx:239
632
- #: src/app.tsx:377
631
+ #: src/app.tsx:262
632
+ #: src/app.tsx:400
633
633
  msgid "Password"
634
634
  msgstr "密碼"
635
635
 
@@ -646,7 +646,7 @@ msgstr "路徑(例如 /archive)或完整網址(例如 https://example.com
646
646
  #. @context: Link to individual post in thread
647
647
  #. @context: Link to permanent URL of post
648
648
  #: src/theme/components/ThreadView.tsx:68
649
- #: src/theme/pages/PostPage.tsx:36
649
+ #: src/themes/minimal/pages/PostPage.tsx:35
650
650
  msgid "Permalink"
651
651
  msgstr "永久鏈接"
652
652
 
@@ -697,9 +697,9 @@ msgstr "上一頁"
697
697
  #. @context: Loading text shown on submit button while request is in progress
698
698
  #. @context: Loading text shown on submit button while request is in progress
699
699
  #. @context: Upload status - processing
700
- #: src/app.tsx:260
701
- #: src/app.tsx:397
702
- #: src/app.tsx:537
700
+ #: src/app.tsx:283
701
+ #: src/app.tsx:420
702
+ #: src/app.tsx:560
703
703
  #: src/routes/dash/collections.tsx:178
704
704
  #: src/routes/dash/collections.tsx:357
705
705
  #: src/routes/dash/media.tsx:119
@@ -755,12 +755,12 @@ msgstr "安靜(正常)"
755
755
  #. @context: Post type option
756
756
  #: src/theme/components/PostForm.tsx:80
757
757
  #: src/theme/components/TypeBadge.tsx:25
758
- #: src/theme/pages/ArchivePage.tsx:23
758
+ #: src/themes/minimal/pages/ArchivePage.tsx:22
759
759
  msgid "Quote"
760
760
  msgstr "引用"
761
761
 
762
762
  #. @context: Post type label plural - quotes
763
- #: src/theme/pages/ArchivePage.tsx:51
763
+ #: src/themes/minimal/pages/ArchivePage.tsx:50
764
764
  msgid "Quotes"
765
765
  msgstr "引用"
766
766
 
@@ -780,8 +780,8 @@ msgstr "移除"
780
780
 
781
781
  #. @context: Password reset form submit button
782
782
  #. @context: Password reset page heading
783
- #: src/app.tsx:478
784
- #: src/app.tsx:531
783
+ #: src/app.tsx:501
784
+ #: src/app.tsx:554
785
785
  msgid "Reset Password"
786
786
  msgstr "重設密碼"
787
787
 
@@ -802,13 +802,13 @@ msgstr "保存設定"
802
802
 
803
803
  #. @context: Search page title
804
804
  #. @context: Search submit button
805
- #: src/theme/pages/SearchPage.tsx:22
806
- #: src/theme/pages/SearchPage.tsx:48
805
+ #: src/themes/minimal/pages/SearchPage.tsx:21
806
+ #: src/themes/minimal/pages/SearchPage.tsx:46
807
807
  msgid "Search"
808
808
  msgstr "搜尋"
809
809
 
810
810
  #. @context: Search input placeholder
811
- #: src/theme/pages/SearchPage.tsx:40
811
+ #: src/themes/minimal/pages/SearchPage.tsx:38
812
812
  msgid "Search posts..."
813
813
  msgstr "搜尋帖子..."
814
814
 
@@ -830,14 +830,14 @@ msgstr "設定"
830
830
 
831
831
  #. @context: Link to show remaining thread replies
832
832
  #. placeholder {0}: remainingCount === 1 ? "reply" : "replies"
833
- #: src/theme/components/timeline/ThreadPreview.tsx:38
833
+ #: src/themes/minimal/timeline/ThreadPreview.tsx:37
834
834
  msgid "Show {remainingCount} more {0}"
835
835
  msgstr "顯示 {remainingCount} 個更多 {0}"
836
836
 
837
837
  #. @context: Sign in form submit button
838
838
  #. @context: Sign in page heading
839
- #: src/app.tsx:344
840
- #: src/app.tsx:391
839
+ #: src/app.tsx:367
840
+ #: src/app.tsx:414
841
841
  msgid "Sign In"
842
842
  msgstr "登入"
843
843
 
@@ -894,7 +894,7 @@ msgid "The URL path for this page. Use lowercase letters, numbers, and hyphens."
894
894
  msgstr "此頁面的 URL 路徑。使用小寫字母、數字和連字符。"
895
895
 
896
896
  #. @context: Password reset error description
897
- #: src/app.tsx:567
897
+ #: src/app.tsx:590
898
898
  msgid "This password reset link is invalid or has expired. Please generate a new one."
899
899
  msgstr "此密碼重設連結無效或已過期。請生成一個新的連結。"
900
900
 
@@ -1033,7 +1033,7 @@ msgid "Visibility"
1033
1033
  msgstr "可見性"
1034
1034
 
1035
1035
  #. @context: Setup page welcome heading
1036
- #: src/app.tsx:188
1036
+ #: src/app.tsx:211
1037
1037
  msgid "Welcome to Jant"
1038
1038
  msgstr "歡迎來到 Jant"
1039
1039
 
@@ -1048,6 +1048,6 @@ msgid "What's this collection about?"
1048
1048
  msgstr "這個收藏是關於什麼的?"
1049
1049
 
1050
1050
  #. @context: Setup form field - user name
1051
- #: src/app.tsx:209
1051
+ #: src/app.tsx:232
1052
1052
  msgid "Your Name"
1053
1053
  msgstr "您的姓名"
package/src/index.ts CHANGED
@@ -4,12 +4,14 @@
4
4
  * @packageDocumentation
5
5
  */
6
6
 
7
- import { createApp as _createApp } from "./app.js";
8
-
9
7
  // Main app factory
10
8
  export { createApp } from "./app.js";
11
9
  export type { App, AppVariables } from "./app.js";
12
10
 
11
+ // Default theme
12
+ export { theme as minimalTheme } from "./themes/minimal/index.js";
13
+ export type { ThemeOptions as MinimalThemeOptions } from "./themes/minimal/index.js";
14
+
13
15
  // Types
14
16
  export type {
15
17
  PostType,
@@ -96,6 +98,3 @@ export {
96
98
  defaultAtomRenderer,
97
99
  defaultSitemapRenderer,
98
100
  } from "./lib/feed.js";
99
-
100
- // Default export for running core directly (e.g., for development)
101
- export default _createApp();
@@ -194,8 +194,8 @@ describe("toPostViews", () => {
194
194
  const posts = [makePostWithMedia({ id: 1 }), makePostWithMedia({ id: 2 })];
195
195
  const views = toPostViews(posts, EMPTY_CTX);
196
196
  expect(views).toHaveLength(2);
197
- expect(views[0]!.id).toBe(1);
198
- expect(views[1]!.id).toBe(2);
197
+ expect(views[0]).toHaveProperty("id", 1);
198
+ expect(views[1]).toHaveProperty("id", 2);
199
199
  });
200
200
  });
201
201
 
@@ -305,9 +305,9 @@ describe("toNavLinkViews", () => {
305
305
  ];
306
306
  const views = toNavLinkViews(links, "/archive");
307
307
  expect(views).toHaveLength(3);
308
- expect(views[0]!.isActive).toBe(false);
309
- expect(views[1]!.isActive).toBe(true);
310
- expect(views[2]!.isExternal).toBe(true);
308
+ expect(views[0]).toHaveProperty("isActive", false);
309
+ expect(views[1]).toHaveProperty("isActive", true);
310
+ expect(views[2]).toHaveProperty("isExternal", true);
311
311
  });
312
312
  });
313
313
 
@@ -347,15 +347,16 @@ describe("toArchiveGroups", () => {
347
347
  const groups = toArchiveGroups(grouped, EMPTY_CTX);
348
348
  expect(groups).toHaveLength(2);
349
349
 
350
- expect(groups[0]!.year).toBe("2024");
351
- expect(groups[0]!.month).toBe("02");
352
- expect(groups[0]!.label).toBe("February 2024");
353
- expect(groups[0]!.posts).toHaveLength(2);
350
+ expect(groups[0]).toHaveProperty("year", "2024");
351
+ expect(groups[0]).toHaveProperty("month", "02");
352
+ expect(groups[0]).toHaveProperty("label", "February 2024");
353
+ expect(groups[0]).toHaveProperty("posts");
354
+ expect(groups[0]?.posts).toHaveLength(2);
354
355
 
355
- expect(groups[1]!.year).toBe("2024");
356
- expect(groups[1]!.month).toBe("01");
357
- expect(groups[1]!.label).toBe("January 2024");
358
- expect(groups[1]!.posts).toHaveLength(1);
356
+ expect(groups[1]).toHaveProperty("year", "2024");
357
+ expect(groups[1]).toHaveProperty("month", "01");
358
+ expect(groups[1]).toHaveProperty("label", "January 2024");
359
+ expect(groups[1]?.posts).toHaveLength(1);
359
360
  });
360
361
 
361
362
  it("converts posts to PostView within groups", () => {
@@ -363,9 +364,10 @@ describe("toArchiveGroups", () => {
363
364
  grouped.set("2024-02", [makePost({ id: 1 })]);
364
365
 
365
366
  const groups = toArchiveGroups(grouped, EMPTY_CTX);
366
- const post = groups[0]!.posts[0]!;
367
- expect(post.permalink).toBeDefined();
368
- expect(post.publishedAtFormatted).toBeDefined();
367
+ const post = groups[0]?.posts[0];
368
+ expect(post).toBeDefined();
369
+ expect(post?.permalink).toBeDefined();
370
+ expect(post?.publishedAtFormatted).toBeDefined();
369
371
  });
370
372
 
371
373
  it("handles empty map", () => {
@@ -12,7 +12,7 @@ import type { Context } from "hono";
12
12
  import type { Child } from "hono/jsx";
13
13
  import type { ThemeComponents, SiteLayoutProps } from "../types.js";
14
14
  import { BaseLayout } from "../theme/layouts/BaseLayout.js";
15
- import { SiteLayout as DefaultSiteLayout } from "../theme/layouts/SiteLayout.js";
15
+ import { SiteLayout as DefaultSiteLayout } from "../themes/minimal/MinimalSiteLayout.js";
16
16
  import type { NavigationData } from "./navigation.js";
17
17
 
18
18
  export interface RenderPublicPageOptions {
package/src/lib/view.ts CHANGED
@@ -109,7 +109,7 @@ export function toMediaView(media: Media, ctx: MediaContext): MediaView {
109
109
  * const postView = toPostView({ ...post, mediaAttachments: [...] }, mediaCtx);
110
110
  * ```
111
111
  */
112
- export function toPostView(post: PostWithMedia, ctx: MediaContext): PostView {
112
+ export function toPostView(post: PostWithMedia, _ctx: MediaContext): PostView {
113
113
  const permalink = `/p/${encode(post.id)}`;
114
114
 
115
115
  // Pre-compute excerpt from raw content
@@ -9,8 +9,8 @@ import type { Bindings, TimelineItemView } from "../../types.js";
9
9
  import type { AppVariables } from "../../app.js";
10
10
  import { sse } from "../../lib/sse.js";
11
11
  import { buildMediaMap } from "../../lib/media-helpers.js";
12
- import { TimelineItem } from "../../theme/components/timeline/TimelineItem.js";
13
- import { ThreadPreview as DefaultThreadPreview } from "../../theme/components/timeline/ThreadPreview.js";
12
+ import { TimelineItem } from "../../themes/minimal/timeline/TimelineItem.js";
13
+ import { ThreadPreview as DefaultThreadPreview } from "../../themes/minimal/timeline/ThreadPreview.js";
14
14
  import { createMediaContext, toPostView, toPostViews } from "../../lib/view.js";
15
15
 
16
16
  type Env = { Bindings: Bindings; Variables: AppVariables };
@@ -140,7 +140,7 @@ timelineApiRoutes.get("/", async (c) => {
140
140
 
141
141
  // Build load-more button HTML
142
142
  const loadMoreHtml = nextCursor
143
- ? `<div id="load-more-container" class="mt-6 text-center"><button class="btn btn-outline" data-on:click="@get('/api/timeline?cursor=${nextCursor}')">Load more</button></div>`
143
+ ? `<div id="load-more-container" class="mt-8 text-center"><button class="text-sm text-muted-foreground hover:text-foreground hover:underline" data-on:click="@get('/api/timeline?cursor=${nextCursor}')">Load more</button></div>`
144
144
  : "";
145
145
 
146
146
  return sse(c, async (stream) => {
@@ -8,7 +8,7 @@ import { Hono } from "hono";
8
8
  import type { Bindings, PostType } from "../../types.js";
9
9
  import type { AppVariables } from "../../app.js";
10
10
  import { POST_TYPES } from "../../types.js";
11
- import { ArchivePage as DefaultArchivePage } from "../../theme/pages/ArchivePage.js";
11
+ import { ArchivePage as DefaultArchivePage } from "../../themes/minimal/pages/ArchivePage.js";
12
12
  import { getNavigationData } from "../../lib/navigation.js";
13
13
  import { renderPublicPage } from "../../lib/render.js";
14
14
  import { createMediaContext, toArchiveGroups } from "../../lib/view.js";
@@ -5,7 +5,7 @@
5
5
  import { Hono } from "hono";
6
6
  import type { Bindings } from "../../types.js";
7
7
  import type { AppVariables } from "../../app.js";
8
- import { CollectionPage as DefaultCollectionPage } from "../../theme/pages/CollectionPage.js";
8
+ import { CollectionPage as DefaultCollectionPage } from "../../themes/minimal/pages/CollectionPage.js";
9
9
  import { getNavigationData } from "../../lib/navigation.js";
10
10
  import { renderPublicPage } from "../../lib/render.js";
11
11
  import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
@@ -10,7 +10,7 @@ import type { AppVariables } from "../../app.js";
10
10
  import { buildMediaMap } from "../../lib/media-helpers.js";
11
11
  import { getNavigationData } from "../../lib/navigation.js";
12
12
  import { renderPublicPage } from "../../lib/render.js";
13
- import { HomePage as DefaultHomePage } from "../../theme/pages/HomePage.js";
13
+ import { HomePage as DefaultHomePage } from "../../themes/minimal/pages/HomePage.js";
14
14
  import { createMediaContext, toPostView, toPostViews } from "../../lib/view.js";
15
15
 
16
16
  type Env = { Bindings: Bindings; Variables: AppVariables };
@@ -7,7 +7,7 @@
7
7
  import { Hono } from "hono";
8
8
  import type { Bindings } from "../../types.js";
9
9
  import type { AppVariables } from "../../app.js";
10
- import { SinglePage as DefaultSinglePage } from "../../theme/pages/SinglePage.js";
10
+ import { SinglePage as DefaultSinglePage } from "../../themes/minimal/pages/SinglePage.js";
11
11
  import { getNavigationData } from "../../lib/navigation.js";
12
12
  import { renderPublicPage } from "../../lib/render.js";
13
13
  import { createMediaContext, toPostViewFromPost } from "../../lib/view.js";
@@ -5,7 +5,7 @@
5
5
  import { Hono } from "hono";
6
6
  import type { Bindings } from "../../types.js";
7
7
  import type { AppVariables } from "../../app.js";
8
- import { PostPage as DefaultPostPage } from "../../theme/pages/PostPage.js";
8
+ import { PostPage as DefaultPostPage } from "../../themes/minimal/pages/PostPage.js";
9
9
  import * as sqid from "../../lib/sqid.js";
10
10
  import { getNavigationData } from "../../lib/navigation.js";
11
11
  import { renderPublicPage } from "../../lib/render.js";
@@ -5,7 +5,7 @@
5
5
  import { Hono } from "hono";
6
6
  import type { Bindings, SearchResult } from "../../types.js";
7
7
  import type { AppVariables } from "../../app.js";
8
- import { SearchPage as DefaultSearchPage } from "../../theme/pages/SearchPage.js";
8
+ import { SearchPage as DefaultSearchPage } from "../../themes/minimal/pages/SearchPage.js";
9
9
  import { getNavigationData } from "../../lib/navigation.js";
10
10
  import { renderPublicPage } from "../../lib/render.js";
11
11
  import { createMediaContext, toSearchResultViews } from "../../lib/view.js";
@@ -124,60 +124,6 @@
124
124
  }
125
125
  }
126
126
 
127
- /* Timeline cards */
128
- @layer components {
129
- .timeline-card {
130
- @apply rounded-lg border p-4;
131
- border-color: var(--color-border);
132
- }
133
-
134
- .timeline-card-link {
135
- border-left-width: 4px;
136
- border-left-color: var(--color-primary);
137
- }
138
-
139
- .timeline-card-quote {
140
- border-left-width: 4px;
141
- border-left-color: var(--color-muted-foreground);
142
- background-color: var(--color-muted);
143
- }
144
-
145
- .timeline-card-image {
146
- @apply p-0 overflow-hidden;
147
- }
148
-
149
- .timeline-card-image-gallery {
150
- /* Remove default margin from MediaGallery inside image cards */
151
- > div {
152
- @apply mt-0;
153
- }
154
- }
155
-
156
- .timeline-card-compact {
157
- @apply p-3 text-sm;
158
- }
159
-
160
- .timeline-thread-replies {
161
- @apply relative ml-5;
162
- }
163
-
164
- .timeline-thread-reply {
165
- @apply relative pl-5 mt-3;
166
-
167
- &::after {
168
- content: "";
169
- @apply absolute left-0 top-0 bottom-0 w-px;
170
- background-color: var(--color-border);
171
- }
172
-
173
- &::before {
174
- content: "";
175
- @apply absolute left-0 top-4 h-px w-4;
176
- background-color: var(--color-border);
177
- }
178
- }
179
- }
180
-
181
127
  @keyframes toast-in {
182
128
  from {
183
129
  opacity: 0;
@@ -21,16 +21,3 @@ export {
21
21
  VisibilityBadge,
22
22
  type VisibilityBadgeProps,
23
23
  } from "./VisibilityBadge.js";
24
-
25
- // Timeline components
26
- export {
27
- NoteCard,
28
- ArticleCard,
29
- LinkCard,
30
- QuoteCard,
31
- ImageCard,
32
- ThreadPreview,
33
- TimelineItem,
34
- TimelineItemFromPost,
35
- TimelineFeed,
36
- } from "./timeline/index.js";
@@ -1,28 +1,22 @@
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 { PostPage } from "@jant/core/theme";
9
- * import type { PostPageProps } from "@jant/core";
10
- *
11
- * export function MyPostPage(props: PostPageProps) {
12
- * return (
13
- * <div class="my-wrapper">
14
- * <PostPage {...props} />
15
- * </div>
16
- * );
17
- * }
9
+ * // In a theme package:
10
+ * import { MediaGallery, Pagination } from "@jant/core/theme";
11
+ * import type { ColorTheme } from "@jant/core/theme";
18
12
  * ```
19
13
  */
20
14
 
21
- // Layout components
15
+ // Layout components (BaseLayout, DashLayout)
22
16
  export * from "./layouts/index.js";
23
17
 
24
- // UI components
18
+ // Shared UI components (MediaGallery, Pagination, EmptyState, etc.)
25
19
  export * from "./components/index.js";
26
20
 
27
- // Page components
28
- export * from "./pages/index.js";
21
+ // Color themes
22
+ export * from "./color-themes.js";
@@ -4,5 +4,4 @@ export {
4
4
  type ToastProps,
5
5
  } from "./BaseLayout.js";
6
6
  export { DashLayout, type DashLayoutProps } from "./DashLayout.js";
7
- export { SiteLayout } from "./SiteLayout.js";
8
7
  export type { SiteLayoutProps } from "../../types.js";