1use wasm_bindgen::prelude::*;
16
17use crate::core::{Element, Node, Render, RenderOptions};
18use crate::document::Document;
19use crate::seo::SeoBuilder;
20
21#[wasm_bindgen(js_name = WElement)]
31#[derive(Debug, Clone)]
32pub struct WElement(Element);
33
34#[wasm_bindgen(js_class = WElement)]
35impl WElement {
36 #[wasm_bindgen(constructor)]
38 #[must_use]
39 pub fn new(tag: &str) -> Self {
40 Self(Element::new(tag))
41 }
42
43 #[wasm_bindgen(js_name = setId)]
45 #[must_use]
46 pub fn set_id(self, id: &str) -> Self {
47 Self(self.0.set_id(id))
48 }
49
50 #[wasm_bindgen(js_name = addClass)]
52 #[must_use]
53 pub fn add_class(self, class_name: &str) -> Self {
54 Self(self.0.add_class(class_name))
55 }
56
57 #[wasm_bindgen(js_name = setStyle)]
59 #[must_use]
60 pub fn set_style(self, style: &str) -> Self {
61 Self(self.0.set_style(style))
62 }
63
64 #[must_use]
69 pub fn attr(self, key: &str, value: &str) -> Self {
70 Self(self.0.attr(key, value))
71 }
72
73 #[wasm_bindgen(js_name = trustedUrl)]
77 #[must_use]
78 pub fn trusted_url(self, key: &str, value: &str) -> Self {
79 Self(self.0.trusted_url(key, value))
80 }
81
82 #[wasm_bindgen(js_name = boolAttr)]
84 #[must_use]
85 pub fn bool_attr(self, key: &str) -> Self {
86 Self(self.0.bool_attr(key))
87 }
88
89 #[wasm_bindgen(js_name = dataAttr)]
91 #[must_use]
92 pub fn data_attr(self, key: &str, value: &str) -> Self {
93 Self(self.0.data_attr(key, value))
94 }
95
96 #[wasm_bindgen(js_name = ariaAttr)]
98 #[must_use]
99 pub fn aria_attr(self, key: &str, value: &str) -> Self {
100 Self(self.0.aria_attr(key, value))
101 }
102
103 #[must_use]
105 pub fn text(self, content: &str) -> Self {
106 Self(self.0.text(content))
107 }
108
109 #[wasm_bindgen(js_name = rawText)]
111 #[must_use]
112 pub fn raw_text(self, content: &str) -> Self {
113 Self(self.0.raw_text(content))
114 }
115
116 #[must_use]
118 pub fn child(self, child: &WElement) -> Self {
119 Self(self.0.child(child.0.clone()))
120 }
121
122 #[must_use]
124 pub fn comment(self, content: &str) -> Self {
125 Self(self.0.child(Node::comment(content)))
126 }
127
128 #[must_use]
130 pub fn render(&self) -> String {
131 self.0.render()
132 }
133
134 #[wasm_bindgen(js_name = renderPretty)]
136 #[must_use]
137 pub fn render_pretty(&self) -> String {
138 self.0.render_pretty()
139 }
140}
141
142#[wasm_bindgen(js_name = WDocument)]
144#[derive(Debug, Clone)]
145pub struct WDocument(Document);
146
147#[wasm_bindgen(js_class = WDocument)]
148impl WDocument {
149 #[allow(clippy::needless_pass_by_value)]
153 #[wasm_bindgen(constructor)]
154 #[must_use]
155 pub fn new(lang: Option<String>) -> Self {
156 Self(Document::new(lang.as_deref()))
157 }
158
159 #[wasm_bindgen(js_name = addHead)]
161 #[must_use]
162 pub fn add_head(self, element: &WElement) -> Self {
163 Self(self.0.head_children([element.0.clone()]))
164 }
165
166 #[wasm_bindgen(js_name = addBody)]
168 #[must_use]
169 pub fn add_body(self, element: &WElement) -> Self {
170 Self(self.0.body_children([element.0.clone()]))
171 }
172
173 #[must_use]
175 pub fn render(&self) -> String {
176 self.0.render()
177 }
178
179 #[wasm_bindgen(js_name = renderCompact)]
181 #[must_use]
182 pub fn render_compact(&self) -> String {
183 self.0.render_compact()
184 }
185
186 #[wasm_bindgen(js_name = renderWithIndent)]
188 #[must_use]
189 pub fn render_with_indent(&self, indent: &str) -> String {
190 self.0
191 .render_with(&RenderOptions::pretty().with_indent(indent))
192 }
193}
194
195#[wasm_bindgen(js_name = WSeo)]
197#[derive(Debug, Clone)]
198pub struct WSeo(SeoBuilder);
199
200#[wasm_bindgen(js_class = WSeo)]
201impl WSeo {
202 #[wasm_bindgen(constructor)]
204 #[must_use]
205 pub fn new(title: &str, description: &str) -> Self {
206 Self(SeoBuilder::new(title, description))
207 }
208
209 #[must_use]
211 pub fn image(self, image_url: &str) -> Self {
212 Self(self.0.image(image_url))
213 }
214
215 #[must_use]
217 pub fn url(self, page_url: &str) -> Self {
218 Self(self.0.url(page_url))
219 }
220
221 #[wasm_bindgen(js_name = twitterSite)]
223 #[must_use]
224 pub fn twitter_site(self, site: &str) -> Self {
225 Self(self.0.twitter_site(site))
226 }
227
228 #[must_use]
230 pub fn render(&self) -> String {
231 self.0.build().iter().map(Render::render).collect()
232 }
233}
234
235#[wasm_bindgen]
238#[must_use]
239pub fn element(tag: &str) -> WElement {
240 WElement::new(tag)
241}
242
243#[wasm_bindgen(js_name = escapeText)]
245#[must_use]
246pub fn escape_text(input: &str) -> String {
247 crate::core::escape::escape_text(input)
248}
249
250#[wasm_bindgen]
252#[must_use]
253pub fn version() -> String {
254 env!("CARGO_PKG_VERSION").to_string()
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn trusted_url_skips_the_script_url_check() {
263 assert_eq!(
264 WElement::new("a")
265 .trusted_url("href", "javascript:print()")
266 .render(),
267 "<a href=\"javascript:print()\"></a>"
268 );
269 assert_eq!(
270 WElement::new("a").attr("href", "javascript:x").render(),
271 "<a href=\"#\"></a>"
272 );
273 }
274}