Add expand all/collapse all to the course tree
An "Expand all" link above the tree (toggling to "Collapse all" once everything's open) opens or closes every section at once - on both the lesson page's sidebar and the dashboard's loaded-course view. Toggles stay right-aligned per section, matching convention rather than moving to the left. Label reflects current state on load and persists through the same remembered-sections mechanism as manual expand/collapse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 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:41 UTC — Move sidebar left, add collapse toggle and remembered sections
|
||||||
2026-08-26 13:17 UTC — Add hover tooltips for truncated sidebar titles
|
2026-08-26 13:17 UTC — Add hover tooltips for truncated sidebar titles
|
||||||
|
|||||||
@@ -238,7 +238,9 @@ silently stay blank instead of erroring.
|
|||||||
scrollable on desktop, stacks below the player on narrow viewports.
|
scrollable on desktop, stacks below the player on narrow viewports.
|
||||||
Collapsible to a slim rail (remembered per device) for a full-width
|
Collapsible to a slim rail (remembered per device) for a full-width
|
||||||
player, and which sections you've manually expanded is remembered too,
|
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/
|
Shares its rendering with the loaded-course dashboard view (`templates/
|
||||||
_course_tree.html`), so the two always look and behave the same.
|
_course_tree.html`), so the two always look and behave the same.
|
||||||
- Auto-play next lesson when one ends, with a cancelable few-second
|
- Auto-play next lesson when one ends, with a cancelable few-second
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -481,9 +481,29 @@
|
|||||||
|
|
||||||
/* Dynamic Directory Tree Styles */
|
/* Dynamic Directory Tree Styles */
|
||||||
.tree-container {
|
.tree-container {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-container-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
margin-top: 20px;
|
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 {
|
.tree-item {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
@@ -1082,7 +1102,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tree-container">
|
<div class="tree-container-header">
|
||||||
|
<button type="button" class="expand-all-btn" id="dashboard-expand-all-btn" onclick="toggleAllTreeSections('#dashboard-tree-container', this)">Expand all</button>
|
||||||
|
</div>
|
||||||
|
<div class="tree-container" id="dashboard-tree-container">
|
||||||
{{ tree.render_tree_node(course.root_node, course.path) }}
|
{{ tree.render_tree_node(course.root_node, course.path) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1396,6 +1419,35 @@
|
|||||||
}
|
}
|
||||||
restoreExpandedSections();
|
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
|
// Single-level drill-down: the Library browser shows one folder's
|
||||||
// contents at a time (replacing the list, not nesting under it) and
|
// contents at a time (replacing the list, not nesting under it) and
|
||||||
// tracks how we got here in libraryTrail so a breadcrumb can jump
|
// tracks how we got here in libraryTrail so a breadcrumb can jump
|
||||||
|
|||||||
@@ -201,6 +201,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.lesson-sidebar.collapsed .lesson-sidebar-header h3,
|
.lesson-sidebar.collapsed .lesson-sidebar-header h3,
|
||||||
|
.lesson-sidebar.collapsed .tree-container-header,
|
||||||
.lesson-sidebar.collapsed .tree-container {
|
.lesson-sidebar.collapsed .tree-container {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -224,9 +225,26 @@
|
|||||||
and similarly-named rules. Mirrors course_dashboard.html's tree
|
and similarly-named rules. Mirrors course_dashboard.html's tree
|
||||||
styling (shared macro, see _course_tree.html) at a denser size
|
styling (shared macro, see _course_tree.html) at a denser size
|
||||||
for the narrower column. */
|
for the narrower column. */
|
||||||
.lesson-sidebar .tree-container {
|
.lesson-sidebar .tree-container-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
margin-top: 10px;
|
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 {
|
.lesson-sidebar .tree-item {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
@@ -846,7 +864,10 @@
|
|||||||
<h3>Course Outline</h3>
|
<h3>Course Outline</h3>
|
||||||
<button type="button" class="sidebar-toggle-btn" id="sidebar-toggle-btn" onclick="toggleSidebar()" title="Collapse sidebar">◀</button>
|
<button type="button" class="sidebar-toggle-btn" id="sidebar-toggle-btn" onclick="toggleSidebar()" title="Collapse sidebar">◀</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tree-container">
|
<div class="tree-container-header">
|
||||||
|
<button type="button" class="expand-all-btn" id="sidebar-expand-all-btn" onclick="toggleAllTreeSections('#sidebar-tree-container', this)">Expand all</button>
|
||||||
|
</div>
|
||||||
|
<div class="tree-container" id="sidebar-tree-container">
|
||||||
{{ tree.render_tree_node(course.root_node, course.path, lesson) }}
|
{{ tree.render_tree_node(course.root_node, course.path, lesson) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1302,6 +1323,35 @@
|
|||||||
current.scrollIntoView({ block: 'center' });
|
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) ----
|
// ---- Sidebar collapse (client-only, remembered per device) ----
|
||||||
function toggleSidebar() {
|
function toggleSidebar() {
|
||||||
const sidebar = document.getElementById('lesson-sidebar');
|
const sidebar = document.getElementById('lesson-sidebar');
|
||||||
|
|||||||
Reference in New Issue
Block a user