bethaus-app/static/search.js
2026-01-26 15:14:31 +00:00

251 lines
11 KiB
JavaScript

function initSearch() {
const form = document.getElementById('searchForm');
if (!form || form.dataset.bound) return;
form.dataset.bound = '1';
// Function to render search results from a response object
function renderResults(data) {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<h5>Suchergebnisse:</h5>';
const audioExts = ['.mp3', '.wav', '.ogg', '.m4a', '.flac'];
if (data.results && data.results.length > 0) {
data.results.forEach(file => {
const card = document.createElement('div');
const filenameWithoutExtension = file.filename.split('.').slice(0, -1).join('.');
const parentFolder = file.relative_path.split('/').slice(0, -1).join('/');
const transcriptURL = '/transcript/' + parentFolder + '/Transkription/' + filenameWithoutExtension + '.md';
const isAudio = audioExts.includes((file.filetype || '').toLowerCase());
const encodedRelPath = encodeURI(file.relative_path);
card.className = 'card';
const fileAction = isAudio
? `<button class="btn btn-light play-audio-btn" onclick="player.loadTrack('${file.relative_path}')" style="width:100%;"><i class="bi bi-volume-up"></i> ${filenameWithoutExtension}</button>`
: `<a class="btn btn-light download-btn" href="/media/${encodedRelPath}" download style="width:100%;"><i class="bi bi-download"></i> ${file.filename}</a>`;
card.innerHTML = `
<div class="card-body">
<p>${fileAction}</p>
<p><button class="btn btn-light btn-sm folder-open-btn" data-folder="${parentFolder}" data-file="${file.relative_path}" style="width:100%;"><i class="bi bi-folder"></i> ${parentFolder || 'Ordner'}</button></p>
<p class="card-text">Anzahl Downloads: ${file.hitcount}</p>
${ file.performance_date !== undefined ? `<p class="card-text">Datum: ${file.performance_date}</p>` : ``}
${ file.transcript_hits !== undefined ? `<p class="card-text">Treffer im Transkript: ${file.transcript_hits} <a href="#" class="show-transcript" data-url="${transcriptURL}" data-audio-url="${file.relative_path}" highlight="${file.query}"><i class="bi bi-journal-text"></i></a></p>` : ``}
</div>
`;
resultsDiv.appendChild(card);
});
attachEventListeners();
attachSearchFolderButtons();
} else {
resultsDiv.innerHTML = '<p>No results found.</p>';
}
}
// Restore previous search response if available from localStorage
const previousResponse = localStorage.getItem("searchResponse");
if (previousResponse) {
try {
const data = JSON.parse(previousResponse);
renderResults(data);
} catch (e) {
console.error('Error parsing searchResponse from localStorage:', e);
}
}
// Restore previous search word (Suchwort) if available
const previousQuery = localStorage.getItem("searchQuery");
if (previousQuery) {
document.getElementById('query').value = previousQuery;
}
// Restore previous selected category if available, otherwise default remains "Alles"
const previousCategory = localStorage.getItem("searchCategory");
if (previousCategory !== null) {
const radio = document.querySelector('input[name="category"][value="' + previousCategory + '"]');
if (radio) {
radio.checked = true;
}
}
// Restore previously selected filetypes (multi-select). Default to audio if none stored.
const previousFiletypes = localStorage.getItem("searchFiletypes");
if (previousFiletypes) {
try {
const list = JSON.parse(previousFiletypes);
document.querySelectorAll('input[name=\"filetype\"]').forEach(cb => {
cb.checked = list.includes(cb.value);
});
} catch (e) {
console.error('Error parsing stored filetypes', e);
document.getElementById('filetype-audio').checked = true;
}
} else {
document.getElementById('filetype-audio').checked = true;
}
// Restore the checkbox state for "Im Transkript suchen"
const previousIncludeTranscript = localStorage.getItem("searchIncludeTranscript");
if (previousIncludeTranscript !== null) {
document.getElementById('includeTranscript').checked = (previousIncludeTranscript === 'true');
}
// Form submission event
form.addEventListener('submit', function(e) {
e.preventDefault();
const query = document.getElementById('query').value.trim();
const includeTranscript = document.getElementById('includeTranscript').checked;
const spinnerTimer = setTimeout(() => {
if (typeof showSpinner === 'function') showSpinner();
}, 200);
// Get the selected category radio button, if any
const categoryRadio = document.querySelector('input[name="category"]:checked');
const category = categoryRadio ? categoryRadio.value : '';
// Get selected filetypes (allow multiple). Default to audio if none selected.
const filetypeCheckboxes = document.querySelectorAll('input[name=\"filetype\"]');
let filetypes = Array.from(filetypeCheckboxes).filter(cb => cb.checked).map(cb => cb.value);
if (filetypes.length === 0) {
// enforce audio as default when user unchecked all
document.getElementById('filetype-audio').checked = true;
filetypes = ['audio'];
}
// Prevent accidental re-selection of already selected radio buttons
const radios = document.querySelectorAll('input[name="category"]');
radios.forEach(radio => {
radio.addEventListener('mousedown', function(e) {
this.wasChecked = this.checked;
});
radio.addEventListener('click', function(e) {
if (this.wasChecked) {
this.checked = false;
this.wasChecked = false;
e.preventDefault();
}
});
});
// Prepare form data for the fetch request
// Send the query and the category as separate parameters.
const formData = new FormData();
formData.append('query', query);
formData.append('category', category);
formData.append('folder', document.getElementById('folder').value);
formData.append('datefrom', document.getElementById('datefrom').value);
formData.append('dateto', document.getElementById('dateto').value);
formData.append('includeTranscript', includeTranscript);
filetypes.forEach(ft => formData.append('filetype', ft));
const settleSpinner = () => {
clearTimeout(spinnerTimer);
if (typeof hideSpinner === 'function') hideSpinner();
};
const fetchPromise = fetch('/searchcommand', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
// Render the results
renderResults(data);
// Store the raw response in localStorage
try {
localStorage.setItem("searchResponse", JSON.stringify(data));
} catch (e) {
console.error('Error saving searchResponse to localStorage:', e);
}
// Save the search word, selected category, and checkbox state in localStorage
localStorage.setItem("searchQuery", query);
localStorage.setItem("searchCategory", category);
localStorage.setItem("searchFiletypes", JSON.stringify(filetypes));
localStorage.setItem("searchIncludeTranscript", includeTranscript);
})
.catch(error => {
console.error('Error:', error);
});
// Always clear/hide spinner once the request settles
if (typeof fetchPromise.finally === 'function') {
fetchPromise.finally(settleSpinner);
} else {
fetchPromise.then(settleSpinner, settleSpinner);
}
});
// Clear button event handler
document.getElementById('clearBtn').addEventListener('click', function() {
// Remove stored items
localStorage.removeItem("searchResponse");
localStorage.removeItem("searchQuery");
localStorage.removeItem("searchCategory");
localStorage.removeItem("searchFiletypes");
localStorage.removeItem("folder");
localStorage.removeItem("datefrom");
localStorage.removeItem("dateto");
localStorage.removeItem("searchIncludeTranscript");
// Reset form fields to defaults
document.getElementById('query').value = '';
document.querySelector('input[name="category"][value=""]').checked = true;
const otherRadios = document.querySelectorAll('input[name="category"]:not([value=""])');
otherRadios.forEach(radio => radio.checked = false);
document.getElementById('filetype-audio').checked = true;
const otherFiletypeBoxes = document.querySelectorAll('input[name=\"filetype\"]:not([value=\"audio\"])');
otherFiletypeBoxes.forEach(cb => cb.checked = false);
document.getElementById('folder').value = ''; // Reset to "Alle"
document.getElementById('datefrom').value = ''; // Reset date from
document.getElementById('dateto').value = ''; // Reset date to
document.getElementById('includeTranscript').checked = false;
// Clear the results div
document.getElementById('results').innerHTML = '';
});
// Back button event handler - redirect to the root path
document.getElementById('backBtn').addEventListener('click', function() {
// window.location.href = '/';
viewMain();
});
}
function attachSearchFolderButtons() {
document.querySelectorAll('.folder-open-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
const folder = btn.dataset.folder;
const file = btn.dataset.file;
openFolderAndHighlight(folder, file);
});
});
}
function openFolderAndHighlight(folderPath, filePath) {
const targetFolder = folderPath || '';
// Switch back to main view before loading folder
viewMain();
loadDirectory(targetFolder).then(() => {
const target = document.querySelector(`.play-file[data-url=\"${filePath}\"]`);
if (target) {
target.classList.add('search-highlight');
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}
function syncThemeColor() {
// read the CSS variable from :root (or any selector)
const cssVar = getComputedStyle(document.documentElement)
.getPropertyValue('--dark-background').trim();
if (cssVar) {
document
.querySelector('meta[name="theme-color"]')
.setAttribute('content', cssVar);
}
}
// sync once on load
// syncThemeColor handled by app init.
window.initSearch = initSearch;