♻️ (theme): Refactoriza renderizado de temas base

This commit is contained in:
Manuel Cillero 2025-11-23 14:35:38 +01:00
parent 9657672ffd
commit a2fe2114cd
8 changed files with 79 additions and 47 deletions

View file

@ -104,12 +104,16 @@ impl Extension for Aliner {
}
impl Theme for Aliner {
fn after_render_page_body(&self, page: &mut Page) {
fn before_render_page_body(&self, page: &mut Page) {
page.alter_param("include_basic_assets", true)
.alter_assets(ContextOp::AddStyleSheet(
StyleSheet::from("/aliner/css/styles.css")
.with_version(env!("CARGO_PKG_VERSION"))
.with_weight(-90),
));
))
.alter_child_in(
Region::FOOTER,
ChildOp::AddIfEmpty(Child::with(PoweredBy::new())),
);
}
}

View file

@ -117,7 +117,7 @@ impl Extension for Bootsier {
}
impl Theme for Bootsier {
fn after_render_page_body(&self, page: &mut Page) {
fn before_render_page_body(&self, page: &mut Page) {
page.alter_assets(ContextOp::AddStyleSheet(
StyleSheet::from("/bootsier/bs/bootstrap.min.css")
.with_version(BOOTSTRAP_VERSION)
@ -129,4 +129,14 @@ impl Theme for Bootsier {
.with_weight(-90),
));
}
fn render_page_body(&self, page: &mut Page) -> Markup {
theme::Container::new()
.with_id("container-wrapper")
.with_width(theme::container::Width::FluidMax(
config::SETTINGS.bootsier.max_width,
))
.add_child(Template::named(page.template()))
.render(page.context())
}
}

View file

@ -17,17 +17,17 @@ pub enum IntroOpening {
Custom,
}
/// Componente para presentar PageTop (como [`Welcome`](crate::base::extension::Welcome)), o mostrar
/// introducciones.
/// Componente para divulgar PageTop (como hace [`Welcome`](crate::base::extension::Welcome)), o
/// mostrar presentaciones.
///
/// Usa la imagen de PageTop para presentar contenidos con:
/// Usa la imagen de PageTop para mostrar:
///
/// - Una **imagen decorativa** (el *monster* de PageTop) antecediendo al contenido.
/// - Una vista destacada con **título + eslogan**.
/// - Una **figura decorativa** (que incluye el *monster* de PageTop) antecediendo al contenido.
/// - Una vista destacada del **título** de la página con un **eslogan** de presentación.
/// - Un **botón opcional** de llamada a la acción con texto y enlace configurables.
/// - El **área de textos** con *badges* predefinidos (en modo [`IntroOpening::PageTop`]) y bloques
/// ([`Block`](crate::base::component::Block)) para crear párrafos vistosos de texto. Aunque
/// admite todo tipo de componentes.
/// - Un **área para la presentación de contenidos**, con *badges* informativos de PageTop (si se
/// opta por [`IntroOpening::PageTop`]) y bloques ([`Block`](crate::base::component::Block)) de
/// contenido libre para crear párrafos vistosos de texto. Aunque admite todo tipo de componentes.
///
/// ### Ejemplos
///
@ -51,7 +51,7 @@ pub enum IntroOpening {
/// )));
/// ```
///
/// **Sin botón + modo *Custom* (sin *badges* predefinidos)**
/// **Sin botón y en modo *Custom* (sin *badges* predefinidos)**
///
/// ```rust
/// # use pagetop::prelude::*;

View file

@ -12,6 +12,10 @@ impl Extension for Basic {
impl Theme for Basic {
fn before_render_page_body(&self, page: &mut Page) {
page.alter_param("include_basic_assets", true);
page.alter_param("include_basic_assets", true)
.alter_child_in(
Region::FOOTER,
ChildOp::AddIfEmpty(Child::with(PoweredBy::new())),
);
}
}

View file

@ -172,6 +172,7 @@ impl<C: Component> Typed<C> {
/// Operaciones para componentes hijo [`Child`] en una lista [`Children`].
pub enum ChildOp {
Add(Child),
AddIfEmpty(Child),
AddMany(Vec<Child>),
InsertAfterId(&'static str, Child),
InsertBeforeId(&'static str, Child),
@ -185,6 +186,7 @@ pub enum ChildOp {
/// Operaciones con un componente hijo tipado [`Typed<C>`] en una lista [`Children`].
pub enum TypedOp<C: Component> {
Add(Typed<C>),
AddIfEmpty(Typed<C>),
AddMany(Vec<Typed<C>>),
InsertAfterId(&'static str, Typed<C>),
InsertBeforeId(&'static str, Typed<C>),
@ -230,6 +232,7 @@ impl Children {
pub fn with_child(mut self, op: ChildOp) -> Self {
match op {
ChildOp::Add(any) => self.add(any),
ChildOp::AddIfEmpty(any) => self.add_if_empty(any),
ChildOp::AddMany(many) => self.add_many(many),
ChildOp::InsertAfterId(id, any) => self.insert_after_id(id, any),
ChildOp::InsertBeforeId(id, any) => self.insert_before_id(id, any),
@ -246,6 +249,7 @@ impl Children {
pub fn with_typed<C: Component>(mut self, op: TypedOp<C>) -> Self {
match op {
TypedOp::Add(typed) => self.add(typed.into()),
TypedOp::AddIfEmpty(typed) => self.add_if_empty(typed.into()),
TypedOp::AddMany(many) => self.add_many(many.into_iter().map(Typed::<C>::into)),
TypedOp::InsertAfterId(id, typed) => self.insert_after_id(id, typed.into()),
TypedOp::InsertBeforeId(id, typed) => self.insert_before_id(id, typed.into()),
@ -266,6 +270,15 @@ impl Children {
self
}
/// Añade un componente hijo en la lista sólo si está vacía.
#[inline]
pub fn add_if_empty(&mut self, child: Child) -> &mut Self {
if self.0.is_empty() {
self.0.push(child);
}
self
}
// **< Children GETTERS >***********************************************************************
/// Devuelve el número de componentes hijo de la lista.

View file

@ -83,12 +83,33 @@ pub trait Theme: Extension + Send + Sync {
/// - Realizar *tracing* o recopilar métricas.
/// - Aplicar ajustes finales al estado de la página antes de producir el `<head>` o la
/// respuesta final.
///
/// La implementación por defecto añade una serie de hojas de estilo básicas (`normalize.css`,
/// `root.css`, `basic.css`) cuando el parámetro `include_basic_assets` de la página está
/// activado.
#[allow(unused_variables)]
fn after_render_page_body(&self, page: &mut Page) {
fn after_render_page_body(&self, page: &mut Page) {}
/// Renderiza el contenido del `<head>` de la página.
///
/// Aunque en una página el `<head>` se encuentra antes del `<body>`, internamente se renderiza
/// después para contar con los ajustes que hayan ido acumulando los componentes. Por ejemplo,
/// permitiría añadir un archivo de iconos sólo si se ha incluido un icono en la página.
///
/// Por defecto incluye:
///
/// - La codificación (`charset="utf-8"`).
/// - El título, usando el título de la página si existe y, en caso contrario, sólo el nombre de
/// la aplicación.
/// - La descripción (`<meta name="description">`), si está definida.
/// - La etiqueta `viewport` básica para diseño adaptable.
/// - Los metadatos (`name`/`content`) y propiedades (`property`/`content`) declarados en la
/// página.
/// - Los *assets* registrados en el contexto de la página. Si el parámetro
/// `include_basic_assets` está activado, añade de serie las siguientes hojas de estilo
/// básicas: `normalize.css`, `root.css`, `basic.css`, útiles para temas sencillos o de uso
/// general.
///
/// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo,
/// *favicons* personalizados, manifest, etiquetas de analítica, etc.).
#[inline]
fn render_page_head(&self, page: &mut Page) -> Markup {
if page.param_or("include_basic_assets", false) {
let pkg_version = env!("CARGO_PKG_VERSION");
@ -108,29 +129,6 @@ pub trait Theme: Extension + Send + Sync {
.with_weight(-99),
));
}
}
/// Renderiza el contenido del `<head>` de la página.
///
/// Aunque en una página el `<head>` se encuentra antes del `<body>`, internamente se renderiza
/// después para contar con los ajustes que hayan ido acumulando los componentes. Por ejemplo,
/// permitiría añadir un archivo de iconos sólo si se ha incluido un icono en la página.
///
/// Por defecto incluye:
///
/// - La codificación (`charset="utf-8"`).
/// - El título, usando el título de la página si existe y, en caso contrario, sólo el nombre de
/// la aplicación.
/// - La descripción (`<meta name="description">`), si está definida.
/// - La etiqueta `viewport` básica para diseño adaptable.
/// - Los metadatos (`name`/`content`) y propiedades (`property`/`content`) declarados en la
/// página.
/// - Todos los *assets* registrados en el contexto de la página.
///
/// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo,
/// *favicons* personalizados, manifest, etiquetas de analítica, etc.).
#[inline]
fn render_page_head(&self, page: &mut Page) -> Markup {
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
html! {
meta charset="utf-8";

View file

@ -14,8 +14,11 @@ body {
-webkit-tap-highlight-color: transparent;
}
/* Page layout */
/*
* Region Footer
*/
.region--footer {
padding-bottom: 2rem;
.region-footer {
padding: .75rem 0 3rem;
text-align: center;
}

View file

@ -50,7 +50,7 @@
}
/*
* Header
* Intro Header
*/
.intro-header {
@ -125,7 +125,7 @@
}
/*
* Content
* Intro Content
*/
.intro-content {
@ -445,7 +445,7 @@
}
/*
* Footer
* Intro Footer
*/
.intro-footer {