use alloc::rc::Rc;
use alloc::string::String;
use core::cell::Cell;
use crate::line::tachys;
use mf2_catalog::{Catalog, Dir, MsgId};
use tachys::html::attribute::AttributeValue;
use tachys::renderer::types::{Element, Node, Text};
use tachys::renderer::{CastFrom, Rndr};
use crate::leptos::state;
#[derive(Clone, Copy)]
pub(crate) struct Lender<'a> {
tag: &'a str,
dir: Option<Dir>,
}
impl<'a> Lender<'a> {
pub(crate) fn of(catalog: &'a Catalog, id: MsgId) -> Option<Lender<'a>> {
let tag = catalog.fallback_locale(id)?;
let dir = state::dir_of(tag).filter(|d| *d != catalog.dir());
Some(Lender { tag, dir })
}
fn dir_attr(self) -> Option<&'static str> {
self.dir.map(|d| match d {
Dir::Rtl => "rtl",
Dir::Ltr => "ltr",
Dir::Auto => "auto",
})
}
fn apply(self, span: &Element) {
Rndr::set_attribute(span, "lang", self.tag);
match self.dir_attr() {
Some(dir) => Rndr::set_attribute(span, "dir", dir),
None => Rndr::remove_attribute(span, "dir"),
}
}
pub(crate) fn wrap(
self,
root: alloc::vec::Vec<tachys::view::any_view::AnyView>,
) -> tachys::view::any_view::AnyView {
use tachys::html::attribute::global::GlobalAttributes;
use tachys::html::element::ElementChild;
use tachys::view::any_view::IntoAny;
tachys::html::element::span()
.lang(String::from(self.tag))
.dir(self.dir_attr())
.child(root)
.into_any()
}
}
pub(crate) fn write_open(buf: &mut String, lender: Lender<'_>) {
buf.push_str("<span");
<&str as AttributeValue>::to_html(lender.tag, "lang", buf);
if let Some(dir) = lender.dir_attr() {
<&str as AttributeValue>::to_html(dir, "dir", buf);
}
buf.push('>');
}
const TEXT_ONLY: &[&str] = &["textarea", "title", "option", "script", "style"];
pub(crate) fn is_text_only(name: &str) -> bool {
TEXT_ONLY.iter().any(|t| t.eq_ignore_ascii_case(name))
}
fn holds_text_only(el: &Element) -> bool {
is_text_only(&el.tag_name())
}
pub(crate) fn open_tag(buf: &str) -> Option<&str> {
let tag = buf.strip_suffix('>')?;
let tag = &tag[tag.rfind('<')? + 1..];
let name = tag.split(|c: char| c.is_ascii_whitespace()).next()?;
(!name.is_empty() && name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-'))
.then_some(name)
}
#[derive(Clone, Default)]
pub(crate) struct Wrapper(Rc<Cell<Option<Element>>>);
impl Wrapper {
pub(crate) fn get(&self) -> Option<Element> {
let el = self.0.take();
self.0.set(el.clone());
el
}
pub(crate) fn outer(&self, text: &Text) -> Node {
match self.get() {
Some(el) => el.into(),
None => text.clone().into(),
}
}
pub(crate) fn adopt(&self, node: Node) -> Option<Text> {
if let Some(text) = Text::cast_from(node.clone()) {
return Some(text);
}
let el = Element::cast_from(node)?;
let text = Rndr::first_child(el.as_ref())
.and_then(Text::cast_from)
.unwrap_or_else(|| {
let text = Rndr::create_text_node("");
Rndr::insert_node(&el, text.as_ref(), None);
text
});
self.0.set(Some(el));
Some(text)
}
pub(crate) fn mount_into(&self, parent: &Element, text: &Text) -> Node {
if self.get().is_some() && holds_text_only(parent) {
self.fit(text, None);
}
self.outer(text)
}
pub(crate) fn fit(&self, text: &Text, lender: Option<Lender<'_>>) {
match (self.0.take(), lender) {
(None, None) => {}
(None, Some(lender)) => {
let parent = Rndr::get_parent(text.as_ref()).and_then(Element::cast_from);
if parent.as_ref().is_some_and(holds_text_only) {
return;
}
let span = Rndr::create_element("span", None);
lender.apply(&span);
if let Some(parent) = parent {
Rndr::insert_node(&parent, span.as_ref(), Some(text.as_ref()));
}
Rndr::insert_node(&span, text.as_ref(), None);
self.0.set(Some(span));
}
(Some(span), Some(lender)) => {
lender.apply(&span);
self.0.set(Some(span));
}
(Some(span), None) => {
match Rndr::get_parent(span.as_ref()).and_then(Element::cast_from) {
Some(parent) => Rndr::insert_node(&parent, text.as_ref(), Some(span.as_ref())),
None => {
let _ = Rndr::remove_node(&span, text.as_ref());
}
}
Rndr::remove(span.as_ref());
}
}
}
}
#[cfg(test)]
mod tests {
use super::{is_text_only, open_tag};
#[test]
fn the_parent_s_tag_is_read_from_what_was_just_written() {
assert_eq!(
open_tag("<p><textarea name=\"a b\" rows=\"2\">"),
Some("textarea")
);
assert_eq!(open_tag("<p>"), Some("p"));
assert_eq!(open_tag("<p>text"), None);
assert_eq!(open_tag("<!>"), None);
assert!(is_text_only("TEXTAREA") && is_text_only("title") && !is_text_only("span"));
}
}