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 {
|
match res {
|
||||||
Ok(changed_path) => {
|
Ok(changed_path) => {
|
||||||
if changed_path.contains(&req_path) {
|
if changed_path.contains(&req_path) {
|
||||||
// Явно указываем типы: Ok<Event, Infallible>
|
|
||||||
Some(Ok::<axum::response::sse::Event, Infallible>(
|
Some(Ok::<axum::response::sse::Event, Infallible>(
|
||||||
axum::response::sse::Event::default()
|
axum::response::sse::Event::default()
|
||||||
.event("reload")
|
.event("reload")
|
||||||
@ -115,6 +114,7 @@ async fn sse_handler(
|
|||||||
|
|
||||||
(headers, sse)
|
(headers, sse)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_file(
|
async fn serve_file(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(full_path): Path<String>,
|
Path(full_path): Path<String>,
|
||||||
@ -156,18 +156,14 @@ async fn serve_file(
|
|||||||
|
|
||||||
/// Запуск наблюдателя за файловой системой
|
/// Запуск наблюдателя за файловой системой
|
||||||
async fn run_file_watcher(state: AppState) {
|
async fn run_file_watcher(state: AppState) {
|
||||||
// Создаем канал MPSC для передачи путей из колбэка notify в основной цикл
|
|
||||||
let (tx_fs, mut rx_fs) = tokio::sync::mpsc::channel::<PathBuf>(100);
|
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 tx_fs_clone = tx_fs.clone();
|
||||||
let mut watcher = RecommendedWatcher::new(
|
let mut watcher = RecommendedWatcher::new(
|
||||||
move |res: Result<notify::Event, notify::Error>| {
|
move |res: Result<notify::Event, notify::Error>| {
|
||||||
if let Ok(event) = res {
|
if let Ok(event) = res {
|
||||||
if matches!(event.kind, EventKind::Modify(_)) {
|
if matches!(event.kind, EventKind::Modify(_)) {
|
||||||
for path in event.paths {
|
for path in event.paths {
|
||||||
// Используем клонированный отправитель внутри замыкания
|
|
||||||
let _ = tx_fs_clone.blocking_send(path);
|
let _ = tx_fs_clone.blocking_send(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,7 +180,6 @@ async fn run_file_watcher(state: AppState) {
|
|||||||
|
|
||||||
println!("Watcher запущен для директории: {:?}", watch_path);
|
println!("Watcher запущен для директории: {:?}", watch_path);
|
||||||
|
|
||||||
// Цикл обработки событий от файловой системы
|
|
||||||
while let Some(path) = rx_fs.recv().await {
|
while let Some(path) = rx_fs.recv().await {
|
||||||
if let Some(path_str) = path.to_str() {
|
if let Some(path_str) = path.to_str() {
|
||||||
let _ = state.tx.send(path_str.to_string());
|
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(_)) => {
|
Event::End(Tag::CodeBlock(_)) => {
|
||||||
in_code_block = false;
|
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 {
|
let highlighted_html = if let Some(lang) = ¤t_lang {
|
||||||
if let Some(syntax) = ss.find_syntax_by_token(lang) {
|
if let Some(syntax) = ss.find_syntax_by_token(lang) {
|
||||||
let mut h = HighlightLines::new(syntax, theme);
|
let mut h = HighlightLines::new(syntax, theme);
|
||||||
let mut result_html = String::new();
|
let mut result_html = String::new();
|
||||||
|
|
||||||
// Разбиваем код на строки и подсвечиваем каждую отдельно
|
|
||||||
for line in current_code.lines() {
|
for line in current_code.lines() {
|
||||||
// Добавляем перевод строки обратно, так как lines() удаляет его
|
|
||||||
let line_with_newline = format!("{}\n", line);
|
let line_with_newline = format!("{}\n", line);
|
||||||
match h.highlight_line(&line_with_newline, ss) {
|
match h.highlight_line(&line_with_newline, ss) {
|
||||||
Ok(regions) => {
|
Ok(regions) => {
|
||||||
@ -250,11 +249,21 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
|||||||
escape_html(¤t_code)
|
escape_html(¤t_code)
|
||||||
};
|
};
|
||||||
|
|
||||||
let full_html = format!(
|
// Формируем HTML с заголовком и кнопкой копирования
|
||||||
r#"<pre style="background-color: #2b303b; border-radius: 6px; padding: 15px; overflow-x: auto; margin: 1em 0; border: 1px solid #444;"><code>{}</code></pre>"#,
|
// Мы экранируем 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
|
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 => {
|
Event::Text(text) if in_code_block => {
|
||||||
current_code.push_str(&text);
|
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);
|
let sse_url = format!("/events/{}", file_path);
|
||||||
|
|
||||||
// Обратите внимание на двойные фигурные скобки {{ и }} для экранирования в format!
|
|
||||||
format!(
|
format!(
|
||||||
r#"<!DOCTYPE html>
|
r#"<!DOCTYPE html>
|
||||||
<html lang="ru">
|
<html lang="ru">
|
||||||
@ -292,8 +300,62 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
|||||||
th {{ background-color: #2c2c2c; }}
|
th {{ background-color: #2c2c2c; }}
|
||||||
blockquote {{ border-left: 4px solid #bb86fc; margin: 1em 0; padding-left: 1em; color: #aaa; background: #252525; padding: 10px; }}
|
blockquote {{ border-left: 4px solid #bb86fc; margin: 1em 0; padding-left: 1em; color: #aaa; background: #252525; padding: 10px; }}
|
||||||
code {{ font-family: 'Consolas', 'Monaco', monospace; }}
|
code {{ font-family: 'Consolas', 'Monaco', monospace; }}
|
||||||
|
|
||||||
|
/* Стили для обычных инлайн кодов */
|
||||||
p > code, li > code {{ background-color: #2c2c2c; padding: 2px 6px; border-radius: 4px; color: #ff79c6; }}
|
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; }}
|
#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; }}
|
.connected {{ background-color: #2ecc71; color: #000; }}
|
||||||
.disconnected {{ background-color: #e74c3c; color: #fff; }}
|
.disconnected {{ background-color: #e74c3c; color: #fff; }}
|
||||||
@ -323,6 +385,34 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
connect();
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>"#,
|
</html>"#,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user