use crate::core::component::Context;
use crate::html::{html, Markup};
use crate::{AutoDefault, CowStr};
#[derive(AutoDefault)]
pub struct Favicon(Vec<Item>);
#[derive(Clone, Debug)]
enum Item {
Icon {
rel: &'static str,
href: CowStr,
sizes: Option<CowStr>,
color: Option<CowStr>,
mime: Option<&'static str>,
},
Meta { name: &'static str, content: CowStr },
}
impl Favicon {
pub fn new() -> Self {
Self::default()
}
pub fn with_icon(self, image: impl Into<CowStr>) -> Self {
self.add_icon_item("icon", image.into(), None, None)
}
pub fn with_icon_for_sizes(self, image: impl Into<CowStr>, sizes: impl Into<CowStr>) -> Self {
self.add_icon_item("icon", image.into(), Some(sizes.into()), None)
}
pub fn with_apple_touch_icon(self, image: impl Into<CowStr>, sizes: impl Into<CowStr>) -> Self {
self.add_icon_item("apple-touch-icon", image.into(), Some(sizes.into()), None)
}
pub fn with_mask_icon(self, image: impl Into<CowStr>, color: impl Into<CowStr>) -> Self {
self.add_icon_item("mask-icon", image.into(), None, Some(color.into()))
}
pub fn with_theme_color(mut self, color: impl Into<CowStr>) -> Self {
self.0.push(Item::Meta {
name: "theme-color",
content: color.into(),
});
self
}
pub fn with_ms_tile_color(mut self, color: impl Into<CowStr>) -> Self {
self.0.push(Item::Meta {
name: "msapplication-TileColor",
content: color.into(),
});
self
}
pub fn with_ms_tile_image(mut self, image: impl Into<CowStr>) -> Self {
self.0.push(Item::Meta {
name: "msapplication-TileImage",
content: image.into(),
});
self
}
#[inline]
fn infer_mime(href: &str) -> Option<&'static str> {
let href = href.split_once('#').map(|(s, _)| s).unwrap_or(href);
let href = href.split_once('?').map(|(s, _)| s).unwrap_or(href);
let (_, ext) = href.rsplit_once('.')?;
match ext.len() {
3 if ext.eq_ignore_ascii_case("gif") => Some("image/gif"),
3 if ext.eq_ignore_ascii_case("ico") => Some("image/x-icon"),
3 if ext.eq_ignore_ascii_case("jpg") => Some("image/jpeg"),
3 if ext.eq_ignore_ascii_case("png") => Some("image/png"),
3 if ext.eq_ignore_ascii_case("svg") => Some("image/svg+xml"),
4 if ext.eq_ignore_ascii_case("avif") => Some("image/avif"),
4 if ext.eq_ignore_ascii_case("jpeg") => Some("image/jpeg"),
4 if ext.eq_ignore_ascii_case("webp") => Some("image/webp"),
_ => None,
}
}
fn add_icon_item(
mut self,
icon_rel: &'static str,
icon_source: CowStr,
icon_sizes: Option<CowStr>,
icon_color: Option<CowStr>,
) -> Self {
let mime = Self::infer_mime(icon_source.as_ref());
self.0.push(Item::Icon {
rel: icon_rel,
href: icon_source,
sizes: icon_sizes,
color: icon_color,
mime,
});
self
}
pub fn render(&self, _cx: &mut Context) -> Markup {
html! {
@for item in &self.0 {
@match item {
Item::Icon { rel, href, sizes, color, mime } => {
link
rel=(rel)
type=[*mime]
sizes=[sizes.as_deref()]
color=[color.as_deref()]
href=(href.as_ref());
}
Item::Meta { name, content } => {
meta name=(name) content=(content.as_ref());
}
}
}
}
}
}