✨ 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:
parent
af309930f7
commit
4cbe84b4c0
14 changed files with 161 additions and 139 deletions
|
|
@ -26,7 +26,7 @@ según las necesidades de cada proyecto, incluyendo:
|
|||
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
|
||||
de PageTop o de terceros.
|
||||
* **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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,3 @@ pub use before_render_component::*;
|
|||
|
||||
mod after_render_component;
|
||||
pub use after_render_component::*;
|
||||
|
||||
mod prepare_render;
|
||||
pub use prepare_render::*;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -149,6 +149,8 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(
|
|||
.expect("Failed to set application run mode")
|
||||
});
|
||||
|
||||
// **< include_config! >****************************************************************************
|
||||
|
||||
/// Incluye los ajustes necesarios de la configuración anticipando valores por defecto.
|
||||
///
|
||||
/// # Sintaxis
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ use list::ActionsList;
|
|||
|
||||
mod all;
|
||||
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).
|
||||
///
|
||||
|
|
@ -22,12 +24,11 @@ pub use all::{dispatch_actions, dispatch_actions_until};
|
|||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use pagetop::prelude::*;
|
||||
/// impl Extension for MyTheme {
|
||||
/// fn actions(&self) -> Vec<ActionBox> {
|
||||
/// actions_boxed![
|
||||
/// 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 {}
|
||||
///
|
||||
/// 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_rules! actions_boxed {
|
||||
|
|
|
|||
|
|
@ -72,18 +72,3 @@ where
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,21 +39,4 @@ impl ActionsList {
|
|||
})
|
||||
.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::base::action;
|
||||
use crate::core::component::{ComponentError, Context, Contextual};
|
||||
use crate::core::theme::ThemeRef;
|
||||
use crate::core::{AnyInfo, TypeInfo};
|
||||
use crate::html::{html, Markup};
|
||||
|
||||
|
|
@ -70,16 +71,15 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync {
|
|||
#[allow(unused_variables)]
|
||||
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
|
||||
/// durante el proceso de construcción del documento. Cada componente lo implementa para generar
|
||||
/// su propio contenido HTML. Los temas hijo pueden sobrescribir opcionalmente su resultado
|
||||
/// mediante la acción [`PrepareRender`](crate::base::action::theme::PrepareRender).
|
||||
/// durante el proceso de construcción del documento cuando ningún tema sobrescribe el
|
||||
/// renderizado mediante [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component).
|
||||
///
|
||||
/// 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
|
||||
/// detalles internos del componente.
|
||||
/// temas puedan implementar [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component)
|
||||
/// sin depender de los detalles internos del componente.
|
||||
///
|
||||
/// 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)
|
||||
/// para que otras extensiones puedan también hacer ajustes previos.
|
||||
/// 5. **Prepara el renderizado del componente**:
|
||||
/// - Despacha [`action::theme::PrepareRender<C>`](crate::base::action::theme::PrepareRender)
|
||||
/// para permitir al tema generar un renderizado alternativo.
|
||||
/// - Si el tema no lo modifica, llama a [`prepare_component()`](Component::prepare_component)
|
||||
/// para obtener el renderizado por defecto del componente.
|
||||
/// - Recorre la cadena de temas llamando a
|
||||
/// [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component) en cada nivel
|
||||
/// (hijo → padre → abuelo…) hasta que uno devuelva `Some`.
|
||||
/// - 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
|
||||
/// que el tema pueda aplicar ajustes finales.
|
||||
/// 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.
|
||||
action::component::BeforeRender::dispatch(self, cx);
|
||||
|
||||
// Prepara el renderizado del componente.
|
||||
let prepare = match action::theme::PrepareRender::dispatch(self, cx) {
|
||||
Ok(markup) if !markup.is_empty() => markup,
|
||||
Ok(_) => match self.prepare_component(cx) {
|
||||
// Prepara el renderizado: recorre la cadena de temas, luego el componente.
|
||||
let prepare = match 'resolve: {
|
||||
let mut t: Option<ThemeRef> = Some(cx.theme());
|
||||
while let Some(theme) = t {
|
||||
if let Some(r) = theme.prepare_component(self, cx) {
|
||||
break 'resolve r;
|
||||
}
|
||||
t = theme.parent();
|
||||
}
|
||||
self.prepare_component(cx)
|
||||
} {
|
||||
Ok(markup) => markup,
|
||||
Err(error) => {
|
||||
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()
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
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 = "PrepareRender",
|
||||
"render failed, using fallback: {}",
|
||||
error.message()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@
|
|||
//! registran componentes en el [`Context`], las plantillas organizan las regiones y las páginas
|
||||
//! 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,
|
||||
//! mediante *enums* adicionales) para añadir nuevas plantillas o exponer regiones específicas.
|
||||
|
||||
|
|
@ -200,6 +206,44 @@ pub enum 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 >********************************************************************************
|
||||
|
||||
mod definition;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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::theme::{DefaultRegion, DefaultTemplate, TemplateRef};
|
||||
use crate::global;
|
||||
|
|
@ -45,6 +45,16 @@ use crate::service::http::StatusCode;
|
|||
/// impl Theme for MyTheme {}
|
||||
/// ```
|
||||
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
|
||||
/// propone como predeterminada.
|
||||
///
|
||||
|
|
@ -57,7 +67,9 @@ pub trait Theme: Extension + Send + Sync {
|
|||
/// seleccionar otra plantilla predeterminada o una plantilla propia.
|
||||
#[inline]
|
||||
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.
|
||||
|
|
@ -71,7 +83,11 @@ pub trait Theme: Extension + Send + Sync {
|
|||
///
|
||||
/// La implementación por defecto no realiza ninguna acción.
|
||||
#[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.
|
||||
///
|
||||
|
|
@ -93,8 +109,12 @@ pub trait Theme: Extension + Send + Sync {
|
|||
/// - Implementar lógicas de composición alternativas.
|
||||
#[inline]
|
||||
fn render_page_body(&self, page: &mut Page) -> Markup {
|
||||
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.
|
||||
///
|
||||
|
|
@ -107,7 +127,11 @@ pub trait Theme: Extension + Send + Sync {
|
|||
///
|
||||
/// La implementación por defecto no realiza ninguna acción.
|
||||
#[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.
|
||||
///
|
||||
|
|
@ -129,6 +153,9 @@ pub trait Theme: Extension + Send + Sync {
|
|||
/// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo,
|
||||
/// *favicons* personalizados, manifest, etiquetas de analítica, etc.).
|
||||
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";
|
||||
html! {
|
||||
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).
|
||||
///
|
||||
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
|
||||
/// página de error.
|
||||
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"))
|
||||
.alter_template(&DefaultTemplate::Error)
|
||||
.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
|
||||
/// página de error.
|
||||
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"))
|
||||
.alter_template(&DefaultTemplate::Error)
|
||||
.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
|
||||
/// página de error.
|
||||
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)
|
||||
.alter_template(&DefaultTemplate::Error)
|
||||
.alter_child_in(
|
||||
|
|
|
|||
|
|
@ -27,8 +27,7 @@ según las necesidades de cada proyecto, incluyendo:
|
|||
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
|
||||
de PageTop o de terceros.
|
||||
* **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
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ pub use request::RequestLocale;
|
|||
mod l10n;
|
||||
pub use l10n::L10n;
|
||||
|
||||
// **< include_locales! >***************************************************************************
|
||||
|
||||
/// 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
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub use crate::include_locales;
|
|||
pub use crate::static_files_service;
|
||||
// crate::core::action
|
||||
pub use crate::actions_boxed;
|
||||
// crate::core::theme
|
||||
pub use crate::render_component;
|
||||
|
||||
// API.
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ pub use pagetop_statics::ResourceFiles;
|
|||
#[doc(hidden)]
|
||||
pub use actix_web::test;
|
||||
|
||||
// **< static_files_service! >**********************************************************************
|
||||
|
||||
/// Configura un servicio web para publicar archivos estáticos.
|
||||
///
|
||||
/// La macro ofrece tres modos para configurar el servicio:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue