From 0700c35310c63a4713eb2589b8610166500ab915 Mon Sep 17 00:00:00 2001 From: Scott Scoble Date: Fri, 15 May 2026 21:18:04 -0700 Subject: [PATCH 1/3] feat: add Typst lesson template with Jellyfin media frontmatter - Add templates/lesson.typ: reusable template with lesson-template() function - Accepts title, grade, subject, date, media, description parameters - Comment-based frontmatter convention documented at top - Renders title heading, metadata line, description, body, media section - Media references use raw(lang: "jellyfin-media") for identifiable HTML elements - Add templates/example-lesson.typ: complete example (Water Cycle lesson) - Demonstrates all template features - Christian-homeschool appropriate content (Psalm 135:7) - Includes 2 Jellyfin media references Closes #3 --- templates/example-lesson.typ | 83 +++++++++++++++++++++++++++++++++ templates/lesson.typ | 90 ++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 templates/example-lesson.typ create mode 100644 templates/lesson.typ diff --git a/templates/example-lesson.typ b/templates/example-lesson.typ new file mode 100644 index 0000000..44122e7 --- /dev/null +++ b/templates/example-lesson.typ @@ -0,0 +1,83 @@ +// example-lesson.typ — Complete example demonstrating the lesson template +// ============================================================ +// Frontmatter: +// title: God's Creation: The Water Cycle +// grade: 3 +// subject: Science +// date: 2026-01-15 +// media: jellyfin://uuid-water-cycle, jellyfin://uuid-cloud-formation +// description: Learn how God designed water to move through His creation — +// evaporation, condensation, and precipitation working together to sustain life. + +#import "lesson.typ": lesson-template, media-ref + +#show: doc => lesson-template( + doc, + title: "God's Creation: The Water Cycle", + grade: "3", + subject: "Science", + date: "2026-01-15", + media: ( + ("uuid-water-cycle", "The Water Cycle Explained"), + ("uuid-cloud-formation", "Cloud Formation and Weather"), + ), + description: "Learn how God designed water to move through His creation — evaporation, condensation, and precipitation working together to sustain life on Earth.", +) + += What Is the Water Cycle? + +Water is one of God's most amazing creations! The water cycle is how water moves +all around our planet. It never runs out — it just keeps going around and around. +There are four main steps: evaporation, condensation, precipitation, and collection. + +== Evaporation + +When the sun shines on oceans, lakes, and rivers, the water gets warm and turns +into a gas called *water vapor*. This gas rises up into the sky. You can't see it, +but it's there! Evaporation happens every day, especially on warm, sunny days. + +== Condensation + +When the water vapor goes high up where it's cold, it turns back into tiny water +droplets. These droplets come together to make clouds. Have you ever looked at +clouds and seen shapes? Those shapes are made of millions of tiny water droplets +sticking together! + +== Precipitation + +When the clouds get too heavy with water droplets, the water falls back down to +Earth. This is called *precipitation*. It can be: + +- Rain — liquid water falling from the sky +- Snow — frozen crystals that fall in cold weather +- Sleet — tiny ice pellets that bounce when they land +- Hail — hard balls of ice that form in big storms + +God sends precipitation to water the plants and fill the rivers, lakes, and oceans. + +== Collection + +The water that falls onto the ground collects in rivers, lakes, and oceans. Some +of it soaks into the ground and becomes *groundwater* — water stored underground +that plants can reach with their roots. Then the sun warms the water, and the +whole cycle starts all over again! + += Why Is the Water Cycle Important? + +God designed the water cycle to do important work: + +- Give plants the water they need to grow +- Provide fresh water for animals and people to drink +- Keep our rivers and lakes full of water +- Help control the temperature of our whole planet +- Clean the water naturally as it moves through the cycle + +*"He causes the vapors to ascend from the ends of the earth; He makes lightning +for the rain; He brings the wind out of His treasuries."* — Psalm 135:7 + += Think About It + +1. What happens to water when the sun shines on a lake? +2. Why do clouds form high up in the sky? +3. What are the four types of precipitation? +4. How does the water cycle show God's care for His creation? diff --git a/templates/lesson.typ b/templates/lesson.typ new file mode 100644 index 0000000..a85cab3 --- /dev/null +++ b/templates/lesson.typ @@ -0,0 +1,90 @@ +// lesson.typ — Typst template for homeschool lessons +// ============================================================ +// This template produces clean, structured lesson documents. +// Designed to compile to HTML for rendering inside Jellyfin. +// +// Frontmatter convention (documented as comments at top of each lesson): +// // title: The Lesson Title +// // grade: 3 +// // subject: Science +// // date: 2026-01-15 +// // media: jellyfin://uuid-1, jellyfin://uuid-2 +// // description: Short description of the lesson +// +// Usage: +// #import "lesson.typ": lesson-template, media-ref +// #show: doc => lesson-template( +// doc, +// title: "The Water Cycle", +// grade: "3", +// subject: "Science", +// date: "2026-01-15", +// media: (), +// description: "Learn about evaporation and condensation.", +// ) + +// Render a single Jellyfin media reference. +// Produces a element in HTML output +// that the Jellyfin plugin can parse and link to the player. +#let media-ref(id, title) = { + raw(lang: "jellyfin-media", id + "|" + title) +} + +// Render a list of media references as a formatted section. +#let media-section(media-items) = { + if media-items.len() > 0 { + heading(level: 2)[Media] + for item in media-items { + list.item(media-ref(item.at(0), item.at(1))) + } + } +} + +// The main lesson template. +// - body: The lesson content (passed via show rule) +// - title: Lesson title (string) +// - grade: Grade level (string, e.g. "3") +// - subject: Subject name (string) +// - date: Lesson date in YYYY-MM-DD format +// - media: Array of (id, title) pairs for linked Jellyfin media +// - description: Short description of the lesson +#let lesson-template( + body, + title: "Untitled Lesson", + grade: none, + subject: none, + date: none, + media: (), + description: none, +) = { + // Document metadata for PDF output + set document(title: title) + + // Title — prominent heading + heading(level: 1, title) + + // Metadata line: Grade X — Subject — Date + let meta-parts = () + if grade != none { meta-parts.push([Grade ] + grade) } + if subject != none { meta-parts.push(subject) } + if date != none { meta-parts.push(date) } + if meta-parts.len() > 0 { + let sep = [ — ] + let meta-text = meta-parts.first() + for i in range(1, meta-parts.len()) { + meta-text += sep + meta-parts.at(i) + } + par(text(style: "italic", size: 0.9em, meta-text)) + } + + // Description paragraph + if description != none { + par([*Description:* ] + description) + } + + // Lesson body content (renders user-authored sections, paragraphs, lists, etc.) + body + + // Media references section — listed with identifiable jellyfin-media codes + media-section(media) +} From d94b71d0a1472e89cd12590a9b2f0724ae332680 Mon Sep 17 00:00:00 2001 From: Scott Scoble Date: Fri, 15 May 2026 21:16:35 -0700 Subject: [PATCH 2/3] feat: scaffold Nix flake with dotnet-sdk_9, typst, and nodejs - devShells.default provides dotnetCorePackages.sdk_9_0, typst, nodejs - packages.default uses buildDotnetModule with net9.0 target via callPackage - Stub plugin/ project with minimal .csproj - .gitignore with .NET/Nix build artifacts --- .gitignore | 14 +++++++ deps.json | 1 + flake.lock | 27 +++++++++++++ flake.nix | 72 ++++++++++++++++++++++++++++++++++ plugin/jellyfin-lessons.csproj | 7 ++++ 5 files changed, 121 insertions(+) create mode 100644 .gitignore create mode 100644 deps.json create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 plugin/jellyfin-lessons.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9bd8ced --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# .NET build artifacts +bin/ +obj/ +*.dll + +# Nix +.result/ +result + +# IDE +.vs/ +.vscode/ +*.user +*.suo diff --git a/deps.json b/deps.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/deps.json @@ -0,0 +1 @@ +[] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..0b0400a --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1778443072, + "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f9e1849 --- /dev/null +++ b/flake.nix @@ -0,0 +1,72 @@ +{ + description = "Jellyfin Lessons — a Jellyfin plugin"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { self, nixpkgs }: + let + forAllSystems = + fn: nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed fn; + in + { + devShells = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + default = pkgs.mkShell { + packages = [ + pkgs.dotnetCorePackages.sdk_9_0 + pkgs.typst + pkgs.nodejs + ]; + + shellHook = '' + echo "jellyfin-lessons dev shell" + echo " dotnet: $(dotnet --version)" + echo " typst: $(typst --version)" + echo " node: $(node --version)" + ''; + }; + } + ); + + packages = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + default = pkgs.callPackage ( + { + buildDotnetModule, + dotnetCorePackages, + lib, + }: + buildDotnetModule { + pname = "jellyfin-lessons"; + version = "0.1.0"; + + src = self; + + projectFile = "plugin/jellyfin-lessons.csproj"; + nugetDeps = ./deps.json; + + dotnet-sdk = dotnetCorePackages.sdk_9_0; + dotnet-runtime = dotnetCorePackages.aspnetcore_9_0; + + meta = { + description = "Jellyfin Lessons plugin"; + license = lib.licenses.mit; + platforms = lib.platforms.all; + }; + } + ) { }; + } + ); + }; +} diff --git a/plugin/jellyfin-lessons.csproj b/plugin/jellyfin-lessons.csproj new file mode 100644 index 0000000..65111ca --- /dev/null +++ b/plugin/jellyfin-lessons.csproj @@ -0,0 +1,7 @@ + + + net9.0 + JellyfinLessons + jellyfin-lessons + + From 4e12bc5b41ce90c52d12b5712b7e06d81e02b8ca Mon Sep 17 00:00:00 2001 From: Scott Scoble Date: Fri, 15 May 2026 21:25:13 -0700 Subject: [PATCH 3/3] fix: populate deps.json, add Jellyfin package refs, rename csproj to Jellyfin.Plugin.Lessons.csproj - Generated correct deps.json with nuget-to-json for all NuGet dependencies - Added Jellyfin.Controller and Jellyfin.Model package references with ExcludeAssets=runtime - Renamed csproj to Jellyfin.Plugin.Lessons.csproj (updated flake.nix projectFile) --- deps.json | 153 +++++++++++++++++++++++++- flake.nix | 2 +- plugin/Jellyfin.Plugin.Lessons.csproj | 16 +++ plugin/jellyfin-lessons.csproj | 7 -- 4 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 plugin/Jellyfin.Plugin.Lessons.csproj delete mode 100644 plugin/jellyfin-lessons.csproj diff --git a/deps.json b/deps.json index fe51488..0243907 100644 --- a/deps.json +++ b/deps.json @@ -1 +1,152 @@ -[] +[ + { + "pname": "Diacritics", + "version": "3.3.29", + "hash": "sha256-sIbdJ3yMthnmJHly3WheUdYjtwPakcczTJx9ycxtgrY=" + }, + { + "pname": "ICU4N", + "version": "60.1.0-alpha.356", + "hash": "sha256-1QyOgO7pNMeoEgBtl6o8IG4o91wD2hFUgRI0jM0ltxY=" + }, + { + "pname": "ICU4N.Transliterator", + "version": "60.1.0-alpha.356", + "hash": "sha256-RLNwQNVqNz8Omfb/mC/rzQWVq8c7uCnNdG2qi4xJmds=" + }, + { + "pname": "J2N", + "version": "2.0.0", + "hash": "sha256-YvtIWErlm2O49hg3lIRm5Ha8/owkQkfMudzuldC3EhA=" + }, + { + "pname": "Jellyfin.Common", + "version": "10.10.7", + "hash": "sha256-9EIigrDheob4vRP+UBAoIPHH4fyz6Cl27GUpelEGpBg=" + }, + { + "pname": "Jellyfin.Controller", + "version": "10.10.7", + "hash": "sha256-/obWAuxWpSn+NlMES+fjyrf1g+qbxmSYQUBvgXQYltQ=" + }, + { + "pname": "Jellyfin.Data", + "version": "10.10.7", + "hash": "sha256-lRQjg/HkFAtCN0woL1gX6j5dMfVLP/fzQTKjFTcRth4=" + }, + { + "pname": "Jellyfin.Extensions", + "version": "10.10.7", + "hash": "sha256-AOGJ2IoT2v+LnlEqnb2O4FnXapcQiH0V9ny8GUysNHg=" + }, + { + "pname": "Jellyfin.Model", + "version": "10.10.7", + "hash": "sha256-ubsClGTLq/aFnMiBCaHVUX2b88beuL07Yn5VXNbTCjQ=" + }, + { + "pname": "Jellyfin.Naming", + "version": "10.10.7", + "hash": "sha256-/jlIS0X4FuBo6Ac1cjLGzgdJ3vR6VotBspxpNrqfMzo=" + }, + { + "pname": "Microsoft.Extensions.Caching.Abstractions", + "version": "2.0.0", + "hash": "sha256-Eg1MES40kzkGW9tZmjaKtbWI00Kbv7fLJQmjrigjxqk=" + }, + { + "pname": "Microsoft.Extensions.Caching.Memory", + "version": "2.0.0", + "hash": "sha256-1fnNvp62KrviVwYlqVl1CbdaZVpCDah9eCZeNDGDbWM=" + }, + { + "pname": "Microsoft.Extensions.Configuration.Abstractions", + "version": "8.0.0", + "hash": "sha256-4eBpDkf7MJozTZnOwQvwcfgRKQGcNXe0K/kF+h5Rl8o=" + }, + { + "pname": "Microsoft.Extensions.Configuration.Binder", + "version": "8.0.2", + "hash": "sha256-aGB0VuoC34YadAEqrwoaXLc5qla55pswDV2xLSmR7SE=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection", + "version": "8.0.1", + "hash": "sha256-O9g0jWS+jfGoT3yqKwZYJGL+jGSIeSbwmvomKDC3hTU=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", + "version": "2.0.0", + "hash": "sha256-H1rEnq/veRWvmp8qmUsrQkQIcVlKilUNzmmKsxJ0md8=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", + "version": "8.0.0", + "hash": "sha256-75KzEGWjbRELczJpCiJub+ltNUMMbz5A/1KQU+5dgP8=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", + "version": "8.0.2", + "hash": "sha256-UfLfEQAkXxDaVPC7foE/J3FVEXd31Pu6uQIhTic3JgY=" + }, + { + "pname": "Microsoft.Extensions.Logging", + "version": "8.0.1", + "hash": "sha256-vkfVw4tQEg86Xg18v6QO0Qb4Ysz0Njx57d1XcNuj6IU=" + }, + { + "pname": "Microsoft.Extensions.Logging.Abstractions", + "version": "8.0.2", + "hash": "sha256-cHpe8X2BgYa5DzulZfq24rg8O2K5Lmq2OiLhoyAVgJc=" + }, + { + "pname": "Microsoft.Extensions.Options", + "version": "2.0.0", + "hash": "sha256-EMvaXxGzueI8lT97bYJQr0kAj1IK0pjnAcWN82hTnzw=" + }, + { + "pname": "Microsoft.Extensions.Options", + "version": "8.0.2", + "hash": "sha256-AjcldddddtN/9aH9pg7ClEZycWtFHLi9IPe1GGhNQys=" + }, + { + "pname": "Microsoft.Extensions.Primitives", + "version": "2.0.0", + "hash": "sha256-q44LtMvyNEKSvgERvA+BrasKapP92Sc91QR4u2TJ9/Y=" + }, + { + "pname": "Microsoft.Extensions.Primitives", + "version": "8.0.0", + "hash": "sha256-FU8qj3DR8bDdc1c+WeGZx/PCZeqqndweZM9epcpXjSo=" + }, + { + "pname": "Microsoft.NETCore.Platforms", + "version": "1.1.0", + "hash": "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM=" + }, + { + "pname": "Microsoft.NETCore.Targets", + "version": "1.1.0", + "hash": "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ=" + }, + { + "pname": "System.Globalization", + "version": "4.3.0", + "hash": "sha256-caL0pRmFSEsaoeZeWN5BTQtGrAtaQPwFi8YOZPZG5rI=" + }, + { + "pname": "System.Runtime", + "version": "4.3.0", + "hash": "sha256-51813WXpBIsuA6fUtE5XaRQjcWdQ2/lmEokJt97u0Rg=" + }, + { + "pname": "System.Text.Json", + "version": "8.0.5", + "hash": "sha256-yKxo54w5odWT6nPruUVsaX53oPRe+gKzGvLnnxtwP68=" + }, + { + "pname": "System.Threading.Tasks.Dataflow", + "version": "8.0.1", + "hash": "sha256-hgCfF91BDd/eOtLEd5jhjzgJdvwmVv4/b42fXRr3nvo=" + } +] diff --git a/flake.nix b/flake.nix index f9e1849..2645a68 100644 --- a/flake.nix +++ b/flake.nix @@ -53,7 +53,7 @@ src = self; - projectFile = "plugin/jellyfin-lessons.csproj"; + projectFile = "plugin/Jellyfin.Plugin.Lessons.csproj"; nugetDeps = ./deps.json; dotnet-sdk = dotnetCorePackages.sdk_9_0; diff --git a/plugin/Jellyfin.Plugin.Lessons.csproj b/plugin/Jellyfin.Plugin.Lessons.csproj new file mode 100644 index 0000000..37bd5cc --- /dev/null +++ b/plugin/Jellyfin.Plugin.Lessons.csproj @@ -0,0 +1,16 @@ + + + net9.0 + JellyfinLessons + jellyfin-lessons + + + + + runtime + + + runtime + + + diff --git a/plugin/jellyfin-lessons.csproj b/plugin/jellyfin-lessons.csproj deleted file mode 100644 index 65111ca..0000000 --- a/plugin/jellyfin-lessons.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - net9.0 - JellyfinLessons - jellyfin-lessons - -