Rust library to generate static websites
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: new RenderWithAlt trait

+83 -33
+5
.sampo/changesets/ancient-guardian-tuulikki.md
··· 1 + --- 2 + maudit: patch 3 + --- 4 + 5 + Add a new RenderWithAlt trait for rendering images, enforcing the usage of alt when trying to render to a String
+1 -1
crates/maudit/src/assets.rs
··· 10 10 mod script; 11 11 mod style; 12 12 mod tailwind; 13 - pub use image::{Image, ImageFormat, ImageOptions}; 13 + pub use image::{Image, ImageFormat, ImageOptions, ImagePlaceholder, RenderWithAlt}; 14 14 pub use script::Script; 15 15 pub use style::{Style, StyleOptions}; 16 16 pub use tailwind::TailwindPlugin;
+16
crates/maudit/src/assets/image.rs
··· 432 432 433 433 bytes 434 434 } 435 + 436 + /// Trait to render an image with an alt text. 437 + pub trait RenderWithAlt { 438 + fn render(&self, alt: &str) -> String; 439 + } 440 + 441 + impl RenderWithAlt for Image { 442 + fn render(&self, alt: &str) -> String { 443 + let (width, height) = self.dimensions(); 444 + 445 + format!( 446 + r#"<img src="{}" width="{}" height="{}" loading="lazy" decoding="async" alt="{}"/>"#, 447 + self.url, width, height, alt 448 + ) 449 + } 450 + }
+4 -1
crates/maudit/src/route.rs
··· 737 737 CachedRoute, DynamicRouteContext, FullRoute, Page, PageContext, PageParams, Pages, 738 738 PaginatedContentPage, PaginationPage, RenderResult, Route, RouteExt, paginate, 739 739 }; 740 - pub use crate::assets::{Asset, Image, ImageFormat, ImageOptions, Script, Style, StyleOptions}; 740 + pub use crate::assets::{ 741 + Asset, Image, ImageFormat, ImageOptions, ImagePlaceholder, RenderWithAlt, Script, Style, 742 + StyleOptions, 743 + }; 741 744 pub use crate::content::{ 742 745 ContentContext, ContentEntry, Entry, EntryInner, MarkdownContent, RouteContent, 743 746 };
+1 -10
crates/maudit/src/templating/maud_ext.rs
··· 2 2 3 3 use crate::{ 4 4 GENERATOR, 5 - assets::{Asset, Image, Script, Style}, 5 + assets::{Asset, Script, Style}, 6 6 route::RenderResult, 7 7 }; 8 8 ··· 18 18 fn render(&self) -> Markup { 19 19 html! { 20 20 script src=(self.url()) type="module" {} 21 - } 22 - } 23 - } 24 - 25 - impl Render for Image { 26 - fn render(&self) -> Markup { 27 - let (width, height) = self.dimensions(); 28 - html! { 29 - img src=(self.url()) width=(width) height=(height) loading="lazy" decoding="async"; 30 21 } 31 22 } 32 23 }
+2 -2
examples/basics/src/main.rs
··· 1 1 mod layout; 2 2 3 - use maudit::{BuildOptions, BuildOutput, coronate, routes}; 3 + use maudit::{BuildOptions, BuildOutput, content_sources, coronate, routes}; 4 4 5 5 mod routes { 6 6 mod index; ··· 10 10 pub use routes::Index; 11 11 12 12 fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 13 - coronate(routes![Index], vec![].into(), BuildOptions::default()) 13 + coronate(routes![Index], content_sources![], BuildOptions::default()) 14 14 }
+1 -1
examples/basics/src/routes/index.rs
··· 10 10 let logo = ctx.assets.add_image("images/logo.svg"); 11 11 12 12 layout(html! { 13 - (logo) 13 + (logo.render("Maudit logo, a crudely drawn crown")) 14 14 h1 { "Hello World" } 15 15 }) 16 16 }
+2 -2
examples/image-processing/src/main.rs
··· 1 1 mod layout; 2 2 3 - use maudit::{BuildOptions, BuildOutput, coronate, routes}; 3 + use maudit::{BuildOptions, BuildOutput, content_sources, coronate, routes}; 4 4 5 5 mod routes { 6 6 mod index; ··· 10 10 pub use routes::Index; 11 11 12 12 fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 13 - coronate(routes![Index], vec![].into(), BuildOptions::default()) 13 + coronate(routes![Index], content_sources![], BuildOptions::default()) 14 14 }
+2 -2
examples/image-processing/src/routes/index.rs
··· 18 18 ); 19 19 20 20 layout(html! { 21 - (logo) 21 + (logo.render("Maudit logo, a crudely drawn crown")) 22 22 h1 { "Hello World" } 23 23 h2 { "Here's a 200x200 walrus:" } 24 - (walrus) 24 + (walrus.render("A walrus with tusks")) 25 25 }) 26 26 } 27 27 }
+2 -2
examples/kitchen-sink/src/main.rs
··· 1 - use maudit::{AssetsOptions, BuildOptions, BuildOutput, coronate, routes}; 1 + use maudit::{AssetsOptions, BuildOptions, BuildOutput, content_sources, coronate, routes}; 2 2 3 3 mod routes { 4 4 mod dynamic; ··· 12 12 fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 13 13 coronate( 14 14 routes![routes::Index, routes::DynamicExample, routes::Endpoint], 15 - vec![].into(), 15 + content_sources![], 16 16 BuildOptions { 17 17 assets: AssetsOptions { 18 18 tailwind_binary_path: "../../node_modules/.bin/tailwindcss".into(),
+1 -1
examples/kitchen-sink/src/routes/dynamic.rs
··· 28 28 title { "Index" } 29 29 } 30 30 h1 { "Hello, world!" } 31 - (image) 31 + (image.render("Maudit social card, a crudely drawn crown")) 32 32 p { (params.page) } 33 33 } 34 34 }
+1
examples/kitchen-sink/src/routes/index.rs
··· 25 25 h1 { "Index" } 26 26 img src=(image.url()) {} 27 27 script src=(script.url()) {} 28 + (image.render("Maudit logo, a crudely drawn crown")) 28 29 a."text-red-500" href=(link_to_first_dynamic) { "Go to first dynamic page" } 29 30 } 30 31 }
+2 -2
examples/oubli-basics/src/main.rs
··· 1 1 mod layout; 2 2 3 - use oubli::{Archetype, BuildOptions, BuildOutput, archetypes, forget, routes}; 3 + use oubli::{Archetype, BuildOptions, BuildOutput, archetypes, content_sources, forget, routes}; 4 4 5 5 mod routes { 6 6 mod index; ··· 13 13 forget( 14 14 archetypes![("Our blog", blog, Archetype::Blog, "content/blog/*.md"),], 15 15 routes![Index], 16 - vec![].into(), 16 + content_sources![], 17 17 BuildOptions::default(), 18 18 ) 19 19 }
+1 -1
examples/oubli-basics/src/routes/index.rs
··· 14 14 .get_source::<oubli::ArchetypeStoreEntry>("archetype_store"); 15 15 16 16 layout(html! { 17 - (logo) 17 + (logo.render("Maudit logo, a crudely drawn crown")) 18 18 h1 { "Hello World" } 19 19 @for archetype in &archetype_store.entries { 20 20 a href=(archetype.id) { (archetype.data(ctx).title) }
+10 -7
website/content/docs/entrypoint.md
··· 9 9 In a `main.rs` file, import the `coronate` function and call it to build your project. Here is an example of a simple Maudit project: 10 10 11 11 ```rs 12 - use maudit::{coronate, routes, BuildOptions, BuildOutput}; 12 + use maudit::{coronate, routes, BuildOptions, BuildOutput, content_sources}; 13 13 use routes::Index; 14 14 15 15 fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 16 - coronate(routes![Index], vec![].into(), BuildOptions::default()) 16 + coronate(routes![Index], content_sources![], BuildOptions::default()) 17 17 } 18 18 ``` 19 19 ··· 24 24 The first argument to the `coronate` function is a `Vec` of all the routes that should be built. For the sake of ergonomics, the `routes!` macro can be used to create this list. 25 25 26 26 ```rs 27 + use maudit::{coronate, routes, BuildOptions}; 27 28 use routes::Index; 28 29 29 - coronate( 30 - routes![Index], 31 - vec![].into(), 32 - BuildOptions::default() 33 - ) 30 + fn main() { 31 + coronate( 32 + routes![Index], 33 + content_sources![], 34 + BuildOptions::default() 35 + ) 36 + } 34 37 ``` 35 38 36 39 See the [Routing](/docs/routing) documentation for more information on how to define routes.
+32 -1
website/content/docs/images.md
··· 16 16 17 17 ```rs 18 18 use maudit::route::prelude::*; 19 + use maud::{html}; 19 20 20 21 #[route("/blog")] 21 22 pub struct Blog; ··· 24 25 fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 25 26 let image = ctx.assets.add_image("logo.png"); 26 27 27 - format!("", image.url) 28 + let (width, height) = image.dimensions(); 29 + format!("<img src=\"{}\" alt=\"My logo\" width=\"{}\" height=\"{}\" />", image.url(), width, height); 30 + 31 + // A more convenient way to render an image is to use the `render()` method, which generates an img tag for you and enforces accessibility by requiring an alt text. 32 + format!("{}", image.render("The logo of my project, a stylized crown")) 28 33 } 29 34 } 30 35 ``` ··· 68 73 ``` 69 74 70 75 Processing images in Markdown files using the standard syntax is currently not supported, but can be achieved using a custom [shortcode](/docs/content/#shortcodes) or [component](/docs/content/#components). 76 + 77 + ## Placeholders 78 + 79 + Maudit supports generating low-quality image placeholders (LQIP) for images. This can be useful to improve the perceived performance of your site by showing a blurred preview of an image while the full image is loading. 80 + 81 + To generate a placeholder, use the `placeholder()` method on an [Image](https://docs.rs/maudit/latest/maudit/assets/struct.Image.html) instance, for example returned by `ctx.assets.add_image()` or `ctx.assets.add_image_with_options()`. 82 + 83 + ```rs 84 + use maudit::route::prelude::*; 85 + 86 + #[route("/image")] 87 + pub struct ImagePage; 88 + 89 + impl Route for ImagePage { 90 + fn render(&self, ctx: &mut PageContext) -> impl Into<RenderResult> { 91 + let image = ctx.assets.add_image("path/to/image.jpg"); 92 + let placeholder = image.placeholder(); 93 + 94 + format!("<img src=\"{}\" alt=\"Image with placeholder\" style=\"background-image: url('{}'); background-size: cover;\" />", image.url(), placeholder.data_uri()) 95 + } 96 + } 97 + ``` 98 + 99 + Alternatively, it is possible to get the dominant colors of an image using the `average_rgba()` method. This method will return a tuple of four `u8` values representing the red, green, blue, and alpha channels of the average color of the image. 100 + 101 + The generation of placeholders is powered by the [ThumbHash](https://evanw.github.io/thumbhash/) library.