Implementa temas hijo y macro render_component!

Añade `Theme::parent()` para declarar jerarquías de herencia entre
temas. Sustituye la acción `PrepareRender<C>` por el método
`Theme::prepare_component()` y la macro `render_component!`.
This commit is contained in:
Manuel Cillero 2026-03-22 08:58:15 +01:00
parent af309930f7
commit 4cbe84b4c0
14 changed files with 161 additions and 139 deletions

View file

@ -26,7 +26,7 @@ según las necesidades de cada proyecto, incluyendo:
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
de PageTop o de terceros. de PageTop o de terceros.
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
componentes sin comprometer su funcionalidad. componentes. Pueden crearse temas hijo que heredan y refinan el comportamiento de su tema padre.
## ⚡️ Guía rápida ## ⚡️ Guía rápida

View file

@ -5,6 +5,3 @@ pub use before_render_component::*;
mod after_render_component; mod after_render_component;
pub use after_render_component::*; pub use after_render_component::*;
mod prepare_render;
pub use prepare_render::*;

View file

@ -1,65 +0,0 @@
use crate::prelude::*;
/// Tipo de función para alterar el renderizado de un componente.
///
/// Permite a un [tema](crate::base::action::theme) sobreescribir el renderizado predeterminado de
/// los componentes.
///
/// Recibe una referencia al componente `component` y una referencia mutable al contexto `cx`.
pub type FnPrepareRender<C> = fn(component: &C, cx: &mut Context) -> Result<Markup, ComponentError>;
/// Ejecuta [`FnPrepareRender`] para preparar el renderizado de un componente.
///
/// Permite a un tema hacer una implementación nueva del renderizado de un componente.
pub struct PrepareRender<C: Component> {
f: FnPrepareRender<C>,
theme_type_id: Option<UniqueId>,
referer_type_id: Option<UniqueId>,
}
/// Filtro para despachar [`FnPrepareRender`] que modifica el renderizado de un componente `C`.
impl<C: Component> ActionDispatcher for PrepareRender<C> {
/// Devuelve el identificador de tipo ([`UniqueId`]) del tema.
fn theme_type_id(&self) -> Option<UniqueId> {
self.theme_type_id
}
/// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`.
fn referer_type_id(&self) -> Option<UniqueId> {
self.referer_type_id
}
}
impl<C: Component> PrepareRender<C> {
/// Permite [registrar](Extension::actions) una nueva acción [`FnPrepareRender`] para un tema
/// dado.
pub fn new(theme: ThemeRef, f: FnPrepareRender<C>) -> Self {
PrepareRender {
f,
theme_type_id: Some(theme.type_id()),
referer_type_id: Some(UniqueId::of::<C>()),
}
}
/// Despacha las acciones. Se detiene en cuanto una renderiza o produce un error.
#[inline]
pub(crate) fn dispatch(component: &C, cx: &mut Context) -> Result<Markup, ComponentError> {
let mut render_result: Result<Markup, ComponentError> = Ok(html! {});
dispatch_actions_until(
&ActionKey::new(
UniqueId::of::<Self>(),
Some(cx.theme().type_id()),
Some(UniqueId::of::<C>()),
None,
),
|action: &Self| match &render_result {
Ok(markup) if markup.is_empty() => {
render_result = (action.f)(component, cx);
std::ops::ControlFlow::Continue(())
}
_ => std::ops::ControlFlow::Break(()),
},
);
render_result
}
}

View file

@ -149,6 +149,8 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(
.expect("Failed to set application run mode") .expect("Failed to set application run mode")
}); });
// **< include_config! >****************************************************************************
/// Incluye los ajustes necesarios de la configuración anticipando valores por defecto. /// Incluye los ajustes necesarios de la configuración anticipando valores por defecto.
/// ///
/// # Sintaxis /// # Sintaxis

View file

@ -12,7 +12,9 @@ use list::ActionsList;
mod all; mod all;
pub(crate) use all::add_action; pub(crate) use all::add_action;
pub use all::{dispatch_actions, dispatch_actions_until}; pub use all::dispatch_actions;
// **< actions_boxed! >*****************************************************************************
/// Facilita la implementación del método [`actions()`](crate::core::extension::Extension::actions). /// Facilita la implementación del método [`actions()`](crate::core::extension::Extension::actions).
/// ///
@ -22,12 +24,11 @@ pub use all::{dispatch_actions, dispatch_actions_until};
/// # Ejemplo /// # Ejemplo
/// ///
/// ```rust,ignore /// ```rust,ignore
/// # use pagetop::prelude::*;
/// impl Extension for MyTheme { /// impl Extension for MyTheme {
/// fn actions(&self) -> Vec<ActionBox> { /// fn actions(&self) -> Vec<ActionBox> {
/// actions_boxed![ /// actions_boxed![
/// action::theme::BeforeRender::<Button>::new(&Self, before_render_button), /// action::theme::BeforeRender::<Button>::new(&Self, before_render_button),
/// action::theme::PrepareRender::<Error404>::new(&Self, render_error404), /// action::theme::AfterRender::<Button>::new(&Self, after_render_button),
/// ] /// ]
/// } /// }
/// } /// }
@ -35,7 +36,7 @@ pub use all::{dispatch_actions, dispatch_actions_until};
/// impl Theme for MyTheme {} /// impl Theme for MyTheme {}
/// ///
/// fn before_render_button(c: &mut Button, cx: &mut Context) { todo!() } /// fn before_render_button(c: &mut Button, cx: &mut Context) { todo!() }
/// fn render_error404(c: &Error404, cx: &mut Context) -> Markup { todo!() } /// fn after_render_button(c: &mut Button, cx: &mut Context) { todo!() }
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! actions_boxed { macro_rules! actions_boxed {

View file

@ -72,18 +72,3 @@ where
list.iter_map(f); list.iter_map(f);
} }
} }
/// Despacha las funciones asociadas a una [`ActionKey`] con posible salida anticipada.
///
/// Funciona igual que [`dispatch_actions`], pero el *closure* puede devolver
/// [`std::ops::ControlFlow::Continue`] para continuar ejecutando la siguiente acción; o
/// [`std::ops::ControlFlow::Break`] para detener la iteración inmediatamente.
pub fn dispatch_actions_until<A, F>(key: &ActionKey, f: F)
where
A: ActionDispatcher,
F: FnMut(&A) -> std::ops::ControlFlow<()>,
{
if let Some(list) = ACTIONS.read().get(key) {
list.iter_try_map(f);
}
}

View file

@ -39,21 +39,4 @@ impl ActionsList {
}) })
.collect(); .collect();
} }
pub fn iter_try_map<A, F>(&self, mut f: F)
where
A: ActionDispatcher,
F: FnMut(&A) -> std::ops::ControlFlow<()>,
{
let list = self.0.read();
for a in list.iter().rev() {
if let Some(action) = (**a).downcast_ref::<A>() {
if f(action).is_break() {
break;
}
} else {
trace::error!("Failed to downcast action of type {}", (**a).type_name());
}
}
}
} }

View file

@ -1,5 +1,6 @@
use crate::base::action; use crate::base::action;
use crate::core::component::{ComponentError, Context, Contextual}; use crate::core::component::{ComponentError, Context, Contextual};
use crate::core::theme::ThemeRef;
use crate::core::{AnyInfo, TypeInfo}; use crate::core::{AnyInfo, TypeInfo};
use crate::html::{html, Markup}; use crate::html::{html, Markup};
@ -70,16 +71,15 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync {
#[allow(unused_variables)] #[allow(unused_variables)]
fn setup_before_prepare(&mut self, cx: &mut Context) {} fn setup_before_prepare(&mut self, cx: &mut Context) {}
/// Devuelve el marcado HTML del componente usando el contexto proporcionado. /// Versión del componente para preparar su propio renderizado.
/// ///
/// Este método forma parte del ciclo de vida de los componentes y se invoca automáticamente /// Este método forma parte del ciclo de vida de los componentes y se invoca automáticamente
/// durante el proceso de construcción del documento. Cada componente lo implementa para generar /// durante el proceso de construcción del documento cuando ningún tema sobrescribe el
/// su propio contenido HTML. Los temas hijo pueden sobrescribir opcionalmente su resultado /// renderizado mediante [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component).
/// mediante la acción [`PrepareRender`](crate::base::action::theme::PrepareRender).
/// ///
/// Se recomienda obtener los datos del componente a través de sus propios métodos para que los /// Se recomienda obtener los datos del componente a través de sus propios métodos para que los
/// temas hijo que implementen dicha acción puedan generar el nuevo HTML sin depender de los /// temas puedan implementar [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component)
/// detalles internos del componente. /// sin depender de los detalles internos del componente.
/// ///
/// Por defecto, devuelve un [`Markup`] vacío (`Ok(html! {})`). /// Por defecto, devuelve un [`Markup`] vacío (`Ok(html! {})`).
/// ///
@ -104,10 +104,11 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync {
/// 4. Despacha [`action::component::BeforeRender<C>`](crate::base::action::component::BeforeRender) /// 4. Despacha [`action::component::BeforeRender<C>`](crate::base::action::component::BeforeRender)
/// para que otras extensiones puedan también hacer ajustes previos. /// para que otras extensiones puedan también hacer ajustes previos.
/// 5. **Prepara el renderizado del componente**: /// 5. **Prepara el renderizado del componente**:
/// - Despacha [`action::theme::PrepareRender<C>`](crate::base::action::theme::PrepareRender) /// - Recorre la cadena de temas llamando a
/// para permitir al tema generar un renderizado alternativo. /// [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component) en cada nivel
/// - Si el tema no lo modifica, llama a [`prepare_component()`](Component::prepare_component) /// (hijo → padre → abuelo…) hasta que uno devuelva `Some`.
/// para obtener el renderizado por defecto del componente. /// - Si ningún tema lo sobrescribe, llama a
/// [`Component::prepare_component()`](Component::prepare_component) del propio componente.
/// 6. Despacha [`action::theme::AfterRender<C>`](crate::base::action::theme::AfterRender) para /// 6. Despacha [`action::theme::AfterRender<C>`](crate::base::action::theme::AfterRender) para
/// que el tema pueda aplicar ajustes finales. /// que el tema pueda aplicar ajustes finales.
/// 7. Despacha [`action::component::AfterRender<C>`](crate::base::action::component::AfterRender) /// 7. Despacha [`action::component::AfterRender<C>`](crate::base::action::component::AfterRender)
@ -129,29 +130,23 @@ impl<C: Component> ComponentRender for C {
// Acciones de las extensiones antes de renderizar el componente. // Acciones de las extensiones antes de renderizar el componente.
action::component::BeforeRender::dispatch(self, cx); action::component::BeforeRender::dispatch(self, cx);
// Prepara el renderizado del componente. // Prepara el renderizado: recorre la cadena de temas, luego el componente.
let prepare = match action::theme::PrepareRender::dispatch(self, cx) { let prepare = match 'resolve: {
Ok(markup) if !markup.is_empty() => markup, let mut t: Option<ThemeRef> = Some(cx.theme());
Ok(_) => match self.prepare_component(cx) { while let Some(theme) = t {
Ok(markup) => markup, if let Some(r) = theme.prepare_component(self, cx) {
Err(error) => { break 'resolve r;
crate::trace::error!(
path = cx.request().map(|r| r.path()).unwrap_or("<unknown>"),
component = self.name(),
id = self.id().as_deref().unwrap_or("<not set>"),
source = "prepare_component",
"render failed, using fallback: {}",
error.message()
);
error.into_fallback()
} }
}, t = theme.parent();
}
self.prepare_component(cx)
} {
Ok(markup) => markup,
Err(error) => { Err(error) => {
crate::trace::error!( crate::trace::error!(
path = cx.request().map(|r| r.path()).unwrap_or("<unknown>"), path = cx.request().map(|r| r.path()).unwrap_or("<unknown>"),
component = self.name(), component = self.name(),
id = self.id().as_deref().unwrap_or("<not set>"), id = self.id().as_deref().unwrap_or("<not set>"),
source = "PrepareRender",
"render failed, using fallback: {}", "render failed, using fallback: {}",
error.message() error.message()
); );

View file

@ -18,6 +18,12 @@
//! registran componentes en el [`Context`], las plantillas organizan las regiones y las páginas //! registran componentes en el [`Context`], las plantillas organizan las regiones y las páginas
//! generan el documento HTML resultante. //! generan el documento HTML resultante.
//! //!
//! PageTop permite crear **temas hijo** que refinan el comportamiento de su tema padre. Un tema
//! hijo hereda automáticamente todos los métodos del padre y puede sobrescribirlos selectivamente:
//! por ejemplo, puede redefinir el renderizado de un componente con [`Theme::prepare_component()`]
//! sin modificar el resto del comportamiento heredado. Un tema hijo puede ser a su vez padre de
//! otro, basta declararlo cada vez con [`Theme::parent()`].
//!
//! Los temas pueden definir sus propias implementaciones de [`Template`] y [`Region`] (por ejemplo, //! Los temas pueden definir sus propias implementaciones de [`Template`] y [`Region`] (por ejemplo,
//! mediante *enums* adicionales) para añadir nuevas plantillas o exponer regiones específicas. //! mediante *enums* adicionales) para añadir nuevas plantillas o exponer regiones específicas.
@ -200,6 +206,44 @@ pub enum DefaultTemplate {
impl Template for DefaultTemplate {} impl Template for DefaultTemplate {}
// **< render_component! >**************************************************************************
/// Sobrescribe el renderizado de componentes en la implementación de
/// [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component).
///
/// Evalúa `$component` contra cada tipo listado en orden. En cuanto encuentra coincidencia,
/// devuelve `Some(Ok(markup))` o `Some(Err(e))` según el resultado de la expresión asociada.
/// Si ningún tipo coincide, devuelve `None` para que el sistema continúe con la cadena de
/// herencia o con el renderizado por defecto del propio componente.
///
/// # Ejemplo
///
/// ```rust,ignore
/// fn prepare_component(
/// &self,
/// component: &dyn Component,
/// cx: &mut Context,
/// ) -> Option<Result<Markup, ComponentError>> {
/// render_component!(component, {
/// Button => |btn| Ok(html! { button.btn.btn-primary { (btn.label()) } }),
/// Heading => |h| Ok(html! { h2.display-4 { (h.text()) } }),
/// })
/// }
/// ```
#[macro_export]
macro_rules! render_component {
($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
'render_component: {
$(
if let Some($var) = ($component).downcast_ref::<$type>() {
break 'render_component Some($body);
}
)*
None
}
};
}
// **< Definitions >******************************************************************************** // **< Definitions >********************************************************************************
mod definition; mod definition;

View file

@ -1,5 +1,5 @@
use crate::base::component::{Html, Intro, IntroOpening}; use crate::base::component::{Html, Intro, IntroOpening};
use crate::core::component::{Child, ChildOp, Component, Contextual}; use crate::core::component::{Child, ChildOp, Component, ComponentError, Context, Contextual};
use crate::core::extension::Extension; use crate::core::extension::Extension;
use crate::core::theme::{DefaultRegion, DefaultTemplate, TemplateRef}; use crate::core::theme::{DefaultRegion, DefaultTemplate, TemplateRef};
use crate::global; use crate::global;
@ -45,6 +45,16 @@ use crate::service::http::StatusCode;
/// impl Theme for MyTheme {} /// impl Theme for MyTheme {}
/// ``` /// ```
pub trait Theme: Extension + Send + Sync { pub trait Theme: Extension + Send + Sync {
/// Devuelve el tema padre del que hereda este tema, si existe.
///
/// Un tema hijo delega automáticamente todos los métodos de esta interfaz al tema padre cuando
/// no los sobrescribe.
///
/// La implementación por defecto devuelve `None` (tema sin padre).
fn parent(&self) -> Option<ThemeRef> {
None
}
/// Devuelve la plantilla ([`Template`](crate::core::theme::Template)) que el propio tema /// Devuelve la plantilla ([`Template`](crate::core::theme::Template)) que el propio tema
/// propone como predeterminada. /// propone como predeterminada.
/// ///
@ -57,7 +67,9 @@ pub trait Theme: Extension + Send + Sync {
/// seleccionar otra plantilla predeterminada o una plantilla propia. /// seleccionar otra plantilla predeterminada o una plantilla propia.
#[inline] #[inline]
fn default_template(&self) -> TemplateRef { fn default_template(&self) -> TemplateRef {
&DefaultTemplate::Standard self.parent().map_or(&DefaultTemplate::Standard, |parent| {
parent.default_template()
})
} }
/// Acciones específicas del tema antes de renderizar el `<body>` de la página. /// Acciones específicas del tema antes de renderizar el `<body>` de la página.
@ -71,7 +83,11 @@ pub trait Theme: Extension + Send + Sync {
/// ///
/// La implementación por defecto no realiza ninguna acción. /// La implementación por defecto no realiza ninguna acción.
#[allow(unused_variables)] #[allow(unused_variables)]
fn before_render_page_body(&self, page: &mut Page) {} fn before_render_page_body(&self, page: &mut Page) {
if let Some(parent) = self.parent() {
parent.before_render_page_body(page);
}
}
/// Renderiza el contenido del `<body>` de la página. /// Renderiza el contenido del `<body>` de la página.
/// ///
@ -93,7 +109,11 @@ pub trait Theme: Extension + Send + Sync {
/// - Implementar lógicas de composición alternativas. /// - Implementar lógicas de composición alternativas.
#[inline] #[inline]
fn render_page_body(&self, page: &mut Page) -> Markup { fn render_page_body(&self, page: &mut Page) -> Markup {
page.template().render(page.context()) if let Some(parent) = self.parent() {
parent.render_page_body(page)
} else {
page.template().render(page.context())
}
} }
/// Acciones específicas del tema después de renderizar el `<body>` de la página. /// Acciones específicas del tema después de renderizar el `<body>` de la página.
@ -107,7 +127,11 @@ pub trait Theme: Extension + Send + Sync {
/// ///
/// La implementación por defecto no realiza ninguna acción. /// La implementación por defecto no realiza ninguna acción.
#[allow(unused_variables)] #[allow(unused_variables)]
fn after_render_page_body(&self, page: &mut Page) {} fn after_render_page_body(&self, page: &mut Page) {
if let Some(parent) = self.parent() {
parent.after_render_page_body(page);
}
}
/// Renderiza el contenido del `<head>` de la página. /// Renderiza el contenido del `<head>` de la página.
/// ///
@ -129,6 +153,9 @@ pub trait Theme: Extension + Send + Sync {
/// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo, /// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo,
/// *favicons* personalizados, manifest, etiquetas de analítica, etc.). /// *favicons* personalizados, manifest, etiquetas de analítica, etc.).
fn render_page_head(&self, page: &mut Page) -> Markup { fn render_page_head(&self, page: &mut Page) -> Markup {
if let Some(parent) = self.parent() {
return parent.render_page_head(page);
}
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
html! { html! {
meta charset="utf-8"; meta charset="utf-8";
@ -157,11 +184,53 @@ pub trait Theme: Extension + Send + Sync {
} }
} }
/// Permite sobrescribir el renderizado de un componente.
///
/// Este método tiene especial utilidad en los **temas hijo** porque permite sobrescribir el
/// renderizado que el propio componente o el tema padre ofrece para un componente concreto, sin
/// modificar el resto del comportamiento heredado.
///
/// Recibe una referencia al componente (como objeto dinámico [`Component`]) y el contexto de
/// renderizado. Devuelve:
///
/// - `None` si este tema no sobrescribe el componente. Es la implementación por defecto. En
/// este caso se recorre la cadena de temas padre y, si ninguno lo sobrescribe, se usa
/// [`Component::prepare_component()`](crate::core::component::Component::prepare_component).
/// - `Some(Ok(markup))` con el HTML generado por el tema para el componente.
/// - `Some(Err(e))` si el tema intentó renderizarlo pero falló.
///
/// La mejor manera de implementar este método es usando la macro [`render_component!`], que
/// determina el componente a renderizar y devuelve `None` si ninguno coincide:
///
/// ```rust,ignore
/// fn prepare_component(
/// &self,
/// component: &dyn Component,
/// cx: &mut Context,
/// ) -> Option<Result<Markup, ComponentError>> {
/// render_component!(component, {
/// Button => |btn| Ok(html! { button.btn.btn-primary { (btn.label()) } }),
/// Heading => |h| Ok(html! { h2.display-4 { (h.text()) } }),
/// })
/// }
/// ```
#[allow(unused_variables)]
fn prepare_component(
&self,
component: &dyn Component,
cx: &mut Context,
) -> Option<Result<Markup, ComponentError>> {
None
}
/// Contenido predefinido para la página de error "*403 - Forbidden*" (acceso denegado). /// Contenido predefinido para la página de error "*403 - Forbidden*" (acceso denegado).
/// ///
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
/// página de error. /// página de error.
fn error_403(&self, page: &mut Page) { fn error_403(&self, page: &mut Page) {
if let Some(parent) = self.parent() {
return parent.error_403(page);
}
page.alter_title(L10n::l("error403_title")) page.alter_title(L10n::l("error403_title"))
.alter_template(&DefaultTemplate::Error) .alter_template(&DefaultTemplate::Error)
.alter_child_in( .alter_child_in(
@ -182,6 +251,9 @@ pub trait Theme: Extension + Send + Sync {
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
/// página de error. /// página de error.
fn error_404(&self, page: &mut Page) { fn error_404(&self, page: &mut Page) {
if let Some(parent) = self.parent() {
return parent.error_404(page);
}
page.alter_title(L10n::l("error404_title")) page.alter_title(L10n::l("error404_title"))
.alter_template(&DefaultTemplate::Error) .alter_template(&DefaultTemplate::Error)
.alter_child_in( .alter_child_in(
@ -209,6 +281,9 @@ pub trait Theme: Extension + Send + Sync {
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la /// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
/// página de error. /// página de error.
fn error_fatal(&self, page: &mut Page, code: StatusCode, title: L10n, alert: L10n, help: L10n) { fn error_fatal(&self, page: &mut Page, code: StatusCode, title: L10n, alert: L10n, help: L10n) {
if let Some(parent) = self.parent() {
return parent.error_fatal(page, code, title, alert, help);
}
page.alter_title(title) page.alter_title(title)
.alter_template(&DefaultTemplate::Error) .alter_template(&DefaultTemplate::Error)
.alter_child_in( .alter_child_in(

View file

@ -27,8 +27,7 @@ según las necesidades de cada proyecto, incluyendo:
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
de PageTop o de terceros. de PageTop o de terceros.
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
componentes sin comprometer su funcionalidad. componentes. Pueden crearse temas hijo que heredan y refinan el comportamiento de su tema padre.
# Guía rápida # Guía rápida

View file

@ -105,6 +105,8 @@ pub use request::RequestLocale;
mod l10n; mod l10n;
pub use l10n::L10n; pub use l10n::L10n;
// **< include_locales! >***************************************************************************
/// Incluye un conjunto de recursos **Fluent** con textos de traducción propios. /// Incluye un conjunto de recursos **Fluent** con textos de traducción propios.
/// ///
/// Esta macro integra en el binario de la aplicación los archivos FTL ubicados en los siguientes /// Esta macro integra en el binario de la aplicación los archivos FTL ubicados en los siguientes

View file

@ -18,6 +18,8 @@ pub use crate::include_locales;
pub use crate::static_files_service; pub use crate::static_files_service;
// crate::core::action // crate::core::action
pub use crate::actions_boxed; pub use crate::actions_boxed;
// crate::core::theme
pub use crate::render_component;
// API. // API.

View file

@ -15,6 +15,8 @@ pub use pagetop_statics::ResourceFiles;
#[doc(hidden)] #[doc(hidden)]
pub use actix_web::test; pub use actix_web::test;
// **< static_files_service! >**********************************************************************
/// Configura un servicio web para publicar archivos estáticos. /// Configura un servicio web para publicar archivos estáticos.
/// ///
/// La macro ofrece tres modos para configurar el servicio: /// La macro ofrece tres modos para configurar el servicio: