From 1f77f34dd63040a5abe0a43aed2bb9bafa770158 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 13:16:16 +0000 Subject: [PATCH] feat: precalculate search and date properties to optimize render loop Calculates `_searchStr`, `_isNew`, and `_formattedDate` when data is initialized in `prepareSearchIndex`, avoiding expensive string operations and object creation on every keypress during the `renderPDFs` loop. Co-authored-by: MrAlokTech <107493955+MrAlokTech@users.noreply.github.com> --- .jules/bolt.md | 3 +++ script.js | 54 +++++++++++++++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..4ee4c7d --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-24 - Pre-calculate Derived Properties +**Learning:** In `script.js`, filtering lists repeatedly (e.g., `renderPDFs` on each keystroke) can be expensive if we perform lowercasing, date parsing, and regex testing on the fly for every document. We can optimize this by doing an upfront calculation step `prepareSearchIndex` right after loading the database to append derived properties (`_searchStr`, `_isNew`, `_formattedDate`) directly to the documents. +**Action:** When filtering objects, if you see properties being derived dynamically using methods like `String.toLowerCase()` or `Date.toLocaleDateString()` inside loops, precalculate them beforehand when the data is initialized. diff --git a/script.js b/script.js index 22f399d..5c68c17 100644 --- a/script.js +++ b/script.js @@ -338,6 +338,25 @@ function getAdData(slotName) { /* ========================================= 5. DATA LOADING WITH CACHING ========================================= */ +function prepareSearchIndex(data) { + const now = new Date(); + data.forEach(pdf => { + // Pre-calculate search string + pdf._searchStr = `${pdf.title || ''} ${pdf.description || ''} ${pdf.category || ''} ${pdf.author || ''}`.toLowerCase(); + + // Handle Firestore Timestamp or string + let dateObj = pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function' + ? pdf.uploadDate.toDate() + : new Date(pdf.uploadDate); + + // Pre-calculate derived values to avoid per-render overhead + pdf._isNew = (now - dateObj) < (7 * 24 * 60 * 60 * 1000); + pdf._formattedDate = dateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + }); +} + function renderSemesterTabs() { const container = document.getElementById('semesterTabsContainer'); if (!container) return; @@ -454,6 +473,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -477,6 +497,8 @@ async function loadPDFDatabase() { data: pdfDatabase })); + prepareSearchIndex(pdfDatabase); + // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -905,26 +927,19 @@ function renderPDFs() { // Locate renderPDFs() in script.js and update the filter section const filteredPdfs = pdfDatabase.filter(pdf => { - const matchesSemester = pdf.semester === currentSemester; - - // 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; + if (pdf.semester !== currentSemester) return false; - let matchesCategory = false; if (currentCategory === 'favorites') { - matchesCategory = favorites.includes(pdf.id); - } else { - matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; + if (!favorites.includes(pdf.id)) return false; + } else 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); + // Fast search using pre-calculated property + 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 +1009,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 + // Use pre-calculated value or fallback + const isNew = pdf._isNew !== undefined ? pdf._isNew : (new Date() - new Date(pdf.uploadDate)) < (7 * 24 * 60 * 60 * 1000); const newBadgeHTML = isNew ? `NEW` @@ -1010,8 +1024,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', { + // Use pre-calculated formatted date or fallback + const formattedDate = pdf._formattedDate || new Date(pdf.uploadDate).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });