use plume_models::{notifications::*, users::User, Connection, PlumeRocket}; use crate::templates::Html; 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::{btree_map::BTreeMap, hash_map::DefaultHasher}; use std::hash::Hasher; pub use askama_escape::escape; pub static CACHE_NAME: &str = env!("CACHE_ID"); pub type BaseContext<'a> = &'a ( &'a Connection, &'a Catalog, Option, Option<(String, String)>, ); pub trait IntoContext { fn to_context( &self, ) -> ( &Connection, &Catalog, Option, Option<(String, String)>, ); } impl IntoContext for PlumeRocket { fn to_context( &self, ) -> ( &Connection, &Catalog, Option, Option<(String, String)>, ) { ( &*self.conn, &self.intl.catalog, self.user.clone(), self.flash_msg.clone(), ) } } #[derive(Debug)] pub struct Ructe(pub Vec); impl<'r> Responder<'r> for Ructe { fn respond_to(self, r: &Request<'_>) -> response::Result<'r> { //if method is not Get or page contain a form, no caching if r.method() != Method::Get || self.0.windows(6).any(|w| w == b"
{ { use crate::templates; let mut res = vec![]; templates::$group::$page( &mut res, $( $param ),* ).unwrap(); Ructe(res) } } } pub fn translate_notification(ctx: BaseContext<'_>, notif: Notification) -> String { let name = notif.get_actor(ctx.0).unwrap().name(); match notif.kind.as_ref() { notification_kind::COMMENT => i18n!(ctx.1, "{0} commented on your article."; &name), notification_kind::FOLLOW => i18n!(ctx.1, "{0} is subscribed to you."; &name), notification_kind::LIKE => i18n!(ctx.1, "{0} liked your article."; &name), notification_kind::MENTION => i18n!(ctx.1, "{0} mentioned you."; &name), notification_kind::RESHARE => i18n!(ctx.1, "{0} boosted your article."; &name), _ => unreachable!("translate_notification: Unknow type"), } } pub fn i18n_timeline_name(cat: &Catalog, tl: &str) -> String { match tl { "Your feed" => i18n!(cat, "Your feed"), "Local feed" => i18n!(cat, "Local feed"), "Federated feed" => i18n!(cat, "Federated feed"), n => n.to_string(), } } pub enum Size { Small, Medium, } impl Size { fn as_str(&self) -> &'static str { match self { Size::Small => "small", Size::Medium => "medium", } } } pub fn avatar( conn: &Connection, user: &User, size: Size, pad: bool, catalog: &Catalog, ) -> Html { let name = escape(&user.name()).to_string(); Html(format!( r#"
"#, size = size.as_str(), padded = if pad { "padded" } else { "" }, url = user.avatar_url(conn), title = i18n!(catalog, "{0}'s avatar"; name), )) } pub fn tabs(links: &[(impl AsRef, String, bool)]) -> Html { let mut res = String::from(r#"
"#); for (url, title, selected) in links { res.push_str(r#""#); } else { res.push_str("\">"); } res.push_str(title); res.push_str(""); } res.push_str("
"); Html(res) } pub fn paginate(catalog: &Catalog, page: i32, total: i32) -> Html { paginate_param(catalog, page, total, None) } pub fn paginate_param( catalog: &Catalog, page: i32, total: i32, param: Option, ) -> Html { let mut res = String::new(); let param = param .map(|mut p| { p.push('&'); p }) .unwrap_or_default(); res.push_str(r#""); Html(res) } pub fn encode_query_param(param: &str) -> String { param .chars() .map(|c| match c { '+' => Ok("%2B"), ' ' => Err('+'), c => Err(c), }) .fold(String::new(), |mut s, r| { match r { Ok(r) => s.push_str(r), Err(r) => s.push(r), }; s }) } #[macro_export] macro_rules! icon { ($name:expr) => { Html(concat!( r#"" )) }; } /// 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#" {error} "#, name = self.name, label = self.label, kind = self.input_type, optional = if self.optional { format!("{}", i18n!(cat, "Optional")) } else { String::new() }, 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 }) )) } }