@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,209 @@
|
|
|
1
|
+
import { TocSection, type TocItem } from '@/components/toc'
|
|
2
|
+
import { CheckCircle, Circle, Loader2 } from 'lucide-react'
|
|
3
|
+
import { memo, useMemo } from 'react'
|
|
4
|
+
|
|
5
|
+
/** Task item structure from @openspecui/core */
|
|
6
|
+
export interface Task {
|
|
7
|
+
id: string
|
|
8
|
+
text: string
|
|
9
|
+
completed: boolean
|
|
10
|
+
section?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Group tasks by their section */
|
|
14
|
+
interface TaskGroup {
|
|
15
|
+
section: string
|
|
16
|
+
tasks: Task[]
|
|
17
|
+
completed: number
|
|
18
|
+
total: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Group tasks by section and calculate progress per group */
|
|
22
|
+
function groupTasksBySection(tasks: Task[]): TaskGroup[] {
|
|
23
|
+
const groups = new Map<string, Task[]>()
|
|
24
|
+
|
|
25
|
+
for (const task of tasks) {
|
|
26
|
+
const section = task.section || 'General'
|
|
27
|
+
const existing = groups.get(section) || []
|
|
28
|
+
existing.push(task)
|
|
29
|
+
groups.set(section, existing)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Array.from(groups.entries()).map(([section, sectionTasks]) => ({
|
|
33
|
+
section,
|
|
34
|
+
tasks: sectionTasks,
|
|
35
|
+
completed: sectionTasks.filter((t) => t.completed).length,
|
|
36
|
+
total: sectionTasks.length,
|
|
37
|
+
}))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Generate a stable ID for a section name */
|
|
41
|
+
export function sectionToId(section: string): string {
|
|
42
|
+
return `section-${section
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
45
|
+
.replace(/^-|-$/g, '')}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Build ToC items for task sections */
|
|
49
|
+
export function buildTaskTocItems(taskGroups: TaskGroup[]): TocItem[] {
|
|
50
|
+
return taskGroups.map((group) => ({
|
|
51
|
+
id: sectionToId(group.section),
|
|
52
|
+
label: `${group.section} (${group.completed}/${group.total})`,
|
|
53
|
+
level: 2,
|
|
54
|
+
}))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface TaskItemProps {
|
|
58
|
+
task: Task
|
|
59
|
+
taskIndex: number
|
|
60
|
+
isToggling: boolean
|
|
61
|
+
onToggle?: (taskIndex: number, completed: boolean) => void
|
|
62
|
+
readonly?: boolean
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const TaskItem = memo(
|
|
66
|
+
function TaskItem({ task, taskIndex, isToggling, onToggle, readonly }: TaskItemProps) {
|
|
67
|
+
const content = (
|
|
68
|
+
<>
|
|
69
|
+
{isToggling ? (
|
|
70
|
+
<Loader2 className="text-primary h-5 w-5 shrink-0 animate-spin" />
|
|
71
|
+
) : task.completed ? (
|
|
72
|
+
<CheckCircle className="h-5 w-5 shrink-0 text-green-500" />
|
|
73
|
+
) : (
|
|
74
|
+
<Circle className="text-muted-foreground group-hover:text-primary h-5 w-5 shrink-0" />
|
|
75
|
+
)}
|
|
76
|
+
<span className={`text-sm ${task.completed ? 'text-muted-foreground line-through' : ''}`}>
|
|
77
|
+
{task.text}
|
|
78
|
+
</span>
|
|
79
|
+
</>
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if (readonly || !onToggle) {
|
|
83
|
+
return <div className="flex w-full items-center gap-3 p-3 text-left">{content}</div>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<button
|
|
88
|
+
onClick={() => onToggle(taskIndex, !task.completed)}
|
|
89
|
+
className="hover:bg-muted/50 group flex w-full items-center gap-3 p-3 text-left transition-colors"
|
|
90
|
+
>
|
|
91
|
+
{content}
|
|
92
|
+
</button>
|
|
93
|
+
)
|
|
94
|
+
},
|
|
95
|
+
(prev, next) =>
|
|
96
|
+
prev.task.id === next.task.id &&
|
|
97
|
+
prev.task.text === next.task.text &&
|
|
98
|
+
prev.task.completed === next.task.completed &&
|
|
99
|
+
prev.isToggling === next.isToggling &&
|
|
100
|
+
prev.readonly === next.readonly
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
export interface TasksViewProps {
|
|
104
|
+
tasks: Task[]
|
|
105
|
+
progress: { total: number; completed: number }
|
|
106
|
+
/** Callback when a task is toggled. If not provided, tasks are readonly. */
|
|
107
|
+
onToggleTask?: (taskIndex: number, completed: boolean) => void
|
|
108
|
+
/** Index of the task currently being toggled (for loading state) */
|
|
109
|
+
togglingIndex?: number | null
|
|
110
|
+
/** Base index for TocSection (for proper ToC navigation) */
|
|
111
|
+
tocBaseIndex?: number
|
|
112
|
+
/** Whether to show as readonly (no interaction) */
|
|
113
|
+
readonly?: boolean
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Unified Tasks view component.
|
|
118
|
+
* Used in both change-view (interactive) and archive-view (readonly).
|
|
119
|
+
*/
|
|
120
|
+
export function TasksView({
|
|
121
|
+
tasks,
|
|
122
|
+
progress,
|
|
123
|
+
onToggleTask,
|
|
124
|
+
togglingIndex = null,
|
|
125
|
+
tocBaseIndex = 0,
|
|
126
|
+
readonly = false,
|
|
127
|
+
}: TasksViewProps) {
|
|
128
|
+
const taskGroups = useMemo(() => groupTasksBySection(tasks), [tasks])
|
|
129
|
+
|
|
130
|
+
const progressPercent =
|
|
131
|
+
progress.total > 0 ? Math.round((progress.completed / progress.total) * 100) : 0
|
|
132
|
+
|
|
133
|
+
// Calculate task index offset for each group (for toggle mutation)
|
|
134
|
+
const getTaskIndex = (groupIndex: number, taskIndexInGroup: number): number => {
|
|
135
|
+
let offset = 0
|
|
136
|
+
for (let i = 0; i < groupIndex; i++) {
|
|
137
|
+
offset += taskGroups[i].tasks.length
|
|
138
|
+
}
|
|
139
|
+
return offset + taskIndexInGroup + 1
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<TocSection id="tasks" index={tocBaseIndex}>
|
|
144
|
+
<div className="mb-3 flex items-center justify-between">
|
|
145
|
+
<h2 className="text-lg font-semibold">
|
|
146
|
+
Tasks ({progress.completed}/{progress.total})
|
|
147
|
+
</h2>
|
|
148
|
+
<span className="text-muted-foreground text-sm">{progressPercent}%</span>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div className="bg-muted mb-4 h-2 w-full rounded-full">
|
|
152
|
+
<div
|
|
153
|
+
className="bg-primary h-2 rounded-full transition-all"
|
|
154
|
+
style={{ width: `${progressPercent}%` }}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{/* Grouped tasks by section */}
|
|
159
|
+
<div className="space-y-6">
|
|
160
|
+
{taskGroups.map((group, groupIndex) => {
|
|
161
|
+
const sectionId = sectionToId(group.section)
|
|
162
|
+
const sectionPercent =
|
|
163
|
+
group.total > 0 ? Math.round((group.completed / group.total) * 100) : 0
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<TocSection
|
|
167
|
+
key={group.section}
|
|
168
|
+
id={sectionId}
|
|
169
|
+
index={tocBaseIndex + 1 + groupIndex}
|
|
170
|
+
as="div"
|
|
171
|
+
>
|
|
172
|
+
<div className="mb-2 flex items-center justify-between">
|
|
173
|
+
<h3 className="text-foreground font-medium">{group.section}</h3>
|
|
174
|
+
<span className="text-muted-foreground text-xs">
|
|
175
|
+
{group.completed}/{group.total} ({sectionPercent}%)
|
|
176
|
+
</span>
|
|
177
|
+
</div>
|
|
178
|
+
<div className="border-border divide-border divide-y rounded-lg border">
|
|
179
|
+
{group.tasks.map((task, taskIndexInGroup) => {
|
|
180
|
+
const taskIndex = getTaskIndex(groupIndex, taskIndexInGroup)
|
|
181
|
+
return (
|
|
182
|
+
<TaskItem
|
|
183
|
+
key={task.id}
|
|
184
|
+
task={task}
|
|
185
|
+
taskIndex={taskIndex}
|
|
186
|
+
isToggling={togglingIndex === taskIndex}
|
|
187
|
+
onToggle={onToggleTask}
|
|
188
|
+
readonly={readonly}
|
|
189
|
+
/>
|
|
190
|
+
)
|
|
191
|
+
})}
|
|
192
|
+
</div>
|
|
193
|
+
</TocSection>
|
|
194
|
+
)
|
|
195
|
+
})}
|
|
196
|
+
{taskGroups.length === 0 && (
|
|
197
|
+
<div className="text-muted-foreground border-border rounded-lg border p-4 text-center">
|
|
198
|
+
No tasks defined
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
</TocSection>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Hook to get task groups for ToC building */
|
|
207
|
+
export function useTaskGroups(tasks: Task[]) {
|
|
208
|
+
return useMemo(() => groupTasksBySection(tasks), [tasks])
|
|
209
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from 'react'
|
|
2
|
+
import type { TocItem } from './toc'
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// TocCollector - 收集 ToC items 的核心类
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
export class TocCollector {
|
|
9
|
+
private items: TocItem[] = []
|
|
10
|
+
private slugCount = new Map<string, number>()
|
|
11
|
+
private levelOffset: number
|
|
12
|
+
|
|
13
|
+
constructor(levelOffset = 0) {
|
|
14
|
+
this.levelOffset = levelOffset
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 添加一个 ToC item,返回分配的 index */
|
|
18
|
+
add(label: string, level: number, fixedId?: string): { id: string; index: number } {
|
|
19
|
+
const adjustedLevel = Math.min(level + this.levelOffset, 6)
|
|
20
|
+
const baseSlug = fixedId ?? (slugify(label) || 'heading')
|
|
21
|
+
|
|
22
|
+
// 处理重复 id
|
|
23
|
+
const count = this.slugCount.get(baseSlug) ?? 0
|
|
24
|
+
this.slugCount.set(baseSlug, count + 1)
|
|
25
|
+
const id = count > 0 ? `${baseSlug}-${count + 1}` : baseSlug
|
|
26
|
+
|
|
27
|
+
const index = this.items.length
|
|
28
|
+
this.items.push({ id, label, level: adjustedLevel })
|
|
29
|
+
|
|
30
|
+
return { id, index }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 批量添加 items(用于嵌套 MarkdownViewer 合并) */
|
|
34
|
+
addAll(items: TocItem[]): number {
|
|
35
|
+
const startIndex = this.items.length
|
|
36
|
+
for (const item of items) {
|
|
37
|
+
const adjustedLevel = Math.min((item.level ?? 1) + this.levelOffset, 6)
|
|
38
|
+
this.items.push({ ...item, level: adjustedLevel })
|
|
39
|
+
}
|
|
40
|
+
return startIndex
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 获取所有收集到的 items */
|
|
44
|
+
getItems(): TocItem[] {
|
|
45
|
+
return [...this.items]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 获取当前 item 数量(用于确定下一个 index) */
|
|
49
|
+
getNextIndex(): number {
|
|
50
|
+
return this.items.length
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 重置收集器 */
|
|
54
|
+
reset(): void {
|
|
55
|
+
this.items = []
|
|
56
|
+
this.slugCount.clear()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 创建子 collector(用于 Section 内部,层级 +1) */
|
|
60
|
+
createChild(additionalOffset = 1): TocCollector {
|
|
61
|
+
return new TocCollector(this.levelOffset + additionalOffset)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Slugify - 生成 URL 友好的 id
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
export function slugify(text: string): string {
|
|
70
|
+
return text
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-')
|
|
73
|
+
.replace(/^-+|-+$/g, '')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// TocContext - React Context
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
interface TocContextValue {
|
|
81
|
+
/** 当前的 collector 实例(顶层创建,所有嵌套共享) */
|
|
82
|
+
collector: TocCollector
|
|
83
|
+
/** 当前层级偏移(Section 嵌套时累加) */
|
|
84
|
+
levelOffset: number
|
|
85
|
+
/** 是否是顶层(决定是否渲染 ToC sidebar) */
|
|
86
|
+
isRoot: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const TocContext = createContext<TocContextValue | null>(null)
|
|
90
|
+
|
|
91
|
+
/** 获取当前 TocContext,如果不存在返回 null */
|
|
92
|
+
export function useTocContext(): TocContextValue | null {
|
|
93
|
+
return useContext(TocContext)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 获取当前 TocContext,如果不存在则抛出错误 */
|
|
97
|
+
export function useTocContextRequired(): TocContextValue {
|
|
98
|
+
const ctx = useContext(TocContext)
|
|
99
|
+
if (!ctx) {
|
|
100
|
+
throw new Error('useTocContextRequired must be used within a TocProvider')
|
|
101
|
+
}
|
|
102
|
+
return ctx
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// TocProvider - 提供 TocContext
|
|
107
|
+
// ============================================================================
|
|
108
|
+
|
|
109
|
+
interface TocProviderProps {
|
|
110
|
+
children: ReactNode
|
|
111
|
+
/** collector 实例(顶层创建) */
|
|
112
|
+
collector: TocCollector
|
|
113
|
+
/** 当前层级偏移 */
|
|
114
|
+
levelOffset?: number
|
|
115
|
+
/** 是否是顶层 */
|
|
116
|
+
isRoot?: boolean
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function TocProvider({
|
|
120
|
+
children,
|
|
121
|
+
collector,
|
|
122
|
+
levelOffset = 0,
|
|
123
|
+
isRoot = true,
|
|
124
|
+
}: TocProviderProps) {
|
|
125
|
+
const value = useMemo<TocContextValue>(
|
|
126
|
+
() => ({ collector, levelOffset, isRoot }),
|
|
127
|
+
[collector, levelOffset, isRoot]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return <TocContext.Provider value={value}>{children}</TocContext.Provider>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 创建一个新的层级 Context(用于 Section) */
|
|
134
|
+
export function TocLevelProvider({
|
|
135
|
+
children,
|
|
136
|
+
additionalOffset = 1,
|
|
137
|
+
}: {
|
|
138
|
+
children: ReactNode
|
|
139
|
+
additionalOffset?: number
|
|
140
|
+
}) {
|
|
141
|
+
const parentCtx = useTocContext()
|
|
142
|
+
if (!parentCtx) {
|
|
143
|
+
// 没有父级 Context,直接渲染 children
|
|
144
|
+
return <>{children}</>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const value = useMemo<TocContextValue>(
|
|
148
|
+
() => ({
|
|
149
|
+
collector: parentCtx.collector,
|
|
150
|
+
levelOffset: parentCtx.levelOffset + additionalOffset,
|
|
151
|
+
isRoot: false,
|
|
152
|
+
}),
|
|
153
|
+
[parentCtx, additionalOffset]
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return <TocContext.Provider value={value}>{children}</TocContext.Provider>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// 从 markdown 字符串提取标题
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
export interface ExtractedHeading {
|
|
164
|
+
level: number
|
|
165
|
+
text: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** 从 markdown 字符串中提取标题 */
|
|
169
|
+
export function extractHeadingsFromMarkdown(markdown: string): ExtractedHeading[] {
|
|
170
|
+
const regex = /^(#{1,6})\s+(.+)$/gm
|
|
171
|
+
const headings: ExtractedHeading[] = []
|
|
172
|
+
let match
|
|
173
|
+
while ((match = regex.exec(markdown)) !== null) {
|
|
174
|
+
headings.push({ level: match[1].length, text: match[2].trim() })
|
|
175
|
+
}
|
|
176
|
+
return headings
|
|
177
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { ChevronDown, List } from 'lucide-react'
|
|
2
|
+
import { useCallback, useMemo, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
export interface TocItem {
|
|
5
|
+
id: string
|
|
6
|
+
label: string
|
|
7
|
+
level?: number // 1 = h1, 2 = h2, etc. Default 1
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** 树形结构节点,用于嵌套渲染 */
|
|
11
|
+
export interface TocNode {
|
|
12
|
+
item: TocItem
|
|
13
|
+
/** 原始 index,用于 CSS timeline binding */
|
|
14
|
+
index: number
|
|
15
|
+
children: TocNode[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 将扁平的 TocItem[] 根据 level 构建为树形结构。
|
|
20
|
+
* level 更大的项成为前一个 level 更小项的子节点。
|
|
21
|
+
*/
|
|
22
|
+
export function buildTocTree(items: TocItem[]): TocNode[] {
|
|
23
|
+
const roots: TocNode[] = []
|
|
24
|
+
const stack: TocNode[] = []
|
|
25
|
+
|
|
26
|
+
items.forEach((item, index) => {
|
|
27
|
+
const node: TocNode = { item, index, children: [] }
|
|
28
|
+
const level = item.level ?? 1
|
|
29
|
+
|
|
30
|
+
// 找到合适的父节点:level 必须比当前小
|
|
31
|
+
while (stack.length > 0) {
|
|
32
|
+
const parent = stack[stack.length - 1]
|
|
33
|
+
const parentLevel = parent.item.level ?? 1
|
|
34
|
+
if (parentLevel < level) {
|
|
35
|
+
// 找到父节点
|
|
36
|
+
parent.children.push(node)
|
|
37
|
+
stack.push(node)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
// 父节点 level >= 当前 level,弹出继续找
|
|
41
|
+
stack.pop()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 没有合适的父节点,作为根节点
|
|
45
|
+
roots.push(node)
|
|
46
|
+
stack.push(node)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return roots
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface TocProps {
|
|
53
|
+
items: TocItem[]
|
|
54
|
+
/** Default collapsed state on mobile */
|
|
55
|
+
defaultCollapsed?: boolean
|
|
56
|
+
className?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Table of Contents component with CSS view-timeline scroll highlighting.
|
|
61
|
+
* Uses container queries for responsive layout.
|
|
62
|
+
*
|
|
63
|
+
* 支持树形嵌套结构:根据 TocItem.level 自动构建父子关系,
|
|
64
|
+
* 渲染为语义化的 `<ul><li>` 嵌套结构。
|
|
65
|
+
*
|
|
66
|
+
* Usage:
|
|
67
|
+
* 1. Pass tocItems to MarkdownViewer for timeline-scope binding
|
|
68
|
+
* 2. Use TocSection for each section to bind viewTimelineName
|
|
69
|
+
* 3. The ToC links will automatically highlight based on scroll position
|
|
70
|
+
*/
|
|
71
|
+
export function Toc({ items, defaultCollapsed = true, className = '' }: TocProps) {
|
|
72
|
+
const [collapsed, setCollapsed] = useState(defaultCollapsed)
|
|
73
|
+
const tree = useMemo(() => buildTocTree(items), [items])
|
|
74
|
+
|
|
75
|
+
// Return hidden placeholder when empty to keep React children stable
|
|
76
|
+
if (items.length === 0) {
|
|
77
|
+
return <aside className={`hidden ${className}`} />
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<aside
|
|
82
|
+
className={`toc-root scrollbar-none sticky top-0 z-10 max-h-[calc(100cqh-3rem)] self-start overflow-y-auto ${className}`}
|
|
83
|
+
>
|
|
84
|
+
<style>{tocStyles}</style>
|
|
85
|
+
|
|
86
|
+
{/* Narrow: collapsible */}
|
|
87
|
+
<div className="toc-narrow border-border bg-background overflow-hidden rounded border">
|
|
88
|
+
<button
|
|
89
|
+
onClick={() => setCollapsed(!collapsed)}
|
|
90
|
+
className={`text-foreground flex w-full items-center gap-2 px-3 py-2 ${collapsed ? '' : 'border-border border-b'}`}
|
|
91
|
+
aria-label={collapsed ? 'Show table of contents' : 'Hide table of contents'}
|
|
92
|
+
>
|
|
93
|
+
<List className="h-4 w-4" />
|
|
94
|
+
<span className="text-sm">Contents</span>
|
|
95
|
+
<ChevronDown
|
|
96
|
+
className={`ml-auto h-4 w-4 transition-transform ${collapsed ? '' : 'rotate-180'}`}
|
|
97
|
+
/>
|
|
98
|
+
</button>
|
|
99
|
+
{!collapsed && <TocTree nodes={tree} />}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Wide: always visible */}
|
|
103
|
+
<nav className="toc-wide flex flex-col">
|
|
104
|
+
<div className="text-muted-foreground flex items-center gap-2 px-3 py-2 text-xs font-medium uppercase tracking-wide">
|
|
105
|
+
<List className="h-3.5 w-3.5" />
|
|
106
|
+
<span>On this page</span>
|
|
107
|
+
</div>
|
|
108
|
+
<div className="scrollbar-thin scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto p-2">
|
|
109
|
+
<TocTree nodes={tree} />
|
|
110
|
+
</div>
|
|
111
|
+
</nav>
|
|
112
|
+
</aside>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Find the nearest scrollable ancestor element.
|
|
117
|
+
*/
|
|
118
|
+
function findScrollableParent(element: HTMLElement): HTMLElement | null {
|
|
119
|
+
return element.closest('.toc-root') as HTMLElement
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Scroll element into view within its scrollable container using scrollTo.
|
|
124
|
+
* NOTE: Cannot use scrollIntoView here because it triggers scroll on all ancestor
|
|
125
|
+
* scrollable containers, which interferes with the main content's smooth scrolling
|
|
126
|
+
* when user clicks a ToC link.
|
|
127
|
+
*/
|
|
128
|
+
function scrollIntoViewWithinContainer(element: HTMLElement) {
|
|
129
|
+
const container = findScrollableParent(element)
|
|
130
|
+
if (!container) return
|
|
131
|
+
|
|
132
|
+
// Use getBoundingClientRect to get positions relative to viewport,
|
|
133
|
+
// then calculate the relative position within container
|
|
134
|
+
const elementRect = element.getBoundingClientRect()
|
|
135
|
+
const containerRect = container.getBoundingClientRect()
|
|
136
|
+
|
|
137
|
+
// Element's position relative to container's visible area
|
|
138
|
+
const relativeTop = elementRect.top - containerRect.top
|
|
139
|
+
const relativeBottom = elementRect.bottom - containerRect.top
|
|
140
|
+
|
|
141
|
+
// Check if element is outside visible area
|
|
142
|
+
if (relativeTop < 0) {
|
|
143
|
+
// Element is above visible area
|
|
144
|
+
container.scrollTo({ top: container.scrollTop + relativeTop, behavior: 'smooth' })
|
|
145
|
+
} else if (relativeBottom > containerRect.height) {
|
|
146
|
+
// Element is below visible area
|
|
147
|
+
container.scrollTo({
|
|
148
|
+
top: container.scrollTop + relativeBottom - containerRect.height,
|
|
149
|
+
behavior: 'smooth',
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 递归渲染树形 ToC 结构 */
|
|
155
|
+
function TocTree({ nodes, depth = 0 }: { nodes: TocNode[]; depth?: number }) {
|
|
156
|
+
const handleAnimationStart = useCallback((e: React.AnimationEvent<HTMLAnchorElement>) => {
|
|
157
|
+
if (e.animationName === 'toc-activate') {
|
|
158
|
+
scrollIntoViewWithinContainer(e.currentTarget)
|
|
159
|
+
}
|
|
160
|
+
}, [])
|
|
161
|
+
|
|
162
|
+
if (nodes.length === 0) return null
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<ul className="toc-list">
|
|
166
|
+
{nodes.map((node) => (
|
|
167
|
+
<li key={node.item.id} className="toc-item">
|
|
168
|
+
<a
|
|
169
|
+
href={`#${node.item.id}`}
|
|
170
|
+
className={`toc-link text-muted-foreground hover:text-foreground block overflow-hidden text-ellipsis whitespace-nowrap border-l-2 border-transparent py-1 pr-3 ${
|
|
171
|
+
depth === 0
|
|
172
|
+
? 'text-[13px]'
|
|
173
|
+
: depth === 1
|
|
174
|
+
? 'text-[12px]'
|
|
175
|
+
: depth === 2
|
|
176
|
+
? 'text-[11px]'
|
|
177
|
+
: 'text-[10px]'
|
|
178
|
+
}`}
|
|
179
|
+
style={
|
|
180
|
+
{
|
|
181
|
+
'--target': `--toc-${node.index}`,
|
|
182
|
+
paddingLeft: `${0.75 + depth * 0.5}rem`,
|
|
183
|
+
} as React.CSSProperties
|
|
184
|
+
}
|
|
185
|
+
title={node.item.label}
|
|
186
|
+
onAnimationStart={handleAnimationStart}
|
|
187
|
+
>
|
|
188
|
+
{node.item.label}
|
|
189
|
+
</a>
|
|
190
|
+
{node.children.length > 0 && <TocTree nodes={node.children} depth={depth + 1} />}
|
|
191
|
+
</li>
|
|
192
|
+
))}
|
|
193
|
+
</ul>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const css = String.raw
|
|
198
|
+
/** CSS for container queries and scroll-driven ToC highlighting */
|
|
199
|
+
const tocStyles = css`
|
|
200
|
+
/* Default: narrow mode (collapsible) */
|
|
201
|
+
.toc-narrow {
|
|
202
|
+
display: block;
|
|
203
|
+
}
|
|
204
|
+
.toc-wide {
|
|
205
|
+
display: none;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Wide container: show sidebar mode */
|
|
209
|
+
@container (min-width: 768px) {
|
|
210
|
+
.toc-narrow {
|
|
211
|
+
display: none;
|
|
212
|
+
}
|
|
213
|
+
.toc-wide {
|
|
214
|
+
display: block;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Tree structure styling */
|
|
219
|
+
.toc-list {
|
|
220
|
+
list-style: none;
|
|
221
|
+
margin: 0;
|
|
222
|
+
padding: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Scroll-driven ToC highlighting animation */
|
|
226
|
+
@keyframes toc-activate {
|
|
227
|
+
0%,
|
|
228
|
+
100% {
|
|
229
|
+
color: var(--muted-foreground);
|
|
230
|
+
border-left-color: transparent;
|
|
231
|
+
}
|
|
232
|
+
1%,
|
|
233
|
+
99% {
|
|
234
|
+
color: var(--foreground);
|
|
235
|
+
border-left-color: var(--primary);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
.toc-link {
|
|
239
|
+
animation-timeline: var(--target);
|
|
240
|
+
animation-name: toc-activate;
|
|
241
|
+
animation-fill-mode: both;
|
|
242
|
+
animation-range: cover 0% cover 100%;
|
|
243
|
+
}
|
|
244
|
+
`
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Generate the timeline-scope CSS value for the container.
|
|
248
|
+
* This should be applied to the common ancestor of both ToC and content.
|
|
249
|
+
*/
|
|
250
|
+
export function generateTimelineScope(items: TocItem[]): string {
|
|
251
|
+
return items.map((_, i) => `--toc-${i}`).join(', ')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 为 MarkdownViewer 外部的内容提供 ToC 滚动追踪绑定。
|
|
256
|
+
*
|
|
257
|
+
* 使用场景:当内容不在 MarkdownViewer 内部时(如独立的 TasksView 组件),
|
|
258
|
+
* 无法使用 Section + Heading 组件自动集成 ToC,需要手动使用 TocSection
|
|
259
|
+
* 绑定 viewTimelineName 以实现滚动高亮。
|
|
260
|
+
*
|
|
261
|
+
* 如果内容在 MarkdownViewer 内部,应优先使用 Section + H1-H6 组件,
|
|
262
|
+
* 它们会自动处理 ToC 注册和 viewTimelineName 绑定。
|
|
263
|
+
*/
|
|
264
|
+
interface TocSectionProps {
|
|
265
|
+
/** DOM id for anchor links */
|
|
266
|
+
id: string
|
|
267
|
+
/** Index in the ToC items array for CSS timeline binding */
|
|
268
|
+
index: number
|
|
269
|
+
children: React.ReactNode
|
|
270
|
+
className?: string
|
|
271
|
+
as?: 'section' | 'div' | 'article'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function TocSection({
|
|
275
|
+
id,
|
|
276
|
+
index,
|
|
277
|
+
children,
|
|
278
|
+
className = '',
|
|
279
|
+
as: Tag = 'section',
|
|
280
|
+
}: TocSectionProps) {
|
|
281
|
+
return (
|
|
282
|
+
<Tag
|
|
283
|
+
id={id}
|
|
284
|
+
className={className}
|
|
285
|
+
style={{ viewTimelineName: `--toc-${index}` } as React.CSSProperties}
|
|
286
|
+
>
|
|
287
|
+
{children}
|
|
288
|
+
</Tag>
|
|
289
|
+
)
|
|
290
|
+
}
|