diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..9cf1326 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - [Optimized render loop strings and dates] +**Learning:** Instantiating `Intl.DateTimeFormat` and parsing dates in `createPDFCard` on every render creates massive JS heap overhead on large sets, blocking typing and scrolling. +**Action:** Created `prepareSearchIndex(data)` that runs exactly once when `pdfDatabase` is populated, assigning private variables `_searchStr`, `_isNew`, and `_formattedDate`. `renderPDFs` now falls back to standard rendering only if properties don't exist. diff --git a/script.js b/script.js index ef463ba..ad32ba8 100644 --- a/script.js +++ b/script.js @@ -490,6 +490,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -513,6 +514,7 @@ async function loadPDFDatabase() { data: pdfDatabase })); + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -961,10 +963,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 = 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.toLowerCase().includes(searchTerm); + } // Update return statement to include matchesClass return matchesSemester && matchesClass && matchesCategory && matchesSearch; @@ -1037,9 +1044,12 @@ 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 + let isNew = pdf._isNew; + if (isNew === undefined) { + const uploadDateObj = new Date(pdf.uploadDate); + const timeDiff = new Date() - uploadDateObj; + isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days + } const newBadgeHTML = isNew ? `NEW` @@ -1054,9 +1064,12 @@ 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' - }); + let formattedDate = pdf._formattedDate; + if (formattedDate === undefined) { + formattedDate = new Date(pdf.uploadDate).toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + } // Uses global escapeHtml() now @@ -1718,6 +1731,30 @@ function triggerPrank(overlay, textEl, barEl, closeBtn) { }); } +/* ========================================= + PERFORMANCE HELPERS + ========================================= */ + +function prepareSearchIndex(data) { + const now = Date.now(); + const formatter = new Intl.DateTimeFormat('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + + data.forEach(item => { + item._searchStr = [ + item.title, + item.description, + item.category, + item.author + ].join(' ').toLowerCase(); + + const uploadTimestamp = new Date(item.uploadDate).getTime(); + item._isNew = !isNaN(uploadTimestamp) ? (now - uploadTimestamp) < (7 * 24 * 60 * 60 * 1000) : false; + item._formattedDate = !isNaN(uploadTimestamp) ? formatter.format(uploadTimestamp) : 'Unknown Date'; + }); +} + /* ========================================= NEW YEAR COUNTDOWN ========================================= */