diff --git a/gleam.toml b/gleam.toml index 39a2b56..4f0a5a5 100644 --- a/gleam.toml +++ b/gleam.toml @@ -25,7 +25,7 @@ pages = [ [dependencies] gleam_stdlib = "~> 0.36 or ~> 1.0" lustre = "~> 4.1" -lustre_ssg = "~> 0.6.0" +lustre_ssg = { path = "../ssg" } gleam_javascript = "~> 0.8" ranged_int = "~> 2.0" bigi = "~> 3.0" diff --git a/manifest.toml b/manifest.toml index a288f7f..3a52357 100644 --- a/manifest.toml +++ b/manifest.toml @@ -12,7 +12,7 @@ packages = [ { name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" }, { name = "jot", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "jot", source = "hex", outer_checksum = "B20A745707EE60B857249D4533656A52964EA024E844005C4AD8135ED432D66C" }, { name = "lustre", version = "4.1.8", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "55C3D26AEE6BF6B859927616B1F5BC25841926E4EA6A2A72ECC90D6769560F04" }, - { name = "lustre_ssg", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "jot", "lustre", "simplifile", "tom"], otp_app = "lustre_ssg", source = "hex", outer_checksum = "F89A4246C011DC8A417A62F53003B525BA39AD86099DFE62C2E942296976897A" }, + { name = "lustre_ssg", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "jot", "lustre", "simplifile", "tom"], source = "local", path = "../ssg" }, { name = "ranged_int", version = "2.0.0", build_tools = ["gleam"], requirements = ["bigi", "gleam_stdlib"], otp_app = "ranged_int", source = "hex", outer_checksum = "9FCDA804C1884015FC25F3F8BE429FC450D402F861B5C561464479F5B1162A41" }, { name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" }, { name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" }, @@ -25,6 +25,6 @@ gleam_javascript = { version = "~> 0.8" } gleam_stdlib = { version = "~> 0.36 or ~> 1.0" } gleeunit = { version = "~> 1.0" } lustre = { version = "~> 4.1" } -lustre_ssg = { version = "~> 0.6.0" } +lustre_ssg = { path = "../ssg" } ranged_int = { version = "~> 2.0" } simplifile = { version = "~> 1.7" } diff --git a/src/lustre/ssg/atom.gleam b/src/lustre/ssg/atom.gleam deleted file mode 100644 index 956987b..0000000 --- a/src/lustre/ssg/atom.gleam +++ /dev/null @@ -1,88 +0,0 @@ -//// Bindings for Atom feed elements. - -import lustre/attribute.{attribute} -import lustre/element.{type Element, advanced, element, namespaced, text} - -pub fn feed(children: List(Element(a))) { - element("feed", [attribute("xmlns", "http://www.w3.org/2005/Atom")], children) -} - -pub fn entry(children: List(Element(a))) { - element("entry", [], children) -} - -pub fn id(uri: String) { - element("id", [], [text(uri)]) -} - -pub fn title(title: String) { - element("title", [attribute("type", "html")], [text(title)]) -} - -pub fn updated(iso_timestamp: String) { - element("updated", [], [text(iso_timestamp)]) -} - -pub fn published(iso_timestamp: String) { - element("published", [], [text(iso_timestamp)]) -} - -pub fn author(children: List(Element(a))) { - element("author", [], children) -} - -pub fn contributor(children: List(Element(a))) { - element("contributor", [], children) -} - -pub fn source(children: List(Element(a))) { - namespaced("", "source", [], children) -} - -pub fn link(attributes: List(attribute.Attribute(a))) { - advanced("", "link", attributes, [], True, False) -} - -pub fn name(name: String) { - element("name", [], [text(name)]) -} - -pub fn email(email: String) { - element("email", [], [text(email)]) -} - -pub fn uri(uri: String) { - element("uri", [], [text(uri)]) -} - -pub fn category(attributes: List(attribute.Attribute(a))) { - advanced("", "category", attributes, [], True, False) -} - -pub fn generator(attributes: List(attribute.Attribute(a)), name: String) { - element("generator", attributes, [text(name)]) -} - -pub fn icon(path: String) { - element("icon", [], [text(path)]) -} - -pub fn logo(path: String) { - element("logo", [], [text(path)]) -} - -pub fn rights(rights: String) { - element("rights", [], [text(rights)]) -} - -pub fn subtitle(subtitle: String) { - element("subtitle", [attribute("type", "html")], [text(subtitle)]) -} - -pub fn summary(summary: String) { - element("summary", [attribute("type", "html")], [text(summary)]) -} - -pub fn content(content: String) { - element("content", [attribute("type", "html")], [text(content)]) -} diff --git a/src/lustre/ssg/xml.gleam b/src/lustre/ssg/xml.gleam deleted file mode 100644 index 3935f4b..0000000 --- a/src/lustre/ssg/xml.gleam +++ /dev/null @@ -1,6 +0,0 @@ -import lustre/element.{advanced} - -/// Generate an XML declaration. -pub fn declaration() { - advanced("", "?xml version=\"1.0\" encoding=\"utf-8\"?", [], [], False, True) -} diff --git a/src/scriptorium/parser/post.gleam b/src/scriptorium/parser/post.gleam index 28bb61a..9bf3430 100644 --- a/src/scriptorium/parser/post.gleam +++ b/src/scriptorium/parser/post.gleam @@ -165,16 +165,16 @@ fn parse_short_content(body: String) -> option.Option(String) { fn parse_time( headers: List(Header), ) -> Result(option.Option(#(Time, String)), ParseError) { - let time_hdr = list.find(headers, fn(hdr) { hdr.0 == "time" }) + let time_hdr = list.key_find(headers, "time") case time_hdr { Error(Nil) -> Ok(option.None) Ok(hdr) -> - case list.split(string.split(hdr.1, " "), 1) { + case list.split(string.split(hdr, " "), 1) { #([ts], rest) -> time.parse(ts) |> result.replace_error(InvalidDate) |> result.map(fn(ts) { option.Some(#(ts, string.join(rest, " "))) }) - _ -> Error(MalformedHeader(hdr.0 <> ": " <> hdr.1)) + _ -> Error(MalformedHeader("time: " <> hdr)) } } } diff --git a/src/scriptorium/paths.gleam b/src/scriptorium/paths.gleam index 9df91a9..f77d4e2 100644 --- a/src/scriptorium/paths.gleam +++ b/src/scriptorium/paths.gleam @@ -15,7 +15,7 @@ const default_root = "" pub const default_index = "/index" /// The default filename where the feed is written. -pub const default_feed_file = "/feed.xml" +pub const default_feed_file = "/feed" /// The default path where the feed is accessible when hosted. pub const default_feed = default_feed_file @@ -52,7 +52,8 @@ pub type PathConfiguration { html: fn(String) -> String, /// Path to the feed as it is accessible from the browser. feed: String, - /// Path and file name of the feed file that will be written. + /// Path and file name of the feed file that will be written, _without_ the + /// file extension! E.g. `/feed` will create a file `/feed.xml`. feed_file: String, ) } diff --git a/src/scriptorium/renderer.gleam b/src/scriptorium/renderer.gleam index 2899a45..2527d0f 100644 --- a/src/scriptorium/renderer.gleam +++ b/src/scriptorium/renderer.gleam @@ -5,6 +5,7 @@ import gleam/dict.{type Dict} import gleam/int import gleam/list import gleam/result +import lustre/element.{type Element} import scriptorium/compiler.{ type CompileDatabase, type CompiledPage, type CompiledPost, } @@ -20,7 +21,6 @@ import scriptorium/rendering/views.{ } import scriptorium/utils/date import scriptorium/utils/ordered_tree -import lustre/element.{type Element} /// Helper struct to pass all the used views to the rendering functions. pub type Views { diff --git a/src/scriptorium/rendering/views.gleam b/src/scriptorium/rendering/views.gleam index 31a40b8..da3871a 100644 --- a/src/scriptorium/rendering/views.gleam +++ b/src/scriptorium/rendering/views.gleam @@ -1,7 +1,7 @@ +import lustre/element.{type Element} import scriptorium/compiler.{type CompiledPage, type CompiledPost} import scriptorium/models/page import scriptorium/models/post -import lustre/element.{type Element} /// The base view renders the base page layout. /// diff --git a/src/scriptorium/rendering/views/feed.gleam b/src/scriptorium/rendering/views/feed.gleam index 66937c1..9e87f8b 100644 --- a/src/scriptorium/rendering/views/feed.gleam +++ b/src/scriptorium/rendering/views/feed.gleam @@ -1,59 +1,64 @@ import gleam/list import gleam/option -import scriptorium/compiler.{type CompiledPost} -import scriptorium/config.{type Configuration} -import scriptorium/models/database.{type Database} -import scriptorium/models/post -import scriptorium/utils/luxon import lustre/attribute.{attribute} import lustre/element import lustre/ssg/atom.{ author, content, email, entry, feed, generator, id, link, name, rights, summary, title, updated, uri, } +import scriptorium/compiler.{type CompiledPost} +import scriptorium/config.{type Configuration} +import scriptorium/models/database.{type Database} +import scriptorium/models/post +import scriptorium/utils/luxon pub fn generate(_db: Database, config: Configuration) { fn(posts: List(CompiledPost)) { let now = luxon.utc_now() let #(posts, _) = list.split(posts, config.rendering.posts_in_feed) - feed([ - title(config.blog_name), - updated(luxon.to_iso(now)), + feed([attribute("xml:lang", config.language)], [ + title([], config.blog_name), + updated([], luxon.to_iso(now)), link([attribute("href", config.blog_url)]), - id(config.blog_url), - author([ - name(config.author.name), + id([], config.blog_url), + author([], [ + name([], config.author.name), case config.author.email { - option.Some(e) -> email(e) + option.Some(e) -> email([], e) option.None -> element.none() }, case config.author.url { - option.Some(u) -> uri(u) + option.Some(u) -> uri([], u) option.None -> element.none() }, ]), generator( [ attribute("uri", "https://gitlab.com/Nicd/scriptorium"), - attribute("version", "1.0.0"), + attribute("version", "2.0.0"), ], "Scriptorium", ), - rights(config.rendering.copyright), + rights([], config.rendering.copyright), ..list.map(posts, fn(post) { + let url = + config.blog_url + <> config.paths.html(config.paths.single_post(post.orig)) let date_str = post.to_iso8601(post.orig) + let entry_attrs = case list.key_find(post.orig.headers, "lang") { + Ok(l) -> [attribute("xml:lang", l)] + Error(_) -> [] + } - entry([ - title(post.orig.title), - id( - config.blog_url - <> config.paths.html(config.paths.single_post(post.orig)), - ), - updated(date_str), - content(post.content.full), + entry(entry_attrs, [ + title([], post.orig.title), + id([], url), + link([attribute("href", url)]), + updated([], date_str), + content([], post.content.full), case post.content.short { - option.Some(s) -> summary(s) + option.Some(s) -> summary([], s) option.None -> element.none() }, ]) diff --git a/src/scriptorium/rendering/views/page.gleam b/src/scriptorium/rendering/views/page.gleam index b1f3faf..6e5d0ba 100644 --- a/src/scriptorium/rendering/views/page.gleam +++ b/src/scriptorium/rendering/views/page.gleam @@ -1,16 +1,22 @@ -import scriptorium/compiler.{type CompiledPage} -import scriptorium/config.{type Configuration} -import scriptorium/models/database.{type Database} +import gleam/list import lustre/attribute.{attribute, class} import lustre/element.{text} import lustre/element/html.{article, div, h2, header} +import scriptorium/compiler.{type CompiledPage} +import scriptorium/config.{type Configuration} +import scriptorium/models/database.{type Database} pub fn generate(db: Database, config: Configuration) { view(_, db, config) } fn view(page: CompiledPage, _db: Database, _config: Configuration) { - article([class("page")], [ + let language = case list.key_find(page.orig.headers, "lang") { + Ok(l) -> [attribute("lang", l)] + Error(_) -> [] + } + + article([class("page"), ..language], [ header([], [h2([], [text(page.orig.title)])]), div([attribute("dangerous-unescaped-html", page.content)], []), ]) diff --git a/src/scriptorium/rendering/views/single_post.gleam b/src/scriptorium/rendering/views/single_post.gleam index 30284b8..b519ba5 100644 --- a/src/scriptorium/rendering/views/single_post.gleam +++ b/src/scriptorium/rendering/views/single_post.gleam @@ -1,5 +1,10 @@ import gleam/list import gleam/option +import lustre/attribute.{attribute, class, href} +import lustre/element.{type Element, text} +import lustre/element/html.{ + a, article, div, footer, h2, header, li, nav, p, time, ul, +} import scriptorium/compiler.{type CompiledPost} import scriptorium/config.{type Configuration} import scriptorium/models/database.{type Database} @@ -7,11 +12,6 @@ import scriptorium/models/post.{type Post} import scriptorium/utils/date import scriptorium/utils/luxon import scriptorium/utils/time -import lustre/attribute.{attribute, class, href} -import lustre/element.{type Element, text} -import lustre/element/html.{ - a, article, div, footer, h2, header, li, nav, p, time, ul, -} /// Generate a view that renders a full post. pub fn full_view(db: Database, config: Configuration) { @@ -32,7 +32,12 @@ fn view(post: CompiledPost, is_full: Bool, _db: Database, config: Configuration) _, _ -> post.content.full } - article([class("post")], [ + let language = case list.key_find(post.orig.headers, "lang") { + Ok(l) -> [attribute("lang", l)] + Error(_) -> [] + } + + article([class("post"), ..language], [ header([], [ wrap_heading(h2([], [text(post.orig.title)]), post_url, is_full), p([class("post__time")], [ diff --git a/src/scriptorium/writer.gleam b/src/scriptorium/writer.gleam index c5bc2cb..b60cd77 100644 --- a/src/scriptorium/writer.gleam +++ b/src/scriptorium/writer.gleam @@ -4,12 +4,10 @@ import gleam/dict import gleam/list import gleam/result import gleam/string +import lustre/ssg import scriptorium/config.{type Configuration, WriteError} import scriptorium/rendering/database.{type RenderDatabase} as _ import scriptorium/utils/priv -import lustre/element -import lustre/ssg -import lustre/ssg/xml /// Write the rendered content into the filesystem using lustre_ssg. pub fn write(db: RenderDatabase, config: Configuration) { @@ -60,12 +58,7 @@ pub fn write(db: RenderDatabase, config: Configuration) { }) }) - let site = - ssg.add_static_asset( - site, - config.paths.feed_file, - element.to_string(xml.declaration()) <> element.to_string(db.feed), - ) + let site = ssg.add_static_xml(site, config.paths.feed_file, db.feed) let site = list.fold(single_posts, site, fn(acc, item) {