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-20 - Pre-calculate derived search strings and use early returns
**Learning:** In a pure vanilla JS client-side filtering loop, grouping multiple boolean conditions (like `matchesSemester && matchesClass && matchesCategory && matchesSearch`) forces all conditions to be evaluated for every item, even if the first condition fails. Furthermore, doing string manipulations (like `.toLowerCase()`) on multiple properties inside the `filter` callback on every keystroke causes significant CPU overhead for large lists.
**Action:** Always use explicit early returns (`if (!condition) return false;`) to short-circuit the loop as soon as possible. Move expensive string processing out of the render loop by calculating a derived `_searchStr` property once during the initial data load, reducing the per-keystroke filter to a simple, pre-computed substring check.
66 changes: 51 additions & 15 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,40 @@ function getAdData(slotName) {
/* =========================================
5. DATA LOADING WITH CACHING
========================================= */

// Optimize search by pre-calculating search strings and derived properties once during load
function prepareSearchIndex(dataList) {
const now = new Date();
dataList.forEach(item => {
// 1. Lowercase search string concatenation for fast O(1) matching later
const title = item.title || '';
const desc = item.description || '';
const cat = item.category || '';
const author = item.author || '';
item._searchStr = `${title} ${desc} ${cat} ${author}`.toLowerCase();

// 2. Pre-calculate formatted date
if (item.uploadDate) {
let uploadDateObj;
if (item.uploadDate && typeof item.uploadDate.toDate === 'function') {
uploadDateObj = item.uploadDate.toDate();
} else {
uploadDateObj = new Date(item.uploadDate);
}
item._formattedDate = uploadDateObj.toLocaleDateString('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
});

// 3. Pre-calculate "isNew" flag (7 days)
const timeDiff = now - uploadDateObj;
item._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000);
} else {
item._formattedDate = '';
item._isNew = false;
}
});
}

function renderSemesterTabs() {
const container = document.getElementById('semesterTabsContainer');
if (!container) return;
Expand Down Expand Up @@ -454,6 +488,8 @@ async function loadPDFDatabase() {

if (shouldUseCache) {
pdfDatabase = cachedData;
// ⚡ Bolt Optimization: Prepare search index
prepareSearchIndex(pdfDatabase);
// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderSemesterTabs();
Expand All @@ -477,6 +513,10 @@ async function loadPDFDatabase() {
data: pdfDatabase
}));

// ⚡ Bolt Optimization: Prepare search index AFTER saving to cache
// to avoid bloating localStorage with derived properties.
prepareSearchIndex(pdfDatabase);

// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderPDFs();
Expand Down Expand Up @@ -905,26 +945,22 @@ function renderPDFs() {

// Locate renderPDFs() in script.js and update the filter section
const filteredPdfs = pdfDatabase.filter(pdf => {
const matchesSemester = pdf.semester === currentSemester;

// 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;
// ⚡ Bolt Optimization: Early returns and pre-calculated search string
if (pdf.class !== currentClass) return false;
if (pdf.semester !== currentSemester) 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);
if (searchTerm) {
// Use pre-calculated lowercase search string
if (pdf._searchStr && !pdf._searchStr.includes(searchTerm)) return false;
}

// Update return statement to include matchesClass
return matchesSemester && matchesClass && matchesCategory && matchesSearch;
return true;
});

updatePDFCount(filteredPdfs.length);
Expand Down