feat: initial release - mobile-first web terminal with tmux touch toolbar and push notifications

This commit is contained in:
Mortdecai
2026-03-26 18:59:37 -04:00
commit 5f1beb4d4d
13 changed files with 376 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
(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:5px 4px;gap:4px;justify-content:center;
flex-wrap:wrap;z-index:99999}
#mb button{background:#222;color:#ccc;border:1px solid #444;border-radius:5px;
padding:10px 12px;font-size:14px;font-family:ui-monospace,monospace;
cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;
min-width:42px;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 .sep{width:1px;background:#333;margin:0 2px;align-self:stretch}
@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=
'<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 class="sep"></div>'+
'<button class="hi" id="selbtn" data-sel="1">Sel</button>'+
'<button data-k="\\x01%">Spl</button>'+
'<button data-k="\\x01o">Pane</button>'+
'<button data-k="\\x01x">Kill</button>';
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();
}
}
bar.addEventListener('click',function(e){
var btn=e.target.closest('button');
if(!btn) return;
if(btn.dataset.sel) return toggleSel();
if(btn.dataset.k) send(btn.dataset.k);
});
// Shrink terminal for toolbar on mobile
var obs=new MutationObserver(function(){
var el=document.querySelector('.xterm');
if(el && window.innerWidth<=900){
el.style.height='calc(100vh - 54px)';
if(window.term && window.term.fit) window.term.fit();
}
});
obs.observe(document.body,{childList:true,subtree:true});
})();