ui: Find & Replace, multi-line comments, indent guides, font size
Find & Replace (Ctrl+H): - Two-row find bar: expand › arrow shows replace row (VS Code pattern) - Replace button (Enter): replaces current match, advances to next - Replace All: replaces all occurrences, shows count - Find input searches live; replace row preserved across open/close Multi-line comment toggle (Ctrl+/): - With multi-line selection: checks if ALL lines are commented → if all commented, uncomments all; otherwise comments all - Empty lines in selection are skipped - After toggle: block stays selected (same as VS Code) - Single-line behavior unchanged Indent guides: - CSS repeating-linear-gradient on #vv-editor-inner: 1px guide every 2ch - First guide at indent level 1 (col 2); then 4, 6, 8... matching 2-space indents - Shows through the transparent overlay; scales automatically with font size - Very subtle (0.045 opacity) — visible but not distracting Font size A− / A+ in status bar (9–22px): - Persisted to localStorage, restored on every editor open - Updates textarea, overlay, gutter, and cur-line height together - All scroll calculations use vvLH() (fontSizePx × 1.5) instead of hardcoded 18 — find nav, goto, select-next-occurrence all correct at any font size Also: fixed duplicate id="vv-find-close" in goto bar (now .vv-find-x class) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
beac98427d
commit
586c7c36c1
@@ -368,24 +368,37 @@
|
||||
#vv-editor-body::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 4px; }
|
||||
#vv-editor-body::-webkit-scrollbar-thumb:hover { background: #444; }
|
||||
|
||||
/* Editor find bar */
|
||||
#vv-editor-find { display: none; align-items: center; gap: 6px; padding: 5px 8px;
|
||||
/* Editor find bar — two-row (find + optional replace) */
|
||||
#vv-editor-find { display: none; flex-direction: column; gap: 0; padding: 5px 8px;
|
||||
margin-bottom: 5px; background: #161616; border: 1px solid #333;
|
||||
border-radius: 4px; font-size: 12px; }
|
||||
#vv-editor-find.vv-find-open { display: flex; }
|
||||
#vv-find-input { flex: 1; min-width: 0; background: #0d0d0d; border: 1px solid #333;
|
||||
color: #ddd; padding: 4px 8px; border-radius: 3px;
|
||||
font-family: monospace; font-size: 12px; }
|
||||
#vv-find-input:focus { border-color: #6495ed; outline: none; box-shadow: 0 0 0 1px rgba(100,149,237,0.25); }
|
||||
.vv-find-row { display: flex; align-items: center; gap: 6px; }
|
||||
.vv-replace-row { display: none; align-items: center; gap: 6px;
|
||||
margin-top: 4px; padding-top: 4px; border-top: 1px solid #222; }
|
||||
.vv-replace-row.vv-repl-open { display: flex; }
|
||||
.vv-find-expand { font-size: 10px; color: #555; cursor: pointer; flex-shrink: 0;
|
||||
width: 14px; text-align: center; user-select: none;
|
||||
transition: transform 0.15s; line-height: 1; }
|
||||
.vv-find-expand:hover { color: #aaa; }
|
||||
.vv-find-expand.open { transform: rotate(90deg); color: #888; }
|
||||
#vv-find-input, #vv-replace-input {
|
||||
flex: 1; min-width: 0; background: #0d0d0d; border: 1px solid #333;
|
||||
color: #ddd; padding: 4px 8px; border-radius: 3px;
|
||||
font-family: monospace; font-size: 12px; }
|
||||
#vv-find-input:focus, #vv-replace-input:focus {
|
||||
border-color: #6495ed; outline: none; box-shadow: 0 0 0 1px rgba(100,149,237,0.25); }
|
||||
#vv-find-input.vv-find-no-match { border-color: #c62828; box-shadow: 0 0 0 1px rgba(198,40,40,0.3); }
|
||||
.vv-find-count { font-size: 11px; color: #555; white-space: nowrap; flex-shrink: 0; min-width: 64px; text-align: right; }
|
||||
.vv-find-nav-btn{ padding: 2px 8px !important; }
|
||||
#vv-find-close { color: #555; cursor: pointer; font-size: 16px; line-height: 1;
|
||||
.vv-repl-btn { border-color: #444 !important; color: #777 !important; }
|
||||
.vv-repl-btn:hover { border-color: #777 !important; color: #ddd !important; }
|
||||
.vv-find-x { color: #555; cursor: pointer; font-size: 16px; line-height: 1;
|
||||
padding: 0 2px; flex-shrink: 0; }
|
||||
#vv-find-close:hover { color: #aaa; }
|
||||
.vv-find-x:hover { color: #aaa; }
|
||||
|
||||
/* Editor status bar */
|
||||
#vv-editor-status { display: none; align-items: center; gap: 14px; padding: 3px 10px;
|
||||
#vv-editor-status { display: none; align-items: center; gap: 10px; padding: 3px 10px;
|
||||
background: #0e0e0e; border: 1px solid #2e2e2e; border-top: none;
|
||||
border-radius: 0 0 4px 4px; font-family: monospace;
|
||||
font-size: 10px; color: #444; user-select: none; flex-shrink: 0; }
|
||||
@@ -393,6 +406,24 @@
|
||||
.vv-es-pos { color: #666; }
|
||||
.vv-es-sel { color: #555; }
|
||||
.vv-es-lang { margin-left: auto; color: #4a6a7a; }
|
||||
/* Status bar action buttons */
|
||||
.vv-es-btn { background: none; border: none; cursor: pointer; color: #555;
|
||||
font-size: 10px; padding: 1px 4px; border-radius: 2px; font-family: monospace; }
|
||||
.vv-es-btn:hover { color: #aaa; background: rgba(255,255,255,0.05); }
|
||||
.vv-es-btn.active { color: #6495ed; }
|
||||
#vv-es-fontsize { color: #444; font-size: 10px; min-width: 26px; text-align: center; }
|
||||
|
||||
/* Indent guides — 1px lines every 2 chars, starting at indent level 1 */
|
||||
#vv-editor-inner {
|
||||
position: relative; flex: 1; overflow: hidden;
|
||||
background-color: #0d0d0d;
|
||||
background-image: repeating-linear-gradient(
|
||||
90deg,
|
||||
rgba(255,255,255,0.045) 0 1px,
|
||||
transparent 1px 2ch
|
||||
);
|
||||
background-position: calc(12px + 2ch) 0;
|
||||
}
|
||||
|
||||
/* Go to line bar */
|
||||
#vv-goto-bar { display: none; align-items: center; gap: 8px; padding: 5px 8px;
|
||||
|
||||
@@ -469,11 +469,13 @@ $runningScripts = array_unique($runningScripts);
|
||||
<li class="vv-info-sep">Editor Keyboard Shortcuts</li>
|
||||
<li><strong>Ctrl+S</strong> — save · <strong>Ctrl+Z / Ctrl+Y</strong> — undo / redo (200 steps)</li>
|
||||
<li><strong>Ctrl+/</strong> — toggle line comment · <strong>Tab / Shift+Tab</strong> — indent / dedent</li>
|
||||
<li><strong>Ctrl+F</strong> — find · <strong>Ctrl+G</strong> — go to line · <strong>Ctrl+D</strong> — select next occurrence</li>
|
||||
<li><strong>Ctrl+L</strong> — select line · <strong>Ctrl+Shift+K</strong> — delete line</li>
|
||||
<li><strong>Ctrl+F</strong> — find · <strong>Ctrl+H</strong> — find & replace · <strong>Ctrl+G</strong> — go to line</li>
|
||||
<li><strong>Ctrl+D</strong> — select next occurrence · <strong>Ctrl+L</strong> — select line · <strong>Ctrl+Shift+K</strong> — delete line</li>
|
||||
<li><strong>Alt+↑/↓</strong> — move line · <strong>Alt+Shift+↓</strong> — duplicate line below</li>
|
||||
<li><strong>Home</strong> — smart (first non-space → col 0)</li>
|
||||
<li><strong>( { [ " ` </strong> — auto-close pair; wraps selection if text is selected; <strong>Backspace</strong> deletes both when between them</li>
|
||||
<li><strong>( { [ " ` </strong> — auto-close pair; wraps selection; Backspace deletes both when between them</li>
|
||||
<li><strong>A− / A+</strong> in status bar — font size (9–22px, persists across sessions)</li>
|
||||
<li><strong>Indent guides</strong> — faint vertical lines every 2 spaces in the background</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -883,19 +885,29 @@ Still the same two servers, two households, the same media stack running itself.
|
||||
<input type="text" id="vv-editor-name" class="vv-cron" style="flex:1" placeholder="script_name (no .sh)">
|
||||
</div>
|
||||
<div id="vv-editor-find">
|
||||
<input type="text" id="vv-find-input" placeholder="Find…"
|
||||
oninput="vvFindSearch()" onkeydown="vvFindKeydown(event)">
|
||||
<span class="vv-find-count" id="vv-find-count"></span>
|
||||
<button class="vv-btn-sm vv-find-nav-btn" onclick="vvFindNav(-1)" title="Previous (Shift+Enter)">↑</button>
|
||||
<button class="vv-btn-sm vv-find-nav-btn" onclick="vvFindNav(1)" title="Next (Enter)">↓</button>
|
||||
<span id="vv-find-close" onclick="vvFindClose()" title="Close (Esc)">×</span>
|
||||
<div class="vv-find-row">
|
||||
<span class="vv-find-expand" id="vv-find-expand" onclick="vvFindToggleReplace()" title="Toggle Replace (Ctrl+H)">›</span>
|
||||
<input type="text" id="vv-find-input" placeholder="Find…"
|
||||
oninput="vvFindSearch()" onkeydown="vvFindKeydown(event)">
|
||||
<span class="vv-find-count" id="vv-find-count"></span>
|
||||
<button class="vv-btn-sm vv-find-nav-btn" onclick="vvFindNav(-1)" title="Previous (Shift+Enter)">↑</button>
|
||||
<button class="vv-btn-sm vv-find-nav-btn" onclick="vvFindNav(1)" title="Next (Enter)">↓</button>
|
||||
<span class="vv-find-x" onclick="vvFindClose()" title="Close (Esc)">×</span>
|
||||
</div>
|
||||
<div class="vv-replace-row" id="vv-replace-row">
|
||||
<span style="width:14px;flex-shrink:0"></span>
|
||||
<input type="text" id="vv-replace-input" placeholder="Replace with…"
|
||||
onkeydown="vvReplaceKeydown(event)">
|
||||
<button class="vv-btn-sm vv-repl-btn" onclick="vvReplaceOne()" title="Replace (Enter)">Replace</button>
|
||||
<button class="vv-btn-sm vv-repl-btn" onclick="vvReplaceAll()" title="Replace All">All</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="vv-goto-bar">
|
||||
<span style="color:#666;font-size:12px;flex-shrink:0">Go to line</span>
|
||||
<input type="number" id="vv-goto-input" min="1" placeholder="#"
|
||||
oninput="vvGotoApply()" onkeydown="vvGotoKeydown(event)">
|
||||
<span class="vv-goto-info" id="vv-goto-info"></span>
|
||||
<span id="vv-find-close" onclick="vvGotoClose()" title="Close (Esc)">×</span>
|
||||
<span class="vv-find-x" onclick="vvGotoClose()" title="Close (Esc)">×</span>
|
||||
</div>
|
||||
<div id="vv-editor-wrap">
|
||||
<pre id="vv-ln-gutter" aria-hidden="true"></pre>
|
||||
@@ -912,6 +924,9 @@ Still the same two servers, two households, the same media stack running itself.
|
||||
<div id="vv-editor-status">
|
||||
<span class="vv-es-pos" id="vv-es-pos">Ln 1, Col 1</span>
|
||||
<span class="vv-es-sel" id="vv-es-sel"></span>
|
||||
<button class="vv-es-btn" onclick="vvFontSize(-1)" title="Decrease font size">A−</button>
|
||||
<span id="vv-es-fontsize">12px</span>
|
||||
<button class="vv-es-btn" onclick="vvFontSize(1)" title="Increase font size">A+</button>
|
||||
<span class="vv-es-lang" id="vv-es-lang"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -997,6 +1012,10 @@ const VV_UNDO_MAX = 200; // max history depth
|
||||
// Auto-close pairs
|
||||
const VV_PAIRS = {'(':')', '[':']', '{':'}', '"':'"', "'":"'", '`':'`'};
|
||||
const VV_CLOSE = new Set([')', ']', '}', '"', "'", '`']);
|
||||
// Editor display prefs
|
||||
let vvFontSizePx = parseInt(localStorage.getItem('vv-editor-fontsize') || '12') || 12;
|
||||
// Line height helper — used everywhere instead of hardcoded 18
|
||||
function vvLH() { return vvFontSizePx * 1.5; }
|
||||
|
||||
// Setup mode — injected by PHP when navigating from the first-run wizard
|
||||
let vvSetupConf = <?= json_encode($vv_setup_conf) ?>;
|
||||
@@ -1613,6 +1632,7 @@ function vvShowEditorMode(title) {
|
||||
document.getElementById('vv-es-lang').textContent = 'Bash';
|
||||
document.getElementById('vv-undo-btn').style.display = '';
|
||||
document.getElementById('vv-redo-btn').style.display = '';
|
||||
vvRestoreEditorPrefs();
|
||||
document.getElementById('vv-suggestions').style.display = 'none';
|
||||
document.getElementById('vv-log-pre').style.display = 'none';
|
||||
document.getElementById('vv-si-view').style.display = 'none';
|
||||
@@ -2815,7 +2835,7 @@ function vvUpdateCurLine() {
|
||||
const ta = document.getElementById('vv-editor-body');
|
||||
const cl = document.getElementById('vv-cur-line');
|
||||
if (!cl || !ta) return;
|
||||
const lineH = 18; // font-size(12) × line-height(1.5)
|
||||
const lineH = vvLH();
|
||||
const padTop = 10; // overlay padding-top
|
||||
const lineN = ta.value.slice(0, ta.selectionStart).split('\n').length - 1; // 0-indexed
|
||||
cl.style.top = (padTop + lineN * lineH - ta.scrollTop) + 'px';
|
||||
@@ -2935,7 +2955,7 @@ function vvFindNav(dir) {
|
||||
ta.selectionStart = cur.start; ta.selectionEnd = cur.end;
|
||||
ta.focus();
|
||||
const lineN = ta.value.slice(0, cur.start).split('\n').length - 1;
|
||||
ta.scrollTop = Math.max(0, lineN * 18 - 80);
|
||||
ta.scrollTop = Math.max(0, lineN * vvLH() - 80);
|
||||
vvSyncHlOverlay();
|
||||
}
|
||||
|
||||
@@ -2944,6 +2964,85 @@ function vvFindKeydown(e) {
|
||||
if (e.key === 'Escape') { e.preventDefault(); vvFindClose(); }
|
||||
}
|
||||
|
||||
// ── Find & Replace ────────────────────────────────────────────────────────────
|
||||
|
||||
function vvFindToggleReplace(forceOpen) {
|
||||
const row = document.getElementById('vv-replace-row');
|
||||
const btn = document.getElementById('vv-find-expand');
|
||||
const show = forceOpen !== undefined ? forceOpen : !row.classList.contains('vv-repl-open');
|
||||
row.classList.toggle('vv-repl-open', show);
|
||||
btn.classList.toggle('open', show);
|
||||
if (show) document.getElementById('vv-replace-input').focus();
|
||||
}
|
||||
|
||||
function vvReplaceOne() {
|
||||
if (!vvFindMatches.length) return;
|
||||
const ta = document.getElementById('vv-editor-body');
|
||||
const rep = document.getElementById('vv-replace-input').value;
|
||||
const m = vvFindMatches[vvFindIdx];
|
||||
vvUndoCapture(true);
|
||||
ta.value = ta.value.slice(0, m.start) + rep + ta.value.slice(m.end);
|
||||
const prevIdx = vvFindIdx;
|
||||
vvFindSearch();
|
||||
if (vvFindMatches.length) {
|
||||
vvFindIdx = Math.min(prevIdx, vvFindMatches.length - 1);
|
||||
document.getElementById('vv-find-count').textContent = `${vvFindIdx + 1} / ${vvFindMatches.length}`;
|
||||
const cur = vvFindMatches[vvFindIdx];
|
||||
ta.selectionStart = cur.start; ta.selectionEnd = cur.end;
|
||||
}
|
||||
vvSyncHlOverlay(); vvUndoCapture(true);
|
||||
}
|
||||
|
||||
function vvReplaceAll() {
|
||||
if (!vvFindTerm) return;
|
||||
const ta = document.getElementById('vv-editor-body');
|
||||
const rep = document.getElementById('vv-replace-input').value;
|
||||
const re = new RegExp(vvFindTerm.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'), 'gi');
|
||||
let n = 0;
|
||||
vvUndoCapture(true);
|
||||
ta.value = ta.value.replace(re, () => { n++; return rep; });
|
||||
ta.selectionStart = ta.selectionEnd = 0;
|
||||
vvFindSearch();
|
||||
document.getElementById('vv-find-count').textContent = n ? `replaced ${n}` : 'no matches';
|
||||
vvUndoCapture(true);
|
||||
}
|
||||
|
||||
function vvReplaceKeydown(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); vvReplaceOne(); }
|
||||
if (e.key === 'Enter' && e.shiftKey) { e.preventDefault(); vvFindNav(-1); }
|
||||
if (e.key === 'Escape') { e.preventDefault(); vvFindClose(); }
|
||||
}
|
||||
|
||||
// ── Font size ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function vvFontSize(delta) {
|
||||
vvFontSizePx = Math.max(9, Math.min(22, vvFontSizePx + delta));
|
||||
localStorage.setItem('vv-editor-fontsize', vvFontSizePx);
|
||||
const fs = vvFontSizePx + 'px';
|
||||
const lh = (vvFontSizePx * 1.5) + 'px';
|
||||
const els = ['vv-editor-body', 'vv-hl-overlay', 'vv-ln-gutter'];
|
||||
els.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) { el.style.fontSize = fs; el.style.lineHeight = lh; }
|
||||
});
|
||||
const cl = document.getElementById('vv-cur-line');
|
||||
if (cl) cl.style.height = lh;
|
||||
const fs_el = document.getElementById('vv-es-fontsize');
|
||||
if (fs_el) fs_el.textContent = fs;
|
||||
vvSyncHlOverlay(); vvUpdateCurLine(); vvUpdateEditorStatus();
|
||||
}
|
||||
|
||||
function vvRestoreEditorPrefs() {
|
||||
const saved = parseInt(localStorage.getItem('vv-editor-fontsize') || '12') || 12;
|
||||
if (saved !== 12) {
|
||||
vvFontSizePx = 12; // reset so vvFontSize applies the delta correctly
|
||||
vvFontSize(saved - 12);
|
||||
} else {
|
||||
const fs_el = document.getElementById('vv-es-fontsize');
|
||||
if (fs_el) fs_el.textContent = '12px';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Go to line ────────────────────────────────────────────────────────────────
|
||||
|
||||
function vvGotoOpen() {
|
||||
@@ -2969,7 +3068,7 @@ function vvGotoApply() {
|
||||
let pos = 0;
|
||||
for (let i = 0; i < n - 1; i++) pos += lines[i].length + 1;
|
||||
ta.selectionStart = ta.selectionEnd = pos;
|
||||
ta.scrollTop = Math.max(0, (n - 5) * 18);
|
||||
ta.scrollTop = Math.max(0, (n - 5) * vvLH());
|
||||
vvSyncHlOverlay();
|
||||
vvEditorCursorMoved();
|
||||
}
|
||||
@@ -3046,7 +3145,7 @@ function vvSelectNextOccurrence() {
|
||||
let m = re.exec(v) ?? (() => { re.lastIndex = 0; return re.exec(v); })();
|
||||
if (!m || m.index === s) return;
|
||||
ta.selectionStart = m.index; ta.selectionEnd = m.index + word.length;
|
||||
ta.scrollTop = Math.max(0, (v.slice(0, m.index).split('\n').length - 5) * 18);
|
||||
ta.scrollTop = Math.max(0, (v.slice(0, m.index).split('\n').length - 5) * vvLH());
|
||||
vvWordMatch = word.replace(/^\$/, '');
|
||||
vvSyncHlOverlay(); vvEditorCursorMoved();
|
||||
}
|
||||
@@ -3054,8 +3153,12 @@ function vvSelectNextOccurrence() {
|
||||
function vvEditorReset() {
|
||||
vvWordMatch = null; vvFindTerm = ''; vvFindMatches = []; vvFindIdx = 0;
|
||||
document.getElementById('vv-editor-find')?.classList.remove('vv-find-open');
|
||||
document.getElementById('vv-replace-row')?.classList.remove('vv-repl-open');
|
||||
document.getElementById('vv-find-expand')?.classList.remove('open');
|
||||
const inp = document.getElementById('vv-find-input');
|
||||
if (inp) { inp.value = ''; inp.classList.remove('vv-find-no-match'); }
|
||||
const rep = document.getElementById('vv-replace-input');
|
||||
if (rep) rep.value = '';
|
||||
const cnt = document.getElementById('vv-find-count');
|
||||
if (cnt) cnt.textContent = '';
|
||||
document.getElementById('vv-goto-bar')?.classList.remove('vv-goto-open');
|
||||
@@ -3164,6 +3267,7 @@ function vvEditorKeydown(e) {
|
||||
if (e.key === 'y' || (e.key === 'z' && e.shiftKey)) { e.preventDefault(); vvRedo(); return; }
|
||||
if (e.key === 's') { e.preventDefault(); if (vvRawConfFile) vvSaveRawConf(); else vvSaveScript(); return; }
|
||||
if (e.key === 'f') { e.preventDefault(); vvFindOpen(); return; }
|
||||
if (e.key === 'h') { e.preventDefault(); vvFindOpen(); vvFindToggleReplace(true); return; }
|
||||
if (e.key === 'g') { e.preventDefault(); vvGotoOpen(); return; }
|
||||
if (e.key === 'l' && !e.shiftKey) {
|
||||
// Select current line
|
||||
@@ -3190,14 +3294,25 @@ function vvEditorKeydown(e) {
|
||||
vvSyncHlOverlay(); vvUndoCapture(true); return;
|
||||
}
|
||||
if (e.key === '/') {
|
||||
// Toggle line comment
|
||||
// Toggle line comment — single line or entire multi-line selection
|
||||
e.preventDefault(); vvUndoCapture(true);
|
||||
const ls = val.lastIndexOf('\n', start - 1) + 1;
|
||||
const le = val.indexOf('\n', start);
|
||||
const ln = val.slice(ls, le === -1 ? undefined : le);
|
||||
const nl = /^\s*#/.test(ln) ? ln.replace(/^(\s*)#\s?/, '$1') : ln.replace(/^(\s*)/, '$1# ');
|
||||
ta.value = val.slice(0, ls) + nl + val.slice(le === -1 ? val.length : le);
|
||||
ta.selectionStart = ta.selectionEnd = start + (nl.length - ln.length);
|
||||
const bs = val.lastIndexOf('\n', start - 1) + 1;
|
||||
const be = end > start ? val.indexOf('\n', end - 1) : val.indexOf('\n', start);
|
||||
const block = val.slice(bs, be === -1 ? val.length : be);
|
||||
const lines = block.split('\n');
|
||||
const allCommented = lines.every(l => !l.trim() || /^\s*#/.test(l));
|
||||
const newBlock = lines.map(l =>
|
||||
!l.trim() ? l
|
||||
: allCommented ? l.replace(/^(\s*)#\s?/, '$1')
|
||||
: l.replace(/^(\s*)/, '$1# ')
|
||||
).join('\n');
|
||||
ta.value = val.slice(0, bs) + newBlock + val.slice(be === -1 ? val.length : be);
|
||||
if (end > start) {
|
||||
ta.selectionStart = bs; ta.selectionEnd = bs + newBlock.length; // keep block selected
|
||||
} else {
|
||||
const curLineDelta = newBlock.split('\n')[0].length - lines[0].length;
|
||||
ta.selectionStart = ta.selectionEnd = start + curLineDelta;
|
||||
}
|
||||
vvSyncHlOverlay(); vvUndoCapture(true); return;
|
||||
}
|
||||
return; // let browser handle other Ctrl combos
|
||||
|
||||
Reference in New Issue
Block a user