diff --git a/CHANGELOG.md b/CHANGELOG.md index a5cb3e6..7af0aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +2026-08-26 14:27 UTC — Add expand all/collapse all to the course tree 2026-08-26 14:13 UTC — Header/footer polish, fix dead link, add focus rings 2026-08-26 13:41 UTC — Move sidebar left, add collapse toggle and remembered sections 2026-08-26 13:17 UTC — Add hover tooltips for truncated sidebar titles diff --git a/README.md b/README.md index e36bfd8..8f32150 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,9 @@ silently stay blank instead of erroring. scrollable on desktop, stacks below the player on narrow viewports. Collapsible to a slim rail (remembered per device) for a full-width player, and which sections you've manually expanded is remembered too, - so re-opening a course doesn't reset everything back to collapsed. + so re-opening a course doesn't reset everything back to collapsed. An + "Expand all" link (toggling to "Collapse all") opens or closes every + section at once - same control on the dashboard's course tree. Shares its rendering with the loaded-course dashboard view (`templates/ _course_tree.html`), so the two always look and behave the same. - Auto-play next lesson when one ends, with a cancelable few-second diff --git a/VERSION b/VERSION index 7550b9c..b84a828 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2026-08-26 14:13 UTC — header/footer polish, fix dead link, add focus rings +2026-08-26 14:27 UTC — add expand all/collapse all to the course tree diff --git a/templates/course_dashboard.html b/templates/course_dashboard.html index cd6b38c..47f9ce4 100644 --- a/templates/course_dashboard.html +++ b/templates/course_dashboard.html @@ -481,9 +481,29 @@ /* Dynamic Directory Tree Styles */ .tree-container { + margin-top: 8px; + } + + .tree-container-header { + display: flex; + justify-content: flex-end; margin-top: 20px; } + .expand-all-btn { + background: none; + border: none; + color: var(--accent); + cursor: pointer; + font-size: 0.85em; + text-decoration: underline; + padding: 4px 0; + } + + .expand-all-btn:hover { + color: var(--accent-hover); + } + .tree-item { margin-bottom: 5px; } @@ -1082,7 +1102,10 @@ {% endif %} -
+
+ +
+
{{ tree.render_tree_node(course.root_node, course.path) }}
@@ -1396,6 +1419,35 @@ } restoreExpandedSections(); + // Expand/collapse every section within a tree at once, toggling + // between the two based on whether everything's already open - + // avoids a one-way "Expand all" with no quick way back. + function toggleAllTreeSections(containerSelector, btnEl) { + const container = document.querySelector(containerSelector); + if (!container) return; + const contents = [...container.querySelectorAll('.tree-content')]; + if (!contents.length) return; + const shouldExpand = !contents.every(c => c.classList.contains('expanded')); + contents.forEach(function(content) { + content.classList.toggle('expanded', shouldExpand); + const header = content.previousElementSibling; + const toggle = header ? header.querySelector('.tree-toggle') : null; + if (toggle) toggle.textContent = shouldExpand ? '▼' : '▶'; + if (content.dataset.sectionPath) saveSectionExpanded(content.dataset.sectionPath, shouldExpand); + }); + if (btnEl) btnEl.textContent = shouldExpand ? 'Collapse all' : 'Expand all'; + } + + (function initExpandAllLabel() { + const btn = document.getElementById('dashboard-expand-all-btn'); + const container = document.getElementById('dashboard-tree-container'); + if (!btn || !container) return; + const contents = [...container.querySelectorAll('.tree-content')]; + if (contents.length && contents.every(c => c.classList.contains('expanded'))) { + btn.textContent = 'Collapse all'; + } + })(); + // 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 diff --git a/templates/lesson_view.html b/templates/lesson_view.html index f9ef69e..cb3c508 100644 --- a/templates/lesson_view.html +++ b/templates/lesson_view.html @@ -201,6 +201,7 @@ overflow: hidden; } .lesson-sidebar.collapsed .lesson-sidebar-header h3, + .lesson-sidebar.collapsed .tree-container-header, .lesson-sidebar.collapsed .tree-container { display: none; } @@ -224,9 +225,26 @@ and similarly-named rules. Mirrors course_dashboard.html's tree styling (shared macro, see _course_tree.html) at a denser size for the narrower column. */ - .lesson-sidebar .tree-container { + .lesson-sidebar .tree-container-header { + display: flex; + justify-content: flex-end; margin-top: 10px; } + .lesson-sidebar .expand-all-btn { + background: none; + border: none; + color: var(--accent); + cursor: pointer; + font-size: 0.8em; + text-decoration: underline; + padding: 2px 0; + } + .lesson-sidebar .expand-all-btn:hover { + color: var(--accent-hover); + } + .lesson-sidebar .tree-container { + margin-top: 6px; + } .lesson-sidebar .tree-item { margin-bottom: 5px; } @@ -846,7 +864,10 @@

Course Outline

-
+
+ +
+
@@ -1302,6 +1323,35 @@ current.scrollIntoView({ block: 'center' }); })(); + // Expand/collapse every section within a tree at once, toggling + // between the two based on whether everything's already open - + // avoids a one-way "Expand all" with no quick way back. + function toggleAllTreeSections(containerSelector, btnEl) { + const container = document.querySelector(containerSelector); + if (!container) return; + const contents = [...container.querySelectorAll('.tree-content')]; + if (!contents.length) return; + const shouldExpand = !contents.every(c => c.classList.contains('expanded')); + contents.forEach(function(content) { + content.classList.toggle('expanded', shouldExpand); + const header = content.previousElementSibling; + const toggle = header ? header.querySelector('.tree-toggle') : null; + if (toggle) toggle.textContent = shouldExpand ? '▼' : '▶'; + if (content.dataset.sectionPath) saveSectionExpanded(content.dataset.sectionPath, shouldExpand); + }); + if (btnEl) btnEl.textContent = shouldExpand ? 'Collapse all' : 'Expand all'; + } + + (function initExpandAllLabel() { + const btn = document.getElementById('sidebar-expand-all-btn'); + const container = document.getElementById('sidebar-tree-container'); + if (!btn || !container) return; + const contents = [...container.querySelectorAll('.tree-content')]; + if (contents.length && contents.every(c => c.classList.contains('expanded'))) { + btn.textContent = 'Collapse all'; + } + })(); + // ---- Sidebar collapse (client-only, remembered per device) ---- function toggleSidebar() { const sidebar = document.getElementById('lesson-sidebar');