diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..a06fd4f --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-24 - Pre-calculating Search and Filter Values +**Learning:** In highly iterated loops like `renderPDFs` and `createPDFCard` on large arrays, performing inline computations (e.g. concatenating strings, calling `toLowerCase()`, instantiating `Date` objects, using `Intl.DateTimeFormat`) drastically reduces performance. +**Action:** Created a `prepareSearchIndex` method to be called immediately after the data array is populated (either via cache or fresh fetch) to map `_searchStr`, `_formattedDate`, and `_isNew` to each object. Then updated the filter logic to simply check `.includes()` against `_searchStr` and format logic to use the pre-calculated formats, reducing repeated calculations and object instantiations. diff --git a/script.js b/script.js index 22f399d..9c1bc9e 100644 --- a/script.js +++ b/script.js @@ -416,6 +416,30 @@ async function syncClassSwitcher() { renderSemesterTabs(); } + +/* ========================================= + PERFORMANCE UTILITIES + ========================================= */ +function prepareSearchIndex(data) { + const now = new Date(); + const dateFormatter = new Intl.DateTimeFormat('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + data.forEach(pdf => { + // Pre-calculate search string + pdf._searchStr = `${pdf.title} ${pdf.description} ${pdf.category} ${pdf.author || ''}`.toLowerCase(); + + // Pre-calculate formatted date + const uploadDateObj = new Date(pdf.uploadDate); + pdf._formattedDate = dateFormatter.format(uploadDateObj); + + // Pre-calculate isNew flag (7 days) + const timeDiff = now - uploadDateObj; + pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + }); +} + async function loadPDFDatabase() { if (isMaintenanceActive) return; @@ -454,6 +478,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -477,6 +502,8 @@ async function loadPDFDatabase() { data: pdfDatabase })); + prepareSearchIndex(pdfDatabase); + // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -918,10 +945,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); + let matchesSearch = true; + if (searchTerm) { + if (pdf._searchStr) { + matchesSearch = pdf._searchStr.includes(searchTerm); + } else { + const searchStrFallback = `${pdf.title} ${pdf.description} ${pdf.category} ${pdf.author || ''}`.toLowerCase(); + matchesSearch = searchStrFallback.includes(searchTerm); + } + } // Update return statement to include matchesClass return matchesSemester && matchesClass && matchesCategory && matchesSearch; @@ -994,9 +1026,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 !== undefined ? pdf._isNew : (new Date() - new Date(pdf.uploadDate) < 7 * 24 * 60 * 60 * 1000); // 7 days const newBadgeHTML = isNew ? `NEW` @@ -1011,7 +1041,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', { + const formattedDate = pdf._formattedDate || new Date(pdf.uploadDate).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });