From 38c282313247655292bc59bd9bfde0544e657bdd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:27:15 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20PDF=20search=20a?= =?UTF-8?q?nd=20date=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: - Introduced `prepareSearchIndex()` to pre-calculate `_searchStr` (lowercased concatenation of searchable fields), `_formattedDate`, and `_isNew` for all PDFs. - Updated `renderPDFs` to use `_searchStr` instead of repetitive `toLowerCase()` calls on multiple fields. - Updated `createPDFCard` to use pre-calculated `_formattedDate` and `_isNew` to avoid `new Date()` allocation in the render loop. - Integrated `prepareSearchIndex()` into `loadPDFDatabase` for both cached and fresh data paths. 🎯 Why: - Rendering the PDF grid was computationally expensive due to $O(N \times M)$ string operations and date parsing for every filter/search event. - This caused UI lag on lower-end devices when typing in the search box. 📊 Impact: - Reduces string operations in the search loop by ~75% (1 check vs 4 checks + lowercase). - Eliminates `new Date()` allocation during list rendering. - Keeps localStorage clean by using non-enumerable properties. 🔬 Measurement: - Verified with `node -c script.js` for syntax correctness. - Verified logic with `test_optimization.js` script (simulating data processing). Co-authored-by: MrAlokTech <107493955+MrAlokTech@users.noreply.github.com> --- script.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/script.js b/script.js index bdc06f3..59a0fc8 100644 --- a/script.js +++ b/script.js @@ -56,6 +56,50 @@ const categoryIcons = { 'Syllabus': 'fa-list-alt' }; +/** + * ⚡ OPTIMIZATION: Pre-calculate search strings and dates. + * This avoids expensive .toLowerCase() and new Date() calls inside the render loop. + * Properties are non-enumerable to prevent localStorage bloat. + */ +function prepareSearchIndex() { + const now = new Date(); + const oneWeekMs = 7 * 24 * 60 * 60 * 1000; + + pdfDatabase.forEach(pdf => { + // Skip if already prepared (optional check) + if (pdf._searchStr) return; + + // Pre-calculate Search String + const searchStr = ( + (pdf.title || "") + " " + + (pdf.description || "") + " " + + (pdf.category || "") + " " + + (pdf.author || "") + ).toLowerCase(); + + // Pre-calculate Date + let formattedDate = ""; + let isNew = false; + + try { + const uploadDateObj = new Date(pdf.uploadDate); + formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + isNew = (now - uploadDateObj) < oneWeekMs; + } catch (e) { + console.warn("Date parse error for PDF", pdf.id); + } + + // Use defineProperty to keep them out of localStorage serialization + Object.defineProperties(pdf, { + _searchStr: { value: searchStr, writable: true, configurable: true, enumerable: false }, + _formattedDate: { value: formattedDate, writable: true, configurable: true, enumerable: false }, + _isNew: { value: isNew, writable: true, configurable: true, enumerable: false } + }); + }); +} + function renderCategoryFilters() { const container = document.getElementById('categoryFilters'); if (!container) return; @@ -451,6 +495,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(); // ⚡ Optimize after cache load // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -474,6 +519,7 @@ async function loadPDFDatabase() { data: pdfDatabase })); + prepareSearchIndex(); // ⚡ Optimize after fresh fetch // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -915,10 +961,15 @@ function renderPDFs() { matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; } - const matchesSearch = pdf.title.toLowerCase().includes(searchTerm) || - pdf.description.toLowerCase().includes(searchTerm) || - pdf.category.toLowerCase().includes(searchTerm) || - pdf.author.toLowerCase().includes(searchTerm); + // ⚡ OPTIMIZED SEARCH: Use pre-calculated _searchStr + const matchesSearch = pdf._searchStr + ? pdf._searchStr.includes(searchTerm) + : ( + (pdf.title || "").toLowerCase().includes(searchTerm) || + (pdf.description || "").toLowerCase().includes(searchTerm) || + (pdf.category || "").toLowerCase().includes(searchTerm) || + (pdf.author || "").toLowerCase().includes(searchTerm) + ); // Update return statement to include matchesClass return matchesSemester && matchesClass && matchesCategory && matchesSearch; @@ -991,9 +1042,20 @@ 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 + // ⚡ OPTIMIZED: Use pre-calculated values + let isNew, formattedDate; + + if (typeof pdf._isNew !== 'undefined') { + isNew = pdf._isNew; + formattedDate = pdf._formattedDate; + } else { + const uploadDateObj = new Date(pdf.uploadDate); + const timeDiff = new Date() - uploadDateObj; + isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days + formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + } const newBadgeHTML = isNew ? `NEW` @@ -1007,11 +1069,6 @@ 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' - }); - // Uses global escapeHtml() now const highlightText = (text) => {