@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
@@ -0,0 +1,98 @@
1
+ import { msg } from "@lingui/core/macro";
2
+ import type { I18n } from "../../i18n/i18n.js";
3
+ import type {
4
+ DimensionContext,
5
+ PostFilterSelection,
6
+ } from "../../lib/filter-dimensions.js";
7
+ import {
8
+ describeFilterSelection,
9
+ hasFilterSelection,
10
+ } from "../../lib/filter-dimensions.js";
11
+
12
+ type Translator = Pick<I18n, "_">;
13
+
14
+ export {
15
+ getFormatLabelPlural,
16
+ getMediaKindLabel,
17
+ getVisibilityLabel,
18
+ } from "../../lib/filter-dimensions.js";
19
+
20
+ /**
21
+ * The name of the unfiltered archive, shared by the page heading and the
22
+ * document title so both resolve to one Lingui hash and can never drift into
23
+ * two different words for the same surface.
24
+ */
25
+ export const ARCHIVE_ALL_POSTS_LABEL = msg({
26
+ message: "All posts",
27
+ comment:
28
+ "@context: Archive page heading and document title when no filter is active. Names the surface, not the current selection — the nav calls the same destination 'All'.",
29
+ });
30
+
31
+ /** Whether any filter dimension is active. `sort` and `layout` are not filters. */
32
+ export function hasActiveArchiveFilter(
33
+ selection: PostFilterSelection,
34
+ ): boolean {
35
+ return hasFilterSelection(selection);
36
+ }
37
+
38
+ /**
39
+ * The active filters reduced to the words a reader sees, most identifying
40
+ * first.
41
+ *
42
+ * The words themselves come from the dimension registry, which owns the
43
+ * vocabulary. What lives here is the archive's *composition* of them: the
44
+ * ordering, and the one place a dimension absorbs another.
45
+ *
46
+ * This is the vocabulary only — every surface composes it differently, because
47
+ * their constraints are opposite. A feed name lives for years in someone's
48
+ * reader with no UI to inspect it, so it takes the whole list; a browser tab
49
+ * truncates from the right, so it takes the first two and stops. The registry
50
+ * order is what makes that truncation safe: a collection title identifies a view
51
+ * far better than one of three format names, so it leads.
52
+ *
53
+ * @param selection - Active filter selection
54
+ * @param i18n - Translator
55
+ * @param ctx - Collection vocabulary, for naming a selected collection
56
+ * @returns Labels in priority order; empty when nothing is filtered
57
+ * @example
58
+ * describeArchiveFilters({ format: "quote" }, i18n, {}); // ["Quotes"]
59
+ */
60
+ export function describeArchiveFilters(
61
+ selection: PostFilterSelection,
62
+ i18n: Translator,
63
+ ctx: DimensionContext = {},
64
+ ): string[] {
65
+ // Title presence absorbs the format it refines, mirroring the filter chip —
66
+ // `Untitled` says everything `Notes, without title` would. The registry lists
67
+ // `format` immediately before `title`, so dropping one leaves the other in
68
+ // exactly the right place.
69
+ const composed =
70
+ selection.title !== undefined
71
+ ? { ...selection, format: undefined }
72
+ : selection;
73
+ return describeFilterSelection(composed, i18n, ctx);
74
+ }
75
+
76
+ /** How many parts a browser tab can carry before it truncates to noise. */
77
+ const TITLE_PART_LIMIT = 2;
78
+
79
+ /**
80
+ * Name for a filtered archive view where no surrounding UI explains it —
81
+ * a browser tab, a bookmark, a shared link's preview.
82
+ *
83
+ * @param selection - Active filter selection
84
+ * @param i18n - Translator
85
+ * @param ctx - Collection vocabulary, for naming a selected collection
86
+ * @returns Capped description, or the unfiltered name when nothing is filtered
87
+ * @example
88
+ * getArchiveViewTitle({ year: 2024 }, i18n, {}); // "2024"
89
+ */
90
+ export function getArchiveViewTitle(
91
+ selection: PostFilterSelection,
92
+ i18n: Translator,
93
+ ctx: DimensionContext = {},
94
+ ): string {
95
+ const parts = describeArchiveFilters(selection, i18n, ctx);
96
+ if (parts.length === 0) return i18n._(ARCHIVE_ALL_POSTS_LABEL);
97
+ return parts.slice(0, TITLE_PART_LIMIT).join(", ");
98
+ }
@@ -55,6 +55,28 @@ const collectionFormMessages = {
55
55
  message: "Collection created.",
56
56
  comment: "@context: Confirmation shown after creating a collection",
57
57
  }),
58
+ cancelLabel: msg({
59
+ message: "Cancel",
60
+ comment: "@context: Button to cancel form",
61
+ }),
62
+ } as const;
63
+
64
+ /**
65
+ * The editing dialog's own strings.
66
+ *
67
+ * The address and validation wording it shares with the quick-create form
68
+ * stays in {@link collectionFormMessages}; only what the dialog alone says
69
+ * lives here.
70
+ */
71
+ const collectionDialogMessages = {
72
+ createHeading: msg({
73
+ message: "New Collection",
74
+ comment: "@context: Title of the dialog that creates a collection",
75
+ }),
76
+ editHeading: msg({
77
+ message: "Edit Collection",
78
+ comment: "@context: Title of the dialog that edits an existing collection",
79
+ }),
58
80
  descriptionLabel: msg({
59
81
  message: "Description (optional)",
60
82
  comment: "@context: Collection form field",
@@ -63,9 +85,9 @@ const collectionFormMessages = {
63
85
  message: "What's this collection about?",
64
86
  comment: "@context: Collection description placeholder",
65
87
  }),
66
- sortOrderLabel: msg({
67
- message: "Sort Order",
68
- comment: "@context: Collection form field",
88
+ orderBy: msg({
89
+ message: "Order by",
90
+ comment: "@context: Collection editing dialog field",
69
91
  }),
70
92
  sortNewest: msg({
71
93
  message: "Newest first",
@@ -79,17 +101,40 @@ const collectionFormMessages = {
79
101
  message: "Highest rated",
80
102
  comment: "@context: Collection sort order option",
81
103
  }),
82
- submitLabel: msg({
83
- message: "Save",
84
- comment: "@context: Button to save collection",
104
+ loadFailed: msg({
105
+ message: "Could not open this collection. Try again.",
106
+ comment: "@context: Collection dialog load failure",
85
107
  }),
86
- cancelLabel: msg({
87
- message: "Cancel",
88
- comment: "@context: Button to cancel form",
108
+ titleAndLinkRequired: msg({
109
+ message: "A collection needs a title and a link.",
110
+ comment: "@context: Collection dialog validation message",
111
+ }),
112
+ } as const;
113
+
114
+ /**
115
+ * Address wording both editing dialogs share.
116
+ *
117
+ * A collection and a smart collection take the same kind of address out of the
118
+ * same namespace, so a clash and a move read the same in both.
119
+ */
120
+ const collectionLinkMessages = {
121
+ linkTaken: msg({
122
+ message: "This link is taken. Choose another.",
123
+ comment:
124
+ "@context: Collection editing dialog — the typed collection link is already in use",
125
+ }),
126
+ linkMovesWarning: msg({
127
+ message: "Changing the link breaks the old one immediately.",
128
+ comment:
129
+ "@context: Collection editing dialog warning shown when editing moves an existing collection link",
89
130
  }),
90
131
  } as const;
91
132
 
92
133
  const collectionMutationMessages = {
134
+ save: msg({
135
+ message: "Save",
136
+ comment: "@context: Button to save collection",
137
+ }),
93
138
  edit: msg({
94
139
  message: "Edit",
95
140
  comment: "@context: Per-collection edit action",
@@ -222,17 +267,50 @@ export const getCollectionFormLabels = (i18n: Translator) => ({
222
267
  quickHint: i18n._(collectionFormMessages.quickHint),
223
268
  quickSubmitLabel: i18n._(collectionFormMessages.quickSubmitLabel),
224
269
  createdLabel: i18n._(collectionFormMessages.createdLabel),
225
- descriptionLabel: i18n._(collectionFormMessages.descriptionLabel),
226
- descriptionPlaceholder: i18n._(collectionFormMessages.descriptionPlaceholder),
227
- sortOrderLabel: i18n._(collectionFormMessages.sortOrderLabel),
228
- sortNewest: i18n._(collectionFormMessages.sortNewest),
229
- sortOldest: i18n._(collectionFormMessages.sortOldest),
230
- sortRatingDesc: i18n._(collectionFormMessages.sortRatingDesc),
231
- submitLabel: i18n._(collectionFormMessages.submitLabel),
232
270
  cancelLabel: i18n._(collectionFormMessages.cancelLabel),
233
271
  });
234
272
 
273
+ /**
274
+ * Every string the editing dialog renders, translated on the server.
275
+ *
276
+ * The dialog is a Lit component and cannot reach the i18n catalogs itself, so
277
+ * whichever page can open it hands them down \u2014 the same arrangement the
278
+ * smart collection dialog uses.
279
+ */
280
+ export const getCollectionDialogLabels = (i18n: Translator) => ({
281
+ createHeading: i18n._(collectionDialogMessages.createHeading),
282
+ editHeading: i18n._(collectionDialogMessages.editHeading),
283
+ title: i18n._(collectionFormMessages.titleLabel),
284
+ titlePlaceholder: i18n._(collectionFormMessages.titlePlaceholder),
285
+ link: i18n._(collectionFormMessages.slugLabel),
286
+ linkHelp: i18n._(collectionFormMessages.slugHelp),
287
+ editLink: i18n._(collectionFormMessages.editSlugLabel),
288
+ resetLink: i18n._(collectionFormMessages.resetSlugLabel),
289
+ linkInvalid: i18n._(collectionFormMessages.slugInvalidHelp),
290
+ linkReserved: i18n._(collectionFormMessages.slugReservedHelp),
291
+ linkTooLong: i18n._(collectionFormMessages.slugTooLongHelp),
292
+ linkTaken: i18n._(collectionLinkMessages.linkTaken),
293
+ linkMovesWarning: i18n._(collectionLinkMessages.linkMovesWarning),
294
+ description: i18n._(collectionDialogMessages.descriptionLabel),
295
+ descriptionPlaceholder: i18n._(
296
+ collectionDialogMessages.descriptionPlaceholder,
297
+ ),
298
+ orderBy: i18n._(collectionDialogMessages.orderBy),
299
+ sortOptions: {
300
+ newest: i18n._(collectionDialogMessages.sortNewest),
301
+ oldest: i18n._(collectionDialogMessages.sortOldest),
302
+ rating_desc: i18n._(collectionDialogMessages.sortRatingDesc),
303
+ },
304
+ cancel: i18n._(collectionMutationMessages.cancel),
305
+ save: i18n._(collectionMutationMessages.save),
306
+ saved: i18n._(collectionMutationMessages.saved),
307
+ saveFailed: i18n._(collectionMutationMessages.saveFailed),
308
+ loadFailed: i18n._(collectionDialogMessages.loadFailed),
309
+ titleAndLinkRequired: i18n._(collectionDialogMessages.titleAndLinkRequired),
310
+ });
311
+
235
312
  export const getCollectionMutationLabels = (i18n: Translator) => ({
313
+ save: i18n._(collectionMutationMessages.save),
236
314
  edit: i18n._(collectionMutationMessages.edit),
237
315
  addToNavigation: i18n._(collectionMutationMessages.addToNavigation),
238
316
  addingToNavigation: i18n._(collectionMutationMessages.addingToNavigation),
@@ -11,6 +11,7 @@ type NavigationLabelItem = {
11
11
  systemKey?: SystemNavKey;
12
12
  label: string;
13
13
  url: string;
14
+ targetTitle?: string;
14
15
  };
15
16
 
16
17
  // `context: "nav"` gives these site-header labels a distinct Lingui hash so
@@ -35,9 +36,10 @@ const BUILTIN_NAV_LABELS = {
35
36
  comment: "@context: Built-in navigation label for the collections page",
36
37
  }),
37
38
  archive: msg({
38
- message: "Archive",
39
+ message: "All",
39
40
  context: "nav",
40
- comment: "@context: Built-in navigation label for the archive page",
41
+ comment:
42
+ "@context: Built-in navigation label for the archive page. Reads as the widest of Featured / Latest / All rather than 'old posts' — the page is the complete index, and its URL stays /archive.",
41
43
  }),
42
44
  settings: msg({
43
45
  message: "Settings",
@@ -45,9 +47,16 @@ const BUILTIN_NAV_LABELS = {
45
47
  comment: "@context: Built-in navigation label for settings",
46
48
  }),
47
49
  rss: msg({
48
- message: "Feed",
50
+ message: "RSS",
51
+ context: "nav",
52
+ comment:
53
+ "@context: Built-in navigation label for the feed. Keep the acronym in every locale — it is what readers scan for. The document /feed serves is Atom, but RSS is the category name readers and feed apps use, and it matches the vocabulary the rest of the product already speaks (systemKey `rss`, `mainRssFeed`). 'Feed' would collide with Featured/Latest/All, which are feeds too.",
54
+ }),
55
+ subscribe: msg({
56
+ message: "Subscribe",
49
57
  context: "nav",
50
- comment: "@context: Built-in navigation label for feed",
58
+ comment:
59
+ "@context: Built-in navigation label for the page that explains the site's feeds. Names what the reader wants to do, unlike the sibling RSS entry which names the format and links at the Atom document itself.",
51
60
  }),
52
61
  signIn: msg({
53
62
  message: "Sign in",
@@ -70,42 +79,21 @@ const BUILTIN_NAV_LABELS = {
70
79
  */
71
80
  export const NAV_MORE_LABEL = BUILTIN_NAV_LABELS.more;
72
81
 
73
- const SYSTEM_NAV_TITLES: Partial<Record<SystemNavKey, MessageDescriptor>> = {
82
+ /**
83
+ * The default label for every system nav entry.
84
+ *
85
+ * Total on purpose: this is the map both the settings toggle list and the
86
+ * rendered navigation read, so a new `SystemNavKey` without an entry here is a
87
+ * compile error rather than a nav item that renders blank.
88
+ */
89
+ const SYSTEM_NAV_TITLES: Record<SystemNavKey, MessageDescriptor> = {
74
90
  latest: BUILTIN_NAV_LABELS.latest,
75
91
  featured: BUILTIN_NAV_LABELS.featured,
76
92
  collections: BUILTIN_NAV_LABELS.collections,
77
93
  archive: BUILTIN_NAV_LABELS.archive,
78
94
  settings: BUILTIN_NAV_LABELS.settings,
79
95
  rss: BUILTIN_NAV_LABELS.rss,
80
- };
81
-
82
- const SYSTEM_NAV_DESCRIPTIONS: Record<SystemNavKey, MessageDescriptor> = {
83
- latest: msg({
84
- message: "Link to your latest posts. Your homepage shows this feed.",
85
- comment: "@context: Description for the latest system navigation toggle",
86
- }),
87
- featured: msg({
88
- message: "Link to posts you've marked as featured.",
89
- comment: "@context: Description for the featured system navigation toggle",
90
- }),
91
- rss: msg({
92
- message:
93
- "Add a link to your main RSS feed. Change what /feed returns in General.",
94
- comment: "@context: Description for the RSS system navigation toggle",
95
- }),
96
- settings: msg({
97
- message: "Shows 'Settings' when logged in, 'Sign in' when logged out",
98
- comment: "@context: Description for the settings system navigation toggle",
99
- }),
100
- collections: msg({
101
- message: "Link to your collections page",
102
- comment:
103
- "@context: Description for the collections system navigation toggle",
104
- }),
105
- archive: msg({
106
- message: "Link to the post archive",
107
- comment: "@context: Description for the archive system navigation toggle",
108
- }),
96
+ subscribe: BUILTIN_NAV_LABELS.subscribe,
109
97
  };
110
98
 
111
99
  function getInternalNavPath(url: string, sitePathPrefix = ""): string | null {
@@ -133,26 +121,8 @@ function getBuiltinNavLabelDescriptor(
133
121
  ): MessageDescriptor | null {
134
122
  if (item.type !== "system" || !item.systemKey) return null;
135
123
 
136
- if (item.systemKey === "collections") {
137
- return BUILTIN_NAV_LABELS.collections;
138
- }
139
-
140
- if (item.systemKey === "latest") {
141
- return BUILTIN_NAV_LABELS.latest;
142
- }
143
-
144
- if (item.systemKey === "featured") {
145
- return BUILTIN_NAV_LABELS.featured;
146
- }
147
-
148
- if (item.systemKey === "archive") {
149
- return BUILTIN_NAV_LABELS.archive;
150
- }
151
-
152
- if (item.systemKey === "rss") {
153
- return BUILTIN_NAV_LABELS.rss;
154
- }
155
-
124
+ // Settings is the one entry whose label depends on where it currently
125
+ // points, so it cannot be read from the key alone.
156
126
  if (item.systemKey === "settings") {
157
127
  const path = getInternalNavPath(item.url, sitePathPrefix);
158
128
  return path === "/signin"
@@ -160,35 +130,39 @@ function getBuiltinNavLabelDescriptor(
160
130
  : BUILTIN_NAV_LABELS.settings;
161
131
  }
162
132
 
163
- return null;
133
+ return SYSTEM_NAV_TITLES[item.systemKey] ?? null;
164
134
  }
165
135
 
136
+ /**
137
+ * The text a nav item shows.
138
+ *
139
+ * A stored label is always the author's own words, so it wins outright. An
140
+ * empty one means the item follows what it points at: a built-in destination
141
+ * shows its translated default, a page or collection shows that row's current
142
+ * title — which is also how a nav entry ends up in the reader's language once
143
+ * a language view has resolved it to a translated page.
144
+ *
145
+ * @param item - Nav item, view or raw
146
+ * @param i18n - Translator for built-in destinations
147
+ * @param sitePathPrefix - Deployment path prefix, such as `/blog`
148
+ * @returns Label to render
149
+ */
166
150
  export function getNavItemDisplayLabel(
167
151
  item: NavigationLabelItem,
168
152
  i18n: Translator,
169
153
  sitePathPrefix = "",
170
154
  ): string {
171
- if (item.type === "system" && item.systemKey) {
172
- // Non-empty label means the user customized it — use as-is.
173
- // Empty label means "use the i18n-translated default".
174
- if (item.label) {
175
- return item.label;
176
- }
177
- }
155
+ if (item.label) return item.label;
156
+
178
157
  const descriptor = getBuiltinNavLabelDescriptor(item, sitePathPrefix);
179
- return descriptor ? i18n._(descriptor) : item.label;
180
- }
158
+ if (descriptor) return i18n._(descriptor);
181
159
 
182
- export function getSystemNavDisplayLabel(
183
- key: SystemNavKey,
184
- i18n: Translator,
185
- ): string {
186
- return i18n._(SYSTEM_NAV_TITLES[key] ?? BUILTIN_NAV_LABELS.rss);
160
+ return item.targetTitle?.trim() || item.label;
187
161
  }
188
162
 
189
- export function getSystemNavDescription(
163
+ export function getSystemNavDisplayLabel(
190
164
  key: SystemNavKey,
191
165
  i18n: Translator,
192
166
  ): string {
193
- return i18n._(SYSTEM_NAV_DESCRIPTIONS[key]);
167
+ return i18n._(SYSTEM_NAV_TITLES[key]);
194
168
  }
@@ -13,6 +13,7 @@ type PostArticleAttributeSource = Pick<
13
13
  | "pinnedInCollection"
14
14
  | "featured"
15
15
  | "draftTailId"
16
+ | "language"
16
17
  >;
17
18
 
18
19
  /**
@@ -47,6 +48,7 @@ export function getPostArticleAttributes(post: PostArticleAttributeSource) {
47
48
  ? { "data-thread-draft-tail-id": post.draftTailId }
48
49
  : {}),
49
50
  "data-post-visibility": post.visibility,
51
+ ...(post.language ? { "data-post-language": post.language } : {}),
50
52
  ...(isChildPost ? { "data-post-reply": "" } : {}),
51
53
  };
52
54
  }