WIP: Añade componente para la gestión de menús #8

Draft
manuelcillero wants to merge 54 commits from add-menu-component into main
8 changed files with 43 additions and 45 deletions
Showing only changes of commit 180cb9c2f6 - Show all commits

View file

@ -1,6 +1,6 @@
//! Componentes nativos proporcionados por PageTop. //! Componentes nativos proporcionados por PageTop.
use crate::AutoDefault; use crate::prelude::*;
use std::fmt; use std::fmt;

View file

@ -23,9 +23,9 @@
//! * Si `PAGETOP_RUN_MODE` no está definida, se asume el valor `default`, y PageTop intentará //! * Si `PAGETOP_RUN_MODE` no está definida, se asume el valor `default`, y PageTop intentará
//! cargar *config/default.toml* si el archivo existe. //! cargar *config/default.toml* si el archivo existe.
//! //!
//! * Útil para definir configuraciones específicas por entorno, garantizando que cada uno (p.ej. //! * Permite definir configuraciones específicas por entorno, garantizando que cada uno (p. ej.,
//! *dev*, *staging* o *production*) disponga de sus propias opciones, como claves de API, //! *dev*, *staging* o *production*) disponga de sus propias opciones, como claves de API, URLs
//! URLs o ajustes de rendimiento, sin afectar a los demás. //! o ajustes de rendimiento, sin afectar a los demás.
//! //!
//! 3. **config/local.{rm}.toml**, útil para configuraciones locales específicas de la máquina o de //! 3. **config/local.{rm}.toml**, útil para configuraciones locales específicas de la máquina o de
//! la ejecución: //! la ejecución:
@ -132,15 +132,15 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(
let config_dir = util::resolve_absolute_dir(&dir).unwrap_or_else(|_| PathBuf::from(&dir)); let config_dir = util::resolve_absolute_dir(&dir).unwrap_or_else(|_| PathBuf::from(&dir));
// Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Si no está definida, se usa // Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Si no está definida, se usa
// por defecto, DEFAULT_RUN_MODE (p.ej.: PAGETOP_RUN_MODE=production). // por defecto DEFAULT_RUN_MODE (p. ej. PAGETOP_RUN_MODE=production).
let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into()); let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into());
Config::builder() Config::builder()
// 1. Configuración común para todos los entornos (common.toml). // 1. Configuración común para todos los entornos (common.toml).
.add_source(File::from(config_dir.join("common.toml")).required(false)) .add_source(File::from(config_dir.join("common.toml")).required(false))
// 2. Configuración específica del entorno (p.ej.: default.toml, production.toml). // 2. Configuración específica del entorno (p. ej. default.toml o production.toml).
.add_source(File::from(config_dir.join(format!("{rm}.toml"))).required(false)) .add_source(File::from(config_dir.join(format!("{rm}.toml"))).required(false))
// 3. Configuración local reservada para cada entorno (p.ej.: local.default.toml). // 3. Configuración local reservada para cada entorno (p. ej. local.default.toml).
.add_source(File::from(config_dir.join(format!("local.{rm}.toml"))).required(false)) .add_source(File::from(config_dir.join(format!("local.{rm}.toml"))).required(false))
// 4. Configuración local común (local.toml). // 4. Configuración local común (local.toml).
.add_source(File::from(config_dir.join("local.toml")).required(false)) .add_source(File::from(config_dir.join("local.toml")).required(false))
@ -206,7 +206,7 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(
/// * **Valores por defecto**. Declara un valor por defecto para cada clave obligatoria. Las claves /// * **Valores por defecto**. Declara un valor por defecto para cada clave obligatoria. Las claves
/// opcionales pueden ser `Option<T>`. /// opcionales pueden ser `Option<T>`.
/// ///
/// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p.ej. `[blog]`) para /// * **Secciones únicas**. Agrupa tus claves dentro de una sección exclusiva (p. ej. `[blog]`) para
/// evitar colisiones con otras librerías. /// evitar colisiones con otras librerías.
/// ///
/// * **Solo lectura**. La variable generada es inmutable durante toda la vida del programa. Para /// * **Solo lectura**. La variable generada es inmutable durante toda la vida del programa. Para
@ -229,9 +229,7 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(
macro_rules! include_config { macro_rules! include_config {
( $SETTINGS_NAME:ident : $Settings_Type:ty => [ $( $k:literal => $v:expr ),* $(,)? ] ) => { ( $SETTINGS_NAME:ident : $Settings_Type:ty => [ $( $k:literal => $v:expr ),* $(,)? ] ) => {
#[doc = concat!( #[doc = concat!(
"Referencia y valores por defecto de los ajustes de configuración para [`", "Instancia los ajustes de configuración para [`", stringify!($Settings_Type), "`]."
stringify!($Settings_Type),
"`]."
)] )]
#[doc = ""] #[doc = ""]
#[doc = "Valores por defecto:"] #[doc = "Valores por defecto:"]

View file

@ -24,7 +24,8 @@ impl ActionKey {
/// - `action_type_id`: Tipo de la acción. /// - `action_type_id`: Tipo de la acción.
/// - `theme_type_id`: Opcional, identificador de tipo ([`UniqueId`]) del tema asociado. /// - `theme_type_id`: Opcional, identificador de tipo ([`UniqueId`]) del tema asociado.
/// - `referer_type_id`: Opcional, identificador de tipo ([`UniqueId`]) del componente referido. /// - `referer_type_id`: Opcional, identificador de tipo ([`UniqueId`]) del componente referido.
/// - `referer_id`: Opcional, identificador de la instancia (p.ej. para un formulario concreto). /// - `referer_id`: Opcional, identificador de la instancia (p. ej. para asociar la acción a un
/// componente concreto).
/// ///
/// Esta clave permitirá seleccionar las funciones a ejecutar para ese tipo de acción, con /// Esta clave permitirá seleccionar las funciones a ejecutar para ese tipo de acción, con
/// filtros opcionales por tema, componente, o una instancia concreta según su identificador. /// filtros opcionales por tema, componente, o una instancia concreta según su identificador.

View file

@ -30,7 +30,7 @@ include_config!(SETTINGS: Settings => [
]); ]);
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
/// Ajustes para las secciones globales [`[app]`](App), [`[dev]`](Dev), [`[log]`](Log) y /// Tipos para las secciones globales [`[app]`](App), [`[dev]`](Dev), [`[log]`](Log) y
/// [`[server]`](Server) de [`SETTINGS`]. /// [`[server]`](Server) de [`SETTINGS`].
pub struct Settings { pub struct Settings {
pub app: App, pub app: App,
@ -105,7 +105,7 @@ pub struct Server {
pub bind_address: String, pub bind_address: String,
/// Puerto de escucha del servidor web. /// Puerto de escucha del servidor web.
pub bind_port: u16, pub bind_port: u16,
/// Duración de la cookie de sesión en segundos (p.ej. `604_800` para una semana). /// Duración de la cookie de sesión en segundos (p. ej., `604_800` para una semana).
/// ///
/// El valor `0` indica que la cookie permanecerá activa hasta que se cierre el navegador. /// El valor `0` indica que la cookie permanecerá activa hasta que se cierre el navegador.
pub session_lifetime: i64, pub session_lifetime: i64,

View file

@ -63,7 +63,7 @@ impl Favicon {
/// Le añade un icono genérico con atributo `sizes`, útil para indicar resoluciones específicas. /// Le añade un icono genérico con atributo `sizes`, útil para indicar resoluciones específicas.
/// ///
/// El atributo `sizes` informa al navegador de las dimensiones de la imagen para que seleccione /// El atributo `sizes` informa al navegador de las dimensiones de la imagen para que seleccione
/// el recurso más adecuado. Puede enumerar varias dimensiones separadas por espacios, p.ej. /// el recurso más adecuado. Puede enumerar varias dimensiones separadas por espacios, p. ej.
/// `"16x16 32x32 48x48"` o usar `any` para iconos escalables (SVG). /// `"16x16 32x32 48x48"` o usar `any` para iconos escalables (SVG).
/// ///
/// No es imprescindible, pero puede mejorar la selección del icono más adecuado. /// No es imprescindible, pero puede mejorar la selección del icono más adecuado.
@ -73,7 +73,7 @@ impl Favicon {
/// Le añade un *Apple Touch Icon*, usado por dispositivos iOS para las pantallas de inicio. /// Le añade un *Apple Touch Icon*, usado por dispositivos iOS para las pantallas de inicio.
/// ///
/// Se recomienda indicar también el tamaño, p.ej. `"256x256"`. /// Se recomienda indicar también el tamaño, p. ej. `"256x256"`.
pub fn with_apple_touch_icon(self, image: impl Into<String>, sizes: impl Into<String>) -> Self { pub fn with_apple_touch_icon(self, image: impl Into<String>, sizes: impl Into<String>) -> Self {
self.add_icon_item("apple-touch-icon", image.into(), Some(sizes.into()), None) self.add_icon_item("apple-touch-icon", image.into(), Some(sizes.into()), None)
} }

View file

@ -34,8 +34,8 @@ use std::str::FromStr;
/// ///
/// - Soporta unidades **absolutas** (`cm`, `in`, `mm`, `pc`, `pt`, `px`) y **relativas** (`em`, /// - Soporta unidades **absolutas** (`cm`, `in`, `mm`, `pc`, `pt`, `px`) y **relativas** (`em`,
/// `rem`, `%`, `vh`, `vw`). /// `rem`, `%`, `vh`, `vw`).
/// - `FromStr` para convertir desde texto (p.ej. `"12px"`, `"1.25rem"`, `"auto"`). /// - `FromStr` para convertir desde texto (p. ej., `"12px"`, `"1.25rem"`, `"auto"`).
/// - `Display` para formatear a cadena (p.ej. `UnitValue::Px(12)` genera `"12px"`). /// - `Display` para formatear a cadena (p. ej., `UnitValue::Px(12)` genera `"12px"`).
/// - `Deserialize` delega en `FromStr`, garantizando una gramática única. /// - `Deserialize` delega en `FromStr`, garantizando una gramática única.
/// ///
/// ## Ejemplos /// ## Ejemplos
@ -54,8 +54,8 @@ use std::str::FromStr;
/// ///
/// ## Notas /// ## Notas
/// ///
/// - Las absolutas **no aceptan** decimales (p.ej., `"1.5px"` sería erróneo). /// - Las absolutas **no aceptan** decimales (p. ej., `"1.5px"` sería erróneo).
/// - Se aceptan signos `+`/`-` en todas las unidades (p.ej., `"-12px"`, `"+0.5em"`). /// - Se aceptan signos `+`/`-` en todas las unidades (p. ej., `"-12px"`, `"+0.5em"`).
/// - La comparación de unidad es *case-insensitive* al interpretar el texto (`"PX"`, `"Px"`, …). /// - La comparación de unidad es *case-insensitive* al interpretar el texto (`"PX"`, `"Px"`, …).
/// - **Sobre píxeles**: Los píxeles (px) son relativos al dispositivo de visualización. En /// - **Sobre píxeles**: Los píxeles (px) son relativos al dispositivo de visualización. En
/// dispositivos con baja densidad de píxeles (dpi), 1px equivale a un píxel (punto) del /// dispositivos con baja densidad de píxeles (dpi), 1px equivale a un píxel (punto) del
@ -69,7 +69,7 @@ use std::str::FromStr;
/// - Ejemplo: si `:root { font-size: 16px }` y un contenedor tiene `font-size: 20px`, entonces /// - Ejemplo: si `:root { font-size: 16px }` y un contenedor tiene `font-size: 20px`, entonces
/// dentro del contenedor `1em == 20px` pero `1rem == 16px`. /// dentro del contenedor `1em == 20px` pero `1rem == 16px`.
/// - Uso típico: `rem` para tipografía y espaciados globales (consistencia al cambiar la base del /// - Uso típico: `rem` para tipografía y espaciados globales (consistencia al cambiar la base del
/// sitio); `em` para tamaños que deban escalar **con el propio componente** (p.ej., /// sitio); `em` para tamaños que deban escalar **con el propio componente** (p. ej.,
/// `padding: 0.5em` que crece si el componente aumenta su `font-size`). /// `padding: 0.5em` que crece si el componente aumenta su `font-size`).
/// - **Sobre el viewport**: Si el ancho de la ventana del navegador es de 50cm, 1vw equivale a /// - **Sobre el viewport**: Si el ancho de la ventana del navegador es de 50cm, 1vw equivale a
/// 0.5cm (1vw siempre es 1% del ancho del viewport, independientemente del zoom del navegador o /// 0.5cm (1vw siempre es 1% del ancho del viewport, independientemente del zoom del navegador o
@ -107,26 +107,25 @@ pub enum UnitValue {
} }
impl UnitValue { impl UnitValue {
/// Indica si el valor es **numérico**. /// Indica si el valor es **medible**, incluyendo `Zero` sin unidad.
/// ///
/// Devuelve `true` para `Zero` y las unidades absolutas/relativas, y `false` para /// Devuelve `false` para [`UnitValue::None`] y [`UnitValue::Auto`].
/// [`UnitValue::None`] y [`UnitValue::Auto`].
/// ///
/// # Ejemplos /// # Ejemplos
/// ///
/// ```rust /// ```rust
/// # use pagetop::prelude::*; /// # use pagetop::prelude::*;
/// // Numéricos (incluido el cero sin unidad). /// // Numéricos (incluido el cero sin unidad).
/// assert!(UnitValue::Zero.is_numeric()); /// assert!(UnitValue::Zero.is_measurable());
/// assert!(UnitValue::Px(0).is_numeric()); /// assert!(UnitValue::Px(0).is_measurable());
/// assert!(UnitValue::Px(10).is_numeric()); /// assert!(UnitValue::Px(10).is_measurable());
/// assert!(UnitValue::RelPct(33.0).is_numeric()); /// assert!(UnitValue::RelPct(33.0).is_measurable());
/// // No numéricos. /// // No numéricos.
/// assert!(!UnitValue::None.is_numeric()); /// assert!(!UnitValue::None.is_measurable());
/// assert!(!UnitValue::Auto.is_numeric()); /// assert!(!UnitValue::Auto.is_measurable());
/// ``` /// ```
#[inline] #[inline]
pub const fn is_numeric(&self) -> bool { pub const fn is_measurable(&self) -> bool {
!matches!(self, UnitValue::None | UnitValue::Auto) !matches!(self, UnitValue::None | UnitValue::Auto)
} }
} }
@ -173,9 +172,9 @@ impl fmt::Display for UnitValue {
/// - `""` para `UnitValue::None` /// - `""` para `UnitValue::None`
/// - `"auto"` /// - `"auto"`
/// - **Cero sin unidad**: `"0"`, `"+0"`, `"-0"`, `"0.0"`, `"0."`, `".0"` para `UnitValue::Zero` /// - **Cero sin unidad**: `"0"`, `"+0"`, `"-0"`, `"0.0"`, `"0."`, `".0"` para `UnitValue::Zero`
/// - Porcentaje: `"<n>%"` (p.ej. `"33%"`, `"33 %"`) /// - Porcentaje: `"<n>%"` (p. ej., `"33%"`, `"33 %"`)
/// - Absolutas enteras: `"<entero><unidad>"`, p.ej. `"12px"`, `"-5pt"` /// - Absolutas enteras: `"<entero><unidad>"`, p. ej., `"12px"`, `"-5pt"`
/// - Relativas decimales: `"<float><unidad>"`, p.ej. `"1.25rem"`, `"-0.5vh"`, `".5em"`, `"1.rem"` /// - Relativas decimales: `"<float><unidad>"`, p. ej., `"1.25rem"`, `"-0.5vh"`, `".5em"`, `"1.rem"`
/// ///
/// (Se toleran espacios entre número y unidad: `"12 px"`, `"1.5 rem"`). /// (Se toleran espacios entre número y unidad: `"12 px"`, `"1.5 rem"`).
/// ///
@ -191,9 +190,9 @@ impl fmt::Display for UnitValue {
/// ///
/// ## Errores de interpretación /// ## Errores de interpretación
/// ///
/// - Falta la unidad cuando es necesaria (p.ej. `"12"`, excepto para el valor cero). /// - Falta la unidad cuando es necesaria (p. ej., `"12"`, excepto para el valor cero).
/// - Decimales en valores que deben ser absolutos (p.ej. `"1.5px"`). /// - Decimales en valores que deben ser absolutos (p. ej. `"1.5px"`).
/// - Unidades desconocidas (p.ej. `"10ch"`, no soportada aún). /// - Unidades desconocidas (p. ej., `"10ch"`, no soportada aún).
/// - Notación científica o bases no decimales: `"1e3vw"`, `"0x10px"` (no soportadas). Los ceros a /// - Notación científica o bases no decimales: `"1e3vw"`, `"0x10px"` (no soportadas). Los ceros a
/// la izquierda (p. ej. `"020px"`) se interpretan en **base 10** (`20px`). /// la izquierda (p. ej. `"020px"`) se interpretan en **base 10** (`20px`).
/// ///

View file

@ -181,8 +181,8 @@ pub enum LangMatch {
/// Cuando el identificador de idioma es una cadena vacía. /// Cuando el identificador de idioma es una cadena vacía.
Unspecified, Unspecified,
/// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que /// Si encuentra un [`LanguageIdentifier`] en la lista de idiomas soportados por PageTop que
/// coincide exactamente con el identificador de idioma (p.ej. "es-ES"), o con el identificador /// coincide exactamente con el identificador de idioma (p. ej. "es-ES"), o con el identificador
/// del idioma base (p.ej. "es"). /// del idioma base (p. ej. "es").
Found(&'static LanguageIdentifier), Found(&'static LanguageIdentifier),
/// Si el identificador de idioma no está entre los soportados por PageTop. /// Si el identificador de idioma no está entre los soportados por PageTop.
Unsupported(String), Unsupported(String),
@ -205,13 +205,13 @@ impl LangMatch {
return Self::Unspecified; return Self::Unspecified;
} }
// Intenta aplicar coincidencia exacta con el código completo (p.ej. "es-MX"). // Intenta aplicar coincidencia exacta con el código completo (p. ej. "es-MX").
let lang = language.to_ascii_lowercase(); let lang = language.to_ascii_lowercase();
if let Some(langid) = LANGUAGES.get(lang.as_str()).map(|(langid, _)| langid) { if let Some(langid) = LANGUAGES.get(lang.as_str()).map(|(langid, _)| langid) {
return Self::Found(langid); return Self::Found(langid);
} }
// Si la variante regional no existe, retrocede al idioma base (p.ej. "es"). // Si la variante regional no existe, retrocede al idioma base (p. ej. "es").
if let Some((base_lang, _)) = lang.split_once('-') { if let Some((base_lang, _)) = lang.split_once('-') {
if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) { if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) {
return Self::Found(langid); return Self::Found(langid);
@ -375,13 +375,13 @@ impl L10n {
} }
} }
/// Añade un argumento `{$arg}` `value` a la traducción. /// Añade un argumento `{$arg}` => `value` a la traducción.
pub fn with_arg(mut self, arg: impl Into<String>, value: impl Into<String>) -> Self { pub fn with_arg(mut self, arg: impl Into<String>, value: impl Into<String>) -> Self {
self.args.insert(arg.into(), value.into()); self.args.insert(arg.into(), value.into());
self self
} }
/// Añade varios argumentos a la traducción de una sola vez (p.ej. usando la macro [`hm!`], /// Añade varios argumentos a la traducción de una sola vez (p. ej. usando la macro [`hm!`],
/// también vec![("k", "v")], incluso un array de duplas u otras colecciones). /// también vec![("k", "v")], incluso un array de duplas u otras colecciones).
pub fn with_args<I, K, V>(mut self, args: I) -> Self pub fn with_args<I, K, V>(mut self, args: I) -> Self
where where

View file

@ -215,9 +215,9 @@ async fn unit_value_display_keeps_minus_zero_for_relatives() {
#[pagetop::test] #[pagetop::test]
async fn unit_value_rejects_non_decimal_notations() { async fn unit_value_rejects_non_decimal_notations() {
// Octal, los ceros a la izquierda (p.ej. `"020px"`) se interpretan en **base 10** (`20px`). // Octal, los ceros a la izquierda (p. ej. `"020px"`) se interpretan en **base 10** (`20px`).
assert_eq!(UnitValue::from_str("020px").unwrap(), UnitValue::Px(20)); assert_eq!(UnitValue::from_str("020px").unwrap(), UnitValue::Px(20));
// Notación científica y bases no decimales (p.ej. `"1e3vw"`, `"0x10px"`) no están soportadas. // Notación científica y bases no decimales (p. ej., `"1e3vw"`, `"0x10px"`) no están soportadas.
assert!(UnitValue::from_str("1e3vw").is_err()); assert!(UnitValue::from_str("1e3vw").is_err());
assert!(UnitValue::from_str("0x10px").is_err()); assert!(UnitValue::from_str("0x10px").is_err());
} }