// Voice Reminder Plugin - Periodically speaks the current task title via Web Speech API if (!window.speechSynthesis) { console.warn('[voice-reminder] Web Speech API not available. Plugin disabled.'); } else { // --- plugin body (runs only when speechSynthesis is available) --- var _vrInterval = null; var _vrCurrentTask = null; var _vrDefaultTtsRate = 0.7; var _vrDefaults = { isEnabled: false, text: 'Your current task is: ${currentTaskTitle}', interval: 300000, volume: 75, voice: '', }; function _vrT(key, params) { return PluginAPI.translate(key, params); } async function _vrLoadConfig() { var raw = await PluginAPI.loadSyncedData(); if (!raw) return Object.assign({}, _vrDefaults); try { return Object.assign({}, _vrDefaults, JSON.parse(raw)); } catch (e) { return Object.assign({}, _vrDefaults); } } async function _vrSaveConfig(cfg) { await PluginAPI.persistDataSynced(JSON.stringify(cfg)); } function _vrSpeak(text, volume, voiceName) { var synth = window.speechSynthesis; if (!synth) { console.error('[voice-reminder] No window.speechSynthesis available.'); return; } synth.cancel(); var voices = synth.getVoices(); var utter = new SpeechSynthesisUtterance(); utter.text = text; utter.voice = voices.find(function (v) { return voiceName && v.voiceURI === voiceName; }) || voices.find(function (v) { return voiceName && v.name === voiceName; }) || voices.find(function (v) { return v.default; }) || null; utter.volume = volume / 100; utter.rate = _vrDefaultTtsRate; synth.speak(utter); } function _vrStopTimer() { if (_vrInterval) { clearInterval(_vrInterval); _vrInterval = null; } } async function _vrStartTimer() { _vrStopTimer(); var cfg = await _vrLoadConfig(); if (!cfg.isEnabled) return; var intervalMs = Math.max(cfg.interval || 300000, 5000); _vrInterval = setInterval(function () { if (!_vrCurrentTask) return; var txt = cfg.text.replace('${currentTaskTitle}', _vrCurrentTask.title); if (txt.length <= 1) { txt = _vrCurrentTask.title; } _vrSpeak(txt, cfg.volume, cfg.voice); }, intervalMs); } // Track current task via hook. The host emits { current, previous }. PluginAPI.registerHook('currentTaskChange', function (payload) { _vrCurrentTask = payload && payload.current ? payload.current : null; }); // Initial load and start _vrLoadConfig().then(function (cfg) { if (cfg.isEnabled) { _vrStartTimer(); } }); // Config dialog helpers function _vrEscapeHtml(str) { var div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML.replace(/"/g, '"').replace(/'/g, '''); } function _vrBuildVoiceOptions(voiceList, selectedVoice) { var opts = ''; for (var i = 0; i < voiceList.length; i++) { var v = voiceList[i]; var selected = v.voiceURI === selectedVoice ? ' selected' : ''; opts += ''; } return opts; } async function openVoiceReminderSettings() { var cfg = await _vrLoadConfig(); var voices = []; if (window.speechSynthesis) { voices = window.speechSynthesis.getVoices(); } var voiceOptions = _vrBuildVoiceOptions(voices, cfg.voice); var html = '
' + _vrEscapeHtml(_vrT('SETTINGS.DESCRIPTION')) + '
' + '