react-cheminfo 0.64.1 → 0.65.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 (604) hide show
  1. package/README.md +282 -0
  2. package/lib/card/core/cardAddress.d.ts +70 -0
  3. package/lib/card/core/cardAddress.d.ts.map +1 -0
  4. package/lib/card/core/cardAddress.js +59 -0
  5. package/lib/card/core/cardAddress.js.map +1 -0
  6. package/lib/card/core/cardPageUrl.d.ts +49 -0
  7. package/lib/card/core/cardPageUrl.d.ts.map +1 -0
  8. package/lib/card/core/cardPageUrl.js +59 -0
  9. package/lib/card/core/cardPageUrl.js.map +1 -0
  10. package/lib/card/core/index.d.ts +7 -0
  11. package/lib/card/core/index.d.ts.map +1 -0
  12. package/lib/card/core/index.js +4 -0
  13. package/lib/card/core/index.js.map +1 -0
  14. package/lib/card/core/panelRoom.d.ts +40 -0
  15. package/lib/card/core/panelRoom.d.ts.map +1 -0
  16. package/lib/card/core/panelRoom.js +40 -0
  17. package/lib/card/core/panelRoom.js.map +1 -0
  18. package/lib/card/ui/CardDetailsButton.d.ts +38 -0
  19. package/lib/card/ui/CardDetailsButton.d.ts.map +1 -0
  20. package/lib/card/ui/CardDetailsButton.js +50 -0
  21. package/lib/card/ui/CardDetailsButton.js.map +1 -0
  22. package/lib/card/ui/CardFigure.d.ts +51 -0
  23. package/lib/card/ui/CardFigure.d.ts.map +1 -0
  24. package/lib/card/ui/CardFigure.js +27 -0
  25. package/lib/card/ui/CardFigure.js.map +1 -0
  26. package/lib/card/ui/CardFrame.d.ts +30 -0
  27. package/lib/card/ui/CardFrame.d.ts.map +1 -0
  28. package/lib/card/ui/CardFrame.js +20 -0
  29. package/lib/card/ui/CardFrame.js.map +1 -0
  30. package/lib/card/ui/CopyTableButton.d.ts +37 -0
  31. package/lib/card/ui/CopyTableButton.d.ts.map +1 -0
  32. package/lib/card/ui/CopyTableButton.js +19 -0
  33. package/lib/card/ui/CopyTableButton.js.map +1 -0
  34. package/lib/card/ui/cardTooltip.d.ts +14 -0
  35. package/lib/card/ui/cardTooltip.d.ts.map +1 -0
  36. package/lib/card/ui/cardTooltip.js +15 -0
  37. package/lib/card/ui/cardTooltip.js.map +1 -0
  38. package/lib/card/ui/embed.d.ts +15 -0
  39. package/lib/card/ui/embed.d.ts.map +1 -0
  40. package/lib/card/ui/embed.js +24 -0
  41. package/lib/card/ui/embed.js.map +1 -0
  42. package/lib/card/ui/fitCardToFrame.d.ts +31 -0
  43. package/lib/card/ui/fitCardToFrame.d.ts.map +1 -0
  44. package/lib/card/ui/fitCardToFrame.js +66 -0
  45. package/lib/card/ui/fitCardToFrame.js.map +1 -0
  46. package/lib/card/ui/holdRoomForPanels.d.ts +16 -0
  47. package/lib/card/ui/holdRoomForPanels.d.ts.map +1 -0
  48. package/lib/card/ui/holdRoomForPanels.js +76 -0
  49. package/lib/card/ui/holdRoomForPanels.js.map +1 -0
  50. package/lib/card/ui/index.d.ts +11 -0
  51. package/lib/card/ui/index.d.ts.map +1 -0
  52. package/lib/card/ui/index.js +7 -0
  53. package/lib/card/ui/index.js.map +1 -0
  54. package/lib/card/ui/openPanels.d.ts +25 -0
  55. package/lib/card/ui/openPanels.d.ts.map +1 -0
  56. package/lib/card/ui/openPanels.js +85 -0
  57. package/lib/card/ui/openPanels.js.map +1 -0
  58. package/lib/card.d.ts +13 -0
  59. package/lib/card.d.ts.map +1 -0
  60. package/lib/card.js +13 -0
  61. package/lib/card.js.map +1 -0
  62. package/lib/conformers.d.ts +1 -0
  63. package/lib/conformers.d.ts.map +1 -1
  64. package/lib/conformers.js +1 -0
  65. package/lib/conformers.js.map +1 -1
  66. package/lib/core.d.ts +4 -0
  67. package/lib/core.d.ts.map +1 -1
  68. package/lib/core.js +5 -0
  69. package/lib/core.js.map +1 -1
  70. package/lib/fetch/core/apiError.d.ts +56 -0
  71. package/lib/fetch/core/apiError.d.ts.map +1 -0
  72. package/lib/fetch/core/apiError.js +43 -0
  73. package/lib/fetch/core/apiError.js.map +1 -0
  74. package/lib/fetch/core/fetchJson.d.ts +47 -0
  75. package/lib/fetch/core/fetchJson.d.ts.map +1 -0
  76. package/lib/fetch/core/fetchJson.js +109 -0
  77. package/lib/fetch/core/fetchJson.js.map +1 -0
  78. package/lib/fetch/core/index.d.ts +5 -0
  79. package/lib/fetch/core/index.d.ts.map +1 -0
  80. package/lib/fetch/core/index.js +3 -0
  81. package/lib/fetch/core/index.js.map +1 -0
  82. package/lib/formula/core/formulaText.d.ts +46 -0
  83. package/lib/formula/core/formulaText.d.ts.map +1 -0
  84. package/lib/formula/core/formulaText.js +74 -0
  85. package/lib/formula/core/formulaText.js.map +1 -0
  86. package/lib/formula/core/index.d.ts +6 -0
  87. package/lib/formula/core/index.d.ts.map +1 -0
  88. package/lib/formula/core/index.js +4 -0
  89. package/lib/formula/core/index.js.map +1 -0
  90. package/lib/formula/core/normalizeFormula.d.ts +22 -0
  91. package/lib/formula/core/normalizeFormula.d.ts.map +1 -0
  92. package/lib/formula/core/normalizeFormula.js +46 -0
  93. package/lib/formula/core/normalizeFormula.js.map +1 -0
  94. package/lib/formula/core/readFormula.d.ts +73 -0
  95. package/lib/formula/core/readFormula.d.ts.map +1 -0
  96. package/lib/formula/core/readFormula.js +132 -0
  97. package/lib/formula/core/readFormula.js.map +1 -0
  98. package/lib/formula/ui/CopyableFormula.d.ts +31 -0
  99. package/lib/formula/ui/CopyableFormula.d.ts.map +1 -0
  100. package/lib/formula/ui/CopyableFormula.js +19 -0
  101. package/lib/formula/ui/CopyableFormula.js.map +1 -0
  102. package/lib/formula/ui/FormulaInput.d.ts +29 -0
  103. package/lib/formula/ui/FormulaInput.d.ts.map +1 -0
  104. package/lib/formula/ui/FormulaInput.js +16 -0
  105. package/lib/formula/ui/FormulaInput.js.map +1 -0
  106. package/lib/formula/ui/QuestionFormula.d.ts +14 -0
  107. package/lib/formula/ui/QuestionFormula.d.ts.map +1 -0
  108. package/lib/formula/ui/QuestionFormula.js +13 -0
  109. package/lib/formula/ui/QuestionFormula.js.map +1 -0
  110. package/lib/formula/ui/index.d.ts +7 -0
  111. package/lib/formula/ui/index.d.ts.map +1 -0
  112. package/lib/formula/ui/index.js +4 -0
  113. package/lib/formula/ui/index.js.map +1 -0
  114. package/lib/formula.d.ts +11 -0
  115. package/lib/formula.d.ts.map +1 -0
  116. package/lib/formula.js +11 -0
  117. package/lib/formula.js.map +1 -0
  118. package/lib/hooks/ui/index.d.ts +1 -0
  119. package/lib/hooks/ui/index.d.ts.map +1 -1
  120. package/lib/hooks/ui/index.js.map +1 -1
  121. package/lib/hooks/ui/keyTargets.d.ts +4 -2
  122. package/lib/hooks/ui/keyTargets.d.ts.map +1 -1
  123. package/lib/hooks/ui/keyTargets.js +5 -3
  124. package/lib/hooks/ui/keyTargets.js.map +1 -1
  125. package/lib/hooks/ui/useAsync.d.ts +20 -5
  126. package/lib/hooks/ui/useAsync.d.ts.map +1 -1
  127. package/lib/hooks/ui/useAsync.js +29 -14
  128. package/lib/hooks/ui/useAsync.js.map +1 -1
  129. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  130. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  131. package/lib/locales/de.json +37 -0
  132. package/lib/locales/en.json +37 -0
  133. package/lib/locales/es.json +37 -0
  134. package/lib/locales/fr.json +37 -0
  135. package/lib/locales/it.json +37 -0
  136. package/lib/moleculesClient/core/index.d.ts +5 -0
  137. package/lib/moleculesClient/core/index.d.ts.map +1 -0
  138. package/lib/moleculesClient/core/index.js +3 -0
  139. package/lib/moleculesClient/core/index.js.map +1 -0
  140. package/lib/moleculesClient/core/moleculesClient.d.ts +12 -0
  141. package/lib/moleculesClient/core/moleculesClient.d.ts.map +1 -0
  142. package/lib/moleculesClient/core/moleculesClient.js +105 -0
  143. package/lib/moleculesClient/core/moleculesClient.js.map +1 -0
  144. package/lib/moleculesClient/core/moleculesClientApi.d.ts +100 -0
  145. package/lib/moleculesClient/core/moleculesClientApi.d.ts.map +1 -0
  146. package/lib/moleculesClient/core/moleculesClientApi.js +20 -0
  147. package/lib/moleculesClient/core/moleculesClientApi.js.map +1 -0
  148. package/lib/moleculesClient/core/moleculesTypes.d.ts +154 -0
  149. package/lib/moleculesClient/core/moleculesTypes.d.ts.map +1 -0
  150. package/lib/moleculesClient/core/moleculesTypes.js +2 -0
  151. package/lib/moleculesClient/core/moleculesTypes.js.map +1 -0
  152. package/lib/moleculesClient/core/requestRetry.d.ts +50 -0
  153. package/lib/moleculesClient/core/requestRetry.d.ts.map +1 -0
  154. package/lib/moleculesClient/core/requestRetry.js +108 -0
  155. package/lib/moleculesClient/core/requestRetry.js.map +1 -0
  156. package/lib/molstar/core/camera.d.ts +2 -1
  157. package/lib/molstar/core/camera.d.ts.map +1 -1
  158. package/lib/molstar/core/camera.js +3 -2
  159. package/lib/molstar/core/camera.js.map +1 -1
  160. package/lib/molstar/core/framing.d.ts +102 -0
  161. package/lib/molstar/core/framing.d.ts.map +1 -0
  162. package/lib/molstar/core/framing.js +120 -0
  163. package/lib/molstar/core/framing.js.map +1 -0
  164. package/lib/molstar/core/hover.d.ts +16 -0
  165. package/lib/molstar/core/hover.d.ts.map +1 -1
  166. package/lib/molstar/core/hover.js +14 -0
  167. package/lib/molstar/core/hover.js.map +1 -1
  168. package/lib/molstar/core/index.d.ts +4 -1
  169. package/lib/molstar/core/index.d.ts.map +1 -1
  170. package/lib/molstar/core/index.js +2 -1
  171. package/lib/molstar/core/index.js.map +1 -1
  172. package/lib/pedagogy/core/answers.d.ts +46 -0
  173. package/lib/pedagogy/core/answers.d.ts.map +1 -0
  174. package/lib/pedagogy/core/answers.js +77 -0
  175. package/lib/pedagogy/core/answers.js.map +1 -0
  176. package/lib/pedagogy/core/attempts.d.ts +86 -0
  177. package/lib/pedagogy/core/attempts.d.ts.map +1 -0
  178. package/lib/pedagogy/core/attempts.js +117 -0
  179. package/lib/pedagogy/core/attempts.js.map +1 -0
  180. package/lib/pedagogy/core/index.d.ts +8 -0
  181. package/lib/pedagogy/core/index.d.ts.map +1 -1
  182. package/lib/pedagogy/core/index.js +4 -0
  183. package/lib/pedagogy/core/index.js.map +1 -1
  184. package/lib/pedagogy/core/progressController.d.ts +64 -0
  185. package/lib/pedagogy/core/progressController.d.ts.map +1 -0
  186. package/lib/pedagogy/core/progressController.js +56 -0
  187. package/lib/pedagogy/core/progressController.js.map +1 -0
  188. package/lib/pedagogy/core/seed.d.ts +96 -0
  189. package/lib/pedagogy/core/seed.d.ts.map +1 -0
  190. package/lib/pedagogy/core/seed.js +128 -0
  191. package/lib/pedagogy/core/seed.js.map +1 -0
  192. package/lib/pedagogy/core/series.d.ts +77 -0
  193. package/lib/pedagogy/core/series.d.ts.map +1 -0
  194. package/lib/pedagogy/core/series.js +10 -0
  195. package/lib/pedagogy/core/series.js.map +1 -0
  196. package/lib/pedagogy/ui/AnswerFields.d.ts +21 -0
  197. package/lib/pedagogy/ui/AnswerFields.d.ts.map +1 -0
  198. package/lib/pedagogy/ui/AnswerFields.js +24 -0
  199. package/lib/pedagogy/ui/AnswerFields.js.map +1 -0
  200. package/lib/pedagogy/ui/ExerciseList.d.ts +82 -0
  201. package/lib/pedagogy/ui/ExerciseList.d.ts.map +1 -0
  202. package/lib/pedagogy/ui/ExerciseList.js +53 -0
  203. package/lib/pedagogy/ui/ExerciseList.js.map +1 -0
  204. package/lib/pedagogy/ui/ExerciseSeries.d.ts +95 -0
  205. package/lib/pedagogy/ui/ExerciseSeries.d.ts.map +1 -0
  206. package/lib/pedagogy/ui/ExerciseSeries.js +74 -0
  207. package/lib/pedagogy/ui/ExerciseSeries.js.map +1 -0
  208. package/lib/pedagogy/ui/QuestionCard.d.ts +64 -0
  209. package/lib/pedagogy/ui/QuestionCard.d.ts.map +1 -0
  210. package/lib/pedagogy/ui/QuestionCard.js +73 -0
  211. package/lib/pedagogy/ui/QuestionCard.js.map +1 -0
  212. package/lib/pedagogy/ui/index.d.ts +10 -0
  213. package/lib/pedagogy/ui/index.d.ts.map +1 -1
  214. package/lib/pedagogy/ui/index.js +6 -0
  215. package/lib/pedagogy/ui/index.js.map +1 -1
  216. package/lib/pedagogy/ui/seriesCounts.d.ts +17 -0
  217. package/lib/pedagogy/ui/seriesCounts.d.ts.map +1 -0
  218. package/lib/pedagogy/ui/seriesCounts.js +27 -0
  219. package/lib/pedagogy/ui/seriesCounts.js.map +1 -0
  220. package/lib/pedagogy/ui/seriesParts.d.ts +55 -0
  221. package/lib/pedagogy/ui/seriesParts.d.ts.map +1 -0
  222. package/lib/pedagogy/ui/seriesParts.js +38 -0
  223. package/lib/pedagogy/ui/seriesParts.js.map +1 -0
  224. package/lib/pedagogy/ui/useAttemptRecords.d.ts +10 -0
  225. package/lib/pedagogy/ui/useAttemptRecords.d.ts.map +1 -0
  226. package/lib/pedagogy/ui/useAttemptRecords.js +11 -0
  227. package/lib/pedagogy/ui/useAttemptRecords.js.map +1 -0
  228. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  229. package/lib/periodic/ui/PeriodicTable.js +6 -3
  230. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  231. package/lib/property/core/copyableText.d.ts +14 -0
  232. package/lib/property/core/copyableText.d.ts.map +1 -0
  233. package/lib/property/core/copyableText.js +21 -0
  234. package/lib/property/core/copyableText.js.map +1 -0
  235. package/lib/property/core/index.d.ts +2 -0
  236. package/lib/property/core/index.d.ts.map +1 -0
  237. package/lib/property/core/index.js +2 -0
  238. package/lib/property/core/index.js.map +1 -0
  239. package/lib/property/ui/PropertyTable.d.ts +43 -0
  240. package/lib/property/ui/PropertyTable.d.ts.map +1 -0
  241. package/lib/property/ui/PropertyTable.js +19 -0
  242. package/lib/property/ui/PropertyTable.js.map +1 -0
  243. package/lib/property/ui/index.d.ts +3 -0
  244. package/lib/property/ui/index.d.ts.map +1 -0
  245. package/lib/property/ui/index.js +2 -0
  246. package/lib/property/ui/index.js.map +1 -0
  247. package/lib/router/core/history.d.ts +24 -3
  248. package/lib/router/core/history.d.ts.map +1 -1
  249. package/lib/router/core/history.js +55 -7
  250. package/lib/router/core/history.js.map +1 -1
  251. package/lib/router/core/index.d.ts +3 -3
  252. package/lib/router/core/index.d.ts.map +1 -1
  253. package/lib/router/core/index.js +2 -2
  254. package/lib/router/core/index.js.map +1 -1
  255. package/lib/router/core/languageRoute.d.ts +61 -0
  256. package/lib/router/core/languageRoute.d.ts.map +1 -0
  257. package/lib/router/core/languageRoute.js +96 -0
  258. package/lib/router/core/languageRoute.js.map +1 -0
  259. package/lib/router/core/siteAddresses.d.ts +23 -0
  260. package/lib/router/core/siteAddresses.d.ts.map +1 -1
  261. package/lib/router/core/siteAddresses.js +26 -0
  262. package/lib/router/core/siteAddresses.js.map +1 -1
  263. package/lib/router/core/tabRouter.d.ts +2 -108
  264. package/lib/router/core/tabRouter.d.ts.map +1 -1
  265. package/lib/router/core/tabRouter.js +38 -5
  266. package/lib/router/core/tabRouter.js.map +1 -1
  267. package/lib/router/core/tabRouterTypes.d.ts +154 -0
  268. package/lib/router/core/tabRouterTypes.d.ts.map +1 -0
  269. package/lib/router/core/tabRouterTypes.js +2 -0
  270. package/lib/router/core/tabRouterTypes.js.map +1 -0
  271. package/lib/seo/core/fitDescription.d.ts +40 -0
  272. package/lib/seo/core/fitDescription.d.ts.map +1 -0
  273. package/lib/seo/core/fitDescription.js +45 -0
  274. package/lib/seo/core/fitDescription.js.map +1 -0
  275. package/lib/seo/core/index.d.ts +7 -0
  276. package/lib/seo/core/index.d.ts.map +1 -1
  277. package/lib/seo/core/index.js +4 -0
  278. package/lib/seo/core/index.js.map +1 -1
  279. package/lib/seo/core/limits.d.ts +24 -0
  280. package/lib/seo/core/limits.d.ts.map +1 -0
  281. package/lib/seo/core/limits.js +24 -0
  282. package/lib/seo/core/limits.js.map +1 -0
  283. package/lib/seo/core/pageContent.d.ts +25 -0
  284. package/lib/seo/core/pageContent.d.ts.map +1 -0
  285. package/lib/seo/core/pageContent.js +25 -0
  286. package/lib/seo/core/pageContent.js.map +1 -0
  287. package/lib/seo/core/plainDescription.d.ts.map +1 -1
  288. package/lib/seo/core/plainDescription.js +3 -2
  289. package/lib/seo/core/plainDescription.js.map +1 -1
  290. package/lib/seo/core/routeProblems.d.ts.map +1 -1
  291. package/lib/seo/core/routeProblems.js +1 -6
  292. package/lib/seo/core/routeProblems.js.map +1 -1
  293. package/lib/seo/core/routeTable.d.ts +31 -0
  294. package/lib/seo/core/routeTable.d.ts.map +1 -0
  295. package/lib/seo/core/routeTable.js +96 -0
  296. package/lib/seo/core/routeTable.js.map +1 -0
  297. package/lib/seo/vite/index.d.ts +2 -0
  298. package/lib/seo/vite/index.d.ts.map +1 -1
  299. package/lib/seo/vite/index.js +1 -0
  300. package/lib/seo/vite/index.js.map +1 -1
  301. package/lib/seo/vite/prerender.d.ts +10 -0
  302. package/lib/seo/vite/prerender.d.ts.map +1 -1
  303. package/lib/seo/vite/prerender.js +12 -1
  304. package/lib/seo/vite/prerender.js.map +1 -1
  305. package/lib/seo/vite/routeTable.d.ts +40 -0
  306. package/lib/seo/vite/routeTable.d.ts.map +1 -0
  307. package/lib/seo/vite/routeTable.js +44 -0
  308. package/lib/seo/vite/routeTable.js.map +1 -0
  309. package/lib/server/answerAddresses.d.ts +51 -0
  310. package/lib/server/answerAddresses.d.ts.map +1 -0
  311. package/lib/server/answerAddresses.js +72 -0
  312. package/lib/server/answerAddresses.js.map +1 -0
  313. package/lib/server/cardMeta.d.ts +57 -0
  314. package/lib/server/cardMeta.d.ts.map +1 -0
  315. package/lib/server/cardMeta.js +52 -0
  316. package/lib/server/cardMeta.js.map +1 -0
  317. package/lib/server/config.d.ts +72 -0
  318. package/lib/server/config.d.ts.map +1 -0
  319. package/lib/server/config.js +67 -0
  320. package/lib/server/config.js.map +1 -0
  321. package/lib/server/envNumber.d.ts +35 -0
  322. package/lib/server/envNumber.d.ts.map +1 -0
  323. package/lib/server/envNumber.js +42 -0
  324. package/lib/server/envNumber.js.map +1 -0
  325. package/lib/server/healthRoute.d.ts +26 -0
  326. package/lib/server/healthRoute.d.ts.map +1 -0
  327. package/lib/server/healthRoute.js +22 -0
  328. package/lib/server/healthRoute.js.map +1 -0
  329. package/lib/server/listenSite.d.ts +38 -0
  330. package/lib/server/listenSite.d.ts.map +1 -0
  331. package/lib/server/listenSite.js +60 -0
  332. package/lib/server/listenSite.js.map +1 -0
  333. package/lib/server/mcpServer.d.ts +8 -0
  334. package/lib/server/mcpServer.d.ts.map +1 -0
  335. package/lib/server/mcpServer.js +11 -0
  336. package/lib/server/mcpServer.js.map +1 -0
  337. package/lib/server/repositoryVersion.d.ts +16 -0
  338. package/lib/server/repositoryVersion.d.ts.map +1 -0
  339. package/lib/server/repositoryVersion.js +33 -0
  340. package/lib/server/repositoryVersion.js.map +1 -0
  341. package/lib/server/routeOptions.d.ts +23 -0
  342. package/lib/server/routeOptions.d.ts.map +1 -0
  343. package/lib/server/routeOptions.js +20 -0
  344. package/lib/server/routeOptions.js.map +1 -0
  345. package/lib/server/routeTable.d.ts +14 -0
  346. package/lib/server/routeTable.d.ts.map +1 -0
  347. package/lib/server/routeTable.js +32 -0
  348. package/lib/server/routeTable.js.map +1 -0
  349. package/lib/server/siteView.d.ts +29 -0
  350. package/lib/server/siteView.d.ts.map +1 -0
  351. package/lib/server/siteView.js +21 -0
  352. package/lib/server/siteView.js.map +1 -0
  353. package/lib/server/spaPages.d.ts +32 -0
  354. package/lib/server/spaPages.d.ts.map +1 -0
  355. package/lib/server/spaPages.js +126 -0
  356. package/lib/server/spaPages.js.map +1 -0
  357. package/lib/server/spaSite.d.ts +30 -0
  358. package/lib/server/spaSite.d.ts.map +1 -0
  359. package/lib/server/spaSite.js +170 -0
  360. package/lib/server/spaSite.js.map +1 -0
  361. package/lib/server/spaSiteOptions.d.ts +126 -0
  362. package/lib/server/spaSiteOptions.d.ts.map +1 -0
  363. package/lib/server/spaSiteOptions.js +2 -0
  364. package/lib/server/spaSiteOptions.js.map +1 -0
  365. package/lib/server/staticSite.d.ts +9 -0
  366. package/lib/server/staticSite.d.ts.map +1 -1
  367. package/lib/server/staticSite.js +41 -19
  368. package/lib/server/staticSite.js.map +1 -1
  369. package/lib/server/toolSiteServer.d.ts +77 -0
  370. package/lib/server/toolSiteServer.d.ts.map +1 -0
  371. package/lib/server/toolSiteServer.js +58 -0
  372. package/lib/server/toolSiteServer.js.map +1 -0
  373. package/lib/server/trustProxy.d.ts +20 -0
  374. package/lib/server/trustProxy.d.ts.map +1 -0
  375. package/lib/server/trustProxy.js +32 -0
  376. package/lib/server/trustProxy.js.map +1 -0
  377. package/lib/server/types.d.ts +13 -0
  378. package/lib/server/types.d.ts.map +1 -0
  379. package/lib/server/types.js +2 -0
  380. package/lib/server/types.js.map +1 -0
  381. package/lib/server.d.ts +22 -0
  382. package/lib/server.d.ts.map +1 -1
  383. package/lib/server.js +12 -0
  384. package/lib/server.js.map +1 -1
  385. package/lib/share/core/config.d.ts +18 -0
  386. package/lib/share/core/config.d.ts.map +1 -1
  387. package/lib/share/core/config.js +33 -2
  388. package/lib/share/core/config.js.map +1 -1
  389. package/lib/share/core/index.d.ts +3 -1
  390. package/lib/share/core/index.d.ts.map +1 -1
  391. package/lib/share/core/index.js +2 -1
  392. package/lib/share/core/index.js.map +1 -1
  393. package/lib/share/core/keySetParam.d.ts +40 -0
  394. package/lib/share/core/keySetParam.d.ts.map +1 -0
  395. package/lib/share/core/keySetParam.js +60 -0
  396. package/lib/share/core/keySetParam.js.map +1 -0
  397. package/lib/share/core/vocabulary.d.ts +8 -0
  398. package/lib/share/core/vocabulary.d.ts.map +1 -1
  399. package/lib/shell/ui/ShareControl.d.ts +75 -0
  400. package/lib/shell/ui/ShareControl.d.ts.map +1 -0
  401. package/lib/shell/ui/ShareControl.js +40 -0
  402. package/lib/shell/ui/ShareControl.js.map +1 -0
  403. package/lib/shell/ui/ShellFrame.d.ts +14 -0
  404. package/lib/shell/ui/ShellFrame.d.ts.map +1 -0
  405. package/lib/shell/ui/ShellFrame.js +45 -0
  406. package/lib/shell/ui/ShellFrame.js.map +1 -0
  407. package/lib/shell/ui/SiteShell.d.ts +251 -0
  408. package/lib/shell/ui/SiteShell.d.ts.map +1 -0
  409. package/lib/shell/ui/SiteShell.js +39 -0
  410. package/lib/shell/ui/SiteShell.js.map +1 -0
  411. package/lib/shell/ui/index.d.ts +5 -0
  412. package/lib/shell/ui/index.d.ts.map +1 -0
  413. package/lib/shell/ui/index.js +3 -0
  414. package/lib/shell/ui/index.js.map +1 -0
  415. package/lib/state/core/index.d.ts +1 -0
  416. package/lib/state/core/index.d.ts.map +1 -1
  417. package/lib/state/core/index.js.map +1 -1
  418. package/lib/state/core/legacyEntries.d.ts +30 -0
  419. package/lib/state/core/legacyEntries.d.ts.map +1 -0
  420. package/lib/state/core/legacyEntries.js +51 -0
  421. package/lib/state/core/legacyEntries.js.map +1 -0
  422. package/lib/state/core/persistBucket.d.ts +26 -2
  423. package/lib/state/core/persistBucket.d.ts.map +1 -1
  424. package/lib/state/core/persistBucket.js +26 -9
  425. package/lib/state/core/persistBucket.js.map +1 -1
  426. package/lib/state/core/persistSignalBucket.d.ts +26 -0
  427. package/lib/state/core/persistSignalBucket.d.ts.map +1 -1
  428. package/lib/state/core/persistSignalBucket.js +31 -0
  429. package/lib/state/core/persistSignalBucket.js.map +1 -1
  430. package/lib/state/core/storageEntry.d.ts +15 -0
  431. package/lib/state/core/storageEntry.d.ts.map +1 -1
  432. package/lib/state/core/storageEntry.js +33 -0
  433. package/lib/state/core/storageEntry.js.map +1 -1
  434. package/lib/structure/ui/StructureExportButton.d.ts +27 -0
  435. package/lib/structure/ui/StructureExportButton.d.ts.map +1 -0
  436. package/lib/structure/ui/StructureExportButton.js +21 -0
  437. package/lib/structure/ui/StructureExportButton.js.map +1 -0
  438. package/lib/structure/ui/index.d.ts +4 -0
  439. package/lib/structure/ui/index.d.ts.map +1 -1
  440. package/lib/structure/ui/index.js +2 -0
  441. package/lib/structure/ui/index.js.map +1 -1
  442. package/lib/structure/ui/useStructureDraft.d.ts +32 -0
  443. package/lib/structure/ui/useStructureDraft.d.ts.map +1 -0
  444. package/lib/structure/ui/useStructureDraft.js +20 -0
  445. package/lib/structure/ui/useStructureDraft.js.map +1 -0
  446. package/lib/ui.d.ts +2 -0
  447. package/lib/ui.d.ts.map +1 -1
  448. package/lib/ui.js +2 -0
  449. package/lib/ui.js.map +1 -1
  450. package/lib/worker/core/index.d.ts +1 -1
  451. package/lib/worker/core/index.d.ts.map +1 -1
  452. package/lib/worker/core/messages.d.ts +4 -2
  453. package/lib/worker/core/messages.d.ts.map +1 -1
  454. package/lib/worker/core/messages.js.map +1 -1
  455. package/lib/worker/core/oclResourcesUrl.d.ts +16 -0
  456. package/lib/worker/core/oclResourcesUrl.d.ts.map +1 -0
  457. package/lib/worker/core/oclResourcesUrl.js +19 -0
  458. package/lib/worker/core/oclResourcesUrl.js.map +1 -0
  459. package/lib/worker/core/serveWorkerRequests.d.ts +8 -0
  460. package/lib/worker/core/serveWorkerRequests.d.ts.map +1 -1
  461. package/lib/worker/core/serveWorkerRequests.js +15 -2
  462. package/lib/worker/core/serveWorkerRequests.js.map +1 -1
  463. package/lib/worker/core/workerChannel.d.ts +3 -4
  464. package/lib/worker/core/workerChannel.d.ts.map +1 -1
  465. package/lib/worker/core/workerChannel.js +72 -67
  466. package/lib/worker/core/workerChannel.js.map +1 -1
  467. package/lib/worker/core/workerChannelParts.d.ts +17 -6
  468. package/lib/worker/core/workerChannelParts.d.ts.map +1 -1
  469. package/lib/worker/core/workerChannelParts.js +20 -4
  470. package/lib/worker/core/workerChannelParts.js.map +1 -1
  471. package/lib/worker/core/workerChannelTypes.d.ts +46 -2
  472. package/lib/worker/core/workerChannelTypes.d.ts.map +1 -1
  473. package/lib/worker/core/workerSlot.d.ts +66 -0
  474. package/lib/worker/core/workerSlot.d.ts.map +1 -0
  475. package/lib/worker/core/workerSlot.js +107 -0
  476. package/lib/worker/core/workerSlot.js.map +1 -0
  477. package/package.json +16 -1
  478. package/src/card/core/cardAddress.ts +100 -0
  479. package/src/card/core/cardPageUrl.ts +97 -0
  480. package/src/card/core/index.ts +6 -0
  481. package/src/card/core/panelRoom.ts +66 -0
  482. package/src/card/ui/CardDetailsButton.tsx +104 -0
  483. package/src/card/ui/CardFigure.tsx +86 -0
  484. package/src/card/ui/CardFrame.tsx +46 -0
  485. package/src/card/ui/CopyTableButton.tsx +81 -0
  486. package/src/card/ui/cardTooltip.ts +18 -0
  487. package/src/card/ui/embed.ts +27 -0
  488. package/src/card/ui/fitCardToFrame.ts +76 -0
  489. package/src/card/ui/holdRoomForPanels.ts +83 -0
  490. package/src/card/ui/index.ts +14 -0
  491. package/src/card/ui/openPanels.ts +106 -0
  492. package/src/card.ts +13 -0
  493. package/src/conformers.ts +4 -0
  494. package/src/core.ts +5 -0
  495. package/src/fetch/core/apiError.ts +65 -0
  496. package/src/fetch/core/fetchJson.ts +129 -0
  497. package/src/fetch/core/index.ts +4 -0
  498. package/src/formula/core/formulaText.ts +96 -0
  499. package/src/formula/core/index.ts +20 -0
  500. package/src/formula/core/normalizeFormula.ts +50 -0
  501. package/src/formula/core/readFormula.ts +211 -0
  502. package/src/formula/ui/CopyableFormula.tsx +51 -0
  503. package/src/formula/ui/FormulaInput.tsx +50 -0
  504. package/src/formula/ui/QuestionFormula.tsx +20 -0
  505. package/src/formula/ui/index.ts +6 -0
  506. package/src/formula.ts +11 -0
  507. package/src/hooks/ui/index.ts +1 -0
  508. package/src/hooks/ui/keyTargets.ts +5 -3
  509. package/src/hooks/ui/useAsync.ts +50 -14
  510. package/src/locales/de.json +37 -0
  511. package/src/locales/en.json +37 -0
  512. package/src/locales/es.json +37 -0
  513. package/src/locales/fr.json +37 -0
  514. package/src/locales/it.json +37 -0
  515. package/src/moleculesClient/core/index.ts +19 -0
  516. package/src/moleculesClient/core/moleculesClient.ts +175 -0
  517. package/src/moleculesClient/core/moleculesClientApi.ts +120 -0
  518. package/src/moleculesClient/core/moleculesTypes.ts +163 -0
  519. package/src/moleculesClient/core/requestRetry.ts +118 -0
  520. package/src/molstar/core/camera.ts +3 -2
  521. package/src/molstar/core/framing.ts +200 -0
  522. package/src/molstar/core/hover.ts +26 -0
  523. package/src/molstar/core/index.ts +21 -1
  524. package/src/pedagogy/core/answers.ts +94 -0
  525. package/src/pedagogy/core/attempts.ts +205 -0
  526. package/src/pedagogy/core/index.ts +36 -0
  527. package/src/pedagogy/core/progressController.ts +116 -0
  528. package/src/pedagogy/core/seed.ts +177 -0
  529. package/src/pedagogy/core/series.ts +82 -0
  530. package/src/pedagogy/ui/AnswerFields.tsx +63 -0
  531. package/src/pedagogy/ui/ExerciseList.tsx +202 -0
  532. package/src/pedagogy/ui/ExerciseSeries.tsx +250 -0
  533. package/src/pedagogy/ui/QuestionCard.tsx +231 -0
  534. package/src/pedagogy/ui/index.ts +18 -0
  535. package/src/pedagogy/ui/seriesCounts.ts +34 -0
  536. package/src/pedagogy/ui/seriesParts.tsx +127 -0
  537. package/src/pedagogy/ui/useAttemptRecords.ts +16 -0
  538. package/src/periodic/ui/PeriodicTable.tsx +6 -3
  539. package/src/property/core/copyableText.ts +20 -0
  540. package/src/property/core/index.ts +1 -0
  541. package/src/property/ui/PropertyTable.tsx +83 -0
  542. package/src/property/ui/index.ts +2 -0
  543. package/src/router/core/history.ts +61 -7
  544. package/src/router/core/index.ts +3 -2
  545. package/src/router/core/languageRoute.ts +136 -0
  546. package/src/router/core/siteAddresses.ts +38 -0
  547. package/src/router/core/tabRouter.ts +59 -117
  548. package/src/router/core/tabRouterTypes.ts +158 -0
  549. package/src/seo/core/fitDescription.ts +74 -0
  550. package/src/seo/core/index.ts +17 -0
  551. package/src/seo/core/limits.ts +27 -0
  552. package/src/seo/core/pageContent.ts +42 -0
  553. package/src/seo/core/plainDescription.ts +4 -2
  554. package/src/seo/core/routeProblems.ts +1 -7
  555. package/src/seo/core/routeTable.ts +103 -0
  556. package/src/seo/vite/index.ts +2 -0
  557. package/src/seo/vite/prerender.ts +26 -1
  558. package/src/seo/vite/routeTable.ts +79 -0
  559. package/src/server/answerAddresses.ts +92 -0
  560. package/src/server/cardMeta.ts +88 -0
  561. package/src/server/config.ts +125 -0
  562. package/src/server/envNumber.ts +65 -0
  563. package/src/server/healthRoute.ts +42 -0
  564. package/src/server/listenSite.ts +90 -0
  565. package/src/server/mcpServer.ts +14 -0
  566. package/src/server/repositoryVersion.ts +33 -0
  567. package/src/server/routeOptions.ts +26 -0
  568. package/src/server/routeTable.ts +35 -0
  569. package/src/server/siteView.ts +42 -0
  570. package/src/server/spaPages.ts +179 -0
  571. package/src/server/spaSite.ts +216 -0
  572. package/src/server/spaSiteOptions.ts +133 -0
  573. package/src/server/staticSite.ts +65 -35
  574. package/src/server/toolSiteServer.ts +127 -0
  575. package/src/server/trustProxy.ts +32 -0
  576. package/src/server/types.ts +25 -0
  577. package/src/server.ts +40 -0
  578. package/src/share/core/config.ts +45 -2
  579. package/src/share/core/index.ts +4 -0
  580. package/src/share/core/keySetParam.ts +92 -0
  581. package/src/share/core/vocabulary.ts +8 -0
  582. package/src/shell/ui/ShareControl.tsx +150 -0
  583. package/src/shell/ui/ShellFrame.tsx +180 -0
  584. package/src/shell/ui/SiteShell.tsx +297 -0
  585. package/src/shell/ui/index.ts +9 -0
  586. package/src/state/core/index.ts +1 -0
  587. package/src/state/core/legacyEntries.ts +76 -0
  588. package/src/state/core/persistBucket.ts +50 -10
  589. package/src/state/core/persistSignalBucket.ts +52 -0
  590. package/src/state/core/storageEntry.ts +32 -0
  591. package/src/structure/ui/StructureExportButton.tsx +57 -0
  592. package/src/structure/ui/index.ts +4 -0
  593. package/src/structure/ui/useStructureDraft.ts +47 -0
  594. package/src/ui.ts +2 -0
  595. package/src/worker/core/index.ts +1 -0
  596. package/src/worker/core/messages.ts +4 -3
  597. package/src/worker/core/oclResourcesUrl.ts +21 -0
  598. package/src/worker/core/serveWorkerRequests.ts +30 -2
  599. package/src/worker/core/workerChannel.ts +74 -74
  600. package/src/worker/core/workerChannelParts.ts +33 -13
  601. package/src/worker/core/workerChannelTypes.ts +47 -2
  602. package/src/worker/core/workerSlot.ts +181 -0
  603. package/styles/card.css +145 -0
  604. package/styles/chrome.css +248 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * What a click on a value of a property table copies.
3
+ */
4
+
5
+ /** What a cell shows when there is no value for it. */
6
+ export const NOT_AVAILABLE = '—';
7
+
8
+ /**
9
+ * The text a reader pastes for a value the page shows, or `null` when there is
10
+ * nothing to take: a value the data does not hold, or one drawn from elements
11
+ * rather than written as text, which names its own copy text instead.
12
+ * @param value - What is on screen.
13
+ * @returns The clipboard text, or null.
14
+ */
15
+ export function copyableText(value: unknown): string | null {
16
+ if (typeof value === 'number') return String(value);
17
+ if (typeof value !== 'string') return null;
18
+ const text = value.trim();
19
+ return text === '' || text === NOT_AVAILABLE ? null : text;
20
+ }
@@ -0,0 +1 @@
1
+ export { NOT_AVAILABLE, copyableText } from './copyableText.ts';
@@ -0,0 +1,83 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+
3
+ import { ClickToCopy } from '../../clipboard/ui/ClickToCopy.tsx';
4
+ import { joinClassNames } from '../../shared/ui/joinClassNames.ts';
5
+ import { copyableText } from '../core/copyableText.ts';
6
+
7
+ /** One row of a {@link PropertyTable}. */
8
+ export interface PropertyRow {
9
+ /** What the row is called. */
10
+ label: string;
11
+ /** The value, already formatted: text, a number, or a drawn element. */
12
+ value: ReactNode;
13
+ /**
14
+ * What the number means, when the name alone does not say.
15
+ * @default undefined
16
+ */
17
+ note?: string;
18
+ /**
19
+ * What a click on the value copies, when the value is drawn rather than
20
+ * written: a formula drawn by `<MF>` copies its plain string.
21
+ * @default the value itself when it is text or a number, nothing otherwise
22
+ */
23
+ copy?: string;
24
+ }
25
+
26
+ /** What {@link PropertyTable} needs. */
27
+ export interface PropertyTableProps {
28
+ /**
29
+ * The heading over the group.
30
+ * @default undefined — no heading
31
+ */
32
+ title?: string;
33
+ /** The rows, in the order they are read. */
34
+ rows: readonly PropertyRow[];
35
+ /**
36
+ * Class the group carries, in addition to `properties`.
37
+ * @default undefined
38
+ */
39
+ className?: string;
40
+ }
41
+
42
+ /**
43
+ * One group of properties as a two-column table, each value its own copy
44
+ * target: a mass, a formula or an identifier is taken away with one click, and
45
+ * a row with no value is drawn as a plain cell.
46
+ * @param props - The heading and the rows.
47
+ * @returns The group.
48
+ */
49
+ export function PropertyTable(props: PropertyTableProps): ReactElement {
50
+ const { title, rows, className } = props;
51
+ return (
52
+ <section className={joinClassNames('properties', className)}>
53
+ {title === undefined ? null : (
54
+ <h3 className="properties__title">{title}</h3>
55
+ )}
56
+ <table className="properties__table">
57
+ <tbody>
58
+ {rows.map((row) => {
59
+ const copy = row.copy ?? copyableText(row.value);
60
+ return (
61
+ <tr key={row.label}>
62
+ <th scope="row">
63
+ {row.label}
64
+ {row.note === undefined ? null : (
65
+ <span className="properties__note">{row.note}</span>
66
+ )}
67
+ </th>
68
+ <ClickToCopy
69
+ as="td"
70
+ value={copy ?? ''}
71
+ label={row.label}
72
+ disabled={copy === null}
73
+ >
74
+ {row.value}
75
+ </ClickToCopy>
76
+ </tr>
77
+ );
78
+ })}
79
+ </tbody>
80
+ </table>
81
+ </section>
82
+ );
83
+ }
@@ -0,0 +1,2 @@
1
+ export type { PropertyRow, PropertyTableProps } from './PropertyTable.tsx';
2
+ export { PropertyTable } from './PropertyTable.tsx';
@@ -8,8 +8,10 @@
8
8
  * script can import a module that uses it.
9
9
  */
10
10
 
11
+ import type { Language } from '../../i18n/core/languages.ts';
11
12
  import { EMBED_PARAM, HIDE_PARAM } from '../../share/core/config.ts';
12
13
 
14
+ import { stripBasePath } from './basePath.ts';
13
15
  import { pageHistory, pageLocation } from './pageAddress.ts';
14
16
  import type { QueryEntry } from './query.ts';
15
17
  import { formatQueryEntries } from './query.ts';
@@ -26,7 +28,10 @@ export const ROUTE_CHANGE_EVENT = 'cheminfo:routechange';
26
28
  *
27
29
  * A link written before the site routed by path — `/#/tutorial/3` — is replaced
28
30
  * in place, once, by the address the router writes for it, when the router
29
- * adopts such links.
31
+ * adopts such links. So is a `?lang=` a sibling site handed the language over
32
+ * in, on a router given `languages`: the language moves into the path, so one
33
+ * reader's bookmark and the next one's are the same address. The language of a
34
+ * prefixed address never changes: `/fr/tutorial?lang=de` stays French.
30
35
  * @param router - The site's router.
31
36
  * @returns The route of the address on screen, or of `/` where there is no browser.
32
37
  */
@@ -34,7 +39,8 @@ export function readRoute<Tab extends string>(
34
39
  router: TabRouter<Tab>,
35
40
  ): TabRoute<Tab> {
36
41
  const address = currentAddress();
37
- const adopted = router.legacyAddress(address);
42
+ const legacy = router.legacyAddress(address);
43
+ const adopted = router.languageAddress?.(legacy ?? address) ?? legacy;
38
44
  if (adopted === null) return router.parse(address);
39
45
  const history = pageHistory();
40
46
  history?.replaceState(history.state, '', adopted);
@@ -42,6 +48,39 @@ export function readRoute<Tab extends string>(
42
48
  return router.parse(adopted);
43
49
  }
44
50
 
51
+ /**
52
+ * Read the route the site opens on, the reader's own language applied to a
53
+ * bare root.
54
+ *
55
+ * A bare `/` — no page, no query, no fragment — names no language, so the one
56
+ * the reader last chose may decide which front page opens, and the address is
57
+ * rewritten in place to say it. Nothing else is steered: a deep link opens what
58
+ * it names for everybody, so `/tutorial` handed out in a course is English and
59
+ * `/fr/tutorial` French, whatever either reader last read.
60
+ * @param router - The site's router, given `languages`.
61
+ * @param preferred - The language the reader last chose; `undefined`, or one
62
+ * the router does not write, steers nothing.
63
+ * @returns The route to open on.
64
+ */
65
+ export function openingRoute<Tab extends string>(
66
+ router: TabRouter<Tab>,
67
+ preferred: Language | undefined,
68
+ ): TabRoute<Tab> {
69
+ const bare = isBareRoot(router);
70
+ const route = readRoute(router);
71
+ const steered =
72
+ bare &&
73
+ preferred !== undefined &&
74
+ preferred !== route.language &&
75
+ router.languages?.includes(preferred) === true;
76
+ if (!steered) return route;
77
+ const address = router.format({ ...route, language: preferred });
78
+ const history = pageHistory();
79
+ history?.replaceState(history.state, '', address);
80
+ globalThis.dispatchEvent?.(new Event(ROUTE_CHANGE_EVENT));
81
+ return router.parse(address);
82
+ }
83
+
45
84
  /** How {@link writeRoute} records a move. */
46
85
  export interface WriteRouteOptions {
47
86
  /**
@@ -66,8 +105,10 @@ export interface WriteRouteOptions {
66
105
  * The share parameters of the address on screen, and the preference names in
67
106
  * `keep`, survive the move, so an embedded page stays embedded after its first
68
107
  * click. A parameter the route names itself — even as `null` or `undefined`, to
69
- * remove it — is the route's. Writing the address already on screen does
70
- * nothing, so echoing state back into the address cannot loop.
108
+ * remove it — is the route's. On a router given `languages`, a route naming no
109
+ * language keeps the one on screen, and changing language counts as changing
110
+ * tab. Writing the address already on screen does nothing, so echoing state
111
+ * back into the address cannot loop.
71
112
  * @param router - The site's router.
72
113
  * @param route - Where to go.
73
114
  * @param options - How the move is recorded and which parameters survive it.
@@ -91,7 +132,9 @@ export function writeRoute<Tab extends string>(
91
132
  if (shown === address) return address;
92
133
 
93
134
  const mode = options.history ?? 'auto';
94
- const sameTab = router.parse(current).tab === router.parse(address).tab;
135
+ const from = router.parse(current);
136
+ const to = router.parse(address);
137
+ const sameTab = from.tab === to.tab && from.language === to.language;
95
138
  if (mode === 'replace' || (mode === 'auto' && sameTab)) {
96
139
  history.replaceState(history.state, '', address);
97
140
  } else {
@@ -139,9 +182,13 @@ function withKeptParams<Tab extends string>(
139
182
  current: string,
140
183
  keep: readonly string[],
141
184
  ): string {
142
- const target = router.format(route);
185
+ const onScreen = router.parse(current);
186
+ const language = route.language ?? onScreen.language;
187
+ const target = router.format(
188
+ language === undefined ? route : { ...route, language },
189
+ );
143
190
  const named = route.params ?? {};
144
- const shown = router.parse(current).params;
191
+ const shown = onScreen.params;
145
192
  const kept: QueryEntry[] = [];
146
193
  for (const key of new Set([EMBED_PARAM, HIDE_PARAM, ...keep])) {
147
194
  if (Object.hasOwn(named, key)) continue;
@@ -152,3 +199,10 @@ function withKeptParams<Tab extends string>(
152
199
  const separator = target.includes('?') ? '&' : '?';
153
200
  return `${target}${separator}${formatQueryEntries(kept)}`;
154
201
  }
202
+
203
+ function isBareRoot<Tab extends string>(router: TabRouter<Tab>): boolean {
204
+ const location = pageLocation();
205
+ if (location === undefined) return false;
206
+ if (location.search !== '' || location.hash !== '') return false;
207
+ return stripBasePath(router.basePath ?? '', location.pathname) === '/';
208
+ }
@@ -9,6 +9,7 @@ export {
9
9
  export type { WriteRouteOptions } from './history.ts';
10
10
  export {
11
11
  ROUTE_CHANGE_EVENT,
12
+ openingRoute,
12
13
  readRoute,
13
14
  subscribeToRoute,
14
15
  writeRoute,
@@ -35,12 +36,12 @@ export {
35
36
  parseQueryString,
36
37
  } from './query.ts';
37
38
  export type { SiteAddresses, SiteAddressesOptions } from './siteAddresses.ts';
38
- export { createSiteAddresses } from './siteAddresses.ts';
39
+ export { anchorHref, createSiteAddresses } from './siteAddresses.ts';
39
40
  export type {
40
41
  TabDefinition,
41
42
  TabRoute,
42
43
  TabRouteInput,
43
44
  TabRouter,
44
45
  TabRouterOptions,
45
- } from './tabRouter.ts';
46
+ } from './tabRouterTypes.ts';
46
47
  export { createTabRouter } from './tabRouter.ts';
@@ -0,0 +1,136 @@
1
+ /**
2
+ * The language half of a translated site's routes: the prefix a page is
3
+ * written under, and the `?lang=` a sibling site hands the language over in.
4
+ *
5
+ * Inside a translated site the language is the path — `/fr/tutorial` — so each
6
+ * language is an address of its own a crawler indexes and a course hands out.
7
+ * Between sites it travels as a parameter, because the site being linked to
8
+ * may not speak it, and a parameter it does not know is ignored where a prefix
9
+ * it does not have is a 404. So a route reads the parameter only where the
10
+ * prefix says nothing, and the prefix always wins: `/fr/tutorial?lang=de` is
11
+ * the French tutorial for everybody.
12
+ */
13
+
14
+ import type { Language } from '../../i18n/core/languages.ts';
15
+ import { DEFAULT_LANGUAGE } from '../../i18n/core/languages.ts';
16
+ import { LANGUAGE_PARAM } from '../../language/core/languageParam.ts';
17
+ import {
18
+ readLanguagePath,
19
+ withLanguagePath,
20
+ } from '../../language/core/languagePath.ts';
21
+
22
+ import { splitAddress } from './address.ts';
23
+ import { joinBasePath, stripBasePath } from './basePath.ts';
24
+ import type { QueryEntry, QueryStringOptions } from './query.ts';
25
+ import { formatQueryEntries, parseQueryEntries } from './query.ts';
26
+
27
+ /** The language a route is read in, and what is left of its address. */
28
+ export interface RouteLanguage {
29
+ /** The language the page is read in. */
30
+ language: Language;
31
+ /** The path from the site's own root, the prefix taken off. */
32
+ path: string;
33
+ /** The parameters, without a `lang` that names a language of the site. */
34
+ params: Record<string, string>;
35
+ }
36
+
37
+ /**
38
+ * Read the language of a route off its path and its parameters.
39
+ * @param path - The path from the site's own root, the mount already off.
40
+ * @param params - The decoded query parameters.
41
+ * @param languages - The languages the site is written in.
42
+ * @returns The language, the path without its prefix, and the parameters.
43
+ */
44
+ export function readRouteLanguage(
45
+ path: string,
46
+ params: Record<string, string>,
47
+ languages: readonly Language[],
48
+ ): RouteLanguage {
49
+ const read = readLanguagePath(path, languages);
50
+ const named = siteLanguage(params[LANGUAGE_PARAM], languages);
51
+ if (named === undefined) return { ...read, params };
52
+ const kept: Record<string, string> = {};
53
+ for (const [key, value] of Object.entries(params)) {
54
+ if (key !== LANGUAGE_PARAM) kept[key] = value;
55
+ }
56
+ const language = read.language === DEFAULT_LANGUAGE ? named : read.language;
57
+ return { language, path: read.path, params: kept };
58
+ }
59
+
60
+ /**
61
+ * Write a path from the site's own root under a language's prefix.
62
+ * @param language - The language asked for, if any.
63
+ * @param path - The path from the site's own root.
64
+ * @param languages - The languages the site is written in.
65
+ * @returns The path under the prefix; a language the site does not speak is
66
+ * written as the default one, with no prefix.
67
+ */
68
+ export function writeRouteLanguage(
69
+ language: Language | undefined,
70
+ path: string,
71
+ languages: readonly Language[],
72
+ ): string {
73
+ const written =
74
+ language !== undefined && languages.includes(language)
75
+ ? language
76
+ : DEFAULT_LANGUAGE;
77
+ return withLanguagePath(written, path);
78
+ }
79
+
80
+ /** How {@link adoptLanguageParam} reads an address. */
81
+ export interface AdoptLanguageOptions {
82
+ /** The mount path, normalised. */
83
+ basePath: string;
84
+ /** The languages the site is written in. */
85
+ languages: readonly Language[];
86
+ /** How the query string is read and written back. */
87
+ queryOptions: QueryStringOptions;
88
+ }
89
+
90
+ /**
91
+ * The address to put in place of one carrying a `?lang=` the site speaks.
92
+ *
93
+ * Only the language moves: the page named stays the page written, whether the
94
+ * router knows it or not, and every other parameter and the fragment are kept.
95
+ * @param address - A browser address: mount, prefix, page, query and fragment.
96
+ * @param options - The mount, the languages and how the query is read.
97
+ * @returns The address with the language in its path, or `null` when the
98
+ * address names no language of the site in its query.
99
+ */
100
+ export function adoptLanguageParam(
101
+ address: string,
102
+ options: AdoptLanguageOptions,
103
+ ): string | null {
104
+ const { basePath, languages, queryOptions } = options;
105
+ const { path, search, fragment } = splitAddress(address);
106
+ const entries = parseQueryEntries(search, queryOptions);
107
+ let named: Language | undefined;
108
+ const kept: QueryEntry[] = [];
109
+ for (const entry of entries) {
110
+ const language =
111
+ entry[0] === LANGUAGE_PARAM
112
+ ? siteLanguage(entry[1], languages)
113
+ : undefined;
114
+ if (language === undefined) kept.push(entry);
115
+ else named ??= language;
116
+ }
117
+ if (named === undefined) return null;
118
+
119
+ const read = readLanguagePath(stripBasePath(basePath, path), languages);
120
+ const language = read.language === DEFAULT_LANGUAGE ? named : read.language;
121
+ const opened = joinBasePath(basePath, withLanguagePath(language, read.path));
122
+ const query = formatQueryEntries(kept);
123
+ const withQuery = query === '' ? opened : `${opened}?${query}`;
124
+ return fragment === '' ? withQuery : `${withQuery}#${fragment}`;
125
+ }
126
+
127
+ function siteLanguage(
128
+ value: string | undefined,
129
+ languages: readonly Language[],
130
+ ): Language | undefined {
131
+ if (value === undefined) return undefined;
132
+ for (const language of languages) {
133
+ if (language === value) return language;
134
+ }
135
+ return undefined;
136
+ }
@@ -52,6 +52,11 @@ export function createSiteAddresses(
52
52
  return environment || ownUrl;
53
53
  }
54
54
 
55
+ function storedAssetSrc(url: string): string {
56
+ if (OWN_ORIGIN.test(url) || url.startsWith('//')) return url;
57
+ return url.startsWith('/') ? withBase(url) : url;
58
+ }
59
+
55
60
  return {
56
61
  basePath,
57
62
  siteName: siteDisplayName(site),
@@ -60,9 +65,31 @@ export function createSiteAddresses(
60
65
  pathWithoutBase,
61
66
  absoluteUrl,
62
67
  configuredSiteUrl,
68
+ anchorHref,
69
+ storedAssetSrc,
63
70
  };
64
71
  }
65
72
 
73
+ /**
74
+ * A link to an anchor on the page being read.
75
+ *
76
+ * Every page of ours carries a `<base>`, so a bare `#id` resolves against the
77
+ * mount rather than against the address open, and clicking it leaves the page
78
+ * instead of scrolling down it. The link names the page's own address and its
79
+ * query instead — the browser's, since that is the document a click scrolls.
80
+ * @param id - The `id` of the element to jump to.
81
+ * @returns The address of that anchor on the page on screen, or `#id` where
82
+ * there is no page.
83
+ */
84
+ export function anchorHref(id: string): string {
85
+ const location = globalThis.location as Location | undefined;
86
+ if (location === undefined) return `#${id}`;
87
+ return `${location.pathname}${location.search}#${id}`;
88
+ }
89
+
90
+ // A scheme names its own origin: `https:`, `data:`, `blob:`.
91
+ const OWN_ORIGIN = /^[a-z][\w+.-]*:/i;
92
+
66
93
  /** How {@link createSiteAddresses} reads the mount. */
67
94
  export interface SiteAddressesOptions {
68
95
  /**
@@ -95,4 +122,15 @@ export interface SiteAddresses {
95
122
  * it says something, the site's own address otherwise.
96
123
  */
97
124
  configuredSiteUrl: () => string;
125
+ /** A link to an anchor on the page on screen; see {@link anchorHref}. */
126
+ anchorHref: (id: string) => string;
127
+ /**
128
+ * The address of something a backend stored and the page shows — an
129
+ * uploaded picture — as this deployment serves it. A backend names those
130
+ * from its own root, `/uploads/<name>`, because a proxy mounting the site
131
+ * under a path strips the path before the request reaches it; so a
132
+ * root-relative address is put under the mount, and one carrying its own
133
+ * scheme or origin, or relative already, is handed back untouched.
134
+ */
135
+ storedAssetSrc: (url: string) => string;
98
136
  }
@@ -1,8 +1,27 @@
1
1
  import { splitAddress, splitPath, trimTrailingSlash } from './address.ts';
2
2
  import { joinBasePath, normalizeBasePath, stripBasePath } from './basePath.ts';
3
+ import {
4
+ adoptLanguageParam,
5
+ readRouteLanguage,
6
+ writeRouteLanguage,
7
+ } from './languageRoute.ts';
3
8
  import { adoptLegacyHashAddress, pathFromLegacyHash } from './legacyHash.ts';
4
9
  import type { QueryStringOptions } from './query.ts';
5
10
  import { formatQueryString, parseQueryString } from './query.ts';
11
+ import type {
12
+ TabRoute,
13
+ TabRouteInput,
14
+ TabRouter,
15
+ TabRouterOptions,
16
+ } from './tabRouterTypes.ts';
17
+
18
+ export type {
19
+ TabDefinition,
20
+ TabRoute,
21
+ TabRouteInput,
22
+ TabRouter,
23
+ TabRouterOptions,
24
+ } from './tabRouterTypes.ts';
6
25
 
7
26
  /**
8
27
  * The two directions between an address and the page it names.
@@ -20,6 +39,7 @@ export function createTabRouter<Tab extends string>(
20
39
  const mode = options.mode ?? 'path';
21
40
  const basePath = normalizeBasePath(options.basePath ?? '');
22
41
  const adoptLegacyHash = options.adoptLegacyHash ?? false;
42
+ const siteLanguages = options.languages;
23
43
  const queryOptions: QueryStringOptions = {
24
44
  literalPlus: options.literalPlus ?? true,
25
45
  keepEmptyValues: options.keepEmptyValues ?? false,
@@ -50,8 +70,17 @@ export function createTabRouter<Tab extends string>(
50
70
 
51
71
  function parse(address: string): TabRoute<Tab> {
52
72
  const parts = readAddress(address, mode, basePath, adoptLegacyHash);
53
- const params = parseQueryString(parts.search, queryOptions);
54
- const segments = splitPath(parts.path);
73
+ const query = parseQueryString(parts.search, queryOptions);
74
+ if (siteLanguages === undefined) return routeOf(parts.path, query);
75
+ const read = readRouteLanguage(parts.path, query, siteLanguages);
76
+ return { ...routeOf(read.path, read.params), language: read.language };
77
+ }
78
+
79
+ function routeOf(
80
+ path: string,
81
+ params: Record<string, string>,
82
+ ): TabRoute<Tab> {
83
+ const segments = splitPath(path);
55
84
  const first = segments[0];
56
85
  const tab = first === undefined ? undefined : tabBySegment.get(first);
57
86
  if (tab === undefined) return { tab: options.home, id: null, params };
@@ -65,8 +94,12 @@ export function createTabRouter<Tab extends string>(
65
94
  const definition = definitions.get(tab);
66
95
  const base = tab === options.home ? '/' : (definition?.path ?? '/');
67
96
  const id = definition?.takesId === true ? (route.id ?? null) : null;
68
- const path =
97
+ const page =
69
98
  id === null || base === '/' ? base : `${base}/${encodeURIComponent(id)}`;
99
+ const path =
100
+ siteLanguages === undefined
101
+ ? page
102
+ : writeRouteLanguage(route.language, page, siteLanguages);
70
103
  const search = formatQueryString(route.params ?? {}, queryOptions);
71
104
  const address = mode === 'hash' ? `#${path}` : joinBasePath(basePath, path);
72
105
  return search === '' ? address : `${address}?${search}`;
@@ -77,125 +110,34 @@ export function createTabRouter<Tab extends string>(
77
110
  const adopted = adoptLegacyHashAddress(address, { basePath });
78
111
  if (adopted === null) return null;
79
112
  const route = parse(adopted);
80
- const path = format({ tab: route.tab, id: route.id });
113
+ const path = format({
114
+ tab: route.tab,
115
+ id: route.id,
116
+ language: route.language,
117
+ });
81
118
  const { search } = splitAddress(adopted);
82
119
  return search === '' ? path : `${path}?${search}`;
83
120
  }
84
121
 
85
- return { mode, parse, format, isTab, legacyAddress };
86
- }
87
-
88
- /** A tab whose address carries more than the tab's own name. */
89
- export interface TabDefinition<Tab extends string = string> {
90
- /** Identifier of the tab, as the state and the components name it. */
91
- id: Tab;
92
- /**
93
- * The address of the tab, from the site's own root. The home tab is always
94
- * written as `/`, whatever it declares here, so the site has one address for
95
- * it instead of two holding the same thing.
96
- * @default `/${id}`
97
- */
98
- path?: string;
99
- /**
100
- * Whether the tab addresses one of its items in a second path segment — a
101
- * tutorial step, an exercise, an element — so a lecturer can hand out a deep
102
- * link and a crawler indexes each item as a page of its own.
103
- * @default false
104
- */
105
- takesId?: boolean;
106
- }
107
-
108
- /** How the addresses of a site's tabs are read and written. */
109
- export interface TabRouterOptions<Tab extends string> {
110
- /** Every tab of the site, named on its own or described in full. */
111
- tabs: ReadonlyArray<Tab | TabDefinition<Tab>>;
112
- /**
113
- * The tab living at the root, and the one an address the site does not know
114
- * opens — a link written before a rename still lands somewhere.
115
- */
116
- home: Tab;
117
- /**
118
- * Whether the address is the path or the fragment. A public tool must use
119
- * `'path'`: a `#` is dropped by half the tools that pass links around, the
120
- * server never sees it, and a crawler indexes the whole site as one page.
121
- * `'hash'` exists only so a site already routing that way can adopt this
122
- * router before it moves.
123
- * @default 'path'
124
- */
125
- mode?: 'path' | 'hash';
126
- /**
127
- * The path the site is served under: nothing on a host of its own, `/surge`
128
- * as one tool among several on a shared one.
129
- * @default ''
130
- */
131
- basePath?: string;
132
- /**
133
- * Read a `+` in a value as the character it is rather than as a space.
134
- * Without it a shared SMILES breaks.
135
- * @default true
136
- */
137
- literalPlus?: boolean;
138
- /**
139
- * Write a parameter carrying nothing back as the bare key, so a hand-typed
140
- * `?embed` survives the round trip.
141
- * @default false
142
- */
143
- keepEmptyValues?: boolean;
144
- /**
145
- * Also read an address whose page is named by an old `#/tab/item` fragment,
146
- * so links handed out before the site routed by path keep working.
147
- * @default false
148
- */
149
- adoptLegacyHash?: boolean;
150
- }
151
-
152
- /** Where the app is: which tab, what it has open, and how it is set up. */
153
- export interface TabRoute<Tab extends string = string> {
154
- /** The tab the address opens. */
155
- tab: Tab;
156
- /** The item its second segment names, `null` on a tab that takes none. */
157
- id: string | null;
158
- /** Decoded query parameters, the tool's own and the share configuration's. */
159
- params: Record<string, string>;
160
- }
161
-
162
- /** A route as a caller writes it, the parts it does not carry left out. */
163
- export interface TabRouteInput<Tab extends string = string> {
164
- /** The tab to open; one the router does not know falls back to the home tab. */
165
- tab: Tab;
166
- /**
167
- * The item the tab has open, ignored by a tab that addresses none.
168
- * @default null
169
- */
170
- id?: string | null;
171
- /**
172
- * Query parameters to carry; an empty, `undefined` or `null` value is left out.
173
- * @default {}
174
- */
175
- params?: Record<string, string | undefined | null>;
176
- }
122
+ function languageAddress(address: string): string | null {
123
+ if (mode !== 'path' || siteLanguages === undefined) return null;
124
+ return adoptLanguageParam(address, {
125
+ basePath,
126
+ languages: siteLanguages,
127
+ queryOptions,
128
+ });
129
+ }
177
130
 
178
- /** The string ↔ route mapping of a site. */
179
- export interface TabRouter<Tab extends string = string> {
180
- /** Whether the route lives in the path or in the fragment. */
181
- mode: 'path' | 'hash';
182
- /**
183
- * The route an address denotes. An unknown tab, a trailing slash, an empty
184
- * address and an escape that does not decode all resolve to something
185
- * sensible rather than throwing.
186
- */
187
- parse: (address: string) => TabRoute<Tab>;
188
- /** The address a route is reachable at, mount path and query included. */
189
- format: (route: TabRouteInput<Tab>) => string;
190
- /** Whether a string read out of an address names one of the site's tabs. */
191
- isTab: (value: string) => value is Tab;
192
- /**
193
- * The address to put in the bar in place of a link written before the site
194
- * routed by path: the page it names as this router writes it, with the query
195
- * string kept as it was. `null` when the address is no such link, or when the
196
- * router does not adopt them.
197
- */
198
- legacyAddress: (address: string) => string | null;
131
+ return {
132
+ mode,
133
+ parse,
134
+ format,
135
+ isTab,
136
+ legacyAddress,
137
+ languageAddress,
138
+ basePath,
139
+ languages: siteLanguages,
140
+ };
199
141
  }
200
142
 
201
143
  interface NormalizedTab {