b51c47dc4c
- Hide desktop .card-section on mobile, add .m-search-input with same HTMX attrs for mobile course search (fixes horizontal overflow) - Remove dead layoutCount var and .m-layouts-pill block in course-cards - Remove dead 768px breakpoints from players.css (table hidden at 880px) - Move .mobile-section-head inside else-block for empty state in both ratings-cards and course-cards (fixes section head showing on empty) - Add tabindex, role=button, aria-expanded, onkeydown to .m-card and .m-course-card; toggle aria-expanded in JS toggle functions - Fix data-history attribute to use <%= (HTML-escaped) instead of <%- - Convert var to const/let in all new/changed JS blocks
126 lines
4.3 KiB
JavaScript
126 lines
4.3 KiB
JavaScript
function toggleAccordion(accordionId) {
|
|
const content = document.getElementById(accordionId);
|
|
const icon = document.getElementById(`${accordionId}-icon`);
|
|
|
|
if (content.classList.contains('expanded')) {
|
|
content.classList.remove('expanded');
|
|
icon.classList.remove('expanded');
|
|
} else {
|
|
content.classList.add('expanded');
|
|
icon.classList.add('expanded');
|
|
}
|
|
}
|
|
|
|
function toggleCourseLayouts(courseId) {
|
|
const layoutsRow = document.getElementById(`layouts-${courseId}`);
|
|
const layoutsContainer = document.getElementById(`layouts-container-${courseId}`);
|
|
|
|
if (layoutsRow.style.display === 'table-row') {
|
|
layoutsRow.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
layoutsRow.style.display = 'table-row';
|
|
|
|
if (layoutsContainer.dataset.loaded === 'true') {
|
|
return;
|
|
}
|
|
|
|
htmx.ajax('GET', `/partials/course-layouts/${courseId}`, {target: `#layouts-container-${courseId}`, swap: 'innerHTML'});
|
|
layoutsContainer.dataset.loaded = 'true';
|
|
}
|
|
|
|
// ── Mobile course card toggle ──────────────────────
|
|
let openMobileCourseId = null;
|
|
|
|
function toggleMobileCourseLayouts(courseId) {
|
|
const card = document.getElementById('m-course-' + courseId);
|
|
if (!card) return;
|
|
|
|
const isOpen = card.classList.contains('is-open');
|
|
|
|
// Close previously open card
|
|
if (openMobileCourseId !== null && openMobileCourseId !== courseId) {
|
|
const prevCard = document.getElementById('m-course-' + openMobileCourseId);
|
|
if (prevCard) {
|
|
prevCard.classList.remove('is-open');
|
|
prevCard.setAttribute('aria-expanded', 'false');
|
|
}
|
|
openMobileCourseId = null;
|
|
}
|
|
|
|
if (isOpen) {
|
|
card.classList.remove('is-open');
|
|
card.setAttribute('aria-expanded', 'false');
|
|
openMobileCourseId = null;
|
|
return;
|
|
}
|
|
|
|
card.classList.add('is-open');
|
|
card.setAttribute('aria-expanded', 'true');
|
|
openMobileCourseId = courseId;
|
|
|
|
// Lazy-load layouts on first expand
|
|
const container = document.getElementById('m-layouts-container-' + courseId);
|
|
if (container && container.dataset.loaded !== 'true') {
|
|
htmx.ajax('GET', '/partials/course-layouts/' + courseId, { target: '#m-layouts-container-' + courseId, swap: 'innerHTML' });
|
|
container.dataset.loaded = 'true';
|
|
}
|
|
}
|
|
|
|
async function scrapeCourses() {
|
|
const btn = document.getElementById('scrape-courses-btn');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Scraping...';
|
|
|
|
try {
|
|
const response = await fetch('/api/scrape-courses', { method: 'POST' });
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
alert(data.message);
|
|
htmx.ajax('GET', '/partials/course-table', '#courses-table');
|
|
} else {
|
|
alert('Failed to scrape courses');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error scraping courses:', error);
|
|
alert('Error scraping courses');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Scrape Courses';
|
|
}
|
|
}
|
|
|
|
async function scrapeLayouts(courseId, courseName) {
|
|
const icon = document.querySelector(`#row-${courseId} .refresh-icon`);
|
|
icon.classList.add('spinning');
|
|
|
|
try {
|
|
const response = await fetch(`/api/scrape-layouts/${courseId}`, { method: 'POST' });
|
|
const data = await response.json();
|
|
|
|
if (response.status === 409) {
|
|
alert(data.message || 'Scrape already in progress for this course. Please wait.');
|
|
} else if (data.success) {
|
|
const layoutsContainer = document.getElementById(`layouts-container-${courseId}`);
|
|
layoutsContainer.dataset.loaded = 'false';
|
|
|
|
const layoutsRow = document.getElementById(`layouts-${courseId}`);
|
|
if (layoutsRow.style.display === 'table-row') {
|
|
htmx.ajax('GET', `/partials/course-layouts/${courseId}`, {target: `#layouts-container-${courseId}`, swap: 'innerHTML'});
|
|
layoutsContainer.dataset.loaded = 'true';
|
|
}
|
|
|
|
alert(data.message);
|
|
} else {
|
|
alert('Failed to scrape layouts');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error scraping layouts:', error);
|
|
alert('Error scraping layouts');
|
|
} finally {
|
|
icon.classList.remove('spinning');
|
|
}
|
|
}
|