diff --git a/script.js b/script.js index bdc06f3..17f0441 100644 --- a/script.js +++ b/script.js @@ -247,6 +247,15 @@ function getSimpleDeviceName() { /* ========================================= 4. UI HELPERS & ADS ========================================= */ +function debounce(func, wait) { + let timeout; + return function(...args) { + const context = this; + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(context, args), wait); + }; +} + function handleGoToTopVisibility() { if (window.scrollY > 400) { goToTopBtn.classList.add('show'); @@ -382,6 +391,36 @@ window.changeSemester = function (sem) { renderPDFs(); }; +function prepareSearchIndex(pdfs) { + const now = new Date(); + pdfs.forEach(pdf => { + // Optimization: Pre-calculate search string + pdf._searchStr = ( + (pdf.title || '') + ' ' + + (pdf.description || '') + ' ' + + (pdf.category || '') + ' ' + + (pdf.author || '') + ).toLowerCase(); + + // Optimization: Pre-calculate date values + let dateObj; + // Handle Firestore Timestamp or String + if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') { + dateObj = pdf.uploadDate.toDate(); + } else { + dateObj = new Date(pdf.uploadDate); + } + + // Optimization: Cache formatted date string + pdf._formattedDate = dateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + const timeDiff = now - dateObj; + pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days + }); +} + async function syncClassSwitcher() { const classSelect = document.getElementById('classSelect'); if (!classSelect) return; @@ -451,6 +490,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -474,6 +514,8 @@ async function loadPDFDatabase() { data: pdfDatabase })); + prepareSearchIndex(pdfDatabase); + // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -656,7 +698,7 @@ function activateHoliday(overlay) { 7. EVENT LISTENERS ========================================= */ function setupEventListeners() { - searchInput.addEventListener('input', renderPDFs); + searchInput.addEventListener('input', debounce(renderPDFs, 300)); tabBtns.forEach(btn => { btn.addEventListener('click', handleSemesterChange); @@ -915,10 +957,14 @@ function renderPDFs() { matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; } - const matchesSearch = pdf.title.toLowerCase().includes(searchTerm) || + // Optimization: Use pre-calculated search string + const matchesSearch = pdf._searchStr ? pdf._searchStr.includes(searchTerm) : ( + // Fallback for safety + pdf.title.toLowerCase().includes(searchTerm) || pdf.description.toLowerCase().includes(searchTerm) || pdf.category.toLowerCase().includes(searchTerm) || - pdf.author.toLowerCase().includes(searchTerm); + pdf.author.toLowerCase().includes(searchTerm) + ); // Update return statement to include matchesClass return matchesSemester && matchesClass && matchesCategory && matchesSearch; @@ -991,9 +1037,8 @@ function createPDFCard(pdf, favoritesList, index = 0, highlightRegex = null) { const heartIconClass = isFav ? 'fas' : 'far'; const btnActiveClass = isFav ? 'active' : ''; - const uploadDateObj = new Date(pdf.uploadDate); - const timeDiff = new Date() - uploadDateObj; - const isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days + // Optimization: Use pre-calculated isNew flag + const isNew = pdf._isNew !== undefined ? pdf._isNew : (new Date() - new Date(pdf.uploadDate) < (7 * 24 * 60 * 60 * 1000)); const newBadgeHTML = isNew ? `NEW` @@ -1008,7 +1053,8 @@ function createPDFCard(pdf, favoritesList, index = 0, highlightRegex = null) { const categoryIcon = categoryIcons[pdf.category] || 'fa-file-pdf'; // Formatting Date - const formattedDate = new Date(pdf.uploadDate).toLocaleDateString('en-US', { + // Optimization: Use pre-formatted date string + const formattedDate = pdf._formattedDate || new Date(pdf.uploadDate).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });