@backmatter/studio 0.0.1

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 (439) hide show
  1. package/dist/chunks/UiKitPage-CgNMOqfE.js +172 -0
  2. package/dist/chunks/WorkspaceRoutes-DgRtdie8.js +87297 -0
  3. package/dist/chunks/abap-Y8Dl9g_6.js +6 -0
  4. package/dist/chunks/actionscript-3-DQVEcJUW.js +6 -0
  5. package/dist/chunks/ada-vP6ak0IW.js +6 -0
  6. package/dist/chunks/andromeeda-BbmzSJq1.js +4 -0
  7. package/dist/chunks/angular-html-DPB4Rf0C.js +32 -0
  8. package/dist/chunks/angular-ts-CTOR2ofC.js +21 -0
  9. package/dist/chunks/apache-BUjz-sD2.js +6 -0
  10. package/dist/chunks/apex-OyTfjCYK.js +6 -0
  11. package/dist/chunks/apl-CjNiDgad.js +16 -0
  12. package/dist/chunks/applescript-BPx7YFFu.js +6 -0
  13. package/dist/chunks/ara-Z2fSOxSw.js +6 -0
  14. package/dist/chunks/asciidoc-C1T9ziH6.js +6 -0
  15. package/dist/chunks/asm-BTWLY5ym.js +6 -0
  16. package/dist/chunks/astro-CFFExE8Q.js +18 -0
  17. package/dist/chunks/aurora-x-BwoVEUWZ.js +4 -0
  18. package/dist/chunks/awk-Fb0P9dkn.js +6 -0
  19. package/dist/chunks/ayu-dark-DJoqd4M9.js +4 -0
  20. package/dist/chunks/ayu-light-BzXEJRJ-.js +4 -0
  21. package/dist/chunks/ayu-mirage-BJ2oZGzi.js +4 -0
  22. package/dist/chunks/ballerina-oZK-YekG.js +6 -0
  23. package/dist/chunks/bat-0FvbqU9S.js +6 -0
  24. package/dist/chunks/beancount-DqJEb89h.js +6 -0
  25. package/dist/chunks/berry-DODBq_Ff.js +6 -0
  26. package/dist/chunks/bibtex-EULQRLY5.js +6 -0
  27. package/dist/chunks/bicep-BorU73w0.js +6 -0
  28. package/dist/chunks/bird2-CIjBSNj3.js +6 -0
  29. package/dist/chunks/blade-D0HyNvCw.js +20 -0
  30. package/dist/chunks/bsl-NkNNwerW.js +8 -0
  31. package/dist/chunks/c-eeMepfLm.js +6 -0
  32. package/dist/chunks/c3-BY5XW5ym.js +6 -0
  33. package/dist/chunks/cadence-02UX7mW8.js +6 -0
  34. package/dist/chunks/cairo-DM6WF2e3.js +8 -0
  35. package/dist/chunks/catppuccin-frappe-D3cH2rXe.js +4 -0
  36. package/dist/chunks/catppuccin-latte-C0LRGUW4.js +4 -0
  37. package/dist/chunks/catppuccin-macchiato-c5wQ11TT.js +4 -0
  38. package/dist/chunks/catppuccin-mocha-WMD6Qvya.js +4 -0
  39. package/dist/chunks/clarity-PKm5CwqM.js +6 -0
  40. package/dist/chunks/clojure-CXJfHrL3.js +6 -0
  41. package/dist/chunks/cmake-BJz8BOTU.js +6 -0
  42. package/dist/chunks/cobol-HihOMwht.js +10 -0
  43. package/dist/chunks/codeowners-Bt9yU6NX.js +6 -0
  44. package/dist/chunks/codeql-DHkodjjI.js +6 -0
  45. package/dist/chunks/coffee-DGBR_nr_.js +8 -0
  46. package/dist/chunks/common-lisp-EVqT9Zhp.js +6 -0
  47. package/dist/chunks/coq-B0L9upzn.js +6 -0
  48. package/dist/chunks/cpp-Cj177cuW.js +18 -0
  49. package/dist/chunks/crystal-CRJyc4G-.js +18 -0
  50. package/dist/chunks/csharp-CCgA4A09.js +6 -0
  51. package/dist/chunks/css-CECN5uSL.js +6 -0
  52. package/dist/chunks/csv-CmYOceLb.js +6 -0
  53. package/dist/chunks/cue-ZzumE7IT.js +6 -0
  54. package/dist/chunks/cypher-jpdmjtA6.js +6 -0
  55. package/dist/chunks/d-CBagWSwH.js +6 -0
  56. package/dist/chunks/dark-plus-pUHDTVV0.js +4 -0
  57. package/dist/chunks/dart-6ObCrKr9.js +6 -0
  58. package/dist/chunks/dashboard-XKKZuJGm.js +1736 -0
  59. package/dist/chunks/dax-CcDT-8dH.js +6 -0
  60. package/dist/chunks/define-studio-BYtYDGLX.js +33 -0
  61. package/dist/chunks/desktop-B93p9R9O.js +6 -0
  62. package/dist/chunks/diff-BxzP2J8R.js +6 -0
  63. package/dist/chunks/docker-CsHqm9tx.js +6 -0
  64. package/dist/chunks/dotenv-BMjVjUL1.js +6 -0
  65. package/dist/chunks/dracula-BtZx2Kac.js +4 -0
  66. package/dist/chunks/dracula-soft-BKa-aqBv.js +4 -0
  67. package/dist/chunks/dream-maker-PAx17jaB.js +6 -0
  68. package/dist/chunks/edge-BYQkOzbF.js +12 -0
  69. package/dist/chunks/elixir-Dh036GMp.js +8 -0
  70. package/dist/chunks/elm-BtNbw_Cd.js +8 -0
  71. package/dist/chunks/emacs-lisp-Cfxdx5D-.js +6 -0
  72. package/dist/chunks/erb-ScVOXVpH.js +10 -0
  73. package/dist/chunks/erlang-DNcDT8Hi.js +8 -0
  74. package/dist/chunks/everforest-dark-DMCBqXCK.js +4 -0
  75. package/dist/chunks/everforest-light-BbXl82Em.js +4 -0
  76. package/dist/chunks/fennel-N4WcXuKx.js +6 -0
  77. package/dist/chunks/fish-BTDEUgqH.js +6 -0
  78. package/dist/chunks/fluent-BMXUxfv1.js +6 -0
  79. package/dist/chunks/fortran-fixed-form-B67KFkXH.js +8 -0
  80. package/dist/chunks/fortran-free-form-CwqYvCLJ.js +6 -0
  81. package/dist/chunks/fsharp-CYOiIVNx.js +8 -0
  82. package/dist/chunks/gdresource-CvCIUjhi.js +10 -0
  83. package/dist/chunks/gdscript-DSAxJ_aS.js +6 -0
  84. package/dist/chunks/gdshader-Bk8fF6yr.js +6 -0
  85. package/dist/chunks/genie-DzUvd7U9.js +6 -0
  86. package/dist/chunks/gherkin-DHRaV0YP.js +6 -0
  87. package/dist/chunks/git-commit-Bd32YZ0K.js +8 -0
  88. package/dist/chunks/git-rebase-ZWUFO_T4.js +8 -0
  89. package/dist/chunks/github-dark-DenFmJkN.js +4 -0
  90. package/dist/chunks/github-dark-default-BJPUVz4H.js +4 -0
  91. package/dist/chunks/github-dark-dimmed-DUshB20C.js +4 -0
  92. package/dist/chunks/github-dark-high-contrast-D3aGCnF8.js +4 -0
  93. package/dist/chunks/github-light-JYsPkUQd.js +4 -0
  94. package/dist/chunks/github-light-default-D99KPAby.js +4 -0
  95. package/dist/chunks/github-light-high-contrast-BbmZE-Mp.js +4 -0
  96. package/dist/chunks/gleam-Bv284lvN.js +6 -0
  97. package/dist/chunks/glimmer-js-DkxNrfPT.js +14 -0
  98. package/dist/chunks/glimmer-ts-D6YZEmK_.js +14 -0
  99. package/dist/chunks/glsl-CkUcVZNK.js +8 -0
  100. package/dist/chunks/gn-XtJxiLa4.js +6 -0
  101. package/dist/chunks/gnuplot-yPG9-sE_.js +6 -0
  102. package/dist/chunks/go-CvFNMTvu.js +6 -0
  103. package/dist/chunks/graphql-CBu2ehBn.js +14 -0
  104. package/dist/chunks/groovy-CJQTphOW.js +6 -0
  105. package/dist/chunks/gruvbox-dark-hard-C5HOtKIh.js +4 -0
  106. package/dist/chunks/gruvbox-dark-medium-FVgwJHuz.js +4 -0
  107. package/dist/chunks/gruvbox-dark-soft-B46F314v.js +4 -0
  108. package/dist/chunks/gruvbox-light-hard-CJD38wDZ.js +4 -0
  109. package/dist/chunks/gruvbox-light-medium-BlIhMYTA.js +4 -0
  110. package/dist/chunks/gruvbox-light-soft-DoPHyLVZ.js +4 -0
  111. package/dist/chunks/hack-ItRNKiEb.js +10 -0
  112. package/dist/chunks/haml-NZLVzu_R.js +10 -0
  113. package/dist/chunks/handlebars-mxStpSE7.js +14 -0
  114. package/dist/chunks/haskell-WeIwNIP6.js +6 -0
  115. package/dist/chunks/haxe-TztHsm5T.js +6 -0
  116. package/dist/chunks/hcl-D438OF-I.js +6 -0
  117. package/dist/chunks/hjson-DYBUbqOl.js +6 -0
  118. package/dist/chunks/hlsl-Bk8TCZNL.js +6 -0
  119. package/dist/chunks/horizon-CJQ10nlf.js +4 -0
  120. package/dist/chunks/horizon-bright-CdkBZOMB.js +4 -0
  121. package/dist/chunks/houston-BDYrDoDW.js +4 -0
  122. package/dist/chunks/html-B50bmoVb.js +10 -0
  123. package/dist/chunks/html-derivative-C6bxFbFW.js +8 -0
  124. package/dist/chunks/http-DLvAkeD-.js +14 -0
  125. package/dist/chunks/hurl-CgkFN90t.js +12 -0
  126. package/dist/chunks/hxml-GbqSQCLa.js +8 -0
  127. package/dist/chunks/hy-Brt5EZ7-.js +6 -0
  128. package/dist/chunks/imba-CimUv-Uh.js +6 -0
  129. package/dist/chunks/index-CFAIR8GH.js +18924 -0
  130. package/dist/chunks/ini-BZIuRIvJ.js +6 -0
  131. package/dist/chunks/java-DY6VlHhP.js +6 -0
  132. package/dist/chunks/javascript-C25yR2R2.js +6 -0
  133. package/dist/chunks/jinja-CFW5uAwY.js +11 -0
  134. package/dist/chunks/jison-DdreslRX.js +8 -0
  135. package/dist/chunks/json-DxJze_jm.js +6 -0
  136. package/dist/chunks/json5-BT4Fjg39.js +6 -0
  137. package/dist/chunks/jsonc-CHjZD8gR.js +6 -0
  138. package/dist/chunks/jsonl-BGuvDmy9.js +6 -0
  139. package/dist/chunks/jsonnet-Bx2cfsXg.js +6 -0
  140. package/dist/chunks/jssm-BcADi6EI.js +6 -0
  141. package/dist/chunks/jsx-BtKADgXT.js +6 -0
  142. package/dist/chunks/julia-CZiKXNNP.js +16 -0
  143. package/dist/chunks/just-CEK2SJ_k.js +18 -0
  144. package/dist/chunks/kanagawa-dragon-CiKur4Hl.js +4 -0
  145. package/dist/chunks/kanagawa-lotus-BKu-smKu.js +4 -0
  146. package/dist/chunks/kanagawa-wave-CQwozSzG.js +4 -0
  147. package/dist/chunks/kdl-BNOv9TC3.js +6 -0
  148. package/dist/chunks/kotlin-ByBMgTeR.js +6 -0
  149. package/dist/chunks/kusto-DH_XitHS.js +6 -0
  150. package/dist/chunks/laserwave-6a00oqik.js +4 -0
  151. package/dist/chunks/latex-B8Egmqc5.js +8 -0
  152. package/dist/chunks/lean-BnVxaZxY.js +6 -0
  153. package/dist/chunks/less-B1GLI2Di.js +6 -0
  154. package/dist/chunks/light-plus-CZuVqSLX.js +4 -0
  155. package/dist/chunks/liquid-CHzkx3jT.js +14 -0
  156. package/dist/chunks/llvm-Bk2XctFf.js +6 -0
  157. package/dist/chunks/log-Al8wyEFV.js +6 -0
  158. package/dist/chunks/logo-DBa4JDzV.js +6 -0
  159. package/dist/chunks/lua-Bp3IRv7I.js +8 -0
  160. package/dist/chunks/luau-UQyhudEE.js +6 -0
  161. package/dist/chunks/make-CsMclxtr.js +6 -0
  162. package/dist/chunks/markdown-CrScaQ96.js +6 -0
  163. package/dist/chunks/marko-CJBA24Ri.js +14 -0
  164. package/dist/chunks/material-theme-D6KBX41T.js +4 -0
  165. package/dist/chunks/material-theme-darker-CkRroheE.js +4 -0
  166. package/dist/chunks/material-theme-lighter-BUBw43Yz.js +4 -0
  167. package/dist/chunks/material-theme-ocean-ClGX14Ja.js +4 -0
  168. package/dist/chunks/material-theme-palenight-C1RVm8K1.js +4 -0
  169. package/dist/chunks/matlab-BOAaUVP0.js +6 -0
  170. package/dist/chunks/mdc-C9YSUwVC.js +12 -0
  171. package/dist/chunks/mdx-BOhZZUJ8.js +6 -0
  172. package/dist/chunks/mermaid-C3rz4dYh.js +6 -0
  173. package/dist/chunks/min-dark-C7ak0t6c.js +4 -0
  174. package/dist/chunks/min-light-CKFxVcPp.js +4 -0
  175. package/dist/chunks/mipsasm-CTx18fBl.js +6 -0
  176. package/dist/chunks/mojo-EIZIZRTQ.js +6 -0
  177. package/dist/chunks/monokai-C1KBYcO0.js +4 -0
  178. package/dist/chunks/moonbit-B1TV0fG3.js +6 -0
  179. package/dist/chunks/move-Db4ltDq1.js +6 -0
  180. package/dist/chunks/narrat-DmhDCBs-.js +6 -0
  181. package/dist/chunks/nextflow-47kAzSl1.js +8 -0
  182. package/dist/chunks/nextflow-groovy-Drz_2V0j.js +6 -0
  183. package/dist/chunks/nginx-D7O4WoB1.js +8 -0
  184. package/dist/chunks/night-owl-Bm2rzalh.js +4 -0
  185. package/dist/chunks/night-owl-light-CBI5u5kw.js +4 -0
  186. package/dist/chunks/nim-QAjRdh9w.js +20 -0
  187. package/dist/chunks/nix-fTRXMGic.js +9 -0
  188. package/dist/chunks/nord-CC5OiUXg.js +4 -0
  189. package/dist/chunks/nushell-BGCPRlV5.js +6 -0
  190. package/dist/chunks/objective-c-Itk8tzmv.js +6 -0
  191. package/dist/chunks/objective-cpp-DGt5UKRO.js +6 -0
  192. package/dist/chunks/ocaml-eSVK32Eg.js +6 -0
  193. package/dist/chunks/odin-BItnF517.js +6 -0
  194. package/dist/chunks/one-dark-pro-DTA3VF0_.js +4 -0
  195. package/dist/chunks/one-light-LkMrt1Cf.js +4 -0
  196. package/dist/chunks/openscad-Sp5uZ6f2.js +6 -0
  197. package/dist/chunks/pascal-xy8pJNns.js +6 -0
  198. package/dist/chunks/perl-DkVrgJBg.js +16 -0
  199. package/dist/chunks/php-BIzsF_5x.js +18 -0
  200. package/dist/chunks/pkl-C-zSNmaA.js +6 -0
  201. package/dist/chunks/plastic-CSTz3KZp.js +4 -0
  202. package/dist/chunks/plsql-pVbGZfOv.js +6 -0
  203. package/dist/chunks/po-BNfHvqmm.js +6 -0
  204. package/dist/chunks/poimandres-C-VADXHD.js +4 -0
  205. package/dist/chunks/polar-CAZahv3u.js +6 -0
  206. package/dist/chunks/postcss-05aHdL-n.js +6 -0
  207. package/dist/chunks/powerquery-DI9HkTvs.js +6 -0
  208. package/dist/chunks/powershell-Clc4ydu-.js +6 -0
  209. package/dist/chunks/prisma-FZjmVtSl.js +6 -0
  210. package/dist/chunks/prolog-C5-yg4TO.js +6 -0
  211. package/dist/chunks/proto-C7QgialS.js +6 -0
  212. package/dist/chunks/pug-XOw9Q1Ho.js +12 -0
  213. package/dist/chunks/puppet-CUJHmnxV.js +6 -0
  214. package/dist/chunks/purescript-rUfGld-4.js +6 -0
  215. package/dist/chunks/python-BFNSHbwJ.js +6 -0
  216. package/dist/chunks/qml-rTNXsquU.js +8 -0
  217. package/dist/chunks/qmldir-BInDYbpo.js +6 -0
  218. package/dist/chunks/qss-AeJTysr_.js +6 -0
  219. package/dist/chunks/r-CSmzDPi7.js +6 -0
  220. package/dist/chunks/racket-B83wSAja.js +6 -0
  221. package/dist/chunks/raku-nEQ4ZJJ7.js +6 -0
  222. package/dist/chunks/razor-Ci8D0dQK.js +10 -0
  223. package/dist/chunks/red-7y8PH7HH.js +4 -0
  224. package/dist/chunks/reg-m_s_Kiip.js +6 -0
  225. package/dist/chunks/regexp-BazyLpPg.js +6 -0
  226. package/dist/chunks/rel-BcRfyd6Q.js +6 -0
  227. package/dist/chunks/riscv-Ce8MAQLP.js +6 -0
  228. package/dist/chunks/ron-DCEEQypA.js +6 -0
  229. package/dist/chunks/rose-pine-BKc3yVeu.js +4 -0
  230. package/dist/chunks/rose-pine-dawn-BulJcPZT.js +4 -0
  231. package/dist/chunks/rose-pine-moon-j6jiXKV8.js +4 -0
  232. package/dist/chunks/rosmsg-Cz0w1km8.js +6 -0
  233. package/dist/chunks/rst-DM32Wy3O.js +22 -0
  234. package/dist/chunks/ruby-BeqCs27h.js +30 -0
  235. package/dist/chunks/rust-CLzF9zIN.js +6 -0
  236. package/dist/chunks/sas-fpEvgATw.js +8 -0
  237. package/dist/chunks/sass-DxHp5rTx.js +6 -0
  238. package/dist/chunks/scala-D4grkFkl.js +6 -0
  239. package/dist/chunks/scheme-BCRWuEm4.js +6 -0
  240. package/dist/chunks/scss-B1FaCqwR.js +8 -0
  241. package/dist/chunks/sdbl-B7T8abf4.js +6 -0
  242. package/dist/chunks/shaderlab-Cr62-Wb4.js +8 -0
  243. package/dist/chunks/shellscript-InADTalH.js +6 -0
  244. package/dist/chunks/shellsession-DiDJNQdy.js +8 -0
  245. package/dist/chunks/slack-dark-i7wN4OET.js +4 -0
  246. package/dist/chunks/slack-ochin-ndHf0LoP.js +4 -0
  247. package/dist/chunks/smalltalk-BlI1_OkM.js +6 -0
  248. package/dist/chunks/snazzy-light-BlSJXAu4.js +4 -0
  249. package/dist/chunks/solarized-dark-UTmkh7lw.js +4 -0
  250. package/dist/chunks/solarized-light-BheCkDPT.js +4 -0
  251. package/dist/chunks/solidity-DUWnFhS6.js +6 -0
  252. package/dist/chunks/soy-C1-6njHa.js +8 -0
  253. package/dist/chunks/sparql-B0KWFEri.js +8 -0
  254. package/dist/chunks/splunk-CRXR8A9s.js +6 -0
  255. package/dist/chunks/sql-Cn_v3PB0.js +6 -0
  256. package/dist/chunks/ssh-config-DP-hNVbF.js +6 -0
  257. package/dist/chunks/stata-B8c5fTjX.js +8 -0
  258. package/dist/chunks/stylus-CyKEU1Ej.js +6 -0
  259. package/dist/chunks/surrealql-CQi7GQN7.js +8 -0
  260. package/dist/chunks/svelte-rYri41WT.js +14 -0
  261. package/dist/chunks/swift-DJpUqPLg.js +6 -0
  262. package/dist/chunks/synthwave-84-NU3C_KFZ.js +4 -0
  263. package/dist/chunks/system-verilog-BCm7smPJ.js +6 -0
  264. package/dist/chunks/systemd-C-4qm6XH.js +6 -0
  265. package/dist/chunks/talonscript-CFF3LF_O.js +6 -0
  266. package/dist/chunks/tasl-Cg_WBUAe.js +6 -0
  267. package/dist/chunks/tcl-DN7buRTF.js +6 -0
  268. package/dist/chunks/templ-Crs9Ui8q.js +12 -0
  269. package/dist/chunks/terraform-DGvcn9zM.js +6 -0
  270. package/dist/chunks/tex-AHxmLTL0.js +8 -0
  271. package/dist/chunks/tokyo-night-DP4TmcQl.js +4 -0
  272. package/dist/chunks/toml-DY62mUL_.js +6 -0
  273. package/dist/chunks/ts-tags-ioVe2PKw.js +41 -0
  274. package/dist/chunks/tsv-BtvSkaG0.js +6 -0
  275. package/dist/chunks/tsx-B8rCNbgL.js +6 -0
  276. package/dist/chunks/turtle-_H59FV7D.js +6 -0
  277. package/dist/chunks/twig-DwV2Fi4L.js +18 -0
  278. package/dist/chunks/typescript-RycA9KXf.js +6 -0
  279. package/dist/chunks/typespec-DWqp-kUU.js +6 -0
  280. package/dist/chunks/typst-D_1QKWns.js +6 -0
  281. package/dist/chunks/v-BPCNiyYe.js +6 -0
  282. package/dist/chunks/vala-uxaPR7d1.js +6 -0
  283. package/dist/chunks/vb-D8_c5-KN.js +6 -0
  284. package/dist/chunks/verilog-B-bybjPF.js +6 -0
  285. package/dist/chunks/vesper-BckBta1U.js +4 -0
  286. package/dist/chunks/vhdl-CUlNa8ac.js +6 -0
  287. package/dist/chunks/viml-DsfA-sWm.js +6 -0
  288. package/dist/chunks/vitesse-black-BoGvW84i.js +4 -0
  289. package/dist/chunks/vitesse-dark-Cym-eLtO.js +4 -0
  290. package/dist/chunks/vitesse-light-CcmG315c.js +4 -0
  291. package/dist/chunks/vue-DXEmyXn9.js +31 -0
  292. package/dist/chunks/vue-html-j9xNEdu3.js +8 -0
  293. package/dist/chunks/vue-vine-D41-N99t.js +18 -0
  294. package/dist/chunks/vyper-CPQuu50u.js +6 -0
  295. package/dist/chunks/wasm-BBXxrAl7.js +6 -0
  296. package/dist/chunks/wasm-DQxwEHae.js +7 -0
  297. package/dist/chunks/wenyan-pbVjoM9_.js +6 -0
  298. package/dist/chunks/wgsl-DY4iK1q1.js +6 -0
  299. package/dist/chunks/wikitext-Z-MoUasO.js +6 -0
  300. package/dist/chunks/wit-CQMQOlTg.js +6 -0
  301. package/dist/chunks/wolfram-Dz4KXISs.js +6 -0
  302. package/dist/chunks/xml-C2J0sS9M.js +8 -0
  303. package/dist/chunks/xsl-BmSZphgC.js +8 -0
  304. package/dist/chunks/yaml-DaO7k5B1.js +6 -0
  305. package/dist/chunks/zenscript-CxBjpf9c.js +6 -0
  306. package/dist/chunks/zig-Vm0PO9wB.js +6 -0
  307. package/dist/dashboard.js +8 -0
  308. package/dist/define.js +4 -0
  309. package/dist/index.js +317 -0
  310. package/dist/studio.css +2272 -0
  311. package/dist/styles.js +1 -0
  312. package/package.json +88 -0
  313. package/prose-node-shims/fs.ts +6 -0
  314. package/prose-node-shims/path.ts +16 -0
  315. package/prose-node-shims/sharp.ts +4 -0
  316. package/prose-ui-node-shim.ts +26 -0
  317. package/src/App.tsx +173 -0
  318. package/src/WorkspaceRoutes.tsx +38 -0
  319. package/src/api.ts +833 -0
  320. package/src/auth-client.ts +15 -0
  321. package/src/chat/ChangeCard.tsx +330 -0
  322. package/src/chat/ChatBar.tsx +238 -0
  323. package/src/chat/ChatMessage.tsx +478 -0
  324. package/src/chat/ChatPanel.tsx +406 -0
  325. package/src/chat/ChatProvider.tsx +661 -0
  326. package/src/chat/Composer.tsx +461 -0
  327. package/src/chat/DocListCard.tsx +322 -0
  328. package/src/chat/LiveStatus.tsx +83 -0
  329. package/src/chat/MentionPopover.tsx +164 -0
  330. package/src/chat/ModelPicker.tsx +113 -0
  331. package/src/chat/Shimmer.tsx +39 -0
  332. package/src/chat/ThreadHistory.tsx +268 -0
  333. package/src/chat/chat-context.ts +41 -0
  334. package/src/chat/chat-helpers.ts +402 -0
  335. package/src/chat/chat-state.ts +59 -0
  336. package/src/chat/icons.tsx +171 -0
  337. package/src/chat/useStickToBottom.ts +110 -0
  338. package/src/chrome/AppShell.tsx +100 -0
  339. package/src/chrome/AppSidebar.tsx +305 -0
  340. package/src/chrome/CommandPalette.tsx +559 -0
  341. package/src/chrome/ContentApiPopover.tsx +215 -0
  342. package/src/chrome/EditorActionsContext.tsx +63 -0
  343. package/src/chrome/EditorFocusContext.tsx +80 -0
  344. package/src/chrome/EditorStatusContext.tsx +84 -0
  345. package/src/chrome/NavItem.tsx +75 -0
  346. package/src/chrome/OpenSiteButton.tsx +148 -0
  347. package/src/chrome/StatusBar.tsx +73 -0
  348. package/src/chrome/UserMenu.tsx +130 -0
  349. package/src/chrome/WorkspaceNavContext.tsx +60 -0
  350. package/src/chrome/WorkspaceStatusBar.tsx +940 -0
  351. package/src/chrome/content-api-url.ts +111 -0
  352. package/src/chrome/content-type-groups.ts +32 -0
  353. package/src/chrome/hotkeys.ts +79 -0
  354. package/src/chrome/icons.tsx +191 -0
  355. package/src/chrome/open-site.ts +30 -0
  356. package/src/chrome/status-bar-action.ts +24 -0
  357. package/src/components/AppChromeContext.tsx +32 -0
  358. package/src/components/Shell.tsx +18 -0
  359. package/src/components/WorkspaceSwitcher.tsx +345 -0
  360. package/src/content-blocks/index.tsx +262 -0
  361. package/src/css.d.ts +2 -0
  362. package/src/dashboard.ts +5 -0
  363. package/src/define-studio.tsx +98 -0
  364. package/src/define.ts +12 -0
  365. package/src/index.css +123 -0
  366. package/src/index.ts +19 -0
  367. package/src/last-user.ts +40 -0
  368. package/src/last-workspace.ts +57 -0
  369. package/src/mcp/McpConnectorsPanel.tsx +872 -0
  370. package/src/mcp/parse-json.ts +52 -0
  371. package/src/pages/AcceptInvitePage.tsx +99 -0
  372. package/src/pages/BlockSettingsDialog.tsx +303 -0
  373. package/src/pages/BlocksList.tsx +402 -0
  374. package/src/pages/BodyProse.tsx +236 -0
  375. package/src/pages/DocEditorPage.tsx +1394 -0
  376. package/src/pages/EmbedChatPage.tsx +110 -0
  377. package/src/pages/EmbedPublishPage.tsx +259 -0
  378. package/src/pages/EnterPage.tsx +96 -0
  379. package/src/pages/GithubConnectPage.tsx +142 -0
  380. package/src/pages/LegacyWorkspaceRedirect.tsx +57 -0
  381. package/src/pages/LoginPage.tsx +335 -0
  382. package/src/pages/NewWorkspacePage.tsx +74 -0
  383. package/src/pages/RenderedBody.tsx +598 -0
  384. package/src/pages/ResumeHome.tsx +18 -0
  385. package/src/pages/SelectionBar.tsx +277 -0
  386. package/src/pages/SettingsGeneralPage.tsx +403 -0
  387. package/src/pages/SettingsGithubPage.tsx +277 -0
  388. package/src/pages/SettingsLayout.tsx +127 -0
  389. package/src/pages/SettingsMcpPage.tsx +30 -0
  390. package/src/pages/SettingsSchemaPage.tsx +93 -0
  391. package/src/pages/SettingsUsersPage.tsx +363 -0
  392. package/src/pages/TypeDocsPage.tsx +225 -0
  393. package/src/pages/UiKitPage.tsx +331 -0
  394. package/src/pages/UserSettingsPage.tsx +94 -0
  395. package/src/pages/WorkspaceHomeRedirect.tsx +17 -0
  396. package/src/pages/WorkspaceLayout.tsx +103 -0
  397. package/src/pages/WorkspacesPage.tsx +35 -0
  398. package/src/pages/blocks-list.ts +73 -0
  399. package/src/pages/body-blocks.ts +233 -0
  400. package/src/pages/body-mdx.tsx +82 -0
  401. package/src/pages/compile-body.ts +37 -0
  402. package/src/pages/host-env.ts +22 -0
  403. package/src/pages/html-to-markdown.ts +99 -0
  404. package/src/pages/preview-bridge.ts +46 -0
  405. package/src/pages/preview-split.ts +30 -0
  406. package/src/pages/preview-url.ts +92 -0
  407. package/src/pages/selection-format.ts +179 -0
  408. package/src/pages/type-docs.ts +9 -0
  409. package/src/replica/BootstrapSplash.tsx +80 -0
  410. package/src/replica/ReplicaProvider.tsx +267 -0
  411. package/src/replica/cache.ts +30 -0
  412. package/src/replica/idb.ts +89 -0
  413. package/src/replica/instance.ts +39 -0
  414. package/src/replica/pull.ts +23 -0
  415. package/src/replica/store.ts +497 -0
  416. package/src/replica/sync-cache.ts +51 -0
  417. package/src/replica/types.ts +119 -0
  418. package/src/replica/useReplica.ts +13 -0
  419. package/src/site.tsx +230 -0
  420. package/src/studio-config.ts +81 -0
  421. package/src/styles/themes.ts +49 -0
  422. package/src/styles/tokens.stylex.ts +58 -0
  423. package/src/styles.ts +11 -0
  424. package/src/theme/AccentOverride.tsx +29 -0
  425. package/src/theme/ThemeProvider.tsx +130 -0
  426. package/src/theme/apply-theme-class.ts +12 -0
  427. package/src/ui/BackLink.tsx +28 -0
  428. package/src/ui/Button.tsx +167 -0
  429. package/src/ui/ConfirmDialog.tsx +141 -0
  430. package/src/ui/Field.tsx +45 -0
  431. package/src/ui/Input.tsx +183 -0
  432. package/src/ui/ListRow.tsx +88 -0
  433. package/src/ui/Page.tsx +151 -0
  434. package/src/ui/Text.tsx +77 -0
  435. package/src/ui/Tooltip.tsx +85 -0
  436. package/src/ui/popup.ts +216 -0
  437. package/src/workspace/permissions.ts +17 -0
  438. package/src/workspace/types.ts +12 -0
  439. package/src/workspace-path.ts +98 -0
@@ -0,0 +1,233 @@
1
+ // packages/studio/src/pages/body-blocks.ts
2
+ import { unified } from "unified";
3
+ import remarkParse from "remark-parse";
4
+ import remarkGfm from "remark-gfm";
5
+ import remarkMdx from "remark-mdx";
6
+
7
+ export type BodyBlockKind = "text" | "code" | "jsx";
8
+
9
+ export type BodyBlock = {
10
+ id: string;
11
+ markdown: string;
12
+ kind: BodyBlockKind;
13
+ lang?: string;
14
+ jsxName?: string;
15
+ };
16
+
17
+ type ParsedNode = {
18
+ type: string;
19
+ name?: string | null;
20
+ lang?: string | null;
21
+ position?: {
22
+ start: { offset?: number };
23
+ end: { offset?: number };
24
+ };
25
+ };
26
+
27
+ const parseBody = unified().use(remarkParse).use(remarkGfm).use(remarkMdx);
28
+
29
+ export function blocksFromSource(
30
+ source: string,
31
+ idFor: () => string = () => crypto.randomUUID(),
32
+ ): BodyBlock[] {
33
+ if (!source.trim()) return [];
34
+ const tree = parseBody.parse(source);
35
+ return (tree.children as ParsedNode[]).flatMap((node) => {
36
+ const block = blockFromNode(source, node, idFor());
37
+ return block ? [block] : [];
38
+ });
39
+ }
40
+
41
+ export function blockFromMarkdown(markdown: string, id: string): BodyBlock {
42
+ if (!markdown.trim()) return { id, markdown: "", kind: "text" };
43
+ return blocksFromSource(markdown, () => id)[0] ?? { id, markdown, kind: "text" };
44
+ }
45
+
46
+ export function joinBlocks(blocks: { markdown: string }[]): string {
47
+ return blocks.map((block) => block.markdown.replace(/^\n+|\n+$/g, "")).join("\n\n");
48
+ }
49
+
50
+ export function codeParts(markdown: string): { lang: string; code: string } {
51
+ const match = /^```([^\n]*)\n?([\s\S]*?)\n?```$/.exec(markdown.trim());
52
+ if (!match) return { lang: "", code: markdown };
53
+ return { lang: match[1]?.trim() ?? "", code: match[2] ?? "" };
54
+ }
55
+
56
+ export function replaceJsxChildren(source: string, name: string, text: string): string {
57
+ const match = new RegExp(`^(\\s*<${name}\\b[^>]*>)([\\s\\S]*)(</${name}>\\s*)$`).exec(
58
+ source.trim(),
59
+ );
60
+ if (!match) return source;
61
+ const body = text.trim();
62
+ if (!body) return `${match[1]}${match[3]}`;
63
+ return `${match[1]}\n\n${body}\n\n${match[3]}`;
64
+ }
65
+
66
+ type JsxAttributeValue = {
67
+ type?: string;
68
+ value?: string;
69
+ data?: {
70
+ estree?: {
71
+ body?: Array<{ expression?: { type?: string; value?: unknown } }>;
72
+ };
73
+ };
74
+ };
75
+
76
+ type JsxAttribute = {
77
+ type: string;
78
+ name?: string;
79
+ value?: string | null | JsxAttributeValue;
80
+ };
81
+
82
+ type JsxElement = ParsedNode & {
83
+ attributes?: JsxAttribute[];
84
+ children?: ParsedNode[];
85
+ };
86
+
87
+ export function propsFromJsx(markdown: string): Record<string, unknown> {
88
+ const node = jsxElement(markdown);
89
+ if (!node?.attributes) return {};
90
+ const props: Record<string, unknown> = {};
91
+ for (const attr of node.attributes) {
92
+ if (attr.type !== "mdxJsxAttribute" || typeof attr.name !== "string") continue;
93
+ props[attr.name] = attributeValue(attr.value);
94
+ }
95
+ return props;
96
+ }
97
+
98
+ export function jsxBlockSource(
99
+ name: string,
100
+ props: Record<string, unknown>,
101
+ fields: Record<string, { kind: string }>,
102
+ ): string {
103
+ return elementSource(name, attrList(props, fields));
104
+ }
105
+
106
+ export function jsxWithProps(
107
+ markdown: string,
108
+ name: string,
109
+ props: Record<string, unknown>,
110
+ fields: Record<string, { kind: string }>,
111
+ ): string {
112
+ const previous = propsFromJsx(markdown);
113
+ const merged: Record<string, unknown> = {};
114
+ for (const [key, value] of Object.entries(previous)) {
115
+ if (!(key in fields)) merged[key] = value;
116
+ }
117
+ for (const key of Object.keys(fields)) merged[key] = props[key];
118
+ const kinds: Record<string, { kind: string }> = { ...fields };
119
+ for (const key of Object.keys(merged)) {
120
+ if (!kinds[key]) kinds[key] = { kind: kindOf(merged[key]) };
121
+ }
122
+ const tag = elementSource(name, attrList(merged, kinds));
123
+ const inner = jsxInner(markdown);
124
+ if (inner == null) return tag;
125
+ const open = tag.replace(/\s*\/>$/, ">");
126
+ const body = inner.trim();
127
+ if (!body) return `${open}</${name}>`;
128
+ return `${open}\n\n${body}\n\n</${name}>`;
129
+ }
130
+
131
+ function jsxElement(markdown: string): JsxElement | null {
132
+ const tree = parseBody.parse(markdown);
133
+ const node = (tree.children as JsxElement[]).find((child) => child.type === "mdxJsxFlowElement");
134
+ return node ?? null;
135
+ }
136
+
137
+ function jsxInner(markdown: string): string | null {
138
+ const node = jsxElement(markdown);
139
+ const children = node?.children ?? [];
140
+ if (children.length === 0) return null;
141
+ const start = children[0]?.position?.start.offset;
142
+ const end = children[children.length - 1]?.position?.end.offset;
143
+ if (typeof start !== "number" || typeof end !== "number") return null;
144
+ return markdown.slice(start, end);
145
+ }
146
+
147
+ function attrList(
148
+ props: Record<string, unknown>,
149
+ fields: Record<string, { kind: string }>,
150
+ ): string[] {
151
+ const attrs: string[] = [];
152
+ for (const [key, value] of Object.entries(props)) {
153
+ const attr = attrSource(key, value, fields[key]?.kind ?? kindOf(value));
154
+ if (attr) attrs.push(attr);
155
+ }
156
+ return attrs;
157
+ }
158
+
159
+ function elementSource(name: string, attrs: string[]): string {
160
+ if (attrs.length === 0) return `<${name} />`;
161
+ if (attrs.join(" ").length < 48) return `<${name} ${attrs.join(" ")} />`;
162
+ return `<${name}\n${attrs.map((attr) => ` ${attr}`).join("\n")}\n/>`;
163
+ }
164
+
165
+ function attrSource(name: string, value: unknown, kind: string): string | null {
166
+ if (value == null || value === "") return null;
167
+ if (kind === "boolean") return value === true ? name : null;
168
+ if (kind === "number") {
169
+ const numeric = typeof value === "number" ? value : Number(value);
170
+ if (!Number.isFinite(numeric)) return null;
171
+ return `${name}={${numeric}}`;
172
+ }
173
+ if (kind === "object" || kind === "array") {
174
+ const json = typeof value === "string" ? value : JSON.stringify(value);
175
+ try {
176
+ JSON.parse(json);
177
+ } catch {
178
+ return null;
179
+ }
180
+ return `${name}={${json}}`;
181
+ }
182
+ return `${name}=${JSON.stringify(String(value))}`;
183
+ }
184
+
185
+ function attributeValue(value: JsxAttribute["value"]): unknown {
186
+ if (value == null) return true;
187
+ if (typeof value === "string") return value;
188
+ const literal = value.data?.estree?.body?.[0]?.expression;
189
+ if (literal?.type === "Literal" && "value" in literal) return literal.value;
190
+ const raw = value.value?.trim() ?? "";
191
+ if (raw === "true") return true;
192
+ if (raw === "false") return false;
193
+ if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
194
+ if (
195
+ (raw.startsWith("{") && raw.endsWith("}")) ||
196
+ (raw.startsWith("[") && raw.endsWith("]"))
197
+ ) {
198
+ try {
199
+ return JSON.parse(raw);
200
+ } catch {
201
+ return raw;
202
+ }
203
+ }
204
+ if (
205
+ (raw.startsWith('"') && raw.endsWith('"')) ||
206
+ (raw.startsWith("'") && raw.endsWith("'"))
207
+ ) {
208
+ return raw.slice(1, -1);
209
+ }
210
+ return raw;
211
+ }
212
+
213
+ function kindOf(value: unknown): string {
214
+ if (typeof value === "boolean") return "boolean";
215
+ if (typeof value === "number") return "number";
216
+ if (Array.isArray(value)) return "array";
217
+ if (value && typeof value === "object") return "object";
218
+ return "text";
219
+ }
220
+
221
+ function blockFromNode(source: string, node: ParsedNode, id: string): BodyBlock | null {
222
+ const start = node.position?.start.offset;
223
+ const end = node.position?.end.offset;
224
+ if (typeof start !== "number" || typeof end !== "number") return null;
225
+ const markdown = source.slice(start, end);
226
+ if (node.type === "code") {
227
+ return { id, markdown, kind: "code", lang: node.lang ?? "" };
228
+ }
229
+ if (node.type === "mdxJsxFlowElement") {
230
+ return { id, markdown, kind: "jsx", jsxName: node.name ?? "" };
231
+ }
232
+ return { id, markdown, kind: "text" };
233
+ }
@@ -0,0 +1,82 @@
1
+ // packages/studio/src/pages/body-mdx.tsx
2
+ import {
3
+ Caption,
4
+ Frame,
5
+ Image,
6
+ Link,
7
+ createMdxComponents,
8
+ type ProseUIImageProps,
9
+ type ProseUILinkProps,
10
+ } from "@prose-ui/react";
11
+ import type { AnchorHTMLAttributes, ReactNode } from "react";
12
+
13
+ function hasCaption(children: ReactNode) {
14
+ if (children == null || children === false) return false;
15
+ if (Array.isArray(children) && children.length === 0) return false;
16
+ return true;
17
+ }
18
+
19
+ /** Image children are the caption. Prose UI's Image renders the picture only. */
20
+ function BodyImage({
21
+ children,
22
+ ...props
23
+ }: ProseUIImageProps & { children?: ReactNode }) {
24
+ if (!hasCaption(children)) return <Image {...props} />;
25
+ return (
26
+ <Frame>
27
+ <Image {...props} />
28
+ <Caption>{children}</Caption>
29
+ </Frame>
30
+ );
31
+ }
32
+
33
+ function BodyLink({ children, ...props }: ProseUILinkProps) {
34
+ return (
35
+ <Link {...props} target="_blank" rel="noreferrer">
36
+ {children}
37
+ </Link>
38
+ );
39
+ }
40
+
41
+ function BodyAnchor({
42
+ children,
43
+ ...props
44
+ }: AnchorHTMLAttributes<HTMLAnchorElement>) {
45
+ return (
46
+ <a {...props} target="_blank" rel="noreferrer">
47
+ {children}
48
+ </a>
49
+ );
50
+ }
51
+
52
+ export const bodyMdxComponents = {
53
+ ...createMdxComponents({
54
+ Image: BodyImage,
55
+ Link: BodyLink,
56
+ }),
57
+ a: BodyAnchor,
58
+ };
59
+
60
+ /** Shown for `<Quote />` and other tags the editor does not ship a component for. */
61
+ function RichtextTag({
62
+ children,
63
+ ...props
64
+ }: { children?: ReactNode } & Record<string, unknown>) {
65
+ const lines = Object.values(props).filter(
66
+ (value): value is string => typeof value === "string" && value.trim() !== "",
67
+ );
68
+ return (
69
+ <div>
70
+ {lines.map((line) => (
71
+ <p key={line}>{line}</p>
72
+ ))}
73
+ {children}
74
+ </div>
75
+ );
76
+ }
77
+
78
+ /** Prose UI keeps `Image`. Other richtext tags get a stand-in so MDX does not throw. */
79
+ export function editorMdxComponents(jsxName?: string): Record<string, unknown> {
80
+ if (!jsxName || jsxName in bodyMdxComponents) return bodyMdxComponents;
81
+ return { ...bodyMdxComponents, [jsxName]: RichtextTag };
82
+ }
@@ -0,0 +1,37 @@
1
+ // packages/studio/src/pages/compile-body.ts
2
+ import { evaluate } from "@mdx-js/mdx";
3
+ import { createElement, type ReactNode } from "react";
4
+ import * as runtime from "react/jsx-runtime";
5
+
6
+ let pluginsPromise: ReturnType<typeof loadPlugins> | null = null;
7
+
8
+ function loadPlugins() {
9
+ return import("@prose-ui/core").then(({ remarkPlugins }) => remarkPlugins());
10
+ }
11
+
12
+ export async function compileBody(
13
+ source: string,
14
+ components: object,
15
+ ): Promise<{ node: ReactNode; error: string | null }> {
16
+ if (!source.trim()) return { node: null, error: null };
17
+
18
+ try {
19
+ pluginsPromise ??= loadPlugins().catch((err) => {
20
+ pluginsPromise = null;
21
+ throw err;
22
+ });
23
+ const remarkPlugins = await pluginsPromise;
24
+ const { default: Content } = await evaluate(source, {
25
+ ...runtime,
26
+ remarkPlugins,
27
+ baseUrl: import.meta.url,
28
+ });
29
+ return {
30
+ node: createElement(Content, { components: components as never }),
31
+ error: null,
32
+ };
33
+ } catch (err) {
34
+ const message = err instanceof Error ? err.message : "Could not render this body";
35
+ return { node: null, error: message };
36
+ }
37
+ }
@@ -0,0 +1,22 @@
1
+ // packages/studio/src/pages/host-env.ts
2
+ /** Copy-paste block for the Next host's Vercel env / `.env`. */
3
+
4
+ export function hostEnvSnippet(input: {
5
+ workspaceId: string;
6
+ contentToken?: string | null;
7
+ previewToken?: string | null;
8
+ hostToken?: string | null;
9
+ siteUrl?: string | null;
10
+ }): string {
11
+ const line = (name: string, value: string | null | undefined) =>
12
+ `${name}=${value?.trim() ?? ""}`;
13
+ return [
14
+ `# backmatter.config.ts → workspaceId: "${input.workspaceId}"`,
15
+ "# API defaults to https://api.backmatter.app in production",
16
+ "# BACKMATTER_API_URL=http://localhost:3001 # local or self-hosted CMS",
17
+ line("BACKMATTER_PREVIEW_TOKEN", input.previewToken),
18
+ line("BACKMATTER_HOST_TOKEN", input.hostToken),
19
+ line("BACKMATTER_CONTENT_TOKEN", input.contentToken),
20
+ line("BACKMATTER_SITE_URL", input.siteUrl),
21
+ ].join("\n");
22
+ }
@@ -0,0 +1,99 @@
1
+ // packages/studio/src/pages/html-to-markdown.ts
2
+ import { codeParts, replaceJsxChildren, type BodyBlock } from "./body-blocks";
3
+
4
+ export function markdownFromEditable(root: HTMLElement, block: BodyBlock): string {
5
+ if (block.kind === "code") {
6
+ const code = textOf(root.querySelector("code") ?? root).replace(/\n$/, "");
7
+ const lang = block.lang ?? codeParts(block.markdown).lang;
8
+ return "```" + lang + "\n" + code + "\n```";
9
+ }
10
+ if (block.kind === "jsx" && block.jsxName) {
11
+ return replaceJsxChildren(block.markdown, block.jsxName, jsxText(root, block.jsxName));
12
+ }
13
+ return blockElementsToMarkdown(root).trim();
14
+ }
15
+
16
+ function textOf(element: Element | null): string {
17
+ return element instanceof HTMLElement ? element.innerText : "";
18
+ }
19
+
20
+ function jsxText(root: HTMLElement, name: string): string {
21
+ if (name === "Image") return textOf(root.querySelector("figcaption"));
22
+ if (name === "Callout") return textOf(root.querySelector(".callout-body")) || root.innerText;
23
+ return root.innerText;
24
+ }
25
+
26
+ function blockElementsToMarkdown(root: HTMLElement): string {
27
+ const parts: string[] = [];
28
+ for (const node of root.childNodes) {
29
+ if (node.nodeType === Node.TEXT_NODE) {
30
+ const text = inline(node).trim();
31
+ if (text) parts.push(text);
32
+ continue;
33
+ }
34
+ if (node instanceof HTMLElement) {
35
+ const markdown = elementToMarkdown(node);
36
+ if (markdown) parts.push(markdown);
37
+ }
38
+ }
39
+ return parts.join("\n\n");
40
+ }
41
+
42
+ function elementToMarkdown(element: HTMLElement): string {
43
+ const tag = element.tagName;
44
+ if (/^H[1-6]$/.test(tag)) {
45
+ const level = Number(tag.slice(1));
46
+ return `${"#".repeat(level)} ${inline(element).trim()}`;
47
+ }
48
+ if (tag === "UL") return list(element, "ul");
49
+ if (tag === "OL") return list(element, "ol");
50
+ if (tag === "BLOCKQUOTE") {
51
+ const inner = blockElementsToMarkdown(element);
52
+ return inner
53
+ .split("\n")
54
+ .map((line) => (line.length > 0 ? `> ${line}` : ">"))
55
+ .join("\n");
56
+ }
57
+ if (tag === "P" || tag === "DIV") return inline(element).trim();
58
+ return inline(element).trim();
59
+ }
60
+
61
+ function list(element: HTMLElement, kind: "ul" | "ol"): string {
62
+ const items = [...element.children].filter((child) => child.tagName === "LI");
63
+ return items
64
+ .map((item, index) => {
65
+ const marker = kind === "ul" ? "-" : `${index + 1}.`;
66
+ return `${marker} ${inline(item).trim()}`;
67
+ })
68
+ .join("\n");
69
+ }
70
+
71
+ function inline(node: Node): string {
72
+ if (node.nodeType === Node.TEXT_NODE) {
73
+ return (node.textContent ?? "").replace(/\u00a0/g, " ");
74
+ }
75
+ if (!(node instanceof HTMLElement)) return "";
76
+ const tag = node.tagName;
77
+ if (tag === "STRONG" || tag === "B") return wrap(node, "**");
78
+ if (tag === "EM" || tag === "I") return wrap(node, "*");
79
+ if (tag === "CODE") {
80
+ const text = node.textContent ?? "";
81
+ return text ? `\`${text}\`` : "";
82
+ }
83
+ if (tag === "A") {
84
+ const href = node.getAttribute("href") ?? "";
85
+ const text = children(node);
86
+ return text ? `[${text}](${href})` : "";
87
+ }
88
+ if (tag === "BR") return "\n";
89
+ return children(node);
90
+ }
91
+
92
+ function wrap(element: HTMLElement, marker: string): string {
93
+ const text = children(element);
94
+ return text ? `${marker}${text}${marker}` : "";
95
+ }
96
+
97
+ function children(element: HTMLElement): string {
98
+ return [...element.childNodes].map(inline).join("");
99
+ }
@@ -0,0 +1,46 @@
1
+ // packages/studio/src/pages/preview-bridge.ts
2
+ // Dashboard side of the preview channel: push the open document into the
3
+ // iframe, and surface "Edit this page" when the iframe navigates elsewhere.
4
+ import {
5
+ createPreviewEditor,
6
+ createWindowPreviewTransport,
7
+ samePreviewRef,
8
+ type PreviewChannelRef,
9
+ type PreviewEditorSession,
10
+ type PreviewSchedule,
11
+ type PreviewTransport,
12
+ type PreviewWindowLike,
13
+ } from "@backmatter/core/preview";
14
+
15
+ export function bindPreviewEditor(options: {
16
+ transport: PreviewTransport;
17
+ ref: () => PreviewChannelRef;
18
+ onOffer: (ref: PreviewChannelRef | null) => void;
19
+ onStatus: (state: "rendered" | "error") => void;
20
+ schedule?: PreviewSchedule;
21
+ }): PreviewEditorSession {
22
+ return createPreviewEditor({
23
+ transport: options.transport,
24
+ ref: options.ref,
25
+ schedule: options.schedule,
26
+ onHello: (hello) => {
27
+ options.onOffer(samePreviewRef(options.ref(), hello) ? null : hello);
28
+ },
29
+ onStatus: (status) => options.onStatus(status.state),
30
+ });
31
+ }
32
+
33
+ export function previewFrameTransport(options: {
34
+ frame: HTMLIFrameElement;
35
+ siteOrigin: string;
36
+ host?: PreviewWindowLike;
37
+ }): PreviewTransport | null {
38
+ const target = options.frame.contentWindow;
39
+ if (!target) return null;
40
+ return createWindowPreviewTransport({
41
+ target,
42
+ targetOrigin: options.siteOrigin,
43
+ allowedOrigins: [options.siteOrigin],
44
+ host: options.host,
45
+ });
46
+ }
@@ -0,0 +1,30 @@
1
+ // packages/studio/src/pages/preview-split.ts
2
+ export const PREVIEW_PCT_KEY = "backmatter-editor-preview-pct";
3
+ export const PREVIEW_PCT_DEFAULT = 46;
4
+ export const PREVIEW_PCT_MIN = 22;
5
+ export const PREVIEW_PCT_MAX = 78;
6
+ export const WIDE_QUERY = "(min-width: 1024px)";
7
+
8
+ export function clampPreviewPct(value: number) {
9
+ return Math.min(PREVIEW_PCT_MAX, Math.max(PREVIEW_PCT_MIN, value));
10
+ }
11
+
12
+ export function readPreviewPct() {
13
+ try {
14
+ const raw = localStorage.getItem(PREVIEW_PCT_KEY);
15
+ const value = raw == null ? NaN : Number(raw);
16
+ if (Number.isFinite(value)) return clampPreviewPct(value);
17
+ } catch {
18
+ // ignore quota / private mode
19
+ }
20
+ return PREVIEW_PCT_DEFAULT;
21
+ }
22
+
23
+ export function persistPreviewPct(value: number) {
24
+ if (!Number.isFinite(value)) return;
25
+ try {
26
+ localStorage.setItem(PREVIEW_PCT_KEY, String(clampPreviewPct(value)));
27
+ } catch {
28
+ // ignore quota / private mode
29
+ }
30
+ }
@@ -0,0 +1,92 @@
1
+ // packages/studio/src/pages/preview-url.ts
2
+ import { routeFor, revalidateUrl, type RouteModel } from "@backmatter/core";
3
+
4
+ export function modelForType(models: readonly RouteModel[], type: string): RouteModel {
5
+ return models.find((m) => m.type === type) ?? { type };
6
+ }
7
+
8
+ export function publicDocPath(model: RouteModel, locale: string, slug: string): string {
9
+ return routeFor(model, locale, slug);
10
+ }
11
+
12
+ /** Public path once checkout is known. Null holds the iframe off HEAD. */
13
+ export function editorPreviewPath(
14
+ model: RouteModel,
15
+ slug: string,
16
+ locale = "en",
17
+ branch?: string | null,
18
+ tree?: string | null,
19
+ ): string | null {
20
+ if (!branch?.trim() || !tree?.trim()) return null;
21
+ return publicDocPath(model, locale, slug);
22
+ }
23
+
24
+ /**
25
+ * A Studio mounted on the site it previews already carries the site session
26
+ * cookie, so the preview frame loads the page directly — no ticket.
27
+ */
28
+ export function studioIsOnSite(siteUrl: string | null | undefined, pageOrigin: string): boolean {
29
+ if (!siteUrl) return false;
30
+ try {
31
+ return new URL(siteUrl).origin === pageOrigin;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ export function onSitePreviewUrl(siteUrl: string, path: string): string {
38
+ return `${siteUrl.replace(/\/$/, "")}${path}`;
39
+ }
40
+
41
+ export function previewEnableUrl(base: string, ticket: string): string {
42
+ const origin = base.replace(/\/$/, "");
43
+ return `${origin}/api/backmatter/preview?ticket=${encodeURIComponent(ticket)}`;
44
+ }
45
+
46
+ export function primarySiteUrl(
47
+ siteUrls: readonly string[] | null | undefined,
48
+ ): string | null {
49
+ const first = siteUrls?.[0]?.trim();
50
+ return first ? first : null;
51
+ }
52
+
53
+ /** Live published page. No preview ticket. */
54
+ export function productionDocUrl(
55
+ siteUrl: string | null | undefined,
56
+ model: RouteModel,
57
+ locale: string,
58
+ slug: string,
59
+ ): string | null {
60
+ const origin = siteUrl?.trim().replace(/\/$/, "");
61
+ if (!origin) return null;
62
+ return `${origin}${publicDocPath(model, locale, slug)}`;
63
+ }
64
+
65
+ export function revalidateWebhookUrl(siteUrl: string | null | undefined): string | null {
66
+ const origin = siteUrl?.trim();
67
+ if (!origin) return null;
68
+ return revalidateUrl(origin);
69
+ }
70
+
71
+ export function previewSiteUrl(
72
+ siteUrls: readonly string[] | null | undefined,
73
+ ticket: string | null,
74
+ ): string | null {
75
+ if (!ticket) return null;
76
+ const base = primarySiteUrl(siteUrls);
77
+ if (!base) return null;
78
+ return previewEnableUrl(base, ticket);
79
+ }
80
+
81
+ export function buildPreviewSiteUrl(
82
+ base: string,
83
+ model: RouteModel,
84
+ slug: string,
85
+ locale = "en",
86
+ branch?: string | null,
87
+ tree?: string | null,
88
+ ticket?: string | null,
89
+ ): string | null {
90
+ if (!editorPreviewPath(model, slug, locale, branch, tree) || !ticket) return null;
91
+ return previewEnableUrl(base, ticket);
92
+ }