@jant/core 0.6.14 → 0.6.16

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 (350) hide show
  1. package/bin/commands/import-site.js +88 -13
  2. package/bin/commands/migrate.js +55 -0
  3. package/bin/commands/posts/rebuild-html.js +184 -0
  4. package/bin/commands/site/snapshot/import.js +14 -10
  5. package/bin/lib/d1-query.js +23 -5
  6. package/bin/lib/migration-runner.js +29 -7
  7. package/bin/lib/node-database.js +32 -0
  8. package/bin/lib/site-snapshot.js +347 -46
  9. package/bin/lib/sql-export.js +1 -1
  10. package/bin/lib/thread-collection-migration.js +326 -0
  11. package/dist/app-C75Zc8dD.js +6 -0
  12. package/dist/{app-2i7-WQHg.js → app-C7_4oab9.js} +7774 -6425
  13. package/dist/client/.vite/manifest.json +8 -8
  14. package/dist/client/_assets/chunks/{url-pLre2DM_.js → url-CU9pEbk5.js} +1 -1
  15. package/dist/client/_assets/{client-BvID5ucz.js → client-Bq0vre8Y.js} +59 -38
  16. package/dist/client/_assets/client-D3VZyG4L.css +2 -0
  17. package/dist/client/_assets/client-auth-F2rb9WfF.js +5571 -0
  18. package/dist/{env-OHRKGcMj.js → env-BPYViDJJ.js} +1 -1
  19. package/dist/{export-DWn149b7.js → export-Dl5KCiEs.js} +1348 -128
  20. package/dist/{github-api-BgSiE71w.js → github-api-BNF35Lkx.js} +1 -1
  21. package/dist/{github-app-BbklkFmU.js → github-app-DdiD-bC4.js} +1 -1
  22. package/dist/{github-sync-C0zyF6XS.js → github-sync-Bqg62IvV.js} +3 -3
  23. package/dist/{github-sync-Ckk0sJxK.js → github-sync-HdK2EgvJ.js} +3 -3
  24. package/dist/index.js +5 -5
  25. package/dist/node.js +7 -7
  26. package/dist/{url-BMYO-Zlt.js → url-CbLAtlZe.js} +517 -12
  27. package/package.json +11 -10
  28. package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
  29. package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
  30. package/src/__tests__/export-hugo-build.test.ts +63 -4
  31. package/src/__tests__/export-import-roundtrip.test.ts +6 -4
  32. package/src/__tests__/export-service.test.ts +142 -9
  33. package/src/__tests__/helpers/app.ts +3 -0
  34. package/src/__tests__/helpers/export-fixtures.ts +2 -0
  35. package/src/__tests__/import-site-command.test.ts +65 -0
  36. package/src/__tests__/mise-config.test.ts +22 -0
  37. package/src/__tests__/vite-config.test.ts +45 -0
  38. package/src/app.tsx +16 -3
  39. package/src/client/__tests__/collection-created-notice.test.ts +36 -0
  40. package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
  41. package/src/client/__tests__/collection-page-actions.test.ts +71 -2
  42. package/src/client/__tests__/compose-bridge.test.ts +316 -2
  43. package/src/client/__tests__/compose-shortcuts.test.ts +20 -0
  44. package/src/client/__tests__/feed-video-player.test.ts +16 -1
  45. package/src/client/__tests__/footnote-rail.test.ts +472 -0
  46. package/src/client/__tests__/hydrate-partial.test.ts +3 -0
  47. package/src/client/__tests__/toast.test.ts +82 -1
  48. package/src/client/collection-created-notice.ts +66 -0
  49. package/src/client/collection-form-bridge.ts +17 -0
  50. package/src/client/collection-navigation.ts +44 -0
  51. package/src/client/collection-page-actions.ts +57 -1
  52. package/src/client/components/__tests__/compose-format-convert.test.ts +0 -19
  53. package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
  54. package/src/client/components/__tests__/jant-command-palette.test.ts +51 -0
  55. package/src/client/components/__tests__/jant-compose-dialog.test.ts +2268 -206
  56. package/src/client/components/__tests__/jant-compose-editor.test.ts +147 -59
  57. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +114 -64
  58. package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
  59. package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
  60. package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
  61. package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
  62. package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
  63. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  64. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  65. package/src/client/components/collection-manager-types.ts +9 -3
  66. package/src/client/components/compose-format-convert.ts +5 -1
  67. package/src/client/components/compose-types.ts +55 -3
  68. package/src/client/components/jant-collection-directory.ts +165 -30
  69. package/src/client/components/jant-command-palette.ts +14 -7
  70. package/src/client/components/jant-compose-dialog.ts +1542 -694
  71. package/src/client/components/jant-compose-editor.ts +237 -99
  72. package/src/client/components/jant-compose-fullscreen.ts +27 -9
  73. package/src/client/components/jant-config-editor.ts +722 -0
  74. package/src/client/components/jant-media-lightbox.ts +100 -18
  75. package/src/client/components/jant-nav-manager.ts +1283 -54
  76. package/src/client/components/jant-post-form.ts +3 -3
  77. package/src/client/components/jant-post-menu.ts +177 -95
  78. package/src/client/components/nav-manager-types.ts +51 -2
  79. package/src/client/components/post-form-types.ts +1 -1
  80. package/src/client/compose-bridge.ts +147 -123
  81. package/src/client/compose-launch.ts +13 -0
  82. package/src/client/compose-shortcuts.ts +5 -1
  83. package/src/client/feed-video-player.ts +62 -2
  84. package/src/client/footnote-rail.ts +478 -0
  85. package/src/client/hydrate-partial.ts +2 -0
  86. package/src/client/media-lightbox-events.ts +63 -0
  87. package/src/client/post-refresh.ts +135 -0
  88. package/src/client/site-header-fragment.ts +38 -0
  89. package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
  90. package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
  91. package/src/client/tiptap/__tests__/link-input-rules.test.ts +121 -0
  92. package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
  93. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
  94. package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
  95. package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
  96. package/src/client/tiptap/bubble-menu.ts +88 -10
  97. package/src/client/tiptap/continuous-lists.ts +102 -0
  98. package/src/client/tiptap/create-editor.ts +3 -0
  99. package/src/client/tiptap/extensions.ts +14 -5
  100. package/src/client/tiptap/link-input-rules.ts +4 -4
  101. package/src/client/tiptap/markdown-clipboard.ts +608 -11
  102. package/src/client/tiptap/slash-commands.ts +8 -1
  103. package/src/client/tiptap/structural-keymap.ts +211 -0
  104. package/src/client/tiptap/tab-indent.ts +8 -22
  105. package/src/client/tiptap/table-control-labels.ts +30 -0
  106. package/src/client/tiptap/table-controls.ts +537 -0
  107. package/src/client/tiptap/table-size-picker.ts +254 -0
  108. package/src/client/toast.ts +133 -39
  109. package/src/client-auth.ts +1 -0
  110. package/src/client.ts +1 -0
  111. package/src/db/__tests__/backfill-thread-activity.test.ts +234 -0
  112. package/src/db/__tests__/d1-query.test.ts +43 -1
  113. package/src/db/__tests__/migration-runner.test.ts +63 -0
  114. package/src/db/__tests__/migrations.test.ts +469 -0
  115. package/src/db/__tests__/thread-activity.test.ts +113 -0
  116. package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
  117. package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
  118. package/src/db/migrations/0027_old_blue_blade.sql +39 -0
  119. package/src/db/migrations/0028_superb_post.sql +2 -0
  120. package/src/db/migrations/0029_boring_magma.sql +1 -0
  121. package/src/db/migrations/0030_typical_vivisector.sql +50 -0
  122. package/src/db/migrations/0031_many_ego.sql +3 -0
  123. package/src/db/migrations/meta/0027_snapshot.json +2511 -0
  124. package/src/db/migrations/meta/0028_snapshot.json +2511 -0
  125. package/src/db/migrations/meta/0029_snapshot.json +2519 -0
  126. package/src/db/migrations/meta/0030_snapshot.json +2541 -0
  127. package/src/db/migrations/meta/0031_snapshot.json +2562 -0
  128. package/src/db/migrations/meta/_journal.json +35 -0
  129. package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
  130. package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
  131. package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
  132. package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
  133. package/src/db/migrations/pg/0029_rare_hairball.sql +3 -0
  134. package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
  135. package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
  136. package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
  137. package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
  138. package/src/db/migrations/pg/meta/0029_snapshot.json +3289 -0
  139. package/src/db/migrations/pg/meta/_journal.json +35 -0
  140. package/src/db/pg/schema.ts +46 -20
  141. package/src/db/rehearsal-fixtures/demo-current.json +39 -4
  142. package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
  143. package/src/db/schema.ts +48 -20
  144. package/src/db/thread-activity.ts +101 -0
  145. package/src/i18n/__tests__/detect.test.ts +8 -2
  146. package/src/i18n/detect.ts +14 -5
  147. package/src/i18n/locales/public/en.po +222 -51
  148. package/src/i18n/locales/public/en.ts +1 -1
  149. package/src/i18n/locales/public/zh-Hans.po +216 -45
  150. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  151. package/src/i18n/locales/public/zh-Hant.po +216 -45
  152. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  153. package/src/i18n/locales/settings/en.po +513 -10
  154. package/src/i18n/locales/settings/en.ts +1 -1
  155. package/src/i18n/locales/settings/zh-Hans.po +515 -12
  156. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  157. package/src/i18n/locales/settings/zh-Hant.po +515 -12
  158. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  159. package/src/lib/__tests__/api-settings.test.ts +299 -0
  160. package/src/lib/__tests__/constants.test.ts +5 -0
  161. package/src/lib/__tests__/feed-policy.test.ts +25 -0
  162. package/src/lib/__tests__/feed.test.ts +168 -0
  163. package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
  164. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
  165. package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
  166. package/src/lib/__tests__/markdown.test.ts +24 -6
  167. package/src/lib/__tests__/navigation.test.ts +59 -2
  168. package/src/lib/__tests__/post-body-html.test.ts +176 -0
  169. package/src/lib/__tests__/post-display.test.ts +64 -0
  170. package/src/lib/__tests__/resolve-config.test.ts +113 -1
  171. package/src/lib/__tests__/slug.test.ts +18 -0
  172. package/src/lib/__tests__/summary.test.ts +82 -0
  173. package/src/lib/__tests__/timeline.test.ts +109 -70
  174. package/src/lib/__tests__/timezones.test.ts +31 -3
  175. package/src/lib/__tests__/tiptap-render.test.ts +268 -9
  176. package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
  177. package/src/lib/__tests__/translit.test.ts +87 -0
  178. package/src/lib/__tests__/url.test.ts +53 -0
  179. package/src/lib/__tests__/view.test.ts +53 -0
  180. package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
  181. package/src/lib/api-settings.ts +239 -12
  182. package/src/lib/collection-sort.ts +7 -5
  183. package/src/lib/constants.ts +2 -0
  184. package/src/lib/feed-path.ts +25 -0
  185. package/src/lib/feed-policy.ts +64 -0
  186. package/src/lib/feed.ts +113 -11
  187. package/src/lib/footnotes.ts +264 -0
  188. package/src/lib/github-sync-site-config.ts +2 -0
  189. package/src/lib/hugo-markdown.ts +15 -3
  190. package/src/lib/jant-branding.ts +1 -0
  191. package/src/lib/link-preview.ts +25 -0
  192. package/src/lib/markdown-manager.ts +193 -5
  193. package/src/lib/markdown.ts +10 -3
  194. package/src/lib/navigation.ts +16 -3
  195. package/src/lib/post-body-html.ts +131 -0
  196. package/src/lib/post-display.ts +47 -8
  197. package/src/lib/render.tsx +9 -2
  198. package/src/lib/resolve-config.ts +100 -16
  199. package/src/lib/schemas.ts +133 -1
  200. package/src/lib/settings-paths.ts +5 -0
  201. package/src/lib/site-skill.ts +78 -0
  202. package/src/lib/summary.ts +16 -5
  203. package/src/lib/timeline.ts +137 -131
  204. package/src/lib/timezones.ts +54 -14
  205. package/src/lib/tiptap-render.ts +359 -83
  206. package/src/lib/translit.ts +288 -0
  207. package/src/lib/url.ts +123 -12
  208. package/src/lib/view.ts +58 -20
  209. package/src/lib/worker-response-cache.ts +11 -18
  210. package/src/middleware/__tests__/public-content-access.test.ts +91 -0
  211. package/src/middleware/config.ts +2 -2
  212. package/src/middleware/public-content-access.ts +87 -0
  213. package/src/node/__tests__/cli-export.test.ts +14 -0
  214. package/src/node/__tests__/cli-migrate.test.ts +6 -0
  215. package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
  216. package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
  217. package/src/node/index.ts +2 -1
  218. package/src/preset.css +16 -2
  219. package/src/routes/__tests__/compose.test.ts +51 -0
  220. package/src/routes/__tests__/site-skill.test.ts +112 -0
  221. package/src/routes/api/__tests__/collections.test.ts +91 -19
  222. package/src/routes/api/__tests__/mcp.test.ts +65 -1
  223. package/src/routes/api/__tests__/nav-items.test.ts +91 -0
  224. package/src/routes/api/__tests__/palette.test.ts +44 -0
  225. package/src/routes/api/__tests__/public-access.test.ts +81 -0
  226. package/src/routes/api/__tests__/settings.test.ts +217 -0
  227. package/src/routes/api/collections.ts +22 -26
  228. package/src/routes/api/export.ts +2 -0
  229. package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
  230. package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
  231. package/src/routes/api/internal/post-body-html.ts +43 -0
  232. package/src/routes/api/internal/sites.ts +24 -0
  233. package/src/routes/api/nav-items.ts +24 -1
  234. package/src/routes/api/posts.ts +14 -10
  235. package/src/routes/api/public/__tests__/archive.test.ts +35 -0
  236. package/src/routes/api/public/__tests__/posts.test.ts +246 -0
  237. package/src/routes/api/public/archive.ts +4 -0
  238. package/src/routes/api/public/posts.ts +48 -24
  239. package/src/routes/api/search.ts +3 -0
  240. package/src/routes/api/settings.ts +33 -0
  241. package/src/routes/auth/__tests__/setup.test.ts +7 -4
  242. package/src/routes/compose.tsx +8 -3
  243. package/src/routes/dash/settings.tsx +45 -2
  244. package/src/routes/feed/__tests__/feed.test.ts +210 -6
  245. package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
  246. package/src/routes/feed/feed.ts +23 -14
  247. package/src/routes/pages/__tests__/archive-params.test.ts +384 -3
  248. package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
  249. package/src/routes/pages/__tests__/collections.test.ts +9 -9
  250. package/src/routes/pages/__tests__/featured.test.ts +6 -2
  251. package/src/routes/pages/__tests__/preview.test.ts +176 -0
  252. package/src/routes/pages/__tests__/search.test.ts +69 -0
  253. package/src/routes/pages/archive.tsx +90 -22
  254. package/src/routes/pages/collection.tsx +32 -19
  255. package/src/routes/pages/collections.tsx +6 -1
  256. package/src/routes/pages/page.tsx +86 -11
  257. package/src/routes/site-skill.ts +18 -0
  258. package/src/services/__tests__/auth.test.ts +7 -5
  259. package/src/services/__tests__/collection.test.ts +190 -44
  260. package/src/services/__tests__/navigation.test.ts +229 -21
  261. package/src/services/__tests__/post-timeline.test.ts +249 -39
  262. package/src/services/__tests__/post.test.ts +844 -5
  263. package/src/services/__tests__/search.test.ts +48 -1
  264. package/src/services/__tests__/settings.test.ts +8 -0
  265. package/src/services/__tests__/site-admin.test.ts +121 -1
  266. package/src/services/auth.ts +2 -2
  267. package/src/services/bootstrap.ts +3 -2
  268. package/src/services/collection.ts +224 -167
  269. package/src/services/export-theme/assets/client-site.css +1 -1
  270. package/src/services/export-theme/assets/client-site.js +46 -25
  271. package/src/services/export-theme/layouts/_default/list.html +18 -4
  272. package/src/services/export-theme/layouts/_default/rss.xml +8 -12
  273. package/src/services/export-theme/layouts/featured/list.html +5 -4
  274. package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
  275. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  276. package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
  277. package/src/services/export-theme/styles/main.css +30 -2
  278. package/src/services/export.ts +94 -85
  279. package/src/services/mcp.ts +37 -27
  280. package/src/services/media.ts +1 -1
  281. package/src/services/navigation.ts +185 -46
  282. package/src/services/path.ts +4 -1
  283. package/src/services/post.ts +1142 -342
  284. package/src/services/search.ts +17 -2
  285. package/src/services/settings.ts +2 -10
  286. package/src/services/site-admin.ts +63 -43
  287. package/src/style-cjk-jp.css +1 -1
  288. package/src/style-cjk-kr.css +1 -1
  289. package/src/style-cjk-tc.css +1 -1
  290. package/src/style-cjk.css +1 -1
  291. package/src/styles/components.css +540 -14
  292. package/src/styles/site-media.css +111 -19
  293. package/src/styles/tokens.css +76 -10
  294. package/src/styles/ui.css +2227 -1347
  295. package/src/types/bindings.ts +3 -0
  296. package/src/types/config.ts +243 -12
  297. package/src/types/constants.ts +22 -1
  298. package/src/types/entities.ts +14 -8
  299. package/src/types/operations.ts +18 -10
  300. package/src/types/props.ts +20 -2
  301. package/src/types/raw-assets.d.ts +5 -0
  302. package/src/types/views.ts +22 -4
  303. package/src/ui/__tests__/font-themes.test.ts +42 -30
  304. package/src/ui/compose/ComposeDialog.tsx +109 -19
  305. package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
  306. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
  307. package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
  308. package/src/ui/dash/settings/GeneralContent.tsx +15 -5
  309. package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
  310. package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
  311. package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
  312. package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
  313. package/src/ui/feed/LinkCard.tsx +2 -12
  314. package/src/ui/feed/LinkPreview.tsx +2 -7
  315. package/src/ui/feed/NoteCard.tsx +10 -20
  316. package/src/ui/feed/PostStatusBadges.tsx +42 -3
  317. package/src/ui/feed/QuoteCard.tsx +2 -12
  318. package/src/ui/feed/ThreadPreview.tsx +26 -34
  319. package/src/ui/feed/TimelineFeed.tsx +2 -2
  320. package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
  321. package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
  322. package/src/ui/feed/thread-preview-state.ts +5 -7
  323. package/src/ui/font-themes.ts +74 -26
  324. package/src/ui/layouts/BaseLayout.tsx +22 -8
  325. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
  326. package/src/ui/pages/ArchivePage.tsx +131 -24
  327. package/src/ui/pages/CollectionPage.tsx +101 -22
  328. package/src/ui/pages/CollectionsPage.tsx +3 -1
  329. package/src/ui/pages/PostPage.tsx +39 -12
  330. package/src/ui/pages/SearchPage.tsx +6 -14
  331. package/src/ui/pages/__tests__/ArchivePage.test.tsx +70 -0
  332. package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
  333. package/src/ui/shared/CollectionDirectory.tsx +13 -9
  334. package/src/ui/shared/CollectionsManager.tsx +17 -7
  335. package/src/ui/shared/DraftPreviewBar.tsx +49 -0
  336. package/src/ui/shared/HomePageBranding.tsx +2 -2
  337. package/src/ui/shared/MediaGallery.tsx +1 -0
  338. package/src/ui/shared/PostFooter.tsx +38 -13
  339. package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
  340. package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
  341. package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
  342. package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
  343. package/src/ui/shared/collection-management-labels.ts +33 -2
  344. package/src/ui/shared/custom-icons.ts +5 -0
  345. package/src/ui/shared/navigation-labels.ts +2 -4
  346. package/src/ui/shared/post-article-attributes.ts +52 -0
  347. package/dist/app-DZDlmh7C.js +0 -6
  348. package/dist/client/_assets/client-Cevfpm8H.css +0 -2
  349. package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
  350. package/src/client/tiptap/wrapping-input-rules.ts +0 -102
@@ -27,6 +27,7 @@ import {
27
27
  getDefaultJantAppleTouchIconBytes,
28
28
  getDefaultJantFaviconIcoBytes,
29
29
  } from "../lib/jant-branding.js";
30
+ import { getRootActivityAt } from "../db/thread-activity.js";
30
31
  import { tiptapJsonToMarkdown } from "../lib/tiptap-to-markdown.js";
31
32
  import { extractBodyText } from "../lib/summary.js";
32
33
  import { getMediaUrl, getPublicUrlForProvider } from "../lib/image.js";
@@ -76,12 +77,20 @@ import PARTIAL_POST_CARD from "./export-theme/layouts/partials/post-card.html?ra
76
77
  import PARTIAL_MEDIA_GALLERY from "./export-theme/layouts/partials/media-gallery.html?raw";
77
78
  import PARTIAL_REPLY from "./export-theme/layouts/partials/reply.html?raw";
78
79
  import PARTIAL_THREAD_PREVIEW from "./export-theme/layouts/partials/thread-preview.html?raw";
80
+ import PARTIAL_FEATURED_THREAD from "./export-theme/layouts/partials/featured-thread.html?raw";
79
81
  import LAYOUT_RSS from "./export-theme/layouts/_default/rss.xml?raw";
80
82
  import PARTIAL_FEED_POST_CONTENT from "./export-theme/layouts/partials/feed-post-content.xml?raw";
81
83
 
82
84
  import type { StorageDriver } from "../lib/storage.js";
83
85
  import { base64ToUint8Array } from "../lib/favicon.js";
84
- import type { Post, Collection, Media, NavItem } from "../types.js";
86
+ import {
87
+ SYSTEM_NAV_KEYS,
88
+ type Collection,
89
+ type Media,
90
+ type NavItem,
91
+ type Post,
92
+ type SystemNavKey,
93
+ } from "../types.js";
85
94
 
86
95
  /** A file entry in the exported Hugo site. */
87
96
  export interface ExportFile {
@@ -102,6 +111,10 @@ export interface SiteConfig {
102
111
  siteDescription: string;
103
112
  siteLanguage: string;
104
113
  showJantBrandingOnHome: boolean;
114
+ /** Whether anonymous JSON reads are enabled on the source Jant site. */
115
+ publicApiEnabled: boolean;
116
+ /** Whether the exported Hugo site should emit Atom feeds. */
117
+ rssFeedsEnabled: boolean;
105
118
  /** "latest" or "featured" — drives the default RSS nav link in the exported site. */
106
119
  mainRssFeed: string;
107
120
  siteFooter: string;
@@ -171,7 +184,7 @@ interface ExportCollectionDirectorySourceItem {
171
184
  slug: string;
172
185
  title: string;
173
186
  description?: string | null;
174
- postCount?: number;
187
+ threadCount?: number;
175
188
  recentActivityAt?: number;
176
189
  };
177
190
  }
@@ -184,14 +197,14 @@ interface SiteIconAssets {
184
197
  }
185
198
 
186
199
  interface ExportedCollectionMetrics {
187
- postCount: number;
200
+ threadCount: number;
188
201
  recentActivityAt: number;
189
202
  }
190
203
 
191
204
  /**
192
205
  * A single `collections:` front-matter entry, already resolved to its
193
206
  * Hugo-visible slug. Assembled from
194
- * `collectionService.getCollectionEntriesByPostIds` + `collectionSlugMap`.
207
+ * `collectionService.getCollectionEntriesByThreadIds` + `collectionSlugMap`.
195
208
  */
196
209
  interface ExportedCollectionEntry {
197
210
  slug: string;
@@ -262,15 +275,15 @@ export function createExportService(
262
275
  const rootPostIds = roots.map((p) => p.id);
263
276
 
264
277
  const [
265
- collectionsByPost,
266
- collectionEntriesByPost,
278
+ collectionsByRoot,
279
+ collectionEntriesByThread,
267
280
  rawMediaByPost,
268
281
  slugMap,
269
282
  aliasMap,
270
283
  collectionSlugMap,
271
284
  ] = await Promise.all([
272
- services.collections.getCollectionsByPostIds(allPostIds),
273
- services.collections.getCollectionEntriesByPostIds(allPostIds),
285
+ services.collections.getCollectionsByPostIds(rootPostIds),
286
+ services.collections.getCollectionEntriesByThreadIds(rootPostIds),
274
287
  services.media.getByPostIds(allPostIds),
275
288
  services.paths.getPostSlugMap(allPostIds),
276
289
  services.paths.getPostAliases(rootPostIds),
@@ -289,7 +302,7 @@ export function createExportService(
289
302
  const collectionMetrics = buildExportedCollectionMetrics(
290
303
  allCollections,
291
304
  allPosts,
292
- collectionsByPost,
305
+ collectionsByRoot,
293
306
  );
294
307
  const exportedCollectionDirectoryItems =
295
308
  buildExportedCollectionDirectoryItems(
@@ -325,9 +338,9 @@ export function createExportService(
325
338
  const threadReplies = repliesByThread.get(root.id) ?? [];
326
339
  const rootAliases = [...(aliasMap.get(root.id) ?? [])];
327
340
 
328
- const rootCollectionEntries = buildExportedCollectionEntriesForPost(
341
+ const rootCollectionEntries = buildExportedCollectionEntriesForThread(
329
342
  root.id,
330
- collectionEntriesByPost,
343
+ collectionEntriesByThread,
331
344
  collectionSlugMap,
332
345
  collectionTitleMap,
333
346
  );
@@ -339,9 +352,6 @@ export function createExportService(
339
352
  rootAliases,
340
353
  rootCollectionEntries,
341
354
  slugMap,
342
- collectionEntriesByPost,
343
- collectionSlugMap,
344
- collectionTitleMap,
345
355
  rawMediaByPost,
346
356
  siteConfig,
347
357
  deps.storage ?? null,
@@ -353,10 +363,16 @@ export function createExportService(
353
363
  // Collection landing pages (`content/{slug}/_index.md`).
354
364
  for (const collection of allCollections) {
355
365
  const slug = collectionSlugMap.get(collection.id) ?? collection.slug;
356
- const entryCount = collectionMetrics.get(collection.id)?.postCount ?? 0;
366
+ const entryCount =
367
+ collectionMetrics.get(collection.id)?.threadCount ?? 0;
357
368
  exportFiles.push({
358
369
  path: `content/${slug}/_index.md`,
359
- content: await buildCollectionSection(collection, slug, entryCount),
370
+ content: await buildCollectionSection(
371
+ collection,
372
+ slug,
373
+ entryCount,
374
+ siteConfig.rssFeedsEnabled,
375
+ ),
360
376
  });
361
377
  }
362
378
 
@@ -375,7 +391,7 @@ export function createExportService(
375
391
  });
376
392
  exportFiles.push({
377
393
  path: "content/archive/_index.md",
378
- content: await buildArchiveSection(),
394
+ content: await buildArchiveSection(siteConfig.rssFeedsEnabled),
379
395
  });
380
396
 
381
397
  const usedSlugs = new Set<string>();
@@ -384,7 +400,7 @@ export function createExportService(
384
400
  if (!usedSlugs.has("featured")) {
385
401
  exportFiles.push({
386
402
  path: "content/featured/_index.md",
387
- content: await buildFeaturedSection(),
403
+ content: await buildFeaturedSection(siteConfig.rssFeedsEnabled),
388
404
  });
389
405
  }
390
406
 
@@ -477,6 +493,10 @@ export function createExportService(
477
493
  path: "themes/jant/layouts/partials/thread-preview.html",
478
494
  content: PARTIAL_THREAD_PREVIEW,
479
495
  });
496
+ exportFiles.push({
497
+ path: "themes/jant/layouts/partials/featured-thread.html",
498
+ content: PARTIAL_FEATURED_THREAD,
499
+ });
480
500
  exportFiles.push({
481
501
  path: "themes/jant/layouts/_default/rss.xml",
482
502
  content: LAYOUT_RSS,
@@ -625,9 +645,9 @@ async function buildSiteIconAssets(
625
645
  // Thread bundle generation
626
646
  // ---------------------------------------------------------------------------
627
647
 
628
- function buildExportedCollectionEntriesForPost(
629
- postId: string,
630
- collectionEntriesByPost: Map<
648
+ function buildExportedCollectionEntriesForThread(
649
+ threadId: string,
650
+ collectionEntriesByThread: Map<
631
651
  string,
632
652
  {
633
653
  collectionId: string;
@@ -639,7 +659,7 @@ function buildExportedCollectionEntriesForPost(
639
659
  collectionSlugMap: Map<string, string>,
640
660
  collectionTitleMap: Map<string, string>,
641
661
  ): ExportedCollectionEntry[] {
642
- const entries = collectionEntriesByPost.get(postId) ?? [];
662
+ const entries = collectionEntriesByThread.get(threadId) ?? [];
643
663
  const resolved: ExportedCollectionEntry[] = [];
644
664
  for (const entry of entries) {
645
665
  const slug = collectionSlugMap.get(entry.collectionId);
@@ -805,23 +825,21 @@ async function buildThreadBundle(
805
825
  rootAliases: string[],
806
826
  rootCollectionEntries: ExportedCollectionEntry[],
807
827
  slugMap: Map<string, string>,
808
- collectionEntriesByPost: Map<
809
- string,
810
- {
811
- collectionId: string;
812
- createdAt: number;
813
- position: number;
814
- pinnedAt: number | null;
815
- }[]
816
- >,
817
- collectionSlugMap: Map<string, string>,
818
- collectionTitleMap: Map<string, string>,
819
828
  mediaByPost: Map<string, Media[]>,
820
829
  siteConfig: SiteConfig,
821
830
  storage: StorageDriver | null,
822
831
  bundleMedia: boolean,
823
832
  ): Promise<ExportFile[]> {
824
833
  const files: ExportFile[] = [];
834
+ const featuredPosts = [root, ...threadReplies].filter(
835
+ (post) => post.status === "published" && post.featuredAt !== null,
836
+ );
837
+ const featuredPostIds = featuredPosts.map((post) => post.id);
838
+ const featuredSortAt = featuredPosts.reduce<number | null>(
839
+ (latest, post) =>
840
+ Math.max(latest ?? -1, post.publishedAt ?? post.createdAt),
841
+ null,
842
+ );
825
843
 
826
844
  // Root aliases = historical root slugs + every reply slug (so
827
845
  // /{reply-slug}/ gets a Hugo alias page that redirects/anchors to
@@ -876,6 +894,9 @@ async function buildThreadBundle(
876
894
  rating: root.rating ?? undefined,
877
895
  featured_at:
878
896
  root.featuredAt !== null ? toISOString(root.featuredAt) : undefined,
897
+ featured_post_ids: featuredPostIds.length > 0 ? featuredPostIds : undefined,
898
+ featured_sort_at:
899
+ featuredSortAt !== null ? toISOString(featuredSortAt) : undefined,
879
900
  pinned_at: root.pinnedAt !== null ? toISOString(root.pinnedAt) : undefined,
880
901
  root_aliases: rootAliases.length > 0 ? rootAliases : undefined,
881
902
  collections:
@@ -926,13 +947,6 @@ async function buildThreadBundle(
926
947
  buildMediaEmission(m, siteConfig, bundleMedia),
927
948
  );
928
949
  const replyMediaList = replyEmissions.map((e) => e.entry);
929
- const replyCollectionEntries = buildExportedCollectionEntriesForPost(
930
- reply.id,
931
- collectionEntriesByPost,
932
- collectionSlugMap,
933
- collectionTitleMap,
934
- );
935
-
936
950
  const replyFrontMatter: HugoFrontMatter = {
937
951
  id: reply.id,
938
952
  title: reply.format !== "quote" ? (reply.title ?? undefined) : undefined,
@@ -961,14 +975,11 @@ async function buildThreadBundle(
961
975
  source_url: reply.format === "quote" && reply.url ? reply.url : undefined,
962
976
  quote_text: reply.quoteText ?? undefined,
963
977
  rating: reply.rating ?? undefined,
978
+ quiet_reply: reply.quietReply ? true : undefined,
964
979
  featured_at:
965
980
  reply.featuredAt !== null ? toISOString(reply.featuredAt) : undefined,
966
981
  pinned_at:
967
982
  reply.pinnedAt !== null ? toISOString(reply.pinnedAt) : undefined,
968
- collections:
969
- replyCollectionEntries.length > 0
970
- ? collectionEntriesToRefs(replyCollectionEntries)
971
- : undefined,
972
983
  media: replyMediaList.length > 0 ? replyMediaList : undefined,
973
984
  };
974
985
 
@@ -1046,22 +1057,22 @@ async function buildCollectionsSection(): Promise<string> {
1046
1057
  return `${await formatFrontMatter(frontMatter)}\n`;
1047
1058
  }
1048
1059
 
1049
- async function buildArchiveSection(): Promise<string> {
1060
+ async function buildArchiveSection(rssFeedsEnabled: boolean): Promise<string> {
1050
1061
  const frontMatter: HugoFrontMatter = {
1051
1062
  title: "Archive",
1052
1063
  type: "archive",
1053
1064
  // Opt into Atom output at /archive/index.xml.
1054
- outputs: ["html", "rss"],
1065
+ outputs: rssFeedsEnabled ? ["html", "rss"] : ["html"],
1055
1066
  };
1056
1067
  return `${await formatFrontMatter(frontMatter)}\n`;
1057
1068
  }
1058
1069
 
1059
- async function buildFeaturedSection(): Promise<string> {
1070
+ async function buildFeaturedSection(rssFeedsEnabled: boolean): Promise<string> {
1060
1071
  const frontMatter: HugoFrontMatter = {
1061
1072
  title: "Featured",
1062
1073
  type: "featured",
1063
1074
  // Opt into Atom output at /featured/index.xml.
1064
- outputs: ["html", "rss"],
1075
+ outputs: rssFeedsEnabled ? ["html", "rss"] : ["html"],
1065
1076
  };
1066
1077
  return `${await formatFrontMatter(frontMatter)}\n`;
1067
1078
  }
@@ -1070,6 +1081,7 @@ async function buildCollectionSection(
1070
1081
  collection: Collection,
1071
1082
  slug: string,
1072
1083
  entryCount: number,
1084
+ rssFeedsEnabled: boolean,
1073
1085
  ): Promise<string> {
1074
1086
  const frontMatter: HugoFrontMatter = {
1075
1087
  title: collection.title,
@@ -1079,7 +1091,7 @@ async function buildCollectionSection(
1079
1091
  sort_order: collection.sortOrder,
1080
1092
  entry_count: entryCount,
1081
1093
  // Opt into Atom output at /{slug}/index.xml.
1082
- outputs: ["html", "rss"],
1094
+ outputs: rssFeedsEnabled ? ["html", "rss"] : ["html"],
1083
1095
  };
1084
1096
  return `${await formatFrontMatter(frontMatter)}\n`;
1085
1097
  }
@@ -1248,7 +1260,11 @@ function buildExportedCollectionDirectoryItems(
1248
1260
  sequence: sequenceLabels[index] ?? "",
1249
1261
  label: item.label,
1250
1262
  url: item.url,
1251
- descriptionHtml: description ? renderMarkdown(description) : null,
1263
+ descriptionHtml: description
1264
+ ? renderMarkdown(description, {
1265
+ namespace: `collection-directory-link-${sequenceLabels[index] ?? index}`,
1266
+ })
1267
+ : null,
1252
1268
  });
1253
1269
  return;
1254
1270
  }
@@ -1273,12 +1289,12 @@ function buildExportedCollectionDirectoryItems(
1273
1289
  slug,
1274
1290
  title: collection.title || slug,
1275
1291
  descriptionHtml: collectionDescription
1276
- ? renderMarkdown(collectionDescription)
1292
+ ? renderMarkdown(collectionDescription, { namespace: collection.id })
1277
1293
  : null,
1278
1294
  entryCount:
1279
- metrics?.postCount ??
1280
- (typeof collection.postCount === "number"
1281
- ? collection.postCount
1295
+ metrics?.threadCount ??
1296
+ (typeof collection.threadCount === "number"
1297
+ ? collection.threadCount
1282
1298
  : undefined),
1283
1299
  recentActivityLabel: formatCollectionActivityLabel(activityTimestamp),
1284
1300
  recentActivityIso: formatCollectionActivityIso(activityTimestamp),
@@ -1291,13 +1307,13 @@ function buildExportedCollectionDirectoryItems(
1291
1307
  function buildExportedCollectionMetrics(
1292
1308
  collections: readonly Collection[],
1293
1309
  posts: readonly Post[],
1294
- collectionsByPost: ReadonlyMap<string, readonly Collection[]>,
1310
+ collectionsByThread: ReadonlyMap<string, readonly Collection[]>,
1295
1311
  ): Map<string, ExportedCollectionMetrics> {
1296
1312
  const metrics = new Map<string, ExportedCollectionMetrics>();
1297
1313
 
1298
1314
  for (const collection of collections) {
1299
1315
  metrics.set(collection.id, {
1300
- postCount: 0,
1316
+ threadCount: 0,
1301
1317
  recentActivityAt: collection.updatedAt,
1302
1318
  });
1303
1319
  }
@@ -1312,20 +1328,16 @@ function buildExportedCollectionMetrics(
1312
1328
  continue;
1313
1329
  }
1314
1330
 
1315
- const activityAt =
1316
- post.lastActivityAt ??
1317
- post.publishedAt ??
1318
- post.updatedAt ??
1319
- post.createdAt;
1320
- const postCollections = collectionsByPost.get(post.id) ?? [];
1331
+ const activityAt = getRootActivityAt(post);
1332
+ const threadCollections = collectionsByThread.get(post.id) ?? [];
1321
1333
 
1322
- for (const collection of postCollections) {
1334
+ for (const collection of threadCollections) {
1323
1335
  const current = metrics.get(collection.id);
1324
1336
  if (!current) {
1325
1337
  continue;
1326
1338
  }
1327
1339
 
1328
- if (current.postCount === 0) {
1340
+ if (current.threadCount === 0) {
1329
1341
  current.recentActivityAt = activityAt;
1330
1342
  } else {
1331
1343
  current.recentActivityAt = Math.max(
@@ -1333,7 +1345,7 @@ function buildExportedCollectionMetrics(
1333
1345
  activityAt,
1334
1346
  );
1335
1347
  }
1336
- current.postCount += 1;
1348
+ current.threadCount += 1;
1337
1349
  }
1338
1350
  }
1339
1351
 
@@ -1344,27 +1356,11 @@ function buildExportedCollectionMetrics(
1344
1356
  // Nav item resolution
1345
1357
  // ---------------------------------------------------------------------------
1346
1358
 
1347
- /**
1348
- * System nav items on the main site store an empty `label` in the DB and
1349
- * resolve their display text at render time through i18n
1350
- * (`getNavItemDisplayLabel`). The Hugo export has no i18n runtime, so fall
1351
- * back to these English defaults when serializing to `data/jant.toml`.
1352
- * Users can still override by setting a custom label on the nav item.
1353
- */
1354
- const SYSTEM_NAV_FALLBACK_LABELS: Record<string, string> = {
1355
- latest: "Latest",
1356
- featured: "Featured",
1357
- collections: "Collections",
1358
- archive: "Archive",
1359
- rss: "RSS",
1360
- settings: "Settings",
1361
- };
1362
-
1363
1359
  function resolveNavItemLabel(item: SiteConfig["navItems"][number]): string {
1364
1360
  if (item.label) return item.label;
1365
1361
  if (item.systemKey) {
1366
- const fallback = SYSTEM_NAV_FALLBACK_LABELS[item.systemKey];
1367
- if (fallback) return fallback;
1362
+ const definition = SYSTEM_NAV_KEYS[item.systemKey as SystemNavKey];
1363
+ if (definition) return definition.defaultLabel;
1368
1364
  }
1369
1365
  return item.label;
1370
1366
  }
@@ -1447,7 +1443,7 @@ function buildHugoToml(config: SiteConfig): string {
1447
1443
  // Override the built-in RSS output format to emit Atom instead of
1448
1444
  // RSS 2.0 so the wire format mirrors the main site's `lib/feed.ts`.
1449
1445
  "[outputs]",
1450
- ' home = ["html", "rss"]',
1446
+ ` home = ["html"${config.rssFeedsEnabled ? ', "rss"' : ""}]`,
1451
1447
  // Hugo's default for sections is ["html", "rss"] — without overriding
1452
1448
  // it here every root post (which is a section) would get its own
1453
1449
  // /{slug}/index.xml. Turn sections off by default and re-enable RSS
@@ -1472,6 +1468,8 @@ function buildHugoToml(config: SiteConfig): string {
1472
1468
  "[params]",
1473
1469
  ` description = "${escapeTomlString(config.siteDescription)}"`,
1474
1470
  ` main_rss_feed = "${escapeTomlString(config.mainRssFeed)}"`,
1471
+ ` public_api_enabled = ${config.publicApiEnabled}`,
1472
+ ` rss_feeds_enabled = ${config.rssFeedsEnabled}`,
1475
1473
  ` show_jant_branding_on_home = ${config.showJantBrandingOnHome}`,
1476
1474
  ` show_header_avatar = ${config.showHeaderAvatar}`,
1477
1475
  ` noindex = ${config.noindex}`,
@@ -1502,7 +1500,9 @@ function buildJantDataToml(
1502
1500
  iconAssets: SiteIconAssets,
1503
1501
  directoryItems: readonly ExportedCollectionDirectoryItem[],
1504
1502
  ): string {
1505
- const footerHtml = config.siteFooter ? renderMarkdown(config.siteFooter) : "";
1503
+ const footerHtml = config.siteFooter
1504
+ ? renderMarkdown(config.siteFooter, { namespace: "site-footer" })
1505
+ : "";
1506
1506
  const parts: string[] = [
1507
1507
  'format = "jant-site"',
1508
1508
  "version = 1",
@@ -1511,6 +1511,8 @@ function buildJantDataToml(
1511
1511
  `site_description = "${escapeTomlString(config.siteDescription)}"`,
1512
1512
  `site_language = "${escapeTomlString(config.siteLanguage)}"`,
1513
1513
  `main_rss_feed = "${escapeTomlString(config.mainRssFeed)}"`,
1514
+ `public_api_enabled = ${config.publicApiEnabled}`,
1515
+ `rss_feeds_enabled = ${config.rssFeedsEnabled}`,
1514
1516
  `show_jant_branding_on_home = ${config.showJantBrandingOnHome}`,
1515
1517
  `show_header_avatar = ${config.showHeaderAvatar}`,
1516
1518
  `noindex = ${config.noindex}`,
@@ -1548,6 +1550,13 @@ function buildJantDataToml(
1548
1550
  // `settings` is authenticated-only and has no corresponding page in the
1549
1551
  // static Hugo site — drop it at export time so it never shows up in nav.
1550
1552
  if (item.systemKey === "settings") continue;
1553
+ if (
1554
+ !config.rssFeedsEnabled &&
1555
+ item.type === "system" &&
1556
+ item.systemKey === "rss"
1557
+ ) {
1558
+ continue;
1559
+ }
1551
1560
  parts.push("");
1552
1561
  parts.push("[[nav]]");
1553
1562
  parts.push(`type = "${escapeTomlString(item.type)}"`);
@@ -132,12 +132,12 @@ const UploadMediaToolSchema = z.object({
132
132
  posterBase64: z.string().min(1).optional(),
133
133
  });
134
134
 
135
- const AddCollectionPostToolSchema = z.object({
135
+ const AddCollectionThreadToolSchema = z.object({
136
136
  collectionId: CollectionIdSchema,
137
- postId: PostIdSchema,
137
+ threadId: PostIdSchema,
138
138
  });
139
139
 
140
- const RemoveCollectionPostToolSchema = AddCollectionPostToolSchema;
140
+ const RemoveCollectionThreadToolSchema = AddCollectionThreadToolSchema;
141
141
 
142
142
  const mcpTools: McpToolDefinition[] = [
143
143
  {
@@ -174,7 +174,8 @@ const mcpTools: McpToolDefinition[] = [
174
174
  },
175
175
  {
176
176
  name: "jant_posts_get",
177
- description: "Get one post, including attachments and collection IDs.",
177
+ description:
178
+ "Get one post, including attachments and shared Thread collection IDs.",
178
179
  inputSchema: {
179
180
  type: "object",
180
181
  properties: {
@@ -687,55 +688,64 @@ const mcpTools: McpToolDefinition[] = [
687
688
  },
688
689
  },
689
690
  {
690
- name: "jant_collections_add_post",
691
- description: "Add a post to a collection.",
691
+ name: "jant_collections_add_thread",
692
+ description: "Add a thread to a collection.",
692
693
  inputSchema: {
693
694
  type: "object",
694
695
  properties: {
695
696
  collectionId: { type: "string", description: "Collection TypeID" },
696
- postId: { type: "string", description: "Post TypeID" },
697
+ threadId: {
698
+ type: "string",
699
+ description: "Root or child Post TypeID in the Thread",
700
+ },
697
701
  },
698
- required: ["collectionId", "postId"],
702
+ required: ["collectionId", "threadId"],
699
703
  additionalProperties: false,
700
704
  },
701
705
  async execute(args, context) {
702
- const input = AddCollectionPostToolSchema.parse(args ?? {});
703
- const [collection, post] = await Promise.all([
704
- context.services.collections.getById(input.collectionId),
705
- context.services.posts.getById(input.postId),
706
- ]);
706
+ const input = AddCollectionThreadToolSchema.parse(args ?? {});
707
+ const collection = await context.services.collections.getById(
708
+ input.collectionId,
709
+ );
707
710
 
708
711
  if (!collection) {
709
712
  throw new NotFoundError("Collection");
710
713
  }
711
- if (!post) {
712
- throw new NotFoundError("Post");
713
- }
714
714
 
715
- await context.services.collections.addPost(
715
+ await context.services.collections.addThread(
716
716
  input.collectionId,
717
- input.postId,
717
+ input.threadId,
718
718
  );
719
719
  return { success: true };
720
720
  },
721
721
  },
722
722
  {
723
- name: "jant_collections_remove_post",
724
- description: "Remove a post from a collection.",
723
+ name: "jant_collections_remove_thread",
724
+ description: "Remove a thread from a collection.",
725
725
  inputSchema: {
726
726
  type: "object",
727
727
  properties: {
728
728
  collectionId: { type: "string", description: "Collection TypeID" },
729
- postId: { type: "string", description: "Post TypeID" },
729
+ threadId: {
730
+ type: "string",
731
+ description: "Root or child Post TypeID in the Thread",
732
+ },
730
733
  },
731
- required: ["collectionId", "postId"],
734
+ required: ["collectionId", "threadId"],
732
735
  additionalProperties: false,
733
736
  },
734
737
  async execute(args, context) {
735
- const input = RemoveCollectionPostToolSchema.parse(args ?? {});
736
- await context.services.collections.removePost(
738
+ const input = RemoveCollectionThreadToolSchema.parse(args ?? {});
739
+ const collection = await context.services.collections.getById(
740
+ input.collectionId,
741
+ );
742
+ if (!collection) {
743
+ throw new NotFoundError("Collection");
744
+ }
745
+
746
+ await context.services.collections.removeThread(
737
747
  input.collectionId,
738
- input.postId,
748
+ input.threadId,
739
749
  );
740
750
  return { success: true };
741
751
  },
@@ -1287,14 +1297,14 @@ async function serializePost(
1287
1297
  post: Parameters<typeof toApiPost>[0],
1288
1298
  context: McpToolContext,
1289
1299
  ) {
1290
- const [mediaList, postCollections] = await Promise.all([
1300
+ const [mediaList, threadCollections] = await Promise.all([
1291
1301
  context.services.media.getByPostId(post.id),
1292
1302
  context.services.collections.getCollectionsByPostId(post.id),
1293
1303
  ]);
1294
1304
 
1295
1305
  return toApiPost(post, {
1296
1306
  attachments: serializeAttachments(mediaList, context.appConfig),
1297
- collectionIds: postCollections.map((collection) => collection.id),
1307
+ collectionIds: threadCollections.map((collection) => collection.id),
1298
1308
  });
1299
1309
  }
1300
1310
 
@@ -1005,7 +1005,7 @@ export function createMediaService(
1005
1005
 
1006
1006
  const markdown = await new Response(object.body).text();
1007
1007
  const tiptapJson = markdownToTiptapJson(markdown);
1008
- const html = renderTiptapJson(tiptapJson);
1008
+ const html = renderTiptapJson(tiptapJson, { namespace: record.id });
1009
1009
 
1010
1010
  return {
1011
1011
  id: record.id,