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
71 changes: 47 additions & 24 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,37 @@ async function syncClassSwitcher() {
renderSemesterTabs();
}

function prepareSearchIndex(data) {
const now = new Date();
data.forEach(pdf => {
// Pre-calculate lowercased search string for faster filtering
const title = (pdf.title || '').toLowerCase();
const desc = (pdf.description || '').toLowerCase();
const cat = (pdf.category || '').toLowerCase();
const author = (pdf.author || '').toLowerCase();
pdf._searchStr = `${title} ${desc} ${cat} ${author}`;

// Handle Firestore Timestamp or ISO string
let uploadDateObj;
if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') {
uploadDateObj = pdf.uploadDate.toDate();
// We don't overwrite uploadDate here because it's already stringified when saving to cache.
// But if it's fresh from DB, we might want to ensure it's a string, or just let new Date() handle it.
} else {
uploadDateObj = new Date(pdf.uploadDate);
}

// Pre-calculate new badge status
const timeDiff = now - uploadDateObj;
pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days

// Pre-calculate formatted date
pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
});
});
}

async function loadPDFDatabase() {
if (isMaintenanceActive) return;

Expand Down Expand Up @@ -454,6 +485,7 @@ async function loadPDFDatabase() {

if (shouldUseCache) {
pdfDatabase = cachedData;
prepareSearchIndex(pdfDatabase);
// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderSemesterTabs();
Expand All @@ -477,6 +509,8 @@ async function loadPDFDatabase() {
data: pdfDatabase
}));

prepareSearchIndex(pdfDatabase);

// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderPDFs();
Expand Down Expand Up @@ -905,26 +939,21 @@ 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 exact matches
if (pdf.class !== currentClass) return false;
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;

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);
// Fast text search using pre-calculated search string
// Note: the `searchTerm` variable is lowercased at the top of renderPDFs
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);
Expand Down Expand Up @@ -994,11 +1023,7 @@ 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

const newBadgeHTML = isNew
const newBadgeHTML = pdf._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 @@ -1010,10 +1035,8 @@ 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'
});
// Use pre-calculated formatted date
const formattedDate = pdf._formattedDate;

// Uses global escapeHtml() now

Expand Down
193 changes: 193 additions & 0 deletions verify_perf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import asyncio
from playwright.async_api import async_playwright
import time
import json

async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()

# Mock Firebase to prevent network requests and overwrite local cache
await page.route("**/*firebase*", lambda route: route.abort())
await page.route("**/*googleapis*", lambda route: route.abort())
await page.route("**/*gstatic*", lambda route: route.abort())

# Seed local storage with mock data
mock_data = {
"timestamp": int(time.time() * 1000),
"data": [
{
"id": "1",
"title": "Organic Chemistry Notes",
"description": "Notes for organic chemistry.",
"category": "Organic",
"author": "Alice",
"class": "MSc Chemistry",
"semester": 1,
"uploadDate": "2023-10-01T00:00:00.000Z"
},
{
"id": "2",
"title": "Inorganic Chemistry Notes",
"description": "Notes for inorganic chemistry.",
"category": "Inorganic",
"author": "Bob",
"class": "MSc Chemistry",
"semester": 1,
"uploadDate": "2023-10-02T00:00:00.000Z"
},
{
"id": "3",
"title": "Physics Notes",
"description": "Basic physics.",
"category": "Physics",
"author": "Charlie",
"class": "BSc Physics",
"semester": 2,
"uploadDate": "2023-10-03T00:00:00.000Z"
}
]
}

# Navigate to a blank page on the same origin first to set localStorage
await page.goto("http://localhost:8000/", wait_until="domcontentloaded")

await page.evaluate(f"localStorage.setItem('classnotes_db_cache', '{json.dumps(mock_data)}');")
await page.evaluate("localStorage.setItem('currentClass', 'MSc Chemistry');")
await page.evaluate("localStorage.setItem('currentSemester', '1');")

# Mock snapshot empty check (simulate cache use)
# Because we abort firebase requests, window.firebase is undefined, and the app waits.
# So we evaluate window.firebase mock so that app can proceed.
await page.evaluate("""
window.firebase = {
apps: [{name: 'mock'}],
initializeApp: function() {},
auth: function() { return { onAuthStateChanged: function(cb) { cb({uid: '123'}); }, signInAnonymously: function() { return Promise.resolve({user: {uid: '123'}}); } } },
firestore: function() {
return {
collection: function(c) {
return {
doc: function(d) {
return {
set: function() {},
onSnapshot: function(cb, errCb) { errCb('mock error'); }
};
},
orderBy: function() {
return {
limit: function() {
return {
get: function() {
return Promise.resolve({ empty: false, docs: [{id: '1'}] });
}
}
},
get: function() {
return Promise.resolve({
forEach: function(cb) {}
});
}
}
}
};
}
};
}
};
window.firebase.firestore.FieldValue = {
serverTimestamp: function() { return new Date(); },
increment: function(v) { return v; }
};
""")

# Reload to let app load from cache
await page.reload(wait_until="domcontentloaded")

# Inject the mock again immediately
await page.evaluate("""
window.firebase = {
apps: [{name: 'mock'}],
initializeApp: function() {},
auth: function() { return { onAuthStateChanged: function(cb) { cb({uid: '123'}); }, signInAnonymously: function() { return Promise.resolve({user: {uid: '123'}}); } } },
firestore: function() {
return {
collection: function(c) {
return {
doc: function(d) {
return {
set: function() {},
onSnapshot: function(cb, errCb) { errCb('mock error'); }
};
},
orderBy: function() {
return {
limit: function() {
return {
get: function() {
return Promise.resolve({ empty: false, docs: [{id: '1'}] });
}
}
},
get: function() {
return Promise.resolve({
forEach: function(cb) {}
});
}
}
}
};
}
};
}
};
window.firebase.firestore.FieldValue = {
serverTimestamp: function() { return new Date(); },
increment: function(v) { return v; }
};
""")

# Wait for the app to initialize
await page.wait_for_timeout(2000)

# Hide full screen overlays
await page.evaluate("document.getElementById('preloader')?.classList.add('hidden');")
await page.evaluate("document.getElementById('holidayOverlay')?.classList.add('hidden');")
await page.evaluate("document.getElementById('contentWrapper')?.classList.add('active');")

# Wait a bit for DOM updates
await page.wait_for_timeout(500)

# Let's inspect what is in pdfDatabase
pdf_count_db = await page.evaluate("pdfDatabase.length")
print(f"pdfDatabase length: {pdf_count_db}")

# Verify initial rendering (MSc Chemistry, Semester 1)
pdf_count = await page.evaluate("document.querySelectorAll('.pdf-card').length")
print(f"Initial PDF count: {pdf_count} (expected 2)")

# Verify prepareSearchIndex populated _searchStr
if pdf_count_db > 0:
search_str_exists = await page.evaluate("pdfDatabase[0]._searchStr !== undefined")
print(f"_searchStr calculated: {search_str_exists} (expected True)")

# Test Search (should match Organic Chemistry)
await page.fill("#searchInput", "organic")
# trigger input event
await page.evaluate("document.getElementById('searchInput').dispatchEvent(new Event('input'))")
await page.wait_for_timeout(500)
pdf_count_search = await page.evaluate("document.querySelectorAll('.pdf-card').length")
print(f"PDF count after search 'organic': {pdf_count_search} (expected 1)")

# Test filter mismatch
await page.fill("#searchInput", "notfoundxyz")
await page.evaluate("document.getElementById('searchInput').dispatchEvent(new Event('input'))")
await page.wait_for_timeout(500)
pdf_count_search = await page.evaluate("document.querySelectorAll('.pdf-card').length")
print(f"PDF count after search 'notfoundxyz': {pdf_count_search} (expected 0)")

await browser.close()

if __name__ == "__main__":
asyncio.run(main())