299 lines
13 KiB
JavaScript
299 lines
13 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');
|
|
const results = Array.isArray(data.results) ? data.results : [];
|
|
const total = Number.isFinite(data.total) ? data.total : results.length;
|
|
const shown = results.length;
|
|
const remaining = Math.max(0, total - shown);
|
|
resultsDiv.innerHTML = `<h5>${total} Treffer</h5>`;
|
|
|
|
const audioExts = ['.mp3', '.wav', '.ogg', '.m4a', '.flac'];
|
|
|
|
if (results.length > 0) {
|
|
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 isSng = (file.filetype || '').toLowerCase() === '.sng' || (file.filename || '').toLowerCase().endsWith('.sng');
|
|
const encodedRelPath = encodeURI(file.relative_path);
|
|
const fulltextType = file.fulltext_type || 'transcript';
|
|
const fulltextUrl = file.fulltext_url || transcriptURL;
|
|
const fulltextLabel = 'Treffer im Volltext';
|
|
const fulltextAction = fulltextType === 'sng'
|
|
? `<a href="#" class="open-sng-link" data-path="${file.relative_path}" title="Liedtext öffnen"><i class="bi bi-journal-text"></i></a>`
|
|
: `<a href="#" class="show-transcript" data-url="${fulltextUrl}" data-audio-url="${file.relative_path}" highlight="${file.query}"><i class="bi bi-journal-text"></i></a>`;
|
|
|
|
card.className = 'card';
|
|
let fileAction = '';
|
|
if (isSng) {
|
|
fileAction = `<button class="btn btn-light open-sng-btn" data-path="${file.relative_path}" style="width:100%;"><i class="bi bi-music-note-list"></i> ${filenameWithoutExtension}</button>`;
|
|
} else if (isAudio) {
|
|
fileAction = `<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>`;
|
|
} else {
|
|
fileAction = `<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 && file.performance_date !== null && String(file.performance_date).trim() !== '') ? `<p class="card-text">Datum: ${file.performance_date}</p>` : ``}
|
|
${ file.transcript_hits !== undefined ? `<p class="card-text">${fulltextLabel}: ${file.transcript_hits} ${fulltextAction}</p>` : ``}
|
|
</div>
|
|
`;
|
|
resultsDiv.appendChild(card);
|
|
});
|
|
if (remaining > 0) {
|
|
const more = document.createElement('p');
|
|
more.className = 'text-muted';
|
|
more.textContent = `und weitere ${remaining} Treffer`;
|
|
resultsDiv.appendChild(more);
|
|
}
|
|
attachEventListeners();
|
|
attachSearchFolderButtons();
|
|
attachSearchSngButtons();
|
|
attachSearchFulltextButtons();
|
|
} else {
|
|
resultsDiv.innerHTML = `<h5>${total} Treffer</h5><p>Keine Treffer gefunden.</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 attachSearchSngButtons() {
|
|
document.querySelectorAll('.open-sng-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const path = btn.dataset.path;
|
|
if (typeof window.openSngModal === 'function') {
|
|
window.openSngModal(path);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function attachSearchFulltextButtons() {
|
|
document.querySelectorAll('.open-sng-link').forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const path = link.dataset.path;
|
|
if (typeof window.openSngModal === 'function') {
|
|
window.openSngModal(path);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
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;
|