@jant/core 0.6.15 → 0.7.0

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 (537) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/import-site.js +67 -4
  4. package/bin/commands/reset-password.js +21 -8
  5. package/bin/commands/site/snapshot/export.js +1 -1
  6. package/bin/commands/site/snapshot/import.js +1 -1
  7. package/bin/lib/d1-query.js +5 -16
  8. package/bin/lib/node-env.js +40 -4
  9. package/bin/lib/site-selection.js +85 -29
  10. package/bin/lib/wrangler-cli.js +28 -2
  11. package/bin/lib/wrangler-json.js +35 -0
  12. package/dist/{app-C-oi_t8W.js → app-K_Aa1MMn.js} +52623 -13193
  13. package/dist/client/.vite/manifest.json +255 -90
  14. package/dist/client/_assets/chunks/confirm-D7GrwsCN.js +45 -0
  15. package/dist/client/_assets/chunks/create-editor-CD3FhrOB.js +257 -0
  16. package/dist/client/_assets/chunks/{native-DpcrFAPh.js → emoji-mart-C4RpRKcU.js} +716 -1
  17. package/dist/client/_assets/chunks/footnote-rail-CmeoVtnU.js +1 -0
  18. package/dist/client/_assets/chunks/heic-to-CuP2Qlsw.js +1 -0
  19. package/dist/client/_assets/chunks/jant-locale-picker-DL6mIOT5.js +69 -0
  20. package/dist/client/_assets/chunks/lit-DW8VmJAT.js +2 -0
  21. package/dist/client/_assets/chunks/mediabunny-DRaCQ1r-.js +31 -0
  22. package/dist/client/_assets/chunks/rolldown-runtime-hePW80VL.js +1 -0
  23. package/dist/client/_assets/chunks/settings-paths-BcdySNx6.js +84 -0
  24. package/dist/client/_assets/chunks/site-header-nav-DzXsFk05.js +1 -0
  25. package/dist/client/_assets/chunks/slug-format-CRXO1AL5.js +1 -0
  26. package/dist/client/_assets/chunks/slugify-373citbs.js +1 -0
  27. package/dist/client/_assets/chunks/sortable-list-CgaL2jCs.js +2 -0
  28. package/dist/client/_assets/chunks/types-D5iF3H1a.js +1 -0
  29. package/dist/client/_assets/chunks/unsafe-html-B5ZEXaxg.js +1 -0
  30. package/dist/client/_assets/chunks/unsafe-svg-0QCkP0vZ.js +2 -0
  31. package/dist/client/_assets/client-GiYENVw8.css +2 -0
  32. package/dist/client/_assets/client-VnFxJN7G.js +295 -0
  33. package/dist/client/_assets/client-auth-k8gJ6QJj.js +828 -0
  34. package/dist/client/_assets/client-author-DQ8R-8KR.css +2 -0
  35. package/dist/client/_assets/client-compose-B_kDtWkW.js +2004 -0
  36. package/dist/client/_assets/client-manage-M90aSTOk.js +2170 -0
  37. package/dist/client/_assets/client-settings-DfYs9n1F.js +1112 -0
  38. package/dist/{export-CsFx6F_M.js → github-sync-BPAvT999.js} +2343 -110
  39. package/dist/index.js +2 -5
  40. package/dist/node.js +5 -7
  41. package/package.json +3 -3
  42. package/src/__tests__/auth-session-freshness.test.ts +57 -0
  43. package/src/__tests__/basecoat-variants.test.ts +121 -0
  44. package/src/__tests__/bin/assets-upload.test.ts +68 -0
  45. package/src/__tests__/export-hugo-build.test.ts +260 -0
  46. package/src/__tests__/export-service.test.ts +231 -0
  47. package/src/__tests__/helpers/component-css.ts +91 -0
  48. package/src/__tests__/helpers/db.ts +33 -0
  49. package/src/__tests__/helpers/export-fixtures.ts +3 -0
  50. package/src/__tests__/mise-config.test.ts +22 -0
  51. package/src/__tests__/stylesheet-audience.test.ts +159 -0
  52. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  53. package/src/app.tsx +71 -16
  54. package/src/auth.ts +18 -1
  55. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  56. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  57. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  58. package/src/client/__tests__/compose-bridge.test.ts +524 -2
  59. package/src/client/__tests__/compose-launch.test.ts +62 -1
  60. package/src/client/__tests__/compose-shortcuts.test.ts +57 -8
  61. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  62. package/src/client/__tests__/copy-field.test.ts +162 -0
  63. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  64. package/src/client/__tests__/image-processor.test.ts +324 -2
  65. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  66. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  67. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  68. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  69. package/src/client/__tests__/toast.test.ts +88 -1
  70. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  71. package/src/client/__tests__/video-processor.test.ts +181 -0
  72. package/src/client/audio-processor.ts +24 -15
  73. package/src/client/audio-waveform.ts +49 -0
  74. package/src/client/collection-dialog-host.ts +72 -0
  75. package/src/client/collection-navigation.ts +51 -16
  76. package/src/client/collection-page-actions.ts +26 -0
  77. package/src/client/components/__tests__/compose-format-convert.test.ts +0 -19
  78. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  79. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  80. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  81. package/src/client/components/__tests__/jant-command-palette.test.ts +79 -0
  82. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  83. package/src/client/components/__tests__/jant-compose-dialog.test.ts +3283 -389
  84. package/src/client/components/__tests__/jant-compose-editor.test.ts +581 -86
  85. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +34 -89
  86. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  87. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  88. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  89. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  90. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  91. package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
  92. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  93. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  94. package/src/client/components/collection-dialog-types.ts +49 -0
  95. package/src/client/components/collection-manager-types.ts +32 -1
  96. package/src/client/components/collection-types.ts +1 -15
  97. package/src/client/components/compose-format-convert.ts +5 -1
  98. package/src/client/components/compose-types.ts +121 -3
  99. package/src/client/components/jant-collection-dialog.ts +722 -0
  100. package/src/client/components/jant-collection-directory.ts +567 -182
  101. package/src/client/components/jant-collection-form.ts +53 -232
  102. package/src/client/components/jant-command-palette.ts +106 -49
  103. package/src/client/components/jant-compose-dialog.ts +2757 -1194
  104. package/src/client/components/jant-compose-editor.ts +866 -397
  105. package/src/client/components/jant-compose-fullscreen.ts +53 -47
  106. package/src/client/components/jant-config-editor.ts +65 -53
  107. package/src/client/components/jant-confirm-dialog.ts +13 -11
  108. package/src/client/components/jant-locale-picker.ts +418 -0
  109. package/src/client/components/jant-media-lightbox.ts +146 -136
  110. package/src/client/components/jant-nav-manager.ts +690 -366
  111. package/src/client/components/jant-post-menu.ts +1142 -268
  112. package/src/client/components/jant-post-picker.ts +324 -0
  113. package/src/client/components/jant-repo-picker.ts +138 -106
  114. package/src/client/components/jant-settings-avatar.ts +31 -27
  115. package/src/client/components/jant-settings-general.ts +617 -543
  116. package/src/client/components/jant-settings-language.ts +1087 -0
  117. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  118. package/src/client/components/jant-text-preview.ts +94 -86
  119. package/src/client/components/nav-manager-types.ts +62 -1
  120. package/src/client/components/settings-types.ts +14 -29
  121. package/src/client/components/smart-collection-conditions.ts +285 -0
  122. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  123. package/src/client/compose-bridge.ts +336 -242
  124. package/src/client/compose-launch.ts +89 -2
  125. package/src/client/compose-shortcuts.ts +9 -7
  126. package/src/client/compose-triggers.ts +50 -0
  127. package/src/client/copy-field.ts +108 -0
  128. package/src/client/custom-url-menu.ts +60 -4
  129. package/src/client/icons.ts +14 -0
  130. package/src/client/image-plan.ts +165 -0
  131. package/src/client/image-processor.ts +195 -117
  132. package/src/client/image-worker-client.ts +45 -0
  133. package/src/client/image-worker.ts +135 -0
  134. package/src/client/lazy-entries.ts +95 -0
  135. package/src/client/lazy-slugify.ts +2 -2
  136. package/src/client/media-scroll-hint.ts +43 -2
  137. package/src/client/mediabunny.ts +29 -0
  138. package/src/client/post-picker.ts +41 -0
  139. package/src/client/post-refresh.ts +135 -0
  140. package/src/client/runtime-paths.ts +74 -1
  141. package/src/client/settings-bridge.ts +4 -10
  142. package/src/client/site-header-nav.js +18 -6
  143. package/src/client/smart-collection-dialog-host.ts +116 -0
  144. package/src/client/smart-collection-page-actions.ts +208 -0
  145. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  146. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  147. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  148. package/src/client/tiptap/__tests__/link-input-rules.test.ts +121 -0
  149. package/src/client/tiptap/bubble-menu.ts +47 -16
  150. package/src/client/tiptap/floating-position.ts +216 -2
  151. package/src/client/tiptap/inline-image-upload.ts +7 -3
  152. package/src/client/tiptap/link-input-rules.ts +4 -4
  153. package/src/client/tiptap/link-toolbar.ts +22 -9
  154. package/src/client/toast.ts +143 -38
  155. package/src/client/types/vite-worker.d.ts +12 -0
  156. package/src/client/upload-with-metadata.ts +3 -13
  157. package/src/client/video-processor.ts +351 -107
  158. package/src/client-auth.ts +14 -31
  159. package/src/client-compose.ts +19 -0
  160. package/src/client-manage.ts +13 -0
  161. package/src/client-settings.ts +14 -0
  162. package/src/client-site.ts +10 -3
  163. package/src/client.ts +4 -0
  164. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  165. package/src/db/__tests__/backfill-thread-activity.test.ts +234 -0
  166. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  167. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  168. package/src/db/__tests__/thread-activity.test.ts +113 -0
  169. package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
  170. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  171. package/src/db/migrations/0031_many_ego.sql +3 -0
  172. package/src/db/migrations/0032_married_tomas.sql +3 -0
  173. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  174. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  175. package/src/db/migrations/meta/0031_snapshot.json +2562 -0
  176. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  177. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  178. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  179. package/src/db/migrations/meta/_journal.json +28 -0
  180. package/src/db/migrations/pg/0029_rare_hairball.sql +3 -0
  181. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  182. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  183. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  184. package/src/db/migrations/pg/meta/0029_snapshot.json +3289 -0
  185. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  186. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  187. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  188. package/src/db/migrations/pg/meta/_journal.json +28 -0
  189. package/src/db/pg/schema.ts +213 -27
  190. package/src/db/post-visibility.ts +117 -0
  191. package/src/db/schema.ts +242 -27
  192. package/src/db/thread-activity.ts +101 -0
  193. package/src/i18n/__tests__/detect.test.ts +26 -81
  194. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  195. package/src/i18n/context.tsx +12 -2
  196. package/src/i18n/detect.ts +7 -66
  197. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  198. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  199. package/src/i18n/locales/public/en.po +568 -168
  200. package/src/i18n/locales/public/en.ts +1 -1
  201. package/src/i18n/locales/public/zh-Hans.po +562 -162
  202. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  203. package/src/i18n/locales/public/zh-Hant.po +562 -162
  204. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  205. package/src/i18n/locales/settings/en.po +551 -69
  206. package/src/i18n/locales/settings/en.ts +1 -1
  207. package/src/i18n/locales/settings/zh-Hans.po +597 -115
  208. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  209. package/src/i18n/locales/settings/zh-Hant.po +610 -128
  210. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  211. package/src/i18n/locales.ts +94 -0
  212. package/src/i18n/middleware.ts +12 -2
  213. package/src/i18n/supported-locales.ts +80 -0
  214. package/src/lib/__tests__/api-settings.test.ts +11 -2
  215. package/src/lib/__tests__/asset-path.test.ts +92 -0
  216. package/src/lib/__tests__/deferred.test.ts +90 -0
  217. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  218. package/src/lib/__tests__/discover.test.ts +410 -0
  219. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  220. package/src/lib/__tests__/feed.test.ts +1691 -12
  221. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  222. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  223. package/src/lib/__tests__/http-cache.test.ts +162 -0
  224. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  225. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  226. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  227. package/src/lib/__tests__/markdown.test.ts +16 -0
  228. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  229. package/src/lib/__tests__/navigation.test.ts +135 -1
  230. package/src/lib/__tests__/resolve-config.test.ts +64 -3
  231. package/src/lib/__tests__/schemas.test.ts +42 -4
  232. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  233. package/src/lib/__tests__/slug.test.ts +88 -0
  234. package/src/lib/__tests__/slugify.test.ts +85 -0
  235. package/src/lib/__tests__/storage.test.ts +32 -0
  236. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  237. package/src/lib/__tests__/translit.test.ts +87 -0
  238. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  239. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  240. package/src/lib/__tests__/url.test.ts +93 -34
  241. package/src/lib/__tests__/view-language.test.ts +413 -0
  242. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  243. package/src/lib/address.ts +57 -0
  244. package/src/lib/api-posts.ts +12 -0
  245. package/src/lib/asset-path.ts +98 -1
  246. package/src/lib/build-env.ts +16 -0
  247. package/src/lib/collection-groups.ts +5 -1
  248. package/src/lib/collection-paths.ts +3 -10
  249. package/src/lib/constants.ts +28 -3
  250. package/src/lib/copy-field.ts +36 -0
  251. package/src/lib/crypto.ts +40 -0
  252. package/src/lib/csp-builder.ts +2 -1
  253. package/src/lib/deferred.ts +79 -0
  254. package/src/lib/discover-ping.ts +141 -0
  255. package/src/lib/discover.ts +437 -0
  256. package/src/lib/env.ts +134 -0
  257. package/src/lib/errors.ts +33 -0
  258. package/src/lib/feed-policy.ts +236 -2
  259. package/src/lib/feed.ts +1021 -66
  260. package/src/lib/filter-dimensions.ts +1349 -0
  261. package/src/lib/github-sync-site-config.ts +3 -0
  262. package/src/lib/hosted-signin.ts +1 -1
  263. package/src/lib/http-cache.ts +148 -0
  264. package/src/lib/hugo-markdown.ts +21 -0
  265. package/src/lib/icons.ts +9 -2
  266. package/src/lib/ids.ts +3 -0
  267. package/src/lib/jant-docs.ts +42 -0
  268. package/src/lib/lang-detect.ts +308 -0
  269. package/src/lib/link-preview.ts +25 -0
  270. package/src/lib/markdown-manager.ts +6 -1
  271. package/src/lib/media-helpers.ts +13 -1
  272. package/src/lib/navigation.ts +78 -11
  273. package/src/lib/pagination.ts +28 -24
  274. package/src/lib/per-language-surfaces.ts +67 -0
  275. package/src/lib/post-display.ts +42 -9
  276. package/src/lib/post-meta.ts +19 -0
  277. package/src/lib/render.tsx +55 -7
  278. package/src/lib/resolve-config.ts +53 -5
  279. package/src/lib/schemas.ts +201 -10
  280. package/src/lib/site-header-fragment.tsx +2 -1
  281. package/src/lib/site-unavailable-page.ts +47 -0
  282. package/src/lib/slug.ts +51 -7
  283. package/src/lib/slugify.ts +113 -0
  284. package/src/lib/startup-config.ts +4 -6
  285. package/src/lib/storage.ts +7 -0
  286. package/src/lib/summary.ts +50 -0
  287. package/src/lib/thread-fold.ts +161 -0
  288. package/src/lib/time.ts +32 -0
  289. package/src/lib/timeline.ts +81 -10
  290. package/src/lib/tiptap-render.ts +5 -4
  291. package/src/lib/translit.ts +288 -0
  292. package/src/lib/upload.ts +16 -13
  293. package/src/lib/url.ts +188 -24
  294. package/src/lib/version.ts +10 -8
  295. package/src/lib/view-language.ts +436 -0
  296. package/src/lib/view.ts +79 -23
  297. package/src/lib/viewer-context.ts +62 -0
  298. package/src/middleware/__tests__/auth.test.ts +70 -2
  299. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  300. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  301. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  302. package/src/middleware/__tests__/session.test.ts +181 -33
  303. package/src/middleware/auth.ts +50 -31
  304. package/src/middleware/cache-control.ts +75 -10
  305. package/src/middleware/config.ts +6 -6
  306. package/src/middleware/error-handler.ts +12 -0
  307. package/src/middleware/onboarding.ts +32 -10
  308. package/src/middleware/secure-headers.ts +1 -1
  309. package/src/middleware/session.ts +96 -8
  310. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  311. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  312. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  313. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  314. package/src/node/__tests__/runtime.test.ts +37 -0
  315. package/src/node/index.ts +1 -0
  316. package/src/preset.css +76 -22
  317. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  318. package/src/routes/api/__tests__/collections.test.ts +114 -0
  319. package/src/routes/api/__tests__/discover.test.ts +222 -0
  320. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  321. package/src/routes/api/__tests__/palette.test.ts +44 -0
  322. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  323. package/src/routes/api/__tests__/settings.test.ts +24 -3
  324. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  325. package/src/routes/api/collections.ts +49 -6
  326. package/src/routes/api/custom-urls.ts +0 -1
  327. package/src/routes/api/discover.ts +95 -0
  328. package/src/routes/api/export.ts +3 -0
  329. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  330. package/src/routes/api/nav-items.ts +55 -8
  331. package/src/routes/api/posts.ts +172 -9
  332. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  333. package/src/routes/api/public/__tests__/posts.test.ts +38 -0
  334. package/src/routes/api/public/archive.ts +77 -81
  335. package/src/routes/api/public/posts.ts +30 -6
  336. package/src/routes/api/smart-collections.ts +132 -0
  337. package/src/routes/api/telegram.ts +2 -40
  338. package/src/routes/api/uploads.ts +1 -2
  339. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  340. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  341. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  342. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  343. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  344. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  345. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  346. package/src/routes/auth/setup.tsx +934 -186
  347. package/src/routes/auth/signin.tsx +10 -8
  348. package/src/routes/compose.tsx +11 -2
  349. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  350. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  351. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  352. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  353. package/src/routes/dash/custom-urls.tsx +1 -37
  354. package/src/routes/dash/settings.tsx +502 -41
  355. package/src/routes/discover-announce.ts +96 -0
  356. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  357. package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
  358. package/src/routes/feed/feed.ts +69 -40
  359. package/src/routes/feed/sitemap.ts +95 -11
  360. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  361. package/src/routes/pages/__tests__/archive-params.test.ts +657 -1
  362. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  363. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  364. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  365. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  366. package/src/routes/pages/__tests__/featured.test.ts +6 -2
  367. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  368. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  369. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  370. package/src/routes/pages/__tests__/preview.test.ts +3 -1
  371. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  372. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  373. package/src/routes/pages/archive.tsx +507 -357
  374. package/src/routes/pages/collection.tsx +94 -96
  375. package/src/routes/pages/collections.tsx +38 -50
  376. package/src/routes/pages/featured.tsx +32 -13
  377. package/src/routes/pages/home.tsx +25 -6
  378. package/src/routes/pages/language.tsx +113 -0
  379. package/src/routes/pages/latest.tsx +26 -16
  380. package/src/routes/pages/new.tsx +3 -0
  381. package/src/routes/pages/page.tsx +183 -19
  382. package/src/routes/pages/partials.tsx +41 -0
  383. package/src/routes/pages/search.tsx +18 -3
  384. package/src/routes/pages/smart-collection.tsx +340 -0
  385. package/src/routes/pages/subscribe.tsx +182 -0
  386. package/src/runtime/__tests__/index.test.ts +19 -0
  387. package/src/runtime/__tests__/node.test.ts +129 -4
  388. package/src/runtime/cloudflare.ts +21 -2
  389. package/src/runtime/index.ts +27 -1
  390. package/src/runtime/node.ts +18 -1
  391. package/src/runtime/site.ts +95 -24
  392. package/src/services/__tests__/auth.test.ts +116 -7
  393. package/src/services/__tests__/collection.test.ts +139 -4
  394. package/src/services/__tests__/language.test.ts +495 -0
  395. package/src/services/__tests__/navigation.test.ts +213 -10
  396. package/src/services/__tests__/path.test.ts +180 -0
  397. package/src/services/__tests__/post-language.test.ts +830 -0
  398. package/src/services/__tests__/post-timeline.test.ts +142 -11
  399. package/src/services/__tests__/post.test.ts +323 -9
  400. package/src/services/__tests__/settings.test.ts +228 -17
  401. package/src/services/__tests__/site-admin.test.ts +5 -5
  402. package/src/services/__tests__/smart-collection.test.ts +651 -0
  403. package/src/services/about-page.ts +22 -1
  404. package/src/services/auth.ts +51 -49
  405. package/src/services/bootstrap.ts +188 -38
  406. package/src/services/collection-directory-position.ts +76 -0
  407. package/src/services/collection.ts +330 -47
  408. package/src/services/custom-url.ts +27 -22
  409. package/src/services/export-theme/assets/client-site.css +1 -1
  410. package/src/services/export-theme/assets/client-site.js +123 -123
  411. package/src/services/export-theme/layouts/_default/rss.xml +77 -1
  412. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  413. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  414. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  415. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  416. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  417. package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
  418. package/src/services/export-theme/styles/main.css +13 -1
  419. package/src/services/export.ts +281 -24
  420. package/src/services/index.ts +34 -3
  421. package/src/services/language.ts +412 -0
  422. package/src/services/mcp.ts +3 -1
  423. package/src/services/media.ts +14 -6
  424. package/src/services/navigation.ts +429 -67
  425. package/src/services/path.ts +225 -7
  426. package/src/services/post.ts +1318 -252
  427. package/src/services/search.ts +48 -2
  428. package/src/services/settings.ts +238 -43
  429. package/src/services/site-admin.ts +24 -7
  430. package/src/services/site.ts +77 -1
  431. package/src/services/smart-collection.ts +636 -0
  432. package/src/services/telegram.ts +20 -22
  433. package/src/services/upload-session.ts +1 -3
  434. package/src/style-author.css +27 -0
  435. package/src/styles/components-author.css +1622 -0
  436. package/src/styles/components.css +24 -1585
  437. package/src/styles/site-media.css +20 -6
  438. package/src/styles/tokens.css +144 -13
  439. package/src/styles/ui-author.css +7602 -0
  440. package/src/styles/ui.css +1224 -7276
  441. package/src/types/app-context.ts +7 -0
  442. package/src/types/bindings.ts +3 -0
  443. package/src/types/config.ts +94 -15
  444. package/src/types/constants.ts +188 -16
  445. package/src/types/entities.ts +92 -1
  446. package/src/types/operations.ts +64 -2
  447. package/src/types/props.ts +168 -30
  448. package/src/types/views.ts +61 -1
  449. package/src/ui/__tests__/font-themes.test.ts +52 -12
  450. package/src/ui/compose/ComposeDialog.tsx +215 -21
  451. package/src/ui/compose/ComposePrompt.tsx +1 -5
  452. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  453. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  454. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  455. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  456. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  457. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  458. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  459. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  460. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  461. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  462. package/src/ui/dash/settings/GeneralContent.tsx +275 -113
  463. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  464. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  465. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  466. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  467. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  468. package/src/ui/dash/settings/discover-copy.ts +116 -0
  469. package/src/ui/feed/LinkCard.tsx +55 -45
  470. package/src/ui/feed/LinkPreview.tsx +2 -7
  471. package/src/ui/feed/NoteCard.tsx +9 -4
  472. package/src/ui/feed/PostStatusBadges.tsx +103 -17
  473. package/src/ui/feed/QuoteCard.tsx +55 -30
  474. package/src/ui/feed/ThreadPreview.tsx +6 -2
  475. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  476. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  477. package/src/ui/feed/thread-preview-state.ts +20 -6
  478. package/src/ui/font-themes.ts +39 -29
  479. package/src/ui/layouts/BaseLayout.tsx +155 -10
  480. package/src/ui/layouts/SiteLayout.tsx +192 -34
  481. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  482. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  483. package/src/ui/pages/ArchivePage.tsx +401 -301
  484. package/src/ui/pages/CollectionPage.tsx +86 -76
  485. package/src/ui/pages/CollectionsPage.tsx +5 -0
  486. package/src/ui/pages/ComposePage.tsx +8 -0
  487. package/src/ui/pages/PostPage.tsx +47 -0
  488. package/src/ui/pages/SearchPage.tsx +3 -3
  489. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  490. package/src/ui/pages/SubscribePage.tsx +236 -0
  491. package/src/ui/pages/__tests__/ArchivePage.test.tsx +122 -3
  492. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  493. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  494. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  495. package/src/ui/shared/CollectionsManager.tsx +70 -17
  496. package/src/ui/shared/CopyField.tsx +127 -0
  497. package/src/ui/shared/DraftPreviewBar.tsx +25 -9
  498. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  499. package/src/ui/shared/MediaGallery.tsx +125 -46
  500. package/src/ui/shared/Pagination.tsx +20 -5
  501. package/src/ui/shared/PostFooter.tsx +55 -23
  502. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  503. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  504. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  505. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  506. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  507. package/src/ui/shared/archive-labels.ts +98 -0
  508. package/src/ui/shared/collection-management-labels.ts +94 -16
  509. package/src/ui/shared/custom-icons.ts +5 -0
  510. package/src/ui/shared/navigation-labels.ts +45 -71
  511. package/src/ui/shared/post-article-attributes.ts +8 -0
  512. package/src/ui/shared/smart-collection-labels.ts +343 -0
  513. package/dist/app-BxCOR3Uc.js +0 -6
  514. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  515. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  516. package/dist/client/_assets/chunks/url-pLre2DM_.js +0 -1
  517. package/dist/client/_assets/client-B_eGKN5p.css +0 -2
  518. package/dist/client/_assets/client-DxY5BFe8.js +0 -296
  519. package/dist/client/_assets/client-auth-CakPESv9.js +0 -5533
  520. package/dist/env-OHRKGcMj.js +0 -281
  521. package/dist/github-api-BgSiE71w.js +0 -176
  522. package/dist/github-app-BbklkFmU.js +0 -275
  523. package/dist/github-sync-Cq1pzzOI.js +0 -431
  524. package/dist/github-sync-ppWXN3jb.js +0 -4
  525. package/dist/url-BMYO-Zlt.js +0 -395
  526. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  527. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  528. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  529. package/src/client/collection-created-notice.ts +0 -66
  530. package/src/client/collection-form-bridge.ts +0 -191
  531. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  532. package/src/client/components/jant-post-form.ts +0 -422
  533. package/src/client/components/post-form-template.ts +0 -352
  534. package/src/client/components/post-form-types.ts +0 -96
  535. package/src/client/media-metadata.ts +0 -247
  536. package/src/client/post-form-bridge.ts +0 -193
  537. package/src/ui/pages/CollectionEditorPage.tsx +0 -175
@@ -15,25 +15,6 @@ vi.mock("../../lazy-slugify.js", () => ({
15
15
  preloadSlug: () => {},
16
16
  }));
17
17
 
18
- // Mock the TipTap editor factory — happy-dom doesn't support contenteditable
19
- let lastEditorOnUpdate: ((markdown: string) => void) | undefined;
20
- vi.mock("../../tiptap/create-editor.js", () => ({
21
- createSettingsEditor: (opts: {
22
- element: HTMLElement;
23
- content?: string;
24
- onUpdate?: (markdown: string) => void;
25
- }) => {
26
- lastEditorOnUpdate = opts.onUpdate;
27
- opts.element.innerHTML =
28
- '<div class="ProseMirror" contenteditable="true"></div>';
29
- return {
30
- getJSON: () => ({ type: "doc", content: [] }),
31
- destroy: () => {},
32
- };
33
- },
34
- jsonToMarkdown: () => "",
35
- }));
36
-
37
18
  import type {
38
19
  CollectionFormInitial,
39
20
  CollectionFormLabels,
@@ -59,22 +40,10 @@ const labels: CollectionFormLabels = {
59
40
  quickHint: "More options are available after you create it.",
60
41
  quickSubmitLabel: "Done",
61
42
  createdLabel: "Collection created.",
62
- descriptionLabel: "Description",
63
- descriptionPlaceholder: "Placeholder Description",
64
- sortOrderLabel: "Sort Order",
65
- sortNewest: "Newest first",
66
- sortOldest: "Oldest first",
67
- sortRatingDesc: "Highest rated",
68
- submitLabel: "Create Collection",
69
43
  cancelLabel: "Cancel",
70
44
  };
71
45
 
72
- const initial: CollectionFormInitial = {
73
- title: "",
74
- slug: "",
75
- description: "",
76
- sortOrder: "newest",
77
- };
46
+ const initial: CollectionFormInitial = { title: "", slug: "" };
78
47
 
79
48
  async function createElement(
80
49
  overrides: Partial<JantCollectionForm> = {},
@@ -85,14 +54,9 @@ async function createElement(
85
54
  el.labels = labels;
86
55
  el.initial = initial;
87
56
  el.action = "/api/collections";
88
- el.cancelHref = "/api/collections";
89
- el.isEdit = false;
90
57
  Object.assign(el, overrides);
91
58
  document.body.appendChild(el);
92
59
  await el.updateComplete;
93
- // Wait for the editor init that happens in updateComplete.then()
94
- await new Promise((resolve) => setTimeout(resolve, 0));
95
- await el.updateComplete;
96
60
  return el;
97
61
  }
98
62
 
@@ -101,189 +65,140 @@ async function flushSlugify(el: JantCollectionForm) {
101
65
  await el.updateComplete;
102
66
  }
103
67
 
68
+ function openSlugEditor(el: JantCollectionForm) {
69
+ Array.from(el.querySelectorAll<HTMLButtonElement>("button"))
70
+ .find((button) => button.textContent?.includes("Edit link"))
71
+ ?.click();
72
+ return el.updateComplete;
73
+ }
74
+
104
75
  describe("JantCollectionForm", () => {
105
76
  beforeEach(() => {
106
77
  document.body.innerHTML = "";
107
- lastEditorOnUpdate = undefined;
108
78
  });
109
79
 
110
- it("renders the core form fields", async () => {
80
+ it("asks for a title and nothing else", async () => {
111
81
  const el = await createElement();
112
- const select = el.querySelector("select") as HTMLSelectElement | null;
113
82
  const titleInput = el.querySelector<HTMLInputElement>(
114
83
  "[data-collection-title-input]",
115
84
  );
116
- const slugInput = el.querySelector<HTMLInputElement>(
117
- "[data-collection-slug-input]",
118
- );
119
- const editorContainer = el.querySelector<HTMLElement>(
120
- "[data-collection-desc-editor]",
121
- );
122
85
 
123
86
  expect(titleInput).not.toBeNull();
124
- expect(slugInput).not.toBeNull();
125
- expect(editorContainer).not.toBeNull();
126
- if (!select || !titleInput || !slugInput || !editorContainer) {
127
- throw new Error("Expected core form fields");
128
- }
129
-
130
- expect(titleInput.maxLength).toBe(MAX_COLLECTION_TITLE_LENGTH);
131
- expect(slugInput.maxLength).toBe(MAX_COLLECTION_SLUG_LENGTH);
132
- expect(editorContainer.classList.contains("settings-tiptap-editor")).toBe(
133
- true,
134
- );
135
- expect(Array.from(select.options).map((option) => option.value)).toEqual([
136
- "newest",
137
- "oldest",
138
- "rating_desc",
139
- ]);
140
- expect(
141
- el.querySelector<HTMLButtonElement>("button[type=submit]")?.textContent,
142
- ).toContain("Create Collection");
87
+ expect(titleInput?.maxLength).toBe(MAX_COLLECTION_TITLE_LENGTH);
88
+ // The description and the ordering belong to the collection dialog; a
89
+ // quick create only needs what a collection cannot exist without.
90
+ expect(el.querySelector("select")).toBeNull();
91
+ expect(el.querySelector("[data-collection-slug-input]")).toBeNull();
143
92
  });
144
93
 
145
- it("auto-generates a slug when creating", async () => {
94
+ it("auto-generates a link from the title", async () => {
146
95
  const el = await createElement();
147
96
  const titleInput = el.querySelector<HTMLInputElement>(
148
97
  "[data-collection-title-input]",
149
98
  );
150
- const slugInput = el.querySelector<HTMLInputElement>(
151
- "[data-collection-slug-input]",
152
- );
153
-
154
- if (!titleInput || !slugInput) {
155
- throw new Error("Expected title and slug inputs");
156
- }
99
+ if (!titleInput) throw new Error("Expected title input");
157
100
 
158
101
  titleInput.value = "My Great Collection!";
159
102
  titleInput.dispatchEvent(new Event("input", { bubbles: true }));
160
103
  await flushSlugify(el);
161
104
 
162
- expect(slugInput.value).toBe("my-great-collection");
105
+ expect(
106
+ el.querySelector<HTMLElement>(".collection-quick-link-preview")
107
+ ?.textContent,
108
+ ).toContain("/my-great-collection");
163
109
  });
164
110
 
165
- it("truncates auto-generated slugs to the configured maximum length", async () => {
111
+ it("truncates auto-generated links to the configured maximum length", async () => {
166
112
  const el = await createElement();
167
113
  const titleInput = el.querySelector<HTMLInputElement>(
168
114
  "[data-collection-title-input]",
169
115
  );
170
- const slugInput = el.querySelector<HTMLInputElement>(
171
- "[data-collection-slug-input]",
172
- );
173
-
174
- if (!titleInput || !slugInput) {
175
- throw new Error("Expected title and slug inputs");
176
- }
116
+ if (!titleInput) throw new Error("Expected title input");
177
117
 
178
118
  titleInput.value = "alpha ".repeat(30).trim();
179
119
  titleInput.dispatchEvent(new Event("input", { bubbles: true }));
180
120
  await flushSlugify(el);
121
+ await openSlugEditor(el);
181
122
 
182
- expect(slugInput.value.length).toBeLessThanOrEqual(
123
+ const slugInput = el.querySelector<HTMLInputElement>(
124
+ "[data-collection-slug-input]",
125
+ );
126
+ expect(slugInput?.value.length).toBeLessThanOrEqual(
183
127
  MAX_COLLECTION_SLUG_LENGTH,
184
128
  );
185
- expect(slugInput.value.endsWith("-")).toBe(false);
129
+ expect(slugInput?.value.endsWith("-")).toBe(false);
186
130
  });
187
131
 
188
- it("enforces the configured title maximum length in the rendered form", async () => {
132
+ it("stops following the title once the link is edited by hand", async () => {
189
133
  const el = await createElement();
190
134
  const titleInput = el.querySelector<HTMLInputElement>(
191
135
  "[data-collection-title-input]",
192
136
  );
137
+ if (!titleInput) throw new Error("Expected title input");
193
138
 
194
- if (!titleInput) {
195
- throw new Error("Expected title input");
196
- }
197
-
198
- expect(titleInput.maxLength).toBe(MAX_COLLECTION_TITLE_LENGTH);
199
- });
200
-
201
- it("does not overwrite an existing slug while editing", async () => {
202
- const el = await createElement({
203
- isEdit: true,
204
- initial: {
205
- ...initial,
206
- title: "Existing",
207
- slug: "existing-slug",
208
- },
209
- });
210
- await el.updateComplete;
139
+ titleInput.value = "Books";
140
+ titleInput.dispatchEvent(new Event("input", { bubbles: true }));
141
+ await flushSlugify(el);
142
+ await openSlugEditor(el);
211
143
 
212
- const titleInput = el.querySelector<HTMLInputElement>(
213
- "[data-collection-title-input]",
214
- );
215
144
  const slugInput = el.querySelector<HTMLInputElement>(
216
145
  "[data-collection-slug-input]",
217
146
  );
147
+ if (!slugInput) throw new Error("Expected slug input");
148
+ slugInput.value = "reading";
149
+ slugInput.dispatchEvent(new Event("input", { bubbles: true }));
150
+ await el.updateComplete;
218
151
 
219
- if (!titleInput || !slugInput) {
220
- throw new Error("Expected title and slug inputs");
221
- }
222
-
223
- titleInput.value = "Updated Title";
152
+ titleInput.value = "Books and More";
224
153
  titleInput.dispatchEvent(new Event("input", { bubbles: true }));
225
154
  await flushSlugify(el);
226
155
 
227
- expect(slugInput.value).toBe("existing-slug");
156
+ expect(slugInput.value).toBe("reading");
228
157
  });
229
158
 
230
- it("dispatches submit detail for the full form", async () => {
159
+ it("dispatches a title and a link on submit", async () => {
231
160
  const el = await createElement();
232
161
  const titleInput = el.querySelector<HTMLInputElement>(
233
162
  "[data-collection-title-input]",
234
163
  );
235
- const slugInput = el.querySelector<HTMLInputElement>(
236
- "[data-collection-slug-input]",
237
- );
238
- const select = el.querySelector("select") as HTMLSelectElement | null;
239
-
240
- if (!titleInput || !slugInput || !select) {
241
- throw new Error("Expected full form inputs");
242
- }
164
+ if (!titleInput) throw new Error("Expected title input");
243
165
 
244
- titleInput.value = "Books";
166
+ titleInput.value = "Reading Notes";
245
167
  titleInput.dispatchEvent(new Event("input", { bubbles: true }));
246
- slugInput.value = "books";
247
- slugInput.dispatchEvent(new Event("input", { bubbles: true }));
248
- select.value = "rating_desc";
249
- select.dispatchEvent(new Event("change", { bubbles: true }));
250
-
251
- // Simulate TipTap editor updating the description via the onUpdate callback
252
- lastEditorOnUpdate?.("All about books");
168
+ await flushSlugify(el);
253
169
 
254
- let submittedData: CollectionSubmitDetail["data"] | null = null;
170
+ let detail: CollectionSubmitDetail | null = null;
255
171
  el.addEventListener("jant:collection-submit", (event) => {
256
- submittedData = (event as CustomEvent<CollectionSubmitDetail>).detail
257
- .data;
172
+ detail = (event as CustomEvent<CollectionSubmitDetail>).detail;
258
173
  });
259
174
 
260
175
  el.querySelector("form")?.dispatchEvent(
261
176
  new Event("submit", { bubbles: true, cancelable: true }),
262
177
  );
263
178
 
264
- expect(submittedData).toEqual({
265
- title: "Books",
266
- slug: "books",
267
- description: "All about books",
268
- sortOrder: "rating_desc",
179
+ expect(detail).not.toBeNull();
180
+ expect((detail as unknown as CollectionSubmitDetail).data).toEqual({
181
+ title: "Reading Notes",
182
+ slug: "reading-notes",
269
183
  });
270
184
  });
271
185
 
272
- it("shows a slug error and blocks submit when the slug is invalid", async () => {
186
+ it("opens the link editor and blocks submit when the link is invalid", async () => {
273
187
  const el = await createElement();
274
188
  const titleInput = el.querySelector<HTMLInputElement>(
275
189
  "[data-collection-title-input]",
276
190
  );
277
- const slugInput = el.querySelector<HTMLInputElement>(
278
- "[data-collection-slug-input]",
279
- );
280
-
281
- if (!titleInput || !slugInput) {
282
- throw new Error("Expected title and slug inputs");
283
- }
191
+ if (!titleInput) throw new Error("Expected title input");
284
192
 
285
193
  titleInput.value = "Books";
286
194
  titleInput.dispatchEvent(new Event("input", { bubbles: true }));
195
+ await flushSlugify(el);
196
+ await openSlugEditor(el);
197
+
198
+ const slugInput = el.querySelector<HTMLInputElement>(
199
+ "[data-collection-slug-input]",
200
+ );
201
+ if (!slugInput) throw new Error("Expected slug input");
287
202
  slugInput.value = "books/2025";
288
203
  slugInput.dispatchEvent(new Event("input", { bubbles: true }));
289
204
  await el.updateComplete;
@@ -303,38 +218,4 @@ describe("JantCollectionForm", () => {
303
218
  el.querySelector("[data-collection-slug-error]")?.textContent?.trim(),
304
219
  ).toBe(labels.slugInvalidHelp);
305
220
  });
306
-
307
- it("uses the quick variant without rendering extra fields", async () => {
308
- const el = await createElement({ variant: "quick" });
309
-
310
- expect(el.querySelector("[data-collection-desc-editor]")).toBeNull();
311
- expect(el.querySelector("select")).toBeNull();
312
- expect(el.querySelector("[data-collection-slug-input]")).toBeNull();
313
-
314
- const titleInput = el.querySelector<HTMLInputElement>(
315
- "[data-collection-title-input]",
316
- );
317
- if (!titleInput) {
318
- throw new Error("Expected title input");
319
- }
320
-
321
- titleInput.value = "Reading Notes";
322
- titleInput.dispatchEvent(new Event("input", { bubbles: true }));
323
- await flushSlugify(el);
324
-
325
- let detail: CollectionSubmitDetail | null = null;
326
- el.addEventListener("jant:collection-submit", (event) => {
327
- detail = (event as CustomEvent<CollectionSubmitDetail>).detail;
328
- });
329
-
330
- el.querySelector("form")?.dispatchEvent(
331
- new Event("submit", { bubbles: true, cancelable: true }),
332
- );
333
-
334
- expect(detail).not.toBeNull();
335
- expect((detail as unknown as CollectionSubmitDetail).data).toEqual({
336
- title: "Reading Notes",
337
- slug: "reading-notes",
338
- });
339
- });
340
221
  });
@@ -11,6 +11,7 @@ interface MockPaletteItem {
11
11
  title: string;
12
12
  path: string;
13
13
  type: "post" | "collection" | "system";
14
+ status?: "draft" | "published";
14
15
  }
15
16
 
16
17
  function mockPaletteApi(items: MockPaletteItem[] = []) {
@@ -44,6 +45,7 @@ describe("JantCommandPalette slash path mode", () => {
44
45
  beforeEach(() => {
45
46
  vi.restoreAllMocks();
46
47
  document.body.innerHTML = "";
48
+ delete document.documentElement.dataset.sitePathPrefix;
47
49
  globalThis.localStorage.clear();
48
50
  globalThis.history.replaceState({}, "", "/");
49
51
  });
@@ -67,12 +69,41 @@ describe("JantCommandPalette slash path mode", () => {
67
69
 
68
70
  expect(resultTitles(el)).toEqual(["Go to /notes", 'Search for "notes"']);
69
71
  });
72
+
73
+ it("treats a pasted URL of this site as the path it points at", async () => {
74
+ // Copied out of the address bar in another tab: the only sensible answer
75
+ // is to go there, so no search alternative is offered.
76
+ const el = await renderPalette(
77
+ `${globalThis.location.origin}/notes?page=2`,
78
+ );
79
+
80
+ expect(resultTitles(el)).toEqual(["Go to /notes?page=2"]);
81
+ });
82
+
83
+ it("drops the deployment prefix from a pasted URL", async () => {
84
+ document.documentElement.dataset.sitePathPrefix = "/blog";
85
+ const el = await renderPalette(`${globalThis.location.origin}/blog/notes`);
86
+
87
+ // navPath adds the prefix back when the jump happens; carrying it here
88
+ // would double it.
89
+ expect(resultTitles(el)).toEqual(["Go to /notes"]);
90
+ });
91
+
92
+ it("leaves somebody else's URL to full-text search", async () => {
93
+ // Looking for the post where you linked something is the likelier intent.
94
+ const el = await renderPalette("https://example.com/notes");
95
+
96
+ expect(resultTitles(el)).toEqual([
97
+ 'Search all content for "https://example.com/notes"',
98
+ ]);
99
+ });
70
100
  });
71
101
 
72
102
  describe("JantCommandPalette persistent search action", () => {
73
103
  beforeEach(() => {
74
104
  vi.restoreAllMocks();
75
105
  document.body.innerHTML = "";
106
+ delete document.documentElement.dataset.sitePathPrefix;
76
107
  globalThis.localStorage.clear();
77
108
  globalThis.history.replaceState({}, "", "/");
78
109
  });
@@ -187,4 +218,52 @@ describe("JantCommandPalette persistent search action", () => {
187
218
 
188
219
  expect(globalThis.location.pathname).toBe("/match-1");
189
220
  });
221
+
222
+ it("opens draft post matches through the authenticated preview route", async () => {
223
+ const el = await renderPalette("draft", [
224
+ {
225
+ title: "Draft post",
226
+ path: "draft-post",
227
+ type: "post",
228
+ status: "draft",
229
+ },
230
+ ]);
231
+ const dialog = el.querySelector<HTMLDialogElement>(".command-palette");
232
+ expect(dialog?.textContent).toContain("Draft · draft-post");
233
+
234
+ dialog?.dispatchEvent(
235
+ new globalThis.KeyboardEvent("keydown", {
236
+ key: "Enter",
237
+ bubbles: true,
238
+ cancelable: true,
239
+ }),
240
+ );
241
+
242
+ expect(globalThis.location.pathname).toBe("/preview/draft-post");
243
+ expect(globalThis.location.search).toBe("?edit=1");
244
+ });
245
+
246
+ it("keeps the site path prefix when opening a draft preview", async () => {
247
+ document.documentElement.dataset.sitePathPrefix = "/blog";
248
+ const el = await renderPalette("draft", [
249
+ {
250
+ title: "Draft post",
251
+ path: "draft-post",
252
+ type: "post",
253
+ status: "draft",
254
+ },
255
+ ]);
256
+ const dialog = el.querySelector<HTMLDialogElement>(".command-palette");
257
+
258
+ dialog?.dispatchEvent(
259
+ new globalThis.KeyboardEvent("keydown", {
260
+ key: "Enter",
261
+ bubbles: true,
262
+ cancelable: true,
263
+ }),
264
+ );
265
+
266
+ expect(globalThis.location.pathname).toBe("/blog/preview/draft-post");
267
+ expect(globalThis.location.search).toBe("?edit=1");
268
+ });
190
269
  });