@jant/core 0.6.16 → 0.7.1

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 (592) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/export.js +3 -1
  4. package/bin/commands/import-site.js +749 -220
  5. package/bin/commands/reset-password.js +21 -8
  6. package/bin/commands/setup.js +136 -0
  7. package/bin/commands/site/export.js +71 -34
  8. package/bin/commands/site/pull-media.js +2 -6
  9. package/bin/commands/site/snapshot/export.js +19 -61
  10. package/bin/commands/site/snapshot/import.js +23 -24
  11. package/bin/lib/d1-query.js +92 -18
  12. package/bin/lib/hugo-markdown.js +4 -0
  13. package/bin/lib/node-env.js +40 -4
  14. package/bin/lib/site-pull-media.js +21 -28
  15. package/bin/lib/site-selection.js +94 -29
  16. package/bin/lib/site-snapshot.js +338 -3
  17. package/bin/lib/sql-export.js +68 -5
  18. package/bin/lib/wrangler-cli.js +37 -2
  19. package/bin/lib/wrangler-json.js +35 -0
  20. package/bin/lib/zip-archive.js +187 -0
  21. package/dist/{app-C7_4oab9.js → app-ZcaI1kPN.js} +53190 -13529
  22. package/dist/client/.vite/manifest.json +255 -90
  23. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  24. package/dist/client/_assets/chunks/create-editor-B-m7X7S5.js +261 -0
  25. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  26. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  27. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  28. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  29. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  30. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  31. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  32. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  33. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  34. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  35. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  36. package/dist/client/_assets/chunks/sortable-list-BJyd-LXE.js +2 -0
  37. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  38. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  39. package/dist/client/_assets/chunks/unsafe-svg-BkscJx69.js +2 -0
  40. package/dist/client/_assets/client-DAhoqPdr.css +2 -0
  41. package/dist/client/_assets/client-auth-CAMfTrKW.js +828 -0
  42. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  43. package/dist/client/_assets/client-compose-D6rExKVK.js +2004 -0
  44. package/dist/client/_assets/client-knSEyUJO.js +295 -0
  45. package/dist/client/_assets/client-manage-sVOkqVbH.js +2170 -0
  46. package/dist/client/_assets/client-settings-CGR_nZ78.js +1112 -0
  47. package/dist/github-sync-Gw4orAHk.js +8398 -0
  48. package/dist/index.js +2 -5
  49. package/dist/node.js +11 -9
  50. package/package.json +15 -5
  51. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  52. package/src/__tests__/basecoat-variants.test.ts +121 -0
  53. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  54. package/src/__tests__/dev-scripts.test.ts +203 -0
  55. package/src/__tests__/export-collection-order.test.ts +276 -0
  56. package/src/__tests__/export-feed-ids.test.ts +184 -0
  57. package/src/__tests__/export-feed-order.test.ts +294 -0
  58. package/src/__tests__/export-hugo-build.test.ts +390 -0
  59. package/src/__tests__/export-import-roundtrip.test.ts +94 -0
  60. package/src/__tests__/export-service.test.ts +842 -28
  61. package/src/__tests__/export-smart-collection.test.ts +329 -0
  62. package/src/__tests__/helpers/component-css.ts +91 -0
  63. package/src/__tests__/helpers/db.ts +33 -0
  64. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  65. package/src/__tests__/helpers/hugo-site.ts +146 -0
  66. package/src/__tests__/import-site-command.test.ts +487 -1
  67. package/src/__tests__/mise-config.test.ts +75 -4
  68. package/src/__tests__/node-dev-tasks.test.ts +264 -0
  69. package/src/__tests__/site-export-canonical-import.test.ts +149 -0
  70. package/src/__tests__/snapshot-canonical-replay.test.ts +179 -0
  71. package/src/__tests__/snapshot-settings.test.ts +97 -0
  72. package/src/__tests__/snapshot-tables.test.ts +219 -0
  73. package/src/__tests__/sql-export.test.ts +173 -0
  74. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  75. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  76. package/src/__tests__/zip-archive.test.ts +106 -0
  77. package/src/app.tsx +86 -22
  78. package/src/auth.ts +18 -1
  79. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  80. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  81. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  82. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  83. package/src/client/__tests__/compose-launch.test.ts +62 -1
  84. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  85. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  86. package/src/client/__tests__/copy-field.test.ts +162 -0
  87. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  88. package/src/client/__tests__/image-processor.test.ts +324 -2
  89. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  90. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  91. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  92. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  93. package/src/client/__tests__/toast.test.ts +23 -0
  94. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  95. package/src/client/__tests__/video-processor.test.ts +181 -0
  96. package/src/client/audio-processor.ts +24 -15
  97. package/src/client/audio-waveform.ts +49 -0
  98. package/src/client/collection-dialog-host.ts +72 -0
  99. package/src/client/collection-navigation.ts +51 -16
  100. package/src/client/collection-page-actions.ts +26 -0
  101. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  102. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  103. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  104. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  105. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  106. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  107. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  108. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  109. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  110. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  111. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  112. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  113. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  114. package/src/client/components/__tests__/jant-settings-general.test.ts +561 -172
  115. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  116. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  117. package/src/client/components/collection-dialog-types.ts +49 -0
  118. package/src/client/components/collection-manager-types.ts +32 -1
  119. package/src/client/components/collection-types.ts +1 -15
  120. package/src/client/components/compose-types.ts +75 -1
  121. package/src/client/components/jant-collection-dialog.ts +722 -0
  122. package/src/client/components/jant-collection-directory.ts +567 -182
  123. package/src/client/components/jant-collection-form.ts +53 -232
  124. package/src/client/components/jant-command-palette.ts +100 -49
  125. package/src/client/components/jant-compose-dialog.ts +1486 -707
  126. package/src/client/components/jant-compose-editor.ts +673 -336
  127. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  128. package/src/client/components/jant-config-editor.ts +65 -53
  129. package/src/client/components/jant-confirm-dialog.ts +13 -11
  130. package/src/client/components/jant-locale-picker.ts +418 -0
  131. package/src/client/components/jant-media-lightbox.ts +146 -136
  132. package/src/client/components/jant-nav-manager.ts +690 -366
  133. package/src/client/components/jant-post-menu.ts +1086 -282
  134. package/src/client/components/jant-post-picker.ts +324 -0
  135. package/src/client/components/jant-repo-picker-types.ts +6 -1
  136. package/src/client/components/jant-repo-picker.ts +137 -108
  137. package/src/client/components/jant-settings-avatar.ts +31 -27
  138. package/src/client/components/jant-settings-general.ts +622 -543
  139. package/src/client/components/jant-settings-language.ts +1087 -0
  140. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  141. package/src/client/components/jant-text-preview.ts +94 -86
  142. package/src/client/components/nav-manager-types.ts +62 -1
  143. package/src/client/components/settings-types.ts +14 -29
  144. package/src/client/components/smart-collection-conditions.ts +285 -0
  145. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  146. package/src/client/compose-bridge.ts +214 -141
  147. package/src/client/compose-launch.ts +76 -2
  148. package/src/client/compose-shortcuts.ts +6 -8
  149. package/src/client/compose-triggers.ts +50 -0
  150. package/src/client/copy-field.ts +108 -0
  151. package/src/client/custom-url-menu.ts +60 -4
  152. package/src/client/icons.ts +14 -0
  153. package/src/client/image-plan.ts +165 -0
  154. package/src/client/image-processor.ts +195 -117
  155. package/src/client/image-worker-client.ts +45 -0
  156. package/src/client/image-worker.ts +135 -0
  157. package/src/client/lazy-entries.ts +95 -0
  158. package/src/client/lazy-slugify.ts +2 -2
  159. package/src/client/media-scroll-hint.ts +43 -2
  160. package/src/client/mediabunny.ts +29 -0
  161. package/src/client/post-picker.ts +41 -0
  162. package/src/client/runtime-paths.ts +74 -1
  163. package/src/client/settings-bridge.ts +4 -10
  164. package/src/client/site-header-nav.js +18 -6
  165. package/src/client/smart-collection-dialog-host.ts +116 -0
  166. package/src/client/smart-collection-page-actions.ts +208 -0
  167. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  168. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  169. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  170. package/src/client/tiptap/__tests__/list-editing.test.ts +224 -86
  171. package/src/client/tiptap/__tests__/mark-exit.test.ts +1 -2
  172. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +26 -0
  173. package/src/client/tiptap/bubble-menu.ts +47 -16
  174. package/src/client/tiptap/extensions.ts +0 -3
  175. package/src/client/tiptap/floating-position.ts +216 -2
  176. package/src/client/tiptap/inline-image-upload.ts +7 -3
  177. package/src/client/tiptap/link-toolbar.ts +22 -9
  178. package/src/client/tiptap/structural-keymap.ts +158 -47
  179. package/src/client/toast.ts +13 -0
  180. package/src/client/types/vite-worker.d.ts +12 -0
  181. package/src/client/upload-with-metadata.ts +3 -13
  182. package/src/client/video-processor.ts +351 -107
  183. package/src/client-auth.ts +14 -31
  184. package/src/client-compose.ts +19 -0
  185. package/src/client-manage.ts +13 -0
  186. package/src/client-settings.ts +14 -0
  187. package/src/client-site.ts +10 -3
  188. package/src/client.ts +4 -0
  189. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  190. package/src/db/__tests__/d1-query.test.ts +56 -1
  191. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  192. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  193. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  194. package/src/db/migrations/0032_married_tomas.sql +3 -0
  195. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  196. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  197. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  198. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  199. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  200. package/src/db/migrations/meta/_journal.json +21 -0
  201. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  202. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  203. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  204. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  205. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  206. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  207. package/src/db/migrations/pg/meta/_journal.json +21 -0
  208. package/src/db/pg/schema.ts +195 -27
  209. package/src/db/post-visibility.ts +117 -0
  210. package/src/db/schema.ts +222 -27
  211. package/src/i18n/__tests__/detect.test.ts +26 -81
  212. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  213. package/src/i18n/context.tsx +12 -2
  214. package/src/i18n/detect.ts +7 -66
  215. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  216. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  217. package/src/i18n/locales/public/en.po +490 -158
  218. package/src/i18n/locales/public/en.ts +1 -1
  219. package/src/i18n/locales/public/zh-Hans.po +487 -155
  220. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  221. package/src/i18n/locales/public/zh-Hant.po +487 -155
  222. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  223. package/src/i18n/locales/settings/en.po +555 -73
  224. package/src/i18n/locales/settings/en.ts +1 -1
  225. package/src/i18n/locales/settings/zh-Hans.po +601 -119
  226. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  227. package/src/i18n/locales/settings/zh-Hant.po +614 -132
  228. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  229. package/src/i18n/locales.ts +94 -0
  230. package/src/i18n/middleware.ts +12 -2
  231. package/src/i18n/supported-locales.ts +80 -0
  232. package/src/lib/__tests__/api-settings.test.ts +11 -2
  233. package/src/lib/__tests__/asset-path.test.ts +92 -0
  234. package/src/lib/__tests__/deferred.test.ts +90 -0
  235. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  236. package/src/lib/__tests__/discover.test.ts +410 -0
  237. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  238. package/src/lib/__tests__/feed.test.ts +1526 -15
  239. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  240. package/src/lib/__tests__/github-sync-repo-name.test.ts +40 -0
  241. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  242. package/src/lib/__tests__/http-cache.test.ts +162 -0
  243. package/src/lib/__tests__/image.test.ts +27 -1
  244. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  245. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  246. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  247. package/src/lib/__tests__/markdown-to-tiptap.test.ts +105 -0
  248. package/src/lib/__tests__/markdown.test.ts +10 -0
  249. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  250. package/src/lib/__tests__/navigation.test.ts +135 -1
  251. package/src/lib/__tests__/resolve-config.test.ts +131 -3
  252. package/src/lib/__tests__/schemas.test.ts +69 -5
  253. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  254. package/src/lib/__tests__/slug.test.ts +70 -0
  255. package/src/lib/__tests__/slugify.test.ts +85 -0
  256. package/src/lib/__tests__/storage.test.ts +32 -0
  257. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  258. package/src/lib/__tests__/timeline.test.ts +87 -0
  259. package/src/lib/__tests__/tiptap-to-markdown.test.ts +172 -4
  260. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  261. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  262. package/src/lib/__tests__/url.test.ts +78 -72
  263. package/src/lib/__tests__/view-language.test.ts +413 -0
  264. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  265. package/src/lib/address.ts +57 -0
  266. package/src/lib/api-posts.ts +12 -0
  267. package/src/lib/asset-path.ts +98 -1
  268. package/src/lib/build-env.ts +16 -0
  269. package/src/lib/collection-groups.ts +5 -1
  270. package/src/lib/collection-paths.ts +3 -10
  271. package/src/lib/constants.ts +28 -3
  272. package/src/lib/copy-field.ts +36 -0
  273. package/src/lib/crypto.ts +40 -0
  274. package/src/lib/csp-builder.ts +2 -1
  275. package/src/lib/deferred.ts +79 -0
  276. package/src/lib/discover-ping.ts +141 -0
  277. package/src/lib/discover.ts +439 -0
  278. package/src/lib/env.ts +134 -0
  279. package/src/lib/errors.ts +33 -0
  280. package/src/lib/feed-policy.ts +236 -2
  281. package/src/lib/feed.ts +926 -73
  282. package/src/lib/filter-dimensions.ts +1349 -0
  283. package/src/lib/github-sync-repo-name.ts +45 -0
  284. package/src/lib/github-sync-site-config.ts +3 -0
  285. package/src/lib/hosted-signin.ts +1 -1
  286. package/src/lib/http-cache.ts +148 -0
  287. package/src/lib/hugo-markdown.ts +62 -0
  288. package/src/lib/icons.ts +9 -2
  289. package/src/lib/ids.ts +3 -0
  290. package/src/lib/image.ts +17 -4
  291. package/src/lib/jant-docs.ts +42 -0
  292. package/src/lib/lang-detect.ts +308 -0
  293. package/src/lib/markdown-manager.ts +392 -2
  294. package/src/lib/media-helpers.ts +13 -1
  295. package/src/lib/navigation.ts +78 -11
  296. package/src/lib/pagination.ts +28 -24
  297. package/src/lib/per-language-surfaces.ts +67 -0
  298. package/src/lib/post-body-html.ts +11 -4
  299. package/src/lib/post-meta.ts +19 -0
  300. package/src/lib/render.tsx +55 -7
  301. package/src/lib/resolve-config.ts +111 -6
  302. package/src/lib/schemas.ts +251 -15
  303. package/src/lib/site-header-fragment.tsx +2 -1
  304. package/src/lib/site-unavailable-page.ts +47 -0
  305. package/src/lib/slug.ts +51 -7
  306. package/src/lib/slugify.ts +113 -0
  307. package/src/lib/startup-config.ts +4 -6
  308. package/src/lib/storage.ts +7 -0
  309. package/src/lib/summary.ts +50 -0
  310. package/src/lib/thread-fold.ts +161 -0
  311. package/src/lib/time.ts +32 -0
  312. package/src/lib/timeline.ts +43 -2
  313. package/src/lib/tiptap-render.ts +1 -2
  314. package/src/lib/tiptap-to-markdown.ts +13 -7
  315. package/src/lib/upload.ts +16 -13
  316. package/src/lib/url.ts +159 -86
  317. package/src/lib/version.ts +10 -8
  318. package/src/lib/view-language.ts +436 -0
  319. package/src/lib/view.ts +53 -25
  320. package/src/lib/viewer-context.ts +62 -0
  321. package/src/middleware/__tests__/auth.test.ts +70 -2
  322. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  323. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  324. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  325. package/src/middleware/__tests__/session.test.ts +181 -33
  326. package/src/middleware/auth.ts +50 -31
  327. package/src/middleware/cache-control.ts +75 -10
  328. package/src/middleware/config.ts +6 -6
  329. package/src/middleware/error-handler.ts +12 -0
  330. package/src/middleware/onboarding.ts +32 -10
  331. package/src/middleware/secure-headers.ts +1 -1
  332. package/src/middleware/session.ts +96 -8
  333. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  334. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  335. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  336. package/src/node/__tests__/cli-setup.test.ts +163 -0
  337. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  338. package/src/node/__tests__/cli-site-snapshot.test.ts +67 -0
  339. package/src/node/__tests__/cli-snapshot-meta.test.ts +20 -0
  340. package/src/node/__tests__/runtime.test.ts +75 -0
  341. package/src/node/index.ts +3 -0
  342. package/src/node/request-handler.ts +3 -1
  343. package/src/preset.css +76 -22
  344. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  345. package/src/routes/api/__tests__/collections.test.ts +114 -0
  346. package/src/routes/api/__tests__/discover.test.ts +222 -0
  347. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  348. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  349. package/src/routes/api/__tests__/posts.test.ts +24 -0
  350. package/src/routes/api/__tests__/settings.test.ts +24 -3
  351. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  352. package/src/routes/api/__tests__/upload.test.ts +34 -0
  353. package/src/routes/api/collections.ts +49 -6
  354. package/src/routes/api/custom-urls.ts +0 -1
  355. package/src/routes/api/discover.ts +95 -0
  356. package/src/routes/api/export.ts +6 -3
  357. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  358. package/src/routes/api/internal/sites.ts +0 -1
  359. package/src/routes/api/nav-items.ts +55 -8
  360. package/src/routes/api/posts.ts +161 -0
  361. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  362. package/src/routes/api/public/archive.ts +77 -81
  363. package/src/routes/api/public/posts.ts +44 -7
  364. package/src/routes/api/smart-collections.ts +132 -0
  365. package/src/routes/api/telegram.ts +2 -40
  366. package/src/routes/api/upload.ts +10 -3
  367. package/src/routes/api/uploads.ts +1 -2
  368. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  369. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  370. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  371. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  372. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  373. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  374. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  375. package/src/routes/auth/setup.tsx +934 -186
  376. package/src/routes/auth/signin.tsx +10 -8
  377. package/src/routes/compose.tsx +9 -0
  378. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  379. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  380. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  381. package/src/routes/dash/__tests__/github-sync-app.test.ts +365 -0
  382. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  383. package/src/routes/dash/custom-urls.tsx +1 -37
  384. package/src/routes/dash/settings.tsx +714 -111
  385. package/src/routes/discover-announce.ts +96 -0
  386. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  387. package/src/routes/feed/feed.ts +69 -40
  388. package/src/routes/feed/sitemap.ts +95 -11
  389. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  390. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  391. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  392. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  393. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  394. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  395. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  396. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  397. package/src/routes/pages/__tests__/post-page-round-trips.test.ts +211 -0
  398. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  399. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  400. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  401. package/src/routes/pages/__tests__/thread-order.test.ts +179 -0
  402. package/src/routes/pages/archive.tsx +487 -379
  403. package/src/routes/pages/collection.tsx +94 -96
  404. package/src/routes/pages/collections.tsx +38 -50
  405. package/src/routes/pages/featured.tsx +43 -18
  406. package/src/routes/pages/home.tsx +25 -6
  407. package/src/routes/pages/language.tsx +113 -0
  408. package/src/routes/pages/latest.tsx +26 -16
  409. package/src/routes/pages/new.tsx +3 -0
  410. package/src/routes/pages/page.tsx +228 -45
  411. package/src/routes/pages/partials.tsx +41 -0
  412. package/src/routes/pages/search.tsx +22 -5
  413. package/src/routes/pages/smart-collection.tsx +340 -0
  414. package/src/routes/pages/subscribe.tsx +182 -0
  415. package/src/runtime/__tests__/index.test.ts +19 -0
  416. package/src/runtime/__tests__/node.test.ts +129 -4
  417. package/src/runtime/__tests__/readiness.test.ts +23 -0
  418. package/src/runtime/cloudflare.ts +21 -2
  419. package/src/runtime/index.ts +27 -1
  420. package/src/runtime/node.ts +67 -1
  421. package/src/runtime/readiness.ts +15 -0
  422. package/src/runtime/site.ts +95 -24
  423. package/src/services/__tests__/auth.test.ts +116 -7
  424. package/src/services/__tests__/bootstrap-setup-instance.test.ts +230 -0
  425. package/src/services/__tests__/collection.test.ts +108 -4
  426. package/src/services/__tests__/custom-url.test.ts +34 -0
  427. package/src/services/__tests__/github-app-installations.test.ts +153 -0
  428. package/src/services/__tests__/github-sync-push.test.ts +46 -0
  429. package/src/services/__tests__/language.test.ts +495 -0
  430. package/src/services/__tests__/media.test.ts +31 -0
  431. package/src/services/__tests__/navigation.test.ts +213 -10
  432. package/src/services/__tests__/path.test.ts +236 -0
  433. package/src/services/__tests__/post-language.test.ts +830 -0
  434. package/src/services/__tests__/post-timeline.test.ts +127 -0
  435. package/src/services/__tests__/post.test.ts +120 -9
  436. package/src/services/__tests__/settings.test.ts +228 -17
  437. package/src/services/__tests__/site-admin.test.ts +5 -5
  438. package/src/services/__tests__/smart-collection.test.ts +651 -0
  439. package/src/services/about-page.ts +22 -1
  440. package/src/services/auth.ts +51 -49
  441. package/src/services/bootstrap.ts +408 -39
  442. package/src/services/collection-directory-position.ts +76 -0
  443. package/src/services/collection.ts +331 -46
  444. package/src/services/custom-url.ts +29 -24
  445. package/src/services/export-theme/assets/client-site.css +1 -1
  446. package/src/services/export-theme/assets/client-site.js +123 -123
  447. package/src/services/export-theme/layouts/_default/alias.html +27 -1
  448. package/src/services/export-theme/layouts/_default/list.html +2 -63
  449. package/src/services/export-theme/layouts/_default/rss.xml +103 -38
  450. package/src/services/export-theme/layouts/collections/list.html +3 -3
  451. package/src/services/export-theme/layouts/featured/list.html +1 -4
  452. package/src/services/export-theme/layouts/index.html +40 -26
  453. package/src/services/export-theme/layouts/partials/collection-members.html +100 -0
  454. package/src/services/export-theme/layouts/partials/collection-threads.html +33 -0
  455. package/src/services/export-theme/layouts/partials/featured-members.html +35 -0
  456. package/src/services/export-theme/layouts/partials/featured-thread.html +1 -1
  457. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  458. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  459. package/src/services/export-theme/layouts/partials/head.html +1 -1
  460. package/src/services/export-theme/layouts/partials/header.html +5 -3
  461. package/src/services/export-theme/layouts/partials/jant-data.html +23 -0
  462. package/src/services/export-theme/layouts/partials/latest-members.html +48 -0
  463. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  464. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  465. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  466. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  467. package/src/services/export-theme/layouts/partials/smart-collection-members.html +129 -0
  468. package/src/services/export-theme/layouts/partials/thread-preview.html +41 -31
  469. package/src/services/export-theme/layouts/post/list.html +1 -1
  470. package/src/services/export-theme/layouts/smart_collection/list.html +24 -0
  471. package/src/services/export-theme/styles/main.css +13 -2
  472. package/src/services/export-theme/theme.toml +1 -1
  473. package/src/services/export.ts +880 -72
  474. package/src/services/github-app-installations.ts +171 -4
  475. package/src/services/github-sync.ts +69 -15
  476. package/src/services/index.ts +34 -3
  477. package/src/services/language.ts +412 -0
  478. package/src/services/mcp.ts +22 -2
  479. package/src/services/media.ts +22 -7
  480. package/src/services/navigation.ts +429 -67
  481. package/src/services/path.ts +254 -6
  482. package/src/services/post.ts +1234 -190
  483. package/src/services/search.ts +39 -2
  484. package/src/services/settings.ts +238 -43
  485. package/src/services/site-admin.ts +27 -14
  486. package/src/services/site.ts +160 -32
  487. package/src/services/smart-collection.ts +636 -0
  488. package/src/services/telegram.ts +20 -22
  489. package/src/services/upload-session.ts +1 -3
  490. package/src/style-author.css +27 -0
  491. package/src/styles/components-author.css +1622 -0
  492. package/src/styles/components.css +24 -1571
  493. package/src/styles/site-media.css +16 -2
  494. package/src/styles/tokens.css +82 -12
  495. package/src/styles/ui-author.css +7602 -0
  496. package/src/styles/ui.css +1090 -7216
  497. package/src/types/app-context.ts +23 -0
  498. package/src/types/bindings.ts +8 -0
  499. package/src/types/config.ts +94 -15
  500. package/src/types/constants.ts +188 -16
  501. package/src/types/entities.ts +87 -1
  502. package/src/types/operations.ts +74 -2
  503. package/src/types/props.ts +157 -31
  504. package/src/types/views.ts +55 -1
  505. package/src/ui/__tests__/font-themes.test.ts +52 -12
  506. package/src/ui/compose/ComposeDialog.tsx +201 -7
  507. package/src/ui/compose/ComposePrompt.tsx +1 -5
  508. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  509. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  510. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  511. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  512. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  513. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  514. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  515. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  516. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  517. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  518. package/src/ui/dash/settings/GeneralContent.tsx +277 -113
  519. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  520. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  521. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  522. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  523. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  524. package/src/ui/dash/settings/discover-copy.ts +116 -0
  525. package/src/ui/feed/LinkCard.tsx +55 -45
  526. package/src/ui/feed/NoteCard.tsx +9 -4
  527. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  528. package/src/ui/feed/QuoteCard.tsx +55 -30
  529. package/src/ui/feed/ThreadPreview.tsx +6 -2
  530. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  531. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  532. package/src/ui/feed/thread-preview-state.ts +20 -6
  533. package/src/ui/font-themes.ts +39 -29
  534. package/src/ui/layouts/BaseLayout.tsx +155 -10
  535. package/src/ui/layouts/SiteLayout.tsx +192 -34
  536. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  537. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  538. package/src/ui/pages/ArchivePage.tsx +282 -289
  539. package/src/ui/pages/CollectionPage.tsx +86 -76
  540. package/src/ui/pages/CollectionsPage.tsx +5 -0
  541. package/src/ui/pages/ComposePage.tsx +8 -0
  542. package/src/ui/pages/PostPage.tsx +47 -0
  543. package/src/ui/pages/SearchPage.tsx +3 -3
  544. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  545. package/src/ui/pages/SubscribePage.tsx +236 -0
  546. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  547. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  548. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  549. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  550. package/src/ui/shared/CollectionsManager.tsx +70 -17
  551. package/src/ui/shared/CopyField.tsx +127 -0
  552. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  553. package/src/ui/shared/MediaGallery.tsx +125 -46
  554. package/src/ui/shared/Pagination.tsx +20 -5
  555. package/src/ui/shared/PostFooter.tsx +20 -11
  556. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  557. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  558. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  559. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  560. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  561. package/src/ui/shared/archive-labels.ts +98 -0
  562. package/src/ui/shared/collection-management-labels.ts +94 -16
  563. package/src/ui/shared/navigation-labels.ts +45 -71
  564. package/src/ui/shared/post-article-attributes.ts +2 -0
  565. package/src/ui/shared/smart-collection-labels.ts +343 -0
  566. package/dist/app-C75Zc8dD.js +0 -6
  567. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  568. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  569. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  570. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  571. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  572. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  573. package/dist/env-BPYViDJJ.js +0 -281
  574. package/dist/export-Dl5KCiEs.js +0 -5562
  575. package/dist/github-api-BNF35Lkx.js +0 -176
  576. package/dist/github-app-DdiD-bC4.js +0 -275
  577. package/dist/github-sync-Bqg62IvV.js +0 -4
  578. package/dist/github-sync-HdK2EgvJ.js +0 -431
  579. package/dist/url-CbLAtlZe.js +0 -900
  580. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  581. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  582. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  583. package/src/client/collection-created-notice.ts +0 -66
  584. package/src/client/collection-form-bridge.ts +0 -191
  585. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  586. package/src/client/components/jant-post-form.ts +0 -422
  587. package/src/client/components/post-form-template.ts +0 -352
  588. package/src/client/components/post-form-types.ts +0 -96
  589. package/src/client/media-metadata.ts +0 -247
  590. package/src/client/post-form-bridge.ts +0 -193
  591. package/src/client/tiptap/exitable-marks.ts +0 -73
  592. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -9,6 +9,15 @@
9
9
  * Routes call these methods instead of touching the settings table to
10
10
  * keep the model relational (one entry per (installation_id, site_id)
11
11
  * pair) instead of serialising a JSON list into a single settings row.
12
+ *
13
+ * Two scopes matter and they are not interchangeable. A *site* scope
14
+ * answers "which accounts is this blog syncing through". A *user* scope
15
+ * answers "which accounts has this person already authorized anywhere",
16
+ * which is what the connect flow needs: a GitHub App installs once per
17
+ * GitHub account, so someone who connected their first site can never
18
+ * install it a second time — GitHub sends them to the installation's
19
+ * Configure page and never calls back. Their other sites have to be able
20
+ * to reuse that installation without leaving Jant.
12
21
  */
13
22
 
14
23
  import { and, eq, inArray } from "drizzle-orm";
@@ -35,11 +44,56 @@ export interface StoredGitHubAppInstallation {
35
44
  addedAt: number;
36
45
  }
37
46
 
47
+ /**
48
+ * An installation as seen by one person rather than by one site: the
49
+ * same installation may back several of their sites, so the site id
50
+ * drops out and `addedAt` carries the most recent binding.
51
+ */
52
+ export interface VisibleGitHubAppInstallation {
53
+ installationId: string;
54
+ account: GitHubInstallationAccount;
55
+ /** Unix seconds — most recent binding of this installation to any of the user's sites. */
56
+ addedAt: number;
57
+ }
58
+
38
59
  export interface GitHubAppInstallationsService {
39
60
  /** Installations this site has authorized, newest-first by `addedAt`. */
40
61
  listInstallationsForSite(
41
62
  siteId: string,
42
63
  ): Promise<StoredGitHubAppInstallation[]>;
64
+ /**
65
+ * Installations authorized on any site this user is a member of,
66
+ * deduplicated by installation and newest-first by `addedAt`.
67
+ *
68
+ * This is the set the connect flow may offer and act on: membership is
69
+ * the tenancy boundary, so one person's sites share their GitHub
70
+ * accounts while other tenants' installations stay invisible.
71
+ */
72
+ listInstallationsForUser(
73
+ userId: string,
74
+ ): Promise<VisibleGitHubAppInstallation[]>;
75
+ /**
76
+ * One installation from `listInstallationsForUser`, or `null` when the
77
+ * user has no site bound to it. Routes use this to authorize an
78
+ * `installationId` that arrived in a request.
79
+ */
80
+ findInstallationForUser(
81
+ installationId: string,
82
+ userId: string,
83
+ ): Promise<VisibleGitHubAppInstallation | null>;
84
+ /**
85
+ * Installations the user's sites are *actively syncing through*, read
86
+ * from each site's settings rather than from the junction table.
87
+ *
88
+ * The two can disagree: settings are written when a site connects,
89
+ * the binding when it was installed, and a site that connected while
90
+ * the binding write failed keeps syncing with nothing to show for it.
91
+ * Callers use this to rebuild the missing row — they have to fetch the
92
+ * account from GitHub, which is why this returns ids and not accounts.
93
+ */
94
+ listSyncingInstallationsForUser(
95
+ userId: string,
96
+ ): Promise<Array<{ siteId: string; installationId: string }>>;
43
97
  /** Site ids that share the given installation. */
44
98
  listSitesForInstallation(installationId: string): Promise<string[]>;
45
99
  /**
@@ -56,6 +110,19 @@ export interface GitHubAppInstallationsService {
56
110
  ): Promise<void>;
57
111
  /** Remove a single (installation, site) binding. */
58
112
  removeInstallation(installationId: string, siteId: string): Promise<void>;
113
+ /**
114
+ * Remove the installation from every site this user is a member of.
115
+ *
116
+ * Used when GitHub reports the installation as gone (401/404): the
117
+ * binding that made it visible may live on another of the user's
118
+ * sites, so clearing only the current site would leave a dead account
119
+ * in their picker forever. Other tenants' bindings are left to the
120
+ * App-level webhook, which sees the authoritative `installation.deleted`.
121
+ */
122
+ removeInstallationForUser(
123
+ installationId: string,
124
+ userId: string,
125
+ ): Promise<void>;
59
126
  /**
60
127
  * Remove the installation from every site it's bound to and return
61
128
  * the previously-bound site ids so callers can fan out side effects
@@ -94,7 +161,63 @@ export function createGitHubAppInstallationsService(
94
161
  db: Database,
95
162
  databaseSchema: DatabaseSchema = sqliteSchemaBundle,
96
163
  ): GitHubAppInstallationsService {
97
- const { githubAppInstallation, settings } = databaseSchema;
164
+ const { githubAppInstallation, settings, siteMembers } = databaseSchema;
165
+
166
+ /**
167
+ * Rows for every binding on a site the user belongs to. One join, not
168
+ * a per-site fan-out — a user has few sites but the query shape is
169
+ * what gets copied.
170
+ */
171
+ function selectUserBindings(userId: string, installationId?: string) {
172
+ const scope = eq(siteMembers.userId, userId);
173
+ return db
174
+ .select({
175
+ installationId: githubAppInstallation.installationId,
176
+ accountLogin: githubAppInstallation.accountLogin,
177
+ accountType: githubAppInstallation.accountType,
178
+ accountAvatarUrl: githubAppInstallation.accountAvatarUrl,
179
+ addedAt: githubAppInstallation.addedAt,
180
+ })
181
+ .from(githubAppInstallation)
182
+ .innerJoin(
183
+ siteMembers,
184
+ eq(siteMembers.siteId, githubAppInstallation.siteId),
185
+ )
186
+ .where(
187
+ installationId
188
+ ? and(scope, eq(githubAppInstallation.installationId, installationId))
189
+ : scope,
190
+ );
191
+ }
192
+
193
+ /** Collapse per-site bindings into one entry per installation. */
194
+ function toVisible(
195
+ rows: Array<{
196
+ installationId: string;
197
+ accountLogin: string;
198
+ accountType: string;
199
+ accountAvatarUrl: string;
200
+ addedAt: number;
201
+ }>,
202
+ ): VisibleGitHubAppInstallation[] {
203
+ const byInstallation = new Map<string, VisibleGitHubAppInstallation>();
204
+ for (const row of rows) {
205
+ const entry = {
206
+ installationId: row.installationId,
207
+ account: {
208
+ login: row.accountLogin,
209
+ type: toAccountType(row.accountType),
210
+ avatarUrl: row.accountAvatarUrl,
211
+ },
212
+ addedAt: row.addedAt,
213
+ };
214
+ const existing = byInstallation.get(row.installationId);
215
+ if (!existing || existing.addedAt < entry.addedAt) {
216
+ byInstallation.set(row.installationId, entry);
217
+ }
218
+ }
219
+ return [...byInstallation.values()].sort((a, b) => b.addedAt - a.addedAt);
220
+ }
98
221
 
99
222
  // Per-site settings mutation helpers. These sit alongside the
100
223
  // junction-table operations because the webhook fan-out inherently
@@ -152,6 +275,32 @@ export function createGitHubAppInstallationsService(
152
275
  return rows.map(toStored).sort((a, b) => b.addedAt - a.addedAt);
153
276
  },
154
277
 
278
+ async listInstallationsForUser(userId) {
279
+ return toVisible(await selectUserBindings(userId));
280
+ },
281
+
282
+ async findInstallationForUser(installationId, userId) {
283
+ const rows = await selectUserBindings(userId, installationId);
284
+ return toVisible(rows)[0] ?? null;
285
+ },
286
+
287
+ async listSyncingInstallationsForUser(userId) {
288
+ const rows = await db
289
+ .select({
290
+ siteId: settings.siteId,
291
+ installationId: settings.value,
292
+ })
293
+ .from(settings)
294
+ .innerJoin(siteMembers, eq(siteMembers.siteId, settings.siteId))
295
+ .where(
296
+ and(
297
+ eq(siteMembers.userId, userId),
298
+ eq(settings.key, "GITHUB_SYNC_APP_INSTALLATION_ID"),
299
+ ),
300
+ );
301
+ return rows.filter((row) => row.installationId.trim().length > 0);
302
+ },
303
+
155
304
  async listSitesForInstallation(installationId) {
156
305
  const rows = await db
157
306
  .select({ siteId: githubAppInstallation.siteId })
@@ -198,6 +347,21 @@ export function createGitHubAppInstallationsService(
198
347
  );
199
348
  },
200
349
 
350
+ async removeInstallationForUser(installationId, userId) {
351
+ const memberSites = db
352
+ .select({ siteId: siteMembers.siteId })
353
+ .from(siteMembers)
354
+ .where(eq(siteMembers.userId, userId));
355
+ await db
356
+ .delete(githubAppInstallation)
357
+ .where(
358
+ and(
359
+ eq(githubAppInstallation.installationId, installationId),
360
+ inArray(githubAppInstallation.siteId, memberSites),
361
+ ),
362
+ );
363
+ },
364
+
201
365
  async removeInstallationEverywhere(installationId) {
202
366
  const rows = await db
203
367
  .select({ siteId: githubAppInstallation.siteId })
@@ -277,6 +441,11 @@ export function createGitHubAppInstallationsService(
277
441
  };
278
442
  }
279
443
 
444
+ /** The column is a checked enum; narrow it without trusting the read. */
445
+ function toAccountType(value: string): GitHubAccountType {
446
+ return value === "Organization" ? "Organization" : "User";
447
+ }
448
+
280
449
  function toStored(row: {
281
450
  installationId: string;
282
451
  siteId: string;
@@ -285,14 +454,12 @@ function toStored(row: {
285
454
  accountAvatarUrl: string;
286
455
  addedAt: number;
287
456
  }): StoredGitHubAppInstallation {
288
- const type: GitHubAccountType =
289
- row.accountType === "Organization" ? "Organization" : "User";
290
457
  return {
291
458
  installationId: row.installationId,
292
459
  siteId: row.siteId,
293
460
  account: {
294
461
  login: row.accountLogin,
295
- type,
462
+ type: toAccountType(row.accountType),
296
463
  avatarUrl: row.accountAvatarUrl,
297
464
  },
298
465
  addedAt: row.addedAt,
@@ -32,7 +32,13 @@ import { getInstallationToken } from "../lib/github-app.js";
32
32
  import type { GitHubAppEnvConfig } from "../lib/env.js";
33
33
  import { parseFrontMatter } from "../lib/hugo-markdown.js";
34
34
  import { markdownToTiptapJson } from "../lib/markdown-to-tiptap.js";
35
- import { createExportService, type SiteConfig } from "./export.js";
35
+ import {
36
+ createExportService,
37
+ isStoredExportFile,
38
+ readStoredExportFile,
39
+ type ExportFile,
40
+ type SiteConfig,
41
+ } from "./export.js";
36
42
  import type { PostService } from "./post.js";
37
43
  import type { PathService } from "./path.js";
38
44
  import type { CollectionService } from "./collection.js";
@@ -116,6 +122,33 @@ export function isManagedPath(path: string): boolean {
116
122
  return JANT_MANAGED_GLOBS.some((g) => pathMatchesManagedGlob(path, g));
117
123
  }
118
124
 
125
+ /**
126
+ * Pick the export files this push should write.
127
+ *
128
+ * Everything is written every push except scaffolding the repo already has.
129
+ * `wrangler.jsonc` is the case that needs this: its Worker name defaults to
130
+ * the repository name, and a user whose Worker is named differently has to
131
+ * correct it by hand. Rewriting the file every push would revert that, and
132
+ * every build after it would fail on the name mismatch.
133
+ *
134
+ * @param exportFiles - Everything the export generated.
135
+ * @param existingPaths - Repo-relative paths already present on the remote.
136
+ * @returns The subset to include in the commit.
137
+ * @example
138
+ * selectFilesToWrite(files, new Set(["wrangler.jsonc"]));
139
+ */
140
+ export function selectFilesToWrite(
141
+ exportFiles: readonly ExportFile[],
142
+ existingPaths: ReadonlySet<string>,
143
+ ): ExportFile[] {
144
+ return exportFiles.filter(
145
+ (file) =>
146
+ isStoredExportFile(file) ||
147
+ !file.scaffoldOnce ||
148
+ !existingPaths.has(file.path),
149
+ );
150
+ }
151
+
119
152
  /**
120
153
  * Compute the tree items that should null-out files on the remote HEAD
121
154
  * which Jant claims ownership of (matches `JANT_MANAGED_GLOBS`) but is
@@ -458,6 +491,9 @@ export function createGitHubSyncService(
458
491
  const exportService = createExportService(services, siteConfig, {
459
492
  storage: deps.storage,
460
493
  bundleMedia: false,
494
+ // Cloudflare names a Worker imported from this repository after it,
495
+ // so the deploy config's Worker name matches with no editing.
496
+ repoName: repo,
461
497
  });
462
498
  const exportFiles = await exportService.generateHugoFiles();
463
499
 
@@ -488,6 +524,26 @@ export function createGitHubSyncService(
488
524
  marker,
489
525
  );
490
526
 
527
+ // The HEAD tree is needed twice: to decide which scaffold files the
528
+ // repo already has, and (below) to delete managed files this push no
529
+ // longer generates. Reading it once, before the tree is assembled,
530
+ // covers both.
531
+ const headCommit = await client.getCommit(owner, repo, headSha);
532
+ const headTree = await client.getTree(owner, repo, headCommit.treeSha, {
533
+ recursive: true,
534
+ });
535
+ if (headTree.truncated) {
536
+ throw new Error(
537
+ "GitHub tree exceeds API limits (>100k entries or >7MB); " +
538
+ "incremental deletion cannot run safely against this repo.",
539
+ );
540
+ }
541
+ const existingPaths = new Set<string>(
542
+ headTree.tree
543
+ .filter((item) => item.type === "blob")
544
+ .map((item) => item.path),
545
+ );
546
+
491
547
  // Convert to Git tree items. The ownership marker is always emitted;
492
548
  // `created_at` is preserved across pushes (Git dedupes identical
493
549
  // blobs by SHA, so a no-op marker push is still cheap).
@@ -500,20 +556,28 @@ export function createGitHubSyncService(
500
556
  },
501
557
  ];
502
558
 
503
- for (const file of exportFiles) {
504
- if (typeof file.content === "string") {
559
+ for (const file of selectFilesToWrite(exportFiles, existingPaths)) {
560
+ // Sync links media by URL, so a stored file only turns up when the
561
+ // site URL is unknown and the export falls back to bundling.
562
+ const content = isStoredExportFile(file)
563
+ ? deps.storage
564
+ ? await readStoredExportFile(file, deps.storage)
565
+ : null
566
+ : file.content;
567
+ if (content === null) continue;
568
+ if (typeof content === "string") {
505
569
  treeItems.push({
506
570
  path: file.path,
507
571
  mode: "100644",
508
572
  type: "blob",
509
- content: file.content,
573
+ content,
510
574
  });
511
575
  } else {
512
576
  // Binary files need to be created as blobs first
513
577
  const blob = await client.createBlob(
514
578
  owner,
515
579
  repo,
516
- uint8ArrayToBase64(file.content),
580
+ uint8ArrayToBase64(content),
517
581
  "base64",
518
582
  );
519
583
  treeItems.push({
@@ -530,16 +594,6 @@ export function createGitHubSyncService(
530
594
  // removed. Without this, deleting a post in Jant would leave the
531
595
  // old file behind on GitHub, because base_tree preserves everything
532
596
  // we don't explicitly overwrite.
533
- const headCommit = await client.getCommit(owner, repo, headSha);
534
- const headTree = await client.getTree(owner, repo, headCommit.treeSha, {
535
- recursive: true,
536
- });
537
- if (headTree.truncated) {
538
- throw new Error(
539
- "GitHub tree exceeds API limits (>100k entries or >7MB); " +
540
- "incremental deletion cannot run safely against this repo.",
541
- );
542
- }
543
597
  const writtenPaths = new Set<string>(treeItems.map((item) => item.path));
544
598
  treeItems.push(...computeManagedDeletions(headTree.tree, writtenPaths));
545
599
 
@@ -21,6 +21,10 @@ import {
21
21
  createCollectionService,
22
22
  type CollectionService,
23
23
  } from "./collection.js";
24
+ import {
25
+ createSmartCollectionService,
26
+ type SmartCollectionService,
27
+ } from "./smart-collection.js";
24
28
  import { createSearchService, type SearchService } from "./search.js";
25
29
  import { createNavItemService, type NavItemService } from "./navigation.js";
26
30
  import { createAuthService, type AuthService } from "./auth.js";
@@ -36,6 +40,7 @@ import {
36
40
  type SiteProfileService,
37
41
  } from "./site-profile.js";
38
42
  import { createAboutPageService, type AboutPageService } from "./about-page.js";
43
+ import { createLanguageService, type LanguageService } from "./language.js";
39
44
  import {
40
45
  createUploadSessionService,
41
46
  type UploadSessionService,
@@ -57,6 +62,7 @@ export interface Services {
57
62
  media: MediaService;
58
63
  uploads: UploadSessionService;
59
64
  collections: CollectionService;
65
+ smartCollections: SmartCollectionService;
60
66
  search: SearchService;
61
67
  navItems: NavItemService;
62
68
  auth: AuthService;
@@ -66,6 +72,7 @@ export interface Services {
66
72
  siteMembers: SiteMemberService;
67
73
  siteProfile: SiteProfileService;
68
74
  aboutPage: AboutPageService;
75
+ language: LanguageService;
69
76
  githubAppInstallations: GitHubAppInstallationsService;
70
77
  telegram: TelegramService;
71
78
  }
@@ -82,6 +89,8 @@ export function createServices(
82
89
  siteResolutionMode?: "single-site" | "host-based";
83
90
  enforceHostedMediaQuota?: boolean;
84
91
  hostedControlPlane?: HostedControlPlaneClient | null;
92
+ /** Signing key for derived tokens, e.g. the account-deletion CSRF token. */
93
+ authSecret?: string;
85
94
  },
86
95
  ): Services {
87
96
  const databaseSchema = config?.schema ?? sqliteSchemaBundle;
@@ -89,7 +98,7 @@ export function createServices(
89
98
  const site = createSiteService(db, databaseSchema);
90
99
  const settings = createSettingsService(db, siteId, databaseSchema, dialect);
91
100
  const paths = createPathService(db, siteId, databaseSchema);
92
- const navItems = createNavItemService(db, siteId, databaseSchema);
101
+ const navItems = createNavItemService(db, siteId, databaseSchema, paths);
93
102
  const posts = createPostService(
94
103
  db,
95
104
  {
@@ -100,12 +109,21 @@ export function createServices(
100
109
  paths,
101
110
  databaseSchema,
102
111
  );
112
+ const smartCollections = createSmartCollectionService(
113
+ db,
114
+ siteId,
115
+ paths,
116
+ posts,
117
+ databaseSchema,
118
+ config?.slugIdLength ?? 5,
119
+ );
103
120
  const collections = createCollectionService(
104
121
  db,
105
122
  siteId,
106
123
  paths,
107
124
  databaseSchema,
108
125
  dialect,
126
+ smartCollections,
109
127
  );
110
128
  const media = createMediaService(db, siteId, databaseSchema, dialect, {
111
129
  enforceHostedQuota: config?.enforceHostedMediaQuota ?? false,
@@ -121,6 +139,7 @@ export function createServices(
121
139
  media,
122
140
  uploads: createUploadSessionService(db, siteId, media, databaseSchema),
123
141
  collections,
142
+ smartCollections,
124
143
  search: createSearchService(rawQuery, siteId, dialect),
125
144
  navItems,
126
145
  auth: createAuthService(
@@ -128,13 +147,16 @@ export function createServices(
128
147
  settings,
129
148
  {
130
149
  databaseDialect: dialect,
150
+ authSecret: config?.authSecret,
131
151
  },
132
152
  databaseSchema,
133
153
  ),
134
154
  apiTokens: createApiTokenService(db, siteId, databaseSchema),
135
- bootstrap: createBootstrapService(db, {
155
+ bootstrap: createBootstrapService(db, siteId, {
136
156
  schema: databaseSchema,
157
+ databaseDialect: dialect,
137
158
  bootstrapSite: config?.bootstrapSite,
159
+ siteResolutionMode: config?.siteResolutionMode,
138
160
  }),
139
161
  siteAdmin: createSiteAdminService(db, databaseSchema, dialect, {
140
162
  siteResolutionMode: config?.siteResolutionMode,
@@ -156,7 +178,9 @@ export function createServices(
156
178
  paths,
157
179
  posts,
158
180
  collections,
181
+ smartCollections,
159
182
  }),
183
+ language: createLanguageService({ settings, posts, paths }),
160
184
  githubAppInstallations: createGitHubAppInstallationsService(
161
185
  db,
162
186
  databaseSchema,
@@ -170,17 +194,24 @@ export type { SiteService } from "./site.js";
170
194
  export type { PathService } from "./path.js";
171
195
  export type { PostService, PostFilters, PostDeleteDeps } from "./post.js";
172
196
  export type { CustomUrlService } from "./custom-url.js";
197
+ export type {
198
+ SmartCollectionService,
199
+ SmartCollectionViewer,
200
+ } from "./smart-collection.js";
173
201
  export type { MediaService, MediaFilters } from "./media.js";
174
202
  export type { UploadSessionService } from "./upload-session.js";
175
203
  export type { CollectionService } from "./collection.js";
176
204
  export type { SearchService, SearchResult, SearchOptions } from "./search.js";
177
205
  export type { NavItemService } from "./navigation.js";
178
206
  export type { AboutPageService, AboutPageStatus } from "./about-page.js";
207
+ export type { LanguageService, LanguageState } from "./language.js";
179
208
  export type { AuthService, DeleteAccountDeps } from "./auth.js";
180
209
  export type { ApiTokenService } from "./api-token.js";
181
210
  export type {
182
211
  BootstrapService,
183
- CompleteInitialSetupData,
212
+ CompleteSiteSetupData,
213
+ CompleteSiteSetupDeps,
214
+ ProvisionOwnerAccountData,
184
215
  } from "./bootstrap.js";
185
216
  export type { SiteMemberService } from "./site-member.js";
186
217
  export type { SiteProfileService } from "./site-profile.js";