Compare commits
3 commits
3bf058b8a5
...
0127d17459
Author | SHA1 | Date | |
---|---|---|---|
0127d17459 | |||
1af3776a50 | |||
f182eb3178 |
9 changed files with 608 additions and 344 deletions
|
@ -39,7 +39,7 @@ mod smart_default;
|
|||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, ItemFn};
|
||||
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
|
||||
|
||||
/// Macro para escribir plantillas HTML (basada en [Maud](https://docs.rs/maud)).
|
||||
#[proc_macro]
|
||||
|
@ -107,114 +107,216 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream {
|
|||
/// `alter_...()`, que permitirá más adelante modificar instancias existentes.
|
||||
#[proc_macro_attribute]
|
||||
pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let fn_with = parse_macro_input!(item as ItemFn);
|
||||
let fn_with_name = fn_with.sig.ident.clone();
|
||||
let fn_with_name_str = fn_with.sig.ident.to_string();
|
||||
use syn::{parse2, FnArg, Ident, ImplItemFn, Pat, ReturnType, TraitItemFn, Type};
|
||||
|
||||
let ts: proc_macro2::TokenStream = item.clone().into();
|
||||
|
||||
enum Kind {
|
||||
Impl(ImplItemFn),
|
||||
Trait(TraitItemFn),
|
||||
}
|
||||
|
||||
// Detecta si estamos en `impl` o `trait`.
|
||||
let kind = if let Ok(it) = parse2::<ImplItemFn>(ts.clone()) {
|
||||
Kind::Impl(it)
|
||||
} else if let Ok(tt) = parse2::<TraitItemFn>(ts.clone()) {
|
||||
Kind::Trait(tt)
|
||||
} else {
|
||||
return quote! {
|
||||
compile_error!("#[builder_fn] only supports methods in `impl` blocks or `trait` items");
|
||||
}
|
||||
.into();
|
||||
};
|
||||
|
||||
// Extrae piezas comunes (sig, attrs, vis, bloque?, es_trait?).
|
||||
let (sig, attrs, vis, body_opt, is_trait) = match &kind {
|
||||
Kind::Impl(m) => (&m.sig, &m.attrs, Some(&m.vis), Some(&m.block), false),
|
||||
Kind::Trait(t) => (&t.sig, &t.attrs, None, t.default.as_ref(), true),
|
||||
};
|
||||
|
||||
let with_name = sig.ident.clone();
|
||||
let with_name_str = sig.ident.to_string();
|
||||
|
||||
// Valida el nombre del método.
|
||||
if !fn_with_name_str.starts_with("with_") {
|
||||
let expanded = quote_spanned! {
|
||||
fn_with.sig.ident.span() =>
|
||||
compile_error!("expected a \"pub fn with_...(mut self, ...) -> Self\" method");
|
||||
if !with_name_str.starts_with("with_") {
|
||||
return quote_spanned! {
|
||||
sig.ident.span() => compile_error!("expected a named `with_...()` method");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
|
||||
// Sólo se exige `pub` en `impl` (en `trait` no aplica).
|
||||
let vis_pub = match (is_trait, vis) {
|
||||
(false, Some(v)) => quote! { #v },
|
||||
_ => quote! {},
|
||||
};
|
||||
return expanded.into();
|
||||
}
|
||||
// Valida que el método es público.
|
||||
if !matches!(fn_with.vis, syn::Visibility::Public(_)) {
|
||||
|
||||
// Validaciones comunes.
|
||||
if sig.asyncness.is_some() {
|
||||
return quote_spanned! {
|
||||
fn_with.sig.ident.span() => compile_error!("expected method to be `pub`");
|
||||
sig.asyncness.span() => compile_error!("`with_...()` cannot be `async`");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
// Valida que el primer argumento es exactamente `mut self`.
|
||||
if let Some(syn::FnArg::Receiver(receiver)) = fn_with.sig.inputs.first() {
|
||||
if receiver.mutability.is_none() || receiver.reference.is_some() {
|
||||
if sig.constness.is_some() {
|
||||
return quote_spanned! {
|
||||
receiver.span() => compile_error!("expected `mut self` as the first argument");
|
||||
sig.constness.span() => compile_error!("`with_...()` cannot be `const`");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
if sig.abi.is_some() {
|
||||
return quote_spanned! {
|
||||
sig.abi.span() => compile_error!("`with_...()` cannot be `extern`");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
if sig.unsafety.is_some() {
|
||||
return quote_spanned! {
|
||||
sig.unsafety.span() => compile_error!("`with_...()` cannot be `unsafe`");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
|
||||
// En `impl` se exige exactamente `mut self`; y en `trait` se exige `self` (sin &).
|
||||
let receiver_ok = match sig.inputs.first() {
|
||||
Some(FnArg::Receiver(r)) => {
|
||||
// Rechaza `self: SomeType`.
|
||||
if r.colon_token.is_some() {
|
||||
false
|
||||
} else if is_trait {
|
||||
// Exactamente `self` (sin &, sin mut).
|
||||
r.reference.is_none() && r.mutability.is_none()
|
||||
} else {
|
||||
// Exactamente `mut self`.
|
||||
r.reference.is_none() && r.mutability.is_some()
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if !receiver_ok {
|
||||
let msg = if is_trait {
|
||||
"expected `self` (not `mut self`, `&self` or `&mut self`) in trait method"
|
||||
} else {
|
||||
"expected first argument to be exactly `mut self`"
|
||||
};
|
||||
let err = sig
|
||||
.inputs
|
||||
.first()
|
||||
.map(|a| a.span())
|
||||
.unwrap_or(sig.ident.span());
|
||||
return quote_spanned! {
|
||||
fn_with.sig.ident.span() => compile_error!("expected `mut self` as the first argument");
|
||||
err => compile_error!(#msg);
|
||||
}
|
||||
.into();
|
||||
}
|
||||
|
||||
// Valida que el método devuelve exactamente `Self`.
|
||||
if let syn::ReturnType::Type(_, ty) = &fn_with.sig.output {
|
||||
if let syn::Type::Path(type_path) = ty.as_ref() {
|
||||
if type_path.qself.is_some() || !type_path.path.is_ident("Self") {
|
||||
return quote_spanned! { ty.span() =>
|
||||
compile_error!("expected return type to be exactly `Self`");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
} else {
|
||||
return quote_spanned! { ty.span() =>
|
||||
compile_error!("expected return type to be exactly `Self`");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
} else {
|
||||
match &sig.output {
|
||||
ReturnType::Type(_, ty) => match ty.as_ref() {
|
||||
Type::Path(p) if p.qself.is_none() && p.path.is_ident("Self") => {}
|
||||
_ => {
|
||||
return quote_spanned! {
|
||||
fn_with.sig.output.span() => compile_error!("expected method to return `Self`");
|
||||
ty.span() => compile_error!("expected return type to be exactly `Self`");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return quote_spanned! {
|
||||
sig.output.span() => compile_error!("expected return type to be exactly `Self`");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
// Genera el nombre del método alter_...().
|
||||
let fn_alter_name_str = fn_with_name_str.replace("with_", "alter_");
|
||||
let fn_alter_name = syn::Ident::new(&fn_alter_name_str, fn_with.sig.ident.span());
|
||||
let stem = with_name_str.strip_prefix("with_").expect("validated");
|
||||
let alter_ident = Ident::new(&format!("alter_{stem}"), with_name.span());
|
||||
|
||||
// Extrae genéricos y cláusulas where.
|
||||
let fn_generics = &fn_with.sig.generics;
|
||||
let where_clause = &fn_with.sig.generics.where_clause;
|
||||
let generics = &sig.generics;
|
||||
let where_clause = &sig.generics.where_clause;
|
||||
|
||||
// Extrae argumentos y parámetros de llamada.
|
||||
let args: Vec<_> = fn_with.sig.inputs.iter().skip(1).collect();
|
||||
let params: Vec<_> = fn_with
|
||||
.sig
|
||||
.inputs
|
||||
// Extrae identificadores de los argumentos para la llamada (sin `mut` ni patrones complejos).
|
||||
let args: Vec<_> = sig.inputs.iter().skip(1).collect();
|
||||
let call_idents: Vec<Ident> = {
|
||||
let mut v = Vec::new();
|
||||
for arg in sig.inputs.iter().skip(1) {
|
||||
match arg {
|
||||
FnArg::Typed(pat) => {
|
||||
if let Pat::Ident(pat_ident) = pat.pat.as_ref() {
|
||||
v.push(pat_ident.ident.clone());
|
||||
} else {
|
||||
return quote_spanned! {
|
||||
pat.pat.span() => compile_error!(
|
||||
"each parameter must be a simple identifier, e.g. `value: T`"
|
||||
);
|
||||
}
|
||||
.into();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return quote_spanned! {
|
||||
arg.span() => compile_error!("unexpected receiver in parameter list");
|
||||
}
|
||||
.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
v
|
||||
};
|
||||
|
||||
// Extrae atributos descartando la documentación para incluir en `alter_...()`.
|
||||
let non_doc_attrs: Vec<_> = attrs
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|arg| match arg {
|
||||
syn::FnArg::Typed(pat) => &pat.pat,
|
||||
_ => panic!("unexpected argument type"),
|
||||
})
|
||||
.cloned()
|
||||
.filter(|a| !a.path().is_ident("doc"))
|
||||
.collect();
|
||||
|
||||
// Extrae bloque del método.
|
||||
let fn_with_block = &fn_with.block;
|
||||
|
||||
// Extrae documentación y otros atributos del método.
|
||||
let fn_with_attrs = &fn_with.attrs;
|
||||
|
||||
// Genera el método alter_...() con el código del método with_...().
|
||||
let fn_alter_doc =
|
||||
format!("Equivalente a [`Self::{fn_with_name_str}()`], pero sin usar el patrón *builder*.");
|
||||
|
||||
let fn_alter = quote! {
|
||||
#[doc = #fn_alter_doc]
|
||||
pub fn #fn_alter_name #fn_generics(&mut self, #(#args),*) -> &mut Self #where_clause {
|
||||
#fn_with_block
|
||||
}
|
||||
};
|
||||
|
||||
// Redefine el método with_...() para que llame a alter_...().
|
||||
let fn_with = quote! {
|
||||
#(#fn_with_attrs)*
|
||||
#[inline]
|
||||
pub fn #fn_with_name #fn_generics(mut self, #(#args),*) -> Self #where_clause {
|
||||
self.#fn_alter_name(#(#params),*);
|
||||
self
|
||||
}
|
||||
};
|
||||
// Documentación del método alter_...().
|
||||
let alter_doc =
|
||||
format!("Equivalente a [`Self::{with_name_str}()`], pero fuera del patrón *builder*.");
|
||||
|
||||
// Genera el código final.
|
||||
let expanded = quote! {
|
||||
#fn_with
|
||||
#[inline]
|
||||
#fn_alter
|
||||
let expanded = match body_opt {
|
||||
None => {
|
||||
quote! {
|
||||
#(#attrs)*
|
||||
fn #with_name #generics (self, #(#args),*) -> Self #where_clause;
|
||||
|
||||
#(#non_doc_attrs)*
|
||||
#[doc = #alter_doc]
|
||||
fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause;
|
||||
}
|
||||
}
|
||||
Some(body) => {
|
||||
let with_fn = if is_trait {
|
||||
quote! {
|
||||
#vis_pub fn #with_name #generics (self, #(#args),*) -> Self #where_clause {
|
||||
let mut s = self;
|
||||
s.#alter_ident(#(#call_idents),*);
|
||||
s
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#vis_pub fn #with_name #generics (mut self, #(#args),*) -> Self #where_clause {
|
||||
self.#alter_ident(#(#call_idents),*);
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#(#attrs)*
|
||||
#with_fn
|
||||
|
||||
#(#non_doc_attrs)*
|
||||
#[doc = #alter_doc]
|
||||
#vis_pub fn #alter_ident #generics (&mut self, #(#args),*) -> &mut Self #where_clause {
|
||||
#body
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
expanded.into()
|
||||
}
|
||||
|
|
|
@ -50,12 +50,11 @@ pub struct App {
|
|||
pub theme: String,
|
||||
/// Idioma por defecto para la aplicación.
|
||||
///
|
||||
/// Si no se especifica un valor válido, normalmente se usará el idioma devuelto por la
|
||||
/// implementación de [`LangId`](crate::locale::LangId) para [`Context`](crate::html::Context),
|
||||
/// en el siguiente orden: primero, el idioma establecido explícitamente con
|
||||
/// [`Context::with_langid()`](crate::html::Context::with_langid); si no se ha definido, se
|
||||
/// usará el indicado en la cabecera `Accept-Language` del navegador; y, si ninguno aplica, se
|
||||
/// empleará el idioma de respaldo ("en-US").
|
||||
/// Si no está definido o no es válido, el idioma efectivo para el renderizado se resolverá
|
||||
/// según la implementación de [`LangId`](crate::locale::LangId) en este orden: primero intenta
|
||||
/// con el establecido en [`Contextual::with_langid()`](crate::html::Contextual::with_langid);
|
||||
/// pero si no se ha definido explícitamente, usará el indicado en la cabecera `Accept-Language`
|
||||
/// del navegador; y, si ninguno aplica, se empleará el idioma de respaldo ("en-US").
|
||||
pub language: String,
|
||||
/// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o
|
||||
/// *"Starwars"*.
|
||||
|
|
|
@ -9,12 +9,12 @@ mod assets;
|
|||
pub use assets::favicon::Favicon;
|
||||
pub use assets::javascript::JavaScript;
|
||||
pub use assets::stylesheet::{StyleSheet, TargetMedia};
|
||||
pub(crate) use assets::Assets;
|
||||
pub use assets::{Asset, Assets};
|
||||
|
||||
// HTML DOCUMENT CONTEXT ***************************************************************************
|
||||
|
||||
mod context;
|
||||
pub use context::{AssetsOp, Context, ErrorParam};
|
||||
pub use context::{AssetsOp, Context, Contextual, ErrorParam};
|
||||
|
||||
// HTML ATTRIBUTES *********************************************************************************
|
||||
|
||||
|
|
|
@ -5,22 +5,49 @@ pub mod stylesheet;
|
|||
use crate::html::{html, Markup, Render};
|
||||
use crate::{AutoDefault, Weight};
|
||||
|
||||
pub trait AssetsTrait: Render {
|
||||
// Devuelve el nombre del recurso, utilizado como clave única.
|
||||
/// Representación genérica de un *script* [`JavaScript`](crate::html::JavaScript) o una hoja de
|
||||
/// estilos [`StyleSheet`](crate::html::StyleSheet).
|
||||
///
|
||||
/// Estos recursos se incluyen en los conjuntos de recursos ([`Assets`]) que suelen renderizarse en
|
||||
/// un documento HTML.
|
||||
///
|
||||
/// Cada recurso se identifica por un **nombre único** ([`Asset::name()`]), usado como clave; y un
|
||||
/// **peso** ([`Asset::weight()`]), que determina su orden relativo de renderizado.
|
||||
pub trait Asset: Render {
|
||||
/// Devuelve el nombre del recurso, utilizado como clave única.
|
||||
fn name(&self) -> &str;
|
||||
|
||||
// Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso.
|
||||
/// Devuelve el peso del recurso, usado para ordenar el renderizado de menor a mayor peso.
|
||||
fn weight(&self) -> Weight;
|
||||
}
|
||||
|
||||
/// Gestión común para conjuntos de recursos como [`JavaScript`](crate::html::JavaScript) y
|
||||
/// [`StyleSheet`](crate::html::StyleSheet).
|
||||
///
|
||||
/// Se emplea normalmente para agrupar, administrar y renderizar los recursos de un documento HTML.
|
||||
/// Cada recurso se identifica por un nombre único ([`Asset::name()`]) y tiene asociado un peso
|
||||
/// ([`Asset::weight()`]) que determina su orden de renderizado.
|
||||
///
|
||||
/// Durante el renderizado, los recursos se procesan en orden ascendente de peso. En caso de
|
||||
/// igualdad, se respeta el orden de inserción.
|
||||
#[derive(AutoDefault)]
|
||||
pub(crate) struct Assets<T>(Vec<T>);
|
||||
pub struct Assets<T>(Vec<T>);
|
||||
|
||||
impl<T: AssetsTrait> Assets<T> {
|
||||
impl<T: Asset> Assets<T> {
|
||||
/// Crea un nuevo conjunto vacío de recursos.
|
||||
///
|
||||
/// Normalmente no se instancia directamente, sino como parte de la gestión de recursos que
|
||||
/// hacen páginas o temas.
|
||||
pub fn new() -> Self {
|
||||
Assets::<T>(Vec::<T>::new())
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
/// Inserta un recurso.
|
||||
///
|
||||
/// Si no existe otro con el mismo nombre, lo añade. Si ya existe y su peso era mayor, lo
|
||||
/// reemplaza. Y si su peso era menor o igual, entonces no realiza ningún cambio.
|
||||
///
|
||||
/// Devuelve `true` si el recurso fue insertado o reemplazado.
|
||||
pub fn add(&mut self, asset: T) -> bool {
|
||||
match self.0.iter().position(|x| x.name() == asset.name()) {
|
||||
Some(index) => {
|
||||
|
@ -39,6 +66,9 @@ impl<T: AssetsTrait> Assets<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Elimina un recurso por nombre.
|
||||
///
|
||||
/// Devuelve `true` si el recurso existía y fue eliminado.
|
||||
pub fn remove(&mut self, name: impl AsRef<str>) -> bool {
|
||||
if let Some(index) = self.0.iter().position(|x| x.name() == name.as_ref()) {
|
||||
self.0.remove(index);
|
||||
|
@ -49,14 +79,14 @@ impl<T: AssetsTrait> Assets<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: AssetsTrait> Render for Assets<T> {
|
||||
impl<T: Asset> Render for Assets<T> {
|
||||
fn render(&self) -> Markup {
|
||||
let mut assets = self.0.iter().collect::<Vec<_>>();
|
||||
assets.sort_by_key(|a| a.weight());
|
||||
|
||||
html! {
|
||||
@for a in assets {
|
||||
(a.render())
|
||||
(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::html::assets::AssetsTrait;
|
||||
use crate::html::assets::Asset;
|
||||
use crate::html::{html, Markup, Render};
|
||||
use crate::{join, join_pair, AutoDefault, Weight};
|
||||
|
||||
|
@ -137,8 +137,10 @@ impl JavaScript {
|
|||
}
|
||||
}
|
||||
|
||||
impl AssetsTrait for JavaScript {
|
||||
// Para *scripts* externos es la ruta; para *scripts* embebidos, un identificador.
|
||||
impl Asset for JavaScript {
|
||||
/// Devuelve el nombre del recurso, utilizado como clave única.
|
||||
///
|
||||
/// Para *scripts* externos es la ruta del recurso; para *scripts* embebidos, un identificador.
|
||||
fn name(&self) -> &str {
|
||||
match &self.source {
|
||||
Source::From(path) => path,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::html::assets::AssetsTrait;
|
||||
use crate::html::assets::Asset;
|
||||
use crate::html::{html, Markup, PreEscaped, Render};
|
||||
use crate::{join_pair, AutoDefault, Weight};
|
||||
|
||||
|
@ -142,8 +142,10 @@ impl StyleSheet {
|
|||
}
|
||||
}
|
||||
|
||||
impl AssetsTrait for StyleSheet {
|
||||
// Para hojas de estilos externas es la ruta; para las embebidas, un identificador.
|
||||
impl Asset for StyleSheet {
|
||||
/// Devuelve el nombre del recurso, utilizado como clave única.
|
||||
///
|
||||
/// Para hojas de estilos externas es la ruta del recurso; para las embebidas, un identificador.
|
||||
fn name(&self) -> &str {
|
||||
match &self.source {
|
||||
Source::From(path) => path,
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{builder_fn, join};
|
|||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Operaciones para modificar el contexto ([`Context`]) del documento.
|
||||
/// Operaciones para modificar el contexto ([`Context`]) de un documento.
|
||||
pub enum AssetsOp {
|
||||
// Favicon.
|
||||
/// Define el *favicon* del documento. Sobrescribe cualquier valor anterior.
|
||||
|
@ -47,12 +47,101 @@ pub enum ErrorParam {
|
|||
},
|
||||
}
|
||||
|
||||
/// Representa el contexto de un documento HTML.
|
||||
/// Interfaz para gestionar el **contexto de renderizado** de un documento HTML.
|
||||
///
|
||||
/// Se crea internamente para manejar información relevante del documento, como la solicitud HTTP de
|
||||
/// origen, el idioma, tema y composición para el renderizado, los recursos *favicon* ([`Favicon`]),
|
||||
/// hojas de estilo ([`StyleSheet`]) y *scripts* ([`JavaScript`]), así como *parámetros dinámicos
|
||||
/// heterogéneos* de contexto definidos en tiempo de ejecución.
|
||||
/// `Contextual` extiende [`LangId`] y define los métodos para:
|
||||
///
|
||||
/// - Establecer el **idioma** del documento.
|
||||
/// - Almacenar la **solicitud HTTP** de origen.
|
||||
/// - Seleccionar **tema** y **composición** (*layout*) de renderizado.
|
||||
/// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo
|
||||
/// [`StyleSheet`] o los *scripts* [`JavaScript`] mediante [`AssetsOp`].
|
||||
/// - Leer y mantener **parámetros dinámicos tipados** de contexto.
|
||||
/// - Generar **identificadores únicos** por tipo de componente.
|
||||
///
|
||||
/// Lo implementan, típicamente, estructuras que representan el contexto de renderizado, como
|
||||
/// [`Context`](crate::html::Context) o [`Page`](crate::response::page::Page).
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// fn prepare_context<C: Contextual>(cx: C) -> C {
|
||||
/// cx.with_langid(&LangMatch::resolve("es-ES"))
|
||||
/// .with_theme("aliner")
|
||||
/// .with_layout("default")
|
||||
/// .with_assets(AssetsOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico"))))
|
||||
/// .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/app.css")))
|
||||
/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/app.js")))
|
||||
/// .with_param("usuario_id", 42_i32)
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Contextual: LangId {
|
||||
// Contextual BUILDER **************************************************************************
|
||||
|
||||
/// Establece el idioma del documento.
|
||||
#[builder_fn]
|
||||
fn with_langid(self, language: &impl LangId) -> Self;
|
||||
|
||||
/// Almacena la solicitud HTTP de origen en el contexto.
|
||||
#[builder_fn]
|
||||
fn with_request(self, request: Option<HttpRequest>) -> Self;
|
||||
|
||||
/// Especifica el tema para renderizar el documento.
|
||||
#[builder_fn]
|
||||
fn with_theme(self, theme_name: &'static str) -> Self;
|
||||
|
||||
/// Especifica la composición para renderizar el documento.
|
||||
#[builder_fn]
|
||||
fn with_layout(self, layout_name: &'static str) -> Self;
|
||||
|
||||
/// Añade o modifica un parámetro dinámico del contexto.
|
||||
#[builder_fn]
|
||||
fn with_param<T: 'static>(self, key: &'static str, value: T) -> Self;
|
||||
|
||||
/// Define los recursos del contexto usando [`AssetsOp`].
|
||||
#[builder_fn]
|
||||
fn with_assets(self, op: AssetsOp) -> Self;
|
||||
|
||||
// Contextual GETTERS **************************************************************************
|
||||
|
||||
/// Devuelve una referencia a la solicitud HTTP asociada, si existe.
|
||||
fn request(&self) -> Option<&HttpRequest>;
|
||||
|
||||
/// Devuelve el tema que se usará para renderizar el documento.
|
||||
fn theme(&self) -> ThemeRef;
|
||||
|
||||
/// Devuelve la composición para renderizar el documento. Por defecto es `"default"`.
|
||||
fn layout(&self) -> &str;
|
||||
|
||||
/// Recupera un parámetro como [`Option`].
|
||||
fn param<T: 'static>(&self, key: &'static str) -> Option<&T>;
|
||||
|
||||
/// Devuelve el Favicon de los recursos del contexto.
|
||||
fn favicon(&self) -> Option<&Favicon>;
|
||||
|
||||
/// Devuelve las hojas de estilo de los recursos del contexto.
|
||||
fn stylesheets(&self) -> &Assets<StyleSheet>;
|
||||
|
||||
/// Devuelve los *scripts* JavaScript de los recursos del contexto.
|
||||
fn javascripts(&self) -> &Assets<JavaScript>;
|
||||
|
||||
// Contextual HELPERS **************************************************************************
|
||||
|
||||
/// Genera un identificador único por tipo (`<tipo>-<n>`) cuando no se aporta uno explícito.
|
||||
///
|
||||
/// Es útil para componentes u otros elementos HTML que necesitan un identificador predecible si
|
||||
/// no se proporciona ninguno.
|
||||
fn required_id<T>(&mut self, id: Option<String>) -> String;
|
||||
}
|
||||
|
||||
/// Implementa un **contexto de renderizado** para un documento HTML.
|
||||
///
|
||||
/// Extiende [`Contextual`] con métodos para **instanciar** y configurar un nuevo contexto,
|
||||
/// **renderizar los recursos** del documento (incluyendo el [`Favicon`], las hojas de estilo
|
||||
/// [`StyleSheet`] y los *scripts* [`JavaScript`]), o extender el uso de **parámetros dinámicos
|
||||
/// tipados** con nuevos métodos.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
|
@ -107,7 +196,7 @@ pub struct Context {
|
|||
favicon : Option<Favicon>, // Favicon, si se ha definido.
|
||||
stylesheets: Assets<StyleSheet>, // Hojas de estilo CSS.
|
||||
javascripts: Assets<JavaScript>, // Scripts JavaScript.
|
||||
params : HashMap<&'static str, (Box<dyn Any>, &'static str)>, // Parámetros definidos en tiempo de ejecución.
|
||||
params : HashMap<&'static str, (Box<dyn Any>, &'static str)>, // Parámetros en ejecución.
|
||||
id_counter : usize, // Contador para generar identificadores únicos.
|
||||
}
|
||||
|
||||
|
@ -151,80 +240,6 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
// Context BUILDER *****************************************************************************
|
||||
|
||||
/// Modifica la fuente de idioma del documento.
|
||||
#[builder_fn]
|
||||
pub fn with_langid(mut self, language: &impl LangId) -> Self {
|
||||
self.langid = language.langid();
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica el tema que se usará para renderizar el documento.
|
||||
///
|
||||
/// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica
|
||||
/// ninguno entonces usará el tema por defecto.
|
||||
#[builder_fn]
|
||||
pub fn with_theme(mut self, theme_name: &'static str) -> Self {
|
||||
self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica la composición para renderizar el documento.
|
||||
#[builder_fn]
|
||||
pub fn with_layout(mut self, layout_name: &'static str) -> Self {
|
||||
self.layout = layout_name;
|
||||
self
|
||||
}
|
||||
|
||||
/// Define los recursos del contexto usando [`AssetsOp`].
|
||||
#[builder_fn]
|
||||
pub fn with_assets(mut self, op: AssetsOp) -> Self {
|
||||
match op {
|
||||
// Favicon.
|
||||
AssetsOp::SetFavicon(favicon) => {
|
||||
self.favicon = favicon;
|
||||
}
|
||||
AssetsOp::SetFaviconIfNone(icon) => {
|
||||
if self.favicon.is_none() {
|
||||
self.favicon = Some(icon);
|
||||
}
|
||||
}
|
||||
// Stylesheets.
|
||||
AssetsOp::AddStyleSheet(css) => {
|
||||
self.stylesheets.add(css);
|
||||
}
|
||||
AssetsOp::RemoveStyleSheet(path) => {
|
||||
self.stylesheets.remove(path);
|
||||
}
|
||||
// JavaScripts.
|
||||
AssetsOp::AddJavaScript(js) => {
|
||||
self.javascripts.add(js);
|
||||
}
|
||||
AssetsOp::RemoveJavaScript(path) => {
|
||||
self.javascripts.remove(path);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// Context GETTERS *****************************************************************************
|
||||
|
||||
/// Devuelve una referencia a la solicitud HTTP asociada, si existe.
|
||||
pub fn request(&self) -> Option<&HttpRequest> {
|
||||
self.request.as_ref()
|
||||
}
|
||||
|
||||
/// Devuelve el tema que se usará para renderizar el documento.
|
||||
pub fn theme(&self) -> ThemeRef {
|
||||
self.theme
|
||||
}
|
||||
|
||||
/// Devuelve la composición para renderizar el documento. Por defecto es `"default"`.
|
||||
pub fn layout(&self) -> &str {
|
||||
self.layout
|
||||
}
|
||||
|
||||
// Context RENDER ******************************************************************************
|
||||
|
||||
/// Renderiza los recursos del contexto.
|
||||
|
@ -240,61 +255,6 @@ impl Context {
|
|||
|
||||
// Context PARAMS ******************************************************************************
|
||||
|
||||
/// Añade o modifica un parámetro dinámico del contexto.
|
||||
///
|
||||
/// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error
|
||||
/// posteriores.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let cx = Context::new(None)
|
||||
/// .with_param("usuario_id", 42_i32)
|
||||
/// .with_param("titulo", String::from("Hola"))
|
||||
/// .with_param("flags", vec!["a", "b"]);
|
||||
/// ```
|
||||
#[builder_fn]
|
||||
pub fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||
let type_name = TypeInfo::FullName.of::<T>();
|
||||
self.params.insert(key, (Box::new(value), type_name));
|
||||
self
|
||||
}
|
||||
|
||||
/// Recupera un parámetro como [`Option`], simplificando el acceso.
|
||||
///
|
||||
/// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con información
|
||||
/// detallada de error, este método devuelve `None` tanto si la clave no existe como si el valor
|
||||
/// guardado no coincide con el tipo solicitado.
|
||||
///
|
||||
/// Resulta útil en escenarios donde sólo interesa saber si el valor existe y es del tipo
|
||||
/// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let cx = Context::new(None).with_param("username", String::from("Alice"));
|
||||
///
|
||||
/// // Devuelve Some(&String) si existe y coincide el tipo.
|
||||
/// assert_eq!(cx.param::<String>("username").map(|s| s.as_str()), Some("Alice"));
|
||||
///
|
||||
/// // Devuelve None si no existe o si el tipo no coincide.
|
||||
/// assert!(cx.param::<i32>("username").is_none());
|
||||
/// assert!(cx.param::<String>("missing").is_none());
|
||||
///
|
||||
/// // Acceso con valor por defecto.
|
||||
/// let user = cx.param::<String>("missing")
|
||||
/// .cloned()
|
||||
/// .unwrap_or_else(|| "visitor".to_string());
|
||||
/// assert_eq!(user, "visitor");
|
||||
/// ```
|
||||
pub fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
||||
self.get_param::<T>(key).ok()
|
||||
}
|
||||
|
||||
/// Recupera una *referencia tipada* al parámetro solicitado.
|
||||
///
|
||||
/// Devuelve:
|
||||
|
@ -328,23 +288,6 @@ impl Context {
|
|||
})
|
||||
}
|
||||
|
||||
/// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó.
|
||||
///
|
||||
/// Devuelve `false` en caso contrario. Usar cuando solo interesa borrar la entrada.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let mut cx = Context::new(None).with_param("temp", 1u8);
|
||||
/// assert!(cx.remove_param("temp"));
|
||||
/// assert!(!cx.remove_param("temp")); // ya no existe
|
||||
/// ```
|
||||
pub fn remove_param(&mut self, key: &'static str) -> bool {
|
||||
self.params.remove(key).is_some()
|
||||
}
|
||||
|
||||
/// Recupera el parámetro solicitado y lo elimina del contexto.
|
||||
///
|
||||
/// Devuelve:
|
||||
|
@ -380,30 +323,21 @@ impl Context {
|
|||
})
|
||||
}
|
||||
|
||||
// Context EXTRAS ******************************************************************************
|
||||
|
||||
/// Genera un identificador único si no se proporciona uno explícito.
|
||||
/// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó.
|
||||
///
|
||||
/// Si no se proporciona un `id`, se genera un identificador único en la forma `<tipo>-<número>`
|
||||
/// donde `<tipo>` es el nombre corto del tipo en minúsculas (sin espacios) y `<número>` es un
|
||||
/// contador interno incremental.
|
||||
pub fn required_id<T>(&mut self, id: Option<String>) -> String {
|
||||
if let Some(id) = id {
|
||||
id
|
||||
} else {
|
||||
let prefix = TypeInfo::ShortName
|
||||
.of::<T>()
|
||||
.trim()
|
||||
.replace(' ', "_")
|
||||
.to_lowercase();
|
||||
let prefix = if prefix.is_empty() {
|
||||
"prefix".to_owned()
|
||||
} else {
|
||||
prefix
|
||||
};
|
||||
self.id_counter += 1;
|
||||
join!(prefix, "-", self.id_counter.to_string())
|
||||
}
|
||||
/// Devuelve `false` en caso contrario. Usar cuando solo interesa borrar la entrada.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let mut cx = Context::new(None).with_param("temp", 1u8);
|
||||
/// assert!(cx.remove_param("temp"));
|
||||
/// assert!(!cx.remove_param("temp")); // ya no existe
|
||||
/// ```
|
||||
pub fn remove_param(&mut self, key: &'static str) -> bool {
|
||||
self.params.remove(key).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -423,3 +357,173 @@ impl LangId for Context {
|
|||
self.langid
|
||||
}
|
||||
}
|
||||
|
||||
impl Contextual for Context {
|
||||
// Contextual BUILDER **************************************************************************
|
||||
|
||||
#[builder_fn]
|
||||
fn with_request(mut self, request: Option<HttpRequest>) -> Self {
|
||||
self.request = request;
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_langid(mut self, language: &impl LangId) -> Self {
|
||||
self.langid = language.langid();
|
||||
self
|
||||
}
|
||||
|
||||
/// Asigna el tema para renderizar el documento.
|
||||
///
|
||||
/// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica
|
||||
/// ninguno entonces usará el tema por defecto.
|
||||
#[builder_fn]
|
||||
fn with_theme(mut self, theme_name: &'static str) -> Self {
|
||||
self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_layout(mut self, layout_name: &'static str) -> Self {
|
||||
self.layout = layout_name;
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade o modifica un parámetro dinámico del contexto.
|
||||
///
|
||||
/// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error
|
||||
/// posteriores.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let cx = Context::new(None)
|
||||
/// .with_param("usuario_id", 42_i32)
|
||||
/// .with_param("titulo", String::from("Hola"))
|
||||
/// .with_param("flags", vec!["a", "b"]);
|
||||
/// ```
|
||||
#[builder_fn]
|
||||
fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||
let type_name = TypeInfo::FullName.of::<T>();
|
||||
self.params.insert(key, (Box::new(value), type_name));
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_assets(mut self, op: AssetsOp) -> Self {
|
||||
match op {
|
||||
// Favicon.
|
||||
AssetsOp::SetFavicon(favicon) => {
|
||||
self.favicon = favicon;
|
||||
}
|
||||
AssetsOp::SetFaviconIfNone(icon) => {
|
||||
if self.favicon.is_none() {
|
||||
self.favicon = Some(icon);
|
||||
}
|
||||
}
|
||||
// Stylesheets.
|
||||
AssetsOp::AddStyleSheet(css) => {
|
||||
self.stylesheets.add(css);
|
||||
}
|
||||
AssetsOp::RemoveStyleSheet(path) => {
|
||||
self.stylesheets.remove(path);
|
||||
}
|
||||
// JavaScripts.
|
||||
AssetsOp::AddJavaScript(js) => {
|
||||
self.javascripts.add(js);
|
||||
}
|
||||
AssetsOp::RemoveJavaScript(path) => {
|
||||
self.javascripts.remove(path);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// Contextual GETTERS **************************************************************************
|
||||
|
||||
fn request(&self) -> Option<&HttpRequest> {
|
||||
self.request.as_ref()
|
||||
}
|
||||
|
||||
fn theme(&self) -> ThemeRef {
|
||||
self.theme
|
||||
}
|
||||
|
||||
fn layout(&self) -> &str {
|
||||
self.layout
|
||||
}
|
||||
|
||||
/// Recupera un parámetro como [`Option`], simplificando el acceso.
|
||||
///
|
||||
/// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con información
|
||||
/// detallada de error, este método devuelve `None` tanto si la clave no existe como si el valor
|
||||
/// guardado no coincide con el tipo solicitado.
|
||||
///
|
||||
/// Resulta útil en escenarios donde sólo interesa saber si el valor existe y es del tipo
|
||||
/// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let cx = Context::new(None).with_param("username", String::from("Alice"));
|
||||
///
|
||||
/// // Devuelve Some(&String) si existe y coincide el tipo.
|
||||
/// assert_eq!(cx.param::<String>("username").map(|s| s.as_str()), Some("Alice"));
|
||||
///
|
||||
/// // Devuelve None si no existe o si el tipo no coincide.
|
||||
/// assert!(cx.param::<i32>("username").is_none());
|
||||
/// assert!(cx.param::<String>("missing").is_none());
|
||||
///
|
||||
/// // Acceso con valor por defecto.
|
||||
/// let user = cx.param::<String>("missing")
|
||||
/// .cloned()
|
||||
/// .unwrap_or_else(|| "visitor".to_string());
|
||||
/// assert_eq!(user, "visitor");
|
||||
/// ```
|
||||
fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
||||
self.get_param::<T>(key).ok()
|
||||
}
|
||||
|
||||
fn favicon(&self) -> Option<&Favicon> {
|
||||
self.favicon.as_ref()
|
||||
}
|
||||
|
||||
fn stylesheets(&self) -> &Assets<StyleSheet> {
|
||||
&self.stylesheets
|
||||
}
|
||||
|
||||
fn javascripts(&self) -> &Assets<JavaScript> {
|
||||
&self.javascripts
|
||||
}
|
||||
|
||||
// Contextual HELPERS **************************************************************************
|
||||
|
||||
/// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona
|
||||
/// un `id` explícito.
|
||||
///
|
||||
/// Si no se proporciona un `id`, se genera un identificador único en la forma `<tipo>-<número>`
|
||||
/// donde `<tipo>` es el nombre corto del tipo en minúsculas (sin espacios) y `<número>` es un
|
||||
/// contador interno incremental.
|
||||
fn required_id<T>(&mut self, id: Option<String>) -> String {
|
||||
if let Some(id) = id {
|
||||
id
|
||||
} else {
|
||||
let prefix = TypeInfo::ShortName
|
||||
.of::<T>()
|
||||
.trim()
|
||||
.replace(' ', "_")
|
||||
.to_lowercase();
|
||||
let prefix = if prefix.is_empty() {
|
||||
"prefix".to_owned()
|
||||
} else {
|
||||
prefix
|
||||
};
|
||||
self.id_counter += 1;
|
||||
join!(prefix, "-", self.id_counter.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ use crate::builder_fn;
|
|||
use crate::core::component::{Child, ChildOp, Component};
|
||||
use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT};
|
||||
use crate::html::{html, Markup, DOCTYPE};
|
||||
use crate::html::{AssetsOp, Context};
|
||||
use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
|
||||
use crate::html::{AssetsOp, Context, Contextual};
|
||||
use crate::html::{AttrClasses, ClassesOp};
|
||||
use crate::html::{AttrId, AttrL10n};
|
||||
use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier};
|
||||
|
@ -25,9 +26,9 @@ pub struct Page {
|
|||
description : AttrL10n,
|
||||
metadata : Vec<(&'static str, &'static str)>,
|
||||
properties : Vec<(&'static str, &'static str)>,
|
||||
context : Context,
|
||||
body_id : AttrId,
|
||||
body_classes: AttrClasses,
|
||||
context : Context,
|
||||
regions : ChildrenInRegions,
|
||||
}
|
||||
|
||||
|
@ -43,9 +44,9 @@ impl Page {
|
|||
description : AttrL10n::default(),
|
||||
metadata : Vec::default(),
|
||||
properties : Vec::default(),
|
||||
context : Context::new(request),
|
||||
body_id : AttrId::default(),
|
||||
body_classes: AttrClasses::default(),
|
||||
context : Context::new(request),
|
||||
regions : ChildrenInRegions::default(),
|
||||
}
|
||||
}
|
||||
|
@ -80,40 +81,6 @@ impl Page {
|
|||
self
|
||||
}
|
||||
|
||||
/// Modifica la fuente de idioma de la página ([`Context::with_langid()`]).
|
||||
#[builder_fn]
|
||||
pub fn with_langid(mut self, language: &impl LangId) -> Self {
|
||||
self.context.alter_langid(language);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica el tema que se usará para renderizar la página ([`Context::with_theme()`]).
|
||||
#[builder_fn]
|
||||
pub fn with_theme(mut self, theme_name: &'static str) -> Self {
|
||||
self.context.alter_theme(theme_name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica la composición para renderizar la página ([`Context::with_layout()`]).
|
||||
#[builder_fn]
|
||||
pub fn with_layout(mut self, layout_name: &'static str) -> Self {
|
||||
self.context.alter_layout(layout_name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Define los recursos de la página usando [`AssetsOp`].
|
||||
#[builder_fn]
|
||||
pub fn with_assets(mut self, op: AssetsOp) -> Self {
|
||||
self.context.alter_assets(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||
self.context.alter_param(key, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el atributo `id` del elemento `<body>`.
|
||||
#[builder_fn]
|
||||
pub fn with_body_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
|
@ -205,25 +172,6 @@ impl Page {
|
|||
&self.properties
|
||||
}
|
||||
|
||||
/// Devuelve la solicitud HTTP asociada.
|
||||
pub fn request(&self) -> Option<&HttpRequest> {
|
||||
self.context.request()
|
||||
}
|
||||
|
||||
/// Devuelve el tema que se usará para renderizar la página.
|
||||
pub fn theme(&self) -> ThemeRef {
|
||||
self.context.theme()
|
||||
}
|
||||
|
||||
/// Devuelve la composición para renderizar la página. Por defecto es `"default"`.
|
||||
pub fn layout(&self) -> &str {
|
||||
self.context.layout()
|
||||
}
|
||||
|
||||
pub fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
||||
self.context.param(key)
|
||||
}
|
||||
|
||||
/// Devuelve el identificador del elemento `<body>`.
|
||||
pub fn body_id(&self) -> &AttrId {
|
||||
&self.body_id
|
||||
|
@ -233,7 +181,7 @@ impl Page {
|
|||
pub fn body_classes(&self) -> &AttrClasses {
|
||||
&self.body_classes
|
||||
}
|
||||
/*
|
||||
|
||||
/// Devuelve una referencia mutable al [`Context`] de la página.
|
||||
///
|
||||
/// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema,
|
||||
|
@ -242,10 +190,10 @@ impl Page {
|
|||
pub fn context(&mut self) -> &mut Context {
|
||||
&mut self.context
|
||||
}
|
||||
*/
|
||||
|
||||
// Page RENDER *********************************************************************************
|
||||
|
||||
/// Renderiza los componentes de una región (`regiona_name`) de la página.
|
||||
/// Renderiza los componentes de una región (`region_name`) de la página.
|
||||
pub fn render_region(&mut self, region_name: &'static str) -> Markup {
|
||||
self.regions
|
||||
.merge_all_components(self.context.theme(), region_name)
|
||||
|
@ -302,3 +250,79 @@ impl LangId for Page {
|
|||
self.context.langid()
|
||||
}
|
||||
}
|
||||
|
||||
impl Contextual for Page {
|
||||
// Contextual BUILDER **************************************************************************
|
||||
|
||||
#[builder_fn]
|
||||
fn with_request(mut self, request: Option<HttpRequest>) -> Self {
|
||||
self.context.alter_request(request);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_langid(mut self, language: &impl LangId) -> Self {
|
||||
self.context.alter_langid(language);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_theme(mut self, theme_name: &'static str) -> Self {
|
||||
self.context.alter_theme(theme_name);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_layout(mut self, layout_name: &'static str) -> Self {
|
||||
self.context.alter_layout(layout_name);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||
self.context.alter_param(key, value);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_assets(mut self, op: AssetsOp) -> Self {
|
||||
self.context.alter_assets(op);
|
||||
self
|
||||
}
|
||||
|
||||
// Contextual GETTERS **************************************************************************
|
||||
|
||||
fn request(&self) -> Option<&HttpRequest> {
|
||||
self.context.request()
|
||||
}
|
||||
|
||||
fn theme(&self) -> ThemeRef {
|
||||
self.context.theme()
|
||||
}
|
||||
|
||||
fn layout(&self) -> &str {
|
||||
self.context.layout()
|
||||
}
|
||||
|
||||
fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
||||
self.context.param(key)
|
||||
}
|
||||
|
||||
fn favicon(&self) -> Option<&Favicon> {
|
||||
self.context.favicon()
|
||||
}
|
||||
|
||||
fn stylesheets(&self) -> &Assets<StyleSheet> {
|
||||
self.context.stylesheets()
|
||||
}
|
||||
|
||||
fn javascripts(&self) -> &Assets<JavaScript> {
|
||||
self.context.javascripts()
|
||||
}
|
||||
|
||||
// Contextual HELPERS **************************************************************************
|
||||
|
||||
fn required_id<T>(&mut self, id: Option<String>) -> String {
|
||||
self.context.required_id::<T>(id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::base::component::Html;
|
||||
use crate::html::Contextual;
|
||||
use crate::locale::L10n;
|
||||
use crate::response::ResponseError;
|
||||
use crate::service::http::{header::ContentType, StatusCode};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue