Title the info banner for its view, and number and stripe the array fields
This commit is contained in:
@@ -336,10 +336,23 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
|
|||||||
.vv-cf-scalar { display: block; width: 100%; box-sizing: border-box; background: #111; border: 1px solid #333;
|
.vv-cf-scalar { display: block; width: 100%; box-sizing: border-box; background: #111; border: 1px solid #333;
|
||||||
color: #ddd; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; }
|
color: #ddd; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; }
|
||||||
.vv-cf-scalar:focus { border-color: #555; outline: none; }
|
.vv-cf-scalar:focus { border-color: #555; outline: none; }
|
||||||
.vv-cf-array { display: block; width: 100%; box-sizing: border-box; background: #0d0d0d; border: 1px solid #333;
|
/* Array fields: numbered gutter + striped rows. The wrap owns the border so the gutter and the
|
||||||
color: #ccc; padding: 8px; border-radius: 4px; font-family: monospace; font-size: 11px;
|
text read as one control. line-height is an exact 18px, not 1.6 — the stripes are a repeating
|
||||||
line-height: 1.6; resize: vertical; min-height: 60px; }
|
gradient and would drift out of register against the text on a fractional line box. */
|
||||||
.vv-cf-array:focus { border-color: #555; outline: none; }
|
.vv-cf-arraywrap { display: flex; border: 1px solid #333; border-radius: 4px; overflow: hidden;
|
||||||
|
background: #0d0d0d; }
|
||||||
|
.vv-cf-arraywrap:focus-within { border-color: #555; }
|
||||||
|
.vv-cf-lines { margin: 0; padding: 8px 6px 8px 0; width: 34px; min-width: 34px; flex-shrink: 0;
|
||||||
|
text-align: right; font-family: monospace; font-size: 11px; line-height: 18px;
|
||||||
|
color: #3a3a3a; background: #0a0a0a; border-right: 1px solid #1c1c1c;
|
||||||
|
overflow: hidden; user-select: none; white-space: pre; }
|
||||||
|
.vv-cf-array { display: block; width: 100%; box-sizing: border-box; background-color: transparent;
|
||||||
|
border: none; color: #ccc; padding: 8px; font-family: monospace; font-size: 11px;
|
||||||
|
line-height: 18px; resize: vertical; min-height: 60px;
|
||||||
|
background-image: repeating-linear-gradient(rgba(255,255,255,0) 0 18px,
|
||||||
|
rgba(255,255,255,0.028) 18px 36px);
|
||||||
|
background-origin: content-box; background-attachment: local; }
|
||||||
|
.vv-cf-array:focus { outline: none; }
|
||||||
.vv-cf-empty { color: #555; font-size: 13px; font-style: italic; padding: 20px 4px; text-align: center; margin: 0; }
|
.vv-cf-empty { color: #555; font-size: 13px; font-style: italic; padding: 20px 4px; text-align: center; margin: 0; }
|
||||||
.vv-custom-empty { color: #666; font-size: 13px; padding: 8px 4px; margin: 0; font-style: italic; }
|
.vv-custom-empty { color: #666; font-size: 13px; padding: 8px 4px; margin: 0; font-style: italic; }
|
||||||
.vv-custom-count { font-size: 12px; color: #666; margin-left: 8px; flex-shrink: 0; }
|
.vv-custom-count { font-size: 12px; color: #666; margin-left: 8px; flex-shrink: 0; }
|
||||||
|
|||||||
@@ -2029,6 +2029,7 @@ function vvEditConf(id) {
|
|||||||
cf.innerHTML = (!d.ok || !d.groups || d.groups.length === 0)
|
cf.innerHTML = (!d.ok || !d.groups || d.groups.length === 0)
|
||||||
? '<p class="vv-cf-empty">No configurable settings found for this host.</p>'
|
? '<p class="vv-cf-empty">No configurable settings found for this host.</p>'
|
||||||
: vvRenderConfForm(d.groups);
|
: vvRenderConfForm(d.groups);
|
||||||
|
vvCfInitArrays(cf);
|
||||||
requestAnimationFrame(vvFitRight);
|
requestAnimationFrame(vvFitRight);
|
||||||
})
|
})
|
||||||
.catch(() => { cf.innerHTML = '<p class="vv-cf-empty">Failed to load configuration.</p>'; });
|
.catch(() => { cf.innerHTML = '<p class="vv-cf-empty">Failed to load configuration.</p>'; });
|
||||||
@@ -2059,10 +2060,18 @@ function vvRenderConfForm(groups) {
|
|||||||
+ ' data-key="' + esc(f.key) + '" data-file="' + esc(f.file) + '" data-type="scalar"'
|
+ ' data-key="' + esc(f.key) + '" data-file="' + esc(f.file) + '" data-type="scalar"'
|
||||||
+ ' value="' + esc(f.value) + '">';
|
+ ' value="' + esc(f.value) + '">';
|
||||||
} else {
|
} else {
|
||||||
|
// Arrays get a numbered gutter and striped rows. DAILY_MAINTENANCE_SCRIPTS is 25 lines
|
||||||
|
// of script paths and interleaved comment blocks; as a bare textarea it reads as one
|
||||||
|
// block of text and you cannot tell where an entry ends. The stripes do the separating
|
||||||
|
// and the numbers give each line something to be referred to by.
|
||||||
const rows = Math.min(20, (f.value.match(/\n/g) || []).length + 3);
|
const rows = Math.min(20, (f.value.match(/\n/g) || []).length + 3);
|
||||||
html += '<textarea class="vv-cf-input vv-cf-array"'
|
html += '<div class="vv-cf-arraywrap">'
|
||||||
|
+ '<pre class="vv-cf-lines" aria-hidden="true"></pre>'
|
||||||
|
+ '<textarea class="vv-cf-input vv-cf-array"'
|
||||||
+ ' data-key="' + esc(f.key) + '" data-file="' + esc(f.file) + '" data-type="' + esc(f.type) + '"'
|
+ ' data-key="' + esc(f.key) + '" data-file="' + esc(f.file) + '" data-type="' + esc(f.type) + '"'
|
||||||
+ ' rows="' + rows + '">' + esc(f.value) + '</textarea>';
|
+ ' oninput="vvCfLines(this)" onscroll="vvCfLineScroll(this)"'
|
||||||
|
+ ' rows="' + rows + '">' + esc(f.value) + '</textarea>'
|
||||||
|
+ '</div>';
|
||||||
}
|
}
|
||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
}
|
}
|
||||||
@@ -2071,6 +2080,28 @@ function vvRenderConfForm(groups) {
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Line numbers for an array field. The gutter is a plain <pre> scrolled in step with the
|
||||||
|
// textarea — same shape as the main editor's gutter, minus the highlight overlay, because these
|
||||||
|
// stay ordinary editable textareas and nothing here needs syntax colouring.
|
||||||
|
function vvCfLines(ta) {
|
||||||
|
const g = ta.parentElement.querySelector('.vv-cf-lines');
|
||||||
|
if (!g) return;
|
||||||
|
const n = ta.value.split('\n').length;
|
||||||
|
let s = '';
|
||||||
|
for (let i = 1; i <= n; i++) s += i + '\n';
|
||||||
|
g.textContent = s;
|
||||||
|
g.scrollTop = ta.scrollTop;
|
||||||
|
}
|
||||||
|
function vvCfLineScroll(ta) {
|
||||||
|
const g = ta.parentElement.querySelector('.vv-cf-lines');
|
||||||
|
if (g) g.scrollTop = ta.scrollTop;
|
||||||
|
}
|
||||||
|
// Numbers cannot be produced server-side: the count follows the textarea's value, which the user
|
||||||
|
// is about to change. Called once after any render that can contain conf fields.
|
||||||
|
function vvCfInitArrays(root) {
|
||||||
|
(root || document).querySelectorAll('.vv-cf-array').forEach(vvCfLines);
|
||||||
|
}
|
||||||
|
|
||||||
function vvSaveConf() {
|
function vvSaveConf() {
|
||||||
if (!vvConfId) return;
|
if (!vvConfId) return;
|
||||||
const _cfName = vvConfId.replace(/\.sh$/, '').split('/').pop();
|
const _cfName = vvConfId.replace(/\.sh$/, '').split('/').pop();
|
||||||
@@ -3796,6 +3827,29 @@ function vvSelectScript(row) {
|
|||||||
vvShowScriptInfoMode(name, hdr, id);
|
vvShowScriptInfoMode(name, hdr, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retitle the banner for this view: "Daily Sync Maintenance" → "Daily Sync Maintenance Info &
|
||||||
|
// Settings", re-centred so the = rules still line up. Done at display time and never to the file
|
||||||
|
// — that banner is the script's own header, it is parsed by include/scheduler.php for the
|
||||||
|
// library listing, and it belongs to the script rather than to this panel.
|
||||||
|
//
|
||||||
|
// Matches only the titled line: the plain rules above and below have no text between their =
|
||||||
|
// runs, so the pattern cannot hit them.
|
||||||
|
function vvSiRetitle(hdr, suffix) {
|
||||||
|
const lines = String(hdr || '').split('\n');
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const m = lines[i].match(/^(\s*)(=+)\s+(\S.*?)\s+(=+)\s*$/);
|
||||||
|
if (!m) continue;
|
||||||
|
const indent = m[1];
|
||||||
|
const width = lines[i].trimEnd().length - indent.length;
|
||||||
|
const title = ' ' + m[3].trim() + ' ' + suffix + ' ';
|
||||||
|
const pad = Math.max(2, width - title.length);
|
||||||
|
const left = Math.floor(pad / 2);
|
||||||
|
lines[i] = indent + '='.repeat(left) + title + '='.repeat(pad - left);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
function vvShowScriptInfoMode(name, hdr, id) {
|
function vvShowScriptInfoMode(name, hdr, id) {
|
||||||
document.getElementById('vv-suggestions').style.display = 'none';
|
document.getElementById('vv-suggestions').style.display = 'none';
|
||||||
document.getElementById('vv-log-pre').style.display = 'none';
|
document.getElementById('vv-log-pre').style.display = 'none';
|
||||||
@@ -3849,7 +3903,7 @@ function vvShowScriptInfoMode(name, hdr, id) {
|
|||||||
|
|
||||||
if (dispHdr) {
|
if (dispHdr) {
|
||||||
html += '<div class="vv-sinfo-block">'
|
html += '<div class="vv-sinfo-block">'
|
||||||
+ '<pre class="vv-si-hdr">' + vvHl(dispHdr, true) + '</pre>'
|
+ '<pre class="vv-si-hdr">' + vvHl(vvSiRetitle(dispHdr, 'Info & Settings'), true) + '</pre>'
|
||||||
+ '</div>';
|
+ '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3873,12 +3927,13 @@ function vvShowScriptInfoMode(name, hdr, id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
si.innerHTML = html || '<p class="vv-cf-empty">No additional information found.</p>';
|
si.innerHTML = html || '<p class="vv-cf-empty">No additional information found.</p>';
|
||||||
|
vvCfInitArrays(si);
|
||||||
requestAnimationFrame(vvFitRight);
|
requestAnimationFrame(vvFitRight);
|
||||||
}).catch(() => { si.innerHTML = '<p class="vv-cf-empty">Failed to load info.</p>'; });
|
}).catch(() => { si.innerHTML = '<p class="vv-cf-empty">Failed to load info.</p>'; });
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Basic mode: header only
|
// Basic mode: header only
|
||||||
si.innerHTML = '<pre class="vv-si-hdr">' + vvHl(hdr, true) + '</pre>';
|
si.innerHTML = '<pre class="vv-si-hdr">' + vvHl(vvSiRetitle(hdr, 'Info & Settings'), true) + '</pre>';
|
||||||
}
|
}
|
||||||
requestAnimationFrame(vvFitRight);
|
requestAnimationFrame(vvFitRight);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user