diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..00e46a7 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-04-21 - [Pre-calculate derived search and date properties] +**Learning:** Instantiating `Date` objects repeatedly inside a render or filter loop for large lists causes measurable UI slowdowns. Calculating lowercase search strings for each field of every PDF on each keystroke or filter change is also expensive and redundant. +**Action:** Always pre-calculate and store such formatted date properties and search strings on the data objects during initial load to achieve a measurable speedup. Keep derived properties out of cache to prevent bloating. diff --git a/script.js b/script.js index ef463ba..4bf5e2a 100644 --- a/script.js +++ b/script.js @@ -452,6 +452,31 @@ async function syncClassSwitcher() { renderSemesterTabs(); } +function prepareSearchIndex(db) { + const formatter = new Intl.DateTimeFormat('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + db.forEach(pdf => { + // Pre-calculate search string for O(1) matching in render loop + const titleStr = pdf.title || ''; + const descStr = pdf.description || ''; + const catStr = pdf.category || ''; + const authorStr = pdf.author || ''; + pdf._searchStr = `${titleStr} ${descStr} ${catStr} ${authorStr}`.toLowerCase(); + + // Pre-calculate derived date properties + if (pdf.uploadDate) { + const uploadDateObj = new Date(pdf.uploadDate); + if (!isNaN(uploadDateObj)) { + const timeDiff = new Date() - uploadDateObj; + pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + pdf._formattedDate = formatter.format(uploadDateObj); + } + } + }); +} + async function loadPDFDatabase() { if (isMaintenanceActive) return; @@ -490,6 +515,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -513,6 +539,8 @@ async function loadPDFDatabase() { data: pdfDatabase })); + prepareSearchIndex(pdfDatabase); + // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -961,10 +989,16 @@ 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); + // Use pre-calculated search string if available, fallback if not + let matchesSearch = false; + if (pdf._searchStr) { + matchesSearch = pdf._searchStr.includes(searchTerm); + } else { + matchesSearch = pdf.title.toLowerCase().includes(searchTerm) || + pdf.description.toLowerCase().includes(searchTerm) || + pdf.category.toLowerCase().includes(searchTerm) || + (pdf.author && pdf.author.toLowerCase().includes(searchTerm)); + } // Update return statement to include matchesClass return matchesSemester && matchesClass && matchesCategory && matchesSearch; @@ -1037,9 +1071,24 @@ 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 date properties if available + let isNew = pdf._isNew; + let formattedDate = pdf._formattedDate; + + // Fallback if pre-calculation didn't happen (e.g. older docs) + if (isNew === undefined || formattedDate === undefined) { + const uploadDateObj = new Date(pdf.uploadDate); + if (!isNaN(uploadDateObj)) { + 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' + }); + } else { + isNew = false; + formattedDate = 'Unknown Date'; + } + } const newBadgeHTML = isNew ? `NEW` @@ -1053,11 +1102,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) => {