🎨 Mejora el uso de regiones y añade BasicRegion
This commit is contained in:
parent
fef927906c
commit
8ceb6fbd9d
5 changed files with 148 additions and 98 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
//! Temas básicos soportados por PageTop.
|
//! Temas básicos soportados por PageTop.
|
||||||
|
|
||||||
mod basic;
|
mod basic;
|
||||||
pub use basic::Basic;
|
pub use basic::{Basic, BasicRegion};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
/// Es el tema básico que incluye PageTop por defecto.
|
/// Es el tema básico que incluye PageTop por defecto.
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// El tema básico usa las mismas regiones predefinidas por [`ThemeRegion`].
|
||||||
|
pub type BasicRegion = ThemeRegion;
|
||||||
|
|
||||||
/// Tema básico por defecto.
|
/// Tema básico por defecto.
|
||||||
///
|
///
|
||||||
/// Ofrece las siguientes composiciones (*layouts*):
|
/// Ofrece las siguientes composiciones (*layouts*):
|
||||||
|
|
@ -90,9 +93,9 @@ fn render_intro(page: &mut Page) -> Markup {
|
||||||
let intro = page.description().unwrap_or_default();
|
let intro = page.description().unwrap_or_default();
|
||||||
|
|
||||||
let theme = page.context().theme();
|
let theme = page.context().theme();
|
||||||
let h = theme.render_page_region(page, "header");
|
let h = theme.render_page_region(page, &BasicRegion::Header);
|
||||||
let c = theme.render_page_region(page, "content");
|
let c = theme.render_page_region(page, &BasicRegion::Content);
|
||||||
let f = theme.render_page_region(page, "footer");
|
let f = theme.render_page_region(page, &BasicRegion::Footer);
|
||||||
|
|
||||||
let intro_button_txt: L10n = page.param_or_default("intro_button_txt");
|
let intro_button_txt: L10n = page.param_or_default("intro_button_txt");
|
||||||
let intro_button_lnk: Option<&String> = page.param("intro_button_lnk");
|
let intro_button_lnk: Option<&String> = page.param("intro_button_lnk");
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@
|
||||||
//! [`Theme`].
|
//! [`Theme`].
|
||||||
|
|
||||||
mod definition;
|
mod definition;
|
||||||
pub use definition::{Theme, ThemePage, ThemeRef};
|
pub use definition::{Theme, ThemePage, ThemeRef, ThemeRegion};
|
||||||
|
|
||||||
mod regions;
|
mod regions;
|
||||||
pub(crate) use regions::{ChildrenInRegions, REGION_CONTENT};
|
pub(crate) use regions::{ChildrenInRegions, REGION_CONTENT};
|
||||||
pub use regions::{InRegion, Region};
|
pub use regions::{InRegion, Region, RegionRef};
|
||||||
|
|
||||||
pub(crate) mod all;
|
pub(crate) mod all;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::core::extension::Extension;
|
use crate::core::extension::Extension;
|
||||||
use crate::core::theme::Region;
|
use crate::core::theme::{Region, RegionRef, REGION_CONTENT};
|
||||||
use crate::global;
|
|
||||||
use crate::html::{html, Markup};
|
use crate::html::{html, Markup};
|
||||||
use crate::locale::L10n;
|
use crate::locale::L10n;
|
||||||
use crate::response::page::Page;
|
use crate::response::page::Page;
|
||||||
|
use crate::{global, join};
|
||||||
|
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
|
@ -13,6 +13,46 @@ use std::sync::LazyLock;
|
||||||
/// implementen [`Theme`] y, a su vez, [`Extension`].
|
/// implementen [`Theme`] y, a su vez, [`Extension`].
|
||||||
pub type ThemeRef = &'static dyn Theme;
|
pub type ThemeRef = &'static dyn Theme;
|
||||||
|
|
||||||
|
/// Conjunto de regiones que los temas pueden exponer para el renderizado.
|
||||||
|
///
|
||||||
|
/// `ThemeRegion` define un conjunto de regiones predefinidas para estructurar un documento HTML.
|
||||||
|
/// Proporciona **identificadores estables** (vía [`Region::key()`]) y **etiquetas localizables**
|
||||||
|
/// (vía [`Region::label()`]) a las regiones donde se añadirán los componentes.
|
||||||
|
///
|
||||||
|
/// Se usa por defecto en [`Theme::page_regions()`](crate::core::theme::Theme::page_regions) y sus
|
||||||
|
/// variantes representan el conjunto mínimo recomendado para cualquier tema. Sin embargo, cada tema
|
||||||
|
/// podría exponer su propio conjunto de regiones.
|
||||||
|
pub enum ThemeRegion {
|
||||||
|
/// Cabecera de la página.
|
||||||
|
///
|
||||||
|
/// Clave: `"header"`. Suele contener *branding*, navegación principal o avisos globales.
|
||||||
|
Header,
|
||||||
|
|
||||||
|
/// Contenido principal de la página (**obligatoria**).
|
||||||
|
///
|
||||||
|
/// Clave: `"content"`. Es el destino por defecto para insertar componentes a nivel de página.
|
||||||
|
Content,
|
||||||
|
|
||||||
|
/// Pie de página.
|
||||||
|
///
|
||||||
|
/// Clave: `"footer"`. Suele contener enlaces legales, créditos o navegación secundaria.
|
||||||
|
Footer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Region for ThemeRegion {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ThemeRegion::Header => "header",
|
||||||
|
ThemeRegion::Content => REGION_CONTENT,
|
||||||
|
ThemeRegion::Footer => "footer",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label(&self) -> L10n {
|
||||||
|
L10n::l(join!("region_", self.key()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Métodos predefinidos de renderizado para las páginas de un tema.
|
/// Métodos predefinidos de renderizado para las páginas de un tema.
|
||||||
///
|
///
|
||||||
/// Contiene las implementaciones base para renderizar las **secciones** `<head>` y `<body>`. Se
|
/// Contiene las implementaciones base para renderizar las **secciones** `<head>` y `<body>`. Se
|
||||||
|
|
@ -37,14 +77,14 @@ pub trait ThemePage {
|
||||||
///
|
///
|
||||||
/// Si la región **no produce contenido**, devuelve un `Markup` vacío.
|
/// Si la región **no produce contenido**, devuelve un `Markup` vacío.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn render_region(&self, page: &mut Page, region: &Region) -> Markup {
|
fn render_region(&self, page: &mut Page, region: RegionRef) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
@let output = page.context().render_components_of(region.key());
|
@let key = region.key();
|
||||||
|
@let output = page.context().render_components_of(key);
|
||||||
@if !output.is_empty() {
|
@if !output.is_empty() {
|
||||||
@let region_name = region.name();
|
|
||||||
div
|
div
|
||||||
id=(region_name)
|
id=(key)
|
||||||
class={ "region region--" (region_name) }
|
class={ "region region--" (key) }
|
||||||
role="region"
|
role="region"
|
||||||
aria-label=[region.label().lookup(page)]
|
aria-label=[region.label().lookup(page)]
|
||||||
{
|
{
|
||||||
|
|
@ -63,10 +103,10 @@ pub trait ThemePage {
|
||||||
///
|
///
|
||||||
/// La etiqueta `<body>` no se incluye aquí; únicamente renderiza su contenido.
|
/// La etiqueta `<body>` no se incluye aquí; únicamente renderiza su contenido.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn render_body(&self, page: &mut Page, regions: &[Region]) -> Markup {
|
fn render_body(&self, page: &mut Page, regions: &[RegionRef]) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
@for region in regions {
|
@for region in regions {
|
||||||
(self.render_region(page, region))
|
(self.render_region(page, *region))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,44 +185,53 @@ pub trait Theme: Extension + ThemePage + Send + Sync {
|
||||||
|
|
||||||
/// Declaración ordenada de las regiones disponibles en la página.
|
/// Declaración ordenada de las regiones disponibles en la página.
|
||||||
///
|
///
|
||||||
/// Devuelve una **lista estática** de regiones ([`Region`](crate::core::theme::Region)) con la
|
/// Retorna una **lista estática** de referencias ([`RegionRef`](crate::core::theme::RegionRef))
|
||||||
/// información necesaria para renderizar el contenedor de cada región.
|
/// que representan las regiones que el tema admite dentro del `<body>`.
|
||||||
///
|
///
|
||||||
/// Si un tema necesita un conjunto distinto de regiones, se puede **sobrescribir** este método
|
/// Cada referencia apunta a una instancia que implementa [`Region`](crate::core::theme::Region)
|
||||||
/// con los siguientes requisitos y recomendaciones:
|
/// para definir cada región de forma segura y estable. Y si un tema necesita un conjunto
|
||||||
|
/// distinto de regiones, puede **sobrescribir** este método siguiendo estas recomendaciones:
|
||||||
///
|
///
|
||||||
/// - Los identificadores deben ser **estables** (p. ej. `"sidebar-left"`, `"content"`).
|
/// - Los identificadores devueltos por [`Region::key()`](crate::core::theme::Region::key)
|
||||||
/// - La región `"content"` es **obligatoria** porque se usa por defecto para añadir componentes
|
/// deben ser **estables** (p. ej. `"sidebar-left"`, `"content"`).
|
||||||
/// para renderizar. Se puede utilizar [`Region::default()`] para declararla.
|
/// - La región `"content"` es **obligatoria**, ya que se usa como destino por defecto para
|
||||||
/// - La etiqueta `L10n` se evaluará con el idioma activo de la página.
|
/// insertar componentes y renderizarlos.
|
||||||
|
/// - El orden de la lista podría tener relevancia como **orden de renderizado** dentro del
|
||||||
|
/// `<body>` segun la implementación de [`render_page_body()`](Self::render_page_body).
|
||||||
|
/// - Las etiquetas (`L10n`) de cada región se evaluarán con el idioma activo de la página.
|
||||||
///
|
///
|
||||||
/// Por defecto devuelve:
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// - `"header"`: cabecera.
|
/// ```rust,ignore
|
||||||
/// - `"content"`: contenido principal (**obligatoria**).
|
/// fn page_regions(&self) -> &'static [RegionRef] {
|
||||||
/// - `"footer"`: pie.
|
/// static REGIONS: LazyLock<[RegionRef; 4]> = LazyLock::new(|| {
|
||||||
fn page_regions(&self) -> &'static [Region] {
|
/// [
|
||||||
static REGIONS: LazyLock<[Region; 3]> = LazyLock::new(|| {
|
/// &ThemeRegion::Header,
|
||||||
|
/// &ThemeRegion::Content,
|
||||||
|
/// &ThemeRegion::Footer,
|
||||||
|
/// ]
|
||||||
|
/// });
|
||||||
|
/// &*REGIONS
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn page_regions(&self) -> &'static [RegionRef] {
|
||||||
|
static REGIONS: LazyLock<[RegionRef; 3]> = LazyLock::new(|| {
|
||||||
[
|
[
|
||||||
Region::declare("header", L10n::l("region_header")),
|
&ThemeRegion::Header,
|
||||||
Region::default(),
|
&ThemeRegion::Content,
|
||||||
Region::declare("footer", L10n::l("region_footer")),
|
&ThemeRegion::Footer,
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
&*REGIONS
|
&*REGIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renderiza una región de la página **por clave**.
|
/// Renderiza una región de la página.
|
||||||
///
|
///
|
||||||
/// Busca en [`page_regions()`](Self::page_regions) la región asociada a una clave y, si existe,
|
/// Si se sobrescribe este método, se puede volver al comportamiento base con:
|
||||||
/// delega en [`ThemePage::render_region()`] su renderizado. Si no se encuentra la clave o la
|
/// `<Self as ThemePage>::render_region(self, page, region)`.
|
||||||
/// región no produce contenido, devuelve un `Markup` vacío.
|
#[inline]
|
||||||
fn render_page_region(&self, page: &mut Page, key: &str) -> Markup {
|
fn render_page_region(&self, page: &mut Page, region: RegionRef) -> Markup {
|
||||||
html! {
|
<Self as ThemePage>::render_region(self, page, region)
|
||||||
@if let Some(region) = self.page_regions().iter().find(|r| r.key() == key) {
|
|
||||||
(self.render_region(page, region))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
|
|
|
||||||
|
|
@ -19,67 +19,65 @@ static COMMON_REGIONS: LazyLock<RwLock<ChildrenInRegions>> =
|
||||||
/// Nombre de la región de contenido por defecto (`"content"`).
|
/// Nombre de la región de contenido por defecto (`"content"`).
|
||||||
pub const REGION_CONTENT: &str = "content";
|
pub const REGION_CONTENT: &str = "content";
|
||||||
|
|
||||||
/// Identificador de una región de página.
|
/// Define la interfaz mínima que describe una **región de renderizado** dentro de una página.
|
||||||
///
|
///
|
||||||
/// Incluye una **clave estática** ([`key()`](Self::key)) que identifica la región en el tema, y un
|
/// Una *región* representa una zona del documento HTML (por ejemplo: `"header"`, `"content"` o
|
||||||
/// **nombre normalizado** ([`name()`](Self::name)) en minúsculas para su uso en atributos HTML
|
/// `"sidebar-left"`), en la que se pueden incluir y renderizar componentes dinámicamente.
|
||||||
/// (p.ej., clases `region__{name}`).
|
|
||||||
///
|
///
|
||||||
/// Se utiliza para declarar las regiones que componen una página en un tema (ver
|
/// Este `trait` abstrae los metadatos básicos de cada región, esencialmente:
|
||||||
/// [`page_regions()`](crate::core::theme::Theme::page_regions)).
|
|
||||||
pub struct Region {
|
|
||||||
key: &'static str,
|
|
||||||
name: String,
|
|
||||||
label: L10n,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Region {
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
key: REGION_CONTENT,
|
|
||||||
name: REGION_CONTENT.to_string(),
|
|
||||||
label: L10n::l("region_content"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Region {
|
|
||||||
/// Declara una región a partir de su clave estática.
|
|
||||||
///
|
///
|
||||||
/// Genera además un nombre normalizado de la clave, eliminando espacios iniciales y finales,
|
/// - su **clave interna** (`key()`), que la identifica de forma única dentro de la página, y
|
||||||
/// convirtiendo a minúsculas y sustituyendo los espacios intermedios por guiones (`-`).
|
/// - su **etiqueta localizada** (`label()`), que se usa como texto accesible (por ejemplo en
|
||||||
|
/// `aria-label` o en descripciones semánticas del contenedor).
|
||||||
///
|
///
|
||||||
/// Esta clave se usará para añadir componentes a la región; por ello se recomiendan nombres
|
/// Las implementaciones típicas son *enumeraciones estáticas* declaradas por cada tema (ver como
|
||||||
/// sencillos, limitando los caracteres a `[a-z0-9-]` (p.ej., `"sidebar"` o `"main-menu"`), cuyo
|
/// ejemplo [`ThemeRegion`](crate::core::theme::ThemeRegion)), de modo que las claves y etiquetas
|
||||||
/// nombre normalizado coincidirá con la clave.
|
/// permanecen inmutables y fácilmente referenciables.
|
||||||
#[inline]
|
///
|
||||||
pub fn declare(key: &'static str, label: L10n) -> Self {
|
/// # Ejemplo
|
||||||
Self {
|
///
|
||||||
key,
|
/// ```rust
|
||||||
name: key.trim().to_ascii_lowercase().replace(' ', "-"),
|
/// use pagetop::prelude::*;
|
||||||
label,
|
///
|
||||||
}
|
/// pub enum MyThemeRegion {
|
||||||
|
/// Header,
|
||||||
|
/// Content,
|
||||||
|
/// Footer,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl Region for MyThemeRegion {
|
||||||
|
/// fn key(&self) -> &str {
|
||||||
|
/// match self {
|
||||||
|
/// MyThemeRegion::Header => "header",
|
||||||
|
/// MyThemeRegion::Content => "content",
|
||||||
|
/// MyThemeRegion::Footer => "footer",
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn label(&self) -> L10n {
|
||||||
|
/// L10n::l(join!("region__", self.key()))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait Region: Send + Sync {
|
||||||
|
/// Devuelve la **clave interna** que identifica de forma única una región.
|
||||||
|
///
|
||||||
|
/// La clave se utiliza para asociar los componentes de la región con su contenedor HTML
|
||||||
|
/// correspondiente. Por convención, se emplean nombres en minúsculas y con guiones (`"header"`,
|
||||||
|
/// `"main"`, `"sidebar-right"`, etc.), y la región `"content"` es **obligatoria** en todos los
|
||||||
|
/// temas.
|
||||||
|
fn key(&self) -> &str;
|
||||||
|
|
||||||
|
/// Devuelve la **etiqueta localizada** (`L10n`) asociada a la región.
|
||||||
|
///
|
||||||
|
/// Esta etiqueta se evalúa en el idioma activo de la página y se utiliza principalmente para
|
||||||
|
/// accesibilidad, como el valor de `aria-label` en el contenedor generado por
|
||||||
|
/// [`ThemePage::render_region()`](crate::core::theme::ThemePage::render_region).
|
||||||
|
fn label(&self) -> L10n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve la clave estática asignada a la región.
|
/// Referencia estática a una región.
|
||||||
#[inline]
|
pub type RegionRef = &'static dyn Region;
|
||||||
pub fn key(&self) -> &'static str {
|
|
||||||
self.key
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve el nombre normalizado de la región (para identificadores y atributos HTML).
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la etiqueta localizada asociada a la región.
|
|
||||||
#[inline]
|
|
||||||
pub fn label(&self) -> &L10n {
|
|
||||||
&self.label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contenedor interno de componentes agrupados por región.
|
// Contenedor interno de componentes agrupados por región.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue