Compare commits

...

6 commits

15 changed files with 218 additions and 74 deletions

View file

@ -3,6 +3,9 @@
mod figfont; mod figfont;
use crate::core::{extension, extension::ExtensionRef}; use crate::core::{extension, extension::ExtensionRef};
use crate::html::Markup;
use crate::response::page::{ErrorPage, ResultPage};
use crate::service::HttpRequest;
use crate::{global, locale, service, trace}; use crate::{global, locale, service, trace};
use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle}; use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle};
@ -170,6 +173,12 @@ impl Application {
InitError = (), InitError = (),
>, >,
> { > {
service::App::new().configure(extension::all::configure_services) service::App::new()
.configure(extension::all::configure_services)
.default_service(service::web::route().to(service_not_found))
} }
} }
async fn service_not_found(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
Err(ErrorPage::NotFound(request))
}

View file

@ -26,7 +26,7 @@ pub type ExtensionRef = &'static dyn Extension;
/// } /// }
/// ``` /// ```
pub trait Extension: AnyInfo + Send + Sync { pub trait Extension: AnyInfo + Send + Sync {
/// Nombre legible para el usuario. /// Nombre localizado de la extensión legible para el usuario.
/// ///
/// Predeterminado por el [`short_name()`](AnyInfo::short_name) del tipo asociado a la /// Predeterminado por el [`short_name()`](AnyInfo::short_name) del tipo asociado a la
/// extensión. /// extensión.
@ -34,18 +34,15 @@ pub trait Extension: AnyInfo + Send + Sync {
L10n::n(self.short_name()) L10n::n(self.short_name())
} }
/// Descripción corta para paneles, listados, etc. /// Descripción corta localizada de la extensión para paneles, listados, etc.
fn description(&self) -> L10n { fn description(&self) -> L10n {
L10n::default() L10n::default()
} }
/// Los temas son extensiones que implementan [`Extension`] y también /// Devuelve una referencia a esta misma extensión cuando se trata de un tema.
/// [`Theme`](crate::core::theme::Theme).
/// ///
/// Si la extensión no es un tema, este método devuelve `None` por defecto. /// Para ello, debe implementar [`Extension`] y también [`Theme`](crate::core::theme::Theme). Si
/// /// la extensión no es un tema, este método devuelve `None` por defecto.
/// En caso contrario, este método debe implementarse para devolver una referencia de sí mismo
/// como tema. Por ejemplo:
/// ///
/// ```rust /// ```rust
/// use pagetop::prelude::*; /// use pagetop::prelude::*;
@ -81,7 +78,7 @@ pub trait Extension: AnyInfo + Send + Sync {
actions_boxed![] actions_boxed![]
} }
/// Inicializa la extensión durante la lógica de arranque de la aplicación. /// Inicializa la extensión durante la fase de arranque de la aplicación.
/// ///
/// Se llama una sola vez, después de que todas las dependencias se han inicializado y antes de /// Se llama una sola vez, después de que todas las dependencias se han inicializado y antes de
/// aceptar cualquier petición HTTP. /// aceptar cualquier petición HTTP.
@ -104,8 +101,8 @@ pub trait Extension: AnyInfo + Send + Sync {
#[allow(unused_variables)] #[allow(unused_variables)]
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {} fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {}
/// Permite crear extensiones para deshabilitar y desinstalar los recursos de otras extensiones /// Permite crear extensiones para deshabilitar y desinstalar recursos de otras de versiones
/// utilizadas en versiones anteriores de la aplicación. /// anteriores de la aplicación.
/// ///
/// Actualmente no se usa, pero se deja como *placeholder* para futuras implementaciones. /// Actualmente no se usa, pero se deja como *placeholder* para futuras implementaciones.
fn drop_extensions(&self) -> Vec<ExtensionRef> { fn drop_extensions(&self) -> Vec<ExtensionRef> {

View file

@ -9,18 +9,16 @@
//! tipografías, espaciados y cualquier otro detalle visual o de comportamiento (comoanimaciones, //! tipografías, espaciados y cualquier otro detalle visual o de comportamiento (comoanimaciones,
//! *scripts* de interfaz, etc.). //! *scripts* de interfaz, etc.).
//! //!
//! Es una extensión más (implementando [`Extension`](crate::core::extension::Extension)). Se //! Los temas son extensiones que implementan [`Extension`](crate::core::extension::Extension); por
//! instala, activa y declara dependencias igual que el resto de extensiones; y se señala a sí misma //! lo que se instancian, declaran sus dependencias y se inician igual que el resto de extensiones;
//! como tema (implementando [`theme()`](crate::core::extension::Extension::theme) y [`Theme`]). //! pero serán temas si además implementan [`theme()`](crate::core::extension::Extension::theme) y
//! [`Theme`].
mod definition; mod definition;
pub use definition::{Theme, ThemeRef}; pub use definition::{Theme, ThemeRef};
mod regions; mod regions;
pub(crate) use regions::ChildrenInRegions; pub(crate) use regions::ChildrenInRegions;
pub use regions::InRegion; pub use regions::{InRegion, Region, REGION_CONTENT};
pub(crate) mod all; pub(crate) mod all;
/// Nombre de la región por defecto: `content`.
pub const CONTENT_REGION_NAME: &str = "content";

View file

@ -1,10 +1,12 @@
use crate::core::extension::Extension; use crate::core::extension::Extension;
use crate::core::theme::CONTENT_REGION_NAME; use crate::core::theme::Region;
use crate::global; 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 std::sync::LazyLock;
/// Representa una referencia a un tema. /// Representa una referencia a un tema.
/// ///
/// Los temas son también extensiones. Por tanto se deben definir igual, es decir, como instancias /// Los temas son también extensiones. Por tanto se deben definir igual, es decir, como instancias
@ -14,7 +16,7 @@ pub type ThemeRef = &'static dyn Theme;
/// Interfaz común que debe implementar cualquier tema de `PageTop`. /// Interfaz común que debe implementar cualquier tema de `PageTop`.
/// ///
/// Un tema implementará [`Theme`] y los métodos que sean necesarios de [`Extension`], aunque el /// Un tema implementará [`Theme`] y los métodos que sean necesarios de [`Extension`], aunque el
/// único obligatorio es [`theme()`](Extension::theme). /// único obligatorio será [`theme()`](Extension::theme).
/// ///
/// ```rust /// ```rust
/// use pagetop::prelude::*; /// use pagetop::prelude::*;
@ -22,8 +24,13 @@ pub type ThemeRef = &'static dyn Theme;
/// pub struct MyTheme; /// pub struct MyTheme;
/// ///
/// impl Extension for MyTheme { /// impl Extension for MyTheme {
/// fn name(&self) -> L10n { L10n::n("My theme") } /// fn name(&self) -> L10n {
/// fn description(&self) -> L10n { L10n::n("Un tema personal") } /// L10n::n("My theme")
/// }
///
/// fn description(&self) -> L10n {
/// L10n::n("A personal theme")
/// }
/// ///
/// fn theme(&self) -> Option<ThemeRef> { /// fn theme(&self) -> Option<ThemeRef> {
/// Some(&Self) /// Some(&Self)
@ -33,20 +40,65 @@ pub type ThemeRef = &'static dyn Theme;
/// impl Theme for MyTheme {} /// impl Theme for MyTheme {}
/// ``` /// ```
pub trait Theme: Extension + Send + Sync { pub trait Theme: Extension + Send + Sync {
/// **Obsoleto desde la versión 0.4.0**: usar [`declared_regions()`](Self::declared_regions) en
/// su lugar.
#[deprecated(since = "0.4.0", note = "Use `declared_regions()` instead")]
fn regions(&self) -> Vec<(&'static str, L10n)> { fn regions(&self) -> Vec<(&'static str, L10n)> {
vec![(CONTENT_REGION_NAME, L10n::l("content"))] vec![("content", L10n::l("content"))]
} }
/// Declaración ordenada de las regiones disponibles en la página.
///
/// Devuelve una lista estática de pares `(Region, L10n)` que se usará para renderizar en el
/// orden indicado todas las regiones que componen una página. Los identificadores deben ser
/// **estables** como `"sidebar-left"` o `"content"`. La etiqueta `L10n` devuelve el nombre de la
/// región en el idioma activo de la página.
///
/// Si el tema requiere un conjunto distinto de regiones, se puede sobrescribir este método para
/// devolver una lista diferente. Si no, se usará la lista predeterminada:
///
/// - `"header"`: cabecera.
/// - `"content"`: contenido principal (**obligatoria**).
/// - `"footer"`: pie.
///
/// Sólo la región `"content"` es obligatoria, usa [`Region::default()`] para declararla.
#[inline]
fn declared_regions(&self) -> &'static [(Region, L10n)] {
static REGIONS: LazyLock<[(Region, L10n); 3]> = LazyLock::new(|| {
[
(Region::declare("header"), L10n::l("region_header")),
(Region::default(), L10n::l("region_content")),
(Region::declare("footer"), L10n::l("region_footer")),
]
});
&REGIONS[..]
}
/// Acciones específicas del tema antes de renderizar el `<body>` de la página.
///
/// Útil para preparar clases, inyectar recursos o ajustar metadatos.
#[allow(unused_variables)] #[allow(unused_variables)]
fn before_render_page_body(&self, page: &mut Page) {} fn before_render_page_body(&self, page: &mut Page) {}
/// Renderiza el contenido del `<body>` de la página.
///
/// Por defecto, recorre [`declared_regions()`](Self::declared_regions) **en el orden que se han
/// declarado** y, para cada región con contenido, genera un contenedor con `role="region"` y
/// `aria-label` localizado.
fn render_page_body(&self, page: &mut Page) -> Markup { fn render_page_body(&self, page: &mut Page) -> Markup {
html! { html! {
body id=[page.body_id().get()] class=[page.body_classes().get()] { body id=[page.body_id().get()] class=[page.body_classes().get()] {
@for (region_name, _) in self.regions() { @for (region, region_label) in self.declared_regions() {
@let output = page.render_region(region_name); @let output = page.render_region(region.key());
@if !output.is_empty() { @if !output.is_empty() {
div id=(region_name) class={ "region-container region-" (region_name) } { @let region_name = region.name();
div
id=(region_name)
class="region"
role="region"
aria-label=[region_label.using(page)]
{
div class={ "region__" (region_name) } {
(output) (output)
} }
} }
@ -54,10 +106,18 @@ pub trait Theme: Extension + Send + Sync {
} }
} }
} }
}
/// Acciones específicas del tema después de renderizar el `<body>` de la página.
///
/// Útil para *tracing*, métricas o ajustes finales del estado de la página.
#[allow(unused_variables)] #[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.
///
/// Por defecto, genera las etiquetas básicas (`charset`, `title`, `description`, `viewport`,
/// `X-UA-Compatible`), los metadatos y propiedades de la página y los recursos (CSS/JS).
fn render_page_head(&self, page: &mut Page) -> Markup { fn render_page_head(&self, page: &mut Page) -> Markup {
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! {
@ -89,11 +149,17 @@ pub trait Theme: Extension + Send + Sync {
} }
} }
fn error403(&self, _page: &mut Page) -> Markup { /// Página de error "*403 Forbidden*" predeterminada.
html! { div { h1 { ("FORBIDDEN ACCESS") } } } ///
/// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema.
fn error403(&self, page: &mut Page) -> Markup {
html! { div { h1 { (L10n::l("error403_notice").to_markup(page)) } } }
} }
fn error404(&self, _page: &mut Page) -> Markup { /// Página de error "*404 Not Found*" predeterminada.
html! { div { h1 { ("RESOURCE NOT FOUND") } } } ///
/// Se puede sobrescribir este método para personalizar y adaptar este contenido al tema.
fn error404(&self, page: &mut Page) -> Markup {
html! { div { h1 { (L10n::l("error404_notice").to_markup(page)) } } }
} }
} }

View file

@ -1,5 +1,5 @@
use crate::core::component::{Child, ChildOp, Children}; use crate::core::component::{Child, ChildOp, Children};
use crate::core::theme::{ThemeRef, CONTENT_REGION_NAME}; use crate::core::theme::ThemeRef;
use crate::{builder_fn, AutoDefault, UniqueId}; use crate::{builder_fn, AutoDefault, UniqueId};
use parking_lot::RwLock; use parking_lot::RwLock;
@ -7,15 +7,71 @@ use parking_lot::RwLock;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
// Regiones globales con componentes para un tema dado. // Conjunto de regiones globales asociadas a un tema específico.
static THEME_REGIONS: LazyLock<RwLock<HashMap<UniqueId, ChildrenInRegions>>> = static THEME_REGIONS: LazyLock<RwLock<HashMap<UniqueId, ChildrenInRegions>>> =
LazyLock::new(|| RwLock::new(HashMap::new())); LazyLock::new(|| RwLock::new(HashMap::new()));
// Regiones globales con componentes para cualquier tema. // Conjunto de regiones globales comunes a todos los temas.
static COMMON_REGIONS: LazyLock<RwLock<ChildrenInRegions>> = static COMMON_REGIONS: LazyLock<RwLock<ChildrenInRegions>> =
LazyLock::new(|| RwLock::new(ChildrenInRegions::default())); LazyLock::new(|| RwLock::new(ChildrenInRegions::default()));
// Estructura interna para mantener los componentes de una región. /// Nombre de la región de contenido por defecto (`"content"`).
pub const REGION_CONTENT: &str = "content";
/// Identificador de una región de página.
///
/// Incluye una **clave estática** ([`key()`](Self::key)) que identifica la región en el tema, y un
/// **nombre normalizado** ([`name()`](Self::name)) en minúsculas para su uso en atributos HTML
/// (p.ej., clases `region__{name}`).
///
/// Se utiliza para declarar las regiones que componen una página en un tema (ver
/// [`declared_regions()`](crate::core::theme::Theme::declared_regions)).
pub struct Region {
key: &'static str,
name: String,
}
impl Default for Region {
#[inline]
fn default() -> Self {
Self {
key: REGION_CONTENT,
name: String::from(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,
/// convirtiendo a minúsculas y sustituyendo los espacios intermedios por guiones (`-`).
///
/// Esta clave se usará para añadir componentes a la región; por ello se recomiendan nombres
/// sencillos, limitando los caracteres a `[a-z0-9-]` (p.ej., `"sidebar"` o `"main-menu"`), cuyo
/// nombre normalizado coincidirá con la clave.
#[inline]
pub fn declare(key: &'static str) -> Self {
Self {
key,
name: key.trim().to_ascii_lowercase().replace(' ', "-"),
}
}
/// Devuelve la clave estática asignada a la región.
#[inline]
pub fn key(&self) -> &'static str {
self.key
}
/// Devuelve el nombre normalizado de la región (para atributos y búsquedas).
#[inline]
pub fn name(&self) -> &str {
&self.name
}
}
// Contenedor interno de componentes agrupados por región.
#[derive(AutoDefault)] #[derive(AutoDefault)]
pub struct ChildrenInRegions(HashMap<&'static str, Children>); pub struct ChildrenInRegions(HashMap<&'static str, Children>);
@ -48,25 +104,24 @@ impl ChildrenInRegions {
} }
} }
/// Permite añadir componentes a regiones globales o regiones de temas concretos. /// Punto de acceso para añadir componentes a regiones globales o específicas de un tema.
/// ///
/// Dada una región, según la variante seleccionada, se le podrán añadir ([`add()`](Self::add)) /// Según la variante, se pueden añadir componentes ([`add()`](Self::add)) que permanecerán
/// componentes que se mantendrán durante la ejecución de la aplicación. /// disponibles durante toda la ejecución.
/// ///
/// Estas estructuras de componentes se renderizarán automáticamente al procesar los documentos HTML /// Estos componentes se renderizarán automáticamente al procesar los documentos HTML que incluyen
/// que las usan, como las páginas de contenido ([`Page`](crate::response::page::Page)), por /// estas regiones, como las páginas de contenido ([`Page`](crate::response::page::Page)).
/// ejemplo.
pub enum InRegion { pub enum InRegion {
/// Representa la región por defecto en la que se pueden añadir componentes. /// Región de contenido por defecto.
Content, Content,
/// Representa la región con el nombre del argumento. /// Región identificada por el nombre proporcionado.
Named(&'static str), Named(&'static str),
/// Representa la región con el nombre y del tema especificado en los argumentos. /// Región identificada por un nombre y asociada a un tema concreto.
OfTheme(&'static str, ThemeRef), OfTheme(&'static str, ThemeRef),
} }
impl InRegion { impl InRegion {
/// Permite añadir un componente en la región de la variante seleccionada. /// Añade un componente a la región indicada por la variante.
/// ///
/// # Ejemplo /// # Ejemplo
/// ///
@ -88,7 +143,7 @@ impl InRegion {
InRegion::Content => { InRegion::Content => {
COMMON_REGIONS COMMON_REGIONS
.write() .write()
.alter_child_in_region(CONTENT_REGION_NAME, ChildOp::Add(child)); .alter_child_in_region(REGION_CONTENT, ChildOp::Add(child));
} }
InRegion::Named(name) => { InRegion::Named(name) => {
COMMON_REGIONS COMMON_REGIONS

View file

@ -9,10 +9,9 @@ use crate::{builder_fn, join};
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::fmt::{self, Display};
use std::str::FromStr; use std::str::FromStr;
use std::fmt;
/// Operaciones para modificar el contexto ([`Context`]) del documento. /// Operaciones para modificar el contexto ([`Context`]) del documento.
pub enum AssetsOp { pub enum AssetsOp {
// Favicon. // Favicon.
@ -43,7 +42,7 @@ pub enum ErrorParam {
ParseError(String), ParseError(String),
} }
impl fmt::Display for ErrorParam { impl Display for ErrorParam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
ErrorParam::NotFound => write!(f, "Parameter not found"), ErrorParam::NotFound => write!(f, "Parameter not found"),

View file

@ -25,6 +25,7 @@ pub enum ClassesOp {
/// ///
/// - El [orden de las clases no es relevante](https://stackoverflow.com/a/1321712) en CSS. /// - El [orden de las clases no es relevante](https://stackoverflow.com/a/1321712) en CSS.
/// - No se permiten clases duplicadas. /// - No se permiten clases duplicadas.
/// - Las clases se convierten a minúsculas.
/// - Las clases vacías se ignoran. /// - Las clases vacías se ignoran.
/// ///
/// # Ejemplo /// # Ejemplo
@ -32,8 +33,8 @@ pub enum ClassesOp {
/// ```rust /// ```rust
/// use pagetop::prelude::*; /// use pagetop::prelude::*;
/// ///
/// let classes = OptionClasses::new("btn btn-primary") /// let classes = OptionClasses::new("Btn btn-primary")
/// .with_value(ClassesOp::Add, "active") /// .with_value(ClassesOp::Add, "Active")
/// .with_value(ClassesOp::Remove, "btn-primary"); /// .with_value(ClassesOp::Remove, "btn-primary");
/// ///
/// assert_eq!(classes.get(), Some(String::from("btn active"))); /// assert_eq!(classes.get(), Some(String::from("btn active")));
@ -51,7 +52,7 @@ impl OptionClasses {
#[builder_fn] #[builder_fn]
pub fn with_value(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self { pub fn with_value(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
let classes: &str = classes.as_ref(); let classes = classes.as_ref().to_ascii_lowercase();
let classes: Vec<&str> = classes.split_ascii_whitespace().collect(); let classes: Vec<&str> = classes.split_ascii_whitespace().collect();
if classes.is_empty() { if classes.is_empty() {

View file

@ -7,6 +7,7 @@ use crate::{builder_fn, AutoDefault};
/// # Normalización /// # Normalización
/// ///
/// - Se eliminan los espacios al principio y al final. /// - Se eliminan los espacios al principio y al final.
/// - Se convierte a minúsculas.
/// - Se sustituyen los espacios intermedios por guiones bajos (`_`). /// - Se sustituyen los espacios intermedios por guiones bajos (`_`).
/// - Si el resultado es una cadena vacía, se guarda `None`. /// - Si el resultado es una cadena vacía, se guarda `None`.
/// ///
@ -15,7 +16,7 @@ use crate::{builder_fn, AutoDefault};
/// ```rust /// ```rust
/// use pagetop::prelude::*; /// use pagetop::prelude::*;
/// ///
/// let id = OptionId::new("main section"); /// let id = OptionId::new(" main Section ");
/// assert_eq!(id.get(), Some(String::from("main_section"))); /// assert_eq!(id.get(), Some(String::from("main_section")));
/// ///
/// let empty = OptionId::default(); /// let empty = OptionId::default();
@ -39,7 +40,7 @@ impl OptionId {
/// El valor se normaliza automáticamente. /// El valor se normaliza automáticamente.
#[builder_fn] #[builder_fn]
pub fn with_value(mut self, value: impl AsRef<str>) -> Self { pub fn with_value(mut self, value: impl AsRef<str>) -> Self {
let value = value.as_ref().trim().replace(' ', "_"); let value = value.as_ref().trim().to_ascii_lowercase().replace(' ', "_");
self.0 = (!value.is_empty()).then_some(value); self.0 = (!value.is_empty()).then_some(value);
self self
} }

View file

@ -7,6 +7,7 @@ use crate::{builder_fn, AutoDefault};
/// # Normalización /// # Normalización
/// ///
/// - Se eliminan los espacios al principio y al final. /// - Se eliminan los espacios al principio y al final.
/// - Se convierte a minúsculas.
/// - Se sustituyen los espacios intermedios por guiones bajos (`_`). /// - Se sustituyen los espacios intermedios por guiones bajos (`_`).
/// - Si el resultado es una cadena vacía, se guarda `None`. /// - Si el resultado es una cadena vacía, se guarda `None`.
/// ///
@ -15,7 +16,7 @@ use crate::{builder_fn, AutoDefault};
/// ```rust /// ```rust
/// use pagetop::prelude::*; /// use pagetop::prelude::*;
/// ///
/// let name = OptionName::new(" display name "); /// let name = OptionName::new(" DISplay name ");
/// assert_eq!(name.get(), Some(String::from("display_name"))); /// assert_eq!(name.get(), Some(String::from("display_name")));
/// ///
/// let empty = OptionName::default(); /// let empty = OptionName::default();
@ -39,7 +40,7 @@ impl OptionName {
/// El valor se normaliza automáticamente. /// El valor se normaliza automáticamente.
#[builder_fn] #[builder_fn]
pub fn with_value(mut self, value: impl AsRef<str>) -> Self { pub fn with_value(mut self, value: impl AsRef<str>) -> Self {
let value = value.as_ref().trim().replace(' ', "_"); let value = value.as_ref().trim().to_ascii_lowercase().replace(' ', "_");
self.0 = (!value.is_empty()).then_some(value); self.0 = (!value.is_empty()).then_some(value);
self self
} }

View file

@ -1,2 +1,9 @@
content = Content # Regions.
region_header = Header
region_content = Content
region_footer = Footer
error403_notice = FORBIDDEN ACCESS
error404_notice = RESOURCE NOT FOUND
pagetop_logo = PageTop Logo pagetop_logo = PageTop Logo

View file

@ -1,2 +1,9 @@
content = Contenido # Regions.
region_header = Cabecera
region_content = Contenido
region_footer = Pie de página
error403_notice = ACCESO NO PERMITIDO
error404_notice = RECURSO NO ENCONTRADO
pagetop_logo = Logotipo de PageTop pagetop_logo = Logotipo de PageTop

View file

@ -6,7 +6,7 @@ pub use actix_web::Result as ResultPage;
use crate::base::action; use crate::base::action;
use crate::builder_fn; use crate::builder_fn;
use crate::core::component::{Child, ChildOp, Component}; use crate::core::component::{Child, ChildOp, Component};
use crate::core::theme::{ChildrenInRegions, ThemeRef, CONTENT_REGION_NAME}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT};
use crate::html::{html, AssetsOp, Context, Markup, DOCTYPE}; use crate::html::{html, AssetsOp, Context, Markup, DOCTYPE};
use crate::html::{ClassesOp, OptionClasses, OptionId, OptionTranslated}; use crate::html::{ClassesOp, OptionClasses, OptionId, OptionTranslated};
use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier};
@ -123,7 +123,7 @@ impl Page {
/// Añade un componente a la región de contenido por defecto. /// Añade un componente a la región de contenido por defecto.
pub fn with_component(mut self, component: impl Component) -> Self { pub fn with_component(mut self, component: impl Component) -> Self {
self.regions self.regions
.alter_child_in_region(CONTENT_REGION_NAME, ChildOp::Add(Child::with(component))); .alter_child_in_region(REGION_CONTENT, ChildOp::Add(Child::with(component)));
self self
} }
@ -172,11 +172,6 @@ impl Page {
self.context.request() self.context.request()
} }
/// Devuelve el identificador de idioma asociado.
pub fn langid(&self) -> &LanguageIdentifier {
self.context.langid()
}
/// Devuelve el tema que se usará para renderizar la página. /// Devuelve el tema que se usará para renderizar la página.
pub fn theme(&self) -> ThemeRef { pub fn theme(&self) -> ThemeRef {
self.context.theme() self.context.theme()
@ -250,3 +245,9 @@ impl Page {
}) })
} }
} }
impl LangId for Page {
fn langid(&self) -> &'static LanguageIdentifier {
self.context.langid()
}
}

View file

@ -6,7 +6,7 @@ use crate::service::{HttpRequest, HttpResponse};
use super::Page; use super::Page;
use std::fmt; use std::fmt::{self, Display};
#[derive(Debug)] #[derive(Debug)]
pub enum ErrorPage { pub enum ErrorPage {
@ -19,7 +19,7 @@ pub enum ErrorPage {
Timeout(HttpRequest), Timeout(HttpRequest),
} }
impl fmt::Display for ErrorPage { impl Display for ErrorPage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
// Error 304. // Error 304.

View file

@ -56,8 +56,10 @@ pub fn resolve_absolute_dir<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
} }
} }
/// **Obsoleto desde la versión 0.3.0**: usar [`resolve_absolute_dir()`] en su lugar.
///
/// Devuelve la ruta absoluta a un directorio existente. /// Devuelve la ruta absoluta a un directorio existente.
#[deprecated(since = "0.3.0", note = "Use [`resolve_absolute_dir`] instead")] #[deprecated(since = "0.3.0", note = "Use `resolve_absolute_dir()` instead")]
pub fn absolute_dir<P, Q>(root_path: P, relative_path: Q) -> io::Result<PathBuf> pub fn absolute_dir<P, Q>(root_path: P, relative_path: Q) -> io::Result<PathBuf>
where where
P: AsRef<Path>, P: AsRef<Path>,

View file

@ -295,11 +295,6 @@ a:hover:visited {
transform: translateX(-100%); transform: translateX(-100%);
} }
} }
#poweredby-link:hover {
transition: all .5s;
transform: rotate(-3deg) scale(1.1);
box-shadow: 0px 3px 5px rgba(0,0,0,.4);
}
#poweredby-link:hover span { #poweredby-link:hover span {
animation-play-state: paused; animation-play-state: paused;
} }
@ -323,6 +318,11 @@ a:hover:visited {
max-width: 29.375rem; max-width: 29.375rem;
margin-bottom: 0; margin-bottom: 0;
} }
#poweredby-link:hover {
transition: all .5s;
transform: rotate(-3deg) scale(1.1);
box-shadow: 0px 3px 5px rgba(0,0,0,.4);
}
} }
.content-text { .content-text {