diff --git a/Cargo.lock b/Cargo.lock index 16b0fd66..eda0a31e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1084,9 +1084,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hkdf" @@ -1290,12 +1290,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.12.1" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.16.0", ] [[package]] @@ -1572,7 +1572,6 @@ dependencies = [ "figlet-rs", "fluent-templates", "getter-methods", - "indexmap", "itoa", "pagetop-aliner", "pagetop-bootsier", diff --git a/Cargo.toml b/Cargo.toml index a801b786..a96620d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ config = { version = "0.15", default-features = false, features = ["toml"] } figlet-rs = "0.1" getter-methods = "2.0" itoa = "1.0" -indexmap = "2.12" parking_lot = "0.12" substring = "1.4" terminal_size = "0.4" diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 4031078b..81f0ab08 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -14,12 +14,11 @@ pub enum ItemKind { Void, /// Etiqueta sin comportamiento interactivo. Label(L10n), - /// Elemento de navegación basado en una [`RoutePath`] dinámica devuelta por - /// [`FnPathByContext`]. Opcionalmente, puede abrirse en una nueva ventana y estar inicialmente - /// deshabilitado. + /// Elemento de navegación. Opcionalmente puede abrirse en una nueva ventana y estar + /// inicialmente deshabilitado. Link { label: L10n, - route: FnPathByContext, + path: FnPathByContext, blank: bool, disabled: bool, }, @@ -41,8 +40,8 @@ pub enum ItemKind { /// visible que puede comportarse como texto, enlace, botón, encabezado o separador, según su /// [`ItemKind`]. /// -/// Permite definir el identificador, las clases de estilo adicionales y el tipo de interacción -/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú. +/// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, +/// manteniendo una interfaz común para renderizar todos los elementos del menú. #[derive(AutoDefault, Getters)] pub struct Item { #[getters(skip)] @@ -76,13 +75,13 @@ impl Component for Item { ItemKind::Link { label, - route, + path, blank, disabled, } => { - let route_link = route(cx); + let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && (current_path == Some(route_link.path())); + let is_current = !*disabled && (current_path == Some(&path)); let mut classes = "dropdown-item".to_string(); if is_current { @@ -92,9 +91,9 @@ impl Component for Item { classes.push_str(" disabled"); } - let href = (!*disabled).then_some(route_link); - let target = (!*disabled && *blank).then_some("_blank"); - let rel = (!*disabled && *blank).then_some("noopener noreferrer"); + let href = (!disabled).then_some(path); + let target = (!disabled && *blank).then_some("_blank"); + let rel = (!disabled && *blank).then_some("noopener noreferrer"); let aria_current = (href.is_some() && is_current).then_some("page"); let aria_disabled = disabled.then_some("true"); @@ -165,15 +164,11 @@ impl Item { } /// Crea un enlace para la navegación. - /// - /// La ruta se obtiene invocando [`FnPathByContext`], que devuelve dinámicamente una - /// [`RoutePath`] en función del [`Context`]. El enlace se marca como `active` si la ruta actual - /// del *request* coincide con la ruta de destino (devuelta por `RoutePath::path`). - pub fn link(label: L10n, route: FnPathByContext) -> Self { + pub fn link(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - route, + path, blank: false, disabled: false, }, @@ -182,11 +177,11 @@ impl Item { } /// Crea un enlace deshabilitado que no permite la interacción. - pub fn link_disabled(label: L10n, route: FnPathByContext) -> Self { + pub fn link_disabled(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - route, + path, blank: false, disabled: true, }, @@ -195,11 +190,11 @@ impl Item { } /// Crea un enlace que se abre en una nueva ventana o pestaña. - pub fn link_blank(label: L10n, route: FnPathByContext) -> Self { + pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - route, + path, blank: true, disabled: false, }, @@ -208,11 +203,11 @@ impl Item { } /// Crea un enlace inicialmente deshabilitado que se abriría en una nueva ventana. - pub fn link_blank_disabled(label: L10n, route: FnPathByContext) -> Self { + pub fn link_blank_disabled(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - route, + path, blank: true, disabled: true, }, diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 6c42a76a..192f8df8 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -17,12 +17,11 @@ pub enum ItemKind { Void, /// Etiqueta sin comportamiento interactivo. Label(L10n), - /// Elemento de navegación basado en una [`RoutePath`] dinámica devuelta por - /// [`FnPathByContext`]. Opcionalmente, puede abrirse en una nueva ventana y estar inicialmente - /// deshabilitado. + /// Elemento de navegación. Opcionalmente puede abrirse en una nueva ventana y estar + /// inicialmente deshabilitado. Link { label: L10n, - route: FnPathByContext, + path: FnPathByContext, blank: bool, disabled: bool, }, @@ -72,10 +71,10 @@ impl ItemKind { /// Representa un **elemento individual** de un menú [`Nav`](crate::theme::Nav). /// /// Cada instancia de [`nav::Item`](crate::theme::nav::Item) se traduce en un componente visible que -/// puede comportarse como texto, enlace, contenido HTML o menú desplegable, según su [`ItemKind`]. +/// puede comportarse como texto, enlace, botón o menú desplegable según su [`ItemKind`]. /// -/// Permite definir el identificador, las clases de estilo adicionales y el tipo de interacción -/// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú. +/// Permite definir identificador, clases de estilo adicionales o tipo de interacción asociada, +/// manteniendo una interfaz común para renderizar todos los elementos del menú. #[derive(AutoDefault, Getters)] pub struct Item { #[getters(skip)] @@ -113,13 +112,13 @@ impl Component for Item { ItemKind::Link { label, - route, + path, blank, disabled, } => { - let route_link = route(cx); + let path = path(cx); let current_path = cx.request().map(|request| request.path()); - let is_current = !*disabled && (current_path == Some(route_link.path())); + let is_current = !*disabled && (current_path == Some(&path)); let mut classes = "nav-link".to_string(); if is_current { @@ -129,7 +128,7 @@ impl Component for Item { classes.push_str(" disabled"); } - let href = (!*disabled).then_some(route_link); + let href = (!*disabled).then_some(path); let target = (!*disabled && *blank).then_some("_blank"); let rel = (!*disabled && *blank).then_some("noopener noreferrer"); @@ -203,15 +202,11 @@ impl Item { } /// Crea un enlace para la navegación. - /// - /// La ruta se obtiene invocando [`FnPathByContext`], que devuelve dinámicamente una - /// [`RoutePath`] en función del [`Context`]. El enlace se marca como `active` si la ruta actual - /// del *request* coincide con la ruta de destino (devuelta por `RoutePath::path`). - pub fn link(label: L10n, route: FnPathByContext) -> Self { + pub fn link(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - route, + path, blank: false, disabled: false, }, @@ -220,11 +215,11 @@ impl Item { } /// Crea un enlace deshabilitado que no permite la interacción. - pub fn link_disabled(label: L10n, route: FnPathByContext) -> Self { + pub fn link_disabled(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - route, + path, blank: false, disabled: true, }, @@ -233,11 +228,11 @@ impl Item { } /// Crea un enlace que se abre en una nueva ventana o pestaña. - pub fn link_blank(label: L10n, route: FnPathByContext) -> Self { + pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - route, + path, blank: true, disabled: false, }, @@ -246,11 +241,11 @@ impl Item { } /// Crea un enlace inicialmente deshabilitado que se abriría en una nueva ventana. - pub fn link_blank_disabled(label: L10n, route: FnPathByContext) -> Self { + pub fn link_blank_disabled(label: L10n, path: FnPathByContext) -> Self { Item { item_kind: ItemKind::Link { label, - route, + path, blank: true, disabled: true, }, @@ -271,9 +266,9 @@ impl Item { /// Crea un elemento de navegación que contiene un menú desplegable [`Dropdown`]. /// - /// Sólo se tienen en cuenta **el título** (si no existe, se asigna uno por defecto) y **la - /// lista de elementos** del [`Dropdown`]; el resto de propiedades del componente no afectarán - /// a su representación en [`Nav`]. + /// Sólo se tienen en cuenta **el título** (si no existe le asigna uno por defecto) y **la lista + /// de elementos** del [`Dropdown`]; el resto de propiedades del componente no afectarán a su + /// representación en [`Nav`]. pub fn dropdown(menu: Dropdown) -> Self { Item { item_kind: ItemKind::Dropdown(Typed::with(menu)), diff --git a/src/core/component.rs b/src/core/component.rs index db959cea..b905a495 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -1,6 +1,6 @@ //! API para construir nuevos componentes. -use crate::html::RoutePath; +use std::borrow::Cow; mod definition; pub use definition::{Component, ComponentRender}; @@ -66,31 +66,12 @@ pub use context::{Context, ContextError, ContextOp, Contextual}; /// ``` pub type FnIsRenderable = fn(cx: &Context) -> bool; -/// Alias de función (*callback*) para **resolver una ruta URL** según el contexto de renderizado. +/// Alias de función (*callback*) para **resolver una URL** según el contexto de renderizado. /// -/// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, parámetros, -/// etc.). El resultado se devuelve como una [`RoutePath`], que representa un *path* base junto con -/// una lista opcional de parámetros de consulta. +/// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, etc.). El +/// resultado se devuelve como [`Cow<'static, str>`](std::borrow::Cow), lo que permite: /// -/// Gracias a la implementación de [`RoutePath`] puedes usar rutas estáticas sin asignaciones -/// adicionales: -/// -/// ```rust -/// # use pagetop::prelude::*; -/// # let static_path: FnPathByContext = -/// |_| "/path/to/resource".into() -/// # ; -/// ``` -/// -/// O construir rutas dinámicas en tiempo de ejecución: -/// -/// ```rust -/// # use pagetop::prelude::*; -/// # let dynamic_path: FnPathByContext = -/// |cx| RoutePath::new("/user").with_param("id", cx.param::("user_id").unwrap().to_string()) -/// # ; -/// ``` -/// -/// El componente que reciba un [`FnPathByContext`] invocará esta función durante el renderizado -/// para obtener la URL final para asignarla al atributo HTML correspondiente. -pub type FnPathByContext = fn(cx: &Context) -> RoutePath; +/// - Usar rutas estáticas sin asignaciones adicionales (`"/path".into()`). +/// - Construir rutas dinámicas en tiempo de ejecución (`format!(...).into()`), por ejemplo, en +/// función de parámetros almacenados en [`Context`]. +pub type FnPathByContext = fn(cx: &Context) -> Cow<'static, str>; diff --git a/src/html.rs b/src/html.rs index e0725dde..d94aeea8 100644 --- a/src/html.rs +++ b/src/html.rs @@ -5,9 +5,6 @@ use crate::AutoDefault; mod maud; pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, DOCTYPE}; -mod route; -pub use route::RoutePath; - // **< HTML DOCUMENT ASSETS >*********************************************************************** mod assets; diff --git a/src/html/attr_classes.rs b/src/html/attr_classes.rs index 57a679bb..bb88f587 100644 --- a/src/html/attr_classes.rs +++ b/src/html/attr_classes.rs @@ -67,15 +67,14 @@ impl AttrClasses { } ClassesOp::Remove => { for class in classes { - self.0.retain(|c| c != class); + self.0.retain(|c| c.ne(&class.to_string())); } } ClassesOp::Replace(classes_to_replace) => { let mut pos = self.0.len(); - let replace = classes_to_replace.to_ascii_lowercase(); - let replace: Vec<&str> = replace.split_ascii_whitespace().collect(); + 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 == class) { + 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; diff --git a/src/html/route.rs b/src/html/route.rs deleted file mode 100644 index c7dac096..00000000 --- a/src/html/route.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::AutoDefault; - -use std::borrow::Cow; -use std::fmt; - -/// Representa una ruta como un *path* inicial más una lista opcional de parámetros. -/// -/// Modela rutas del estilo `/path/to/resource?foo=bar&debug` o `https://example.com/path?foo=bar`, -/// pensadas para usarse en atributos HTML como `href`, `action` o `src`. -/// -/// `RoutePath` no valida ni interpreta la estructura del *path*; simplemente concatena los -/// parámetros de consulta sobre el valor proporcionado. -/// -/// # Ejemplos -/// -/// ```rust -/// # use pagetop::prelude::*; -/// // Ruta relativa con parámetros y una *flag* sin valor. -/// let route = RoutePath::new("/search") -/// .with_param("q", "rust") -/// .with_param("page", "2") -/// .with_flag("debug"); -/// assert_eq!(route.to_string(), "/search?q=rust&page=2&debug"); -/// -/// // Ruta absoluta a un recurso externo. -/// let external = RoutePath::new("https://example.com/export").with_param("format", "csv"); -/// assert_eq!(external.to_string(), "https://example.com/export?format=csv"); -/// ``` -#[derive(AutoDefault)] -pub struct RoutePath { - // *Path* inicial sobre el que se añadirán los parámetros. - // - // Puede ser relativo (p. ej. `/about`) o una ruta completa (`https://example.com/about`). - // `RoutePath` no realiza ninguna validación ni normalización. - // - // Se almacena como `Cow<'static, str>` para reutilizar literales estáticos sin asignación - // adicional y, al mismo tiempo, aceptar rutas dinámicas representadas como `String`. - path: Cow<'static, str>, - - // Conjunto de parámetros asociados a la ruta. - // - // Cada clave es única y se mantiene el orden de inserción. El valor vacío se utiliza para - // representar *flags* sin valor explícito (por ejemplo `?debug`). - query: indexmap::IndexMap, -} - -impl RoutePath { - /// Crea un `RoutePath` a partir de un *path* inicial. - /// - /// Por ejemplo: `RoutePath::new("/about")`. - pub fn new(path: impl Into>) -> Self { - Self { - path: path.into(), - query: indexmap::IndexMap::new(), - } - } - - /// Añade o sustituye un parámetro `key=value`. Si la clave ya existe, el valor se sobrescribe. - pub fn with_param(mut self, key: impl Into, value: impl Into) -> Self { - self.query.insert(key.into(), value.into()); - self - } - - /// Añade o sustituye un *flag* sin valor, por ejemplo `?debug`. - pub fn with_flag(mut self, flag: impl Into) -> Self { - self.query.insert(flag.into(), String::new()); - self - } - - /// Devuelve el *path* inicial tal y como se pasó a [`RoutePath::new`], sin parámetros. - pub fn path(&self) -> &str { - &self.path - } -} - -impl fmt::Display for RoutePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.path)?; - if !self.query.is_empty() { - f.write_str("?")?; - for (i, (key, value)) in self.query.iter().enumerate() { - if i > 0 { - f.write_str("&")?; - } - f.write_str(key)?; - if !value.is_empty() { - f.write_str("=")?; - f.write_str(value)?; - } - } - } - Ok(()) - } -} - -impl From<&'static str> for RoutePath { - fn from(path: &'static str) -> Self { - RoutePath::new(path) - } -} - -impl From for RoutePath { - fn from(path: String) -> Self { - RoutePath::new(path) - } -} diff --git a/tools/changelog.sh b/tools/changelog.sh index 035fa729..bd5f20bb 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -35,6 +35,10 @@ cd "$(dirname "$0")/.." || exit 1 # Determina ruta del archivo y ámbito de los archivos afectados para el crate # ------------------------------------------------------------------------------ case "$CRATE" in + pagetop-statics) + CHANGELOG_FILE="helpers/pagetop-statics/CHANGELOG.md" + PATH_FLAGS=(--include-path "helpers/pagetop-statics/**/*") + ;; pagetop-build) CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md" PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*") @@ -43,22 +47,13 @@ case "$CRATE" in CHANGELOG_FILE="helpers/pagetop-macros/CHANGELOG.md" PATH_FLAGS=(--include-path "helpers/pagetop-macros/**/*") ;; - pagetop-minimal) - CHANGELOG_FILE="helpers/pagetop-minimal/CHANGELOG.md" - PATH_FLAGS=(--include-path "helpers/pagetop-minimal/**/*") - ;; - pagetop-statics) - CHANGELOG_FILE="helpers/pagetop-statics/CHANGELOG.md" - PATH_FLAGS=(--include-path "helpers/pagetop-statics/**/*") - ;; pagetop) CHANGELOG_FILE="CHANGELOG.md" PATH_FLAGS=( # Helpers + --exclude-path "helpers/pagetop-statics/**/*" --exclude-path "helpers/pagetop-build/**/*" --exclude-path "helpers/pagetop-macros/**/*" - --exclude-path "helpers/pagetop-minimal/**/*" - --exclude-path "helpers/pagetop-statics/**/*" # Extensions --exclude-path "extensions/pagetop-aliner/**/*" --exclude-path "extensions/pagetop-bootsier/**/*"