Fix file picker double-open across all remaining tools

Same defensive guard as legal-analysis + summarize, now applied to the
upload-zone and audio-zone handlers in tools.js. Affects redact,
timeline, and transcribe (which all share these zones via tools.js's
setupUpload / setupAudioUpload). Stops native label-for clicks and the
input's own click from bubbling into the zone handler that would
otherwise programmatically re-open the picker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 08:23:58 +02:00
parent 5589e891f4
commit 2509a596c1
+16 -3
View File
@@ -1180,9 +1180,16 @@ function setupUpload() {
if (e.dataTransfer?.files?.length) handleFiles(e.dataTransfer.files); if (e.dataTransfer?.files?.length) handleFiles(e.dataTransfer.files);
}); });
// Stop label-for and the input itself from bubbling into the zone click
// handler — otherwise the picker opens twice (native + programmatic).
const _uploadLabel = els.uploadZone.querySelector('label[for="' + els.uploadInput.id + '"]');
if (_uploadLabel) _uploadLabel.addEventListener('click', (e) => e.stopPropagation());
els.uploadInput.addEventListener('click', (e) => e.stopPropagation());
els.uploadZone.addEventListener('click', (e) => { els.uploadZone.addEventListener('click', (e) => {
if (e.target === els.uploadClear || els.uploadClear?.contains(e.target)) return; if (e.target === els.uploadClear || els.uploadClear?.contains(e.target)) return;
if (e.target.tagName === 'LABEL') return; if (e.target === els.uploadInput) return;
const lbl = e.target.closest && e.target.closest('label');
if (lbl && lbl.getAttribute('for') === els.uploadInput.id) return;
els.uploadInput.click(); els.uploadInput.click();
}); });
@@ -2268,11 +2275,17 @@ function setupAudio() {
if (e.dataTransfer?.files?.length) handleAudioFiles(e.dataTransfer.files); if (e.dataTransfer?.files?.length) handleAudioFiles(e.dataTransfer.files);
}); });
// Stop label-for and the input itself from bubbling into the zone click
// handler — otherwise the picker opens twice (native + programmatic).
const _audioLabel = els.audioZone.querySelector('label[for="' + els.audioInput.id + '"]');
if (_audioLabel) _audioLabel.addEventListener('click', (e) => e.stopPropagation());
els.audioInput.addEventListener('click', (e) => e.stopPropagation());
els.audioZone.addEventListener('click', (e) => { els.audioZone.addEventListener('click', (e) => {
if (e.target === els.audioClear || els.audioClear?.contains(e.target)) return; if (e.target === els.audioClear || els.audioClear?.contains(e.target)) return;
if (e.target === els.audioInput) return; if (e.target === els.audioInput) return;
if (e.target.tagName === 'LABEL') return; const lbl = e.target.closest && e.target.closest('label');
if (e.target.closest('#audioFileInfo') && e.target.tagName !== 'LABEL') return; if (lbl && lbl.getAttribute('for') === els.audioInput.id) return;
if (e.target.closest('#audioFileInfo')) return;
els.audioInput.click(); els.audioInput.click();
}); });