@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
@@ -13,12 +13,27 @@ import type { FC } from "hono/jsx";
13
13
  import { MAX_THREAD_POSTS, type Collection } from "../../types.js";
14
14
  import { useLingui } from "../../i18n/context.js";
15
15
  import { getCollectionFormLabels } from "../shared/collection-management-labels.js";
16
- import type { ComposeLabels } from "../../client/components/compose-types.js";
16
+ import type {
17
+ ComposeLabels,
18
+ ComposeLanguage,
19
+ } from "../../client/components/compose-types.js";
17
20
 
18
21
  export interface ComposeDialogProps {
19
22
  collections?: Collection[];
20
23
  uploadMaxFileSize?: number;
21
24
  slashCommandDiscovered?: boolean;
25
+ /**
26
+ * Languages the site publishes. Empty on a single-language site, and the
27
+ * composer then shows no language UI at all — an author who never turned
28
+ * multilingual content on should not meet it.
29
+ */
30
+ languages?: ComposeLanguage[];
31
+ /**
32
+ * Content language of the page the composer opens from. A post left on
33
+ * automatic publishes in this language until detection reads the writing as
34
+ * another one, which the composer says on the button beside Post.
35
+ */
36
+ contextLanguage?: string | null;
22
37
  }
23
38
 
24
39
  export interface ComposeFormProps extends ComposeDialogProps {
@@ -31,6 +46,8 @@ export const ComposeForm: FC<ComposeFormProps> = ({
31
46
  collections,
32
47
  uploadMaxFileSize,
33
48
  slashCommandDiscovered = false,
49
+ languages,
50
+ contextLanguage,
34
51
  pageMode = false,
35
52
  closeHref,
36
53
  autoRestoreDraft = false,
@@ -41,7 +58,15 @@ export const ComposeForm: FC<ComposeFormProps> = ({
41
58
  cancel: i18n._(
42
59
  msg({
43
60
  message: "Cancel",
44
- comment: "@context: Close compose dialog",
61
+ comment:
62
+ "@context: Back out of the attached-text sub-editor, paired with Done. Leaving the composer itself says Close instead — it may keep the work as a draft, so nothing is being cancelled.",
63
+ }),
64
+ ),
65
+ close: i18n._(
66
+ msg({
67
+ message: "Close",
68
+ comment:
69
+ "@context: Leaves the composer — the × on a single post, and the button in a thread. Prompts to save a draft first if there is unsaved work.",
45
70
  }),
46
71
  ),
47
72
  imageNotRehosted: i18n._(
@@ -126,8 +151,9 @@ export const ComposeForm: FC<ComposeFormProps> = ({
126
151
  ),
127
152
  titlePlaceholder: i18n._(
128
153
  msg({
129
- message: "Title",
130
- comment: "@context: Compose note title placeholder",
154
+ message: "Untitled",
155
+ comment:
156
+ "@context: Compose note title placeholder. States what the note is while the field is empty rather than asking for a title — untitled notes are a normal kind of note here, and Archive files them under this same word.",
131
157
  }),
132
158
  ),
133
159
  bodyPlaceholder: i18n._(
@@ -237,7 +263,7 @@ export const ComposeForm: FC<ComposeFormProps> = ({
237
263
  title: i18n._(
238
264
  msg({
239
265
  message: "Title",
240
- comment: "@context: Compose toolbar - title tooltip",
266
+ comment: "@context: Compose toolbar - show or hide the title field",
241
267
  }),
242
268
  ),
243
269
  fullscreen: i18n._(
@@ -321,12 +347,39 @@ export const ComposeForm: FC<ComposeFormProps> = ({
321
347
  comment: "@context: Button to remove an uploaded attachment in compose",
322
348
  }),
323
349
  ),
350
+ moveAttachmentEarlier: i18n._(
351
+ msg({
352
+ message: "Move attachment earlier",
353
+ comment:
354
+ "@context: Button that moves an attachment one place toward the start in compose",
355
+ }),
356
+ ),
357
+ moveAttachmentLater: i18n._(
358
+ msg({
359
+ message: "Move attachment later",
360
+ comment:
361
+ "@context: Button that moves an attachment one place toward the end in compose",
362
+ }),
363
+ ),
324
364
  uploading: i18n._(
325
365
  msg({
326
366
  message: "Uploading...",
327
367
  comment: "@context: Toast shown during background upload",
328
368
  }),
329
369
  ),
370
+ publishing: i18n._(
371
+ msg({
372
+ message: "Publishing...",
373
+ comment: "@context: Toast shown while a post is being published",
374
+ }),
375
+ ),
376
+ saving: i18n._(
377
+ msg({
378
+ message: "Saving...",
379
+ comment:
380
+ "@context: Toast shown while a draft or an edited post is being saved",
381
+ }),
382
+ ),
330
383
  loadingPost: i18n._(
331
384
  msg({
332
385
  message: "Loading post...",
@@ -390,9 +443,9 @@ export const ComposeForm: FC<ComposeFormProps> = ({
390
443
  ),
391
444
  confirmCloseCancel: i18n._(
392
445
  msg({
393
- message: "Cancel",
446
+ message: "Keep editing",
394
447
  comment:
395
- "@context: Confirm close action sheet - cancel and return to editor",
448
+ "@context: Confirm close action sheet - dismiss the sheet and return to the editor. Says what it does rather than 'Cancel', which the composer already spends on leaving.",
396
449
  }),
397
450
  ),
398
451
  confirmCloseDiscard: i18n._(
@@ -509,6 +562,20 @@ export const ComposeForm: FC<ComposeFormProps> = ({
509
562
  "@context: Toast when publish fails and post is auto-saved as draft",
510
563
  }),
511
564
  ),
565
+ publishFailed: i18n._(
566
+ msg({
567
+ message: "Couldn't publish. Try again.",
568
+ comment:
569
+ "@context: Toast when publish fails and the server gave no reason",
570
+ }),
571
+ ),
572
+ publishFailedOffline: i18n._(
573
+ msg({
574
+ message: "Couldn't reach the server. Try again.",
575
+ comment:
576
+ "@context: Toast when a publish request never reached the server",
577
+ }),
578
+ ),
512
579
  uploadFailedDraft: i18n._(
513
580
  msg({
514
581
  message: "Some uploads failed. Saved as draft.",
@@ -649,9 +716,9 @@ export const ComposeForm: FC<ComposeFormProps> = ({
649
716
  ),
650
717
  publishDateSummaryNow: i18n._(
651
718
  msg({
652
- message: "Publish now",
719
+ message: "Now",
653
720
  comment:
654
- "@context: Compose summary shown near the submit controls when an edited post no longer has a custom publish date",
721
+ "@context: Value shown on a post's date control when it will publish at the current time",
655
722
  }),
656
723
  ),
657
724
  publishDateSummaryAction: i18n._(
@@ -688,9 +755,9 @@ export const ComposeForm: FC<ComposeFormProps> = ({
688
755
  ),
689
756
  publishSlugSummaryAuto: i18n._(
690
757
  msg({
691
- message: "Auto link",
758
+ message: "/…",
692
759
  comment:
693
- "@context: Compose summary shown near the submit controls when a manual custom link is cleared",
760
+ "@context: Value shown on a post's permalink control when the link is generated automatically — a stand-in for the path the server will assign. Sits next to real permalinks rendered as /my-post, so keep the leading slash",
694
761
  }),
695
762
  ),
696
763
  publishSlugSummaryAction: i18n._(
@@ -794,24 +861,24 @@ export const ComposeForm: FC<ComposeFormProps> = ({
794
861
  "@context: Toast shown when a local draft is restored on compose open",
795
862
  }),
796
863
  ),
797
- newThread: i18n._(
864
+ draftStoreFailed: i18n._(
798
865
  msg({
799
- message: "New Thread",
866
+ message: "This browser can't keep a local copy. Save as a draft.",
800
867
  comment:
801
- "@context: Compose dialog header title when composing a thread",
868
+ "@context: Toast shown when the composer cannot write its local draft to browser storage",
802
869
  }),
803
870
  ),
804
- replyTitle: i18n._(
871
+ editing: i18n._(
805
872
  msg({
806
- message: "Reply",
873
+ message: "Editing",
807
874
  comment:
808
- "@context: Compose dialog header title when replying to a post",
875
+ "@context: Marker above the composer when changing a post that is already published",
809
876
  }),
810
877
  ),
811
- editTitle: i18n._(
878
+ composeDialogLabel: i18n._(
812
879
  msg({
813
- message: "Edit",
814
- comment: "@context: Compose dialog header title when editing a post",
880
+ message: "Compose",
881
+ comment: "@context: Accessible name for the compose dialog",
815
882
  }),
816
883
  ),
817
884
  slashHint: i18n._(
@@ -896,10 +963,132 @@ export const ComposeForm: FC<ComposeFormProps> = ({
896
963
  }),
897
964
  ),
898
965
  },
966
+ languageLabel: i18n._(
967
+ msg({
968
+ message: "Language",
969
+ comment: "@context: Compose field for the post's content language",
970
+ }),
971
+ ),
972
+ languageAuto: i18n._(
973
+ msg({
974
+ message: "Detect",
975
+ comment:
976
+ "@context: Compose language option that leaves detection to Jant",
977
+ }),
978
+ ),
979
+ languageAutoHint: i18n._(
980
+ msg({
981
+ message: "Read from what you write",
982
+ comment: "@context: Help text under the compose language field",
983
+ }),
984
+ ),
985
+ languageAutoDetected: i18n._(
986
+ msg({
987
+ message: "Read from what you write — looks like {language}",
988
+ comment:
989
+ "@context: Help text under the compose language field once a language has been detected",
990
+ }),
991
+ // Detected in the browser from the current text; see `translationOf`.
992
+ { language: "{language}" },
993
+ ),
994
+ languageAutoPending: i18n._(
995
+ msg({
996
+ message: "Not enough text to tell yet — publishes in {language}",
997
+ comment:
998
+ "@context: Help text under the compose language field before there is enough writing to read a language out of; {language} is the one it would publish in meanwhile, which is the language of the page the composer was opened from",
999
+ }),
1000
+ // Only known in the browser; see `translationOf`.
1001
+ { language: "{language}" },
1002
+ ),
1003
+ languageTriggerLabel: i18n._(
1004
+ msg({
1005
+ message: "Language: {language}",
1006
+ comment:
1007
+ "@context: Accessible name of the composer's language button, which sits beside Post and names the language this post would publish in",
1008
+ }),
1009
+ // Filled in the browser with whichever language that is; see
1010
+ // `translationOf`.
1011
+ { language: "{language}" },
1012
+ ),
1013
+ translationOf: i18n._(
1014
+ msg({
1015
+ message: "Translation of “{title}”",
1016
+ comment:
1017
+ "@context: Compose banner shown while writing a translation of an existing post",
1018
+ }),
1019
+ // The post being translated is only known in the browser, and Lingui
1020
+ // resolves an ICU message completely at `i18n._()` time — formatted with
1021
+ // no value, this slot would come out empty. Passing the placeholder back
1022
+ // as the value leaves it intact for the component to fill.
1023
+ { title: "{title}" },
1024
+ ),
1025
+ translationContext: i18n._(
1026
+ msg({
1027
+ message: "Translating",
1028
+ comment:
1029
+ "@context: Divider between the original post and the editor writing its translation, when the target language is not known yet",
1030
+ }),
1031
+ ),
1032
+ translationContextInLanguage: i18n._(
1033
+ msg({
1034
+ message: "Translating into {language}",
1035
+ comment:
1036
+ "@context: Divider between the original post above and the editor below, naming the language being written",
1037
+ }),
1038
+ // Filled in the browser from the chosen language; see `translationOf`.
1039
+ { language: "{language}" },
1040
+ ),
1041
+ translationContextOpen: i18n._(
1042
+ msg({
1043
+ message: "Open the original in a new tab",
1044
+ comment:
1045
+ "@context: Tooltip on the source post link above the compose editor while writing a translation",
1046
+ }),
1047
+ ),
1048
+ translationContextOriginal: i18n._(
1049
+ msg({
1050
+ message: "The original",
1051
+ comment:
1052
+ "@context: Accessible name of the scrollable panel holding the post being translated",
1053
+ }),
1054
+ ),
1055
+ translationContextHide: i18n._(
1056
+ msg({
1057
+ message: "Hide",
1058
+ comment:
1059
+ "@context: Button that folds away the original while writing its translation. Sits right after the words “Translating into …”, which is what makes it clear what gets hidden — the accessible name spells it out",
1060
+ }),
1061
+ ),
1062
+ translationContextHideLong: i18n._(
1063
+ msg({
1064
+ message: "Hide the original",
1065
+ comment:
1066
+ "@context: Accessible name of the button that folds away the post being translated",
1067
+ }),
1068
+ ),
1069
+ translationContextShow: i18n._(
1070
+ msg({
1071
+ message: "Show",
1072
+ comment:
1073
+ "@context: Button that brings back the folded-away original while writing its translation",
1074
+ }),
1075
+ ),
1076
+ translationContextShowLong: i18n._(
1077
+ msg({
1078
+ message: "Show the original",
1079
+ comment:
1080
+ "@context: Accessible name of the button that brings back the folded-away post being translated",
1081
+ }),
1082
+ ),
899
1083
  collectionFormLabels: getCollectionFormLabels(i18n),
900
1084
  } satisfies ComposeLabels;
901
1085
  const labels = JSON.stringify(labelsObject).replace(/</g, "\\u003c");
902
1086
 
1087
+ const languagesJson = JSON.stringify(languages ?? []).replace(
1088
+ /</g,
1089
+ "\\u003c",
1090
+ );
1091
+
903
1092
  const collectionsJson = JSON.stringify(
904
1093
  (collections ?? []).map((c) => ({
905
1094
  id: c.id,
@@ -911,6 +1100,8 @@ export const ComposeForm: FC<ComposeFormProps> = ({
911
1100
  return (
912
1101
  <jant-compose-dialog
913
1102
  collections={collectionsJson}
1103
+ languages={languagesJson}
1104
+ {...(contextLanguage ? { "context-language": contextLanguage } : {})}
914
1105
  labels={labels}
915
1106
  upload-max-file-size={uploadMaxFileSize ?? 500}
916
1107
  {...(pageMode ? { "page-mode": "" } : {})}
@@ -920,7 +1111,6 @@ export const ComposeForm: FC<ComposeFormProps> = ({
920
1111
  >
921
1112
  {/* SSR fallback skeleton */}
922
1113
  <div class="compose-dialog-inner">
923
- <div class="compose-dialog-header" />
924
1114
  <div class="compose-body skel-section-md" />
925
1115
  </div>
926
1116
  </jant-compose-dialog>
@@ -931,6 +1121,8 @@ export const ComposeDialog: FC<ComposeDialogProps> = ({
931
1121
  collections,
932
1122
  uploadMaxFileSize,
933
1123
  slashCommandDiscovered = false,
1124
+ languages,
1125
+ contextLanguage,
934
1126
  }) => {
935
1127
  return (
936
1128
  <dialog id="compose-dialog" class="compose-dialog">
@@ -938,6 +1130,8 @@ export const ComposeDialog: FC<ComposeDialogProps> = ({
938
1130
  collections={collections}
939
1131
  uploadMaxFileSize={uploadMaxFileSize}
940
1132
  slashCommandDiscovered={slashCommandDiscovered}
1133
+ languages={languages}
1134
+ contextLanguage={contextLanguage}
941
1135
  />
942
1136
  </dialog>
943
1137
  );
@@ -25,11 +25,7 @@ export const ComposePrompt: FC<ComposePromptProps> = ({
25
25
  composeOpenShortcutDiscovered ? "true" : "false"
26
26
  }
27
27
  >
28
- <button
29
- type="button"
30
- class="compose-prompt-trigger"
31
- data-on:click="document.getElementById('compose-dialog')?.querySelector('jant-compose-dialog')?.openNew()"
32
- >
28
+ <button type="button" class="compose-prompt-trigger" data-compose-open>
33
29
  <span class="compose-prompt-avatar">
34
30
  <svg
35
31
  xmlns="http://www.w3.org/2000/svg"
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Compose dialog labels.
3
+ *
4
+ * The composer fills two labels in the browser, from values only it knows: the
5
+ * post being translated and the detected language. Lingui resolves an ICU
6
+ * message completely at `i18n._()` time, so those slots have to be carried
7
+ * through deliberately — a label formatted with no value renders as an empty
8
+ * string, which reads as a missing title rather than as a bug.
9
+ */
10
+
11
+ import type { Context } from "hono";
12
+ import { renderToString } from "hono/jsx/dom/server";
13
+ import { describe, expect, it } from "vitest";
14
+ import { I18nProvider } from "../../../i18n/context.js";
15
+ import { createI18n } from "../../../i18n/i18n.js";
16
+ import { ComposeForm } from "../ComposeDialog.js";
17
+
18
+ function renderComposeLabels(): Record<string, string> {
19
+ const i18n = createI18n("en");
20
+ const c = {
21
+ get(key: string) {
22
+ return key === "i18n" ? i18n : undefined;
23
+ },
24
+ } as unknown as Context;
25
+ I18nProvider({ c, children: "" });
26
+
27
+ const html = renderToString(
28
+ ComposeForm({
29
+ languages: [
30
+ { tag: "zh-Hans", label: "简体中文" },
31
+ { tag: "en", label: "English" },
32
+ ],
33
+ }),
34
+ );
35
+
36
+ const match = /labels="([^"]*)"/.exec(html);
37
+ if (!match?.[1]) throw new Error("expected a labels attribute");
38
+ const decoded = match[1]
39
+ .replace(/&quot;/g, '"')
40
+ .replace(/&amp;/g, "&")
41
+ .replace(/&lt;/g, "<")
42
+ .replace(/&gt;/g, ">")
43
+ .replace(/&#39;/g, "'");
44
+ return JSON.parse(decoded) as Record<string, string>;
45
+ }
46
+
47
+ describe("ComposeForm labels", () => {
48
+ it("leaves the slots the browser fills", () => {
49
+ const labels = renderComposeLabels();
50
+
51
+ expect(labels.translationOf).toContain("{title}");
52
+ expect(labels.translationContextInLanguage).toContain("{language}");
53
+ expect(labels.languageAutoDetected).toContain("{language}");
54
+ expect(labels.languageAutoPending).toContain("{language}");
55
+ expect(labels.languageTriggerLabel).toContain("{language}");
56
+ });
57
+
58
+ it("never ships an unresolved ICU construct", () => {
59
+ for (const [key, value] of Object.entries(renderComposeLabels())) {
60
+ if (typeof value !== "string") continue;
61
+ expect(value, key).not.toContain("plural,");
62
+ expect(value, key).not.toContain("NaN");
63
+ }
64
+ });
65
+
66
+ it("passes the site's languages to the component", () => {
67
+ const i18n = createI18n("en");
68
+ const c = {
69
+ get(key: string) {
70
+ return key === "i18n" ? i18n : undefined;
71
+ },
72
+ } as unknown as Context;
73
+ I18nProvider({ c, children: "" });
74
+
75
+ const html = renderToString(
76
+ ComposeForm({
77
+ languages: [{ tag: "en", label: "English" }],
78
+ }),
79
+ );
80
+ expect(html).toContain("languages=");
81
+ expect(html).toContain("English");
82
+ });
83
+
84
+ it("hands the component the page's language as the automatic default", () => {
85
+ const i18n = createI18n("en");
86
+ const c = {
87
+ get(key: string) {
88
+ return key === "i18n" ? i18n : undefined;
89
+ },
90
+ } as unknown as Context;
91
+ I18nProvider({ c, children: "" });
92
+
93
+ const html = renderToString(
94
+ ComposeForm({
95
+ languages: [
96
+ { tag: "zh-Hans", label: "简体中文" },
97
+ { tag: "ja", label: "日本語" },
98
+ ],
99
+ contextLanguage: "ja",
100
+ }),
101
+ );
102
+ expect(html).toContain('context-language="ja"');
103
+
104
+ // Without a page language the attribute stays off, and the component
105
+ // falls back to the primary language on its own.
106
+ expect(
107
+ renderToString(
108
+ ComposeForm({ languages: [{ tag: "en", label: "English" }] }),
109
+ ),
110
+ ).not.toContain("context-language");
111
+ });
112
+
113
+ it("sends an empty list on a single-language site", () => {
114
+ const i18n = createI18n("en");
115
+ const c = {
116
+ get(key: string) {
117
+ return key === "i18n" ? i18n : undefined;
118
+ },
119
+ } as unknown as Context;
120
+ I18nProvider({ c, children: "" });
121
+
122
+ // The composer renders no language UI at all in that case.
123
+ expect(renderToString(ComposeForm({}))).toContain('languages="[]"');
124
+ });
125
+ });
@@ -4,10 +4,10 @@
4
4
 
5
5
  import { msg } from "@lingui/core/macro";
6
6
  import { useLingui } from "../../../i18n/context.js";
7
+ import { getJantDocsUrl } from "../../../lib/jant-docs.js";
7
8
  import { toPublicPath } from "../../../lib/url.js";
8
9
 
9
- const THEMING_DOCS_URL =
10
- "https://github.com/jant-me/jant/blob/main/docs/theming.md";
10
+ const THEMING_DOCS_URL = getJantDocsUrl("theming");
11
11
 
12
12
  export function AdvancedContent({
13
13
  customCSS,
@@ -33,11 +33,7 @@ export function CodeInjectionContent({
33
33
  data-indicator="_codeLoading"
34
34
  class="max-w-form"
35
35
  >
36
- <div
37
- role="note"
38
- class="alert alert-destructive mb-6"
39
- style="display:flex;gap:.75rem;align-items:flex-start"
40
- >
36
+ <div role="note" class="alert-destructive mb-6">
41
37
  <svg
42
38
  xmlns="http://www.w3.org/2000/svg"
43
39
  width="20"
@@ -54,16 +50,16 @@ export function CodeInjectionContent({
54
50
  <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
55
51
  <path d="M12 17h.01" />
56
52
  </svg>
57
- <div>
58
- <p class="font-medium">
59
- {i18n._(
60
- msg({
61
- message: "This code runs on every page of your site.",
62
- comment: "@context: Code Injection security warning headline",
63
- }),
64
- )}
65
- </p>
66
- <p class="text-sm" style="margin-top:.25rem">
53
+ <strong>
54
+ {i18n._(
55
+ msg({
56
+ message: "This code runs on every page of your site.",
57
+ comment: "@context: Code Injection security warning headline",
58
+ }),
59
+ )}
60
+ </strong>
61
+ <section>
62
+ <p>
67
63
  {i18n._(
68
64
  msg({
69
65
  message:
@@ -72,7 +68,7 @@ export function CodeInjectionContent({
72
68
  }),
73
69
  )}
74
70
  </p>
75
- </div>
71
+ </section>
76
72
  </div>
77
73
 
78
74
  <fieldset class="mb-6">