@jant/core 0.6.16 → 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 (508) hide show
  1. package/README.md +11 -11
  2. package/bin/commands/assets/upload.js +192 -15
  3. package/bin/commands/import-site.js +60 -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-C7_4oab9.js → app-K_Aa1MMn.js} +52476 -13330
  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-Dl5KCiEs.js → github-sync-BPAvT999.js} +2254 -107
  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__/stylesheet-audience.test.ts +159 -0
  51. package/src/__tests__/stylesheet-breakpoints.test.ts +183 -0
  52. package/src/app.tsx +71 -16
  53. package/src/auth.ts +18 -1
  54. package/src/client/__tests__/collection-page-actions.test.ts +51 -2
  55. package/src/client/__tests__/compose-bridge-image-upload.test.ts +196 -0
  56. package/src/client/__tests__/compose-bridge-video-poster.test.ts +159 -0
  57. package/src/client/__tests__/compose-bridge.test.ts +272 -0
  58. package/src/client/__tests__/compose-launch.test.ts +62 -1
  59. package/src/client/__tests__/compose-shortcuts.test.ts +38 -9
  60. package/src/client/__tests__/compose-triggers.test.ts +90 -0
  61. package/src/client/__tests__/copy-field.test.ts +162 -0
  62. package/src/client/__tests__/custom-url-menu.test.ts +127 -6
  63. package/src/client/__tests__/image-processor.test.ts +324 -2
  64. package/src/client/__tests__/lazy-entries.test.ts +92 -0
  65. package/src/client/__tests__/media-scroll-hint.test.ts +155 -0
  66. package/src/client/__tests__/runtime-paths.test.ts +84 -0
  67. package/src/client/__tests__/smart-collection-page-actions.test.ts +158 -0
  68. package/src/client/__tests__/toast.test.ts +23 -0
  69. package/src/client/__tests__/video-processor-probe.test.ts +175 -0
  70. package/src/client/__tests__/video-processor.test.ts +181 -0
  71. package/src/client/audio-processor.ts +24 -15
  72. package/src/client/audio-waveform.ts +49 -0
  73. package/src/client/collection-dialog-host.ts +72 -0
  74. package/src/client/collection-navigation.ts +51 -16
  75. package/src/client/collection-page-actions.ts +26 -0
  76. package/src/client/components/__tests__/jant-collection-dialog.test.ts +329 -0
  77. package/src/client/components/__tests__/jant-collection-directory.test.ts +274 -10
  78. package/src/client/components/__tests__/jant-collection-form.test.ts +59 -178
  79. package/src/client/components/__tests__/jant-command-palette.test.ts +28 -0
  80. package/src/client/components/__tests__/jant-compose-dialog-draft-storage.test.ts +340 -0
  81. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1250 -53
  82. package/src/client/components/__tests__/jant-compose-editor.test.ts +544 -40
  83. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +0 -1
  84. package/src/client/components/__tests__/jant-locale-picker.test.ts +207 -0
  85. package/src/client/components/__tests__/jant-nav-manager.test.ts +234 -15
  86. package/src/client/components/__tests__/jant-post-menu.test.ts +413 -2
  87. package/src/client/components/__tests__/jant-post-picker.test.ts +197 -0
  88. package/src/client/components/__tests__/jant-settings-avatar.test.ts +15 -12
  89. package/src/client/components/__tests__/jant-settings-general.test.ts +541 -172
  90. package/src/client/components/__tests__/jant-settings-language.test.ts +851 -0
  91. package/src/client/components/__tests__/jant-smart-collection-dialog.test.ts +665 -0
  92. package/src/client/components/collection-dialog-types.ts +49 -0
  93. package/src/client/components/collection-manager-types.ts +32 -1
  94. package/src/client/components/collection-types.ts +1 -15
  95. package/src/client/components/compose-types.ts +75 -1
  96. package/src/client/components/jant-collection-dialog.ts +722 -0
  97. package/src/client/components/jant-collection-directory.ts +567 -182
  98. package/src/client/components/jant-collection-form.ts +53 -232
  99. package/src/client/components/jant-command-palette.ts +100 -49
  100. package/src/client/components/jant-compose-dialog.ts +1486 -707
  101. package/src/client/components/jant-compose-editor.ts +673 -336
  102. package/src/client/components/jant-compose-fullscreen.ts +51 -43
  103. package/src/client/components/jant-config-editor.ts +65 -53
  104. package/src/client/components/jant-confirm-dialog.ts +13 -11
  105. package/src/client/components/jant-locale-picker.ts +418 -0
  106. package/src/client/components/jant-media-lightbox.ts +146 -136
  107. package/src/client/components/jant-nav-manager.ts +690 -366
  108. package/src/client/components/jant-post-menu.ts +1086 -282
  109. package/src/client/components/jant-post-picker.ts +324 -0
  110. package/src/client/components/jant-repo-picker.ts +138 -106
  111. package/src/client/components/jant-settings-avatar.ts +31 -27
  112. package/src/client/components/jant-settings-general.ts +617 -543
  113. package/src/client/components/jant-settings-language.ts +1087 -0
  114. package/src/client/components/jant-smart-collection-dialog.ts +1021 -0
  115. package/src/client/components/jant-text-preview.ts +94 -86
  116. package/src/client/components/nav-manager-types.ts +62 -1
  117. package/src/client/components/settings-types.ts +14 -29
  118. package/src/client/components/smart-collection-conditions.ts +285 -0
  119. package/src/client/components/smart-collection-dialog-types.ts +68 -0
  120. package/src/client/compose-bridge.ts +214 -141
  121. package/src/client/compose-launch.ts +76 -2
  122. package/src/client/compose-shortcuts.ts +6 -8
  123. package/src/client/compose-triggers.ts +50 -0
  124. package/src/client/copy-field.ts +108 -0
  125. package/src/client/custom-url-menu.ts +60 -4
  126. package/src/client/icons.ts +14 -0
  127. package/src/client/image-plan.ts +165 -0
  128. package/src/client/image-processor.ts +195 -117
  129. package/src/client/image-worker-client.ts +45 -0
  130. package/src/client/image-worker.ts +135 -0
  131. package/src/client/lazy-entries.ts +95 -0
  132. package/src/client/lazy-slugify.ts +2 -2
  133. package/src/client/media-scroll-hint.ts +43 -2
  134. package/src/client/mediabunny.ts +29 -0
  135. package/src/client/post-picker.ts +41 -0
  136. package/src/client/runtime-paths.ts +74 -1
  137. package/src/client/settings-bridge.ts +4 -10
  138. package/src/client/site-header-nav.js +18 -6
  139. package/src/client/smart-collection-dialog-host.ts +116 -0
  140. package/src/client/smart-collection-page-actions.ts +208 -0
  141. package/src/client/tiptap/__tests__/bubble-menu.test.ts +110 -0
  142. package/src/client/tiptap/__tests__/floating-position.test.ts +136 -1
  143. package/src/client/tiptap/__tests__/inline-image-upload.test.ts +37 -0
  144. package/src/client/tiptap/bubble-menu.ts +47 -16
  145. package/src/client/tiptap/floating-position.ts +216 -2
  146. package/src/client/tiptap/inline-image-upload.ts +7 -3
  147. package/src/client/tiptap/link-toolbar.ts +22 -9
  148. package/src/client/toast.ts +13 -0
  149. package/src/client/types/vite-worker.d.ts +12 -0
  150. package/src/client/upload-with-metadata.ts +3 -13
  151. package/src/client/video-processor.ts +351 -107
  152. package/src/client-auth.ts +14 -31
  153. package/src/client-compose.ts +19 -0
  154. package/src/client-manage.ts +13 -0
  155. package/src/client-settings.ts +14 -0
  156. package/src/client-site.ts +10 -3
  157. package/src/client.ts +4 -0
  158. package/src/db/__tests__/backfill-nav-label-mirrors.test.ts +149 -0
  159. package/src/db/__tests__/schema-enum-sources.test.ts +33 -0
  160. package/src/db/__tests__/smart-collection-placement.test.ts +402 -0
  161. package/src/db/backfills/0006_clear_mirrored_page_nav_labels.sql +31 -0
  162. package/src/db/migrations/0032_married_tomas.sql +3 -0
  163. package/src/db/migrations/0033_busy_johnny_storm.sql +198 -0
  164. package/src/db/migrations/0034_cheerful_rumiko_fujikawa.sql +35 -0
  165. package/src/db/migrations/meta/0032_snapshot.json +2582 -0
  166. package/src/db/migrations/meta/0033_snapshot.json +2816 -0
  167. package/src/db/migrations/meta/0034_snapshot.json +2816 -0
  168. package/src/db/migrations/meta/_journal.json +21 -0
  169. package/src/db/migrations/pg/0030_abandoned_nighthawk.sql +3 -0
  170. package/src/db/migrations/pg/0031_strong_ultimatum.sql +128 -0
  171. package/src/db/migrations/pg/0032_furry_multiple_man.sql +6 -0
  172. package/src/db/migrations/pg/meta/0030_snapshot.json +3329 -0
  173. package/src/db/migrations/pg/meta/0031_snapshot.json +3660 -0
  174. package/src/db/migrations/pg/meta/0032_snapshot.json +3660 -0
  175. package/src/db/migrations/pg/meta/_journal.json +21 -0
  176. package/src/db/pg/schema.ts +195 -27
  177. package/src/db/post-visibility.ts +117 -0
  178. package/src/db/schema.ts +222 -27
  179. package/src/i18n/__tests__/detect.test.ts +26 -81
  180. package/src/i18n/__tests__/supported-locales.test.ts +83 -0
  181. package/src/i18n/context.tsx +12 -2
  182. package/src/i18n/detect.ts +7 -66
  183. package/src/i18n/locales/glossary.zh-Hans.yml +44 -4
  184. package/src/i18n/locales/glossary.zh-Hant.yml +44 -4
  185. package/src/i18n/locales/public/en.po +490 -158
  186. package/src/i18n/locales/public/en.ts +1 -1
  187. package/src/i18n/locales/public/zh-Hans.po +487 -155
  188. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  189. package/src/i18n/locales/public/zh-Hant.po +487 -155
  190. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  191. package/src/i18n/locales/settings/en.po +551 -69
  192. package/src/i18n/locales/settings/en.ts +1 -1
  193. package/src/i18n/locales/settings/zh-Hans.po +597 -115
  194. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  195. package/src/i18n/locales/settings/zh-Hant.po +610 -128
  196. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  197. package/src/i18n/locales.ts +94 -0
  198. package/src/i18n/middleware.ts +12 -2
  199. package/src/i18n/supported-locales.ts +80 -0
  200. package/src/lib/__tests__/api-settings.test.ts +11 -2
  201. package/src/lib/__tests__/asset-path.test.ts +92 -0
  202. package/src/lib/__tests__/deferred.test.ts +90 -0
  203. package/src/lib/__tests__/discover-ping.test.ts +89 -0
  204. package/src/lib/__tests__/discover.test.ts +410 -0
  205. package/src/lib/__tests__/feed-policy.test.ts +26 -0
  206. package/src/lib/__tests__/feed.test.ts +1526 -15
  207. package/src/lib/__tests__/filter-dimensions.test.ts +341 -0
  208. package/src/lib/__tests__/hosted-signin.test.ts +1 -1
  209. package/src/lib/__tests__/http-cache.test.ts +162 -0
  210. package/src/lib/__tests__/jant-docs-dev.test.ts +21 -0
  211. package/src/lib/__tests__/jant-docs.test.ts +23 -0
  212. package/src/lib/__tests__/lang-detect.test.ts +231 -0
  213. package/src/lib/__tests__/media-helpers.test.ts +81 -0
  214. package/src/lib/__tests__/navigation.test.ts +135 -1
  215. package/src/lib/__tests__/resolve-config.test.ts +64 -3
  216. package/src/lib/__tests__/schemas.test.ts +42 -4
  217. package/src/lib/__tests__/site-unavailable-page.test.ts +25 -0
  218. package/src/lib/__tests__/slug.test.ts +70 -0
  219. package/src/lib/__tests__/slugify.test.ts +85 -0
  220. package/src/lib/__tests__/storage.test.ts +32 -0
  221. package/src/lib/__tests__/thread-fold.test.ts +113 -0
  222. package/src/lib/__tests__/upload-sideload.test.ts +14 -0
  223. package/src/lib/__tests__/url-fetch.test.ts +1 -2
  224. package/src/lib/__tests__/url.test.ts +78 -72
  225. package/src/lib/__tests__/view-language.test.ts +413 -0
  226. package/src/lib/__tests__/worker-response-cache.test.ts +90 -0
  227. package/src/lib/address.ts +57 -0
  228. package/src/lib/api-posts.ts +12 -0
  229. package/src/lib/asset-path.ts +98 -1
  230. package/src/lib/build-env.ts +16 -0
  231. package/src/lib/collection-groups.ts +5 -1
  232. package/src/lib/collection-paths.ts +3 -10
  233. package/src/lib/constants.ts +28 -3
  234. package/src/lib/copy-field.ts +36 -0
  235. package/src/lib/crypto.ts +40 -0
  236. package/src/lib/csp-builder.ts +2 -1
  237. package/src/lib/deferred.ts +79 -0
  238. package/src/lib/discover-ping.ts +141 -0
  239. package/src/lib/discover.ts +437 -0
  240. package/src/lib/env.ts +134 -0
  241. package/src/lib/errors.ts +33 -0
  242. package/src/lib/feed-policy.ts +236 -2
  243. package/src/lib/feed.ts +926 -73
  244. package/src/lib/filter-dimensions.ts +1349 -0
  245. package/src/lib/github-sync-site-config.ts +3 -0
  246. package/src/lib/hosted-signin.ts +1 -1
  247. package/src/lib/http-cache.ts +148 -0
  248. package/src/lib/hugo-markdown.ts +16 -0
  249. package/src/lib/icons.ts +9 -2
  250. package/src/lib/ids.ts +3 -0
  251. package/src/lib/jant-docs.ts +42 -0
  252. package/src/lib/lang-detect.ts +308 -0
  253. package/src/lib/media-helpers.ts +13 -1
  254. package/src/lib/navigation.ts +78 -11
  255. package/src/lib/pagination.ts +28 -24
  256. package/src/lib/per-language-surfaces.ts +67 -0
  257. package/src/lib/post-meta.ts +19 -0
  258. package/src/lib/render.tsx +55 -7
  259. package/src/lib/resolve-config.ts +53 -5
  260. package/src/lib/schemas.ts +201 -10
  261. package/src/lib/site-header-fragment.tsx +2 -1
  262. package/src/lib/site-unavailable-page.ts +47 -0
  263. package/src/lib/slug.ts +51 -7
  264. package/src/lib/slugify.ts +113 -0
  265. package/src/lib/startup-config.ts +4 -6
  266. package/src/lib/storage.ts +7 -0
  267. package/src/lib/summary.ts +50 -0
  268. package/src/lib/thread-fold.ts +161 -0
  269. package/src/lib/time.ts +32 -0
  270. package/src/lib/timeline.ts +42 -1
  271. package/src/lib/tiptap-render.ts +1 -2
  272. package/src/lib/upload.ts +16 -13
  273. package/src/lib/url.ts +139 -86
  274. package/src/lib/version.ts +10 -8
  275. package/src/lib/view-language.ts +436 -0
  276. package/src/lib/view.ts +51 -23
  277. package/src/lib/viewer-context.ts +62 -0
  278. package/src/middleware/__tests__/auth.test.ts +70 -2
  279. package/src/middleware/__tests__/cache-control.test.ts +71 -3
  280. package/src/middleware/__tests__/onboarding.test.ts +76 -5
  281. package/src/middleware/__tests__/secure-headers.test.ts +1 -1
  282. package/src/middleware/__tests__/session.test.ts +181 -33
  283. package/src/middleware/auth.ts +50 -31
  284. package/src/middleware/cache-control.ts +75 -10
  285. package/src/middleware/config.ts +6 -6
  286. package/src/middleware/error-handler.ts +12 -0
  287. package/src/middleware/onboarding.ts +32 -10
  288. package/src/middleware/secure-headers.ts +1 -1
  289. package/src/middleware/session.ts +96 -8
  290. package/src/node/__tests__/cli-migrate.test.ts +21 -19
  291. package/src/node/__tests__/cli-reset-password.test.ts +81 -8
  292. package/src/node/__tests__/cli-runtime-target.test.ts +48 -1
  293. package/src/node/__tests__/cli-site-selection.test.ts +128 -38
  294. package/src/node/__tests__/runtime.test.ts +37 -0
  295. package/src/node/index.ts +1 -0
  296. package/src/preset.css +76 -22
  297. package/src/routes/__tests__/feed-validator.test.ts +156 -0
  298. package/src/routes/api/__tests__/collections.test.ts +114 -0
  299. package/src/routes/api/__tests__/discover.test.ts +222 -0
  300. package/src/routes/api/__tests__/nav-items.test.ts +212 -2
  301. package/src/routes/api/__tests__/post-translations.test.ts +829 -0
  302. package/src/routes/api/__tests__/settings.test.ts +24 -3
  303. package/src/routes/api/__tests__/telegram.test.ts +6 -5
  304. package/src/routes/api/collections.ts +49 -6
  305. package/src/routes/api/custom-urls.ts +0 -1
  306. package/src/routes/api/discover.ts +95 -0
  307. package/src/routes/api/export.ts +3 -0
  308. package/src/routes/api/internal/__tests__/sites.test.ts +42 -2
  309. package/src/routes/api/nav-items.ts +55 -8
  310. package/src/routes/api/posts.ts +159 -0
  311. package/src/routes/api/public/__tests__/archive.test.ts +111 -0
  312. package/src/routes/api/public/archive.ts +77 -81
  313. package/src/routes/api/public/posts.ts +23 -6
  314. package/src/routes/api/smart-collections.ts +132 -0
  315. package/src/routes/api/telegram.ts +2 -40
  316. package/src/routes/api/uploads.ts +1 -2
  317. package/src/routes/auth/__tests__/setup-auth.test.ts +137 -0
  318. package/src/routes/auth/__tests__/setup-discover.test.ts +204 -0
  319. package/src/routes/auth/__tests__/setup-flow.test.ts +270 -0
  320. package/src/routes/auth/__tests__/setup-page.test.tsx +329 -0
  321. package/src/routes/auth/__tests__/setup-steps.test.ts +310 -0
  322. package/src/routes/auth/__tests__/setup.test.ts +278 -26
  323. package/src/routes/auth/__tests__/signin.test.ts +135 -0
  324. package/src/routes/auth/setup.tsx +934 -186
  325. package/src/routes/auth/signin.tsx +10 -8
  326. package/src/routes/compose.tsx +5 -0
  327. package/src/routes/dash/__tests__/custom-urls-archive.test.ts +44 -0
  328. package/src/routes/dash/__tests__/custom-urls-page.test.ts +49 -0
  329. package/src/routes/dash/__tests__/delete-account.test.ts +106 -0
  330. package/src/routes/dash/__tests__/settings-discover.test.ts +213 -0
  331. package/src/routes/dash/custom-urls.tsx +1 -37
  332. package/src/routes/dash/settings.tsx +502 -41
  333. package/src/routes/discover-announce.ts +96 -0
  334. package/src/routes/feed/__tests__/feed.test.ts +165 -2
  335. package/src/routes/feed/feed.ts +69 -40
  336. package/src/routes/feed/sitemap.ts +95 -11
  337. package/src/routes/pages/__tests__/archive-indexing.test.ts +200 -0
  338. package/src/routes/pages/__tests__/archive-params.test.ts +370 -6
  339. package/src/routes/pages/__tests__/collection-routing.test.ts +16 -0
  340. package/src/routes/pages/__tests__/collections-feed-links.test.ts +72 -0
  341. package/src/routes/pages/__tests__/collections.test.ts +9 -3
  342. package/src/routes/pages/__tests__/custom-archive-url.test.ts +119 -0
  343. package/src/routes/pages/__tests__/feed-autodiscovery.test.ts +148 -0
  344. package/src/routes/pages/__tests__/language-routing.test.ts +679 -0
  345. package/src/routes/pages/__tests__/post-preview-partial.test.ts +96 -0
  346. package/src/routes/pages/__tests__/smart-collection.test.ts +298 -0
  347. package/src/routes/pages/__tests__/subscribe.test.ts +248 -0
  348. package/src/routes/pages/archive.tsx +471 -369
  349. package/src/routes/pages/collection.tsx +94 -96
  350. package/src/routes/pages/collections.tsx +38 -50
  351. package/src/routes/pages/featured.tsx +32 -13
  352. package/src/routes/pages/home.tsx +25 -6
  353. package/src/routes/pages/language.tsx +113 -0
  354. package/src/routes/pages/latest.tsx +26 -16
  355. package/src/routes/pages/new.tsx +3 -0
  356. package/src/routes/pages/page.tsx +178 -17
  357. package/src/routes/pages/partials.tsx +41 -0
  358. package/src/routes/pages/search.tsx +18 -3
  359. package/src/routes/pages/smart-collection.tsx +340 -0
  360. package/src/routes/pages/subscribe.tsx +182 -0
  361. package/src/runtime/__tests__/index.test.ts +19 -0
  362. package/src/runtime/__tests__/node.test.ts +129 -4
  363. package/src/runtime/cloudflare.ts +21 -2
  364. package/src/runtime/index.ts +27 -1
  365. package/src/runtime/node.ts +18 -1
  366. package/src/runtime/site.ts +95 -24
  367. package/src/services/__tests__/auth.test.ts +116 -7
  368. package/src/services/__tests__/collection.test.ts +108 -4
  369. package/src/services/__tests__/language.test.ts +495 -0
  370. package/src/services/__tests__/navigation.test.ts +213 -10
  371. package/src/services/__tests__/path.test.ts +180 -0
  372. package/src/services/__tests__/post-language.test.ts +830 -0
  373. package/src/services/__tests__/post.test.ts +46 -9
  374. package/src/services/__tests__/settings.test.ts +228 -17
  375. package/src/services/__tests__/site-admin.test.ts +5 -5
  376. package/src/services/__tests__/smart-collection.test.ts +651 -0
  377. package/src/services/about-page.ts +22 -1
  378. package/src/services/auth.ts +51 -49
  379. package/src/services/bootstrap.ts +188 -38
  380. package/src/services/collection-directory-position.ts +76 -0
  381. package/src/services/collection.ts +331 -46
  382. package/src/services/custom-url.ts +27 -22
  383. package/src/services/export-theme/assets/client-site.css +1 -1
  384. package/src/services/export-theme/assets/client-site.js +123 -123
  385. package/src/services/export-theme/layouts/_default/rss.xml +77 -1
  386. package/src/services/export-theme/layouts/partials/feed-post-content.xml +105 -2
  387. package/src/services/export-theme/layouts/partials/media-gallery.html +35 -21
  388. package/src/services/export-theme/layouts/partials/pagination.html +14 -13
  389. package/src/services/export-theme/layouts/partials/post-card.html +8 -4
  390. package/src/services/export-theme/layouts/partials/reply.html +2 -1
  391. package/src/services/export-theme/layouts/partials/thread-preview.html +40 -30
  392. package/src/services/export-theme/styles/main.css +13 -1
  393. package/src/services/export.ts +278 -19
  394. package/src/services/index.ts +34 -3
  395. package/src/services/language.ts +412 -0
  396. package/src/services/mcp.ts +3 -1
  397. package/src/services/media.ts +14 -6
  398. package/src/services/navigation.ts +429 -67
  399. package/src/services/path.ts +221 -6
  400. package/src/services/post.ts +1021 -152
  401. package/src/services/search.ts +39 -2
  402. package/src/services/settings.ts +238 -43
  403. package/src/services/site-admin.ts +24 -7
  404. package/src/services/site.ts +77 -1
  405. package/src/services/smart-collection.ts +636 -0
  406. package/src/services/telegram.ts +20 -22
  407. package/src/services/upload-session.ts +1 -3
  408. package/src/style-author.css +27 -0
  409. package/src/styles/components-author.css +1622 -0
  410. package/src/styles/components.css +24 -1571
  411. package/src/styles/site-media.css +16 -2
  412. package/src/styles/tokens.css +82 -12
  413. package/src/styles/ui-author.css +7602 -0
  414. package/src/styles/ui.css +1087 -7219
  415. package/src/types/app-context.ts +7 -0
  416. package/src/types/bindings.ts +3 -0
  417. package/src/types/config.ts +94 -15
  418. package/src/types/constants.ts +188 -16
  419. package/src/types/entities.ts +87 -1
  420. package/src/types/operations.ts +64 -2
  421. package/src/types/props.ts +157 -31
  422. package/src/types/views.ts +55 -1
  423. package/src/ui/__tests__/font-themes.test.ts +52 -12
  424. package/src/ui/compose/ComposeDialog.tsx +201 -7
  425. package/src/ui/compose/ComposePrompt.tsx +1 -5
  426. package/src/ui/compose/__tests__/ComposeDialog.test.tsx +125 -0
  427. package/src/ui/dash/appearance/AdvancedContent.tsx +2 -2
  428. package/src/ui/dash/appearance/CodeInjectionContent.tsx +12 -16
  429. package/src/ui/dash/appearance/NavigationContent.tsx +117 -16
  430. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +33 -11
  431. package/src/ui/dash/appearance/__tests__/system-nav-descriptions.test.ts +44 -0
  432. package/src/ui/dash/appearance/system-nav-descriptions.ts +75 -0
  433. package/src/ui/dash/settings/AccountMenuContent.tsx +2 -2
  434. package/src/ui/dash/settings/ApiTokensContent.tsx +2 -1
  435. package/src/ui/dash/settings/ConfigEditorContent.tsx +35 -16
  436. package/src/ui/dash/settings/GeneralContent.tsx +275 -113
  437. package/src/ui/dash/settings/LanguageContent.tsx +409 -0
  438. package/src/ui/dash/settings/SettingsRootContent.tsx +19 -1
  439. package/src/ui/dash/settings/__tests__/GeneralContent.test.tsx +324 -4
  440. package/src/ui/dash/settings/__tests__/LanguageContent.test.tsx +144 -0
  441. package/src/ui/dash/settings/__tests__/discover-copy.test.ts +66 -0
  442. package/src/ui/dash/settings/discover-copy.ts +116 -0
  443. package/src/ui/feed/LinkCard.tsx +55 -45
  444. package/src/ui/feed/NoteCard.tsx +9 -4
  445. package/src/ui/feed/PostStatusBadges.tsx +90 -43
  446. package/src/ui/feed/QuoteCard.tsx +55 -30
  447. package/src/ui/feed/ThreadPreview.tsx +6 -2
  448. package/src/ui/feed/__tests__/thread-preview.test.ts +13 -16
  449. package/src/ui/feed/__tests__/timeline-cards.test.ts +314 -49
  450. package/src/ui/feed/thread-preview-state.ts +20 -6
  451. package/src/ui/font-themes.ts +39 -29
  452. package/src/ui/layouts/BaseLayout.tsx +155 -10
  453. package/src/ui/layouts/SiteLayout.tsx +192 -34
  454. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +196 -17
  455. package/src/ui/layouts/__tests__/SiteLayout.test.tsx +109 -85
  456. package/src/ui/pages/ArchivePage.tsx +282 -289
  457. package/src/ui/pages/CollectionPage.tsx +86 -76
  458. package/src/ui/pages/CollectionsPage.tsx +5 -0
  459. package/src/ui/pages/ComposePage.tsx +8 -0
  460. package/src/ui/pages/PostPage.tsx +47 -0
  461. package/src/ui/pages/SearchPage.tsx +3 -3
  462. package/src/ui/pages/SmartCollectionPage.tsx +434 -0
  463. package/src/ui/pages/SubscribePage.tsx +236 -0
  464. package/src/ui/pages/__tests__/ArchivePage.test.tsx +65 -9
  465. package/src/ui/pages/__tests__/collections-directory-rows.test.tsx +92 -0
  466. package/src/ui/pages/__tests__/collections-view-links.test.tsx +81 -0
  467. package/src/ui/shared/CollectionDirectory.tsx +178 -3
  468. package/src/ui/shared/CollectionsManager.tsx +70 -17
  469. package/src/ui/shared/CopyField.tsx +127 -0
  470. package/src/ui/shared/EmptyPostContent.tsx +23 -0
  471. package/src/ui/shared/MediaGallery.tsx +125 -46
  472. package/src/ui/shared/Pagination.tsx +20 -5
  473. package/src/ui/shared/PostFooter.tsx +20 -11
  474. package/src/ui/shared/__tests__/archive-labels.test.ts +100 -0
  475. package/src/ui/shared/__tests__/media-gallery.test.ts +210 -2
  476. package/src/ui/shared/__tests__/navigation-labels.test.ts +16 -22
  477. package/src/ui/shared/__tests__/pagination.test.ts +38 -24
  478. package/src/ui/shared/__tests__/post-footer.test.ts +32 -0
  479. package/src/ui/shared/archive-labels.ts +98 -0
  480. package/src/ui/shared/collection-management-labels.ts +94 -16
  481. package/src/ui/shared/navigation-labels.ts +45 -71
  482. package/src/ui/shared/post-article-attributes.ts +2 -0
  483. package/src/ui/shared/smart-collection-labels.ts +343 -0
  484. package/dist/app-C75Zc8dD.js +0 -6
  485. package/dist/client/_assets/chunks/heic-to-DUUaO23q.js +0 -1
  486. package/dist/client/_assets/chunks/module-DcsAZQZ_.js +0 -716
  487. package/dist/client/_assets/chunks/url-CU9pEbk5.js +0 -1
  488. package/dist/client/_assets/client-Bq0vre8Y.js +0 -296
  489. package/dist/client/_assets/client-D3VZyG4L.css +0 -2
  490. package/dist/client/_assets/client-auth-F2rb9WfF.js +0 -5571
  491. package/dist/env-BPYViDJJ.js +0 -281
  492. package/dist/github-api-BNF35Lkx.js +0 -176
  493. package/dist/github-app-DdiD-bC4.js +0 -275
  494. package/dist/github-sync-Bqg62IvV.js +0 -4
  495. package/dist/github-sync-HdK2EgvJ.js +0 -431
  496. package/dist/url-CbLAtlZe.js +0 -900
  497. package/src/client/__tests__/collection-created-notice.test.ts +0 -36
  498. package/src/client/__tests__/collection-form-bridge.test.ts +0 -216
  499. package/src/client/__tests__/post-form-bridge.test.ts +0 -82
  500. package/src/client/collection-created-notice.ts +0 -66
  501. package/src/client/collection-form-bridge.ts +0 -191
  502. package/src/client/components/__tests__/jant-post-form.test.ts +0 -193
  503. package/src/client/components/jant-post-form.ts +0 -422
  504. package/src/client/components/post-form-template.ts +0 -352
  505. package/src/client/components/post-form-types.ts +0 -96
  506. package/src/client/media-metadata.ts +0 -247
  507. package/src/client/post-form-bridge.ts +0 -193
  508. 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._(
@@ -322,12 +347,39 @@ export const ComposeForm: FC<ComposeFormProps> = ({
322
347
  comment: "@context: Button to remove an uploaded attachment in compose",
323
348
  }),
324
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
+ ),
325
364
  uploading: i18n._(
326
365
  msg({
327
366
  message: "Uploading...",
328
367
  comment: "@context: Toast shown during background upload",
329
368
  }),
330
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
+ ),
331
383
  loadingPost: i18n._(
332
384
  msg({
333
385
  message: "Loading post...",
@@ -391,9 +443,9 @@ export const ComposeForm: FC<ComposeFormProps> = ({
391
443
  ),
392
444
  confirmCloseCancel: i18n._(
393
445
  msg({
394
- message: "Cancel",
446
+ message: "Keep editing",
395
447
  comment:
396
- "@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.",
397
449
  }),
398
450
  ),
399
451
  confirmCloseDiscard: i18n._(
@@ -510,6 +562,20 @@ export const ComposeForm: FC<ComposeFormProps> = ({
510
562
  "@context: Toast when publish fails and post is auto-saved as draft",
511
563
  }),
512
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
+ ),
513
579
  uploadFailedDraft: i18n._(
514
580
  msg({
515
581
  message: "Some uploads failed. Saved as draft.",
@@ -795,11 +861,11 @@ export const ComposeForm: FC<ComposeFormProps> = ({
795
861
  "@context: Toast shown when a local draft is restored on compose open",
796
862
  }),
797
863
  ),
798
- closeCompose: i18n._(
864
+ draftStoreFailed: i18n._(
799
865
  msg({
800
- message: "Close compose",
866
+ message: "This browser can't keep a local copy. Save as a draft.",
801
867
  comment:
802
- "@context: Post options row that leaves the composer; prompts to save a draft first if there is unsaved work",
868
+ "@context: Toast shown when the composer cannot write its local draft to browser storage",
803
869
  }),
804
870
  ),
805
871
  editing: i18n._(
@@ -897,10 +963,132 @@ export const ComposeForm: FC<ComposeFormProps> = ({
897
963
  }),
898
964
  ),
899
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
+ ),
900
1083
  collectionFormLabels: getCollectionFormLabels(i18n),
901
1084
  } satisfies ComposeLabels;
902
1085
  const labels = JSON.stringify(labelsObject).replace(/</g, "\\u003c");
903
1086
 
1087
+ const languagesJson = JSON.stringify(languages ?? []).replace(
1088
+ /</g,
1089
+ "\\u003c",
1090
+ );
1091
+
904
1092
  const collectionsJson = JSON.stringify(
905
1093
  (collections ?? []).map((c) => ({
906
1094
  id: c.id,
@@ -912,6 +1100,8 @@ export const ComposeForm: FC<ComposeFormProps> = ({
912
1100
  return (
913
1101
  <jant-compose-dialog
914
1102
  collections={collectionsJson}
1103
+ languages={languagesJson}
1104
+ {...(contextLanguage ? { "context-language": contextLanguage } : {})}
915
1105
  labels={labels}
916
1106
  upload-max-file-size={uploadMaxFileSize ?? 500}
917
1107
  {...(pageMode ? { "page-mode": "" } : {})}
@@ -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">