8d075f2b48
- 200K scrollback, vi-mode copy, copy-on-select - Ctrl-A S captures pane to ~/logs/, Ctrl-A L toggles live logging - Status bar shows git branch + working directory - Pre-built session: code/git/run/logs windows - Aggressive resize + focus events for multi-device switching - Toolbar: added Paste (clipboard), Zoom (pane fullscreen), Save (capture) - All new windows inherit current working directory - Toolbar split into 2 rows for more buttons without clutter
119 lines
4.4 KiB
JavaScript
119 lines
4.4 KiB
JavaScript
(function(){
|
|
if(window._toolbar) return;
|
|
window._toolbar=true;
|
|
|
|
var css=document.createElement('style');
|
|
css.textContent=`
|
|
#mb{display:none;position:fixed;bottom:0;left:0;right:0;background:#111;
|
|
border-top:2px solid #D35400;padding:4px 3px;gap:3px;justify-content:center;
|
|
flex-wrap:wrap;z-index:99999}
|
|
#mb button{background:#222;color:#ccc;border:1px solid #444;border-radius:5px;
|
|
padding:9px 10px;font-size:13px;font-family:ui-monospace,monospace;
|
|
cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;
|
|
min-width:38px;text-align:center;user-select:none}
|
|
#mb button:active{background:#D35400;color:#0a0a0a;border-color:#D35400}
|
|
#mb button.hi{border-color:#D35400;color:#D35400}
|
|
#mb button.on{background:#D35400;color:#0a0a0a;border-color:#D35400}
|
|
#mb button.grn{border-color:#4e9a06;color:#4e9a06}
|
|
#mb button.grn.on{background:#4e9a06;color:#0a0a0a}
|
|
#mb .sep{width:1px;background:#333;margin:0 1px;align-self:stretch}
|
|
#mb .row{display:flex;gap:3px;justify-content:center;width:100%}
|
|
@media(max-width:900px){#mb{display:flex}}
|
|
body.selmode .xterm-screen{pointer-events:none!important;
|
|
user-select:text!important;-webkit-user-select:text!important}
|
|
`;
|
|
document.head.appendChild(css);
|
|
|
|
var bar=document.createElement('div');
|
|
bar.id='mb';
|
|
bar.innerHTML=
|
|
'<div class="row">'+
|
|
'<button class="hi" data-k="\\x01c">+Tab</button>'+
|
|
'<button data-k="\\x01n">Next</button>'+
|
|
'<button data-k="\\x01p">Prev</button>'+
|
|
'<div class="sep"></div>'+
|
|
'<button data-k="\\x03">^C</button>'+
|
|
'<button data-k="\\x04">^D</button>'+
|
|
'<button data-k="\\x0c">Clr</button>'+
|
|
'<div class="sep"></div>'+
|
|
'<button data-k="\\x1b">Esc</button>'+
|
|
'<button data-k="\\t">Tab</button>'+
|
|
'<button data-k="\\x1bOA">\u25B2</button>'+
|
|
'<button data-k="\\x1bOB">\u25BC</button>'+
|
|
'</div>'+
|
|
'<div class="row">'+
|
|
'<button class="hi" id="selbtn" data-sel="1">Sel</button>'+
|
|
'<button class="hi" id="pastebtn" data-paste="1">Paste</button>'+
|
|
'<button data-k="\\x01z">Zoom</button>'+
|
|
'<button class="grn" data-save="1">Save</button>'+
|
|
'<div class="sep"></div>'+
|
|
'<button data-k="\\x01v">V.Spl</button>'+
|
|
'<button data-k="\\x01s">H.Spl</button>'+
|
|
'<button data-k="\\x01o">Pane</button>'+
|
|
'<button data-k="\\x01x">Kill</button>'+
|
|
'</div>';
|
|
document.body.appendChild(bar);
|
|
|
|
function send(k){
|
|
if(document.body.classList.contains('selmode')) toggleSel();
|
|
k=k.replace(/\\x([0-9a-f]{2})/gi,function(_,h){return String.fromCharCode(parseInt(h,16));});
|
|
k=k.replace(/\\t/g,'\t');
|
|
if(window.term){window.term.input(k);window.term.focus();}
|
|
}
|
|
|
|
function toggleSel(){
|
|
var b=document.getElementById('selbtn');
|
|
document.body.classList.toggle('selmode');
|
|
if(document.body.classList.contains('selmode')){
|
|
b.classList.add('on');b.textContent='Done';
|
|
} else {
|
|
b.classList.remove('on');b.textContent='Sel';
|
|
window.getSelection().removeAllRanges();
|
|
if(window.term) window.term.focus();
|
|
}
|
|
}
|
|
|
|
function doPaste(){
|
|
if(!navigator.clipboard||!navigator.clipboard.readText){
|
|
alert('Clipboard access not available (needs HTTPS)');
|
|
return;
|
|
}
|
|
navigator.clipboard.readText().then(function(text){
|
|
if(text && window.term){
|
|
window.term.input(text);
|
|
window.term.focus();
|
|
}
|
|
}).catch(function(e){
|
|
alert('Clipboard read failed: '+e.message);
|
|
});
|
|
}
|
|
|
|
function doSave(){
|
|
// Trigger tmux capture-pane via the keybinding Ctrl-A S
|
|
send('\\x01S');
|
|
var btn=document.querySelector('[data-save]');
|
|
btn.classList.add('on');
|
|
btn.textContent='\u2713';
|
|
setTimeout(function(){btn.classList.remove('on');btn.textContent='Save';},1500);
|
|
}
|
|
|
|
bar.addEventListener('click',function(e){
|
|
var btn=e.target.closest('button');
|
|
if(!btn) return;
|
|
if(btn.dataset.sel) return toggleSel();
|
|
if(btn.dataset.paste) return doPaste();
|
|
if(btn.dataset.save) return doSave();
|
|
if(btn.dataset.k) send(btn.dataset.k);
|
|
});
|
|
|
|
// Shrink terminal for toolbar on mobile (2 rows now)
|
|
var obs=new MutationObserver(function(){
|
|
var el=document.querySelector('.xterm');
|
|
if(el && window.innerWidth<=900){
|
|
el.style.height='calc(100vh - 88px)';
|
|
if(window.term && window.term.fit) window.term.fit();
|
|
}
|
|
});
|
|
obs.observe(document.body,{childList:true,subtree:true});
|
|
})();
|