tmux-weblink 1.5.2 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
var
|
|
1
|
+
var At="https://esm.sh/ghostty-web@0.4.0",Yt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function oe(t){return t.replace(Yt,"")}var le;function Pt(){return le||(document.querySelector("link[data-tmux-web-xterm-css]")?le=Promise.resolve():(le=new Promise(t=>{let e=document.createElement("link");e.rel="stylesheet",e.href="/assets/xterm.css",e.setAttribute("data-tmux-web-xterm-css",""),e.addEventListener("load",()=>t(),{once:!0}),e.addEventListener("error",()=>t(),{once:!0}),document.head.appendChild(e)}),le))}var Ke=class ut{terminal;fitAddon;container;constructor(e,n,i){this.container=e,this.terminal=n,this.fitAddon=i,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,n,i){let[{Terminal:o},{FitAddon:l}]=await Promise.all([import("./xterm-BXEES47M.js"),import("./addon-fit-D5N335P2.js"),Pt()]),d=new o({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:i});return new ut(e,d,new l)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(n=>this.terminal.write(e,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(e){this.terminal.scrollToLine(Math.max(0,e))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){let e=qe(this.container);return e.width<=0||e.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(e){this.terminal.paste(e)}input(e){this.terminal.input(e,!1)}onData(e){this.terminal.onData(e),this.terminal.onBinary(e)}onResize(e){this.terminal.onResize(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(e,n){let i=this.terminal.buffer.active,[o,l]=e<=n?[e,n]:[n,e],d=i.viewportY,r=[];for(let u=o;u<=l;u++){let f=i.getLine(d+u);r.push(f?f.translateToString(!0):"")}return r.join(`
|
|
2
|
+
`)}selectRows(e,n){let i=this.terminal.buffer.active,[o,l]=e<=n?[e,n]:[n,e];this.terminal.selectLines(i.viewportY+o,i.viewportY+l)}hasSelectionSupport(){return!0}},zt=class pt{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,n){this.container=e,this.terminal=n,this.terminal.open(e)}static async create(e,n,i){let o=await import(At);await o.init();let l=new o.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:n,convertEol:!1,theme:i});return new pt(e,l)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(n=>this.terminal.write(e,n))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(e){this.terminal.scrollToLine?.(Math.max(0,e))}isNearScrollbackTop(){let e=this.terminal.buffer?.active;return e?e.viewportY>=e.baseY:!1}viewportY(){return this.terminal.buffer?.active?.viewportY??0}baseY(){return this.terminal.buffer?.active?.baseY??0}fit(){let e=qe(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);let n=Math.floor(e.width/this.charW),i=Math.floor(e.height/this.charH);if(n<10||i<5)return!1;if(n!==this.colsValue||i!==this.rowsValue){this.colsValue=n,this.rowsValue=i,this.terminal.resize(n,i);for(let o of this.resizeCallbacks)o({cols:n,rows:i})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){let n=this.terminal.getMode?.(2004)??!1;this.input(n?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(n=>e(n))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){let e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.rowsValue-1)}getRowsText(e,n){try{let i=this.terminal.buffer?.active;if(!i||typeof i.getLine!="function")return"";let[o,l]=e<=n?[e,n]:[n,e],d=typeof i.viewportY=="number"?i.viewportY:0,r=[];for(let u=o;u<=l;u++){let f=i.getLine(d+u);r.push(typeof f?.translateToString=="function"?f.translateToString(!0):"")}return r.join(`
|
|
3
|
+
`)}catch{return""}}selectRows(e,n){try{if(typeof this.terminal?.selectLines!="function")return;let i=this.terminal.buffer?.active,o=typeof i?.viewportY=="number"?i.viewportY:0,[l,d]=e<=n?[e,n]:[n,e];this.terminal.selectLines(o+l,o+d)}catch{}}hasSelectionSupport(){return typeof this.terminal?.selectLines=="function"}updateCellMetrics(e=!1){let n=this.container.querySelector("canvas");if(n instanceof HTMLElement&&n.offsetWidth>0&&n.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){let i=n.offsetWidth/this.colsValue,o=n.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=i,this.charH=o)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}};function qe(t){let e=t.getBoundingClientRect(),n=window.visualViewport;return n?{width:Math.min(e.width,n.width),height:Math.max(0,Math.min(e.height,n.height-Math.max(0,e.top)))}:e}function jt(t,e,n){return t.innerHTML="",t.classList.add("terminal-pending"),(async()=>{let i;if(n.renderer==="ghostty")try{i=await zt.create(t,n.scrollback,n.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),i=await Ke.create(t,n.scrollback,n.theme)}else i=await Ke.create(t,n.scrollback,n.theme);let o,l=0,d,r=null,u=0,f="connecting",C=0,ne=!1,ie=[],$="",A=!1,z=1e3,ft=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),mt="/api/session/"+encodeURIComponent(e)+"/upload",ht=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function vt(){return ie.join("")+$}async function yt(a,s=0){let c=a?i.viewportY():0,b=a?i.baseY():0,x=oe(vt());if(i.reset(),!x){a||i.scrollToBottom();return}await i.write(x),a?i.scrollToLine(b+c+s):i.scrollToBottom()}function j(a){o?.readyState===WebSocket.OPEN&&o.send(JSON.stringify(a))}function gt(a){let s;try{s=JSON.parse(a)}catch{f==="live"&&($+=a,i.write(oe(a)));return}if(s.type==="auth.required"){let c=localStorage.getItem("tmux-web-token");c?j({type:"auth.token",token:c}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(s.type==="auth.ok"){s.token&&localStorage.setItem("tmux-web-token",s.token),z=1e3,N(),j({type:"resize",cols:i.cols,rows:i.rows}),w();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 c=oe(s.data);ie=[c],$="",C=typeof s.lines=="number"?s.lines:n.terminal.initialLines,f="live",i.reset(),i.write(c).then(()=>i.scrollToBottom());return}if(s.type==="window_changed"&&typeof s.activeIndex=="number"){let c=new URL(location.href);c.searchParams.get("window")!==String(s.activeIndex)&&(c.searchParams.set("window",String(s.activeIndex)),history.replaceState(history.state,"",c)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:s.windows}));return}if(s.type==="history"&&typeof s.data=="string"){ne=!1,s.lines>0&&s.data&&(ie.unshift(s.data),C+=s.lines,yt(!0,s.lines));return}if(s.type==="data"&&typeof s.data=="string"){let c=oe(s.data);if(f==="connecting"){f="live",$=c,i.write(c);return}f==="live"&&($+=c,i.write(c))}}function Se(){let a=ft,s=localStorage.getItem("tmux-web-token");s&&(a+=(a.includes("?")?"&":"?")+"token="+encodeURIComponent(s)),o=new WebSocket(a),o.onopen=()=>{f="connecting",C=0,ne=!1,ie=[],$="",i.reset(),z=1e3},o.onmessage=c=>{typeof c.data=="string"&>(c.data)},o.onclose=()=>{A||(f="connecting",setTimeout(()=>{z=Math.min(z*2,1e4),A||Se()},z))},o.onerror=()=>o?.close()}function ae(a){j({type:"input",data:a})}function N(){return i.fit()}function be(){t.classList.remove("terminal-pending")}function w(){l&&cancelAnimationFrame(l),d&&clearTimeout(d),N(),l=requestAnimationFrame(()=>{l=0,N()&&be()}),d=setTimeout(()=>{N()&&be()},120)}function wt(){w(),setTimeout(w,50),setTimeout(w,150),setTimeout(w,300)}function He(a){return typeof a=="string"&&a.startsWith("image/")}function De(a){return a?.files?.length?a.files[0]??null:null}function bt(a){if(!a?.items)return null;for(let s=0;s<a.items.length;s++){let c=a.items[s];if(c?.kind==="file"&&He(c.type))return c.getAsFile()}return null}async function Lt(a){let s=new FormData,c=a instanceof File&&a.name?a.name:"upload";s.append("file",a,c);let b=localStorage.getItem("tmux-web-token"),x={};b&&(x.Authorization="Bearer "+b);let xe=await fetch(mt,{method:"POST",body:s,headers:x});if(!xe.ok){let Fe="upload failed";try{let Ve=await xe.json();Ve.error&&(Fe=Ve.error)}catch{}throw new Error(Fe)}let We=await xe.json();if(!We.path)throw new Error("no path in response");return We.path}async function Le(a){try{let s=await Lt(a);ae(s)}catch{}}i.onData(a=>ae(a)),i.onResize(({cols:a,rows:s})=>j({type:"resize",cols:a,rows:s})),i.onScroll(()=>{f!=="live"||ne||!i.isNearScrollbackTop()||(ne=!0,j({type:"load_history",before:C}))});let W=!1,F=!1,E=null,S=null,V=document.createElement("div");V.className="terminal-copy-banner",V.innerHTML='<strong>Copy mode</strong><span class="hint">drag to select \xB7 Esc exits</span>',V.hidden=!0,t.appendChild(V);let Y=document.createElement("div");Y.className="terminal-copy-banner terminal-copy-toast",Y.textContent="Copied",Y.hidden=!0,t.appendChild(Y);function Re(a){let s=t.getBoundingClientRect(),c=s.height>0&&i.rows>0?s.height/i.rows:18;return Math.max(0,Math.min(i.rows-1,Math.floor((a-s.top)/c)))}async function $e(a){if(!a)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(a);return}}catch{}let s=document.createElement("textarea");s.value=a,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 K;function Ae(){Y.hidden=!1,K&&clearTimeout(K),K=setTimeout(()=>{Y.hidden=!0},1e3)}function q(a){W=a,V.hidden=!a,t.classList.toggle("terminal-copy-mode-active",a),a&&i.focus()}function Ye(){q(!W)}function Et(){return E===null||S===null?null:E<=S?[E,S]:[S,E]}function kt(a){A||a.button!==0||F||(W||a.shiftKey)&&(a.preventDefault(),a.stopPropagation(),F=!0,E=Re(a.clientY),S=E,i.selectRows(E,E),document.addEventListener("mousemove",Ee,!0),document.addEventListener("mouseup",ke,!0))}function Ee(a){A||!F||E===null||(a.preventDefault(),a.stopPropagation(),S=Re(a.clientY),i.selectRows(E,S))}function ke(a){if(A||!F||E===null)return;a.preventDefault(),a.stopPropagation();let s=Et();document.removeEventListener("mousemove",Ee,!0),document.removeEventListener("mouseup",ke,!0),F=!1,q(!1),s&&($e(i.getRowsText(s[0],s[1])).then(Ae),i.focus())}t.addEventListener("mousedown",kt,!0),i.attachCustomKeyEventHandler(a=>{if(a.type!=="keydown")return!0;if(W){if(a.key==="Escape")return a.preventDefault(),q(!1),!1;if(a.key==="Enter"||(a.ctrlKey||a.metaKey)&&a.code==="KeyC")return a.preventDefault(),$e(i.getScreenText()).then(Ae),q(!1),!1}return a.altKey&&a.code==="KeyC"&&!a.ctrlKey&&!a.metaKey?(a.preventDefault(),Ye(),!1):(a.ctrlKey||a.metaKey)&&a.code==="KeyV"?(a.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){let s=await navigator.clipboard.read();for(let c of s)for(let b of c.types)if(He(b)){let x=await c.getType(b);await Le(x);return}}}catch{}try{let s=await navigator.clipboard?.readText();s&&i.pasteText(Pe(s))}catch{}})(),!1):!0});let se=0;function Pe(a){return a.split(`\r
|
|
2
4
|
`).join(`
|
|
3
5
|
`).split(`
|
|
4
|
-
`).join("\r")}async function
|
|
6
|
+
`).join("\r")}async function xt(a){let s=bt(a.clipboardData);if(s){a.preventDefault(),a.stopPropagation();let x=Date.now();if(x-se<50)return;se=x,await Le(s);return}let c=a.clipboardData?.getData("text/plain");if(!c)return;a.preventDefault(),a.stopPropagation();let b=Date.now();b-se<50||(se=b,i.pasteText(Pe(c)))}let O=0;function Tt(a){a.preventDefault(),O++,De(a.dataTransfer)&&t.classList.add("terminal-drag-over")}function Ct(a){a.preventDefault(),a.dataTransfer&&(a.dataTransfer.dropEffect="copy")}function Mt(a){a.preventDefault(),O--,O<=0&&(O=0,t.classList.remove("terminal-drag-over"))}async function Bt(a){a.preventDefault(),O=0,t.classList.remove("terminal-drag-over");let s=De(a.dataTransfer);s&&await Le(s)}function ze(a){!ht||a.key!=="Escape"||W||a.defaultPrevented||a.repeat||a.metaKey||a.ctrlKey||a.altKey||i.isFocused()&&(a.preventDefault(),a.stopPropagation(),ae("\x1B"))}t.addEventListener("paste",xt),t.addEventListener("dragenter",Tt),t.addEventListener("dragover",Ct),t.addEventListener("dragleave",Mt),t.addEventListener("drop",Bt),document.addEventListener("keydown",ze,!0);function It(a){if(a.touches.length!==1){r=null;return}let s=a.touches[0];s&&(r={startX:s.clientX,startY:s.clientY,lastX:s.clientX,lastY:s.clientY,scrolling:!1},a.stopPropagation())}function St(a){if(!r||a.touches.length!==1)return;let s=a.touches[0];if(!s)return;let c=s.clientY-r.startY,b=s.clientX-r.startX;if(!r.scrolling){if(Math.abs(c)<8||Math.abs(c)<Math.abs(b))return;r.scrolling=!0,u=Date.now()+500}a.preventDefault(),a.stopPropagation(),$t(-(s.clientY-r.lastY),s.clientX,s.clientY),r.lastX=s.clientX,r.lastY=s.clientY}function Ht(a){if(!r)return;let s=r.scrolling;r=null,a.stopPropagation(),s?(u=Date.now()+500,a.preventDefault()):i.focus()}function Dt(a){r=null,a.stopPropagation()}function Rt(a){a.pointerType==="touch"&&Date.now()<u&&(a.preventDefault(),a.stopPropagation())}function $t(a,s,c){(t.querySelector(".xterm-screen")??t).dispatchEvent(new WheelEvent("wheel",{deltaY:a,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:s,clientY:c,bubbles:!0,cancelable:!0}))}t.addEventListener("touchstart",It,{passive:!0,capture:!0}),t.addEventListener("touchmove",St,{passive:!1,capture:!0}),t.addEventListener("touchend",Ht,{passive:!1,capture:!0}),t.addEventListener("touchcancel",Dt,{passive:!0,capture:!0}),t.addEventListener("pointerup",Rt,!0);function je(){w()}window.addEventListener("resize",je),window.visualViewport?.addEventListener("resize",w),window.visualViewport?.addEventListener("scroll",w);let Ne=new ResizeObserver(()=>w());return Ne.observe(t),document.fonts?.ready.then(()=>{w(),setTimeout(w,50),setTimeout(w,200)}),t.addEventListener("focusin",wt,!0),requestAnimationFrame(()=>{N()&&be(),Se(),w()}),{destroy(){if(A=!0,q(!1),K&&clearTimeout(K),document.removeEventListener("mousemove",Ee,!0),document.removeEventListener("mouseup",ke,!0),o){try{o.close(1e3,"session switch")}catch{}o=void 0}t.innerHTML="",t.classList.remove("terminal-pending"),window.removeEventListener("resize",je),Ne.disconnect(),document.removeEventListener("keydown",ze,!0)},focus(){i.focus()},sendInput(a){ae(a)},toggleCopyMode(){Ye()}}})()}var U=window.__TMUX_WEB_SHELL__,m=null,y=null,Oe="sessions",B="",re="",ce="",Ue="",h=document.getElementById("sidebar-content"),H=document.getElementById("main-placeholder"),L=document.getElementById("terminal-container"),X=document.getElementById("file-editor"),Nt=document.getElementById("fe-path"),k=document.getElementById("fe-status"),_=document.getElementById("fe-content"),D=document.getElementById("fe-save"),Wt=document.getElementById("fe-delete"),Ft=document.getElementById("fe-back"),Xe=document.getElementById("fe-new-name"),Vt=document.getElementById("fe-new-btn"),Te=document.getElementById("settings-backdrop"),_e=document.getElementById("settings-popover"),Je=document.querySelector(".sidebar");function Kt(){return window.matchMedia("(max-width: 640px)").matches}function Ce(){Kt()&&Je.classList.add("collapsed")}function Ze(){Je.classList.remove("collapsed")}document.getElementById("mode-sessions").addEventListener("click",()=>{Ze(),Ge("sessions")}),document.getElementById("mode-files").addEventListener("click",()=>{Ze(),Ge("files")});function Ge(t){Oe=t,document.querySelectorAll(".mode-btn").forEach(n=>n.classList.remove("active"));let e=document.getElementById(t==="sessions"?"mode-sessions":"mode-files");e&&e.classList.add("active"),t==="sessions"?J():Qt()}async function J(){h.innerHTML='<div class="sidebar-section-label">Sessions</div><button class="new-session-sidebar-btn" id="ns-btn">+ New Session</button>';try{let e=(await(await fetch("/api/sidebar/sessions")).json()).recent||[];for(let n of e){let i=document.createElement("div");i.className="session-item"+(n.name===y?" active":""),i.setAttribute("data-session",n.name),i.innerHTML=`<svg viewBox="0 0 24 24" fill="currentColor"><path d="${n.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
7
|
<span>${p(n.name)}</span>
|
|
6
8
|
<button class="session-edit-btn" data-session="${p(n.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>
|
|
7
|
-
`,
|
|
9
|
+
`,i.addEventListener("click",l=>{l.target.closest(".session-edit-btn")||de(n.name)}),h.appendChild(i);let o=i.querySelector(".session-edit-btn");o&&o.addEventListener("click",l=>{l.stopPropagation(),sn(n.name,o)})}}catch{h.innerHTML+='<div class="file-tree-empty">Failed to load sessions</div>'}let t=document.getElementById("ns-btn");t&&t.addEventListener("click",()=>document.getElementById("new-session-modal")?.classList.add("open"))}function p(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function de(t){if(!(y===t&&m)){X.style.display="none",H.style.display="none",L.style.display="",L.classList.add("terminal-pending"),Ce(),m&&(ge(),m.destroy(),m=null),y=t;for(let e of h.querySelectorAll(".session-item"))e.classList.toggle("active",e.getAttribute("data-session")===t);try{m=await jt(L,t,{terminal:U.terminal,scrollback:U.scrollback,theme:U.theme,renderer:U.renderer}),rt()}catch(e){console.error("[shell] terminal init failed:",e);let n=e&&typeof e=="object"&&"message"in e?e.message:String(e);L.textContent="Failed to open terminal for "+t+": "+n,L.classList.remove("terminal-pending")}}}var ue=[],v=null,Qe=!1;function qt(t){if(!v||!v.files.length)return null;let e=v.repoRoot;if(!e||!t.startsWith(e))return null;let n=t.slice(e.length+1);for(let i of v.files)if(i.path===n)return i.status;return null}async function Ot(t){Qe=!0;try{let e=await fetch("/api/git/status?path="+encodeURIComponent(t));e.ok?v=await e.json():v=null}catch{v=null}finally{Qe=!1}}function Ut(){if(!v||!v.branch)return"";let{branch:t,linesAdded:e,linesRemoved:n}=v,i=`<div class="sidebar-git"><div class="git-branch-badge">
|
|
8
10
|
<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>
|
|
9
|
-
${p(t)}</div>`;return(e>0||n>0)&&(
|
|
10
|
-
<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let e=`<div class="git-popover-header">${t.length} file${t.length>1?"s":""} changed</div>`;for(let n=0;n<t.length;n++){let
|
|
11
|
-
${d}<span class="file-path">${p(
|
|
11
|
+
${p(t)}</div>`;return(e>0||n>0)&&(i+=`<div class="git-diff-stats">+<span class="add">${e}</span> <span class="del">-${n}</span></div>`),i+"</div>"}h.addEventListener("click",t=>{t.target.closest(".sidebar-git")&&P&&v&&(t.stopPropagation(),at())});var Z=document.getElementById("header-git"),et=document.getElementById("git-diff-view"),tt=document.getElementById("gd-path"),pe=document.getElementById("gd-status"),fe=document.getElementById("gd-content"),Xt=document.getElementById("gd-back"),P=null;function G(t){if(!t||!t.branch){Z.style.display="none",P=null;return}P=t.repoRoot;let{branch:e,linesAdded:n,linesRemoved:i}=t,o=`<span class="branch">${p(e)}</span>`;(n>0||i>0)&&(o+=`<span class="sep">\xB7</span>+<span class="diff-add">${n}</span> <span class="diff-del">-${i}</span>`),Z.innerHTML=o,Z.style.display="block"}Z.addEventListener("click",t=>{P&&v&&(t.stopPropagation(),at())});var g=document.getElementById("git-popover"),me=document.getElementById("git-popover-backdrop");function Me(){let t=Z.getBoundingClientRect();g.style.top=t.bottom+4+"px",g.style.left=Math.max(8,Math.min(t.left,window.innerWidth-400))+"px"}function nt(){let t=P,e=document.getElementById("git-popover-browse");e&&e.addEventListener("click",()=>{if(ve(),t){Oe="files",document.querySelectorAll(".mode-btn").forEach(i=>i.classList.remove("active"));let n=document.getElementById("mode-files");n&&n.classList.add("active"),I(t)}})}var it="";function at(){if(!v)return;let t=v.files||[];if(!t.length)g.innerHTML=`<div class="git-popover-header">No changes \u2014 clean working tree</div>
|
|
12
|
+
<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>`;else{let e=`<div class="git-popover-header">${t.length} file${t.length>1?"s":""} changed</div>`;for(let n=0;n<t.length;n++){let i=t[n],o=i.additions||0,l=i.deletions||0,d="";switch(i.status){case"M":d='<span class="git-file-status M">M</span>';break;case"A":d='<span class="git-file-status A">A</span>';break;case"D":d='<span class="git-file-status D">D</span>';break;case"?":d='<span class="git-file-status ?">?</span>';break;case"R":d='<span class="git-file-status R">R</span>';break}e+=`<div class="git-popover-item" data-idx="${n}">
|
|
13
|
+
${d}<span class="file-path">${p(i.path)}</span>
|
|
12
14
|
<span class="file-add">${o>0?"+"+o:""}</span>
|
|
13
15
|
<span class="file-del">${l>0?"-"+l:""}</span>
|
|
14
|
-
</div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',
|
|
16
|
+
</div>`}e+='<div class="git-popover-footer"><button id="git-popover-browse">Browse repository \u2192</button></div>',it=e,g.innerHTML=e,g.querySelectorAll(".git-popover-item").forEach(n=>{let i=parseInt(n.dataset.idx||"0",10),o=t[i];!o||o.status==="D"||(n.classList.add("clickable"),n.style.cursor="pointer",n.addEventListener("click",()=>ot(o.path)))})}Me(),g.classList.add("open"),me.classList.add("open"),nt()}function _t(){return window.innerWidth>=1024}function st(t,e){let n=(t+(e&&t?`
|
|
15
17
|
`:"")+e).split(`
|
|
16
|
-
`),
|
|
18
|
+
`),i="",o=0,l=0;for(let d of n){let r=d;if(r.startsWith("@@")){let u=r.match(/@@\s+-(\d+)[^+]*\+(\d+)/);u&&(o=parseInt(u[1],10)-1,l=parseInt(u[2],10)-1),i+=`<div class="diff-line hunk"><span class="ln-body">${p(r)}</span></div>`;continue}if(r.startsWith("diff --git")||r.startsWith("index ")||r.startsWith("--- ")||r.startsWith("+++ ")||r.startsWith("new file")||r.startsWith("deleted file")){r.startsWith("diff --git")&&(i+=`<div class="diff-line header"><span class="ln-body">${p(r)}</span></div>`);continue}if(r.startsWith("+"))l++,i+=`<div class="diff-line add"><span class="ln-no">${l}</span><span class="ln-body">${p(r)}</span></div>`;else if(r.startsWith("-"))o++,i+=`<div class="diff-line del"><span class="ln-no">${o}</span><span class="ln-body">${p(r)}</span></div>`;else if(r.startsWith("\\"))i+=`<div class="diff-line"><span class="ln-body">${p(r)}</span></div>`;else{if(!r&&!o&&!l)continue;o++,l++,i+=`<div class="diff-line"><span class="ln-no">${o}</span><span class="ln-no" style="margin-right:12px">${l}</span><span class="ln-body">${p(r)}</span></div>`}}return{html:i,lineCount:n.length}}var he=null;async function ot(t){let e=P;if(e)if(ve(),_t()){L.style.display="none",m&&(ge(),m.destroy(),m=null,he=y,y=null),G(null),X.style.display="none",H.style.display="none",et.style.display="flex",tt.textContent=t,pe.textContent="Loading\u2026",fe.innerHTML="";try{let n=await fetch("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t));if(!n.ok)throw new Error(await n.text());let i=await n.json();Zt(t,i.diff,i.stagedDiff)}catch(n){let i=n instanceof Error?n.message:String(n);pe.textContent="Error",fe.innerHTML=`<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">Error: ${p(i)}</div>`}}else{g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
17
19
|
<button class="diff-back">\u2190 Back</button>
|
|
18
20
|
<span class="diff-filename">${p(t)}</span>
|
|
19
21
|
<span class="diff-loading">Loading\u2026</span>
|
|
20
|
-
</div></div>`,
|
|
22
|
+
</div></div>`,Me(),g.classList.add("open"),me.classList.add("open");try{let n=await fetch("/api/git/diff?path="+encodeURIComponent(e)+"&file="+encodeURIComponent(t));if(!n.ok)throw new Error(await n.text());let i=await n.json();Jt(t,i.diff,i.stagedDiff)}catch(n){let i=n instanceof Error?n.message:String(n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
21
23
|
<button class="diff-back">\u2190 Back</button>
|
|
22
24
|
<span class="diff-filename">${p(t)}</span>
|
|
23
|
-
</div><div class="diff-empty">Error loading diff: ${p(
|
|
25
|
+
</div><div class="diff-empty">Error loading diff: ${p(i)}</div></div>`}}}function Jt(t,e,n){if(!e&&!n){g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
24
26
|
<button class="diff-back">\u2190 Back</button>
|
|
25
27
|
<span class="diff-filename">${p(t)}</span>
|
|
26
|
-
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:
|
|
28
|
+
</div><div class="diff-empty">No diff content available</div></div>`;return}let{html:i,lineCount:o}=st(e,n);g.innerHTML=`<div class="git-diff-view"><div class="diff-header">
|
|
27
29
|
<button class="diff-back">\u2190 Back</button>
|
|
28
30
|
<span class="diff-filename">${p(t)}</span>
|
|
29
31
|
<span class="diff-filesize">${o} lines</span>
|
|
30
|
-
</div><div class="diff-content">${
|
|
32
|
+
</div><div class="diff-content">${i}</div></div>`,g.querySelector(".diff-back")?.addEventListener("click",()=>{g.innerHTML=it,Me(),v&&g.querySelectorAll(".git-popover-item").forEach(l=>{let d=parseInt(l.dataset.idx||"0",10),r=v.files[d];!r||r.status==="D"||(l.style.cursor="pointer",l.addEventListener("click",()=>ot(r.path)))}),nt()})}function Zt(t,e,n){if(!e&&!n){pe.textContent="No diff",fe.innerHTML='<div class="diff-empty" style="padding:24px;text-align:center;color:var(--panel-muted)">No diff content available</div>';return}let{html:i,lineCount:o}=st(e,n);tt.textContent=t,pe.textContent=o+" lines",fe.innerHTML=i}function Gt(){et.style.display="none",he?(L.style.display="",L.classList.add("terminal-pending"),de(he),he=null):y?(L.style.display="",L.classList.add("terminal-pending"),rt()):H.style.display="flex"}Xt.addEventListener("click",Gt);function ve(){g.classList.remove("open"),me.classList.remove("open")}me.addEventListener("click",ve),document.addEventListener("keydown",t=>{t.key==="Escape"&&(ve(),we())});async function lt(t){try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();if(e.path){let n=await fetch("/api/git/status?path="+encodeURIComponent(e.path));if(n.ok){let i=await n.json();v=i,G(i);return}}}catch{}v=null,G(null)}var ye=null;function rt(){ge(),y&&(lt(y),ye=setInterval(()=>{y&<(y)},3e4))}function ge(){ye&&(clearInterval(ye),ye=null)}function Qt(){if(ue=U.fsRoots||[],ue.length===0){if(y){en(y);return}h.innerHTML='<div class="file-tree-info">Open a session first, then switch to Files to browse its directory.</div>';return}h.innerHTML="";for(let t of ue){let e=document.createElement("div");e.className="file-tree-item",e.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> ${p(t)}`,e.addEventListener("click",()=>I(t)),h.appendChild(e)}}async function en(t){h.innerHTML='<div class="file-tree-empty">Loading...</div>';try{let e=await(await fetch("/api/fs/session-path?session="+encodeURIComponent(t))).json();e.path?I(e.path):h.innerHTML='<div class="file-tree-info">Could not determine session directory.</div>'}catch{h.innerHTML='<div class="file-tree-error">Failed to get session directory</div>'}}async function I(t){B=t,h.innerHTML='<div class="file-tree-empty">Loading...</div>';let e=Ot(t);try{let n=await(await fetch("/api/fs/list?path="+encodeURIComponent(t))).json();await e,h.innerHTML="",G(v),h.innerHTML+=Ut();let i=tn(t);if(i&&!ue.some(l=>t===l)){let l=document.createElement("div");l.className="file-tree-item",l.innerHTML="..",l.addEventListener("click",()=>I(i)),h.appendChild(l)}if(n.dirs)for(let l of n.dirs){let d=l.split("/").pop()||l.split("\\").pop(),r=document.createElement("div");r.className="file-tree-item",r.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> ${p(d)}`,r.addEventListener("click",()=>I(l)),h.appendChild(r)}else h.innerHTML="";let o=n.files||[];for(let l of o){let d=l.split("/").pop()||l.split("\\").pop(),r=qt(l),u="",f="";if(r)switch(r){case"M":u="git-mod",f='<span class="git-file-status M">M</span>';break;case"A":u="git-add",f='<span class="git-file-status A">A</span>';break;case"D":u="git-del",f='<span class="git-file-status D">D</span>';break;case"?":u="git-untracked",f='<span class="git-file-status ?">?</span>';break;case"R":u="git-mod",f='<span class="git-file-status R">R</span>';break}let C=document.createElement("div");C.className="file-tree-item file"+(u?" "+u:""),C.innerHTML=`<span class="file-icon">\u{1F4C4}</span> ${f}${p(d)}`,C.addEventListener("click",()=>nn(l)),h.appendChild(C)}}catch{h.innerHTML='<div class="file-tree-error">Failed to load directory</div>'}}function tn(t){let e=t.lastIndexOf("/");return e<=0?null:t.substring(0,e)}async function nn(t){re=t,L.style.display="none",m&&(ge(),m.destroy(),m=null,y=null),G(null),Ce(),H.style.display="none",X.style.display="flex",Nt.textContent=t,k.textContent="Loading...",D.style.display="none";try{let e=await fetch("/api/file?path="+encodeURIComponent(t));if(!e.ok){let i=await e.json();k.textContent=i.error||"Failed to load",_.value="";return}let n=await e.json();_.value=n.content,ce=n.content,Ue=n.content,k.textContent=n.size+" bytes",D.style.display=""}catch{k.textContent="Failed to load",_.value=""}}_.addEventListener("input",()=>{ce=_.value}),D.addEventListener("click",async()=>{D.textContent="Saving...",D.disabled=!0;try{let t=await fetch("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:re,content:ce})});if(!t.ok){let e=await t.json();k.textContent=e.error||"Save failed";return}Ue=ce,k.textContent="Saved"}catch{k.textContent="Save failed"}finally{D.disabled=!1,D.textContent="Save"}}),Wt.addEventListener("click",async()=>{if(confirm("Delete "+re+"?"))try{let t=await fetch("/api/file/delete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:re})});if(!t.ok){let e=await t.json();k.textContent=e.error||"Delete failed";return}X.style.display="none",H.style.display="flex",B&&I(B)}catch{k.textContent="Delete failed"}}),Ft.addEventListener("click",()=>{X.style.display="none",H.style.display="flex",B&&I(B)}),Vt.addEventListener("click",async()=>{let t=Xe.value.trim();if(!t)return;let e=B+"/"+t;try{let n=await fetch("/api/file/touch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})});if(!n.ok){let i=await n.json();k.textContent=i.error||"Create failed";return}Xe.value="",B&&I(B)}catch{k.textContent="Create failed"}}),document.getElementById("mode-settings").addEventListener("click",()=>{let t=_e.classList.toggle("open");Te.classList.toggle("open",t)}),Te.addEventListener("click",()=>{_e.classList.remove("open"),Te.classList.remove("open")});var R="",Q=document.createElement("div");Q.className="session-popover-backdrop",document.body.appendChild(Q);var M=document.createElement("div");M.id="session-popover",M.className="session-popover",M.style.display="none",M.innerHTML=`<div class="sp-field">
|
|
31
33
|
<label>Rename session</label>
|
|
32
34
|
<input type="text" id="sp-name" />
|
|
33
35
|
</div>
|
|
@@ -36,5 +38,5 @@ var wt="https://esm.sh/ghostty-web@0.4.0",bt=/\x1b\](?:10|11|110|111|104)(?:;[^\
|
|
|
36
38
|
<button class="btn" id="sp-cancel">Cancel</button>
|
|
37
39
|
</div>
|
|
38
40
|
<hr class="sp-divider" />
|
|
39
|
-
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(M);function
|
|
40
|
-
`).length;
|
|
41
|
+
<button class="btn danger" id="sp-delete">Delete session</button>`,document.body.appendChild(M);function an(t){let e=t.getBoundingClientRect();M.style.top=e.bottom+4+"px",M.style.left=Math.max(8,Math.min(e.left,window.innerWidth-240))+"px"}function sn(t,e){R=t;let n=document.getElementById("sp-name");n.value=t,Q.classList.add("open"),M.style.display="block",an(e),setTimeout(()=>n.focus(),50)}function ee(){Q.classList.remove("open"),M.style.display="none"}Q.addEventListener("click",ee),document.getElementById("sp-save").addEventListener("click",async()=>{let t=document.getElementById("sp-name").value.trim();if(!t||t===R){ee();return}try{if(!(await fetch("/api/sessions/rename",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldName:R,newName:t})})).ok)return;ee(),J(),y===R&&de(t)}catch{}}),document.getElementById("sp-delete").addEventListener("click",async()=>{if(confirm('Delete session "'+R+'"?'))try{if(!(await fetch("/api/sessions/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:R})})).ok)return;ee(),y===R&&(m&&(m.destroy(),m=null),y=null,L.style.display="none",H.style.display="flex"),J()}catch{}}),document.getElementById("sp-cancel").addEventListener("click",ee);var on={esc:"\x1B",tab:" ","s-tab":"\x1B[Z",up:"\x1B[A",down:"\x1B[B",left:"\x1B[D",right:"\x1B[C",space:" ",enter:"\r",exit:"exit\r",yes:"yes\r"};document.getElementById("mobile-keys").addEventListener("click",t=>{let e=t.target.closest("button[data-key]");if(!e||!m)return;let n=on[e.dataset.key];n&&(m.sendInput(n),m.focus())});var T=document.getElementById("mk-input"),ct=document.getElementById("mk-send");function dt(){if(!T||!m)return;let t=T.value;t&&(m.sendInput(t+"\r"),T.value="",T.rows=1,m.focus())}if(T&&(T.addEventListener("input",()=>{T.rows=1;let t=T.value.split(`
|
|
42
|
+
`).length;T.rows=Math.min(t,4)}),T.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),dt())})),ct&&ct.addEventListener("click",dt),window.visualViewport){let t=document.querySelector(".app-layout"),e=()=>{t&&(t.style.height=window.visualViewport.height+"px"),window.scrollTo(0,0),document.documentElement.scrollTop=0,document.body.scrollTop=0};window.visualViewport.addEventListener("resize",e),window.addEventListener("orientationchange",()=>setTimeout(e,100)),document.addEventListener("touchstart",()=>{(window.scrollY>0||document.documentElement.scrollTop>0)&&window.scrollTo(0,0)},{passive:!0})}var Be=document.getElementById("process-panel"),te=document.getElementById("proc-list"),ln=document.getElementById("proc-close"),Ie=document.getElementById("proc-backdrop"),rn=document.getElementById("header-ram");function cn(){te.innerHTML='<div class="proc-empty">Loading\u2026</div>',fetch("/api/system/processes").then(t=>t.json()).then(t=>{if(!Array.isArray(t)||!t.length){te.innerHTML='<div class="proc-empty">No processes</div>';return}let e="";for(let n of t){let i=n.rss,o=i<1048576?(i/1024).toFixed(0)+"K":(i/1048576).toFixed(1)+"M";e+='<div class="proc-row" data-pid="'+n.pid+'"><span class="proc-mem">'+n.mem+'%</span><span class="proc-rss">'+o+'</span><span class="proc-cmd" title="'+p(n.command)+'">'+p(n.command)+'</span><button class="proc-kill" title="Kill PID '+n.pid+'">×</button></div>'}te.innerHTML=e;for(let n of te.querySelectorAll(".proc-row")){let i=n.querySelector(".proc-kill");i&&i.addEventListener("click",o=>{let l=o.target.closest(".proc-row");!l||!confirm("Kill PID "+l.dataset.pid+"?")||fetch("/api/system/kill",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pid:parseInt(l.dataset.pid,10)})}).then(d=>d.json()).then(d=>{d.ok?l.style.opacity="0.3":alert("Failed: "+(d.error||"unknown"))}).catch(()=>alert("Network error"))})}}).catch(()=>{te.innerHTML='<div class="proc-empty">Failed to load</div>'})}function dn(){Be.classList.add("open"),Ie.classList.add("open"),cn()}function we(){Be.classList.remove("open"),Ie.classList.remove("open")}rn.addEventListener("click",t=>{t.stopPropagation(),Be.classList.contains("open")?we():dn()}),ln.addEventListener("click",we),Ie.addEventListener("click",we),window.__openSession=de,window.__refreshSidebar=J,J(),Ce();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const Dt="https://esm.sh/ghostty-web@0.4.0",Mt=/\x1b\](?:10|11|110|111|104)(?:;[^\x07\x1b]*)?\x1b?(?:\\|\x07)/g;function K(s){return s.replace(Mt,"")}let Y;function kt(){return Y||(document.querySelector("link[data-tmux-web-xterm-css]")?Y=Promise.resolve():(Y=new Promise(e=>{const i=document.createElement("link");i.rel="stylesheet",i.href="/assets/xterm.css",i.setAttribute("data-tmux-web-xterm-css",""),i.addEventListener("load",()=>e(),{once:!0}),i.addEventListener("error",()=>e(),{once:!0}),document.head.appendChild(i)}),Y))}class B{terminal;fitAddon;container;constructor(e,i,r){this.container=e,this.terminal=i,this.fitAddon=r,this.terminal.loadAddon(this.fitAddon),this.terminal.open(e)}static async create(e,i,r){const[{Terminal:a},{FitAddon:c}]=await Promise.all([import("@xterm/xterm"),import("@xterm/addon-fit"),kt()]),d=new a({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:r});return new B(e,d,new c)}get cols(){return this.terminal.cols}get rows(){return this.terminal.rows}write(e){return new Promise(i=>this.terminal.write(e,i))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom()}scrollToLine(e){this.terminal.scrollToLine(Math.max(0,e))}isNearScrollbackTop(){return this.terminal.buffer.active.viewportY<=1}viewportY(){return this.terminal.buffer.active.viewportY}baseY(){return this.terminal.buffer.active.baseY}fit(){const e=it(this.container);return e.width<=0||e.height<=0?!1:(this.fitAddon.fit(),!0)}focus(){this.terminal.focus()}pasteText(e){this.terminal.paste(e)}input(e){this.terminal.input(e,!1)}onData(e){this.terminal.onData(e),this.terminal.onBinary(e)}onResize(e){this.terminal.onResize(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){const e=document.activeElement;return!!e&&(e===this.container||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.terminal.rows-1)}getRowsText(e,i){const r=this.terminal.buffer.active,[a,c]=e<=i?[e,i]:[i,e],d=r.viewportY,l=[];for(let m=a;m<=c;m++){const u=r.getLine(d+m);l.push(u?u.translateToString(!0):"")}return l.join(`
|
|
2
|
+
`)}selectRows(e,i){const r=this.terminal.buffer.active,[a,c]=e<=i?[e,i]:[i,e];this.terminal.selectLines(r.viewportY+a,r.viewportY+c)}hasSelectionSupport(){return!0}}class U{terminal;container;colsValue=80;rowsValue=24;charW=0;charH=0;resizeCallbacks=[];constructor(e,i){this.container=e,this.terminal=i,this.terminal.open(e)}static async create(e,i,r){const a=await import(Dt);await a.init();const c=new a.Terminal({fontSize:14,fontFamily:"'JetBrains Mono', 'SF Mono', 'Menlo', monospace",cursorBlink:!0,cursorStyle:"bar",scrollback:i,convertEol:!1,theme:r});return new U(e,c)}get cols(){return this.colsValue}get rows(){return this.rowsValue}write(e){return new Promise(i=>this.terminal.write(e,i))}reset(){this.terminal.reset()}scrollToBottom(){this.terminal.scrollToBottom?.()}scrollToLine(e){this.terminal.scrollToLine?.(Math.max(0,e))}isNearScrollbackTop(){const e=this.terminal.buffer?.active;return e?e.viewportY>=e.baseY:!1}viewportY(){return this.terminal.buffer?.active?.viewportY??0}baseY(){return this.terminal.buffer?.active?.baseY??0}fit(){const e=it(this.container);if(e.width<=0||e.height<=0)return!1;this.updateCellMetrics(!this.charW||!this.charH);const i=Math.floor(e.width/this.charW),r=Math.floor(e.height/this.charH);if(i<10||r<5)return!1;if(i!==this.colsValue||r!==this.rowsValue){this.colsValue=i,this.rowsValue=r,this.terminal.resize(i,r);for(const a of this.resizeCallbacks)a({cols:i,rows:r})}return!0}focus(){(this.terminal.textarea||this.container.querySelector("textarea")||this.container)?.focus?.()}pasteText(e){const i=this.terminal.getMode?.(2004)??!1;this.input(i?"\x1B[200~"+e+"\x1B[201~":e)}input(e){this.terminal.input?.(e,!1)}onData(e){this.terminal.onData(i=>e(i))}onResize(e){this.resizeCallbacks.push(e)}onScroll(e){this.terminal.onScroll(e)}attachCustomKeyEventHandler(e){this.terminal.attachCustomKeyEventHandler(e)}isFocused(){const e=document.activeElement;return!!e&&(e===this.container||e===this.terminal.textarea||this.container.contains(e))}getScreenText(){return this.getRowsText(0,this.rowsValue-1)}getRowsText(e,i){try{const r=this.terminal.buffer?.active;if(!r||typeof r.getLine!="function")return"";const[a,c]=e<=i?[e,i]:[i,e],d=typeof r.viewportY=="number"?r.viewportY:0,l=[];for(let m=a;m<=c;m++){const u=r.getLine(d+m);l.push(typeof u?.translateToString=="function"?u.translateToString(!0):"")}return l.join(`
|
|
3
|
+
`)}catch{return""}}selectRows(e,i){try{if(typeof this.terminal?.selectLines!="function")return;const r=this.terminal.buffer?.active,a=typeof r?.viewportY=="number"?r.viewportY:0,[c,d]=e<=i?[e,i]:[i,e];this.terminal.selectLines(a+c,a+d)}catch{}}hasSelectionSupport(){return typeof this.terminal?.selectLines=="function"}updateCellMetrics(e=!1){const i=this.container.querySelector("canvas");if(i instanceof HTMLElement&&i.offsetWidth>0&&i.offsetHeight>0&&this.colsValue>0&&this.rowsValue>0){const r=i.offsetWidth/this.colsValue,a=i.offsetHeight/this.rowsValue;(e||!this.charW||!this.charH)&&(this.charW=r,this.charH=a)}(!this.charW||!this.charH)&&(this.charW=9,this.charH=18)}}function it(s){const e=s.getBoundingClientRect(),i=window.visualViewport;return i?{width:Math.min(e.width,i.width),height:Math.max(0,Math.min(e.height,i.height-Math.max(0,e.top)))}:e}function Pt(s,e,i){return s.innerHTML="",s.classList.add("terminal-pending"),(async()=>{let r;if(i.renderer==="ghostty")try{r=await U.create(s,i.scrollback,i.theme)}catch{console.error("[tmux-web] ghostty-web failed; falling back to xterm.js"),r=await B.create(s,i.scrollback,i.theme)}else r=await B.create(s,i.scrollback,i.theme);let a,c=0,d,l=null,m=0,u="connecting",P=0,F=!1,H=[],g="",v=!1,T=1e3;const ot=(location.protocol==="https:"?"wss:":"ws:")+"//"+location.host+"/ws/"+encodeURIComponent(e),st="/api/session/"+encodeURIComponent(e)+"/upload",at=/^((?!chrome|android|crios|fxios|edgios).)*safari/i.test(navigator.userAgent);function lt(){return H.join("")+g}async function ct(t,n=0){const o=t?r.viewportY():0,h=t?r.baseY():0,w=K(lt());if(r.reset(),!w){t||r.scrollToBottom();return}await r.write(w),t?r.scrollToLine(h+o+n):r.scrollToBottom()}function x(t){a?.readyState===WebSocket.OPEN&&a.send(JSON.stringify(t))}function ut(t){let n;try{n=JSON.parse(t)}catch{u==="live"&&(g+=t,r.write(K(t)));return}if(n.type==="auth.required"){const o=localStorage.getItem("tmux-web-token");o?x({type:"auth.token",token:o}):location.href="/login?returnTo="+encodeURIComponent(location.pathname+location.search);return}if(n.type==="auth.ok"){n.token&&localStorage.setItem("tmux-web-token",n.token),T=1e3,E(),x({type:"resize",cols:r.cols,rows:r.rows}),f();return}if(n.type==="auth.failed"){if(n.permanentLock||n.retryAfterMs&&n.retryAfterMs>6e4){localStorage.removeItem("tmux-web-token"),location.href="/login?error="+encodeURIComponent(n.message);return}return}if(n.type==="snapshot"&&typeof n.data=="string"){const o=K(n.data);H=[o],g="",P=typeof n.lines=="number"?n.lines:i.terminal.initialLines,u="live",r.reset(),r.write(o).then(()=>r.scrollToBottom());return}if(n.type==="window_changed"&&typeof n.activeIndex=="number"){const o=new URL(location.href);o.searchParams.get("window")!==String(n.activeIndex)&&(o.searchParams.set("window",String(n.activeIndex)),history.replaceState(history.state,"",o)),window.dispatchEvent(new CustomEvent("tmux:windows",{detail:n.windows}));return}if(n.type==="history"&&typeof n.data=="string"){F=!1,n.lines>0&&n.data&&(H.unshift(n.data),P+=n.lines,ct(!0,n.lines));return}if(n.type==="data"&&typeof n.data=="string"){const o=K(n.data);if(u==="connecting"){u="live",g=o,r.write(o);return}u==="live"&&(g+=o,r.write(o))}}function X(){let t=ot;const n=localStorage.getItem("tmux-web-token");n&&(t+=(t.includes("?")?"&":"?")+"token="+encodeURIComponent(n)),a=new WebSocket(t),a.onopen=()=>{u="connecting",P=0,F=!1,H=[],g="",r.reset(),T=1e3},a.onmessage=o=>{typeof o.data=="string"&&ut(o.data)},a.onclose=()=>{v||(u="connecting",setTimeout(()=>{T=Math.min(T*2,1e4),v||X()},T))},a.onerror=()=>a?.close()}function z(t){x({type:"input",data:t})}function E(){return r.fit()}function V(){s.classList.remove("terminal-pending")}function f(){c&&cancelAnimationFrame(c),d&&clearTimeout(d),E(),c=requestAnimationFrame(()=>{c=0,E()&&V()}),d=setTimeout(()=>{E()&&V()},120)}function ft(){f(),setTimeout(f,50),setTimeout(f,150),setTimeout(f,300)}function j(t){return typeof t=="string"&&t.startsWith("image/")}function q(t){return t?.files?.length?t.files[0]??null:null}function ht(t){if(!t?.items)return null;for(let n=0;n<t.items.length;n++){const o=t.items[n];if(o?.kind==="file"&&j(o.type))return o.getAsFile()}return null}async function dt(t){const n=new FormData,o=t instanceof File&&t.name?t.name:"upload";n.append("file",t,o);const h=localStorage.getItem("tmux-web-token"),w={};h&&(w.Authorization="Bearer "+h);const O=await fetch(st,{method:"POST",body:n,headers:w});if(!O.ok){let nt="upload failed";try{const rt=await O.json();rt.error&&(nt=rt.error)}catch{}throw new Error(nt)}const et=await O.json();if(!et.path)throw new Error("no path in response");return et.path}async function A(t){try{const n=await dt(t);z(n)}catch{}}r.onData(t=>z(t)),r.onResize(({cols:t,rows:n})=>x({type:"resize",cols:t,rows:n})),r.onScroll(()=>{u!=="live"||F||!r.isNearScrollbackTop()||(F=!0,x({type:"load_history",before:P}))});let L=!1,S=!1,p=null,y=null;const C=document.createElement("div");C.className="terminal-copy-banner",C.innerHTML='<strong>Copy mode</strong><span class="hint">drag to select \xB7 Esc exits</span>',C.hidden=!0,s.appendChild(C);const b=document.createElement("div");b.className="terminal-copy-banner terminal-copy-toast",b.textContent="Copied",b.hidden=!0,s.appendChild(b);function N(t){const n=s.getBoundingClientRect(),o=n.height>0&&r.rows>0?n.height/r.rows:18;return Math.max(0,Math.min(r.rows-1,Math.floor((t-n.top)/o)))}async function _(t){if(!t)return;try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(t);return}}catch{}const n=document.createElement("textarea");n.value=t,n.style.position="fixed",n.style.opacity="0",n.style.pointerEvents="none",document.body.appendChild(n),n.focus(),n.select();try{document.execCommand("copy")}catch{}n.remove()}let D;function J(){b.hidden=!1,D&&clearTimeout(D),D=setTimeout(()=>{b.hidden=!0},1e3)}function M(t){L=t,C.hidden=!t,s.classList.toggle("terminal-copy-mode-active",t),t&&r.focus()}function G(){M(!L)}function mt(){return p===null||y===null?null:p<=y?[p,y]:[y,p]}function pt(t){v||t.button!==0||S||(L||t.shiftKey)&&(t.preventDefault(),t.stopPropagation(),S=!0,p=N(t.clientY),y=p,r.selectRows(p,p),document.addEventListener("mousemove",W,!0),document.addEventListener("mouseup",R,!0))}function W(t){v||!S||p===null||(t.preventDefault(),t.stopPropagation(),y=N(t.clientY),r.selectRows(p,y))}function R(t){if(v||!S||p===null)return;t.preventDefault(),t.stopPropagation();const n=mt();document.removeEventListener("mousemove",W,!0),document.removeEventListener("mouseup",R,!0),S=!1,M(!1),n&&(_(r.getRowsText(n[0],n[1])).then(J),r.focus())}s.addEventListener("mousedown",pt,!0),r.attachCustomKeyEventHandler(t=>{if(t.type!=="keydown")return!0;if(L){if(t.key==="Escape")return t.preventDefault(),M(!1),!1;if(t.key==="Enter"||(t.ctrlKey||t.metaKey)&&t.code==="KeyC")return t.preventDefault(),_(r.getScreenText()).then(J),M(!1),!1}return t.altKey&&t.code==="KeyC"&&!t.ctrlKey&&!t.metaKey?(t.preventDefault(),G(),!1):(t.ctrlKey||t.metaKey)&&t.code==="KeyV"?(t.preventDefault(),(async()=>{try{if(navigator.clipboard?.read){const n=await navigator.clipboard.read();for(const o of n)for(const h of o.types)if(j(h)){const w=await o.getType(h);await A(w);return}}}catch{}try{const n=await navigator.clipboard?.readText();n&&r.pasteText(Q(n))}catch{}})(),!1):!0});let I=0;function Q(t){return t.split(`\r
|
|
2
4
|
`).join(`
|
|
3
5
|
`).split(`
|
|
4
|
-
`).join("\r")}async function
|
|
6
|
+
`).join("\r")}async function wt(t){const n=ht(t.clipboardData);if(n){t.preventDefault(),t.stopPropagation();const w=Date.now();if(w-I<50)return;I=w,await A(n);return}const o=t.clipboardData?.getData("text/plain");if(!o)return;t.preventDefault(),t.stopPropagation();const h=Date.now();h-I<50||(I=h,r.pasteText(Q(o)))}let k=0;function yt(t){t.preventDefault(),k++,q(t.dataTransfer)&&s.classList.add("terminal-drag-over")}function gt(t){t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect="copy")}function vt(t){t.preventDefault(),k--,k<=0&&(k=0,s.classList.remove("terminal-drag-over"))}async function bt(t){t.preventDefault(),k=0,s.classList.remove("terminal-drag-over");const n=q(t.dataTransfer);n&&await A(n)}function Z(t){!at||t.key!=="Escape"||L||t.defaultPrevented||t.repeat||t.metaKey||t.ctrlKey||t.altKey||r.isFocused()&&(t.preventDefault(),t.stopPropagation(),z("\x1B"))}s.addEventListener("paste",wt),s.addEventListener("dragenter",yt),s.addEventListener("dragover",gt),s.addEventListener("dragleave",vt),s.addEventListener("drop",bt),document.addEventListener("keydown",Z,!0);function Tt(t){if(t.touches.length!==1){l=null;return}const n=t.touches[0];n&&(l={startX:n.clientX,startY:n.clientY,lastX:n.clientX,lastY:n.clientY,scrolling:!1},t.stopPropagation())}function xt(t){if(!l||t.touches.length!==1)return;const n=t.touches[0];if(!n)return;const o=n.clientY-l.startY,h=n.clientX-l.startX;if(!l.scrolling){if(Math.abs(o)<8||Math.abs(o)<Math.abs(h))return;l.scrolling=!0,m=Date.now()+500}t.preventDefault(),t.stopPropagation(),Ct(-(n.clientY-l.lastY),n.clientX,n.clientY),l.lastX=n.clientX,l.lastY=n.clientY}function Et(t){if(!l)return;const n=l.scrolling;l=null,t.stopPropagation(),n?(m=Date.now()+500,t.preventDefault()):r.focus()}function Lt(t){l=null,t.stopPropagation()}function St(t){t.pointerType==="touch"&&Date.now()<m&&(t.preventDefault(),t.stopPropagation())}function Ct(t,n,o){(s.querySelector(".xterm-screen")??s).dispatchEvent(new WheelEvent("wheel",{deltaY:t,deltaMode:WheelEvent.DOM_DELTA_PIXEL,clientX:n,clientY:o,bubbles:!0,cancelable:!0}))}s.addEventListener("touchstart",Tt,{passive:!0,capture:!0}),s.addEventListener("touchmove",xt,{passive:!1,capture:!0}),s.addEventListener("touchend",Et,{passive:!1,capture:!0}),s.addEventListener("touchcancel",Lt,{passive:!0,capture:!0}),s.addEventListener("pointerup",St,!0);function $(){f()}window.addEventListener("resize",$),window.visualViewport?.addEventListener("resize",f),window.visualViewport?.addEventListener("scroll",f);const tt=new ResizeObserver(()=>f());return tt.observe(s),document.fonts?.ready.then(()=>{f(),setTimeout(f,50),setTimeout(f,200)}),s.addEventListener("focusin",ft,!0),requestAnimationFrame(()=>{E()&&V(),X(),f()}),{destroy(){if(v=!0,M(!1),D&&clearTimeout(D),document.removeEventListener("mousemove",W,!0),document.removeEventListener("mouseup",R,!0),a){try{a.close(1e3,"session switch")}catch{}a=void 0}s.innerHTML="",s.classList.remove("terminal-pending"),window.removeEventListener("resize",$),tt.disconnect(),document.removeEventListener("keydown",Z,!0)},focus(){r.focus()},sendInput(t){z(t)},toggleCopyMode(){G()}}})()}export{Pt as initTerminal};
|
package/dist/lib/pages/shell.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarHTML as b,commandbarScript as x}from"../commandbar.js";import{newSessionModalCSS as m,newSessionModalHTML as v,newSessionModalScript as u,reducedMotion as h}from"../shared-layout.js";import{icon as i}from"../icons.js";function C(o){return o.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function n(o="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${o}; outline: none;`}function $(o){const{theme:e,commandbarEnabled:t,commandbarSessions:a,fsRoots:s,terminalCfg:
|
|
1
|
+
import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarHTML as b,commandbarScript as x}from"../commandbar.js";import{newSessionModalCSS as m,newSessionModalHTML as v,newSessionModalScript as u,reducedMotion as h}from"../shared-layout.js";import{icon as i}from"../icons.js";function C(o){return o.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function n(o="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${o}; outline: none;`}function $(o){const{theme:e,commandbarEnabled:t,commandbarSessions:a,fsRoots:s,terminalCfg:p,renderer:r,scrollback:d}=o,l=`
|
|
2
2
|
*, *::before, *::after { box-sizing: border-box; }
|
|
3
3
|
html, body {
|
|
4
4
|
background: var(--page-bg); color: var(--page-fg);
|
|
@@ -487,13 +487,30 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
|
|
|
487
487
|
|
|
488
488
|
/* \u2500\u2500 Terminal container \u2500\u2500 */
|
|
489
489
|
#terminal-container {
|
|
490
|
-
flex: 1; overflow: hidden;
|
|
490
|
+
flex: 1; overflow: hidden; position: relative;
|
|
491
491
|
}
|
|
492
492
|
#terminal-container.terminal-pending { visibility: hidden; }
|
|
493
493
|
#terminal-container .terminal-drag-over {
|
|
494
494
|
outline: 2px dashed var(--panel-accent);
|
|
495
495
|
outline-offset: -2px;
|
|
496
496
|
}
|
|
497
|
+
#terminal-container .terminal-copy-banner {
|
|
498
|
+
position: absolute; bottom: 6px; left: 6px; z-index: 20;
|
|
499
|
+
display: flex; align-items: center; gap: 8px;
|
|
500
|
+
padding: 4px 10px; border-radius: 6px;
|
|
501
|
+
background: rgba(0,0,0,0.72); color: #fff;
|
|
502
|
+
font-size: 11px; font-family: var(--font-mono);
|
|
503
|
+
pointer-events: none; box-shadow: 0 2px 8px rgba(0,0,0,0.35);
|
|
504
|
+
white-space: nowrap;
|
|
505
|
+
}
|
|
506
|
+
#terminal-container .terminal-copy-banner[hidden] { display: none; }
|
|
507
|
+
#terminal-container .terminal-copy-banner .hint { opacity: 0.7; }
|
|
508
|
+
#terminal-container .terminal-copy-toast {
|
|
509
|
+
right: 6px; left: auto;
|
|
510
|
+
background: rgba(22,163,74,0.92);
|
|
511
|
+
font-weight: 600;
|
|
512
|
+
}
|
|
513
|
+
#terminal-container.terminal-copy-mode-active { cursor: text; }
|
|
497
514
|
|
|
498
515
|
/* \u2500\u2500 Mobile key toolbar \u2500\u2500 */
|
|
499
516
|
.mobile-keys {
|
|
@@ -568,7 +585,7 @@ import{cssVarsStyle as f}from"../theme.js";import{commandbarCSS as g,commandbarH
|
|
|
568
585
|
.mobile-keys .mk-buttons { display: none; }
|
|
569
586
|
.mobile-keys.mk-focused .mk-buttons { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
570
587
|
}
|
|
571
|
-
`,y=JSON.stringify(e.terminal).replace(/</g,"\\u003c"),c=JSON.stringify({terminal:
|
|
588
|
+
`,y=JSON.stringify(e.terminal).replace(/</g,"\\u003c"),c=JSON.stringify({terminal:p,scrollback:d,theme:e.terminal,renderer:r,fsRoots:s,commandbarEnabled:t}).replace(/</g,"\\u003c");return`<!DOCTYPE html>
|
|
572
589
|
<html lang="en">
|
|
573
590
|
<head>
|
|
574
591
|
<meta charset="UTF-8" />
|
package/dist/lib/web-browse.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{lookup as P}from"node:dns/promises";const C=3*1024*1024,W=8*1024*1024,T=15e3;class b extends Error{status;constructor(n,r=400){super(n),this.status=r,this.name="BrowseError"}}const
|
|
1
|
+
import{lookup as P}from"node:dns/promises";const C=3*1024*1024,W=8*1024*1024,T=15e3;class b extends Error{status;constructor(n,r=400){super(n),this.status=r,this.name="BrowseError"}}const R=new Map;function q(t){R.delete(t)}function U(t,n,r){let e=R.get(t);e||(e=new Map,R.set(t,e));const o=new URL(n).hostname.toLowerCase(),s=typeof r.getSetCookie=="function"?r.getSetCookie():[];for(const i of s){const a=i.split(";")[0]?.trim();if(!a)continue;const u=/;\s*domain=([^;\s]+)/i.exec(i)?.[1]?.trim().replace(/^\./,"").toLowerCase();e.set(u??o,a)}}function k(t,n){const r=R.get(t);if(!r||r.size===0)return"";const e=new URL(n).hostname.toLowerCase(),o=[];for(const[s,i]of r)(e===s||e.endsWith("."+s))&&o.push(i);return o.join("; ")}function I(t){const n=t.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);if(n){const[e,o]=[Number(n[1]),Number(n[2])];return e===0||e===10||e===127||e===169&&o===254||e===172&&o>=16&&o<=31||e===192&&o===168||e>=224}const r=t.toLowerCase();return r==="::1"||r==="::"||r.startsWith("fe80:")||r.startsWith("fc")||r.startsWith("fd")||r.startsWith("fec0:")}async function E(t){const n=new URL(t);if(n.protocol!=="http:"&&n.protocol!=="https:")throw new b("only http/https URLs are supported");if(process.env.TMUX_WEB_BROWSE_ALLOW_PRIVATE==="1")return;const r=n.hostname;if(I(r))throw new b("private network addresses are blocked");try{const e=await P(r,{all:!0});for(const{address:o}of e)if(I(o))throw new b("private network addresses are blocked")}catch(e){if(e instanceof b)throw e}}async function _(t,n,r){const e=new AbortController,o=setTimeout(()=>e.abort(),T);try{const s=await fetch(t,{...n,redirect:"follow",signal:e.signal});if(!s.ok)throw new b(`the site responded with HTTP ${s.status}`,502);if(!s.body)return{res:s,buf:Buffer.alloc(0),truncated:!1};const i=s.body.getReader(),a=[];let u=0,l=!1;for(;;){const{done:c,value:f}=await i.read();if(c)break;if(u+=f.byteLength,u>r){l=!0,await i.cancel();break}a.push(f)}return{res:s,buf:Buffer.concat(a),truncated:l}}catch(s){if(s instanceof b)throw s;if(s instanceof Error&&s.name==="AbortError")throw new b("timed out after "+T+"ms",504);let i;const a=s.cause;a&&(i=typeof a=="string"?a:a.code||a.message||String(a));const u=s.message||"request failed";throw new b(i&&u!==i?`${u} (${i})`:u,502)}finally{clearTimeout(o)}}function B(t,n){const r=/charset=([\w.-]+)/i.exec(t||"");if(r)return r[1];const e=n.subarray(0,4096).toString("latin1"),o=/<meta[^>]+charset=["']?([\w.-]+)/i.exec(e);return o?o[1]:"utf-8"}const M={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:"\xA0",copy:"\xA9",reg:"\xAE",trade:"\u2122",hellip:"\u2026",mdash:"\u2014",ndash:"\u2013",lsquo:"\u2018",rsquo:"\u2019",ldquo:"\u201C",rdquo:"\u201D"};function $(t){return t.replace(/&#x([0-9a-f]+);/gi,(n,r)=>String.fromCodePoint(parseInt(r,16)||0)).replace(/&#(\d+);/g,(n,r)=>String.fromCodePoint(parseInt(r,10)||0)).replace(/&([a-zA-Z][a-zA-Z0-9]*);/g,(n,r)=>M[r]??n)}function z(t,n,r){return`${r==="nav"?"/api/browse":"/api/browse/asset"}?url=${encodeURIComponent(t)}&tab=${encodeURIComponent(n)}`}function y(t,n,r,e){const o=t.trim();if(!o)return t;if(o.startsWith("#"))return o;if(/^(javascript|data|vbscript):/i.test(o))return e==="nav"?"":t;if(/^(mailto|tel|sms|about|blob|file):/i.test(o))return t;let s;try{s=new URL(o,n).href}catch{return t}if(!/^https?:/i.test(s))return t;try{const i=new URL(s);if(/(^|\.)duckduckgo\.com$/i.test(i.hostname)&&i.pathname==="/l/"&&e==="nav"){const a=i.searchParams.get("uddg");a&&/^https?:/i.test(a)&&(s=a)}}catch{}return z(s,r,e)}function Z(t,n,r){return t.split(",").map(e=>{const o=e.trim().match(/^(\S+)(\s+.+)?$/);if(!o)return e;const s=y(o[1],n,r,"asset");return s?s+(o[2]??""):e}).join(", ")}function j(t,n,r){return t.replace(/url\(\s*(['"]?)(.*?)\1\s*\)/g,(e,o,s)=>{const i=$(s.trim()),a=y(i,n,r,"asset");return a?`url(${a})`:e})}function N(t){const n=/([a-zA-Z-]+)(?:\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g,r=[];let e;for(;e=n.exec(t);)r.push({name:e[1],value:e[3]??e[4]??e[5]??""});return r}function L(t){return t.replace(/&/g,"&").replace(/"/g,""")}function A(t,n,r){const e=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(t),o=e?$(e[1]).replace(/\s+/g," ").trim():"",s=n,i=[];let a=0;const u=/<(\/)?([a-zA-Z][a-zA-Z0-9]*)((?:\s+[^<>]*?)?)(\/?)>/gs;let l;for(;l=u.exec(t);){i.push(t.slice(a,l.index));const[c,f,d,p,x]=l,h=d.toLowerCase();if(a=l.index+c.length,f){i.push(c);continue}if(h==="script"||h==="style"){const m=l.index+c.length,v=new RegExp(`</${h}\\s*>`,"gi"),g=t.slice(m),w=v.exec(g),O=w?g.slice(0,w.index):g,S=w?w[0]:"";h==="style"?i.push(c+j(O,s,r)+S):i.push(c+S),a=w?m+w.index+S.length:t.length,u.lastIndex=a;continue}if(h==="iframe"||h==="object"||h==="embed"){if(!x){const m=l.index+c.length,v=new RegExp(`</${h}\\s*>`,"gi"),g=t.slice(m),w=v.exec(g);a=w?m+w.index+w[0].length:t.length}u.lastIndex=a;continue}if(h==="base"){u.lastIndex=a;continue}if(h==="meta"&&/http-equiv=["']?refresh/i.test(p)){u.lastIndex=a;continue}i.push(D(c,h,p,s,r,l.index,t))}return i.push(t.slice(a)),{html:i.join(""),title:o}}function D(t,n,r,e,o,s,i){const a=N(r),u=[];for(const c of a){const f=c.name.toLowerCase(),d=c.value;if(f==="href"){if(n==="a"||n==="area")/^javascript:/i.test(d.trim())?c.remove=!0:c.value=y(d,e,o,"nav");else if(n==="link"){const p=(a.find(x=>x.name.toLowerCase()==="rel")?.value||"").toLowerCase();p.includes("stylesheet")||p.includes("icon")?c.value=y(d,e,o,"asset"):c.remove=!0}}else f==="src"&&(n==="img"||n==="source"||n==="video"||n==="audio"||n==="input")?c.value=y(d,e,o,"asset"):f==="srcset"&&n==="img"?c.value=Z(d,e,o):f==="poster"&&n==="video"?c.value=y(d,e,o,"asset"):f==="style"?c.value=j(d,e,o):f.startsWith("on")&&(c.remove=!0)}if(n==="form"){let c=a.find(p=>p.name.toLowerCase()==="action"),f=e;if(c&&c.value.trim())try{f=new URL($(c.value.trim()),e).href}catch{f=e}const d=(a.find(p=>p.name.toLowerCase()==="method")?.value||"get").toLowerCase();c?c.value=`/api/browse/submit?tab=${encodeURIComponent(o)}`:a.push({name:"action",value:`/api/browse/submit?tab=${encodeURIComponent(o)}`}),u.push(`<input type="hidden" name="__browse_action" value="${L(f)}">`),u.push(`<input type="hidden" name="__browse_method" value="${L(d)}">`)}let l="<"+n;for(const c of a)c.remove||c.name==="href"&&n==="a"&&c.value===""||(l+=" "+c.name+'="'+L(c.value)+'"');return l+=">",u.length&&(l+=u.join("")),l}async function H(t,n){await E(t);const r=k(n,t),{res:e,buf:o,truncated:s}=await _(t,{headers:r?{cookie:r}:void 0},C);U(n,t,e.headers);const i=e.headers.get("content-type")||"",a=B(i,o),u=new TextDecoder(a).decode(o),l=e.url||t;if(/html/i.test(i)||!/^(text|application)\/(json|xml|xhtml)/i.test(i)&&/<html[\s>]/i.test(u.slice(0,2e3))){const{html:f,title:d}=A(u,l,n);return{url:t,finalUrl:l,title:d,html:f,contentType:i,truncated:s}}if(/json/i.test(i))try{const f=JSON.stringify(JSON.parse(u),null,2);return{url:t,finalUrl:l,title:new URL(l).hostname,html:`<pre>${L(f)}</pre>`,contentType:i,truncated:s}}catch{}const c=L(u);return{url:t,finalUrl:l,title:new URL(l).hostname,html:`<pre style="white-space:pre-wrap;word-break:break-word;font-family:var(--font-mono);font-size:12px;line-height:1.6;padding:16px">${c}</pre>`,contentType:i,truncated:s}}async function X(t,n){await E(t);const r=k(n,t),{res:e,buf:o,truncated:s}=await _(t,{headers:r?{cookie:r}:void 0},W);return U(n,t,e.headers),{buf:o,contentType:e.headers.get("content-type")||"application/octet-stream"}}async function J(t,n,r,e){let o=t,s;if(r==="get"){const m=new URL(t);for(const[v,g]of n)m.searchParams.append(v,g);o=m.href,s={}}else{const m=new URLSearchParams;for(const[v,g]of n)m.append(v,g);s={method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:m.toString()}}await E(o);const i=k(e,o),{res:a,buf:u,truncated:l}=await _(o,{...s,headers:{...s.headers,...i?{cookie:i}:{}}},C);U(e,o,a.headers);const c=a.headers.get("content-type")||"",f=B(c,u),d=new TextDecoder(f).decode(u),p=a.url||o,{html:x,title:h}=A(d,p,e);return{url:o,finalUrl:p,title:h,html:x,contentType:c,truncated:l}}export{W as BROWSE_MAX_ASSET_BYTES,C as BROWSE_MAX_HTML_BYTES,T as BROWSE_TIMEOUT_MS,b as BrowseError,X as browseAsset,H as browsePage,J as browseSubmit,q as clearTabCookies,A as rewriteHtml};
|