add copy button
This commit is contained in:
parent
40bf0f413b
commit
d6a1a3e67b
116
src/main.rs
116
src/main.rs
@ -91,7 +91,6 @@ async fn sse_handler(
|
||||
match res {
|
||||
Ok(changed_path) => {
|
||||
if changed_path.contains(&req_path) {
|
||||
// Явно указываем типы: Ok<Event, Infallible>
|
||||
Some(Ok::<axum::response::sse::Event, Infallible>(
|
||||
axum::response::sse::Event::default()
|
||||
.event("reload")
|
||||
@ -115,6 +114,7 @@ async fn sse_handler(
|
||||
|
||||
(headers, sse)
|
||||
}
|
||||
|
||||
async fn serve_file(
|
||||
State(state): State<AppState>,
|
||||
Path(full_path): Path<String>,
|
||||
@ -156,18 +156,14 @@ async fn serve_file(
|
||||
|
||||
/// Запуск наблюдателя за файловой системой
|
||||
async fn run_file_watcher(state: AppState) {
|
||||
// Создаем канал MPSC для передачи путей из колбэка notify в основной цикл
|
||||
let (tx_fs, mut rx_fs) = tokio::sync::mpsc::channel::<PathBuf>(100);
|
||||
|
||||
// Настраиваем notify watcher
|
||||
// Перемещаем tx_fs внутрь замыкания через clone
|
||||
let tx_fs_clone = tx_fs.clone();
|
||||
let mut watcher = RecommendedWatcher::new(
|
||||
move |res: Result<notify::Event, notify::Error>| {
|
||||
if let Ok(event) = res {
|
||||
if matches!(event.kind, EventKind::Modify(_)) {
|
||||
for path in event.paths {
|
||||
// Используем клонированный отправитель внутри замыкания
|
||||
let _ = tx_fs_clone.blocking_send(path);
|
||||
}
|
||||
}
|
||||
@ -184,7 +180,6 @@ async fn run_file_watcher(state: AppState) {
|
||||
|
||||
println!("Watcher запущен для директории: {:?}", watch_path);
|
||||
|
||||
// Цикл обработки событий от файловой системы
|
||||
while let Some(path) = rx_fs.recv().await {
|
||||
if let Some(path_str) = path.to_str() {
|
||||
let _ = state.tx.send(path_str.to_string());
|
||||
@ -223,15 +218,19 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
||||
Event::End(Tag::CodeBlock(_)) => {
|
||||
in_code_block = false;
|
||||
|
||||
// Исправленная подсветка синтаксиса (построчно)
|
||||
// Определяем отображаемое имя языка
|
||||
let lang_display = current_lang.as_deref().unwrap_or("text");
|
||||
|
||||
// Экранируем имя языка для HTML атрибута и текста
|
||||
let lang_escaped = escape_html(lang_display);
|
||||
|
||||
// Подсветка синтаксиса (построчно)
|
||||
let highlighted_html = if let Some(lang) = ¤t_lang {
|
||||
if let Some(syntax) = ss.find_syntax_by_token(lang) {
|
||||
let mut h = HighlightLines::new(syntax, theme);
|
||||
let mut result_html = String::new();
|
||||
|
||||
// Разбиваем код на строки и подсвечиваем каждую отдельно
|
||||
for line in current_code.lines() {
|
||||
// Добавляем перевод строки обратно, так как lines() удаляет его
|
||||
let line_with_newline = format!("{}\n", line);
|
||||
match h.highlight_line(&line_with_newline, ss) {
|
||||
Ok(regions) => {
|
||||
@ -250,11 +249,21 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
||||
escape_html(¤t_code)
|
||||
};
|
||||
|
||||
let full_html = format!(
|
||||
r#"<pre style="background-color: #2b303b; border-radius: 6px; padding: 15px; overflow-x: auto; margin: 1em 0; border: 1px solid #444;"><code>{}</code></pre>"#,
|
||||
// Формируем HTML с заголовком и кнопкой копирования
|
||||
// Мы экранируем current_code еще раз для data-атрибута, хотя для копирования будем брать текст из pre
|
||||
let code_container = format!(
|
||||
r#"<div class="code-block-wrapper">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">{}</span>
|
||||
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
||||
</div>
|
||||
<pre style="margin: 0; border-radius: 0 0 6px 6px;"><code>{}</code></pre>
|
||||
</div>"#,
|
||||
lang_escaped,
|
||||
highlighted_html
|
||||
);
|
||||
processed_events.push(Event::Html(full_html.into()));
|
||||
|
||||
processed_events.push(Event::Html(code_container.into()));
|
||||
},
|
||||
Event::Text(text) if in_code_block => {
|
||||
current_code.push_str(&text);
|
||||
@ -272,7 +281,6 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
||||
|
||||
let sse_url = format!("/events/{}", file_path);
|
||||
|
||||
// Обратите внимание на двойные фигурные скобки {{ и }} для экранирования в format!
|
||||
format!(
|
||||
r#"<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
@ -292,8 +300,62 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
||||
th {{ background-color: #2c2c2c; }}
|
||||
blockquote {{ border-left: 4px solid #bb86fc; margin: 1em 0; padding-left: 1em; color: #aaa; background: #252525; padding: 10px; }}
|
||||
code {{ font-family: 'Consolas', 'Monaco', monospace; }}
|
||||
|
||||
/* Стили для обычных инлайн кодов */
|
||||
p > code, li > code {{ background-color: #2c2c2c; padding: 2px 6px; border-radius: 4px; color: #ff79c6; }}
|
||||
|
||||
/* Стили для блоков кода с подсветкой */
|
||||
.code-block-wrapper {{
|
||||
margin: 1em 0;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background-color: #2b303b;
|
||||
}}
|
||||
.code-header {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #232730;
|
||||
padding: 6px 12px;
|
||||
border-bottom: 1px solid #444;
|
||||
font-size: 0.85em;
|
||||
color: #a0a0a0;
|
||||
}}
|
||||
.code-lang {{
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
.copy-btn {{
|
||||
background: transparent;
|
||||
border: 1px solid #555;
|
||||
color: #ccc;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8em;
|
||||
transition: all 0.2s;
|
||||
}}
|
||||
.copy-btn:hover {{
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
border-color: #777;
|
||||
}}
|
||||
.copy-btn:active {{
|
||||
transform: scale(0.95);
|
||||
}}
|
||||
pre {{
|
||||
padding: 15px;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}}
|
||||
pre code {{
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}}
|
||||
|
||||
#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; }}
|
||||
.disconnected {{ background-color: #e74c3c; color: #fff; }}
|
||||
@ -323,6 +385,34 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
||||
}}
|
||||
|
||||
connect();
|
||||
|
||||
function copyCode(button) {{
|
||||
// Находим обертку, затем pre внутри неё
|
||||
const wrapper = button.closest('.code-block-wrapper');
|
||||
if (!wrapper) return;
|
||||
|
||||
const pre = wrapper.querySelector('pre');
|
||||
if (!pre) return;
|
||||
|
||||
// Получаем текстовое содержимое (без HTML тегов подсветки)
|
||||
const codeText = pre.innerText;
|
||||
|
||||
navigator.clipboard.writeText(codeText).then(() => {{
|
||||
const originalText = button.innerText;
|
||||
button.innerText = 'Copied!';
|
||||
button.style.borderColor = '#2ecc71';
|
||||
button.style.color = '#2ecc71';
|
||||
|
||||
setTimeout(() => {{
|
||||
button.innerText = originalText;
|
||||
button.style.borderColor = '';
|
||||
button.style.color = '';
|
||||
}}, 2000);
|
||||
}}).catch(err => {{
|
||||
console.error('Ошибка копирования:', err);
|
||||
button.innerText = 'Error';
|
||||
}});
|
||||
}}
|
||||
</script>
|
||||
</body>
|
||||
</html>"#,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user