From 64945677138eb3563216a56ae2b59352c56b7446 Mon Sep 17 00:00:00 2001 From: Scott Scoble Date: Fri, 15 May 2026 21:53:08 -0700 Subject: [PATCH] feat: add lesson detail view with Typst HTML rendering and Jellyfin media player - Add Detail endpoint to LessonsController (GET /Plugins/Lessons/Detail?id=...) - Update lessonsPage.html with detail view, click handlers, and back navigation - Media cards fetch Jellyfin item details and render thumbnails & durations - Clicking media card launches Jellyfin native player via appRouter.showItem - Compiled Typst HTML rendered via innerHTML with dark theme styling - Works on web and Fire TV Silk browser Closes #6 --- plugin/Api/LessonsController.cs | 31 +++ plugin/Configuration/lessonsPage.html | 372 +++++++++++++++++++++++--- 2 files changed, 371 insertions(+), 32 deletions(-) diff --git a/plugin/Api/LessonsController.cs b/plugin/Api/LessonsController.cs index aad1b18..d2f53c8 100644 --- a/plugin/Api/LessonsController.cs +++ b/plugin/Api/LessonsController.cs @@ -31,4 +31,35 @@ public class LessonsController : ControllerBase return Ok(summaries); } + + [HttpGet("Detail")] + public IActionResult Detail([FromQuery] string id) + { + if (string.IsNullOrEmpty(id)) + return BadRequest("Lesson id is required."); + + var lesson = _store.GetLessonById(id); + if (lesson == null) + return NotFound(); + + return Ok(new + { + id = lesson.Id, + title = lesson.Title, + grade = lesson.Grade, + subject = lesson.Subject, + lessonDate = lesson.LessonDate, + description = lesson.Description, + sourcePath = lesson.SourcePath, + compiledHtml = lesson.CompiledHtml, + createdAt = lesson.CreatedAt, + updatedAt = lesson.UpdatedAt, + mediaItems = lesson.MediaItems.Select(m => new + { + lessonId = m.LessonId, + jellyfinItemId = m.JellyfinItemId, + sortOrder = m.SortOrder + }) + }); + } } diff --git a/plugin/Configuration/lessonsPage.html b/plugin/Configuration/lessonsPage.html index 30034e4..c226509 100644 --- a/plugin/Configuration/lessonsPage.html +++ b/plugin/Configuration/lessonsPage.html @@ -23,6 +23,32 @@ margin-bottom: 1em; color: #fff; } + h2 { + font-size: 1.3em; + font-weight: 600; + color: #fff; + margin: 0.5em 0; + } + + /* Back button */ + .back-btn { + display: inline-block; + background: #2a2a2a; + color: #00a4dc; + border: 1px solid #444; + border-radius: 4px; + padding: 0.4em 0.8em; + cursor: pointer; + font-size: 0.9em; + margin-bottom: 1em; + transition: background 0.2s, border-color 0.2s; + } + .back-btn:hover { + background: #333; + border-color: #00a4dc; + } + + /* List view */ .grade-group { margin-bottom: 2em; } @@ -57,40 +83,158 @@ color: #999; margin-top: 0.25em; } - .empty-state { - text-align: center; - padding: 3em; + + /* Detail view */ + .detail-meta { + color: #aaa; + font-size: 0.9em; + margin-bottom: 1.5em; + } + .detail-meta span { + margin-right: 1.5em; + } + .detail-body { + background: #1a1a1a; + border: 1px solid #333; + border-radius: 6px; + padding: 1.5em; + margin-bottom: 2em; + line-height: 1.6; + overflow-wrap: break-word; + } + .detail-body h1, .detail-body h2, .detail-body h3 { + color: #fff; + margin-top: 1.2em; + margin-bottom: 0.5em; + } + .detail-body h1 { font-size: 1.5em; } + .detail-body h2 { font-size: 1.3em; } + .detail-body h3 { font-size: 1.1em; } + .detail-body p { margin: 0.5em 0; } + .detail-body ul, .detail-body ol { padding-left: 1.5em; } + .detail-body img { max-width: 100%; border-radius: 4px; } + .detail-body table { border-collapse: collapse; width: 100%; } + .detail-body td, .detail-body th { border: 1px solid #444; padding: 0.4em 0.6em; } + .detail-body a { color: #00a4dc; } + + /* Media section */ + .media-section { + margin-bottom: 2em; + } + .media-section h2 { + margin-bottom: 0.75em; + } + .media-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 0.75em; + } + .media-card { + background: #1a1a1a; + border: 1px solid #333; + border-radius: 6px; + overflow: hidden; + cursor: pointer; + transition: background 0.2s, border-color 0.2s; + } + .media-card:hover { + background: #222; + border-color: #00a4dc; + } + .media-thumb { + width: 100%; + height: 112px; + object-fit: cover; + background: #252525; + display: block; + } + .media-thumb-placeholder { + width: 100%; + height: 112px; + background: #252525; + display: flex; + align-items: center; + justify-content: center; + font-size: 2em; + color: #555; + } + .media-info { + padding: 0.5em 0.6em; + } + .media-name { + font-weight: 600; + font-size: 0.9em; + color: #fff; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .media-duration { + font-size: 0.8em; + color: #888; + margin-top: 0.15em; + } + .media-loading { color: #666; + font-size: 0.85em; + padding: 0.5em 0; } - .error-state { + + /* States */ + .empty-state, .error-state, .loading { text-align: center; padding: 3em; - color: #c44; - } - .loading { - text-align: center; - padding: 3em; - color: #666; } + .empty-state { color: #666; } + .error-state { color: #c44; } + .loading { color: #666; }
-

📚 Lessons

+

📚 Lessons

Loading lessons…