use crate::markdown::is_numeric; use serde::{Deserialize, Serialize}; use serde_valid::Validate; use std::fmt::Display; #[derive(Serialize, Deserialize)] pub struct Entry { pub slug: String, pub edit_code: String, pub salt: String, pub created: usize, pub edited: usize, pub content: String, #[serde(default)] pub metadata: String, /// The IP address of the last editor of the entry. #[serde(default)] pub last_edit_from: String, } #[derive(Serialize, Deserialize, PartialEq, Eq)] pub enum RecommendedTheme { #[serde(alias = "none")] None, #[serde(alias = "light")] Light, #[serde(alias = "dark")] Dark, } impl Default for RecommendedTheme { fn default() -> Self { Self::None } } #[derive(Serialize, Deserialize, PartialEq, Eq)] pub enum TextAlignment { #[serde(alias = "left")] Left, #[serde(alias = "center")] Center, #[serde(alias = "right")] Right, #[serde(alias = "justify")] Justify, } impl Default for TextAlignment { fn default() -> Self { Self::Left } } impl Display for TextAlignment { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Self::Left => "left", Self::Center => "center", Self::Right => "right", Self::Justify => "justify", }) } } #[derive(Serialize, Deserialize, Validate, Default)] pub struct EntryMetadata { /// The title of the page. #[serde(default, alias = "PAGE_TITLE")] #[validate(max_length = 128)] pub page_title: String, /// The description of the page. #[serde(default, alias = "PAGE_DESCRIPTION")] #[validate(max_length = 256)] pub page_description: String, /// The favicon of the page. #[serde(default, alias = "PAGE_ICON")] #[validate(max_length = 128)] pub page_icon: String, /// The title of the page shown in external embeds. #[serde(default, alias = "SHARE_TITLE")] #[validate(max_length = 128)] pub share_title: String, /// The description of the page shown in external embeds. #[serde(default, alias = "SHARE_DESCRIPTION")] #[validate(max_length = 256)] pub share_description: String, /// The image shown in external embeds. #[serde(default, alias = "SHARE_IMAGE")] #[validate(max_length = 128)] pub share_image: String, /// If views are counted and shown for this entry. #[serde(default, alias = "OPTION_DISABLE_VIEWS")] pub option_disable_views: bool, /// If this entry shows up in search engines. #[serde(default, alias = "OPTION_DISABLE_SEARCH_ENGINE")] pub option_disable_search_engine: bool, /// The theme that is automatically used when this entry is viewed. #[serde(default, alias = "ACCESS_RECOMMENDED_THEME")] pub access_recommended_theme: RecommendedTheme, /// The slug of the easy-to-read (no metadata) version of this entry. /// /// Should not begin with "/". #[serde(default, alias = "ACCESS_EASY_READ")] #[validate(max_length = 32)] pub access_easy_read: String, /// The padding of the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_PADDING")] #[validate(max_length = 32)] pub container_padding: String, /// The maximum width of the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_MAX_WIDTH")] #[validate(max_length = 16)] pub container_max_width: String, /// The padding of the container. /// The color of the text in the inner container. #[serde(default, alias = "CONTAINER_INNER_FOREGROUND_COLOR")] #[validate(max_length = 32)] pub container_inner_foreground_color: String, /// The background of the inner container. /// /// Syntax: #[serde(default, alias = "CONTAINER_INNER_BACKGROUND")] #[validate(max_length = 256)] pub container_inner_background: String, /// The background of the inner container. /// /// Syntax: #[serde(default, alias = "CONTAINER_INNER_BACKGROUND_COLOR")] #[validate(max_length = 32)] pub container_inner_background_color: String, /// The background of the inner container. /// /// Syntax: #[serde(default, alias = "CONTAINER_INNER_BACKGROUND_IMAGE")] #[validate(max_length = 128)] pub container_inner_background_image: String, /// The background of the inner container. /// /// Syntax: #[serde(default, alias = "CONTAINER_INNER_BACKGROUND_IMAGE_REPEAT")] #[validate(max_length = 16)] pub container_inner_background_image_repeat: String, /// The background of the inner container. /// /// Syntax: #[serde(default, alias = "CONTAINER_INNER_BACKGROUND_IMAGE_POSITION")] #[validate(max_length = 16)] pub container_inner_background_image_position: String, /// The background of the inner container. /// /// Syntax: #[serde(default, alias = "CONTAINER_INNER_BACKGROUND_IMAGE_SIZE")] #[validate(max_length = 16)] pub container_inner_background_image_size: String, /// The color of the text in the outer container. #[serde(default, alias = "CONTAINER_OUTER_FOREGROUND_COLOR")] #[validate(max_length = 32)] pub container_outer_foreground_color: String, /// The background of the outer container. /// /// Syntax: #[serde(default, alias = "CONTAINER_OUTER_BACKGROUND")] #[validate(max_length = 256)] pub container_outer_background: String, /// The background of the outer container. /// /// Syntax: #[serde(default, alias = "CONTAINER_OUTER_BACKGROUND_COLOR")] #[validate(max_length = 32)] pub container_outer_background_color: String, /// The background of the outer container. /// /// Syntax: #[serde(default, alias = "CONTAINER_OUTER_BACKGROUND_IMAGE")] #[validate(max_length = 128)] pub container_outer_background_image: String, /// The background of the outer container. /// /// Syntax: #[serde(default, alias = "CONTAINER_OUTER_BACKGROUND_IMAGE_REPEAT")] #[validate(max_length = 16)] pub container_outer_background_image_repeat: String, /// The background of the outer container. /// /// Syntax: #[serde(default, alias = "CONTAINER_OUTER_BACKGROUND_IMAGE_POSITION")] #[validate(max_length = 16)] pub container_outer_background_image_position: String, /// The background of the outer container. /// /// Syntax: #[serde(default, alias = "CONTAINER_OUTER_BACKGROUND_IMAGE_SIZE")] #[validate(max_length = 16)] pub container_outer_background_image_size: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER")] #[validate(max_length = 256)] pub container_border: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_IMAGE")] #[validate(max_length = 128)] pub container_border_image: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_IMAGE_SLICE")] #[validate(max_length = 16)] pub container_border_image_slice: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_IMAGE_WIDTH")] #[validate(max_length = 16)] pub container_border_image_width: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_IMAGE_OUTSET")] #[validate(max_length = 32)] pub container_border_image_outset: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_IMAGE_REPEAT")] #[validate(max_length = 16)] pub container_border_image_repeat: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_COLOR")] #[validate(max_length = 32)] pub container_border_color: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_WIDTH")] #[validate(max_length = 16)] pub container_border_width: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_STYLE")] #[validate(max_length = 16)] pub container_border_style: String, /// The border around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_BORDER_RADIUS")] #[validate(max_length = 16)] pub container_border_radius: String, /// The shadow around the container. /// /// Syntax: #[serde(default, alias = "CONTAINER_SHADOW")] #[validate(max_length = 32)] pub container_shadow: String, /// If the container is a flexbox. #[serde(default, alias = "CONTAINER_FLEX")] pub container_flex: bool, /// The direction of the container's content (if container has flex enabled). #[serde(default, alias = "CONTAINER_FLEX_DIRECTION")] #[validate(max_length = 16)] pub container_flex_direction: String, /// The gap of the container's content (if container has flex enabled). /// /// Syntax: #[serde(default, alias = "CONTAINER_FLEX_GAP")] #[validate(max_length = 16)] pub container_flex_gap: String, /// If the container's content wraps (if container has flex enabled). /// /// Syntax: #[serde(default, alias = "CONTAINER_FLEX_WRAP")] pub container_flex_wrap: bool, /// The alignment of the container's content horizontally (if container has flex enabled). /// /// Swapped to vertical when direction is `column*`. /// /// Syntax: #[serde(default, alias = "CONTAINER_FLEX_ALIGN_HORIZONTAL")] #[validate(max_length = 16)] pub container_flex_align_horizontal: String, /// The alignment of the container's content vertically (if container has flex enabled). /// /// Swapped to horizontal when direction is `column*`. /// /// Syntax: #[serde(default, alias = "CONTAINER_FLEX_ALIGN_VERTICAL")] #[validate(max_length = 16)] pub container_flex_align_vertical: String, /// The shadow under text. /// /// Syntax: #[serde(default, alias = "CONTENT_TEXT_SHADOW")] #[validate(max_length = 32)] pub content_text_shadow: String, /// The shadow under text. /// /// Syntax: #[serde(default, alias = "CONTENT_TEXT_SHADOW_COLOR")] #[validate(max_length = 32)] pub content_text_shadow_color: String, /// The shadow under text. /// /// Syntax: #[serde(default, alias = "CONTENT_TEXT_SHADOW_OFFSET")] #[validate(max_length = 32)] pub content_text_shadow_offset: String, /// The shadow under text. /// /// Syntax: #[serde(default, alias = "CONTENT_TEXT_SHADOW_BLUR")] #[validate(max_length = 32)] pub content_text_shadow_blur: String, /// The name of a font from Google Fonts to use. #[serde(default, alias = "CONTENT_FONT")] #[validate(max_length = 32)] pub content_font: String, /// The weight to use for the body text. #[serde(default, alias = "CONTENT_FONT_WEIGHT")] pub content_font_weight: u32, /// The text size of elements by element tag. /// /// # Example /// ```toml /// # ... /// content_text_size = [["h1", "16px"]] /// ``` #[serde(default, alias = "CONTENT_TEXT_SIZE")] pub content_text_size: Vec<(String, String)>, /// The default text alignment. #[serde(default, alias = "CONTENT_TEXT_ALIGN")] pub content_text_align: TextAlignment, /// The color of links. #[serde(default, alias = "CONTENT_LINK_COLOR")] pub content_link_color: String, /// If paragraph elements have a margin below them. #[serde(default, alias = "CONTENT_DISABLE_PARAGRAPH_MARGIN")] pub content_disable_paragraph_margin: bool, } macro_rules! metadata_css { ($selector:literal, $property:literal, $self:ident.$field:ident->$output:ident) => { if !$self.$field.is_empty() { $output.push_str(&format!( "{} {{ {}: {}; }}\n", $selector, $property, EntryMetadata::css_escape(&$self.$field) )); } }; ($selector:literal, $property:literal !important, $self:ident.$field:ident->$output:ident) => { if !$self.$field.is_empty() { $output.push_str(&format!( "{} {{ {}: {} !important; }}\n", $selector, $property, EntryMetadata::css_escape(&$self.$field) )); } }; ($selector:literal, $property:literal, $format:literal, $self:ident.$field:ident->$output:ident) => { if !$self.$field.is_empty() { $output.push_str(&format!( "{} {{ {}: {}; }}\n", $selector, $property, format!($format, EntryMetadata::css_escape(&$self.$field)) )); } }; } impl EntryMetadata { pub fn head_tags(&self) -> String { let mut output = String::new(); if !self.page_title.is_empty() { output.push_str(&format!("{}", self.page_title)); } if !self.page_description.is_empty() { output.push_str(&format!( "", self.page_description.replace("\"", "\\\"") )); } if !self.page_icon.is_empty() { output.push_str(&format!( "", self.page_icon.replace("\"", "\\\"") )); } if !self.share_title.is_empty() { output.push_str(&format!( "", self.share_title.replace("\"", "\\\""), self.share_title.replace("\"", "\\\"") )); } if !self.share_description.is_empty() { output.push_str(&format!( "", self.share_description.replace("\"", "\\\""), self.share_description.replace("\"", "\\\"") )); } if !self.share_image.is_empty() { output.push_str(&format!( "", self.share_image.replace("\"", "\\\""), self.share_image.replace("\"", "\\\"") )); } if !self.content_font.is_empty() { output.push_str(&format!( " ", self.content_font.replace(" ", "+").replace("_", "+"), )); } output } pub fn css_escape(input: &str) -> String { input.replace("}", "").replace(";", "").replace("/*", "") } pub fn css(&self) -> String { let mut output = "" } pub fn ini_to_toml(input: &str) -> String { let mut output = String::new(); for line in input.split("\n") { if !line.contains("=") { // no equal sign, skip line (some other toml function) continue; } let mut key = String::new(); let mut value = String::new(); let mut chars = line.chars(); let mut in_value = false; while let Some(char) = chars.next() { if !in_value { key.push(char); if char == '=' { in_value = true; } continue; } else { value.push(char); continue; } } value = value.trim().to_string(); // determine if we need to stringify let is_numeric = is_numeric(&value); if !is_numeric && !value.starts_with("[") && !value.starts_with("\"") && value != "true" && value != "false" || value.starts_with("#") { value = format!("\"{value}\""); } // push line output.push_str(&format!("{key} {value}\n")); } output } }