From df46b5c14ec7baa44cc57ef6a2f570518b141fab Mon Sep 17 00:00:00 2001 From: Augustin Date: Fri, 24 Apr 2026 22:28:15 +0200 Subject: [PATCH] feat(shell): add Ctrl+/- zoom and display all shortcuts in footer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ctrl+/Ctrl-/Ctrl+0 to zoom in/out/reset terminal font size - Zoom badge indicator in tab bar - All shell shortcuts now shown in statusbar footer - Added i18n labels for search, zoom, switch tab, next tab 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush --- web/src/components/App.jsx | 4 +++ web/src/components/Shell.jsx | 53 ++++++++++++++++++++++++++++++++++++ web/src/i18n/en.js | 4 +++ web/src/i18n/fr.js | 4 +++ web/src/styles/global.css | 8 ++++++ 5 files changed, 73 insertions(+) diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx index 5232c0d..563a7ba 100644 --- a/web/src/components/App.jsx +++ b/web/src/components/App.jsx @@ -94,6 +94,10 @@ export default function App() { shell: [ { keys: `${layout.keys.ctrl}+${layout.keys.shift}+C`, desc: t('statusbar.copy') }, { keys: `${layout.keys.ctrl}+${layout.keys.shift}+V`, desc: t('statusbar.paste') }, + { keys: `${layout.keys.ctrl}+F`, desc: t('statusbar.search') }, + { keys: `${layout.keys.ctrl}+/Ctrl−`, desc: t('statusbar.zoom') }, + { keys: `Alt+1-7`, desc: t('statusbar.switchTab') }, + { keys: `${layout.keys.shift}+Tab`, desc: t('statusbar.nextTab') }, { keys: layout.keys.enter, desc: t('statusbar.runCommand') }, { keys: `${layout.keys.up}/${layout.keys.down}`, desc: t('statusbar.commandHistory') }, ], diff --git a/web/src/components/Shell.jsx b/web/src/components/Shell.jsx index 0d01481..5e88be7 100644 --- a/web/src/components/Shell.jsx +++ b/web/src/components/Shell.jsx @@ -255,6 +255,27 @@ function createTerminal(container, settings = {}) { return false } + if (ctrl && (e.key === '=' || e.key === '+')) { + e.preventDefault() + e.stopPropagation() + window.dispatchEvent(new CustomEvent('shell-zoom', { detail: 1 })) + return false + } + + if (ctrl && e.key === '-') { + e.preventDefault() + e.stopPropagation() + window.dispatchEvent(new CustomEvent('shell-zoom', { detail: -1 })) + return false + } + + if (ctrl && e.key === '0') { + e.preventDefault() + e.stopPropagation() + window.dispatchEvent(new CustomEvent('shell-zoom', { detail: 0 })) + return false + } + return true }) @@ -387,9 +408,36 @@ export default function Shell({ api }) { const [searchText, setSearchText] = useState('') const searchInputRef = useRef(null) const searchDecorationsRef = useRef(null) + const [zoomLevel, setZoomLevel] = useState(0) + const baseFontSizeRef = useRef(12) useEffect(() => { settingsRef.current = terminalSettings }, [terminalSettings]) + useEffect(() => { + baseFontSizeRef.current = terminalSettings.fontSize || 12 + }, [terminalSettings.fontSize]) + + useEffect(() => { + const handler = (e) => { + const direction = e.detail + setZoomLevel(prev => { + let next + if (direction === 0) next = 0 + else next = Math.max(-8, Math.min(10, prev + direction)) + const newSize = baseFontSizeRef.current + next * 2 + for (const entry of Object.values(tabsRef.current)) { + if (entry.term && !entry.term._disposed) { + entry.term.options.fontSize = newSize + try { entry.fitAddon.fit() } catch {} + } + } + return next + }) + } + window.addEventListener('shell-zoom', handler) + return () => window.removeEventListener('shell-zoom', handler) + }, []) + const [sshForm, setSshForm] = useState({ name: '', host: '', port: 22, user: '', key_path: '', }) @@ -1059,6 +1107,11 @@ export default function Shell({ api }) {
+ {zoomLevel !== 0 && ( + + {zoomLevel > 0 ? '+' : ''}{zoomLevel > 0 ? zoomLevel * 2 : zoomLevel * 2}px + + )} {tabs.length < MAX_TABS && (