@jant/core 0.3.36 → 0.3.37

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
@@ -13,9 +13,10 @@ describe("Collections API Routes", () => {
13
13
 
14
14
  const body = await res.json();
15
15
  expect(body.collections).toEqual([]);
16
+ expect(body.sidebarItems).toEqual([]);
16
17
  });
17
18
 
18
- it("returns collections with post counts", async () => {
19
+ it("returns collections with post counts and sidebar items", async () => {
19
20
  const { app, services } = createTestApp();
20
21
  app.route("/api/collections", collectionsApiRoutes);
21
22
 
@@ -25,7 +26,7 @@ describe("Collections API Routes", () => {
25
26
  });
26
27
  const post = await services.posts.create({
27
28
  format: "note",
28
- body: "tech post",
29
+ bodyMarkdown: "tech post",
29
30
  });
30
31
  await services.collections.addPost(col.id, post.id);
31
32
 
@@ -35,6 +36,10 @@ describe("Collections API Routes", () => {
35
36
  expect(body.collections).toHaveLength(1);
36
37
  expect(body.collections[0].slug).toBe("tech");
37
38
  expect(body.collections[0].postCount).toBe(1);
39
+
40
+ expect(body.sidebarItems).toHaveLength(1);
41
+ expect(body.sidebarItems[0].type).toBe("collection");
42
+ expect(body.sidebarItems[0].collectionId).toBe(col.id);
38
43
  });
39
44
  });
40
45
 
@@ -60,7 +65,7 @@ describe("Collections API Routes", () => {
60
65
  const { app } = createTestApp();
61
66
  app.route("/api/collections", collectionsApiRoutes);
62
67
 
63
- const res = await app.request("/api/collections/abc");
68
+ const res = await app.request("/api/collections/!!invalid!!");
64
69
  expect(res.status).toBe(400);
65
70
  });
66
71
 
@@ -68,7 +73,9 @@ describe("Collections API Routes", () => {
68
73
  const { app } = createTestApp();
69
74
  app.route("/api/collections", collectionsApiRoutes);
70
75
 
71
- const res = await app.request("/api/collections/9999");
76
+ const res = await app.request(
77
+ `/api/collections/${"00000000-0000-0000-0000-000000009999"}`,
78
+ );
72
79
  expect(res.status).toBe(404);
73
80
  });
74
81
  });
@@ -122,46 +129,6 @@ describe("Collections API Routes", () => {
122
129
  });
123
130
  });
124
131
 
125
- describe("PUT /api/collections/reorder", () => {
126
- it("returns 401 when not authenticated", async () => {
127
- const { app } = createTestApp({ authenticated: false });
128
- app.route("/api/collections", collectionsApiRoutes);
129
-
130
- const res = await app.request("/api/collections/reorder", {
131
- method: "PUT",
132
- headers: { "Content-Type": "application/json" },
133
- body: JSON.stringify({ ids: [1, 2] }),
134
- });
135
-
136
- expect(res.status).toBe(401);
137
- });
138
-
139
- it("reorders collections when authenticated", async () => {
140
- const { app, services } = createTestApp({ authenticated: true });
141
- app.route("/api/collections", collectionsApiRoutes);
142
-
143
- const col1 = await services.collections.create({
144
- slug: "first",
145
- title: "First",
146
- });
147
- const col2 = await services.collections.create({
148
- slug: "second",
149
- title: "Second",
150
- });
151
-
152
- const res = await app.request("/api/collections/reorder", {
153
- method: "PUT",
154
- headers: { "Content-Type": "application/json" },
155
- body: JSON.stringify({ ids: [col2.id, col1.id] }),
156
- });
157
-
158
- expect(res.status).toBe(200);
159
- const body = await res.json();
160
- expect(body.collections[0].slug).toBe("second");
161
- expect(body.collections[1].slug).toBe("first");
162
- });
163
- });
164
-
165
132
  describe("PUT /api/collections/:id", () => {
166
133
  it("updates a collection when authenticated", async () => {
167
134
  const { app, services } = createTestApp({ authenticated: true });
@@ -187,11 +154,14 @@ describe("Collections API Routes", () => {
187
154
  const { app } = createTestApp({ authenticated: true });
188
155
  app.route("/api/collections", collectionsApiRoutes);
189
156
 
190
- const res = await app.request("/api/collections/9999", {
191
- method: "PUT",
192
- headers: { "Content-Type": "application/json" },
193
- body: JSON.stringify({ title: "test" }),
194
- });
157
+ const res = await app.request(
158
+ `/api/collections/${"00000000-0000-0000-0000-000000009999"}`,
159
+ {
160
+ method: "PUT",
161
+ headers: { "Content-Type": "application/json" },
162
+ body: JSON.stringify({ title: "test" }),
163
+ },
164
+ );
195
165
 
196
166
  expect(res.status).toBe(404);
197
167
  });
@@ -239,14 +209,87 @@ describe("Collections API Routes", () => {
239
209
  const { app } = createTestApp({ authenticated: true });
240
210
  app.route("/api/collections", collectionsApiRoutes);
241
211
 
242
- const res = await app.request("/api/collections/9999", {
243
- method: "DELETE",
244
- });
212
+ const res = await app.request(
213
+ `/api/collections/${"00000000-0000-0000-0000-000000009999"}`,
214
+ { method: "DELETE" },
215
+ );
245
216
 
246
217
  expect(res.status).toBe(404);
247
218
  });
248
219
  });
249
220
 
221
+ describe("POST /api/collections/sidebar-items", () => {
222
+ it("creates a divider sidebar item", async () => {
223
+ const { app } = createTestApp({ authenticated: true });
224
+ app.route("/api/collections", collectionsApiRoutes);
225
+
226
+ const res = await app.request("/api/collections/sidebar-items", {
227
+ method: "POST",
228
+ headers: { "Content-Type": "application/json" },
229
+ });
230
+
231
+ expect(res.status).toBe(201);
232
+ const body = await res.json();
233
+ expect(body.type).toBe("divider");
234
+ expect(body.collectionId).toBeNull();
235
+ });
236
+ });
237
+
238
+ describe("DELETE /api/collections/sidebar-items/:id", () => {
239
+ it("deletes a sidebar item", async () => {
240
+ const { app, services } = createTestApp({ authenticated: true });
241
+ app.route("/api/collections", collectionsApiRoutes);
242
+
243
+ const item = await services.collections.createSidebarItem("divider");
244
+
245
+ const res = await app.request(
246
+ `/api/collections/sidebar-items/${item.id}`,
247
+ { method: "DELETE" },
248
+ );
249
+
250
+ expect(res.status).toBe(200);
251
+ const body = await res.json();
252
+ expect(body.success).toBe(true);
253
+ });
254
+ });
255
+
256
+ describe("PUT /api/collections/sidebar-items/:id/move", () => {
257
+ it("moves a sidebar item", async () => {
258
+ const { app, services } = createTestApp({ authenticated: true });
259
+ app.route("/api/collections", collectionsApiRoutes);
260
+
261
+ await services.collections.create({ slug: "a", title: "A" });
262
+ await services.collections.create({ slug: "b", title: "B" });
263
+ await services.collections.create({ slug: "c", title: "C" });
264
+
265
+ const items = await services.collections.listSidebarItems();
266
+ expect(items).toHaveLength(3);
267
+ const itemA = items[0];
268
+ const itemB = items[1];
269
+ const itemC = items[2];
270
+
271
+ // Move C between A and B
272
+ const res = await app.request(
273
+ `/api/collections/sidebar-items/${itemC?.id ?? ""}/move`,
274
+ {
275
+ method: "PUT",
276
+ headers: { "Content-Type": "application/json" },
277
+ body: JSON.stringify({
278
+ after: itemA?.id ?? "",
279
+ before: itemB?.id ?? "",
280
+ }),
281
+ },
282
+ );
283
+
284
+ expect(res.status).toBe(200);
285
+
286
+ const reordered = await services.collections.listSidebarItems();
287
+ expect(reordered[0]?.id).toBe(itemA?.id);
288
+ expect(reordered[1]?.id).toBe(itemC?.id);
289
+ expect(reordered[2]?.id).toBe(itemB?.id);
290
+ });
291
+ });
292
+
250
293
  describe("POST /api/collections/:id/posts", () => {
251
294
  it("adds a post to a collection", async () => {
252
295
  const { app, services } = createTestApp({ authenticated: true });
@@ -258,7 +301,7 @@ describe("Collections API Routes", () => {
258
301
  });
259
302
  const post = await services.posts.create({
260
303
  format: "note",
261
- body: "test",
304
+ bodyMarkdown: "test",
262
305
  });
263
306
 
264
307
  const res = await app.request(`/api/collections/${col.id}/posts`, {
@@ -279,14 +322,17 @@ describe("Collections API Routes", () => {
279
322
 
280
323
  const post = await services.posts.create({
281
324
  format: "note",
282
- body: "test",
325
+ bodyMarkdown: "test",
283
326
  });
284
327
 
285
- const res = await app.request("/api/collections/9999/posts", {
286
- method: "POST",
287
- headers: { "Content-Type": "application/json" },
288
- body: JSON.stringify({ postId: post.id }),
289
- });
328
+ const res = await app.request(
329
+ `/api/collections/${"00000000-0000-0000-0000-000000009999"}/posts`,
330
+ {
331
+ method: "POST",
332
+ headers: { "Content-Type": "application/json" },
333
+ body: JSON.stringify({ postId: post.id }),
334
+ },
335
+ );
290
336
 
291
337
  expect(res.status).toBe(404);
292
338
  });
@@ -303,7 +349,9 @@ describe("Collections API Routes", () => {
303
349
  const res = await app.request(`/api/collections/${col.id}/posts`, {
304
350
  method: "POST",
305
351
  headers: { "Content-Type": "application/json" },
306
- body: JSON.stringify({ postId: 1 }),
352
+ body: JSON.stringify({
353
+ postId: "00000000-0000-0000-0000-000000000001",
354
+ }),
307
355
  });
308
356
 
309
357
  expect(res.status).toBe(401);
@@ -321,7 +369,7 @@ describe("Collections API Routes", () => {
321
369
  });
322
370
  const post = await services.posts.create({
323
371
  format: "note",
324
- body: "test",
372
+ bodyMarkdown: "test",
325
373
  });
326
374
 
327
375
  await services.collections.addPost(col.id, post.id);
@@ -92,21 +92,8 @@ describe("Nav Items API Routes", () => {
92
92
  });
93
93
  });
94
94
 
95
- describe("PUT /api/nav-items/reorder", () => {
96
- it("returns 401 when not authenticated", async () => {
97
- const { app } = createTestApp({ authenticated: false });
98
- app.route("/api/nav-items", navItemsApiRoutes);
99
-
100
- const res = await app.request("/api/nav-items/reorder", {
101
- method: "PUT",
102
- headers: { "Content-Type": "application/json" },
103
- body: JSON.stringify({ ids: [1, 2] }),
104
- });
105
-
106
- expect(res.status).toBe(401);
107
- });
108
-
109
- it("reorders nav items when authenticated", async () => {
95
+ describe("PUT /api/nav-items/:id/move", () => {
96
+ it("moves a nav item between two others", async () => {
110
97
  const { app, services } = createTestApp({ authenticated: true });
111
98
  app.route("/api/nav-items", navItemsApiRoutes);
112
99
 
@@ -120,18 +107,44 @@ describe("Nav Items API Routes", () => {
120
107
  label: "Second",
121
108
  url: "/second",
122
109
  });
110
+ const item3 = await services.navItems.create({
111
+ type: "link",
112
+ label: "Third",
113
+ url: "/third",
114
+ });
123
115
 
124
- // Reverse order
125
- const res = await app.request("/api/nav-items/reorder", {
116
+ // Move Third between First and Second
117
+ const res = await app.request(`/api/nav-items/${item3.id}/move`, {
126
118
  method: "PUT",
127
119
  headers: { "Content-Type": "application/json" },
128
- body: JSON.stringify({ ids: [item2.id, item1.id] }),
120
+ body: JSON.stringify({
121
+ after: item1.id,
122
+ before: item2.id,
123
+ }),
129
124
  });
130
125
 
131
126
  expect(res.status).toBe(200);
132
- const body = await res.json();
133
- expect(body.navItems[0].label).toBe("Second");
134
- expect(body.navItems[1].label).toBe("First");
127
+
128
+ const items = await services.navItems.list();
129
+ expect(items[0]?.label).toBe("First");
130
+ expect(items[1]?.label).toBe("Third");
131
+ expect(items[2]?.label).toBe("Second");
132
+ });
133
+
134
+ it("returns 404 for non-existent item", async () => {
135
+ const { app } = createTestApp({ authenticated: true });
136
+ app.route("/api/nav-items", navItemsApiRoutes);
137
+
138
+ const res = await app.request(
139
+ `/api/nav-items/${"00000000-0000-0000-0000-000000009999"}/move`,
140
+ {
141
+ method: "PUT",
142
+ headers: { "Content-Type": "application/json" },
143
+ body: JSON.stringify({ after: null, before: null }),
144
+ },
145
+ );
146
+
147
+ expect(res.status).toBe(404);
135
148
  });
136
149
  });
137
150
 
@@ -161,11 +174,14 @@ describe("Nav Items API Routes", () => {
161
174
  const { app } = createTestApp({ authenticated: true });
162
175
  app.route("/api/nav-items", navItemsApiRoutes);
163
176
 
164
- const res = await app.request("/api/nav-items/9999", {
165
- method: "PUT",
166
- headers: { "Content-Type": "application/json" },
167
- body: JSON.stringify({ label: "test" }),
168
- });
177
+ const res = await app.request(
178
+ `/api/nav-items/${"00000000-0000-0000-0000-000000009999"}`,
179
+ {
180
+ method: "PUT",
181
+ headers: { "Content-Type": "application/json" },
182
+ body: JSON.stringify({ label: "test" }),
183
+ },
184
+ );
169
185
 
170
186
  expect(res.status).toBe(404);
171
187
  });
@@ -212,9 +228,10 @@ describe("Nav Items API Routes", () => {
212
228
  const { app } = createTestApp({ authenticated: true });
213
229
  app.route("/api/nav-items", navItemsApiRoutes);
214
230
 
215
- const res = await app.request("/api/nav-items/9999", {
216
- method: "DELETE",
217
- });
231
+ const res = await app.request(
232
+ `/api/nav-items/${"00000000-0000-0000-0000-000000009999"}`,
233
+ { method: "DELETE" },
234
+ );
218
235
 
219
236
  expect(res.status).toBe(404);
220
237
  });