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
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

## 2025-02-18 - [Optimizing List Filtering]
**Learning:** Pre-calculating derived search strings and dates immediately after data load and explicitly indexing it dramatically improves performance during list filtering compared to recalculating on the fly. Doing inline calculations or object cloning on large array iterations is costly.
**Action:** When filtering or looping over large datasets frequently, proactively calculate common string concatenation or boolean derivation into the object beforehand.
76 changes: 54 additions & 22 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,30 @@ function getAdData(slotName) {
/* =========================================
5. DATA LOADING WITH CACHING
========================================= */

// ⚡ Bolt: Pre-calculate derived fields to avoid recalculating in high-frequency loops (render/filter)
function prepareSearchIndex(data) {
if (!data || !Array.isArray(data)) return;
const now = new Date();
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000;
const dateFormatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
});

for (let i = 0; i < data.length; i++) {
const pdf = data[i];

// Pre-calculate lowercased search string
pdf._searchStr = ((pdf.title || '') + ' ' + (pdf.description || '') + ' ' + (pdf.category || '') + ' ' + (pdf.author || '')).toLowerCase();

// Pre-calculate date fields
const uploadDateObj = new Date(pdf.uploadDate);
if (!isNaN(uploadDateObj)) {
pdf._isNew = (now - uploadDateObj) < SEVEN_DAYS;
pdf._formattedDate = dateFormatter.format(uploadDateObj);
}
}
}
function renderSemesterTabs() {
const container = document.getElementById('semesterTabsContainer');
if (!container) return;
Expand Down Expand Up @@ -490,6 +514,7 @@ async function loadPDFDatabase() {

if (shouldUseCache) {
pdfDatabase = cachedData;
prepareSearchIndex(pdfDatabase); // ⚡ Bolt: Index cached data before rendering
// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderSemesterTabs();
Expand All @@ -513,6 +538,8 @@ async function loadPDFDatabase() {
data: pdfDatabase
}));

prepareSearchIndex(pdfDatabase); // ⚡ Bolt: Index fresh data before rendering

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

// Locate renderPDFs() in script.js and update the filter section
const filteredPdfs = pdfDatabase.filter(pdf => {
const matchesSemester = pdf.semester === currentSemester;
// ⚡ Bolt: Fast explicit early returns
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);

// Update return statement to include matchesClass
return matchesSemester && matchesClass && matchesCategory && matchesSearch;
// ⚡ Bolt: Use pre-calculated search string, guarding against unindexed/malformed items
if (!pdf._searchStr) return false;
return pdf._searchStr.includes(searchTerm);
});

updatePDFCount(filteredPdfs.length);
Expand Down Expand Up @@ -1037,9 +1060,23 @@ 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
// ⚡ Bolt: Use pre-calculated fields with fallback for legacy cold caches
let isNew = pdf._isNew;
let formattedDate = pdf._formattedDate;

if (isNew === undefined || !formattedDate) {
const uploadDateObj = new Date(pdf.uploadDate);
if (!isNaN(uploadDateObj)) {
const timeDiff = new Date() - uploadDateObj;
isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days
formattedDate = uploadDateObj.toLocaleDateString('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
});
} else {
isNew = false;
formattedDate = 'Unknown Date';
}
}

const newBadgeHTML = isNew
? `<span style="background:var(--error-color); color:white; font-size:0.6rem; padding:2px 6px; border-radius:4px; margin-left:8px; vertical-align:middle;">NEW</span>`
Expand All @@ -1053,11 +1090,6 @@ 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'
});

// Uses global escapeHtml() now

const highlightText = (text) => {
Expand Down