@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.
- package/dist/assets/abap-BdImnpbu.js +1 -0
- package/dist/assets/actionscript-3-CfeIJUat.js +1 -0
- package/dist/assets/ada-bCR0ucgS.js +1 -0
- package/dist/assets/andromeeda-C-Jbm3Hp.js +1 -0
- package/dist/assets/angular-html-CU67Zn6k.js +1 -0
- package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
- package/dist/assets/apache-Pmp26Uib.js +1 -0
- package/dist/assets/apex-DDbsPZ6N.js +1 -0
- package/dist/assets/apl-B4CMkyY2.js +1 -0
- package/dist/assets/apl-dKokRX4l.js +1 -0
- package/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/assets/ara-BRHolxvo.js +1 -0
- package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/assets/asciidoc-Dv7Oe6Be.js +1 -0
- package/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/assets/astro-CbQHKStN.js +1 -0
- package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/assets/awk-DMzUqQB5.js +1 -0
- package/dist/assets/ayu-dark-Cv9koXgw.js +1 -0
- package/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/assets/bat-BkioyH1T.js +1 -0
- package/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/assets/berry-uYugtg8r.js +1 -0
- package/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/assets/blade-DVc8C-J4.js +1 -0
- package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/assets/c-BIGW1oBm.js +1 -0
- package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/assets/clarity-D53aC0YG.js +1 -0
- package/dist/assets/clike-B9uivgTg.js +1 -0
- package/dist/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/assets/clojure-P80f7IUj.js +1 -0
- package/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/assets/cobol-nwyudZeR.js +1 -0
- package/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/assets/coq-DkFqJrB1.js +1 -0
- package/dist/assets/cpp-CofmeUqb.js +1 -0
- package/dist/assets/crystal-SjHAIU92.js +1 -0
- package/dist/assets/crystal-tKQVLTB8.js +1 -0
- package/dist/assets/csharp-K5feNrxe.js +1 -0
- package/dist/assets/css-BnMrqG3P.js +1 -0
- package/dist/assets/css-DPfMkruS.js +1 -0
- package/dist/assets/csv-fuZLfV_i.js +1 -0
- package/dist/assets/cue-D82EKSYY.js +1 -0
- package/dist/assets/cypher-COkxafJQ.js +1 -0
- package/dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/assets/d-85-TOEBH.js +1 -0
- package/dist/assets/d-pRatUO7H.js +1 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/dart-CF10PKvl.js +1 -0
- package/dist/assets/dax-CEL-wOlO.js +1 -0
- package/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/assets/diff-D97Zzqfu.js +1 -0
- package/dist/assets/diff-DbItnlRl.js +1 -0
- package/dist/assets/docker-BcOcwvcX.js +1 -0
- package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/assets/dylan-DwRh75JA.js +1 -0
- package/dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/assets/ecl-Cabwm37j.js +1 -0
- package/dist/assets/edge-BkV0erSs.js +1 -0
- package/dist/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/assets/elixir-CDX3lj18.js +1 -0
- package/dist/assets/elm-DbKCFpqz.js +1 -0
- package/dist/assets/elm-vLlmbW-K.js +1 -0
- package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/dist/assets/erb-BOJIQeun.js +1 -0
- package/dist/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/assets/factor-kuTfRLto.js +1 -0
- package/dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/assets/fennel-BYunw83y.js +1 -0
- package/dist/assets/fish-BvzEVeQv.js +1 -0
- package/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/assets/forth-Ffai-XNe.js +1 -0
- package/dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
- package/dist/assets/fortran-free-form-D22FLkUw.js +1 -0
- package/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/assets/gas-Bneqetm1.js +1 -0
- package/dist/assets/gdresource-B7Tvp0Sc.js +1 -0
- package/dist/assets/gdscript-DTMYz4Jt.js +1 -0
- package/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/assets/genie-D0YGMca9.js +1 -0
- package/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/assets/gleam-BspZqrRM.js +1 -0
- package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
- package/dist/assets/glsl-DplSGwfg.js +1 -0
- package/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/assets/go-Dn2_MT6a.js +1 -0
- package/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/assets/hack-CaT9iCJl.js +1 -0
- package/dist/assets/haml-B8DHNrY2.js +1 -0
- package/dist/assets/handlebars-BL8al0AC.js +1 -0
- package/dist/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/assets/hjson-D5-asLiD.js +1 -0
- package/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/assets/houston-DnULxvSX.js +1 -0
- package/dist/assets/html-GMplVEZG.js +1 -0
- package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/dist/assets/http-DBlCnlav.js +1 -0
- package/dist/assets/http-jrhK8wxY.js +1 -0
- package/dist/assets/hurl-irOxFIW8.js +1 -0
- package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/assets/hy-DFXneXwc.js +1 -0
- package/dist/assets/idl-BEugSyMb.js +1 -0
- package/dist/assets/imba-DGztddWO.js +1 -0
- package/dist/assets/index-8c6bEJ99.js +1 -0
- package/dist/assets/index-AbWe21oh.js +2 -0
- package/dist/assets/index-ArhptQw0.js +1 -0
- package/dist/assets/index-B1hpa--1.js +3 -0
- package/dist/assets/index-Bafja8o4.js +1 -0
- package/dist/assets/index-Bp00uZNc.js +1 -0
- package/dist/assets/index-BsTieXqQ.js +1 -0
- package/dist/assets/index-BvGAWAqS.js +1 -0
- package/dist/assets/index-CCfVkFzN.js +1 -0
- package/dist/assets/index-D-Urq2hl.css +1 -0
- package/dist/assets/index-D3mXuuih.js +1 -0
- package/dist/assets/index-DFOLYN6W.js +1 -0
- package/dist/assets/index-DpxkOmNJ.js +7 -0
- package/dist/assets/index-YZ-iXB95.js +309 -0
- package/dist/assets/index-eA_XNQ_L.js +1 -0
- package/dist/assets/index-ftYom_wU.js +1 -0
- package/dist/assets/index-gvPT4BlL.js +1 -0
- package/dist/assets/ini-BEwlwnbL.js +1 -0
- package/dist/assets/java-CylS5w8V.js +1 -0
- package/dist/assets/javascript-iXu5QeM3.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
- package/dist/assets/jison-wvAkD_A8.js +1 -0
- package/dist/assets/json-Cp-IABpG.js +1 -0
- package/dist/assets/json5-C9tS-k6U.js +1 -0
- package/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/assets/julia-C8NyazO9.js +1 -0
- package/dist/assets/julia-DuME0IfC.js +1 -0
- package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/assets/kdl-DV7GczEv.js +1 -0
- package/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/assets/kusto-BvAqAH-y.js +1 -0
- package/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/assets/latex-BdAV_C_H.js +1 -0
- package/dist/assets/lean-Bc6EcWN3.js +1 -0
- package/dist/assets/less-B1dDrJ26.js +1 -0
- package/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/assets/liquid-DYVedYrR.js +1 -0
- package/dist/assets/livescript-BwQOo05w.js +1 -0
- package/dist/assets/llvm-BtvRca6l.js +1 -0
- package/dist/assets/log-2UxHyX5q.js +1 -0
- package/dist/assets/logo-BtOb2qkB.js +1 -0
- package/dist/assets/lua-BbnMAYS6.js +1 -0
- package/dist/assets/lua-BgMRiT3U.js +1 -0
- package/dist/assets/luau-CXu1NL6O.js +1 -0
- package/dist/assets/make-CHLpvVh8.js +1 -0
- package/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/assets/marko-CPi9NSCl.js +1 -0
- package/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/assets/matlab-D7o27uSR.js +1 -0
- package/dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/assets/mdc-DUICxH0z.js +1 -0
- package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/assets/mermaid-DKYwYmdq.js +1 -0
- package/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/assets/min-light-CTRr51gU.js +1 -0
- package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/assets/mllike-CXdrOF99.js +1 -0
- package/dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/assets/mojo-1DNp92w6.js +1 -0
- package/dist/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/assets/move-Bu9oaDYs.js +1 -0
- package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/assets/mumps-BT43cFF4.js +1 -0
- package/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/assets/nextflow-BrzmwbiE.js +1 -0
- package/dist/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/assets/nginx-DknmC5AR.js +1 -0
- package/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/assets/nim-CVrawwO9.js +1 -0
- package/dist/assets/nix-c8nO5XWb.js +1 -0
- package/dist/assets/nord-Ddv68eIx.js +1 -0
- package/dist/assets/nsis-LdVXkNf5.js +1 -0
- package/dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/assets/nushell-C-sUppwS.js +1 -0
- package/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/assets/octave-Ck1zUtKM.js +1 -0
- package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/assets/one-light-PoHY5YXO.js +1 -0
- package/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/assets/oz-BzwKVEFT.js +1 -0
- package/dist/assets/pascal--L3eBynH.js +1 -0
- package/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/assets/perl-C0TMdlhV.js +1 -0
- package/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/assets/php-CDn_0X-4.js +1 -0
- package/dist/assets/pig-CevX1Tat.js +1 -0
- package/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/assets/po-BTJTHyun.js +1 -0
- package/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/assets/polar-C0HS_06l.js +1 -0
- package/dist/assets/postcss-CXtECtnM.js +1 -0
- package/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/assets/properties-C78fOPTZ.js +1 -0
- package/dist/assets/proto-DyJlTyXw.js +1 -0
- package/dist/assets/protobuf-ChK-085T.js +1 -0
- package/dist/assets/pug-CGlum2m_.js +1 -0
- package/dist/assets/pug-DeIclll2.js +1 -0
- package/dist/assets/puppet-BMWR74SV.js +1 -0
- package/dist/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/assets/purescript-CklMAg4u.js +1 -0
- package/dist/assets/python-B6aJPvgy.js +1 -0
- package/dist/assets/python-BuPzkPfP.js +1 -0
- package/dist/assets/q-pXgVlZs6.js +1 -0
- package/dist/assets/qml-3beO22l8.js +1 -0
- package/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/assets/qss-IeuSbFQv.js +1 -0
- package/dist/assets/r-B6wPVr8A.js +1 -0
- package/dist/assets/r-DiinP2Uv.js +1 -0
- package/dist/assets/racket-BqYA7rlc.js +1 -0
- package/dist/assets/raku-DXvB9xmW.js +1 -0
- package/dist/assets/razor-CE9lU5zL.js +1 -0
- package/dist/assets/red-bN70gL4F.js +1 -0
- package/dist/assets/reg-C-SQnVFl.js +1 -0
- package/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/assets/rel-C3B-1QV4.js +1 -0
- package/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/assets/rst-B0xPkSld.js +1 -0
- package/dist/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/assets/ruby-BvKwtOVI.js +1 -0
- package/dist/assets/rust-B1yitclQ.js +1 -0
- package/dist/assets/sas-B4kiWyti.js +1 -0
- package/dist/assets/sas-cz2c8ADy.js +1 -0
- package/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/assets/scala-C151Ov-r.js +1 -0
- package/dist/assets/scheme-C41bIUwD.js +1 -0
- package/dist/assets/scheme-C98Dy4si.js +1 -0
- package/dist/assets/scss-OYdSNvt2.js +1 -0
- package/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/assets/shell-CjFT_Tl9.js +1 -0
- package/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/assets/solidity-rGO070M0.js +1 -0
- package/dist/assets/solr-DehyRSwq.js +1 -0
- package/dist/assets/soy-Brmx7dQM.js +1 -0
- package/dist/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/assets/sql-BLtJtn59.js +1 -0
- package/dist/assets/sql-D0XecflT.js +1 -0
- package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/assets/stata-BH5u7GGu.js +1 -0
- package/dist/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/assets/stylus-B533Al4x.js +1 -0
- package/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/assets/svelte-3Dk4HxPD.js +1 -0
- package/dist/assets/swift-BzpIVaGY.js +1 -0
- package/dist/assets/swift-Dg5xB15N.js +1 -0
- package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/assets/templ-W15q3VgB.js +1 -0
- package/dist/assets/terraform-BETggiCN.js +1 -0
- package/dist/assets/tex-CxkMU7Pf.js +1 -0
- package/dist/assets/textile-CnDTJFAw.js +1 -0
- package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/assets/tiki-DGYXhP31.js +1 -0
- package/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/assets/toml-vGWfd6FD.js +1 -0
- package/dist/assets/troff-wAsdV37c.js +1 -0
- package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/assets/tsx-COt5Ahok.js +1 -0
- package/dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/assets/turtle-BsS91CYL.js +1 -0
- package/dist/assets/twig-CO9l9SDP.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/typespec-BGHnOYBU.js +1 -0
- package/dist/assets/typst-DHCkPAjA.js +1 -0
- package/dist/assets/v-BcVCzyr7.js +1 -0
- package/dist/assets/vala-CsfeWuGM.js +1 -0
- package/dist/assets/vb-CmGdzxic.js +1 -0
- package/dist/assets/vb-D17OF-Vu.js +1 -0
- package/dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/assets/velocity-D8B20fx6.js +1 -0
- package/dist/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/assets/vesper-DU1UobuO.js +1 -0
- package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/assets/viml-CJc9bBzg.js +1 -0
- package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/assets/vue-DnHKYNfI.js +1 -0
- package/dist/assets/vue-html-CChd_i61.js +1 -0
- package/dist/assets/vue-vine-8moa0y9V.js +1 -0
- package/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/assets/wit-5i3qLPDT.js +1 -0
- package/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/assets/xquery-DzFWVndE.js +1 -0
- package/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/assets/yaml-Buea-lGh.js +1 -0
- package/dist/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/assets/zig-VOosw3JB.js +1 -0
- package/dist/index.html +47 -0
- package/dist/logo.svg +8 -0
- package/dist/openspec_pixel_dark.svg +89 -0
- package/dist/openspec_pixel_light.svg +89 -0
- package/index.html +46 -0
- package/package.json +66 -0
- package/src/App.tsx +124 -0
- package/src/components/StaticModeBanner.tsx +46 -0
- package/src/components/change-overview.tsx +156 -0
- package/src/components/cli-terminal.tsx +93 -0
- package/src/components/code-editor.tsx +232 -0
- package/src/components/copyable-path.tsx +44 -0
- package/src/components/dialog.tsx +193 -0
- package/src/components/folder-editor-viewer.tsx +411 -0
- package/src/components/global-archive-modal.tsx +205 -0
- package/src/components/layout/desktop-sidebar.tsx +48 -0
- package/src/components/layout/index.ts +6 -0
- package/src/components/layout/mobile-header.tsx +91 -0
- package/src/components/layout/mobile-tabbar.tsx +20 -0
- package/src/components/layout/nav-items.ts +33 -0
- package/src/components/layout/root-layout.tsx +67 -0
- package/src/components/layout/status-bar.tsx +69 -0
- package/src/components/markdown-content.tsx +104 -0
- package/src/components/markdown-viewer.tsx +415 -0
- package/src/components/path-marquee.tsx +104 -0
- package/src/components/tabs.tsx +151 -0
- package/src/components/tasks-view.test.tsx +45 -0
- package/src/components/tasks-view.tsx +209 -0
- package/src/components/toc-context.tsx +177 -0
- package/src/components/toc.tsx +290 -0
- package/src/entry-client.tsx +47 -0
- package/src/index.css +481 -0
- package/src/lib/api-config.ts +72 -0
- package/src/lib/archive-modal-context.tsx +45 -0
- package/src/lib/codemirror-markdown-preview.ts +494 -0
- package/src/lib/format-time.ts +51 -0
- package/src/lib/shiki-highlighter.ts +47 -0
- package/src/lib/static-data-provider.ts +386 -0
- package/src/lib/static-mode.ts +158 -0
- package/src/lib/trpc.ts +107 -0
- package/src/lib/use-cli-runner.tsx +433 -0
- package/src/lib/use-dark-mode.ts +28 -0
- package/src/lib/use-server-status.ts +208 -0
- package/src/lib/use-subscription.ts +375 -0
- package/src/lib/use-tabs-status-by-query.ts +78 -0
- package/src/main.tsx +9 -0
- package/src/routes/archive-list.tsx +65 -0
- package/src/routes/archive-view.tsx +116 -0
- package/src/routes/change-list.tsx +63 -0
- package/src/routes/change-view.tsx +188 -0
- package/src/routes/dashboard.tsx +204 -0
- package/src/routes/project.tsx +272 -0
- package/src/routes/settings.tsx +816 -0
- package/src/routes/spec-list.tsx +49 -0
- package/src/routes/spec-view.tsx +164 -0
- package/src/ssg/entry-server.tsx +111 -0
- package/src/ssg/index.ts +6 -0
- package/src/ssg/prerender.ts +111 -0
- package/src/ssg/static-data-context.tsx +49 -0
- package/src/ssg/types.ts +5 -0
- package/src/test/setup.ts +1 -0
- package/src/vite-env.d.ts +6 -0
- package/tsconfig.json +18 -0
- package/vite.config.ts +58 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { useArchiveModal } from '@/lib/archive-modal-context'
|
|
2
|
+
import { useCliRunner } from '@/lib/use-cli-runner'
|
|
3
|
+
import { useNavigate } from '@tanstack/react-router'
|
|
4
|
+
import { Archive, CheckCircle, Loader2 } from 'lucide-react'
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
6
|
+
import { Dialog } from './dialog'
|
|
7
|
+
import { CliTerminal } from './cli-terminal'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 全局 Archive Modal(单一对话框,点击 Archive 后直接串行 validate -> archive)
|
|
11
|
+
*/
|
|
12
|
+
export function GlobalArchiveModal() {
|
|
13
|
+
const navigate = useNavigate()
|
|
14
|
+
const { state, closeArchiveModal } = useArchiveModal()
|
|
15
|
+
const { open, changeId, changeName } = state
|
|
16
|
+
|
|
17
|
+
const [skipSpecs, setSkipSpecs] = useState(false)
|
|
18
|
+
const [noValidate, setNoValidate] = useState(false)
|
|
19
|
+
const [detectedArchiveId, setDetectedArchiveId] = useState<string | null>(null)
|
|
20
|
+
|
|
21
|
+
const runner = useCliRunner({
|
|
22
|
+
onCreateProcess: (process) => {
|
|
23
|
+
process.on('data', (data) => {
|
|
24
|
+
const match = /Change ['"](.+?)['"] archived as ['"](.+?)['"]/.exec(String(data))
|
|
25
|
+
if (match?.[2]) {
|
|
26
|
+
setDetectedArchiveId(match[2])
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
const { lines, status, hasStarted, commands, reset, cancel } = runner
|
|
32
|
+
|
|
33
|
+
// 当 Modal 打开时重置状态
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (open) {
|
|
36
|
+
setSkipSpecs(false)
|
|
37
|
+
setNoValidate(false)
|
|
38
|
+
setDetectedArchiveId(null)
|
|
39
|
+
}
|
|
40
|
+
}, [open])
|
|
41
|
+
|
|
42
|
+
// 关闭并重置 - 使用 useCallback 稳定引用
|
|
43
|
+
const handleClose = () => {
|
|
44
|
+
cancel()
|
|
45
|
+
reset()
|
|
46
|
+
setSkipSpecs(false)
|
|
47
|
+
setNoValidate(false)
|
|
48
|
+
setDetectedArchiveId(null)
|
|
49
|
+
closeArchiveModal()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const buildQueue = useCallback(() => {
|
|
53
|
+
if (!changeId) return []
|
|
54
|
+
const queue: Array<{ command: string; args?: string[] }> = []
|
|
55
|
+
if (!noValidate) {
|
|
56
|
+
queue.push({ command: 'openspec', args: ['validate', changeId] })
|
|
57
|
+
}
|
|
58
|
+
const archiveArgs = ['archive', '-y', changeId]
|
|
59
|
+
if (skipSpecs) archiveArgs.push('--skip-specs')
|
|
60
|
+
archiveArgs.push('--no-validate')
|
|
61
|
+
queue.push({ command: 'openspec', args: archiveArgs })
|
|
62
|
+
return queue
|
|
63
|
+
}, [changeId, noValidate, skipSpecs])
|
|
64
|
+
|
|
65
|
+
const isRunning = status === 'running'
|
|
66
|
+
const isArchiveSuccess = status === 'success' && !!detectedArchiveId
|
|
67
|
+
const isArchiveOutputMissingId = status === 'success' && !detectedArchiveId
|
|
68
|
+
const archiveStatus = isArchiveSuccess ? 'success' : isArchiveOutputMissingId ? 'error' : status
|
|
69
|
+
const successArchiveId = detectedArchiveId ?? ''
|
|
70
|
+
|
|
71
|
+
// 开始执行 archive(若之前失败则自动重置并重跑)
|
|
72
|
+
const handleStartArchive = () => {
|
|
73
|
+
if (!changeId) return
|
|
74
|
+
commands.runAll()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handleReset = () => {
|
|
78
|
+
reset()
|
|
79
|
+
setSkipSpecs(false)
|
|
80
|
+
setNoValidate(false)
|
|
81
|
+
setDetectedArchiveId(null)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!open || !changeId || hasStarted) return
|
|
86
|
+
const queue = buildQueue()
|
|
87
|
+
commands.replaceAll(queue)
|
|
88
|
+
}, [buildQueue, changeId, commands, hasStarted, open])
|
|
89
|
+
|
|
90
|
+
if (!open || !changeId) return null
|
|
91
|
+
|
|
92
|
+
const borderVariant = archiveStatus === 'error' ? 'error' : archiveStatus === 'success' ? 'success' : 'default'
|
|
93
|
+
|
|
94
|
+
const footer = archiveStatus === 'success' && successArchiveId ? (
|
|
95
|
+
<div className="flex w-full items-center justify-between gap-3">
|
|
96
|
+
<div className="text-sm text-green-600">Archived as {successArchiveId}</div>
|
|
97
|
+
<div className="flex items-center gap-2">
|
|
98
|
+
<button onClick={handleClose} className="bg-muted hover:bg-muted/80 rounded-md px-4 py-2">
|
|
99
|
+
Close
|
|
100
|
+
</button>
|
|
101
|
+
<button
|
|
102
|
+
onClick={() => {
|
|
103
|
+
handleClose()
|
|
104
|
+
navigate({ to: '/archive/$changeId', params: { changeId: successArchiveId } })
|
|
105
|
+
}}
|
|
106
|
+
className="bg-primary text-primary-foreground rounded-md px-4 py-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
107
|
+
disabled={!successArchiveId}
|
|
108
|
+
>
|
|
109
|
+
View Archive
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
) : (
|
|
114
|
+
<>
|
|
115
|
+
<button onClick={handleReset} className="bg-muted hover:bg-muted/80 rounded-md px-4 py-2">
|
|
116
|
+
{archiveStatus === 'error' ? 'Reset & Retry' : 'Reset'}
|
|
117
|
+
</button>
|
|
118
|
+
<button
|
|
119
|
+
onClick={handleClose}
|
|
120
|
+
className="bg-muted hover:bg-muted/80 rounded-md px-4 py-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
121
|
+
disabled={isRunning}
|
|
122
|
+
>
|
|
123
|
+
Close
|
|
124
|
+
</button>
|
|
125
|
+
<button
|
|
126
|
+
onClick={archiveStatus === 'error' ? handleReset : handleStartArchive}
|
|
127
|
+
disabled={isRunning}
|
|
128
|
+
className="flex items-center gap-2 rounded-md bg-red-600 px-4 py-2 text-white hover:bg-red-700 disabled:cursor-not-allowed disabled:opacity-50"
|
|
129
|
+
>
|
|
130
|
+
{isRunning ? <Loader2 className="h-4 w-4 animate-spin" /> : <Archive className="h-4 w-4" />}
|
|
131
|
+
{archiveStatus === 'error' ? 'Reset before Archive' : 'Archive'}
|
|
132
|
+
</button>
|
|
133
|
+
</>
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Dialog
|
|
138
|
+
open={open}
|
|
139
|
+
onClose={handleClose}
|
|
140
|
+
title={
|
|
141
|
+
<div className="flex items-center gap-2">
|
|
142
|
+
{archiveStatus === 'success' ? (
|
|
143
|
+
<CheckCircle className="h-5 w-5 text-green-500" />
|
|
144
|
+
) : (
|
|
145
|
+
<Archive className="h-5 w-5 text-red-500" />
|
|
146
|
+
)}
|
|
147
|
+
<span className="font-semibold">Archive: {changeName}</span>
|
|
148
|
+
</div>
|
|
149
|
+
}
|
|
150
|
+
footer={footer}
|
|
151
|
+
borderVariant={borderVariant}
|
|
152
|
+
>
|
|
153
|
+
<div className="space-y-4">
|
|
154
|
+
<div className="bg-muted/50 rounded-lg p-3">
|
|
155
|
+
<p className="text-muted-foreground text-sm">Change to archive:</p>
|
|
156
|
+
<p className="font-medium">{changeName}</p>
|
|
157
|
+
<p className="text-muted-foreground mt-1 text-xs">ID: {changeId}</p>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<CliTerminal
|
|
161
|
+
lines={lines}
|
|
162
|
+
maxHeight="50vh"
|
|
163
|
+
/>
|
|
164
|
+
|
|
165
|
+
{isArchiveOutputMissingId && (
|
|
166
|
+
<div className="border-amber-200 bg-amber-100 text-amber-900 rounded-md border px-3 py-2 text-sm">
|
|
167
|
+
Archive output did not include the archived change name. Treating archive as failed.
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
<div className="space-y-3">
|
|
172
|
+
<p className="text-sm font-medium">Options</p>
|
|
173
|
+
|
|
174
|
+
<label className="flex cursor-pointer items-start gap-3">
|
|
175
|
+
<input
|
|
176
|
+
type="checkbox"
|
|
177
|
+
checked={skipSpecs}
|
|
178
|
+
onChange={(e) => setSkipSpecs(e.target.checked)}
|
|
179
|
+
className="border-border mt-1 h-4 w-4 rounded"
|
|
180
|
+
disabled={hasStarted}
|
|
181
|
+
/>
|
|
182
|
+
<div>
|
|
183
|
+
<p className="text-sm font-medium">Skip specs update</p>
|
|
184
|
+
<p className="text-muted-foreground text-xs">Don't update spec files with delta changes (--skip-specs)</p>
|
|
185
|
+
</div>
|
|
186
|
+
</label>
|
|
187
|
+
|
|
188
|
+
<label className="flex cursor-pointer items-start gap-3">
|
|
189
|
+
<input
|
|
190
|
+
type="checkbox"
|
|
191
|
+
checked={noValidate}
|
|
192
|
+
onChange={(e) => setNoValidate(e.target.checked)}
|
|
193
|
+
className="border-border mt-1 h-4 w-4 rounded"
|
|
194
|
+
disabled={hasStarted}
|
|
195
|
+
/>
|
|
196
|
+
<div>
|
|
197
|
+
<p className="text-sm font-medium">Skip validation</p>
|
|
198
|
+
<p className="text-muted-foreground text-xs">Don't validate the change before archiving (--no-validate)</p>
|
|
199
|
+
</div>
|
|
200
|
+
</label>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</Dialog>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getBasePath } from '@/lib/static-mode'
|
|
2
|
+
import { useDarkMode } from '@/lib/use-dark-mode'
|
|
3
|
+
import { Link } from '@tanstack/react-router'
|
|
4
|
+
import { navItems, settingsItem } from './nav-items'
|
|
5
|
+
|
|
6
|
+
/** Desktop sidebar navigation */
|
|
7
|
+
export function DesktopSidebar() {
|
|
8
|
+
const isDark = useDarkMode()
|
|
9
|
+
|
|
10
|
+
// Get base path from runtime configuration
|
|
11
|
+
const basePath = getBasePath()
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<nav className="desktop-sidebar border-border bg-muted/30 flex w-64 shrink-0 flex-col border-r p-4">
|
|
15
|
+
<div className="mb-6">
|
|
16
|
+
<img
|
|
17
|
+
src={
|
|
18
|
+
isDark ? `${basePath}openspec_pixel_dark.svg` : `${basePath}openspec_pixel_light.svg`
|
|
19
|
+
}
|
|
20
|
+
alt="OpenSpec"
|
|
21
|
+
className="h-6"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
<ul className="flex-1 space-y-1">
|
|
25
|
+
{navItems.map((item) => (
|
|
26
|
+
<li key={item.to}>
|
|
27
|
+
<Link
|
|
28
|
+
to={item.to}
|
|
29
|
+
className="hover:bg-muted [&.active]:bg-primary [&.active]:text-primary-foreground flex items-center gap-2 rounded-md px-3 py-2"
|
|
30
|
+
>
|
|
31
|
+
<item.icon className="h-4 w-4 shrink-0" />
|
|
32
|
+
<span className="font-nav text-base tracking-[0.04em]">{item.label}</span>
|
|
33
|
+
</Link>
|
|
34
|
+
</li>
|
|
35
|
+
))}
|
|
36
|
+
</ul>
|
|
37
|
+
<div className="border-border border-t pt-4">
|
|
38
|
+
<Link
|
|
39
|
+
to={settingsItem.to}
|
|
40
|
+
className="hover:bg-muted [&.active]:bg-primary [&.active]:text-primary-foreground flex items-center gap-2 rounded-md px-3 py-2"
|
|
41
|
+
>
|
|
42
|
+
<settingsItem.icon className="h-4 w-4 shrink-0" />
|
|
43
|
+
<span className="font-nav text-base tracking-[0.04em]">{settingsItem.label}</span>
|
|
44
|
+
</Link>
|
|
45
|
+
</div>
|
|
46
|
+
</nav>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { RootLayout } from './root-layout'
|
|
2
|
+
export { DesktopSidebar } from './desktop-sidebar'
|
|
3
|
+
export { MobileHeader } from './mobile-header'
|
|
4
|
+
export { MobileTabBar } from './mobile-tabbar'
|
|
5
|
+
export { StatusIndicator, DesktopStatusBar } from './status-bar'
|
|
6
|
+
export { navItems, settingsItem, type NavItem } from './nav-items'
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getBasePath } from '@/lib/static-mode'
|
|
2
|
+
import { useDarkMode } from '@/lib/use-dark-mode'
|
|
3
|
+
import { useServerStatus } from '@/lib/use-server-status'
|
|
4
|
+
import { Link } from '@tanstack/react-router'
|
|
5
|
+
import { Menu, X } from 'lucide-react'
|
|
6
|
+
import { useState } from 'react'
|
|
7
|
+
import { navItems, settingsItem } from './nav-items'
|
|
8
|
+
import { StatusIndicator } from './status-bar'
|
|
9
|
+
|
|
10
|
+
/** Mobile header with hamburger menu */
|
|
11
|
+
export function MobileHeader() {
|
|
12
|
+
const [menuOpen, setMenuOpen] = useState(false)
|
|
13
|
+
const isDark = useDarkMode()
|
|
14
|
+
const serverStatus = useServerStatus()
|
|
15
|
+
const pageTitle = serverStatus.dirName ?? 'OpenSpec'
|
|
16
|
+
|
|
17
|
+
// Get base path from runtime configuration
|
|
18
|
+
const basePath = getBasePath()
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<header className="mobile-header border-border bg-background flex h-12 items-center justify-between border-b px-4">
|
|
23
|
+
<div className="flex items-center gap-3">
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => setMenuOpen(true)}
|
|
26
|
+
className="hover:bg-muted -ml-1.5 rounded-md p-1.5"
|
|
27
|
+
aria-label="Open menu"
|
|
28
|
+
>
|
|
29
|
+
<Menu className="h-5 w-5" />
|
|
30
|
+
</button>
|
|
31
|
+
<span className="font-nav text-[12px] tracking-[0.04em]">{pageTitle}</span>
|
|
32
|
+
</div>
|
|
33
|
+
<StatusIndicator />
|
|
34
|
+
</header>
|
|
35
|
+
|
|
36
|
+
{/* Mobile menu overlay */}
|
|
37
|
+
{menuOpen && (
|
|
38
|
+
<div className="fixed inset-0 z-50 flex">
|
|
39
|
+
<div
|
|
40
|
+
className="animate-in fade-in absolute inset-0 bg-black/50 duration-200"
|
|
41
|
+
onClick={() => setMenuOpen(false)}
|
|
42
|
+
/>
|
|
43
|
+
<nav className="bg-background border-border animate-in slide-in-from-left relative flex w-64 max-w-[80vw] flex-col border-r p-4 duration-200">
|
|
44
|
+
<div className="mb-6 flex items-center justify-between">
|
|
45
|
+
<img
|
|
46
|
+
src={
|
|
47
|
+
isDark
|
|
48
|
+
? `${basePath}openspec_pixel_dark.svg`
|
|
49
|
+
: `${basePath}openspec_pixel_light.svg`
|
|
50
|
+
}
|
|
51
|
+
alt="OpenSpec"
|
|
52
|
+
className="h-5"
|
|
53
|
+
/>
|
|
54
|
+
<button
|
|
55
|
+
onClick={() => setMenuOpen(false)}
|
|
56
|
+
className="hover:bg-muted rounded-md p-1.5"
|
|
57
|
+
aria-label="Close menu"
|
|
58
|
+
>
|
|
59
|
+
<X className="h-5 w-5" />
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
<ul className="flex-1 space-y-1">
|
|
63
|
+
{navItems.map((item) => (
|
|
64
|
+
<li key={item.to}>
|
|
65
|
+
<Link
|
|
66
|
+
to={item.to}
|
|
67
|
+
onClick={() => setMenuOpen(false)}
|
|
68
|
+
className="hover:bg-muted [&.active]:bg-primary [&.active]:text-primary-foreground flex items-center gap-2 rounded-md px-3 py-2"
|
|
69
|
+
>
|
|
70
|
+
<item.icon className="h-4 w-4 shrink-0" />
|
|
71
|
+
<span className="font-nav text-[12px] tracking-[0.04em]">{item.label}</span>
|
|
72
|
+
</Link>
|
|
73
|
+
</li>
|
|
74
|
+
))}
|
|
75
|
+
</ul>
|
|
76
|
+
<div className="border-border border-t pt-4">
|
|
77
|
+
<Link
|
|
78
|
+
to={settingsItem.to}
|
|
79
|
+
onClick={() => setMenuOpen(false)}
|
|
80
|
+
className="hover:bg-muted [&.active]:bg-primary [&.active]:text-primary-foreground flex items-center gap-2 rounded-md px-3 py-2"
|
|
81
|
+
>
|
|
82
|
+
<settingsItem.icon className="h-4 w-4 shrink-0" />
|
|
83
|
+
<span className="font-nav text-[12px] tracking-[0.04em]">{settingsItem.label}</span>
|
|
84
|
+
</Link>
|
|
85
|
+
</div>
|
|
86
|
+
</nav>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
</>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Link } from '@tanstack/react-router'
|
|
2
|
+
import { navItems } from './nav-items'
|
|
3
|
+
|
|
4
|
+
/** Mobile bottom tab bar - quick access to main sections */
|
|
5
|
+
export function MobileTabBar() {
|
|
6
|
+
return (
|
|
7
|
+
<nav className="mobile-tabbar h-14 border-t border-border bg-background flex items-stretch">
|
|
8
|
+
{navItems.slice(0, 5).map((item) => (
|
|
9
|
+
<Link
|
|
10
|
+
key={item.to}
|
|
11
|
+
to={item.to}
|
|
12
|
+
className="flex-1 flex flex-col items-center justify-center gap-0.5 text-muted-foreground hover:text-foreground [&.active]:text-primary"
|
|
13
|
+
>
|
|
14
|
+
<item.icon className="w-5 h-5 shrink-0" />
|
|
15
|
+
<span className="text-[10px] font-nav tracking-[0.03em]">{item.label}</span>
|
|
16
|
+
</Link>
|
|
17
|
+
))}
|
|
18
|
+
</nav>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FileText,
|
|
3
|
+
GitBranch,
|
|
4
|
+
LayoutDashboard,
|
|
5
|
+
Archive,
|
|
6
|
+
Folder,
|
|
7
|
+
Settings,
|
|
8
|
+
type LucideIcon,
|
|
9
|
+
} from 'lucide-react'
|
|
10
|
+
|
|
11
|
+
/** Valid top-level routes in the application */
|
|
12
|
+
type AppRoute = '/' | '/project' | '/specs' | '/changes' | '/archive' | '/settings'
|
|
13
|
+
|
|
14
|
+
export interface NavItem {
|
|
15
|
+
to: AppRoute
|
|
16
|
+
icon: LucideIcon
|
|
17
|
+
label: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Navigation items configuration */
|
|
21
|
+
export const navItems: NavItem[] = [
|
|
22
|
+
{ to: '/', icon: LayoutDashboard, label: 'Dashboard' },
|
|
23
|
+
{ to: '/project', icon: Folder, label: 'Project' },
|
|
24
|
+
{ to: '/specs', icon: FileText, label: 'Specs' },
|
|
25
|
+
{ to: '/changes', icon: GitBranch, label: 'Changes' },
|
|
26
|
+
{ to: '/archive', icon: Archive, label: 'Archive' },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
export const settingsItem: NavItem = {
|
|
30
|
+
to: '/settings',
|
|
31
|
+
icon: Settings,
|
|
32
|
+
label: 'Settings',
|
|
33
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Outlet, useRouterState } from '@tanstack/react-router'
|
|
2
|
+
import { DesktopSidebar } from './desktop-sidebar'
|
|
3
|
+
import { MobileHeader } from './mobile-header'
|
|
4
|
+
import { MobileTabBar } from './mobile-tabbar'
|
|
5
|
+
import { DesktopStatusBar } from './status-bar'
|
|
6
|
+
import { GlobalArchiveModal } from '@/components/global-archive-modal'
|
|
7
|
+
import { StaticModeBanner } from '@/components/StaticModeBanner'
|
|
8
|
+
import { flushSync } from 'react-dom'
|
|
9
|
+
import { useLayoutEffect, useState } from 'react'
|
|
10
|
+
|
|
11
|
+
type ViewTransitionCapableDocument = Document & {
|
|
12
|
+
startViewTransition?: (updateCallback: () => void) => { finished: Promise<unknown> }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function ViewTransitionOutlet() {
|
|
16
|
+
// Ignore hash-only changes to avoid triggering view transitions on in-page anchor jumps
|
|
17
|
+
const locationKey = useRouterState({
|
|
18
|
+
select: (state) => `${state.location.pathname}${state.location.search}`,
|
|
19
|
+
})
|
|
20
|
+
const [renderKey, setRenderKey] = useState(locationKey)
|
|
21
|
+
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
if (locationKey === renderKey) return
|
|
24
|
+
const doc = document as ViewTransitionCapableDocument
|
|
25
|
+
const performUpdate = () => setRenderKey(locationKey)
|
|
26
|
+
|
|
27
|
+
if (typeof doc.startViewTransition === 'function') {
|
|
28
|
+
document.documentElement.dataset.vtRunning = '1'
|
|
29
|
+
const transition = doc.startViewTransition(() => {
|
|
30
|
+
flushSync(performUpdate)
|
|
31
|
+
})
|
|
32
|
+
transition.finished
|
|
33
|
+
.catch(() => {
|
|
34
|
+
/* swallow errors; fallback already applied */
|
|
35
|
+
})
|
|
36
|
+
.finally(() => {
|
|
37
|
+
delete document.documentElement.dataset.vtRunning
|
|
38
|
+
})
|
|
39
|
+
} else {
|
|
40
|
+
performUpdate()
|
|
41
|
+
}
|
|
42
|
+
}, [locationKey, renderKey])
|
|
43
|
+
|
|
44
|
+
return <Outlet key={renderKey} />
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Root layout with responsive navigation */
|
|
48
|
+
export function RootLayout() {
|
|
49
|
+
return (
|
|
50
|
+
<div className="fixed inset-0 @container/app" style={{ containerName: 'app' }}>
|
|
51
|
+
<div className="app-layout h-full">
|
|
52
|
+
<DesktopSidebar />
|
|
53
|
+
<div className="app-body flex flex-col flex-1 min-h-0">
|
|
54
|
+
<StaticModeBanner />
|
|
55
|
+
<MobileHeader />
|
|
56
|
+
<main className="main-content flex flex-col view-transition-route">
|
|
57
|
+
<ViewTransitionOutlet />
|
|
58
|
+
</main>
|
|
59
|
+
<MobileTabBar />
|
|
60
|
+
<DesktopStatusBar />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
{/* 全局 Archive Modal - 在 Router 内部渲染 */}
|
|
64
|
+
<GlobalArchiveModal />
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { PathMarquee } from '@/components/path-marquee'
|
|
2
|
+
import { useManualReconnect, useServerStatus } from '@/lib/use-server-status'
|
|
3
|
+
import { FolderOpen, RefreshCw, Wifi, WifiOff } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
/** Status indicator - simplified for mobile, full for desktop */
|
|
6
|
+
export function StatusIndicator() {
|
|
7
|
+
const status = useServerStatus()
|
|
8
|
+
const reconnect = useManualReconnect()
|
|
9
|
+
|
|
10
|
+
if (status.connected) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="status-indicator flex items-center gap-1.5 text-xs">
|
|
13
|
+
<Wifi className="h-3.5 w-3.5 text-green-500" />
|
|
14
|
+
<span className="status-text text-green-600">Live</span>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 断开连接时显示重连提示
|
|
20
|
+
return (
|
|
21
|
+
<div className="status-indicator flex items-center gap-1.5 text-xs">
|
|
22
|
+
<WifiOff className="h-3.5 w-3.5 text-red-500" />
|
|
23
|
+
<span className="status-text text-red-600">Offline</span>
|
|
24
|
+
{status.reconnectCountdown !== null && (
|
|
25
|
+
<button
|
|
26
|
+
onClick={reconnect}
|
|
27
|
+
className="text-muted-foreground hover:text-foreground flex cursor-pointer items-center gap-1 transition-colors"
|
|
28
|
+
title="Click to reconnect now"
|
|
29
|
+
>
|
|
30
|
+
<span className="text-xs">({status.reconnectCountdown}s)</span>
|
|
31
|
+
<RefreshCw className="h-3 w-3" />
|
|
32
|
+
</button>
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Desktop status bar - full information */
|
|
39
|
+
export function DesktopStatusBar() {
|
|
40
|
+
const status = useServerStatus()
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="desktop-status border-border bg-muted/30 text-muted-foreground flex h-8 items-center justify-between gap-4 border-t px-4 text-xs">
|
|
44
|
+
<div className="flex items-center gap-4">
|
|
45
|
+
<StatusIndicator />
|
|
46
|
+
{status.projectDir && (
|
|
47
|
+
<div className="flex items-center gap-1.5">
|
|
48
|
+
<FolderOpen className="h-3.5 w-3.5 shrink-0" />
|
|
49
|
+
<PathMarquee
|
|
50
|
+
children={status.projectDir}
|
|
51
|
+
maxWidth={300}
|
|
52
|
+
duration={12}
|
|
53
|
+
className="text-xs"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
{status.connected && (
|
|
59
|
+
<div className="flex items-center gap-1.5">
|
|
60
|
+
{status.watcherEnabled ? (
|
|
61
|
+
<span className="text-green-600">Watching for changes</span>
|
|
62
|
+
) : (
|
|
63
|
+
<span className="text-yellow-600">File watcher disabled</span>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import Markdown, { type Components } from 'react-markdown'
|
|
3
|
+
import remarkGfm from 'remark-gfm'
|
|
4
|
+
import { codeToHtml } from 'shiki'
|
|
5
|
+
|
|
6
|
+
interface MarkdownContentProps {
|
|
7
|
+
children: string
|
|
8
|
+
className?: string
|
|
9
|
+
/** Additional component overrides for react-markdown */
|
|
10
|
+
components?: Components
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Simple markdown renderer with GFM support and shiki code highlighting.
|
|
15
|
+
* For full markdown viewing with ToC, use MarkdownViewer instead.
|
|
16
|
+
*/
|
|
17
|
+
export function MarkdownContent({ children, className = '', components }: MarkdownContentProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div className={`markdown-content ${className}`}>
|
|
20
|
+
<Markdown
|
|
21
|
+
remarkPlugins={[remarkGfm]}
|
|
22
|
+
components={{
|
|
23
|
+
code: CodeBlock,
|
|
24
|
+
pre: ({ children }) => <>{children}</>,
|
|
25
|
+
...components,
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</Markdown>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface CodeBlockProps {
|
|
35
|
+
children?: React.ReactNode
|
|
36
|
+
className?: string
|
|
37
|
+
node?: unknown
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Shared code block component with shiki syntax highlighting */
|
|
41
|
+
export function CodeBlock({ children, className }: CodeBlockProps) {
|
|
42
|
+
const [html, setHtml] = useState<string | null>(null)
|
|
43
|
+
const code = String(children).replace(/\n$/, '')
|
|
44
|
+
const match = /language-(\w+)/.exec(className || '')
|
|
45
|
+
const lang = match ? match[1] : undefined
|
|
46
|
+
|
|
47
|
+
// Check if this is inline code (no language, short content)
|
|
48
|
+
const isInline = !lang && !code.includes('\n')
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (isInline) return
|
|
52
|
+
|
|
53
|
+
let mounted = true
|
|
54
|
+
const highlight = async () => {
|
|
55
|
+
try {
|
|
56
|
+
const result = await codeToHtml(code, {
|
|
57
|
+
lang: lang || 'text',
|
|
58
|
+
themes: {
|
|
59
|
+
light: 'github-light',
|
|
60
|
+
dark: 'github-dark',
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
if (mounted) setHtml(result)
|
|
64
|
+
} catch {
|
|
65
|
+
// Fallback for unknown languages
|
|
66
|
+
const result = await codeToHtml(code, {
|
|
67
|
+
lang: 'text',
|
|
68
|
+
themes: {
|
|
69
|
+
light: 'github-light',
|
|
70
|
+
dark: 'github-dark',
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
if (mounted) setHtml(result)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
highlight()
|
|
77
|
+
return () => {
|
|
78
|
+
mounted = false
|
|
79
|
+
}
|
|
80
|
+
}, [code, lang, isInline])
|
|
81
|
+
|
|
82
|
+
if (isInline) {
|
|
83
|
+
return (
|
|
84
|
+
<code className="bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-sm">
|
|
85
|
+
{code}
|
|
86
|
+
</code>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!html) {
|
|
91
|
+
return (
|
|
92
|
+
<pre className="bg-muted/50 border-border overflow-x-auto rounded-md border p-4">
|
|
93
|
+
<code className="text-foreground font-mono text-sm">{code}</code>
|
|
94
|
+
</pre>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
className="shiki-wrapper overflow-x-auto rounded-md text-sm [&_pre]:m-0 [&_pre]:bg-transparent [&_pre]:p-4"
|
|
101
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|