Terminal: Ctrl+C copies a selection (else ^C), Ctrl+V pastes, over http too; drop Log out everywhere
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d7ed4d8549
commit
d3f2a83249
@@ -124,12 +124,6 @@ export default function Settings({ me }: { me: Me }) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="dash-bar"><h2 className="section">Sessions</h2></div>
|
||||
<section className="card">
|
||||
<div className="muted small" style={{ marginBottom: 8 }}>Signs out every browser and phone that is logged in to this Ham Lab, this one included. Access tokens are not sessions and keep working.</div>
|
||||
<Btn className="btn danger" confirm="Log out everywhere? Every device, including this one, will need the password again." onClick={async () => { try { await api.logoutAll() } finally { window.dispatchEvent(new Event('hamlab:unauthenticated')) } }}>Log out everywhere</Btn>
|
||||
</section>
|
||||
|
||||
{making && <TokenDialog onClose={() => setMaking(false)} onMade={(t) => { setMaking(false); setFresh(t); loadTokens() }} />}
|
||||
|
||||
<div className="dash-bar"><h2 className="section">About</h2></div>
|
||||
|
||||
@@ -84,6 +84,26 @@ export default function TerminalPane({ id, active, register, onStatus, onExit }:
|
||||
}
|
||||
connect()
|
||||
|
||||
// Ctrl+C / Ctrl+V as on any desktop. Returning false tells xterm to leave the key alone,
|
||||
// and *not* preventing the default lets the browser fire its own copy / paste event —
|
||||
// which xterm answers with its selection, or pastes (bracketed, if the shell asked). That
|
||||
// path needs no navigator.clipboard, so it works over plain http on the LAN as well.
|
||||
term.attachCustomKeyEventHandler((e) => {
|
||||
if (e.type !== 'keydown') return true
|
||||
const k = e.key.toLowerCase(), ctrl = e.ctrlKey && !e.altKey && !e.metaKey
|
||||
const copy = (ctrl && k === 'c' && (e.shiftKey || term.hasSelection())) || (ctrl && k === 'insert')
|
||||
const paste = (ctrl && k === 'v') || (e.shiftKey && !ctrl && k === 'insert')
|
||||
if (copy) {
|
||||
if (!term.hasSelection()) { e.preventDefault(); return false } // Ctrl+Shift+C with nothing selected: not devtools, not ^C
|
||||
if (e.shiftKey) { e.preventDefault(); document.execCommand('copy') } // Chrome keeps Ctrl+Shift+C for itself; copy by hand
|
||||
if (navigator.clipboard) navigator.clipboard.writeText(term.getSelection()).catch(() => {})
|
||||
window.setTimeout(() => term.clearSelection(), 50) // so the next Ctrl+C is an interrupt again
|
||||
return false
|
||||
}
|
||||
if (paste) return false
|
||||
return true
|
||||
})
|
||||
|
||||
term.onData((d) => {
|
||||
if (ctrlArmed && d.length === 1) {
|
||||
ctrlArmed = false
|
||||
|
||||
@@ -182,7 +182,6 @@ export const api = {
|
||||
changePassword: (current: string, password: string) => req<void>('POST', '/api/password', { current, password }, true),
|
||||
tokens: () => req<{ name: string; created: string; scope?: string; sensors?: string[] }[]>('GET', '/api/tokens', undefined, true),
|
||||
createReadToken: (name: string, sensors: string[] = []) => req<{ token: string; name: string }>('POST', '/api/tokens/read', { name, sensors }, true),
|
||||
logoutAll: () => req<{ ended: number }>('POST', '/api/logout/all', undefined, true),
|
||||
revokeToken: (name: string) => req<void>('DELETE', `/api/tokens/${encodeURIComponent(name)}`, undefined, true),
|
||||
uiPrefs: () => req<{ theme?: string; refreshMs?: number }>('GET', '/api/prefs/ui', undefined, true),
|
||||
saveUIPrefs: (p: { theme?: string; refreshMs?: number }) => req<void>('PUT', '/api/prefs/ui', p, true),
|
||||
|
||||
Reference in New Issue
Block a user