Files
offlineu/templates/_course_tree.html
T
rmsitzandClaude Sonnet 5 bf0a02d008 Redesign expandable lists to a leaner, flat style
Course tree (lesson-page sidebar + dashboard's loaded-course view) and
File Management's Manage Library browser both move from boxed,
bordered rows to a flat list: no per-row background/border, a small
leading chevron that doubles as the expand affordance instead of a
separate right-edge button, indentation for hierarchy. The chevron's
rotation is pure CSS (:has(+ .tree-content.expanded) / :has(+
.curate-children.expanded)) rather than JS swapping glyph text, which
also let a fair amount of now-redundant JS come out.

The course tree's new styles are scoped under .tree-container (course
dashboard) and .lesson-sidebar (lesson page) so they don't leak into
the Library folder browser and other rows that reuse the same base
.tree-header/.lesson-item/etc. class names elsewhere in
course_dashboard.html - confirmed those are visually untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 10:45:38 -04:00

71 lines
4.0 KiB
HTML

{% import '_icons.html' as icons %}
{#
Shared recursive course-outline renderer - one section/lesson tree
markup used by both the loaded-course dashboard view and the lesson
page's sidebar, so the two stay visually and behaviorally in sync
instead of drifting apart as separate copies. Needs `toggleTree()`
(JS) and the .tree-*/.lesson-*/.status-icon/.watched-badge CSS to be
defined by whichever page imports this - see course_dashboard.html
for the canonical versions.
Flat list styling (no per-row box/border), a leading chevron that
doubles as the expand affordance instead of a separate right-edge
button, and indentation for hierarchy - the chevron's rotation is
driven purely by CSS (:has(+ .tree-content.expanded)), so toggling
is just adding/removing one class on .tree-content; nothing in JS
ever touches the icon directly.
`current_lesson` (a Lesson object, not a path string) is optional -
when given, the matching row gets a `current` class instead of being
a plain click target, and stays unclickable since you're already
there. Path-string comparison isn't used for this because the lesson
page's incoming URL can be in several different formats (see
find_lesson_in_tree) - comparing lesson.path (the raw absolute
filesystem path, always unique and unambiguous) sidesteps all of that.
#}
{% macro render_tree_node(node, course_path, current_lesson=none, depth=0) %}
{% set stats = section_stats(node) %}
<div class="tree-item">
<div class="tree-header directory" onclick="toggleTree(this)">
<span class="tree-toggle-icon"></span>
<span class="tree-name" title="{{ node.name }}">{{ node.name }}</span>
<span class="tree-stats">
{% if stats.total_lessons %}{{ stats.completed_lessons }}/{{ stats.total_lessons }}{% else %}Empty{% endif %}
</span>
</div>
{% if node.children or node.lessons %}
<div class="tree-content" data-section-path="{{ node.path }}">
{% for child_name, child_node in node.children.items() %}
{{ render_tree_node(child_node, course_path, current_lesson, depth + 1) }}
{% endfor %}
{% for lesson in node.lessons %}
{% set lesson_relative_path = lesson.path|replace('\\', '/')|replace(course_path|replace('\\', '/'), '')|replace('//', '/')|replace('/', '', 1) %}
{% set percent_watched = 100 if lesson.completed else (((100 * lesson.progress_seconds / lesson.duration_seconds)|round|int) if (lesson.duration_seconds and lesson.progress_seconds) else 0) %}
{% set is_current = current_lesson and lesson.path == current_lesson.path %}
<div class="lesson-item {% if is_current %}current{% elif lesson.completed %}completed{% elif percent_watched %}in-progress{% endif %}"
{% if not is_current %}onclick="window.location.href='/lesson/{{ lesson_relative_path }}/{{ lesson.title|replace(' ', '_') }}'"{% endif %}>
<span class="lesson-icon">
{% if lesson.lesson_type == 'video' %}{{ icons.icon('video', 15) }}
{% elif lesson.lesson_type == 'audio' %}{{ icons.icon('music', 15) }}
{% elif lesson.lesson_type == 'quiz' %}{{ icons.icon('clipboard', 15) }}
{% elif lesson.lesson_type == 'mixed' %}{{ icons.icon('package', 15) }}
{% else %}{{ icons.icon('file-text', 15) }}{% endif %}
</span>
<span class="lesson-name" title="{{ lesson.title }}">{{ lesson.title }}</span>
{% if lesson.duration_seconds and lesson.duration_seconds >= 60 %}
<span class="lesson-duration">{{ lesson.duration_seconds|format_duration }}</span>
{% endif %}
{% if lesson.completed %}
<span class="status-icon completed">{{ icons.icon('check', 13) }}</span>
{% elif percent_watched %}
<span class="watched-badge">{{ percent_watched }}%</span>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}