use maud::{Markup, PreEscaped, html};
use crate::components::attrs::escape_attr;
#[cfg(feature = "plugin-users")]
use crate::components::container::container_error;
use crate::components::swap::hx_nav_app_layout_for_url;
fn is_external_href(href: &str) -> bool {
href.starts_with("http://")
|| href.starts_with("https://")
|| href.starts_with("mailto:")
|| href.starts_with("tel:")
}
pub struct FieldTitle<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_title(opts: FieldTitle<'_>) -> Markup {
let class = format!("text-xl font-semibold text-primary {}", opts.classes);
html! { div class=(class) { (opts.value) } }
}
pub struct FieldSubtitle<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_subtitle(opts: FieldSubtitle<'_>) -> Markup {
let class = if opts.classes.is_empty() {
"text-md text-gray-500".to_string()
} else {
opts.classes.to_string()
};
html! { div class=(class) { (opts.value) } }
}
pub struct FieldText<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_text(opts: FieldText<'_>) -> Markup {
html! { div class=(opts.classes) { (opts.value) } }
}
pub struct FieldTextarea<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_textarea(opts: FieldTextarea<'_>) -> Markup {
html! { div class=(format!("whitespace-pre-wrap {}", opts.classes)) { (opts.value) } }
}
pub struct FieldCheckbox<'a> {
pub checked: bool,
pub classes: &'a str,
}
pub fn field_checkbox(opts: FieldCheckbox<'_>) -> Markup {
let (name, classes) = if opts.checked {
("check-circle", "text-success")
} else {
("x-circle", "text-error")
};
html! {
span {
(crate::components::text::icon(name, classes))
}
}
}
pub struct FieldLink<'a> {
pub href: &'a str,
pub label: &'a str,
pub classes: &'a str,
}
pub fn field_link(opts: FieldLink<'_>) -> Markup {
let class = if opts.classes.is_empty() {
"link link-primary".to_string()
} else {
opts.classes.to_string()
};
let hx = if opts.href.starts_with('/') && !is_external_href(opts.href) {
hx_nav_app_layout_for_url(opts.href).as_string()
} else {
String::new()
};
html! {
(PreEscaped(format!(
r#"<a href="{}" class="{}"{}>"#,
escape_attr(opts.href),
escape_attr(&class),
hx,
)))
(opts.label)
(PreEscaped("</a>"))
}
}
pub struct FieldPhone<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_phone(opts: FieldPhone<'_>) -> Markup {
#[cfg(feature = "plugin-users")]
{
match phonenumber::parse(Some(phonenumber::country::IN), opts.value) {
Ok(parsed) => {
let formatted = parsed.format().mode(phonenumber::Mode::E164).to_string();
html! { div class=(opts.classes) { (formatted) } }
}
Err(err) => {
let msg = err.to_string();
container_error(Some(&msg), Markup::default())
}
}
}
#[cfg(not(feature = "plugin-users"))]
{
html! { div class=(opts.classes) { (opts.value) } }
}
}
pub struct FieldDate<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_date(opts: FieldDate<'_>) -> Markup {
html! { div class=(opts.classes) { (opts.value) } }
}
pub struct FieldTime<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_time(opts: FieldTime<'_>) -> Markup {
html! { div class=(opts.classes) { (opts.value) } }
}
pub struct FieldDatetime<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_datetime(opts: FieldDatetime<'_>) -> Markup {
html! { div class=(opts.classes) { (opts.value) } }
}
pub struct FieldDuration<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_duration(opts: FieldDuration<'_>) -> Markup {
html! { div class=(opts.classes) { (opts.value) } }
}
pub struct FieldManyToMany<'a> {
pub items: &'a [(&'a str, Option<&'a str>)],
pub classes: &'a str,
}
pub fn field_many_to_many(opts: FieldManyToMany<'_>) -> Markup {
html! {
div class=(format!("flex flex-wrap gap-2 {}", opts.classes)) {
@for (label, href) in opts.items {
@if let Some(url) = href {
(PreEscaped(format!(
r#"<a href="{}" class="badge badge-outline"{}>"#,
escape_attr(url),
if url.starts_with('/') && !is_external_href(url) {
hx_nav_app_layout_for_url(url).as_string()
} else {
String::new()
},
)))
(label)
(PreEscaped("</a>"))
} @else {
span class="badge badge-outline" { (label) }
}
}
}
}
}
pub struct FieldMarkdown<'a> {
pub value: &'a str,
pub classes: &'a str,
}
pub fn field_markdown(opts: FieldMarkdown<'_>) -> Markup {
if opts.value.is_empty() {
return html! {};
}
let rendered = crate::components::markdown::render_markdown(opts.value);
let class = format!(
"block w-full min-w-0 max-w-full break-words overflow-x-hidden border border-base-300 p-2 rounded-md {}",
opts.classes
);
html! {
div class=(class) {
(PreEscaped(rendered))
}
}
}