feat(sync): QR des reglages sur le PC, Paste settings sur le telephone

Le poste genere le QR (DelphiZXingQRCode, Apache 2.0) depuis ses prefs :
URL + jeton, jamais le mot de passe de synchro. Le telephone lit le texte
copie par l'appareil photo et remplit les champs ; Save valide.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-17 18:03:00 +01:00
parent 05ff08886d
commit f817a6df43
10 changed files with 3913 additions and 1 deletions
+2
View File
@@ -8191,6 +8191,8 @@ async function init() {
Bridge.setPref(SYNC_PREFS.preBackup, e.target.checked ? '1' : ''));
if (syncSetPwd) syncSetPwd.addEventListener('click', syncSetEncPwdFlow);
if (syncTest) syncTest.addEventListener('click', syncTestConnection);
const syncQr = document.getElementById('syncQrBtn');
if (syncQr) syncQr.addEventListener('click', syncQrToggle);
// Guard the user entry point: ignore clicks while a sync is running (no
// concurrent syncs) and call runSyncNow() with NO arg — the click event
// must not land in its `_attempt` retry counter. Internal retries call
+35
View File
@@ -37,6 +37,41 @@ const SYNC_PREFS = {
};
let _webdavResolvers = {};
// QR POUR LE TELEPHONE. L'exe le fabrique depuis ses prefs (URL + jeton, jamais
// le mot de passe de synchro) et n'envoie que la matrice : '1' = module noir,
// ligne par ligne. Masque au bout d'une minute - il donne acces au depot.
let syncQrTimer = null;
function syncQrHide() {
const box = document.getElementById('syncQrBox');
if (box) { box.hidden = true; box.innerHTML = ''; }
clearTimeout(syncQrTimer);
}
function syncQrToggle() {
const box = document.getElementById('syncQrBox');
if (box && !box.hidden) return syncQrHide();
window.location.href = 'cmd://sync/qr';
}
Bridge.onSyncQr = function (size, bits) {
const box = document.getElementById('syncQrBox');
if (!box) return;
if (!size) return toast('Set the URL and the token first', 'warning');
let d = '';
for (let r = 0; r < size; r++)
for (let c = 0; c < size; c++)
if (bits.charCodeAt(r * size + c) === 49) d += 'M' + c + ' ' + r + 'h1v1h-1z';
box.innerHTML =
'<svg viewBox="0 0 ' + size + ' ' + size + '" width="240" height="240" ' +
'shape-rendering="crispEdges" style="background:#fff;display:block;border-radius:6px">' +
'<path d="' + d + '" fill="#000"/></svg>' +
'<p style="font-size:11px;color:var(--text-faint);margin:6px 0 0">' +
'Scan it with the phone camera and copy the text, then in the app: ' +
'Settings &rarr; Paste settings. It holds the URL and the token, not the ' +
'sync password. Hidden again in 60 s.</p>';
box.hidden = false;
clearTimeout(syncQrTimer);
syncQrTimer = setTimeout(syncQrHide, 60000);
};
Bridge.onWebdavResult = function(reqId, status, payload, etag) {
const r = _webdavResolvers[reqId];
if (!r) return;