///|
using @respo_node {
static_style,
blockquote,
create_element,
div,
img,
span,
a,
code,
i,
b,
h1,
h2,
h3,
h4,
pre,
li,
}
///|
using @css {respo_style}
///|
let code_fonts = @respo.code_fonts
///|
fn[T] comp_code_block(
lines : Array[String],
highlighter? : (String, String) -> String = fn(x, _) -> String { x },
) -> @respo_node.RespoNode[T] {
let lang = lines[0]
let content = lines[1:].to_array().join("\n")
return comp_snippet(
content,
class_name=style_code_block,
highliter=highlighter,
lang~,
)
}
///|
fn[T] comp_snippet(
content : String,
class_name? : String,
highliter? : (String, String) -> String,
lang? : String,
) -> @respo_node.RespoNode[T] {
let highliter = highliter.unwrap_or(fn(content, _) -> String { content })
let highlighted = highliter(content, lang.unwrap_or("text"))
return div(class_list=[style_snippet, class_name.unwrap_or("")], [
pre(class_name=style_code_block, children=[code(inner_text=highlighted)]),
])
}
///|
let style_snippet : String = static_style([
(
"&",
respo_style(
font_family=code_fonts,
border=CssBorder(1, Solid, Hsl(0, 0, 90)),
padding=8 |> Px,
border_radius=4,
line_height=20 |> Px,
font_size=12,
),
),
])
///|
fn[T] comp_image(chunk : String) -> @respo_node.RespoNode[T] {
let useful = chunk.unsafe_substring(start=2, end=chunk.length() - 1)
let xs = useful.split("](").map(StringView::to_string).collect()
if xs is [content, url, ..] {
img(class_name=style_image, src=url, alt=content)
} else {
return div(class_name=style_image, [
span(inner_text="Image not found", class_name=style_image, []),
])
}
}
///|
fn[T] comp_line(line : String) -> @respo_node.RespoNode[T] {
if line.has_prefix("# ") {
h1(render_inline(line.unsafe_substring(start=2, end=line.length())))
} else if line.has_prefix("## ") {
h2(render_inline(line.unsafe_substring(start=3, end=line.length())))
} else if line.has_prefix("### ") {
h3(render_inline(line.unsafe_substring(start=4, end=line.length())))
} else if line.has_prefix("#### ") {
h4(render_inline(line.unsafe_substring(start=5, end=line.length())))
} else if line.has_prefix("> ") {
blockquote(
class_name=style_blockquote,
render_inline(line.unsafe_substring(start=2, end=line.length())),
)
} else if line.has_prefix(" ") {
let content = line.trim_start(chars=" ").to_string()
let space_size = line.length() - content.length()
let spaces = line.unsafe_substring(start=0, end=space_size)
div(class_name=style_indent, [
span(inner_text=spaces, class_name=style_indent, []),
comp_line(content),
])
} else if line.has_prefix("* ") || line.has_prefix("- ") {
li(
class_name=style_line_list,
render_inline(line.unsafe_substring(start=2, end=line.length())),
)
} else if line.has_prefix("#!html ") {
div(
class_name="html-container",
innerHTML=line.unsafe_substring(start=7, end=line.length()),
[],
)
} else {
div(render_inline(line))
}
}
///|
fn[T] comp_link(chunk : String) -> @respo_node.RespoNode[T] {
let useful = chunk.unsafe_substring(start=1, end=chunk.length() - 1)
let xs = useful.split("](").map(StringView::to_string).collect()
if xs is [content, url] {
span(class_name=style_default_link, [
if content.has_prefix("`") && content.has_suffix("`") {
a(class_name=style_default_link, href=url, target=Blank, children=[
code(
inner_text=content.unsafe_substring(
start=1,
end=content.length() - 1,
),
),
])
} else {
a(
class_name=style_default_link,
href=url,
target=Blank,
inner_text="🌐 \{content}",
)
},
])
} else {
@dom_ffi.log("Error in comp-link: " + chunk)
span(class_name=style_default_link, inner_text="\{xs}", [])
}
}
///|
pub fn[T] comp_md(
text : String,
class_name? : String,
) -> @respo_node.RespoNode[T] {
div(class_name=class_name.unwrap_or(""), render_inline(text))
}
///|
pub fn[T] comp_md_block(
text : String,
class_name? : String,
style? : @css.RespoStyle,
highlighter? : (String, String) -> String = fn(x, _) -> String { x },
) -> @respo_node.RespoNode[T] {
let blocks = split_block(text)
return div(
class_list=[class_name.unwrap_or(""), "md-block"],
style=style.unwrap_or(respo_style()),
blocks
.iter()
.map(fn(block) {
match block {
Text(lines) => comp_text_block(lines.to_array())
Code(lines) => comp_code_block(lines.to_array(), highlighter~)
Table(lines) => comp_table_block(lines.to_array())
Empty => div([])
}
})
.collect(),
)
}
///|
fn[T] comp_table_block(
lines : Array[Array[String]],
) -> @respo_node.RespoNode[T] {
let header_line = lines[0]
let mark_line = lines[1]
let body_lines = if mark_line[0].has_prefix(":") ||
mark_line[0].has_suffix("-") {
lines[2:]
} else {
lines[1:]
}
let attrs = {}
attrs.set("class-name", style_md_table)
create_element("table", attrs~, event={}, style=respo_style(), [
create_element("thead", attrs={}, event={}, style=respo_style(), [
create_element(
"tr",
header_line
.iter()
.map(fn(x) {
create_element(
"th",
attrs={},
event={},
style=respo_style(),
render_inline(x.to_string()),
)
})
.collect(),
),
]),
create_element(
"tbody",
attrs={},
event={},
style=respo_style(),
body_lines
.iter()
.map(fn(line) {
create_element(
"tr",
line
.iter()
.map(fn(x) { create_element("td", render_inline(x.to_string())) })
.collect(),
)
})
.collect(),
),
])
}
///|
fn[T] comp_text_block(lines : Array[String]) -> @respo_node.RespoNode[T] {
return div(
class_name=style_paragraph,
lines.iter().map(fn(line) { comp_line(line) }).collect(),
)
}
///|
fn[T] render_inline(text : String) -> Array[@respo_node.RespoNode[T]] {
let els = []
println("render inline: " + text)
let lines = split_line(text)
for chunk in lines {
match chunk {
Code(content) => {
println("inline code: " + content)
els.push(code(class_name=style_inline_code, inner_text=content))
}
Url(content) =>
els.push(a(href=content, inner_text=content, target=Blank))
Link(content) => els.push(comp_link(content))
Image(content) => els.push(comp_image(content))
Text(content) => els.push(span(inner_text=content, []))
Emphasis(content) => els.push(b(render_inline(content)))
Italic(content) => els.push(i(inner_text=content, []))
}
}
els
}
///|
let style_blockquote : String = static_style([
(
"&",
respo_style(
border=CssBorder(0, Solid, Hsl(0, 0, 90)),
margin_left=0 |> Px,
padding_left=12 |> Px,
color=Hsl(0, 0, 50),
).add("border-width", "0 0 0 6px"),
),
])
///|
let style_code_block : String = static_style([
("&", respo_style(max_width=60 |> Vw)),
])
///|
let style_default_link : String = static_style([
("&", respo_style(opacity=0.9, transition_duration=200 |> Ms)),
("&:hover", respo_style(opacity=1, transform=1 |> Scale)),
])
///|
let style_image : String = static_style([
(
"&",
respo_style(
max_width=480 |> Px,
max_height=320 |> Px,
border=CssBorder(0, Solid, Hsl(0, 0, 90)),
border_radius=8,
),
),
])
///|
let style_indent : String = static_style([
("&", respo_style(white_space="pre", float="left")),
])
///|
let style_inline_code : String = static_style([
(
"&",
respo_style(
border=CssBorder(1, Solid, Hsl(0, 0, 86)),
border_radius=4,
font_size=12,
padding="2px 4px" |> Custom,
margin="2px 4px" |> Custom,
),
),
])
///|
let style_line_list : String = static_style([
("&", respo_style(margin_left=12 |> Px)),
(
"&::marker",
respo_style(
color=Hsl(0, 0, 80),
font_family="ui/font-code",
white_space="pre",
content="'● '",
transition_duration=300 |> Ms,
),
),
("&:hover::marker", respo_style(color=Hsl(0, 0, 50), content="'● '")),
])
///|
let style_md_table : String = static_style([
(
"&",
respo_style(border=CssBorder(1, Solid, Hsl(0, 0, 90)))
.add("empty-cells", "show")
.add("border-collapse", "collapse"),
),
("& thead", respo_style(background_color=Hsl(0, 0, 96))),
(
"& tr",
respo_style(
border=CssBorder(0, Solid, Hsl(0, 0, 94)),
border_width="1px 0 0 0" |> Custom,
),
),
(
"& td, & th",
respo_style(
border=CssBorder(0, Solid, Hsl(0, 0, 94)),
border_width="0 0 0 1px" |> Custom,
padding="0.5em 1em" |> Custom,
line_height=1.5 |> Em,
text_align=Left,
vertical_align=Middle,
),
),
])
///|
let style_paragraph : String = static_style([("&", respo_style())])