add directory
This commit is contained in:
parent
700fc80bc5
commit
cc2a4f91bf
135
src/main.rs
135
src/main.rs
@ -17,13 +17,11 @@ use futures::StreamExt;
|
||||
use std::convert::Infallible;
|
||||
use std::time::Duration;
|
||||
|
||||
// Импорт для syntect
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::html::{styled_line_to_highlighted_html, IncludeBackground};
|
||||
use syntect::parsing::SyntaxSet;
|
||||
|
||||
/// Храним тяжелые ресурсы и канал для уведомлений
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
syntax_set: Arc<SyntaxSet>,
|
||||
@ -141,6 +139,18 @@ async fn serve_file(
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
let metadata = match fs::metadata(&safe_path).await {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!("Ошибка получения метаданных: {}", e);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
if metadata.is_dir() {
|
||||
return render_directory_index(&safe_path, &full_path).await;
|
||||
}
|
||||
|
||||
let content = match fs::read_to_string(&safe_path).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
@ -154,6 +164,122 @@ async fn serve_file(
|
||||
Ok(Html(html_content))
|
||||
}
|
||||
|
||||
async fn render_directory_index(
|
||||
dir_path: &PathBuf,
|
||||
request_path: &str,
|
||||
) -> Result<Html<String>, StatusCode> {
|
||||
let mut entries = match fs::read_dir(dir_path).await {
|
||||
Ok(list) => list,
|
||||
Err(e) => {
|
||||
eprintln!("Ошибка чтения директории: {}", e);
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
};
|
||||
|
||||
let mut files: Vec<(String, String, bool)> = Vec::new(); // (name, link, is_dir)
|
||||
|
||||
while let Some(entry) = entries.next_entry().await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? {
|
||||
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||
|
||||
// Пропускаем скрытые файлы (начинающиеся с точки)
|
||||
if file_name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_dir = entry.metadata().await.map(|m| m.is_dir()).unwrap_or(false);
|
||||
|
||||
// Формируем ссылку
|
||||
let mut link_path = request_path.to_string();
|
||||
if !link_path.ends_with('/') {
|
||||
link_path.push('/');
|
||||
}
|
||||
link_path.push_str(&file_name);
|
||||
|
||||
files.push((file_name, link_path, is_dir));
|
||||
}
|
||||
|
||||
// Сортировка: сначала директории, потом файлы, по алфавиту
|
||||
files.sort_by(|a, b| {
|
||||
match (a.2, b.2) {
|
||||
(true, false) => std::cmp::Ordering::Less, // Директории первыми
|
||||
(false, true) => std::cmp::Ordering::Greater,
|
||||
_ => a.0.cmp(&b.0), // Затем по имени
|
||||
}
|
||||
});
|
||||
|
||||
let mut list_html = String::from("<ul style=\"list-style-type: none; padding: 0; margin: 0;\">");
|
||||
|
||||
// Добавляем ссылку на родительскую директорию, если мы не в корне
|
||||
if request_path != "files" && !request_path.is_empty() {
|
||||
let parent_path = request_path.rsplit_once('/').map(|(p, _)| p).unwrap_or("");
|
||||
let parent_link = if parent_path.is_empty() { "/".to_string() } else { format!("/{}", parent_path) };
|
||||
|
||||
list_html.push_str(&format!(
|
||||
r#"<li style="padding: 10px 0; border-bottom: 1px solid #333;">
|
||||
<a href="{}" style="color: #90a4ae; font-weight: bold; text-decoration: none !important; display: block;">📁 ..</a>
|
||||
</li>"#,
|
||||
parent_link
|
||||
));
|
||||
}
|
||||
|
||||
for (name, link, is_dir) in files {
|
||||
let icon = if is_dir { "📁" } else { "📄" };
|
||||
// Новый цвет: светло-голубой для файлов и папок
|
||||
let color = "#64b5f6";
|
||||
let hover_color = "#90caf9"; // Чуть светлее при наведении
|
||||
|
||||
list_html.push_str(&format!(
|
||||
r#"<li style="padding: 10px 0; border-bottom: 1px solid #333; transition: background-color 0.2s;">
|
||||
<a href="/{}" style="color: {}; text-decoration: none !important; font-size: 1.1em; display: flex; align-items: center;"
|
||||
onmouseover="this.style.color='{}'" onmouseout="this.style.color='{}'">
|
||||
<span style="margin-right: 10px; min-width: 24px;">{}</span>
|
||||
<span>{}</span>
|
||||
</a>
|
||||
</li>"#,
|
||||
link.trim_start_matches('/'),
|
||||
color,
|
||||
hover_color,
|
||||
color,
|
||||
icon,
|
||||
name
|
||||
));
|
||||
}
|
||||
list_html.push_str("</ul>");
|
||||
|
||||
let html_content = format!(
|
||||
r#"<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Обзор директории</title>
|
||||
<style>
|
||||
body {{ background-color: #121212; color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 40px 20px; display: flex; justify-content: center; }}
|
||||
.content {{ max-width: 800px; width: 100%; background-color: #1e1e1e; padding: 40px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); }}
|
||||
h1 {{ color: #ffffff; border-bottom: 1px solid #333; padding-bottom: 10px; margin-top: 0; }}
|
||||
/* Глобальный сброс подчеркивания для всех ссылок в этом шаблоне */
|
||||
a {{ text-decoration: none !important; transition: opacity 0.2s; }}
|
||||
a:hover {{ opacity: 0.8; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>📂 Обзор директории: /{}</h1>
|
||||
{}
|
||||
<p style="margin-top: 30px; color: #666; font-size: 0.9em; border-top: 1px solid #333; padding-top: 15px;">
|
||||
<a href="/" style="color: #757575;">← На главную</a>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>"#,
|
||||
request_path.trim_start_matches('/'),
|
||||
list_html
|
||||
);
|
||||
|
||||
Ok(Html(html_content))
|
||||
}
|
||||
// <--- КОНЕЦ ИЗМЕНЕНИЯ
|
||||
|
||||
/// Запуск наблюдателя за файловой системой
|
||||
async fn run_file_watcher(state: AppState) {
|
||||
let (tx_fs, mut rx_fs) = tokio::sync::mpsc::channel::<PathBuf>(100);
|
||||
@ -308,8 +434,7 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
||||
<style>
|
||||
body {{ background-color: #121212; color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 40px 20px; display: flex; justify-content: center; line-height: 1.6; }}
|
||||
.content {{ max-width: 800px; width: 100%; background-color: #1e1e1e; padding: 40px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); }}
|
||||
a {{ color: #bb86fc; text-decoration: none; }}
|
||||
a:hover {{ text-decoration: underline; }}
|
||||
a {{ color: #bb86fc; text-decoration: none !important; }}
|
||||
h1, h2, h3, h4 {{ color: #ffffff; margin-top: 1.5em; margin-bottom: 0.5em; }}
|
||||
h1 {{ border-bottom: 1px solid #333; padding-bottom: 10px; }}
|
||||
table {{ border-collapse: collapse; width: 100%; margin: 1em 0; }}
|
||||
@ -380,8 +505,6 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}}
|
||||
/* Принудительная темная тема для SVG внутри mermaid, если нужно,
|
||||
но лучше использовать конфиг mermaid.init ниже */
|
||||
|
||||
#status {{ position: fixed; top: 10px; right: 10px; padding: 5px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; }}
|
||||
.connected {{ background-color: #2ecc71; color: #000; }}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user