WIP: Añade componente para la gestión de menús #8
6 changed files with 90 additions and 97 deletions
|
|
@ -87,6 +87,9 @@ use crate::{core, AutoDefault};
|
||||||
#[allow(type_alias_bounds)]
|
#[allow(type_alias_bounds)]
|
||||||
pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>;
|
pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>;
|
||||||
|
|
||||||
|
mod join_classes;
|
||||||
|
pub use join_classes::JoinClasses;
|
||||||
|
|
||||||
mod unit;
|
mod unit;
|
||||||
pub use unit::UnitValue;
|
pub use unit::UnitValue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -215,13 +215,13 @@ impl Asset for JavaScript {
|
||||||
fn render(&self, cx: &mut Context) -> Markup {
|
fn render(&self, cx: &mut Context) -> Markup {
|
||||||
match &self.source {
|
match &self.source {
|
||||||
Source::From(path) => html! {
|
Source::From(path) => html! {
|
||||||
script src=(join_pair!(path, "?v=", self.version.as_str())) {};
|
script src=(join_pair!(path, "?v=", &self.version)) {};
|
||||||
},
|
},
|
||||||
Source::Defer(path) => html! {
|
Source::Defer(path) => html! {
|
||||||
script src=(join_pair!(path, "?v=", self.version.as_str())) defer {};
|
script src=(join_pair!(path, "?v=", &self.version)) defer {};
|
||||||
},
|
},
|
||||||
Source::Async(path) => html! {
|
Source::Async(path) => html! {
|
||||||
script src=(join_pair!(path, "?v=", self.version.as_str())) async {};
|
script src=(join_pair!(path, "?v=", &self.version)) async {};
|
||||||
},
|
},
|
||||||
Source::Inline(_, f) => html! {
|
Source::Inline(_, f) => html! {
|
||||||
script { (PreEscaped((f)(cx))) };
|
script { (PreEscaped((f)(cx))) };
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ impl Asset for StyleSheet {
|
||||||
Source::From(path) => html! {
|
Source::From(path) => html! {
|
||||||
link
|
link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href=(join_pair!(path, "?v=", self.version.as_str()))
|
href=(join_pair!(path, "?v=", &self.version))
|
||||||
media=[self.media.as_str_opt()];
|
media=[self.media.as_str_opt()];
|
||||||
},
|
},
|
||||||
Source::Inline(_, f) => html! {
|
Source::Inline(_, f) => html! {
|
||||||
|
|
|
||||||
67
src/html/join_classes.rs
Normal file
67
src/html/join_classes.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
/// Añade a los *slices* de elementos [`AsRef<str>`] un método para unir clases CSS.
|
||||||
|
///
|
||||||
|
/// El método es [`join_classes()`](JoinClasses::join_classes), que une las cadenas **no vacías**
|
||||||
|
/// del *slice* usando un espacio como separador.
|
||||||
|
pub trait JoinClasses {
|
||||||
|
/// Une las cadenas **no vacías** de un *slice* usando un espacio como separador.
|
||||||
|
///
|
||||||
|
/// Son cadenas vacías únicamente los elementos del *slice* cuya longitud es `0` (p. ej., `""`);
|
||||||
|
/// no se realiza recorte ni normalización, por lo que elementos como `" "` no se consideran
|
||||||
|
/// vacíos.
|
||||||
|
///
|
||||||
|
/// Si todas las cadenas están vacías, devuelve una cadena vacía. Acepta elementos que
|
||||||
|
/// implementen [`AsRef<str>`] como `&str`, [`String`] o `Cow<'_, str>`.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// let classes = ["btn", "", "btn-primary"];
|
||||||
|
/// assert_eq!(classes.join_classes(), "btn btn-primary");
|
||||||
|
///
|
||||||
|
/// let empty: [&str; 3] = ["", "", ""];
|
||||||
|
/// assert_eq!(empty.join_classes(), "");
|
||||||
|
///
|
||||||
|
/// let border = String::from("border");
|
||||||
|
/// let border_top = String::from("border-top-0");
|
||||||
|
/// let v = vec![&border, "", "", "", &border_top];
|
||||||
|
/// assert_eq!(v.as_slice().join_classes(), "border border-top-0");
|
||||||
|
///
|
||||||
|
/// // Elementos con espacios afectan al resultado.
|
||||||
|
/// let spaced = ["btn", " ", "primary "];
|
||||||
|
/// assert_eq!(spaced.join_classes(), "btn primary ");
|
||||||
|
/// ```
|
||||||
|
fn join_classes(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> JoinClasses for [T]
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn join_classes(&self) -> String {
|
||||||
|
let mut count = 0usize;
|
||||||
|
let mut total = 0usize;
|
||||||
|
for s in self.iter().map(T::as_ref).filter(|s| !s.is_empty()) {
|
||||||
|
count += 1;
|
||||||
|
total += s.len();
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
let separator = " ";
|
||||||
|
let mut result = String::with_capacity(total + separator.len() * count.saturating_sub(1));
|
||||||
|
for (i, s) in self
|
||||||
|
.iter()
|
||||||
|
.map(T::as_ref)
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if i > 0 {
|
||||||
|
result.push_str(separator);
|
||||||
|
}
|
||||||
|
result.push_str(s);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ pub use crate::{AutoDefault, StaticResources, UniqueId, Weight};
|
||||||
// MACROS.
|
// MACROS.
|
||||||
|
|
||||||
// crate::util
|
// crate::util
|
||||||
pub use crate::{hm, join, join_opt, join_pair, join_strict};
|
pub use crate::{hm, join, join_pair};
|
||||||
// crate::config
|
// crate::config
|
||||||
pub use crate::include_config;
|
pub use crate::include_config;
|
||||||
// crate::locale
|
// crate::locale
|
||||||
|
|
@ -33,8 +33,8 @@ pub use crate::trace;
|
||||||
// alias obsoletos se volverá a declarar como `pub use crate::html::*;`.
|
// alias obsoletos se volverá a declarar como `pub use crate::html::*;`.
|
||||||
pub use crate::html::{
|
pub use crate::html::{
|
||||||
display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue,
|
display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue,
|
||||||
ClassesOp, Escaper, Favicon, JavaScript, Markup, PageTopSvg, PreEscaped, PrepareMarkup,
|
ClassesOp, Escaper, Favicon, JavaScript, JoinClasses, Markup, PageTopSvg, PreEscaped,
|
||||||
StyleSheet, TargetMedia, UnitValue, DOCTYPE,
|
PrepareMarkup, StyleSheet, TargetMedia, UnitValue, DOCTYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::locale::*;
|
pub use crate::locale::*;
|
||||||
|
|
|
||||||
103
src/util.rs
103
src/util.rs
|
|
@ -65,53 +65,11 @@ macro_rules! hm {
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! join {
|
macro_rules! join {
|
||||||
($($arg:tt)*) => {
|
($($arg:expr),+) => {
|
||||||
$crate::util::concat_string!($($arg)*)
|
$crate::util::concat_string!($($arg),+)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Concatena los fragmentos **no vacíos** en un [`Option<String>`] con un separador opcional.
|
|
||||||
///
|
|
||||||
/// Acepta cualquier número de fragmentos que implementen [`AsRef<str>`]. Si todos los fragmentos
|
|
||||||
/// están vacíos, devuelve `None`.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop::prelude::*;
|
|
||||||
/// // Concatena los fragmentos no vacíos con un espacio como separador.
|
|
||||||
/// let result_with_separator = join_opt!(["Hello", "", "World"]; " ");
|
|
||||||
/// assert_eq!(result_with_separator, Some("Hello World".to_string()));
|
|
||||||
///
|
|
||||||
/// // Concatena los fragmentos no vacíos sin un separador.
|
|
||||||
/// let result_without_separator = join_opt!(["Hello", "", "World"]);
|
|
||||||
/// assert_eq!(result_without_separator, Some("HelloWorld".to_string()));
|
|
||||||
///
|
|
||||||
/// // Devuelve `None` si todos los fragmentos están vacíos.
|
|
||||||
/// let result_empty = join_opt!(["", "", ""]; ",");
|
|
||||||
/// assert_eq!(result_empty, None);
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! join_opt {
|
|
||||||
([$($arg:expr),* $(,)?]) => {{
|
|
||||||
let s = $crate::util::concat_string!($($arg),*);
|
|
||||||
(!s.is_empty()).then_some(s)
|
|
||||||
}};
|
|
||||||
([$($arg:expr),* $(,)?]; $separator:expr) => {{
|
|
||||||
let sep = ($separator).as_ref();
|
|
||||||
let mut s = String::new();
|
|
||||||
for part in [ $( ($arg).as_ref() ),* ] {
|
|
||||||
if !(part as &str).is_empty() {
|
|
||||||
if !s.is_empty() {
|
|
||||||
s.push_str(sep);
|
|
||||||
}
|
|
||||||
s.push_str(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(!s.is_empty()).then_some(s)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Concatena dos fragmentos en un [`String`] usando un separador.
|
/// Concatena dos fragmentos en un [`String`] usando un separador.
|
||||||
///
|
///
|
||||||
/// Une los dos fragmentos, que deben implementar [`AsRef<str>`], usando el separador proporcionado.
|
/// Une los dos fragmentos, que deben implementar [`AsRef<str>`], usando el separador proporcionado.
|
||||||
|
|
@ -145,54 +103,19 @@ macro_rules! join_opt {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! join_pair {
|
macro_rules! join_pair {
|
||||||
($first:expr, $separator:expr, $second:expr) => {{
|
($first:expr, $separator:expr, $second:expr) => {{
|
||||||
if $first.is_empty() {
|
let first_val = $first;
|
||||||
String::from($second)
|
let second_val = $second;
|
||||||
} else if $second.is_empty() {
|
let separator_val = $separator;
|
||||||
String::from($first)
|
|
||||||
} else {
|
|
||||||
$crate::util::concat_string!($first, $separator, $second)
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Concatena varios fragmentos en un [`Option<String>`] **si ninguno está vacío**.
|
let first = AsRef::<str>::as_ref(&first_val);
|
||||||
///
|
let second = AsRef::<str>::as_ref(&second_val);
|
||||||
/// Si alguno de los fragmentos, que deben implementar [`AsRef<str>`], está vacío, devuelve
|
let separator = if first.is_empty() || second.is_empty() {
|
||||||
/// [`None`]. Opcionalmente se puede indicar un separador entre los fragmentos concatenados.
|
""
|
||||||
///
|
|
||||||
/// # Ejemplo
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop::prelude::*;
|
|
||||||
/// // Concatena los fragmentos.
|
|
||||||
/// let result = join_strict!(["Hello", "World"]);
|
|
||||||
/// assert_eq!(result, Some("HelloWorld".to_string()));
|
|
||||||
///
|
|
||||||
/// // Concatena los fragmentos con un separador.
|
|
||||||
/// let result_with_separator = join_strict!(["Hello", "World"]; " ");
|
|
||||||
/// assert_eq!(result_with_separator, Some("Hello World".to_string()));
|
|
||||||
///
|
|
||||||
/// // Devuelve `None` si alguno de los fragmentos está vacío.
|
|
||||||
/// let result_with_empty = join_strict!(["Hello", "", "World"]);
|
|
||||||
/// assert_eq!(result_with_empty, None);
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! join_strict {
|
|
||||||
([$($arg:expr),* $(,)?]) => {{
|
|
||||||
let fragments = [$($arg),*];
|
|
||||||
if fragments.iter().any(|&item| item.is_empty()) {
|
|
||||||
None
|
|
||||||
} else {
|
} else {
|
||||||
Some(fragments.concat())
|
AsRef::<str>::as_ref(&separator_val)
|
||||||
}
|
};
|
||||||
}};
|
|
||||||
([$($arg:expr),* $(,)?]; $separator:expr) => {{
|
$crate::util::concat_string!(first, separator, second)
|
||||||
let fragments = [$($arg),*];
|
|
||||||
if fragments.iter().any(|&item| item.is_empty()) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(fragments.join($separator))
|
|
||||||
}
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue