diff --git a/offlineu_core.py b/offlineu_core.py index 4117d8e..a544e81 100644 --- a/offlineu_core.py +++ b/offlineu_core.py @@ -1446,6 +1446,23 @@ class ProgressTracker: ProgressTracker.save_progress(course, progress) + @staticmethod + def touch_lesson_accessed(course: Course, lesson_path: str): + """ + Record that a lesson was opened, without touching its saved + completed/progress_seconds/duration_seconds - update_lesson_progress + is for the client reporting real playback progress, and calling it + (with its completed=False, progress_seconds=0 defaults) just for + viewing a page would silently reset an already-watched lesson back + to 0% every time it's opened, before the client gets a chance to + report anything real. + """ + progress = ProgressTracker.load_progress(course) + entry = progress.setdefault(lesson_path, {}) + entry['last_accessed'] = datetime.now().isoformat() + progress['last_accessed_path'] = lesson_path + ProgressTracker.save_progress(course, progress) + @staticmethod def _notes_from_entry(entry: Dict[str, Any]) -> List[Dict[str, Any]]: """ @@ -2340,15 +2357,16 @@ def view_lesson(lesson_path: str): if current_index < len(all_lessons) - 1: next_lesson = all_lessons[current_index + 1][0] - # Update last accessed - ProgressTracker.update_lesson_progress(current_course, lesson_path) + # Update last accessed without touching saved progress/completed state + ProgressTracker.touch_lesson_accessed(current_course, lesson_path) # Record for the cross-course "Recently Viewed" list on the dashboard record_recent_view(current_course.name, current_course.path, lesson_path, lesson.title) - # Read notes directly from the progress file rather than the Lesson - # object - apply_progress_to_tree (which populates Lesson fields) isn't - # called on this code path, only on the dashboard's tree render. + # Read notes and progress directly from the progress file rather than + # the Lesson object - apply_progress_to_tree (which populates Lesson + # fields) isn't called on this code path, only on the dashboard's tree + # render. lesson_progress = ProgressTracker.load_progress(current_course).get(lesson_path, {}) seek_seconds = request.args.get('t', type=int) @@ -2358,6 +2376,7 @@ def view_lesson(lesson_path: str): lesson=lesson, lesson_path=lesson_path, lesson_notes=ProgressTracker._notes_from_entry(lesson_progress), + lesson_progress_seconds=lesson_progress.get('progress_seconds', 0), initial_seek_seconds=seek_seconds, outline_topic_id=lesson_progress.get('outline_topic_id', ''), outline_topic_name=lesson_progress.get('outline_topic_name', ''), diff --git a/templates/lesson_view.html b/templates/lesson_view.html index 6f8e1eb..55c1864 100644 --- a/templates/lesson_view.html +++ b/templates/lesson_view.html @@ -912,12 +912,27 @@ renderNotes(); - if (activeMedia && INITIAL_SEEK_SECONDS !== null && INITIAL_SEEK_SECONDS !== undefined) { - const doInitialSeek = function() { activeMedia.currentTime = INITIAL_SEEK_SECONDS; }; + // Seek once metadata is available (activeMedia.duration is NaN + // before that, so doing this synchronously on script load - as + // this used to - silently never applied). An explicit jump to + // a note's timestamp (?t=, see INITIAL_SEEK_SECONDS) takes + // priority over resuming general playback progress. + const savedProgressSeconds = {{ lesson_progress_seconds|default(0) }}; + + function applyInitialSeek() { + if (INITIAL_SEEK_SECONDS !== null && INITIAL_SEEK_SECONDS !== undefined) { + activeMedia.currentTime = INITIAL_SEEK_SECONDS; + } else if (savedProgressSeconds > 0 && savedProgressSeconds < activeMedia.duration - 30) { + activeMedia.currentTime = savedProgressSeconds; + showNotification(`Resumed from ${Math.floor(savedProgressSeconds / 60)}:${String(Math.floor(savedProgressSeconds % 60)).padStart(2, '0')}`); + } + } + + if (activeMedia) { if (activeMedia.readyState >= 1) { - doInitialSeek(); + applyInitialSeek(); } else { - activeMedia.addEventListener('loadedmetadata', doInitialSeek, { once: true }); + activeMedia.addEventListener('loadedmetadata', applyInitialSeek, { once: true }); } } @@ -1047,13 +1062,6 @@ saveProgress(activeMedia.currentTime, true); markAsCompleted(); }); - - // Resume from saved position - const savedProgress = {{ lesson.progress_seconds|default(0) }}; - if (savedProgress > 0 && savedProgress < activeMedia.duration - 30) { - activeMedia.currentTime = savedProgress; - showNotification(`Resumed from ${Math.floor(savedProgress / 60)}:${String(Math.floor(savedProgress % 60)).padStart(2, '0')}`); - } } function saveProgress(progressSeconds, completed = false) { diff --git a/templates/notes_hub.html b/templates/notes_hub.html index feee094..cc7e17d 100644 --- a/templates/notes_hub.html +++ b/templates/notes_hub.html @@ -117,6 +117,17 @@ text-align: center; padding: 40px 20px; } + .notes-search-input { + width: 100%; + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius); + padding: 10px 14px; + font-size: 0.95em; + font-family: var(--font-family); + margin-bottom: 20px; + } .note-card { background: var(--bg-secondary); border-radius: var(--radius); @@ -213,10 +224,14 @@ {% if not notes %}
No notes yet - jot something down on a lesson page and it'll show up here.
+ {% else %} + + {% endif %} {% for note in notes %} -
+
📝
@@ -251,6 +266,22 @@
+