Move sidebar left, add collapse toggle and remembered sections

- Course outline sidebar now sits on the left of the player instead
  of the right (mobile stacking order unchanged - video still first).
- Collapsible to a slim rail via a toggle button, remembered per
  device, for a full-width player when wanted.
- Which sections a user has manually expanded is now remembered
  (keyed by the section's path) and shared between the lesson-page
  sidebar and the dashboard's course view, so re-opening a course
  doesn't reset everything back to collapsed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 09:41:52 -04:00
co-authored by Claude Sonnet 5
parent 0ed531503e
commit becbb0b419
6 changed files with 156 additions and 34 deletions
+36 -16
View File
@@ -1360,29 +1360,49 @@
}
function toggleTree(element) {
console.log('Toggle clicked:', element);
// Find the content div that comes after this header
const content = element.nextElementSibling;
const toggle = element.querySelector('.tree-toggle');
console.log('Content element:', content);
console.log('Toggle element:', toggle);
if (content && content.classList.contains('tree-content')) {
const isExpanded = content.classList.contains('expanded');
content.classList.toggle('expanded');
if (toggle) {
toggle.textContent = content.classList.contains('expanded') ? '▼' : '▶';
}
console.log('Toggled tree, expanded:', content.classList.contains('expanded'));
} else {
console.log('No content element found or not tree-content');
const isExpanded = content.classList.toggle('expanded');
if (toggle) toggle.textContent = isExpanded ? '▼' : '▶';
if (content.dataset.sectionPath) saveSectionExpanded(content.dataset.sectionPath, isExpanded);
}
}
// Remembers which course sections a user has manually expanded
// (keyed by the section's absolute path, so it's stable across
// page loads and shared between this tree and the lesson page's
// sidebar - see _course_tree.html) so re-opening a course doesn't
// reset every section back to collapsed.
function getExpandedSectionPaths() {
try {
return new Set(JSON.parse(localStorage.getItem('offlineu-expanded-sections') || '[]'));
} catch (e) {
return new Set();
}
}
function saveSectionExpanded(path, expanded) {
const paths = getExpandedSectionPaths();
if (expanded) paths.add(path); else paths.delete(path);
localStorage.setItem('offlineu-expanded-sections', JSON.stringify([...paths]));
}
function restoreExpandedSections() {
const saved = getExpandedSectionPaths();
if (!saved.size) return;
document.querySelectorAll('.tree-content[data-section-path]').forEach(function(content) {
if (!saved.has(content.dataset.sectionPath)) return;
content.classList.add('expanded');
const header = content.previousElementSibling;
const toggle = header ? header.querySelector('.tree-toggle') : null;
if (toggle) toggle.textContent = '▼';
});
}
restoreExpandedSections();
// Single-level drill-down: the Library browser shows one folder's
// contents at a time (replacing the list, not nesting under it) and
// tracks how we got here in libraryTrail so a breadcrumb can jump