feat(webssh): replace the arrow keys with a Paste button

- drop the four on-screen arrow buttons: a physical keyboard already sends
  them, and the app-cursor mode that made them awkward is handled by keySequence
- add a Paste button that reads the clipboard and hands the text to the shell
  the same way a browser paste does, bracketed paste included
- report a refusal (no secure context or permission) instead of looking dead,
  and fall back to Ctrl+V
This commit is contained in:
bluebluesoda committed 2026-09-26 15:46:48 +00:00
1 parent 769eeb0f2c
commit ef5be89643
3 files changed
+37 -9

No files matched your search

+4 -1
View File
@@ -52,7 +52,10 @@ Insert/Delete, Tab, Escape and the function keys all send what a terminal sends,
and the arrows follow the mode a full-screen program asks for.
A small bar above the terminal carries the few keys a browser keeps for itself
(Ctrl+C, Ctrl+Z, Ctrl+D, Escape, Tab), so they are never out of reach.
(Ctrl+C, Ctrl+Z, Ctrl+D, Escape, Tab), so they are never out of reach, plus a
**Paste** button. It reads the clipboard through the browser's clipboard API,
so it needs a secure context and permission; if either is missing the bar says
so, and Ctrl+V still works.
Selecting text is left to the browser, so it works like text anywhere: drag
across the terminal to select, double-click a word, and the selection survives
+13 -2
View File
@@ -664,12 +664,20 @@
e.preventDefault();
};
// paste hands text to the shell the way a browser paste would, including the
// bracketed-paste wrapper when the running program asked for it. The toolbar's
// Paste button goes through here so it behaves exactly like Ctrl+V.
Term.prototype.paste = function (text) {
if (!text) return;
if (this.modes.bracketed) this._send("\x1b[200~" + text + "\x1b[201~");
else this._send(text);
};
Term.prototype._onPaste = function (e) {
var text = (e.clipboardData || window.clipboardData).getData("text");
if (!text) return;
e.preventDefault();
if (this.modes.bracketed) this._send("\x1b[200~" + text + "\x1b[201~");
else this._send(text);
this.paste(text);
};
// keySequence is what a named key sends. The arrows depend on the mode the
@@ -961,6 +969,9 @@
if (seq !== null) { input(seq); term.focus(); }
},
raw: function (text) { input(text); if (term) term.focus(); },
// paste is the toolbar's Paste button: clipboard text is delivered the
// same way a browser paste is, so bracketed paste still applies.
paste: function (text) { if (term) { term.paste(text); term.focus(); } },
// reconnect is the manual path offered once the retries are exhausted.
reconnect: function () {
finished = false;
+20 -6
View File
@@ -71,14 +71,11 @@ main>div{height:100%}
<button type="button" data-key="Escape">Esc</button>
<button type="button" data-key="Tab">Tab</button>
<span class="sep"></span>
<button type="button" data-key="ArrowLeft">←</button>
<button type="button" data-key="ArrowUp">↑</button>
<button type="button" data-key="ArrowDown">↓</button>
<button type="button" data-key="ArrowRight">→</button>
<span class="sep"></span>
<button type="button" data-ctl="c">^C</button>
<button type="button" data-ctl="z">^Z</button>
<button type="button" data-ctl="d">^D</button>
<span class="sep"></span>
<button type="button" id="pasteBtn">{{if eq .Lang "zh"}}粘贴{{else}}Paste{{end}}</button>
</div>
<main><div id="termMount"></div></main>
<div class="ask" id="ask" hidden>
@@ -165,11 +162,28 @@ main>div{height:100%}
// A control character cannot be written into an HTML attribute, so the
// buttons carry a name and the byte is looked up here.
var CTL={c:"\u0003",z:"\u001a",d:"\u0004"};
// The clipboard API needs a secure context and the page's permission, and
// neither is guaranteed, so a refusal is reported instead of looking dead.
function doPaste(){
if(!session)return;
if(!(navigator.clipboard&&navigator.clipboard.readText)){
setStatus(T('此浏览器不能用按钮粘贴,请按 Ctrl+V / Cmd+V','this browser cannot paste from a button — press Ctrl+V'));
session.focus();
return;
}
navigator.clipboard.readText().then(function(text){
if(text)session.paste(text); else session.focus();
}).catch(function(){
setStatus(T('无法读取剪贴板,请按 Ctrl+V / Cmd+V','could not read the clipboard — press Ctrl+V'));
session.focus();
});
}
keys.addEventListener('mousedown',function(e){ e.preventDefault(); });
keys.addEventListener('click',function(e){
var b=e.target.closest('button');
if(!b||!session)return;
if(b.dataset.key)session.key(b.dataset.key);
if(b.id==='pasteBtn')doPaste();
else if(b.dataset.key)session.key(b.dataset.key);
else if(b.dataset.ctl&&CTL[b.dataset.ctl])session.raw(CTL[b.dataset.ctl]);
});