diff --git a/static/app.js b/static/app.js index 32c72eb..a04c6d1 100644 --- a/static/app.js +++ b/static/app.js @@ -56,13 +56,28 @@ const graphModalClose = document.getElementById("graphModalClose"); const channelMap = new Map(); const transcriptCache = new Map(); + const SETTINGS_KEY = "tlc-search-settings"; + const DEFAULT_SETTINGS = { + channel: "", + year: "", + sort: "relevant", + size: "10", + exact: true, + fuzzy: true, + phrase: true, + external: false, + queryString: false, + }; + let settings = loadSettings(); let lastFocusBeforeModal = null; let pendingChannelSelection = ""; let channelsReady = false; - let previousToggleState = { exact: true, fuzzy: true, phrase: true }; - let currentPage = - parseInt(qs.get("page") || "0", 10) || - 0; + let previousToggleState = { + exact: settings.exact, + fuzzy: settings.fuzzy, + phrase: settings.phrase, + }; + let currentPage = 0; function toggleAboutPanel(show) { if (!aboutPanel) return; @@ -73,13 +88,6 @@ } } - function parseBoolParam(name, defaultValue) { - const raw = qs.get(name); - if (raw === null) return defaultValue; - const lowered = raw.toLowerCase(); - return !["0", "false", "no"].includes(lowered); - } - function parseChannelParam(params) { if (!params) return ""; const seen = new Set(); @@ -102,6 +110,69 @@ return first || ""; } + function loadSettings() { + try { + const raw = localStorage.getItem(SETTINGS_KEY); + if (!raw) return { ...DEFAULT_SETTINGS }; + const parsed = JSON.parse(raw); + return { ...DEFAULT_SETTINGS, ...parsed }; + } catch (err) { + console.warn("Failed to load settings", err); + return { ...DEFAULT_SETTINGS }; + } + } + + function persistSettings() { + try { + localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); + } catch (err) { + console.warn("Failed to persist settings", err); + } + } + + function applyStoredSettings() { + yearSel.value = settings.year || ""; + sortSel.value = settings.sort || "relevant"; + sizeSel.value = settings.size || "10"; + exactToggle.checked = settings.exact; + fuzzyToggle.checked = settings.fuzzy; + phraseToggle.checked = settings.phrase; + if (externalToggle) { + externalToggle.checked = settings.external; + } + if (queryToggle) { + queryToggle.checked = settings.queryString; + } + } + + function currentTogglePreferences() { + if (queryToggle && queryToggle.checked) { + return { ...previousToggleState }; + } + return { + exact: !!exactToggle.checked, + fuzzy: !!fuzzyToggle.checked, + phrase: !!phraseToggle.checked, + }; + } + + function syncSettingsFromControls() { + const togglePrefs = currentTogglePreferences(); + const next = { + ...settings, + channel: channelSelect ? channelSelect.value || "" : "", + year: yearSel.value || "", + sort: sortSel.value || "relevant", + size: sizeSel.value || "10", + external: externalToggle ? !!externalToggle.checked : false, + queryString: queryToggle ? !!queryToggle.checked : false, + ...togglePrefs, + }; + settings = next; + persistSettings(); + return settings; + } + function getSelectedChannels() { if (!channelSelect) return []; const value = channelSelect.value; @@ -130,20 +201,18 @@ function setFromQuery() { qInput.value = qs.get("q") || ""; - yearSel.value = qs.get("year") || ""; - sortSel.value = qs.get("sort") || "relevant"; - sizeSel.value = qs.get("size") || "10"; - pendingChannelSelection = parseChannelParam(qs); + const urlChannel = parseChannelParam(qs); + if (urlChannel) { + pendingChannelSelection = urlChannel; + settings.channel = urlChannel; + persistSettings(); + } else { + pendingChannelSelection = settings.channel || ""; + } + applyStoredSettings(); if (channelSelect) { channelSelect.value = pendingChannelSelection || ""; } - exactToggle.checked = parseBoolParam("exact", true); - fuzzyToggle.checked = parseBoolParam("fuzzy", true); - phraseToggle.checked = parseBoolParam("phrase", true); - if (externalToggle) { - externalToggle.checked = parseBoolParam("external", false); - } - queryToggle.checked = parseBoolParam("query_string", false); applyQueryMode(); rememberToggleState(); } @@ -157,6 +226,8 @@ fuzzy: fuzzyToggle.checked, phrase: phraseToggle.checked, }; + settings = { ...settings, ...previousToggleState }; + persistSettings(); } exactToggle.checked = false; fuzzyToggle.checked = false; @@ -172,6 +243,8 @@ fuzzyToggle.checked = previousToggleState.fuzzy; phraseToggle.checked = previousToggleState.phrase; } + settings.queryString = !!(queryToggle && queryToggle.checked); + persistSettings(); } function rememberToggleState() { @@ -181,6 +254,8 @@ fuzzy: !!fuzzyToggle.checked, phrase: !!phraseToggle.checked, }; + settings = { ...settings, ...previousToggleState }; + persistSettings(); } } @@ -313,6 +388,8 @@ } else { channelSelect.value = ""; } + settings.channel = channelSelect.value || ""; + persistSettings(); channelsReady = true; } catch (err) { @@ -322,25 +399,24 @@ } } - function updateUrl(q, sort, channels, year, page, size, exact, fuzzy, phrase, queryMode, includeExternal) { + function updateUrl(q) { const next = new URL(window.location.href); - next.searchParams.set("q", q); - next.searchParams.set("sort", sort); + if (q) { + next.searchParams.set("q", q); + } else { + next.searchParams.delete("q"); + } + next.searchParams.delete("page"); + next.searchParams.delete("sort"); next.searchParams.delete("channel_id"); next.searchParams.delete("channel"); - channels.forEach((id) => next.searchParams.append("channel_id", id)); - if (year) { - next.searchParams.set("year", year); - } else { - next.searchParams.delete("year"); - } - next.searchParams.set("page", page); - next.searchParams.set("size", size); - next.searchParams.set("exact", exact ? "1" : "0"); - next.searchParams.set("fuzzy", fuzzy ? "1" : "0"); - next.searchParams.set("phrase", phrase ? "1" : "0"); - next.searchParams.set("query_string", queryMode ? "1" : "0"); - next.searchParams.set("external", includeExternal ? "1" : "0"); + next.searchParams.delete("year"); + next.searchParams.delete("size"); + next.searchParams.delete("exact"); + next.searchParams.delete("fuzzy"); + next.searchParams.delete("phrase"); + next.searchParams.delete("query_string"); + next.searchParams.delete("external"); history.pushState({}, "", next.toString()); } @@ -1542,24 +1618,14 @@ async function updateFrequencyChart(term, channels, year, queryMode, toggles = { fuzzy, phrase, }; + settings = { ...settings, ...previousToggleState }; + persistSettings(); } const page = pageOverride != null ? pageOverride : currentPage; currentPage = page; if (pushState) { - updateUrl( - q, - sort, - channels, - year, - page, - size, - exact, - fuzzy, - phrase, - queryMode, - includeExternal - ); + updateUrl(q); } const params = new URLSearchParams(); @@ -1575,6 +1641,8 @@ async function updateFrequencyChart(term, channels, year, queryMode, toggles = { channels.forEach((id) => params.append("channel_id", id)); if (year) params.set("year", year); + syncSettingsFromControls(); + const res = await fetch(`/api/search?${params.toString()}`); const payload = await res.json(); renderResults(payload, page); @@ -1598,31 +1666,39 @@ async function updateFrequencyChart(term, channels, year, queryMode, toggles = { if (channelSelect) { channelSelect.addEventListener("change", () => { pendingChannelSelection = channelSelect.value || ""; + settings.channel = pendingChannelSelection; + persistSettings(); if (channelsReady) { runSearch(0); } }); } - yearSel.addEventListener("change", () => runSearch(0)); - sortSel.addEventListener("change", () => runSearch(0)); - sizeSel.addEventListener("change", () => runSearch(0)); - exactToggle.addEventListener("change", () => { rememberToggleState(); runSearch(0); }); - fuzzyToggle.addEventListener("change", () => { rememberToggleState(); runSearch(0); }); - phraseToggle.addEventListener("change", () => { rememberToggleState(); runSearch(0); }); + yearSel.addEventListener("change", () => { syncSettingsFromControls(); runSearch(0); }); + sortSel.addEventListener("change", () => { syncSettingsFromControls(); runSearch(0); }); + sizeSel.addEventListener("change", () => { syncSettingsFromControls(); runSearch(0); }); + exactToggle.addEventListener("change", () => { rememberToggleState(); syncSettingsFromControls(); runSearch(0); }); + fuzzyToggle.addEventListener("change", () => { rememberToggleState(); syncSettingsFromControls(); runSearch(0); }); + phraseToggle.addEventListener("change", () => { rememberToggleState(); syncSettingsFromControls(); runSearch(0); }); if (externalToggle) { externalToggle.addEventListener("change", () => { pendingChannelSelection = ""; + settings.external = !!externalToggle.checked; + persistSettings(); loadChannels().then(() => runSearch(0)); }); } if (queryToggle) { - queryToggle.addEventListener("change", () => { applyQueryMode(); runSearch(0); }); + queryToggle.addEventListener("change", () => { + applyQueryMode(); + syncSettingsFromControls(); + runSearch(0); + }); } window.addEventListener("popstate", () => { qs = new URLSearchParams(window.location.search); setFromQuery(); - currentPage = parseInt(qs.get("page") || "0", 10) || 0; + currentPage = 0; runSearch(currentPage, false); });