use dioxus::prelude::*;
#[component]
pub fn RichTextEditor(
value: String,
on_input: EventHandler<String>,
placeholder: Option<String>,
disabled: Option<bool>,
) -> Element {
let is_disabled = disabled.unwrap_or(false);
let placeholder_text = placeholder.unwrap_or_else(|| "Send a message...".to_string());
let mut is_preview = use_signal(|| false);
let value_for_bold = value.clone();
let on_bold = {
let on_input = on_input.clone();
move |_| {
let v = value_for_bold.clone();
if v.is_empty() {
on_input.call("****".to_string());
} else {
on_input.call(format!("**{v}**"));
}
}
};
let value_for_italic = value.clone();
let on_italic = {
let on_input = on_input.clone();
move |_| {
let v = value_for_italic.clone();
if v.is_empty() {
on_input.call("__".to_string());
} else {
on_input.call(format!("_{v}_"));
}
}
};
let value_for_strike = value.clone();
let on_strikethrough = {
let on_input = on_input.clone();
move |_| {
let v = value_for_strike.clone();
if v.is_empty() {
on_input.call("~~~~".to_string());
} else {
on_input.call(format!("~~{v}~~"));
}
}
};
let value_for_code = value.clone();
let on_code = {
let on_input = on_input.clone();
move |_| {
let v = value_for_code.clone();
if v.is_empty() {
on_input.call("``".to_string());
} else {
on_input.call(format!("`{v}`"));
}
}
};
let value_for_codeblock = value.clone();
let on_codeblock = {
let on_input = on_input.clone();
move |_| {
let v = value_for_codeblock.clone();
if v.is_empty() {
on_input.call("```\n\n```".to_string());
} else {
on_input.call(format!("```\n{v}\n```"));
}
}
};
let value_for_quote = value.clone();
let on_quote = {
let on_input = on_input.clone();
move |_| {
let v = value_for_quote.clone();
if v.is_empty() {
on_input.call("> ".to_string());
} else {
let quoted = v.lines()
.map(|l| format!("> {l}"))
.collect::<Vec<_>>()
.join("\n");
on_input.call(quoted);
}
}
};
let value_for_list = value.clone();
let on_list = {
let on_input = on_input.clone();
move |_| {
let v = value_for_list.clone();
if v.is_empty() {
on_input.call("- ".to_string());
} else {
let listed = v.lines()
.map(|l| format!("- {l}"))
.collect::<Vec<_>>()
.join("\n");
on_input.call(listed);
}
}
};
let value_for_link = value.clone();
let on_link = {
let on_input = on_input.clone();
move |_| {
let v = value_for_link.clone();
if v.is_empty() {
on_input.call("[text](url)".to_string());
} else {
on_input.call(format!("[{v}](url)"));
}
}
};
let preview_html = if *is_preview.read() {
crate::utils::markdown::markdown_to_html_string(&value)
} else {
String::new()
};
rsx! {
div {
class: "rich-text-editor",
div {
class: "rich-text-editor__toolbar",
button {
class: "rich-text-editor__tool-btn",
title: "Bold (Ctrl+B)",
disabled: is_disabled,
onclick: on_bold,
span { class: "rich-text-editor__tool-icon", "B" }
}
button {
class: "rich-text-editor__tool-btn",
title: "Italic (Ctrl+I)",
disabled: is_disabled,
onclick: on_italic,
span { class: "rich-text-editor__tool-icon rich-text-editor__tool-icon--italic", "I" }
}
button {
class: "rich-text-editor__tool-btn",
title: "Strikethrough",
disabled: is_disabled,
onclick: on_strikethrough,
span { class: "rich-text-editor__tool-icon rich-text-editor__tool-icon--strike", "S" }
}
div { class: "rich-text-editor__toolbar-divider" }
button {
class: "rich-text-editor__tool-btn",
title: "Inline code",
disabled: is_disabled,
onclick: on_code,
"<>"
}
button {
class: "rich-text-editor__tool-btn",
title: "Code block",
disabled: is_disabled,
onclick: on_codeblock,
"[]"
}
div { class: "rich-text-editor__toolbar-divider" }
button {
class: "rich-text-editor__tool-btn",
title: "Quote",
disabled: is_disabled,
onclick: on_quote,
"\""
}
button {
class: "rich-text-editor__tool-btn",
title: "Bullet list",
disabled: is_disabled,
onclick: on_list,
"•"
}
button {
class: "rich-text-editor__tool-btn",
title: "Link",
disabled: is_disabled,
onclick: on_link,
"🔗"
}
div { class: "rich-text-editor__toolbar-spacer" }
button {
class: if *is_preview.read() { "rich-text-editor__tool-btn rich-text-editor__tool-btn--active" } else { "rich-text-editor__tool-btn" },
title: "Toggle preview",
onclick: move |_| {
let current = *is_preview.read();
is_preview.set(!current);
},
"👁"
}
}
if *is_preview.read() {
div {
class: "rich-text-editor__preview",
dangerous_inner_html: "{preview_html}",
}
} else {
textarea {
class: "rich-text-editor__input",
placeholder: "{placeholder_text}",
value: "{value}",
oninput: move |evt| on_input.call(evt.value()),
disabled: is_disabled,
}
}
}
}
}