@sanurb/ringi 0.2.0 → 0.3.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/cli.mjs +2132 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/mcp.mjs +1057 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/runtime.mjs +3116 -0
- package/dist/runtime.mjs.map +1 -0
- package/package.json +15 -14
- package/server/nitro.json +17 -0
- package/server/public/assets/ClientOnly-QdfAxyFs.js +1 -0
- package/server/public/assets/_reviewId-CmXHvWLn.js +1 -0
- package/server/public/assets/_reviewId-DdOpDx4U.js +1 -0
- package/server/public/assets/abap-B1dkBSPn.js +1 -0
- package/server/public/assets/action-bar-DLRNvLjj.js +45 -0
- package/server/public/assets/actionscript-3-BT4ibYlP.js +1 -0
- package/server/public/assets/ada-CD92zeps.js +1 -0
- package/server/public/assets/andromeeda-DqSmgxi0.js +1 -0
- package/server/public/assets/angular-html-BDC0PfKr.js +1 -0
- package/server/public/assets/angular-ts-B9yoQMtj.js +1 -0
- package/server/public/assets/apache-D5suuoa_.js +1 -0
- package/server/public/assets/apex-BL-m4VHy.js +1 -0
- package/server/public/assets/apl-CldhY0Pn.js +1 -0
- package/server/public/assets/applescript-CLiBqvKT.js +1 -0
- package/server/public/assets/ara-LdDF8cmv.js +1 -0
- package/server/public/assets/asciidoc-2DZ9hC2N.js +1 -0
- package/server/public/assets/asm-0ZPGRSUy.js +1 -0
- package/server/public/assets/astro-DR6labZJ.js +1 -0
- package/server/public/assets/aurora-x-Da7Zfvbg.js +1 -0
- package/server/public/assets/awk-Bn0gn_B_.js +1 -0
- package/server/public/assets/ayu-dark-bqYKoqpM.js +1 -0
- package/server/public/assets/ayu-light-C45jTIzZ.js +1 -0
- package/server/public/assets/ayu-mirage-BV_FCTQi.js +1 -0
- package/server/public/assets/ballerina-D2nw_w8Q.js +1 -0
- package/server/public/assets/bat-ByUBN5gS.js +1 -0
- package/server/public/assets/beancount-BTb2W6Mp.js +1 -0
- package/server/public/assets/berry-5SO2uITG.js +1 -0
- package/server/public/assets/bibtex-CDBTNfUI.js +1 -0
- package/server/public/assets/bicep-fgxG_4rP.js +1 -0
- package/server/public/assets/bird2-BCwzDhwX.js +1 -0
- package/server/public/assets/blade-BBTRu2-g.js +1 -0
- package/server/public/assets/bsl-CG-fq4sc.js +1 -0
- package/server/public/assets/c-CEvNj7xl.js +1 -0
- package/server/public/assets/c3-Dlaci63_.js +1 -0
- package/server/public/assets/cadence-DHbRuEmm.js +1 -0
- package/server/public/assets/cairo-Ds0kTeYT.js +1 -0
- package/server/public/assets/catppuccin-frappe-DrL1fUuH.js +1 -0
- package/server/public/assets/catppuccin-latte-C0REgVjl.js +1 -0
- package/server/public/assets/catppuccin-macchiato-ChQpylWO.js +1 -0
- package/server/public/assets/catppuccin-mocha-Dd0JU1T0.js +1 -0
- package/server/public/assets/clarity-DMoTOm4G.js +1 -0
- package/server/public/assets/clojure-DBhE3PpS.js +1 -0
- package/server/public/assets/cmake-DwMc40Or.js +1 -0
- package/server/public/assets/cobol-BdNJSMRt.js +1 -0
- package/server/public/assets/codeowners-1lVr8wqV.js +1 -0
- package/server/public/assets/codeql-DWflolvo.js +1 -0
- package/server/public/assets/coffee-RA4xA24H.js +1 -0
- package/server/public/assets/common-lisp-CrVQ5xT-.js +1 -0
- package/server/public/assets/compiler-runtime-DZXZ41-q.js +1 -0
- package/server/public/assets/coq-CjfoyYSh.js +1 -0
- package/server/public/assets/cpp-BoW7e2Ow.js +1 -0
- package/server/public/assets/createServerFn-DTk395iP.js +9 -0
- package/server/public/assets/crystal-CYKRo3F9.js +1 -0
- package/server/public/assets/csharp-C7bIWP5y.js +1 -0
- package/server/public/assets/css-Ck2tii2d.js +1 -0
- package/server/public/assets/csv-DsAkDVtA.js +1 -0
- package/server/public/assets/cue-BWmQgbOB.js +1 -0
- package/server/public/assets/cypher-D-jVC50Q.js +1 -0
- package/server/public/assets/d-CaviyOrm.js +1 -0
- package/server/public/assets/dark-plus-DIrnwZt9.js +1 -0
- package/server/public/assets/dart-CZEi7JgC.js +1 -0
- package/server/public/assets/dax-BK-8zffy.js +1 -0
- package/server/public/assets/desktop-D3cjbL4D.js +1 -0
- package/server/public/assets/diff-sHAzLvlp.js +1 -0
- package/server/public/assets/docker--xs2Ng3w.js +1 -0
- package/server/public/assets/dotenv-Cm4nwcJ7.js +1 -0
- package/server/public/assets/dracula-CAUSusef.js +1 -0
- package/server/public/assets/dracula-soft-cjNkMFza.js +1 -0
- package/server/public/assets/dream-maker-fjmWTFCO.js +1 -0
- package/server/public/assets/edge-DxycC9wl.js +1 -0
- package/server/public/assets/elixir-B-50Er3p.js +1 -0
- package/server/public/assets/elm-B4-ygIVo.js +1 -0
- package/server/public/assets/emacs-lisp-CJzqStIa.js +1 -0
- package/server/public/assets/erb-DJvYE1L1.js +1 -0
- package/server/public/assets/erlang-C-m_88FN.js +1 -0
- package/server/public/assets/everforest-dark-DBpaSMx1.js +1 -0
- package/server/public/assets/everforest-light-CiGrXwia.js +1 -0
- package/server/public/assets/fennel-DRaXF7k8.js +1 -0
- package/server/public/assets/file-tree-CI3Xwwid.js +1907 -0
- package/server/public/assets/fish-Bn-Yh3Jj.js +1 -0
- package/server/public/assets/fluent-DF5F8Ks_.js +1 -0
- package/server/public/assets/fortran-fixed-form-Cx1lv7HN.js +1 -0
- package/server/public/assets/fortran-free-form-hCQHRqew.js +1 -0
- package/server/public/assets/fsharp-DC5k9sy2.js +1 -0
- package/server/public/assets/gdresource-D0EsKdgH.js +1 -0
- package/server/public/assets/gdscript-_C9_Hi_w.js +1 -0
- package/server/public/assets/gdshader-BW7b1X1Y.js +1 -0
- package/server/public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/server/public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/server/public/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/server/public/assets/genie-Ch_6TCHd.js +1 -0
- package/server/public/assets/gherkin-CaNUsmTq.js +1 -0
- package/server/public/assets/git-commit-BcFsuO5E.js +1 -0
- package/server/public/assets/git-rebase-ChGA-z50.js +1 -0
- package/server/public/assets/github-dark-B9ygjgg6.js +1 -0
- package/server/public/assets/github-dark-default-Br2bgYSx.js +1 -0
- package/server/public/assets/github-dark-dimmed-CmtqpPJ-.js +1 -0
- package/server/public/assets/github-dark-high-contrast-fSfmrZcC.js +1 -0
- package/server/public/assets/github-light-BZjUqfZl.js +1 -0
- package/server/public/assets/github-light-default-lIytXXhR.js +1 -0
- package/server/public/assets/github-light-high-contrast-BRrjFb7n.js +1 -0
- package/server/public/assets/gleam-DALMDpNs.js +1 -0
- package/server/public/assets/glimmer-js-maLb6ysA.js +1 -0
- package/server/public/assets/glimmer-ts-DGNr-OBA.js +1 -0
- package/server/public/assets/glsl-CmplqyQ1.js +1 -0
- package/server/public/assets/gn-DGjqrYN9.js +1 -0
- package/server/public/assets/gnuplot-BYckvgQI.js +1 -0
- package/server/public/assets/go-JycvP538.js +1 -0
- package/server/public/assets/graphql-VhP7n4--.js +1 -0
- package/server/public/assets/groovy-D5qMRONT.js +1 -0
- package/server/public/assets/gruvbox-dark-hard-M1dj1e6V.js +1 -0
- package/server/public/assets/gruvbox-dark-medium-cqq_ncQu.js +1 -0
- package/server/public/assets/gruvbox-dark-soft-B4QwL2a9.js +1 -0
- package/server/public/assets/gruvbox-light-hard-DLayMKOQ.js +1 -0
- package/server/public/assets/gruvbox-light-medium-D52XgPKf.js +1 -0
- package/server/public/assets/gruvbox-light-soft-Dola3KdD.js +1 -0
- package/server/public/assets/hack-BVSQ2bxM.js +1 -0
- package/server/public/assets/haml-CwTtRHoj.js +1 -0
- package/server/public/assets/handlebars-CcO01SVo.js +1 -0
- package/server/public/assets/haskell-ys7wPPEd.js +1 -0
- package/server/public/assets/haxe-94kiChn7.js +1 -0
- package/server/public/assets/hcl-DmHt_-wq.js +1 -0
- package/server/public/assets/hjson-xMmoJ0Gx.js +1 -0
- package/server/public/assets/hlsl-b-Pskdze.js +1 -0
- package/server/public/assets/horizon-BKMqttiR.js +1 -0
- package/server/public/assets/horizon-bright-HNkBlnm5.js +1 -0
- package/server/public/assets/houston-BkBSBSOQ.js +1 -0
- package/server/public/assets/html-derivative-Cz-cKMi2.js +1 -0
- package/server/public/assets/html-zQbUS8Is.js +1 -0
- package/server/public/assets/http-CaGQ9BgA.js +1 -0
- package/server/public/assets/hurl-BBoki9bg.js +1 -0
- package/server/public/assets/hxml-iQTOTWpM.js +1 -0
- package/server/public/assets/hy-DKl1XhBq.js +1 -0
- package/server/public/assets/imba-DPxkOTAg.js +1 -0
- package/server/public/assets/ini-lkLGq_1x.js +1 -0
- package/server/public/assets/java-LAx6oszV.js +1 -0
- package/server/public/assets/javascript-COqx-gKX.js +1 -0
- package/server/public/assets/jinja-x-G_qSCP.js +1 -0
- package/server/public/assets/jison-7oSeVkKJ.js +1 -0
- package/server/public/assets/json-sTLOVXhc.js +1 -0
- package/server/public/assets/json5-Cy6ypJuJ.js +1 -0
- package/server/public/assets/jsonc-Cw2ugYAK.js +1 -0
- package/server/public/assets/jsonl-Dp5_qBVH.js +1 -0
- package/server/public/assets/jsonnet-BTbmg_-u.js +1 -0
- package/server/public/assets/jssm-CnT7nPea.js +1 -0
- package/server/public/assets/jsx-zXeIBQLI.js +1 -0
- package/server/public/assets/julia-E-6Xm9nd.js +1 -0
- package/server/public/assets/just-D9n74gZy.js +1 -0
- package/server/public/assets/kanagawa-dragon-CxsBnuhV.js +1 -0
- package/server/public/assets/kanagawa-lotus-vHdxDDOS.js +1 -0
- package/server/public/assets/kanagawa-wave-CIkfTKWk.js +1 -0
- package/server/public/assets/kdl-BwK60g80.js +1 -0
- package/server/public/assets/kotlin-Dbd9Vi-v.js +1 -0
- package/server/public/assets/kusto-BuTk9usc.js +1 -0
- package/server/public/assets/laserwave-C0wf_d3o.js +1 -0
- package/server/public/assets/latex-D0t4RtEU.js +1 -0
- package/server/public/assets/lean-CYAW8bRN.js +1 -0
- package/server/public/assets/less-D4uen21c.js +1 -0
- package/server/public/assets/light-plus-oqYyWKEE.js +1 -0
- package/server/public/assets/liquid-BzXN12F6.js +1 -0
- package/server/public/assets/llvm-Z1xJzteV.js +1 -0
- package/server/public/assets/log-BGUxlsk3.js +1 -0
- package/server/public/assets/logo-wVUhvQ1b.js +1 -0
- package/server/public/assets/lua-B0Cg8RP4.js +1 -0
- package/server/public/assets/luau-rPFZzCmq.js +1 -0
- package/server/public/assets/main-FvxVz-kD.js +15 -0
- package/server/public/assets/make-BmPf6m0P.js +1 -0
- package/server/public/assets/markdown-AseU6zcW.js +1 -0
- package/server/public/assets/marko-BlRPXWOe.js +1 -0
- package/server/public/assets/material-theme-6_W6rQhR.js +1 -0
- package/server/public/assets/material-theme-darker-VPEo3Sem.js +1 -0
- package/server/public/assets/material-theme-lighter-CUhzCcZ9.js +1 -0
- package/server/public/assets/material-theme-ocean-B2JdsaGb.js +1 -0
- package/server/public/assets/material-theme-palenight-DhY-sklA.js +1 -0
- package/server/public/assets/matlab-BOj_BDQv.js +1 -0
- package/server/public/assets/mdc-FiVDZSZ4.js +1 -0
- package/server/public/assets/mdx-Cm6cDkDI.js +1 -0
- package/server/public/assets/mermaid-DLO-R4hv.js +1 -0
- package/server/public/assets/min-dark-D34a_pX7.js +1 -0
- package/server/public/assets/min-light-Cdd4KORE.js +1 -0
- package/server/public/assets/mipsasm-DYpHF-GA.js +1 -0
- package/server/public/assets/mojo-DqYVFv_G.js +1 -0
- package/server/public/assets/monokai-CDR4sQ2n.js +1 -0
- package/server/public/assets/moonbit-DRKee9wk.js +1 -0
- package/server/public/assets/move-DbRk6Vn9.js +1 -0
- package/server/public/assets/narrat-IOfmaXfb.js +1 -0
- package/server/public/assets/new-DOyplRwM.js +1 -0
- package/server/public/assets/nextflow-D-Ec_bsY.js +1 -0
- package/server/public/assets/nextflow-groovy-EYl0c2BQ.js +1 -0
- package/server/public/assets/nginx-3JLAqmJa.js +1 -0
- package/server/public/assets/night-owl-light-Bedht9b4.js +1 -0
- package/server/public/assets/night-owl-yQJ3-I0I.js +1 -0
- package/server/public/assets/nim-DyjFVMzT.js +1 -0
- package/server/public/assets/nix-C2IovEl2.js +1 -0
- package/server/public/assets/nord-BjZ63GNL.js +1 -0
- package/server/public/assets/nushell-BflTrRB5.js +1 -0
- package/server/public/assets/objective-c-GRClK1S7.js +1 -0
- package/server/public/assets/objective-cpp-l3qYw-v5.js +1 -0
- package/server/public/assets/ocaml-BBDyhyMH.js +1 -0
- package/server/public/assets/odin-jCJ7Js99.js +1 -0
- package/server/public/assets/one-dark-pro-PIx2Diul.js +1 -0
- package/server/public/assets/one-light-BFMEz49S.js +1 -0
- package/server/public/assets/openscad-Drf0LgCX.js +1 -0
- package/server/public/assets/pascal-BT2XAUTl.js +1 -0
- package/server/public/assets/perl-Dr47G_2Q.js +1 -0
- package/server/public/assets/php-BhBDWTJe.js +1 -0
- package/server/public/assets/pierre-dark-CTXzTLfO.js +1 -0
- package/server/public/assets/pierre-light-C_5rlJRo.js +1 -0
- package/server/public/assets/pkl-ML-dWShO.js +1 -0
- package/server/public/assets/plastic-BFI-Z5Z2.js +1 -0
- package/server/public/assets/plsql-0vd5cLro.js +1 -0
- package/server/public/assets/po-CbZ_uqQA.js +1 -0
- package/server/public/assets/poimandres-Cayhd01L.js +1 -0
- package/server/public/assets/polar-C4hfV8Nc.js +1 -0
- package/server/public/assets/postcss-osFUbTLw.js +1 -0
- package/server/public/assets/powerquery-CTlGUQPj.js +1 -0
- package/server/public/assets/powershell-DyZsOmuq.js +1 -0
- package/server/public/assets/preload-helper-D7oT-Xwl.js +20 -0
- package/server/public/assets/prisma-SS92PO_I.js +1 -0
- package/server/public/assets/prolog-B1O1NNVC.js +1 -0
- package/server/public/assets/proto-BWu3eZTs.js +1 -0
- package/server/public/assets/pug-Dij_IK5w.js +1 -0
- package/server/public/assets/puppet-tvtRVdr6.js +1 -0
- package/server/public/assets/purescript-Dtbpb7D-.js +1 -0
- package/server/public/assets/python-Dlk0Acio.js +1 -0
- package/server/public/assets/qml-qUwk3nhh.js +1 -0
- package/server/public/assets/qmldir-B-iEOngH.js +1 -0
- package/server/public/assets/qss-Ba0p-aHw.js +1 -0
- package/server/public/assets/r-WmtNicKM.js +1 -0
- package/server/public/assets/racket-BXDsxf2U.js +1 -0
- package/server/public/assets/raku-Dw1WWFXK.js +1 -0
- package/server/public/assets/razor-DaqiVx3Q.js +1 -0
- package/server/public/assets/red-BC3Ds49b.js +1 -0
- package/server/public/assets/reg-DXFHGaM4.js +1 -0
- package/server/public/assets/regexp-CiSWN5Ne.js +1 -0
- package/server/public/assets/rel-Dc5_Ytx2.js +1 -0
- package/server/public/assets/reviews-CJvVXRLH.js +1 -0
- package/server/public/assets/reviews-CfbuF6ib.js +1 -0
- package/server/public/assets/riscv-ZgswiWij.js +1 -0
- package/server/public/assets/ron-YghabWAH.js +1 -0
- package/server/public/assets/rose-pine-ByWLnVr3.js +1 -0
- package/server/public/assets/rose-pine-dawn-DBmeySrz.js +1 -0
- package/server/public/assets/rose-pine-moon-B9J-N3nK.js +1 -0
- package/server/public/assets/rosmsg-DTKmAsVH.js +1 -0
- package/server/public/assets/routes-DNxq1Fba.js +1 -0
- package/server/public/assets/routes-Dp0ODZ55.js +2 -0
- package/server/public/assets/rst-CP6xOYlY.js +1 -0
- package/server/public/assets/ruby-BXYLc1CM.js +1 -0
- package/server/public/assets/rust-nfXwuE6F.js +1 -0
- package/server/public/assets/sas-CFdtZutF.js +1 -0
- package/server/public/assets/sass-CbRjkld3.js +1 -0
- package/server/public/assets/scala-BzD3eypx.js +1 -0
- package/server/public/assets/scheme-D4d1PV1y.js +1 -0
- package/server/public/assets/scss-D8KhdObH.js +1 -0
- package/server/public/assets/sdbl-Cf-Ydnvx.js +1 -0
- package/server/public/assets/shaderlab-DGohHMiF.js +1 -0
- package/server/public/assets/shellscript-BHdEbumI.js +1 -0
- package/server/public/assets/shellsession-Dh-bxrap.js +1 -0
- package/server/public/assets/slack-dark-MszIyPZ2.js +1 -0
- package/server/public/assets/slack-ochin-tQ3Q0gE9.js +1 -0
- package/server/public/assets/smalltalk-_uWoArwn.js +1 -0
- package/server/public/assets/snazzy-light-9sniMEk5.js +1 -0
- package/server/public/assets/solarized-dark-CdD0Hxzv.js +1 -0
- package/server/public/assets/solarized-light-C-nsEdqF.js +1 -0
- package/server/public/assets/solidity-D6uC-xwP.js +1 -0
- package/server/public/assets/soy-cDuODfbT.js +1 -0
- package/server/public/assets/sparql-BgU2QITA.js +1 -0
- package/server/public/assets/splunk-LQYHRu14.js +1 -0
- package/server/public/assets/sql-CKZpK620.js +1 -0
- package/server/public/assets/ssh-config-B7BUl8Rd.js +1 -0
- package/server/public/assets/stata-BLJTbKOO.js +1 -0
- package/server/public/assets/styles-UDowwF7S.css +2 -0
- package/server/public/assets/stylus-Byjxdx_q.js +1 -0
- package/server/public/assets/surrealql-C96KvYaj.js +1 -0
- package/server/public/assets/svelte-Qnbj2GWx.js +1 -0
- package/server/public/assets/swift-BexLlMrU.js +1 -0
- package/server/public/assets/synthwave-84-BxMBwQMS.js +1 -0
- package/server/public/assets/system-verilog-DVGwm0mw.js +1 -0
- package/server/public/assets/systemd-H2IT3-p5.js +1 -0
- package/server/public/assets/talonscript-mKZIGM8n.js +1 -0
- package/server/public/assets/tasl-B7he_Ugr.js +1 -0
- package/server/public/assets/tcl-5mT3RxHH.js +1 -0
- package/server/public/assets/templ-CQPDll3D.js +1 -0
- package/server/public/assets/terraform-BZP0GLsT.js +1 -0
- package/server/public/assets/test-D7JRfog1.js +1 -0
- package/server/public/assets/tex-97QNLoBJ.js +1 -0
- package/server/public/assets/tokyo-night-CTPVdZt9.js +1 -0
- package/server/public/assets/toml-CTFA98he.js +1 -0
- package/server/public/assets/ts-tags-B8zlXe2n.js +1 -0
- package/server/public/assets/tsv-BayJtYdY.js +1 -0
- package/server/public/assets/tsx-VqRU8NCz.js +1 -0
- package/server/public/assets/turtle-TVCBh_kY.js +1 -0
- package/server/public/assets/twig-FTTF8rVk.js +1 -0
- package/server/public/assets/typescript-CuX0hIVY.js +1 -0
- package/server/public/assets/typespec-BUvaJDLF.js +1 -0
- package/server/public/assets/typst-8NBaY7Ec.js +1 -0
- package/server/public/assets/useStore-M3H8PB1v.js +1 -0
- package/server/public/assets/utils-DElCu2hq.js +1 -0
- package/server/public/assets/v-VihyTigi.js +1 -0
- package/server/public/assets/vala-DyFAPyX6.js +1 -0
- package/server/public/assets/vb-Dg1Iqi4J.js +1 -0
- package/server/public/assets/verilog-D2Xc-vhD.js +1 -0
- package/server/public/assets/vesper-DJbtqYNr.js +1 -0
- package/server/public/assets/vhdl-CU3BVeE7.js +1 -0
- package/server/public/assets/viml-hG2shuOW.js +1 -0
- package/server/public/assets/vitesse-black-DbG2gsc0.js +1 -0
- package/server/public/assets/vitesse-dark-B6WV4xXH.js +1 -0
- package/server/public/assets/vitesse-light-DC1pdD02.js +1 -0
- package/server/public/assets/vue-DXwaEU0U.js +1 -0
- package/server/public/assets/vue-html-QD7AJ6JJ.js +1 -0
- package/server/public/assets/vue-vine-Bh2m1D2Z.js +1 -0
- package/server/public/assets/vyper-C1wojIuk.js +1 -0
- package/server/public/assets/wasm-C6Y0s02M.js +1 -0
- package/server/public/assets/wasm-qTvCOSHz.js +1 -0
- package/server/public/assets/wenyan-BG5vPQF0.js +1 -0
- package/server/public/assets/wgsl-DrVb-Cub.js +1 -0
- package/server/public/assets/wikitext-PRC4s8sH.js +1 -0
- package/server/public/assets/wit-ChW5qvg_.js +1 -0
- package/server/public/assets/wolfram-B8mKuZSQ.js +1 -0
- package/server/public/assets/xml-BK-rcb5a.js +1 -0
- package/server/public/assets/xsl-dt-d2R7p.js +1 -0
- package/server/public/assets/yaml-UiXU3hGj.js +1 -0
- package/server/public/assets/zenscript-C-jEPC9j.js +1 -0
- package/server/public/assets/zig-EbnRGjcz.js +1 -0
- package/server/public/favicon.ico +0 -0
- package/server/public/logo192.png +0 -0
- package/server/public/logo512.png +0 -0
- package/server/public/manifest.json +25 -0
- package/server/public/robots.txt +3 -0
- package/server/public/tanstack-circle-logo.png +0 -0
- package/server/public/tanstack-word-logo-white.svg +1 -0
- package/server/server/_chunks/ssr-renderer.mjs +15 -0
- package/server/server/_libs/@floating-ui/core+[...].mjs +698 -0
- package/server/server/_libs/@floating-ui/dom+[...].mjs +644 -0
- package/server/server/_libs/@floating-ui/react-dom+[...].mjs +839 -0
- package/server/server/_libs/@pierre/diffs+[...].mjs +18578 -0
- package/server/server/_libs/@radix-ui/react-arrow+[...].mjs +174 -0
- package/server/server/_libs/@radix-ui/react-collection+[...].mjs +162 -0
- package/server/server/_libs/@radix-ui/react-dialog+[...].mjs +1666 -0
- package/server/server/_libs/@radix-ui/react-popper+[...].mjs +289 -0
- package/server/server/_libs/@radix-ui/react-radio-group+[...].mjs +420 -0
- package/server/server/_libs/@radix-ui/react-select+[...].mjs +990 -0
- package/server/server/_libs/@tanstack/react-router+[...].mjs +14113 -0
- package/server/server/_libs/_.mjs +2 -0
- package/server/server/_libs/chokidar+readdirp.mjs +1599 -0
- package/server/server/_libs/class-variance-authority+clsx.mjs +69 -0
- package/server/server/_libs/effect+[...].mjs +34047 -0
- package/server/server/_libs/h3+rou3+srvx.mjs +1195 -0
- package/server/server/_libs/hookable.mjs +41 -0
- package/server/server/_libs/lucide-react.mjs +298 -0
- package/server/server/_libs/pierre__theme.mjs +2668 -0
- package/server/server/_libs/radix-ui__number.mjs +6 -0
- package/server/server/_libs/radix-ui__primitive.mjs +9 -0
- package/server/server/_libs/radix-ui__react-direction.mjs +11 -0
- package/server/server/_libs/shiki.mjs +16 -0
- package/server/server/_libs/shikijs__langs.mjs +1355 -0
- package/server/server/_libs/shikijs__themes.mjs +262 -0
- package/server/server/_libs/tailwind-merge.mjs +1962 -0
- package/server/server/_libs/tanstack__history.mjs +342 -0
- package/server/server/_libs/tanstack__router-core.mjs +6 -0
- package/server/server/_libs/ufo.mjs +64 -0
- package/server/server/_reviewId-AWnOGz5k.mjs +33 -0
- package/server/server/_reviewId-Com4yOlc.mjs +29 -0
- package/server/server/_reviewId-DAhmekJ2.mjs +277 -0
- package/server/server/_reviewId-p9mhYVwa.mjs +18 -0
- package/server/server/_runtime.mjs +35 -0
- package/server/server/_ssr/action-bar-C68xGnWW.mjs +592 -0
- package/server/server/_ssr/api-handler-CstW2n82.mjs +189 -0
- package/server/server/_ssr/client-runtime-BoPuAEoA.mjs +245 -0
- package/server/server/_ssr/createServerRpc--0mcGlWK.mjs +12 -0
- package/server/server/_ssr/createSsrRpc-AwdiLXmF.mjs +16 -0
- package/server/server/_ssr/domain-rpc-3Ds9DPr0.mjs +287 -0
- package/server/server/_ssr/file-tree-CQ5w2GHh.mjs +1951 -0
- package/server/server/_ssr/load-scoped-diff-NL2XAcdz.mjs +45 -0
- package/server/server/_ssr/new-BKl_G2Ks.mjs +37 -0
- package/server/server/_ssr/new-BREdMFAM.mjs +12 -0
- package/server/server/_ssr/new-DCz5eHkb.mjs +137 -0
- package/server/server/_ssr/reviews-BL5Nsgst.mjs +7 -0
- package/server/server/_ssr/reviews-BoaEgGKs.mjs +100 -0
- package/server/server/_ssr/reviews-C7_NIhY8.mjs +19 -0
- package/server/server/_ssr/reviews-Dd69YBDa.mjs +12 -0
- package/server/server/_ssr/router-DLxN8FOm.mjs +415 -0
- package/server/server/_ssr/routes-D25G8OuS.mjs +80 -0
- package/server/server/_ssr/routes-lz0AN75A.mjs +929 -0
- package/server/server/_ssr/runtime-D9IbnMlF.mjs +1401 -0
- package/server/server/_ssr/server-runtime-D99qpmma.mjs +12 -0
- package/server/server/_ssr/ssr.mjs +5318 -0
- package/server/server/_ssr/start-BIQfOZtj.mjs +4 -0
- package/server/server/_ssr/test-CQdMYlqa.mjs +6 -0
- package/server/server/_ssr/todo-m_uUvxca.mjs +88 -0
- package/server/server/_ssr/use-keyboard-shortcuts-D5b1Mxpq.mjs +25 -0
- package/server/server/_ssr/utils-BuOt9_LA.mjs +8 -0
- package/server/server/_tanstack-start-manifest_v-CnL10NRH.mjs +71 -0
- package/server/server/index.mjs +2615 -0
- package/server/server/node_modules/detect-libc/lib/detect-libc.js +313 -0
- package/server/server/node_modules/detect-libc/lib/elf.js +39 -0
- package/server/server/node_modules/detect-libc/lib/filesystem.js +51 -0
- package/server/server/node_modules/detect-libc/lib/process.js +24 -0
- package/server/server/node_modules/detect-libc/package.json +44 -0
- package/server/server/node_modules/msgpackr-extract/index.js +1 -0
- package/server/server/node_modules/msgpackr-extract/package.json +50 -0
- package/server/server/node_modules/node-gyp-build-optional-packages/index.js +6 -0
- package/server/server/node_modules/node-gyp-build-optional-packages/node-gyp-build.js +236 -0
- package/server/server/node_modules/node-gyp-build-optional-packages/package.json +32 -0
- package/server/server/node_modules/tslib/modules/index.js +70 -0
- package/server/server/node_modules/tslib/modules/package.json +3 -0
- package/server/server/node_modules/tslib/package.json +47 -0
- package/server/server/node_modules/tslib/tslib.js +484 -0
- package/server/server/package.json +12 -0
- package/dist/chunk-KMYSGMD3.js +0 -3526
- package/dist/chunk-KMYSGMD3.js.map +0 -1
- package/dist/cli.js +0 -1839
- package/dist/cli.js.map +0 -1
- package/dist/mcp.js +0 -1228
- package/dist/mcp.js.map +0 -1
|
@@ -0,0 +1,1951 @@
|
|
|
1
|
+
import { i as __toESM } from "../_runtime.mjs";
|
|
2
|
+
import { u as require_react } from "../_libs/@floating-ui/react-dom+[...].mjs";
|
|
3
|
+
import { p as useRouter } from "../_libs/@tanstack/react-router+[...].mjs";
|
|
4
|
+
import { n as require_jsx_runtime, t as PatchDiff } from "../_libs/@pierre/diffs+[...].mjs";
|
|
5
|
+
import { Ct as tap, It as hasInterruptsOnly, Lt as pretty, N as interrupt, St as sync, Tt as timeout, ct as ensuring, dt as gen, gt as matchCauseEffect, kt as withSpan, vt as promise, wt as tapCause } from "../_libs/effect+[...].mjs";
|
|
6
|
+
import { i as clientRuntime, t as ApiClient } from "./client-runtime-BoPuAEoA.mjs";
|
|
7
|
+
import { t as cn } from "./utils-BuOt9_LA.mjs";
|
|
8
|
+
import { b as Check, c as Folder, f as Download, l as FolderOpen, p as Clipboard, u as File, v as ChevronRight } from "../_libs/lucide-react.mjs";
|
|
9
|
+
import { a as DialogFooter, i as DialogDescription, n as Dialog$1, o as DialogHeader, r as DialogContent, s as DialogTitle } from "./action-bar-C68xGnWW.mjs";
|
|
10
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/file-tree-CQ5w2GHh.js
|
|
11
|
+
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
12
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
13
|
+
var LINE_TYPE_LABELS = {
|
|
14
|
+
added: "new",
|
|
15
|
+
context: "context",
|
|
16
|
+
removed: "old"
|
|
17
|
+
};
|
|
18
|
+
var lineTypeLabel = (type) => (type && LINE_TYPE_LABELS[type]) ?? "context";
|
|
19
|
+
var groupByFile = (comments) => {
|
|
20
|
+
const groups = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const comment of comments) {
|
|
22
|
+
const key = comment.filePath || "(general)";
|
|
23
|
+
const existing = groups.get(key);
|
|
24
|
+
if (existing) existing.push(comment);
|
|
25
|
+
else groups.set(key, [comment]);
|
|
26
|
+
}
|
|
27
|
+
return groups;
|
|
28
|
+
};
|
|
29
|
+
var sortByLine = (a, b) => (a.lineNumber ?? 0) - (b.lineNumber ?? 0);
|
|
30
|
+
var formatReviewFeedback = (comments) => {
|
|
31
|
+
if (comments.length === 0) return "";
|
|
32
|
+
const lines = ["# Code Review Feedback", ""];
|
|
33
|
+
const groups = groupByFile(comments);
|
|
34
|
+
const sortedFiles = [...groups.keys()].toSorted((a, b) => a.localeCompare(b));
|
|
35
|
+
for (const filePath of sortedFiles) {
|
|
36
|
+
const fileComments = groups.get(filePath);
|
|
37
|
+
if (!fileComments || fileComments.length === 0) continue;
|
|
38
|
+
fileComments.sort(sortByLine);
|
|
39
|
+
lines.push(`## ${filePath}`, "");
|
|
40
|
+
for (const comment of fileComments) {
|
|
41
|
+
const lineLabel = comment.lineNumber === null || comment.lineNumber === void 0 ? "General" : `Line ${comment.lineNumber} (${lineTypeLabel(comment.lineType)})`;
|
|
42
|
+
lines.push(`### ${lineLabel}`);
|
|
43
|
+
lines.push(comment.content);
|
|
44
|
+
if (comment.suggestion) {
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push("```suggestion");
|
|
47
|
+
lines.push(comment.suggestion);
|
|
48
|
+
lines.push("```");
|
|
49
|
+
}
|
|
50
|
+
lines.push("");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
54
|
+
};
|
|
55
|
+
var COPY_FEEDBACK_MS = 1800;
|
|
56
|
+
var EMPTY_API_COMMENTS = [];
|
|
57
|
+
var EMPTY_LOCAL_COMMENTS = [];
|
|
58
|
+
var buttonMotionClass = "transition-[transform,background-color,color,border-color,box-shadow,opacity] duration-150 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] motion-reduce:transform-none";
|
|
59
|
+
var EmptyState = () => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
60
|
+
className: "flex flex-col items-center justify-center gap-2 py-12",
|
|
61
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Clipboard, { className: "size-4 text-text-tertiary" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
62
|
+
className: "text-sm text-text-tertiary",
|
|
63
|
+
children: "No comments to export"
|
|
64
|
+
})]
|
|
65
|
+
});
|
|
66
|
+
var MarkdownPreview = ({ markdown }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
67
|
+
className: "relative min-h-0 flex-1 overflow-hidden rounded-lg border border-border-default bg-surface-inset/40",
|
|
68
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
|
|
69
|
+
className: "h-full overflow-y-auto whitespace-pre-wrap break-words p-4 font-mono text-xs leading-relaxed text-text-secondary selection:bg-accent-muted",
|
|
70
|
+
children: markdown
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
var CopyButton = ({ copied, disabled, onClick }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
74
|
+
type: "button",
|
|
75
|
+
disabled,
|
|
76
|
+
onClick,
|
|
77
|
+
className: cn("inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium active:scale-[0.97]", buttonMotionClass, copied ? "bg-status-success/15 text-status-success" : "bg-accent-primary text-white hover:bg-accent-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/40 disabled:cursor-not-allowed disabled:opacity-40"),
|
|
78
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
79
|
+
className: cn("inline-flex transition-[transform,opacity] duration-150 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", copied ? "scale-110" : "scale-100"),
|
|
80
|
+
children: copied ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "size-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Clipboard, { className: "size-3.5" })
|
|
81
|
+
}), copied ? "Copied!" : "Copy to Clipboard"]
|
|
82
|
+
});
|
|
83
|
+
var DownloadButton = ({ disabled, onClick }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
84
|
+
type: "button",
|
|
85
|
+
disabled,
|
|
86
|
+
onClick,
|
|
87
|
+
className: cn("inline-flex items-center gap-1.5 rounded-lg border border-border-default bg-surface-elevated px-3 py-1.5 text-xs text-text-secondary hover:bg-surface-overlay hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 active:scale-[0.97] disabled:cursor-not-allowed disabled:opacity-40", buttonMotionClass),
|
|
88
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Download, { className: "size-3.5" }), "Download .md"]
|
|
89
|
+
});
|
|
90
|
+
var ExportFeedbackModal = ({ open, onOpenChange, comments = EMPTY_API_COMMENTS, localComments = EMPTY_LOCAL_COMMENTS, reviewId }) => {
|
|
91
|
+
const [copied, setCopied] = (0, import_react.useState)(false);
|
|
92
|
+
const timerRef = (0, import_react.useRef)(null);
|
|
93
|
+
(0, import_react.useEffect)(() => {
|
|
94
|
+
if (!open) setCopied(false);
|
|
95
|
+
return () => {
|
|
96
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
97
|
+
};
|
|
98
|
+
}, [open]);
|
|
99
|
+
const allComments = (0, import_react.useMemo)(() => {
|
|
100
|
+
return [...comments.map((c) => ({
|
|
101
|
+
content: c.content,
|
|
102
|
+
filePath: c.filePath,
|
|
103
|
+
lineNumber: c.lineNumber,
|
|
104
|
+
lineType: c.lineType,
|
|
105
|
+
suggestion: c.suggestion
|
|
106
|
+
})), ...localComments];
|
|
107
|
+
}, [comments, localComments]);
|
|
108
|
+
const markdown = (0, import_react.useMemo)(() => formatReviewFeedback(allComments), [allComments]);
|
|
109
|
+
const commentCount = allComments.length;
|
|
110
|
+
const handleCopy = (0, import_react.useCallback)(async () => {
|
|
111
|
+
if (!markdown) return;
|
|
112
|
+
await navigator.clipboard.writeText(markdown);
|
|
113
|
+
setCopied(true);
|
|
114
|
+
timerRef.current = setTimeout(() => setCopied(false), COPY_FEEDBACK_MS);
|
|
115
|
+
}, [markdown]);
|
|
116
|
+
const handleDownload = (0, import_react.useCallback)(() => {
|
|
117
|
+
if (!markdown) return;
|
|
118
|
+
const blob = new Blob([markdown], { type: "text/markdown" });
|
|
119
|
+
const url = URL.createObjectURL(blob);
|
|
120
|
+
const anchor = document.createElement("a");
|
|
121
|
+
anchor.href = url;
|
|
122
|
+
anchor.download = reviewId ? `review-${reviewId}.md` : "review-feedback.md";
|
|
123
|
+
anchor.click();
|
|
124
|
+
URL.revokeObjectURL(url);
|
|
125
|
+
}, [markdown, reviewId]);
|
|
126
|
+
const isEmpty = commentCount === 0;
|
|
127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
|
|
128
|
+
open,
|
|
129
|
+
onOpenChange,
|
|
130
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
|
|
131
|
+
className: "flex max-h-[min(640px,calc(100dvh-2rem))] w-full max-w-[calc(100%-1.5rem)] flex-col gap-0 overflow-hidden sm:max-w-2xl",
|
|
132
|
+
children: [
|
|
133
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Export Feedback" }), isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: "No comments to export." }) : null] }),
|
|
134
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
135
|
+
className: "flex min-h-0 flex-1 flex-col px-5 py-4",
|
|
136
|
+
children: isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyState, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownPreview, { markdown })
|
|
137
|
+
}),
|
|
138
|
+
isEmpty ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DownloadButton, {
|
|
139
|
+
disabled: isEmpty,
|
|
140
|
+
onClick: handleDownload
|
|
141
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
|
|
142
|
+
copied,
|
|
143
|
+
disabled: isEmpty,
|
|
144
|
+
onClick: handleCopy
|
|
145
|
+
})] })
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
function DiffSummary({ summary }) {
|
|
151
|
+
if (summary.totalAdditions === 0 && summary.totalDeletions === 0) return null;
|
|
152
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153
|
+
className: "flex items-center gap-2 text-xs tabular-nums",
|
|
154
|
+
children: [summary.totalAdditions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
155
|
+
className: "text-diff-add-text",
|
|
156
|
+
children: ["+", summary.totalAdditions]
|
|
157
|
+
}) : null, summary.totalDeletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
158
|
+
className: "text-diff-remove-text",
|
|
159
|
+
children: ["-", summary.totalDeletions]
|
|
160
|
+
}) : null]
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
var STORAGE_KEY = "ringi:split-diff-ratio";
|
|
164
|
+
var DEFAULT_RATIO = .5;
|
|
165
|
+
var MIN_RATIO = .2;
|
|
166
|
+
var MAX_RATIO = .8;
|
|
167
|
+
var KEYBOARD_STEP = .01;
|
|
168
|
+
var KEYBOARD_STEP_LARGE = .05;
|
|
169
|
+
/**
|
|
170
|
+
* CSS override injected once into the pierre/diffs shadow root.
|
|
171
|
+
* CSS custom properties set on the host element inherit into shadow DOM,
|
|
172
|
+
* so `--ringi-split-left` / `--ringi-split-right` are readable here.
|
|
173
|
+
*/
|
|
174
|
+
var SHADOW_STYLE_ID = "ringi-split-override";
|
|
175
|
+
var SHADOW_CSS = `
|
|
176
|
+
[data-diff-type='split'][data-overflow='scroll'] {
|
|
177
|
+
grid-template-columns: var(--ringi-split-left, 1fr) var(--ringi-split-right, 1fr) !important;
|
|
178
|
+
}
|
|
179
|
+
[data-diff-type='split'][data-overflow='wrap'] {
|
|
180
|
+
grid-template-columns:
|
|
181
|
+
var(--diffs-grid-number-column-width)
|
|
182
|
+
var(--ringi-split-left, 1fr)
|
|
183
|
+
var(--diffs-grid-number-column-width)
|
|
184
|
+
var(--ringi-split-right, 1fr) !important;
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
var clampRatio = (r) => Math.min(MAX_RATIO, Math.max(MIN_RATIO, r));
|
|
188
|
+
var loadRatio = () => {
|
|
189
|
+
try {
|
|
190
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
191
|
+
if (stored !== null) {
|
|
192
|
+
const parsed = Number.parseFloat(stored);
|
|
193
|
+
if (Number.isFinite(parsed)) return clampRatio(parsed);
|
|
194
|
+
}
|
|
195
|
+
} catch {}
|
|
196
|
+
return DEFAULT_RATIO;
|
|
197
|
+
};
|
|
198
|
+
var saveRatio = (ratio) => {
|
|
199
|
+
try {
|
|
200
|
+
localStorage.setItem(STORAGE_KEY, ratio.toFixed(4));
|
|
201
|
+
} catch {}
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* Injects the CSS override into the shadow root of a `diffs-container` element.
|
|
205
|
+
* Returns `true` if injection succeeded.
|
|
206
|
+
*/
|
|
207
|
+
var injectShadowStyle = (container) => {
|
|
208
|
+
const shadow = container.shadowRoot;
|
|
209
|
+
if (!shadow) return false;
|
|
210
|
+
if (shadow.querySelector(`#${SHADOW_STYLE_ID}`)) return true;
|
|
211
|
+
const style = document.createElement("style");
|
|
212
|
+
style.id = SHADOW_STYLE_ID;
|
|
213
|
+
style.textContent = SHADOW_CSS;
|
|
214
|
+
shadow.append(style);
|
|
215
|
+
return true;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Sets the CSS custom properties on the `diffs-container` host element.
|
|
219
|
+
* These inherit into the shadow DOM where our injected CSS rule reads them.
|
|
220
|
+
*/
|
|
221
|
+
var applySplitRatio = (container, ratio) => {
|
|
222
|
+
container.style.setProperty("--ringi-split-left", `${ratio}fr`);
|
|
223
|
+
container.style.setProperty("--ringi-split-right", `${1 - ratio}fr`);
|
|
224
|
+
};
|
|
225
|
+
var useSplitDiffResizer = (enabled) => {
|
|
226
|
+
const [ratio, setRatioState] = (0, import_react.useState)(loadRatio);
|
|
227
|
+
const [isDragging, setIsDragging] = (0, import_react.useState)(false);
|
|
228
|
+
const wrapperElRef = (0, import_react.useRef)(null);
|
|
229
|
+
const diffsContainerRef = (0, import_react.useRef)(null);
|
|
230
|
+
const ratioRef = (0, import_react.useRef)(ratio);
|
|
231
|
+
const draggingRef = (0, import_react.useRef)(false);
|
|
232
|
+
ratioRef.current = ratio;
|
|
233
|
+
const setRatio = (0, import_react.useCallback)((next) => {
|
|
234
|
+
const clamped = clampRatio(next);
|
|
235
|
+
ratioRef.current = clamped;
|
|
236
|
+
setRatioState(clamped);
|
|
237
|
+
saveRatio(clamped);
|
|
238
|
+
if (diffsContainerRef.current) applySplitRatio(diffsContainerRef.current, clamped);
|
|
239
|
+
}, []);
|
|
240
|
+
const setupDiffsContainer = (0, import_react.useCallback)((wrapper) => {
|
|
241
|
+
if (!wrapper || !enabled) return;
|
|
242
|
+
const tryInject = () => {
|
|
243
|
+
const container = wrapper.querySelector("diffs-container");
|
|
244
|
+
if (!container) return false;
|
|
245
|
+
const el = container;
|
|
246
|
+
diffsContainerRef.current = el;
|
|
247
|
+
const ok = injectShadowStyle(el);
|
|
248
|
+
if (ok) applySplitRatio(el, ratioRef.current);
|
|
249
|
+
return ok;
|
|
250
|
+
};
|
|
251
|
+
if (tryInject()) return;
|
|
252
|
+
const observer = new MutationObserver(() => {
|
|
253
|
+
if (tryInject()) observer.disconnect();
|
|
254
|
+
});
|
|
255
|
+
observer.observe(wrapper, {
|
|
256
|
+
childList: true,
|
|
257
|
+
subtree: true
|
|
258
|
+
});
|
|
259
|
+
const timeout = setTimeout(() => observer.disconnect(), 5e3);
|
|
260
|
+
return () => {
|
|
261
|
+
observer.disconnect();
|
|
262
|
+
clearTimeout(timeout);
|
|
263
|
+
};
|
|
264
|
+
}, [enabled]);
|
|
265
|
+
const wrapperRef = (0, import_react.useCallback)((node) => {
|
|
266
|
+
wrapperElRef.current = node;
|
|
267
|
+
setupDiffsContainer(node);
|
|
268
|
+
}, [setupDiffsContainer]);
|
|
269
|
+
(0, import_react.useEffect)(() => {
|
|
270
|
+
if (diffsContainerRef.current && enabled) {
|
|
271
|
+
injectShadowStyle(diffsContainerRef.current);
|
|
272
|
+
applySplitRatio(diffsContainerRef.current, ratio);
|
|
273
|
+
}
|
|
274
|
+
}, [ratio, enabled]);
|
|
275
|
+
const handlePointerDown = (0, import_react.useCallback)((e) => {
|
|
276
|
+
if (!enabled || !wrapperElRef.current) return;
|
|
277
|
+
e.preventDefault();
|
|
278
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
279
|
+
draggingRef.current = true;
|
|
280
|
+
setIsDragging(true);
|
|
281
|
+
document.body.style.userSelect = "none";
|
|
282
|
+
document.body.style.cursor = "col-resize";
|
|
283
|
+
const wrapperRect = wrapperElRef.current.getBoundingClientRect();
|
|
284
|
+
const onPointerMove = (ev) => {
|
|
285
|
+
if (!draggingRef.current) return;
|
|
286
|
+
const newRatio = clampRatio((ev.clientX - wrapperRect.left) / wrapperRect.width);
|
|
287
|
+
ratioRef.current = newRatio;
|
|
288
|
+
if (diffsContainerRef.current) applySplitRatio(diffsContainerRef.current, newRatio);
|
|
289
|
+
if (wrapperElRef.current) wrapperElRef.current.style.setProperty("--ringi-splitter-left", `${newRatio * 100}%`);
|
|
290
|
+
};
|
|
291
|
+
const onPointerUp = () => {
|
|
292
|
+
draggingRef.current = false;
|
|
293
|
+
setIsDragging(false);
|
|
294
|
+
document.body.style.userSelect = "";
|
|
295
|
+
document.body.style.cursor = "";
|
|
296
|
+
setRatio(ratioRef.current);
|
|
297
|
+
document.removeEventListener("pointermove", onPointerMove);
|
|
298
|
+
document.removeEventListener("pointerup", onPointerUp);
|
|
299
|
+
};
|
|
300
|
+
document.addEventListener("pointermove", onPointerMove);
|
|
301
|
+
document.addEventListener("pointerup", onPointerUp);
|
|
302
|
+
}, [enabled, setRatio]);
|
|
303
|
+
const handleKeyDown = (0, import_react.useCallback)((e) => {
|
|
304
|
+
if (!enabled) return;
|
|
305
|
+
const step = e.shiftKey ? KEYBOARD_STEP_LARGE : KEYBOARD_STEP;
|
|
306
|
+
let handled = false;
|
|
307
|
+
if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
|
|
308
|
+
setRatio(ratioRef.current - step);
|
|
309
|
+
handled = true;
|
|
310
|
+
} else if (e.key === "ArrowRight" || e.key === "ArrowUp") {
|
|
311
|
+
setRatio(ratioRef.current + step);
|
|
312
|
+
handled = true;
|
|
313
|
+
} else if (e.key === "Home") {
|
|
314
|
+
setRatio(MIN_RATIO);
|
|
315
|
+
handled = true;
|
|
316
|
+
} else if (e.key === "End") {
|
|
317
|
+
setRatio(MAX_RATIO);
|
|
318
|
+
handled = true;
|
|
319
|
+
} else if (e.key === "Enter" || e.key === " ") {
|
|
320
|
+
setRatio(DEFAULT_RATIO);
|
|
321
|
+
handled = true;
|
|
322
|
+
}
|
|
323
|
+
if (handled) {
|
|
324
|
+
e.preventDefault();
|
|
325
|
+
e.stopPropagation();
|
|
326
|
+
}
|
|
327
|
+
}, [enabled, setRatio]);
|
|
328
|
+
const handleDoubleClick = (0, import_react.useCallback)(() => {
|
|
329
|
+
setRatio(DEFAULT_RATIO);
|
|
330
|
+
}, [setRatio]);
|
|
331
|
+
return {
|
|
332
|
+
isDragging,
|
|
333
|
+
ratio,
|
|
334
|
+
splitterProps: {
|
|
335
|
+
"aria-label": "Resize diff panes",
|
|
336
|
+
"aria-orientation": "vertical",
|
|
337
|
+
"aria-valuemax": MAX_RATIO * 100,
|
|
338
|
+
"aria-valuemin": MIN_RATIO * 100,
|
|
339
|
+
"aria-valuenow": Math.round(ratio * 100),
|
|
340
|
+
onDoubleClick: handleDoubleClick,
|
|
341
|
+
onKeyDown: handleKeyDown,
|
|
342
|
+
onPointerDown: handlePointerDown,
|
|
343
|
+
role: "separator",
|
|
344
|
+
tabIndex: 0
|
|
345
|
+
},
|
|
346
|
+
wrapperRef
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* Wraps a PatchDiff (split mode) and overlays a draggable vertical splitter.
|
|
351
|
+
*
|
|
352
|
+
* Architecture:
|
|
353
|
+
* - The wrapper div receives a ref that discovers the `<diffs-container>`
|
|
354
|
+
* shadow DOM and injects a CSS override so the split grid columns read
|
|
355
|
+
* `--ringi-split-left` / `--ringi-split-right` CSS variables.
|
|
356
|
+
* - The splitter handle is absolutely positioned at the split point.
|
|
357
|
+
* - During drag, DOM updates happen via direct style manipulation (no React
|
|
358
|
+
* re-renders), then the final ratio is committed to state + localStorage.
|
|
359
|
+
* - Keyboard: Arrow keys adjust ratio, Enter/Space resets to 50%.
|
|
360
|
+
*/
|
|
361
|
+
var SplitDiffSplitter = ({ children, enabled = true }) => {
|
|
362
|
+
const { ratio, isDragging, wrapperRef, splitterProps } = useSplitDiffResizer(enabled);
|
|
363
|
+
if (!enabled) return children;
|
|
364
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
365
|
+
ref: wrapperRef,
|
|
366
|
+
className: "ringi-split-diff-wrapper relative",
|
|
367
|
+
style: { "--ringi-splitter-left": `${ratio * 100}%` },
|
|
368
|
+
children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
369
|
+
...splitterProps,
|
|
370
|
+
className: cn("ringi-split-splitter group/splitter", "absolute top-0 bottom-0 z-10", "-translate-x-1/2", "flex items-center justify-center", "cursor-col-resize", "outline-none", isDragging && "ringi-split-splitter--active"),
|
|
371
|
+
style: {
|
|
372
|
+
left: "var(--ringi-splitter-left)",
|
|
373
|
+
width: "13px"
|
|
374
|
+
},
|
|
375
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("ringi-split-splitter__bar", "h-full transition-[width,background-color,opacity] duration-100", "[transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", isDragging ? "w-[3px] bg-accent-primary/60" : "w-px bg-border-default group-hover/splitter:w-[2px] group-hover/splitter:bg-accent-primary/30") }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
376
|
+
className: cn("ringi-split-splitter__grip", "pointer-events-none absolute", "flex flex-col items-center gap-[3px]", "transition-opacity duration-100", "[transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", isDragging ? "opacity-100" : "opacity-0 group-hover/splitter:opacity-70 group-focus-visible/splitter:opacity-70"),
|
|
377
|
+
children: [
|
|
378
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-[3px] w-[3px] rounded-full bg-accent-primary/70" }),
|
|
379
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-[3px] w-[3px] rounded-full bg-accent-primary/70" }),
|
|
380
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-[3px] w-[3px] rounded-full bg-accent-primary/70" })
|
|
381
|
+
]
|
|
382
|
+
})]
|
|
383
|
+
})]
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
var pierreDiffOptions = {
|
|
387
|
+
diffIndicators: "bars",
|
|
388
|
+
diffStyle: "split",
|
|
389
|
+
expandUnchanged: true,
|
|
390
|
+
hunkSeparators: "line-info",
|
|
391
|
+
lineDiffType: "word",
|
|
392
|
+
overflow: "scroll",
|
|
393
|
+
theme: "pierre-dark",
|
|
394
|
+
themeType: "dark"
|
|
395
|
+
};
|
|
396
|
+
var MIN_TEXTAREA_ROWS$1 = 2;
|
|
397
|
+
var MAX_TEXTAREA_ROWS$1 = 8;
|
|
398
|
+
var resizeTextarea$1 = (textarea) => {
|
|
399
|
+
if (!textarea) return;
|
|
400
|
+
const styles = window.getComputedStyle(textarea);
|
|
401
|
+
const lineHeight = Number.parseFloat(styles.lineHeight || "0") || 16;
|
|
402
|
+
const paddingTop = Number.parseFloat(styles.paddingTop || "0");
|
|
403
|
+
const paddingBottom = Number.parseFloat(styles.paddingBottom || "0");
|
|
404
|
+
const borderTop = Number.parseFloat(styles.borderTopWidth || "0");
|
|
405
|
+
const borderBottom = Number.parseFloat(styles.borderBottomWidth || "0");
|
|
406
|
+
const minHeight = lineHeight * MIN_TEXTAREA_ROWS$1 + paddingTop + paddingBottom + borderTop + borderBottom;
|
|
407
|
+
const maxHeight = lineHeight * MAX_TEXTAREA_ROWS$1 + paddingTop + paddingBottom + borderTop + borderBottom;
|
|
408
|
+
textarea.style.height = "0px";
|
|
409
|
+
const nextHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
|
|
410
|
+
textarea.style.height = `${nextHeight}px`;
|
|
411
|
+
textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
|
|
412
|
+
};
|
|
413
|
+
var editorPaneClassName = "min-h-[220px] rounded-lg border border-border-default bg-surface-primary shadow-inner shadow-black/10";
|
|
414
|
+
var codeBlockClassName = "h-full min-h-[180px] w-full rounded-md border border-transparent bg-transparent px-3 py-2.5 font-mono text-xs leading-5 text-text-primary outline-none";
|
|
415
|
+
var getOriginalCodeValue = (originalCode) => {
|
|
416
|
+
if (originalCode && originalCode.length > 0) return originalCode;
|
|
417
|
+
return "No original code available";
|
|
418
|
+
};
|
|
419
|
+
var CodeSuggestionEditor = ({ value, onChange, originalCode, filePath, lineNumber }) => {
|
|
420
|
+
const dialogRef = (0, import_react.useRef)(null);
|
|
421
|
+
const inlineTextareaRef = (0, import_react.useRef)(null);
|
|
422
|
+
const expandedTextareaRef = (0, import_react.useRef)(null);
|
|
423
|
+
const [expandedValue, setExpandedValue] = (0, import_react.useState)(value);
|
|
424
|
+
const [isExpanded, setIsExpanded] = (0, import_react.useState)(false);
|
|
425
|
+
(0, import_react.useEffect)(() => {
|
|
426
|
+
resizeTextarea$1(inlineTextareaRef.current);
|
|
427
|
+
}, [value]);
|
|
428
|
+
(0, import_react.useEffect)(() => {
|
|
429
|
+
if (!isExpanded) setExpandedValue(value);
|
|
430
|
+
}, [isExpanded, value]);
|
|
431
|
+
(0, import_react.useEffect)(() => {
|
|
432
|
+
if (!isExpanded) return;
|
|
433
|
+
const focusTimer = window.setTimeout(() => {
|
|
434
|
+
expandedTextareaRef.current?.focus();
|
|
435
|
+
const length = expandedTextareaRef.current?.value.length ?? 0;
|
|
436
|
+
expandedTextareaRef.current?.setSelectionRange(length, length);
|
|
437
|
+
resizeTextarea$1(expandedTextareaRef.current);
|
|
438
|
+
}, 0);
|
|
439
|
+
return () => window.clearTimeout(focusTimer);
|
|
440
|
+
}, [isExpanded]);
|
|
441
|
+
const handleDialogClose = (0, import_react.useCallback)(() => {
|
|
442
|
+
setIsExpanded(false);
|
|
443
|
+
}, []);
|
|
444
|
+
const closeExpandedModal = (0, import_react.useCallback)(() => {
|
|
445
|
+
if (dialogRef.current?.open) dialogRef.current.close();
|
|
446
|
+
setExpandedValue(value);
|
|
447
|
+
setIsExpanded(false);
|
|
448
|
+
}, [value]);
|
|
449
|
+
const openExpandedModal = (0, import_react.useCallback)(() => {
|
|
450
|
+
if (dialogRef.current?.open) return;
|
|
451
|
+
setExpandedValue(value);
|
|
452
|
+
dialogRef.current?.showModal();
|
|
453
|
+
setIsExpanded(true);
|
|
454
|
+
}, [value]);
|
|
455
|
+
const handleApply = (0, import_react.useCallback)(() => {
|
|
456
|
+
onChange(expandedValue);
|
|
457
|
+
if (dialogRef.current?.open) dialogRef.current.close();
|
|
458
|
+
setIsExpanded(false);
|
|
459
|
+
}, [expandedValue, onChange]);
|
|
460
|
+
const handleRemove = (0, import_react.useCallback)(() => {
|
|
461
|
+
onChange("");
|
|
462
|
+
if (dialogRef.current?.open) dialogRef.current.close();
|
|
463
|
+
setExpandedValue("");
|
|
464
|
+
setIsExpanded(false);
|
|
465
|
+
}, [onChange]);
|
|
466
|
+
const handleDialogCancel = (0, import_react.useCallback)((event) => {
|
|
467
|
+
event.preventDefault();
|
|
468
|
+
event.stopPropagation();
|
|
469
|
+
closeExpandedModal();
|
|
470
|
+
}, [closeExpandedModal]);
|
|
471
|
+
const handleExpandedKeyDown = (0, import_react.useCallback)((event) => {
|
|
472
|
+
if (event.key !== "Escape") return;
|
|
473
|
+
event.preventDefault();
|
|
474
|
+
event.stopPropagation();
|
|
475
|
+
closeExpandedModal();
|
|
476
|
+
}, [closeExpandedModal]);
|
|
477
|
+
const handleInlineChange = (0, import_react.useCallback)((event) => {
|
|
478
|
+
onChange(event.target.value);
|
|
479
|
+
resizeTextarea$1(event.target);
|
|
480
|
+
}, [onChange]);
|
|
481
|
+
const handleExpandedChange = (0, import_react.useCallback)((event) => {
|
|
482
|
+
setExpandedValue(event.target.value);
|
|
483
|
+
resizeTextarea$1(event.target);
|
|
484
|
+
}, []);
|
|
485
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
486
|
+
className: "animate-in fade-in rounded-lg border border-border-default/80 bg-surface-elevated duration-100",
|
|
487
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
488
|
+
className: "flex items-center justify-between gap-3 border-b border-border-subtle px-3 py-2",
|
|
489
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
490
|
+
className: "text-[10px] font-medium uppercase tracking-[0.16em] text-text-tertiary",
|
|
491
|
+
children: "Suggestion"
|
|
492
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
493
|
+
className: "flex items-center gap-1",
|
|
494
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
495
|
+
type: "button",
|
|
496
|
+
onClick: openExpandedModal,
|
|
497
|
+
className: "inline-flex h-6 items-center rounded-md px-2 font-mono text-[10px] text-text-secondary transition-colors hover:bg-surface-overlay hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
|
|
498
|
+
"aria-label": "Expand code suggestion editor",
|
|
499
|
+
children: "Expand"
|
|
500
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
501
|
+
type: "button",
|
|
502
|
+
onClick: handleRemove,
|
|
503
|
+
className: "inline-flex h-6 items-center rounded-md px-2 font-mono text-[10px] text-text-tertiary transition-colors hover:bg-surface-overlay hover:text-status-error focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
|
|
504
|
+
"aria-label": "Remove code suggestion",
|
|
505
|
+
children: "Remove"
|
|
506
|
+
})]
|
|
507
|
+
})]
|
|
508
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
509
|
+
className: "px-3 py-2.5",
|
|
510
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
|
|
511
|
+
ref: inlineTextareaRef,
|
|
512
|
+
value,
|
|
513
|
+
onChange: handleInlineChange,
|
|
514
|
+
rows: MIN_TEXTAREA_ROWS$1,
|
|
515
|
+
spellCheck: false,
|
|
516
|
+
placeholder: "Suggest a replacement…",
|
|
517
|
+
className: "max-h-[13rem] min-h-[3.5rem] w-full resize-none overflow-y-hidden rounded-md border border-border-default/70 bg-surface-primary px-3 py-2 font-mono text-xs leading-5 text-text-primary shadow-inner shadow-black/10 outline-none transition-colors placeholder:text-text-tertiary/70 focus:border-accent-primary focus:ring-1 focus:ring-accent-primary/30",
|
|
518
|
+
"aria-label": "Suggested replacement code"
|
|
519
|
+
})
|
|
520
|
+
})]
|
|
521
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dialog", {
|
|
522
|
+
ref: dialogRef,
|
|
523
|
+
onCancel: handleDialogCancel,
|
|
524
|
+
onClose: handleDialogClose,
|
|
525
|
+
className: "m-auto w-full max-w-5xl rounded-2xl border border-border-default/80 bg-surface-elevated p-0 text-text-primary shadow-2xl shadow-black/40 backdrop:bg-black/50",
|
|
526
|
+
"aria-label": "Expanded code suggestion editor",
|
|
527
|
+
children: [
|
|
528
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
529
|
+
className: "flex items-center justify-between gap-3 border-b border-border-subtle px-4 py-3",
|
|
530
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
531
|
+
className: "flex min-w-0 items-center gap-2",
|
|
532
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
533
|
+
className: "text-sm font-medium text-text-primary",
|
|
534
|
+
children: "Suggestion"
|
|
535
|
+
}), filePath ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
536
|
+
className: "truncate font-mono text-[10px] text-text-tertiary",
|
|
537
|
+
children: [filePath, lineNumber != null ? `:${lineNumber}` : ""]
|
|
538
|
+
}) : null]
|
|
539
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
540
|
+
type: "button",
|
|
541
|
+
onClick: closeExpandedModal,
|
|
542
|
+
className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-text-tertiary transition-colors hover:bg-surface-overlay hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
|
|
543
|
+
"aria-label": "Close expanded code suggestion editor",
|
|
544
|
+
children: "×"
|
|
545
|
+
})]
|
|
546
|
+
}),
|
|
547
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
548
|
+
className: "grid gap-4 px-4 py-4 lg:grid-cols-2",
|
|
549
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
550
|
+
className: editorPaneClassName,
|
|
551
|
+
"aria-label": "Original code",
|
|
552
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
553
|
+
className: "border-b border-status-error/20 bg-status-error/8 px-3 py-2",
|
|
554
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
555
|
+
className: "font-mono text-[10px] font-medium uppercase tracking-[0.16em] text-diff-remove-text",
|
|
556
|
+
children: "Original"
|
|
557
|
+
})
|
|
558
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
|
|
559
|
+
className: cn(codeBlockClassName, "overflow-x-auto whitespace-pre-wrap break-words text-text-secondary"),
|
|
560
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: getOriginalCodeValue(originalCode) })
|
|
561
|
+
})]
|
|
562
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
563
|
+
className: editorPaneClassName,
|
|
564
|
+
"aria-label": "Suggested code",
|
|
565
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
566
|
+
className: "border-b border-status-success/20 bg-status-success/8 px-3 py-2",
|
|
567
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
568
|
+
className: "font-mono text-[10px] font-medium uppercase tracking-[0.16em] text-diff-add-text",
|
|
569
|
+
children: "Suggested"
|
|
570
|
+
})
|
|
571
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
|
|
572
|
+
ref: expandedTextareaRef,
|
|
573
|
+
value: expandedValue,
|
|
574
|
+
onChange: handleExpandedChange,
|
|
575
|
+
onKeyDown: handleExpandedKeyDown,
|
|
576
|
+
rows: 10,
|
|
577
|
+
spellCheck: false,
|
|
578
|
+
placeholder: "Write the suggested replacement…",
|
|
579
|
+
className: cn(codeBlockClassName, "resize-none overflow-y-auto placeholder:text-text-tertiary/70 focus:border-accent-primary focus:ring-1 focus:ring-accent-primary/30"),
|
|
580
|
+
"aria-label": "Suggested replacement code in expanded editor"
|
|
581
|
+
})]
|
|
582
|
+
})]
|
|
583
|
+
}),
|
|
584
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
585
|
+
className: "flex items-center justify-end gap-2 border-t border-border-subtle px-4 py-3",
|
|
586
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
587
|
+
type: "button",
|
|
588
|
+
onClick: closeExpandedModal,
|
|
589
|
+
className: "rounded-md px-3 py-1.5 text-xs text-text-secondary transition-colors hover:bg-surface-overlay hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
|
|
590
|
+
children: "Cancel"
|
|
591
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
592
|
+
type: "button",
|
|
593
|
+
onClick: handleApply,
|
|
594
|
+
className: "rounded-md bg-accent-primary px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-accent-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/50",
|
|
595
|
+
children: "Apply"
|
|
596
|
+
})]
|
|
597
|
+
})
|
|
598
|
+
]
|
|
599
|
+
})] });
|
|
600
|
+
};
|
|
601
|
+
var MIN_TEXTAREA_ROWS = 2;
|
|
602
|
+
var MAX_TEXTAREA_ROWS = 6;
|
|
603
|
+
var resizeTextarea = (textarea) => {
|
|
604
|
+
if (!textarea) return;
|
|
605
|
+
const styles = window.getComputedStyle(textarea);
|
|
606
|
+
const lineHeight = Number.parseFloat(styles.lineHeight || "0") || 16;
|
|
607
|
+
const paddingTop = Number.parseFloat(styles.paddingTop || "0");
|
|
608
|
+
const paddingBottom = Number.parseFloat(styles.paddingBottom || "0");
|
|
609
|
+
const borderTop = Number.parseFloat(styles.borderTopWidth || "0");
|
|
610
|
+
const borderBottom = Number.parseFloat(styles.borderBottomWidth || "0");
|
|
611
|
+
const minHeight = lineHeight * MIN_TEXTAREA_ROWS + paddingTop + paddingBottom + borderTop + borderBottom;
|
|
612
|
+
const maxHeight = lineHeight * MAX_TEXTAREA_ROWS + paddingTop + paddingBottom + borderTop + borderBottom;
|
|
613
|
+
textarea.style.height = "0px";
|
|
614
|
+
const nextHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
|
|
615
|
+
textarea.style.height = `${nextHeight}px`;
|
|
616
|
+
textarea.style.overflowY = textarea.scrollHeight > maxHeight ? "auto" : "hidden";
|
|
617
|
+
};
|
|
618
|
+
var InlineCommentComposer = ({ draft, reviewId, originalCode, onCancel, onSubmitted, onLocalSubmit }) => {
|
|
619
|
+
const [value, setValue] = (0, import_react.useState)("");
|
|
620
|
+
const [suggestion, setSuggestion] = (0, import_react.useState)("");
|
|
621
|
+
const [showSuggestion, setShowSuggestion] = (0, import_react.useState)(false);
|
|
622
|
+
const [submitting, setSubmitting] = (0, import_react.useState)(false);
|
|
623
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
624
|
+
const textareaRef = (0, import_react.useRef)(null);
|
|
625
|
+
const router = useRouter();
|
|
626
|
+
const errorMessageId = (0, import_react.useId)();
|
|
627
|
+
const trimmedValue = value.trim();
|
|
628
|
+
(0, import_react.useEffect)(() => {
|
|
629
|
+
textareaRef.current?.focus();
|
|
630
|
+
}, []);
|
|
631
|
+
(0, import_react.useEffect)(() => {
|
|
632
|
+
resizeTextarea(textareaRef.current);
|
|
633
|
+
}, [value]);
|
|
634
|
+
const canSubmit = trimmedValue.length > 0 && !submitting && Boolean(reviewId || onLocalSubmit);
|
|
635
|
+
const handleSubmit = (0, import_react.useCallback)(() => {
|
|
636
|
+
if (submitting || trimmedValue.length === 0) return;
|
|
637
|
+
const trimmedSuggestion = suggestion.trim();
|
|
638
|
+
const resolvedSuggestion = showSuggestion && trimmedSuggestion ? trimmedSuggestion : null;
|
|
639
|
+
if (!reviewId) {
|
|
640
|
+
onLocalSubmit?.(trimmedValue, resolvedSuggestion);
|
|
641
|
+
onSubmitted();
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
setSubmitting(true);
|
|
645
|
+
setError(null);
|
|
646
|
+
clientRuntime.runFork(gen(function* submitInlineComment() {
|
|
647
|
+
const { http } = yield* ApiClient;
|
|
648
|
+
return yield* http.comments.create({
|
|
649
|
+
path: { reviewId },
|
|
650
|
+
payload: {
|
|
651
|
+
content: trimmedValue,
|
|
652
|
+
filePath: draft.filePath,
|
|
653
|
+
lineNumber: draft.lineNumber,
|
|
654
|
+
lineType: draft.lineType,
|
|
655
|
+
suggestion: resolvedSuggestion
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
}).pipe(tap(() => sync(onSubmitted)), tap(() => promise(() => router.invalidate())), tapCause((cause) => sync(() => setError(pretty(cause)))), ensuring(sync(() => setSubmitting(false)))));
|
|
659
|
+
}, [
|
|
660
|
+
draft.filePath,
|
|
661
|
+
draft.lineNumber,
|
|
662
|
+
draft.lineType,
|
|
663
|
+
onLocalSubmit,
|
|
664
|
+
onSubmitted,
|
|
665
|
+
reviewId,
|
|
666
|
+
router,
|
|
667
|
+
showSuggestion,
|
|
668
|
+
submitting,
|
|
669
|
+
suggestion,
|
|
670
|
+
trimmedValue
|
|
671
|
+
]);
|
|
672
|
+
const handleFormSubmit = (0, import_react.useCallback)((event) => {
|
|
673
|
+
event.preventDefault();
|
|
674
|
+
handleSubmit();
|
|
675
|
+
}, [handleSubmit]);
|
|
676
|
+
const handleKeyDownCapture = (0, import_react.useCallback)((event) => {
|
|
677
|
+
if (event.key === "Escape") {
|
|
678
|
+
event.preventDefault();
|
|
679
|
+
event.stopPropagation();
|
|
680
|
+
onCancel();
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
684
|
+
event.preventDefault();
|
|
685
|
+
handleSubmit();
|
|
686
|
+
}
|
|
687
|
+
}, [handleSubmit, onCancel]);
|
|
688
|
+
const handleCommentChange = (0, import_react.useCallback)((event) => {
|
|
689
|
+
setValue(event.target.value);
|
|
690
|
+
resizeTextarea(event.target);
|
|
691
|
+
}, []);
|
|
692
|
+
const handleSuggestionToggle = (0, import_react.useCallback)(() => {
|
|
693
|
+
setShowSuggestion((current) => !current);
|
|
694
|
+
}, []);
|
|
695
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
696
|
+
onSubmit: handleFormSubmit,
|
|
697
|
+
onKeyDownCapture: handleKeyDownCapture,
|
|
698
|
+
className: "animate-in fade-in duration-100 border-t border-border-subtle bg-surface-elevated px-3 py-2.5",
|
|
699
|
+
children: [
|
|
700
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
|
|
701
|
+
ref: textareaRef,
|
|
702
|
+
value,
|
|
703
|
+
onChange: handleCommentChange,
|
|
704
|
+
rows: MIN_TEXTAREA_ROWS,
|
|
705
|
+
placeholder: "Add a comment... (⌘Enter to submit)",
|
|
706
|
+
className: "w-full resize-none rounded-sm border border-border-default bg-surface-primary p-2 font-mono text-xs text-text-primary transition-[border-color,box-shadow] duration-150 ease placeholder:text-text-tertiary/60 focus:border-accent-primary focus:outline-none focus:ring-1 focus:ring-accent-primary/30",
|
|
707
|
+
"aria-label": `Comment for ${draft.filePath} line ${draft.lineNumber}`,
|
|
708
|
+
"aria-describedby": error ? errorMessageId : void 0,
|
|
709
|
+
"aria-invalid": error ? true : void 0
|
|
710
|
+
}),
|
|
711
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
712
|
+
id: errorMessageId,
|
|
713
|
+
className: "mt-1 text-xs text-status-error",
|
|
714
|
+
role: "alert",
|
|
715
|
+
children: error
|
|
716
|
+
}) : null,
|
|
717
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
718
|
+
type: "button",
|
|
719
|
+
onClick: handleSuggestionToggle,
|
|
720
|
+
"aria-expanded": showSuggestion,
|
|
721
|
+
className: "mt-2 rounded px-1 py-0.5 text-xs text-text-secondary transition-colors hover:bg-surface-overlay hover:text-accent-primary focus-visible:bg-surface-overlay focus-visible:text-accent-primary focus-visible:outline-none",
|
|
722
|
+
children: showSuggestion ? "− Remove suggestion" : "+ Add suggested code"
|
|
723
|
+
}),
|
|
724
|
+
showSuggestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
725
|
+
className: "mt-2",
|
|
726
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CodeSuggestionEditor, {
|
|
727
|
+
value: suggestion,
|
|
728
|
+
onChange: setSuggestion,
|
|
729
|
+
originalCode,
|
|
730
|
+
filePath: draft.filePath,
|
|
731
|
+
lineNumber: draft.lineNumber
|
|
732
|
+
})
|
|
733
|
+
}) : null,
|
|
734
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
735
|
+
className: "mt-2 flex items-center justify-end gap-2",
|
|
736
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
737
|
+
type: "button",
|
|
738
|
+
onClick: onCancel,
|
|
739
|
+
className: "rounded px-2 py-1 text-xs text-text-tertiary transition-colors hover:bg-surface-overlay hover:text-text-secondary focus-visible:bg-surface-overlay focus-visible:text-text-secondary focus-visible:outline-none",
|
|
740
|
+
children: "Cancel"
|
|
741
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
742
|
+
type: "submit",
|
|
743
|
+
disabled: !canSubmit,
|
|
744
|
+
className: cn("rounded bg-accent-primary px-2.5 py-1 text-xs font-medium text-white transition-[transform,background-color] duration-150 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] hover:bg-accent-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/40 active:scale-[0.97] disabled:cursor-not-allowed disabled:opacity-40 motion-reduce:transform-none", submitting && "pointer-events-none"),
|
|
745
|
+
children: submitting ? "Posting…" : "Comment"
|
|
746
|
+
})]
|
|
747
|
+
})
|
|
748
|
+
]
|
|
749
|
+
});
|
|
750
|
+
};
|
|
751
|
+
var INITIAL_VISIBLE_COMMENTS = 3;
|
|
752
|
+
var formatCompactDate = (value) => {
|
|
753
|
+
const date = new Date(value);
|
|
754
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
755
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
756
|
+
day: "numeric",
|
|
757
|
+
month: "short"
|
|
758
|
+
}).format(date);
|
|
759
|
+
};
|
|
760
|
+
var getCommentInitials = (commentId) => commentId.slice(0, 2).toUpperCase();
|
|
761
|
+
var CommentCard = ({ comment, index, error, pending, runCommentAction }) => {
|
|
762
|
+
const resolveLabel = comment.resolved ? "Unresolve" : "Resolve";
|
|
763
|
+
const handleResolveToggle = (0, import_react.useCallback)(() => {
|
|
764
|
+
runCommentAction(comment.id, comment.resolved ? "unresolve" : "resolve");
|
|
765
|
+
}, [
|
|
766
|
+
comment.id,
|
|
767
|
+
comment.resolved,
|
|
768
|
+
runCommentAction
|
|
769
|
+
]);
|
|
770
|
+
const handleDelete = (0, import_react.useCallback)(() => {
|
|
771
|
+
runCommentAction(comment.id, "delete");
|
|
772
|
+
}, [comment.id, runCommentAction]);
|
|
773
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", {
|
|
774
|
+
className: cn("group animate-in fade-in duration-100 rounded bg-surface-elevated/50 px-2 py-1.5 transition-opacity", comment.resolved && "opacity-50"),
|
|
775
|
+
style: { animationDelay: `${index * 40}ms` },
|
|
776
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
777
|
+
className: "flex items-start gap-2",
|
|
778
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
779
|
+
className: "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-full bg-text-tertiary/10 font-mono text-[9px] font-medium text-text-tertiary",
|
|
780
|
+
children: getCommentInitials(comment.id)
|
|
781
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
782
|
+
className: "min-w-0 flex-1",
|
|
783
|
+
children: [
|
|
784
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
785
|
+
className: "flex items-center gap-1.5 text-[10px] text-text-tertiary",
|
|
786
|
+
children: [
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
788
|
+
className: "font-mono",
|
|
789
|
+
children: formatCompactDate(comment.createdAt)
|
|
790
|
+
}),
|
|
791
|
+
comment.resolved ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
792
|
+
className: "text-[10px] text-text-tertiary",
|
|
793
|
+
children: "· Resolved"
|
|
794
|
+
}) : null,
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
796
|
+
className: "ml-auto flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",
|
|
797
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
798
|
+
type: "button",
|
|
799
|
+
onClick: handleResolveToggle,
|
|
800
|
+
disabled: pending,
|
|
801
|
+
"aria-label": `${resolveLabel} comment ${comment.id}`,
|
|
802
|
+
className: "rounded px-1 py-0.5 text-[10px] text-text-secondary transition-colors hover:bg-surface-overlay hover:text-text-primary focus-visible:bg-surface-overlay focus-visible:text-text-primary focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
803
|
+
children: resolveLabel
|
|
804
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
805
|
+
type: "button",
|
|
806
|
+
onClick: handleDelete,
|
|
807
|
+
disabled: pending,
|
|
808
|
+
"aria-label": `Delete comment ${comment.id}`,
|
|
809
|
+
className: "rounded px-1 py-0.5 text-[10px] text-text-tertiary transition-colors hover:bg-status-error/10 hover:text-status-error focus-visible:bg-status-error/10 focus-visible:text-status-error focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
810
|
+
children: "Delete"
|
|
811
|
+
})]
|
|
812
|
+
})
|
|
813
|
+
]
|
|
814
|
+
}),
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
816
|
+
className: "mt-1 whitespace-pre-wrap break-words text-xs leading-5 text-text-primary",
|
|
817
|
+
children: comment.content
|
|
818
|
+
}),
|
|
819
|
+
comment.suggestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
820
|
+
className: "mt-1.5 rounded bg-surface-primary/60 px-2 py-1",
|
|
821
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
|
|
822
|
+
className: "overflow-x-auto whitespace-pre-wrap break-words font-mono text-[10px] leading-4 text-text-secondary",
|
|
823
|
+
children: comment.suggestion
|
|
824
|
+
})
|
|
825
|
+
}) : null,
|
|
826
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
827
|
+
className: "mt-1 text-[10px] text-status-error",
|
|
828
|
+
children: error
|
|
829
|
+
}) : null
|
|
830
|
+
]
|
|
831
|
+
})]
|
|
832
|
+
})
|
|
833
|
+
});
|
|
834
|
+
};
|
|
835
|
+
var InlineCommentThread = ({ comments, reviewId }) => {
|
|
836
|
+
const [expanded, setExpanded] = (0, import_react.useState)(false);
|
|
837
|
+
const [pendingCommentId, setPendingCommentId] = (0, import_react.useState)(null);
|
|
838
|
+
const [errorByCommentId, setErrorByCommentId] = (0, import_react.useState)({});
|
|
839
|
+
const router = useRouter();
|
|
840
|
+
const hiddenCount = Math.max(0, comments.length - INITIAL_VISIBLE_COMMENTS);
|
|
841
|
+
const visibleComments = expanded ? comments : comments.slice(0, INITIAL_VISIBLE_COMMENTS);
|
|
842
|
+
const runCommentAction = (0, import_react.useCallback)((commentId, action) => {
|
|
843
|
+
if (pendingCommentId) return;
|
|
844
|
+
setPendingCommentId(commentId);
|
|
845
|
+
setErrorByCommentId((current) => ({
|
|
846
|
+
...current,
|
|
847
|
+
[commentId]: null
|
|
848
|
+
}));
|
|
849
|
+
clientRuntime.runFork(gen(function* performCommentAction() {
|
|
850
|
+
const { http } = yield* ApiClient;
|
|
851
|
+
if (action === "resolve") return yield* http.comments.resolve({ path: { id: commentId } });
|
|
852
|
+
if (action === "unresolve") return yield* http.comments.unresolve({ path: { id: commentId } });
|
|
853
|
+
return yield* http.comments.remove({ path: { id: commentId } });
|
|
854
|
+
}).pipe(tap(() => promise(() => router.invalidate())), tapCause((cause) => sync(() => {
|
|
855
|
+
setErrorByCommentId((current) => ({
|
|
856
|
+
...current,
|
|
857
|
+
[commentId]: pretty(cause)
|
|
858
|
+
}));
|
|
859
|
+
})), ensuring(sync(() => setPendingCommentId(null)))));
|
|
860
|
+
}, [pendingCommentId, router]);
|
|
861
|
+
const toggleExpanded = (0, import_react.useCallback)(() => {
|
|
862
|
+
setExpanded((current) => !current);
|
|
863
|
+
}, []);
|
|
864
|
+
if (comments.length === 0) return null;
|
|
865
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
866
|
+
"data-review-id": reviewId,
|
|
867
|
+
className: "animate-in fade-in duration-100 pl-2",
|
|
868
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
869
|
+
className: "flex flex-col gap-1.5",
|
|
870
|
+
children: [visibleComments.map((comment, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommentCard, {
|
|
871
|
+
comment,
|
|
872
|
+
index,
|
|
873
|
+
error: errorByCommentId[comment.id] ?? null,
|
|
874
|
+
pending: pendingCommentId === comment.id,
|
|
875
|
+
runCommentAction
|
|
876
|
+
}, comment.id)), hiddenCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
877
|
+
type: "button",
|
|
878
|
+
onClick: toggleExpanded,
|
|
879
|
+
"aria-expanded": expanded,
|
|
880
|
+
className: "self-start rounded px-1 py-0.5 text-[10px] font-medium text-text-tertiary transition-colors hover:bg-surface-overlay hover:text-text-secondary focus-visible:bg-surface-overlay focus-visible:text-text-secondary focus-visible:outline-none",
|
|
881
|
+
children: expanded ? "Show less" : `Show ${hiddenCount} more`
|
|
882
|
+
}) : null]
|
|
883
|
+
})
|
|
884
|
+
});
|
|
885
|
+
};
|
|
886
|
+
/**
|
|
887
|
+
* Reconstruct a unified diff patch string from ringi's DiffFile schema.
|
|
888
|
+
*
|
|
889
|
+
* @pierre/diffs has a well-tested patch parser that produces correct
|
|
890
|
+
* FileDiffMetadata (with all line indices, hunk counts, etc.) from a
|
|
891
|
+
* patch string. Building FileDiffMetadata by hand is fragile because
|
|
892
|
+
* the internal renderer relies on exact index alignment between
|
|
893
|
+
* deletionLines/additionLines arrays and hunkContent groups.
|
|
894
|
+
*
|
|
895
|
+
* By reconstructing the patch text and using PatchDiff, we delegate
|
|
896
|
+
* all that bookkeeping to the library's own parser.
|
|
897
|
+
*/
|
|
898
|
+
function toPatchString(file) {
|
|
899
|
+
const oldPath = file.oldPath || file.newPath;
|
|
900
|
+
const { newPath } = file;
|
|
901
|
+
return [`--- a/${oldPath}\n+++ b/${newPath}`, ...file.hunks.map(hunkToPatch)].join("\n");
|
|
902
|
+
}
|
|
903
|
+
function hunkToPatch(hunk) {
|
|
904
|
+
return [`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`, ...hunk.lines.map(lineToPatch)].join("\n");
|
|
905
|
+
}
|
|
906
|
+
var LINE_PREFIX = {
|
|
907
|
+
added: "+",
|
|
908
|
+
context: " ",
|
|
909
|
+
removed: "-"
|
|
910
|
+
};
|
|
911
|
+
function lineToPatch(line) {
|
|
912
|
+
return `${LINE_PREFIX[line.type]}${line.content}`;
|
|
913
|
+
}
|
|
914
|
+
var EMPTY_COMMENTS$1 = [];
|
|
915
|
+
var statusBadge = {
|
|
916
|
+
added: {
|
|
917
|
+
className: "bg-status-success/15 text-status-success",
|
|
918
|
+
label: "A"
|
|
919
|
+
},
|
|
920
|
+
deleted: {
|
|
921
|
+
className: "bg-status-error/15 text-status-error",
|
|
922
|
+
label: "D"
|
|
923
|
+
},
|
|
924
|
+
modified: {
|
|
925
|
+
className: "bg-status-warning/15 text-status-warning",
|
|
926
|
+
label: "M"
|
|
927
|
+
},
|
|
928
|
+
renamed: {
|
|
929
|
+
className: "bg-status-info/15 text-status-info",
|
|
930
|
+
label: "R"
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
var buildActiveCommentKey = (lineNumber, side) => `${lineNumber}:${side}`;
|
|
934
|
+
var sideToCommentLineType = (side) => side === "additions" ? "added" : "removed";
|
|
935
|
+
var diffFileReducer = (state, action) => {
|
|
936
|
+
switch (action.type) {
|
|
937
|
+
case "toggle_expand": return {
|
|
938
|
+
...state,
|
|
939
|
+
expanded: !state.expanded
|
|
940
|
+
};
|
|
941
|
+
case "fetch_start": return {
|
|
942
|
+
...state,
|
|
943
|
+
error: null,
|
|
944
|
+
loading: true
|
|
945
|
+
};
|
|
946
|
+
case "set_hunks": return {
|
|
947
|
+
...state,
|
|
948
|
+
hunks: action.hunks,
|
|
949
|
+
loading: false
|
|
950
|
+
};
|
|
951
|
+
case "fetch_error": return {
|
|
952
|
+
...state,
|
|
953
|
+
error: action.message,
|
|
954
|
+
loading: false
|
|
955
|
+
};
|
|
956
|
+
case "fetch_done": return {
|
|
957
|
+
...state,
|
|
958
|
+
loading: false
|
|
959
|
+
};
|
|
960
|
+
case "toggle_comment_line": return {
|
|
961
|
+
...state,
|
|
962
|
+
activeComment: state.activeComment?.key === action.draft.key ? null : action.draft
|
|
963
|
+
};
|
|
964
|
+
case "close_comment": return {
|
|
965
|
+
...state,
|
|
966
|
+
activeComment: null
|
|
967
|
+
};
|
|
968
|
+
case "add_local_comment": return {
|
|
969
|
+
...state,
|
|
970
|
+
activeComment: null,
|
|
971
|
+
localComments: [...state.localComments, action.comment]
|
|
972
|
+
};
|
|
973
|
+
case "delete_local_comment": return {
|
|
974
|
+
...state,
|
|
975
|
+
localComments: state.localComments.filter((c) => c.id !== action.id)
|
|
976
|
+
};
|
|
977
|
+
default: return state;
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
var fetchHunks = (reviewId, filePath) => gen(function* loadReviewHunks() {
|
|
981
|
+
const { http } = yield* ApiClient;
|
|
982
|
+
const { hunks } = yield* http.reviewFiles.hunks({
|
|
983
|
+
path: { reviewId },
|
|
984
|
+
urlParams: { path: filePath }
|
|
985
|
+
});
|
|
986
|
+
return hunks;
|
|
987
|
+
}).pipe(timeout("30 seconds"), withSpan("fetchHunks", { attributes: {
|
|
988
|
+
filePath,
|
|
989
|
+
reviewId
|
|
990
|
+
} }));
|
|
991
|
+
var useLazyHunks = (reviewId, filePath, expanded, hasHunks, dispatch) => {
|
|
992
|
+
const fetchedRef = (0, import_react.useRef)(false);
|
|
993
|
+
(0, import_react.useEffect)(() => {
|
|
994
|
+
if (!expanded || !reviewId || hasHunks) return;
|
|
995
|
+
if (fetchedRef.current) return;
|
|
996
|
+
fetchedRef.current = true;
|
|
997
|
+
dispatch({ type: "fetch_start" });
|
|
998
|
+
const fiber = clientRuntime.runFork(fetchHunks(reviewId, filePath).pipe(matchCauseEffect({
|
|
999
|
+
onFailure: (cause) => hasInterruptsOnly(cause) ? sync(() => {
|
|
1000
|
+
fetchedRef.current = false;
|
|
1001
|
+
dispatch({ type: "fetch_done" });
|
|
1002
|
+
}) : sync(() => dispatch({
|
|
1003
|
+
message: pretty(cause),
|
|
1004
|
+
type: "fetch_error"
|
|
1005
|
+
})),
|
|
1006
|
+
onSuccess: (hunks) => sync(() => dispatch({
|
|
1007
|
+
hunks,
|
|
1008
|
+
type: "set_hunks"
|
|
1009
|
+
}))
|
|
1010
|
+
})));
|
|
1011
|
+
return () => {
|
|
1012
|
+
clientRuntime.runFork(interrupt(fiber));
|
|
1013
|
+
};
|
|
1014
|
+
}, [
|
|
1015
|
+
dispatch,
|
|
1016
|
+
expanded,
|
|
1017
|
+
filePath,
|
|
1018
|
+
hasHunks,
|
|
1019
|
+
reviewId
|
|
1020
|
+
]);
|
|
1021
|
+
};
|
|
1022
|
+
/**
|
|
1023
|
+
* The "+" button shown in the gutter when hovering a commentable diff line.
|
|
1024
|
+
*
|
|
1025
|
+
* Rendering context (from @pierre/diffs):
|
|
1026
|
+
* The library's `renderGutterUtility` slot mechanism works as follows:
|
|
1027
|
+
* Shadow DOM: [data-column-number] (position: relative)
|
|
1028
|
+
* └─ [data-gutter-utility-slot] (position: absolute; right:0; top:0; bottom:0;
|
|
1029
|
+
* │ display: flex; justify-content: flex-end)
|
|
1030
|
+
* │ └─ <slot name="gutter-utility-slot">
|
|
1031
|
+
* Light DOM: <div slot="gutter-utility-slot" ← projected into shadow slot
|
|
1032
|
+
* style="position: absolute; top:0; bottom:0; text-align: center">
|
|
1033
|
+
* └─ THIS BUTTON
|
|
1034
|
+
*
|
|
1035
|
+
* The slotted wrapper div has `position: absolute` (set by the library's
|
|
1036
|
+
* GutterUtilitySlotStyles), which takes it out of the flex flow of the
|
|
1037
|
+
* shadow-DOM [data-gutter-utility-slot] container. This collapses the
|
|
1038
|
+
* container to zero width. Without corrective styles the button ends up
|
|
1039
|
+
* overflowing into the code-content column — effectively invisible.
|
|
1040
|
+
*
|
|
1041
|
+
* We fix this by adding a CSS override (in styles.css) that changes the
|
|
1042
|
+
* slotted wrapper to `position: static !important` so it participates in
|
|
1043
|
+
* the parent's flex layout. Then the button sits inside the gutter column,
|
|
1044
|
+
* aligned to the flex-end, matching the library's own default utility
|
|
1045
|
+
* button placement.
|
|
1046
|
+
*
|
|
1047
|
+
* The button itself mirrors the library's `[data-utility-button]` styles:
|
|
1048
|
+
* • `position: relative; z-index: 4` to paint above line-number content.
|
|
1049
|
+
*/
|
|
1050
|
+
var GutterAddButton = ({ getHoveredLine, onAddComment }) => {
|
|
1051
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1052
|
+
type: "button",
|
|
1053
|
+
onClick: (0, import_react.useCallback)(() => {
|
|
1054
|
+
const hoveredLine = getHoveredLine();
|
|
1055
|
+
if (!hoveredLine) return;
|
|
1056
|
+
onAddComment(hoveredLine.lineNumber, hoveredLine.side);
|
|
1057
|
+
}, [getHoveredLine, onAddComment]),
|
|
1058
|
+
className: "ringi-gutter-add-btn relative z-[4] flex items-center justify-center rounded-sm bg-accent-primary text-[11px] font-medium text-white shadow-sm shadow-accent-primary/30 transition-[transform,background-color] duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] hover:bg-accent-primary-hover focus-visible:bg-accent-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary/40 active:scale-[0.92]",
|
|
1059
|
+
"aria-label": "Add comment on this line",
|
|
1060
|
+
tabIndex: 0,
|
|
1061
|
+
children: "+"
|
|
1062
|
+
});
|
|
1063
|
+
};
|
|
1064
|
+
var formatCompactTimestamp = (iso) => {
|
|
1065
|
+
const date = new Date(iso);
|
|
1066
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
1067
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
1068
|
+
hour: "numeric",
|
|
1069
|
+
minute: "2-digit"
|
|
1070
|
+
}).format(date);
|
|
1071
|
+
};
|
|
1072
|
+
var LocalCommentCard = ({ comment, index, onDelete }) => {
|
|
1073
|
+
const handleDelete = (0, import_react.useCallback)(() => {
|
|
1074
|
+
onDelete(comment.id);
|
|
1075
|
+
}, [comment.id, onDelete]);
|
|
1076
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("article", {
|
|
1077
|
+
className: "ringi-comment-card group rounded bg-surface-elevated/50 px-2 py-1.5",
|
|
1078
|
+
style: { animationDelay: `${index * 40}ms` },
|
|
1079
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1080
|
+
className: "flex items-start gap-2",
|
|
1081
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1082
|
+
className: "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-full bg-text-tertiary/10 text-[10px] text-text-tertiary",
|
|
1083
|
+
children: "✎"
|
|
1084
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1085
|
+
className: "min-w-0 flex-1",
|
|
1086
|
+
children: [
|
|
1087
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1088
|
+
className: "flex items-center gap-1.5 text-[10px] text-text-tertiary",
|
|
1089
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1090
|
+
className: "font-mono",
|
|
1091
|
+
children: formatCompactTimestamp(comment.createdAt)
|
|
1092
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1093
|
+
type: "button",
|
|
1094
|
+
onClick: handleDelete,
|
|
1095
|
+
"aria-label": "Delete comment",
|
|
1096
|
+
className: "ml-auto rounded px-1 py-0.5 text-[10px] text-text-tertiary opacity-0 transition-opacity hover:bg-status-error/10 hover:text-status-error focus-visible:bg-status-error/10 focus-visible:text-status-error focus-visible:opacity-100 focus-visible:outline-none group-hover:opacity-100",
|
|
1097
|
+
children: "Delete"
|
|
1098
|
+
})]
|
|
1099
|
+
}),
|
|
1100
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1101
|
+
className: "mt-1 whitespace-pre-wrap break-words text-xs leading-5 text-text-primary",
|
|
1102
|
+
children: comment.content
|
|
1103
|
+
}),
|
|
1104
|
+
comment.suggestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1105
|
+
className: "mt-1.5 rounded bg-surface-primary/60 px-2 py-1",
|
|
1106
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", {
|
|
1107
|
+
className: "overflow-x-auto whitespace-pre-wrap break-words font-mono text-[10px] leading-4 text-text-secondary",
|
|
1108
|
+
children: comment.suggestion
|
|
1109
|
+
})
|
|
1110
|
+
}) : null
|
|
1111
|
+
]
|
|
1112
|
+
})]
|
|
1113
|
+
})
|
|
1114
|
+
});
|
|
1115
|
+
};
|
|
1116
|
+
var DiffFile = ({ file, defaultExpanded = false, reviewId, diffMode = "unified", comments = EMPTY_COMMENTS$1, onLocalCommentsChange, viewed = false, onToggleViewed, pendingDeleteId, onPendingDeleteHandled }) => {
|
|
1117
|
+
const [state, dispatch] = (0, import_react.useReducer)(diffFileReducer, {
|
|
1118
|
+
activeComment: null,
|
|
1119
|
+
error: null,
|
|
1120
|
+
expanded: defaultExpanded,
|
|
1121
|
+
hunks: file.hunks,
|
|
1122
|
+
loading: false,
|
|
1123
|
+
localComments: []
|
|
1124
|
+
});
|
|
1125
|
+
const { expanded, hunks, loading, error, activeComment, localComments } = state;
|
|
1126
|
+
const badge = statusBadge[file.status];
|
|
1127
|
+
useLazyHunks(reviewId, file.newPath, expanded, hunks.length > 0, dispatch);
|
|
1128
|
+
(0, import_react.useEffect)(() => {
|
|
1129
|
+
onLocalCommentsChange?.(file.newPath, localComments);
|
|
1130
|
+
}, [
|
|
1131
|
+
file.newPath,
|
|
1132
|
+
localComments,
|
|
1133
|
+
onLocalCommentsChange
|
|
1134
|
+
]);
|
|
1135
|
+
(0, import_react.useEffect)(() => {
|
|
1136
|
+
if (!pendingDeleteId) return;
|
|
1137
|
+
dispatch({
|
|
1138
|
+
id: pendingDeleteId,
|
|
1139
|
+
type: "delete_local_comment"
|
|
1140
|
+
});
|
|
1141
|
+
onPendingDeleteHandled?.();
|
|
1142
|
+
}, [pendingDeleteId, onPendingDeleteHandled]);
|
|
1143
|
+
const handleAddComment = (0, import_react.useCallback)((lineNumber, side) => {
|
|
1144
|
+
dispatch({
|
|
1145
|
+
draft: {
|
|
1146
|
+
filePath: file.newPath,
|
|
1147
|
+
key: buildActiveCommentKey(lineNumber, side),
|
|
1148
|
+
lineNumber,
|
|
1149
|
+
lineType: sideToCommentLineType(side),
|
|
1150
|
+
side
|
|
1151
|
+
},
|
|
1152
|
+
type: "toggle_comment_line"
|
|
1153
|
+
});
|
|
1154
|
+
}, [file.newPath]);
|
|
1155
|
+
const handleCloseComment = (0, import_react.useCallback)(() => {
|
|
1156
|
+
dispatch({ type: "close_comment" });
|
|
1157
|
+
}, []);
|
|
1158
|
+
const handleLocalSubmit = (0, import_react.useCallback)((content, suggestion) => {
|
|
1159
|
+
if (!activeComment) return;
|
|
1160
|
+
dispatch({
|
|
1161
|
+
comment: {
|
|
1162
|
+
content,
|
|
1163
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1164
|
+
filePath: activeComment.filePath,
|
|
1165
|
+
id: `local-${crypto.randomUUID()}`,
|
|
1166
|
+
lineNumber: activeComment.lineNumber,
|
|
1167
|
+
lineType: activeComment.lineType,
|
|
1168
|
+
side: activeComment.side,
|
|
1169
|
+
suggestion
|
|
1170
|
+
},
|
|
1171
|
+
type: "add_local_comment"
|
|
1172
|
+
});
|
|
1173
|
+
}, [activeComment]);
|
|
1174
|
+
const handleDeleteLocalComment = (0, import_react.useCallback)((id) => {
|
|
1175
|
+
dispatch({
|
|
1176
|
+
id,
|
|
1177
|
+
type: "delete_local_comment"
|
|
1178
|
+
});
|
|
1179
|
+
}, []);
|
|
1180
|
+
const handleToggleExpand = (0, import_react.useCallback)(() => {
|
|
1181
|
+
dispatch({ type: "toggle_expand" });
|
|
1182
|
+
}, []);
|
|
1183
|
+
const handleToggleViewed = (0, import_react.useCallback)(() => {
|
|
1184
|
+
onToggleViewed?.(file.newPath);
|
|
1185
|
+
}, [file.newPath, onToggleViewed]);
|
|
1186
|
+
const getOriginalCodeForLine = (0, import_react.useCallback)((lineNumber, side) => {
|
|
1187
|
+
for (const hunk of hunks) for (const line of hunk.lines) if ((side === "additions" ? line.newLineNumber : line.oldLineNumber) === lineNumber) return line.content;
|
|
1188
|
+
return "";
|
|
1189
|
+
}, [hunks]);
|
|
1190
|
+
const getOrCreateAnnotation = (map, key) => {
|
|
1191
|
+
const existing = map.get(key);
|
|
1192
|
+
if (existing) return existing;
|
|
1193
|
+
const fresh = {
|
|
1194
|
+
comments: [],
|
|
1195
|
+
draft: null,
|
|
1196
|
+
localComments: []
|
|
1197
|
+
};
|
|
1198
|
+
map.set(key, fresh);
|
|
1199
|
+
return fresh;
|
|
1200
|
+
};
|
|
1201
|
+
const lineAnnotations = (0, import_react.useMemo)(() => {
|
|
1202
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
1203
|
+
for (const comment of comments) {
|
|
1204
|
+
if (comment.lineNumber === null || comment.lineNumber === void 0) continue;
|
|
1205
|
+
const side = comment.lineType === "removed" ? "deletions" : "additions";
|
|
1206
|
+
const entry = getOrCreateAnnotation(annotationMap, buildActiveCommentKey(comment.lineNumber, side));
|
|
1207
|
+
entry.comments = [...entry.comments, comment];
|
|
1208
|
+
}
|
|
1209
|
+
for (const local of localComments) {
|
|
1210
|
+
const entry = getOrCreateAnnotation(annotationMap, buildActiveCommentKey(local.lineNumber, local.side));
|
|
1211
|
+
entry.localComments = [...entry.localComments, local];
|
|
1212
|
+
}
|
|
1213
|
+
if (activeComment) {
|
|
1214
|
+
const entry = getOrCreateAnnotation(annotationMap, activeComment.key);
|
|
1215
|
+
entry.draft = activeComment;
|
|
1216
|
+
}
|
|
1217
|
+
return [...annotationMap.entries()].map(([key, metadata]) => {
|
|
1218
|
+
const [lineNumber, side] = key.split(":");
|
|
1219
|
+
return {
|
|
1220
|
+
lineNumber: Number(lineNumber),
|
|
1221
|
+
metadata,
|
|
1222
|
+
side
|
|
1223
|
+
};
|
|
1224
|
+
});
|
|
1225
|
+
}, [
|
|
1226
|
+
activeComment,
|
|
1227
|
+
comments,
|
|
1228
|
+
localComments
|
|
1229
|
+
]);
|
|
1230
|
+
const patchString = (0, import_react.useMemo)(() => {
|
|
1231
|
+
if (hunks.length === 0) return "";
|
|
1232
|
+
return toPatchString({
|
|
1233
|
+
...file,
|
|
1234
|
+
hunks
|
|
1235
|
+
});
|
|
1236
|
+
}, [file, hunks]);
|
|
1237
|
+
const diffOptions = {
|
|
1238
|
+
...pierreDiffOptions,
|
|
1239
|
+
diffStyle: diffMode,
|
|
1240
|
+
disableFileHeader: true,
|
|
1241
|
+
enableGutterUtility: true,
|
|
1242
|
+
lineHoverHighlight: "both"
|
|
1243
|
+
};
|
|
1244
|
+
const renderAnnotation = (0, import_react.useCallback)((annotation) => {
|
|
1245
|
+
if (!annotation.metadata) return null;
|
|
1246
|
+
const { draft, comments: lineComments, localComments: lineLocalComments } = annotation.metadata;
|
|
1247
|
+
if (!(draft || lineComments.length > 0 || lineLocalComments.length > 0)) return null;
|
|
1248
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1249
|
+
lineComments.length > 0 && reviewId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineCommentThread, {
|
|
1250
|
+
comments: lineComments,
|
|
1251
|
+
reviewId
|
|
1252
|
+
}) : null,
|
|
1253
|
+
lineLocalComments.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1254
|
+
className: "pl-2",
|
|
1255
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1256
|
+
className: "flex flex-col gap-1.5",
|
|
1257
|
+
children: lineLocalComments.map((lc, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LocalCommentCard, {
|
|
1258
|
+
comment: lc,
|
|
1259
|
+
index: i,
|
|
1260
|
+
onDelete: handleDeleteLocalComment
|
|
1261
|
+
}, lc.id))
|
|
1262
|
+
})
|
|
1263
|
+
}) : null,
|
|
1264
|
+
draft ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InlineCommentComposer, {
|
|
1265
|
+
draft,
|
|
1266
|
+
reviewId,
|
|
1267
|
+
originalCode: getOriginalCodeForLine(draft.lineNumber, draft.side),
|
|
1268
|
+
onCancel: handleCloseComment,
|
|
1269
|
+
onSubmitted: handleCloseComment,
|
|
1270
|
+
onLocalSubmit: reviewId ? void 0 : handleLocalSubmit
|
|
1271
|
+
}) : null
|
|
1272
|
+
] });
|
|
1273
|
+
}, [
|
|
1274
|
+
getOriginalCodeForLine,
|
|
1275
|
+
handleCloseComment,
|
|
1276
|
+
handleDeleteLocalComment,
|
|
1277
|
+
handleLocalSubmit,
|
|
1278
|
+
reviewId
|
|
1279
|
+
]);
|
|
1280
|
+
const renderGutterUtility = (0, import_react.useCallback)((getHoveredLine) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GutterAddButton, {
|
|
1281
|
+
getHoveredLine,
|
|
1282
|
+
onAddComment: handleAddComment
|
|
1283
|
+
}), [handleAddComment]);
|
|
1284
|
+
let diffContent;
|
|
1285
|
+
if (loading) diffContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1286
|
+
className: "px-4 py-3 text-xs italic text-text-tertiary",
|
|
1287
|
+
children: "Loading diff…"
|
|
1288
|
+
});
|
|
1289
|
+
else if (error) diffContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1290
|
+
className: "px-4 py-3 text-xs italic text-status-error",
|
|
1291
|
+
children: error
|
|
1292
|
+
});
|
|
1293
|
+
else if (hunks.length === 0) diffContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1294
|
+
className: "px-4 py-3 text-xs italic text-text-tertiary",
|
|
1295
|
+
children: "Binary file or mode change"
|
|
1296
|
+
});
|
|
1297
|
+
else diffContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SplitDiffSplitter, {
|
|
1298
|
+
enabled: diffMode === "split",
|
|
1299
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PatchDiff, {
|
|
1300
|
+
patch: patchString,
|
|
1301
|
+
options: diffOptions,
|
|
1302
|
+
lineAnnotations,
|
|
1303
|
+
renderAnnotation,
|
|
1304
|
+
renderGutterUtility,
|
|
1305
|
+
className: "ringi-diff-file"
|
|
1306
|
+
})
|
|
1307
|
+
});
|
|
1308
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1309
|
+
id: `diff-file-${file.newPath.replaceAll("/", "-")}`,
|
|
1310
|
+
className: "overflow-hidden rounded-sm border border-border-subtle bg-surface-elevated",
|
|
1311
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1312
|
+
className: "flex items-center gap-1.5 px-2.5 py-1.5",
|
|
1313
|
+
children: [
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1315
|
+
type: "button",
|
|
1316
|
+
onClick: handleToggleExpand,
|
|
1317
|
+
className: "inline-flex size-4 shrink-0 items-center justify-center rounded text-text-tertiary/60 transition-[color,background-color] duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] hover:text-text-secondary focus-visible:text-text-secondary focus-visible:outline-none",
|
|
1318
|
+
"aria-expanded": expanded,
|
|
1319
|
+
"aria-label": expanded ? "Collapse file" : "Expand file",
|
|
1320
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, {
|
|
1321
|
+
size: 12,
|
|
1322
|
+
className: cn("transition-transform duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none", expanded && "rotate-90")
|
|
1323
|
+
})
|
|
1324
|
+
}),
|
|
1325
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1326
|
+
className: cn("w-3 shrink-0 text-center font-mono text-[10px] font-semibold leading-none", badge.className.includes("success") ? "text-diff-add-text" : badge.className.includes("error") ? "text-diff-remove-text" : badge.className.includes("warning") ? "text-accent-primary" : "text-status-info"),
|
|
1327
|
+
children: badge.label
|
|
1328
|
+
}),
|
|
1329
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1330
|
+
className: "min-w-0 flex-1 truncate font-mono text-[11px] text-text-primary",
|
|
1331
|
+
children: file.newPath
|
|
1332
|
+
}),
|
|
1333
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1334
|
+
className: "flex shrink-0 items-center gap-1.5 text-[10px] tabular-nums text-text-tertiary/60",
|
|
1335
|
+
children: [file.additions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1336
|
+
className: "text-diff-add-text/60",
|
|
1337
|
+
children: ["+", file.additions]
|
|
1338
|
+
}) : null, file.deletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1339
|
+
className: "text-diff-remove-text/60",
|
|
1340
|
+
children: ["-", file.deletions]
|
|
1341
|
+
}) : null]
|
|
1342
|
+
}),
|
|
1343
|
+
onToggleViewed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1344
|
+
type: "button",
|
|
1345
|
+
onClick: handleToggleViewed,
|
|
1346
|
+
title: viewed ? "Unmark as viewed" : "Mark as viewed",
|
|
1347
|
+
className: cn("ml-0.5 flex size-5 shrink-0 items-center justify-center rounded transition-[transform,background-color,color] duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] active:scale-[0.93] motion-reduce:transform-none", viewed ? "bg-status-success/12 text-status-success" : "text-text-tertiary/40 hover:bg-surface-overlay hover:text-text-secondary"),
|
|
1348
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
1349
|
+
width: "11",
|
|
1350
|
+
height: "11",
|
|
1351
|
+
viewBox: "0 0 12 12",
|
|
1352
|
+
fill: "none",
|
|
1353
|
+
className: "shrink-0",
|
|
1354
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
1355
|
+
d: "M2.5 6.5L5 9l4.5-6",
|
|
1356
|
+
stroke: "currentColor",
|
|
1357
|
+
strokeWidth: viewed ? "1.75" : "1.25",
|
|
1358
|
+
strokeLinecap: "round",
|
|
1359
|
+
strokeLinejoin: "round"
|
|
1360
|
+
})
|
|
1361
|
+
})
|
|
1362
|
+
}) : null
|
|
1363
|
+
]
|
|
1364
|
+
}), expanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1365
|
+
className: "border-t border-border-subtle",
|
|
1366
|
+
children: diffContent
|
|
1367
|
+
}) : null]
|
|
1368
|
+
});
|
|
1369
|
+
};
|
|
1370
|
+
var EMPTY_COMMENTS = [];
|
|
1371
|
+
/**
|
|
1372
|
+
* Single-file diff renderer.
|
|
1373
|
+
*
|
|
1374
|
+
* Renders **only** the currently selected file. The previous implementation
|
|
1375
|
+
* mounted every DiffFile in the review, which meant N × PatchDiff instances
|
|
1376
|
+
* on load. This version renders exactly one.
|
|
1377
|
+
*/
|
|
1378
|
+
var DiffView = ({ file, reviewId, diffMode = "split", comments = EMPTY_COMMENTS, onLocalCommentsChange, viewed = false, onToggleViewed, pendingDeleteId, onPendingDeleteHandled }) => {
|
|
1379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiffFile, {
|
|
1380
|
+
file,
|
|
1381
|
+
comments: (0, import_react.useMemo)(() => comments.filter((c) => c.filePath === file.newPath), [comments, file.newPath]),
|
|
1382
|
+
defaultExpanded: true,
|
|
1383
|
+
diffMode,
|
|
1384
|
+
reviewId,
|
|
1385
|
+
onLocalCommentsChange,
|
|
1386
|
+
viewed,
|
|
1387
|
+
onToggleViewed,
|
|
1388
|
+
pendingDeleteId,
|
|
1389
|
+
onPendingDeleteHandled
|
|
1390
|
+
}, file.newPath);
|
|
1391
|
+
};
|
|
1392
|
+
var EMPTY_IDS = [];
|
|
1393
|
+
var TreeContext = (0, import_react.createContext)(void 0);
|
|
1394
|
+
var useTree = () => {
|
|
1395
|
+
const ctx = (0, import_react.useContext)(TreeContext);
|
|
1396
|
+
if (!ctx) throw new Error("Tree components must be used within <Tree>");
|
|
1397
|
+
return ctx;
|
|
1398
|
+
};
|
|
1399
|
+
var NodeContext = (0, import_react.createContext)(void 0);
|
|
1400
|
+
var useNode = () => {
|
|
1401
|
+
const ctx = (0, import_react.useContext)(NodeContext);
|
|
1402
|
+
if (!ctx) throw new Error("TreeNode components must be used within <TreeNode>");
|
|
1403
|
+
return ctx;
|
|
1404
|
+
};
|
|
1405
|
+
var Tree = ({ children, defaultExpandedIds = EMPTY_IDS, expandedIds: controlledExpanded, onExpandedChange, selectedIds: controlledSelected, onSelectionChange, showIcons = true, indent = 14, className }) => {
|
|
1406
|
+
const [internalExpanded, setInternalExpanded] = (0, import_react.useState)(() => new Set(defaultExpandedIds));
|
|
1407
|
+
const isExpandedControlled = controlledExpanded !== void 0 && onExpandedChange !== void 0;
|
|
1408
|
+
const expandedIds = isExpandedControlled ? controlledExpanded : internalExpanded;
|
|
1409
|
+
const toggleExpanded = (0, import_react.useCallback)((id) => {
|
|
1410
|
+
const next = new Set(expandedIds);
|
|
1411
|
+
if (next.has(id)) next.delete(id);
|
|
1412
|
+
else next.add(id);
|
|
1413
|
+
if (isExpandedControlled && onExpandedChange) onExpandedChange(next);
|
|
1414
|
+
else setInternalExpanded(next);
|
|
1415
|
+
}, [
|
|
1416
|
+
expandedIds,
|
|
1417
|
+
isExpandedControlled,
|
|
1418
|
+
onExpandedChange
|
|
1419
|
+
]);
|
|
1420
|
+
const [internalSelected, setInternalSelected] = (0, import_react.useState)([]);
|
|
1421
|
+
const isSelControlled = controlledSelected !== void 0 && onSelectionChange !== void 0;
|
|
1422
|
+
const selectedIds = isSelControlled ? controlledSelected : internalSelected;
|
|
1423
|
+
const handleSelection = (0, import_react.useCallback)((id, _ctrlKey) => {
|
|
1424
|
+
const next = selectedIds.includes(id) ? [] : [id];
|
|
1425
|
+
if (isSelControlled && onSelectionChange) onSelectionChange(next);
|
|
1426
|
+
else setInternalSelected(next);
|
|
1427
|
+
}, [
|
|
1428
|
+
selectedIds,
|
|
1429
|
+
isSelControlled,
|
|
1430
|
+
onSelectionChange
|
|
1431
|
+
]);
|
|
1432
|
+
const ctx = (0, import_react.useMemo)(() => ({
|
|
1433
|
+
expandedIds,
|
|
1434
|
+
handleSelection,
|
|
1435
|
+
indent,
|
|
1436
|
+
selectedIds,
|
|
1437
|
+
showIcons,
|
|
1438
|
+
toggleExpanded
|
|
1439
|
+
}), [
|
|
1440
|
+
expandedIds,
|
|
1441
|
+
selectedIds,
|
|
1442
|
+
toggleExpanded,
|
|
1443
|
+
handleSelection,
|
|
1444
|
+
showIcons,
|
|
1445
|
+
indent
|
|
1446
|
+
]);
|
|
1447
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeContext.Provider, {
|
|
1448
|
+
value: ctx,
|
|
1449
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1450
|
+
className: cn("w-full", className),
|
|
1451
|
+
role: "tree",
|
|
1452
|
+
children
|
|
1453
|
+
})
|
|
1454
|
+
});
|
|
1455
|
+
};
|
|
1456
|
+
var TreeNode = ({ nodeId: providedId, level = 0, children, className, ...props }) => {
|
|
1457
|
+
const generatedId = (0, import_react.useId)();
|
|
1458
|
+
const nodeId = providedId ?? generatedId;
|
|
1459
|
+
const ctx = (0, import_react.useMemo)(() => ({
|
|
1460
|
+
level,
|
|
1461
|
+
nodeId
|
|
1462
|
+
}), [nodeId, level]);
|
|
1463
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NodeContext.Provider, {
|
|
1464
|
+
value: ctx,
|
|
1465
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1466
|
+
className: cn("select-none", className),
|
|
1467
|
+
role: "treeitem",
|
|
1468
|
+
...props,
|
|
1469
|
+
children
|
|
1470
|
+
})
|
|
1471
|
+
});
|
|
1472
|
+
};
|
|
1473
|
+
var TreeNodeTrigger = ({ children, className, onClick, ...props }) => {
|
|
1474
|
+
const { selectedIds, toggleExpanded, handleSelection, indent } = useTree();
|
|
1475
|
+
const { nodeId, level } = useNode();
|
|
1476
|
+
const isSelected = selectedIds.includes(nodeId);
|
|
1477
|
+
const handleClick = (0, import_react.useCallback)((e) => {
|
|
1478
|
+
toggleExpanded(nodeId);
|
|
1479
|
+
handleSelection(nodeId, e.ctrlKey || e.metaKey);
|
|
1480
|
+
onClick?.(e);
|
|
1481
|
+
}, [
|
|
1482
|
+
toggleExpanded,
|
|
1483
|
+
handleSelection,
|
|
1484
|
+
nodeId,
|
|
1485
|
+
onClick
|
|
1486
|
+
]);
|
|
1487
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1488
|
+
type: "button",
|
|
1489
|
+
"aria-current": isSelected ? "true" : void 0,
|
|
1490
|
+
"data-selected": isSelected ? "" : void 0,
|
|
1491
|
+
className: cn("group relative flex w-full cursor-pointer items-center text-left outline-none", "transition-colors duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", "focus-visible:ring-1 focus-visible:ring-accent-primary/40", className),
|
|
1492
|
+
onClick: handleClick,
|
|
1493
|
+
style: { paddingLeft: level * indent + 8 },
|
|
1494
|
+
...props,
|
|
1495
|
+
children
|
|
1496
|
+
});
|
|
1497
|
+
};
|
|
1498
|
+
var TreeNodeContent = ({ children, className, forceMount, ...props }) => {
|
|
1499
|
+
const { expandedIds } = useTree();
|
|
1500
|
+
const { nodeId } = useNode();
|
|
1501
|
+
const isExpanded = expandedIds.has(nodeId);
|
|
1502
|
+
if (!isExpanded && !forceMount) return null;
|
|
1503
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1504
|
+
className: cn(!isExpanded && "hidden", className),
|
|
1505
|
+
role: "group",
|
|
1506
|
+
...props,
|
|
1507
|
+
children
|
|
1508
|
+
});
|
|
1509
|
+
};
|
|
1510
|
+
var TreeExpander = ({ hasChildren = false, className, ...props }) => {
|
|
1511
|
+
const { expandedIds } = useTree();
|
|
1512
|
+
const { nodeId } = useNode();
|
|
1513
|
+
const isExpanded = expandedIds.has(nodeId);
|
|
1514
|
+
if (!hasChildren) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1515
|
+
className: "inline-flex h-4 w-4 shrink-0",
|
|
1516
|
+
"aria-hidden": true
|
|
1517
|
+
});
|
|
1518
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1519
|
+
className: cn("inline-flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground", "transition-transform duration-100 [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]", isExpanded && "rotate-90", className),
|
|
1520
|
+
"aria-hidden": true,
|
|
1521
|
+
...props,
|
|
1522
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "h-3 w-3" })
|
|
1523
|
+
});
|
|
1524
|
+
};
|
|
1525
|
+
var TreeIcon = ({ icon, hasChildren = false, className, ...props }) => {
|
|
1526
|
+
const { showIcons, expandedIds } = useTree();
|
|
1527
|
+
const { nodeId } = useNode();
|
|
1528
|
+
const isExpanded = expandedIds.has(nodeId);
|
|
1529
|
+
if (!showIcons) return null;
|
|
1530
|
+
const getDefaultIcon = () => {
|
|
1531
|
+
if (!hasChildren) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(File, { className: "h-3.5 w-3.5" });
|
|
1532
|
+
if (isExpanded) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderOpen, { className: "h-3.5 w-3.5" });
|
|
1533
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Folder, { className: "h-3.5 w-3.5" });
|
|
1534
|
+
};
|
|
1535
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1536
|
+
className: cn("mr-1.5 inline-flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground/70", className),
|
|
1537
|
+
"aria-hidden": true,
|
|
1538
|
+
...props,
|
|
1539
|
+
children: icon ?? getDefaultIcon()
|
|
1540
|
+
});
|
|
1541
|
+
};
|
|
1542
|
+
var EASE_OUT = "[transition-timing-function:cubic-bezier(0.23,1,0.32,1)]";
|
|
1543
|
+
var collapseTree = (node) => {
|
|
1544
|
+
for (const child of node.children) collapseTree(child);
|
|
1545
|
+
while (node.children.length === 1 && !node.children[0].file && node.children[0].children.length > 0) {
|
|
1546
|
+
const [onlyChild] = node.children;
|
|
1547
|
+
node.name = node.name ? `${node.name}/${onlyChild.name}` : onlyChild.name;
|
|
1548
|
+
node.path = onlyChild.path;
|
|
1549
|
+
node.children = onlyChild.children;
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
var sortTree = (node) => {
|
|
1553
|
+
node.children.sort((a, b) => {
|
|
1554
|
+
const aIsDir = !a.file;
|
|
1555
|
+
if (aIsDir !== !b.file) return aIsDir ? -1 : 1;
|
|
1556
|
+
return a.name.localeCompare(b.name);
|
|
1557
|
+
});
|
|
1558
|
+
for (const child of node.children) sortTree(child);
|
|
1559
|
+
};
|
|
1560
|
+
var collectDirPaths = (node) => {
|
|
1561
|
+
const paths = [];
|
|
1562
|
+
for (const child of node.children) if (!child.file) {
|
|
1563
|
+
paths.push(child.path);
|
|
1564
|
+
paths.push(...collectDirPaths(child));
|
|
1565
|
+
}
|
|
1566
|
+
return paths;
|
|
1567
|
+
};
|
|
1568
|
+
var flatFileList = (node) => {
|
|
1569
|
+
const result = [];
|
|
1570
|
+
for (const child of node.children) if (child.file) result.push(child.file.newPath);
|
|
1571
|
+
else result.push(...flatFileList(child));
|
|
1572
|
+
return result;
|
|
1573
|
+
};
|
|
1574
|
+
var buildTree = (files) => {
|
|
1575
|
+
const root = {
|
|
1576
|
+
children: [],
|
|
1577
|
+
name: "",
|
|
1578
|
+
path: ""
|
|
1579
|
+
};
|
|
1580
|
+
for (const file of files) {
|
|
1581
|
+
const segments = file.newPath.split("/");
|
|
1582
|
+
let current = root;
|
|
1583
|
+
for (const [index, segment] of segments.entries()) {
|
|
1584
|
+
const isFile = index === segments.length - 1;
|
|
1585
|
+
const childPath = segments.slice(0, index + 1).join("/");
|
|
1586
|
+
let child = current.children.find((c) => c.name === segment);
|
|
1587
|
+
if (!child) {
|
|
1588
|
+
child = {
|
|
1589
|
+
children: [],
|
|
1590
|
+
name: segment,
|
|
1591
|
+
path: childPath
|
|
1592
|
+
};
|
|
1593
|
+
if (isFile) child.file = file;
|
|
1594
|
+
current.children.push(child);
|
|
1595
|
+
}
|
|
1596
|
+
current = child;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
collapseTree(root);
|
|
1600
|
+
sortTree(root);
|
|
1601
|
+
return root;
|
|
1602
|
+
};
|
|
1603
|
+
var statusColor = {
|
|
1604
|
+
added: "text-diff-add-text",
|
|
1605
|
+
deleted: "text-diff-remove-text",
|
|
1606
|
+
modified: "text-accent-primary",
|
|
1607
|
+
renamed: "text-status-info"
|
|
1608
|
+
};
|
|
1609
|
+
var statusLetter = {
|
|
1610
|
+
added: "A",
|
|
1611
|
+
deleted: "D",
|
|
1612
|
+
modified: "M",
|
|
1613
|
+
renamed: "R"
|
|
1614
|
+
};
|
|
1615
|
+
var ViewedCheckbox = ({ checked, onToggle, filePath }) => {
|
|
1616
|
+
const handleClick = (0, import_react.useCallback)((event) => {
|
|
1617
|
+
event.stopPropagation();
|
|
1618
|
+
onToggle?.(filePath);
|
|
1619
|
+
}, [filePath, onToggle]);
|
|
1620
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
|
|
1621
|
+
className: cn("relative flex size-3 shrink-0 cursor-pointer items-center justify-center rounded-[3px] border transition-[border-color,background-color,color] duration-100", EASE_OUT, checked ? "border-status-success/50 bg-status-success/15 text-status-success" : "border-border-subtle text-transparent hover:border-text-tertiary/40"),
|
|
1622
|
+
onClick: handleClick,
|
|
1623
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
1624
|
+
type: "checkbox",
|
|
1625
|
+
checked,
|
|
1626
|
+
readOnly: true,
|
|
1627
|
+
className: "sr-only",
|
|
1628
|
+
"aria-label": checked ? "Unmark as viewed" : "Mark as viewed",
|
|
1629
|
+
tabIndex: 0
|
|
1630
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
1631
|
+
width: "7",
|
|
1632
|
+
height: "7",
|
|
1633
|
+
viewBox: "0 0 7 7",
|
|
1634
|
+
fill: "none",
|
|
1635
|
+
className: cn("pointer-events-none transition-opacity duration-100", checked ? "opacity-100" : "opacity-0"),
|
|
1636
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
1637
|
+
d: "M1 3.5L2.75 5.25L6 1.75",
|
|
1638
|
+
stroke: "currentColor",
|
|
1639
|
+
strokeWidth: "1.25",
|
|
1640
|
+
strokeLinecap: "round",
|
|
1641
|
+
strokeLinejoin: "round"
|
|
1642
|
+
})
|
|
1643
|
+
})]
|
|
1644
|
+
});
|
|
1645
|
+
};
|
|
1646
|
+
var FileTreeContext = (0, import_react.createContext)(null);
|
|
1647
|
+
var useFileTreeContext = () => {
|
|
1648
|
+
const ctx = (0, import_react.useContext)(FileTreeContext);
|
|
1649
|
+
if (!ctx) throw new Error("FileItem must be rendered inside <FileTree>");
|
|
1650
|
+
return ctx;
|
|
1651
|
+
};
|
|
1652
|
+
var FileItem = ({ node, depth }) => {
|
|
1653
|
+
const { selectedFile, onSelectFile, onToggleViewed, onStageFile, reviewedFiles } = useFileTreeContext();
|
|
1654
|
+
const { file } = node;
|
|
1655
|
+
const filePath = file?.newPath ?? "";
|
|
1656
|
+
const isSelected = selectedFile === filePath;
|
|
1657
|
+
const isReviewed = reviewedFiles?.has(filePath) ?? false;
|
|
1658
|
+
const handleSelect = (0, import_react.useCallback)(() => {
|
|
1659
|
+
if (filePath) onSelectFile(filePath);
|
|
1660
|
+
}, [onSelectFile, filePath]);
|
|
1661
|
+
const handleStage = (0, import_react.useCallback)((e) => {
|
|
1662
|
+
e.stopPropagation();
|
|
1663
|
+
onStageFile?.(filePath);
|
|
1664
|
+
}, [onStageFile, filePath]);
|
|
1665
|
+
const handleKeyDown = (0, import_react.useCallback)((e) => {
|
|
1666
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1667
|
+
e.preventDefault();
|
|
1668
|
+
handleSelect();
|
|
1669
|
+
}
|
|
1670
|
+
}, [handleSelect]);
|
|
1671
|
+
if (!file) return null;
|
|
1672
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeNode, {
|
|
1673
|
+
nodeId: filePath,
|
|
1674
|
+
level: depth,
|
|
1675
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1676
|
+
role: "row",
|
|
1677
|
+
"aria-current": isSelected ? "true" : void 0,
|
|
1678
|
+
"data-selected": isSelected ? "" : void 0,
|
|
1679
|
+
onClick: handleSelect,
|
|
1680
|
+
onKeyDown: handleKeyDown,
|
|
1681
|
+
tabIndex: 0,
|
|
1682
|
+
className: cn("group flex w-full cursor-pointer items-center gap-1.5 py-[3px] pr-2 text-left outline-none", "transition-[background-color,border-color,opacity] duration-100", EASE_OUT, isSelected ? "border-l-2 border-accent-primary bg-accent-muted/40" : "border-l-2 border-transparent hover:bg-surface-overlay/40", isReviewed && !isSelected && "opacity-45", "focus-visible:ring-1 focus-visible:ring-accent-primary/40"),
|
|
1683
|
+
style: { paddingLeft: `${depth * 14 + 8}px` },
|
|
1684
|
+
children: [
|
|
1685
|
+
onToggleViewed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewedCheckbox, {
|
|
1686
|
+
checked: isReviewed,
|
|
1687
|
+
onToggle: onToggleViewed,
|
|
1688
|
+
filePath
|
|
1689
|
+
}) : null,
|
|
1690
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1691
|
+
className: cn("inline-flex w-3 shrink-0 text-center font-mono text-[10px] font-semibold leading-none", statusColor[file.status]),
|
|
1692
|
+
children: statusLetter[file.status]
|
|
1693
|
+
}),
|
|
1694
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1695
|
+
className: cn("min-w-0 flex-1 truncate font-mono text-[11px]", isSelected ? "text-text-primary" : "text-text-secondary"),
|
|
1696
|
+
children: node.name
|
|
1697
|
+
}),
|
|
1698
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1699
|
+
className: cn("ml-auto flex shrink-0 items-center gap-1 text-[10px] tabular-nums", "transition-opacity duration-100", isSelected ? "opacity-50" : "opacity-0 group-hover:opacity-50"),
|
|
1700
|
+
children: [
|
|
1701
|
+
onStageFile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1702
|
+
type: "button",
|
|
1703
|
+
onClick: handleStage,
|
|
1704
|
+
title: `git add ${filePath}`,
|
|
1705
|
+
"aria-label": `Stage ${node.name}`,
|
|
1706
|
+
className: cn("inline-flex h-4 items-center rounded px-1 font-sans text-[9px] font-medium uppercase tracking-wide text-text-tertiary", "transition-[color,background-color] duration-75", "opacity-0 group-hover:opacity-100", "hover:!bg-diff-add-bg hover:!text-diff-add-text", "active:scale-[0.95]", EASE_OUT),
|
|
1707
|
+
children: "+stage"
|
|
1708
|
+
}) : null,
|
|
1709
|
+
file.additions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1710
|
+
className: "text-diff-add-text/70",
|
|
1711
|
+
children: ["+", file.additions]
|
|
1712
|
+
}) : null,
|
|
1713
|
+
file.deletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1714
|
+
className: "text-diff-remove-text/70",
|
|
1715
|
+
children: ["-", file.deletions]
|
|
1716
|
+
}) : null
|
|
1717
|
+
]
|
|
1718
|
+
})
|
|
1719
|
+
]
|
|
1720
|
+
})
|
|
1721
|
+
});
|
|
1722
|
+
};
|
|
1723
|
+
var DirItem = ({ node, depth }) => {
|
|
1724
|
+
const hasChildren = node.children.length > 0;
|
|
1725
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TreeNode, {
|
|
1726
|
+
nodeId: node.path,
|
|
1727
|
+
level: depth,
|
|
1728
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TreeNodeTrigger, {
|
|
1729
|
+
className: cn("gap-1 py-[3px] pr-2", "hover:bg-surface-overlay/40"),
|
|
1730
|
+
children: [
|
|
1731
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeExpander, { hasChildren }),
|
|
1732
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeIcon, { hasChildren: true }),
|
|
1733
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1734
|
+
className: "truncate text-[11px] text-text-tertiary",
|
|
1735
|
+
children: node.name
|
|
1736
|
+
})
|
|
1737
|
+
]
|
|
1738
|
+
}), hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeNodeContent, { children: node.children.map((child) => child.file ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileItem, {
|
|
1739
|
+
node: child,
|
|
1740
|
+
depth: depth + 1
|
|
1741
|
+
}, child.path) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirItem, {
|
|
1742
|
+
node: child,
|
|
1743
|
+
depth: depth + 1
|
|
1744
|
+
}, child.path)) }) : null]
|
|
1745
|
+
});
|
|
1746
|
+
};
|
|
1747
|
+
var useFileKeyboardNav = (flatFiles, selectedFile, onSelectFile) => {
|
|
1748
|
+
(0, import_react.useEffect)(() => {
|
|
1749
|
+
const handler = (event) => {
|
|
1750
|
+
const tag = event.target.tagName;
|
|
1751
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
1752
|
+
if (event.key === "j" || event.key === "ArrowDown") {
|
|
1753
|
+
event.preventDefault();
|
|
1754
|
+
if (flatFiles.length === 0) return;
|
|
1755
|
+
if (selectedFile === null) {
|
|
1756
|
+
onSelectFile(flatFiles[0]);
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
const idx = flatFiles.indexOf(selectedFile);
|
|
1760
|
+
if (idx < flatFiles.length - 1) onSelectFile(flatFiles[idx + 1]);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
if (event.key === "k" || event.key === "ArrowUp") {
|
|
1764
|
+
event.preventDefault();
|
|
1765
|
+
if (flatFiles.length === 0) return;
|
|
1766
|
+
if (selectedFile === null) {
|
|
1767
|
+
const last = flatFiles.at(-1);
|
|
1768
|
+
if (last) onSelectFile(last);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
const idx = flatFiles.indexOf(selectedFile);
|
|
1772
|
+
if (idx > 0) onSelectFile(flatFiles[idx - 1]);
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
document.addEventListener("keydown", handler);
|
|
1776
|
+
return () => document.removeEventListener("keydown", handler);
|
|
1777
|
+
}, [
|
|
1778
|
+
flatFiles,
|
|
1779
|
+
onSelectFile,
|
|
1780
|
+
selectedFile
|
|
1781
|
+
]);
|
|
1782
|
+
};
|
|
1783
|
+
var renderTreeChildren = (nodes, depth) => nodes.map((child) => child.file ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileItem, {
|
|
1784
|
+
node: child,
|
|
1785
|
+
depth
|
|
1786
|
+
}, child.path) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirItem, {
|
|
1787
|
+
node: child,
|
|
1788
|
+
depth
|
|
1789
|
+
}, child.path));
|
|
1790
|
+
var FileTree = ({ files, selectedFile, onSelectFile, reviewedFiles, onToggleViewed, onStageFile, groupLabel, headerAction, emptyStateMessage = "No files" }) => {
|
|
1791
|
+
const [hideViewed, setHideViewed] = (0, import_react.useState)(false);
|
|
1792
|
+
const toggleHideViewed = (0, import_react.useCallback)(() => setHideViewed((p) => !p), []);
|
|
1793
|
+
const reviewedCount = reviewedFiles?.size ?? 0;
|
|
1794
|
+
const visibleFiles = (0, import_react.useMemo)(() => {
|
|
1795
|
+
if (!hideViewed || !reviewedFiles || reviewedFiles.size === 0) return files;
|
|
1796
|
+
return files.filter((f) => !reviewedFiles.has(f.newPath));
|
|
1797
|
+
}, [
|
|
1798
|
+
files,
|
|
1799
|
+
hideViewed,
|
|
1800
|
+
reviewedFiles
|
|
1801
|
+
]);
|
|
1802
|
+
const hiddenCount = files.length - visibleFiles.length;
|
|
1803
|
+
const tree = (0, import_react.useMemo)(() => buildTree(visibleFiles), [visibleFiles]);
|
|
1804
|
+
const [expanded, setExpanded] = (0, import_react.useState)(() => new Set(collectDirPaths(tree)));
|
|
1805
|
+
const [groupOpen, setGroupOpen] = (0, import_react.useState)(true);
|
|
1806
|
+
(0, import_react.useEffect)(() => {
|
|
1807
|
+
setExpanded(new Set(collectDirPaths(tree)));
|
|
1808
|
+
}, [tree]);
|
|
1809
|
+
const handleExpandedChange = (0, import_react.useCallback)((ids) => {
|
|
1810
|
+
setExpanded(ids);
|
|
1811
|
+
}, []);
|
|
1812
|
+
const toggleGroupOpen = (0, import_react.useCallback)(() => setGroupOpen((p) => !p), []);
|
|
1813
|
+
useFileKeyboardNav((0, import_react.useMemo)(() => flatFileList(tree), [tree]), selectedFile, onSelectFile);
|
|
1814
|
+
const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
|
|
1815
|
+
const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
|
|
1816
|
+
const reviewCtx = (0, import_react.useMemo)(() => ({
|
|
1817
|
+
onSelectFile,
|
|
1818
|
+
onStageFile,
|
|
1819
|
+
onToggleViewed,
|
|
1820
|
+
reviewedFiles,
|
|
1821
|
+
selectedFile
|
|
1822
|
+
}), [
|
|
1823
|
+
selectedFile,
|
|
1824
|
+
onSelectFile,
|
|
1825
|
+
reviewedFiles,
|
|
1826
|
+
onToggleViewed,
|
|
1827
|
+
onStageFile
|
|
1828
|
+
]);
|
|
1829
|
+
const allReviewed = files.length > 0 && reviewedCount === files.length;
|
|
1830
|
+
const progressPct = files.length > 0 ? reviewedCount / files.length * 100 : 0;
|
|
1831
|
+
const treeContent = groupLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
1832
|
+
type: "button",
|
|
1833
|
+
onClick: toggleGroupOpen,
|
|
1834
|
+
className: "ringi-tree-item flex w-full items-center gap-1 px-3 py-1.5 text-left",
|
|
1835
|
+
children: [
|
|
1836
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1837
|
+
className: cn("inline-flex size-4 shrink-0 items-center justify-center text-[10px] text-text-tertiary/60 transition-transform duration-100", EASE_OUT, groupOpen && "rotate-90"),
|
|
1838
|
+
children: "▶"
|
|
1839
|
+
}),
|
|
1840
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1841
|
+
className: "truncate text-[11px] font-medium text-text-secondary",
|
|
1842
|
+
children: groupLabel
|
|
1843
|
+
}),
|
|
1844
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1845
|
+
className: "ml-auto text-[10px] tabular-nums text-text-tertiary",
|
|
1846
|
+
children: visibleFiles.length
|
|
1847
|
+
})
|
|
1848
|
+
]
|
|
1849
|
+
}), groupOpen ? renderTreeChildren(tree.children, 0) : null] }) : renderTreeChildren(tree.children, 0);
|
|
1850
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
|
|
1851
|
+
className: "flex h-full w-60 shrink-0 flex-col border-r border-border-default bg-surface-secondary",
|
|
1852
|
+
children: [
|
|
1853
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1854
|
+
className: "flex flex-col gap-1.5 px-3 py-2",
|
|
1855
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1856
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1857
|
+
children: headerAction
|
|
1858
|
+
}), files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1859
|
+
className: "flex items-center gap-2",
|
|
1860
|
+
children: [
|
|
1861
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1862
|
+
className: "h-[2px] flex-1 overflow-hidden rounded-full bg-border-subtle",
|
|
1863
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1864
|
+
className: cn("h-full rounded-full transition-[width] duration-300", EASE_OUT, allReviewed ? "bg-status-success/60" : "bg-accent-primary/40"),
|
|
1865
|
+
style: { width: `${progressPct}%` }
|
|
1866
|
+
})
|
|
1867
|
+
}),
|
|
1868
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1869
|
+
className: "shrink-0 text-[10px] tabular-nums text-text-tertiary",
|
|
1870
|
+
children: [
|
|
1871
|
+
reviewedCount,
|
|
1872
|
+
"/",
|
|
1873
|
+
files.length
|
|
1874
|
+
]
|
|
1875
|
+
}),
|
|
1876
|
+
reviewedCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1877
|
+
type: "button",
|
|
1878
|
+
onClick: toggleHideViewed,
|
|
1879
|
+
"aria-pressed": hideViewed,
|
|
1880
|
+
title: hideViewed ? `Show all (${hiddenCount} hidden)` : "Hide reviewed",
|
|
1881
|
+
className: cn("flex size-5 shrink-0 items-center justify-center rounded transition-[background-color,color] duration-100", EASE_OUT, hideViewed ? "bg-accent-muted text-accent-primary" : "text-text-tertiary/50 hover:bg-surface-overlay hover:text-text-tertiary"),
|
|
1882
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
1883
|
+
width: "10",
|
|
1884
|
+
height: "10",
|
|
1885
|
+
viewBox: "0 0 12 12",
|
|
1886
|
+
fill: "none",
|
|
1887
|
+
className: "shrink-0",
|
|
1888
|
+
children: hideViewed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
1889
|
+
d: "M1.5 1.5L10.5 10.5",
|
|
1890
|
+
stroke: "currentColor",
|
|
1891
|
+
strokeWidth: "1.25",
|
|
1892
|
+
strokeLinecap: "round"
|
|
1893
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
1894
|
+
d: "M2.4 4.8C1.8 5.4 1.5 6 1.5 6s1.5 3 4.5 3c.6 0 1.1-.1 1.6-.3M9.6 7.2C10.2 6.6 10.5 6 10.5 6s-1.5-3-4.5-3c-.6 0-1.1.1-1.6.3",
|
|
1895
|
+
stroke: "currentColor",
|
|
1896
|
+
strokeWidth: "1.25",
|
|
1897
|
+
strokeLinecap: "round",
|
|
1898
|
+
strokeLinejoin: "round"
|
|
1899
|
+
})] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
1900
|
+
d: "M1.5 6s1.5-3 4.5-3 4.5 3 4.5 3-1.5 3-4.5 3S1.5 6 1.5 6z",
|
|
1901
|
+
stroke: "currentColor",
|
|
1902
|
+
strokeWidth: "1.25",
|
|
1903
|
+
strokeLinecap: "round",
|
|
1904
|
+
strokeLinejoin: "round"
|
|
1905
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
|
|
1906
|
+
cx: "6",
|
|
1907
|
+
cy: "6",
|
|
1908
|
+
r: "1.5",
|
|
1909
|
+
stroke: "currentColor",
|
|
1910
|
+
strokeWidth: "1.25"
|
|
1911
|
+
})] })
|
|
1912
|
+
})
|
|
1913
|
+
}) : null
|
|
1914
|
+
]
|
|
1915
|
+
}) : null]
|
|
1916
|
+
}),
|
|
1917
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px bg-border-subtle" }),
|
|
1918
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1919
|
+
className: "flex-1 overflow-x-hidden overflow-y-auto py-0.5",
|
|
1920
|
+
children: visibleFiles.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1921
|
+
className: "flex h-32 items-center justify-center px-3 text-center text-[11px] text-text-tertiary",
|
|
1922
|
+
children: hideViewed && hiddenCount > 0 ? "All files reviewed" : emptyStateMessage
|
|
1923
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileTreeContext.Provider, {
|
|
1924
|
+
value: reviewCtx,
|
|
1925
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tree, {
|
|
1926
|
+
expandedIds: expanded,
|
|
1927
|
+
onExpandedChange: handleExpandedChange,
|
|
1928
|
+
showIcons: true,
|
|
1929
|
+
indent: 14,
|
|
1930
|
+
children: treeContent
|
|
1931
|
+
})
|
|
1932
|
+
})
|
|
1933
|
+
}),
|
|
1934
|
+
totalAdditions > 0 || totalDeletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1935
|
+
className: "flex items-center border-t border-border-subtle px-3 py-1.5",
|
|
1936
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1937
|
+
className: "flex gap-1.5 text-[10px] tabular-nums",
|
|
1938
|
+
children: [totalAdditions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1939
|
+
className: "text-diff-add-text/60",
|
|
1940
|
+
children: ["+", totalAdditions]
|
|
1941
|
+
}) : null, totalDeletions > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1942
|
+
className: "text-diff-remove-text/60",
|
|
1943
|
+
children: ["-", totalDeletions]
|
|
1944
|
+
}) : null]
|
|
1945
|
+
})
|
|
1946
|
+
}) : null
|
|
1947
|
+
]
|
|
1948
|
+
});
|
|
1949
|
+
};
|
|
1950
|
+
//#endregion
|
|
1951
|
+
export { formatReviewFeedback as a, FileTree as i, DiffView as n, ExportFeedbackModal as r, DiffSummary as t };
|