♻️ Major code restructuring
This commit is contained in:
parent
a96e203bb3
commit
fa66d628a0
221 changed files with 228 additions and 315 deletions
54
src/html/assets.rs
Normal file
54
src/html/assets.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
pub mod headscript;
|
||||
pub mod headstyles;
|
||||
pub mod javascript;
|
||||
pub mod stylesheet;
|
||||
|
||||
use crate::html::{html, Markup};
|
||||
use crate::{SmartDefault, Weight};
|
||||
|
||||
pub trait AssetsTrait {
|
||||
fn path(&self) -> &str;
|
||||
|
||||
fn weight(&self) -> Weight;
|
||||
|
||||
fn prepare(&self) -> Markup;
|
||||
}
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct Assets<T>(Vec<T>);
|
||||
|
||||
impl<T: AssetsTrait> Assets<T> {
|
||||
pub fn new() -> Self {
|
||||
Assets::<T>(Vec::<T>::new())
|
||||
}
|
||||
|
||||
pub fn add(&mut self, asset: T) -> &mut Self {
|
||||
match self.0.iter().position(|x| x.path() == asset.path()) {
|
||||
Some(index) => {
|
||||
if self.0[index].weight() > asset.weight() {
|
||||
self.0.remove(index);
|
||||
self.0.push(asset);
|
||||
}
|
||||
}
|
||||
_ => self.0.push(asset),
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, path: &'static str) -> &mut Self {
|
||||
if let Some(index) = self.0.iter().position(|x| x.path() == path) {
|
||||
self.0.remove(index);
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn prepare(&mut self) -> Markup {
|
||||
let assets = &mut self.0;
|
||||
assets.sort_by_key(|a| a.weight());
|
||||
html! {
|
||||
@for a in assets {
|
||||
(a.prepare())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/html/assets/headscript.rs
Normal file
44
src/html/assets/headscript.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use crate::html::assets::AssetsTrait;
|
||||
use crate::html::{html, Markup};
|
||||
use crate::{SmartDefault, Weight};
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(SmartDefault)]
|
||||
pub struct HeadScript {
|
||||
path : String,
|
||||
code : String,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
impl AssetsTrait for HeadScript {
|
||||
fn path(&self) -> &str {
|
||||
self.path.as_str()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn prepare(&self) -> Markup {
|
||||
html! { script { (self.code) }; }
|
||||
}
|
||||
}
|
||||
|
||||
impl HeadScript {
|
||||
pub fn named(path: impl Into<String>) -> Self {
|
||||
HeadScript {
|
||||
path: path.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_code(mut self, code: impl Into<String>) -> Self {
|
||||
self.code = code.into().trim().to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
44
src/html/assets/headstyles.rs
Normal file
44
src/html/assets/headstyles.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use crate::html::assets::AssetsTrait;
|
||||
use crate::html::{html, Markup};
|
||||
use crate::{SmartDefault, Weight};
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(SmartDefault)]
|
||||
pub struct HeadStyles {
|
||||
path : String,
|
||||
styles: String,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
impl AssetsTrait for HeadStyles {
|
||||
fn path(&self) -> &str {
|
||||
self.path.as_str()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn prepare(&self) -> Markup {
|
||||
html! { styles { (self.styles) }; }
|
||||
}
|
||||
}
|
||||
|
||||
impl HeadStyles {
|
||||
pub fn named(path: impl Into<String>) -> Self {
|
||||
HeadStyles {
|
||||
path: path.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_styles(mut self, styles: impl Into<String>) -> Self {
|
||||
self.styles = styles.into().trim().to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
69
src/html/assets/javascript.rs
Normal file
69
src/html/assets/javascript.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use crate::html::assets::AssetsTrait;
|
||||
use crate::html::{html, Markup};
|
||||
use crate::{SmartDefault, Weight};
|
||||
|
||||
#[derive(Default, Eq, PartialEq)]
|
||||
pub enum ModeJS {
|
||||
Async,
|
||||
#[default]
|
||||
Defer,
|
||||
Normal,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(SmartDefault)]
|
||||
pub struct JavaScript {
|
||||
path : String,
|
||||
prefix : &'static str,
|
||||
version: &'static str,
|
||||
weight : Weight,
|
||||
mode : ModeJS,
|
||||
}
|
||||
|
||||
impl AssetsTrait for JavaScript {
|
||||
fn path(&self) -> &str {
|
||||
self.path.as_str()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn prepare(&self) -> Markup {
|
||||
html! {
|
||||
script type="text/javascript"
|
||||
src=(crate::concat_string!(self.path, self.prefix, self.version))
|
||||
async[self.mode == ModeJS::Async]
|
||||
defer[self.mode == ModeJS::Defer]
|
||||
{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JavaScript {
|
||||
pub fn at(path: impl Into<String>) -> Self {
|
||||
JavaScript {
|
||||
path: path.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_version(mut self, version: &'static str) -> Self {
|
||||
(self.prefix, self.version) = if version.is_empty() {
|
||||
("", "")
|
||||
} else {
|
||||
("?v=", version)
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_mode(mut self, mode: ModeJS) -> Self {
|
||||
self.mode = mode;
|
||||
self
|
||||
}
|
||||
}
|
||||
73
src/html/assets/stylesheet.rs
Normal file
73
src/html/assets/stylesheet.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use crate::html::assets::AssetsTrait;
|
||||
use crate::html::{html, Markup};
|
||||
use crate::{SmartDefault, Weight};
|
||||
|
||||
pub enum TargetMedia {
|
||||
Default,
|
||||
Print,
|
||||
Screen,
|
||||
Speech,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(SmartDefault)]
|
||||
pub struct StyleSheet {
|
||||
path : String,
|
||||
prefix : &'static str,
|
||||
version: &'static str,
|
||||
media : Option<&'static str>,
|
||||
weight : Weight,
|
||||
}
|
||||
|
||||
impl AssetsTrait for StyleSheet {
|
||||
fn path(&self) -> &str {
|
||||
self.path.as_str()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn prepare(&self) -> Markup {
|
||||
html! {
|
||||
link
|
||||
rel="stylesheet"
|
||||
href=(crate::concat_string!(self.path, self.prefix, self.version))
|
||||
media=[self.media];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleSheet {
|
||||
pub fn at(path: impl Into<String>) -> Self {
|
||||
StyleSheet {
|
||||
path: path.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_version(mut self, version: &'static str) -> Self {
|
||||
(self.prefix, self.version) = if version.is_empty() {
|
||||
("", "")
|
||||
} else {
|
||||
("?v=", version)
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn for_media(mut self, media: TargetMedia) -> Self {
|
||||
self.media = match media {
|
||||
TargetMedia::Print => Some("print"),
|
||||
TargetMedia::Screen => Some("screen"),
|
||||
TargetMedia::Speech => Some("speech"),
|
||||
_ => None,
|
||||
};
|
||||
self
|
||||
}
|
||||
}
|
||||
93
src/html/favicon.rs
Normal file
93
src/html/favicon.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use crate::html::{html, Markup};
|
||||
use crate::SmartDefault;
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct Favicon(Vec<Markup>);
|
||||
|
||||
impl Favicon {
|
||||
pub fn new() -> Self {
|
||||
Favicon::default()
|
||||
}
|
||||
|
||||
// Favicon BUILDER.
|
||||
|
||||
pub fn with_icon(self, image: &str) -> Self {
|
||||
self.add_icon_item("icon", image, None, None)
|
||||
}
|
||||
|
||||
pub fn with_icon_for_sizes(self, image: &str, sizes: &str) -> Self {
|
||||
self.add_icon_item("icon", image, Some(sizes), None)
|
||||
}
|
||||
|
||||
pub fn with_apple_touch_icon(self, image: &str, sizes: &str) -> Self {
|
||||
self.add_icon_item("apple-touch-icon", image, Some(sizes), None)
|
||||
}
|
||||
|
||||
pub fn with_mask_icon(self, image: &str, color: &str) -> Self {
|
||||
self.add_icon_item("mask-icon", image, None, Some(color))
|
||||
}
|
||||
|
||||
pub fn with_manifest(self, file: &str) -> Self {
|
||||
self.add_icon_item("manifest", file, None, None)
|
||||
}
|
||||
|
||||
pub fn with_theme_color(mut self, color: &str) -> Self {
|
||||
self.0.push(html! {
|
||||
meta name="theme-color" content=(color);
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ms_tile_color(mut self, color: &str) -> Self {
|
||||
self.0.push(html! {
|
||||
meta name="msapplication-TileColor" content=(color);
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ms_tile_image(mut self, image: &str) -> Self {
|
||||
self.0.push(html! {
|
||||
meta name="msapplication-TileImage" content=(image);
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
fn add_icon_item(
|
||||
mut self,
|
||||
icon_rel: &str,
|
||||
icon_source: &str,
|
||||
icon_sizes: Option<&str>,
|
||||
icon_color: Option<&str>,
|
||||
) -> Self {
|
||||
let icon_type = match icon_source.rfind('.') {
|
||||
Some(i) => match icon_source[i..].to_owned().to_lowercase().as_str() {
|
||||
".gif" => Some("image/gif"),
|
||||
".ico" => Some("image/x-icon"),
|
||||
".jpg" => Some("image/jpg"),
|
||||
".png" => Some("image/png"),
|
||||
".svg" => Some("image/svg+xml"),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
self.0.push(html! {
|
||||
link
|
||||
rel=(icon_rel)
|
||||
type=[(icon_type)]
|
||||
sizes=[(icon_sizes)]
|
||||
color=[(icon_color)]
|
||||
href=(icon_source);
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
// Favicon PREPARE.
|
||||
|
||||
pub(crate) fn prepare(&self) -> Markup {
|
||||
html! {
|
||||
@for item in &self.0 {
|
||||
(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
350
src/html/maud.rs
Normal file
350
src/html/maud.rs
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
//#![no_std]
|
||||
|
||||
//! A macro for writing HTML templates.
|
||||
//!
|
||||
//! This documentation only describes the runtime API. For a general
|
||||
//! guide, check out the [book] instead.
|
||||
//!
|
||||
//! [book]: https://maud.lambda.xyz/
|
||||
|
||||
//#![doc(html_root_url = "https://docs.rs/maud/0.25.0")]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::{borrow::Cow, boxed::Box, string::String};
|
||||
use core::fmt::{self, Arguments, Display, Write};
|
||||
|
||||
pub use pagetop_macros::html;
|
||||
|
||||
mod escape;
|
||||
|
||||
/// An adapter that escapes HTML special characters.
|
||||
///
|
||||
/// The following characters are escaped:
|
||||
///
|
||||
/// * `&` is escaped as `&`
|
||||
/// * `<` is escaped as `<`
|
||||
/// * `>` is escaped as `>`
|
||||
/// * `"` is escaped as `"`
|
||||
///
|
||||
/// All other characters are passed through unchanged.
|
||||
///
|
||||
/// **Note:** In versions prior to 0.13, the single quote (`'`) was
|
||||
/// escaped as well.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use maud::Escaper;
|
||||
/// use std::fmt::Write;
|
||||
/// let mut s = String::new();
|
||||
/// write!(Escaper::new(&mut s), "<script>launchMissiles()</script>").unwrap();
|
||||
/// assert_eq!(s, "<script>launchMissiles()</script>");
|
||||
/// ```
|
||||
pub struct Escaper<'a>(&'a mut String);
|
||||
|
||||
impl<'a> Escaper<'a> {
|
||||
/// Creates an `Escaper` from a `String`.
|
||||
pub fn new(buffer: &'a mut String) -> Escaper<'a> {
|
||||
Escaper(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Write for Escaper<'a> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
escape::escape_to_string(s, self.0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a type that can be rendered as HTML.
|
||||
///
|
||||
/// To implement this for your own type, override either the `.render()`
|
||||
/// or `.render_to()` methods; since each is defined in terms of the
|
||||
/// other, you only need to implement one of them. See the example below.
|
||||
///
|
||||
/// # Minimal implementation
|
||||
///
|
||||
/// An implementation of this trait must override at least one of
|
||||
/// `.render()` or `.render_to()`. Since the default definitions of
|
||||
/// these methods call each other, not doing this will result in
|
||||
/// infinite recursion.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use maud::{html, Markup, Render};
|
||||
///
|
||||
/// /// Provides a shorthand for linking to a CSS stylesheet.
|
||||
/// pub struct Stylesheet(&'static str);
|
||||
///
|
||||
/// impl Render for Stylesheet {
|
||||
/// fn render(&self) -> Markup {
|
||||
/// html! {
|
||||
/// link rel="stylesheet" type="text/css" href=(self.0);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Render {
|
||||
/// Renders `self` as a block of `Markup`.
|
||||
fn render(&self) -> Markup {
|
||||
let mut buffer = String::new();
|
||||
self.render_to(&mut buffer);
|
||||
PreEscaped(buffer)
|
||||
}
|
||||
|
||||
/// Appends a representation of `self` to the given buffer.
|
||||
///
|
||||
/// Its default implementation just calls `.render()`, but you may
|
||||
/// override it with something more efficient.
|
||||
///
|
||||
/// Note that no further escaping is performed on data written to
|
||||
/// the buffer. If you override this method, you must make sure that
|
||||
/// any data written is properly escaped, whether by hand or using
|
||||
/// the [`Escaper`](struct.Escaper.html) wrapper struct.
|
||||
fn render_to(&self, buffer: &mut String) {
|
||||
buffer.push_str(&self.render().into_string());
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for str {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
escape::escape_to_string(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for String {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
str::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Render for Cow<'a, str> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
str::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Render for Arguments<'a> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
let _ = Escaper::new(w).write_fmt(*self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Render + ?Sized> Render for &'a T {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
T::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Render + ?Sized> Render for &'a mut T {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
T::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Render + ?Sized> Render for Box<T> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
T::render_to(self, w);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_render_with_display {
|
||||
($($ty:ty)*) => {
|
||||
$(
|
||||
impl Render for $ty {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
// TODO: remove the explicit arg when Rust 1.58 is released
|
||||
format_args!("{self}", self = self).render_to(w);
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_render_with_display! {
|
||||
char f32 f64
|
||||
}
|
||||
|
||||
macro_rules! impl_render_with_itoa {
|
||||
($($ty:ty)*) => {
|
||||
$(
|
||||
impl Render for $ty {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
w.push_str(itoa::Buffer::new().format(*self));
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_render_with_itoa! {
|
||||
i8 i16 i32 i64 i128 isize
|
||||
u8 u16 u32 u64 u128 usize
|
||||
}
|
||||
|
||||
/// Renders a value using its [`Display`] impl.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use maud::html;
|
||||
/// use std::net::Ipv4Addr;
|
||||
///
|
||||
/// let ip_address = Ipv4Addr::new(127, 0, 0, 1);
|
||||
///
|
||||
/// let markup = html! {
|
||||
/// "My IP address is: "
|
||||
/// (maud::display(ip_address))
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(markup.into_string(), "My IP address is: 127.0.0.1");
|
||||
/// ```
|
||||
pub fn display(value: impl Display) -> impl Render {
|
||||
struct DisplayWrapper<T>(T);
|
||||
|
||||
impl<T: Display> Render for DisplayWrapper<T> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
format_args!("{0}", self.0).render_to(w);
|
||||
}
|
||||
}
|
||||
|
||||
DisplayWrapper(value)
|
||||
}
|
||||
|
||||
/// A wrapper that renders the inner value without escaping.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PreEscaped<T: AsRef<str>>(pub T);
|
||||
|
||||
impl<T: AsRef<str>> Render for PreEscaped<T> {
|
||||
fn render_to(&self, w: &mut String) {
|
||||
w.push_str(self.0.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
/// A block of markup is a string that does not need to be escaped.
|
||||
///
|
||||
/// The `html!` macro expands to an expression of this type.
|
||||
pub type Markup = PreEscaped<String>;
|
||||
|
||||
impl Markup {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Into<String>> PreEscaped<T> {
|
||||
/// Converts the inner value to a string.
|
||||
pub fn into_string(self) -> String {
|
||||
self.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Into<String>> From<PreEscaped<T>> for String {
|
||||
fn from(value: PreEscaped<T>) -> String {
|
||||
value.into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Default> Default for PreEscaped<T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// The literal string `<!DOCTYPE html>`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// A minimal web page:
|
||||
///
|
||||
/// ```rust
|
||||
/// use maud::{DOCTYPE, html};
|
||||
///
|
||||
/// let markup = html! {
|
||||
/// (DOCTYPE)
|
||||
/// html {
|
||||
/// head {
|
||||
/// meta charset="utf-8";
|
||||
/// title { "Test page" }
|
||||
/// }
|
||||
/// body {
|
||||
/// p { "Hello, world!" }
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped("<!DOCTYPE html>");
|
||||
|
||||
mod actix_support {
|
||||
extern crate alloc;
|
||||
|
||||
use crate::html::PreEscaped;
|
||||
use actix_web::{http::header, HttpRequest, HttpResponse, Responder};
|
||||
use alloc::string::String;
|
||||
|
||||
impl Responder for PreEscaped<String> {
|
||||
type Body = String;
|
||||
|
||||
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
HttpResponse::Ok()
|
||||
.content_type(header::ContentType::html())
|
||||
.message_body(self.0)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod html_private {
|
||||
extern crate alloc;
|
||||
|
||||
use super::{display, Render};
|
||||
use alloc::string::String;
|
||||
use core::fmt::Display;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! render_to {
|
||||
($x:expr, $buffer:expr) => {{
|
||||
use $crate::html::html_private::*;
|
||||
match ChooseRenderOrDisplay($x) {
|
||||
x => (&&x).implements_render_or_display().render_to(x.0, $buffer),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub use render_to;
|
||||
|
||||
pub struct ChooseRenderOrDisplay<T>(pub T);
|
||||
|
||||
pub struct ViaRenderTag;
|
||||
pub struct ViaDisplayTag;
|
||||
|
||||
pub trait ViaRender {
|
||||
fn implements_render_or_display(&self) -> ViaRenderTag {
|
||||
ViaRenderTag
|
||||
}
|
||||
}
|
||||
pub trait ViaDisplay {
|
||||
fn implements_render_or_display(&self) -> ViaDisplayTag {
|
||||
ViaDisplayTag
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
|
||||
impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}
|
||||
|
||||
impl ViaRenderTag {
|
||||
pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
|
||||
value.render_to(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl ViaDisplayTag {
|
||||
pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
|
||||
display(value).render_to(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/html/maud/escape.rs
Normal file
34
src/html/maud/escape.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// !!!!! PLEASE KEEP THIS IN SYNC WITH `maud_macros/src/escape.rs` !!!!!
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
pub fn escape_to_string(input: &str, output: &mut String) {
|
||||
for b in input.bytes() {
|
||||
match b {
|
||||
b'&' => output.push_str("&"),
|
||||
b'<' => output.push_str("<"),
|
||||
b'>' => output.push_str(">"),
|
||||
b'"' => output.push_str("""),
|
||||
_ => unsafe { output.as_mut_vec().push(b) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate alloc;
|
||||
|
||||
use super::escape_to_string;
|
||||
use alloc::string::String;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut s = String::new();
|
||||
escape_to_string("<script>launchMissiles()</script>", &mut s);
|
||||
assert_eq!(s, "<script>launchMissiles()</script>");
|
||||
}
|
||||
}
|
||||
106
src/html/opt_classes.rs
Normal file
106
src/html/opt_classes.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
//! **OptionClasses** implements a *helper* for dynamically adding class names to components.
|
||||
//!
|
||||
//! This *helper* differentiates between default classes (generally associated with styles provided
|
||||
//! by the theme) and user classes (for customizing components based on application styles).
|
||||
//!
|
||||
//! Classes can be added using [Add]. Operations to [Remove], [Replace] or [Toggle] a class, as well
|
||||
//! as [Clear] all classes, are also provided.
|
||||
//!
|
||||
//! **OptionClasses** assumes that the order of the classes is irrelevant
|
||||
//! (<https://stackoverflow.com/a/1321712>), and duplicate classes will not be allowed.
|
||||
|
||||
use crate::{fn_with, SmartDefault};
|
||||
|
||||
pub enum ClassesOp {
|
||||
Add,
|
||||
Prepend,
|
||||
Remove,
|
||||
Replace(String),
|
||||
Toggle,
|
||||
Set,
|
||||
}
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct OptionClasses(Vec<String>);
|
||||
|
||||
impl OptionClasses {
|
||||
pub fn new(classes: impl Into<String>) -> Self {
|
||||
OptionClasses::default().with_value(ClassesOp::Prepend, classes)
|
||||
}
|
||||
|
||||
// OptionClasses BUILDER.
|
||||
|
||||
#[fn_with]
|
||||
pub fn alter_value(&mut self, op: ClassesOp, classes: impl Into<String>) -> &mut Self {
|
||||
let classes: String = classes.into();
|
||||
let classes: Vec<&str> = classes.split_ascii_whitespace().collect();
|
||||
|
||||
match op {
|
||||
ClassesOp::Add => {
|
||||
self.add(&classes, self.0.len());
|
||||
}
|
||||
ClassesOp::Prepend => {
|
||||
self.add(&classes, 0);
|
||||
}
|
||||
ClassesOp::Remove => {
|
||||
for class in classes {
|
||||
self.0.retain(|c| c.ne(&class.to_string()));
|
||||
}
|
||||
}
|
||||
ClassesOp::Replace(classes_to_replace) => {
|
||||
let mut pos = self.0.len();
|
||||
let replace: Vec<&str> = classes_to_replace.split_ascii_whitespace().collect();
|
||||
for class in replace {
|
||||
if let Some(replace_pos) = self.0.iter().position(|c| c.eq(class)) {
|
||||
self.0.remove(replace_pos);
|
||||
if pos > replace_pos {
|
||||
pos = replace_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.add(&classes, pos);
|
||||
}
|
||||
ClassesOp::Toggle => {
|
||||
for class in classes {
|
||||
if !class.is_empty() {
|
||||
if let Some(pos) = self.0.iter().position(|c| c.eq(class)) {
|
||||
self.0.remove(pos);
|
||||
} else {
|
||||
self.0.push(class.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ClassesOp::Set => {
|
||||
self.0.clear();
|
||||
self.add(&classes, 0);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add(&mut self, classes: &Vec<&str>, mut pos: usize) {
|
||||
for class in classes {
|
||||
if !class.is_empty() && !self.0.iter().any(|c| c.eq(class)) {
|
||||
self.0.insert(pos, class.to_string());
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OptionClasses GETTERS.
|
||||
|
||||
pub fn get(&self) -> Option<String> {
|
||||
if self.0.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.0.join(" "))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, class: impl Into<String>) -> bool {
|
||||
let class: String = class.into();
|
||||
self.0.iter().any(|c| c.eq(&class))
|
||||
}
|
||||
}
|
||||
45
src/html/opt_component.rs
Normal file
45
src/html/opt_component.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use crate::core::component::{ArcTypedComponent, ComponentTrait, Context};
|
||||
use crate::fn_with;
|
||||
use crate::html::{html, Markup};
|
||||
|
||||
pub struct OptionComponent<C: ComponentTrait>(Option<ArcTypedComponent<C>>);
|
||||
|
||||
impl<C: ComponentTrait> Default for OptionComponent<C> {
|
||||
fn default() -> Self {
|
||||
OptionComponent(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> OptionComponent<C> {
|
||||
pub fn new(component: C) -> Self {
|
||||
OptionComponent::default().with_value(Some(component))
|
||||
}
|
||||
|
||||
// OptionComponent BUILDER.
|
||||
|
||||
#[fn_with]
|
||||
pub fn alter_value(&mut self, component: Option<C>) -> &mut Self {
|
||||
if let Some(component) = component {
|
||||
self.0 = Some(ArcTypedComponent::new(component));
|
||||
} else {
|
||||
self.0 = None;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// OptionComponent GETTERS.
|
||||
|
||||
pub fn get(&self) -> Option<ArcTypedComponent<C>> {
|
||||
if let Some(value) = &self.0 {
|
||||
return Some(value.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn render(&self, cx: &mut Context) -> Markup {
|
||||
match &self.0 {
|
||||
Some(component) => component.render(cx),
|
||||
_ => html! {},
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/html/opt_id.rs
Normal file
29
src/html/opt_id.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::{fn_with, SmartDefault};
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct OptionId(Option<String>);
|
||||
|
||||
impl OptionId {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
OptionId::default().with_value(value)
|
||||
}
|
||||
|
||||
// OptionId BUILDER.
|
||||
|
||||
#[fn_with]
|
||||
pub fn alter_value(&mut self, value: impl Into<String>) -> &mut Self {
|
||||
self.0 = Some(value.into().trim().replace(' ', "_"));
|
||||
self
|
||||
}
|
||||
|
||||
// OptionId GETTERS.
|
||||
|
||||
pub fn get(&self) -> Option<String> {
|
||||
if let Some(value) = &self.0 {
|
||||
if !value.is_empty() {
|
||||
return Some(value.to_owned());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
29
src/html/opt_name.rs
Normal file
29
src/html/opt_name.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::{fn_with, SmartDefault};
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct OptionName(Option<String>);
|
||||
|
||||
impl OptionName {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
OptionName::default().with_value(value)
|
||||
}
|
||||
|
||||
// OptionName BUILDER.
|
||||
|
||||
#[fn_with]
|
||||
pub fn alter_value(&mut self, value: impl Into<String>) -> &mut Self {
|
||||
self.0 = Some(value.into().trim().replace(' ', "_"));
|
||||
self
|
||||
}
|
||||
|
||||
// OptionName GETTERS.
|
||||
|
||||
pub fn get(&self) -> Option<String> {
|
||||
if let Some(value) = &self.0 {
|
||||
if !value.is_empty() {
|
||||
return Some(value.to_owned());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
29
src/html/opt_string.rs
Normal file
29
src/html/opt_string.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::{fn_with, SmartDefault};
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct OptionString(Option<String>);
|
||||
|
||||
impl OptionString {
|
||||
pub fn new(value: impl Into<String>) -> Self {
|
||||
OptionString::default().with_value(value)
|
||||
}
|
||||
|
||||
// OptionString BUILDER.
|
||||
|
||||
#[fn_with]
|
||||
pub fn alter_value(&mut self, value: impl Into<String>) -> &mut Self {
|
||||
self.0 = Some(value.into().trim().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
// OptionString GETTERS.
|
||||
|
||||
pub fn get(&self) -> Option<String> {
|
||||
if let Some(value) = &self.0 {
|
||||
if !value.is_empty() {
|
||||
return Some(value.to_owned());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
30
src/html/opt_translated.rs
Normal file
30
src/html/opt_translated.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use crate::html::Markup;
|
||||
use crate::locale::{L10n, LanguageIdentifier};
|
||||
use crate::{fn_with, SmartDefault};
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
pub struct OptionTranslated(L10n);
|
||||
|
||||
impl OptionTranslated {
|
||||
pub fn new(value: L10n) -> Self {
|
||||
OptionTranslated(value)
|
||||
}
|
||||
|
||||
// OptionTranslated BUILDER.
|
||||
|
||||
#[fn_with]
|
||||
pub fn alter_value(&mut self, value: L10n) -> &mut Self {
|
||||
self.0 = value;
|
||||
self
|
||||
}
|
||||
|
||||
// OptionTranslated GETTERS.
|
||||
|
||||
pub fn using(&self, langid: &LanguageIdentifier) -> Option<String> {
|
||||
self.0.using(langid)
|
||||
}
|
||||
|
||||
pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup {
|
||||
self.0.escaped(langid)
|
||||
}
|
||||
}
|
||||
54
src/html/unit.rs
Normal file
54
src/html/unit.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use crate::{concat_string, SmartDefault};
|
||||
|
||||
// About pixels: Pixels (px) are relative to the viewing device. For low-dpi devices, 1px is one
|
||||
// device pixel (dot) of the display. For printers and high resolution screens 1px implies multiple
|
||||
// device pixels.
|
||||
|
||||
// About em: 2em means 2 times the size of the current font. The em and rem units are practical in
|
||||
// creating perfectly scalable layout!
|
||||
|
||||
// About viewport: If the browser window size is 50cm wide, 1vw = 0.5cm.
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(SmartDefault)]
|
||||
pub enum Value {
|
||||
#[default]
|
||||
None,
|
||||
Auto,
|
||||
|
||||
Cm(isize), // Centimeters.
|
||||
In(isize), // Inches (1in = 96px = 2.54cm).
|
||||
Mm(isize), // Millimeters.
|
||||
Pc(isize), // Picas (1pc = 12pt).
|
||||
Pt(isize), // Points (1pt = 1/72 of 1in).
|
||||
Px(isize), // Pixels (1px = 1/96th of 1in).
|
||||
|
||||
RelEm(f32), // Relative to the font-size of the element.
|
||||
RelPct(f32), // Percentage relative to the parent element.
|
||||
RelRem(f32), // Relative to font-size of the root element.
|
||||
RelVh(f32), // Relative to 1% of the height of the viewport.
|
||||
RelVw(f32), // Relative to 1% of the value of the viewport.
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl ToString for Value {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Value::None => "".to_owned(),
|
||||
Value::Auto => "auto".to_owned(),
|
||||
// Absolute value.
|
||||
Value::Cm(av) => concat_string!(av.to_string(), "cm"),
|
||||
Value::In(av) => concat_string!(av.to_string(), "in"),
|
||||
Value::Mm(av) => concat_string!(av.to_string(), "mm"),
|
||||
Value::Pc(av) => concat_string!(av.to_string(), "pc"),
|
||||
Value::Pt(av) => concat_string!(av.to_string(), "pt"),
|
||||
Value::Px(av) => concat_string!(av.to_string(), "px"),
|
||||
// Relative value.
|
||||
Value::RelEm(rv) => concat_string!(rv.to_string(), "em"),
|
||||
Value::RelPct(rv) => concat_string!(rv.to_string(), "%"),
|
||||
Value::RelRem(rv) => concat_string!(rv.to_string(), "rem"),
|
||||
Value::RelVh(rv) => concat_string!(rv.to_string(), "vh"),
|
||||
Value::RelVw(rv) => concat_string!(rv.to_string(), "vw"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue