diff --git a/src/template_utils.rs b/src/template_utils.rs
index 697084c1..1e0b9fee 100644
--- a/src/template_utils.rs
+++ b/src/template_utils.rs
@@ -5,7 +5,7 @@ use rocket::http::{Method, Status};
use rocket::request::Request;
use rocket::response::{self, content::Html as HtmlCt, Responder, Response};
use rocket_i18n::Catalog;
-use std::collections::hash_map::DefaultHasher;
+use std::collections::{btree_map::BTreeMap, hash_map::DefaultHasher};
use std::hash::Hasher;
use templates::Html;
@@ -231,11 +231,108 @@ macro_rules! icon {
};
}
-macro_rules! input {
- ($catalog:expr, $name:tt ($kind:tt), $label:expr, $optional:expr, $details:expr, $form:expr, $err:expr, $props:expr) => {{
- use std::borrow::Cow;
- use validator::ValidationErrorsKind;
- let cat = $catalog;
+/// A builder type to generate `` tags in a type-safe way.
+///
+/// # Example
+///
+/// This example uses all options, but you don't have to specify everything.
+///
+/// ```rust
+/// # let current_email = "foo@bar.baz";
+/// # let catalog = gettext::Catalog::parse("").unwrap();
+/// Input::new("mail", "Your email address")
+/// .input_type("email")
+/// .default(current_email)
+/// .optional()
+/// .details("We won't use it for advertising.")
+/// .set_prop("class", "email-input")
+/// .to_html(catalog);
+/// ```
+pub struct Input {
+ /// The name of the input (`name` and `id` in HTML).
+ name: String,
+ /// The description of this field.
+ label: String,
+ /// The `type` of the input (`text`, `email`, `password`, etc).
+ input_type: String,
+ /// The default value for this input field.
+ default: Option,
+ /// `true` if this field is not required (will add a little badge next to the label).
+ optional: bool,
+ /// A small message to display next to the label.
+ details: Option,
+ /// Additional HTML properties.
+ props: BTreeMap,
+ /// The error message to show next to this field.
+ error: Option,
+}
+
+impl Input {
+ /// Creates a new input with a given name.
+ pub fn new(name: impl ToString, label: impl ToString) -> Input {
+ Input {
+ name: name.to_string(),
+ label: label.to_string(),
+ input_type: "text".into(),
+ default: None,
+ optional: false,
+ details: None,
+ props: BTreeMap::new(),
+ error: None,
+ }
+ }
+
+ /// Set the `type` of this input.
+ pub fn input_type(mut self, t: impl ToString) -> Input {
+ self.input_type = t.to_string();
+ self
+ }
+
+ /// Marks this field as optional.
+ pub fn optional(mut self) -> Input {
+ self.optional = true;
+ self
+ }
+
+ /// Fills the input with a default value (useful for edition form, to show the current values).
+ pub fn default(mut self, val: impl ToString) -> Input {
+ self.default = Some(val.to_string());
+ self
+ }
+
+ /// Adds additional information next to the label.
+ pub fn details(mut self, text: impl ToString) -> Input {
+ self.details = Some(text.to_string());
+ self
+ }
+
+ /// Defines an additional HTML property.
+ ///
+ /// This method can be called multiple times for the same input.
+ pub fn set_prop(mut self, key: impl ToString, val: impl ToString) -> Input {
+ self.props.insert(key.to_string(), val.to_string());
+ self
+ }
+
+ /// Shows an error message
+ pub fn error(mut self, errs: &validator::ValidationErrors) -> Input {
+ if let Some(field_errs) = errs.clone().field_errors().get(self.name.as_str()) {
+ self.error = Some(
+ field_errs[0]
+ .message
+ .clone()
+ .unwrap_or_default()
+ .to_string(),
+ );
+ }
+ self
+ }
+
+ /// Returns the HTML markup for this field.
+ pub fn html(mut self, cat: &Catalog) -> Html {
+ if !self.optional {
+ self = self.set_prop("required", true);
+ }
Html(format!(
r#"
@@ -247,98 +344,30 @@ macro_rules! input {
{error}
"#,
- name = stringify!($name),
- label = i18n!(cat, $label),
- kind = stringify!($kind),
- optional = if $optional {
+ name = self.name,
+ label = self.label,
+ kind = self.input_type,
+ optional = if self.optional {
format!("{}", i18n!(cat, "Optional"))
} else {
String::new()
},
- details = if $details.len() > 0 {
- format!("{}", i18n!(cat, $details))
- } else {
- String::new()
- },
- error = if let Some(ValidationErrorsKind::Field(errs)) =
- $err.errors().get(stringify!($name))
- {
- format!(
- r#"{}
"#,
- errs[0]
- .message
- .clone()
- .unwrap_or(Cow::from("Unknown error"))
- )
- } else {
- String::new()
- },
- val = escape(&$form.$name),
- props = $props
+ details = self
+ .details
+ .map(|d| format!("{}", d))
+ .unwrap_or_default(),
+ error = self
+ .error
+ .map(|e| format!(r#"{}
"#, e))
+ .unwrap_or_default(),
+ val = escape(&self.default.unwrap_or_default()),
+ props = self
+ .props
+ .into_iter()
+ .fold(String::new(), |mut res, (key, val)| {
+ res.push_str(&format!("{}=\"{}\" ", key, val));
+ res
+ })
))
- }};
- ($catalog:expr, $name:tt (optional $kind:tt), $label:expr, $details:expr, $form:expr, $err:expr, $props:expr) => {
- input!(
- $catalog,
- $name($kind),
- $label,
- true,
- $details,
- $form,
- $err,
- $props
- )
- };
- ($catalog:expr, $name:tt (optional $kind:tt), $label:expr, $form:expr, $err:expr, $props:expr) => {
- input!(
- $catalog,
- $name($kind),
- $label,
- true,
- "",
- $form,
- $err,
- $props
- )
- };
- ($catalog:expr, $name:tt ($kind:tt), $label:expr, $details:expr, $form:expr, $err:expr, $props:expr) => {
- input!(
- $catalog,
- $name($kind),
- $label,
- false,
- $details,
- $form,
- $err,
- $props
- )
- };
- ($catalog:expr, $name:tt ($kind:tt), $label:expr, $form:expr, $err:expr, $props:expr) => {
- input!(
- $catalog,
- $name($kind),
- $label,
- false,
- "",
- $form,
- $err,
- $props
- )
- };
- ($catalog:expr, $name:tt ($kind:tt), $label:expr, $form:expr, $err:expr) => {
- input!($catalog, $name($kind), $label, false, "", $form, $err, "")
- };
- ($catalog:expr, $name:tt ($kind:tt), $label:expr, $props:expr) => {{
- let cat = $catalog;
- Html(format!(
- r#"
-
-
- "#,
- name = stringify!($name),
- label = i18n!(cat, $label),
- kind = stringify!($kind),
- props = $props
- ))
- }};
+ }
}
diff --git a/templates/blogs/edit.rs.html b/templates/blogs/edit.rs.html
index 0b1febbf..fa14a106 100644
--- a/templates/blogs/edit.rs.html
+++ b/templates/blogs/edit.rs.html
@@ -19,7 +19,11 @@
- @input!(ctx.1, title (text), "Title", form, errors.clone(), "minlenght=\"1\"")
+ @(Input::new("title", i18n!(ctx.1, "Title"))
+ .default(&form.title)
+ .error(&errors)
+ .set_prop("minlenght", 1)
+ .html(ctx.1))
diff --git a/templates/blogs/new.rs.html b/templates/blogs/new.rs.html
index b9a186b1..8480814b 100644
--- a/templates/blogs/new.rs.html
+++ b/templates/blogs/new.rs.html
@@ -9,7 +9,11 @@
@:base(ctx, i18n!(ctx.1, "New Blog"), {}, {}, {
@i18n!(ctx.1, "Create a blog")
})
diff --git a/templates/instance/admin.rs.html b/templates/instance/admin.rs.html
index 50bde423..db4441c3 100644
--- a/templates/instance/admin.rs.html
+++ b/templates/instance/admin.rs.html
@@ -17,7 +17,11 @@
])
diff --git a/templates/medias/new.rs.html b/templates/medias/new.rs.html
index b49b998d..5b3b4e5c 100644
--- a/templates/medias/new.rs.html
+++ b/templates/medias/new.rs.html
@@ -7,22 +7,19 @@
@:base(ctx, i18n!(ctx.1, "Media upload"), {}, {}, {
@i18n!(ctx.1, "Media upload")
diff --git a/templates/posts/details.rs.html b/templates/posts/details.rs.html
index 7a67b40c..17829633 100644
--- a/templates/posts/details.rs.html
+++ b/templates/posts/details.rs.html
@@ -144,7 +144,11 @@
@if ctx.2.is_some() {
- @input!(ctx.1, tags (optional text), "Tags, separated by commas", form, errors.clone(), "")
-
- @input!(ctx.1, license (optional text), "License", "Leave it empty to reserve all rights", form, errors, "")
+ @(Input::new("tags", i18n!(ctx.1, "Tags, separated by commas"))
+ .default(&form.tags)
+ .error(&errors)
+ .optional()
+ .html(ctx.1))
+ @(Input::new("license", i18n!(ctx.1, "License"))
+ .default(&form.license)
+ .error(&errors)
+ .optional()
+ .details("Leave it empty to reserve all rights")
+ .html(ctx.1))
@:image_select(ctx, "cover", i18n!(ctx.1, "Illustration"), true, medias, form.cover)
diff --git a/templates/remote_interact_base.rs.html b/templates/remote_interact_base.rs.html
index ea7b2b50..c5fc03f9 100644
--- a/templates/remote_interact_base.rs.html
+++ b/templates/remote_interact_base.rs.html
@@ -16,15 +16,28 @@
diff --git a/templates/search/index.rs.html b/templates/search/index.rs.html
index a10a72ae..5f0463a2 100644
--- a/templates/search/index.rs.html
+++ b/templates/search/index.rs.html
@@ -6,21 +6,48 @@
@:base(ctx, i18n!(ctx.1, "Search"), {}, {}, {
@i18n!(ctx.1, "Search")
diff --git a/templates/session/login.rs.html b/templates/session/login.rs.html
index a8448932..c38bc059 100644
--- a/templates/session/login.rs.html
+++ b/templates/session/login.rs.html
@@ -12,8 +12,17 @@
@message
}
Forgot your password?
diff --git a/templates/session/password_reset.rs.html b/templates/session/password_reset.rs.html
index be450cfc..918b9057 100644
--- a/templates/session/password_reset.rs.html
+++ b/templates/session/password_reset.rs.html
@@ -9,8 +9,18 @@
@i18n!(ctx.1, "Reset your password")
})
diff --git a/templates/session/password_reset_request.rs.html b/templates/session/password_reset_request.rs.html
index f75431bb..55d2176c 100644
--- a/templates/session/password_reset_request.rs.html
+++ b/templates/session/password_reset_request.rs.html
@@ -9,7 +9,12 @@
@i18n!(ctx.1, "Reset your password")
})
diff --git a/templates/users/edit.rs.html b/templates/users/edit.rs.html
index 7dce82bd..0ffb969d 100644
--- a/templates/users/edit.rs.html
+++ b/templates/users/edit.rs.html
@@ -18,8 +18,15 @@
- @input!(ctx.1, display_name (text), "Display name", form, errors.clone())
- @input!(ctx.1, email (text), "Email", form, errors.clone())
+ @(Input::new("display_name", i18n!(ctx.1, ""))
+ .default(&form.display_name)
+ .error(&errors)
+ .html(ctx.1))
+ @(Input::new("email", i18n!(ctx.1, ""))
+ .default(&form.email)
+ .error(&errors)
+ .input_type("email")
+ .html(ctx.1))
diff --git a/templates/users/new.rs.html b/templates/users/new.rs.html
index b7f5a520..b75168da 100644
--- a/templates/users/new.rs.html
+++ b/templates/users/new.rs.html
@@ -11,13 +11,32 @@
@if enabled {
@i18n!(ctx.1, "Create an account")