///|
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],
indent? : Int = 0,
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,
indent~,
lang~,
)
}
///|
fn[T] comp_snippet(
content : String,
class_name? : String,
highliter? : (String, String) -> String,
indent? : Int = 0,
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("")],
style=respo_style(margin_left=Px(Float::from_int(indent) * 8.0)),
[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 == "---" || line == "***" || line == "___" {
let attrs = {}
attrs.set("class", style_hr)
create_element("hr", attrs~, event={}, style=respo_style(), [])
} 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([
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 {
@md_util.Text(lines) => comp_text_block(lines.to_array())
@md_util.Code(indent, lines) =>
comp_code_block(lines.to_array(), indent~, highlighter~)
@md_util.Table(lines) => comp_table_block(lines.to_array())
@md_util.Empty => div([])
}
})
.collect(),
)
}
///|
fn[T] comp_table_block(
lines : Array[Array[String]],
) -> @respo_node.RespoNode[T] {
let header_line = lines[0]
// Check if second line is a separator line (e.g., | --- | :--- |)
let body_lines = if lines.length() > 1 && @md_util.is_separator_line(lines[1]) {
lines[2:]
} else {
lines[1:]
}
let table_style = respo_style(margin="16px 0" |> Custom)
.add("empty-cells", "show")
.add("border-collapse", "collapse")
.add("width", "100%")
let thead_style = respo_style(background_color=Hsl(220, 10, 97)).add(
"font-weight", "600",
)
let th_style = respo_style(
border=CssBorder(1, Solid, Hsl(220, 10, 90)),
padding="10px 14px" |> Custom,
text_align=Left,
background_color=Hsl(220, 10, 97),
).add("border-bottom-width", "1px")
let td_style = respo_style(
padding="8px 14px" |> Custom,
text_align=Left,
vertical_align=Top,
)
.add("border-left", "1px solid hsl(220, 10%, 90%)")
.add("border-right", "1px solid hsl(220, 10%, 90%)")
.add("border-bottom", "1px solid hsl(220, 10%, 90%)")
create_element("table", style=table_style, [
create_element("thead", style=thead_style, [
create_element(
"tr",
header_line
.iter()
.map(fn(x) {
create_element(
"th",
attrs={},
event={},
style=th_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", style=td_style, 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 {
@md_util.Code(content) => {
println("inline code: " + content)
els.push(code(class_name=style_inline_code, inner_text=content))
}
@md_util.Url(content) =>
els.push(a(href=content, inner_text=content, target=Blank))
@md_util.Link(content) => els.push(comp_link(content))
@md_util.Image(content) => els.push(comp_image(content))
@md_util.Text(content) => els.push(span(inner_text=content, []))
@md_util.Emphasis(content) => els.push(b(render_inline(content)))
@md_util.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_hr : String = static_style([
(
"&",
respo_style(
border=CssBorder(0, Solid, Hsl(220, 6, 94)),
margin="24px 40px" |> Custom,
)
.add("border-top-width", "1px")
.add("height", "0"),
),
])
///|
let style_paragraph : String = static_style([("&", respo_style())])