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..0243907 --- /dev/null +++ b/deps.json @@ -0,0 +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.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..2645a68 --- /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.Plugin.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.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/templates/example-lesson.typ b/templates/example-lesson.typ deleted file mode 100644 index 44122e7..0000000 --- a/templates/example-lesson.typ +++ /dev/null @@ -1,83 +0,0 @@ -// 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 deleted file mode 100644 index a85cab3..0000000 --- a/templates/lesson.typ +++ /dev/null @@ -1,90 +0,0 @@ -// 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) -}