// yy_toast_tts.js.php //
// // (function (w, d) { "use strict"; if (w.YYToastTTS) return; // 중복 로드 방지 const YYToastTTS = { // ========================================================= // [설정부] 기본값 / 상태 // ========================================================= HARD_DEFAULTS: { enabled: true, // 전체 마스터 toastEnabled: true, ttsEnabled: true, vibrateEnabled: false, duration: 5000, // 기본 토스트 표시시간(ms) sticky: false, // 기본 고정 표시 여부 voice: "female", // "female" | "male" lang: "ko-KR", volume: 1, rate: 1, pitch: 1, // 다음 단계(반복 알림) 확장용 예약값 - 지금은 저장만 가능 repeatMode: "once", // once | until_click | for_minutes repeatEverySec: 30, repeatForMin: 10 }, _booted: false, _config: { mount: null, storageKey: "yy_toast_tts_v1", defaults: {} }, settings: null, mountEl: null, voices: [], femaleVoices: [], maleVoices: [], preferredFemaleVoice: null, preferredMaleVoice: null, toasts: new Map(), // toastId -> element _toastSeq: 0, // ========================================================= // [로더부] 시작점 // ========================================================= boot(cfg) { cfg = cfg || {}; this._config.mount = cfg.mount || null; this._config.storageKey = cfg.storageKey || "yy_toast_tts_v1"; this._config.defaults = cfg.defaults || {}; this.settings = this.loadSettings(); this.injectStyles(); this.ensureToastRoot(); this.initVoiceList(); // 음성 목록 갱신 대응 if (w.speechSynthesis) { try { w.speechSynthesis.addEventListener("voiceschanged", () => this.initVoiceList()); } catch (e) { w.speechSynthesis.onvoiceschanged = () => this.initVoiceList(); } } // mount UI (있을 때만) if (this._config.mount) { const mount = d.querySelector(this._config.mount); if (mount) { this.mountEl = mount; this.renderSettingsUI(); this.bindSettingsUI(); this.syncSettingsUI(); } } this._booted = true; return this; }, // ========================================================= // [설정부] 저장/로드 // ========================================================= loadSettings() { let saved = null; try { saved = localStorage.getItem(this._config.storageKey); } catch (e) {} let parsed = {}; if (saved) { try { parsed = JSON.parse(saved) || {}; } catch (e) { parsed = {}; } } // 우선순위: HARD_DEFAULTS < boot.defaults < saved return Object.assign({}, this.HARD_DEFAULTS, this._config.defaults || {}, parsed); }, saveSettings() { try { localStorage.setItem(this._config.storageKey, JSON.stringify(this.settings || {})); } catch (e) {} }, setSettings(patch) { this.settings = Object.assign({}, this.settings || {}, patch || {}); this.saveSettings(); this.syncSettingsUI(); return this.settings; }, getSettings() { return Object.assign({}, this.settings || {}); }, // ========================================================= // [설정부] UI (한 div에 붙는 설정판) // ========================================================= renderSettingsUI() { if (!this.mountEl) return; this.mountEl.innerHTML = `${this.escapeHtml(this._config.storageKey)} 키로 저장됩니다.