♻️ (pagetop): Optimiza cadenas con CowStr
This commit is contained in:
parent
cf7aba2b53
commit
b39ed38d0d
11 changed files with 229 additions and 159 deletions
|
|
@ -54,22 +54,46 @@ pub enum Source {
|
|||
Logo(PageTopSvg),
|
||||
/// Imagen que se adapta automáticamente a su contenedor.
|
||||
///
|
||||
/// El `String` asociado es la URL (o ruta) de la imagen.
|
||||
Responsive(String),
|
||||
/// Lleva asociada la URL (o ruta) de la imagen.
|
||||
Responsive(CowStr),
|
||||
/// Imagen que aplica el estilo **miniatura** de Bootstrap.
|
||||
///
|
||||
/// El `String` asociado es la URL (o ruta) de la imagen.
|
||||
Thumbnail(String),
|
||||
/// Lleva asociada la URL (o ruta) de la imagen.
|
||||
Thumbnail(CowStr),
|
||||
/// Imagen sin clases específicas de Bootstrap, útil para controlar con CSS propio.
|
||||
///
|
||||
/// El `String` asociado es la URL (o ruta) de la imagen.
|
||||
Plain(String),
|
||||
/// Lleva asociada la URL (o ruta) de la imagen.
|
||||
Plain(CowStr),
|
||||
}
|
||||
|
||||
impl Source {
|
||||
const IMG_FLUID: &str = "img-fluid";
|
||||
const IMG_THUMBNAIL: &str = "img-thumbnail";
|
||||
|
||||
/// Imagen con el logotipo de PageTop.
|
||||
#[inline]
|
||||
pub fn logo(svg: PageTopSvg) -> Self {
|
||||
Self::Logo(svg)
|
||||
}
|
||||
|
||||
/// Imagen responsive (`img-fluid`).
|
||||
#[inline]
|
||||
pub fn responsive(url: impl Into<CowStr>) -> Self {
|
||||
Self::Responsive(url.into())
|
||||
}
|
||||
|
||||
/// Imagen miniatura (`img-thumbnail`).
|
||||
#[inline]
|
||||
pub fn thumbnail(url: impl Into<CowStr>) -> Self {
|
||||
Self::Thumbnail(url.into())
|
||||
}
|
||||
|
||||
/// Imagen sin clases adicionales.
|
||||
#[inline]
|
||||
pub fn plain(url: impl Into<CowStr>) -> Self {
|
||||
Self::Plain(url.into())
|
||||
}
|
||||
|
||||
/// Devuelve la clase base asociada a la imagen según la fuente.
|
||||
#[inline]
|
||||
fn as_str(&self) -> &'static str {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@ use crate::html::{html, Markup, RoutePath};
|
|||
use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
|
||||
use crate::locale::{LangId, LanguageIdentifier, RequestLocale};
|
||||
use crate::service::HttpRequest;
|
||||
use crate::{builder_fn, util};
|
||||
use crate::{builder_fn, util, CowStr};
|
||||
|
||||
use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Operaciones para modificar recursos asociados al [`Context`] de un documento.
|
||||
|
|
@ -376,7 +375,7 @@ impl Context {
|
|||
///
|
||||
/// Esto garantiza que los enlaces generados desde el contexto preservan la preferencia de
|
||||
/// idioma del usuario cuando procede.
|
||||
pub fn route(&self, path: impl Into<Cow<'static, str>>) -> RoutePath {
|
||||
pub fn route(&self, path: impl Into<CowStr>) -> RoutePath {
|
||||
let mut route = RoutePath::new(path);
|
||||
if self.locale.needs_lang_query() {
|
||||
route.alter_param("lang", self.locale.langid().to_string());
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ pub trait Theme: Extension + Send + Sync {
|
|||
&DefaultRegion::Content,
|
||||
ChildOp::Prepend(Child::with(
|
||||
Intro::new()
|
||||
.with_title(L10n::l("error_code").with_arg("code", code.as_str()))
|
||||
.with_title(L10n::l("error_code").with_arg("code", code.to_string()))
|
||||
.with_slogan(L10n::n(code.to_string()))
|
||||
.with_button(None)
|
||||
.with_opening(IntroOpening::Custom)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::core::component::Context;
|
||||
use crate::html::{html, Markup};
|
||||
use crate::AutoDefault;
|
||||
use crate::{AutoDefault, CowStr};
|
||||
|
||||
/// Un **Favicon** es un recurso gráfico que usa el navegador como icono asociado al sitio.
|
||||
///
|
||||
|
|
@ -41,7 +41,32 @@ use crate::AutoDefault;
|
|||
/// .with_ms_tile_image("/icons/mstile-144x144.png");
|
||||
/// ```
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Favicon(Vec<Markup>);
|
||||
pub struct Favicon(Vec<Item>);
|
||||
|
||||
/// Elementos que componen un favicon.
|
||||
#[derive(Clone, Debug)]
|
||||
enum Item {
|
||||
/// Etiqueta `<link>` para iconos.
|
||||
///
|
||||
/// - `rel`: `"icon"`, `"apple-touch-icon"`, `"mask-icon"`, etc.
|
||||
/// - `href`: URL/ruta del recurso.
|
||||
/// - `sizes`: tamaños opcionales (p. ej. `"32x32"` o `"16x16 32x32"`).
|
||||
/// - `color`: color opcional (relevante para `mask-icon`).
|
||||
/// - `mime`: tipo MIME inferido por la extensión del recurso.
|
||||
Icon {
|
||||
rel: &'static str,
|
||||
href: CowStr,
|
||||
sizes: Option<CowStr>,
|
||||
color: Option<CowStr>,
|
||||
mime: Option<&'static str>,
|
||||
},
|
||||
|
||||
/// Etiqueta `<meta>` para configuraciones del navegador/sistema.
|
||||
///
|
||||
/// - `name`: `"theme-color"`, `"msapplication-TileColor"`, `"msapplication-TileImage"`, etc.
|
||||
/// - `content`: valor asociado.
|
||||
Meta { name: &'static str, content: CowStr },
|
||||
}
|
||||
|
||||
impl Favicon {
|
||||
/// Crea un nuevo `Favicon` vacío.
|
||||
|
|
@ -56,7 +81,7 @@ impl Favicon {
|
|||
|
||||
/// Le añade un icono genérico apuntando a `image`. El tipo MIME se infiere automáticamente a
|
||||
/// partir de la extensión.
|
||||
pub fn with_icon(self, image: impl Into<String>) -> Self {
|
||||
pub fn with_icon(self, image: impl Into<CowStr>) -> Self {
|
||||
self.add_icon_item("icon", image.into(), None, None)
|
||||
}
|
||||
|
||||
|
|
@ -67,14 +92,14 @@ impl Favicon {
|
|||
/// `"16x16 32x32 48x48"` o usar `any` para iconos escalables (SVG).
|
||||
///
|
||||
/// No es imprescindible, pero puede mejorar la selección del icono más adecuado.
|
||||
pub fn with_icon_for_sizes(self, image: impl Into<String>, sizes: impl Into<String>) -> Self {
|
||||
pub fn with_icon_for_sizes(self, image: impl Into<CowStr>, sizes: impl Into<CowStr>) -> Self {
|
||||
self.add_icon_item("icon", image.into(), Some(sizes.into()), None)
|
||||
}
|
||||
|
||||
/// Le añade un *Apple Touch Icon*, usado por dispositivos iOS para las pantallas de inicio.
|
||||
///
|
||||
/// Se recomienda indicar también el tamaño, p. ej. `"256x256"`.
|
||||
pub fn with_apple_touch_icon(self, image: impl Into<String>, sizes: impl Into<String>) -> Self {
|
||||
/// Se recomienda indicar también el tamaño, p. ej. `"180x180"`.
|
||||
pub fn with_apple_touch_icon(self, image: impl Into<CowStr>, sizes: impl Into<CowStr>) -> Self {
|
||||
self.add_icon_item("apple-touch-icon", image.into(), Some(sizes.into()), None)
|
||||
}
|
||||
|
||||
|
|
@ -83,71 +108,85 @@ impl Favicon {
|
|||
/// El atributo `color` lo usa Safari para colorear el trazado SVG cuando el icono se muestra en
|
||||
/// modo *Pinned Tab*. Aunque Safari 12+ acepta *favicons normales*, este método garantiza
|
||||
/// compatibilidad con versiones anteriores.
|
||||
pub fn with_mask_icon(self, image: impl Into<String>, color: impl Into<String>) -> Self {
|
||||
pub fn with_mask_icon(self, image: impl Into<CowStr>, color: impl Into<CowStr>) -> Self {
|
||||
self.add_icon_item("mask-icon", image.into(), None, Some(color.into()))
|
||||
}
|
||||
|
||||
/// Define el color del tema (`<meta name="theme-color">`).
|
||||
///
|
||||
/// Lo usan algunos navegadores para colorear la barra de direcciones o interfaces.
|
||||
pub fn with_theme_color(mut self, color: impl Into<String>) -> Self {
|
||||
self.0.push(html! {
|
||||
meta name="theme-color" content=(color.into());
|
||||
pub fn with_theme_color(mut self, color: impl Into<CowStr>) -> Self {
|
||||
self.0.push(Item::Meta {
|
||||
name: "theme-color",
|
||||
content: color.into(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Define el color del *tile* en Windows (`<meta name="msapplication-TileColor">`).
|
||||
pub fn with_ms_tile_color(mut self, color: impl Into<String>) -> Self {
|
||||
self.0.push(html! {
|
||||
meta name="msapplication-TileColor" content=(color.into());
|
||||
pub fn with_ms_tile_color(mut self, color: impl Into<CowStr>) -> Self {
|
||||
self.0.push(Item::Meta {
|
||||
name: "msapplication-TileColor",
|
||||
content: color.into(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Define la imagen del *tile* en Windows (`<meta name="msapplication-TileImage">`).
|
||||
pub fn with_ms_tile_image(mut self, image: impl Into<String>) -> Self {
|
||||
self.0.push(html! {
|
||||
meta name="msapplication-TileImage" content=(image.into());
|
||||
pub fn with_ms_tile_image(mut self, image: impl Into<CowStr>) -> Self {
|
||||
self.0.push(Item::Meta {
|
||||
name: "msapplication-TileImage",
|
||||
content: image.into(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Función interna que centraliza la creación de las etiquetas `<link>`.
|
||||
// **< Favicon HELPERS >************************************************************************
|
||||
|
||||
/// Infiere el tipo MIME (`type="..."`) a partir de la extensión del recurso.
|
||||
#[inline]
|
||||
fn infer_mime(href: &str) -> Option<&'static str> {
|
||||
// Ignora query/fragment sin asignaciones (p. ej. ".png?v=1" o ".svg#v2").
|
||||
let href = href.split_once('#').map(|(s, _)| s).unwrap_or(href);
|
||||
let href = href.split_once('?').map(|(s, _)| s).unwrap_or(href);
|
||||
|
||||
let (_, ext) = href.rsplit_once('.')?;
|
||||
|
||||
match ext.len() {
|
||||
3 if ext.eq_ignore_ascii_case("gif") => Some("image/gif"),
|
||||
3 if ext.eq_ignore_ascii_case("ico") => Some("image/x-icon"),
|
||||
3 if ext.eq_ignore_ascii_case("jpg") => Some("image/jpeg"),
|
||||
3 if ext.eq_ignore_ascii_case("png") => Some("image/png"),
|
||||
3 if ext.eq_ignore_ascii_case("svg") => Some("image/svg+xml"),
|
||||
4 if ext.eq_ignore_ascii_case("avif") => Some("image/avif"),
|
||||
4 if ext.eq_ignore_ascii_case("jpeg") => Some("image/jpeg"),
|
||||
4 if ext.eq_ignore_ascii_case("webp") => Some("image/webp"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Centraliza la creación de los elementos `<link>`.
|
||||
///
|
||||
/// - `icon_rel`: indica el tipo de recurso (`"icon"`, `"apple-touch-icon"`, etc.).
|
||||
/// - `icon_source`: URL del recurso.
|
||||
/// - `icon_sizes`: tamaños opcionales.
|
||||
/// - `icon_color`: color opcional (solo relevante para `mask-icon`).
|
||||
/// - `href`: URL del recurso.
|
||||
/// - `sizes`: tamaños opcionales.
|
||||
/// - `color`: color opcional (solo relevante para `mask-icon`).
|
||||
///
|
||||
/// También infiere automáticamente el tipo MIME (`type`) según la extensión del archivo.
|
||||
fn add_icon_item(
|
||||
mut self,
|
||||
icon_rel: &str,
|
||||
icon_source: String,
|
||||
icon_sizes: Option<String>,
|
||||
icon_color: Option<String>,
|
||||
icon_rel: &'static str,
|
||||
icon_source: CowStr,
|
||||
icon_sizes: Option<CowStr>,
|
||||
icon_color: Option<CowStr>,
|
||||
) -> Self {
|
||||
let icon_type = match icon_source.rfind('.') {
|
||||
Some(i) => match icon_source[i..].to_string().to_lowercase().as_str() {
|
||||
".avif" => Some("image/avif"),
|
||||
".gif" => Some("image/gif"),
|
||||
".ico" => Some("image/x-icon"),
|
||||
".jpg" | ".jpeg" => Some("image/jpeg"),
|
||||
".png" => Some("image/png"),
|
||||
".svg" => Some("image/svg+xml"),
|
||||
".webp" => Some("image/webp"),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
self.0.push(html! {
|
||||
link
|
||||
rel=(icon_rel)
|
||||
type=[(icon_type)]
|
||||
sizes=[(icon_sizes)]
|
||||
color=[(icon_color)]
|
||||
href=(icon_source);
|
||||
let mime = Self::infer_mime(icon_source.as_ref());
|
||||
self.0.push(Item::Icon {
|
||||
rel: icon_rel,
|
||||
href: icon_source,
|
||||
sizes: icon_sizes,
|
||||
color: icon_color,
|
||||
mime,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
|
@ -161,7 +200,19 @@ impl Favicon {
|
|||
pub fn render(&self, _cx: &mut Context) -> Markup {
|
||||
html! {
|
||||
@for item in &self.0 {
|
||||
(item)
|
||||
@match item {
|
||||
Item::Icon { rel, href, sizes, color, mime } => {
|
||||
link
|
||||
rel=(rel)
|
||||
type=[*mime]
|
||||
sizes=[sizes.as_deref()]
|
||||
color=[color.as_deref()]
|
||||
href=(href.as_ref());
|
||||
}
|
||||
Item::Meta { name, content } => {
|
||||
meta name=(name) content=(content.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
use crate::core::component::Context;
|
||||
use crate::html::assets::Asset;
|
||||
use crate::html::{html, Markup, PreEscaped};
|
||||
use crate::{util, AutoDefault, Weight};
|
||||
use crate::{util, AutoDefault, CowStr, Weight};
|
||||
|
||||
// Define el origen del recurso JavaScript y cómo debe cargarse en el navegador.
|
||||
//
|
||||
// Los distintos modos de carga permiten optimizar el rendimiento y controlar el comportamiento del
|
||||
// script en relación con el análisis del documento HTML y la ejecución del resto de scripts.
|
||||
//
|
||||
// - [`From`] - Carga estándar con la etiqueta `<script src="...">`.
|
||||
// - [`Defer`] - Igual que [`From`], pero con el atributo `defer`, descarga en paralelo y se
|
||||
// ejecuta tras el análisis del documento HTML, respetando el orden de
|
||||
// aparición.
|
||||
// - [`Async`] - Igual que [`From`], pero con el atributo `async`, descarga en paralelo y se
|
||||
// ejecuta en cuanto esté listo, **sin garantizar** el orden relativo respecto a
|
||||
// otros scripts.
|
||||
// - [`Inline`] - Inserta el código directamente en la etiqueta `<script>`.
|
||||
// - [`OnLoad`] - Inserta el código JavaScript y lo ejecuta tras el evento `DOMContentLoaded`.
|
||||
// - [`OnLoadAsync`] - Igual que [`OnLoad`], pero con manejador asíncrono (`async`), útil si dentro
|
||||
// del código JavaScript se utiliza `await`.
|
||||
/// Define el origen del recurso JavaScript y cómo debe cargarse en el navegador.
|
||||
///
|
||||
/// Los distintos modos de carga permiten optimizar el rendimiento y controlar el comportamiento del
|
||||
/// script en relación con el análisis del documento HTML y la ejecución del resto de scripts.
|
||||
///
|
||||
/// - [`From`] - Carga estándar con la etiqueta `<script src="...">`.
|
||||
/// - [`Defer`] - Igual que [`From`], pero con el atributo `defer`, descarga en paralelo y se
|
||||
/// ejecuta tras el análisis del documento HTML, respetando el orden de
|
||||
/// aparición.
|
||||
/// - [`Async`] - Igual que [`From`], pero con el atributo `async`, descarga en paralelo y se
|
||||
/// ejecuta en cuanto esté listo, **sin garantizar** el orden relativo respecto
|
||||
/// a otros scripts.
|
||||
/// - [`Inline`] - Inserta el código directamente en la etiqueta `<script>`.
|
||||
/// - [`OnLoad`] - Inserta el código JavaScript y lo ejecuta tras el evento `DOMContentLoaded`.
|
||||
/// - [`OnLoadAsync`] - Igual que [`OnLoad`], pero con manejador asíncrono (`async`), útil si dentro
|
||||
/// del código JavaScript se utiliza `await`.
|
||||
#[derive(AutoDefault)]
|
||||
enum Source {
|
||||
#[default]
|
||||
From(String),
|
||||
Defer(String),
|
||||
Async(String),
|
||||
// `name`, `closure(Context) -> String`.
|
||||
Inline(String, Box<dyn Fn(&mut Context) -> String + Send + Sync>),
|
||||
// `name`, `closure(Context) -> String` (se ejecuta tras `DOMContentLoaded`).
|
||||
OnLoad(String, Box<dyn Fn(&mut Context) -> String + Send + Sync>),
|
||||
// `name`, `closure(Context) -> String` (manejador `async` tras `DOMContentLoaded`).
|
||||
OnLoadAsync(String, Box<dyn Fn(&mut Context) -> String + Send + Sync>),
|
||||
From(CowStr),
|
||||
Defer(CowStr),
|
||||
Async(CowStr),
|
||||
/// `name`, `closure(&mut Context) -> String`.
|
||||
Inline(CowStr, Box<dyn Fn(&mut Context) -> String + Send + Sync>),
|
||||
/// `name`, `closure(&mut Context) -> String` (se ejecuta tras `DOMContentLoaded`).
|
||||
OnLoad(CowStr, Box<dyn Fn(&mut Context) -> String + Send + Sync>),
|
||||
/// `name`, `closure(&mut Context) -> String` (manejador `async` tras `DOMContentLoaded`).
|
||||
OnLoadAsync(CowStr, Box<dyn Fn(&mut Context) -> String + Send + Sync>),
|
||||
}
|
||||
|
||||
/// Define un recurso **JavaScript** para incluir en un documento HTML.
|
||||
|
|
@ -74,12 +74,11 @@ enum Source {
|
|||
/// "#, uid)
|
||||
/// });
|
||||
/// ```
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct JavaScript {
|
||||
source : Source, // Fuente y estrategia de carga del script.
|
||||
version: String, // Versión del recurso para la caché del navegador.
|
||||
weight : Weight, // Peso que determina el orden.
|
||||
source: Source, // Fuente y estrategia de carga del script.
|
||||
version: CowStr, // Versión del recurso para la caché del navegador.
|
||||
weight: Weight, // Peso que determina el orden.
|
||||
}
|
||||
|
||||
impl JavaScript {
|
||||
|
|
@ -87,7 +86,7 @@ impl JavaScript {
|
|||
/// del documento HTML.
|
||||
///
|
||||
/// Equivale a `<script src="...">`.
|
||||
pub fn from(path: impl Into<String>) -> Self {
|
||||
pub fn from(path: impl Into<CowStr>) -> Self {
|
||||
Self {
|
||||
source: Source::From(path.into()),
|
||||
..Default::default()
|
||||
|
|
@ -99,7 +98,7 @@ impl JavaScript {
|
|||
///
|
||||
/// Equivale a `<script src="..." defer>`. Suele ser la opción recomendada para scripts no
|
||||
/// críticos.
|
||||
pub fn defer(path: impl Into<String>) -> Self {
|
||||
pub fn defer(path: impl Into<CowStr>) -> Self {
|
||||
Self {
|
||||
source: Source::Defer(path.into()),
|
||||
..Default::default()
|
||||
|
|
@ -110,7 +109,7 @@ impl JavaScript {
|
|||
/// tan pronto como esté disponible.
|
||||
///
|
||||
/// Equivale a `<script src="..." async>`. **No garantiza** el orden relativo con otros scripts.
|
||||
pub fn asynchronous(path: impl Into<String>) -> Self {
|
||||
pub fn asynchronous(path: impl Into<CowStr>) -> Self {
|
||||
Self {
|
||||
source: Source::Async(path.into()),
|
||||
..Default::default()
|
||||
|
|
@ -123,7 +122,7 @@ impl JavaScript {
|
|||
/// script.
|
||||
///
|
||||
/// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado.
|
||||
pub fn inline<F>(name: impl Into<String>, f: F) -> Self
|
||||
pub fn inline<F>(name: impl Into<CowStr>, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut Context) -> String + Send + Sync + 'static,
|
||||
{
|
||||
|
|
@ -140,10 +139,10 @@ impl JavaScript {
|
|||
/// Útil para inicializaciones que no dependen de `await`. El parámetro `name` se usa como
|
||||
/// identificador interno del script.
|
||||
///
|
||||
/// Los scripts con `defer` se ejecutan antes de `DOMContentLoaded`.
|
||||
/// En condiciones normales, los scripts con `defer` se ejecutan antes de `DOMContentLoaded`.
|
||||
///
|
||||
/// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado.
|
||||
pub fn on_load<F>(name: impl Into<String>, f: F) -> Self
|
||||
pub fn on_load<F>(name: impl Into<CowStr>, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut Context) -> String + Send + Sync + 'static,
|
||||
{
|
||||
|
|
@ -161,7 +160,7 @@ impl JavaScript {
|
|||
/// iniciales.
|
||||
///
|
||||
/// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado.
|
||||
pub fn on_load_async<F>(name: impl Into<String>, f: F) -> Self
|
||||
pub fn on_load_async<F>(name: impl Into<CowStr>, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut Context) -> String + Send + Sync + 'static,
|
||||
{
|
||||
|
|
@ -176,7 +175,7 @@ impl JavaScript {
|
|||
/// Asocia una **versión** al recurso (usada para control de la caché del navegador).
|
||||
///
|
||||
/// Si `version` está vacío, **no** se añade ningún parámetro a la URL.
|
||||
pub fn with_version(mut self, version: impl Into<String>) -> Self {
|
||||
pub fn with_version(mut self, version: impl Into<CowStr>) -> Self {
|
||||
self.version = version.into();
|
||||
self
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
use crate::core::component::Context;
|
||||
use crate::html::assets::Asset;
|
||||
use crate::html::{html, Markup, PreEscaped};
|
||||
use crate::{util, AutoDefault, Weight};
|
||||
use crate::{util, AutoDefault, CowStr, Weight};
|
||||
|
||||
// Define el origen del recurso CSS y cómo se incluye en el documento.
|
||||
//
|
||||
// Los estilos pueden cargarse desde un archivo externo o estar embebidos directamente en una
|
||||
// etiqueta `<style>`.
|
||||
//
|
||||
// - [`From`] - Carga la hoja de estilos desde un archivo externo, insertándola mediante una
|
||||
// etiqueta `<link>` con `rel="stylesheet"`.
|
||||
// - [`Inline`] - Inserta directamente el contenido CSS dentro de una etiqueta `<style>`.
|
||||
/// Define el origen del recurso CSS y cómo se incluye en el documento.
|
||||
///
|
||||
/// Los estilos pueden cargarse desde un archivo externo o estar embebidos directamente en una
|
||||
/// etiqueta `<style>`.
|
||||
///
|
||||
/// - [`From`] - Carga la hoja de estilos desde un archivo externo, insertándola mediante una
|
||||
/// etiqueta `<link>` con `rel="stylesheet"`.
|
||||
/// - [`Inline`] - Inserta directamente el contenido CSS dentro de una etiqueta `<style>`.
|
||||
#[derive(AutoDefault)]
|
||||
enum Source {
|
||||
#[default]
|
||||
From(String),
|
||||
// `name`, `closure(Context) -> String`.
|
||||
Inline(String, Box<dyn Fn(&mut Context) -> String + Send + Sync>),
|
||||
From(CowStr),
|
||||
/// `name`, `closure(&mut Context) -> String`.
|
||||
Inline(CowStr, Box<dyn Fn(&mut Context) -> String + Send + Sync>),
|
||||
}
|
||||
|
||||
/// Define el medio objetivo para la hoja de estilos.
|
||||
|
|
@ -37,14 +37,13 @@ pub enum TargetMedia {
|
|||
}
|
||||
|
||||
/// Devuelve el valor para el atributo `media` (`Some(...)`) o `None` para `Default`.
|
||||
#[rustfmt::skip]
|
||||
impl TargetMedia {
|
||||
const fn as_str(self) -> Option<&'static str> {
|
||||
match self {
|
||||
TargetMedia::Default => None,
|
||||
TargetMedia::Print => Some("print"),
|
||||
TargetMedia::Screen => Some("screen"),
|
||||
TargetMedia::Speech => Some("speech"),
|
||||
TargetMedia::Print => Some("print"),
|
||||
TargetMedia::Screen => Some("screen"),
|
||||
TargetMedia::Speech => Some("speech"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -77,20 +76,19 @@ impl TargetMedia {
|
|||
/// }
|
||||
/// "#.to_string());
|
||||
/// ```
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct StyleSheet {
|
||||
source : Source, // Fuente y modo de inclusión del CSS.
|
||||
version: String, // Versión del recurso para la caché del navegador.
|
||||
media : TargetMedia, // Medio objetivo para los estilos (`print`, `screen`, ...).
|
||||
weight : Weight, // Peso que determina el orden.
|
||||
source: Source, // Fuente y modo de inclusión del CSS.
|
||||
version: CowStr, // Versión del recurso para la caché del navegador.
|
||||
media: TargetMedia, // Medio objetivo para los estilos (`print`, `screen`, ...).
|
||||
weight: Weight, // Peso que determina el orden.
|
||||
}
|
||||
|
||||
impl StyleSheet {
|
||||
/// Crea una hoja de estilos externa.
|
||||
///
|
||||
/// Equivale a `<link rel="stylesheet" href="...">`.
|
||||
pub fn from(path: impl Into<String>) -> Self {
|
||||
pub fn from(path: impl Into<CowStr>) -> Self {
|
||||
Self {
|
||||
source: Source::From(path.into()),
|
||||
..Default::default()
|
||||
|
|
@ -103,7 +101,7 @@ impl StyleSheet {
|
|||
/// recurso.
|
||||
///
|
||||
/// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado.
|
||||
pub fn inline<F>(name: impl Into<String>, f: F) -> Self
|
||||
pub fn inline<F>(name: impl Into<CowStr>, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut Context) -> String + Send + Sync + 'static,
|
||||
{
|
||||
|
|
@ -118,7 +116,7 @@ impl StyleSheet {
|
|||
/// Asocia una versión al recurso (usada para control de la caché del navegador).
|
||||
///
|
||||
/// Si `version` está vacío, no se añade ningún parámetro a la URL.
|
||||
pub fn with_version(mut self, version: impl Into<String>) -> Self {
|
||||
pub fn with_version(mut self, version: impl Into<CowStr>) -> Self {
|
||||
self.version = version.into();
|
||||
self
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{builder_fn, util, AutoDefault};
|
||||
use crate::{builder_fn, util, AutoDefault, CowStr};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// Operaciones disponibles sobre la lista de clases en [`Classes`].
|
||||
|
|
@ -19,7 +18,7 @@ pub enum ClassesOp {
|
|||
Remove,
|
||||
/// Sustituye una o varias clases existentes (indicadas en la variante) por las clases
|
||||
/// proporcionadas.
|
||||
Replace(Cow<'static, str>),
|
||||
Replace(CowStr),
|
||||
/// Alterna presencia/ausencia de una o más clases.
|
||||
///
|
||||
/// Si en una misma llamada se repite una clase (p. ej. `"a a"`) que ya existe, el resultado
|
||||
|
|
@ -52,10 +51,11 @@ pub enum ClassesOp {
|
|||
/// # use pagetop::prelude::*;
|
||||
/// let classes = Classes::new("Btn btn-primary")
|
||||
/// .with_classes(ClassesOp::Add, "Active")
|
||||
/// .with_classes(ClassesOp::Replace("active".into()), "Disabled")
|
||||
/// .with_classes(ClassesOp::Remove, "btn-primary");
|
||||
///
|
||||
/// assert_eq!(classes.get(), Some("btn active".to_string()));
|
||||
/// assert!(classes.contains("active"));
|
||||
/// assert_eq!(classes.get(), Some("btn disabled".to_string()));
|
||||
/// assert!(classes.contains("disabled"));
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug)]
|
||||
pub struct Classes(Vec<String>);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{builder_fn, AutoDefault};
|
||||
use crate::{builder_fn, AutoDefault, CowStr};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
/// Representa una ruta como un *path* inicial más una lista opcional de parámetros.
|
||||
|
|
@ -28,19 +27,16 @@ use std::fmt;
|
|||
/// ```
|
||||
#[derive(AutoDefault)]
|
||||
pub struct RoutePath {
|
||||
// *Path* inicial sobre el que se añadirán los parámetros.
|
||||
//
|
||||
// Puede ser relativo (p. ej. `/about`) o una ruta completa (`https://example.com/about`).
|
||||
// `RoutePath` no realiza ninguna validación ni normalización.
|
||||
//
|
||||
// Se almacena como `Cow<'static, str>` para reutilizar literales estáticos sin asignación
|
||||
// adicional y, al mismo tiempo, aceptar rutas dinámicas representadas como `String`.
|
||||
path: Cow<'static, str>,
|
||||
/// *Path* inicial sobre el que se añadirán los parámetros.
|
||||
///
|
||||
/// Puede ser relativo (p. ej. `/about`) o una ruta completa (`https://example.com/about`).
|
||||
/// `RoutePath` no realiza ninguna validación ni normalización.
|
||||
path: CowStr,
|
||||
|
||||
// Conjunto de parámetros asociados a la ruta.
|
||||
//
|
||||
// Cada clave es única y se mantiene el orden de inserción. El valor vacío se utiliza para
|
||||
// representar *flags* sin valor explícito (por ejemplo `?debug`).
|
||||
/// Conjunto de parámetros asociados a la ruta.
|
||||
///
|
||||
/// Cada clave es única y se mantiene el orden de inserción. El valor vacío se utiliza para
|
||||
/// representar *flags* sin valor explícito (por ejemplo `?debug`).
|
||||
query: indexmap::IndexMap<String, String>,
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +44,7 @@ impl RoutePath {
|
|||
/// Crea un `RoutePath` a partir de un *path* inicial.
|
||||
///
|
||||
/// Por ejemplo: `RoutePath::new("/about")`.
|
||||
pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn new(path: impl Into<CowStr>) -> Self {
|
||||
Self {
|
||||
path: path.into(),
|
||||
query: indexmap::IndexMap::new(),
|
||||
|
|
|
|||
|
|
@ -161,6 +161,13 @@ impl Deref for StaticResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// Alias para `Cow<'static, str>`.
|
||||
///
|
||||
/// Es un puntero inteligente con semántica *copy-on-write* para cadenas. Permite reutilizar
|
||||
/// literales estáticos sin asignación de memoria adicional y, al mismo tiempo, aceptar cadenas
|
||||
/// dinámicas representadas como `String`.
|
||||
pub type CowStr = std::borrow::Cow<'static, str>;
|
||||
|
||||
/// Identificador único de un tipo estático durante la ejecución de la aplicación.
|
||||
///
|
||||
/// **Nota:** El valor es único sólo dentro del proceso actual y cambia en cada compilación.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use crate::html::{Markup, PreEscaped};
|
||||
use crate::{include_locales, AutoDefault};
|
||||
use crate::{include_locales, AutoDefault, CowStr};
|
||||
|
||||
use super::{LangId, Locale};
|
||||
|
||||
use fluent_templates::Loader;
|
||||
use fluent_templates::StaticLoader as Locales;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::fmt;
|
||||
|
|
@ -22,8 +21,8 @@ include_locales!(LOCALES_PAGETOP);
|
|||
enum L10nOp {
|
||||
#[default]
|
||||
None,
|
||||
Text(Cow<'static, str>),
|
||||
Translate(Cow<'static, str>),
|
||||
Text(CowStr),
|
||||
Translate(CowStr),
|
||||
}
|
||||
|
||||
/// Crea instancias para traducir *textos localizados*.
|
||||
|
|
@ -60,12 +59,12 @@ pub struct L10n {
|
|||
op: L10nOp,
|
||||
#[default(&LOCALES_PAGETOP)]
|
||||
locales: &'static Locales,
|
||||
args: HashMap<String, String>,
|
||||
args: Vec<(CowStr, CowStr)>,
|
||||
}
|
||||
|
||||
impl L10n {
|
||||
/// **n** = *“native”*. Crea una instancia con una cadena literal sin traducción.
|
||||
pub fn n(text: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn n(text: impl Into<CowStr>) -> Self {
|
||||
Self {
|
||||
op: L10nOp::Text(text.into()),
|
||||
..Default::default()
|
||||
|
|
@ -74,7 +73,7 @@ impl L10n {
|
|||
|
||||
/// **l** = *“lookup”*. Crea una instancia para traducir usando una clave del conjunto de
|
||||
/// traducciones predefinidas.
|
||||
pub fn l(key: impl Into<Cow<'static, str>>) -> Self {
|
||||
pub fn l(key: impl Into<CowStr>) -> Self {
|
||||
Self {
|
||||
op: L10nOp::Translate(key.into()),
|
||||
..Default::default()
|
||||
|
|
@ -83,7 +82,7 @@ impl L10n {
|
|||
|
||||
/// **t** = *“translate”*. Crea una instancia para traducir usando una clave de un conjunto de
|
||||
/// traducciones específico.
|
||||
pub fn t(key: impl Into<Cow<'static, str>>, locales: &'static Locales) -> Self {
|
||||
pub fn t(key: impl Into<CowStr>, locales: &'static Locales) -> Self {
|
||||
Self {
|
||||
op: L10nOp::Translate(key.into()),
|
||||
locales,
|
||||
|
|
@ -92,8 +91,8 @@ impl L10n {
|
|||
}
|
||||
|
||||
/// Añade un argumento `{$arg}` => `value` a la traducción.
|
||||
pub fn with_arg(mut self, arg: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
self.args.insert(arg.into(), value.into());
|
||||
pub fn with_arg(mut self, arg: impl Into<CowStr>, value: impl Into<CowStr>) -> Self {
|
||||
self.args.push((arg.into(), value.into()));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -103,8 +102,8 @@ impl L10n {
|
|||
pub fn with_args<I, K, V>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Into<String>,
|
||||
V: Into<String>,
|
||||
K: Into<CowStr>,
|
||||
V: Into<CowStr>,
|
||||
{
|
||||
self.args
|
||||
.extend(args.into_iter().map(|(k, v)| (k.into(), v.into())));
|
||||
|
|
@ -153,15 +152,12 @@ impl L10n {
|
|||
if self.args.is_empty() {
|
||||
self.locales.try_lookup(language.langid(), key.as_ref())
|
||||
} else {
|
||||
self.locales.try_lookup_with_args(
|
||||
language.langid(),
|
||||
key.as_ref(),
|
||||
&self
|
||||
.args
|
||||
.iter()
|
||||
.map(|(k, v)| (Cow::Owned(k.clone()), v.clone().into()))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
)
|
||||
let mut args = HashMap::with_capacity(self.args.len());
|
||||
for (k, v) in self.args.iter() {
|
||||
args.insert(k.clone(), v.as_ref().into());
|
||||
}
|
||||
self.locales
|
||||
.try_lookup_with_args(language.langid(), key.as_ref(), &args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ pub use crate::PAGETOP_VERSION;
|
|||
|
||||
pub use crate::{builder_fn, html, main, test};
|
||||
|
||||
pub use crate::{AutoDefault, Getters, StaticResources, UniqueId, Weight};
|
||||
pub use crate::{AutoDefault, CowStr, Getters, StaticResources, UniqueId, Weight};
|
||||
|
||||
// MACROS.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue