diff --git a/gleam.toml b/gleam.toml index 21d677a..74a9a11 100644 --- a/gleam.toml +++ b/gleam.toml @@ -7,6 +7,8 @@ lustre = ">= 5.0.0 and < 6.0.0" simplifile = ">= 2.0.0 and < 3.0.0" lustre_ssg = ">= 0.8.3 and < 1.0.0" tom = ">= 1.1.1 and < 2.0.0" +jot = ">= 4.0.0 and < 5.0.0" +gleam_regexp = ">= 1.1.1 and < 2.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index ee0b19c..d4612a4 100644 --- a/manifest.toml +++ b/manifest.toml @@ -23,8 +23,10 @@ packages = [ ] [requirements] +gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } gleam_stdlib = { version = ">= 0.60.0 and < 0.70.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +jot = { version = ">= 4.0.0 and < 5.0.0" } lustre = { version = ">= 5.0.0 and < 6.0.0" } lustre_ssg = { version = ">= 0.8.3 and < 1.0.0" } simplifile = { version = ">= 2.0.0 and < 3.0.0" } diff --git a/src/website/common.gleam b/src/website/common.gleam index 8fd5e05..efe4bf9 100644 --- a/src/website/common.gleam +++ b/src/website/common.gleam @@ -5,6 +5,8 @@ import lustre/element import lustre/element/html import lustre/ssg/djot import tom + +// import website/djot_renderer import website/style pub type Post(a) { @@ -60,12 +62,27 @@ pub fn wrapper( ]), html.footer([style.footer()], [ html.hr([attr.style("color", "lightgrey")]), - .." -The source code for this website is available [here](https://codeberg.org/naomi/website) or [here](https://tangled.sh/@lesbian.skin/website) under the [BSD 3 with Attribution](https://spdx.org/licenses/BSD-3-Clause-Attribution) license. - -If you wish to get in contact, please send an email to [mia@naomieow.xyz](mailto:mia@naomieow.xyz), or contact me on [Discord](https://chat.lesbian.skin). -" - |> djot.render(djot.default_renderer()) + html.p([], [ + html.text("The source code for this website is available "), + link([], "here", "https://codeberg.org/naomi/website", True), + html.text(" or "), + link([], "here", "https://tangled.sh/@lesbian.skin/website", True), + html.text(" under the "), + link( + [], + "BSD 3 with Attribution", + "https://spdx.org/licenses/BSD-3-Clause-Attribution", + True, + ), + html.text(" license."), + ]), + html.p([], [ + html.text("If you wish to get in contact, please send an email to "), + link([], "mia@naomieow.xyz", "mailto:mia@naomieow.xyz", True), + html.text(", or contact me on "), + link([], "Discord", "https://chat.lesbian.skin", True), + html.text("."), + ]), ]), ]) } @@ -149,6 +166,27 @@ pub fn link( ) } +pub fn raw_link( + attributes attributes: List(attr.Attribute(a)), + content content: List(element.Element(a)), + href href: String, + external external: Bool, +) -> element.Element(a) { + let attrs = [attr.href(href), style.link(), ..attributes] + html.a( + case external { + False -> attrs + True -> [ + attr.target("_blank"), + attr.rel("noopener noreferrer"), + attr.style("display", "inline-block"), + ..attrs + ] + }, + content, + ) +} + pub fn warning(title: String, contents: String) -> element.Element(a) { html.div([], [ html.h3([], [element.text(title)]), diff --git a/src/website/data/posts.gleam b/src/website/data/posts.gleam index f383fe1..89d4b0b 100644 --- a/src/website/data/posts.gleam +++ b/src/website/data/posts.gleam @@ -7,6 +7,7 @@ import lustre/ssg/djot import simplifile import tom import website/common.{type Post, Post} +import website/djot_renderer pub fn all() -> List(Post(a)) { let path = "./src/website/data/posts/" @@ -32,7 +33,7 @@ pub fn all() -> List(Post(a)) { [], post_md |> djot.content() - |> djot.render(djot.default_renderer()), + |> djot.render(djot_renderer.renderer()), ), ) }) diff --git a/src/website/data/projects.gleam b/src/website/data/projects.gleam index 54f8dc7..56cc000 100644 --- a/src/website/data/projects.gleam +++ b/src/website/data/projects.gleam @@ -7,6 +7,7 @@ import lustre/ssg/djot import simplifile import tom import website/common.{type Project, Link, Project} +import website/djot_renderer pub fn all() -> List(Project(a)) { let path = "./src/website/data/projects/" @@ -56,7 +57,7 @@ pub fn all() -> List(Project(a)) { [], project_md |> djot.content() - |> djot.render(djot.default_renderer()), + |> djot.render(djot_renderer.renderer()), ), ) }) diff --git a/src/website/djot_renderer.gleam b/src/website/djot_renderer.gleam new file mode 100644 index 0000000..0e10bc4 --- /dev/null +++ b/src/website/djot_renderer.gleam @@ -0,0 +1,104 @@ +import gleam/dict +import gleam/list +import gleam/option +import gleam/regexp +import gleam/string +import jot +import lustre/attribute as attr +import lustre/element +import lustre/element/html +import lustre/ssg/djot +import website/common +import website/style + +pub fn renderer() -> djot.Renderer(element.Element(msg)) { + let to_attributes = fn(attrs) { + use attrs, key, val <- dict.fold(attrs, []) + [attr.attribute(key, val), ..attrs] + } + + djot.Renderer( + codeblock: fn(attrs, lang, code) { + let lang = option.unwrap(lang, "text") + html.pre([style.code_block(), ..to_attributes(attrs)], [ + html.code([attr.attribute("data-lang", lang), style.monospaced()], [ + html.text(code), + ]), + ]) + }, + emphasis: fn(content) { html.em([], content) }, + heading: fn(attrs, level, content) { + case level { + 1 -> html.h1(to_attributes(attrs), content) + 2 -> html.h2(to_attributes(attrs), content) + 3 -> html.h3(to_attributes(attrs), content) + 4 -> html.h4(to_attributes(attrs), content) + 5 -> html.h5(to_attributes(attrs), content) + 6 -> html.h6(to_attributes(attrs), content) + _ -> html.p(to_attributes(attrs), content) + } + }, + link: fn(destination, references, content) { + case destination { + jot.Reference(ref) -> + case dict.get(references, ref) { + Ok(url) -> + common.raw_link( + attributes: [], + content:, + href: url, + external: False, + ) + Error(_) -> + common.raw_link( + attributes: [attr.id(linkify("back-to-" <> ref))], + content:, + href: "#" <> linkify(ref), + external: False, + ) + } + jot.Url(url) -> + common.raw_link(attributes: [], content:, href: url, external: False) + } + }, + paragraph: fn(attrs, content) { html.p(to_attributes(attrs), content) }, + bullet_list: fn(layout, style, items) { + let list_style_type = + attr.style("list-style-type", case style { + "-" -> "'-'" + "*" -> "disc" + _ -> "circle" + }) + + html.ul([list_style_type], { + list.map(items, fn(item) { + case layout { + jot.Tight -> html.li([], item) + jot.Loose -> html.li([], [html.p([], item)]) + } + }) + }) + }, + raw_html: fn(content) { element.unsafe_raw_html("", "div", [], content) }, + strong: fn(content) { html.strong([], content) }, + text: fn(text) { html.text(text) }, + code: fn(content) { html.code([style.monospaced()], [html.text(content)]) }, + image: fn(destination, alt) { + case destination { + jot.Reference(ref) -> + html.img([attr.src("#" <> linkify(ref)), attr.alt(alt)]) + jot.Url(url) -> html.img([attr.src(url), attr.alt(alt)]) + } + }, + linebreak: html.br([]), + thematicbreak: html.hr([]), + ) +} + +fn linkify(text: String) -> String { + let assert Ok(re) = regexp.from_string(" +") + + text + |> regexp.split(re, _) + |> string.join("-") +} diff --git a/src/website/page/post.gleam b/src/website/page/post.gleam index 70e4db8..c01f661 100644 --- a/src/website/page/post.gleam +++ b/src/website/page/post.gleam @@ -4,6 +4,7 @@ import lustre/element/html import lustre/ssg/djot import website/common.{type Post} import website/data/posts +import website/djot_renderer import website/style pub fn view_all(posts: List(Post(a))) -> List(Element(a)) { @@ -32,7 +33,7 @@ pub fn view_all(posts: List(Post(a))) -> List(Element(a)) { ]), ]), html.div([], [ - html.p([], post.summary |> djot.render(djot.default_renderer())), + html.p([], post.summary |> djot.render(djot_renderer.renderer())), ]), ]) }) @@ -57,7 +58,7 @@ pub fn view(post: Post(a)) -> List(Element(a)) { ]), ]), html.hr([]), - html.p([], post.summary |> djot.render(djot.default_renderer())), + html.p([], post.summary |> djot.render(djot_renderer.renderer())), post.content, ]), ] diff --git a/src/website/page/project.gleam b/src/website/page/project.gleam index 38c0bcf..e2d4e0e 100644 --- a/src/website/page/project.gleam +++ b/src/website/page/project.gleam @@ -5,6 +5,7 @@ import lustre/element/html import lustre/ssg/djot import website/common.{type Project, Project} import website/data/projects +import website/djot_renderer import website/style pub fn view_all(projects: List(Project(a))) -> List(Element(a)) { @@ -35,7 +36,7 @@ pub fn view_all(projects: List(Project(a))) -> List(Element(a)) { ), ]), html.div([], [ - html.p([], project.summary |> djot.render(djot.default_renderer())), + html.p([], project.summary |> djot.render(djot_renderer.renderer())), ]), ]) }) @@ -64,7 +65,7 @@ pub fn view(project: Project(a)) -> List(Element(a)) { }), ), ]), - html.p([], project.summary |> djot.render(djot.default_renderer())), + html.p([], project.summary |> djot.render(djot_renderer.renderer())), project.content, ]), ] diff --git a/src/website/style.gleam b/src/website/style.gleam index de25566..223f432 100644 --- a/src/website/style.gleam +++ b/src/website/style.gleam @@ -120,7 +120,7 @@ pub fn infobox_inner() { pub fn link() { attr.styles([ #("text-decoration", "underline"), - #("color", "black"), + #("color", "darkblue"), #("font-variant-emoji", "text"), ]) } @@ -195,3 +195,16 @@ pub fn project_icon_link_holder() { #("gap", "0.5em"), ]) } + +pub fn monospaced() { + attr.styles([#("font-family", "monospace, monospace")]) +} + +pub fn code_block() { + attr.styles([ + #("font-family", "monospace, monospace"), + #("border-radius", "0.4em"), + #("border", "2px solid black"), + #("padding", "1em 1em 1em 1em"), + ]) +}