1import gleam/dict
2import gleam/list
3import gleam/option
4import gleam/regexp
5import gleam/string
6import jot
7import lustre/attribute as attr
8import lustre/element
9import lustre/element/html
10import lustre/ssg/djot
11import website/common
12import website/style
13
14pub fn renderer() -> djot.Renderer(element.Element(msg)) {
15 let to_attributes = fn(attrs) {
16 use attrs, key, val <- dict.fold(attrs, [])
17 [attr.attribute(key, val), ..attrs]
18 }
19
20 djot.Renderer(
21 codeblock: fn(attrs, lang, code) {
22 let lang = option.unwrap(lang, "text")
23 html.pre([style.code_block(), ..to_attributes(attrs)], [
24 html.code([attr.attribute("data-lang", lang), style.monospaced()], [
25 html.text(code),
26 ]),
27 ])
28 },
29 emphasis: fn(content) { html.em([], content) },
30 heading: fn(attrs, level, content) {
31 case level {
32 1 -> html.h1(to_attributes(attrs), content)
33 2 -> html.h2(to_attributes(attrs), content)
34 3 -> html.h3(to_attributes(attrs), content)
35 4 -> html.h4(to_attributes(attrs), content)
36 5 -> html.h5(to_attributes(attrs), content)
37 6 -> html.h6(to_attributes(attrs), content)
38 _ -> html.p(to_attributes(attrs), content)
39 }
40 },
41 link: fn(destination, references, content) {
42 case destination {
43 jot.Reference(ref) ->
44 case dict.get(references, ref) {
45 Ok(url) ->
46 common.raw_link(
47 attributes: [],
48 content:,
49 href: url,
50 external: False,
51 )
52 Error(_) ->
53 common.raw_link(
54 attributes: [attr.id(linkify("back-to-" <> ref))],
55 content:,
56 href: "#" <> linkify(ref),
57 external: False,
58 )
59 }
60 jot.Url(url) ->
61 common.raw_link(attributes: [], content:, href: url, external: False)
62 }
63 },
64 paragraph: fn(attrs, content) { html.p(to_attributes(attrs), content) },
65 bullet_list: fn(layout, style, items) {
66 let list_style_type =
67 attr.style("list-style-type", case style {
68 "-" -> "'-'"
69 "*" -> "disc"
70 _ -> "circle"
71 })
72
73 html.ul([list_style_type], {
74 list.map(items, fn(item) {
75 case layout {
76 jot.Tight -> html.li([], item)
77 jot.Loose -> html.li([], [html.p([], item)])
78 }
79 })
80 })
81 },
82 raw_html: fn(content) { element.unsafe_raw_html("", "div", [], content) },
83 strong: fn(content) { html.strong([], content) },
84 text: fn(text) { html.text(text) },
85 code: fn(content) { html.code([style.monospaced()], [html.text(content)]) },
86 image: fn(destination, alt) {
87 case destination {
88 jot.Reference(ref) ->
89 html.img([attr.src("#" <> linkify(ref)), attr.alt(alt)])
90 jot.Url(url) -> html.img([attr.src(url), attr.alt(alt)])
91 }
92 },
93 linebreak: html.br([]),
94 thematicbreak: html.hr([]),
95 )
96}
97
98fn linkify(text: String) -> String {
99 let assert Ok(re) = regexp.from_string(" +")
100
101 text
102 |> regexp.split(re, _)
103 |> string.join("-")
104}