diff --git a/src/api/upload.rs b/src/api/upload.rs index e7790fa..8d30a16 100644 --- a/src/api/upload.rs +++ b/src/api/upload.rs @@ -17,6 +17,17 @@ const ALLOWED_MIME_TYPES: &[&str] = &["image/jpeg", "image/png", "image/gif", "i #[cfg(feature = "server")] const MAX_FILE_SIZE: usize = 5 * 1024 * 1024; // 5MB +#[cfg(feature = "server")] +fn mime_to_ext(mime: &str) -> &'static str { + match mime { + "image/jpeg" => "jpg", + "image/png" => "png", + "image/webp" => "webp", + "image/gif" => "gif", + _ => "bin", + } +} + #[cfg(feature = "server")] pub async fn upload_image( headers: HeaderMap, @@ -139,44 +150,47 @@ pub async fn upload_image( } let is_gif = mime_type.as_str() == "image/gif"; + let is_webp = mime_type.as_str() == "image/webp"; let (final_data, final_ext) = if is_gif { - (data.to_vec(), "gif") + (data.to_vec(), "gif".to_string()) + } else if is_webp { + (data.to_vec(), "webp".to_string()) } else { - match image::load_from_memory(&data) { - Ok(img) => { - let mut buf = std::io::Cursor::new(Vec::new()); - match img.write_to(&mut buf, image::ImageFormat::WebP) { - Ok(_) => { - tracing::info!( - "Converted upload to WebP: {} bytes -> {} bytes", - data.len(), - buf.get_ref().len() - ); - (buf.into_inner(), "webp") - } - Err(e) => { - tracing::warn!("WebP encoding failed, storing original: {:?}", e); - let ext = match mime_type.as_str() { - "image/jpeg" => "jpg", - "image/png" => "png", - "image/webp" => "webp", - _ => "bin", - }; - (data.to_vec(), ext) + let original_data = data.to_vec(); + let mime = mime_type.clone(); + let original_len = data.len(); + let result = tokio::task::spawn_blocking(move || -> (Vec, String, bool) { + match image::load_from_memory(&original_data) { + Ok(img) => { + let mut buf = std::io::Cursor::new(Vec::new()); + match img.write_to(&mut buf, image::ImageFormat::WebP) { + Ok(_) => { + let webp_data = buf.into_inner(); + if webp_data.len() < original_data.len() { + (webp_data, "webp".to_string(), true) + } else { + (original_data, mime_to_ext(&mime).to_string(), false) + } + } + Err(_) => (original_data, mime_to_ext(&mime).to_string(), false), } } + Err(_) => (original_data, mime_to_ext(&mime).to_string(), false), } - Err(e) => { - tracing::warn!("Image decode failed, storing raw bytes: {:?}", e); - let ext = match mime_type.as_str() { - "image/jpeg" => "jpg", - "image/png" => "png", - "image/webp" => "webp", - _ => "bin", - }; - (data.to_vec(), ext) + }) + .await; + + match result { + Ok((converted_data, ext, was_converted)) => { + if was_converted { + tracing::info!("Converted upload to WebP: {} bytes -> {} bytes", original_len, converted_data.len()); + } else { + tracing::info!("Keeping original format (ext: {})", ext); + } + (converted_data, ext) } + Err(_) => (data.to_vec(), mime_to_ext(&mime_type).to_string()), } }; diff --git a/src/components/post/post_footer.rs b/src/components/post/post_footer.rs index b5d5b23..b95896a 100644 --- a/src/components/post/post_footer.rs +++ b/src/components/post/post_footer.rs @@ -8,7 +8,7 @@ use crate::router::Route; #[component] pub fn PostFooter(post: Post) -> Element { let tags = post.tags.clone(); - + rsx! { footer { class: "post-footer", if !tags.is_empty() { @@ -25,7 +25,7 @@ pub fn PostFooter(post: Post) -> Element { } if post.prev_post.is_some() || post.next_post.is_some() { - PostNavLinks { + PostNavLinks { prev: post.prev_post, next: post.next_post } diff --git a/src/components/post/post_nav_links.rs b/src/components/post/post_nav_links.rs index 2e7e9ce..4023d14 100644 --- a/src/components/post/post_nav_links.rs +++ b/src/components/post/post_nav_links.rs @@ -19,7 +19,7 @@ pub fn PostNavLinks(prev: Option, next: Option) -> Element { } else { span { class: "prev" } } - + if let Some(next_post) = next { Link { class: "next",