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.">
|
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>
|
<input type="checkbox" id="vv-nc-follow" checked onchange="vvNcPref('follow')"> <span>Auto Scroll</span>
|
||||||
</label>
|
</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>
|
<span id="vv-nc-status" style="margin-left:auto;font-size:9px;color:#3a3a3a;"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -347,7 +354,8 @@ function _vvNcLoadPrefs() {
|
|||||||
if (el && p[k] != null) el.value = p[k];
|
if (el && p[k] != null) el.value = p[k];
|
||||||
}
|
}
|
||||||
for (const k of ['names', 'follow']) {
|
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];
|
if (el && p[k] != null) el.checked = !!p[k];
|
||||||
}
|
}
|
||||||
_vvNc.fmt = Object.assign(_vvNc.fmt, p.fmt || {});
|
_vvNc.fmt = Object.assign(_vvNc.fmt, p.fmt || {});
|
||||||
@@ -356,7 +364,8 @@ function _vvNcLoadPrefs() {
|
|||||||
function vvNcPref() {
|
function vvNcPref() {
|
||||||
const p = { fmt: _vvNc.fmt };
|
const p = { fmt: _vvNc.fmt };
|
||||||
for (const k of ['color', 'font', 'size']) p[k] = document.getElementById('vv-nc-' + k)?.value ?? '';
|
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 (_) {}
|
try { localStorage.setItem(_VV_NC_PREF, JSON.stringify(p)); } catch (_) {}
|
||||||
_vvNcPaintFmt();
|
_vvNcPaintFmt();
|
||||||
}
|
}
|
||||||
@@ -390,6 +399,12 @@ function vvNcToggleFmt() {
|
|||||||
// Style string for a message. Closed set on both ends — the server validates the same values,
|
// 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.
|
// so nothing here can emit a property the store did not agree to.
|
||||||
function _vvNcStyle(m, mine) {
|
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 st = m.style || {};
|
||||||
const sizes = { sm: '11px', lg: '14px' };
|
const sizes = { sm: '11px', lg: '14px' };
|
||||||
let s = `color:${st.color || (mine ? '#7a9a7a' : '#9aa')};`;
|
let s = `color:${st.color || (mine ? '#7a9a7a' : '#9aa')};`;
|
||||||
@@ -544,22 +559,48 @@ function vvNcMode(force) {
|
|||||||
const t = document.getElementById(p + '-title');
|
const t = document.getElementById(p + '-title');
|
||||||
if (t) t.textContent = _vvNcMeshOn ? 'Mesh Chat' : (t.dataset.ai || t.textContent);
|
if (t) t.textContent = _vvNcMeshOn ? 'Mesh Chat' : (t.dataset.ai || t.textContent);
|
||||||
try { localStorage.setItem('vvNcMode:' + p, _vvNcMeshOn ? '1' : '0'); } catch (_) {}
|
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) {
|
function vvNcInit(prefix, meshDefault) {
|
||||||
_vvNcPrefix = prefix;
|
_vvNcPrefix = prefix;
|
||||||
const t = document.getElementById(prefix + '-title');
|
const t = document.getElementById(prefix + '-title');
|
||||||
if (t && !t.dataset.ai) t.dataset.ai = t.textContent.trim();
|
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;
|
let want = !!meshDefault;
|
||||||
try {
|
if (!meshDefault) {
|
||||||
const s = localStorage.getItem('vvNcMode:' + prefix);
|
try {
|
||||||
if (s !== null) want = s === '1';
|
const s = localStorage.getItem('vvNcMode:' + prefix);
|
||||||
} catch (_) {}
|
if (s !== null) want = s === '1';
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
_vvNcLoadPrefs();
|
_vvNcLoadPrefs();
|
||||||
vvNcMode(want);
|
vvNcMode(want);
|
||||||
vvNcChans();
|
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' ?>);
|
vvNcInit(<?= json_encode($prefix) ?>, <?= $meshDefault ? 'true' : 'false' ?>);
|
||||||
|
|||||||
Reference in New Issue
Block a user