@jant/core 0.3.23 → 0.3.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/app.js +4 -5
  2. package/dist/db/schema.js +72 -47
  3. package/dist/i18n/locales/en.js +1 -1
  4. package/dist/i18n/locales/zh-Hans.js +1 -1
  5. package/dist/i18n/locales/zh-Hant.js +1 -1
  6. package/dist/index.js +3 -3
  7. package/dist/lib/constants.js +1 -4
  8. package/dist/lib/excerpt.js +76 -0
  9. package/dist/lib/feed.js +18 -7
  10. package/dist/lib/navigation.js +4 -5
  11. package/dist/lib/render.js +1 -1
  12. package/dist/lib/schemas.js +80 -38
  13. package/dist/lib/theme-components.js +8 -11
  14. package/dist/lib/time.js +56 -1
  15. package/dist/lib/timeline.js +119 -0
  16. package/dist/lib/view.js +61 -72
  17. package/dist/routes/api/posts.js +29 -35
  18. package/dist/routes/api/search.js +5 -6
  19. package/dist/routes/api/upload.js +13 -13
  20. package/dist/routes/dash/collections.js +22 -40
  21. package/dist/routes/dash/index.js +2 -2
  22. package/dist/routes/dash/navigation.js +25 -24
  23. package/dist/routes/dash/pages.js +42 -57
  24. package/dist/routes/dash/posts.js +27 -35
  25. package/dist/routes/feed/rss.js +2 -4
  26. package/dist/routes/feed/sitemap.js +10 -7
  27. package/dist/routes/pages/archive.js +12 -11
  28. package/dist/routes/pages/collection.js +11 -5
  29. package/dist/routes/pages/home.js +53 -61
  30. package/dist/routes/pages/page.js +60 -29
  31. package/dist/routes/pages/post.js +5 -12
  32. package/dist/routes/pages/search.js +3 -4
  33. package/dist/services/collection.js +52 -64
  34. package/dist/services/index.js +5 -3
  35. package/dist/services/navigation.js +29 -53
  36. package/dist/services/page.js +80 -0
  37. package/dist/services/post.js +68 -69
  38. package/dist/services/search.js +24 -18
  39. package/dist/theme/components/MediaGallery.js +19 -91
  40. package/dist/theme/components/PageForm.js +15 -15
  41. package/dist/theme/components/PostForm.js +136 -129
  42. package/dist/theme/components/PostList.js +13 -8
  43. package/dist/theme/components/ThreadView.js +3 -3
  44. package/dist/theme/components/TypeBadge.js +3 -14
  45. package/dist/theme/components/VisibilityBadge.js +33 -23
  46. package/dist/themes/threads/ThreadsSiteLayout.js +172 -0
  47. package/dist/themes/threads/index.js +81 -0
  48. package/dist/themes/{minimal → threads}/pages/ArchivePage.js +32 -47
  49. package/dist/themes/threads/pages/CollectionPage.js +65 -0
  50. package/dist/themes/{minimal → threads}/pages/HomePage.js +3 -3
  51. package/dist/themes/{minimal → threads}/pages/PostPage.js +12 -9
  52. package/dist/themes/{minimal → threads}/pages/SearchPage.js +13 -14
  53. package/dist/themes/{minimal → threads}/pages/SinglePage.js +4 -4
  54. package/dist/themes/threads/timeline/LinkCard.js +68 -0
  55. package/dist/themes/threads/timeline/NoteCard.js +53 -0
  56. package/dist/themes/threads/timeline/QuoteCard.js +59 -0
  57. package/dist/themes/{minimal → threads}/timeline/ThreadPreview.js +17 -13
  58. package/dist/themes/threads/timeline/TimelineFeed.js +58 -0
  59. package/dist/themes/{minimal → threads}/timeline/TimelineItem.js +8 -16
  60. package/dist/themes/threads/timeline/TimelineLoadMore.js +23 -0
  61. package/dist/themes/threads/timeline/groupByDate.js +22 -0
  62. package/dist/themes/threads/timeline/timelineMore.js +107 -0
  63. package/dist/types.js +24 -40
  64. package/package.json +2 -1
  65. package/src/__tests__/helpers/app.ts +4 -0
  66. package/src/__tests__/helpers/db.ts +51 -74
  67. package/src/app.tsx +4 -6
  68. package/src/db/migrations/0005_v2_schema_migration.sql +268 -0
  69. package/src/db/migrations/meta/_journal.json +7 -0
  70. package/src/db/schema.ts +63 -46
  71. package/src/i18n/locales/en.po +216 -164
  72. package/src/i18n/locales/en.ts +1 -1
  73. package/src/i18n/locales/zh-Hans.po +216 -164
  74. package/src/i18n/locales/zh-Hans.ts +1 -1
  75. package/src/i18n/locales/zh-Hant.po +216 -164
  76. package/src/i18n/locales/zh-Hant.ts +1 -1
  77. package/src/index.ts +28 -12
  78. package/src/lib/__tests__/excerpt.test.ts +125 -0
  79. package/src/lib/__tests__/schemas.test.ts +166 -105
  80. package/src/lib/__tests__/theme-components.test.ts +4 -25
  81. package/src/lib/__tests__/time.test.ts +62 -0
  82. package/src/{routes/api → lib}/__tests__/timeline.test.ts +108 -66
  83. package/src/lib/__tests__/view.test.ts +199 -51
  84. package/src/lib/constants.ts +1 -4
  85. package/src/lib/excerpt.ts +87 -0
  86. package/src/lib/feed.ts +22 -7
  87. package/src/lib/navigation.ts +6 -7
  88. package/src/lib/render.tsx +1 -1
  89. package/src/lib/schemas.ts +118 -52
  90. package/src/lib/theme-components.ts +10 -13
  91. package/src/lib/time.ts +64 -0
  92. package/src/lib/timeline.ts +170 -0
  93. package/src/lib/view.ts +80 -82
  94. package/src/preset.css +45 -0
  95. package/src/routes/api/__tests__/posts.test.ts +50 -108
  96. package/src/routes/api/__tests__/search.test.ts +2 -3
  97. package/src/routes/api/posts.ts +30 -30
  98. package/src/routes/api/search.ts +4 -4
  99. package/src/routes/api/upload.ts +16 -6
  100. package/src/routes/dash/collections.tsx +18 -40
  101. package/src/routes/dash/index.tsx +2 -2
  102. package/src/routes/dash/navigation.tsx +27 -26
  103. package/src/routes/dash/pages.tsx +45 -60
  104. package/src/routes/dash/posts.tsx +44 -52
  105. package/src/routes/feed/rss.ts +2 -1
  106. package/src/routes/feed/sitemap.ts +14 -4
  107. package/src/routes/pages/archive.tsx +14 -10
  108. package/src/routes/pages/collection.tsx +17 -6
  109. package/src/routes/pages/home.tsx +56 -81
  110. package/src/routes/pages/page.tsx +64 -27
  111. package/src/routes/pages/post.tsx +5 -14
  112. package/src/routes/pages/search.tsx +2 -2
  113. package/src/services/__tests__/collection.test.ts +257 -158
  114. package/src/services/__tests__/media.test.ts +18 -18
  115. package/src/services/__tests__/navigation.test.ts +161 -87
  116. package/src/services/__tests__/post-timeline.test.ts +92 -88
  117. package/src/services/__tests__/post.test.ts +342 -206
  118. package/src/services/__tests__/search.test.ts +19 -25
  119. package/src/services/collection.ts +71 -113
  120. package/src/services/index.ts +9 -8
  121. package/src/services/navigation.ts +38 -71
  122. package/src/services/page.ts +124 -0
  123. package/src/services/post.ts +93 -103
  124. package/src/services/search.ts +38 -27
  125. package/src/theme/components/MediaGallery.tsx +27 -96
  126. package/src/theme/components/PageForm.tsx +21 -21
  127. package/src/theme/components/PostForm.tsx +122 -118
  128. package/src/theme/components/PostList.tsx +58 -49
  129. package/src/theme/components/ThreadView.tsx +6 -3
  130. package/src/theme/components/TypeBadge.tsx +9 -17
  131. package/src/theme/components/VisibilityBadge.tsx +40 -23
  132. package/src/themes/threads/ThreadsSiteLayout.tsx +194 -0
  133. package/src/themes/{minimal → threads}/index.ts +30 -13
  134. package/src/themes/{minimal → threads}/pages/ArchivePage.tsx +53 -53
  135. package/src/themes/threads/pages/CollectionPage.tsx +61 -0
  136. package/src/themes/{minimal → threads}/pages/HomePage.tsx +3 -3
  137. package/src/themes/{minimal → threads}/pages/PostPage.tsx +12 -8
  138. package/src/themes/{minimal → threads}/pages/SearchPage.tsx +15 -13
  139. package/src/themes/{minimal → threads}/pages/SinglePage.tsx +4 -4
  140. package/src/themes/threads/style.css +336 -0
  141. package/src/themes/threads/timeline/LinkCard.tsx +67 -0
  142. package/src/themes/threads/timeline/NoteCard.tsx +58 -0
  143. package/src/themes/threads/timeline/QuoteCard.tsx +63 -0
  144. package/src/themes/{minimal → threads}/timeline/ThreadPreview.tsx +15 -13
  145. package/src/themes/threads/timeline/TimelineFeed.tsx +62 -0
  146. package/src/themes/{minimal → threads}/timeline/TimelineItem.tsx +9 -17
  147. package/src/themes/threads/timeline/TimelineLoadMore.tsx +35 -0
  148. package/src/themes/threads/timeline/groupByDate.ts +30 -0
  149. package/src/themes/threads/timeline/timelineMore.tsx +130 -0
  150. package/src/types.ts +242 -98
  151. package/dist/routes/api/timeline.js +0 -120
  152. package/dist/themes/minimal/MinimalSiteLayout.js +0 -83
  153. package/dist/themes/minimal/index.js +0 -65
  154. package/dist/themes/minimal/pages/CollectionPage.js +0 -65
  155. package/dist/themes/minimal/timeline/ArticleCard.js +0 -36
  156. package/dist/themes/minimal/timeline/ImageCard.js +0 -67
  157. package/dist/themes/minimal/timeline/LinkCard.js +0 -47
  158. package/dist/themes/minimal/timeline/NoteCard.js +0 -34
  159. package/dist/themes/minimal/timeline/QuoteCard.js +0 -48
  160. package/dist/themes/minimal/timeline/TimelineFeed.js +0 -48
  161. package/src/routes/api/timeline.tsx +0 -159
  162. package/src/themes/minimal/MinimalSiteLayout.tsx +0 -100
  163. package/src/themes/minimal/pages/CollectionPage.tsx +0 -60
  164. package/src/themes/minimal/timeline/ArticleCard.tsx +0 -37
  165. package/src/themes/minimal/timeline/ImageCard.tsx +0 -63
  166. package/src/themes/minimal/timeline/LinkCard.tsx +0 -48
  167. package/src/themes/minimal/timeline/NoteCard.tsx +0 -35
  168. package/src/themes/minimal/timeline/QuoteCard.tsx +0 -49
  169. package/src/themes/minimal/timeline/TimelineFeed.tsx +0 -57
@@ -13,76 +13,50 @@ describe("PostService - Timeline features", () => {
13
13
  postService = createPostService(db);
14
14
  });
15
15
 
16
- describe("excludeTypes filter", () => {
17
- it("excludes posts of specified types", async () => {
18
- await postService.create({ type: "note", content: "a note" });
19
- await postService.create({ type: "page", content: "a page" });
16
+ describe("format filter", () => {
17
+ it("filters by format", async () => {
18
+ await postService.create({ format: "note", body: "a note" });
20
19
  await postService.create({
21
- type: "article",
22
- content: "an article",
23
- title: "Article",
20
+ format: "link",
21
+ body: "a link",
22
+ url: "https://example.com",
24
23
  });
25
-
26
- const posts = await postService.list({ excludeTypes: ["page"] });
27
- expect(posts).toHaveLength(2);
28
- expect(posts.every((p) => p.type !== "page")).toBe(true);
29
- });
30
-
31
- it("excludes multiple types", async () => {
32
- await postService.create({ type: "note", content: "a note" });
33
- await postService.create({ type: "page", content: "a page" });
34
24
  await postService.create({
35
- type: "article",
36
- content: "an article",
37
- title: "Article",
38
- });
39
- await postService.create({
40
- type: "link",
41
- content: "a link",
42
- sourceUrl: "https://example.com",
25
+ format: "quote",
26
+ body: "a quote",
27
+ quoteText: "something wise",
43
28
  });
44
29
 
45
- const posts = await postService.list({
46
- excludeTypes: ["page", "link"],
47
- });
48
- expect(posts).toHaveLength(2);
49
- expect(posts.every((p) => p.type !== "page" && p.type !== "link")).toBe(
50
- true,
51
- );
52
- });
53
-
54
- it("returns all posts when excludeTypes is empty", async () => {
55
- await postService.create({ type: "note", content: "a note" });
56
- await postService.create({ type: "page", content: "a page" });
57
-
58
- const posts = await postService.list({ excludeTypes: [] });
59
- expect(posts).toHaveLength(2);
30
+ const posts = await postService.list({ format: "note" });
31
+ expect(posts).toHaveLength(1);
32
+ expect(posts[0]?.format).toBe("note");
60
33
  });
61
34
 
62
- it("works combined with other filters", async () => {
35
+ it("combines format and status filters", async () => {
63
36
  await postService.create({
64
- type: "note",
65
- content: "featured note",
66
- visibility: "featured",
37
+ format: "note",
38
+ body: "published note",
39
+ status: "published",
67
40
  });
68
41
  await postService.create({
69
- type: "page",
70
- content: "featured page",
71
- visibility: "featured",
42
+ format: "note",
43
+ body: "draft note",
44
+ status: "draft",
72
45
  });
73
46
  await postService.create({
74
- type: "note",
75
- content: "draft note",
76
- visibility: "draft",
47
+ format: "link",
48
+ body: "published link",
49
+ status: "published",
50
+ url: "https://example.com",
77
51
  });
78
52
 
79
53
  const posts = await postService.list({
80
- excludeTypes: ["page"],
81
- visibility: "featured",
54
+ format: "note",
55
+ status: "published",
82
56
  });
83
57
  expect(posts).toHaveLength(1);
84
- expect(posts[0]?.type).toBe("note");
85
- expect(posts[0]?.visibility).toBe("featured");
58
+ expect(posts[0]?.format).toBe("note");
59
+ expect(posts[0]?.status).toBe("published");
86
60
  });
87
61
  });
88
62
 
@@ -94,17 +68,17 @@ describe("PostService - Timeline features", () => {
94
68
 
95
69
  it("returns preview replies for a thread root", async () => {
96
70
  const root = await postService.create({
97
- type: "note",
98
- content: "root",
71
+ format: "note",
72
+ body: "root",
99
73
  });
100
74
  await postService.create({
101
- type: "note",
102
- content: "reply 1",
75
+ format: "note",
76
+ body: "reply 1",
103
77
  replyToId: root.id,
104
78
  });
105
79
  await postService.create({
106
- type: "note",
107
- content: "reply 2",
80
+ format: "note",
81
+ body: "reply 2",
108
82
  replyToId: root.id,
109
83
  });
110
84
 
@@ -112,19 +86,19 @@ describe("PostService - Timeline features", () => {
112
86
  const replies = previews.get(root.id);
113
87
  expect(replies).toBeDefined();
114
88
  expect(replies).toHaveLength(2);
115
- expect(replies?.[0]?.content).toBe("reply 1");
116
- expect(replies?.[1]?.content).toBe("reply 2");
89
+ expect(replies?.[0]?.body).toBe("reply 1");
90
+ expect(replies?.[1]?.body).toBe("reply 2");
117
91
  });
118
92
 
119
93
  it("limits preview replies to previewCount", async () => {
120
94
  const root = await postService.create({
121
- type: "note",
122
- content: "root",
95
+ format: "note",
96
+ body: "root",
123
97
  });
124
98
  for (let i = 0; i < 5; i++) {
125
99
  await postService.create({
126
- type: "note",
127
- content: `reply ${i}`,
100
+ format: "note",
101
+ body: `reply ${i}`,
128
102
  replyToId: root.id,
129
103
  });
130
104
  }
@@ -132,19 +106,19 @@ describe("PostService - Timeline features", () => {
132
106
  const previews = await postService.getThreadPreviews([root.id], 2);
133
107
  const replies = previews.get(root.id);
134
108
  expect(replies).toHaveLength(2);
135
- expect(replies?.[0]?.content).toBe("reply 0");
136
- expect(replies?.[1]?.content).toBe("reply 1");
109
+ expect(replies?.[0]?.body).toBe("reply 0");
110
+ expect(replies?.[1]?.body).toBe("reply 1");
137
111
  });
138
112
 
139
113
  it("defaults to 3 preview replies", async () => {
140
114
  const root = await postService.create({
141
- type: "note",
142
- content: "root",
115
+ format: "note",
116
+ body: "root",
143
117
  });
144
118
  for (let i = 0; i < 5; i++) {
145
119
  await postService.create({
146
- type: "note",
147
- content: `reply ${i}`,
120
+ format: "note",
121
+ body: `reply ${i}`,
148
122
  replyToId: root.id,
149
123
  });
150
124
  }
@@ -156,21 +130,21 @@ describe("PostService - Timeline features", () => {
156
130
 
157
131
  it("handles multiple thread roots", async () => {
158
132
  const root1 = await postService.create({
159
- type: "note",
160
- content: "root 1",
133
+ format: "note",
134
+ body: "root 1",
161
135
  });
162
136
  const root2 = await postService.create({
163
- type: "note",
164
- content: "root 2",
137
+ format: "note",
138
+ body: "root 2",
165
139
  });
166
140
  await postService.create({
167
- type: "note",
168
- content: "reply to root 1",
141
+ format: "note",
142
+ body: "reply to root 1",
169
143
  replyToId: root1.id,
170
144
  });
171
145
  await postService.create({
172
- type: "note",
173
- content: "reply to root 2",
146
+ format: "note",
147
+ body: "reply to root 2",
174
148
  replyToId: root2.id,
175
149
  });
176
150
 
@@ -185,17 +159,17 @@ describe("PostService - Timeline features", () => {
185
159
 
186
160
  it("excludes deleted replies", async () => {
187
161
  const root = await postService.create({
188
- type: "note",
189
- content: "root",
162
+ format: "note",
163
+ body: "root",
190
164
  });
191
165
  const reply1 = await postService.create({
192
- type: "note",
193
- content: "reply 1",
166
+ format: "note",
167
+ body: "reply 1",
194
168
  replyToId: root.id,
195
169
  });
196
170
  await postService.create({
197
- type: "note",
198
- content: "reply 2",
171
+ format: "note",
172
+ body: "reply 2",
199
173
  replyToId: root.id,
200
174
  });
201
175
 
@@ -204,17 +178,47 @@ describe("PostService - Timeline features", () => {
204
178
  const previews = await postService.getThreadPreviews([root.id]);
205
179
  const replies = previews.get(root.id);
206
180
  expect(replies).toHaveLength(1);
207
- expect(replies?.[0]?.content).toBe("reply 2");
181
+ expect(replies?.[0]?.body).toBe("reply 2");
208
182
  });
209
183
 
210
184
  it("returns empty for roots with no replies", async () => {
211
185
  const root = await postService.create({
212
- type: "note",
213
- content: "root with no replies",
186
+ format: "note",
187
+ body: "root with no replies",
214
188
  });
215
189
 
216
190
  const previews = await postService.getThreadPreviews([root.id]);
217
191
  expect(previews.get(root.id)).toBeUndefined();
218
192
  });
219
193
  });
194
+
195
+ describe("timeline assembly", () => {
196
+ it("fetches published non-reply posts for the timeline", async () => {
197
+ const root = await postService.create({
198
+ format: "note",
199
+ body: "a published note",
200
+ status: "published",
201
+ });
202
+ await postService.create({
203
+ format: "note",
204
+ body: "a reply",
205
+ status: "published",
206
+ replyToId: root.id,
207
+ });
208
+ await postService.create({
209
+ format: "note",
210
+ body: "a draft",
211
+ status: "draft",
212
+ });
213
+
214
+ const posts = await postService.list({
215
+ status: "published",
216
+ excludeReplies: true,
217
+ limit: 21,
218
+ });
219
+
220
+ expect(posts).toHaveLength(1);
221
+ expect(posts[0]?.body).toBe("a published note");
222
+ });
223
+ });
220
224
  });