From 8c81e5d405013fa7d7fc2d0665485bbabd91f2ad Mon Sep 17 00:00:00 2001 From: rajchales-monito Date: Fri, 24 Jul 2026 14:57:03 +0200 Subject: [PATCH] Add repeatable admin notification sounds --- public/admin.html | 3 +++ public/admin.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/public/admin.html b/public/admin.html index 80b3a62..e11b349 100644 --- a/public/admin.html +++ b/public/admin.html @@ -159,8 +159,11 @@ + + + diff --git a/public/admin.js b/public/admin.js index 11ed0de..ddbfcd9 100644 --- a/public/admin.js +++ b/public/admin.js @@ -12,8 +12,10 @@ const state = { soundMuted: localStorage.getItem("mf_admin_sound_muted") === "1", soundVolume: Number(localStorage.getItem("mf_admin_sound_volume") || 70), soundStyle: localStorage.getItem("mf_admin_sound_style") || "bright", + soundRepeat: localStorage.getItem("mf_admin_sound_repeat") === "1", replyFiles: [], attentionTimer: null, + soundRepeatTimer: null, attentionOn: false, originalTitle: document.title, originalFavicon: document.querySelector("link[rel='icon']")?.href || "/favicon.svg" @@ -68,6 +70,12 @@ $("#adminSoundStyle").addEventListener("change", (event) => { localStorage.setItem("mf_admin_sound_style", state.soundStyle); renderAdminSoundControls(); }); +$("#adminSoundRepeat").addEventListener("change", (event) => { + state.soundRepeat = event.target.checked; + localStorage.setItem("mf_admin_sound_repeat", state.soundRepeat ? "1" : "0"); + if (!state.soundRepeat) stopRepeatingSound(); + renderAdminSoundControls(); +}); $("#adminSoundTest").addEventListener("click", () => { playNotifySound({ ignoreMuted: true }); }); @@ -804,6 +812,7 @@ async function saveSite(patch) { function notify() { startAttention(); playNotifySound(); + startRepeatingSound(); } function renderAdminSoundControls() { @@ -812,6 +821,7 @@ function renderAdminSoundControls() { const settingsToggle = $("#adminSoundToggleSettings"); const settingsVolume = $("#adminSoundVolumeSettings"); const settingsStyle = $("#adminSoundStyle"); + const settingsRepeat = $("#adminSoundRepeat"); const safeVolume = clamp(state.soundVolume, 0, 100); if (toggle) { toggle.classList.toggle("muted", state.soundMuted || safeVolume <= 0); @@ -825,6 +835,7 @@ function renderAdminSoundControls() { if (settingsToggle) settingsToggle.checked = !state.soundMuted; if (settingsVolume) settingsVolume.value = String(safeVolume); if (settingsStyle) settingsStyle.value = state.soundStyle; + if (settingsRepeat) settingsRepeat.checked = state.soundRepeat; } function setAdminSoundMuted(isMuted) { @@ -832,6 +843,7 @@ function setAdminSoundMuted(isMuted) { if (!state.soundMuted && state.soundVolume <= 0) state.soundVolume = 70; localStorage.setItem("mf_admin_sound_muted", state.soundMuted ? "1" : "0"); localStorage.setItem("mf_admin_sound_volume", String(state.soundVolume)); + if (state.soundMuted) stopRepeatingSound(); renderAdminSoundControls(); } @@ -840,6 +852,7 @@ function setAdminSoundVolume(value) { state.soundMuted = state.soundVolume <= 0; localStorage.setItem("mf_admin_sound_volume", String(state.soundVolume)); localStorage.setItem("mf_admin_sound_muted", state.soundMuted ? "1" : "0"); + if (state.soundMuted) stopRepeatingSound(); renderAdminSoundControls(); } @@ -863,8 +876,28 @@ function playNotifySound(options = {}) { setTimeout(() => audio.close().catch(() => {}), 720); } +function startRepeatingSound() { + if (!state.soundRepeat || state.soundRepeatTimer || state.soundMuted || clamp(state.soundVolume, 0, 100) <= 0) return; + state.soundRepeatTimer = setInterval(() => { + playNotifySound(); + }, 5500); +} + +function stopRepeatingSound() { + if (state.soundRepeatTimer) clearInterval(state.soundRepeatTimer); + state.soundRepeatTimer = null; +} + function soundPattern(style) { const patterns = { + tap: [ + { offset: 0, duration: 0.08, frequency: 720, type: "sine" } + ], + double: [ + { offset: 0, duration: 0.09, frequency: 760, type: "sine" }, + { offset: 0.12, duration: 0.09, frequency: 760, type: "sine" }, + { offset: 0.28, duration: 0.1, frequency: 980, type: "triangle" } + ], soft: [ { offset: 0, duration: 0.13, frequency: 520, type: "sine" }, { offset: 0.16, duration: 0.12, frequency: 680, type: "sine" } @@ -905,6 +938,7 @@ function startAttention() { function stopAttention() { if (state.attentionTimer) clearInterval(state.attentionTimer); state.attentionTimer = null; + stopRepeatingSound(); state.attentionOn = false; document.title = state.originalTitle; const favicon = document.querySelector("link[rel='icon']");