From 902b5192d58c362016589d49508660edc1f4e83e Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Mon, 6 Apr 2026 20:06:35 +0100 Subject: [PATCH] feat: render .md files as HTML (pulldown-cmark), ?raw for plain text --- src/api/files.rs | 49 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/api/files.rs b/src/api/files.rs index f8feb7e..9970f0c 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -81,6 +81,7 @@ async fn list_root(Path(project_id): Path) -> Result async fn get_file( Path((project_id, file_path)): Path<(String, String)>, + query: axum::extract::Query>, ) -> Response { let full = match resolve_path(&project_id, &file_path) { Ok(p) => p, @@ -95,16 +96,46 @@ async fn get_file( }; } - // Otherwise serve the file - match tokio::fs::read(&full).await { - Ok(bytes) => { - let mime = mime_guess::from_path(&full) - .first_or_octet_stream() - .to_string(); - ([(axum::http::header::CONTENT_TYPE, mime)], bytes).into_response() - } - Err(_) => (StatusCode::NOT_FOUND, "File not found").into_response(), + // Read file + let bytes = match tokio::fs::read(&full).await { + Ok(b) => b, + Err(_) => return (StatusCode::NOT_FOUND, "File not found").into_response(), + }; + + // Render markdown as HTML if file is .md + let is_md = full.extension().is_some_and(|e| e == "md"); + if is_md && !query.contains_key("raw") { + let md_text = String::from_utf8_lossy(&bytes); + let html = render_markdown(&md_text, &file_path); + return ([(axum::http::header::CONTENT_TYPE, "text/html; charset=utf-8".to_string())], html).into_response(); } + + let mime = mime_guess::from_path(&full) + .first_or_octet_stream() + .to_string(); + ([(axum::http::header::CONTENT_TYPE, mime)], bytes).into_response() +} + +fn render_markdown(md: &str, title: &str) -> String { + use pulldown_cmark::{Parser, Options, html}; + let opts = Options::ENABLE_TABLES | Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TASKLISTS; + let parser = Parser::new_ext(md, opts); + let mut html_out = String::new(); + html::push_html(&mut html_out, parser); + format!(r#" + + +{title} + +{html_out}"#) } async fn do_upload(project_id: &str, rel_dir: &str, mut multipart: Multipart) -> Response {