@openspecui/web 0.9.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 (463) hide show
  1. package/dist/assets/abap-BdImnpbu.js +1 -0
  2. package/dist/assets/actionscript-3-CfeIJUat.js +1 -0
  3. package/dist/assets/ada-bCR0ucgS.js +1 -0
  4. package/dist/assets/andromeeda-C-Jbm3Hp.js +1 -0
  5. package/dist/assets/angular-html-CU67Zn6k.js +1 -0
  6. package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
  7. package/dist/assets/apache-Pmp26Uib.js +1 -0
  8. package/dist/assets/apex-DDbsPZ6N.js +1 -0
  9. package/dist/assets/apl-B4CMkyY2.js +1 -0
  10. package/dist/assets/apl-dKokRX4l.js +1 -0
  11. package/dist/assets/applescript-Co6uUVPk.js +1 -0
  12. package/dist/assets/ara-BRHolxvo.js +1 -0
  13. package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
  14. package/dist/assets/asciidoc-Dv7Oe6Be.js +1 -0
  15. package/dist/assets/asm-D_Q5rh1f.js +1 -0
  16. package/dist/assets/asn1-EdZsLKOL.js +1 -0
  17. package/dist/assets/asterisk-B-8jnY81.js +1 -0
  18. package/dist/assets/astro-CbQHKStN.js +1 -0
  19. package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  20. package/dist/assets/awk-DMzUqQB5.js +1 -0
  21. package/dist/assets/ayu-dark-Cv9koXgw.js +1 -0
  22. package/dist/assets/ballerina-BFfxhgS-.js +1 -0
  23. package/dist/assets/bat-BkioyH1T.js +1 -0
  24. package/dist/assets/beancount-k_qm7-4y.js +1 -0
  25. package/dist/assets/berry-uYugtg8r.js +1 -0
  26. package/dist/assets/bibtex-CHM0blh-.js +1 -0
  27. package/dist/assets/bicep-Bmn6On1c.js +1 -0
  28. package/dist/assets/blade-DVc8C-J4.js +1 -0
  29. package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  30. package/dist/assets/bsl-BO_Y6i37.js +1 -0
  31. package/dist/assets/c-BIGW1oBm.js +1 -0
  32. package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
  33. package/dist/assets/cairo-KRGpt6FW.js +1 -0
  34. package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  35. package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  36. package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  37. package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  38. package/dist/assets/clarity-D53aC0YG.js +1 -0
  39. package/dist/assets/clike-B9uivgTg.js +1 -0
  40. package/dist/assets/clojure-BMjYHr_A.js +1 -0
  41. package/dist/assets/clojure-P80f7IUj.js +1 -0
  42. package/dist/assets/cmake-BQqOBYOt.js +1 -0
  43. package/dist/assets/cmake-D1j8_8rp.js +1 -0
  44. package/dist/assets/cobol-CWcv1MsR.js +1 -0
  45. package/dist/assets/cobol-nwyudZeR.js +1 -0
  46. package/dist/assets/codeowners-Bp6g37R7.js +1 -0
  47. package/dist/assets/codeql-DsOJ9woJ.js +1 -0
  48. package/dist/assets/coffee-Ch7k5sss.js +1 -0
  49. package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
  50. package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
  51. package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
  52. package/dist/assets/coq-DkFqJrB1.js +1 -0
  53. package/dist/assets/cpp-CofmeUqb.js +1 -0
  54. package/dist/assets/crystal-SjHAIU92.js +1 -0
  55. package/dist/assets/crystal-tKQVLTB8.js +1 -0
  56. package/dist/assets/csharp-K5feNrxe.js +1 -0
  57. package/dist/assets/css-BnMrqG3P.js +1 -0
  58. package/dist/assets/css-DPfMkruS.js +1 -0
  59. package/dist/assets/csv-fuZLfV_i.js +1 -0
  60. package/dist/assets/cue-D82EKSYY.js +1 -0
  61. package/dist/assets/cypher-COkxafJQ.js +1 -0
  62. package/dist/assets/cypher-C_CwsFkJ.js +1 -0
  63. package/dist/assets/d-85-TOEBH.js +1 -0
  64. package/dist/assets/d-pRatUO7H.js +1 -0
  65. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  66. package/dist/assets/dart-CF10PKvl.js +1 -0
  67. package/dist/assets/dax-CEL-wOlO.js +1 -0
  68. package/dist/assets/desktop-BmXAJ9_W.js +1 -0
  69. package/dist/assets/diff-D97Zzqfu.js +1 -0
  70. package/dist/assets/diff-DbItnlRl.js +1 -0
  71. package/dist/assets/docker-BcOcwvcX.js +1 -0
  72. package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
  73. package/dist/assets/dotenv-Da5cRb03.js +1 -0
  74. package/dist/assets/dracula-BzJJZx-M.js +1 -0
  75. package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  76. package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
  77. package/dist/assets/dtd-DF_7sFjM.js +1 -0
  78. package/dist/assets/dylan-DwRh75JA.js +1 -0
  79. package/dist/assets/ebnf-CDyGwa7X.js +1 -0
  80. package/dist/assets/ecl-Cabwm37j.js +1 -0
  81. package/dist/assets/edge-BkV0erSs.js +1 -0
  82. package/dist/assets/eiffel-CnydiIhH.js +1 -0
  83. package/dist/assets/elixir-CDX3lj18.js +1 -0
  84. package/dist/assets/elm-DbKCFpqz.js +1 -0
  85. package/dist/assets/elm-vLlmbW-K.js +1 -0
  86. package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
  87. package/dist/assets/erb-BOJIQeun.js +1 -0
  88. package/dist/assets/erlang-BNw1qcRV.js +1 -0
  89. package/dist/assets/erlang-DsQrWhSR.js +1 -0
  90. package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  91. package/dist/assets/everforest-light-C8M2exoo.js +1 -0
  92. package/dist/assets/factor-kuTfRLto.js +1 -0
  93. package/dist/assets/fcl-Kvtd6kyn.js +1 -0
  94. package/dist/assets/fennel-BYunw83y.js +1 -0
  95. package/dist/assets/fish-BvzEVeQv.js +1 -0
  96. package/dist/assets/fluent-C4IJs8-o.js +1 -0
  97. package/dist/assets/forth-Ffai-XNe.js +1 -0
  98. package/dist/assets/fortran-DYz_wnZ1.js +1 -0
  99. package/dist/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  100. package/dist/assets/fortran-free-form-D22FLkUw.js +1 -0
  101. package/dist/assets/fsharp-CXgrBDvD.js +1 -0
  102. package/dist/assets/gas-Bneqetm1.js +1 -0
  103. package/dist/assets/gdresource-B7Tvp0Sc.js +1 -0
  104. package/dist/assets/gdscript-DTMYz4Jt.js +1 -0
  105. package/dist/assets/gdshader-DkwncUOv.js +1 -0
  106. package/dist/assets/genie-D0YGMca9.js +1 -0
  107. package/dist/assets/gherkin-DyxjwDmM.js +1 -0
  108. package/dist/assets/gherkin-heZmZLOM.js +1 -0
  109. package/dist/assets/git-commit-F4YmCXRG.js +1 -0
  110. package/dist/assets/git-rebase-r7XF79zn.js +1 -0
  111. package/dist/assets/github-dark-DHJKELXO.js +1 -0
  112. package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  113. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  114. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  115. package/dist/assets/github-light-DAi9KRSo.js +1 -0
  116. package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  117. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  118. package/dist/assets/gleam-BspZqrRM.js +1 -0
  119. package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
  120. package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
  121. package/dist/assets/glsl-DplSGwfg.js +1 -0
  122. package/dist/assets/gnuplot-DdkO51Og.js +1 -0
  123. package/dist/assets/go-Dn2_MT6a.js +1 -0
  124. package/dist/assets/graphql-ChdNCCLP.js +1 -0
  125. package/dist/assets/groovy-D9Dt4D0W.js +1 -0
  126. package/dist/assets/groovy-gcz8RCvz.js +1 -0
  127. package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  128. package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  129. package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  130. package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  131. package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  132. package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  133. package/dist/assets/hack-CaT9iCJl.js +1 -0
  134. package/dist/assets/haml-B8DHNrY2.js +1 -0
  135. package/dist/assets/handlebars-BL8al0AC.js +1 -0
  136. package/dist/assets/haskell-Cw1EW3IL.js +1 -0
  137. package/dist/assets/haskell-Df6bDoY_.js +1 -0
  138. package/dist/assets/haxe-CzTSHFRz.js +1 -0
  139. package/dist/assets/haxe-H-WmDvRZ.js +1 -0
  140. package/dist/assets/hcl-BWvSN4gD.js +1 -0
  141. package/dist/assets/hjson-D5-asLiD.js +1 -0
  142. package/dist/assets/hlsl-D3lLCCz7.js +1 -0
  143. package/dist/assets/houston-DnULxvSX.js +1 -0
  144. package/dist/assets/html-GMplVEZG.js +1 -0
  145. package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
  146. package/dist/assets/http-DBlCnlav.js +1 -0
  147. package/dist/assets/http-jrhK8wxY.js +1 -0
  148. package/dist/assets/hurl-irOxFIW8.js +1 -0
  149. package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
  150. package/dist/assets/hy-DFXneXwc.js +1 -0
  151. package/dist/assets/idl-BEugSyMb.js +1 -0
  152. package/dist/assets/imba-DGztddWO.js +1 -0
  153. package/dist/assets/index-8c6bEJ99.js +1 -0
  154. package/dist/assets/index-AbWe21oh.js +2 -0
  155. package/dist/assets/index-ArhptQw0.js +1 -0
  156. package/dist/assets/index-B1hpa--1.js +3 -0
  157. package/dist/assets/index-Bafja8o4.js +1 -0
  158. package/dist/assets/index-Bp00uZNc.js +1 -0
  159. package/dist/assets/index-BsTieXqQ.js +1 -0
  160. package/dist/assets/index-BvGAWAqS.js +1 -0
  161. package/dist/assets/index-CCfVkFzN.js +1 -0
  162. package/dist/assets/index-D-Urq2hl.css +1 -0
  163. package/dist/assets/index-D3mXuuih.js +1 -0
  164. package/dist/assets/index-DFOLYN6W.js +1 -0
  165. package/dist/assets/index-DpxkOmNJ.js +7 -0
  166. package/dist/assets/index-YZ-iXB95.js +309 -0
  167. package/dist/assets/index-eA_XNQ_L.js +1 -0
  168. package/dist/assets/index-ftYom_wU.js +1 -0
  169. package/dist/assets/index-gvPT4BlL.js +1 -0
  170. package/dist/assets/ini-BEwlwnbL.js +1 -0
  171. package/dist/assets/java-CylS5w8V.js +1 -0
  172. package/dist/assets/javascript-iXu5QeM3.js +1 -0
  173. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  174. package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
  175. package/dist/assets/jison-wvAkD_A8.js +1 -0
  176. package/dist/assets/json-Cp-IABpG.js +1 -0
  177. package/dist/assets/json5-C9tS-k6U.js +1 -0
  178. package/dist/assets/jsonc-Des-eS-w.js +1 -0
  179. package/dist/assets/jsonl-DcaNXYhu.js +1 -0
  180. package/dist/assets/jsonnet-DFQXde-d.js +1 -0
  181. package/dist/assets/jssm-C2t-YnRu.js +1 -0
  182. package/dist/assets/jsx-g9-lgVsj.js +1 -0
  183. package/dist/assets/julia-C8NyazO9.js +1 -0
  184. package/dist/assets/julia-DuME0IfC.js +1 -0
  185. package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  186. package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  187. package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
  188. package/dist/assets/kdl-DV7GczEv.js +1 -0
  189. package/dist/assets/kotlin-BdnUsdx6.js +1 -0
  190. package/dist/assets/kusto-BvAqAH-y.js +1 -0
  191. package/dist/assets/laserwave-DUszq2jm.js +1 -0
  192. package/dist/assets/latex-BdAV_C_H.js +1 -0
  193. package/dist/assets/lean-Bc6EcWN3.js +1 -0
  194. package/dist/assets/less-B1dDrJ26.js +1 -0
  195. package/dist/assets/light-plus-B7mTdjB0.js +1 -0
  196. package/dist/assets/liquid-DYVedYrR.js +1 -0
  197. package/dist/assets/livescript-BwQOo05w.js +1 -0
  198. package/dist/assets/llvm-BtvRca6l.js +1 -0
  199. package/dist/assets/log-2UxHyX5q.js +1 -0
  200. package/dist/assets/logo-BtOb2qkB.js +1 -0
  201. package/dist/assets/lua-BbnMAYS6.js +1 -0
  202. package/dist/assets/lua-BgMRiT3U.js +1 -0
  203. package/dist/assets/luau-CXu1NL6O.js +1 -0
  204. package/dist/assets/make-CHLpvVh8.js +1 -0
  205. package/dist/assets/markdown-Cvjx9yec.js +1 -0
  206. package/dist/assets/marko-CPi9NSCl.js +1 -0
  207. package/dist/assets/material-theme-D5KoaKCx.js +1 -0
  208. package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  209. package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  210. package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  211. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  212. package/dist/assets/mathematica-DTrFuWx2.js +1 -0
  213. package/dist/assets/matlab-D7o27uSR.js +1 -0
  214. package/dist/assets/mbox-CNhZ1qSd.js +1 -0
  215. package/dist/assets/mdc-DUICxH0z.js +1 -0
  216. package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  217. package/dist/assets/mermaid-DKYwYmdq.js +1 -0
  218. package/dist/assets/min-dark-CafNBF8u.js +1 -0
  219. package/dist/assets/min-light-CTRr51gU.js +1 -0
  220. package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
  221. package/dist/assets/mirc-CjQqDB4T.js +1 -0
  222. package/dist/assets/mllike-CXdrOF99.js +1 -0
  223. package/dist/assets/modelica-Dc1JOy9r.js +1 -0
  224. package/dist/assets/mojo-1DNp92w6.js +1 -0
  225. package/dist/assets/monokai-D4h5O-jR.js +1 -0
  226. package/dist/assets/move-Bu9oaDYs.js +1 -0
  227. package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
  228. package/dist/assets/mumps-BT43cFF4.js +1 -0
  229. package/dist/assets/narrat-DRg8JJMk.js +1 -0
  230. package/dist/assets/nextflow-BrzmwbiE.js +1 -0
  231. package/dist/assets/nginx-DdIZxoE0.js +1 -0
  232. package/dist/assets/nginx-DknmC5AR.js +1 -0
  233. package/dist/assets/night-owl-C39BiMTA.js +1 -0
  234. package/dist/assets/nim-CVrawwO9.js +1 -0
  235. package/dist/assets/nix-c8nO5XWb.js +1 -0
  236. package/dist/assets/nord-Ddv68eIx.js +1 -0
  237. package/dist/assets/nsis-LdVXkNf5.js +1 -0
  238. package/dist/assets/ntriples-BfvgReVJ.js +1 -0
  239. package/dist/assets/nushell-C-sUppwS.js +1 -0
  240. package/dist/assets/objective-c-DXmwc3jG.js +1 -0
  241. package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
  242. package/dist/assets/ocaml-C0hk2d4L.js +1 -0
  243. package/dist/assets/octave-Ck1zUtKM.js +1 -0
  244. package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  245. package/dist/assets/one-light-PoHY5YXO.js +1 -0
  246. package/dist/assets/openscad-C4EeE6gA.js +1 -0
  247. package/dist/assets/oz-BzwKVEFT.js +1 -0
  248. package/dist/assets/pascal--L3eBynH.js +1 -0
  249. package/dist/assets/pascal-D93ZcfNL.js +1 -0
  250. package/dist/assets/perl-C0TMdlhV.js +1 -0
  251. package/dist/assets/perl-CdXCOZ3F.js +1 -0
  252. package/dist/assets/php-CDn_0X-4.js +1 -0
  253. package/dist/assets/pig-CevX1Tat.js +1 -0
  254. package/dist/assets/pkl-u5AG7uiY.js +1 -0
  255. package/dist/assets/plastic-3e1v2bzS.js +1 -0
  256. package/dist/assets/plsql-ChMvpjG-.js +1 -0
  257. package/dist/assets/po-BTJTHyun.js +1 -0
  258. package/dist/assets/poimandres-CS3Unz2-.js +1 -0
  259. package/dist/assets/polar-C0HS_06l.js +1 -0
  260. package/dist/assets/postcss-CXtECtnM.js +1 -0
  261. package/dist/assets/powerquery-CEu0bR-o.js +1 -0
  262. package/dist/assets/powershell-CFHJl5sT.js +1 -0
  263. package/dist/assets/powershell-Dpen1YoG.js +1 -0
  264. package/dist/assets/prisma-Dd19v3D-.js +1 -0
  265. package/dist/assets/prolog-CbFg5uaA.js +1 -0
  266. package/dist/assets/properties-C78fOPTZ.js +1 -0
  267. package/dist/assets/proto-DyJlTyXw.js +1 -0
  268. package/dist/assets/protobuf-ChK-085T.js +1 -0
  269. package/dist/assets/pug-CGlum2m_.js +1 -0
  270. package/dist/assets/pug-DeIclll2.js +1 -0
  271. package/dist/assets/puppet-BMWR74SV.js +1 -0
  272. package/dist/assets/puppet-DMA9R1ak.js +1 -0
  273. package/dist/assets/purescript-CklMAg4u.js +1 -0
  274. package/dist/assets/python-B6aJPvgy.js +1 -0
  275. package/dist/assets/python-BuPzkPfP.js +1 -0
  276. package/dist/assets/q-pXgVlZs6.js +1 -0
  277. package/dist/assets/qml-3beO22l8.js +1 -0
  278. package/dist/assets/qmldir-C8lEn-DE.js +1 -0
  279. package/dist/assets/qss-IeuSbFQv.js +1 -0
  280. package/dist/assets/r-B6wPVr8A.js +1 -0
  281. package/dist/assets/r-DiinP2Uv.js +1 -0
  282. package/dist/assets/racket-BqYA7rlc.js +1 -0
  283. package/dist/assets/raku-DXvB9xmW.js +1 -0
  284. package/dist/assets/razor-CE9lU5zL.js +1 -0
  285. package/dist/assets/red-bN70gL4F.js +1 -0
  286. package/dist/assets/reg-C-SQnVFl.js +1 -0
  287. package/dist/assets/regexp-CDVJQ6XC.js +1 -0
  288. package/dist/assets/rel-C3B-1QV4.js +1 -0
  289. package/dist/assets/riscv-BM1_JUlF.js +1 -0
  290. package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  291. package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  292. package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
  293. package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
  294. package/dist/assets/rpm-CTu-6PCP.js +1 -0
  295. package/dist/assets/rst-B0xPkSld.js +1 -0
  296. package/dist/assets/ruby-B2Rjki9n.js +1 -0
  297. package/dist/assets/ruby-BvKwtOVI.js +1 -0
  298. package/dist/assets/rust-B1yitclQ.js +1 -0
  299. package/dist/assets/sas-B4kiWyti.js +1 -0
  300. package/dist/assets/sas-cz2c8ADy.js +1 -0
  301. package/dist/assets/sass-Cj5Yp3dK.js +1 -0
  302. package/dist/assets/scala-C151Ov-r.js +1 -0
  303. package/dist/assets/scheme-C41bIUwD.js +1 -0
  304. package/dist/assets/scheme-C98Dy4si.js +1 -0
  305. package/dist/assets/scss-OYdSNvt2.js +1 -0
  306. package/dist/assets/sdbl-DVxCFoDh.js +1 -0
  307. package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
  308. package/dist/assets/shell-CjFT_Tl9.js +1 -0
  309. package/dist/assets/shellscript-Yzrsuije.js +1 -0
  310. package/dist/assets/shellsession-BADoaaVG.js +1 -0
  311. package/dist/assets/sieve-C3Gn_uJK.js +1 -0
  312. package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
  313. package/dist/assets/slack-dark-BthQWCQV.js +1 -0
  314. package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  315. package/dist/assets/smalltalk-BERRCDM3.js +1 -0
  316. package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
  317. package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  318. package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
  319. package/dist/assets/solarized-light-L9t79GZl.js +1 -0
  320. package/dist/assets/solidity-rGO070M0.js +1 -0
  321. package/dist/assets/solr-DehyRSwq.js +1 -0
  322. package/dist/assets/soy-Brmx7dQM.js +1 -0
  323. package/dist/assets/sparql-DkYu6x3z.js +1 -0
  324. package/dist/assets/sparql-rVzFXLq3.js +1 -0
  325. package/dist/assets/splunk-BtCnVYZw.js +1 -0
  326. package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  327. package/dist/assets/sql-BLtJtn59.js +1 -0
  328. package/dist/assets/sql-D0XecflT.js +1 -0
  329. package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
  330. package/dist/assets/stata-BH5u7GGu.js +1 -0
  331. package/dist/assets/stex-C3f8Ysf7.js +1 -0
  332. package/dist/assets/stylus-B533Al4x.js +1 -0
  333. package/dist/assets/stylus-BEDo0Tqx.js +1 -0
  334. package/dist/assets/svelte-3Dk4HxPD.js +1 -0
  335. package/dist/assets/swift-BzpIVaGY.js +1 -0
  336. package/dist/assets/swift-Dg5xB15N.js +1 -0
  337. package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  338. package/dist/assets/system-verilog-CnnmHF94.js +1 -0
  339. package/dist/assets/systemd-4A_iFExJ.js +1 -0
  340. package/dist/assets/talonscript-CkByrt1z.js +1 -0
  341. package/dist/assets/tasl-QIJgUcNo.js +1 -0
  342. package/dist/assets/tcl-DVfN8rqt.js +1 -0
  343. package/dist/assets/tcl-dwOrl1Do.js +1 -0
  344. package/dist/assets/templ-W15q3VgB.js +1 -0
  345. package/dist/assets/terraform-BETggiCN.js +1 -0
  346. package/dist/assets/tex-CxkMU7Pf.js +1 -0
  347. package/dist/assets/textile-CnDTJFAw.js +1 -0
  348. package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  349. package/dist/assets/tiki-DGYXhP31.js +1 -0
  350. package/dist/assets/tokyo-night-hegEt444.js +1 -0
  351. package/dist/assets/toml-Bm5Em-hy.js +1 -0
  352. package/dist/assets/toml-vGWfd6FD.js +1 -0
  353. package/dist/assets/troff-wAsdV37c.js +1 -0
  354. package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
  355. package/dist/assets/tsv-B_m7g4N7.js +1 -0
  356. package/dist/assets/tsx-COt5Ahok.js +1 -0
  357. package/dist/assets/ttcn-CfJYG6tj.js +1 -0
  358. package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  359. package/dist/assets/turtle-B1tBg_DP.js +1 -0
  360. package/dist/assets/turtle-BsS91CYL.js +1 -0
  361. package/dist/assets/twig-CO9l9SDP.js +1 -0
  362. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  363. package/dist/assets/typespec-BGHnOYBU.js +1 -0
  364. package/dist/assets/typst-DHCkPAjA.js +1 -0
  365. package/dist/assets/v-BcVCzyr7.js +1 -0
  366. package/dist/assets/vala-CsfeWuGM.js +1 -0
  367. package/dist/assets/vb-CmGdzxic.js +1 -0
  368. package/dist/assets/vb-D17OF-Vu.js +1 -0
  369. package/dist/assets/vbscript-BuJXcnF6.js +1 -0
  370. package/dist/assets/velocity-D8B20fx6.js +1 -0
  371. package/dist/assets/verilog-BQ8w6xss.js +1 -0
  372. package/dist/assets/verilog-C6RDOZhf.js +1 -0
  373. package/dist/assets/vesper-DU1UobuO.js +1 -0
  374. package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
  375. package/dist/assets/vhdl-lSbBsy5d.js +1 -0
  376. package/dist/assets/viml-CJc9bBzg.js +1 -0
  377. package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  378. package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  379. package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  380. package/dist/assets/vue-DnHKYNfI.js +1 -0
  381. package/dist/assets/vue-html-CChd_i61.js +1 -0
  382. package/dist/assets/vue-vine-8moa0y9V.js +1 -0
  383. package/dist/assets/vyper-CDx5xZoG.js +1 -0
  384. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  385. package/dist/assets/wasm-MzD3tlZU.js +1 -0
  386. package/dist/assets/webidl-ZXfAyPTL.js +1 -0
  387. package/dist/assets/wenyan-BV7otONQ.js +1 -0
  388. package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
  389. package/dist/assets/wikitext-BhOHFoWU.js +1 -0
  390. package/dist/assets/wit-5i3qLPDT.js +1 -0
  391. package/dist/assets/wolfram-lXgVvXCa.js +1 -0
  392. package/dist/assets/xml-sdJ4AIDG.js +1 -0
  393. package/dist/assets/xquery-DzFWVndE.js +1 -0
  394. package/dist/assets/xsl-CtQFsRM5.js +1 -0
  395. package/dist/assets/yacas-BJ4BC0dw.js +1 -0
  396. package/dist/assets/yaml-Buea-lGh.js +1 -0
  397. package/dist/assets/z80-Hz9HOZM7.js +1 -0
  398. package/dist/assets/zenscript-DVFEvuxE.js +1 -0
  399. package/dist/assets/zig-VOosw3JB.js +1 -0
  400. package/dist/index.html +47 -0
  401. package/dist/logo.svg +8 -0
  402. package/dist/openspec_pixel_dark.svg +89 -0
  403. package/dist/openspec_pixel_light.svg +89 -0
  404. package/index.html +46 -0
  405. package/package.json +66 -0
  406. package/src/App.tsx +124 -0
  407. package/src/components/StaticModeBanner.tsx +46 -0
  408. package/src/components/change-overview.tsx +156 -0
  409. package/src/components/cli-terminal.tsx +93 -0
  410. package/src/components/code-editor.tsx +232 -0
  411. package/src/components/copyable-path.tsx +44 -0
  412. package/src/components/dialog.tsx +193 -0
  413. package/src/components/folder-editor-viewer.tsx +411 -0
  414. package/src/components/global-archive-modal.tsx +205 -0
  415. package/src/components/layout/desktop-sidebar.tsx +48 -0
  416. package/src/components/layout/index.ts +6 -0
  417. package/src/components/layout/mobile-header.tsx +91 -0
  418. package/src/components/layout/mobile-tabbar.tsx +20 -0
  419. package/src/components/layout/nav-items.ts +33 -0
  420. package/src/components/layout/root-layout.tsx +67 -0
  421. package/src/components/layout/status-bar.tsx +69 -0
  422. package/src/components/markdown-content.tsx +104 -0
  423. package/src/components/markdown-viewer.tsx +415 -0
  424. package/src/components/path-marquee.tsx +104 -0
  425. package/src/components/tabs.tsx +151 -0
  426. package/src/components/tasks-view.test.tsx +45 -0
  427. package/src/components/tasks-view.tsx +209 -0
  428. package/src/components/toc-context.tsx +177 -0
  429. package/src/components/toc.tsx +290 -0
  430. package/src/entry-client.tsx +47 -0
  431. package/src/index.css +481 -0
  432. package/src/lib/api-config.ts +72 -0
  433. package/src/lib/archive-modal-context.tsx +45 -0
  434. package/src/lib/codemirror-markdown-preview.ts +494 -0
  435. package/src/lib/format-time.ts +51 -0
  436. package/src/lib/shiki-highlighter.ts +47 -0
  437. package/src/lib/static-data-provider.ts +386 -0
  438. package/src/lib/static-mode.ts +158 -0
  439. package/src/lib/trpc.ts +107 -0
  440. package/src/lib/use-cli-runner.tsx +433 -0
  441. package/src/lib/use-dark-mode.ts +28 -0
  442. package/src/lib/use-server-status.ts +208 -0
  443. package/src/lib/use-subscription.ts +375 -0
  444. package/src/lib/use-tabs-status-by-query.ts +78 -0
  445. package/src/main.tsx +9 -0
  446. package/src/routes/archive-list.tsx +65 -0
  447. package/src/routes/archive-view.tsx +116 -0
  448. package/src/routes/change-list.tsx +63 -0
  449. package/src/routes/change-view.tsx +188 -0
  450. package/src/routes/dashboard.tsx +204 -0
  451. package/src/routes/project.tsx +272 -0
  452. package/src/routes/settings.tsx +816 -0
  453. package/src/routes/spec-list.tsx +49 -0
  454. package/src/routes/spec-view.tsx +164 -0
  455. package/src/ssg/entry-server.tsx +111 -0
  456. package/src/ssg/index.ts +6 -0
  457. package/src/ssg/prerender.ts +111 -0
  458. package/src/ssg/static-data-context.tsx +49 -0
  459. package/src/ssg/types.ts +5 -0
  460. package/src/test/setup.ts +1 -0
  461. package/src/vite-env.d.ts +6 -0
  462. package/tsconfig.json +18 -0
  463. package/vite.config.ts +58 -0
@@ -0,0 +1,193 @@
1
+ import { useEffect, useMemo, useRef, type ReactNode } from 'react'
2
+
3
+ interface DialogProps {
4
+ open: boolean
5
+ title: ReactNode // can include icon / status chips etc.
6
+ onClose: () => void
7
+ children: ReactNode
8
+ footer?: ReactNode
9
+ className?: string
10
+ bodyClassName?: string
11
+ maxHeight?: string
12
+ borderVariant?: 'default' | 'success' | 'error'
13
+ }
14
+
15
+ /**
16
+ * Unified dialog component backed by the native HTMLDialogElement.
17
+ * Preserves the previous DialogShell layout while using showModal/close
18
+ * for proper focus trapping and ESC handling.
19
+ */
20
+ export function Dialog({
21
+ open,
22
+ title,
23
+ onClose,
24
+ children,
25
+ footer,
26
+ className = '',
27
+ bodyClassName = '',
28
+ maxHeight = '86vh',
29
+ borderVariant = 'default',
30
+ }: DialogProps) {
31
+ const dialogRef = useRef<HTMLDialogElement>(null)
32
+
33
+ // Synchronize the native dialog with the controlled `open` prop
34
+ useEffect(() => {
35
+ const dialog = dialogRef.current
36
+ if (!dialog) return
37
+
38
+ if (open && !dialog.open) {
39
+ dialog.showModal()
40
+ } else if (!open && dialog.open) {
41
+ dialog.close()
42
+ }
43
+ }, [open])
44
+
45
+ // Close on ESC / cancel and backdrop clicks
46
+ useEffect(() => {
47
+ const dialog = dialogRef.current
48
+ if (!dialog) return
49
+
50
+ const handleCancel = (event: Event) => {
51
+ event.preventDefault()
52
+ onClose()
53
+ }
54
+
55
+ const handleClick = (event: MouseEvent) => {
56
+ const rect = dialog.getBoundingClientRect()
57
+ const isInDialog =
58
+ event.clientX >= rect.left &&
59
+ event.clientX <= rect.right &&
60
+ event.clientY >= rect.top &&
61
+ event.clientY <= rect.bottom
62
+
63
+ if (!isInDialog) {
64
+ onClose()
65
+ }
66
+ }
67
+
68
+ dialog.addEventListener('cancel', handleCancel)
69
+ dialog.addEventListener('click', handleClick)
70
+
71
+ return () => {
72
+ dialog.removeEventListener('cancel', handleCancel)
73
+ dialog.removeEventListener('click', handleClick)
74
+ }
75
+ }, [onClose])
76
+
77
+ const borderClass =
78
+ borderVariant === 'error'
79
+ ? 'border-red-500/60'
80
+ : borderVariant === 'success'
81
+ ? 'border-green-500/50'
82
+ : 'border-border'
83
+
84
+ const styles = useMemo(() => {
85
+ const css = String.raw
86
+ return (
87
+ <style>{css`
88
+ /* 1. 对话框的基础状态(关闭状态) */
89
+ dialog.openspec-dialog {
90
+ margin: auto;
91
+ opacity: 0.6;
92
+ transform: translateY(12px);
93
+ /* 必须把 overlay 也加入过渡,否则关闭时会瞬间消失 */
94
+ transition:
95
+ opacity 260ms cubic-bezier(0.22, 0.61, 0.36, 1),
96
+ transform 260ms cubic-bezier(0.22, 0.61, 0.36, 1),
97
+ overlay 320ms allow-discrete,
98
+ display 260ms allow-discrete;
99
+ }
100
+
101
+ /* 2. 对话框的打开状态 */
102
+ dialog.openspec-dialog[open] {
103
+ opacity: 1;
104
+ transform: translateY(0);
105
+ }
106
+
107
+ /* 3. 对话框打开瞬间的起始帧 */
108
+ @starting-style {
109
+ dialog.openspec-dialog[open] {
110
+ opacity: 0.6;
111
+ transform: translateY(12px);
112
+ }
113
+ }
114
+
115
+ /* --- 下面是 Backdrop (背景遮罩) 的动画 --- */
116
+
117
+ /* 4. 背景遮罩的基础状态 */
118
+ dialog.openspec-dialog::backdrop {
119
+ background-color: rgba(0, 0, 0, 0); /* 初始透明 */
120
+ backdrop-filter: grayscale(0.5);
121
+ transition:
122
+ display 0.35s allow-discrete,
123
+ overlay 0.35s allow-discrete,
124
+ background-color 0.35s ease,
125
+ backdrop-filter 0.35s ease;
126
+ }
127
+
128
+ /* 5. 背景遮罩的打开状态 */
129
+ dialog.openspec-dialog[open]::backdrop {
130
+ background-color: rgba(0, 0, 0, 0.5);
131
+ backdrop-filter: grayscale(1);
132
+ }
133
+
134
+ /* 6. 背景遮罩的起始帧 */
135
+ @starting-style {
136
+ dialog.openspec-dialog[open]::backdrop {
137
+ background-color: rgba(0, 0, 0, 0);
138
+ backdrop-filter: grayscale(0.5);
139
+ }
140
+ }
141
+
142
+ `}</style>
143
+ )
144
+ }, [])
145
+
146
+ return (
147
+ <>
148
+ {styles}
149
+ <dialog
150
+ ref={dialogRef}
151
+ className="openspec-dialog w-[calc(100%-2rem)] max-w-2xl border-0 bg-transparent p-0"
152
+ >
153
+ <div
154
+ className={`bg-background relative flex w-full flex-col overflow-hidden rounded-lg border shadow-xl ${borderClass} ${className}`}
155
+ style={{ maxHeight }}
156
+ >
157
+ {/* Header (non-shrinking) */}
158
+ <div className="border-border flex flex-none shrink-0 items-center justify-between border-b px-4 py-3">
159
+ <div className="flex items-center gap-2">{title}</div>
160
+ <button
161
+ onClick={onClose}
162
+ className="hover:bg-muted rounded p-1"
163
+ aria-label="Close dialog"
164
+ >
165
+ <svg
166
+ xmlns="http://www.w3.org/2000/svg"
167
+ viewBox="0 0 24 24"
168
+ className="h-4 w-4"
169
+ stroke="currentColor"
170
+ fill="none"
171
+ strokeWidth="2"
172
+ >
173
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 6l12 12M6 18L18 6" />
174
+ </svg>
175
+ </button>
176
+ </div>
177
+
178
+ {/* Body */}
179
+ <div className={`min-h-0 flex-1 overflow-auto px-4 py-3 ${bodyClassName}`}>
180
+ {children}
181
+ </div>
182
+
183
+ {/* Footer */}
184
+ {footer && (
185
+ <div className="border-border flex flex-none shrink-0 items-center justify-end gap-2 border-t px-4 py-3">
186
+ {footer}
187
+ </div>
188
+ )}
189
+ </div>
190
+ </dialog>
191
+ </>
192
+ )
193
+ }
@@ -0,0 +1,411 @@
1
+ import { CodeEditor } from '@/components/code-editor'
2
+ import { useArchiveFilesSubscription, useChangeFilesSubscription } from '@/lib/use-subscription'
3
+ import { ChevronRight, File, FileText, Folder, Loader2 } from 'lucide-react'
4
+ import { useEffect, useMemo, useState } from 'react'
5
+
6
+ /**
7
+ * 排序文件条目,确保子项紧跟在父目录后面
8
+ * 规则:同一目录下,文件夹优先于文件,同类型按字母排序
9
+ */
10
+ function compareEntries(
11
+ a: { path: string; type: 'file' | 'directory' },
12
+ b: { path: string; type: 'file' | 'directory' }
13
+ ): number {
14
+ const aParts = a.path.split('/')
15
+ const bParts = b.path.split('/')
16
+
17
+ // 逐级比较路径
18
+ const minLen = Math.min(aParts.length, bParts.length)
19
+ for (let i = 0; i < minLen; i++) {
20
+ const aIsLast = i === aParts.length - 1
21
+ const bIsLast = i === bParts.length - 1
22
+
23
+ // 如果当前层级的名称不同
24
+ if (aParts[i] !== bParts[i]) {
25
+ // 如果都是最后一级,比较类型(文件夹优先)
26
+ if (aIsLast && bIsLast) {
27
+ if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
28
+ }
29
+ // 如果只有一个是最后一级,另一个还有子级(说明是文件夹)
30
+ else if (aIsLast && !bIsLast) {
31
+ // a 是当前级的项,b 还有子级(b 的当前级是文件夹)
32
+ if (a.type === 'directory') {
33
+ // 都是文件夹,按字母排序
34
+ return aParts[i].localeCompare(bParts[i])
35
+ }
36
+ // a 是文件,b 是文件夹,文件夹优先
37
+ return 1
38
+ } else if (!aIsLast && bIsLast) {
39
+ if (b.type === 'directory') {
40
+ return aParts[i].localeCompare(bParts[i])
41
+ }
42
+ return -1
43
+ }
44
+ // 都不是最后一级,直接按字母排序
45
+ return aParts[i].localeCompare(bParts[i])
46
+ }
47
+
48
+ // 名称相同,继续比较下一级
49
+ }
50
+
51
+ // 路径前缀相同,短的在前(父目录在子项之前)
52
+ return aParts.length - bParts.length
53
+ }
54
+
55
+ /** 获取文件名(不含路径) */
56
+ function getFileName(path: string): string {
57
+ return path.split('/').pop() ?? path
58
+ }
59
+
60
+ /** 获取父目录路径 */
61
+ function getParentPath(path: string): string {
62
+ const parts = path.split('/')
63
+ return parts.slice(0, -1).join('/')
64
+ }
65
+
66
+ const css = String.raw
67
+ const layoutStyles = css`
68
+ /* 窄屏:单列布局 */
69
+ .fev-layout {
70
+ display: flex;
71
+ flex-direction: column;
72
+ height: 100%;
73
+ gap: 0.75rem;
74
+ }
75
+ .fev-sidebar-tabs {
76
+ flex-shrink: 0;
77
+ }
78
+ .fev-sidebar-tree {
79
+ display: none;
80
+ }
81
+ .fev-editor-wrapper {
82
+ display: flex;
83
+ flex-direction: column;
84
+ flex: 1;
85
+ min-height: 320px;
86
+ }
87
+
88
+ /* 宽屏:grid 布局,文件列表在右侧 */
89
+ @container (min-width: 768px) {
90
+ .fev-layout {
91
+ display: grid;
92
+ grid-template-columns: 1fr 220px;
93
+ gap: 1rem;
94
+ }
95
+ .fev-sidebar-tabs {
96
+ display: none;
97
+ }
98
+ .fev-sidebar-tree {
99
+ display: block;
100
+ order: 2;
101
+ }
102
+ .fev-editor-wrapper {
103
+ order: 1;
104
+ min-height: 480px;
105
+ }
106
+ }
107
+ .CodeMirror {
108
+ line-height: 21px;
109
+ }
110
+ `
111
+
112
+ interface FileEntry {
113
+ path: string
114
+ type: 'file' | 'directory'
115
+ content?: string | null
116
+ }
117
+
118
+ /** 面包屑路径导航 */
119
+ function Breadcrumb({
120
+ path,
121
+ entries,
122
+ onNavigate,
123
+ }: {
124
+ path: string
125
+ entries: FileEntry[]
126
+ onNavigate: (path: string) => void
127
+ }) {
128
+ const parts = path.split('/')
129
+ const isMarkdown = path.endsWith('.md')
130
+
131
+ // 构建可点击的路径段
132
+ const segments: { name: string; path: string; isFile: boolean }[] = []
133
+ for (let i = 0; i < parts.length; i++) {
134
+ const segmentPath = parts.slice(0, i + 1).join('/')
135
+ const isFile = i === parts.length - 1
136
+ segments.push({ name: parts[i], path: segmentPath, isFile })
137
+ }
138
+
139
+ return (
140
+ <div className="border-border/50 bg-muted/20 flex items-center gap-1 overflow-x-auto border-b px-3 py-2 text-xs">
141
+ {segments.map((segment, i) => {
142
+ const isLast = i === segments.length - 1
143
+ const canNavigate =
144
+ !isLast && entries.some((e) => e.type === 'file' && e.path.startsWith(segment.path + '/'))
145
+
146
+ return (
147
+ <span key={segment.path} className="flex items-center gap-1">
148
+ {i > 0 && <ChevronRight className="text-muted-foreground/50 h-3 w-3" />}
149
+ {isLast ? (
150
+ <span className="text-foreground flex items-center gap-1.5">
151
+ {segment.isFile ? (
152
+ isMarkdown ? (
153
+ <FileText className="h-3.5 w-3.5" />
154
+ ) : (
155
+ <File className="h-3.5 w-3.5" />
156
+ )
157
+ ) : (
158
+ <Folder className="h-3.5 w-3.5" />
159
+ )}
160
+ {segment.name}
161
+ </span>
162
+ ) : canNavigate ? (
163
+ <button
164
+ onClick={() => {
165
+ // 找到该目录下的第一个文件
166
+ const firstFile = entries.find(
167
+ (e) => e.type === 'file' && e.path.startsWith(segment.path + '/')
168
+ )
169
+ if (firstFile) onNavigate(firstFile.path)
170
+ }}
171
+ className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 transition-colors"
172
+ >
173
+ <Folder className="h-3.5 w-3.5" />
174
+ {segment.name}
175
+ </button>
176
+ ) : (
177
+ <span className="text-muted-foreground flex items-center gap-1.5">
178
+ <Folder className="h-3.5 w-3.5" />
179
+ {segment.name}
180
+ </span>
181
+ )}
182
+ </span>
183
+ )
184
+ })}
185
+ </div>
186
+ )
187
+ }
188
+
189
+ /** 窄屏下的文件标签栏 */
190
+ function FileTabs({
191
+ entries,
192
+ selectedPath,
193
+ onSelect,
194
+ }: {
195
+ entries: FileEntry[]
196
+ selectedPath: string | null
197
+ onSelect: (path: string) => void
198
+ }) {
199
+ const files = entries.filter((e) => e.type === 'file')
200
+
201
+ return (
202
+ <div className="scrollbar-thin scrollbar-track-transparent border-border bg-muted/30 flex gap-1 overflow-x-auto rounded-md border p-1">
203
+ {files.map((entry) => {
204
+ const isActive = entry.path === selectedPath
205
+ const isMarkdown = entry.path.endsWith('.md')
206
+
207
+ return (
208
+ <button
209
+ key={entry.path}
210
+ onClick={() => onSelect(entry.path)}
211
+ title={entry.path}
212
+ className={`flex shrink-0 items-center gap-1.5 rounded px-2.5 py-1.5 text-xs transition-colors ${
213
+ isActive
214
+ ? 'bg-background text-foreground shadow-sm'
215
+ : 'text-muted-foreground hover:bg-background/50 hover:text-foreground'
216
+ }`}
217
+ >
218
+ {isMarkdown ? <FileText className="h-3.5 w-3.5" /> : <File className="h-3.5 w-3.5" />}
219
+ <span className="max-w-[120px] truncate">{getFileName(entry.path)}</span>
220
+ </button>
221
+ )
222
+ })}
223
+ </div>
224
+ )
225
+ }
226
+
227
+ /** 宽屏下的文件树列表 */
228
+ function FileTree({
229
+ entries,
230
+ selectedPath,
231
+ onSelect,
232
+ }: {
233
+ entries: FileEntry[]
234
+ selectedPath: string | null
235
+ onSelect: (path: string) => void
236
+ }) {
237
+ // 计算每个条目相对于其父目录的缩进级别
238
+ const getIndentLevel = (entry: FileEntry): number => {
239
+ const parentPath = getParentPath(entry.path)
240
+ if (!parentPath) return 0
241
+
242
+ // 找到直接父目录
243
+ const parentExists = entries.some((e) => e.type === 'directory' && e.path === parentPath)
244
+ if (parentExists) {
245
+ const parentEntry = entries.find((e) => e.path === parentPath)!
246
+ return getIndentLevel(parentEntry) + 1
247
+ }
248
+ // 父目录不存在于列表中,计算路径深度
249
+ return entry.path.split('/').length - 1
250
+ }
251
+
252
+ return (
253
+ <div className="border-border bg-muted/30 flex h-full flex-col rounded-md border">
254
+ <div className="border-border/50 text-muted-foreground border-b px-3 py-2 text-xs font-medium uppercase">
255
+ Files
256
+ </div>
257
+ <div className="scrollbar-thin scrollbar-track-transparent flex-1 overflow-y-auto">
258
+ {entries.map((entry) => {
259
+ const depth = getIndentLevel(entry)
260
+ const isActive = entry.path === selectedPath
261
+ const isFile = entry.type === 'file'
262
+
263
+ const icon = isFile ? (
264
+ entry.path.endsWith('.md') ? (
265
+ <FileText className="h-4 w-4 shrink-0" />
266
+ ) : (
267
+ <File className="h-4 w-4 shrink-0" />
268
+ )
269
+ ) : (
270
+ <Folder className="h-4 w-4 shrink-0" />
271
+ )
272
+
273
+ return (
274
+ <button
275
+ key={entry.path}
276
+ disabled={!isFile}
277
+ onClick={() => isFile && onSelect(entry.path)}
278
+ className={`flex w-full items-center gap-2 px-3 py-1.5 text-sm transition-colors ${
279
+ isActive
280
+ ? 'bg-primary/10 text-foreground'
281
+ : isFile
282
+ ? 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
283
+ : 'text-muted-foreground cursor-default'
284
+ }`}
285
+ style={{ paddingLeft: 12 + depth * 14 }}
286
+ >
287
+ {icon}
288
+ <span className={`truncate ${!isFile ? 'text-foreground font-medium' : ''}`}>
289
+ {getFileName(entry.path)}
290
+ </span>
291
+ </button>
292
+ )
293
+ })}
294
+ </div>
295
+ </div>
296
+ )
297
+ }
298
+
299
+ export function FolderEditorViewer({
300
+ changeId,
301
+ archived = false,
302
+ }: {
303
+ changeId: string
304
+ archived?: boolean
305
+ }) {
306
+ const {
307
+ data: files,
308
+ isLoading,
309
+ error,
310
+ } = archived ? useArchiveFilesSubscription(changeId) : useChangeFilesSubscription(changeId)
311
+ const [selectedPath, setSelectedPath] = useState<string | null>(null)
312
+
313
+ const sortedEntries = useMemo(() => {
314
+ if (!files) return []
315
+ return [...files].sort(compareEntries)
316
+ }, [files])
317
+
318
+ useEffect(() => {
319
+ if (!sortedEntries.length) {
320
+ setSelectedPath(null)
321
+ return
322
+ }
323
+ const current = sortedEntries.find(
324
+ (entry) => entry.path === selectedPath && entry.type === 'file'
325
+ )
326
+ if (!current) {
327
+ const firstFile = sortedEntries.find((entry) => entry.type === 'file')
328
+ setSelectedPath(firstFile?.path ?? null)
329
+ }
330
+ }, [sortedEntries, selectedPath])
331
+
332
+ const activeFile = useMemo(() => {
333
+ if (!sortedEntries.length || !selectedPath) return null
334
+ return (
335
+ sortedEntries.find((entry) => entry.path === selectedPath && entry.type === 'file') ?? null
336
+ )
337
+ }, [sortedEntries, selectedPath])
338
+
339
+ if (isLoading) {
340
+ return (
341
+ <div className="border-border bg-muted/20 flex h-[400px] items-center justify-center rounded-md border">
342
+ <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
343
+ </div>
344
+ )
345
+ }
346
+
347
+ if (error) {
348
+ return (
349
+ <div className="border-destructive/50 bg-destructive/10 text-destructive rounded-md border p-4 text-sm">
350
+ Failed to load files: {error.message}
351
+ </div>
352
+ )
353
+ }
354
+
355
+ if (!sortedEntries.length) {
356
+ return (
357
+ <div className="bg-muted/20 text-muted-foreground rounded-md p-4 text-sm">
358
+ No files found for this change.
359
+ </div>
360
+ )
361
+ }
362
+
363
+ return (
364
+ <div className="@container-[size] h-full">
365
+ <style>{layoutStyles}</style>
366
+ <div className="fev-layout">
367
+ {/* 窄屏:文件标签栏 */}
368
+ <div className="fev-sidebar-tabs">
369
+ <FileTabs
370
+ entries={sortedEntries}
371
+ selectedPath={selectedPath}
372
+ onSelect={setSelectedPath}
373
+ />
374
+ </div>
375
+
376
+ {/* 宽屏:文件树 */}
377
+ <div className="fev-sidebar-tree">
378
+ <FileTree
379
+ entries={sortedEntries}
380
+ selectedPath={selectedPath}
381
+ onSelect={setSelectedPath}
382
+ />
383
+ </div>
384
+
385
+ {/* 编辑器区域:面包屑 + CodeMirror */}
386
+ <div className="fev-editor-wrapper border-border bg-background overflow-hidden rounded-md border shadow-sm">
387
+ {activeFile ? (
388
+ <>
389
+ <Breadcrumb
390
+ path={activeFile.path}
391
+ entries={sortedEntries}
392
+ onNavigate={setSelectedPath}
393
+ />
394
+ <CodeEditor
395
+ key={activeFile.path}
396
+ value={activeFile.content ?? ''}
397
+ filename={activeFile.path}
398
+ readOnly
399
+ className="min-h-0 flex-1"
400
+ />
401
+ </>
402
+ ) : (
403
+ <div className="text-muted-foreground flex h-full items-center justify-center">
404
+ Select a file to view
405
+ </div>
406
+ )}
407
+ </div>
408
+ </div>
409
+ </div>
410
+ )
411
+ }