use std::sync::Arc;
const TEXT_MIME_PREFIX: &str = "text/";
const TEXT_EXTENSIONS: &[&str] = &[
"c", "cfg", "conf", "cpp", "css", "csv", "env", "go", "h", "hpp", "html", "ini", "java", "js",
"json", "log", "md", "py", "rs", "sh", "sql", "toml", "ts", "tsx", "txt", "xml", "yaml", "yml",
];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreviewRequest {
pub relative_path: String,
pub file_name: String,
pub extension: Option<String>,
pub mime_type: String,
pub raw_url: String,
pub text_excerpt: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreviewDocument {
pub eyebrow: String,
pub title: String,
pub subtitle: String,
pub body_html: String,
}
impl PreviewDocument {
pub fn unsupported(request: &PreviewRequest) -> Self {
Self {
eyebrow: "Preview".to_string(),
title: request.file_name.clone(),
subtitle: request.mime_type.clone(),
body_html: format!(
"<div class=\"preview-empty\"><p>This file type does not have a built-in inline renderer yet.</p></div>"
),
}
}
}
pub trait PreviewHandler: Send + Sync {
fn render(&self, request: &PreviewRequest) -> Option<PreviewDocument>;
}
#[derive(Clone, Default)]
pub struct PreviewRegistry {
handlers: Vec<Arc<dyn PreviewHandler>>,
}
impl PreviewRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with_handler(mut self, handler: impl PreviewHandler + 'static) -> Self {
self.handlers.push(Arc::new(handler));
self
}
pub fn render(&self, request: &PreviewRequest) -> PreviewDocument {
for handler in &self.handlers {
if let Some(document) = handler.render(request) {
return document;
}
}
built_in_preview(request).unwrap_or_else(|| PreviewDocument::unsupported(request))
}
}
fn built_in_preview(request: &PreviewRequest) -> Option<PreviewDocument> {
let escaped_name = html_escape(&request.file_name);
if is_text_previewable(request) {
return Some(text_preview(request));
}
if request.mime_type.starts_with("image/") {
return Some(media_preview(
request,
"Image preview",
format!(
"<img class=\"preview-media\" src=\"{}\" alt=\"{}\">",
request.raw_url, escaped_name
),
));
}
if request.mime_type == "application/pdf" {
return Some(media_preview(
request,
"PDF preview",
format!(
"<iframe class=\"preview-frame\" src=\"{}\" title=\"{}\"></iframe>",
request.raw_url, escaped_name
),
));
}
if request.mime_type.starts_with("audio/") {
return Some(media_preview(
request,
"Audio preview",
format!(
"<audio class=\"preview-audio\" controls preload=\"metadata\" src=\"{}\"></audio>",
request.raw_url
),
));
}
if request.mime_type.starts_with("video/") {
return Some(media_preview(
request,
"Video preview",
format!(
"<video class=\"preview-video\" controls preload=\"metadata\" src=\"{}\"></video>",
request.raw_url
),
));
}
None
}
fn is_text_previewable(request: &PreviewRequest) -> bool {
request.mime_type.starts_with(TEXT_MIME_PREFIX)
|| matches!(
request.mime_type.as_str(),
"application/json"
| "application/javascript"
| "application/toml"
| "application/xml"
| "application/x-yaml"
)
|| request
.extension
.as_deref()
.map(|extension| TEXT_EXTENSIONS.contains(&extension))
.unwrap_or(false)
}
fn text_preview(request: &PreviewRequest) -> PreviewDocument {
let excerpt = request
.text_excerpt
.as_deref()
.map(html_escape)
.unwrap_or_else(|| "No text content available.".to_string());
PreviewDocument {
eyebrow: "Preview".to_string(),
title: request.file_name.clone(),
subtitle: request.mime_type.clone(),
body_html: format!("<pre class=\"preview-code\">{}</pre>", excerpt),
}
}
fn media_preview(request: &PreviewRequest, eyebrow: &str, body_html: String) -> PreviewDocument {
PreviewDocument {
eyebrow: eyebrow.to_string(),
title: request.file_name.clone(),
subtitle: request.mime_type.clone(),
body_html,
}
}
fn html_escape(input: &str) -> String {
input
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
#[cfg(test)]
mod tests {
use super::{PreviewDocument, PreviewHandler, PreviewRegistry, PreviewRequest};
struct CustomHandler;
impl PreviewHandler for CustomHandler {
fn render(&self, request: &PreviewRequest) -> Option<PreviewDocument> {
if request.extension.as_deref() == Some("special") {
Some(PreviewDocument {
eyebrow: "Custom".to_string(),
title: request.file_name.clone(),
subtitle: "Handled by custom extension".to_string(),
body_html: "<div>Custom preview</div>".to_string(),
})
} else {
None
}
}
}
fn request(extension: Option<&str>, mime_type: &str) -> PreviewRequest {
PreviewRequest {
relative_path: "demo/file".to_string(),
file_name: "file".to_string(),
extension: extension.map(str::to_string),
mime_type: mime_type.to_string(),
raw_url: "/raw".to_string(),
text_excerpt: Some("hello".to_string()),
}
}
#[test]
fn custom_handlers_override_builtins() {
let registry = PreviewRegistry::new().with_handler(CustomHandler);
let preview = registry.render(&request(Some("special"), "application/octet-stream"));
assert_eq!(preview.eyebrow, "Custom");
}
#[test]
fn built_in_text_preview_escapes_content() {
let registry = PreviewRegistry::new();
let mut request = request(Some("rs"), "text/plain");
request.text_excerpt = Some("<hello>".to_string());
let preview = registry.render(&request);
assert!(preview.body_html.contains("<hello>"));
}
}