Add support for "lang" header

This commit is contained in:
Mikko Ahlroth 2024-05-23 21:43:42 +03:00
parent a00a808618
commit ee06f93f65
12 changed files with 63 additions and 147 deletions

View file

@ -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"

View file

@ -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" }

View file

@ -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)])
}

View file

@ -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)
}

View file

@ -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))
}
}
}

View file

@ -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,
)
}

View file

@ -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 {

View file

@ -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.
///

View file

@ -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()
},
])

View file

@ -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)], []),
])

View file

@ -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")], [

View file

@ -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) {