import { humanTime, parseSrt } from './subtitles.mjs'; import { computeStats } from './stats.mjs'; function debounce(func, timeout = 300){ let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => func.apply(this, args), timeout); }; } async function computeGlobalWordSearch() { const debates = await fetch('./content/index.json').then(r => r.json()); const wordOccurrences = new Map(); for (const name of debates) { const metadata = await fetch(`content/${name}.json`).then(r => r.json()); const subtitleSrt = await fetch(`content/${name}.srt`).then(r => r.text()); const subtitles = parseSrt(subtitleSrt); const stats = computeStats(subtitles, metadata); for (const [w, occurrences] of stats.wordOccurrences) { const prevOccurrences = wordOccurrences.get(w) || []; const newOccurrences = occurrences.map(([t, speaker]) => [name, t, speaker]); const updatedOccurrences = [...prevOccurrences, ...newOccurrences]; wordOccurrences.set(w, updatedOccurrences); } } return { wordOccurrences, debates }; } function partialMatch(word, words) { return words.filter((w) => w.includes(word)); } function plural(word, count, suffix = 's') { return `${word}${count === 1 ? '' : suffix}`; } function fillSelect(selectEl, options) { selectEl.innerHTML = ''; for (const option of options) { const el = document.createElement('option'); el.value = option; el.appendChild(document.createTextNode(option)); selectEl.appendChild(el); } } const MAX_VISIBLE_RESULTS = 200; const ALL = '-todos-'; export async function search() { const listEl = document.getElementById('list'); const searchEl = document.getElementById('search'); const searchInputEl = document.getElementById('search-input'); const searchExactWordEl = document.getElementById('search-exact-word'); const searchSpeakerEl = document.getElementById('search-speaker'); const searchDebateEl = document.getElementById('search-debate'); const searchResultsCountEl = document.getElementById('search-results-count'); const searchResultsEl = document.getElementById('search-results'); const { wordOccurrences: wo, debates } = await computeGlobalWordSearch(); const words = Array.from(wo.keys()); let speakers = new Set(); for (const values of wo.values()) { for (const [_, __, speaker] of values) { speaker && speakers.add(speaker); } } speakers = Array.from(speakers); debates.sort(); debates.unshift(ALL); speakers.sort(); speakers.unshift(ALL); fillSelect(searchDebateEl, debates); fillSelect(searchSpeakerEl, speakers); searchEl.style.display = ''; const updateResults = () => { listEl.style.display = 'none'; searchResultsEl.innerHTML = ''; const w = searchInputEl.value.toLowerCase(); const isExact = searchExactWordEl.checked; const desiredSpeaker = searchSpeakerEl.value; const filterSpeaker = desiredSpeaker !== ALL; const desiredDebate = searchDebateEl.value; const filterDebate = desiredDebate !== ALL; if (w.length < 2) { searchResultsCountEl.innerHTML = ''; return; } let hits; if (isExact) { hits = wo.get(w) || []; } else { hits = partialMatch(w, words).reduce((prev, curr) => { return [...prev, ...(wo.get(curr).map(arr => [...arr, curr]) || [])]; }, []); } let renderedCount = 0; let availableCount = 0; for (const [name, time, speaker, _word] of hits) { if (filterDebate && desiredDebate !== name) continue; if (filterSpeaker && desiredSpeaker !== speaker) continue; ++availableCount; if (renderedCount >= MAX_VISIBLE_RESULTS) continue; ++renderedCount; const [_, title] = name.split('_'); const resultEl = document.createElement('p'); const aEl = document.createElement('a'); const url = `./?r=${Date.now() % 10000}#${name}/${time}`; aEl.href = url; let label = `${humanTime(time)}`; if (!filterDebate) label += ` no debate ${title}`; if (!filterSpeaker) label = `${speaker} ao ` + label; if (_word) label = `'${_word}' ` + label; aEl.appendChild(document.createTextNode(label)); resultEl.appendChild(aEl); searchResultsEl.appendChild(resultEl); } searchResultsCountEl.innerHTML = `${plural('Existe', availableCount, 'm')} ${availableCount} ${plural('resultado', availableCount)}. A mostrar ${renderedCount}.` }; const debounceUpdateResults = debounce(updateResults, 200); function setupEvents(unset) { const fn = unset ? 'removeEventListener' : 'addEventListener'; searchInputEl[fn]('keyup', debounceUpdateResults); searchExactWordEl[fn]('change', debounceUpdateResults); searchSpeakerEl[fn]('change', debounceUpdateResults); searchDebateEl[fn]('change', debounceUpdateResults); } setupEvents(false); }