tmux-weblink 2.0.26 → 2.0.28
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.
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
var Xt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
|
|
2
|
-
`)}selectRows(t,s){let i=this.terminal.buffer.active,[o,r]=t<=s?[t,s]:[s,t];this.terminal.selectLines(i.viewportY+o,i.viewportY+r)}selectCells(t,s,i,o){let r=this.terminal.buffer.active,d=Math.min(s,o),l=Math.max(s,o);if(d===l){let u=Math.min(t,i),f=Math.max(t,i);this.terminal.select(u,r.viewportY+d,f-u+1)}else this.terminal.selectLines(r.viewportY+d,r.viewportY+l)}getCellsText(t,s,i,o){let r=this.terminal.buffer.active,d=Math.min(s,o),l=Math.max(s,o),u=s<o?t:s>o?i:Math.min(t,i),f=s<o?i:s>o?t:Math.max(t,i),R=r.viewportY,q=[];for(let N=d;N<=l;N++){let
|
|
3
|
-
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function Gt(e){let t=e.getBoundingClientRect(),s=window.visualViewport;return s?{width:Math.min(t.width,s.width),height:Math.max(0,Math.min(t.height,s.height-Math.max(0,t.top)))}:t}function Qt(e,t,s){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let i=await Zt.create(e,s.scrollback,s.theme),o,r=0,d,l=null,u=0,f=0,R=.2,q=10,N=.95,
|
|
4
|
-
\x1B[31m`+
|
|
5
|
-
`).then(()=>{be();try{o?.close()}catch{}});return}if(a.type==="auth.ok"){a.token&&localStorage.setItem("tmux-web-token",a.token),
|
|
1
|
+
var Xt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function Fe(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function oe(e){return e.replace(Xt,"")}var ke;function Jt(){return ke||(document.querySelector("link[data-tmux-web-xterm-css]")?ke=Promise.resolve():(ke=new Promise(e=>{let t=document.createElement("link");t.rel="stylesheet",t.href="/assets/xterm.css",t.setAttribute("data-tmux-web-xterm-css",""),t.addEventListener("load",()=>e(),{once:!0}),t.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(t)}),ke))}var Zt=class xt{terminal;fitAddon;container;constructor(t,s,i){this.container=t,this.terminal=s,this.fitAddon=i,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,s,i){let[{Terminal:o},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Jt()]),d=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:s,convertEol:!1,smoothScrollDuration:120,theme:i});return new xt(t,d,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(s=>this.terminal.write(t,s))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(t,s=!1){this.terminal.scrollToLine(Math.max(0,t),s)}scrollLines(t){let s=this.terminal.buffer?.active;this.terminal.scrollLines(t)}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let t=Gt(this.container);return t.width<=0||t.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(t){this.terminal.paste(t)}input(t){this.terminal.input(t,!1)}onData(t){this.terminal.onData(t),this.terminal.onBinary(t)}onResize(t){this.terminal.onResize(t)}onScroll(t){this.terminal.onScroll(t)}attachCustomKeyEventHandler(t){this.terminal.attachCustomKeyEventHandler(t)}isFocused(){let t=document.activeElement;return t?t===this.container||t===this.terminal.textarea||this.container.contains(t)||!!t.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(t,s){let i=this.terminal.buffer.active,[o,r]=t<=s?[t,s]:[s,t],d=i.viewportY,l=[];for(let u=o;u<=r;u++){let f=i.getLine(d+u);l.push(f?f.translateToString(!0):"")}return l.join(`
|
|
2
|
+
`)}selectRows(t,s){let i=this.terminal.buffer.active,[o,r]=t<=s?[t,s]:[s,t];this.terminal.selectLines(i.viewportY+o,i.viewportY+r)}selectCells(t,s,i,o){let r=this.terminal.buffer.active,d=Math.min(s,o),l=Math.max(s,o);if(d===l){let u=Math.min(t,i),f=Math.max(t,i);this.terminal.select(u,r.viewportY+d,f-u+1)}else this.terminal.selectLines(r.viewportY+d,r.viewportY+l)}getCellsText(t,s,i,o){let r=this.terminal.buffer.active,d=Math.min(s,o),l=Math.max(s,o),u=s<o?t:s>o?i:Math.min(t,i),f=s<o?i:s>o?t:Math.max(t,i),R=r.viewportY,q=[];for(let N=d;N<=l;N++){let he=r.getLine(R+N);if(!he){q.push("");continue}let O=N===d?u:0,Pe=N===l?f:this.terminal.cols-1,K="";for(let k=O;k<=Pe;k++){let z=he.getCell(k);z&&(K+=z.getChars())}q.push(K.replace(/\s+$/,""))}return q.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function Gt(e){let t=e.getBoundingClientRect(),s=window.visualViewport;return s?{width:Math.min(t.width,s.width),height:Math.max(0,Math.min(t.height,s.height-Math.max(0,t.top)))}:t}function Qt(e,t,s){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let i=await Zt.create(e,s.scrollback,s.theme),o,r=0,d,l=null,u=0,f=0,R=.2,q=10,N=.95,he=.04;function O(){u&&(cancelAnimationFrame(u),u=0),f=0}function Pe(n){if(O(),Math.abs(n)<R)return;f=Math.max(-q,Math.min(q,n));let a=()=>{if(A){u=0;return}let c=e.querySelector(".xterm-rows > div"),p=c?c.getBoundingClientRect().height:16,b=p>0?Math.round(f*16/p):f>0?1:-1;b===0&&(b=f>0?1:-1);let B=i.viewportY()-b,j=i.baseY(),ae=Math.max(0,Math.min(B,j));if(i.scrollToLine(ae,!0),f*=N,ae!==B){u=0;return}if(Math.abs(f)<he){u=0;return}u=requestAnimationFrame(a)};u=requestAnimationFrame(a)}let K=0,k="connecting",z=0,G=!1,ge=[],V="",A=!1,Q=1e3,kt=location.protocol==="https:"?"wss:":"ws:",Ct=s.wsBase||"/ws",Tt=kt+"//"+location.host+Ct+"/"+encodeURIComponent(t),Mt="/api/session/"+encodeURIComponent(t)+"/upload",Bt=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function It(){return ge.join("")+V}async function St(n,a=0){let c=n?i.viewportY():0,p=oe(It());if(i.reset(),!p){n||i.scrollToBottom();return}await i.write(p),n?i.scrollToLine(a+c):i.scrollToBottom()}function _(n){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(n))}function Ht(n){let a;try{a=JSON.parse(n)}catch{k==="live"&&(V+=n,i.write(oe(n)));return}if(a.type==="auth.required"){let c=localStorage.getItem("tmux-web-token");c?_({type:"auth.token",token:c}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(a.type==="attach_failed"&&typeof a.message=="string"){ye=!0,k="dead",i.reset(),i.write(`\r
|
|
4
|
+
\x1B[31m`+oe(a.message)+`\x1B[0m\r
|
|
5
|
+
`).then(()=>{be();try{o?.close()}catch{}});return}if(a.type==="auth.ok"){a.token&&localStorage.setItem("tmux-web-token",a.token),Q=1e3,F(),_({type:"resize",cols:i.cols,rows:i.rows}),C();return}if(a.type==="auth.failed"){if(a.permanentLock||a.retryAfterMs&&a.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(a.message);return}return}if(a.type==="snapshot"&&typeof a.data=="string"){let c=Fe(oe(a.data));ge=[c],V="",z=typeof a.lines=="number"?a.lines:s.terminal.initialLines,k="live",i.reset(),F(),_({type:"resize",cols:i.cols,rows:i.rows}),i.write(c).then(()=>i.scrollToBottom());return}if(a.type==="window_changed"&&typeof a.activeIndex=="number"){let c=new URL(location.href);c.searchParams.get("window")!==String(a.activeIndex)&&(c.searchParams.set("window",String(a.activeIndex)),history.replaceState(history.state,"",c)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:a.windows}));return}if(a.type==="history"&&typeof a.data=="string"){a.lines>0&&a.data?(ge.unshift(Fe(a.data)),z+=a.lines,St(!0,a.lines).finally(()=>{G=!1})):G=!1;return}if(a.type==="data"&&typeof a.data=="string"){let c=Fe(oe(a.data));if(k==="connecting"){k="live",V=c,i.write(c);return}k==="live"&&(V+=c,i.write(c))}}let ye=!1;function Ve(){if(ye)return;let n=Tt,a=localStorage.getItem("tmux-web-token");a&&(n+=(n.includes("?")?"&":"?")+"token="+encodeURIComponent(a)),n+=(n.includes("?")?"&":"?")+"cols="+i.cols+"&rows="+i.rows,o=new WebSocket(n),o.onopen=()=>{k="connecting",z=0,G=!1,ge=[],V="",i.reset(),Q=1e3},o.onmessage=c=>{typeof c.data=="string"&&Ht(c.data)},o.onclose=()=>{A||(k="connecting",!ye&&setTimeout(()=>{Q=Math.min(Q*2,1e4),!A&&!ye&&Ve()},Q))},o.onerror=()=>o?.close()}function we(n){_({type:"input",data:n})}function F(){return i.fit()}function be(){e.classList.remove("terminal-pending")}function C(){r&&cancelAnimationFrame(r),d&&clearTimeout(d),F(),r=requestAnimationFrame(()=>{r=0,F()&&be()}),d=setTimeout(()=>{F()&&be()},120)}function Dt(){C(),setTimeout(C,50),setTimeout(C,150),setTimeout(C,300)}function Xe(n){return typeof n=="string"&&n.startsWith("image/")}function Je(n){return n?.files?.length?n.files[0]??null:null}function Rt(n){if(!n?.items)return null;for(let a=0;a<n.items.length;a++){let c=n.items[a];if(c?.kind==="file"&&Xe(c.type))return c.getAsFile()}return null}async function At(n){let a=new FormData,c=n instanceof File&&n.name?n.name:"upload";a.append("file",n,c);let p=localStorage.getItem("tmux-web-token"),b={};p&&(b.Authorization="Bearer "+p);let B=await fetch(Mt,{method:"POST",body:a,headers:b});if(!B.ok){let ae="upload failed";try{let it=await B.json();it.error&&(ae=it.error)}catch{}throw new Error(ae)}let j=await B.json();if(!j.path)throw new Error("no path in response");return j.path}async function Ne(n){try{let a=await At(n);we(a)}catch{}}i.onData(n=>we(n)),i.onResize(({cols:n,rows:a})=>_({type:"resize",cols:n,rows:a})),i.onScroll(()=>{k!=="live"||G||!i.isNearScrollbackTop()||(G=!0,_({type:"load_history",before:z}))});let ee=!1,te=!1,w=null,L=null,ne=document.createElement("div");ne.className="terminal-copy-banner",ne.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',ne.hidden=!0,e.appendChild(ne);let H=document.createElement("div");H.className="terminal-copy-banner terminal-copy-toast",H.textContent="Copied",H.hidden=!0,e.appendChild(H);function Ze(n,a){let c=e.querySelector(".xterm-screen, canvas"),p=(c&&c.getBoundingClientRect().width>0?c:e).getBoundingClientRect(),b=p.height>0&&i.rows>0?p.height/i.rows:18,B=p.width>0&&i.cols>0?p.width/i.cols:9;return{row:Math.max(0,Math.min(i.rows-1,Math.floor((a-p.top)/b))),col:Math.max(0,Math.min(i.cols-1,Math.floor((n-p.left)/B)))}}async function Le(n){if(!n)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(n);return}}catch{}let a=document.createElement("textarea");a.value=n,a.style.position="fixed",a.style.opacity="0",a.style.pointerEvents="none",document.body.appendChild(a),a.focus(),a.select();try{document.execCommand("copy")}catch{}a.remove()}let ie;function Ee(){H.hidden=!1,ie&&clearTimeout(ie),ie=setTimeout(()=>{H.hidden=!0},1e3)}function X(n){ee=n,ne.hidden=!n,e.classList.toggle("terminal-copy-mode-active",n),n&&i.focus()}function Ge(){X(!ee)}function $t(n){A||n.button!==0||te||(ee||n.shiftKey)&&(n.preventDefault(),n.stopPropagation(),te=!0,w=Ze(n.clientX,n.clientY),L=w,i.selectCells(w.col,w.row,L.col,L.row),document.addEventListener("mousemove",ze,!0),document.addEventListener("mouseup",_e,!0))}function ze(n){A||!te||!w||(n.preventDefault(),n.stopPropagation(),L=Ze(n.clientX,n.clientY),i.selectCells(w.col,w.row,L.col,L.row))}function _e(n){if(A||!te||!w||!L)return;n.preventDefault(),n.stopPropagation(),document.removeEventListener("mousemove",ze,!0),document.removeEventListener("mouseup",_e,!0),te=!1;let a=i.getCellsText(w.col,w.row,L.col,L.row);w=null,L=null,i.clearSelection(),X(!1),a&&(H.textContent="\u5DF2\u590D\u5236",Le(a).then(Ee)),i.focus()}e.addEventListener("mousedown",$t,!0),document.addEventListener("keydown",Pt,!0);function Pt(n){if(!(A||!i.isFocused()||n.defaultPrevented||n.isComposing)){if(ee){if(n.key==="Escape"){n.preventDefault(),n.stopPropagation(),w=null,L=null,i.clearSelection(),X(!1);return}if(n.key==="Enter"||(n.ctrlKey||n.metaKey)&&n.code==="KeyC"){n.preventDefault(),n.stopPropagation(),Le(i.getScreenText()).then(Ee),X(!1);return}}if(n.altKey&&n.code==="KeyC"&&!n.ctrlKey&&!n.metaKey){if(n.preventDefault(),n.stopPropagation(),w&&L){let a=i.getCellsText(w.col,w.row,L.col,L.row);w=null,L=null,i.clearSelection(),X(!1),a&&(H.textContent="Copied",Le(a).then(Ee))}else{let a=i.getSelection();a?(i.clearSelection(),H.textContent="Copied",Le(a).then(Ee)):Ge()}return}if((n.ctrlKey||n.metaKey)&&n.code==="KeyV"){n.preventDefault(),n.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let a=await navigator.clipboard.read();for(let c of a)for(let p of c.types)if(Xe(p)){let b=await c.getType(p);await Ne(b);return}}}catch{}try{let a=await navigator.clipboard?.readText();a&&i.pasteText(Qe(a))}catch{}})();return}}}let xe=0;function Qe(n){return n.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function Nt(n){let a=Rt(n.clipboardData);if(a){n.preventDefault(),n.stopPropagation();let
|
|
8
|
+
`).join("\r")}async function Nt(n){let a=Rt(n.clipboardData);if(a){n.preventDefault(),n.stopPropagation();let b=Date.now();if(b-xe<50)return;xe=b,await Ne(a);return}let c=n.clipboardData?.getData("text/plain");if(!c)return;n.preventDefault(),n.stopPropagation();let p=Date.now();p-xe<50||(xe=p,i.pasteText(Qe(c)))}let se=0;function zt(n){n.preventDefault(),se++,Je(n.dataTransfer)&&e.classList.add("terminal-drag-over")}function _t(n){n.preventDefault(),n.dataTransfer&&(n.dataTransfer.dropEffect="copy")}function Ft(n){n.preventDefault(),se--,se<=0&&(se=0,e.classList.remove("terminal-drag-over"))}async function jt(n){n.preventDefault(),se=0,e.classList.remove("terminal-drag-over");let a=Je(n.dataTransfer);a&&await Ne(a)}function et(n){!Bt||n.key!=="Escape"||ee||n.defaultPrevented||n.repeat||n.metaKey||n.ctrlKey||n.altKey||i.isFocused()&&(n.preventDefault(),n.stopPropagation(),we("\x1B"))}e.addEventListener("paste",Nt),e.addEventListener("dragenter",zt),e.addEventListener("dragover",_t),e.addEventListener("dragleave",Ft),e.addEventListener("drop",jt),document.addEventListener("keydown",et,!0);function Yt(n){if(n.touches.length!==1){l=null;return}let a=n.touches[0];a&&(O(),l={startX:a.clientX,startY:a.clientY,lastX:a.clientX,lastY:a.clientY,lastT:performance.now(),velocity:0,scrolling:!1},n.stopPropagation())}function Ut(n){if(!l||n.touches.length!==1)return;let a=n.touches[0];if(!a)return;let c=a.clientY-l.startY,p=a.clientX-l.startX;if(!l.scrolling){if(Math.abs(c)<8||Math.abs(c)<Math.abs(p))return;l.scrolling=!0,K=Date.now()+500}n.preventDefault(),n.stopPropagation();let b=performance.now(),B=a.clientY-l.lastY,j=b-l.lastT;j>0&&(l.velocity=B/j),Kt(-B),l.lastX=a.clientX,l.lastY=a.clientY,l.lastT=b}function Wt(n){if(!l)return;let a=l.scrolling,c=l.velocity;if(l=null,n.stopPropagation(),!a){i.focus();return}K=Date.now()+500,n.preventDefault(),c&&Pe(c)}function qt(n){l=null,O(),n.stopPropagation()}function Ot(n){n.pointerType==="touch"&&Date.now()<K&&(n.preventDefault(),n.stopPropagation())}function Kt(n){let a=e.querySelector(".xterm-rows > div"),c=a?a.getBoundingClientRect().height:16,p=c>0?Math.round(n/c):n>0?1:-1;p===0&&(p=n>0?1:-1),i.scrollLines(p)}e.addEventListener("touchstart",Yt,{passive:!0,capture:!0}),e.addEventListener("touchmove",Ut,{passive:!1,capture:!0}),e.addEventListener("touchend",Wt,{passive:!1,capture:!0}),e.addEventListener("touchcancel",qt,{passive:!0,capture:!0}),e.addEventListener("pointerup",Ot,!0);function tt(){C()}window.addEventListener("resize",tt),window.visualViewport?.addEventListener("resize",C),window.visualViewport?.addEventListener("scroll",C);let nt=new ResizeObserver(()=>C());nt.observe(e),document.fonts?.ready.then(()=>{C(),setTimeout(C,50),setTimeout(C,200)}),e.addEventListener("focusin",Dt,!0);async function Vt(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(n=>setTimeout(n,1e3))])}catch{}}return requestAnimationFrame(()=>{F()&&be(),(async()=>(await Vt(),F()&&_({type:"resize",cols:i.cols,rows:i.rows}),Ve(),C()))()}),{destroy(){if(A=!0,O(),X(!1),ie&&clearTimeout(ie),document.removeEventListener("mousemove",ze,!0),document.removeEventListener("mouseup",_e,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}e.innerHTML="",e.classList.remove("terminal-pending"),window.removeEventListener("resize",tt),nt.disconnect(),document.removeEventListener("keydown",et,!0)},focus(){i.focus()},sendInput(n){we(n)},toggleCopyMode(){Ge()}}})()}var le=window.__TMUX_WEB_SHELL__,h=null,y=null,st=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,x=st;window.__tmuxWebScopeAgent=st;var je="sessions",$="",Ce="",Te="",at="";function T(e){return x?"/a/"+encodeURIComponent(x)+e:e}var v=document.getElementById("sidebar-content"),Y=document.getElementById("main-placeholder"),M=document.getElementById("terminal-container"),re=document.getElementById("file-editor"),en=document.getElementById("fe-path"),I=document.getElementById("fe-status"),ce=document.getElementById("fe-content"),U=document.getElementById("fe-save"),tn=document.getElementById("fe-delete"),nn=document.getElementById("fe-back"),ot=document.getElementById("fe-new-name"),sn=document.getElementById("fe-new-btn"),Ye=document.getElementById("settings-backdrop"),lt=document.getElementById("settings-popover"),rt=document.querySelector(".sidebar");function an(){return window.matchMedia("(max-width: 640px)").matches}function Ue(){an()&&rt.classList.add("collapsed")}function ct(){rt.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{ct(),dt("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{ct(),dt("files")});function dt(e){je=e,document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?(We="",J()):vn()}var We="";async function J(){if(je!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents});if(s===We)return;We=s,on(t)}catch{v.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{let t=window.__openNewSessionModal;typeof t=="function"?t():document.getElementById("new-session-modal")?.classList.add("open")})}function on(e){v.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],s=document.createElement("div");s.className="sidebar-section-label"+(x?" clickable":""),s.textContent=x?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",x&&(s.title="Back to hub",s.addEventListener("click",()=>{window.location.href="/"})),v.appendChild(s);for(let o of t){let r=document.createElement("div");r.className="session-item"+(o.name===y&&!x?" active":""),r.setAttribute("data-session",o.name),r.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${o.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
|
|
9
9
|
<span>${m(o.name)}</span>
|
|
10
10
|
<button class="session-edit-btn" data-session="${m(o.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
|
|
11
|
-
`,r.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||
|
|
12
|
-
<span>${m(l.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&u.addEventListener("click",f=>{f.target.closest(".session-edit-btn")||
|
|
11
|
+
`,r.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||de(o.name)}),v.appendChild(r);let d=r.querySelector(".session-edit-btn");d&&d.addEventListener("click",l=>{l.stopPropagation(),bn(o.name,d)})}let i=e.agents||[];for(let o of i){if(!o.sessions||o.sessions.length===0)continue;let r=o.agentId===x,d=document.createElement("div");d.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!r&&o.online?" clickable":""),d.textContent=o.agentName+(o.online?"":" (offline)"),!r&&o.online&&(d.title="\u6253\u5F00 "+o.agentName,d.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),v.appendChild(d);for(let l of o.sessions){let u=document.createElement("div");u.className="session-item"+(l.name===y&&x===o.agentId?" active":"")+(o.online?"":" session-offline"),u.setAttribute("data-session",l.name),u.setAttribute("data-agent",o.agentId),u.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${l.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
|
|
12
|
+
<span>${m(l.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&u.addEventListener("click",f=>{f.target.closest(".session-edit-btn")||de(l.name,o.agentId)}),v.appendChild(u)}}}function m(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function de(e,t){if(!(y===e&&h&&x===(t||null))){re.style.display="none",Y.style.display="none",M.style.display="",M.classList.add("terminal-pending"),Ue(),h&&(Ae(),h.destroy(),h=null),y=e,x=t||null,window.__tmuxWebScopeAgent=x,window.__tmuxWebCurrentSession=y;for(let s of v.querySelectorAll(".session-item"))s.classList.toggle("active",s.getAttribute("data-session")===e&&(s.getAttribute("data-agent")||null)===x);try{h=await Qt(M,e,{terminal:le.terminal,scrollback:le.scrollback,theme:le.theme,wsBase:x?"/ws/a/"+encodeURIComponent(x):le.wsBase||"/ws"}),bt()}catch(s){console.error("[shell] terminal init failed:",s);let i=s&&typeof s=="object"&&"message"in s?s.message:String(s);M.textContent="Failed to open terminal for "+e+": "+i,M.classList.remove("terminal-pending")}}}var Me=[],g=null,ut=!1;function ln(e){if(!g||!g.files.length)return null;let t=g.repoRoot;if(!t||!e.startsWith(t))return null;let s=e.slice(t.length+1);for(let i of g.files)if(i.path===s)return i.status;return null}async function rn(e){ut=!0;try{let t=await fetch(T("/api/git/status?path="+encodeURIComponent(e)));t.ok?g=await t.json():g=null}catch{g=null}finally{ut=!1}}function cn(){if(!g||!g.branch)return"";let{branch:e,linesAdded:t,linesRemoved:s}=g,i=`<div class="sidebar-git"><div class="git-branch-badge">
|
|
13
13
|
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
|
|
14
|
-
${m(e)}</div>`;return(t>0||s>0)&&(i+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${s}</span></div>`),i+"</div>"}v.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&Z&&g&&(e.stopPropagation(),ht())});var
|
|
14
|
+
${m(e)}</div>`;return(t>0||s>0)&&(i+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${s}</span></div>`),i+"</div>"}v.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&Z&&g&&(e.stopPropagation(),ht())});var ue=document.getElementById("header-git"),pt=document.getElementById("git-diff-view"),ft=document.getElementById("gd-path"),Be=document.getElementById("gd-status"),Ie=document.getElementById("gd-content"),dn=document.getElementById("gd-back"),Z=null;function pe(e){if(!e||!e.branch){ue.style.display="none",Z=null;return}Z=e.repoRoot;let{branch:t,linesAdded:s,linesRemoved:i}=e,o=`<span class="branch">${m(t)}</span>`;(s>0||i>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${s}</span> <span class="diff-del">-${i}</span>`),ue.innerHTML=o,ue.style.display="block"}ue.addEventListener("click",e=>{Z&&g&&(e.stopPropagation(),ht())});var E=document.getElementById("git-popover"),Se=document.getElementById("git-popover-backdrop");function qe(){let e=ue.getBoundingClientRect();E.style.top=e.bottom+4+"px",E.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function mt(){let e=Z,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(De(),e){je="files",document.querySelectorAll(".mode-btn").forEach(i=>i.classList.remove("active"));let s=document.getElementById("mode-files");s&&s.classList.add("active"),P(e)}})}var vt="";function ht(){if(!g)return;let e=g.files||[];if(!e.length)E.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
15
15
|
<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let t=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let s=0;s<e.length;s++){let i=e[s],o=i.additions||0,r=i.deletions||0,d="";switch(i.status){case"M":d='<span class="git-file-status M">M</span>';break;case"A":d='<span class="git-file-status A">A</span>';break;case"D":d='<span class="git-file-status D">D</span>';break;case"?":d='<span class="git-file-status ?">?</span>';break;case"R":d='<span class="git-file-status R">R</span>';break}t+=`<div class="git-popover-item" data-idx="${s}">
|
|
16
16
|
${d}<span class="file-path">${m(i.path)}</span>
|
|
17
17
|
<span class="file-add">${o>0?"+"+o:""}</span>
|
|
18
18
|
<span class="file-del">${r>0?"-"+r:""}</span>
|
|
19
|
-
</div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',vt=t,E.innerHTML=t,E.querySelectorAll(".git-popover-item").forEach(s=>{let i=parseInt(s.dataset.idx||"0",10),o=e[i];!o||o.status==="D"||(s.classList.add("clickable"),s.style.cursor="pointer",s.addEventListener("click",()=>
|
|
19
|
+
</div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',vt=t,E.innerHTML=t,E.querySelectorAll(".git-popover-item").forEach(s=>{let i=parseInt(s.dataset.idx||"0",10),o=e[i];!o||o.status==="D"||(s.classList.add("clickable"),s.style.cursor="pointer",s.addEventListener("click",()=>yt(o.path)))})}qe(),E.classList.add("open"),Se.classList.add("open"),mt()}function un(){return window.innerWidth>=1024}function gt(e,t){let s=(e+(t&&e?`
|
|
20
20
|
`:"")+t).split(`
|
|
21
|
-
`),i="",o=0,r=0;for(let d of s){let l=d;if(l.startsWith("@@")){let u=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(o=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${m(l)}</span></div>`;continue}if(l.startsWith("diff --git")||l.startsWith("index ")||l.startsWith("--- ")||l.startsWith("+++ ")||l.startsWith("new file")||l.startsWith("deleted file")){l.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${m(l)}</span></div>`);continue}if(l.startsWith("+"))r++,i+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${m(l)}</span></div>`;else if(l.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${m(l)}</span></div>`;else if(l.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${m(l)}</span></div>`;else{if(!l&&!o&&!r)continue;o++,r++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${m(l)}</span></div>`}}return{html:i,lineCount:s.length}}var He=null;async function
|
|
21
|
+
`),i="",o=0,r=0;for(let d of s){let l=d;if(l.startsWith("@@")){let u=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(o=parseInt(u[1],10)-1,r=parseInt(u[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${m(l)}</span></div>`;continue}if(l.startsWith("diff --git")||l.startsWith("index ")||l.startsWith("--- ")||l.startsWith("+++ ")||l.startsWith("new file")||l.startsWith("deleted file")){l.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${m(l)}</span></div>`);continue}if(l.startsWith("+"))r++,i+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${m(l)}</span></div>`;else if(l.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${m(l)}</span></div>`;else if(l.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${m(l)}</span></div>`;else{if(!l&&!o&&!r)continue;o++,r++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${r}</span><span class="ln-body">${m(l)}</span></div>`}}return{html:i,lineCount:s.length}}var He=null;async function yt(e){let t=Z;if(t)if(De(),un()){M.style.display="none",h&&(Ae(),h.destroy(),h=null,He=y,y=null),pe(null),re.style.display="none",Y.style.display="none",pt.style.display="flex",ft.textContent=e,Be.textContent="Loading\u2026",Ie.innerHTML="";try{let s=await fetch(T("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!s.ok)throw new Error(await s.text());let i=await s.json();fn(e,i.diff,i.stagedDiff)}catch(s){let i=s instanceof Error?s.message:String(s);Be.textContent="Error",Ie.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${m(i)}</div>`}}else{E.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
22
22
|
<button class="diff-back">\u2190 Back</button>
|
|
23
23
|
<span class="diff-filename">${m(e)}</span>
|
|
24
24
|
<span class="diff-loading">Loading\u2026</span>
|
|
@@ -28,11 +28,11 @@ var Xt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
|
|
|
28
28
|
</div><div class="diff-empty">Error loading diff: ${m(i)}</div></div>`}}}function pn(e,t,s){if(!t&&!s){E.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
29
29
|
<button class="diff-back">\u2190 Back</button>
|
|
30
30
|
<span class="diff-filename">${m(e)}</span>
|
|
31
|
-
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:i,lineCount:o}=
|
|
31
|
+
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:i,lineCount:o}=gt(t,s);E.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
32
32
|
<button class="diff-back">\u2190 Back</button>
|
|
33
33
|
<span class="diff-filename">${m(e)}</span>
|
|
34
34
|
<span class="diff-filesize">${o} lines</span>
|
|
35
|
-
</div><div class="diff-content">${i}</div></div>`,E.querySelector(".diff-back")?.addEventListener("click",()=>{E.innerHTML=vt,qe(),g&&E.querySelectorAll(".git-popover-item").forEach(r=>{let d=parseInt(r.dataset.idx||"0",10),l=g.files[d];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>
|
|
35
|
+
</div><div class="diff-content">${i}</div></div>`,E.querySelector(".diff-back")?.addEventListener("click",()=>{E.innerHTML=vt,qe(),g&&E.querySelectorAll(".git-popover-item").forEach(r=>{let d=parseInt(r.dataset.idx||"0",10),l=g.files[d];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>yt(l.path)))}),mt()})}function fn(e,t,s){if(!t&&!s){Be.textContent="No diff",Ie.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:i,lineCount:o}=gt(t,s);ft.textContent=e,Be.textContent=o+" lines",Ie.innerHTML=i}function mn(){pt.style.display="none",He?(M.style.display="",M.classList.add("terminal-pending"),de(He),He=null):y?(M.style.display="",M.classList.add("terminal-pending"),bt()):Y.style.display="flex"}dn.addEventListener("click",mn);function De(){E.classList.remove("open"),Se.classList.remove("open")}Se.addEventListener("click",De),document.addEventListener("keydown",e=>{e.key==="Escape"&&(De(),$e())});async function wt(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){let i=await s.json();g=i,pe(i);return}}}catch{}g=null,pe(null)}var Re=null;function bt(){Ae(),y&&(wt(y),Re=setInterval(()=>{y&&wt(y)},3e4))}function Ae(){Re&&(clearInterval(Re),Re=null)}function vn(){if(Me=le.fsRoots||[],Me.length===0){if(y){hn(y);return}v.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}v.innerHTML="";for(let e of Me){let t=document.createElement("div");t.className="file-tree-item",t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${m(e)}`,t.addEventListener("click",()=>P(e)),v.appendChild(t)}}async function hn(e){v.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(T("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?P(t.path):v.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{v.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function P(e){$=e,v.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=rn(e);try{let s=await(await fetch(T("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,v.innerHTML="",pe(g),v.innerHTML+=cn();let i=gn(e);if(i&&!Me.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>P(i)),v.appendChild(r)}if(s.dirs)for(let r of s.dirs){let d=r.split("/").pop()||r.split("\\").pop(),l=document.createElement("div");l.className="file-tree-item",l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${m(d)}`,l.addEventListener("click",()=>P(r)),v.appendChild(l)}else v.innerHTML="";let o=s.files||[];for(let r of o){let d=r.split("/").pop()||r.split("\\").pop(),l=ln(r),u="",f="";if(l)switch(l){case"M":u="git-mod",f='<span class="git-file-status M">M</span>';break;case"A":u="git-add",f='<span class="git-file-status A">A</span>';break;case"D":u="git-del",f='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",f='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",f='<span class="git-file-status R">R</span>';break}let R=document.createElement("div");R.className="file-tree-item file"+(u?" "+u:""),R.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${m(d)}`,R.addEventListener("click",()=>yn(r)),v.appendChild(R)}}catch{v.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function gn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function yn(e){Ce=e,M.style.display="none",h&&(Ae(),h.destroy(),h=null,y=null),pe(null),Ue(),Y.style.display="none",re.style.display="flex",en.textContent=e,I.textContent="Loading...",U.style.display="none";try{let t=await fetch(T("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let i=await t.json();I.textContent=i.error||"Failed to load",ce.value="";return}let s=await t.json();ce.value=s.content,Te=s.content,at=s.content,I.textContent=s.size+" bytes",U.style.display=""}catch{I.textContent="Failed to load",ce.value=""}}ce.addEventListener("input",()=>{Te=ce.value}),U.addEventListener("click",async()=>{U.textContent="Saving...",U.disabled=!0;try{let e=await fetch(T("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Ce,content:Te})});if(!e.ok){let t=await e.json();I.textContent=t.error||"Save failed";return}at=Te,I.textContent="Saved"}catch{I.textContent="Save failed"}finally{U.disabled=!1,U.textContent="Save"}}),tn.addEventListener("click",async()=>{if(confirm("Delete "+Ce+"?"))try{let e=await fetch(T("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:Ce})});if(!e.ok){let t=await e.json();I.textContent=t.error||"Delete failed";return}re.style.display="none",Y.style.display="flex",$&&P($)}catch{I.textContent="Delete failed"}}),nn.addEventListener("click",()=>{re.style.display="none",Y.style.display="flex",$&&P($)}),sn.addEventListener("click",async()=>{let e=ot.value.trim();if(!e)return;let t=$+"/"+e;try{let s=await fetch(T("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!s.ok){let i=await s.json();I.textContent=i.error||"Create failed";return}ot.value="",$&&P($)}catch{I.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let e=lt.classList.toggle("open");Ye.classList.toggle("open",e)}),Ye.addEventListener("click",()=>{lt.classList.remove("open"),Ye.classList.remove("open")});var W="",fe=document.createElement("div");fe.className="session-popover-backdrop",document.body.appendChild(fe);var D=document.createElement("div");D.id="session-popover",D.className="session-popover",D.style.display="none",D.innerHTML=`<div class="sp-field">
|
|
36
36
|
<label>Rename session</label>
|
|
37
37
|
<input type="text" id="sp-name" />
|
|
38
38
|
</div>
|
|
@@ -41,5 +41,5 @@ var Xt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function
|
|
|
41
41
|
<button class="btn" id="sp-cancel">Cancel</button>
|
|
42
42
|
</div>
|
|
43
43
|
<hr class="sp-divider" />
|
|
44
|
-
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(D);function wn(e){let t=e.getBoundingClientRect();D.style.top=t.bottom+4+"px",D.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function bn(e,t){
|
|
45
|
-
`).length;S.rows=Math.min(e,4)}),S.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Et())})),Lt&&Lt.addEventListener("click",Et),window.visualViewport){let e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var Oe=document.getElementById("process-panel"),
|
|
44
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(D);function wn(e){let t=e.getBoundingClientRect();D.style.top=t.bottom+4+"px",D.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function bn(e,t){W=e;let s=document.getElementById("sp-name");s.value=e,fe.classList.add("open"),D.style.display="block",wn(t),setTimeout(()=>s.focus(),50)}function me(){fe.classList.remove("open"),D.style.display="none"}fe.addEventListener("click",me),document.getElementById("sp-save").addEventListener("click",async()=>{let e=document.getElementById("sp-name").value.trim();if(!e||e===W){me();return}try{if(!(await fetch(T("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:W,newName:e})})).ok)return;me(),J(),y===W&&de(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+W+'"?'))try{if(!(await fetch(T("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:W})})).ok)return;me(),y===W&&(h&&(h.destroy(),h=null),y=null,M.style.display="none",Y.style.display="flex"),J()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",me);var Ln={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{let t=e.target.closest("button[data-key]");if(!t||!h)return;let s=Ln[t.dataset.key];s&&(h.sendInput(s),h.focus())});var S=document.getElementById("mk-input"),Lt=document.getElementById("mk-send");function Et(){if(!S||!h)return;let e=S.value;e&&(h.sendInput(e+"\r"),S.value="",S.rows=1,h.focus())}if(S&&(S.addEventListener("input",()=>{S.rows=1;let e=S.value.split(`
|
|
45
|
+
`).length;S.rows=Math.min(e,4)}),S.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Et())})),Lt&&Lt.addEventListener("click",Et),window.visualViewport){let e=document.querySelector(".app-layout"),t=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",t),window.addEventListener("orientationchange",()=>setTimeout(t,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var Oe=document.getElementById("process-panel"),ve=document.getElementById("proc-list"),En=document.getElementById("proc-close"),Ke=document.getElementById("proc-backdrop"),xn=document.getElementById("header-ram");function kn(){ve.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(T("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){ve.innerHTML='<div class="proc-empty">No processes</div>';return}let t="";for(let s of e){let i=s.rss,o=i<1048576?(i/1024).toFixed(0)+"K":(i/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+s.pid+'"><span class="proc-mem">'+s.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+m(s.command)+'">'+m(s.command)+'</span><button class="proc-kill" title="Kill PID '+s.pid+'">×</button></div>'}ve.innerHTML=t;for(let s of ve.querySelectorAll(".proc-row")){let i=s.querySelector(".proc-kill");i&&i.addEventListener("click",o=>{let r=o.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(T("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(r.dataset.pid,10)})}).then(d=>d.json()).then(d=>{d.ok?r.style.opacity="0.3":alert("Failed: "+(d.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{ve.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Cn(){Oe.classList.add("open"),Ke.classList.add("open"),kn()}function $e(){Oe.classList.remove("open"),Ke.classList.remove("open")}xn.addEventListener("click",e=>{e.stopPropagation(),Oe.classList.contains("open")?$e():Cn()}),En.addEventListener("click",$e),Ke.addEventListener("click",$e),window.__openSession=de,window.__refreshSidebar=J,J(),Ue(),setInterval(()=>{document.visibilityState==="visible"&&J()},1e4);
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import{initTerminal as ke}from"./terminal-core.js";const T=window.__TMUX_WEB_SHELL__;let p=null,u=null;const ie=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let v=ie;window.__tmuxWebScopeAgent=ie;let Y="sessions",E="",j="",W="",oe="";function y(e){return v?"/a/"+encodeURIComponent(v)+e:e}const r=document.getElementById("sidebar-content"),C=document.getElementById("main-placeholder"),h=document.getElementById("terminal-container"),H=document.getElementById("file-editor"),Ce=document.getElementById("fe-path"),b=document.getElementById("fe-status"),$=document.getElementById("fe-content"),I=document.getElementById("fe-save"),Ie=document.getElementById("fe-delete"),
|
|
1
|
+
import{initTerminal as ke}from"./terminal-core.js";const T=window.__TMUX_WEB_SHELL__;let p=null,u=null;const ie=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null;let v=ie;window.__tmuxWebScopeAgent=ie;let Y="sessions",E="",j="",W="",oe="";function y(e){return v?"/a/"+encodeURIComponent(v)+e:e}const r=document.getElementById("sidebar-content"),C=document.getElementById("main-placeholder"),h=document.getElementById("terminal-container"),H=document.getElementById("file-editor"),Ce=document.getElementById("fe-path"),b=document.getElementById("fe-status"),$=document.getElementById("fe-content"),I=document.getElementById("fe-save"),Ie=document.getElementById("fe-delete"),xe=document.getElementById("fe-back"),ae=document.getElementById("fe-new-name"),Be=document.getElementById("fe-new-btn"),Z=document.getElementById("settings-backdrop"),le=document.getElementById("settings-popover"),ce=document.querySelector(".sidebar");function Me(){return window.matchMedia("(max-width: 640px)").matches}function Q(){Me()&&ce.classList.add("collapsed")}function de(){ce.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{de(),re("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{de(),re("files")});function re(e){Y=e,document.querySelectorAll(".mode-btn").forEach(t=>t.classList.remove("active"));const n=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");n&&n.classList.add("active"),e==="sessions"?(ee="",M()):Ae()}let ee="";async function M(){if(Y!=="sessions")return;try{const t=await(await fetch("/api/sidebar/sessions")).json(),s=JSON.stringify({recent:t.recent,agents:t.agents});if(s===ee)return;ee=s,Se(t)}catch{r.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}const e=document.getElementById("ns-btn");e&&e.addEventListener("click",()=>{const n=window.__openNewSessionModal;typeof n=="function"?n():document.getElementById("new-session-modal")?.classList.add("open")})}function Se(e){r.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';const n=e.recent||[],t=document.createElement("div");t.className="sidebar-section-label"+(v?" clickable":""),t.textContent=v?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",v&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t);for(const i of n){const l=document.createElement("div");l.className="session-item"+(i.name===u&&!v?" active":""),l.setAttribute("data-session",i.name),l.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${i.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
|
|
2
2
|
<span>${d(i.name)}</span>
|
|
3
3
|
<button class="session-edit-btn" data-session="${d(i.name)}" title="Edit session"><svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 000-1.41l-2.34-2.34a1 1 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg></button>
|
|
4
4
|
`,l.addEventListener("click",c=>{c.target.closest(".session-edit-btn")||R(i.name)}),r.appendChild(l);const o=l.querySelector(".session-edit-btn");o&&o.addEventListener("click",c=>{c.stopPropagation(),Oe(i.name,o)})}const s=e.agents||[];for(const i of s){if(!i.sessions||i.sessions.length===0)continue;const l=i.agentId===v,o=document.createElement("div");o.className="sidebar-section-label machine-label"+(i.online?"":" agent-offline-label")+(!l&&i.online?" clickable":""),o.textContent=i.agentName+(i.online?"":" (offline)"),!l&&i.online&&(o.title="\u6253\u5F00 "+i.agentName,o.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(i.agentId)+"/"})),r.appendChild(o);for(const c of i.sessions){const a=document.createElement("div");a.className="session-item"+(c.name===u&&v===i.agentId?" active":"")+(i.online?"":" session-offline"),a.setAttribute("data-session",c.name),a.setAttribute("data-agent",i.agentId),a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${c.attached?"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H5V6h14v12z":"M4 6h16v2H4V6zm0 5h16v2H4v-2zm0 5h16v2H4v-2z"}"></svg>
|
|
5
|
-
<span>${d(c.name)}</span><span class="meta">${i.online?"":"offline"}</span>`,i.online&&a.addEventListener("click",g=>{g.target.closest(".session-edit-btn")||R(c.name,i.agentId)}),r.appendChild(a)}}}function d(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function R(e,n){if(!(u===e&&p&&v===(n||null))){H.style.display="none",C.style.display="none",h.style.display="",h.classList.add("terminal-pending"),Q(),p&&(K(),p.destroy(),p=null),u=e,v=n||null,window.__tmuxWebScopeAgent=v;for(const t of r.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e&&(t.getAttribute("data-agent")||null)===v);try{p=await ke(h,e,{terminal:T.terminal,scrollback:T.scrollback,theme:T.theme,wsBase:v?"/ws/a/"+encodeURIComponent(v):T.wsBase||"/ws"}),we()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);h.textContent="Failed to open terminal for "+e+": "+s,h.classList.remove("terminal-pending")}}}let z=[],f=null,pe=!1;function Te(e){if(!f||!f.files.length)return null;const n=f.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of f.files)if(s.path===t)return s.status;return null}async function He(e){pe=!0;try{const n=await fetch(y("/api/git/status?path="+encodeURIComponent(e)));n.ok?f=await n.json():f=null}catch{f=null}finally{pe=!1}}function $e(){if(!f||!f.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=f;let s=`<div class="sidebar-git"><div class="git-branch-badge">
|
|
5
|
+
<span>${d(c.name)}</span><span class="meta">${i.online?"":"offline"}</span>`,i.online&&a.addEventListener("click",g=>{g.target.closest(".session-edit-btn")||R(c.name,i.agentId)}),r.appendChild(a)}}}function d(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function R(e,n){if(!(u===e&&p&&v===(n||null))){H.style.display="none",C.style.display="none",h.style.display="",h.classList.add("terminal-pending"),Q(),p&&(K(),p.destroy(),p=null),u=e,v=n||null,window.__tmuxWebScopeAgent=v,window.__tmuxWebCurrentSession=u;for(const t of r.querySelectorAll(".session-item"))t.classList.toggle("active",t.getAttribute("data-session")===e&&(t.getAttribute("data-agent")||null)===v);try{p=await ke(h,e,{terminal:T.terminal,scrollback:T.scrollback,theme:T.theme,wsBase:v?"/ws/a/"+encodeURIComponent(v):T.wsBase||"/ws"}),we()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);h.textContent="Failed to open terminal for "+e+": "+s,h.classList.remove("terminal-pending")}}}let z=[],f=null,pe=!1;function Te(e){if(!f||!f.files.length)return null;const n=f.repoRoot;if(!n||!e.startsWith(n))return null;const t=e.slice(n.length+1);for(const s of f.files)if(s.path===t)return s.status;return null}async function He(e){pe=!0;try{const n=await fetch(y("/api/git/status?path="+encodeURIComponent(e)));n.ok?f=await n.json():f=null}catch{f=null}finally{pe=!1}}function $e(){if(!f||!f.branch)return"";const{branch:e,linesAdded:n,linesRemoved:t}=f;let s=`<div class="sidebar-git"><div class="git-branch-badge">
|
|
6
6
|
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M11.63 1.22a.75.75 0 00-1.06 0L5.99 5.8 4.12 3.93a.75.75 0 10-1.06 1.06L4.93 6.87A3.5 3.5 0 003 10.09v3.16a.75.75 0 001 0v-3.16a2.5 2.5 0 012.5-2.5h.27l-1.06 1.06a.75.75 0 001.06 1.06l2.37-2.38a.75.75 0 000-1.06L7.59 5.27l4.04-4.05a.75.75 0 000-1.06L11.63 1.22zM3.25 13.94a.75.75 0 01-.75-.75v-.69a.75.75 0 011.5 0v.69c0 .414-.336.75-.75.75z"/></svg>
|
|
7
|
-
${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&S&&f&&(e.stopPropagation(),ge())});const N=document.getElementById("header-git"),fe=document.getElementById("git-diff-view"),ue=document.getElementById("gd-path"),O=document.getElementById("gd-status"),U=document.getElementById("gd-content"),Re=document.getElementById("gd-back");let S=null;function
|
|
7
|
+
${d(e)}</div>`;return(n>0||t>0)&&(s+=`<div class="git-diff-stats">+<span class="add">${n}</span> <span class="del">-${t}</span></div>`),s+"</div>"}r.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&S&&f&&(e.stopPropagation(),ge())});const N=document.getElementById("header-git"),fe=document.getElementById("git-diff-view"),ue=document.getElementById("gd-path"),O=document.getElementById("gd-status"),U=document.getElementById("gd-content"),Re=document.getElementById("gd-back");let S=null;function _(e){if(!e||!e.branch){N.style.display="none",S=null;return}S=e.repoRoot;const{branch:n,linesAdded:t,linesRemoved:s}=e;let i=`<span class="branch">${d(n)}</span>`;(t>0||s>0)&&(i+=`<span class="sep">\xB7</span>+<span class="diff-add">${t}</span> <span class="diff-del">-${s}</span>`),N.innerHTML=i,N.style.display="block"}N.addEventListener("click",e=>{S&&f&&(e.stopPropagation(),ge())});const m=document.getElementById("git-popover"),G=document.getElementById("git-popover-backdrop");function te(){const e=N.getBoundingClientRect();m.style.top=e.bottom+4+"px",m.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function me(){const e=S,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(q(),e){Y="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),k(e)}})}let ve="";function ge(){if(!f)return;const e=f.files||[];if(!e.length)m.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
8
8
|
<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let n=`<div class="git-popover-header">${e.length} file${e.length>1?"s":""} changed</div>`;for(let t=0;t<e.length;t++){const s=e[t],i=s.additions||0,l=s.deletions||0;let o="";switch(s.status){case"M":o='<span class="git-file-status M">M</span>';break;case"A":o='<span class="git-file-status A">A</span>';break;case"D":o='<span class="git-file-status D">D</span>';break;case"?":o='<span class="git-file-status ?">?</span>';break;case"R":o='<span class="git-file-status R">R</span>';break}n+=`<div class="git-popover-item" data-idx="${t}">
|
|
9
9
|
${o}<span class="file-path">${d(s.path)}</span>
|
|
10
10
|
<span class="file-add">${i>0?"+"+i:""}</span>
|
|
11
11
|
<span class="file-del">${l>0?"-"+l:""}</span>
|
|
12
12
|
</div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ve=n,m.innerHTML=n,m.querySelectorAll(".git-popover-item").forEach(t=>{const s=parseInt(t.dataset.idx||"0",10),i=e[s];!i||i.status==="D"||(t.classList.add("clickable"),t.style.cursor="pointer",t.addEventListener("click",()=>he(i.path)))})}te(),m.classList.add("open"),G.classList.add("open"),me()}function Ne(){return window.innerWidth>=1024}function ye(e,n){const s=(e+(n&&e?`
|
|
13
13
|
`:"")+n).split(`
|
|
14
|
-
`);let i="",l=0,o=0;for(const c of s){const a=c;if(a.startsWith("@@")){const g=a.match(/@@\s+-(\d+)[^+]*\+(\d+)/);g&&(l=parseInt(g[1],10)-1,o=parseInt(g[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(a)}</span></div>`;continue}if(a.startsWith("diff --git")||a.startsWith("index ")||a.startsWith("--- ")||a.startsWith("+++ ")||a.startsWith("new file")||a.startsWith("deleted file")){a.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(a)}</span></div>`);continue}if(a.startsWith("+"))o++,i+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("-"))l++,i+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(a)}</span></div>`;else{if(!a&&!l&&!o)continue;l++,o++,i+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${d(a)}</span></div>`}}return{html:i,lineCount:s.length}}let V=null;async function he(e){const n=S;if(n)if(q(),Ne()){h.style.display="none",p&&(K(),p.destroy(),p=null,V=u,u=null),
|
|
14
|
+
`);let i="",l=0,o=0;for(const c of s){const a=c;if(a.startsWith("@@")){const g=a.match(/@@\s+-(\d+)[^+]*\+(\d+)/);g&&(l=parseInt(g[1],10)-1,o=parseInt(g[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(a)}</span></div>`;continue}if(a.startsWith("diff --git")||a.startsWith("index ")||a.startsWith("--- ")||a.startsWith("+++ ")||a.startsWith("new file")||a.startsWith("deleted file")){a.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(a)}</span></div>`);continue}if(a.startsWith("+"))o++,i+=`<div class="diff-line add"><span class="ln-no">${o}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("-"))l++,i+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${d(a)}</span></div>`;else if(a.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(a)}</span></div>`;else{if(!a&&!l&&!o)continue;l++,o++,i+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${o}</span><span class="ln-body">${d(a)}</span></div>`}}return{html:i,lineCount:s.length}}let V=null;async function he(e){const n=S;if(n)if(q(),Ne()){h.style.display="none",p&&(K(),p.destroy(),p=null,V=u,u=null),_(null),H.style.display="none",C.style.display="none",fe.style.display="flex",ue.textContent=e,O.textContent="Loading\u2026",U.innerHTML="";try{const t=await fetch(y("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();De(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);O.textContent="Error",U.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
15
15
|
<button class="diff-back">\u2190 Back</button>
|
|
16
16
|
<span class="diff-filename">${d(e)}</span>
|
|
17
17
|
<span class="diff-loading">Loading\u2026</span>
|
|
18
|
-
</div></div>`,te(),m.classList.add("open"),G.classList.add("open");try{const t=await fetch(y("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();
|
|
18
|
+
</div></div>`,te(),m.classList.add("open"),G.classList.add("open");try{const t=await fetch(y("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();_e(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
19
19
|
<button class="diff-back">\u2190 Back</button>
|
|
20
20
|
<span class="diff-filename">${d(e)}</span>
|
|
21
|
-
</div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function
|
|
21
|
+
</div><div class="diff-empty">Error loading diff: ${d(s)}</div></div>`}}}function _e(e,n,t){if(!n&&!t){m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
22
22
|
<button class="diff-back">\u2190 Back</button>
|
|
23
23
|
<span class="diff-filename">${d(e)}</span>
|
|
24
24
|
</div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=ye(n,t);m.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
25
25
|
<button class="diff-back">\u2190 Back</button>
|
|
26
26
|
<span class="diff-filename">${d(e)}</span>
|
|
27
27
|
<span class="diff-filesize">${i} lines</span>
|
|
28
|
-
</div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=ve,te(),f&&m.querySelectorAll(".git-popover-item").forEach(l=>{const o=parseInt(l.dataset.idx||"0",10),c=f.files[o];!c||c.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>he(c.path)))}),me()})}function
|
|
28
|
+
</div><div class="diff-content">${s}</div></div>`,m.querySelector(".diff-back")?.addEventListener("click",()=>{m.innerHTML=ve,te(),f&&m.querySelectorAll(".git-popover-item").forEach(l=>{const o=parseInt(l.dataset.idx||"0",10),c=f.files[o];!c||c.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>he(c.path)))}),me()})}function De(e,n,t){if(!n&&!t){O.textContent="No diff",U.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}const{html:s,lineCount:i}=ye(n,t);ue.textContent=e,O.textContent=i+" lines",U.innerHTML=s}function Pe(){fe.style.display="none",V?(h.style.display="",h.classList.add("terminal-pending"),R(V),V=null):u?(h.style.display="",h.classList.add("terminal-pending"),we()):C.style.display="flex"}Re.addEventListener("click",Pe);function q(){m.classList.remove("open"),G.classList.remove("open")}G.addEventListener("click",q),document.addEventListener("keydown",e=>{e.key==="Escape"&&(q(),X())});async function be(e){try{const t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){const s=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(s.ok){const i=await s.json();f=i,_(i);return}}}catch{}f=null,_(null)}let J=null;function we(){K(),u&&(be(u),J=setInterval(()=>{u&&be(u)},3e4))}function K(){J&&(clearInterval(J),J=null)}function Ae(){if(z=T.fsRoots||[],z.length===0){if(u){Fe(u);return}r.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}r.innerHTML="";for(const e of z){const n=document.createElement("div");n.className="file-tree-item",n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(e)}`,n.addEventListener("click",()=>k(e)),r.appendChild(n)}}async function Fe(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(y("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?k(t.path):r.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{r.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function k(e){E=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=He(e);try{const s=await(await fetch(y("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",_(f),r.innerHTML+=$e();const i=je(e);if(i&&!z.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>k(i)),r.appendChild(c)}if(s.dirs)for(const o of s.dirs){const c=o.split("/").pop()||o.split("\\").pop(),a=document.createElement("div");a.className="file-tree-item",a.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor" class="file-icon"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg> ${d(c)}`,a.addEventListener("click",()=>k(o)),r.appendChild(a)}else r.innerHTML="";const l=s.files||[];for(const o of l){const c=o.split("/").pop()||o.split("\\").pop(),a=Te(o);let g="",B="";if(a)switch(a){case"M":g="git-mod",B='<span class="git-file-status M">M</span>';break;case"A":g="git-add",B='<span class="git-file-status A">A</span>';break;case"D":g="git-del",B='<span class="git-file-status D">D</span>';break;case"?":g="git-untracked",B='<span class="git-file-status ?">?</span>';break;case"R":g="git-mod",B='<span class="git-file-status R">R</span>';break}const F=document.createElement("div");F.className="file-tree-item file"+(g?" "+g:""),F.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${B}${d(c)}`,F.addEventListener("click",()=>We(o)),r.appendChild(F)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function je(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function We(e){j=e,h.style.display="none",p&&(K(),p.destroy(),p=null,u=null),_(null),Q(),C.style.display="none",H.style.display="flex",Ce.textContent=e,b.textContent="Loading...",I.style.display="none";try{const n=await fetch(y("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();b.textContent=s.error||"Failed to load",$.value="";return}const t=await n.json();$.value=t.content,W=t.content,oe=t.content,b.textContent=t.size+" bytes",I.style.display=""}catch{b.textContent="Failed to load",$.value=""}}$.addEventListener("input",()=>{W=$.value}),I.addEventListener("click",async()=>{I.textContent="Saving...",I.disabled=!0;try{const e=await fetch(y("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:j,content:W})});if(!e.ok){const n=await e.json();b.textContent=n.error||"Save failed";return}oe=W,b.textContent="Saved"}catch{b.textContent="Save failed"}finally{I.disabled=!1,I.textContent="Save"}}),Ie.addEventListener("click",async()=>{if(confirm("Delete "+j+"?"))try{const e=await fetch(y("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:j})});if(!e.ok){const n=await e.json();b.textContent=n.error||"Delete failed";return}H.style.display="none",C.style.display="flex",E&&k(E)}catch{b.textContent="Delete failed"}}),xe.addEventListener("click",()=>{H.style.display="none",C.style.display="flex",E&&k(E)}),Be.addEventListener("click",async()=>{const e=ae.value.trim();if(!e)return;const n=E+"/"+e;try{const t=await fetch(y("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();b.textContent=s.error||"Create failed";return}ae.value="",E&&k(E)}catch{b.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{const e=le.classList.toggle("open");Z.classList.toggle("open",e)}),Z.addEventListener("click",()=>{le.classList.remove("open"),Z.classList.remove("open")});let x="";const D=document.createElement("div");D.className="session-popover-backdrop",document.body.appendChild(D);const L=document.createElement("div");L.id="session-popover",L.className="session-popover",L.style.display="none",L.innerHTML=`<div class="sp-field">
|
|
29
29
|
<label>Rename session</label>
|
|
30
30
|
<input type="text" id="sp-name" />
|
|
31
31
|
</div>
|
|
@@ -34,5 +34,5 @@ import{initTerminal as ke}from"./terminal-core.js";const T=window.__TMUX_WEB_SHE
|
|
|
34
34
|
<button class="btn" id="sp-cancel">Cancel</button>
|
|
35
35
|
</div>
|
|
36
36
|
<hr class="sp-divider" />
|
|
37
|
-
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(L);function ze(e){const n=e.getBoundingClientRect();L.style.top=n.bottom+4+"px",L.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function Oe(e,n){
|
|
38
|
-
`).length;w.rows=Math.min(e,4)}),w.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Ee())})),Le&&Le.addEventListener("click",Ee),window.visualViewport){const e=document.querySelector(".app-layout"),n=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",n),window.addEventListener("orientationchange",()=>setTimeout(n,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}const ne=document.getElementById("process-panel"),
|
|
37
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(L);function ze(e){const n=e.getBoundingClientRect();L.style.top=n.bottom+4+"px",L.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function Oe(e,n){x=e;const t=document.getElementById("sp-name");t.value=e,D.classList.add("open"),L.style.display="block",ze(n),setTimeout(()=>t.focus(),50)}function P(){D.classList.remove("open"),L.style.display="none"}D.addEventListener("click",P),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===x){P();return}try{if(!(await fetch(y("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:x,newName:e})})).ok)return;P(),M(),u===x&&R(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+x+'"?'))try{if(!(await fetch(y("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:x})})).ok)return;P(),u===x&&(p&&(p.destroy(),p=null),u=null,h.style.display="none",C.style.display="flex"),M()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",P);const Ue={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r","ctrl-r":"","ctrl-c":"",btw:"/btw ",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",e=>{const n=e.target.closest("button[data-key]");if(!n||!p)return;const t=Ue[n.dataset.key];t&&(p.sendInput(t),p.focus())});const w=document.getElementById("mk-input"),Le=document.getElementById("mk-send");function Ee(){if(!w||!p)return;const e=w.value;e&&(p.sendInput(e+"\r"),w.value="",w.rows=1,p.focus())}if(w&&(w.addEventListener("input",()=>{w.rows=1;const e=w.value.split(`
|
|
38
|
+
`).length;w.rows=Math.min(e,4)}),w.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Ee())})),Le&&Le.addEventListener("click",Ee),window.visualViewport){const e=document.querySelector(".app-layout"),n=()=>{e&&(e.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",n),window.addEventListener("orientationchange",()=>setTimeout(n,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}const ne=document.getElementById("process-panel"),A=document.getElementById("proc-list"),Ge=document.getElementById("proc-close"),se=document.getElementById("proc-backdrop"),Ve=document.getElementById("header-ram");function qe(){A.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(y("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){A.innerHTML='<div class="proc-empty">No processes</div>';return}let n="";for(const t of e){const s=t.rss,i=s<1048576?(s/1024).toFixed(0)+"K":(s/1048576).toFixed(1)+"M";n+='<div class="proc-row" data-pid="'+t.pid+'"><span class="proc-mem">'+t.mem+'%</span><span class="proc-rss">'+i+'</span><span class="proc-cmd" title="'+d(t.command)+'">'+d(t.command)+'</span><button class="proc-kill" title="Kill PID '+t.pid+'">×</button></div>'}A.innerHTML=n;for(const t of A.querySelectorAll(".proc-row")){const s=t.querySelector(".proc-kill");s&&s.addEventListener("click",i=>{const l=i.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch(y("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(o=>o.json()).then(o=>{o.ok?l.style.opacity="0.3":alert("Failed: "+(o.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{A.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Je(){ne.classList.add("open"),se.classList.add("open"),qe()}function X(){ne.classList.remove("open"),se.classList.remove("open")}Ve.addEventListener("click",e=>{e.stopPropagation(),ne.classList.contains("open")?X():Je()}),Ge.addEventListener("click",X),se.addEventListener("click",X),window.__openSession=R,window.__refreshSidebar=M,M(),Q(),setInterval(()=>{document.visibilityState==="visible"&&M()},1e4);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const Oe=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ne(s){return s.replace(/\x1b\[\?1049[hl]/g,"")}function
|
|
2
|
-
`)}selectRows(o,i){const n=this.terminal.buffer.active,[a,f]=o<=i?[o,i]:[i,o];this.terminal.selectLines(n.viewportY+a,n.viewportY+f)}selectCells(o,i,n,a){const f=this.terminal.buffer.active,h=Math.min(i,a),l=Math.max(i,a);if(h===l){const d=Math.min(o,n),g=Math.max(o,n);this.terminal.select(d,f.viewportY+h,g-d+1)}else this.terminal.selectLines(f.viewportY+h,f.viewportY+l)}getCellsText(o,i,n,a){const f=this.terminal.buffer.active,h=Math.min(i,a),l=Math.max(i,a),d=i<a?o:i>a?n:Math.min(o,n),g=i<a?n:i>a?o:Math.max(o,n),$=f.viewportY,
|
|
3
|
-
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function Re(s){const o=s.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(o.width,i.width),height:Math.max(0,Math.min(o.height,i.height-Math.max(0,o.top)))}:o}function Xe(s,o,i){return s.innerHTML="",s.classList.add("terminal-pending"),(async()=>{const n=await oe.create(s,i.scrollback,i.theme);let a,f=0,h,l=null,d=0,g=0;const $=.2,
|
|
4
|
-
\x1B[31m`+
|
|
5
|
-
`).then(()=>{H();try{a?.close()}catch{}});return}if(t.type==="auth.ok"){t.token&&localStorage.setItem("tmux-web-token",t.token),
|
|
1
|
+
const Oe=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function ne(s){return s.replace(/\x1b\[\?1049[hl]/g,"")}function R(s){return s.replace(Oe,"")}let X;function Ne(){return X||(document.querySelector("link[data-tmux-web-xterm-css]")?X=Promise.resolve():(X=new Promise(o=>{const i=document.createElement("link");i.rel="stylesheet",i.href="/assets/xterm.css",i.setAttribute("data-tmux-web-xterm-css",""),i.addEventListener("load",()=>o(),{once:!0}),i.addEventListener("error",()=>o(),{once:!0}),document.head.appendChild(i)}),X))}class oe{terminal;fitAddon;container;constructor(o,i,n){this.container=o,this.terminal=i,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(o)}static async create(o,i,n){const[{Terminal:a},{FitAddon:f}]=await Promise.all([import("@xterm/xterm"),import("@xterm/addon-fit"),Ne()]),h=new a({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,smoothScrollDuration:120,theme:n});return new oe(o,h,new f)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(o){return new Promise(i=>this.terminal.write(o,i))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(o,i=!1){this.terminal.scrollToLine(Math.max(0,o),i)}scrollLines(o){const i=this.terminal.buffer?.active;this.terminal.scrollLines(o)}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){const o=Re(this.container);return o.width<=0||o.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(o){this.terminal.paste(o)}input(o){this.terminal.input(o,!1)}onData(o){this.terminal.onData(o),this.terminal.onBinary(o)}onResize(o){this.terminal.onResize(o)}onScroll(o){this.terminal.onScroll(o)}attachCustomKeyEventHandler(o){this.terminal.attachCustomKeyEventHandler(o)}isFocused(){const o=document.activeElement;return o?o===this.container||o===this.terminal.textarea||this.container.contains(o)||!!o.closest?.(".xterm"):!1}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(o,i){const n=this.terminal.buffer.active,[a,f]=o<=i?[o,i]:[i,o],h=n.viewportY,l=[];for(let d=a;d<=f;d++){const g=n.getLine(h+d);l.push(g?g.translateToString(!0):"")}return l.join(`
|
|
2
|
+
`)}selectRows(o,i){const n=this.terminal.buffer.active,[a,f]=o<=i?[o,i]:[i,o];this.terminal.selectLines(n.viewportY+a,n.viewportY+f)}selectCells(o,i,n,a){const f=this.terminal.buffer.active,h=Math.min(i,a),l=Math.max(i,a);if(h===l){const d=Math.min(o,n),g=Math.max(o,n);this.terminal.select(d,f.viewportY+h,g-d+1)}else this.terminal.selectLines(f.viewportY+h,f.viewportY+l)}getCellsText(o,i,n,a){const f=this.terminal.buffer.active,h=Math.min(i,a),l=Math.max(i,a),d=i<a?o:i>a?n:Math.min(o,n),g=i<a?n:i>a?o:Math.max(o,n),$=f.viewportY,M=[];for(let E=h;E<=l;E++){const q=f.getLine($+E);if(!q){M.push("");continue}const D=E===h?d:0,Q=E===l?g:this.terminal.cols-1;let P="";for(let w=D;w<=Q;w++){const L=q.getCell(w);L&&(P+=L.getChars())}M.push(P.replace(/\s+$/,""))}return M.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}}function Re(s){const o=s.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(o.width,i.width),height:Math.max(0,Math.min(o.height,i.height-Math.max(0,o.top)))}:o}function Xe(s,o,i){return s.innerHTML="",s.classList.add("terminal-pending"),(async()=>{const n=await oe.create(s,i.scrollback,i.theme);let a,f=0,h,l=null,d=0,g=0;const $=.2,M=10,E=.95,q=.04;function D(){d&&(cancelAnimationFrame(d),d=0),g=0}function Q(e){if(D(),Math.abs(e)<$)return;g=Math.max(-M,Math.min(M,e));const t=()=>{if(b){d=0;return}const r=s.querySelector(".xterm-rows > div"),c=r?r.getBoundingClientRect().height:16;let u=c>0?Math.round(g*16/c):g>0?1:-1;u===0&&(u=g>0?1:-1);const v=n.viewportY()-u,G=n.baseY(),N=Math.max(0,Math.min(v,G));if(n.scrollToLine(N,!0),g*=E,N!==v){d=0;return}if(Math.abs(g)<q){d=0;return}d=requestAnimationFrame(t)};d=requestAnimationFrame(t)}let P=0,w="connecting",L=0,Y=!1,U=[],I="",b=!1,F=1e3;const me=location.protocol==="https:"?"wss:":"ws:",pe=i.wsBase||"/ws",he=me+"//"+location.host+pe+"/"+encodeURIComponent(o),ge="/api/session/"+encodeURIComponent(o)+"/upload",we=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function ye(){return U.join("")+I}async function ve(e,t=0){const r=e?n.viewportY():0,c=R(ye());if(n.reset(),!c){e||n.scrollToBottom();return}await n.write(c),e?n.scrollToLine(t+r):n.scrollToBottom()}function C(e){a?.readyState===WebSocket.OPEN&&a.send(JSON.stringify(e))}function Te(e){let t;try{t=JSON.parse(e)}catch{w==="live"&&(I+=e,n.write(R(e)));return}if(t.type==="auth.required"){const r=localStorage.getItem("tmux-web-token");r?C({type:"auth.token",token:r}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(t.type==="attach_failed"&&typeof t.message=="string"){_=!0,w="dead",n.reset(),n.write(`\r
|
|
4
|
+
\x1B[31m`+R(t.message)+`\x1B[0m\r
|
|
5
|
+
`).then(()=>{H();try{a?.close()}catch{}});return}if(t.type==="auth.ok"){t.token&&localStorage.setItem("tmux-web-token",t.token),F=1e3,S(),C({type:"resize",cols:n.cols,rows:n.rows}),y();return}if(t.type==="auth.failed"){if(t.permanentLock||t.retryAfterMs&&t.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(t.message);return}return}if(t.type==="snapshot"&&typeof t.data=="string"){const r=ne(R(t.data));U=[r],I="",L=typeof t.lines=="number"?t.lines:i.terminal.initialLines,w="live",n.reset(),S(),C({type:"resize",cols:n.cols,rows:n.rows}),n.write(r).then(()=>n.scrollToBottom());return}if(t.type==="window_changed"&&typeof t.activeIndex=="number"){const r=new URL(location.href);r.searchParams.get("window")!==String(t.activeIndex)&&(r.searchParams.set("window",String(t.activeIndex)),history.replaceState(history.state,"",r)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:t.windows}));return}if(t.type==="history"&&typeof t.data=="string"){t.lines>0&&t.data?(U.unshift(ne(t.data)),L+=t.lines,ve(!0,t.lines).finally(()=>{Y=!1})):Y=!1;return}if(t.type==="data"&&typeof t.data=="string"){const r=ne(R(t.data));if(w==="connecting"){w="live",I=r,n.write(r);return}w==="live"&&(I+=r,n.write(r))}}let _=!1;function re(){if(_)return;let e=he;const t=localStorage.getItem("tmux-web-token");t&&(e+=(e.includes("?")?"&":"?")+"token="+encodeURIComponent(t)),e+=(e.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,a=new WebSocket(e),a.onopen=()=>{w="connecting",L=0,Y=!1,U=[],I="",n.reset(),F=1e3},a.onmessage=r=>{typeof r.data=="string"&&Te(r.data)},a.onclose=()=>{b||(w="connecting",!_&&setTimeout(()=>{F=Math.min(F*2,1e4),!b&&!_&&re()},F))},a.onerror=()=>a?.close()}function j(e){C({type:"input",data:e})}function S(){return n.fit()}function H(){s.classList.remove("terminal-pending")}function y(){f&&cancelAnimationFrame(f),h&&clearTimeout(h),S(),f=requestAnimationFrame(()=>{f=0,S()&&H()}),h=setTimeout(()=>{S()&&H()},120)}function xe(){y(),setTimeout(y,50),setTimeout(y,150),setTimeout(y,300)}function ie(e){return typeof e=="string"&&e.startsWith("image/")}function se(e){return e?.files?.length?e.files[0]??null:null}function be(e){if(!e?.items)return null;for(let t=0;t<e.items.length;t++){const r=e.items[t];if(r?.kind==="file"&&ie(r.type))return r.getAsFile()}return null}async function Ee(e){const t=new FormData,r=e instanceof File&&e.name?e.name:"upload";t.append("file",e,r);const c=localStorage.getItem("tmux-web-token"),u={};c&&(u.Authorization="Bearer "+c);const x=await fetch(ge,{method:"POST",body:t,headers:u});if(!x.ok){let G="upload failed";try{const N=await x.json();N.error&&(G=N.error)}catch{}throw new Error(G)}const v=await x.json();if(!v.path)throw new Error("no path in response");return v.path}async function Z(e){try{const t=await Ee(e);j(t)}catch{}}n.onData(e=>j(e)),n.onResize(({cols:e,rows:t})=>C({type:"resize",cols:e,rows:t})),n.onScroll(()=>{w!=="live"||Y||!n.isNearScrollbackTop()||(Y=!0,C({type:"load_history",before:L}))});let A=!1,K=!1,m=null,p=null;const B=document.createElement("div");B.className="terminal-copy-banner",B.innerHTML='<strong>Copy mode</strong><span class="hint">drag to copy \xB7 Esc exits</span>',B.hidden=!0,s.appendChild(B);const T=document.createElement("div");T.className="terminal-copy-banner terminal-copy-toast",T.textContent="Copied",T.hidden=!0,s.appendChild(T);function ae(e,t){const r=s.querySelector(".xterm-screen, canvas"),u=(r&&r.getBoundingClientRect().width>0?r:s).getBoundingClientRect(),x=u.height>0&&n.rows>0?u.height/n.rows:18,v=u.width>0&&n.cols>0?u.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((t-u.top)/x))),col:Math.max(0,Math.min(n.cols-1,Math.floor((e-u.left)/v)))}}async function V(e){if(!e)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(e);return}}catch{}const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",t.style.pointerEvents="none",document.body.appendChild(t),t.focus(),t.select();try{document.execCommand("copy")}catch{}t.remove()}let z;function J(){T.hidden=!1,z&&clearTimeout(z),z=setTimeout(()=>{T.hidden=!0},1e3)}function k(e){A=e,B.hidden=!e,s.classList.toggle("terminal-copy-mode-active",e),e&&n.focus()}function le(){k(!A)}function Le(e){b||e.button!==0||K||(A||e.shiftKey)&&(e.preventDefault(),e.stopPropagation(),K=!0,m=ae(e.clientX,e.clientY),p=m,n.selectCells(m.col,m.row,p.col,p.row),document.addEventListener("mousemove",ee,!0),document.addEventListener("mouseup",te,!0))}function ee(e){b||!K||!m||(e.preventDefault(),e.stopPropagation(),p=ae(e.clientX,e.clientY),n.selectCells(m.col,m.row,p.col,p.row))}function te(e){if(b||!K||!m||!p)return;e.preventDefault(),e.stopPropagation(),document.removeEventListener("mousemove",ee,!0),document.removeEventListener("mouseup",te,!0),K=!1;const t=n.getCellsText(m.col,m.row,p.col,p.row);m=null,p=null,n.clearSelection(),k(!1),t&&(T.textContent="\u5DF2\u590D\u5236",V(t).then(J)),n.focus()}s.addEventListener("mousedown",Le,!0),document.addEventListener("keydown",Ce,!0);function Ce(e){if(!(b||!n.isFocused()||e.defaultPrevented||e.isComposing)){if(A){if(e.key==="Escape"){e.preventDefault(),e.stopPropagation(),m=null,p=null,n.clearSelection(),k(!1);return}if(e.key==="Enter"||(e.ctrlKey||e.metaKey)&&e.code==="KeyC"){e.preventDefault(),e.stopPropagation(),V(n.getScreenText()).then(J),k(!1);return}}if(e.altKey&&e.code==="KeyC"&&!e.ctrlKey&&!e.metaKey){if(e.preventDefault(),e.stopPropagation(),m&&p){const t=n.getCellsText(m.col,m.row,p.col,p.row);m=null,p=null,n.clearSelection(),k(!1),t&&(T.textContent="Copied",V(t).then(J))}else{const t=n.getSelection();t?(n.clearSelection(),T.textContent="Copied",V(t).then(J)):le()}return}if((e.ctrlKey||e.metaKey)&&e.code==="KeyV"){e.preventDefault(),e.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){const t=await navigator.clipboard.read();for(const r of t)for(const c of r.types)if(ie(c)){const u=await r.getType(c);await Z(u);return}}}catch{}try{const t=await navigator.clipboard?.readText();t&&n.pasteText(ce(t))}catch{}})();return}}}let W=0;function ce(e){return e.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function
|
|
8
|
+
`).join("\r")}async function Se(e){const t=be(e.clipboardData);if(t){e.preventDefault(),e.stopPropagation();const u=Date.now();if(u-W<50)return;W=u,await Z(t);return}const r=e.clipboardData?.getData("text/plain");if(!r)return;e.preventDefault(),e.stopPropagation();const c=Date.now();c-W<50||(W=c,n.pasteText(ce(r)))}let O=0;function Me(e){e.preventDefault(),O++,se(e.dataTransfer)&&s.classList.add("terminal-drag-over")}function De(e){e.preventDefault(),e.dataTransfer&&(e.dataTransfer.dropEffect="copy")}function Pe(e){e.preventDefault(),O--,O<=0&&(O=0,s.classList.remove("terminal-drag-over"))}async function Ie(e){e.preventDefault(),O=0,s.classList.remove("terminal-drag-over");const t=se(e.dataTransfer);t&&await Z(t)}function ue(e){!we||e.key!=="Escape"||A||e.defaultPrevented||e.repeat||e.metaKey||e.ctrlKey||e.altKey||n.isFocused()&&(e.preventDefault(),e.stopPropagation(),j("\x1B"))}s.addEventListener("paste",Se),s.addEventListener("dragenter",Me),s.addEventListener("dragover",De),s.addEventListener("dragleave",Pe),s.addEventListener("drop",Ie),document.addEventListener("keydown",ue,!0);function ke(e){if(e.touches.length!==1){l=null;return}const t=e.touches[0];t&&(D(),l={startX:t.clientX,startY:t.clientY,lastX:t.clientX,lastY:t.clientY,lastT:performance.now(),velocity:0,scrolling:!1},e.stopPropagation())}function Ye(e){if(!l||e.touches.length!==1)return;const t=e.touches[0];if(!t)return;const r=t.clientY-l.startY,c=t.clientX-l.startX;if(!l.scrolling){if(Math.abs(r)<8||Math.abs(r)<Math.abs(c))return;l.scrolling=!0,P=Date.now()+500}e.preventDefault(),e.stopPropagation();const u=performance.now(),x=t.clientY-l.lastY,v=u-l.lastT;v>0&&(l.velocity=x/v),Be(-x),l.lastX=t.clientX,l.lastY=t.clientY,l.lastT=u}function Fe(e){if(!l)return;const t=l.scrolling,r=l.velocity;if(l=null,e.stopPropagation(),!t){n.focus();return}P=Date.now()+500,e.preventDefault(),r&&Q(r)}function Ae(e){l=null,D(),e.stopPropagation()}function Ke(e){e.pointerType==="touch"&&Date.now()<P&&(e.preventDefault(),e.stopPropagation())}function Be(e){const t=s.querySelector(".xterm-rows > div"),r=t?t.getBoundingClientRect().height:16;let c=r>0?Math.round(e/r):e>0?1:-1;c===0&&(c=e>0?1:-1),n.scrollLines(c)}s.addEventListener("touchstart",ke,{passive:!0,capture:!0}),s.addEventListener("touchmove",Ye,{passive:!1,capture:!0}),s.addEventListener("touchend",Fe,{passive:!1,capture:!0}),s.addEventListener("touchcancel",Ae,{passive:!0,capture:!0}),s.addEventListener("pointerup",Ke,!0);function fe(){y()}window.addEventListener("resize",fe),window.visualViewport?.addEventListener("resize",y),window.visualViewport?.addEventListener("scroll",y);const de=new ResizeObserver(()=>y());de.observe(s),document.fonts?.ready.then(()=>{y(),setTimeout(y,50),setTimeout(y,200)}),s.addEventListener("focusin",xe,!0);async function ze(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(e=>setTimeout(e,1e3))])}catch{}}return requestAnimationFrame(()=>{S()&&H(),(async()=>(await ze(),S()&&C({type:"resize",cols:n.cols,rows:n.rows}),re(),y()))()}),{destroy(){if(b=!0,D(),k(!1),z&&clearTimeout(z),document.removeEventListener("mousemove",ee,!0),document.removeEventListener("mouseup",te,!0),a){try{a.close(1e3,"session switch")}catch{}a=void 0}s.innerHTML="",s.classList.remove("terminal-pending"),window.removeEventListener("resize",fe),de.disconnect(),document.removeEventListener("keydown",ue,!0)},focus(){n.focus()},sendInput(e){j(e)},toggleCopyMode(){le()}}})()}export{Xe as initTerminal,ne as stripAltScreenSequences};
|
package/dist/lib/pages/shell.js
CHANGED
|
@@ -53,7 +53,9 @@ import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSes
|
|
|
53
53
|
height: 100dvh;
|
|
54
54
|
overflow: hidden;
|
|
55
55
|
padding-top: calc(48px + env(safe-area-inset-top, 0px));
|
|
56
|
-
transition: height
|
|
56
|
+
/* No height transition: on iOS WebKit a 50ms animated height makes
|
|
57
|
+
ResizeObserver fire with intermediate sizes while load_history
|
|
58
|
+
rewrites the buffer, corrupting line wrapping (see issue #4). */
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
/* \u2500\u2500 Sidebar \u2500\u2500 */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as
|
|
1
|
+
import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as r}from"./icons.js";const d=[{id:"vscode",name:"VS Code",dot:"#007acc"},{id:"ghostty",name:"Ghostty",dot:"#ff5f00"},{id:"warm-clay",name:"Warm Clay",dot:"#b86b52"},{id:"dark-cove",name:"Dark Cove",dot:"#7aa2f7"}];function n(e="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${e}; outline: none;`}function v(e=""){return`
|
|
2
2
|
@media (prefers-reduced-motion: reduce) {
|
|
3
3
|
*, *::before, *::after {
|
|
4
4
|
animation-duration: 0.01ms !important;
|
|
@@ -7,15 +7,15 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
7
7
|
scroll-behavior: auto !important;
|
|
8
8
|
}
|
|
9
9
|
${e}
|
|
10
|
-
}`}function w(e="vscode"){const
|
|
10
|
+
}`}function w(e="vscode"){const o=d.find(t=>t.id===e)??d[0],a=d.map(t=>{const s=t.id===e;return`<button type="button" class="theme-option${s?" active":""}" data-theme="${t.id}" role="menuitem">
|
|
11
11
|
<span class="theme-dot" style="background:${t.dot}"></span>
|
|
12
12
|
${c(t.name)}
|
|
13
13
|
${s?i("check",'class="theme-check" aria-hidden="true"'):""}
|
|
14
14
|
</button>`}).join(`
|
|
15
15
|
`);return`<div class="theme-switcher" id="theme-switcher">
|
|
16
16
|
<button type="button" class="theme-switcher-btn" aria-label="Theme" aria-haspopup="true" aria-expanded="false">
|
|
17
|
-
<span class="theme-dot" style="background:${
|
|
18
|
-
<span>${c(
|
|
17
|
+
<span class="theme-dot" style="background:${o.dot}"></span>
|
|
18
|
+
<span>${c(o.name)}</span>
|
|
19
19
|
</button>
|
|
20
20
|
<div class="theme-switcher-popover" role="menu">
|
|
21
21
|
${a}
|
|
@@ -47,7 +47,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
47
47
|
document.addEventListener('click', (e) => {
|
|
48
48
|
if (switcher.classList.contains('open') && !switcher.contains(e.target)) close();
|
|
49
49
|
});
|
|
50
|
-
})();`}function
|
|
50
|
+
})();`}function S(e=""){return`
|
|
51
51
|
*, *::before, *::after { box-sizing: border-box; }
|
|
52
52
|
html, body { background: var(--page-bg); color: var(--page-fg); min-height: 100%; font-family: var(--font-sans); margin: 0; padding: 0; }
|
|
53
53
|
|
|
@@ -75,7 +75,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
75
75
|
font-size: var(--text-sm); text-decoration: none;
|
|
76
76
|
}
|
|
77
77
|
.header-btn:hover, .icon-btn:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
78
|
-
.header-btn:focus-visible, .icon-btn:focus-visible { ${
|
|
78
|
+
.header-btn:focus-visible, .icon-btn:focus-visible { ${n()} }
|
|
79
79
|
.header-btn svg, .icon-btn svg { width: 18px; height: 18px; fill: currentColor; flex-shrink: 0; }
|
|
80
80
|
|
|
81
81
|
/* \u2500\u2500 Theme switcher popover \u2500\u2500 */
|
|
@@ -88,7 +88,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
88
88
|
font-size: var(--text-sm); text-decoration: none; font-family: inherit;
|
|
89
89
|
}
|
|
90
90
|
.theme-switcher-btn:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
91
|
-
.theme-switcher-btn:focus-visible { ${
|
|
91
|
+
.theme-switcher-btn:focus-visible { ${n()} }
|
|
92
92
|
.theme-switcher-btn svg { width: 18px; height: 18px; fill: currentColor; flex-shrink: 0; }
|
|
93
93
|
.theme-switcher-popover {
|
|
94
94
|
position: absolute; top: calc(100% + 6px); right: 0;
|
|
@@ -104,7 +104,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
104
104
|
color: var(--page-fg); font-size: var(--text-sm); cursor: pointer; text-align: left; font-family: inherit;
|
|
105
105
|
}
|
|
106
106
|
.theme-option:hover { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); color: var(--panel-accent); }
|
|
107
|
-
.theme-option:focus-visible { ${
|
|
107
|
+
.theme-option:focus-visible { ${n()} }
|
|
108
108
|
.theme-option.active { color: var(--panel-accent); font-weight: 500; }
|
|
109
109
|
.theme-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
|
|
110
110
|
.theme-check { width: 16px; height: 16px; margin-left: auto; color: var(--panel-accent); }
|
|
@@ -138,7 +138,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
138
138
|
text-align: left;
|
|
139
139
|
}
|
|
140
140
|
.sidebar-btn:hover { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); color: var(--panel-accent); }
|
|
141
|
-
.sidebar-btn:focus-visible { ${
|
|
141
|
+
.sidebar-btn:focus-visible { ${n()} }
|
|
142
142
|
.sidebar-btn.primary {
|
|
143
143
|
background: var(--panel-accent); border-color: var(--panel-accent); color: var(--panel-accent-on);
|
|
144
144
|
font-weight: 500; margin-bottom: 16px; justify-content: center;
|
|
@@ -197,7 +197,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
197
197
|
color: var(--page-fg); transition: opacity 0.15s;
|
|
198
198
|
}
|
|
199
199
|
.modal-btn:hover { opacity: 0.85; }
|
|
200
|
-
.modal-btn:focus-visible { ${
|
|
200
|
+
.modal-btn:focus-visible { ${n()} }
|
|
201
201
|
.modal-btn.confirm {
|
|
202
202
|
background: var(--panel-accent); border-color: var(--panel-accent);
|
|
203
203
|
color: var(--panel-accent-on); font-weight: 500;
|
|
@@ -217,7 +217,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
${v()}
|
|
220
|
-
${e}`}function
|
|
220
|
+
${e}`}function R(e){const{title:o="TMUX Sessions",themeTemplate:a="vscode"}=e;return`<script>
|
|
221
221
|
(function() {
|
|
222
222
|
const token = localStorage.getItem('tmux-web-token');
|
|
223
223
|
if (token) {
|
|
@@ -247,10 +247,10 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
247
247
|
</a>
|
|
248
248
|
</div>
|
|
249
249
|
</header>
|
|
250
|
-
<script>${y()}</script>`}function
|
|
251
|
-
<svg viewBox="0 0 24 24" fill="currentColor">${
|
|
252
|
-
${
|
|
253
|
-
</a>`}const s=
|
|
250
|
+
<script>${y()}</script>`}function A(e){const{activePage:o,refreshHref:a}=e;function t(l,b,f,x,g=""){return`<a href="${b}" class="${l!==null&&l===o?"sidebar-btn current":"sidebar-btn"}"${g}>
|
|
251
|
+
<svg viewBox="0 0 24 24" fill="currentColor">${f}</svg>
|
|
252
|
+
${x}
|
|
253
|
+
</a>`}const s=r("sessions"),p=r("notes"),h=r("history"),m=r("quick-commands"),u=r("file"),k=r("refresh");return`<aside class="action-sidebar">
|
|
254
254
|
<p class="sidebar-label">Actions</p>
|
|
255
255
|
<button class="sidebar-btn primary" id="new-session-btn">
|
|
256
256
|
${i("add")}
|
|
@@ -260,14 +260,14 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
260
260
|
${t("home","/",s,"Sessions")}
|
|
261
261
|
${t("notes","/notes",p,"All Notes")}
|
|
262
262
|
${t("files","/files",u,"Files")}
|
|
263
|
-
${t("history","/history",
|
|
264
|
-
${t("quickCommands","/quick-commands",
|
|
263
|
+
${t("history","/history",h,"History")}
|
|
264
|
+
${t("quickCommands","/quick-commands",m,"Quick Commands")}
|
|
265
265
|
<hr class="sidebar-divider">
|
|
266
266
|
<a href="${a}" class="sidebar-btn">
|
|
267
267
|
${i("refresh")}
|
|
268
268
|
Refresh
|
|
269
269
|
</a>
|
|
270
|
-
</aside>`}function
|
|
270
|
+
</aside>`}function T(){return` /* \u2500\u2500 New session modal \u2500\u2500 */
|
|
271
271
|
.modal-backdrop {
|
|
272
272
|
display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.3);
|
|
273
273
|
z-index: 500; align-items: center; justify-content: center;
|
|
@@ -303,6 +303,43 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
303
303
|
.modal-dropdown-item:hover, .modal-dropdown-item.active, .modal-dropdown-item:focus-visible {
|
|
304
304
|
background: color-mix(in srgb, var(--panel-accent) 8%, transparent); color: var(--panel-accent);
|
|
305
305
|
}
|
|
306
|
+
.ns-dir-wrap { position: relative; }
|
|
307
|
+
.ns-dir-wrap input { padding-right: 46px; }
|
|
308
|
+
.ns-dir-browse {
|
|
309
|
+
position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
|
|
310
|
+
width: 34px; height: 34px; border: none; border-radius: 9px;
|
|
311
|
+
background: transparent; color: var(--panel-muted); font-size: 14px; cursor: pointer;
|
|
312
|
+
display: flex; align-items: center; justify-content: center;
|
|
313
|
+
}
|
|
314
|
+
.ns-dir-browse:hover {
|
|
315
|
+
background: color-mix(in srgb, var(--panel-accent) 10%, transparent); color: var(--panel-accent);
|
|
316
|
+
}
|
|
317
|
+
.ns-dir-tree {
|
|
318
|
+
display: none; position: absolute; left: 0; right: 0; top: 100%;
|
|
319
|
+
margin-top: 4px; max-height: 260px; overflow-y: auto; z-index: 10;
|
|
320
|
+
background: var(--panel-bg); border: 1px solid var(--panel-border);
|
|
321
|
+
border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
|
322
|
+
padding: 6px 0;
|
|
323
|
+
}
|
|
324
|
+
.ns-dir-tree.open { display: block; }
|
|
325
|
+
.ns-tree-row {
|
|
326
|
+
display: flex; align-items: center; min-height: 36px;
|
|
327
|
+
padding: 4px 10px 4px 0; font-size: var(--text-sm); color: var(--page-fg);
|
|
328
|
+
cursor: pointer; white-space: nowrap; user-select: none;
|
|
329
|
+
}
|
|
330
|
+
.ns-tree-row:hover, .ns-tree-row.active { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
331
|
+
.ns-tree-row.current > .ns-tree-label { color: var(--panel-accent); font-weight: 600; }
|
|
332
|
+
.ns-tree-arrow {
|
|
333
|
+
width: 20px; height: 20px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
|
|
334
|
+
color: var(--panel-muted); font-size: 10px; border-radius: 5px;
|
|
335
|
+
}
|
|
336
|
+
.ns-tree-arrow:hover {
|
|
337
|
+
background: color-mix(in srgb, var(--panel-accent) 14%, transparent); color: var(--panel-accent);
|
|
338
|
+
}
|
|
339
|
+
.ns-tree-arrow.leaf { visibility: hidden; }
|
|
340
|
+
.ns-tree-icon { flex-shrink: 0; margin: 0 6px 0 2px; font-size: 13px; }
|
|
341
|
+
.ns-tree-label { overflow: hidden; text-overflow: ellipsis; }
|
|
342
|
+
.ns-tree-empty { color: var(--panel-muted); font-size: var(--text-sm); padding: 8px 12px; }
|
|
306
343
|
.modal-dropdown-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--panel-accent); }
|
|
307
344
|
.modal-error { font-size: var(--text-sm); color: #b91c1c; margin-bottom: 12px; display: none; }
|
|
308
345
|
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 8px; }
|
|
@@ -312,13 +349,13 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
312
349
|
color: var(--page-fg); transition: opacity 0.15s;
|
|
313
350
|
}
|
|
314
351
|
.modal-btn:hover { opacity: 0.85; }
|
|
315
|
-
.modal-btn:focus-visible { ${
|
|
352
|
+
.modal-btn:focus-visible { ${n()} }
|
|
316
353
|
.modal-btn.confirm {
|
|
317
354
|
background: var(--panel-accent); border-color: var(--panel-accent);
|
|
318
355
|
color: var(--panel-accent-on); font-weight: 500;
|
|
319
356
|
}
|
|
320
357
|
.modal-btn.confirm:hover { opacity: 0.9; }
|
|
321
|
-
`}function
|
|
358
|
+
`}function B(){return`<div class="modal-backdrop" id="new-session-modal" role="dialog" aria-modal="true" aria-label="Create new tmux session">
|
|
322
359
|
<div class="modal-panel">
|
|
323
360
|
<h2>New Session</h2>
|
|
324
361
|
<div class="modal-field">
|
|
@@ -327,8 +364,12 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
327
364
|
</div>
|
|
328
365
|
<div class="modal-field">
|
|
329
366
|
<label for="ns-dir">Start directory</label>
|
|
330
|
-
<
|
|
367
|
+
<div class="ns-dir-wrap">
|
|
368
|
+
<input type="text" id="ns-dir" placeholder="~" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="ns-dir-list" />
|
|
369
|
+
<button type="button" class="ns-dir-browse" id="ns-dir-browse" title="Browse directories" aria-label="Browse directories">📁</button>
|
|
370
|
+
</div>
|
|
331
371
|
<div class="modal-dropdown" id="ns-dir-list" role="listbox"></div>
|
|
372
|
+
<div class="ns-dir-tree" id="ns-dir-tree" role="tree" aria-label="Directory tree"></div>
|
|
332
373
|
</div>
|
|
333
374
|
<p class="modal-error" id="ns-error"></p>
|
|
334
375
|
<div class="modal-actions">
|
|
@@ -336,7 +377,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
336
377
|
<button class="modal-btn confirm" id="ns-submit">Create</button>
|
|
337
378
|
</div>
|
|
338
379
|
</div>
|
|
339
|
-
</div>`}function
|
|
380
|
+
</div>`}function z(e){return`(function() {
|
|
340
381
|
const modal = document.getElementById('new-session-modal');
|
|
341
382
|
const openBtn = document.getElementById('new-session-btn');
|
|
342
383
|
const cancelBtn = document.getElementById('ns-cancel');
|
|
@@ -368,10 +409,10 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
368
409
|
errorEl.style.display = 'none';
|
|
369
410
|
errorEl.textContent = '';
|
|
370
411
|
closeDropdown();
|
|
371
|
-
setTimeout(() => nameInput.focus(), 50);
|
|
412
|
+
openTreeForSession().then(() => setTimeout(() => nameInput.focus(), 50));
|
|
372
413
|
}
|
|
373
414
|
|
|
374
|
-
function closeModal() { modal.classList.remove('open'); }
|
|
415
|
+
function closeModal() { modal.classList.remove('open'); closeDropdown(); closeTree(); }
|
|
375
416
|
|
|
376
417
|
if (openBtn) openBtn.addEventListener('click', openModal);
|
|
377
418
|
cancelBtn.addEventListener('click', closeModal);
|
|
@@ -420,6 +461,7 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
420
461
|
|
|
421
462
|
dirInput.addEventListener('input', () => {
|
|
422
463
|
clearTimeout(debounceTimer);
|
|
464
|
+
closeTree();
|
|
423
465
|
const val = dirInput.value.trim();
|
|
424
466
|
if (!val) { closeDropdown(); return; }
|
|
425
467
|
debounceTimer = setTimeout(async () => {
|
|
@@ -433,6 +475,181 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
433
475
|
|
|
434
476
|
dirInput.addEventListener('blur', () => { setTimeout(closeDropdown, 120); });
|
|
435
477
|
|
|
478
|
+
// \u2500\u2500 Directory tree (expandable, lazy-loads each level) \u2500\u2500
|
|
479
|
+
const treePanel = document.getElementById('ns-dir-tree');
|
|
480
|
+
const browseBtn = document.getElementById('ns-dir-browse');
|
|
481
|
+
let treeCache = {}; // path -> dirs[] (full paths)
|
|
482
|
+
let treeRoot = '/';
|
|
483
|
+
let treeExpanded = new Set(); // paths currently expanded
|
|
484
|
+
let treeSelected = ''; // path currently selected (highlighted)
|
|
485
|
+
let treeRows = []; // visible rows [{path, el}] for keyboard nav
|
|
486
|
+
let treeActiveIdx = -1;
|
|
487
|
+
let treeOpen = false;
|
|
488
|
+
|
|
489
|
+
function treeEsc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); }
|
|
490
|
+
function treeBase(p) {
|
|
491
|
+
if (p === '/' || p === '~') return p;
|
|
492
|
+
const parts = p.split('/').filter(Boolean);
|
|
493
|
+
return parts[parts.length - 1] || '/';
|
|
494
|
+
}
|
|
495
|
+
async function treeLoadDirs(path) {
|
|
496
|
+
if (treeCache[path]) return treeCache[path];
|
|
497
|
+
try {
|
|
498
|
+
const res = await fetch(selectedBase() + '/api/fs/list?path=' + encodeURIComponent(path));
|
|
499
|
+
const data = await res.json();
|
|
500
|
+
const dirs = (data.dirs || []).filter(d => d !== path).sort((a, b) => a.localeCompare(b));
|
|
501
|
+
treeCache[path] = dirs;
|
|
502
|
+
return dirs;
|
|
503
|
+
} catch { treeCache[path] = []; return []; }
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function treeRowEl(path, depth) {
|
|
507
|
+
const row = document.createElement('div');
|
|
508
|
+
row.className = 'ns-tree-row' + (treeSelected === path ? ' current' : '');
|
|
509
|
+
row.dataset.path = path;
|
|
510
|
+
row.style.paddingLeft = (12 + depth * 18) + 'px';
|
|
511
|
+
const arrow = document.createElement('span');
|
|
512
|
+
arrow.className = 'ns-tree-arrow' + (treeExpanded.has(path) ? '' : ' leaf');
|
|
513
|
+
arrow.textContent = treeExpanded.has(path) ? '\u25BE' : '\u25B8';
|
|
514
|
+
const icon = document.createElement('span');
|
|
515
|
+
icon.className = 'ns-tree-icon';
|
|
516
|
+
icon.textContent = '\u{1F4C1}';
|
|
517
|
+
const label = document.createElement('span');
|
|
518
|
+
label.className = 'ns-tree-label';
|
|
519
|
+
label.textContent = treeBase(path);
|
|
520
|
+
label.title = path;
|
|
521
|
+
row.append(arrow, icon, label);
|
|
522
|
+
// Arrow toggles expansion (lazy-loads that level).
|
|
523
|
+
arrow.addEventListener('click', (e) => {
|
|
524
|
+
e.stopPropagation();
|
|
525
|
+
void treeToggle(path, depth);
|
|
526
|
+
});
|
|
527
|
+
// Clicking the row selects the directory.
|
|
528
|
+
row.addEventListener('click', () => {
|
|
529
|
+
dirInput.value = path;
|
|
530
|
+
closeTree();
|
|
531
|
+
dirInput.focus();
|
|
532
|
+
});
|
|
533
|
+
return row;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function treeToggle(path, depth) {
|
|
537
|
+
if (treeExpanded.has(path)) treeExpanded.delete(path);
|
|
538
|
+
else treeExpanded.add(path);
|
|
539
|
+
await treeRender();
|
|
540
|
+
if (treeExpanded.has(path)) {
|
|
541
|
+
// After expanding, reveal any newly visible selected row.
|
|
542
|
+
const el = document.querySelector('.ns-tree-row.current');
|
|
543
|
+
if (el) el.scrollIntoView({ block: 'nearest' });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function treeRender() {
|
|
548
|
+
treePanel.innerHTML = '';
|
|
549
|
+
treeRows = [];
|
|
550
|
+
const rootRow = treeRowEl(treeRoot, 0);
|
|
551
|
+
treePanel.appendChild(rootRow);
|
|
552
|
+
treeRows.push({ path: treeRoot, el: rootRow });
|
|
553
|
+
if (treeExpanded.has(treeRoot)) {
|
|
554
|
+
await treeRenderChildren(treeRoot, 0, rootRow);
|
|
555
|
+
}
|
|
556
|
+
treeRefreshActive();
|
|
557
|
+
if (!treeRows.length) treePanel.innerHTML = '<div class="ns-tree-empty">No directories</div>';
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function treeRenderChildren(path, depth, parentRow) {
|
|
561
|
+
if (!treeExpanded.has(path)) return;
|
|
562
|
+
const dirs = await treeLoadDirs(path);
|
|
563
|
+
const frag = document.createDocumentFragment();
|
|
564
|
+
for (const d of dirs) {
|
|
565
|
+
const row = treeRowEl(d, depth + 1);
|
|
566
|
+
frag.appendChild(row);
|
|
567
|
+
treeRows.push({ path: d, el: row });
|
|
568
|
+
}
|
|
569
|
+
parentRow.after(frag);
|
|
570
|
+
for (const d of dirs) {
|
|
571
|
+
if (treeExpanded.has(d)) {
|
|
572
|
+
const row = treePanel.querySelector('.ns-tree-row[data-path="' + CSS.escape(d) + '"]');
|
|
573
|
+
if (row) await treeRenderChildren(d, depth + 1, row);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Expand the tree down to the given path (each level lazy-loads and expands).
|
|
579
|
+
async function treeExpandTo(path) {
|
|
580
|
+
treeExpanded.clear();
|
|
581
|
+
treeExpanded.add(treeRoot);
|
|
582
|
+
let cur = treeRoot;
|
|
583
|
+
let acc = '';
|
|
584
|
+
const segs = String(path).split('/').filter(Boolean);
|
|
585
|
+
for (const seg of segs) {
|
|
586
|
+
acc = acc ? acc + '/' + seg : '/' + seg;
|
|
587
|
+
if (cur === '~') cur = acc;
|
|
588
|
+
else if (cur === '/') cur = '/' + seg;
|
|
589
|
+
else cur = cur + '/' + seg;
|
|
590
|
+
await treeLoadDirs(cur);
|
|
591
|
+
treeExpanded.add(cur);
|
|
592
|
+
}
|
|
593
|
+
treeSelected = cur;
|
|
594
|
+
await treeRender();
|
|
595
|
+
const el = treePanel.querySelector('.ns-tree-row.current');
|
|
596
|
+
if (el) el.scrollIntoView({ block: 'nearest' });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function treeRefreshActive() {
|
|
600
|
+
treeActiveIdx = Math.max(-1, Math.min(treeActiveIdx, treeRows.length - 1));
|
|
601
|
+
treeRows.forEach((r, i) => r.el.classList.toggle('active', i === treeActiveIdx));
|
|
602
|
+
if (treeActiveIdx >= 0 && treeRows[treeActiveIdx]) treeRows[treeActiveIdx].el.scrollIntoView({ block: 'nearest' });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function closeTree() {
|
|
606
|
+
treePanel.classList.remove('open');
|
|
607
|
+
treePanel.innerHTML = '';
|
|
608
|
+
treeOpen = false;
|
|
609
|
+
treeExpanded = new Set();
|
|
610
|
+
treeSelected = '';
|
|
611
|
+
treeRows = [];
|
|
612
|
+
treeActiveIdx = -1;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function openTree() {
|
|
616
|
+
closeDropdown();
|
|
617
|
+
const v = dirInput.value.trim();
|
|
618
|
+
const target = (!v || v === '~') ? '/' : (v.startsWith('/') ? v : '/' + v);
|
|
619
|
+
treeRoot = '/';
|
|
620
|
+
treePanel.classList.add('open');
|
|
621
|
+
treeOpen = true;
|
|
622
|
+
await treeExpandTo(target);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// On modal open, expand the tree directly to the currently selected
|
|
626
|
+
// session's working directory (window.__tmuxWebCurrentSession is set by
|
|
627
|
+
// the shell client; falls back to / otherwise).
|
|
628
|
+
async function openTreeForSession() {
|
|
629
|
+
const session = (typeof window.__tmuxWebCurrentSession === 'string' && window.__tmuxWebCurrentSession)
|
|
630
|
+
? window.__tmuxWebCurrentSession : '';
|
|
631
|
+
let target = '/';
|
|
632
|
+
if (session) {
|
|
633
|
+
try {
|
|
634
|
+
const res = await fetch(selectedBase() + '/api/fs/session-path?session=' + encodeURIComponent(session));
|
|
635
|
+
const data = await res.json();
|
|
636
|
+
if (data && typeof data.path === 'string' && data.path) {
|
|
637
|
+
dirInput.value = data.path;
|
|
638
|
+
target = data.path.startsWith('/') ? data.path : '/' + data.path;
|
|
639
|
+
}
|
|
640
|
+
} catch {}
|
|
641
|
+
}
|
|
642
|
+
treeRoot = '/';
|
|
643
|
+
treePanel.classList.add('open');
|
|
644
|
+
treeOpen = true;
|
|
645
|
+
await treeExpandTo(target);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (browseBtn) browseBtn.addEventListener('click', () => {
|
|
649
|
+
if (treeOpen) { closeTree(); return; }
|
|
650
|
+
void openTree();
|
|
651
|
+
});
|
|
652
|
+
|
|
436
653
|
async function submit() {
|
|
437
654
|
const name = nameInput.value.trim();
|
|
438
655
|
const dir = dirInput.value.trim();
|
|
@@ -462,6 +679,29 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
462
679
|
submitBtn.addEventListener('click', submit);
|
|
463
680
|
nameInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') dirInput.focus(); });
|
|
464
681
|
dirInput.addEventListener('keydown', (e) => {
|
|
682
|
+
if (treeOpen && treeRows.length) {
|
|
683
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); treeActiveIdx = Math.min(treeActiveIdx + 1, treeRows.length - 1); treeRefreshActive(); return; }
|
|
684
|
+
if (e.key === 'ArrowUp') { e.preventDefault(); treeActiveIdx = Math.max(treeActiveIdx - 1, 0); treeRefreshActive(); return; }
|
|
685
|
+
if (e.key === 'ArrowRight') {
|
|
686
|
+
e.preventDefault();
|
|
687
|
+
const row = treeRows[Math.max(treeActiveIdx, 0)];
|
|
688
|
+
if (row && !treeExpanded.has(row.path)) void treeToggle(row.path, row.el.style.paddingLeft ? Math.round((parseInt(row.el.style.paddingLeft) - 12) / 18) : 0);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (e.key === 'ArrowLeft') {
|
|
692
|
+
e.preventDefault();
|
|
693
|
+
const row = treeRows[Math.max(treeActiveIdx, 0)];
|
|
694
|
+
if (row && treeExpanded.has(row.path)) void treeToggle(row.path, 0);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (e.key === 'Enter' && treeActiveIdx >= 0) {
|
|
698
|
+
e.preventDefault();
|
|
699
|
+
dirInput.value = treeRows[treeActiveIdx].path;
|
|
700
|
+
closeTree();
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeTree(); return; }
|
|
704
|
+
}
|
|
465
705
|
const open = dirList.classList.contains('open');
|
|
466
706
|
const items = dirList.querySelectorAll('.modal-dropdown-item');
|
|
467
707
|
if (open && items.length) {
|
|
@@ -477,4 +717,5 @@ import{escapeHtml as c}from"./html.js";import{icon as i,iconPath as n}from"./ico
|
|
|
477
717
|
}
|
|
478
718
|
if (e.key === 'Enter') submit();
|
|
479
719
|
});
|
|
480
|
-
|
|
720
|
+
window.__openNewSessionModal = openModal;
|
|
721
|
+
})();`}export{T as newSessionModalCSS,B as newSessionModalHTML,z as newSessionModalScript,v as reducedMotion,R as sharedHeader,S as sharedLayoutCSS,A as sharedSidebar,w as themeSwitcherButtonHTML,y as themeSwitcherScript};
|
package/dist/lib/tmux-env.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function n(){const e={...process.env};return delete e.TMUX,delete e.TMUX_PANE,!e.LC_ALL&&!e.LANG&&(e.LC_ALL=process.platform==="darwin"?"en_US.UTF-8":"C.UTF-8"),e}export{n as tmuxEnv};
|