(feat) copy code content
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
This commit is contained in:
@@ -17,5 +17,5 @@ tauri-plugin-dialog = "2"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
comrak = { version = "0.28", features = ["syntect"] }
|
comrak = { version = "0.28", features = ["syntect"] }
|
||||||
syntect = "5"
|
syntect = { version = "5", features = ["yaml-load"] }
|
||||||
notify = "7"
|
notify = "7"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -102,13 +102,11 @@ body {
|
|||||||
.content pre:not(.syntax-highlighting) {
|
.content pre:not(.syntax-highlighting) {
|
||||||
background: #0f172a;
|
background: #0f172a;
|
||||||
border: 1px solid #1e293b;
|
border: 1px solid #1e293b;
|
||||||
border-radius: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bloc de code avec coloration syntaxique */
|
/* Bloc de code avec coloration syntaxique */
|
||||||
.content .syntax-highlighting {
|
.content .syntax-highlighting {
|
||||||
border: 1px solid #1e293b;
|
border: 1px solid #1e293b;
|
||||||
border-radius: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Texte dans un bloc sans coloration */
|
/* Texte dans un bloc sans coloration */
|
||||||
|
|||||||
@@ -2,15 +2,30 @@ use comrak::plugins::syntect::SyntectAdapterBuilder;
|
|||||||
use comrak::{markdown_to_html_with_plugins, Options, Plugins};
|
use comrak::{markdown_to_html_with_plugins, Options, Plugins};
|
||||||
use syntect::highlighting::ThemeSet;
|
use syntect::highlighting::ThemeSet;
|
||||||
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
|
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
|
||||||
|
use syntect::parsing::SyntaxDefinition;
|
||||||
|
|
||||||
use crate::domain::MarkdownRenderer;
|
use crate::domain::MarkdownRenderer;
|
||||||
|
|
||||||
|
const TYPESCRIPT_SYNTAX: &str =
|
||||||
|
include_str!("../../resources/syntaxes/TypeScript.sublime-syntax");
|
||||||
|
|
||||||
|
fn build_syntax_set() -> syntect::parsing::SyntaxSet {
|
||||||
|
let mut builder = syntect::parsing::SyntaxSet::load_defaults_nonewlines().into_builder();
|
||||||
|
if let Ok(def) = SyntaxDefinition::load_from_str(TYPESCRIPT_SYNTAX, true, None) {
|
||||||
|
builder.add(def);
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ComrakRenderer;
|
pub struct ComrakRenderer;
|
||||||
pub struct ComrakPreviewRenderer;
|
pub struct ComrakPreviewRenderer;
|
||||||
|
|
||||||
impl MarkdownRenderer for ComrakRenderer {
|
impl MarkdownRenderer for ComrakRenderer {
|
||||||
fn render(&self, content: &str) -> String {
|
fn render(&self, content: &str) -> String {
|
||||||
let adapter = SyntectAdapterBuilder::new().css().build();
|
let adapter = SyntectAdapterBuilder::new()
|
||||||
|
.syntax_set(build_syntax_set())
|
||||||
|
.css()
|
||||||
|
.build();
|
||||||
let mut options = Options::default();
|
let mut options = Options::default();
|
||||||
options.extension.table = true;
|
options.extension.table = true;
|
||||||
options.extension.strikethrough = true;
|
options.extension.strikethrough = true;
|
||||||
@@ -24,7 +39,10 @@ impl MarkdownRenderer for ComrakRenderer {
|
|||||||
|
|
||||||
impl MarkdownRenderer for ComrakPreviewRenderer {
|
impl MarkdownRenderer for ComrakPreviewRenderer {
|
||||||
fn render(&self, content: &str) -> String {
|
fn render(&self, content: &str) -> String {
|
||||||
let adapter = SyntectAdapterBuilder::new().css().build();
|
let adapter = SyntectAdapterBuilder::new()
|
||||||
|
.syntax_set(build_syntax_set())
|
||||||
|
.css()
|
||||||
|
.build();
|
||||||
let options = Options::default();
|
let options = Options::default();
|
||||||
let mut plugins = Plugins::default();
|
let mut plugins = Plugins::default();
|
||||||
plugins.render.codefence_syntax_highlighter = Some(&adapter);
|
plugins.render.codefence_syntax_highlighter = Some(&adapter);
|
||||||
@@ -62,6 +80,13 @@ mod tests {
|
|||||||
assert!(html.contains("<span class="));
|
assert!(html.contains("<span class="));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn typescript_syntax_is_highlighted() {
|
||||||
|
let r = ComrakRenderer;
|
||||||
|
let html = r.render("```typescript\ninterface Foo { id: string; }\n```\n");
|
||||||
|
assert!(html.contains("<span class="), "TypeScript should produce highlighted spans");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn syntax_css_known_theme_contains_wrapper() {
|
fn syntax_css_known_theme_contains_wrapper() {
|
||||||
let css = syntax_css_for_theme("base16-ocean.dark");
|
let css = syntax_css_for_theme("base16-ocean.dark");
|
||||||
@@ -80,5 +105,4 @@ mod tests {
|
|||||||
let css = syntax_css_for_theme("nonexistent-theme-xyz");
|
let css = syntax_css_for_theme("nonexistent-theme-xyz");
|
||||||
assert!(css.is_empty());
|
assert!(css.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 993 B After Width: | Height: | Size: 993 B |
@@ -455,6 +455,29 @@ body {
|
|||||||
.content pre:not(.syntax-highlighting) { background: #f4f4f7; }
|
.content pre:not(.syntax-highlighting) { background: #f4f4f7; }
|
||||||
.content pre code { background: none; padding: 0; font-weight: 400; font-size: 13px; border: none; border-radius: 0; color: inherit; }
|
.content pre code { background: none; padding: 0; font-weight: 400; font-size: 13px; border: none; border-radius: 0; color: inherit; }
|
||||||
|
|
||||||
|
/* ── Bouton « copier » des blocs de code ── */
|
||||||
|
.content .code-block { position: relative; }
|
||||||
|
.content .code-block .copy-code-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 12px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #c0c5ce;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease, background 0.15s ease;
|
||||||
|
}
|
||||||
|
.content .code-block:hover .copy-code-btn,
|
||||||
|
.content .code-block .copy-code-btn:focus-visible { opacity: 1; }
|
||||||
|
.content .code-block .copy-code-btn:hover { background: rgba(255, 255, 255, 0.18); }
|
||||||
|
.content .code-block .copy-code-btn.copied { color: #a3be8c; border-color: rgba(163, 190, 140, 0.5); }
|
||||||
|
|
||||||
/* ── Coloration syntaxique (base16-ocean.dark) ── */
|
/* ── Coloration syntaxique (base16-ocean.dark) ── */
|
||||||
.syntax-highlighting {
|
.syntax-highlighting {
|
||||||
color: #c0c5ce;
|
color: #c0c5ce;
|
||||||
|
|||||||
@@ -35,10 +35,42 @@ function wireLinks(filePath, mode, sidebarEl) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCopyButtons() {
|
||||||
|
content.querySelectorAll('pre').forEach(pre => {
|
||||||
|
if (pre.parentElement?.classList.contains('code-block')) return;
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'code-block';
|
||||||
|
pre.parentNode.insertBefore(wrapper, pre);
|
||||||
|
wrapper.appendChild(pre);
|
||||||
|
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'copy-code-btn';
|
||||||
|
btn.textContent = 'Copier';
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const code = (pre.querySelector('code') ?? pre).textContent;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
btn.textContent = 'Copié !';
|
||||||
|
btn.classList.add('copied');
|
||||||
|
} catch {
|
||||||
|
btn.textContent = 'Erreur';
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.textContent = 'Copier';
|
||||||
|
btn.classList.remove('copied');
|
||||||
|
}, 1500);
|
||||||
|
});
|
||||||
|
wrapper.appendChild(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadPage(filePath, mode, sidebarEl) {
|
export async function loadPage(filePath, mode, sidebarEl) {
|
||||||
try {
|
try {
|
||||||
const html = await convertFile(filePath);
|
const html = await convertFile(filePath);
|
||||||
content.innerHTML = html;
|
content.innerHTML = html;
|
||||||
|
addCopyButtons();
|
||||||
|
|
||||||
const basename = filePath.split('/').pop().split('\\').pop();
|
const basename = filePath.split('/').pop().split('\\').pop();
|
||||||
const title = basename.replace(/\.md$/i, '');
|
const title = basename.replace(/\.md$/i, '');
|
||||||
|
|||||||
Reference in New Issue
Block a user