feat: render .md files as HTML (pulldown-cmark), ?raw for plain text
This commit is contained in:
@@ -81,6 +81,7 @@ async fn list_root(Path(project_id): Path<String>) -> Result<Json<Vec<FileEntry>
|
||||
|
||||
async fn get_file(
|
||||
Path((project_id, file_path)): Path<(String, String)>,
|
||||
query: axum::extract::Query<std::collections::HashMap<String, String>>,
|
||||
) -> 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) => {
|
||||
// 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()
|
||||
}
|
||||
Err(_) => (StatusCode::NOT_FOUND, "File not found").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#"<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="utf-8">
|
||||
<title>{title}</title>
|
||||
<style>
|
||||
body {{ max-width: 800px; margin: 40px auto; padding: 0 20px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; }}
|
||||
h1,h2,h3 {{ border-bottom: 1px solid #eee; padding-bottom: 0.3em; }}
|
||||
pre {{ background: #f6f8fa; padding: 16px; border-radius: 6px; overflow-x: auto; }}
|
||||
code {{ background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }}
|
||||
pre code {{ background: none; padding: 0; }}
|
||||
table {{ border-collapse: collapse; }} th,td {{ border: 1px solid #ddd; padding: 8px 12px; }}
|
||||
blockquote {{ border-left: 4px solid #ddd; margin: 0; padding-left: 16px; color: #666; }}
|
||||
</style>
|
||||
</head><body>{html_out}</body></html>"#)
|
||||
}
|
||||
|
||||
async fn do_upload(project_id: &str, rel_dir: &str, mut multipart: Multipart) -> Response {
|
||||
|
||||
Reference in New Issue
Block a user