diff --git a/src/markdown.rs b/src/markdown.rs index 2a5abf6..396ab21 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -4,8 +4,64 @@ use syntect::highlighting::ThemeSet; use syntect::html::{IncludeBackground, styled_line_to_highlighted_html}; use syntect::parsing::SyntaxSet; +fn normalize_lists(input: &str) -> String { + let mut result = String::with_capacity(input.len() + input.len() / 10); + + for line in input.lines() { + if let Some(first_non_ws) = line.find(|c: char| !c.is_whitespace()) { + let marker_char = line.chars().nth(first_non_ws); + + if let Some(marker) = marker_char { + if marker == '*' || marker == '-' || marker == '+' { + let rest_start = first_non_ws + 1; + + if rest_start < line.len() { + let next_char = line.chars().nth(rest_start).unwrap(); + + if !next_char.is_whitespace() { + let mut marker_count = 1; + let mut is_only_markers = true; + + for c in line.chars().skip(rest_start) { + if c == marker { + marker_count += 1; + } else if !c.is_whitespace() { + is_only_markers = false; + break; + } + } + + if !(is_only_markers && marker_count >= 2) { + result.push_str(&line[..rest_start]); + result.push(' '); + result.push_str(&line[rest_start..]); + result.push('\n'); + continue; + } + } + } + } + } + } + + result.push_str(line); + result.push('\n'); + } + + if result.ends_with('\n') && !input.ends_with('\n') { + result.pop(); + } + + result +} + pub fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &str) -> String { - let theme = &ts.themes["base16-ocean.dark"]; + let normalized_markdown = normalize_lists(markdown); + + let theme = ts + .themes + .get("base16-ocean.dark") + .unwrap_or_else(|| ts.themes.values().next().expect("No themes available")); let mut options = Options::empty(); options.insert(Options::ENABLE_TABLES); @@ -14,7 +70,7 @@ pub fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_pat options.insert(Options::ENABLE_TASKLISTS); options.insert(Options::ENABLE_SMART_PUNCTUATION); - let parser = pulldown_cmark::Parser::new_ext(markdown, options); + let parser = pulldown_cmark::Parser::new_ext(&normalized_markdown, options); let mut processed_events: Vec = Vec::new(); let mut in_code_block = false; @@ -23,6 +79,10 @@ pub fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_pat for event in parser { match event { + Event::Rule => { + continue; + } + Event::Start(Tag::CodeBlock(kind)) => { in_code_block = true; current_code.clear(); @@ -56,19 +116,21 @@ pub fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_pat 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() { - let line_with_newline = format!("{line}\n"); - match h.highlight_line(&line_with_newline, ss) { + + let lines: Vec<&str> = current_code.lines().collect(); + + for line in lines.iter() { + match h.highlight_line(&line, ss) { Ok(regions) => { let html_line = styled_line_to_highlighted_html( ®ions[..], IncludeBackground::No, ) - .unwrap_or_else(|_| escape_html(&line_with_newline)); + .unwrap_or_else(|_| escape_html(&line)); result_html.push_str(&html_line); } Err(_) => { - result_html.push_str(&escape_html(&line_with_newline)); + result_html.push_str(&escape_html(&line)); } } } @@ -104,6 +166,7 @@ pub fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_pat } let mut body_html = String::new(); + html::push_html(&mut body_html, processed_events.into_iter()); body_html } diff --git a/templates/base.html b/templates/base.html index fac419d..60ba7ed 100644 --- a/templates/base.html +++ b/templates/base.html @@ -113,24 +113,40 @@ } .back-link span { margin-right: 8px; font-size: 1.2em; } img { - max-width: 100%; - height: auto; - display: block; - margin: 1.5em auto; - border-radius: 6px; - box-shadow: 0 4px 6px rgba(0,0,0,0.1); + max-width: 100%; + height: auto; + display: block; + margin: 1.5em auto; + border-radius: 6px; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); } - /* Стили для списка файлов */ - ul { list-style-type: none; padding: 0; margin: 0; } - li { padding: 10px 0; border-bottom: 1px solid #333; } + ul { + list-style-type: disc; + padding: 0; + margin: 0; + padding-left: 1.5em; + } + ol { + padding-left: 1.5em; + list-style-type: decimal; + } + li { + padding: .5em 0; + } + ul > li::marker, ol > li::marker { display: inline-block; } .file-link { color: #64b5f6; font-size: 1.1em; display: flex; align-items: center; } - .file-link:hover { color: #90caf9; } - .icon { margin-right: 10px; min-width: 24px; } + .file-link:hover { + color: #90caf9; + } + .icon { + margin-right: 10px; + min-width: 24px; + }