fix(copy-button): fix copy code button
This commit is contained in:
parent
b1114217e0
commit
f15e8166d1
@ -27,7 +27,7 @@ repos:
|
||||
- id: clippy
|
||||
name: clippy
|
||||
language: system
|
||||
entry: cargo clippy -- -W clippy::all -W clippy::pedantic
|
||||
entry: cargo clippy -- -D clippy::all -D clippy::pedantic
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
|
||||
|
||||
2
TODO.md
2
TODO.md
@ -2,3 +2,5 @@
|
||||
|
||||
* [X] ~~Сделать кнопку скопировать путь~~
|
||||
* [X] ~~Сделать кнопку скопировать путь вместе с командой (`note edit ...`)~~
|
||||
* [X] ~~Сделать отображение типа кода и кнопку скопировать код~~
|
||||
* [ ] Починить заголовки таблицы
|
||||
|
||||
62
src/main.rs
62
src/main.rs
@ -28,7 +28,6 @@ use syntect::parsing::SyntaxSet;
|
||||
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use webbrowser;
|
||||
|
||||
use clap::Parser;
|
||||
use std::io;
|
||||
@ -152,10 +151,8 @@ async fn main() {
|
||||
|
||||
if args.browser {
|
||||
let mut url = format!("http://{actual_addr}");
|
||||
if args.random {
|
||||
if !state.is_root_file {
|
||||
url = format!("{url}/random")
|
||||
}
|
||||
if args.random && !state.is_root_file {
|
||||
url = format!("{url}/random");
|
||||
}
|
||||
let _ = webbrowser::open(url.as_str());
|
||||
}
|
||||
@ -185,12 +182,9 @@ async fn root_handler(State(state): State<AppState>) -> impl IntoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
async fn render_single_file(
|
||||
file_path: &StdPath,
|
||||
) -> Result<NoteTemplate, StatusCode> {
|
||||
let metadata = match fs::metadata(file_path).await {
|
||||
Ok(m) => m,
|
||||
Err(_) => return Err(StatusCode::NOT_FOUND),
|
||||
async fn render_single_file(file_path: &StdPath) -> Result<NoteTemplate, StatusCode> {
|
||||
let Ok(metadata) = fs::metadata(file_path).await else {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
};
|
||||
|
||||
if metadata.is_dir() {
|
||||
@ -203,9 +197,8 @@ async fn render_single_file(
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
|
||||
let content = match fs::read_to_string(file_path).await {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
let Ok(content) = fs::read_to_string(file_path).await else {
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
};
|
||||
|
||||
let display_path = "";
|
||||
@ -216,12 +209,7 @@ async fn render_single_file(
|
||||
let html_content = if extension == "md" {
|
||||
markdown_to_html(&content, display_path)
|
||||
} else {
|
||||
code_to_html(
|
||||
&content,
|
||||
extension.as_str(),
|
||||
&ss,
|
||||
&ts,
|
||||
)
|
||||
code_to_html(&content, extension.as_str(), &ss, &ts)
|
||||
};
|
||||
|
||||
let filename = file_path
|
||||
@ -275,9 +263,8 @@ async fn serve_file(
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
let metadata = match fs::metadata(&safe_path).await {
|
||||
Ok(m) => m,
|
||||
Err(_) => return Err(StatusCode::NOT_FOUND),
|
||||
let Ok(metadata) = fs::metadata(&safe_path).await else {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
};
|
||||
|
||||
if metadata.is_dir() {
|
||||
@ -299,17 +286,15 @@ async fn serve_file(
|
||||
);
|
||||
|
||||
if is_image {
|
||||
let file_content = match fs::read(&safe_path).await {
|
||||
Ok(content) => content,
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
let Ok(file_content) = fs::read(&safe_path).await else {
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
};
|
||||
let mime_type = from_path(&safe_path).first_or_octet_stream();
|
||||
return Ok(([(header::CONTENT_TYPE, mime_type.as_ref())], file_content).into_response());
|
||||
}
|
||||
|
||||
let content = match fs::read_to_string(&safe_path).await {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
let Ok(content) = fs::read_to_string(&safe_path).await else {
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
};
|
||||
|
||||
let ss = SyntaxSet::load_defaults_newlines();
|
||||
@ -318,12 +303,7 @@ async fn serve_file(
|
||||
let html_content = if extension == "md" {
|
||||
markdown_to_html(&content, &full_path)
|
||||
} else {
|
||||
code_to_html(
|
||||
&content,
|
||||
extension.as_str(),
|
||||
&ss,
|
||||
&ts,
|
||||
)
|
||||
code_to_html(&content, extension.as_str(), &ss, &ts)
|
||||
};
|
||||
|
||||
let filename = safe_path
|
||||
@ -365,9 +345,8 @@ async fn render_directory_index(
|
||||
dir_path: &StdPath,
|
||||
request_path: &str,
|
||||
) -> Result<DirectoryTemplate, StatusCode> {
|
||||
let mut entries = match fs::read_dir(dir_path).await {
|
||||
Ok(list) => list,
|
||||
Err(_) => return Err(StatusCode::FORBIDDEN),
|
||||
let Ok(mut entries) = fs::read_dir(dir_path).await else {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
};
|
||||
|
||||
let mut files: Vec<FileEntry> = Vec::new();
|
||||
@ -388,7 +367,7 @@ async fn render_directory_index(
|
||||
let link = if request_path.is_empty() {
|
||||
file_name.clone()
|
||||
} else {
|
||||
format!("{}/{}", request_path, file_name)
|
||||
format!("{request_path}/{file_name}")
|
||||
};
|
||||
|
||||
files.push(FileEntry {
|
||||
@ -513,9 +492,8 @@ async fn random_file(State(state): State<AppState>) -> impl IntoResponse {
|
||||
let mut rng = rand::thread_rng();
|
||||
let random_path = files.choose(&mut rng).unwrap();
|
||||
|
||||
let relative_path = match random_path.strip_prefix(&state.root) {
|
||||
Ok(p) => p,
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
let Ok(relative_path) = random_path.strip_prefix(&state.root) else {
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
};
|
||||
|
||||
let url_path = relative_path.to_string_lossy().replace('\\', "/");
|
||||
|
||||
148
src/markdown.rs
148
src/markdown.rs
@ -3,6 +3,8 @@ use pulldown_cmark::{
|
||||
TextMergeStream,
|
||||
};
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
pub fn markdown_to_html(markdown: &str, _file_path: &str) -> String {
|
||||
let mut options = Options::empty();
|
||||
options.insert(Options::ENABLE_TABLES);
|
||||
@ -51,21 +53,21 @@ pub fn markdown_to_html(markdown: &str, _file_path: &str) -> String {
|
||||
Event::Code(code) => {
|
||||
html_output.push_str(format!("<code>{}</code>", escape_html(&code)).as_str());
|
||||
}
|
||||
Event::SoftBreak => html_output.push_str(" "),
|
||||
Event::SoftBreak => html_output.push(' '),
|
||||
Event::HardBreak => html_output.push_str("<br>"),
|
||||
|
||||
Event::Html(html) => html_output.push_str(&html),
|
||||
|
||||
Event::Rule => html_output.push_str("<hr>"),
|
||||
|
||||
Event::FootnoteReference(fref) => html_output.push_str(&format!(
|
||||
"<sup><a href=\"#{}\" id=\"{}\"></a></sup>",
|
||||
fref, fref
|
||||
)),
|
||||
Event::FootnoteReference(fref) => {
|
||||
let _ = write!(
|
||||
html_output,
|
||||
"<sup><a href=\"#{fref}\" id=\"{fref}\"></a></sup>"
|
||||
);
|
||||
}
|
||||
|
||||
Event::InlineHtml(_) => (),
|
||||
Event::InlineMath(_) => (),
|
||||
Event::DisplayMath(_) => (),
|
||||
Event::InlineHtml(_) | Event::InlineMath(_) | Event::DisplayMath(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,31 +96,30 @@ fn render_start_tag(tag: Tag<'_>) -> String {
|
||||
dest_url,
|
||||
title,
|
||||
..
|
||||
} => output.push_str(render_link(link_type, dest_url, title).as_str()),
|
||||
} => output.push_str(render_link(link_type, &dest_url, &title).as_str()),
|
||||
Tag::Image {
|
||||
link_type,
|
||||
dest_url,
|
||||
title,
|
||||
..
|
||||
} => output.push_str(render_image(link_type, dest_url, title).as_str()),
|
||||
} => output.push_str(render_image(link_type, &dest_url, &title).as_str()),
|
||||
|
||||
Tag::Table(_) => output.push_str("<table>"), // TODO: fix table
|
||||
Tag::Table(_) => output.push_str("<table>"),
|
||||
Tag::TableHead => output.push_str("<thead>"),
|
||||
Tag::TableCell => output.push_str("<td>"),
|
||||
Tag::TableRow => output.push_str("<tr>"),
|
||||
|
||||
Tag::HtmlBlock => (), // TODO: implement
|
||||
|
||||
Tag::FootnoteDefinition(fref) => {
|
||||
output.push_str(&format!("<sup><a href=\"#{}\" id=\"{}\">", fref, fref))
|
||||
let _ = write!(output, "<sup><a href=\"#{fref}\" id=\"{fref}\">");
|
||||
}
|
||||
|
||||
Tag::DefinitionList => (),
|
||||
Tag::DefinitionListTitle => (),
|
||||
Tag::DefinitionListDefinition => (),
|
||||
Tag::Superscript => (),
|
||||
Tag::Subscript => (),
|
||||
Tag::MetadataBlock(_) => (),
|
||||
Tag::HtmlBlock
|
||||
| Tag::DefinitionList
|
||||
| Tag::DefinitionListTitle
|
||||
| Tag::DefinitionListDefinition
|
||||
| Tag::Superscript
|
||||
| Tag::Subscript
|
||||
| Tag::MetadataBlock(_) => (),
|
||||
}
|
||||
|
||||
output
|
||||
@ -137,7 +138,7 @@ fn render_end_tag(tag: TagEnd) -> String {
|
||||
TagEnd::List(false) => output.push_str("</ul>"),
|
||||
TagEnd::Item => output.push_str("</li>"),
|
||||
TagEnd::Paragraph => output.push_str("</p>"),
|
||||
TagEnd::CodeBlock => output.push_str("</code></pre>"),
|
||||
TagEnd::CodeBlock => output.push_str("</code></pre></div>"),
|
||||
TagEnd::Emphasis => output.push_str("</em>"),
|
||||
TagEnd::Strong => output.push_str("</strong>"),
|
||||
TagEnd::BlockQuote(_) => output.push_str("</blockquote>"),
|
||||
@ -150,78 +151,81 @@ fn render_end_tag(tag: TagEnd) -> String {
|
||||
TagEnd::TableRow => output.push_str("</tr>"),
|
||||
|
||||
TagEnd::FootnoteDefinition => output.push_str("</a></sup>"),
|
||||
TagEnd::HtmlBlock => (), // TODO: implement
|
||||
|
||||
TagEnd::DefinitionList => (),
|
||||
TagEnd::DefinitionListTitle => (),
|
||||
TagEnd::DefinitionListDefinition => (),
|
||||
TagEnd::Superscript => (),
|
||||
TagEnd::Subscript => (),
|
||||
TagEnd::MetadataBlock(_) => (),
|
||||
TagEnd::HtmlBlock
|
||||
| TagEnd::DefinitionList
|
||||
| TagEnd::DefinitionListTitle
|
||||
| TagEnd::DefinitionListDefinition
|
||||
| TagEnd::Superscript
|
||||
| TagEnd::Subscript
|
||||
| TagEnd::MetadataBlock(_) => (),
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn render_codeblock(block_kind: CodeBlockKind<'_>) -> String {
|
||||
let header = String::from(
|
||||
r#"
|
||||
<div class="code-block-wrapper">
|
||||
<div class="code-header">
|
||||
<span class="code-lang">%CODE%</span> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
||||
</div>
|
||||
<pre>"#,
|
||||
);
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
match block_kind {
|
||||
CodeBlockKind::Indented => "<pre><code>".to_string(),
|
||||
CodeBlockKind::Indented => {
|
||||
output.push_str(&header.replace("%CODE%", "code"));
|
||||
output.push_str("<code>");
|
||||
}
|
||||
|
||||
CodeBlockKind::Fenced(CowStr::Borrowed("mermaid")) => {
|
||||
"<pre><code class=\"language-text mermaid\">".to_string()
|
||||
output.push_str(&header.replace("%CODE%", "mermaid"));
|
||||
output.push_str("<code class=\"language-text mermaid\">");
|
||||
}
|
||||
|
||||
CodeBlockKind::Fenced(CowStr::Borrowed("rawmermaid")) => {
|
||||
"<pre><code class=\"language-text\">".to_string()
|
||||
output.push_str(&header.replace("%CODE%", "mermaid"));
|
||||
output.push_str("<code class=\"language-text\">");
|
||||
}
|
||||
CodeBlockKind::Fenced(code_type) => format!("<pre><code class=\"language-{}\">", code_type),
|
||||
|
||||
CodeBlockKind::Fenced(code_type) => {
|
||||
output.push_str(&header.replace("%CODE%", &code_type));
|
||||
output.push_str(format!("<code class=\"language-{code_type}\">").as_str());
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn render_blockquote(quote_kind: Option<BlockQuoteKind>) -> String {
|
||||
match quote_kind {
|
||||
None => {
|
||||
"<blockquote>".to_string()
|
||||
},
|
||||
Some(BlockQuoteKind::Note) => "<blockquote><div class=\"markdown-alert markdown-alert-note\"><p class=\"markdown-alert-title\">Note</p>".to_string(),
|
||||
Some(BlockQuoteKind::Tip) => "<blockquote><div class=\"markdown-alert markdown-alert-tip\"><p class=\"markdown-alert-title\">Tip</p>".to_string(),
|
||||
Some(BlockQuoteKind::Important) =>"<blockquote><div class=\"markdown-alert markdown-alert-important\"><p class=\"markdown-alert-title\">Important</p>".to_string(),
|
||||
Some(BlockQuoteKind::Warning) => "<blockquote><div class=\"markdown-alert markdown-alert-warning\"><p class=\"markdown-alert-title\">Warning</p>".to_string(),
|
||||
Some(BlockQuoteKind::Caution) => "<blockquote><div class=\"markdown-alert markdown-alert-caution\"><p class=\"markdown-alert-title\">Caution</p>".to_string(),
|
||||
let alert_class_and_title = match quote_kind {
|
||||
None => return "<blockquote>".to_string(),
|
||||
Some(BlockQuoteKind::Note) => ("note", "Note"),
|
||||
Some(BlockQuoteKind::Tip) => ("tip", "Tip"),
|
||||
Some(BlockQuoteKind::Important) => ("important", "Important"),
|
||||
Some(BlockQuoteKind::Warning) => ("warning", "Warning"),
|
||||
Some(BlockQuoteKind::Caution) => ("caution", "Caution"),
|
||||
};
|
||||
|
||||
format!(
|
||||
"<blockquote><div class=\"markdown-alert markdown-alert-{}\"><p class=\"markdown-alert-title\">{}</p>",
|
||||
alert_class_and_title.0, alert_class_and_title.1
|
||||
)
|
||||
}
|
||||
}
|
||||
fn render_link(link_type: LinkType, dest_url: CowStr<'_>, title: CowStr<'_>) -> String {
|
||||
|
||||
fn render_link(link_type: LinkType, dest_url: &CowStr<'_>, title: &CowStr<'_>) -> String {
|
||||
match link_type {
|
||||
LinkType::Inline => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::Reference => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::ReferenceUnknown => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::Collapsed => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::CollapsedUnknown => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::Shortcut => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::ShortcutUnknown => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::Autolink => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::Email => format!("<a href=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::WikiLink { has_pothole: _ } => {
|
||||
// TODO: refactor
|
||||
format!("<a href=\"{}\" title=\"{}\">", dest_url, title)
|
||||
}
|
||||
LinkType::Email => format!("<a href=\"mailto:{dest_url}\" title=\"{title}\">"),
|
||||
_ => format!("<a href=\"{dest_url}\" title=\"{title}\">"),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_image(link_type: LinkType, dest_url: CowStr<'_>, title: CowStr<'_>) -> String {
|
||||
match link_type {
|
||||
LinkType::Inline => format!("<img src=\"{}\" alt=\"{}\">", dest_url, title),
|
||||
LinkType::Reference => format!("<img src=\"{}\" title=\"{}\">", dest_url, title),
|
||||
LinkType::ReferenceUnknown => format!("<img src=\"{}\" alt=\"{}\">", dest_url, title),
|
||||
LinkType::Collapsed => format!("<img src=\"{}\" alt=\"{}\">", dest_url, title),
|
||||
LinkType::CollapsedUnknown => format!("<img src=\"{}\" alt=\"{}\">", dest_url, title),
|
||||
LinkType::Shortcut => format!("<img src=\"{}\" alt=\"{}\">", dest_url, title),
|
||||
LinkType::ShortcutUnknown => format!("<img src=\"{}\" alt=\"{}\">", dest_url, title),
|
||||
LinkType::Autolink => format!("<img src=\"{}\" alt=\"{}\">", dest_url, title),
|
||||
LinkType::Email => format!("<img src=\"{}\" alt=\"{}\">", dest_url, title),
|
||||
LinkType::WikiLink { has_pothole: _ } => {
|
||||
// TODO: refactor
|
||||
format!("<img src=\"{}\" alt=\"{}\">", dest_url, title)
|
||||
}
|
||||
}
|
||||
fn render_image(_link_type: LinkType, dest_url: &CowStr<'_>, title: &CowStr<'_>) -> String {
|
||||
format!("<img src=\"{dest_url}\" alt=\"{title}\">")
|
||||
}
|
||||
|
||||
fn escape_html(text: &str) -> String {
|
||||
|
||||
12
src/other.rs
12
src/other.rs
@ -8,8 +8,8 @@ use syntect::parsing::SyntaxSet;
|
||||
/// # Аргументы
|
||||
/// * `code` - Исходный код как строка.
|
||||
/// * `lang` - Идентификатор языка (например, "rust", "python", "mermaid").
|
||||
/// * `ss` - Набор синтаксисов (SyntaxSet).
|
||||
/// * `ts` - Набор тем (ThemeSet).
|
||||
/// * `ss` - Набор синтаксисов (`SyntaxSet`).
|
||||
/// * `ts` - Набор тем (`ThemeSet`).
|
||||
///
|
||||
/// # Возвращает
|
||||
/// Строку HTML, содержащую обертку блока кода с заголовком и кнопкой копирования.
|
||||
@ -32,8 +32,9 @@ pub fn code_to_html(code: &str, lang: &str, ss: &SyntaxSet, ts: &ThemeSet) -> St
|
||||
let lang_display = if lang.is_empty() { "text" } else { lang };
|
||||
let lang_escaped = escape_html(lang_display);
|
||||
|
||||
let highlighted_html = if !lang.is_empty() {
|
||||
if let Some(syntax) = ss.find_syntax_by_token(lang) {
|
||||
let highlighted_html = if lang.is_empty() {
|
||||
escape_html(code)
|
||||
} else if let Some(syntax) = ss.find_syntax_by_token(lang) {
|
||||
let mut h = HighlightLines::new(syntax, theme);
|
||||
let mut result_html = String::new();
|
||||
|
||||
@ -55,9 +56,6 @@ pub fn code_to_html(code: &str, lang: &str, ss: &SyntaxSet, ts: &ThemeSet) -> St
|
||||
result_html
|
||||
} else {
|
||||
escape_html(code)
|
||||
}
|
||||
} else {
|
||||
escape_html(code)
|
||||
};
|
||||
|
||||
format!(
|
||||
|
||||
@ -55,6 +55,7 @@
|
||||
overflow: hidden;
|
||||
background-color: #2b303b;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user