From df899bd1a8af3bbd6edb2c0f6139cf0d4dbe592b Mon Sep 17 00:00:00 2001 From: Gato Date: Fri, 19 Jun 2026 15:36:08 +0200 Subject: [PATCH] feat: coloration syntaxique via CSS classes (themeable) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remplace les styles inline syntect par des classes CSS. Le CSS est généré côté Rust (base16-ocean.dark) et injecté au démarrage via get_syntax_highlight_css. Les thèmes peuvent surcharger .syntax-highlighting et les classes de tokens. Supprime le lien CDN highlight.js inutilisé. Co-Authored-By: Claude Sonnet 4.6 --- src-tauri/resources/themes/default.css | 14 ++-- src-tauri/resources/themes/shell-indigo.css | 5 +- src-tauri/src/commands/render.rs | 31 ++++++--- .../src/infrastructure/comrak_renderer.rs | 65 ++++++++++++++++--- src-tauri/src/lib.rs | 1 + src/index.html | 3 +- src/main.js | 11 ++++ src/style.css | 3 +- 8 files changed, 107 insertions(+), 26 deletions(-) diff --git a/src-tauri/resources/themes/default.css b/src-tauri/resources/themes/default.css index 00d0a22..8e8b2f9 100644 --- a/src-tauri/resources/themes/default.css +++ b/src-tauri/resources/themes/default.css @@ -98,15 +98,21 @@ body { border: 1px solid #e2e8f0; } -/* Bloc de code (entre triple backticks) */ -.content pre { +/* Bloc de code sans coloration syntaxique */ +.content pre:not(.syntax-highlighting) { background: #0f172a; border: 1px solid #1e293b; border-radius: 10px; } -/* Texte à l'intérieur d'un bloc de code */ -.content pre code { +/* Bloc de code avec coloration syntaxique */ +.content .syntax-highlighting { + border: 1px solid #1e293b; + border-radius: 10px; +} + +/* Texte dans un bloc sans coloration */ +.content pre:not(.syntax-highlighting) code { background: none; color: #e2e8f0; } diff --git a/src-tauri/resources/themes/shell-indigo.css b/src-tauri/resources/themes/shell-indigo.css index d1be62e..e3b5740 100644 --- a/src-tauri/resources/themes/shell-indigo.css +++ b/src-tauri/resources/themes/shell-indigo.css @@ -9,8 +9,9 @@ body { background: #f1f5f9; } .content p { color: #1e293b; } .content ul li, .content ol li { color: #1e293b; } .content code { background: #f1f5f9; color: #4f46e5; border: 1px solid #e2e8f0; } -.content pre { background: #0f172a; border: 1px solid #1e293b; border-radius: 10px; } -.content pre code { background: none; color: #e2e8f0; } +.content pre:not(.syntax-highlighting) { background: #0f172a; border: 1px solid #1e293b; border-radius: 10px; } +.content .syntax-highlighting { border: 1px solid #1e293b; border-radius: 10px; } +.content pre:not(.syntax-highlighting) code { background: none; color: #e2e8f0; } .content blockquote { border-color: #818cf8; background: #f5f3ff; color: #4338ca; } .content table { color: #374151; } .content table th { border-bottom-color: #4f46e5; color: #334155; } diff --git a/src-tauri/src/commands/render.rs b/src-tauri/src/commands/render.rs index 5cbee7e..030bb85 100644 --- a/src-tauri/src/commands/render.rs +++ b/src-tauri/src/commands/render.rs @@ -1,22 +1,25 @@ use crate::application::{file_service, render_service}; -use crate::infrastructure::comrak_renderer::{ComrakPreviewRenderer, ComrakRenderer}; +use crate::infrastructure::comrak_renderer::{ + syntax_css_for_theme, ComrakPreviewRenderer, ComrakRenderer, +}; #[tauri::command] pub fn render_markdown(content: String) -> String { - let renderer = ComrakPreviewRenderer { - theme: "base16-ocean.dark".to_string(), - }; + let renderer = ComrakPreviewRenderer; render_service::render_string(&renderer, &content) } #[tauri::command] pub fn convert_file(path: String) -> Result { - let renderer = ComrakRenderer { - theme: "InspiredGitHub".to_string(), - }; + let renderer = ComrakRenderer; render_service::render_file(&renderer, &path) } +#[tauri::command] +pub fn get_syntax_highlight_css() -> String { + syntax_css_for_theme("base16-ocean.dark") +} + #[tauri::command] pub fn list_md_files(dir: String) -> Result, String> { file_service::list_markdown_files(&dir) @@ -41,7 +44,7 @@ mod tests { #[test] fn render_markdown_code_block() { let html = render_markdown("```rust\nfn main() {}\n```\n".to_string()); - assert!(html.contains("")); + assert!(html.contains("syntax-highlighting")); } #[test] @@ -80,6 +83,18 @@ mod tests { assert!(result.is_err()); } + #[test] + fn get_syntax_highlight_css_returns_non_empty() { + let css = get_syntax_highlight_css(); + assert!(!css.is_empty()); + } + + #[test] + fn get_syntax_highlight_css_contains_wrapper_class() { + let css = get_syntax_highlight_css(); + assert!(css.contains(".syntax-highlighting {")); + } + #[test] fn list_md_files_sorted() { let dir = tmpdir("pena_list_sorted"); diff --git a/src-tauri/src/infrastructure/comrak_renderer.rs b/src-tauri/src/infrastructure/comrak_renderer.rs index f337f3c..150a9cd 100644 --- a/src-tauri/src/infrastructure/comrak_renderer.rs +++ b/src-tauri/src/infrastructure/comrak_renderer.rs @@ -1,19 +1,16 @@ use comrak::plugins::syntect::SyntectAdapterBuilder; use comrak::{markdown_to_html_with_plugins, Options, Plugins}; +use syntect::highlighting::ThemeSet; +use syntect::html::{css_for_theme_with_class_style, ClassStyle}; use crate::domain::MarkdownRenderer; -pub struct ComrakRenderer { - pub theme: String, -} - -pub struct ComrakPreviewRenderer { - pub theme: String, -} +pub struct ComrakRenderer; +pub struct ComrakPreviewRenderer; impl MarkdownRenderer for ComrakRenderer { fn render(&self, content: &str) -> String { - let adapter = SyntectAdapterBuilder::new().theme(&self.theme).build(); + let adapter = SyntectAdapterBuilder::new().css().build(); let mut options = Options::default(); options.extension.table = true; options.extension.strikethrough = true; @@ -27,10 +24,60 @@ impl MarkdownRenderer for ComrakRenderer { impl MarkdownRenderer for ComrakPreviewRenderer { fn render(&self, content: &str) -> String { - let adapter = SyntectAdapterBuilder::new().theme(&self.theme).build(); + let adapter = SyntectAdapterBuilder::new().css().build(); let options = Options::default(); let mut plugins = Plugins::default(); plugins.render.codefence_syntax_highlighter = Some(&adapter); markdown_to_html_with_plugins(content, &options, &plugins) } } + +pub fn syntax_css_for_theme(syntect_theme_name: &str) -> String { + let ts = ThemeSet::load_defaults(); + let theme = match ts.themes.get(syntect_theme_name) { + Some(t) => t, + None => return String::new(), + }; + match css_for_theme_with_class_style(theme, ClassStyle::Spaced) { + Ok(css) => css.replace(".code {", ".syntax-highlighting {"), + Err(_) => String::new(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn render_outputs_syntax_highlighting_class() { + let r = ComrakRenderer; + let html = r.render("```rust\nfn main() {}\n```\n"); + assert!(html.contains("syntax-highlighting")); + } + + #[test] + fn render_outputs_classed_spans() { + let r = ComrakRenderer; + let html = r.render("```rust\nfn main() {}\n```\n"); + assert!(html.contains("Pena — Markdown Viewer - - + diff --git a/src/main.js b/src/main.js index f6182ac..dfe7dab 100644 --- a/src/main.js +++ b/src/main.js @@ -1,8 +1,18 @@ import { showHome, openPath } from './router.js'; import { initCssModal } from './ui/css-modal.js'; +const { invoke } = window.__TAURI__.core; const win = window.__TAURI__.window.getCurrentWindow(); +async function injectSyntaxCss() { + const css = await invoke('get_syntax_highlight_css'); + if (!css) return; + const style = document.createElement('style'); + style.id = 'pena-syntax-style'; + style.textContent = css; + document.head.appendChild(style); +} + document.getElementById('titlebar').addEventListener('mousedown', (e) => { if (e.target.closest('.titlebar-controls')) return; if (e.buttons === 1) win.startDragging(); @@ -32,5 +42,6 @@ document.getElementById('btn-open-dir').addEventListener('click', async () => { document.getElementById('btn-back').addEventListener('click', showHome); +await injectSyntaxCss(); await initCssModal(); showHome(); diff --git a/src/style.css b/src/style.css index 605a1ba..a212a91 100644 --- a/src/style.css +++ b/src/style.css @@ -451,7 +451,8 @@ body { border-radius: 4px; line-height: 1.4; } -.content pre { background: #f4f4f7; padding: 20px 24px; border-radius: 6px; overflow-x: auto; margin: 16px 0; } +.content pre { padding: 20px 24px; border-radius: 6px; overflow-x: auto; margin: 16px 0; } +.content pre:not(.syntax-highlighting) { background: #f4f4f7; } .content pre code { background: none; padding: 0; font-weight: 400; font-size: 13px; } .content blockquote {