use wasm_bindgen::prelude::*;
use crate::core::{Element, Node, Render, RenderOptions};
use crate::document::Document;
use crate::seo::SeoBuilder;
#[wasm_bindgen(js_name = WElement)]
#[derive(Debug, Clone)]
pub struct WElement(Element);
#[wasm_bindgen(js_class = WElement)]
impl WElement {
#[wasm_bindgen(constructor)]
#[must_use]
pub fn new(tag: &str) -> Self {
Self(Element::new(tag))
}
#[wasm_bindgen(js_name = setId)]
#[must_use]
pub fn set_id(self, id: &str) -> Self {
Self(self.0.set_id(id))
}
#[wasm_bindgen(js_name = addClass)]
#[must_use]
pub fn add_class(self, class_name: &str) -> Self {
Self(self.0.add_class(class_name))
}
#[wasm_bindgen(js_name = setStyle)]
#[must_use]
pub fn set_style(self, style: &str) -> Self {
Self(self.0.set_style(style))
}
#[must_use]
pub fn attr(self, key: &str, value: &str) -> Self {
Self(self.0.attr(key, value))
}
#[wasm_bindgen(js_name = trustedUrl)]
#[must_use]
pub fn trusted_url(self, key: &str, value: &str) -> Self {
Self(self.0.trusted_url(key, value))
}
#[wasm_bindgen(js_name = boolAttr)]
#[must_use]
pub fn bool_attr(self, key: &str) -> Self {
Self(self.0.bool_attr(key))
}
#[wasm_bindgen(js_name = dataAttr)]
#[must_use]
pub fn data_attr(self, key: &str, value: &str) -> Self {
Self(self.0.data_attr(key, value))
}
#[wasm_bindgen(js_name = ariaAttr)]
#[must_use]
pub fn aria_attr(self, key: &str, value: &str) -> Self {
Self(self.0.aria_attr(key, value))
}
#[must_use]
pub fn text(self, content: &str) -> Self {
Self(self.0.text(content))
}
#[wasm_bindgen(js_name = rawText)]
#[must_use]
pub fn raw_text(self, content: &str) -> Self {
Self(self.0.raw_text(content))
}
#[must_use]
pub fn child(self, child: WElement) -> Self {
Self(self.0.child(child.0))
}
#[wasm_bindgen(js_name = addClasses)]
#[must_use]
pub fn add_classes(self, classes: Vec<String>) -> Self {
Self(self.0.add_classes(classes))
}
#[wasm_bindgen(js_name = rawChild)]
#[must_use]
pub fn raw_child(self, markup: &str) -> Self {
Self(self.0.child(Node::raw(markup)))
}
#[wasm_bindgen(js_name = selfClosing)]
#[must_use]
pub fn self_closing(self, yes: bool) -> Self {
Self(self.0.self_closing(yes))
}
#[must_use]
pub fn comment(self, content: &str) -> Self {
Self(self.0.child(Node::comment(content)))
}
#[must_use]
pub fn render(&self) -> String {
self.0.render()
}
#[wasm_bindgen(js_name = renderPretty)]
#[must_use]
pub fn render_pretty(&self) -> String {
self.0.render_pretty()
}
#[wasm_bindgen(js_name = renderWithOptions)]
#[must_use]
pub fn render_with_options(&self, pretty: bool, indent: &str, xhtml: bool) -> String {
self.0.render_with(&options(pretty, indent, xhtml))
}
#[wasm_bindgen(js_name = renderBytes)]
#[must_use]
pub fn render_bytes(&self) -> Vec<u8> {
self.0.render().into_bytes()
}
}
fn options(pretty: bool, indent: &str, xhtml: bool) -> RenderOptions {
let base = if pretty {
RenderOptions::pretty()
} else {
RenderOptions::compact()
};
base.with_indent(indent).with_xhtml_self_closing(xhtml)
}
#[wasm_bindgen(js_name = WDocument)]
#[derive(Debug, Clone)]
pub struct WDocument(Document);
#[wasm_bindgen(js_class = WDocument)]
impl WDocument {
#[allow(clippy::needless_pass_by_value)]
#[wasm_bindgen(constructor)]
#[must_use]
pub fn new(lang: Option<String>) -> Self {
Self(Document::new(lang.as_deref()))
}
#[wasm_bindgen(js_name = addHead)]
#[must_use]
pub fn add_head(self, element: WElement) -> Self {
Self(self.0.head_children([element.0]))
}
#[wasm_bindgen(js_name = addBody)]
#[must_use]
pub fn add_body(self, element: WElement) -> Self {
Self(self.0.body_children([element.0]))
}
#[allow(clippy::needless_pass_by_value)]
#[wasm_bindgen(js_name = addSeo)]
#[must_use]
pub fn add_seo(self, seo: WSeo) -> Self {
Self(self.0.head_children(seo.0.build()))
}
#[must_use]
pub fn render(&self) -> String {
self.0.render()
}
#[wasm_bindgen(js_name = renderCompact)]
#[must_use]
pub fn render_compact(&self) -> String {
self.0.render_compact()
}
#[wasm_bindgen(js_name = renderWithIndent)]
#[must_use]
pub fn render_with_indent(&self, indent: &str) -> String {
self.0
.render_with(&RenderOptions::pretty().with_indent(indent))
}
#[wasm_bindgen(js_name = renderWithOptions)]
#[must_use]
pub fn render_with_options(&self, pretty: bool, indent: &str, xhtml: bool) -> String {
self.0.render_with(&options(pretty, indent, xhtml))
}
}
#[wasm_bindgen(js_name = WSeo)]
#[derive(Debug, Clone)]
pub struct WSeo(SeoBuilder);
#[wasm_bindgen(js_class = WSeo)]
impl WSeo {
#[wasm_bindgen(constructor)]
#[must_use]
pub fn new(title: &str, description: &str) -> Self {
Self(SeoBuilder::new(title, description))
}
#[must_use]
pub fn image(self, image_url: &str) -> Self {
Self(self.0.image(image_url))
}
#[must_use]
pub fn url(self, page_url: &str) -> Self {
Self(self.0.url(page_url))
}
#[wasm_bindgen(js_name = twitterSite)]
#[must_use]
pub fn twitter_site(self, site: &str) -> Self {
Self(self.0.twitter_site(site))
}
#[must_use]
pub fn render(&self) -> String {
self.0.build().iter().map(Render::render).collect()
}
}
#[wasm_bindgen]
#[must_use]
pub fn element(tag: &str) -> WElement {
WElement::new(tag)
}
#[wasm_bindgen(js_name = jsonLd)]
#[must_use]
pub fn json_ld(json: &str) -> WElement {
WElement(crate::seo::json_ld(json))
}
#[wasm_bindgen(js_name = escapeText)]
#[must_use]
pub fn escape_text(input: &str) -> String {
crate::core::escape::escape_text(input).into_owned()
}
#[wasm_bindgen]
#[must_use]
pub fn version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trusted_url_skips_the_script_url_check() {
assert_eq!(
WElement::new("a")
.trusted_url("href", "javascript:print()")
.render(),
"<a href=\"javascript:print()\"></a>"
);
assert_eq!(
WElement::new("a").attr("href", "javascript:x").render(),
"<a href=\"#\"></a>"
);
}
}