Mesh chat: reader-side formatting toggle, poll backoff when hidden, Partnership always opens on mesh
This commit is contained in:
@@ -257,6 +257,13 @@ function vv_nc_pane_markup(string $prefix, bool $meshDefault = false): void {
|
||||
title="Follow the newest message. Unticks when you scroll up, re-ticks at the bottom.">
|
||||
<input type="checkbox" id="vv-nc-follow" checked onchange="vvNcPref('follow')"> <span>Auto Scroll</span>
|
||||
</label>
|
||||
<!-- Reader's choice, not the sender's. Someone else's purple italic large is their idea of
|
||||
emphasis, and on a wall-mounted dashboard it is just noise — this turns every message
|
||||
back into plain text here without changing what they sent or what anyone else sees. -->
|
||||
<label class="vv-ai-cb" style="font-size:10px;"
|
||||
title="Render colours, fonts and bold as sent. Unticked shows every message as plain text.">
|
||||
<input type="checkbox" id="vv-nc-fmt-on" checked onchange="vvNcPref('fmtOn');vvNcRender()"> <span>Formatting</span>
|
||||
</label>
|
||||
<span id="vv-nc-status" style="margin-left:auto;font-size:9px;color:#3a3a3a;"></span>
|
||||
</div>
|
||||
|
||||
@@ -347,7 +354,8 @@ function _vvNcLoadPrefs() {
|
||||
if (el && p[k] != null) el.value = p[k];
|
||||
}
|
||||
for (const k of ['names', 'follow']) {
|
||||
const el = document.getElementById('vv-nc-' + k);
|
||||
const id = k === 'fmtOn' ? 'vv-nc-fmt-on' : 'vv-nc-' + k;
|
||||
const el = document.getElementById(id);
|
||||
if (el && p[k] != null) el.checked = !!p[k];
|
||||
}
|
||||
_vvNc.fmt = Object.assign(_vvNc.fmt, p.fmt || {});
|
||||
@@ -356,7 +364,8 @@ function _vvNcLoadPrefs() {
|
||||
function vvNcPref() {
|
||||
const p = { fmt: _vvNc.fmt };
|
||||
for (const k of ['color', 'font', 'size']) p[k] = document.getElementById('vv-nc-' + k)?.value ?? '';
|
||||
for (const k of ['names', 'follow']) p[k] = !!document.getElementById('vv-nc-' + k)?.checked;
|
||||
for (const k of ['names', 'follow', 'fmtOn'])
|
||||
p[k] = !!document.getElementById(k === 'fmtOn' ? 'vv-nc-fmt-on' : 'vv-nc-' + k)?.checked;
|
||||
try { localStorage.setItem(_VV_NC_PREF, JSON.stringify(p)); } catch (_) {}
|
||||
_vvNcPaintFmt();
|
||||
}
|
||||
@@ -390,6 +399,12 @@ function vvNcToggleFmt() {
|
||||
// Style string for a message. Closed set on both ends — the server validates the same values,
|
||||
// so nothing here can emit a property the store did not agree to.
|
||||
function _vvNcStyle(m, mine) {
|
||||
// Reader wins over sender. Someone else's purple italic large is their idea of emphasis, and
|
||||
// on a wall-mounted dashboard it is noise — this renders every message plain here without
|
||||
// changing what they sent or what anyone else sees. The only thing kept is the you/them colour
|
||||
// distinction, which is orientation rather than decoration.
|
||||
if (!document.getElementById('vv-nc-fmt-on')?.checked)
|
||||
return `color:${mine ? '#7a9a7a' : '#9aa'};font-size:12px;`;
|
||||
const st = m.style || {};
|
||||
const sizes = { sm: '11px', lg: '14px' };
|
||||
let s = `color:${st.color || (mine ? '#7a9a7a' : '#9aa')};`;
|
||||
@@ -544,22 +559,48 @@ function vvNcMode(force) {
|
||||
const t = document.getElementById(p + '-title');
|
||||
if (t) t.textContent = _vvNcMeshOn ? 'Mesh Chat' : (t.dataset.ai || t.textContent);
|
||||
try { localStorage.setItem('vvNcMode:' + p, _vvNcMeshOn ? '1' : '0'); } catch (_) {}
|
||||
if (_vvNcMeshOn) vvNcLoad();
|
||||
// Opening the mesh resets the backoff, so switching to it never waits on a 90-second timer.
|
||||
if (_vvNcMeshOn) { _vvNcStep = 0; vvNcLoad(); }
|
||||
if (typeof _vvNcSchedule === 'function') _vvNcSchedule();
|
||||
}
|
||||
|
||||
function vvNcInit(prefix, meshDefault) {
|
||||
_vvNcPrefix = prefix;
|
||||
const t = document.getElementById(prefix + '-title');
|
||||
if (t && !t.dataset.ai) t.dataset.ai = t.textContent.trim();
|
||||
// meshDefault means "this page IS the mesh page" — Partnership opens on it every time rather
|
||||
// than on whatever was last toggled. Everywhere else the choice is remembered, because there
|
||||
// the mesh is a thing you switch to and being returned to it is the surprise.
|
||||
let want = !!meshDefault;
|
||||
if (!meshDefault) {
|
||||
try {
|
||||
const s = localStorage.getItem('vvNcMode:' + prefix);
|
||||
if (s !== null) want = s === '1';
|
||||
} catch (_) {}
|
||||
}
|
||||
_vvNcLoadPrefs();
|
||||
vvNcMode(want);
|
||||
vvNcChans();
|
||||
setInterval(vvNcChans, 15000);
|
||||
_vvNcSchedule();
|
||||
}
|
||||
|
||||
// Poll fast while the mesh is on screen, and back off while it is not.
|
||||
//
|
||||
// Eight pages each asking every 15 seconds is eight times the traffic for one store, and while
|
||||
// the card is showing the assistant the only thing the poll feeds is the unread badge — which
|
||||
// nobody is watching to the second. Backs off 30 → 60 → 90 and stays there; snaps back to 15 the
|
||||
// moment the mesh pane is opened, so switching to it is never waiting on a slow timer.
|
||||
let _vvNcTimer = null, _vvNcStep = 0;
|
||||
const _VV_NC_BACKOFF = [30000, 60000, 90000];
|
||||
|
||||
function _vvNcSchedule() {
|
||||
if (_vvNcTimer) clearTimeout(_vvNcTimer);
|
||||
const wait = _vvNcMeshOn ? 15000 : _VV_NC_BACKOFF[Math.min(_vvNcStep, _VV_NC_BACKOFF.length - 1)];
|
||||
_vvNcTimer = setTimeout(() => {
|
||||
vvNcChans();
|
||||
if (!_vvNcMeshOn) _vvNcStep++;
|
||||
_vvNcSchedule();
|
||||
}, wait);
|
||||
}
|
||||
|
||||
vvNcInit(<?= json_encode($prefix) ?>, <?= $meshDefault ? 'true' : 'false' ?>);
|
||||
|
||||
Reference in New Issue
Block a user