use web_framework_markdown::{render_markdown, CowStr, MarkdownProps};
use std::collections::BTreeMap;
pub type MdComponentProps = web_framework_markdown::MdComponentProps<Element>;
use core::ops::Range;
pub use web_framework_markdown::{
ComponentCreationError, Context, ElementAttributes, HtmlElement, LinkDescription, Options,
};
use dioxus::prelude::*;
pub type HtmlCallback<T> = Callback<T, Element>;
#[cfg(feature = "debug")]
pub mod debug {
use dioxus::signals::{GlobalMemo, GlobalSignal, Signal};
pub(crate) static DEBUG_INFO_SOURCE: GlobalSignal<Vec<String>> = Signal::global(|| Vec::new());
pub static DEBUG_INFO: GlobalMemo<Vec<String>> = Signal::global_memo(|| DEBUG_INFO_SOURCE());
}
#[derive(Clone, PartialEq, Default, Props)]
pub struct MdProps {
src: ReadOnlySignal<String>,
on_click: Option<EventHandler<MarkdownMouseEvent>>,
render_links: Option<HtmlCallback<LinkDescription<Element>>>,
theme: Option<&'static str>,
#[props(default = false)]
wikilinks: bool,
#[props(default = false)]
hard_line_breaks: bool,
parse_options: Option<Options>,
#[props(default)]
components: ReadOnlySignal<CustomComponents>,
frontmatter: Option<Signal<String>>,
}
#[derive(Clone, Debug)]
pub struct MarkdownMouseEvent {
pub mouse_event: MouseEvent,
pub position: Range<usize>,
}
#[derive(Clone, Copy)]
pub struct MdContext(ReadOnlySignal<MdProps>);
#[derive(Default)]
pub struct CustomComponents(
BTreeMap<String, Callback<MdComponentProps, Result<Element, ComponentCreationError>>>,
);
impl CustomComponents {
pub fn new() -> Self {
Self(Default::default())
}
pub fn register<F>(&mut self, name: &'static str, component: F)
where
F: Fn(MdComponentProps) -> Result<Element, ComponentCreationError> + 'static,
{
self.0.insert(name.to_string(), Callback::new(component));
}
pub fn get_callback(
&self,
name: &str,
) -> Option<&Callback<MdComponentProps, Result<Element, ComponentCreationError>>> {
self.0.get(name)
}
}
impl<'src> Context<'src, 'static> for MdContext {
type View = Element;
type Handler<T: 'static> = EventHandler<T>;
type MouseEvent = MouseEvent;
#[cfg(feature = "debug")]
fn send_debug_info(self, info: Vec<String>) {
*debug::DEBUG_INFO_SOURCE.write() = info;
}
fn el_with_attributes(
self,
e: HtmlElement,
inside: Self::View,
attributes: ElementAttributes<EventHandler<MouseEvent>>,
) -> Self::View {
let class = attributes.classes.join(" ");
let style = attributes.style.unwrap_or_default();
let onclick = attributes.on_click.unwrap_or_default();
let onclick = move |e| onclick.call(e);
match e {
HtmlElement::Div => {
rsx! {div {onclick:onclick, style: "{style}", class: "{class}", {inside}} }
}
HtmlElement::Span => {
rsx! {span {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Paragraph => {
rsx! {p {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::BlockQuote => {
rsx! {blockquote {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Ul => {
rsx! {ul {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Ol(x) => {
rsx! {ol {onclick: onclick, style: "{style}", class: "{class}", start: x as i64, {inside} } }
}
HtmlElement::Li => {
rsx! {li {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Heading(1) => {
rsx! {h1 {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Heading(2) => {
rsx! {h2 {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Heading(3) => {
rsx! {h3 {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Heading(4) => {
rsx! {h4 {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Heading(5) => {
rsx! {h5 {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Heading(6) => {
rsx! {h6 {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Heading(_) => panic!(),
HtmlElement::Table => {
rsx! {table {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Thead => {
rsx! {thead {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Trow => {
rsx! {tr {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Tcell => {
rsx! {td {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Italics => {
rsx! {i {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Bold => {
rsx! {b {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::StrikeThrough => {
rsx! {s {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Pre => {
rsx! {p {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
HtmlElement::Code => {
rsx! {code {onclick: onclick, style: "{style}", class: "{class}", {inside} } }
}
}
}
fn el_span_with_inner_html(
self,
inner_html: String,
attributes: ElementAttributes<EventHandler<MouseEvent>>,
) -> Self::View {
let class = attributes.classes.join(" ");
let style = attributes.style.unwrap_or_default();
let onclick = move |e| {
if let Some(f) = &attributes.on_click {
f.call(e)
}
};
rsx! {
span {
dangerous_inner_html: "{inner_html}",
style: "{style}",
class: "{class}",
onclick: onclick
}
}
}
fn el_hr(self, attributes: ElementAttributes<EventHandler<MouseEvent>>) -> Self::View {
let class = attributes.classes.join(" ");
let style = attributes.style.unwrap_or_default();
let onclick = move |e| {
if let Some(f) = &attributes.on_click {
f.call(e)
}
};
rsx!(hr {
onclick: onclick,
style: "{style}",
class: "{class}"
})
}
fn el_br(self) -> Self::View {
rsx!(br {})
}
fn el_fragment(self, children: Vec<Self::View>) -> Self::View {
rsx! {
{children.into_iter()}
}
}
fn el_a(self, children: Self::View, href: String) -> Self::View {
rsx! {a {
href: "{href}",
{children}
}}
}
fn el_img(self, src: String, alt: String) -> Self::View {
rsx!(img {
src: "{src}",
alt: "{alt}"
})
}
fn el_text<'a>(self, text: CowStr<'a>) -> Self::View {
rsx! {
{text.as_ref()}
}
}
fn mount_dynamic_link(self, _rel: &str, _href: &str, _integrity: &str, _crossorigin: &str) {
}
fn el_input_checkbox(
self,
checked: bool,
attributes: ElementAttributes<EventHandler<MouseEvent>>,
) -> Self::View {
let class = attributes.classes.join(" ");
let style = attributes.style.unwrap_or_default();
let onclick = move |e| {
if let Some(f) = &attributes.on_click {
f.call(e)
}
};
rsx!(input {
r#type: "checkbox",
checked: checked,
style: "{style}",
class: "{class}",
onclick: onclick
})
}
fn props(self) -> MarkdownProps {
let props = self.0();
MarkdownProps {
hard_line_breaks: props.hard_line_breaks,
wikilinks: props.wikilinks,
parse_options: props.parse_options,
theme: props.theme,
}
}
fn call_handler<T: 'static>(callback: &Self::Handler<T>, input: T) {
callback.call(input)
}
fn make_md_handler(
self,
position: std::ops::Range<usize>,
stop_propagation: bool,
) -> Self::Handler<MouseEvent> {
let on_click = self.0().on_click.as_ref().cloned();
EventHandler::new(move |e: MouseEvent| {
if stop_propagation {
e.stop_propagation()
}
let report = MarkdownMouseEvent {
position: position.clone(),
mouse_event: e,
};
on_click.map(|x| x.call(report));
})
}
fn set_frontmatter(&mut self, frontmatter: String) {
self.0().frontmatter.as_mut().map(|x| x.set(frontmatter));
}
fn has_custom_links(self) -> bool {
self.0().render_links.is_some()
}
fn render_links(self, link: LinkDescription<Self::View>) -> Result<Self::View, String> {
Ok(self.0().render_links.as_ref().unwrap()(link))
}
fn has_custom_component(self, name: &str) -> bool {
self.0().components.read().get_callback(name).is_some()
}
fn render_custom_component(
self,
name: &str,
input: MdComponentProps,
) -> Result<Self::View, ComponentCreationError> {
let f: Callback<_, _> = self.0()
.components
.read()
.get_callback(name)
.unwrap()
.clone();
f(input)
}
}
#[allow(non_snake_case)]
pub fn Markdown(props: MdProps) -> Element {
let src: String = props.src.to_string();
let signal: Signal<MdProps> = Signal::new(props);
render_markdown(MdContext(signal.into()), &src)
}