diff --git a/CHANGELOG.md b/CHANGELOG.md index f2926a6..bfc9067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ internos pueden omitirse si no afectan al uso del proyecto. - [context] Generaliza los parámetros de contexto - [context] Define un `trait` común de contexto - Modifica tipos para atributos HTML a minúsculas -- Renombra `with_component` por `add_component` +- Renombra `with_component` por `add_child` ### Corregido diff --git a/Cargo.lock b/Cargo.lock index f1b09f9..2f9fa42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1593,6 +1593,7 @@ version = "0.0.18" dependencies = [ "pagetop", "pagetop-build", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0d17082..b1bae0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ actix-web = { workspace = true, default-features = true } actix-session = { version = "0.11", features = ["cookie-session"] } actix-web-files = { package = "actix-files", version = "0.6" } -serde = { version = "1.0", features = ["derive"] } +serde.workspace = true pagetop-macros.workspace = true pagetop-statics.workspace = true @@ -78,6 +78,7 @@ authors = ["Manuel Cillero "] [workspace.dependencies] actix-web = { version = "4.11", default-features = false } +serde = { version = "1.0", features = ["derive"] } # Helpers pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.2", path = "helpers/pagetop-macros" } diff --git a/README.md b/README.md index be72091..9a12c84 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(request) - .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .add_child(Html::with(|_| html! { h1 { "Hello World!" } })) .render() } @@ -102,7 +102,7 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui es un tema para demos y pruebas que muestra esquemáticamente la composición de las páginas HTML. * **[pagetop-bootsier](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier)**, - tema basado en [Bootstrap](https://getbootstrap.com) para ofrecer su catálogo de estilos y + tema basado en [Bootstrap](https://getbootstrap.com) para integrar su catálogo de estilos y componentes flexibles. diff --git a/examples/hello-name.rs b/examples/hello-name.rs index e1285d0..c6a82aa 100644 --- a/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -14,7 +14,7 @@ async fn hello_name( ) -> ResultPage { let name = path.into_inner(); Page::new(request) - .add_component(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) + .add_child(Html::with(move |_| html! { h1 { "Hello " (name) "!" } })) .render() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index d56f210..6481746 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -10,7 +10,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(request) - .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .add_child(Html::with(|_| html! { h1 { "Hello World!" } })) .render() } diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index 0560a06..43fb65a 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -67,10 +67,10 @@ use pagetop::prelude::*; async fn homepage(request: HttpRequest) -> ResultPage { Page::new(request) .with_theme("Aliner") - .add_component( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .add_component(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index b51800c..edbb504 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -68,10 +68,10 @@ use pagetop::prelude::*; async fn homepage(request: HttpRequest) -> ResultPage { Page::new(request) .with_theme("Aliner") - .add_component( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .add_component(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) diff --git a/extensions/pagetop-bootsier/Cargo.toml b/extensions/pagetop-bootsier/Cargo.toml index 11306b6..6df6cf6 100644 --- a/extensions/pagetop-bootsier/Cargo.toml +++ b/extensions/pagetop-bootsier/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.18" edition = "2021" description = """ - Tema de PageTop basado en Bootstrap para dar vida a tus diseños web. + Tema de PageTop basado en Bootstrap para aplicar su catálogo de estilos y componentes flexibles. """ categories = ["web-programming", "gui"] keywords = ["pagetop", "theme", "bootstrap", "css", "js"] @@ -16,6 +16,7 @@ authors.workspace = true [dependencies] pagetop.workspace = true +serde.workspace = true [build-dependencies] pagetop-build.workspace = true diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index 7495bc2..84e11b5 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -2,7 +2,7 @@

PageTop Bootsier

-

Tema de PageTop basado en Bootstrap para dar vida a tus diseños web.

+

Tema de PageTop basado en Bootstrap para aplicar su catálogo de estilos y componentes flexibles.

[![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) @@ -67,10 +67,10 @@ use pagetop::prelude::*; async fn homepage(request: HttpRequest) -> ResultPage { Page::new(request) .with_theme("Bootsier") - .add_component( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .add_component(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index a658333..777f71e 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -3,7 +3,7 @@

PageTop Bootsier

-

Tema de PageTop basado en Bootstrap para ofrecer su catálogo de estilos y componentes flexibles.

+

Tema de PageTop basado en Bootstrap para aplicar su catálogo de estilos y componentes flexibles.

[![Doc API](https://img.shields.io/docsrs/pagetop-bootsier?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-bootsier) [![Crates.io](https://img.shields.io/crates/v/pagetop-bootsier.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-bootsier) diff --git a/src/base/component/block.rs b/src/base/component/block.rs index 9a04c4e..17af50c 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -71,7 +71,7 @@ impl Block { } /// Añade un nuevo componente hijo al bloque. - pub fn add_component(mut self, component: impl Component) -> Self { + pub fn add_child(mut self, component: impl Component) -> Self { self.children .alter_child(ChildOp::Add(Child::with(component))); self diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 5d3440e..7e5c393 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -65,10 +65,10 @@ pub enum IntroOpening { /// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default() -/// .add_component( +/// .add_child( /// Block::new() /// .with_title(L10n::l("intro_custom_block_title")) -/// .add_component(Html::with(move |cx| { +/// .add_child(Html::with(move |cx| { /// html! { /// p { (L10n::l("intro_custom_paragraph_1").using(cx)) } /// p { (L10n::l("intro_custom_paragraph_2").using(cx)) } @@ -301,7 +301,7 @@ impl Intro { /// Añade un nuevo componente hijo a la intro. /// /// Si es un bloque ([`Block`]) aplica estilos específicos para destacarlo. - pub fn add_component(mut self, component: impl Component) -> Self { + pub fn add_child(mut self, component: impl Component) -> Self { self.children .alter_child(ChildOp::Add(Child::with(component))); self diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index d94afad..b875163 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -26,22 +26,22 @@ async fn homepage(request: HttpRequest) -> ResultPage { Page::new(request) .with_title(L10n::l("welcome_title")) - .add_component( + .add_child( Intro::new() - .add_component( + .add_child( Block::new() .with_title(L10n::l("welcome_status_title")) - .add_component(Html::with(move |cx| { + .add_child(Html::with(move |cx| { html! { p { (L10n::l("welcome_status_1").using(cx)) } p { (L10n::l("welcome_status_2").using(cx)) } } })), ) - .add_component( + .add_child( Block::new() .with_title(L10n::l("welcome_support_title")) - .add_component(Html::with(move |cx| { + .add_child(Html::with(move |cx| { html! { p { (L10n::l("welcome_support_1").using(cx)) } p { (L10n::l("welcome_support_2").with_arg("app", app).using(cx)) } diff --git a/src/core/component.rs b/src/core/component.rs index be9bbad..a7faa2f 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -10,4 +10,9 @@ pub use children::{Typed, TypedOp}; mod context; pub use context::{Context, ContextError, ContextOp, Contextual}; + +/// 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, etc.). Debe +/// devolver una referencia válida durante el renderizado. pub type FnPathByContext = fn(cx: &Context) -> &str; diff --git a/src/html/unit.rs b/src/html/unit.rs index 53319dc..4a99de1 100644 --- a/src/html/unit.rs +++ b/src/html/unit.rs @@ -145,9 +145,9 @@ impl UnitValue { impl fmt::Display for UnitValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - UnitValue::None => write!(f, ""), - UnitValue::Auto => write!(f, "auto"), - UnitValue::Zero => write!(f, "0"), + UnitValue::None => Ok(()), + UnitValue::Auto => f.write_str("auto"), + UnitValue::Zero => f.write_str("0"), // Valor absoluto. UnitValue::Cm(av) => write!(f, "{av}cm"), UnitValue::In(av) => write!(f, "{av}in"), diff --git a/src/lib.rs b/src/lib.rs index de6d9e6..a268349 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(request) - .add_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .add_child(Html::with(|_| html! { h1 { "Hello World!" } })) .render() } diff --git a/src/response/page.rs b/src/response/page.rs index 1649d54..036c999 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -93,29 +93,28 @@ impl Page { self } - /// **Obsoleto desde la versión 0.4.0**: usar [`add_component()`](Self::add_component) en su - /// lugar. - #[deprecated(since = "0.4.0", note = "Use `add_component()` instead")] + /// **Obsoleto desde la versión 0.4.0**: usar [`add_child()`](Self::add_child) en su lugar. + #[deprecated(since = "0.4.0", note = "Use `add_child()` instead")] pub fn with_component(self, component: impl Component) -> Self { - self.add_component(component) + self.add_child(component) } - /// **Obsoleto desde la versión 0.4.0**: usar [`add_component_in()`](Self::add_component_in) en - /// su lugar. - #[deprecated(since = "0.4.0", note = "Use `add_component_in()` instead")] + /// **Obsoleto desde la versión 0.4.0**: usar [`add_child_in()`](Self::add_child_in) en su + /// lugar. + #[deprecated(since = "0.4.0", note = "Use `add_child_in()` instead")] pub fn with_component_in(self, region_key: &'static str, component: impl Component) -> Self { - self.add_component_in(region_key, component) + self.add_child_in(region_key, component) } - /// Añade un componente a la región de contenido por defecto. - pub fn add_component(mut self, component: impl Component) -> Self { + /// Añade un componente hijo a la región de contenido por defecto. + pub fn add_child(mut self, component: impl Component) -> Self { self.context .alter_child_in(REGION_CONTENT, ChildOp::Add(Child::with(component))); self } - /// Añade un componente en una región (`region_key`) de la página. - pub fn add_component_in(mut self, region_key: &'static str, component: impl Component) -> Self { + /// Añade un componente hijo en una región (`region_key`) de la página. + pub fn add_child_in(mut self, region_key: &'static str, component: impl Component) -> Self { self.context .alter_child_in(region_key, ChildOp::Add(Child::with(component))); self diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 50e1c77..9945a94 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -24,9 +24,9 @@ impl Display for ErrorPage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { // Error 304. - ErrorPage::NotModified(_) => write!(f, "Not Modified"), + ErrorPage::NotModified(_) => f.write_str("Not Modified"), // Error 400. - ErrorPage::BadRequest(_) => write!(f, "Bad Client Data"), + ErrorPage::BadRequest(_) => f.write_str("Bad Client Data"), // Error 403. ErrorPage::AccessDenied(request) => { let mut error_page = Page::new(request.clone()); @@ -34,12 +34,12 @@ impl Display for ErrorPage { if let Ok(page) = error_page .with_title(L10n::n("Error FORBIDDEN")) .with_layout("error") - .add_component(Html::with(move |_| error403.clone())) + .add_child(Html::with(move |_| error403.clone())) .render() { write!(f, "{}", page.into_string()) } else { - write!(f, "Access Denied") + f.write_str("Access Denied") } } // Error 404. @@ -49,20 +49,20 @@ impl Display for ErrorPage { if let Ok(page) = error_page .with_title(L10n::n("Error RESOURCE NOT FOUND")) .with_layout("error") - .add_component(Html::with(move |_| error404.clone())) + .add_child(Html::with(move |_| error404.clone())) .render() { write!(f, "{}", page.into_string()) } else { - write!(f, "Not Found") + f.write_str("Not Found") } } // Error 412. - ErrorPage::PreconditionFailed(_) => write!(f, "Precondition Failed"), + ErrorPage::PreconditionFailed(_) => f.write_str("Precondition Failed"), // Error 500. - ErrorPage::InternalError(_) => write!(f, "Internal Error"), + ErrorPage::InternalError(_) => f.write_str("Internal Error"), // Error 504. - ErrorPage::Timeout(_) => write!(f, "Timeout"), + ErrorPage::Timeout(_) => f.write_str("Timeout"), } } } diff --git a/src/util.rs b/src/util.rs index f73cf78..3d07361 100644 --- a/src/util.rs +++ b/src/util.rs @@ -70,14 +70,12 @@ macro_rules! join { }; } -/// Concatena los fragmentos no vacíos en un [`Option`] con un separador opcional. +/// Concatena los fragmentos **no vacíos** en un [`Option`] con un separador opcional. /// -/// Esta macro acepta cualquier número de fragmentos que implementen [`AsRef`] para concatenar -/// todos los fragmentos no vacíos usando opcionalmente un separador. +/// Acepta cualquier número de fragmentos que implementen [`AsRef`]. Si todos los fragmentos +/// están vacíos, devuelve `None`. /// -/// Si todos los fragmentos están vacíos, devuelve [`None`]. -/// -/// # Ejemplo +/// # Ejemplos /// /// ```rust /// # use pagetop::prelude::*; @@ -90,7 +88,7 @@ macro_rules! join { /// assert_eq!(result_without_separator, Some("HelloWorld".to_string())); /// /// // Devuelve `None` si todos los fragmentos están vacíos. -/// let result_empty = join_opt!(["", "", ""]); +/// let result_empty = join_opt!(["", "", ""]; ","); /// assert_eq!(result_empty, None); /// ``` #[macro_export] @@ -100,12 +98,16 @@ macro_rules! join_opt { (!s.is_empty()).then_some(s) }}; ([$($arg:expr),* $(,)?]; $separator:expr) => {{ - let s = [$($arg),*] - .iter() - .filter(|&item| !item.is_empty()) - .cloned() - .collect::>() - .join($separator); + 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) }}; } @@ -153,7 +155,7 @@ macro_rules! join_pair { }}; } -/// Concatena varios fragmentos en un [`Option`] si ninguno está vacío. +/// Concatena varios fragmentos en un [`Option`] **si ninguno está vacío**. /// /// Si alguno de los fragmentos, que deben implementar [`AsRef`], está vacío, devuelve /// [`None`]. Opcionalmente se puede indicar un separador entre los fragmentos concatenados.