simple-learning

Simple learning web program
git clone git://git.laack.co/simple-learning.git
Log | Files | Refs | README | LICENSE

commit 2bec5677a4b7c1fbbc1d1ec291cba39502e69985
parent 26e2d82b001d3ca5de79d427bdb19f1c6135c465
Author: Andrew Laack <andrew@laack.co>
Date:   Fri, 28 Aug 2026 19:34:25 -0500

I support all of the different line typesgit status!

Diffstat:
MREADME.md | 2+-
Mcourses/haskell/0_haskell.sl | 21+++++++++++++++++++++
Mdocs/sl.sl | 17++++++++---------
Mlearning/bin/parser.ml | 25++++++++++++++++++++++---
Mlearning/bin/render.ml | 2+-
Mlearning/static/styles.css | 5+++--
Mlearning/templates/course_page.jingoo | 6++++++
7 files changed, 62 insertions(+), 16 deletions(-)

diff --git a/README.md b/README.md @@ -1,4 +1,4 @@ -# Learning Takes Times +# Learning is Simple There are many learning tools out there. There aren't many that can be self-hosted. There are even fewer that are built in a sane way to aid in learning, based on the idea that learning takes time and is difficult. This very notion is why there are so few companies that have science-backed learning tools, and why duolingo has become a joke. diff --git a/courses/haskell/0_haskell.sl b/courses/haskell/0_haskell.sl @@ -3,3 +3,24 @@ This is just some information from this site <script> alert() </script> + +> There once was a person who did such things and they were totally fucking stupid. Like who in the fuck even thought that shit was a good fucking idea. + +* aostien +* stieas +* stiea +* tiesrote +* tieote +* oierote +* osieasrote +* ossrote +* stienasrote + +=> https://laack.co + +?? tioenast + +``` +toiaesrnt +``` +atoris diff --git a/docs/sl.sl b/docs/sl.sl @@ -4,7 +4,7 @@ The simple learning file format is intended to be a minimal but sufficient file format for the distribution of learning content. While not a superset of gemtext, sl is inspired by the syntax and ethos of the format. -=> [https://geminiprotocol.net/docs/gemtext-specification.gmi] Gemtext Specification +=> [https://geminiprotocol.net/docs/gemtext-specification.gmi] ## Specifics @@ -12,13 +12,12 @@ A parser of sl files must track one bit of state: whether the current line is co Outside of preformatted blocks, there are eight line types: -- Text lines -- Text link lines -- Embedded link lines - - Renders an iframe -- Heading lines -- List items -- Quote lines -- Hidden Lines +* Text lines +* Embedded link lines +* Heading lines +* Hidden Lines +* Quote lines +* Text link lines +* List items While links could've be combined into one category, building alternative parsers and frontends is made much simpler by requiring users to specify what type of content they are linking to. By doing this, instructors can choose to link to external videos, or have them rendered in line. diff --git a/learning/bin/parser.ml b/learning/bin/parser.ml @@ -4,25 +4,44 @@ type page_line = { is_embedded: bool; is_preformatted: bool; is_hidden: bool; + is_quote: bool; + is_link: bool; (* if is_embedded == true => not is_link *) + is_list: bool; } -let permute_line line embedded hidden = +let permute_line line embedded hidden link quote list = if embedded then (String.sub line 4 (String.length(line) - 4)) else - if hidden then String.sub line 3 (String.length(line) - 3) + if hidden || link then String.sub line 3 (String.length(line) - 3) else + if quote || list then String.sub line 2 (String.length(line) - 2) else line (* TODO: These should be doing prefix matching...*) let is_hidden line preformatted = if not preformatted && String.length(line) > 2 && line.[0] = '?' && line.[1] = '?' && line.[2] = ' ' then true else false + +let is_list line preformatted = + if not preformatted && String.length(line) > 1 && line.[0] = '*' && line.[1] = ' ' then true else false + +let is_quote line preformatted = + if not preformatted && String.length(line) > 1 && line.[0] = '>' && line.[1] = ' ' then true else false + let is_embedded_link line preformatted = if not preformatted && String.length(line) > 3 && line.[0] = '=' && line.[1] = '>' && line.[2] = 'e' && line.[3] = ' ' then true else false +let is_link line preformatted = + if not preformatted && not (is_embedded_link line preformatted) && String.length(line) > 2 && line.[0] = '=' && line.[1] = '>' && line.[2] = ' ' then true else false + let parse_simple_line line preformatted = let hidden = is_hidden line preformatted in let hash_count = if not preformatted then Basic_functions.count_prefix '#' line else 0 in let embedded_link = is_embedded_link line preformatted in - {header_level = hash_count; content = (permute_line line embedded_link hidden); is_embedded = embedded_link; is_preformatted = preformatted; is_hidden = hidden} + (* This re-computes is embedded, but maintains the 1 bit of state invariant in exchange. *) + let link = is_link line preformatted in + let quote = is_quote line preformatted in + let list = is_list line preformatted in + + {header_level = hash_count; content = (permute_line line embedded_link hidden link quote list); is_embedded = embedded_link; is_preformatted = preformatted; is_hidden = hidden; is_link = link; is_quote = quote; is_list = list} let is_format_line cl = diff --git a/learning/bin/render.ml b/learning/bin/render.ml @@ -29,7 +29,7 @@ let render_sidebar s = (Jg_template.from_file "./templates/sidebar.jingoo" ~models:[("courses", get_courses)]) ^ s let page_line_to_tobj (line : Parser.page_line) = - Jg_types.Tobj [("header_level", Jg_types.Tint line.header_level); ("content", Jg_types.Tstr line.content); ("is_embedded", Jg_types.Tbool line.is_embedded); ("is_preformatted", Jg_types.Tbool line.is_preformatted); ("is_hidden", Jg_types.Tbool line.is_hidden)] + Jg_types.Tobj [("header_level", Jg_types.Tint line.header_level); ("content", Jg_types.Tstr line.content); ("is_embedded", Jg_types.Tbool line.is_embedded); ("is_preformatted", Jg_types.Tbool line.is_preformatted); ("is_hidden", Jg_types.Tbool line.is_hidden); ("is_quote", Jg_types.Tbool line.is_quote); ("is_link", Jg_types.Tbool line.is_link); ("is_list", Jg_types.Tbool line.is_list)] let render_course_home course_name = (Jg_template.from_file "./templates/course_home.jingoo" ~models:[("pages", (Jg_types.Tlist (get_course_home course_name)))]) diff --git a/learning/static/styles.css b/learning/static/styles.css @@ -48,11 +48,12 @@ pre-formatted { word-wrap: break-word; } -summary { - list-style: none; +blockquote { + font-style: italic; } summary { + list-style: none; cursor: pointer; filter: blur(6px); transition: filter 0.2s ease; diff --git a/learning/templates/course_page.jingoo b/learning/templates/course_page.jingoo @@ -2,6 +2,12 @@ {% for line in lines %} {% if line.is_preformatted %} <pre><pre-formatted>{{line.content}}</pre-formatted></pre> + {% else if line.is_quote %} + <blockquote>{{line.content}}</blockquote> + {% else if line.is_list %} + <li>{{line.content}}</li> + {% else if line.is_link %} + <a href="{{line.content}}">{{line.content}}</a> {% else if line.is_hidden %} <details> <summary>{{line.content}}</summary>