From be22fe5356239d6e0ca89fe7c62ac9a4670a0fd8 Mon Sep 17 00:00:00 2001 From: Jage9 Date: Sat, 21 Feb 2026 03:43:11 -0500 Subject: [PATCH] Enable Ctrl+C/X/V speech feedback in text editors --- client/public/version.js | 2 +- client/src/main.ts | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/client/public/version.js b/client/public/version.js index 6c63f1b..3d3deb0 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.21 R91"; +window.CHGRID_WEB_VERSION = "2026.02.21 R92"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/main.ts b/client/src/main.ts index 911b512..702623c 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -153,6 +153,7 @@ let connecting = false; const messageBuffer: string[] = []; let messageCursor = -1; const radioRuntime = new RadioStationRuntime(audio); +let internalClipboardText = ''; let replaceTextOnNextType = false; let pendingEscapeDisconnect = false; @@ -571,6 +572,10 @@ function pasteIntoActiveTextInput(raw: string): boolean { return true; } +function isTextEditingMode(mode: typeof state.mode): boolean { + return mode === 'nickname' || mode === 'chat' || mode === 'itemPropertyEdit'; +} + function mapTextInputKey(code: string, key: string): string { if (code === 'ArrowLeft') return 'arrowleft'; if (code === 'ArrowRight') return 'arrowright'; @@ -2035,12 +2040,36 @@ function setupInputHandlers(): void { if (!state.running) return; if (document.activeElement !== dom.canvas) return; - if (event.ctrlKey || event.altKey) return; + if (event.altKey) return; + if (event.ctrlKey && !isTextEditingMode(state.mode)) return; if (state.mode !== 'normal' || !code.startsWith('Arrow')) { event.preventDefault(); } + if (event.ctrlKey && isTextEditingMode(state.mode)) { + if (code === 'KeyC') { + const text = state.nicknameInput; + internalClipboardText = text; + void navigator.clipboard?.writeText(text).catch(() => undefined); + updateStatus('copied'); + return; + } + if (code === 'KeyX') { + const text = state.nicknameInput; + internalClipboardText = text; + void navigator.clipboard?.writeText(text).catch(() => undefined); + state.nicknameInput = ''; + state.cursorPos = 0; + replaceTextOnNextType = false; + updateStatus('cut'); + return; + } + if (code === 'KeyV') { + updateStatus('pasted'); + } + } + if (isTypingKey(code) && state.keysPressed[code]) return; if (state.mode === 'nickname') { @@ -2077,9 +2106,10 @@ function setupInputHandlers(): void { document.addEventListener('paste', (event) => { if (document.activeElement !== dom.canvas) return; if (!state.running) return; - const pasted = event.clipboardData?.getData('text') ?? ''; + const pasted = event.clipboardData?.getData('text') ?? internalClipboardText; if (!pasteIntoActiveTextInput(pasted)) return; event.preventDefault(); + updateStatus('pasted'); }); }