diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 23e07722..5fabdacc 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -35,6 +35,7 @@ use pagetop::prelude::*; struct MyApp; +#[async_trait] impl Extension for MyApp { fn dependencies(&self) -> Vec { vec![ @@ -69,13 +70,13 @@ async fn homepage(request: HttpRequest) -> Result { p { (L10n::l("sample_content").using(cx)) } })), ) - .render() + .render().await } ``` */ #![doc( - html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico" )] use pagetop::prelude::*; @@ -99,6 +100,7 @@ mod handlers { /// Implementa el tema. pub struct Bootsier; +#[async_trait] impl Extension for Bootsier { fn name(&self) -> L10n { L10n::t("extension_name", &LOCALES_BOOTSIER) @@ -128,13 +130,14 @@ impl Extension for Bootsier { } } +#[async_trait] impl Theme for Bootsier { #[inline] fn default_template(&self) -> TemplateRef { &BootsierTemplate::Standard } - fn handle_component( + async fn handle_component( &self, component: &mut dyn Component, cx: &mut Context, diff --git a/extensions/pagetop-bootsier/src/theme/bs/container/component.rs b/extensions/pagetop-bootsier/src/theme/bs/container/component.rs index a398a05b..b0fba98e 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/container/component.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/container/component.rs @@ -31,6 +31,7 @@ pub struct Container { children: Children, } +#[async_trait] impl Component for Container { fn new() -> Self { Self::default() @@ -44,8 +45,8 @@ impl Component for Container { self.alter_prop(PropsOp::prepend_classes(self.container_width().to_class())); } - fn prepare(&self, cx: &mut Context) -> Result { - let output = self.children().render(cx); + async fn prepare(&self, cx: &mut Context) -> Result { + let output = self.children().render(cx).await; if output.is_empty() { return Ok(html! {}); } diff --git a/extensions/pagetop-bootsier/src/theme/bs/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/bs/dropdown/component.rs index 0ae36f51..942f425c 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/dropdown/component.rs @@ -63,6 +63,7 @@ pub struct Dropdown { items: Children, } +#[async_trait] impl Component for Dropdown { fn new() -> Self { Self::default() @@ -78,9 +79,9 @@ impl Component for Dropdown { )); } - fn prepare(&self, cx: &mut Context) -> Result { + async fn prepare(&self, cx: &mut Context) -> Result { // Si no hay elementos en el menú, no se prepara. - let items = self.items().render(cx); + let items = self.items().render(cx).await; if items.is_empty() { return Ok(html! {}); } diff --git a/extensions/pagetop-bootsier/src/theme/bs/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/bs/dropdown/item.rs index 75d5dd8b..df3947b7 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/dropdown/item.rs @@ -51,6 +51,7 @@ pub struct Item { item_kind: ItemKind, } +#[async_trait] impl Component for Item { fn new() -> Self { Self::default() @@ -60,7 +61,7 @@ impl Component for Item { self.props.get_id() } - fn prepare(&self, cx: &mut Context) -> Result { + async fn prepare(&self, cx: &mut Context) -> Result { Ok(match self.item_kind() { ItemKind::Void => html! {}, diff --git a/extensions/pagetop-bootsier/src/theme/bs/dropdown/props.rs b/extensions/pagetop-bootsier/src/theme/bs/dropdown/props.rs index ef1a0de0..a607fc80 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/dropdown/props.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/dropdown/props.rs @@ -112,7 +112,7 @@ impl Direction { /// Alineación horizontal del menú desplegable [`Dropdown`](crate::theme::bs::Dropdown). /// /// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una -/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]). +/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`](token::BreakPoint)). #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum MenuAlign { /// Alineación al inicio (comportamiento por defecto). diff --git a/extensions/pagetop-bootsier/src/theme/bs/icon.rs b/extensions/pagetop-bootsier/src/theme/bs/icon.rs index 970a6cd4..1b519eaf 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/icon.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/icon.rs @@ -21,6 +21,7 @@ pub struct Icon { aria_label: AttrL10n, } +#[async_trait] impl Component for Icon { fn new() -> Self { Self::default() @@ -39,7 +40,7 @@ impl Component for Icon { } } - fn prepare(&self, cx: &mut Context) -> Result { + async fn prepare(&self, cx: &mut Context) -> Result { Ok(match self.icon_kind() { IconKind::None => html! {}, IconKind::Font(_) => { diff --git a/extensions/pagetop-bootsier/src/theme/bs/image/component.rs b/extensions/pagetop-bootsier/src/theme/bs/image/component.rs index 2fe128b6..fb740ab1 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/image/component.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/image/component.rs @@ -24,6 +24,7 @@ pub struct Image { alternative: Attr, } +#[async_trait] impl Component for Image { fn new() -> Self { Self::default() @@ -38,7 +39,7 @@ impl Component for Image { self.alter_prop(PropsOp::prepend_classes(self.source().to_class())); } - fn prepare(&self, cx: &mut Context) -> Result { + async fn prepare(&self, cx: &mut Context) -> Result { let dimensions = self.size().to_style(); let alt_text = self.alternative().lookup(cx).unwrap_or_default(); let is_decorative = alt_text.is_empty(); @@ -52,7 +53,7 @@ impl Component for Image { aria-label=[(!is_decorative).then_some(alt_text)] aria-hidden=[is_decorative.then_some("true")] { - (logo.render(cx)) + (logo.markup(cx)) } }); } diff --git a/extensions/pagetop-bootsier/src/theme/bs/nav/component.rs b/extensions/pagetop-bootsier/src/theme/bs/nav/component.rs index 70ed1861..c7f3df0f 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/nav/component.rs @@ -44,6 +44,7 @@ pub struct Nav { items: Children, } +#[async_trait] impl Component for Nav { fn new() -> Self { Self::default() @@ -63,8 +64,8 @@ impl Component for Nav { })); } - fn prepare(&self, cx: &mut Context) -> Result { - let items = self.items().render(cx); + async fn prepare(&self, cx: &mut Context) -> Result { + let items = self.items().render(cx).await; if items.is_empty() { return Ok(html! {}); } diff --git a/extensions/pagetop-bootsier/src/theme/bs/nav/item.rs b/extensions/pagetop-bootsier/src/theme/bs/nav/item.rs index 81a00d36..80078594 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/nav/item.rs @@ -65,6 +65,7 @@ pub struct Item { item_kind: ItemKind, } +#[async_trait] impl Component for Item { fn new() -> Self { Self::default() @@ -78,7 +79,7 @@ impl Component for Item { self.alter_prop(PropsOp::prepend_classes(self.item_kind().as_str())); } - fn prepare(&self, cx: &mut Context) -> Result { + async fn prepare(&self, cx: &mut Context) -> Result { Ok(match self.item_kind() { ItemKind::Void => html! {}, @@ -133,13 +134,13 @@ impl Component for Item { ItemKind::Html(html) => html! { li (self.props()) { - (html.render(cx)) + (html.render(cx).await) } }, ItemKind::Dropdown(menu) => { if let Some(dd) = menu.get() { - let items = dd.items().render(cx); + let items = dd.items().render(cx).await; if items.is_empty() { return Ok(html! {}); } diff --git a/extensions/pagetop-bootsier/src/theme/bs/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/bs/navbar/brand.rs index 96e34934..8b2b434c 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/navbar/brand.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/navbar/brand.rs @@ -25,13 +25,14 @@ pub struct Brand { route: Option, } +#[async_trait] impl Component for Brand { fn new() -> Self { Self::default() } - fn prepare(&self, cx: &mut Context) -> Result { - let image = self.image().render(cx); + async fn prepare(&self, cx: &mut Context) -> Result { + let image = self.image().render(cx).await; let title = self.title().using(cx); if title.is_empty() && image.is_empty() { return Ok(html! {}); diff --git a/extensions/pagetop-bootsier/src/theme/bs/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/bs/navbar/component.rs index 25f9ee60..d6de4de1 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/navbar/component.rs @@ -149,6 +149,7 @@ pub struct Navbar { items: Children, } +#[async_trait] impl Component for Navbar { fn new() -> Self { Self::default() @@ -171,7 +172,7 @@ impl Component for Navbar { })); } - fn prepare(&self, cx: &mut Context) -> Result { + async fn prepare(&self, cx: &mut Context) -> Result { // Botón de despliegue (colapso u offcanvas) para la barra. fn button(cx: &mut Context, data_bs_toggle: &str, id_content: &str) -> Markup { let id_content_target = util::join!("#", id_content); @@ -196,7 +197,7 @@ impl Component for Navbar { } // Si no hay contenidos, no tiene sentido mostrar una barra vacía. - let items = self.items().render(cx); + let items = self.items().render(cx).await; if items.is_empty() { return Ok(html! {}); } @@ -225,7 +226,7 @@ impl Component for Navbar { // Barra con marca a la izquierda, siempre visible. bs::navbar::Layout::SimpleBrandLeft(brand) => { - (brand.render(cx)) + (brand.render(cx).await) (items) }, @@ -233,7 +234,7 @@ impl Component for Navbar { bs::navbar::Layout::BrandLeft(brand) => { @let id_content = util::join!(id, "-content"); - (brand.render(cx)) + (brand.render(cx).await) (button(cx, TOGGLE_COLLAPSE, &id_content)) div id=(&id_content) class="collapse navbar-collapse" { (items) @@ -245,7 +246,7 @@ impl Component for Navbar { @let id_content = util::join!(id, "-content"); (button(cx, TOGGLE_COLLAPSE, &id_content)) - (brand.render(cx)) + (brand.render(cx).await) div id=(&id_content) class="collapse navbar-collapse" { (items) } @@ -257,7 +258,7 @@ impl Component for Navbar { (button(cx, TOGGLE_OFFCANVAS, &id_content)) @if let Some(oc) = offcanvas.get() { - (oc.render_offcanvas(cx, Some(self.items()))) + (oc.render_offcanvas(cx, Some(self.items())).await) } }, @@ -265,10 +266,10 @@ impl Component for Navbar { bs::navbar::Layout::OffcanvasBrandLeft(brand, offcanvas) => { @let id_content = offcanvas.id().unwrap_or_default(); - (brand.render(cx)) + (brand.render(cx).await) (button(cx, TOGGLE_OFFCANVAS, &id_content)) @if let Some(oc) = offcanvas.get() { - (oc.render_offcanvas(cx, Some(self.items()))) + (oc.render_offcanvas(cx, Some(self.items())).await) } }, @@ -277,9 +278,9 @@ impl Component for Navbar { @let id_content = offcanvas.id().unwrap_or_default(); (button(cx, TOGGLE_OFFCANVAS, &id_content)) - (brand.render(cx)) + (brand.render(cx).await) @if let Some(oc) = offcanvas.get() { - (oc.render_offcanvas(cx, Some(self.items()))) + (oc.render_offcanvas(cx, Some(self.items())).await) } }, } diff --git a/extensions/pagetop-bootsier/src/theme/bs/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/bs/navbar/item.rs index a4b1a2c6..27e785aa 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/navbar/item.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/navbar/item.rs @@ -26,6 +26,7 @@ pub enum Item { Text(L10n), } +#[async_trait] impl Component for Item { fn new() -> Self { Self::default() @@ -42,19 +43,19 @@ impl Component for Item { fn setup(&mut self, _cx: &Context) { if let Self::Nav(nav) = self { - if let Some(mut nav) = nav.get() { + if let Some(nav) = nav.get_mut() { nav.alter_prop(PropsOp::prepend_classes("navbar-nav")); } } } - fn prepare(&self, cx: &mut Context) -> Result { + async fn prepare(&self, cx: &mut Context) -> Result { Ok(match self { Self::Void => html! {}, - Self::Brand(brand) => html! { (brand.render(cx)) }, + Self::Brand(brand) => html! { (brand.render(cx).await) }, Self::Nav(nav) => { if let Some(nav) = nav.get() { - let items = nav.items().render(cx); + let items = nav.items().render(cx).await; if items.is_empty() { return Ok(html! {}); } diff --git a/extensions/pagetop-bootsier/src/theme/bs/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/bs/offcanvas/component.rs index effd3069..d1931510 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/offcanvas/component.rs @@ -61,6 +61,7 @@ pub struct Offcanvas { children: Children, } +#[async_trait] impl Component for Offcanvas { fn new() -> Self { Self::default() @@ -84,8 +85,8 @@ impl Component for Offcanvas { })); } - fn prepare(&self, cx: &mut Context) -> Result { - Ok(self.render_offcanvas(cx, None)) + async fn prepare(&self, cx: &mut Context) -> Result { + Ok(self.render_offcanvas(cx, None).await) } } @@ -167,9 +168,17 @@ impl Offcanvas { // **< Offcanvas HELPERS >********************************************************************** - pub(crate) fn render_offcanvas(&self, cx: &mut Context, extra: Option<&Children>) -> Markup { - let body = self.children().render(cx); - let body_extra = extra.map(|c| c.render(cx)).unwrap_or_else(|| html! {}); + pub(crate) async fn render_offcanvas( + &self, + cx: &mut Context, + extra: Option<&Children>, + ) -> Markup { + let body = self.children().render(cx).await; + let body_extra = if let Some(c) = extra { + c.render(cx).await + } else { + html! {} + }; if body.is_empty() && body_extra.is_empty() { return html! {}; } diff --git a/extensions/pagetop-htmx/README.md b/extensions/pagetop-htmx/README.md index 1bf1c623..5d7cf15f 100644 --- a/extensions/pagetop-htmx/README.md +++ b/extensions/pagetop-htmx/README.md @@ -34,6 +34,7 @@ use pagetop::prelude::*; struct MyApp; +#[async_trait] impl Extension for MyApp { fn dependencies(&self) -> Vec { vec![ @@ -59,7 +60,7 @@ async fn homepage(request: HttpRequest) -> Result { } div #result {} })) - .render() + .render().await } ``` diff --git a/extensions/pagetop-htmx/src/hx.rs b/extensions/pagetop-htmx/src/hx.rs index f1f1f666..744965e4 100644 --- a/extensions/pagetop-htmx/src/hx.rs +++ b/extensions/pagetop-htmx/src/hx.rs @@ -332,7 +332,7 @@ pub const DISABLE: &str = "hx-disable"; /// Genera `hx-on:{event}` para escuchar eventos nativos del DOM en línea. /// -/// Es la alternativa de HTMX a los manejadores `on*` de HTML (`onclick`, `onmouseenter`, ...). La +/// Es la alternativa de HTMX a los *handlers* `on*` de HTML (`onclick`, `onmouseenter`, ...). La /// diferencia clave está en cómo los trata el navegador bajo una política CSP (*Content Security /// Policy*): los atributos `on*` son JavaScript en línea y quedan bloqueados si la CSP no incluye /// `'unsafe-inline'`; en cambio, `hx-on:*` es un atributo de datos que HTMX lee e interpreta desde diff --git a/extensions/pagetop-htmx/src/lib.rs b/extensions/pagetop-htmx/src/lib.rs index e65f310c..d3c0c782 100644 --- a/extensions/pagetop-htmx/src/lib.rs +++ b/extensions/pagetop-htmx/src/lib.rs @@ -35,6 +35,7 @@ use pagetop::prelude::*; struct MyApp; +#[async_trait] impl Extension for MyApp { fn dependencies(&self) -> Vec { vec![ @@ -60,7 +61,7 @@ async fn homepage(request: HttpRequest) -> Result { } div #result {} })) - .render() + .render().await } ``` */ @@ -91,6 +92,7 @@ include_locales!(LOCALES_HTMX); /// /// struct MyApp; /// +/// #[async_trait] /// impl Extension for MyApp { /// fn dependencies(&self) -> Vec { /// vec![ @@ -103,6 +105,7 @@ include_locales!(LOCALES_HTMX); /// ``` pub struct Htmx; +#[async_trait] impl Extension for Htmx { fn name(&self) -> L10n { L10n::t("extension_name", &LOCALES_HTMX) diff --git a/extensions/pagetop-htmx/src/request.rs b/extensions/pagetop-htmx/src/request.rs index 32b68f86..55dee38f 100644 --- a/extensions/pagetop-htmx/src/request.rs +++ b/extensions/pagetop-htmx/src/request.rs @@ -29,7 +29,7 @@ use pagetop::prelude::*; /// .with_child(Html::with(|_| html! { /// ul { li { "Item 1" } li { "Item 2" } } /// })) -/// .render() +/// .render().await /// .into_response() /// } /// }