🚧 Working on 0.1.0 version

This commit is contained in:
Manuel Cillero 2024-11-30 11:35:48 +01:00
parent 9f62955acb
commit 41cedf2541
28 changed files with 3473 additions and 535 deletions

2
.gitignore vendored
View file

@ -2,5 +2,3 @@
**/log/*.log* **/log/*.log*
**/local.*.toml **/local.*.toml
**/local.toml **/local.toml
Cargo.lock
workdir

View file

@ -1,41 +1,30 @@
# 🔃 Dependencies # 🔃 Dependencies
PageTop is developed in the [Rust programming language](https://www.rust-lang.org/) and stands on PageTop is developed in the [Rust programming language](https://www.rust-lang.org/) and stands on
the shoulders of true giants, using some of the most stable and renowned libraries (*crates*) from the shoulders of giants by leveraging some of the most robust and renowned libraries (*crates*) from
the [Rust ecosystem](https://lib.rs), such as: the [Rust ecosystem](https://lib.rs), including:
* [Actix Web](https://actix.rs/) for web services and server management. * [Actix Web](https://actix.rs/) for web services and server management.
* [Tracing](https://github.com/tokio-rs/tracing) for the diagnostic system and structured logging. * [Tracing](https://github.com/tokio-rs/tracing) for diagnostics and structured logging.
* [Fluent templates](https://github.com/XAMPPRocky/fluent-templates) that incorporate * [Fluent templates](https://github.com/XAMPPRocky/fluent-templates), which integrate
[Fluent](https://projectfluent.org/) for project internationalization. [Fluent](https://projectfluent.org/) for internationalization.
* Among others, which you can review in the PageTop * Additional crates, which you can explore in the `Cargo.toml` files of PageTop and its packages.
[`Cargo.toml`](https://github.com/manuelcillero/pagetop/blob/main/Cargo.toml) file.
# ⌨️ Code # ⌨️ Code
PageTop integrates code from various renowned crates to enhance functionality: PageTop includes code from [config-rs](https://crates.io/crates/config) (version
[0.11.0](https://github.com/mehcode/config-rs/tree/0.11.0)) by
* [**Config (v0.11.0)**](https://github.com/mehcode/config-rs/tree/0.11.0): Includes code from [Ryan Leckey](https://crates.io/users/mehcode), chosen for its advantages in reading configuration
[config-rs](https://crates.io/crates/config) by [Ryan Leckey](https://crates.io/users/mehcode), settings and delegating assignment to safe types, tailored to the specific needs of each package,
chosen for its advantages in reading configuration settings and delegating assignment to safe theme, or application.
types, tailored to the specific needs of each package, theme, or application.
* [**Maud (v0.25.0)**](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud): An adapted version
of the excellent [maud](https://crates.io/crates/maud) crate by
[Chris Wong](https://crates.io/users/lambda-fairy) is incorporated to leverage its functionalities without requiring a reference to `maud` in the `Cargo.toml` files.
* **SmartDefault (v0.7.1)**: Embedded [SmartDefault](https://crates.io/crates/smart_default) by
[Jane Doe](https://crates.io/users/jane-doe) as `AutoDefault`to simplify the documentation of
Default implementations and also removes the need to explicitly list `smart_default` in the
`Cargo.toml` files.
# 🗚 FIGfonts # 🗚 FIGfonts
PageTop uses the [figlet-rs](https://crates.io/crates/figlet-rs) package by *yuanbohan* to display a PageTop uses the [figlet-rs](https://crates.io/crates/figlet-rs) package by *yuanbohan* to display a
presentation banner in the terminal with the application's name using presentation banner in the terminal featuring the application's name in
[FIGlet](http://www.figlet.org) characters. The fonts included in `src/app` are: [FIGlet](http://www.figlet.org) characters. The fonts included in `pagetop/src/app` are:
* [slant.flf](http://www.figlet.org/fontdb_example.cgi?font=slant.flf) by *Glenn Chappell* * [slant.flf](http://www.figlet.org/fontdb_example.cgi?font=slant.flf) by *Glenn Chappell*
* [small.flf](http://www.figlet.org/fontdb_example.cgi?font=small.flf) by *Glenn Chappell* (default) * [small.flf](http://www.figlet.org/fontdb_example.cgi?font=small.flf) by *Glenn Chappell* (default)
@ -45,14 +34,13 @@ presentation banner in the terminal with the application's name using
# 📰 Templates # 📰 Templates
* The default welcome homepage design is based on the The default welcome homepage design is inspired by a tutorial for creating a unique
[Zinc](https://themewagon.com/themes/free-bootstrap-5-html5-business-website-template-zinc) [Neobrutalism](https://www.codewithfaraz.com/content/109/creating-a-unique-neobrutalism-portfolio-page-with-html-css-and-javascript)
template created by [inovatik](https://inovatik.com/) and distributed by portfolio page by [Faraz](https://www.codewithfaraz.com/).
[ThemeWagon](https://themewagon.com).
# 🎨 Icon # 🎨 Icon
"The creature" smiling is a fun creation by [Webalys](https://www.iconfinder.com/webalys). It can be "The Creature" smiling is a playful creation by [Webalys](https://www.iconfinder.com/webalys). It is
found in their [Nasty Icons](https://www.iconfinder.com/iconsets/nasty) collection available on part of their [Nasty Icons](https://www.iconfinder.com/iconsets/nasty) collection, available on
[ICONFINDER](https://www.iconfinder.com). [ICONFINDER](https://www.iconfinder.com).

2979
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -3,60 +3,55 @@ name = "pagetop"
version = "0.0.56" version = "0.0.56"
edition = "2021" edition = "2021"
description = "An opinionated web framework to build modular Server-Side Rendering web solutions." description = """\
homepage = "https://pagetop.cillero.es" An opinionated web framework to build modular Server-Side Rendering web solutions.\
repository = "https://github.com/manuelcillero/pagetop" """
license = "MIT OR Apache-2.0" categories = ["web-programming", "gui", "development-tools", "asynchronous"]
keywords = ["pagetop", "web", "framework", "frontend", "ssr"]
authors = [ repository = "https://github.com/manuelcillero/pagetop"
"Manuel Cillero <manuel@cillero.es>" homepage = "https://pagetop.cillero.es"
] license = "MIT OR Apache-2.0"
categories = [ authors = ["Manuel Cillero <manuel@cillero.es>"]
"web-programming", "gui", "development-tools", "asynchronous"
] exclude = ["examples/", "helpers/", "tests/"]
keywords = [
"pagetop", "web", "framework", "frontend", "ssr"
]
exclude = [
"examples/", "helpers/", "tests/"
]
rust-version = "1.80.0" rust-version = "1.80.0"
[workspace] [workspace]
resolver = "2"
members = ["helpers/*"] members = ["helpers/*"]
[lib] [lib]
name = "pagetop" name = "pagetop"
[dependencies] [dependencies]
chrono = "0.4.38" chrono = "0.4.38"
colored = "2.1.0"
concat-string = "1.0.1" concat-string = "1.0.1"
figlet-rs = "0.1.5" figlet-rs = "0.1.5"
itoa = "1.0.11" itoa = "1.0.14"
nom = "7.1.3" nom = "7.1.3"
paste = "1.0.15" paste = "1.0.15"
substring = "1.4.5" serde = { version = "1.0", features = ["derive"] }
terminal_size = "0.3.0" substring = "1.4.5"
toml = "0.8.19" terminal_size = "0.4.1"
toml = "0.8.19"
tracing = "0.1.40" tracing = "0.1.41"
tracing-appender = "0.2.3" tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] } tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] }
tracing-actix-web = "0.7.11" tracing-actix-web = "0.7.15"
fluent-templates = "0.9.4" fluent-templates = "0.11.0"
unic-langid = { version = "0.9.5", features = ["macros"] } unic-langid = { version = "0.9.5", features = ["macros"] }
actix-web = "4" actix-web = "4.9.0"
actix-session = { version = "0.10.0", features = ["cookie-session"] } actix-session = { version = "0.10.1", features = ["cookie-session"] }
actix-web-files = { package = "actix-files", version = "0.6.6" } actix-web-files = { package = "actix-files", version = "0.6.6" }
actix-web-static-files = "4.0.1" actix-web-static-files = "4.0.1"
static-files = "0.2.4" static-files = "0.2.4"
pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" } pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" }
serde = { version = "1.0", features = ["derive"] }
[build-dependencies] [build-dependencies]
pagetop-build = { version = "0.0", path = "helpers/pagetop-build" } pagetop-build = { version = "0.0", path = "helpers/pagetop-build" }

View file

@ -1,7 +1,7 @@
use pagetop_build::StaticFilesBundle; use pagetop_build::StaticFilesBundle;
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
StaticFilesBundle::from_dir("./static/base") StaticFilesBundle::from_dir("./static/assets", None)
.with_name("base") .with_name("assets")
.build() .build()
} }

View file

@ -3,20 +3,17 @@ name = "pagetop-build"
version = "0.0.11" version = "0.0.11"
edition = "2021" edition = "2021"
description = "Simplifies the process of embedding resources in PageTop app binaries." description = """\
homepage = "https://pagetop.cillero.es" Simplifies the process of embedding resources in PageTop app binaries.\
repository = "https://github.com/manuelcillero/pagetop" """
license = "MIT OR Apache-2.0" categories = ["development-tools::build-utils", "web-programming"]
keywords = ["pagetop", "build", "assets", "resources", "static"]
authors = [ repository = "https://github.com/manuelcillero/pagetop"
"Manuel Cillero <manuel@cillero.es>" homepage = "https://pagetop.cillero.es"
] license = "MIT OR Apache-2.0"
categories = [ authors = ["Manuel Cillero <manuel@cillero.es>"]
"development-tools::build-utils", "web-programming"
]
keywords = [
"pagetop", "build", "assets", "resources", "static"
]
[dependencies] [dependencies]
grass = "0.13.4"
static-files = "0.2.4" static-files = "0.2.4"

View file

@ -1,57 +1,74 @@
//! This function uses the [static_files](https://docs.rs/static-files/latest/static_files/) library //! Easily embed static or compiled SCSS files into your binary at compile time.
//! to embed at compile time a bundle of static files in your binary.
//! //!
//! Just create folder with static resources in your project (for example `static`): //! ## Adding to your project
//! //!
//! ```bash //! Add the following to your `Cargo.toml`:
//! cd project_dir
//! mkdir static
//! echo "Hello, world!" > static/hello
//! ```
//!
//! Add to `Cargo.toml` the required dependencies:
//! //!
//! ```toml //! ```toml
//! [build-dependencies] //! [build-dependencies]
//! pagetop-build = { ... } //! pagetop-build = { ... }
//! ``` //! ```
//! //!
//! Add `build.rs` with call to bundle resources (*guides* will be the magic word in this example): //! Next, create a `build.rs` file to configure how your static resources or SCSS files will be
//! bundled in your PageTop application, package, or theme.
//!
//! ## Usage examples
//!
//! ### 1. Embedding static files from a directory
//!
//! Include all files from a directory:
//! //!
//! ```rust#ignore //! ```rust#ignore
//! use pagetop_build::StaticFilesBundle; //! use pagetop_build::StaticFilesBundle;
//! //!
//! fn main() -> std::io::Result<()> { //! fn main() -> std::io::Result<()> {
//! StaticFilesBundle::from_dir("./static") //! StaticFilesBundle::from_dir("./static", None)
//! .with_name("guides") //! .with_name("guides")
//! .build() //! .build()
//! } //! }
//! ``` //! ```
//! //!
//! Optionally, you can pass a function to filter those files into the `./static` folder which //! Apply a filter to include only specific files:
//! should be excluded in the resources bundle:
//! //!
//! ```rust#ignore //! ```rust#ignore
//! use pagetop_build::StaticFilesBundle; //! use pagetop_build::StaticFilesBundle;
//! use std::path::Path;
//! //!
//! fn main() -> std::io::Result<()> { //! fn main() -> std::io::Result<()> {
//! StaticFilesBundle::from_dir("./static") //! fn only_css_files(path: &Path) -> bool {
//! .with_name("guides") //! // Include only files with `.css` extension.
//! .with_filter(except_css_dir) //! path.extension().map_or(false, |ext| ext == "css")
//! .build()
//! }
//!
//! fn except_css_dir(p: &Path) -> bool {
//! if let Some(parent) = p.parent() {
//! !matches!(parent.to_str(), Some("/css"))
//! } //! }
//! true //!
//! StaticFilesBundle::from_dir("./static", Some(only_css_files))
//! .with_name("guides")
//! .build()
//! } //! }
//! ``` //! ```
//! //!
//! This will create a file called `guides.rs` in the standard directory //! ### 2. Compiling SCSS files to CSS
//!
//! Compile a SCSS file into CSS and embed it:
//!
//! ```rust#ignore
//! use pagetop_build::StaticFilesBundle;
//!
//! fn main() -> std::io::Result<()> {
//! StaticFilesBundle::from_scss("./styles/main.scss", "main.css")
//! .with_name("main_styles")
//! .build()
//! }
//! ```
//!
//! This compiles the `main.scss` file, including all imported SCSS files, into `main.css`. All
//! imports are resolved automatically, and the result is accessible within the binary file.
//!
//! ## Generated module
//!
//! [`StaticFilesBundle`] generates a file in the standard directory
//! [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html) where all //! [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html) where all
//! intermediate and output artifacts are placed during compilation. //! intermediate and output artifacts are placed during compilation. For example, if you use
//! `with_name("guides")`, it generates a file named `guides.rs`:
//! //!
//! You don't need to access this file, just include it in your project using the builder name as an //! You don't need to access this file, just include it in your project using the builder name as an
//! identifier: //! identifier:
@ -59,27 +76,118 @@
//! ```rust#ignore //! ```rust#ignore
//! use pagetop::prelude::*; //! use pagetop::prelude::*;
//! //!
//! static_files!(guides); //! include_files!(guides);
//! ``` //! ```
//! //!
//! Also you can get the bundle as a static reference to the generated `HashMap` resources //! Or, access the entire bundle as a global static `HashMap`:
//! collection:
//! //!
//! ```rust#ignore //! ```rust#ignore
//! use pagetop::prelude::*; //! use pagetop::prelude::*;
//! //!
//! static_files!(guides => BUNDLE_GUIDES); //! include_files!(guides => BUNDLE_GUIDES);
//! ``` //! ```
//! //!
//! You can build more than one resources file to compile with your project. //! You can build more than one resources file to compile with your project.
use grass::{from_path, Options, OutputStyle};
use static_files::{resource_dir, ResourceDir};
use std::fs::{create_dir_all, remove_dir_all, File};
use std::io::Write;
use std::path::Path; use std::path::Path;
pub struct StaticFilesBundle(static_files::ResourceDir); /// Generates the resources to embed at compile time using
/// [static_files](https://docs.rs/static-files/latest/static_files/).
pub struct StaticFilesBundle {
resource_dir: ResourceDir,
}
impl StaticFilesBundle { impl StaticFilesBundle {
pub fn from_dir(dir: &'static str) -> Self { /// Creates a bundle from a directory of static files, with an optional filter.
StaticFilesBundle(static_files::resource_dir(dir)) ///
/// # Arguments
///
/// * `dir` - The directory containing the static files.
/// * `filter` - An optional function to filter files or directories to include.
pub fn from_dir(dir: &'static str, filter: Option<fn(p: &Path) -> bool>) -> Self {
let mut resource_dir = resource_dir(dir);
// Apply the filter if provided.
if let Some(f) = filter {
resource_dir.with_filter(f);
}
StaticFilesBundle { resource_dir }
}
/// Creates a bundle starting from a SCSS file.
///
/// # Arguments
///
/// * `path` - The SCSS file to compile.
/// * `target_name` - The name for the CSS file in the bundle.
///
/// This function will panic:
///
/// * If the environment variable `OUT_DIR` is not set.
/// * If it is unable to create a temporary directory in the `OUT_DIR`.
/// * If the SCSS file cannot be compiled due to syntax errors in the SCSS file or missing
/// dependencies or import paths required for compilation.
/// * If it is unable to create the output CSS file in the temporary directory due to an invalid
/// `target_name` or insufficient permissions to create files in the temporary directory.
/// * If the function fails to write the compiled CSS content to the file.
pub fn from_scss<P>(path: P, target_name: &str) -> Self
where
P: AsRef<Path>,
{
// Create a temporary directory for the CSS file.
let out_dir = std::env::var("OUT_DIR").unwrap();
let temp_dir = Path::new(&out_dir).join("from_scss_files");
// Clean up the temporary directory from previous runs, if it exists.
if temp_dir.exists() {
remove_dir_all(&temp_dir).unwrap_or_else(|e| {
panic!(
"Failed to clean temporary directory `{}`: {e}",
temp_dir.display()
);
});
}
create_dir_all(&temp_dir).unwrap_or_else(|e| {
panic!(
"Failed to create temporary directory `{}`: {e}",
temp_dir.display()
);
});
// Compile SCSS to CSS.
let css_content = from_path(
path.as_ref(),
&Options::default().style(OutputStyle::Compressed),
)
.unwrap_or_else(|e| {
panic!(
"Failed to compile SCSS file `{}`: {e}",
path.as_ref().display(),
)
});
// Write the compiled CSS to the temporary directory.
let css_path = temp_dir.join(target_name);
File::create(&css_path)
.expect(&format!(
"Failed to create CSS file `{}`",
css_path.display()
))
.write_all(css_content.as_bytes())
.expect(&format!(
"Failed to write CSS content to `{}`",
css_path.display()
));
// Initialize ResourceDir with the temporary directory.
StaticFilesBundle {
resource_dir: resource_dir(temp_dir.to_str().unwrap()),
}
} }
/// Configures the name for the bundle of static files. /// Configures the name for the bundle of static files.
@ -88,16 +196,11 @@ impl StaticFilesBundle {
/// ///
/// This function will panic if the standard `OUT_DIR` environment variable is not set. /// This function will panic if the standard `OUT_DIR` environment variable is not set.
pub fn with_name(mut self, name: &'static str) -> Self { pub fn with_name(mut self, name: &'static str) -> Self {
self.0.with_generated_filename( let out_dir = std::env::var("OUT_DIR").unwrap();
Path::new(std::env::var("OUT_DIR").unwrap().as_str()).join(format!("{name}.rs")), let filename = Path::new(&out_dir).join(format!("{name}.rs"));
); self.resource_dir.with_generated_filename(filename);
self.0.with_module_name(format!("bundle_{name}")); self.resource_dir.with_module_name(format!("bundle_{name}"));
self.0.with_generated_fn(name); self.resource_dir.with_generated_fn(name);
self
}
pub fn with_filter(mut self, filter: fn(p: &Path) -> bool) -> Self {
self.0.with_filter(filter);
self self
} }
@ -108,6 +211,6 @@ impl StaticFilesBundle {
/// This function will return an error if there is an issue with I/O operations, such as failing /// This function will return an error if there is an issue with I/O operations, such as failing
/// to read or write to a file. /// to read or write to a file.
pub fn build(self) -> std::io::Result<()> { pub fn build(self) -> std::io::Result<()> {
self.0.build() self.resource_dir.build()
} }
} }

View file

@ -3,28 +3,23 @@ name = "pagetop-macros"
version = "0.0.13" version = "0.0.13"
edition = "2021" edition = "2021"
description = "A collection of procedural macros that boost PageTop development." description = """\
homepage = "https://pagetop.cillero.es" A collection of macros that boost PageTop development.\
repository = "https://github.com/manuelcillero/pagetop" """
license = "MIT OR Apache-2.0" categories = ["development-tools::procedural-macro-helpers", "web-programming"]
keywords = ["pagetop", "macros", "proc-macros", "codegen"]
authors = [ repository = "https://github.com/manuelcillero/pagetop"
"Manuel Cillero <manuel@cillero.es>" homepage = "https://pagetop.cillero.es"
] license = "MIT OR Apache-2.0"
categories = [ authors = ["Manuel Cillero <manuel@cillero.es>"]
"development-tools::procedural-macro-helpers", "web-programming"
]
keywords = [
"pagetop", "macros", "proc-macros", "codegen"
]
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies] [dependencies]
concat-string = "1.0.1" proc-macro2 = "1.0.92"
proc-macro2 = "1.0" proc-macro-crate = "3.2.0"
proc-macro-crate = "3.1.0"
proc-macro-error = "1.0.4" proc-macro-error = "1.0.4"
quote = "1.0" quote = "1.0.37"
syn = { version = "2.0", features = ["full"] } syn = { version = "2.0.90", features = ["full"] }

View file

@ -2,7 +2,7 @@
<h1>PageTop Macros</h1> <h1>PageTop Macros</h1>
<p>A collection of procedural macros that boost PageTop development.</p> <p>A collection of macros that boost PageTop development.</p>
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?style=for-the-badge)](#-license) [![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?style=for-the-badge)](#-license)
[![API Docs](https://img.shields.io/docsrs/pagetop-macros?label=API%20Docs&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) [![API Docs](https://img.shields.io/docsrs/pagetop-macros?label=API%20Docs&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros)
@ -25,11 +25,16 @@ frequent changes. Production use is not recommended until version **0.1.0**.
# 🔖 Credits # 🔖 Credits
This crate includes an adapted version of [maud-macros](https://crates.io/crates/maud_macros), This crate includes an adapted version of [maud-macros](https://crates.io/crates/maud_macros)
version [0.25.0](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud_macros), by (version [0.25.0](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud_macros)) by
[Chris Wong](https://crates.io/users/lambda-fairy). This adaptation integrates its functionalities [Chris Wong](https://crates.io/users/lambda-fairy).
into **PageTop**, eliminating the need for direct references to `maud` in the `Cargo.toml` file of
each project. Additionally, the [SmartDefault](https://crates.io/crates/smart_default) crate (version 0.7.1) by
[Jane Doe](https://crates.io/users/jane-doe) has been embedded as `AutoDefault`, simplifying
`Default` implementations.
Both eliminate the need to explicitly reference `maud` or `smart_default` in the `Cargo.toml` file
of each project.
# 📜 License # 📜 License

View file

@ -1,18 +1,11 @@
mod maud; mod maud;
mod smart_default; mod smart_default;
use concat_string::concat_string;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error; use proc_macro_error::proc_macro_error;
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use syn::{parse_macro_input, parse_str, DeriveInput, ItemFn}; use syn::{parse_macro_input, parse_str, DeriveInput, ItemFn};
#[proc_macro]
#[proc_macro_error]
pub fn html(input: TokenStream) -> TokenStream {
maud::expand(input.into()).into()
}
/// Macro attribute to generate builder methods from `set_` methods. /// Macro attribute to generate builder methods from `set_` methods.
/// ///
/// This macro takes a method with the `set_` prefix and generates a corresponding method with the /// This macro takes a method with the `set_` prefix and generates a corresponding method with the
@ -57,7 +50,7 @@ pub fn fn_builder(_: TokenStream, item: TokenStream) -> TokenStream {
fn_with_name.clone() fn_with_name.clone()
} else { } else {
let g = &fn_set.sig.generics; let g = &fn_set.sig.generics;
concat_string!(fn_with_name, quote! { #g }.to_string()) format!("{fn_with_name}{}", quote! { #g }.to_string())
}; };
let where_clause = fn_set let where_clause = fn_set
@ -66,7 +59,7 @@ pub fn fn_builder(_: TokenStream, item: TokenStream) -> TokenStream {
.where_clause .where_clause
.as_ref() .as_ref()
.map_or(String::new(), |where_clause| { .map_or(String::new(), |where_clause| {
concat_string!(quote! { #where_clause }.to_string(), " ") format!("{} ", quote! { #where_clause }.to_string())
}); });
let args: Vec<String> = fn_set let args: Vec<String> = fn_set
@ -89,21 +82,21 @@ pub fn fn_builder(_: TokenStream, item: TokenStream) -> TokenStream {
.collect(); .collect();
#[rustfmt::skip] #[rustfmt::skip]
let fn_with = parse_str::<ItemFn>(concat_string!(" let fn_with = parse_str::<ItemFn>(format!(r##"
pub fn ", fn_with_generics, "(mut self, ", args.join(", "), ") -> Self ", where_clause, "{ pub fn {fn_with_generics}(mut self, {}) -> Self {where_clause} {{
self.", fn_set_name, "(", params.join(", "), "); self.{fn_set_name}({});
self self
} }}
").as_str()).unwrap(); "##, args.join(", "), params.join(", ")
).as_str()).unwrap();
#[rustfmt::skip] #[rustfmt::skip]
let fn_set_doc = concat_string!( let fn_set_doc = format!(r##"
"<p id=\"method.", fn_with_name, "\" style=\"margin-bottom: 12px;\">Use ", <p id="method.{fn_with_name}" style="margin-bottom: 12px;">Use
"<code class=\"code-header\">pub fn <span class=\"fn\" href=\"#method.", fn_with_name, "\">", <code class="code-header">pub fn <span class="fn" href="#method.{fn_with_name}">{fn_with_name}</span>(self, ) -> Self</code>
fn_with_name, for the <a href="#method.new">builder pattern</a>.
"</span>(self, …) -> Self</code> for the <a href=\"#method.new\">builder pattern</a>.", </p>
"</p>" "##);
);
let expanded = quote! { let expanded = quote! {
#[doc(hidden)] #[doc(hidden)]
@ -115,6 +108,52 @@ pub fn fn_builder(_: TokenStream, item: TokenStream) -> TokenStream {
expanded.into() expanded.into()
} }
#[proc_macro]
#[proc_macro_error]
pub fn html(input: TokenStream) -> TokenStream {
maud::expand(input.into()).into()
}
#[proc_macro_derive(AutoDefault, attributes(default))]
pub fn derive_auto_default(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match smart_default::body_impl::impl_my_derive(&input) {
Ok(output) => output.into(),
Err(error) => error.to_compile_error().into(),
}
}
#[proc_macro_derive(ComponentClasses)]
pub fn derive_component_classes(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
#[rustfmt::skip]
let fn_set_doc = format!(r##"
<p id="method.with_classes">Use
<code class="code-header"><span class="fn" href="#method.with_classes">with_classes</span>(self, ) -> Self</code>
to apply the <a href="#method.new">builder pattern</a>.
</p>
"##);
let expanded = quote! {
impl ComponentClasses for #name {
#[inline]
#[doc = #fn_set_doc]
fn set_classes(&mut self, op: ClassesOp, classes: impl Into<String>) -> &mut Self {
self.classes.set_value(op, classes);
self
}
fn classes(&self) -> &OptionClasses {
&self.classes
}
}
};
TokenStream::from(expanded)
}
/// Marks async main function as the `PageTop` entry-point. /// Marks async main function as the `PageTop` entry-point.
/// ///
/// # Examples /// # Examples
@ -154,43 +193,3 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
output.extend(item); output.extend(item);
output output
} }
#[proc_macro_derive(AutoDefault, attributes(default))]
pub fn derive_auto_default(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match smart_default::body_impl::impl_my_derive(&input) {
Ok(output) => output.into(),
Err(error) => error.to_compile_error().into(),
}
}
#[proc_macro_derive(ComponentClasses)]
pub fn derive_component_classes(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let fn_set_doc = concat_string!(
"<p id=\"method.with_classes\">",
"Use <code class=\"code-header\">",
" <span class=\"fn\" href=\"#method.with_classes\">with_classes</span>(self, …) -> Self ",
"</code> to apply the <a href=\"#method.new\">builder pattern</a>.",
"</p>"
);
let expanded = quote! {
impl ComponentClasses for #name {
#[inline]
#[doc = #fn_set_doc]
fn set_classes(&mut self, op: ClassesOp, classes: impl Into<String>) -> &mut Self {
self.classes.set_value(op, classes);
self
}
fn classes(&self) -> &OptionClasses {
&self.classes
}
}
};
TokenStream::from(expanded)
}

View file

@ -45,7 +45,7 @@ impl Application {
LazyLock::force(&trace::TRACING); LazyLock::force(&trace::TRACING);
// Validates the default language identifier. // Validates the default language identifier.
LazyLock::force(&locale::LANGID_DEFAULT); LazyLock::force(&locale::DEFAULT_LANGID);
// Registers the application's packages. // Registers the application's packages.
package::all::register_packages(root_package); package::all::register_packages(root_package);
@ -61,11 +61,13 @@ impl Application {
// Displays the application banner based on the configuration. // Displays the application banner based on the configuration.
fn show_banner() { fn show_banner() {
use colored::Colorize;
use terminal_size::{terminal_size, Width}; use terminal_size::{terminal_size, Width};
if global::SETTINGS.app.startup_banner.to_lowercase() != "off" { if global::SETTINGS.app.startup_banner.to_lowercase() != "off" {
// Application name, formatted for the terminal width if necessary. // Application name, formatted for the terminal width if necessary.
let mut app_name = global::SETTINGS.app.name.to_string(); let mut app_ff = "".to_string();
let app_name = &global::SETTINGS.app.name;
if let Some((Width(term_width), _)) = terminal_size() { if let Some((Width(term_width), _)) = terminal_size() {
if term_width >= 80 { if term_width >= 80 {
let maxlen: usize = ((term_width / 10) - 2).into(); let maxlen: usize = ((term_width / 10) - 2).into();
@ -74,19 +76,27 @@ impl Application {
app = format!("{app}..."); app = format!("{app}...");
} }
if let Some(ff) = figfont::FIGFONT.convert(&app) { if let Some(ff) = figfont::FIGFONT.convert(&app) {
app_name = ff.to_string(); app_ff = ff.to_string();
} }
} }
} }
println!("\n{app_name}"); if app_ff.is_empty() {
println!("\n{app_name}");
} else {
print!("\n{app_ff}");
}
// Application description. // Application description.
if !global::SETTINGS.app.description.is_empty() { if !global::SETTINGS.app.description.is_empty() {
println!("{}\n", global::SETTINGS.app.description); println!("{}", global::SETTINGS.app.description.cyan());
}; };
// PageTop version. // PageTop version.
println!("Powered by PageTop {}\n", env!("CARGO_PKG_VERSION")); println!(
"{} {}\n",
"Powered by PageTop".yellow(),
env!("CARGO_PKG_VERSION").yellow()
);
} }
} }

View file

@ -12,229 +12,122 @@ impl PackageTrait for Welcome {
} }
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
scfg.route("/", service::web::get().to(home_page)) scfg.route("/", service::web::get().to(homepage));
.route("/{lang}", service::web::get().to(home_lang));
} }
} }
async fn home_page(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { async fn homepage(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
home(request, &LANGID_DEFAULT)
}
async fn home_lang(
request: HttpRequest,
path: service::web::Path<String>,
) -> ResultPage<Markup, ErrorPage> {
match langid_for(path.into_inner()) {
Ok(lang) => home(request, lang),
_ => Err(ErrorPage::NotFound(request)),
}
}
fn home(request: HttpRequest, lang: &'static LanguageIdentifier) -> ResultPage<Markup, ErrorPage> {
Page::new(request) Page::new(request)
.with_title(L10n::l("welcome_title")) .with_title(L10n::l("welcome_page"))
.with_assets(AssetsOp::LangId(lang)) .with_assets(AssetsOp::Theme("Basic"))
.with_assets(AssetsOp::AddStyleSheet(StyleSheet::from( .with_assets(AssetsOp::AddStyleSheet(StyleSheet::inline("styles", r##"
"/base/css/welcome.css", body {
))) background-color: #f3d060;
.with_body_id("welcome") font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
.with_component(hello_world()) font-size: 20px;
.with_component(welcome()) }
.with_component(about_pagetop()) .skip__to_content {
.with_component(promo_pagetop()) display: none;
.with_component(reporting_issues()) }
.wrapper {
max-width: 1200px;
width: 100%;
margin: 0 auto;
padding: 0;
}
.container {
padding: 0 16px;
}
.title {
font-size: clamp(3rem, 10vw, 10rem);
letter-spacing: -0.05em;
line-height: 1.2;
margin: 0;
}
.subtitle {
font-size: clamp(1.8rem, 2vw, 3rem);
letter-spacing: -0.02em;
line-height: 1.2;
margin: 0;
}
.powered {
margin: .5em 0 1em;
}
.box-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: stretch;
gap: 1.5em;
}
.box {
flex: 1 1 280px;
border: 3px solid #25282a;
box-shadow: 5px 5px 0px #25282a;
box-sizing: border-box;
padding: 0 16px;
}
footer {
margin-top: 5em;
font-size: 14px;
font-weight: 500;
color: #a5282c;
}
"##)))
.with_component(Html::with(html! {
div class="wrapper" {
div class="container" {
h1 class="title" { (L10n::l("welcome_title").markup()) }
p class="subtitle" {
(L10n::l("welcome_intro").with_arg("app", format!(
"<span style=\"font-weight: bold;\">{}</span>",
&global::SETTINGS.app.name
)).markup())
}
p class="powered" {
(L10n::l("welcome_powered").with_arg("pagetop", format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://crates.io/crates/pagetop", "PageTop"
)).markup())
}
h2 { (L10n::l("welcome_page").markup()) }
div class="box-container" {
section class="box" style="background-color: #5eb0e5;" {
h3 {
(L10n::l("welcome_subtitle")
.with_arg("app", &global::SETTINGS.app.name)
.markup())
}
p { (L10n::l("welcome_text1").markup()) }
p { (L10n::l("welcome_text2").markup()) }
}
section class="box" style="background-color: #aee1cd;" {
h3 {
(L10n::l("welcome_pagetop_title").markup())
}
p { (L10n::l("welcome_pagetop_text1").markup()) }
p { (L10n::l("welcome_pagetop_text2").markup()) }
p { (L10n::l("welcome_pagetop_text3").markup()) }
}
section class="box" style="background-color: #ebebe3;" {
h3 {
(L10n::l("welcome_issues_title").markup())
}
p { (L10n::l("welcome_issues_text1").markup()) }
p {
(L10n::l("welcome_issues_text2")
.with_arg("app", &global::SETTINGS.app.name)
.markup())
}
}
}
footer { "[ " (L10n::l("welcome_have_fun").markup()) " ]" }
}
}
}))
.render() .render()
} }
fn hello_world() -> flex::Container {
flex::Container::header()
.with_classes(ClassesOp::Add, "hello-world")
.with_justify(flex::Justify::Center)
.add_item(
flex::Item::new()
.with_size(flex::Size::Percent90)
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "hello-col-text")
.with_size(flex::Size::Percent40)
.add_component(
Heading::h1(L10n::l("welcome_title"))
.with_size(HeadingSize::Medium),
)
.add_component(
Paragraph::fluent(L10n::l("welcome_intro").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&global::SETTINGS.app.name,
),
))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(
L10n::l("welcome_powered").with_arg(
"pagetop",
format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://pagetop.cillero.es", "PageTop",
),
),
))
.add_component(
Button::anchor(
"https://github.com/manuelcillero/pagetop",
L10n::l("welcome_code"),
)
.with_target(ButtonTarget::Blank)
.with_left_icon(Some(Icon::with("git")))
.with_classes(ClassesOp::Add, "code-link")
.with_font_size(FontSize::Medium),
)
.add_component(
Button::anchor("#welcome-page", L10n::l("welcome"))
.with_style(StyleBase::Link)
.with_left_icon(Some(Icon::with("arrow-down-circle-fill")))
.with_classes(ClassesOp::Add, "welcome-link")
.with_font_size(FontSize::Medium),
),
)
.add_item(
flex::Item::with(Image::with("/base/images/header.svg"))
.with_classes(ClassesOp::Add, "hello-col-image")
.with_size(flex::Size::Percent60),
),
),
)
}
fn welcome() -> flex::Container {
flex::Container::section()
.with_id("welcome-page")
.with_classes(ClassesOp::Add, "welcome")
.with_justify(flex::Justify::Center)
.add_item(
flex::Item::new()
.with_size(flex::Size::Percent80)
.add_component(Heading::h2(L10n::l("welcome_page")))
.add_component(
Heading::h3(L10n::l("welcome_subtitle").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&global::SETTINGS.app.name
),
))
.with_size(HeadingSize::Subtitle),
)
.add_component(
Paragraph::fluent(L10n::l("welcome_text1")).with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(L10n::l("welcome_text2"))),
)
}
fn about_pagetop() -> flex::Container {
flex::Container::new()
.with_classes(ClassesOp::Add, "pagetop")
.with_justify(flex::Justify::Center)
.add_item(
flex::Item::new()
.with_size(flex::Size::Percent90)
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::SM))
.add_item(
flex::Item::with(Image::with("/base/images/about.svg"))
.with_classes(ClassesOp::Add, "pagetop-col-image")
.with_size(flex::Size::Percent40),
)
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "pagetop-col-text")
.add_component(Heading::h2(L10n::l("welcome_pagetop_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_pagetop_text1"))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(L10n::l("welcome_pagetop_text2")))
.add_component(Paragraph::fluent(L10n::l("welcome_pagetop_text3"))),
),
),
)
}
fn promo_pagetop() -> flex::Container {
flex::Container::new()
.with_classes(ClassesOp::Add, "promo")
.with_justify(flex::Justify::Center)
.add_item(
flex::Item::new()
.with_size(flex::Size::Percent75)
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "promo-col-text")
.with_size(flex::Size::Percent50)
.add_component(Heading::h2(L10n::l("welcome_promo_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_promo_text1").with_arg(
"pagetop",
format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://crates.io/crates/pagetop", "PageTop",
),
))
.with_font_size(FontSize::Medium),
),
)
.add_item(
flex::Item::with(Image::with("/base/images/pagetop.png"))
.with_classes(ClassesOp::Add, "promo-col-image")
.with_size(flex::Size::Percent50),
),
),
)
}
fn reporting_issues() -> flex::Container {
flex::Container::new()
.with_classes(ClassesOp::Add, "issues")
.with_justify(flex::Justify::Center)
.add_item(
flex::Item::new()
.with_size(flex::Size::Percent90)
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::with(Image::with("/base/images/issues.jpg"))
.with_classes(ClassesOp::Add, "issues-col-image"),
)
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "issues-col-text")
.with_size(flex::Size::Percent50)
.add_component(Heading::h2(L10n::l("welcome_issues_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_issues_text1"))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(
L10n::l("welcome_issues_text2").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&global::SETTINGS.app.name,
),
),
)),
),
),
)
}

View file

@ -3,30 +3,9 @@ use crate::prelude::*;
pub struct Basic; pub struct Basic;
impl PackageTrait for Basic { impl PackageTrait for Basic {
fn name(&self) -> L10n {
L10n::n("Basic")
}
fn theme(&self) -> Option<ThemeRef> { fn theme(&self) -> Option<ThemeRef> {
Some(&Basic) Some(&Basic)
} }
} }
impl ThemeTrait for Basic { impl ThemeTrait for Basic {}
fn after_prepare_body(&self, page: &mut Page) {
page.set_assets(AssetsOp::SetFavicon(Some(
Favicon::new().with_icon("/base/favicon.ico"),
)))
.set_assets(AssetsOp::AddStyleSheet(
StyleSheet::from("/base/css/normalize.min.css")
.with_version("8.0.1")
.with_weight(-90),
))
.set_assets(AssetsOp::AddBaseAssets)
.set_assets(AssetsOp::AddStyleSheet(
StyleSheet::from("/base/css/basic.css")
.with_version("0.0.1")
.with_weight(-90),
));
}
}

View file

@ -14,10 +14,7 @@ impl PackageTrait for Chassis {
impl ThemeTrait for Chassis { impl ThemeTrait for Chassis {
fn after_prepare_body(&self, page: &mut Page) { fn after_prepare_body(&self, page: &mut Page) {
page.set_assets(AssetsOp::SetFavicon(Some( page.set_assets(AssetsOp::AddStyleSheet(
Favicon::new().with_icon("/base/favicon.ico"),
)))
.set_assets(AssetsOp::AddStyleSheet(
StyleSheet::from("/base/css/normalize.min.css") StyleSheet::from("/base/css/normalize.min.css")
.with_version("8.0.1") .with_version("8.0.1")
.with_weight(-90), .with_weight(-90),

View file

@ -14,10 +14,7 @@ impl PackageTrait for Inception {
impl ThemeTrait for Inception { impl ThemeTrait for Inception {
fn after_prepare_body(&self, page: &mut Page) { fn after_prepare_body(&self, page: &mut Page) {
page.set_assets(AssetsOp::SetFavicon(Some( page.set_assets(AssetsOp::AddStyleSheet(
Favicon::new().with_icon("/base/favicon.ico"),
)))
.set_assets(AssetsOp::AddStyleSheet(
StyleSheet::from("/base/css/normalize.min.css") StyleSheet::from("/base/css/normalize.min.css")
.with_version("8.0.1") .with_version("8.0.1")
.with_weight(-90), .with_weight(-90),

View file

@ -1,12 +1,12 @@
use crate::base::component::add_base_assets; use crate::base::component::add_base_assets;
use crate::concat_string; use crate::concat_string;
use crate::core::component::AnyOp; use crate::core::component::AnyOp;
use crate::core::theme::all::{theme_by_short_name, THEME_DEFAULT}; use crate::core::theme::all::{theme_by_short_name, DEFAULT_THEME};
use crate::core::theme::{ComponentsInRegions, ThemeRef}; use crate::core::theme::{ComponentsInRegions, ThemeRef};
use crate::global::TypeInfo; use crate::global::TypeInfo;
use crate::html::{html, Markup}; use crate::html::{html, Markup};
use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
use crate::locale::{LanguageIdentifier, LANGID_DEFAULT}; use crate::locale::{LanguageIdentifier, DEFAULT_LANGID};
use crate::service::HttpRequest; use crate::service::HttpRequest;
use std::collections::HashMap; use std::collections::HashMap;
@ -68,8 +68,8 @@ impl Context {
pub(crate) fn new(request: HttpRequest) -> Self { pub(crate) fn new(request: HttpRequest) -> Self {
Context { Context {
request, request,
langid : &LANGID_DEFAULT, langid : &DEFAULT_LANGID,
theme : *THEME_DEFAULT, theme : *DEFAULT_THEME,
layout : "default", layout : "default",
favicon : None, favicon : None,
stylesheet: Assets::<StyleSheet>::new(), stylesheet: Assets::<StyleSheet>::new(),
@ -86,7 +86,7 @@ impl Context {
self.langid = langid; self.langid = langid;
} }
AssetsOp::Theme(theme_name) => { AssetsOp::Theme(theme_name) => {
self.theme = theme_by_short_name(theme_name).unwrap_or(*THEME_DEFAULT); self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME);
} }
AssetsOp::Layout(layout) => { AssetsOp::Layout(layout) => {
self.layout = layout; self.layout = layout;

View file

@ -5,7 +5,7 @@ use crate::{global, service, static_files, static_files_service, trace};
use std::sync::{LazyLock, RwLock}; use std::sync::{LazyLock, RwLock};
static_files!(base); static_files!(assets);
// PACKAGES **************************************************************************************** // PACKAGES ****************************************************************************************
@ -21,14 +21,12 @@ pub fn register_packages(root_package: Option<PackageRef>) {
// Initialize a list for packages to be enabled. // Initialize a list for packages to be enabled.
let mut enabled_list: Vec<PackageRef> = Vec::new(); let mut enabled_list: Vec<PackageRef> = Vec::new();
// Add default theme to the enabled list.
add_to_enabled(&mut enabled_list, &crate::base::theme::Basic);
// Add default welcome page package to the enabled list. // Add default welcome page package to the enabled list.
add_to_enabled(&mut enabled_list, &crate::base::package::Welcome); add_to_enabled(&mut enabled_list, &crate::base::package::Welcome);
// Add default theme packages to the enabled list.
add_to_enabled(&mut enabled_list, &crate::base::theme::Basic);
add_to_enabled(&mut enabled_list, &crate::base::theme::Chassis);
add_to_enabled(&mut enabled_list, &crate::base::theme::Inception);
// If a root package is provided, add it to the enabled list. // If a root package is provided, add it to the enabled list.
if let Some(package) = root_package { if let Some(package) = root_package {
add_to_enabled(&mut enabled_list, package); add_to_enabled(&mut enabled_list, package);
@ -130,12 +128,12 @@ pub fn init_packages() {
// CONFIGURE SERVICES ****************************************************************************** // CONFIGURE SERVICES ******************************************************************************
pub fn configure_services(scfg: &mut service::web::ServiceConfig) { pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
static_files_service!(
scfg,
base => "/base",
[&global::SETTINGS.dev.pagetop_project_dir, "static/base"]
);
for m in ENABLED_PACKAGES.read().unwrap().iter() { for m in ENABLED_PACKAGES.read().unwrap().iter() {
m.configure_service(scfg); m.configure_service(scfg);
} }
static_files_service!(
scfg,
assets => "/",
[&global::SETTINGS.dev.pagetop_project_dir, "static/assets"]
);
} }

View file

@ -9,10 +9,10 @@ pub static THEMES: LazyLock<RwLock<Vec<ThemeRef>>> = LazyLock::new(|| RwLock::ne
// DEFAULT THEME *********************************************************************************** // DEFAULT THEME ***********************************************************************************
pub static THEME_DEFAULT: LazyLock<ThemeRef> = pub static DEFAULT_THEME: LazyLock<ThemeRef> =
LazyLock::new(|| match theme_by_short_name(&global::SETTINGS.app.theme) { LazyLock::new(|| match theme_by_short_name(&global::SETTINGS.app.theme) {
Some(theme) => theme, Some(theme) => theme,
None => &crate::base::theme::Inception, None => &crate::base::theme::Basic,
}); });
// THEME BY NAME *********************************************************************************** // THEME BY NAME ***********************************************************************************

View file

@ -1,7 +1,7 @@
use crate::base::component::*; use crate::base::component::*;
use crate::core::component::{AssetsOp, ComponentBase, ComponentTrait}; use crate::core::component::{ComponentBase, ComponentTrait};
use crate::core::package::PackageTrait; use crate::core::package::PackageTrait;
use crate::html::{html, Favicon, PrepareMarkup}; use crate::html::{html, PrepareMarkup};
use crate::locale::L10n; use crate::locale::L10n;
use crate::response::page::Page; use crate::response::page::Page;
use crate::{concat_string, global}; use crate::{concat_string, global};
@ -69,11 +69,8 @@ pub trait ThemeTrait: PackageTrait + Send + Sync {
}) })
} }
fn after_prepare_body(&self, page: &mut Page) { #[allow(unused_variables)]
page.set_assets(AssetsOp::SetFaviconIfNone( fn after_prepare_body(&self, page: &mut Page) {}
Favicon::new().with_icon("/base/favicon.ico"),
));
}
fn prepare_head(&self, page: &mut Page) -> PrepareMarkup { fn prepare_head(&self, page: &mut Page) -> PrepareMarkup {
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";

View file

@ -1,5 +1,5 @@
use crate::html::assets::AssetsTrait; use crate::html::assets::AssetsTrait;
use crate::html::{html, Markup}; use crate::html::{html, Markup, PreEscaped};
use crate::{concat_string, AutoDefault, Weight}; use crate::{concat_string, AutoDefault, Weight};
#[derive(AutoDefault)] #[derive(AutoDefault)]
@ -47,7 +47,7 @@ impl AssetsTrait for StyleSheet {
media=[self.media]; media=[self.media];
}, },
Source::Inline(_, code) => html! { Source::Inline(_, code) => html! {
styles { (code) }; style { (PreEscaped(code)) };
}, },
} }
} }

View file

@ -98,7 +98,7 @@ pub type Weight = i8;
// Global settings, functions and macro helpers. // Global settings, functions and macro helpers.
pub mod global; pub mod global;
static_locales!(LOCALES_PAGETOP); include_locales!(LOCALES_PAGETOP);
// ************************************************************************************************* // *************************************************************************************************
// PUBLIC API. // PUBLIC API.

View file

@ -114,13 +114,13 @@ static LANGUAGES: LazyLock<HashMap<String, (LanguageIdentifier, &str)>> = LazyLo
] ]
}); });
pub static LANGID_FALLBACK: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!("en-US")); static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!("en-US"));
/// Sets the application's default /// Sets the application's default
/// [Unicode Language Identifier](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier) /// [Unicode Language Identifier](https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier)
/// through `SETTINGS.app.language`. /// through `SETTINGS.app.language`.
pub static LANGID_DEFAULT: LazyLock<&LanguageIdentifier> = LazyLock::new(|| { pub static DEFAULT_LANGID: LazyLock<&LanguageIdentifier> = LazyLock::new(|| {
langid_for(global::SETTINGS.app.language.as_str()).unwrap_or(&LANGID_FALLBACK) langid_for(global::SETTINGS.app.language.as_str()).unwrap_or(&FALLBACK_LANGID)
}); });
pub fn langid_for(language: impl Into<String>) -> Result<&'static LanguageIdentifier, String> { pub fn langid_for(language: impl Into<String>) -> Result<&'static LanguageIdentifier, String> {
@ -129,7 +129,7 @@ pub fn langid_for(language: impl Into<String>) -> Result<&'static LanguageIdenti
Some((langid, _)) => Ok(langid), Some((langid, _)) => Ok(langid),
None => { None => {
if language.is_empty() { if language.is_empty() {
Ok(&LANGID_FALLBACK) Ok(&FALLBACK_LANGID)
} else { } else {
Err(format!( Err(format!(
"No langid for Unicode Language Identifier \"{language}\".", "No langid for Unicode Language Identifier \"{language}\".",
@ -141,7 +141,7 @@ pub fn langid_for(language: impl Into<String>) -> Result<&'static LanguageIdenti
#[macro_export] #[macro_export]
/// Defines a set of localization elements and local translation texts. /// Defines a set of localization elements and local translation texts.
macro_rules! static_locales { macro_rules! include_locales {
( $LOCALES:ident $(, $core_locales:literal)? ) => { ( $LOCALES:ident $(, $core_locales:literal)? ) => {
$crate::locale::fluent_templates::static_loader! { $crate::locale::fluent_templates::static_loader! {
static $LOCALES = { static $LOCALES = {
@ -241,6 +241,10 @@ impl L10n {
} }
} }
pub fn markup(&self) -> Markup {
PreEscaped(self.using(&DEFAULT_LANGID).unwrap_or_default())
}
pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup { pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup {
PreEscaped(self.using(langid).unwrap_or_default()) PreEscaped(self.using(langid).unwrap_or_default())
} }
@ -259,16 +263,16 @@ impl fmt::Display for L10n {
if self.args.is_empty() { if self.args.is_empty() {
locales.lookup( locales.lookup(
match key.as_str() { match key.as_str() {
LANGUAGE_SET_FAILURE => &LANGID_FALLBACK, LANGUAGE_SET_FAILURE => &FALLBACK_LANGID,
_ => &LANGID_DEFAULT, _ => &DEFAULT_LANGID,
}, },
key, key,
) )
} else { } else {
locales.lookup_with_args( locales.lookup_with_args(
match key.as_str() { match key.as_str() {
LANGUAGE_SET_FAILURE => &LANGID_FALLBACK, LANGUAGE_SET_FAILURE => &FALLBACK_LANGID,
_ => &LANGID_DEFAULT, _ => &DEFAULT_LANGID,
}, },
key, key,
&self &self

View file

@ -0,0 +1,5 @@
english = English
english_british = English (British)
english_united_states = English (United States)
spanish = Spanish
spanish_spain = Spanish (Spain)

View file

@ -1,26 +1,23 @@
welcome_package_name = Default homepage welcome_package_name = Default homepage
welcome_package_description = Displays a demo homepage when none is configured. welcome_package_description = Displays a landing page when none is configured.
welcome_title = Hello world! welcome_title = Hello world!
welcome_intro = This page is used to check the proper operation of the { $app } installation. welcome_intro = Verifying the installation of { $app }.
welcome_powered = This web solution is powered by { $pagetop }. welcome_powered = A web solution powered by { $pagetop }.
welcome_code = Code
welcome = Welcome
welcome_page = Welcome page welcome_page = Welcome Page
welcome_subtitle = Are you user of { $app }? welcome_subtitle = Are you a { $app } user?
welcome_text1 = If you don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance. welcome_text1 = If you don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance.
welcome_text2 = If the problem persists, please contact your system administrator. welcome_text2 = If the issue persists, please contact your system administrator for assistance.
welcome_pagetop_title = About PageTop welcome_pagetop_title = About PageTop
welcome_pagetop_text1 = If you can read this page, it means that the PageTop server is working properly, but has not yet been configured. welcome_pagetop_text1 = If you can read this page, it means that the <strong>PageTop</strong> server is working properly, but has not yet been configured.
welcome_pagetop_text2 = PageTop is a <a href="https://www.rust-lang.org" target="_blank">Rust</a>-based web development framework to build modular, extensible, and configurable web solutions. welcome_pagetop_text2 = <strong>PageTop</strong> is a <a href="https://www.rust-lang.org" target="_blank">Rust</a>-based web development framework designed to create modular, extensible, and configurable web solutions.
welcome_pagetop_text3 = For more information on PageTop please visit the <a href="https://docs.rs/pagetop/latest/pagetop" target="_blank">technical documentation</a>. welcome_pagetop_text3 = For detailed information, please visit the <a href="https://docs.rs/pagetop/latest/pagetop" target="_blank">official technical documentation</a>.
welcome_promo_title = Promoting PageTop welcome_issues_title = Reporting Issues
welcome_promo_text1 = You are free to use the image below on applications powered by { $pagetop }. Thanks for using PageTop! welcome_issues_text1 = To report any issues with <strong>PageTop</strong>, please use <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank">GitHub</a>. However, check the existing error reports to avoid duplicates.
welcome_issues_text2 = For issues specific to <strong>{ $app }</strong>, please refer to its official repository or support channel, rather than directly to <strong>PageTop</strong>.
welcome_issues_title = Reporting problems welcome_have_fun = Coding is creating
welcome_issues_text1 = Please use <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank">GitHub to report any issues</a> with PageTop. However, check the existing error reports before submitting a new issue.
welcome_issues_text2 = If the issues are specific to { $app }, please refer to its official repository or support channel, rather than directly to PageTop.

View file

@ -0,0 +1,5 @@
english = Inglés
english_british = Inglés (Gran Bretaña)
english_united_states = Inglés (Estados Unidos)
spanish = Español
spanish_spain = Español (España)

View file

@ -1,26 +1,23 @@
welcome_package_name = Página de inicio predeterminada welcome_package_name = Página de inicio predeterminada
welcome_package_description = Muestra una página de demostración predeterminada cuando no hay ninguna configurada. welcome_package_description = Muestra una página de inicio predeterminada cuando no hay ninguna configurada.
welcome_title = ¡Hola mundo! welcome_title = ¡Hola mundo!
welcome_intro = Esta página se utiliza para verificar el correcto funcionamiento de la instalación de { $app }. welcome_intro = Verificando la instalación de { $app }.
welcome_powered = Esta solución web funciona con { $pagetop }. welcome_powered = Una solución web creada con { $pagetop }.
welcome_code = Código
welcome = Bienvenida
welcome_page = Página de bienvenida welcome_page = Página de Bienvenida
welcome_subtitle = ¿Eres usuario de { $app }? welcome_subtitle = ¿Eres usuario de { $app }?
welcome_text1 = Si no sabes de qué trata esta página, probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina. welcome_text1 = Si no sabes por qué se muestra esta página probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina.
welcome_text2 = Si el problema persiste, póngase en contacto con el administrador del sistema. welcome_text2 = Si el problema persiste, por favor póngase en contacto con el administrador del sistema.
welcome_pagetop_title = Sobre PageTop welcome_pagetop_title = Sobre PageTop
welcome_pagetop_text1 = Si puedes leer esta página, significa que el servidor PageTop funciona correctamente, pero aún no se ha configurado. welcome_pagetop_text1 = Si puedes leer esta página significa que el servidor <strong>PageTop</strong> funciona correctamente, pero aún no se ha configurado.
welcome_pagetop_text2 = PageTop es un entorno de desarrollo web basado en <a href="https://www.rust-lang.org/es" target="_blank">Rust</a> para construir soluciones web modulares, extensibles y configurables. welcome_pagetop_text2 = <strong>PageTop</strong> es un entorno de desarrollo web basado en <a href="https://www.rust-lang.org/es" target="_blank">Rust</a>, diseñado para crear soluciones web modulares, extensibles y configurables.
welcome_pagetop_text3 = Para más información sobre PageTop, por favor visita la <a href="https://docs.rs/pagetop/latest/pagetop" target="_blank">documentación técnica</a>. welcome_pagetop_text3 = Para más información visita la <a href="https://docs.rs/pagetop/latest/pagetop" target="_blank">documentación técnica oficial</a>.
welcome_promo_title = Promociona PageTop welcome_issues_title = Informando Problemas
welcome_promo_text1 = Eres libre de usar la siguiente imagen en aplicaciones desarrolladas con { $pagetop }. ¡Gracias por usar PageTop! welcome_issues_text1 = Para comunicar cualquier problema con <strong>PageTop</strong> utiliza <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank">GitHub</a>. No obstante, comprueba los informes de errores ya existentes para evitar duplicados.
welcome_issues_text2 = Si son fallos específicos de <strong>{ $app }</strong>, por favor acude a su repositorio oficial o canal de soporte, y no al de <strong>PageTop</strong> directamente.
welcome_issues_title = Informando problemas welcome_have_fun = Programar es crear
welcome_issues_text1 = Por favor, utiliza <a href="https://github.com/manuelcillero/pagetop/issues" target="_blank">GitHub para reportar cualquier problema</a> con PageTop. No obstante, comprueba los informes de errores existentes antes de enviar uno nuevo.
welcome_issues_text2 = Si son fallos específicos de { $app }, por favor acude a su repositorio o canal de soporte oficial y no al de PageTop directamente.

View file

@ -16,7 +16,7 @@ pub use crate::config_defaults;
// crate::html // crate::html
pub use crate::html; pub use crate::html;
// crate::locale // crate::locale
pub use crate::static_locales; pub use crate::include_locales;
// crate::service // crate::service
pub use crate::{static_files, static_files_service}; pub use crate::{static_files, static_files_service};
// crate::core::action // crate::core::action

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After