Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-18 - [Optimize List Filtering with Early Returns and Caching]
**Learning:** During heavy interactions like keystrokes on large lists, doing computationally heavy string lowercasing multiple times per item in `filter()` acts as a huge UI blocker. Pre-calculating a `_searchStr` ahead of time during the data fetch prevents recalculating the same string over and over. Also, standard multiple condition boolean variables (`const a = X; const b = Y; return a && b`) don't actually early-return for fast paths natively when structured sequentially and grouped at the end. Explicit `if (!condition) return false;` clauses act much faster to eliminate 90% of search space before any expensive operations like regex matching or `includes()` happen.
**Action:** For lists and grids, always calculate derived search properties on initialization (like data load) instead of per-render loop, and prefer sequential fast-failing `if-return` blocks for complex filtering logic.
47 changes: 33 additions & 14 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,20 @@ async function loadPDFDatabase() {

pdfDatabase = [];
snapshot.forEach(doc => {
pdfDatabase.push({ id: doc.id, ...doc.data() });
const data = doc.data();
// Pre-calculate search string for performance
const _searchStr = [
data.title,
data.description,
data.category,
data.author
].filter(Boolean).join(' ').toLowerCase();

pdfDatabase.push({
id: doc.id,
_searchStr,
...data
});
});

localStorage.setItem(CACHE_KEY, JSON.stringify({
Expand Down Expand Up @@ -902,26 +915,32 @@ function renderPDFs() {

// Locate renderPDFs() in script.js and update the filter section
const filteredPdfs = pdfDatabase.filter(pdf => {
const matchesSemester = pdf.semester === currentSemester;
// Fast early returns for cheap equality checks
if (pdf.semester !== currentSemester) return false;

// NEW: Check if the PDF class matches the UI's current class selection
// 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);
} else {
matchesCategory = currentCategory === 'all' || pdf.category === currentCategory;
if (!favorites.includes(pdf.id)) return false;
} else if (currentCategory !== 'all') {
if (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);
// Use pre-calculated search string or fallback
if (searchTerm) {
const searchTarget = pdf._searchStr || [
pdf.title,
pdf.description,
pdf.category,
pdf.author
].filter(Boolean).join(' ').toLowerCase();

// Update return statement to include matchesClass
return matchesSemester && matchesClass && matchesCategory && matchesSearch;
if (!searchTarget.includes(searchTerm)) return false;
}

return true;
});

updatePDFCount(filteredPdfs.length);
Expand Down