fef67c148e
Tab key was being captured by the browser for focus cycling instead of being sent to the terminal for bash completion. Now intercepted at the document level and forwarded to the terminal when xterm has focus.
136 lines
4.9 KiB
JavaScript
136 lines
4.9 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);
|
|
|
|
// Send data to terminal via xterm's _core (bypasses any .input() issues)
|
|
function send(k){
|
|
if(document.body.classList.contains('selmode')) toggleSel();
|
|
var t=window.term;
|
|
if(!t) return;
|
|
// Try _core.triggerDataEvent first (fires onData which ttyd hooks)
|
|
if(t._core && t._core.coreService && t._core.coreService.triggerDataEvent){
|
|
t._core.coreService.triggerDataEvent(k);
|
|
} else if(t._core && t._core._onData){
|
|
t._core._onData.fire(k);
|
|
} else if(t.input){
|
|
t.input(k);
|
|
}
|
|
t.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) send(text);
|
|
}).catch(function(e){
|
|
alert('Clipboard read failed: '+e.message);
|
|
});
|
|
}
|
|
|
|
function doSave(){
|
|
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);
|
|
});
|
|
|
|
// Prevent browser from stealing Tab key — send it to the terminal instead
|
|
document.addEventListener('keydown', function(e){
|
|
if(e.key === 'Tab' && !e.altKey && !e.ctrlKey && !e.metaKey){
|
|
var active = document.activeElement;
|
|
// Only intercept if focus is on the terminal or body (not on toolbar buttons)
|
|
if(!active || active === document.body || active.closest('.xterm')){
|
|
e.preventDefault();
|
|
send('\t');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Shrink terminal for toolbar on mobile (2 rows)
|
|
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});
|
|
})();
|