diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..1f04648 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-23 - Pre-indexing Derived Properties for Faster Filtering +**Learning:** In vanilla JS applications with large lists, filtering performance can degrade significantly when searching involves concatenating strings or checking dates inline inside the filter callback (`pdfDatabase.filter()`). For example, doing `.toLowerCase().includes()` on multiple fields per item. +**Action:** Pre-calculate a `_searchStr` (and other derived properties like `_isNew` and `_formattedDate`) during the initial data load (`loadPDFDatabase`) exactly once. Use this pre-calculated string inside the filter/render loop for O(1) property access and faster substring searching. Add early returns to skip further checks if a condition fails. diff --git a/script.js b/script.js index ef463ba..55c1691 100644 --- a/script.js +++ b/script.js @@ -452,6 +452,23 @@ async function syncClassSwitcher() { renderSemesterTabs(); } +function prepareSearchIndex(pdfs) { + const dateFormatter = new Intl.DateTimeFormat('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + const now = new Date(); + const sevenDaysMs = 7 * 24 * 60 * 60 * 1000; + + pdfs.forEach(pdf => { + pdf._searchStr = `${pdf.title} ${pdf.description || ''} ${pdf.category || ''} ${pdf.author || ''}`.toLowerCase(); + + const uploadDateObj = new Date(pdf.uploadDate); + const timeDiff = now - uploadDateObj; + pdf._isNew = !isNaN(uploadDateObj) ? (timeDiff < sevenDaysMs) : false; + pdf._formattedDate = !isNaN(uploadDateObj) ? dateFormatter.format(uploadDateObj) : 'Unknown Date'; + }); +} + async function loadPDFDatabase() { if (isMaintenanceActive) return; @@ -490,6 +507,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -513,6 +531,7 @@ async function loadPDFDatabase() { data: pdfDatabase })); + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -948,26 +967,21 @@ function renderPDFs() { // Locate renderPDFs() in script.js and update the filter section const filteredPdfs = pdfDatabase.filter(pdf => { - const matchesSemester = pdf.semester === currentSemester; + if (pdf.semester !== currentSemester) return false; // 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; - let matchesCategory = false; if (currentCategory === 'favorites') { - matchesCategory = favorites.includes(pdf.id); + if (!favorites.includes(pdf.id)) return false; } else { - matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; + 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); + 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); @@ -1037,9 +1051,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 +1071,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