diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..f046d82 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2026-03-18 - Caching Search Index and Optimizing Loops +**Learning:** Pre-calculating derived fields (like lowercased search strings, `isNew`, `formattedDate`) on initial list load dramatically improves the performance of frequent operations like searching/filtering by avoiding redundant calculations on every render. Also, grouping boolean variables (`a && b && c`) in array filters is less efficient than explicit early returns (`if (!a) return false;`), which bypasses further evaluations immediately when a basic condition fails. +**Action:** When working with large lists or frequent render cycles, always identify expensive string/date operations that can be moved upstream (to data load/init time) and favor explicit early returns for cheap equality checks. diff --git a/script.js b/script.js index 22f399d..89104b6 100644 --- a/script.js +++ b/script.js @@ -454,6 +454,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -472,6 +473,8 @@ async function loadPDFDatabase() { pdfDatabase.push({ id: doc.id, ...doc.data() }); }); + prepareSearchIndex(pdfDatabase); + localStorage.setItem(CACHE_KEY, JSON.stringify({ timestamp: new Date().getTime(), data: pdfDatabase @@ -503,6 +506,34 @@ function hidePreloader() { } } +window.prepareSearchIndex = function prepareSearchIndex(dataList) { + const now = new Date(); + dataList.forEach(pdf => { + // Handle Firestore Timestamp vs regular ISO string + let uploadDateObj; + if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') { + uploadDateObj = pdf.uploadDate.toDate(); + } else { + uploadDateObj = new Date(pdf.uploadDate); + } + + const timeDiff = now - uploadDateObj; + pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days + + pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + // Pre-calculate lowercased search string + const title = pdf.title || ''; + const desc = pdf.description || ''; + const cat = pdf.category || ''; + const author = pdf.author || ''; + pdf._searchStr = `${title} ${desc} ${cat} ${author}`.toLowerCase(); + }); +} + + /* ========================================= 6. MAINTENANCE & HOLIDAYS ========================================= */ @@ -905,26 +936,21 @@ function renderPDFs() { // Locate renderPDFs() in script.js and update the filter section const filteredPdfs = pdfDatabase.filter(pdf => { - const matchesSemester = pdf.semester === currentSemester; + if (pdf.semester !== currentSemester) return false; // NEW: Check if the PDF class matches the UI's current class selection // Note: If old documents don't have this field, they will be hidden. - const matchesClass = pdf.class === currentClass; + if (pdf.class !== currentClass) return false; - let matchesCategory = false; if (currentCategory === 'favorites') { - matchesCategory = favorites.includes(pdf.id); + if (!favorites.includes(pdf.id)) return false; } else { - matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; + if (currentCategory !== 'all' && pdf.category !== currentCategory) return false; } - const matchesSearch = pdf.title.toLowerCase().includes(searchTerm) || - pdf.description.toLowerCase().includes(searchTerm) || - pdf.category.toLowerCase().includes(searchTerm) || - pdf.author.toLowerCase().includes(searchTerm); + if (searchTerm && (!pdf._searchStr || !pdf._searchStr.includes(searchTerm))) return false; - // Update return statement to include matchesClass - return matchesSemester && matchesClass && matchesCategory && matchesSearch; + return true; }); updatePDFCount(filteredPdfs.length); @@ -994,9 +1020,7 @@ 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 + const isNew = pdf._isNew; const newBadgeHTML = isNew ? `NEW` @@ -1011,9 +1035,7 @@ 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', { - year: 'numeric', month: 'short', day: 'numeric' - }); + const formattedDate = pdf._formattedDate; // Uses global escapeHtml() now