Skip to main content

privatus_analytics/
maud.rs

1//! maud support: [`SafeHtml`] and [`Tag`] implement [`maud::Render`].
2//!
3//! ```
4//! use maud::html;
5//! use privatus_analytics::{click_attr, event_attrs, maud::element, props, Config};
6//!
7//! let config = Config::new().site_id("pa_YOURSITEID").enabled(true);
8//! let page = html! {
9//!     head { (config.tag()) }
10//!     body {
11//!         (element("button", &event_attrs("Report exported", props! { "format" => "csv" }), html! { "Export" }))
12//!         (element("a", &click_attr("pricing.cta"), "See plans"))
13//!     }
14//! };
15//! assert!(page.into_string().contains(r#"<button data-privatus-event="Report exported" data-privatus-prop-format="csv">Export</button>"#));
16//! ```
17//!
18//! maud does not let you add attributes from a string inside an element, so
19//! use [`element`] to wrap content in a tag with the attributes. When the
20//! names are fixed you can also write them in maud as usual:
21//! `button data-privatus-event="Report exported" { "Export" }`.
22
23use crate::html::{escape_html, SafeHtml};
24use crate::tag::Tag;
25use maud::{Markup, PreEscaped, Render};
26
27impl Render for SafeHtml {
28    fn render_to(&self, buffer: &mut String) {
29        buffer.push_str(self.as_str());
30    }
31}
32
33impl Render for Tag<'_> {
34    fn render_to(&self, buffer: &mut String) {
35        buffer.push_str(self.to_html().as_str());
36    }
37}
38
39/// An element with the given attributes around `content`, for example
40/// `element("button", &event_attrs(...), "Export")`.
41///
42/// `name` must be a plain tag name (letters, digits and `-`). Any other name
43/// renders the content with no tag around it.
44pub fn element(name: &str, attrs: &SafeHtml, content: impl Render) -> Markup {
45    let valid = !name.is_empty()
46        && name.starts_with(|c: char| c.is_ascii_alphabetic())
47        && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-');
48    let content = content.render().into_string();
49    if !valid {
50        return PreEscaped(content);
51    }
52    let name = escape_html(name);
53    let space = if attrs.is_empty() { "" } else { " " };
54    PreEscaped(format!("<{name}{space}{attrs}>{content}</{name}>"))
55}