harden audio backup settings rendering

This commit is contained in:
Daniel 2026-05-08 08:23:38 +02:00
parent 04e736eb2e
commit 6da5565f89
2 changed files with 68 additions and 19 deletions

View file

@ -251,28 +251,74 @@ var _db = null;
var container = document.getElementById('audio-backups-list');
if (!container) return;
getAudioBackups().then(function(backups) {
container.textContent = '';
if (backups.length === 0) {
container.innerHTML = '<p style="color:var(--g400);font-size:13px;">No audio backups. Recordings are saved automatically to the server and kept for 24 hours.</p>';
var empty = document.createElement('p');
empty.style.color = 'var(--g400)';
empty.style.fontSize = '13px';
empty.textContent = 'No audio backups. Recordings are saved automatically to the server and kept for 24 hours.';
container.appendChild(empty);
return;
}
container.innerHTML = backups.map(function(b) {
backups.forEach(function(b) {
var date = new Date(b.timestamp);
var sizeKb = Math.round(b.size / 1024);
var age = Math.round((Date.now() - b.timestamp) / 60000);
var ageStr = age < 60 ? age + 'm ago' : Math.round(age / 60) + 'h ago';
var sourceTag = b.source === 'server'
? '<span style="font-size:10px;padding:1px 5px;border-radius:3px;background:var(--blue-light);color:var(--blue);">server</span>'
: '<span style="font-size:10px;padding:1px 5px;border-radius:3px;background:var(--g100);color:var(--g500);">local</span>';
var compInfo = b.compressedSize ? ' (' + Math.round(b.compressedSize / 1024) + ' KB compressed)' : '';
return '<div class="saved-enc-item" style="padding:8px 12px;">' +
'<div style="flex:1;">' +
'<div style="font-weight:600;font-size:13px;display:flex;align-items:center;gap:6px;">' + esc(b.module) + ' recording ' + sourceTag + '</div>' +
'<div style="font-size:11px;color:var(--g500);">' + date.toLocaleString() + ' · ' + sizeKb + ' KB' + compInfo + ' · ' + ageStr + '</div>' +
'</div>' +
'<button class="btn-sm btn-primary audio-backup-retry" data-id="' + b.id + '"><i class="fas fa-rotate-right"></i> Retry</button>' +
'<button class="btn-sm btn-ghost audio-backup-delete" data-id="' + b.id + '" style="color:var(--red);"><i class="fas fa-trash"></i></button>' +
'</div>';
}).join('');
var row = document.createElement('div');
row.className = 'saved-enc-item';
row.style.padding = '8px 12px';
var body = document.createElement('div');
body.style.flex = '1';
var title = document.createElement('div');
title.style.fontWeight = '600';
title.style.fontSize = '13px';
title.style.display = 'flex';
title.style.alignItems = 'center';
title.style.gap = '6px';
title.appendChild(document.createTextNode((b.module || '') + ' recording '));
var sourceTag = document.createElement('span');
sourceTag.style.fontSize = '10px';
sourceTag.style.padding = '1px 5px';
sourceTag.style.borderRadius = '3px';
sourceTag.style.background = b.source === 'server' ? 'var(--blue-light)' : 'var(--g100)';
sourceTag.style.color = b.source === 'server' ? 'var(--blue)' : 'var(--g500)';
sourceTag.textContent = b.source === 'server' ? 'server' : 'local';
title.appendChild(sourceTag);
var meta = document.createElement('div');
meta.style.fontSize = '11px';
meta.style.color = 'var(--g500)';
meta.textContent = date.toLocaleString() + ' · ' + sizeKb + ' KB' + compInfo + ' · ' + ageStr;
body.appendChild(title);
body.appendChild(meta);
var retryBtn = document.createElement('button');
retryBtn.className = 'btn-sm btn-primary audio-backup-retry';
retryBtn.dataset.id = b.id;
var retryIcon = document.createElement('i');
retryIcon.className = 'fas fa-rotate-right';
retryBtn.appendChild(retryIcon);
retryBtn.appendChild(document.createTextNode(' Retry'));
var deleteBtn = document.createElement('button');
deleteBtn.className = 'btn-sm btn-ghost audio-backup-delete';
deleteBtn.dataset.id = b.id;
deleteBtn.style.color = 'var(--red)';
var deleteIcon = document.createElement('i');
deleteIcon.className = 'fas fa-trash';
deleteBtn.appendChild(deleteIcon);
row.appendChild(body);
row.appendChild(retryBtn);
row.appendChild(deleteBtn);
container.appendChild(row);
});
container.querySelectorAll('.audio-backup-retry').forEach(function(btn) {
btn.addEventListener('click', function() { retryAudioBackup(btn.dataset.id); });
});
@ -287,9 +333,4 @@ var _db = null;
});
};
function esc(str) {
if (!str) return '';
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
cleanupOldLocalBackups();

View file

@ -62,3 +62,11 @@ test('browser speech recognition is gated by explicit user setting', () => {
const speechFactory = app.match(/function createSpeechRecognition\(\) \{[\s\S]*?\n\}/)[0];
assert.match(speechFactory, /window\.WebSpeechRecognition && !window\.WebSpeechRecognition\.isEnabled\(\)/);
});
test('audio backup settings render without dynamic HTML templates', () => {
const audioBackup = read('public/js/audioBackup.js');
const renderer = audioBackup.match(/window\.renderAudioBackups = function\(\) \{[\s\S]*?\n \};/)[0];
assert.doesNotMatch(renderer, /innerHTML/);
assert.match(renderer, /document\.createElement\('button'\)/);
assert.match(renderer, /textContent =/);
});