tmux-weblink 2.0.27 → 2.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/shell-client.js +21 -21
- package/dist/browser/shell-client.js +16 -16
- package/dist/lib/pages/shell.js +22 -3
- package/dist/lib/sessions-sidebar.js +1 -1
- package/dist/lib/shared-layout.js +266 -25
- package/package.json +1 -1
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)}selectRows(t,
|
|
3
|
-
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function
|
|
4
|
-
\x1B[31m`+oe(
|
|
5
|
-
`).then(()=>{be();try{o?.close()}catch{}});return}if(
|
|
1
|
+
var Zt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function je(e){return e.replace(/\x1b\[\?1049[hl]/g,"")}function oe(e){return e.replace(Zt,"")}var ke;function Gt(){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 Qt=class Ct{terminal;fitAddon;container;constructor(t,a,n){this.container=t,this.terminal=a,this.fitAddon=n,this.terminal.loadAddon(this.fitAddon),this.terminal.open(t)}static async create(t,a,n){let[{Terminal:o},{FitAddon:r}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Gt()]),u=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:a,convertEol:!1,smoothScrollDuration:120,theme:n});return new Ct(t,u,new r)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(t){return new Promise(a=>this.terminal.write(t,a))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(t,a=!1){this.terminal.scrollToLine(Math.max(0,t),a)}scrollLines(t){let a=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=en(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,a){let n=this.terminal.buffer.active,[o,r]=t<=a?[t,a]:[a,t],u=n.viewportY,l=[];for(let c=o;c<=r;c++){let p=n.getLine(u+c);l.push(p?p.translateToString(!0):"")}return l.join(`
|
|
2
|
+
`)}selectRows(t,a){let n=this.terminal.buffer.active,[o,r]=t<=a?[t,a]:[a,t];this.terminal.selectLines(n.viewportY+o,n.viewportY+r)}selectCells(t,a,n,o){let r=this.terminal.buffer.active,u=Math.min(a,o),l=Math.max(a,o);if(u===l){let c=Math.min(t,n),p=Math.max(t,n);this.terminal.select(c,r.viewportY+u,p-c+1)}else this.terminal.selectLines(r.viewportY+u,r.viewportY+l)}getCellsText(t,a,n,o){let r=this.terminal.buffer.active,u=Math.min(a,o),l=Math.max(a,o),c=a<o?t:a>o?n:Math.min(t,n),p=a<o?n:a>o?t:Math.max(t,n),L=r.viewportY,D=[];for(let z=u;z<=l;z++){let he=r.getLine(L+z);if(!he){D.push("");continue}let U=z===u?c:0,Ne=z===l?p:this.terminal.cols-1,K="";for(let C=U;C<=Ne;C++){let _=he.getCell(C);_&&(K+=_.getChars())}D.push(K.replace(/\s+$/,""))}return D.join(`
|
|
3
|
+
`)}getSelection(){return this.terminal.getSelection()}clearSelection(){this.terminal.clearSelection()}hasSelectionSupport(){return!0}};function en(e){let t=e.getBoundingClientRect(),a=window.visualViewport;return a?{width:Math.min(t.width,a.width),height:Math.max(0,Math.min(t.height,a.height-Math.max(0,t.top)))}:t}function tn(e,t,a){return e.innerHTML="",e.classList.add("terminal-pending"),(async()=>{let n=await Qt.create(e,a.scrollback,a.theme),o,r=0,u,l=null,c=0,p=0,L=.2,D=10,z=.95,he=.04;function U(){c&&(cancelAnimationFrame(c),c=0),p=0}function Ne(i){if(U(),Math.abs(i)<L)return;p=Math.max(-D,Math.min(D,i));let s=()=>{if(A){c=0;return}let d=e.querySelector(".xterm-rows > div"),f=d?d.getBoundingClientRect().height:16,b=f>0?Math.round(p*16/f):p>0?1:-1;b===0&&(b=p>0?1:-1);let S=n.viewportY()-b,Y=n.baseY(),se=Math.max(0,Math.min(S,Y));if(n.scrollToLine(se,!0),p*=z,se!==S){c=0;return}if(Math.abs(p)<he){c=0;return}c=requestAnimationFrame(s)};c=requestAnimationFrame(s)}let K=0,C="connecting",_=0,G=!1,ge=[],V="",A=!1,Q=1e3,Tt=location.protocol==="https:"?"wss:":"ws:",Mt=a.wsBase||"/ws",Bt=Tt+"//"+location.host+Mt+"/"+encodeURIComponent(t),St="/api/session/"+encodeURIComponent(t)+"/upload",It=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function Ht(){return ge.join("")+V}async function Dt(i,s=0){let d=i?n.viewportY():0,f=oe(Ht());if(n.reset(),!f){i||n.scrollToBottom();return}await n.write(f),i?n.scrollToLine(s+d):n.scrollToBottom()}function j(i){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(i))}function Rt(i){let s;try{s=JSON.parse(i)}catch{C==="live"&&(V+=i,n.write(oe(i)));return}if(s.type==="auth.required"){let d=localStorage.getItem("tmux-web-token");d?j({type:"auth.token",token:d}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(s.type==="attach_failed"&&typeof s.message=="string"){ye=!0,C="dead",n.reset(),n.write(`\r
|
|
4
|
+
\x1B[31m`+oe(s.message)+`\x1B[0m\r
|
|
5
|
+
`).then(()=>{be();try{o?.close()}catch{}});return}if(s.type==="auth.ok"){s.token&&localStorage.setItem("tmux-web-token",s.token),Q=1e3,F(),j({type:"resize",cols:n.cols,rows:n.rows}),T();return}if(s.type==="auth.failed"){if(s.permanentLock||s.retryAfterMs&&s.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(s.message);return}return}if(s.type==="snapshot"&&typeof s.data=="string"){let d=je(oe(s.data));ge=[d],V="",_=typeof s.lines=="number"?s.lines:a.terminal.initialLines,C="live",n.reset(),F(),j({type:"resize",cols:n.cols,rows:n.rows}),n.write(d).then(()=>n.scrollToBottom());return}if(s.type==="window_changed"&&typeof s.activeIndex=="number"){let d=new URL(location.href);d.searchParams.get("window")!==String(s.activeIndex)&&(d.searchParams.set("window",String(s.activeIndex)),history.replaceState(history.state,"",d)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:s.windows}));return}if(s.type==="history"&&typeof s.data=="string"){s.lines>0&&s.data?(ge.unshift(je(s.data)),_+=s.lines,Dt(!0,s.lines).finally(()=>{G=!1})):G=!1;return}if(s.type==="data"&&typeof s.data=="string"){let d=je(oe(s.data));if(C==="connecting"){C="live",V=d,n.write(d);return}C==="live"&&(V+=d,n.write(d))}}let ye=!1;function Ve(){if(ye)return;let i=Bt,s=localStorage.getItem("tmux-web-token");s&&(i+=(i.includes("?")?"&":"?")+"token="+encodeURIComponent(s)),i+=(i.includes("?")?"&":"?")+"cols="+n.cols+"&rows="+n.rows,o=new WebSocket(i),o.onopen=()=>{C="connecting",_=0,G=!1,ge=[],V="",n.reset(),Q=1e3},o.onmessage=d=>{typeof d.data=="string"&&Rt(d.data)},o.onclose=()=>{A||(C="connecting",!ye&&setTimeout(()=>{Q=Math.min(Q*2,1e4),!A&&!ye&&Ve()},Q))},o.onerror=()=>o?.close()}function we(i){j({type:"input",data:i})}function F(){return n.fit()}function be(){e.classList.remove("terminal-pending")}function T(){r&&cancelAnimationFrame(r),u&&clearTimeout(u),F(),r=requestAnimationFrame(()=>{r=0,F()&&be()}),u=setTimeout(()=>{F()&&be()},120)}function $t(){T(),setTimeout(T,50),setTimeout(T,150),setTimeout(T,300)}function Xe(i){return typeof i=="string"&&i.startsWith("image/")}function Je(i){return i?.files?.length?i.files[0]??null:null}function At(i){if(!i?.items)return null;for(let s=0;s<i.items.length;s++){let d=i.items[s];if(d?.kind==="file"&&Xe(d.type))return d.getAsFile()}return null}async function Nt(i){let s=new FormData,d=i instanceof File&&i.name?i.name:"upload";s.append("file",i,d);let f=localStorage.getItem("tmux-web-token"),b={};f&&(b.Authorization="Bearer "+f);let S=await fetch(St,{method:"POST",body:s,headers:b});if(!S.ok){let se="upload failed";try{let it=await S.json();it.error&&(se=it.error)}catch{}throw new Error(se)}let Y=await S.json();if(!Y.path)throw new Error("no path in response");return Y.path}async function Pe(i){try{let s=await Nt(i);we(s)}catch{}}n.onData(i=>we(i)),n.onResize(({cols:i,rows:s})=>j({type:"resize",cols:i,rows:s})),n.onScroll(()=>{C!=="live"||G||!n.isNearScrollbackTop()||(G=!0,j({type:"load_history",before:_}))});let ee=!1,te=!1,w=null,E=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 R=document.createElement("div");R.className="terminal-copy-banner terminal-copy-toast",R.textContent="Copied",R.hidden=!0,e.appendChild(R);function Ze(i,s){let d=e.querySelector(".xterm-screen, canvas"),f=(d&&d.getBoundingClientRect().width>0?d:e).getBoundingClientRect(),b=f.height>0&&n.rows>0?f.height/n.rows:18,S=f.width>0&&n.cols>0?f.width/n.cols:9;return{row:Math.max(0,Math.min(n.rows-1,Math.floor((s-f.top)/b))),col:Math.max(0,Math.min(n.cols-1,Math.floor((i-f.left)/S)))}}async function Le(i){if(!i)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(i);return}}catch{}let s=document.createElement("textarea");s.value=i,s.style.position="fixed",s.style.opacity="0",s.style.pointerEvents="none",document.body.appendChild(s),s.focus(),s.select();try{document.execCommand("copy")}catch{}s.remove()}let ie;function Ee(){R.hidden=!1,ie&&clearTimeout(ie),ie=setTimeout(()=>{R.hidden=!0},1e3)}function X(i){ee=i,ne.hidden=!i,e.classList.toggle("terminal-copy-mode-active",i),i&&n.focus()}function Ge(){X(!ee)}function Pt(i){A||i.button!==0||te||(ee||i.shiftKey)&&(i.preventDefault(),i.stopPropagation(),te=!0,w=Ze(i.clientX,i.clientY),E=w,n.selectCells(w.col,w.row,E.col,E.row),document.addEventListener("mousemove",ze,!0),document.addEventListener("mouseup",_e,!0))}function ze(i){A||!te||!w||(i.preventDefault(),i.stopPropagation(),E=Ze(i.clientX,i.clientY),n.selectCells(w.col,w.row,E.col,E.row))}function _e(i){if(A||!te||!w||!E)return;i.preventDefault(),i.stopPropagation(),document.removeEventListener("mousemove",ze,!0),document.removeEventListener("mouseup",_e,!0),te=!1;let s=n.getCellsText(w.col,w.row,E.col,E.row);w=null,E=null,n.clearSelection(),X(!1),s&&(R.textContent="\u5DF2\u590D\u5236",Le(s).then(Ee)),n.focus()}e.addEventListener("mousedown",Pt,!0),document.addEventListener("keydown",zt,!0);function zt(i){if(!(A||!n.isFocused()||i.defaultPrevented||i.isComposing)){if(ee){if(i.key==="Escape"){i.preventDefault(),i.stopPropagation(),w=null,E=null,n.clearSelection(),X(!1);return}if(i.key==="Enter"||(i.ctrlKey||i.metaKey)&&i.code==="KeyC"){i.preventDefault(),i.stopPropagation(),Le(n.getScreenText()).then(Ee),X(!1);return}}if(i.altKey&&i.code==="KeyC"&&!i.ctrlKey&&!i.metaKey){if(i.preventDefault(),i.stopPropagation(),w&&E){let s=n.getCellsText(w.col,w.row,E.col,E.row);w=null,E=null,n.clearSelection(),X(!1),s&&(R.textContent="Copied",Le(s).then(Ee))}else{let s=n.getSelection();s?(n.clearSelection(),R.textContent="Copied",Le(s).then(Ee)):Ge()}return}if((i.ctrlKey||i.metaKey)&&i.code==="KeyV"){i.preventDefault(),i.stopPropagation(),(async()=>{try{if(navigator.clipboard?.read){let s=await navigator.clipboard.read();for(let d of s)for(let f of d.types)if(Xe(f)){let b=await d.getType(f);await Pe(b);return}}}catch{}try{let s=await navigator.clipboard?.readText();s&&n.pasteText(Qe(s))}catch{}})();return}}}let xe=0;function Qe(i){return i.split(`\r
|
|
6
6
|
`).join(`
|
|
7
7
|
`).split(`
|
|
8
|
-
`).join("\r")}async function
|
|
9
|
-
<span>${m(
|
|
10
|
-
<button class="session-edit-btn" data-session="${m(
|
|
11
|
-
`,
|
|
12
|
-
<span>${m(l.name)}</span><span class="meta">${o.online?"":"offline"}</span>`,o.online&&
|
|
8
|
+
`).join("\r")}async function _t(i){let s=At(i.clipboardData);if(s){i.preventDefault(),i.stopPropagation();let b=Date.now();if(b-xe<50)return;xe=b,await Pe(s);return}let d=i.clipboardData?.getData("text/plain");if(!d)return;i.preventDefault(),i.stopPropagation();let f=Date.now();f-xe<50||(xe=f,n.pasteText(Qe(d)))}let ae=0;function jt(i){i.preventDefault(),ae++,Je(i.dataTransfer)&&e.classList.add("terminal-drag-over")}function Ft(i){i.preventDefault(),i.dataTransfer&&(i.dataTransfer.dropEffect="copy")}function Yt(i){i.preventDefault(),ae--,ae<=0&&(ae=0,e.classList.remove("terminal-drag-over"))}async function Wt(i){i.preventDefault(),ae=0,e.classList.remove("terminal-drag-over");let s=Je(i.dataTransfer);s&&await Pe(s)}function et(i){!It||i.key!=="Escape"||ee||i.defaultPrevented||i.repeat||i.metaKey||i.ctrlKey||i.altKey||n.isFocused()&&(i.preventDefault(),i.stopPropagation(),we("\x1B"))}e.addEventListener("paste",_t),e.addEventListener("dragenter",jt),e.addEventListener("dragover",Ft),e.addEventListener("dragleave",Yt),e.addEventListener("drop",Wt),document.addEventListener("keydown",et,!0);function qt(i){if(i.touches.length!==1){l=null;return}let s=i.touches[0];s&&(U(),l={startX:s.clientX,startY:s.clientY,lastX:s.clientX,lastY:s.clientY,lastT:performance.now(),velocity:0,scrolling:!1},i.stopPropagation())}function Ot(i){if(!l||i.touches.length!==1)return;let s=i.touches[0];if(!s)return;let d=s.clientY-l.startY,f=s.clientX-l.startX;if(!l.scrolling){if(Math.abs(d)<8||Math.abs(d)<Math.abs(f))return;l.scrolling=!0,K=Date.now()+500}i.preventDefault(),i.stopPropagation();let b=performance.now(),S=s.clientY-l.lastY,Y=b-l.lastT;Y>0&&(l.velocity=S/Y),Xt(-S),l.lastX=s.clientX,l.lastY=s.clientY,l.lastT=b}function Ut(i){if(!l)return;let s=l.scrolling,d=l.velocity;if(l=null,i.stopPropagation(),!s){n.focus();return}K=Date.now()+500,i.preventDefault(),d&&Ne(d)}function Kt(i){l=null,U(),i.stopPropagation()}function Vt(i){i.pointerType==="touch"&&Date.now()<K&&(i.preventDefault(),i.stopPropagation())}function Xt(i){let s=e.querySelector(".xterm-rows > div"),d=s?s.getBoundingClientRect().height:16,f=d>0?Math.round(i/d):i>0?1:-1;f===0&&(f=i>0?1:-1),n.scrollLines(f)}e.addEventListener("touchstart",qt,{passive:!0,capture:!0}),e.addEventListener("touchmove",Ot,{passive:!1,capture:!0}),e.addEventListener("touchend",Ut,{passive:!1,capture:!0}),e.addEventListener("touchcancel",Kt,{passive:!0,capture:!0}),e.addEventListener("pointerup",Vt,!0);function tt(){T()}window.addEventListener("resize",tt),window.visualViewport?.addEventListener("resize",T),window.visualViewport?.addEventListener("scroll",T);let nt=new ResizeObserver(()=>T());nt.observe(e),document.fonts?.ready.then(()=>{T(),setTimeout(T,50),setTimeout(T,200)}),e.addEventListener("focusin",$t,!0);async function Jt(){if(document.fonts?.ready)try{await Promise.race([document.fonts.ready,new Promise(i=>setTimeout(i,1e3))])}catch{}}return requestAnimationFrame(()=>{F()&&be(),(async()=>(await Jt(),F()&&j({type:"resize",cols:n.cols,rows:n.rows}),Ve(),T()))()}),{destroy(){if(A=!0,U(),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(){n.focus()},sendInput(i){we(i)},toggleCopyMode(){Ge()}}})()}var le=window.__TMUX_WEB_SHELL__,h=null,y=null,at=typeof window.__TMUX_WEB_SCOPE__?.agentId=="string"?window.__TMUX_WEB_SCOPE__.agentId:null,k=at;window.__tmuxWebScopeAgent=at;var Fe="sessions",N="",Ce="",Te="",st="";function M(e){return k?"/a/"+encodeURIComponent(k)+e:e}var v=document.getElementById("sidebar-content"),W=document.getElementById("main-placeholder"),B=document.getElementById("terminal-container"),re=document.getElementById("file-editor"),nn=document.getElementById("fe-path"),I=document.getElementById("fe-status"),ce=document.getElementById("fe-content"),q=document.getElementById("fe-save"),an=document.getElementById("fe-delete"),sn=document.getElementById("fe-back"),ot=document.getElementById("fe-new-name"),on=document.getElementById("fe-new-btn"),Ye=document.getElementById("settings-backdrop"),lt=document.getElementById("settings-popover"),rt=document.querySelector(".sidebar");function ln(){return window.matchMedia("(max-width: 640px)").matches}function We(){ln()&&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){Fe=e,document.querySelectorAll(".mode-btn").forEach(a=>a.classList.remove("active"));let t=document.getElementById(e==="sessions"?"mode-sessions":"mode-files");t&&t.classList.add("active"),e==="sessions"?(qe="",J()):Ln()}var qe="";async function J(){if(Fe!=="sessions")return;try{let t=await(await fetch("/api/sidebar/sessions")).json(),a=JSON.stringify({recent:t.recent,agents:t.agents});if(a===qe)return;qe=a,pn(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")})}var ut="tmux-web-sidebar-group-collapsed";function pt(){try{return new Set(JSON.parse(localStorage.getItem(ut)||"[]"))}catch{return new Set}}function rn(e){try{localStorage.setItem(ut,JSON.stringify([...e]))}catch{}}function cn(e,t){if(!e)return"\u2026";let a=e.split("/").filter(Boolean);if(a.length===0)return e;for(let n=2;n<=a.length;n++){let o=a.slice(-n).join("/");if(!t.some(r=>r&&r!==e&&r.split("/").filter(Boolean).slice(-n).join("/")===o))return"\u2026/"+o}return e}function dn(e){let t=new Map;for(let o of e){let r=o.path||"";t.has(r)||t.set(r,[]),t.get(r).push(o)}let a=[...t.keys()],n=pt();for(let[o,r]of t){let u=cn(o,a),l=n.has(o),c=document.createElement("div");c.className="session-group-header"+(l?" collapsed":""),c.title=o||"No working directory",c.innerHTML=`<span class="sg-folder">📁</span><span class="sg-label">${m(u)}</span><span class="sg-count">${r.length}</span><span class="sg-arrow">${l?"\u25B8":"\u25BE"}</span>`,c.addEventListener("click",()=>{let L=pt(),D=c.nextElementSibling;L.has(o)?(L.delete(o),c.classList.remove("collapsed"),D?.classList.remove("collapsed"),c.querySelector(".sg-arrow").textContent="\u25BE"):(L.add(o),c.classList.add("collapsed"),D?.classList.add("collapsed"),c.querySelector(".sg-arrow").textContent="\u25B8"),rn(L)});let p=document.createElement("div");p.className="session-group-body"+(l?" collapsed":"");for(let L of r)p.appendChild(un(L));v.appendChild(c),v.appendChild(p)}}function un(e){let t=document.createElement("div");t.className="session-item"+(e.name===y&&!k?" active":""),t.setAttribute("data-session",e.name),t.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.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
|
+
<span>${m(e.name)}</span>
|
|
10
|
+
<button class="session-edit-btn" data-session="${m(e.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
|
+
`,t.addEventListener("click",n=>{n.target.closest(".session-edit-btn")||de(e.name)});let a=t.querySelector(".session-edit-btn");return a&&a.addEventListener("click",n=>{n.stopPropagation(),Tn(e.name,a)}),t}function pn(e){v.innerHTML='<button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';let t=e.recent||[],a=document.createElement("div");a.className="sidebar-section-label"+(k?" clickable":""),a.textContent=k?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",k&&(a.title="Back to hub",a.addEventListener("click",()=>{window.location.href="/"})),v.appendChild(a),dn(t);let n=e.agents||[];for(let o of n){if(!o.sessions||o.sessions.length===0)continue;let r=o.agentId===k,u=document.createElement("div");u.className="sidebar-section-label machine-label"+(o.online?"":" agent-offline-label")+(!r&&o.online?" clickable":""),u.textContent=o.agentName+(o.online?"":" (offline)"),!r&&o.online&&(u.title="\u6253\u5F00 "+o.agentName,u.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(o.agentId)+"/"})),v.appendChild(u);for(let l of o.sessions){let c=document.createElement("div");c.className="session-item"+(l.name===y&&k===o.agentId?" active":"")+(o.online?"":" session-offline"),c.setAttribute("data-session",l.name),c.setAttribute("data-agent",o.agentId),c.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&&c.addEventListener("click",p=>{p.target.closest(".session-edit-btn")||de(l.name,o.agentId)}),v.appendChild(c)}}}function m(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function de(e,t){if(!(y===e&&h&&k===(t||null))){re.style.display="none",W.style.display="none",B.style.display="",B.classList.add("terminal-pending"),We(),h&&($e(),h.destroy(),h=null),y=e,k=t||null,window.__tmuxWebScopeAgent=k,window.__tmuxWebCurrentSession=y;for(let a of v.querySelectorAll(".session-item"))a.classList.toggle("active",a.getAttribute("data-session")===e&&(a.getAttribute("data-agent")||null)===k);try{h=await tn(B,e,{terminal:le.terminal,scrollback:le.scrollback,theme:le.theme,wsBase:k?"/ws/a/"+encodeURIComponent(k):le.wsBase||"/ws"}),Et()}catch(a){console.error("[shell] terminal init failed:",a);let n=a&&typeof a=="object"&&"message"in a?a.message:String(a);B.textContent="Failed to open terminal for "+e+": "+n,B.classList.remove("terminal-pending")}}}var Me=[],g=null,ft=!1;function fn(e){if(!g||!g.files.length)return null;let t=g.repoRoot;if(!t||!e.startsWith(t))return null;let a=e.slice(t.length+1);for(let n of g.files)if(n.path===a)return n.status;return null}async function mn(e){ft=!0;try{let t=await fetch(M("/api/git/status?path="+encodeURIComponent(e)));t.ok?g=await t.json():g=null}catch{g=null}finally{ft=!1}}function vn(){if(!g||!g.branch)return"";let{branch:e,linesAdded:t,linesRemoved:a}=g,n=`<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||
|
|
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
|
|
16
|
-
${
|
|
14
|
+
${m(e)}</div>`;return(t>0||a>0)&&(n+=`<div class="git-diff-stats">+<span class="add">${t}</span> <span class="del">-${a}</span></div>`),n+"</div>"}v.addEventListener("click",e=>{e.target.closest(".sidebar-git")&&Z&&g&&(e.stopPropagation(),yt())});var ue=document.getElementById("header-git"),mt=document.getElementById("git-diff-view"),vt=document.getElementById("gd-path"),Be=document.getElementById("gd-status"),Se=document.getElementById("gd-content"),hn=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:a,linesRemoved:n}=e,o=`<span class="branch">${m(t)}</span>`;(a>0||n>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${a}</span> <span class="diff-del">-${n}</span>`),ue.innerHTML=o,ue.style.display="block"}ue.addEventListener("click",e=>{Z&&g&&(e.stopPropagation(),yt())});var x=document.getElementById("git-popover"),Ie=document.getElementById("git-popover-backdrop");function Oe(){let e=ue.getBoundingClientRect();x.style.top=e.bottom+4+"px",x.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function ht(){let e=Z,t=document.getElementById("git-popover-browse");t&&t.addEventListener("click",()=>{if(De(),e){Fe="files",document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let a=document.getElementById("mode-files");a&&a.classList.add("active"),P(e)}})}var gt="";function yt(){if(!g)return;let e=g.files||[];if(!e.length)x.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
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 a=0;a<e.length;a++){let n=e[a],o=n.additions||0,r=n.deletions||0,u="";switch(n.status){case"M":u='<span class="git-file-status M">M</span>';break;case"A":u='<span class="git-file-status A">A</span>';break;case"D":u='<span class="git-file-status D">D</span>';break;case"?":u='<span class="git-file-status ?">?</span>';break;case"R":u='<span class="git-file-status R">R</span>';break}t+=`<div class="git-popover-item" data-idx="${a}">
|
|
16
|
+
${u}<span class="file-path">${m(n.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>',
|
|
19
|
+
</div>`}t+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',gt=t,x.innerHTML=t,x.querySelectorAll(".git-popover-item").forEach(a=>{let n=parseInt(a.dataset.idx||"0",10),o=e[n];!o||o.status==="D"||(a.classList.add("clickable"),a.style.cursor="pointer",a.addEventListener("click",()=>bt(o.path)))})}Oe(),x.classList.add("open"),Ie.classList.add("open"),ht()}function gn(){return window.innerWidth>=1024}function wt(e,t){let a=(e+(t&&e?`
|
|
20
20
|
`:"")+t).split(`
|
|
21
|
-
`),
|
|
21
|
+
`),n="",o=0,r=0;for(let u of a){let l=u;if(l.startsWith("@@")){let c=l.match(/@@\s+-(\d+)[^+]*\+(\d+)/);c&&(o=parseInt(c[1],10)-1,r=parseInt(c[2],10)-1),n+=`<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")&&(n+=`<div class="diff-line header"><span class="ln-body">${m(l)}</span></div>`);continue}if(l.startsWith("+"))r++,n+=`<div class="diff-line add"><span class="ln-no">${r}</span><span class="ln-body">${m(l)}</span></div>`;else if(l.startsWith("-"))o++,n+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${m(l)}</span></div>`;else if(l.startsWith("\\"))n+=`<div class="diff-line"><span class="ln-body">${m(l)}</span></div>`;else{if(!l&&!o&&!r)continue;o++,r++,n+=`<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:n,lineCount:a.length}}var He=null;async function bt(e){let t=Z;if(t)if(De(),gn()){B.style.display="none",h&&($e(),h.destroy(),h=null,He=y,y=null),pe(null),re.style.display="none",W.style.display="none",mt.style.display="flex",vt.textContent=e,Be.textContent="Loading\u2026",Se.innerHTML="";try{let a=await fetch(M("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!a.ok)throw new Error(await a.text());let n=await a.json();wn(e,n.diff,n.stagedDiff)}catch(a){let n=a instanceof Error?a.message:String(a);Be.textContent="Error",Se.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${m(n)}</div>`}}else{x.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>
|
|
25
|
-
</div></div>`,
|
|
25
|
+
</div></div>`,Oe(),x.classList.add("open"),Ie.classList.add("open");try{let a=await fetch(M("/api/git/diff?path="+encodeURIComponent(t)+"&file="+encodeURIComponent(e)));if(!a.ok)throw new Error(await a.text());let n=await a.json();yn(e,n.diff,n.stagedDiff)}catch(a){let n=a instanceof Error?a.message:String(a);x.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
26
26
|
<button class="diff-back">\u2190 Back</button>
|
|
27
27
|
<span class="diff-filename">${m(e)}</span>
|
|
28
|
-
</div><div class="diff-empty">Error loading diff: ${m(
|
|
28
|
+
</div><div class="diff-empty">Error loading diff: ${m(n)}</div></div>`}}}function yn(e,t,a){if(!t&&!a){x.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:
|
|
31
|
+
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:n,lineCount:o}=wt(t,a);x.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">${
|
|
35
|
+
</div><div class="diff-content">${n}</div></div>`,x.querySelector(".diff-back")?.addEventListener("click",()=>{x.innerHTML=gt,Oe(),g&&x.querySelectorAll(".git-popover-item").forEach(r=>{let u=parseInt(r.dataset.idx||"0",10),l=g.files[u];!l||l.status==="D"||(r.style.cursor="pointer",r.addEventListener("click",()=>bt(l.path)))}),ht()})}function wn(e,t,a){if(!t&&!a){Be.textContent="No diff",Se.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:n,lineCount:o}=wt(t,a);vt.textContent=e,Be.textContent=o+" lines",Se.innerHTML=n}function bn(){mt.style.display="none",He?(B.style.display="",B.classList.add("terminal-pending"),de(He),He=null):y?(B.style.display="",B.classList.add("terminal-pending"),Et()):W.style.display="flex"}hn.addEventListener("click",bn);function De(){x.classList.remove("open"),Ie.classList.remove("open")}Ie.addEventListener("click",De),document.addEventListener("keydown",e=>{e.key==="Escape"&&(De(),Ae())});async function Lt(e){try{let t=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(e))).json();if(t.path){let a=await fetch("/api/git/status?path="+encodeURIComponent(t.path));if(a.ok){let n=await a.json();g=n,pe(n);return}}}catch{}g=null,pe(null)}var Re=null;function Et(){$e(),y&&(Lt(y),Re=setInterval(()=>{y&&Lt(y)},3e4))}function $e(){Re&&(clearInterval(Re),Re=null)}function Ln(){if(Me=le.fsRoots||[],Me.length===0){if(y){En(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 En(e){v.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let t=await(await fetch(M("/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){N=e,v.innerHTML='<div class="file-tree-empty">Loading...</div>';let t=mn(e);try{let a=await(await fetch(M("/api/fs/list?path="+encodeURIComponent(e)))).json();await t,v.innerHTML="",pe(g),v.innerHTML+=vn();let n=xn(e);if(n&&!Me.some(r=>e===r)){let r=document.createElement("div");r.className="file-tree-item",r.innerHTML="..",r.addEventListener("click",()=>P(n)),v.appendChild(r)}if(a.dirs)for(let r of a.dirs){let u=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(u)}`,l.addEventListener("click",()=>P(r)),v.appendChild(l)}else v.innerHTML="";let o=a.files||[];for(let r of o){let u=r.split("/").pop()||r.split("\\").pop(),l=fn(r),c="",p="";if(l)switch(l){case"M":c="git-mod",p='<span class="git-file-status M">M</span>';break;case"A":c="git-add",p='<span class="git-file-status A">A</span>';break;case"D":c="git-del",p='<span class="git-file-status D">D</span>';break;case"?":c="git-untracked",p='<span class="git-file-status ?">?</span>';break;case"R":c="git-mod",p='<span class="git-file-status R">R</span>';break}let L=document.createElement("div");L.className="file-tree-item file"+(c?" "+c:""),L.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${p}${m(u)}`,L.addEventListener("click",()=>kn(r)),v.appendChild(L)}}catch{v.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function xn(e){let t=e.lastIndexOf("/");return t<=0?null:e.substring(0,t)}async function kn(e){Ce=e,B.style.display="none",h&&($e(),h.destroy(),h=null,y=null),pe(null),We(),W.style.display="none",re.style.display="flex",nn.textContent=e,I.textContent="Loading...",q.style.display="none";try{let t=await fetch(M("/api/file?path="+encodeURIComponent(e)));if(!t.ok){let n=await t.json();I.textContent=n.error||"Failed to load",ce.value="";return}let a=await t.json();ce.value=a.content,Te=a.content,st=a.content,I.textContent=a.size+" bytes",q.style.display=""}catch{I.textContent="Failed to load",ce.value=""}}ce.addEventListener("input",()=>{Te=ce.value}),q.addEventListener("click",async()=>{q.textContent="Saving...",q.disabled=!0;try{let e=await fetch(M("/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}st=Te,I.textContent="Saved"}catch{I.textContent="Save failed"}finally{q.disabled=!1,q.textContent="Save"}}),an.addEventListener("click",async()=>{if(confirm("Delete "+Ce+"?"))try{let e=await fetch(M("/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",W.style.display="flex",N&&P(N)}catch{I.textContent="Delete failed"}}),sn.addEventListener("click",()=>{re.style.display="none",W.style.display="flex",N&&P(N)}),on.addEventListener("click",async()=>{let e=ot.value.trim();if(!e)return;let t=N+"/"+e;try{let a=await fetch(M("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t})});if(!a.ok){let n=await a.json();I.textContent=n.error||"Create failed";return}ot.value="",N&&P(N)}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 O="",fe=document.createElement("div");fe.className="session-popover-backdrop",document.body.appendChild(fe);var $=document.createElement("div");$.id="session-popover",$.className="session-popover",$.style.display="none",$.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(
|
|
45
|
-
`).length;
|
|
44
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild($);function Cn(e){let t=e.getBoundingClientRect();$.style.top=t.bottom+4+"px",$.style.left=Math.max(8,Math.min(t.left,window.innerWidth-240))+"px"}function Tn(e,t){O=e;let a=document.getElementById("sp-name");a.value=e,fe.classList.add("open"),$.style.display="block",Cn(t),setTimeout(()=>a.focus(),50)}function me(){fe.classList.remove("open"),$.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===O){me();return}try{if(!(await fetch(M("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:O,newName:e})})).ok)return;me(),J(),y===O&&de(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+O+'"?'))try{if(!(await fetch(M("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:O})})).ok)return;me(),y===O&&(h&&(h.destroy(),h=null),y=null,B.style.display="none",W.style.display="flex"),J()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",me);var Mn={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 a=Mn[t.dataset.key];a&&(h.sendInput(a),h.focus())});var H=document.getElementById("mk-input"),xt=document.getElementById("mk-send");function kt(){if(!H||!h)return;let e=H.value;e&&(h.sendInput(e+"\r"),H.value="",H.rows=1,h.focus())}if(H&&(H.addEventListener("input",()=>{H.rows=1;let e=H.value.split(`
|
|
45
|
+
`).length;H.rows=Math.min(e,4)}),H.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),kt())})),xt&&xt.addEventListener("click",kt),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 Ue=document.getElementById("process-panel"),ve=document.getElementById("proc-list"),Bn=document.getElementById("proc-close"),Ke=document.getElementById("proc-backdrop"),Sn=document.getElementById("header-ram");function In(){ve.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(M("/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 a of e){let n=a.rss,o=n<1048576?(n/1024).toFixed(0)+"K":(n/1048576).toFixed(1)+"M";t+='<div class="proc-row" data-pid="'+a.pid+'"><span class="proc-mem">'+a.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+m(a.command)+'">'+m(a.command)+'</span><button class="proc-kill" title="Kill PID '+a.pid+'">×</button></div>'}ve.innerHTML=t;for(let a of ve.querySelectorAll(".proc-row")){let n=a.querySelector(".proc-kill");n&&n.addEventListener("click",o=>{let r=o.target.closest(".proc-row");!r||!confirm("Kill PID "+r.dataset.pid+"?")||fetch(M("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(r.dataset.pid,10)})}).then(u=>u.json()).then(u=>{u.ok?r.style.opacity="0.3":alert("Failed: "+(u.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{ve.innerHTML='<div class="proc-empty">Failed to load</div>'})}function Hn(){Ue.classList.add("open"),Ke.classList.add("open"),In()}function Ae(){Ue.classList.remove("open"),Ke.classList.remove("open")}Sn.addEventListener("click",e=>{e.stopPropagation(),Ue.classList.contains("open")?Ae():Hn()}),Bn.addEventListener("click",Ae),Ke.addEventListener("click",Ae),window.__openSession=de,window.__refreshSidebar=J,J(),We(),setInterval(()=>{document.visibilityState==="visible"&&J()},1e4);
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import{initTerminal as
|
|
2
|
-
<span>${d(
|
|
3
|
-
<button class="session-edit-btn" data-session="${d(
|
|
4
|
-
`,
|
|
5
|
-
<span>${d(c.name)}</span><span class="meta">${i.online?"":"offline"}</span>`,i.online&&
|
|
1
|
+
import{initTerminal as Be}from"./terminal-core.js";const H=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 g=ie;window.__tmuxWebScopeAgent=ie;let Y="sessions",k="",F="",W="",oe="";function h(e){return g?"/a/"+encodeURIComponent(g)+e:e}const r=document.getElementById("sidebar-content"),I=document.getElementById("main-placeholder"),b=document.getElementById("terminal-container"),$=document.getElementById("file-editor"),Ie=document.getElementById("fe-path"),w=document.getElementById("fe-status"),R=document.getElementById("fe-content"),x=document.getElementById("fe-save"),xe=document.getElementById("fe-delete"),Se=document.getElementById("fe-back"),ae=document.getElementById("fe-new-name"),Me=document.getElementById("fe-new-btn"),Z=document.getElementById("settings-backdrop"),le=document.getElementById("settings-popover"),ce=document.querySelector(".sidebar");function Te(){return window.matchMedia("(max-width: 640px)").matches}function Q(){Te()&&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()):ze()}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,_e(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")})}const pe="tmux-web-sidebar-group-collapsed";function fe(){try{return new Set(JSON.parse(localStorage.getItem(pe)||"[]"))}catch{return new Set}}function He(e){try{localStorage.setItem(pe,JSON.stringify([...e]))}catch{}}function $e(e,n){if(!e)return"\u2026";const t=e.split("/").filter(Boolean);if(t.length===0)return e;for(let s=2;s<=t.length;s++){const i=t.slice(-s).join("/");if(!n.some(a=>a&&a!==e&&a.split("/").filter(Boolean).slice(-s).join("/")===i))return"\u2026/"+i}return e}function Re(e){const n=new Map;for(const i of e){const l=i.path||"";n.has(l)||n.set(l,[]),n.get(l).push(i)}const t=[...n.keys()],s=fe();for(const[i,l]of n){const a=$e(i,t),c=s.has(i),o=document.createElement("div");o.className="session-group-header"+(c?" collapsed":""),o.title=i||"No working directory",o.innerHTML=`<span class="sg-folder">📁</span><span class="sg-label">${d(a)}</span><span class="sg-count">${l.length}</span><span class="sg-arrow">${c?"\u25B8":"\u25BE"}</span>`,o.addEventListener("click",()=>{const y=fe(),B=o.nextElementSibling;y.has(i)?(y.delete(i),o.classList.remove("collapsed"),B?.classList.remove("collapsed"),o.querySelector(".sg-arrow").textContent="\u25BE"):(y.add(i),o.classList.add("collapsed"),B?.classList.add("collapsed"),o.querySelector(".sg-arrow").textContent="\u25B8"),He(y)});const m=document.createElement("div");m.className="session-group-body"+(c?" collapsed":"");for(const y of l)m.appendChild(Ne(y));r.appendChild(o),r.appendChild(m)}}function Ne(e){const n=document.createElement("div");n.className="session-item"+(e.name===u&&!g?" active":""),n.setAttribute("data-session",e.name),n.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${e.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
|
+
<span>${d(e.name)}</span>
|
|
3
|
+
<button class="session-edit-btn" data-session="${d(e.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
|
+
`,n.addEventListener("click",s=>{s.target.closest(".session-edit-btn")||N(e.name)});const t=n.querySelector(".session-edit-btn");return t&&t.addEventListener("click",s=>{s.stopPropagation(),Ke(e.name,t)}),n}function _e(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"+(g?" clickable":""),t.textContent=g?"\u2190 \u8FD4\u56DE\u672C\u673A":"\u672C\u673A",g&&(t.title="Back to hub",t.addEventListener("click",()=>{window.location.href="/"})),r.appendChild(t),Re(n);const s=e.agents||[];for(const i of s){if(!i.sessions||i.sessions.length===0)continue;const l=i.agentId===g,a=document.createElement("div");a.className="sidebar-section-label machine-label"+(i.online?"":" agent-offline-label")+(!l&&i.online?" clickable":""),a.textContent=i.agentName+(i.online?"":" (offline)"),!l&&i.online&&(a.title="\u6253\u5F00 "+i.agentName,a.addEventListener("click",()=>{window.location.href="/a/"+encodeURIComponent(i.agentId)+"/"})),r.appendChild(a);for(const c of i.sessions){const o=document.createElement("div");o.className="session-item"+(c.name===u&&g===i.agentId?" active":"")+(i.online?"":" session-offline"),o.setAttribute("data-session",c.name),o.setAttribute("data-agent",i.agentId),o.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&&o.addEventListener("click",m=>{m.target.closest(".session-edit-btn")||N(c.name,i.agentId)}),r.appendChild(o)}}}function d(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function N(e,n){if(!(u===e&&p&&g===(n||null))){$.style.display="none",I.style.display="none",b.style.display="",b.classList.add("terminal-pending"),Q(),p&&(K(),p.destroy(),p=null),u=e,g=n||null,window.__tmuxWebScopeAgent=g,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)===g);try{p=await Be(b,e,{terminal:H.terminal,scrollback:H.scrollback,theme:H.theme,wsBase:g?"/ws/a/"+encodeURIComponent(g):H.wsBase||"/ws"}),Ee()}catch(t){console.error("[shell] terminal init failed:",t);const s=t&&typeof t=="object"&&"message"in t?t.message:String(t);b.textContent="Failed to open terminal for "+e+": "+s,b.classList.remove("terminal-pending")}}}let G=[],f=null,ue=!1;function Pe(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 Ae(e){ue=!0;try{const n=await fetch(h("/api/git/status?path="+encodeURIComponent(e)));n.ok?f=await n.json():f=null}catch{f=null}finally{ue=!1}}function De(){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")&&
|
|
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
|
|
9
|
-
${
|
|
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")&&T&&f&&(e.stopPropagation(),he())});const _=document.getElementById("header-git"),me=document.getElementById("git-diff-view"),ve=document.getElementById("gd-path"),O=document.getElementById("gd-status"),z=document.getElementById("gd-content"),je=document.getElementById("gd-back");let T=null;function P(e){if(!e||!e.branch){_.style.display="none",T=null;return}T=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>`),_.innerHTML=i,_.style.display="block"}_.addEventListener("click",e=>{T&&f&&(e.stopPropagation(),he())});const v=document.getElementById("git-popover"),U=document.getElementById("git-popover-backdrop");function te(){const e=_.getBoundingClientRect();v.style.top=e.bottom+4+"px",v.style.left=Math.max(8,Math.min(e.left,window.innerWidth-400))+"px"}function ge(){const e=T,n=document.getElementById("git-popover-browse");n&&n.addEventListener("click",()=>{if(V(),e){Y="files",document.querySelectorAll(".mode-btn").forEach(s=>s.classList.remove("active"));const t=document.getElementById("mode-files");t&&t.classList.add("active"),C(e)}})}let ye="";function he(){if(!f)return;const e=f.files||[];if(!e.length)v.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
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 a="";switch(s.status){case"M":a='<span class="git-file-status M">M</span>';break;case"A":a='<span class="git-file-status A">A</span>';break;case"D":a='<span class="git-file-status D">D</span>';break;case"?":a='<span class="git-file-status ?">?</span>';break;case"R":a='<span class="git-file-status R">R</span>';break}n+=`<div class="git-popover-item" data-idx="${t}">
|
|
9
|
+
${a}<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
|
-
</div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',
|
|
12
|
+
</div>`}n+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',ye=n,v.innerHTML=n,v.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",()=>we(i.path)))})}te(),v.classList.add("open"),U.classList.add("open"),ge()}function Fe(){return window.innerWidth>=1024}function be(e,n){const s=(e+(n&&e?`
|
|
13
13
|
`:"")+n).split(`
|
|
14
|
-
`);let i="",l=0,
|
|
14
|
+
`);let i="",l=0,a=0;for(const c of s){const o=c;if(o.startsWith("@@")){const m=o.match(/@@\s+-(\d+)[^+]*\+(\d+)/);m&&(l=parseInt(m[1],10)-1,a=parseInt(m[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${d(o)}</span></div>`;continue}if(o.startsWith("diff --git")||o.startsWith("index ")||o.startsWith("--- ")||o.startsWith("+++ ")||o.startsWith("new file")||o.startsWith("deleted file")){o.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${d(o)}</span></div>`);continue}if(o.startsWith("+"))a++,i+=`<div class="diff-line add"><span class="ln-no">${a}</span><span class="ln-body">${d(o)}</span></div>`;else if(o.startsWith("-"))l++,i+=`<div class="diff-line del"><span class="ln-no">${l}</span><span class="ln-body">${d(o)}</span></div>`;else if(o.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${d(o)}</span></div>`;else{if(!o&&!l&&!a)continue;l++,a++,i+=`<div class="diff-line"><span class="ln-no">${l}</span><span class="ln-no" style="margin-right:12px">${a}</span><span class="ln-body">${d(o)}</span></div>`}}return{html:i,lineCount:s.length}}let q=null;async function we(e){const n=T;if(n)if(V(),Fe()){b.style.display="none",p&&(K(),p.destroy(),p=null,q=u,u=null),P(null),$.style.display="none",I.style.display="none",me.style.display="flex",ve.textContent=e,O.textContent="Loading\u2026",z.innerHTML="";try{const t=await fetch(h("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();Ge(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);O.textContent="Error",z.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${d(s)}</div>`}}else{v.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(),
|
|
18
|
+
</div></div>`,te(),v.classList.add("open"),U.classList.add("open");try{const t=await fetch(h("/api/git/diff?path="+encodeURIComponent(n)+"&file="+encodeURIComponent(e)));if(!t.ok)throw new Error(await t.text());const s=await t.json();We(e,s.diff,s.stagedDiff)}catch(t){const s=t instanceof Error?t.message:String(t);v.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 We(e,n,t){if(!n&&!t){v.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
|
-
</div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=
|
|
24
|
+
</div><div class="diff-empty">No diff content available</div></div>`;return}const{html:s,lineCount:i}=be(n,t);v.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>`,
|
|
28
|
+
</div><div class="diff-content">${s}</div></div>`,v.querySelector(".diff-back")?.addEventListener("click",()=>{v.innerHTML=ye,te(),f&&v.querySelectorAll(".git-popover-item").forEach(l=>{const a=parseInt(l.dataset.idx||"0",10),c=f.files[a];!c||c.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>we(c.path)))}),ge()})}function Ge(e,n,t){if(!n&&!t){O.textContent="No diff",z.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}=be(n,t);ve.textContent=e,O.textContent=i+" lines",z.innerHTML=s}function Oe(){me.style.display="none",q?(b.style.display="",b.classList.add("terminal-pending"),N(q),q=null):u?(b.style.display="",b.classList.add("terminal-pending"),Ee()):I.style.display="flex"}je.addEventListener("click",Oe);function V(){v.classList.remove("open"),U.classList.remove("open")}U.addEventListener("click",V),document.addEventListener("keydown",e=>{e.key==="Escape"&&(V(),X())});async function Le(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,P(i);return}}}catch{}f=null,P(null)}let J=null;function Ee(){K(),u&&(Le(u),J=setInterval(()=>{u&&Le(u)},3e4))}function K(){J&&(clearInterval(J),J=null)}function ze(){if(G=H.fsRoots||[],G.length===0){if(u){Ue(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 G){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",()=>C(e)),r.appendChild(n)}}async function Ue(e){r.innerHTML='<div class="file-tree-empty">Loading...</div>';try{const t=await(await fetch(h("/api/fs/session-path?session="+encodeURIComponent(e)))).json();t.path?C(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 C(e){k=e,r.innerHTML='<div class="file-tree-empty">Loading...</div>';const n=Ae(e);try{const s=await(await fetch(h("/api/fs/list?path="+encodeURIComponent(e)))).json();await n,r.innerHTML="",P(f),r.innerHTML+=De();const i=qe(e);if(i&&!G.some(c=>e===c)){const c=document.createElement("div");c.className="file-tree-item",c.innerHTML="..",c.addEventListener("click",()=>C(i)),r.appendChild(c)}if(s.dirs)for(const a of s.dirs){const c=a.split("/").pop()||a.split("\\").pop(),o=document.createElement("div");o.className="file-tree-item",o.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)}`,o.addEventListener("click",()=>C(a)),r.appendChild(o)}else r.innerHTML="";const l=s.files||[];for(const a of l){const c=a.split("/").pop()||a.split("\\").pop(),o=Pe(a);let m="",y="";if(o)switch(o){case"M":m="git-mod",y='<span class="git-file-status M">M</span>';break;case"A":m="git-add",y='<span class="git-file-status A">A</span>';break;case"D":m="git-del",y='<span class="git-file-status D">D</span>';break;case"?":m="git-untracked",y='<span class="git-file-status ?">?</span>';break;case"R":m="git-mod",y='<span class="git-file-status R">R</span>';break}const B=document.createElement("div");B.className="file-tree-item file"+(m?" "+m:""),B.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${y}${d(c)}`,B.addEventListener("click",()=>Ve(a)),r.appendChild(B)}}catch{r.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function qe(e){const n=e.lastIndexOf("/");return n<=0?null:e.substring(0,n)}async function Ve(e){F=e,b.style.display="none",p&&(K(),p.destroy(),p=null,u=null),P(null),Q(),I.style.display="none",$.style.display="flex",Ie.textContent=e,w.textContent="Loading...",x.style.display="none";try{const n=await fetch(h("/api/file?path="+encodeURIComponent(e)));if(!n.ok){const s=await n.json();w.textContent=s.error||"Failed to load",R.value="";return}const t=await n.json();R.value=t.content,W=t.content,oe=t.content,w.textContent=t.size+" bytes",x.style.display=""}catch{w.textContent="Failed to load",R.value=""}}R.addEventListener("input",()=>{W=R.value}),x.addEventListener("click",async()=>{x.textContent="Saving...",x.disabled=!0;try{const e=await fetch(h("/api/file"),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:F,content:W})});if(!e.ok){const n=await e.json();w.textContent=n.error||"Save failed";return}oe=W,w.textContent="Saved"}catch{w.textContent="Save failed"}finally{x.disabled=!1,x.textContent="Save"}}),xe.addEventListener("click",async()=>{if(confirm("Delete "+F+"?"))try{const e=await fetch(h("/api/file/delete"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:F})});if(!e.ok){const n=await e.json();w.textContent=n.error||"Delete failed";return}$.style.display="none",I.style.display="flex",k&&C(k)}catch{w.textContent="Delete failed"}}),Se.addEventListener("click",()=>{$.style.display="none",I.style.display="flex",k&&C(k)}),Me.addEventListener("click",async()=>{const e=ae.value.trim();if(!e)return;const n=k+"/"+e;try{const t=await fetch(h("/api/file/touch"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n})});if(!t.ok){const s=await t.json();w.textContent=s.error||"Create failed";return}ae.value="",k&&C(k)}catch{w.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 S="";const A=document.createElement("div");A.className="session-popover-backdrop",document.body.appendChild(A);const E=document.createElement("div");E.id="session-popover",E.className="session-popover",E.style.display="none",E.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(
|
|
38
|
-
`).length;
|
|
37
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(E);function Je(e){const n=e.getBoundingClientRect();E.style.top=n.bottom+4+"px",E.style.left=Math.max(8,Math.min(n.left,window.innerWidth-240))+"px"}function Ke(e,n){S=e;const t=document.getElementById("sp-name");t.value=e,A.classList.add("open"),E.style.display="block",Je(n),setTimeout(()=>t.focus(),50)}function D(){A.classList.remove("open"),E.style.display="none"}A.addEventListener("click",D),document.getElementById("sp-save").addEventListener("click",async()=>{const e=document.getElementById("sp-name").value.trim();if(!e||e===S){D();return}try{if(!(await fetch(h("/api/sessions/rename"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:S,newName:e})})).ok)return;D(),M(),u===S&&N(e)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+S+'"?'))try{if(!(await fetch(h("/api/sessions/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:S})})).ok)return;D(),u===S&&(p&&(p.destroy(),p=null),u=null,b.style.display="none",I.style.display="flex"),M()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",D);const Xe={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=Xe[n.dataset.key];t&&(p.sendInput(t),p.focus())});const L=document.getElementById("mk-input"),ke=document.getElementById("mk-send");function Ce(){if(!L||!p)return;const e=L.value;e&&(p.sendInput(e+"\r"),L.value="",L.rows=1,p.focus())}if(L&&(L.addEventListener("input",()=>{L.rows=1;const e=L.value.split(`
|
|
38
|
+
`).length;L.rows=Math.min(e,4)}),L.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Ce())})),ke&&ke.addEventListener("click",Ce),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"),j=document.getElementById("proc-list"),Ye=document.getElementById("proc-close"),se=document.getElementById("proc-backdrop"),Ze=document.getElementById("header-ram");function Qe(){j.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch(h("/api/system/processes")).then(e=>e.json()).then(e=>{if(!Array.isArray(e)||!e.length){j.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>'}j.innerHTML=n;for(const t of j.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(h("/api/system/kill"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(a=>a.json()).then(a=>{a.ok?l.style.opacity="0.3":alert("Failed: "+(a.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{j.innerHTML='<div class="proc-empty">Failed to load</div>'})}function et(){ne.classList.add("open"),se.classList.add("open"),Qe()}function X(){ne.classList.remove("open"),se.classList.remove("open")}Ze.addEventListener("click",e=>{e.stopPropagation(),ne.classList.contains("open")?X():et()}),Ye.addEventListener("click",X),se.addEventListener("click",X),window.__openSession=N,window.__refreshSidebar=M,M(),Q(),setInterval(()=>{document.visibilityState==="visible"&&M()},1e4);
|
package/dist/lib/pages/shell.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSessionModalHTML as f,newSessionModalScript as g,reducedMotion as
|
|
1
|
+
import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSessionModalHTML as f,newSessionModalScript as g,reducedMotion as x}from"../shared-layout.js";import{icon as i}from"../icons.js";function h(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function o(t="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${t}; outline: none;`}function y(t){const{theme:e,fsRoots:n,terminalCfg:r,scrollback:a,wsBase:s}=t,l=`
|
|
2
2
|
*, *::before, *::after { box-sizing: border-box; }
|
|
3
3
|
html, body {
|
|
4
4
|
background: var(--page-bg); color: var(--page-fg);
|
|
@@ -124,6 +124,25 @@ import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSes
|
|
|
124
124
|
transition: color 0.15s;
|
|
125
125
|
}
|
|
126
126
|
.sidebar-section-label.clickable:hover { color: var(--panel-accent); }
|
|
127
|
+
.session-group-header {
|
|
128
|
+
display: flex; align-items: center; gap: 6px;
|
|
129
|
+
padding: 7px 12px 3px; cursor: pointer; user-select: none;
|
|
130
|
+
font-size: var(--text-xs); font-weight: 500; color: var(--panel-muted);
|
|
131
|
+
}
|
|
132
|
+
.session-group-header:hover { color: var(--page-fg); }
|
|
133
|
+
.sg-folder { flex-shrink: 0; font-size: 12px; }
|
|
134
|
+
.sg-label {
|
|
135
|
+
flex: 1; overflow: hidden; white-space: nowrap;
|
|
136
|
+
direction: rtl; text-align: left; text-overflow: ellipsis;
|
|
137
|
+
}
|
|
138
|
+
.sg-count {
|
|
139
|
+
flex-shrink: 0; font-size: 10px; font-weight: 600;
|
|
140
|
+
color: color-mix(in srgb, var(--panel-muted) 80%, transparent);
|
|
141
|
+
background: color-mix(in srgb, var(--panel-border) 55%, transparent);
|
|
142
|
+
border-radius: 8px; padding: 1px 6px;
|
|
143
|
+
}
|
|
144
|
+
.sg-arrow { flex-shrink: 0; font-size: 9px; }
|
|
145
|
+
.session-group-body.collapsed { display: none; }
|
|
127
146
|
.session-item.session-offline { opacity: 0.5; cursor: default; }
|
|
128
147
|
.session-item .meta { margin-left: auto; }
|
|
129
148
|
.new-session-sidebar-btn {
|
|
@@ -595,7 +614,7 @@ import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSes
|
|
|
595
614
|
.file-roots-list li:hover { border-color: var(--panel-accent); }
|
|
596
615
|
.file-roots-list li svg { width: 20px; height: 20px; flex-shrink: 0; fill: var(--panel-accent); }
|
|
597
616
|
|
|
598
|
-
${
|
|
617
|
+
${x()}
|
|
599
618
|
${c()}
|
|
600
619
|
@media (max-width: 640px) {
|
|
601
620
|
.app-layout { flex-direction: column; }
|
|
@@ -607,7 +626,7 @@ import{cssVarsStyle as d}from"../theme.js";import{newSessionModalCSS as c,newSes
|
|
|
607
626
|
.mobile-keys .mk-buttons { display: none; }
|
|
608
627
|
.mobile-keys.mk-focused .mk-buttons { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
609
628
|
}
|
|
610
|
-
`,
|
|
629
|
+
`,b=JSON.stringify(e.terminal).replace(/</g,"\\u003c"),p=JSON.stringify({terminal:r,scrollback:a,theme:e.terminal,fsRoots:n,wsBase:s||""}).replace(/</g,"\\u003c");return`<!DOCTYPE html>
|
|
611
630
|
<html lang="en">
|
|
612
631
|
<head>
|
|
613
632
|
<meta charset="UTF-8" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{viewKey as
|
|
1
|
+
import{viewKey as c}from"./pinned-views.js";import{captureSessionWindowsWithPath as f,listSessionWindows as h}from"./tmux-windows.js";function A(s,i){return s.has(c(i))}function l(s){try{const i=f(s);return(i.find(a=>a.active)??i[0])?.path}catch{return}}function S(s,i,o){const a=new Map(s.map(n=>[n.name,n])),m=new Set(o.map(n=>c(n.sessionName,n.windowIndex))),w=[...o].sort((n,t)=>t.pinnedAt-n.pinnedAt).map(n=>{const t=a.get(n.sessionName),e={sessionName:n.sessionName,windowIndex:n.windowIndex,pinnedAt:n.pinnedAt};if(!t)return e.missing=!0,e;if(e.windows=t.windows,e.attached=t.attached,n.windowIndex!==void 0){const r=h(n.sessionName).find(u=>u.index===n.windowIndex);r?e.windowName=r.name:e.missing=!0}return e}),p=s.map(n=>({...n,path:l(n.name),lastAccessedAt:i.get(n.name)})).filter(n=>!A(m,n.name)).sort((n,t)=>{const e=n.lastAccessedAt??0,d=t.lastAccessedAt??0;return e!==d?d-e:n.name.localeCompare(t.name)});return{pinned:w,recent:p}}export{S as buildSidebarSessions,l as sessionWorkingPath};
|
|
@@ -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};
|