@jant/core 0.3.36 → 0.3.38

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 (271) hide show
  1. package/bin/commands/export.js +1 -1
  2. package/bin/commands/import-site.js +529 -0
  3. package/bin/commands/reset-password.js +3 -2
  4. package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
  5. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  6. package/dist/client/client.css +1 -1
  7. package/dist/client/client.js +4012 -3276
  8. package/dist/index.js +10285 -5809
  9. package/package.json +11 -3
  10. package/src/__tests__/helpers/app.ts +9 -9
  11. package/src/__tests__/helpers/db.ts +91 -93
  12. package/src/app.tsx +157 -27
  13. package/src/auth.ts +20 -2
  14. package/src/client/archive-nav.js +187 -0
  15. package/src/client/audio-player.ts +478 -0
  16. package/src/client/audio-processor.ts +84 -0
  17. package/src/client/avatar-upload.ts +3 -2
  18. package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
  19. package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
  20. package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
  21. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  22. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  23. package/src/client/components/collection-sidebar-types.ts +7 -9
  24. package/src/client/components/compose-types.ts +101 -4
  25. package/src/client/components/jant-collection-form.ts +43 -7
  26. package/src/client/components/jant-collection-sidebar.ts +88 -84
  27. package/src/client/components/jant-compose-dialog.ts +1655 -219
  28. package/src/client/components/jant-compose-editor.ts +732 -168
  29. package/src/client/components/jant-compose-fullscreen.ts +23 -78
  30. package/src/client/components/jant-media-lightbox.ts +2 -0
  31. package/src/client/components/jant-nav-manager.ts +24 -284
  32. package/src/client/components/jant-post-form.ts +89 -9
  33. package/src/client/components/jant-post-menu.ts +1019 -0
  34. package/src/client/components/jant-settings-avatar.ts +3 -3
  35. package/src/client/components/jant-settings-general.ts +38 -4
  36. package/src/client/components/jant-text-preview.ts +232 -0
  37. package/src/client/components/nav-manager-types.ts +4 -19
  38. package/src/client/components/post-form-template.ts +107 -12
  39. package/src/client/components/post-form-types.ts +11 -4
  40. package/src/client/compose-bridge.ts +410 -109
  41. package/src/client/image-processor.ts +26 -8
  42. package/src/client/media-metadata.ts +247 -0
  43. package/src/client/multipart-upload.ts +160 -0
  44. package/src/client/post-form-bridge.ts +52 -1
  45. package/src/client/settings-bridge.ts +0 -12
  46. package/src/client/thread-context.ts +140 -0
  47. package/src/client/tiptap/create-editor.ts +46 -0
  48. package/src/client/tiptap/extensions.ts +5 -0
  49. package/src/client/tiptap/image-node.ts +2 -8
  50. package/src/client/tiptap/paste-image.ts +2 -13
  51. package/src/client/tiptap/slash-commands.ts +173 -63
  52. package/src/client/toast.ts +101 -3
  53. package/src/client/types/sortablejs.d.ts +15 -0
  54. package/src/client/upload-with-metadata.ts +54 -0
  55. package/src/client/video-processor.ts +207 -0
  56. package/src/client.ts +5 -2
  57. package/src/db/__tests__/migrations.test.ts +118 -0
  58. package/src/db/index.ts +52 -0
  59. package/src/db/migrations/0000_baseline.sql +269 -0
  60. package/src/db/migrations/0001_fts_setup.sql +31 -0
  61. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  62. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  63. package/src/db/migrations/meta/_journal.json +4 -39
  64. package/src/db/schema.ts +409 -145
  65. package/src/i18n/__tests__/detect.test.ts +115 -0
  66. package/src/i18n/context.tsx +2 -2
  67. package/src/i18n/detect.ts +85 -1
  68. package/src/i18n/i18n.ts +1 -1
  69. package/src/i18n/index.ts +2 -1
  70. package/src/i18n/locales/en.po +487 -1217
  71. package/src/i18n/locales/en.ts +1 -1
  72. package/src/i18n/locales/zh-Hans.po +613 -996
  73. package/src/i18n/locales/zh-Hans.ts +1 -1
  74. package/src/i18n/locales/zh-Hant.po +624 -1007
  75. package/src/i18n/locales/zh-Hant.ts +1 -1
  76. package/src/i18n/middleware.ts +6 -0
  77. package/src/index.ts +5 -7
  78. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  79. package/src/lib/__tests__/constants.test.ts +0 -1
  80. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  81. package/src/lib/__tests__/nanoid.test.ts +26 -0
  82. package/src/lib/__tests__/schemas.test.ts +181 -63
  83. package/src/lib/__tests__/slug.test.ts +126 -0
  84. package/src/lib/__tests__/sse.test.ts +6 -6
  85. package/src/lib/__tests__/summary.test.ts +264 -0
  86. package/src/lib/__tests__/theme.test.ts +1 -1
  87. package/src/lib/__tests__/timeline.test.ts +33 -30
  88. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  89. package/src/lib/__tests__/view.test.ts +141 -66
  90. package/src/lib/blurhash-placeholder.ts +102 -0
  91. package/src/lib/constants.ts +3 -1
  92. package/src/lib/emoji-catalog.ts +885 -68
  93. package/src/lib/errors.ts +11 -8
  94. package/src/lib/feed.ts +78 -32
  95. package/src/lib/html.ts +2 -1
  96. package/src/lib/icon-catalog.ts +5033 -1
  97. package/src/lib/icons.ts +3 -2
  98. package/src/lib/index.ts +0 -1
  99. package/src/lib/markdown-to-tiptap.ts +286 -0
  100. package/src/lib/media-helpers.ts +12 -3
  101. package/src/lib/nanoid.ts +29 -0
  102. package/src/lib/navigation.ts +1 -1
  103. package/src/lib/render.tsx +20 -2
  104. package/src/lib/resolve-config.ts +6 -2
  105. package/src/lib/schemas.ts +224 -55
  106. package/src/lib/search-snippet.ts +34 -0
  107. package/src/lib/slug.ts +96 -0
  108. package/src/lib/sse.ts +6 -6
  109. package/src/lib/storage.ts +115 -7
  110. package/src/lib/summary.ts +66 -0
  111. package/src/lib/theme.ts +11 -8
  112. package/src/lib/timeline.ts +74 -34
  113. package/src/lib/tiptap-render.ts +5 -10
  114. package/src/lib/tiptap-to-markdown.ts +305 -0
  115. package/src/lib/upload.ts +190 -29
  116. package/src/lib/url.ts +31 -0
  117. package/src/lib/view.ts +204 -37
  118. package/src/middleware/__tests__/auth.test.ts +191 -11
  119. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  120. package/src/middleware/auth.ts +63 -9
  121. package/src/middleware/onboarding.ts +1 -1
  122. package/src/middleware/secure-headers.ts +40 -0
  123. package/src/preset.css +45 -2
  124. package/src/routes/__tests__/compose.test.ts +17 -24
  125. package/src/routes/api/__tests__/collections.test.ts +109 -61
  126. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  127. package/src/routes/api/__tests__/posts.test.ts +132 -68
  128. package/src/routes/api/__tests__/search.test.ts +15 -2
  129. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  130. package/src/routes/api/collections.ts +51 -42
  131. package/src/routes/api/custom-urls.ts +80 -0
  132. package/src/routes/api/export.ts +31 -0
  133. package/src/routes/api/nav-items.ts +23 -19
  134. package/src/routes/api/posts.ts +43 -39
  135. package/src/routes/api/search.ts +3 -4
  136. package/src/routes/api/upload-multipart.ts +245 -0
  137. package/src/routes/api/upload.ts +85 -19
  138. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  139. package/src/routes/auth/setup.tsx +26 -33
  140. package/src/routes/auth/signin.tsx +3 -7
  141. package/src/routes/compose.tsx +10 -55
  142. package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
  143. package/src/routes/dash/custom-urls.tsx +414 -0
  144. package/src/routes/dash/settings.tsx +304 -232
  145. package/src/routes/feed/__tests__/rss.test.ts +27 -28
  146. package/src/routes/feed/rss.ts +6 -4
  147. package/src/routes/feed/sitemap.ts +2 -12
  148. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  149. package/src/routes/pages/__tests__/featured.test.ts +41 -22
  150. package/src/routes/pages/archive.tsx +175 -39
  151. package/src/routes/pages/collection.tsx +22 -10
  152. package/src/routes/pages/collections.tsx +3 -3
  153. package/src/routes/pages/featured.tsx +28 -4
  154. package/src/routes/pages/home.tsx +16 -15
  155. package/src/routes/pages/latest.tsx +1 -11
  156. package/src/routes/pages/new.tsx +39 -0
  157. package/src/routes/pages/page.tsx +95 -49
  158. package/src/routes/pages/search.tsx +1 -1
  159. package/src/services/__tests__/api-token.test.ts +135 -0
  160. package/src/services/__tests__/collection.test.ts +275 -227
  161. package/src/services/__tests__/custom-url.test.ts +213 -0
  162. package/src/services/__tests__/media.test.ts +162 -22
  163. package/src/services/__tests__/navigation.test.ts +109 -68
  164. package/src/services/__tests__/post-timeline.test.ts +205 -32
  165. package/src/services/__tests__/post.test.ts +713 -234
  166. package/src/services/__tests__/search.test.ts +67 -10
  167. package/src/services/api-token.ts +166 -0
  168. package/src/services/auth.ts +17 -2
  169. package/src/services/collection.ts +397 -131
  170. package/src/services/custom-url.ts +188 -0
  171. package/src/services/export.ts +802 -0
  172. package/src/services/index.ts +26 -19
  173. package/src/services/media.ts +100 -22
  174. package/src/services/navigation.ts +158 -47
  175. package/src/services/path.ts +339 -0
  176. package/src/services/post.ts +687 -154
  177. package/src/services/search.ts +160 -75
  178. package/src/styles/components.css +58 -7
  179. package/src/styles/tokens.css +84 -6
  180. package/src/styles/ui.css +2964 -457
  181. package/src/types/bindings.ts +4 -1
  182. package/src/types/config.ts +12 -4
  183. package/src/types/constants.ts +15 -3
  184. package/src/types/entities.ts +74 -35
  185. package/src/types/operations.ts +11 -24
  186. package/src/types/props.ts +51 -16
  187. package/src/types/views.ts +45 -22
  188. package/src/ui/color-themes.ts +133 -23
  189. package/src/ui/compose/ComposeDialog.tsx +239 -17
  190. package/src/ui/compose/ComposePrompt.tsx +1 -1
  191. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  192. package/src/ui/dash/ListItemRow.tsx +1 -1
  193. package/src/ui/dash/StatusBadge.tsx +3 -1
  194. package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
  195. package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
  196. package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
  197. package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
  198. package/src/ui/dash/index.ts +0 -3
  199. package/src/ui/dash/settings/AccountContent.tsx +3 -57
  200. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  201. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  202. package/src/ui/dash/settings/AvatarContent.tsx +8 -0
  203. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  204. package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
  205. package/src/ui/feed/LinkCard.tsx +89 -40
  206. package/src/ui/feed/NoteCard.tsx +39 -25
  207. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  208. package/src/ui/feed/QuoteCard.tsx +38 -23
  209. package/src/ui/feed/ThreadPreview.tsx +90 -26
  210. package/src/ui/feed/TimelineFeed.tsx +3 -2
  211. package/src/ui/feed/TimelineItem.tsx +15 -6
  212. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  213. package/src/ui/feed/thread-preview-state.ts +61 -0
  214. package/src/ui/font-themes.ts +2 -2
  215. package/src/ui/layouts/BaseLayout.tsx +2 -2
  216. package/src/ui/layouts/SiteLayout.tsx +105 -92
  217. package/src/ui/pages/ArchivePage.tsx +923 -98
  218. package/src/ui/pages/ComposePage.tsx +54 -0
  219. package/src/ui/pages/PostPage.tsx +30 -45
  220. package/src/ui/pages/SearchPage.tsx +181 -37
  221. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  222. package/src/ui/shared/CollectionsSidebar.tsx +47 -37
  223. package/src/ui/shared/MediaGallery.tsx +445 -149
  224. package/src/ui/shared/PostFooter.tsx +204 -0
  225. package/src/ui/shared/StarRating.tsx +27 -0
  226. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  227. package/src/ui/shared/index.ts +0 -1
  228. package/dist/client/assets/url-8Dj-5CLW.js +0 -1
  229. package/src/client/media-upload.ts +0 -161
  230. package/src/client/page-slug-bridge.ts +0 -42
  231. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  232. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  233. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  234. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  235. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  236. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  237. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  238. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  239. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  240. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  241. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  242. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  243. package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
  244. package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
  245. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  246. package/src/lib/__tests__/sqid.test.ts +0 -65
  247. package/src/lib/sqid.ts +0 -79
  248. package/src/routes/api/__tests__/pages.test.ts +0 -218
  249. package/src/routes/api/pages.ts +0 -73
  250. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  251. package/src/routes/dash/index.tsx +0 -109
  252. package/src/routes/dash/media.tsx +0 -135
  253. package/src/routes/dash/pages.tsx +0 -245
  254. package/src/routes/dash/posts.tsx +0 -338
  255. package/src/routes/dash/redirects.tsx +0 -263
  256. package/src/routes/pages/post.tsx +0 -59
  257. package/src/services/__tests__/page.test.ts +0 -298
  258. package/src/services/__tests__/path-registry.test.ts +0 -165
  259. package/src/services/__tests__/redirect.test.ts +0 -159
  260. package/src/services/page.ts +0 -216
  261. package/src/services/path-registry.ts +0 -160
  262. package/src/services/redirect.ts +0 -97
  263. package/src/ui/dash/PageForm.tsx +0 -187
  264. package/src/ui/dash/PostList.tsx +0 -95
  265. package/src/ui/dash/media/MediaListContent.tsx +0 -206
  266. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  267. package/src/ui/dash/pages/PagesContent.tsx +0 -75
  268. package/src/ui/dash/posts/PostForm.tsx +0 -260
  269. package/src/ui/layouts/DashLayout.tsx +0 -247
  270. package/src/ui/pages/SinglePage.tsx +0 -23
  271. package/src/ui/shared/ThreadView.tsx +0 -136
@@ -4,9 +4,9 @@ import type { Bindings } from "../../../types.js";
4
4
  import type { AppVariables } from "../../../types/app-context.js";
5
5
  import { createTestDatabase } from "../../../__tests__/helpers/db.js";
6
6
  import { createPostService } from "../../../services/post.js";
7
+ import { createPathService } from "../../../services/path.js";
7
8
  import { createSettingsService } from "../../../services/settings.js";
8
9
  import { createMediaService } from "../../../services/media.js";
9
- import { createPathRegistryService } from "../../../services/path-registry.js";
10
10
  import { resolveConfig } from "../../../lib/resolve-config.js";
11
11
  import { rssRoutes } from "../rss.js";
12
12
 
@@ -14,12 +14,11 @@ type Env = { Bindings: Bindings; Variables: AppVariables };
14
14
 
15
15
  function createFeedTestApp(envOverrides: Partial<Bindings> = {}) {
16
16
  const { db } = createTestDatabase();
17
+ const pathService = createPathService(db as never);
17
18
 
18
19
  const services = {
19
- posts: createPostService(
20
- db as never,
21
- createPathRegistryService(db as never),
22
- ),
20
+ paths: pathService,
21
+ posts: createPostService(db as never, { slugIdLength: 5 }, pathService),
23
22
  settings: createSettingsService(db as never),
24
23
  media: createMediaService(db as never),
25
24
  };
@@ -54,15 +53,15 @@ describe("RSS Feed Routes", () => {
54
53
  await services.posts.create({
55
54
  format: "note",
56
55
  title: "Regular Post",
57
- body: "Not featured",
56
+ bodyMarkdown: "Not featured",
58
57
  status: "published",
59
58
  });
60
59
  await services.posts.create({
61
60
  format: "note",
62
61
  title: "Featured Post",
63
- body: "This is featured",
62
+ bodyMarkdown: "This is featured",
64
63
  status: "published",
65
- visibility: "featured",
64
+ featured: true,
66
65
  });
67
66
 
68
67
  const res = await app.request("/feed");
@@ -79,7 +78,7 @@ describe("RSS Feed Routes", () => {
79
78
  await services.posts.create({
80
79
  format: "note",
81
80
  title: "Regular Post",
82
- body: "Not featured",
81
+ bodyMarkdown: "Not featured",
83
82
  status: "published",
84
83
  });
85
84
 
@@ -107,15 +106,15 @@ describe("RSS Feed Routes", () => {
107
106
  await services.posts.create({
108
107
  format: "note",
109
108
  title: "Regular Post",
110
- body: "Not featured",
109
+ bodyMarkdown: "Not featured",
111
110
  status: "published",
112
111
  });
113
112
  await services.posts.create({
114
113
  format: "note",
115
114
  title: "Featured Post",
116
- body: "This is featured",
115
+ bodyMarkdown: "This is featured",
117
116
  status: "published",
118
- visibility: "featured",
117
+ featured: true,
119
118
  });
120
119
 
121
120
  const res = await app.request("/feed/atom.xml");
@@ -137,20 +136,20 @@ describe("RSS Feed Routes", () => {
137
136
  await services.posts.create({
138
137
  format: "note",
139
138
  title: "Regular Post",
140
- body: "Not featured",
139
+ bodyMarkdown: "Not featured",
141
140
  status: "published",
142
141
  });
143
142
  await services.posts.create({
144
143
  format: "note",
145
144
  title: "Featured Post",
146
- body: "This is featured",
145
+ bodyMarkdown: "This is featured",
147
146
  status: "published",
148
- visibility: "featured",
147
+ featured: true,
149
148
  });
150
149
  await services.posts.create({
151
150
  format: "note",
152
151
  title: "Draft Post",
153
- body: "Draft",
152
+ bodyMarkdown: "Draft",
154
153
  status: "draft",
155
154
  });
156
155
 
@@ -169,7 +168,7 @@ describe("RSS Feed Routes", () => {
169
168
  await services.posts.create({
170
169
  format: "note",
171
170
  title: "My Note",
172
- body: "A note",
171
+ bodyMarkdown: "A note",
173
172
  status: "published",
174
173
  });
175
174
  await services.posts.create({
@@ -200,7 +199,7 @@ describe("RSS Feed Routes", () => {
200
199
  await services.posts.create({
201
200
  format: "note",
202
201
  title: "My Note",
203
- body: "A note",
202
+ bodyMarkdown: "A note",
204
203
  status: "published",
205
204
  });
206
205
  await services.posts.create({
@@ -236,15 +235,15 @@ describe("RSS Feed Routes", () => {
236
235
  await services.posts.create({
237
236
  format: "note",
238
237
  title: "Regular Post",
239
- body: "Not featured",
238
+ bodyMarkdown: "Not featured",
240
239
  status: "published",
241
240
  });
242
241
  await services.posts.create({
243
242
  format: "note",
244
243
  title: "Featured Post",
245
- body: "This is featured",
244
+ bodyMarkdown: "This is featured",
246
245
  status: "published",
247
- visibility: "featured",
246
+ featured: true,
248
247
  });
249
248
 
250
249
  const res = await app.request("/feed/all/atom.xml");
@@ -264,7 +263,7 @@ describe("RSS Feed Routes", () => {
264
263
  await services.posts.create({
265
264
  format: "note",
266
265
  title: "My Note",
267
- body: "A note",
266
+ bodyMarkdown: "A note",
268
267
  status: "published",
269
268
  });
270
269
  await services.posts.create({
@@ -292,9 +291,9 @@ describe("RSS Feed Routes", () => {
292
291
  await services.posts.create({
293
292
  format: "note",
294
293
  title: `Post ${i}`,
295
- body: `Body ${i}`,
294
+ bodyMarkdown: `Body ${i}`,
296
295
  status: "published",
297
- visibility: "featured",
296
+ featured: true,
298
297
  });
299
298
  }
300
299
 
@@ -318,7 +317,7 @@ describe("RSS Feed Routes", () => {
318
317
  await services.posts.create({
319
318
  format: "note",
320
319
  title: `Post ${i}`,
321
- body: `Body ${i}`,
320
+ bodyMarkdown: `Body ${i}`,
322
321
  status: "published",
323
322
  });
324
323
  }
@@ -346,7 +345,7 @@ describe("RSS Feed Routes", () => {
346
345
  await services.posts.create({
347
346
  format: "note",
348
347
  title: `Post ${i}`,
349
- body: `Body ${i}`,
348
+ bodyMarkdown: `Body ${i}`,
350
349
  status: "published",
351
350
  });
352
351
  }
@@ -369,9 +368,9 @@ describe("RSS Feed Routes", () => {
369
368
  await services.posts.create({
370
369
  format: "note",
371
370
  title: `Post ${i}`,
372
- body: `Body ${i}`,
371
+ bodyMarkdown: `Body ${i}`,
373
372
  status: "published",
374
- visibility: "featured",
373
+ featured: true,
375
374
  });
376
375
  }
377
376
 
@@ -22,8 +22,9 @@ type Env = { Bindings: Bindings; Variables: AppVariables };
22
22
  export const rssRoutes = new Hono<Env>();
23
23
 
24
24
  interface FeedOptions {
25
- visibility?: "featured";
25
+ featured?: boolean;
26
26
  excludeUnlisted?: boolean;
27
+ excludePrivate?: boolean;
27
28
  format?: Format;
28
29
  }
29
30
 
@@ -48,8 +49,9 @@ async function buildFeedData(
48
49
  const posts = await c.var.services.posts.list({
49
50
  status: "published",
50
51
  excludeReplies: true,
51
- visibility: opts?.visibility,
52
+ featured: opts?.featured,
52
53
  excludeUnlisted: opts?.excludeUnlisted,
54
+ excludePrivate: opts?.excludePrivate ?? true,
53
55
  format: opts?.format,
54
56
  limit: feedLimit,
55
57
  });
@@ -99,7 +101,7 @@ function parseFormatQuery(c: Context<Env>): Format | undefined {
99
101
 
100
102
  // RSS 2.0 — /feed
101
103
  rssRoutes.get("/", async (c) => {
102
- const feedData = await buildFeedData(c, { visibility: "featured" });
104
+ const feedData = await buildFeedData(c, { featured: true });
103
105
  const xml = defaultRssRenderer(feedData);
104
106
 
105
107
  return new Response(xml, {
@@ -111,7 +113,7 @@ rssRoutes.get("/", async (c) => {
111
113
 
112
114
  // Atom — /feed/atom.xml
113
115
  rssRoutes.get("/atom.xml", async (c) => {
114
- const feedData = await buildFeedData(c, { visibility: "featured" });
116
+ const feedData = await buildFeedData(c, { featured: true });
115
117
  const xml = defaultAtomRenderer(feedData);
116
118
 
117
119
  return new Response(xml, {
@@ -6,11 +6,7 @@ import { Hono } from "hono";
6
6
  import type { Bindings } from "../../types.js";
7
7
  import type { AppVariables } from "../../types/app-context.js";
8
8
  import { defaultSitemapRenderer } from "../../lib/feed.js";
9
- import {
10
- createMediaContext,
11
- toPostViewsFromPosts,
12
- toPageView,
13
- } from "../../lib/view.js";
9
+ import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
14
10
 
15
11
  type Env = { Bindings: Bindings; Variables: AppVariables };
16
12
 
@@ -24,23 +20,17 @@ sitemapRoutes.get("/sitemap.xml", async (c) => {
24
20
  const posts = await c.var.services.posts.list({
25
21
  status: "published",
26
22
  excludeReplies: true,
23
+ excludePrivate: true,
27
24
  limit: 1000,
28
25
  });
29
26
 
30
- // Fetch published pages
31
- const publishedPages = await c.var.services.pages.list({
32
- status: "published",
33
- });
34
-
35
27
  // Transform to View Models
36
28
  const mediaCtx = createMediaContext(appConfig);
37
29
  const postViews = toPostViewsFromPosts(posts, mediaCtx);
38
- const pageViews = publishedPages.map(toPageView);
39
30
 
40
31
  const xml = defaultSitemapRenderer({
41
32
  siteUrl,
42
33
  posts: postViews,
43
- pages: pageViews,
44
34
  });
45
35
 
46
36
  return new Response(xml, {
@@ -10,7 +10,6 @@ import { describe, it, expect, beforeEach } from "vitest";
10
10
  import { createTestDatabase } from "../../../__tests__/helpers/db.js";
11
11
  import { createCollectionService } from "../../../services/collection.js";
12
12
  import { createPostService } from "../../../services/post.js";
13
- import { createPathRegistryService } from "../../../services/path-registry.js";
14
13
  import type { Database } from "../../../db/index.js";
15
14
 
16
15
  describe("Collections Listing Page - Data Logic", () => {
@@ -22,7 +21,7 @@ describe("Collections Listing Page - Data Logic", () => {
22
21
  const testDb = createTestDatabase();
23
22
  db = testDb.db as unknown as Database;
24
23
  collectionService = createCollectionService(db);
25
- postService = createPostService(db, createPathRegistryService(db));
24
+ postService = createPostService(db, { slugIdLength: 5 });
26
25
  });
27
26
 
28
27
  it("returns collections with post counts", async () => {
@@ -38,11 +37,11 @@ describe("Collections Listing Page - Data Logic", () => {
38
37
  // Add posts to recipes collection via junction table
39
38
  const p1 = await postService.create({
40
39
  format: "note",
41
- body: "Recipe 1",
40
+ bodyMarkdown: "Recipe 1",
42
41
  });
43
42
  const p2 = await postService.create({
44
43
  format: "note",
45
- body: "Recipe 2",
44
+ bodyMarkdown: "Recipe 2",
46
45
  });
47
46
  await collectionService.addPost(recipes.id, p1.id);
48
47
  await collectionService.addPost(recipes.id, p2.id);
@@ -78,11 +77,11 @@ describe("Collections Listing Page - Data Logic", () => {
78
77
 
79
78
  const post = await postService.create({
80
79
  format: "note",
81
- body: "Will be deleted",
80
+ bodyMarkdown: "Will be deleted",
82
81
  });
83
82
  const post2 = await postService.create({
84
83
  format: "note",
85
- body: "Will remain",
84
+ bodyMarkdown: "Will remain",
86
85
  });
87
86
 
88
87
  await collectionService.addPost(col.id, post.id);
@@ -9,7 +9,6 @@
9
9
  import { describe, it, expect, beforeEach } from "vitest";
10
10
  import { createTestDatabase } from "../../../__tests__/helpers/db.js";
11
11
  import { createPostService } from "../../../services/post.js";
12
- import { createPathRegistryService } from "../../../services/path-registry.js";
13
12
  import type { Database } from "../../../db/index.js";
14
13
 
15
14
  describe("Featured Page - Data Logic", () => {
@@ -19,76 +18,96 @@ describe("Featured Page - Data Logic", () => {
19
18
  beforeEach(() => {
20
19
  const testDb = createTestDatabase();
21
20
  db = testDb.db as unknown as Database;
22
- postService = createPostService(db, createPathRegistryService(db));
21
+ postService = createPostService(db, { slugIdLength: 5 });
23
22
  });
24
23
 
25
24
  it("returns only featured published posts", async () => {
26
25
  await postService.create({
27
26
  format: "note",
28
- body: "Featured post",
29
- visibility: "featured",
27
+ bodyMarkdown: "Featured post",
28
+ featured: true,
30
29
  status: "published",
31
30
  });
32
31
  await postService.create({
33
32
  format: "note",
34
- body: "Normal post",
33
+ bodyMarkdown: "Normal post",
35
34
  status: "published",
36
35
  });
37
36
  await postService.create({
38
37
  format: "note",
39
- body: "Draft featured",
40
- visibility: "featured",
38
+ bodyMarkdown: "Draft featured",
39
+ featured: true,
41
40
  status: "draft",
42
41
  });
43
42
 
44
43
  const posts = await postService.list({
45
- visibility: "featured",
44
+ featured: true,
46
45
  status: "published",
47
- excludeReplies: true,
48
46
  });
49
47
 
50
48
  expect(posts).toHaveLength(1);
51
- expect(posts[0]?.body).toBe("Featured post");
49
+ expect(posts[0]?.bodyText).toBe("Featured post");
52
50
  });
53
51
 
54
52
  it("returns empty list when no featured posts exist", async () => {
55
53
  await postService.create({
56
54
  format: "note",
57
- body: "Normal post",
55
+ bodyMarkdown: "Normal post",
58
56
  status: "published",
59
57
  });
60
58
 
61
59
  const posts = await postService.list({
62
- visibility: "featured",
60
+ featured: true,
63
61
  status: "published",
64
- excludeReplies: true,
65
62
  });
66
63
 
67
64
  expect(posts).toHaveLength(0);
68
65
  });
69
66
 
70
- it("excludes replies from featured posts", async () => {
67
+ it("includes featured reply posts", async () => {
71
68
  const root = await postService.create({
72
69
  format: "note",
73
- body: "Featured root",
74
- visibility: "featured",
70
+ bodyMarkdown: "Root post",
75
71
  status: "published",
76
72
  });
77
73
 
78
- // Reply inherits featured from root
79
- await postService.create({
74
+ // Create a reply and feature it independently
75
+ const reply = await postService.create({
80
76
  format: "note",
81
- body: "Reply to featured",
77
+ bodyMarkdown: "Reply to root",
82
78
  replyToId: root.id,
83
79
  });
80
+ await postService.update(reply.id, { featured: true });
84
81
 
85
82
  const posts = await postService.list({
86
- visibility: "featured",
83
+ featured: true,
87
84
  status: "published",
88
- excludeReplies: true,
89
85
  });
90
86
 
91
87
  expect(posts).toHaveLength(1);
92
- expect(posts[0]?.body).toBe("Featured root");
88
+ expect(posts[0]?.bodyText).toBe("Reply to root");
89
+ });
90
+
91
+ it("featured root and featured reply both appear", async () => {
92
+ const root = await postService.create({
93
+ format: "note",
94
+ bodyMarkdown: "Featured root",
95
+ featured: true,
96
+ status: "published",
97
+ });
98
+
99
+ const reply = await postService.create({
100
+ format: "note",
101
+ bodyMarkdown: "Featured reply",
102
+ replyToId: root.id,
103
+ });
104
+ await postService.update(reply.id, { featured: true });
105
+
106
+ const posts = await postService.list({
107
+ featured: true,
108
+ status: "published",
109
+ });
110
+
111
+ expect(posts).toHaveLength(2);
93
112
  });
94
113
  });