editor: add word wrap toggle to status bar

Adds a "Wrap" button to the editor status bar that toggles word wrap
on/off, persisted in localStorage. Default is nowrap with horizontal
scroll (standard code editor behaviour). In wrap mode the line-number
gutter and current-line highlight are hidden — both rely on fixed
line-height calculations that break when lines wrap visually.

CSS: textarea/overlay default to white-space:pre; .vv-ed-wrap class
(on #vv-editor-wrap) overrides to pre-wrap, hides gutter and cur-line.
JS: vvWordWrap state, vvApplyWordWrap(), vvToggleWordWrap(); guards in
vvUpdateLineNums() and vvUpdateCurLine(); restored via
vvRestoreEditorPrefs() on both script and raw-conf editor entry points.
This commit is contained in:
Gmer4Lfe
2026-06-04 21:56:22 -04:00
parent c1919febc2
commit 0b3d7d542f
2 changed files with 38 additions and 4 deletions
+9 -2
View File
@@ -323,7 +323,8 @@
#vv-editor { flex-direction: column; gap: 0; }
.vv-editor-body { font-family: monospace; font-size: 12px; background: #0d0d0d; color: #ccc;
border: 1px solid #333; border-radius: 4px; padding: 10px 12px; resize: none;
line-height: 1.5; scroll-behavior: auto; tab-size: 2; width: 100%; box-sizing: border-box; }
line-height: 1.5; scroll-behavior: auto; tab-size: 2; width: 100%; box-sizing: border-box;
white-space: pre; overflow-x: auto; }
/* Editor layout: gutter + inner area — unified bordered block */
#vv-editor-wrap { display: flex; border: 1px solid #2e2e2e; border-radius: 4px 4px 0 0;
@@ -347,7 +348,7 @@
#vv-hl-overlay { display: none; position: absolute; top: 0; left: 0; right: 0; bottom: 0;
margin: 0; padding: 10px 12px; box-sizing: border-box;
font-family: monospace; font-size: 12px; line-height: 1.5; tab-size: 2;
white-space: pre-wrap; word-break: break-all; overflow: hidden;
white-space: pre; overflow: hidden;
pointer-events: none; user-select: none;
background: transparent; border: none; border-radius: 0; }
.vv-editor-hl #vv-hl-overlay { display: block; }
@@ -414,6 +415,12 @@
.vv-es-btn.active { color: #6495ed; }
#vv-es-fontsize { color: #444; font-size: 10px; min-width: 26px; text-align: center; }
/* Word wrap mode — applied to #vv-editor-wrap when wrap is enabled */
#vv-editor-wrap.vv-ed-wrap .vv-editor-body { white-space: pre-wrap !important; overflow-x: hidden !important; }
#vv-editor-wrap.vv-ed-wrap #vv-hl-overlay { white-space: pre-wrap !important; word-break: break-word !important; }
#vv-editor-wrap.vv-ed-wrap #vv-ln-gutter { display: none; }
#vv-editor-wrap.vv-ed-wrap #vv-cur-line { display: none !important; }
/* Indent guides — 1px lines every 2 chars, starting at indent level 1 */
#vv-editor-inner {
position: relative; flex: 1; overflow: hidden;
+29 -2
View File
@@ -944,6 +944,7 @@ Still the same two servers, two households, the same media stack running itself.
<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>
<button class="vv-es-btn" id="vv-es-wrap-btn" onclick="vvToggleWordWrap()" title="Word wrap OFF — click to enable">Wrap</button>
<span class="vv-es-lang" id="vv-es-lang"></span>
</div>
</div>
@@ -1031,6 +1032,7 @@ const VV_PAIRS = {'(':')', '[':']', '{':'}', '"':'"', "'":"'", '`':'`'};
const VV_CLOSE = new Set([')', ']', '}', '"', "'", '`']);
// Editor display prefs
let vvFontSizePx = parseInt(localStorage.getItem('vv-editor-fontsize') || '12') || 12;
let vvWordWrap = localStorage.getItem('vv-editor-wordwrap') === '1';
// Line height helper — used everywhere instead of hardcoded 18
function vvLH() { return vvFontSizePx * 1.5; }
@@ -2693,6 +2695,7 @@ function vvEditRawConf(file) {
function vvShowRawConfMode(title) {
vvEditorReset();
vvRestoreEditorPrefs();
document.getElementById('vv-editor-status').classList.add('vv-ed-active');
document.getElementById('vv-es-lang').textContent = 'Bash / Config';
document.getElementById('vv-undo-btn').style.display = '';
@@ -2836,7 +2839,7 @@ function vvSyncHlScroll() {
function vvUpdateLineNums() {
const gutter = document.getElementById('vv-ln-gutter');
const ta = document.getElementById('vv-editor-body');
if (!gutter || !ta) return;
if (!gutter || !ta || vvWordWrap) return;
const lines = ta.value.split('\n').length;
const curLn = ta.value.slice(0, ta.selectionStart).split('\n').length;
let html = '';
@@ -2851,7 +2854,7 @@ function vvUpdateLineNums() {
function vvUpdateCurLine() {
const ta = document.getElementById('vv-editor-body');
const cl = document.getElementById('vv-cur-line');
if (!cl || !ta) return;
if (!cl || !ta || vvWordWrap) return;
const lineH = vvLH();
const padTop = 10; // overlay padding-top
const lineN = ta.value.slice(0, ta.selectionStart).split('\n').length - 1; // 0-indexed
@@ -3058,6 +3061,30 @@ function vvRestoreEditorPrefs() {
const fs_el = document.getElementById('vv-es-fontsize');
if (fs_el) fs_el.textContent = '12px';
}
vvWordWrap = localStorage.getItem('vv-editor-wordwrap') === '1';
vvApplyWordWrap(false);
}
// ── Word wrap toggle ──────────────────────────────────────────────────────────
function vvApplyWordWrap(save) {
const wrap = document.getElementById('vv-editor-wrap');
const btn = document.getElementById('vv-es-wrap-btn');
if (!wrap) return;
if (vvWordWrap) {
wrap.classList.add('vv-ed-wrap');
if (btn) { btn.classList.add('active'); btn.title = 'Word wrap ON — click to disable'; }
} else {
wrap.classList.remove('vv-ed-wrap');
if (btn) { btn.classList.remove('active'); btn.title = 'Word wrap OFF — click to enable'; }
}
if (save) localStorage.setItem('vv-editor-wordwrap', vvWordWrap ? '1' : '0');
vvSyncHlOverlay();
}
function vvToggleWordWrap() {
vvWordWrap = !vvWordWrap;
vvApplyWordWrap(true);
}
// ── Go to line ────────────────────────────────────────────────────────────────