Files
WaifuBoard/desktop/src/scripts/utils/search-manager.js
itsskaiya 28ff6ccc68 Organized the differences between server and docker versions.
We are launching a docker version (server version) today so we want to just organize the repo
so its easier to navigate.
2025-12-16 21:50:22 -05:00

176 lines
6.4 KiB
JavaScript

const SearchManager = {
availableExtensions: [],
searchTimeout: null,
init(inputSelector, resultsSelector, type = 'anime') {
const searchInput = document.querySelector(inputSelector);
const searchResults = document.querySelector(resultsSelector);
if (!searchInput || !searchResults) {
console.error('Search elements not found');
return;
}
this.loadExtensions(type);
searchInput.addEventListener('input', (e) => {
const query = e.target.value;
clearTimeout(this.searchTimeout);
if (query.length < 2) {
searchResults.classList.remove('active');
searchResults.innerHTML = '';
searchInput.style.borderRadius = '99px';
return;
}
this.searchTimeout = setTimeout(() => {
this.search(query, type, searchResults);
}, 300);
});
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-wrapper')) {
searchResults.classList.remove('active');
searchInput.style.borderRadius = '99px';
}
});
},
async loadExtensions(type) {
try {
const endpoint = type === 'book' ? '/api/extensions/book' : '/api/extensions/anime';
const res = await fetch(endpoint);
const data = await res.json();
this.availableExtensions = data.extensions || [];
console.log(`${type} extensions loaded:`, this.availableExtensions);
} catch (err) {
console.error('Error loading extensions:', err);
}
},
async search(query, type, resultsContainer) {
try {
let apiUrl, extensionName = null, finalQuery = query;
const parts = query.split(':');
if (parts.length >= 2) {
const potentialExtension = parts[0].trim().toLowerCase();
const foundExtension = this.availableExtensions.find(
ext => ext.toLowerCase() === potentialExtension
);
if (foundExtension) {
extensionName = foundExtension;
finalQuery = parts.slice(1).join(':').trim();
if (finalQuery.length === 0) {
this.renderResults([], resultsContainer, type);
return;
}
}
}
if (extensionName) {
const endpoint = type === 'book' ? 'books' : '';
apiUrl = `/api/search/${endpoint ? endpoint + '/' : ''}${extensionName}?q=${encodeURIComponent(finalQuery)}`;
} else {
const endpoint = type === 'book' ? '/api/search/books' : '/api/search';
apiUrl = `${endpoint}?q=${encodeURIComponent(query)}`;
}
const res = await fetch(apiUrl);
const data = await res.json();
const results = (data.results || []).map(item => ({
...item,
isExtensionResult: !!extensionName,
extensionName
}));
this.renderResults(results, resultsContainer, type);
} catch (err) {
console.error("Search Error:", err);
this.renderResults([], resultsContainer, type);
}
},
renderResults(results, container, type) {
container.innerHTML = '';
if (!results || results.length === 0) {
container.innerHTML = '<div style="padding:1rem; color:#888; text-align:center">No results found</div>';
} else {
results.forEach(item => {
const resultElement = this.createResultElement(item, type);
container.appendChild(resultElement);
});
}
container.classList.add('active');
const searchInput = container.previousElementSibling || document.querySelector('.search-input');
if (searchInput) {
searchInput.style.borderRadius = '12px 12px 0 0';
}
},
createResultElement(item, type) {
const element = document.createElement('a');
element.className = 'search-item';
if (type === 'book') {
const title = item.title?.english || item.title?.romaji || "Unknown";
const img = item.coverImage?.medium || item.coverImage?.large || '';
const rating = Number.isInteger(item.averageScore) ? `${item.averageScore}%` : item.averageScore || 'N/A';
const year = item.seasonYear || item.startDate?.year || '????';
const format = item.format || 'MANGA';
element.href = item.isExtensionResult
? `/book/${item.extensionName}/${item.id}`
: `/book/${item.id}`;
element.innerHTML = `
<img src="${img}" class="search-poster" alt="${title}">
<div class="search-info">
<div class="search-title">${title}</div>
<div class="search-meta">
<span class="rating-pill">${rating}</span>
<span>• ${year}</span>
<span>• ${format}</span>
</div>
</div>
`;
} else {
const title = item.title?.english || item.title?.romaji || "Unknown Title";
const img = item.coverImage?.medium || item.coverImage?.large || '';
const rating = item.averageScore ? `${item.averageScore}%` : 'N/A';
const year = item.seasonYear || '';
const format = item.format || 'TV';
element.href = item.isExtensionResult
? `/anime/${item.extensionName}/${item.id}`
: `/anime/${item.id}`;
element.innerHTML = `
<img src="${img}" class="search-poster" alt="${title}">
<div class="search-info">
<div class="search-title">${title}</div>
<div class="search-meta">
<span class="rating-pill">${rating}</span>
<span>• ${year}</span>
<span>• ${format}</span>
</div>
</div>
`;
}
return element;
},
getTitle(item) {
return item.title?.english || item.title?.romaji || "Unknown Title";
}
};
window.SearchManager = SearchManager;