added copy button to code blocks and tweaked some more style

This commit is contained in:
2026-01-07 18:58:53 +01:00
parent 1c4e99b138
commit 0422c3042a
5 changed files with 109 additions and 38 deletions

1
Cargo.lock generated
View File

@@ -208,6 +208,7 @@ dependencies = [
"clap",
"lazy_static",
"pulldown-cmark",
"pulldown-cmark-escape",
"reqwest",
"serde",
"syntect",

View File

@@ -10,6 +10,7 @@ chrono = "0.4.42"
clap = { version = "4.5.54", features = ["derive"] }
lazy_static = "1.5.0"
pulldown-cmark = "0.13.0"
pulldown-cmark-escape = "0.11.0"
serde = { version = "1.0.228", features = ["derive"] }
syntect = "5.3.0"
tera = "1.20.1"

View File

@@ -1,4 +1,5 @@
use pulldown_cmark::{CodeBlockKind, CowStr, Event, Parser as MarkdownParser, Tag, TagEnd};
use pulldown_cmark_escape::escape_html;
use syntect::html::highlighted_html_for_string;
use crate::{SYNTAX_SET, THEME_SET};
@@ -43,6 +44,12 @@ impl<'a> Iterator for CodeblockRenderer<'a> {
let rendered_html = render_code_to_html(&code_content, lang);
let mut escaped_code = String::new();
let _ = escape_html(&mut escaped_code, &code_content);
let rendered_html =
rendered_html.replace("<pre", &format!("<pre data-code=\"{}\"", escaped_code));
Some(Event::Html(CowStr::Boxed(rendered_html.into_boxed_str())))
}
}

View File

@@ -4,6 +4,8 @@
<meta charset="UTF-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="/style.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<article>
@@ -11,19 +13,43 @@
</article>
<nav>
<!-- {% if prev_page %} -->
<!-- <a class="btn" href="/{{ prev_page }}">← Previous</a> -->
<!-- {% else %} -->
<!-- <span class="btn disabled">← Previous</span> -->
<!-- {% endif %} -->
<a href="/">Home (Summary)</a>
<!-- {% if next_page %} -->
<!-- <a class="btn" href="/{{ next_page }}">Next →</a> -->
<!-- {% else %} -->
<!-- <span class="btn disabled">Next →</span> -->
<!-- {% endif %} -->
</nav>
<script>
document.querySelectorAll('pre[data-code]').forEach((block) => {
const wrapper = document.createElement('div');
wrapper.className = 'code-wrapper';
block.parentNode.insertBefore(wrapper, block);
wrapper.appendChild(block);
const button = document.createElement('button');
button.className = 'copy-button';
button.type = 'button';
button.innerHTML = '<i class="fa-regular fa-copy"></i>';
wrapper.appendChild(button);
button.addEventListener('click', async () => {
const text = block.getAttribute('data-code');
try {
await navigator.clipboard.writeText(text);
button.innerHTML = '<i class="fa-solid fa-check"></i>';
button.classList.add('copied');
setTimeout(() => {
button.innerHTML = '<i class="fa-regular fa-copy"></i>';
button.classList.remove('copied');
}, 2000);
} catch (err) {
console.error('Copy failed', err);
}
});
});
</script>
</body>
</html>

View File

@@ -1,16 +1,16 @@
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap');
:root {
--bg-color: #0d0d12;
--container-bg: rgba(25, 25, 35, 0.75);
--bg-color: #0f1116;
--container-bg: #171922;
--text-main: #e2e2e9;
--text-muted: #a1a1b5;
--accent-pink: #ff4d94;
--accent-pink-glow: rgba(255, 77, 148, 0.3);
--accent-pink-dark: #cc3d76;
--accent: #ff4d94;
--accent-glow: rgba(255, 77, 148, 0.3);
--accent-dark: #cc3d76;
--code-bg: rgba(25, 25, 35, 0.75);
--code-bg: var(--container-bg);
--border-color: rgba(255, 255, 255, 0.1);
--selection-bg: rgba(255, 77, 148, 0.4);
@@ -27,7 +27,6 @@
body {
background-color: var(--bg-color);
background-image: radial-gradient(circle at top right, #1a1a2e, #0d0d12);
color: var(--text-main);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.7;
@@ -57,26 +56,29 @@ h1, h2, h3, h4, h5, h6 {
h1 {
font-size: 2.5rem;
border-bottom: 2px solid var(--accent-pink);
padding-bottom: 0.8rem;
margin-bottom: 2.5rem;
border-bottom: 2px solid;
border-image-source: linear-gradient(to right, var(--accent), transparent);
border-image-slice: 1;
}
h2 {
font-size: 1.8rem;
color: var(--accent-pink);
color: var(--accent);
}
h3 {
font-size: 1.5rem;
}
a {
color: var(--accent-pink);
color: var(--accent);
text-decoration: none;
transition: color 0.2s ease, text-shadow 0.2s ease;
}
a:hover {
color: #ff80b3;
text-shadow: 0 0 8px var(--accent-pink-glow);
text-shadow: 0 0 8px var(--accent-glow);
}
p { margin-bottom: 1.2rem; }
@@ -91,21 +93,22 @@ li { margin-bottom: 0.5rem; }
blockquote {
margin: 2rem 0;
padding: 0.1rem 1.5rem;
border-left: 4px solid var(--accent-pink);
background: rgba(255, 77, 148, 0.05);
border-left: 4px solid var(--accent);
background: var(--container-bg);
border-radius: var(--radius-sm);
color: var(--text-muted);
font-style: italic;
}
pre {
position: relative;
background: var(--code-bg);
background-color: var(--code-bg) !important;
padding: 1.2rem;
border-radius: var(--radius-md);
overflow-x: auto;
border: 1px solid var(--border-color);
margin: 1.5rem 0;
border: 2px solid var(--border-color);
/* margin: 1.5rem 0; */
}
code {
@@ -117,8 +120,10 @@ code {
}
pre code {
display: block;
background: none !important;
padding: 0;
padding-right: 40px;
}
pre,
@@ -140,13 +145,13 @@ table {
th {
background: rgba(255, 77, 148, 0.1);
color: var(--accent-pink);
color: var(--accent);
text-align: left;
}
th, td {
padding: 12px;
border: 1px solid var(--border-color);
border: 2px solid var(--border-color);
}
tr:nth-child(even) {
@@ -155,7 +160,7 @@ tr:nth-child(even) {
hr {
border: none;
height: 1px;
height: 2px;
background: linear-gradient(to right, transparent, var(--border-color), transparent);
margin: 3rem 0;
}
@@ -164,7 +169,7 @@ img {
max-width: 100%;
border-radius: var(--radius-md);
margin: 1.5rem 0;
border: 1px solid var(--border-color);
border: 2px solid var(--border-color);
}
input[type="checkbox"] {
@@ -175,7 +180,7 @@ input[type="checkbox"] {
margin-right: 0.5rem;
font: inherit;
color: var(--accent-pink);
color: var(--accent);
width: 1.25em;
height: 1.25em;
border: 2px solid var(--text-muted);
@@ -193,13 +198,13 @@ input[type="checkbox"]::before {
height: 0.65em;
transform: scale(0);
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em var(--accent-pink);
box-shadow: inset 1em 1em var(--accent);
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
input[type="checkbox"]:checked {
border-color: var(--accent-pink);
border-color: var(--accent);
}
input[type="checkbox"]:checked::before {
@@ -217,8 +222,8 @@ nav {
width: 100%;
z-index: 1000;
background: var(--container-bg);
backdrop-filter: blur(var(--blur-amount));
-webkit-backdrop-filter: blur(var(--blur-amount));
/* backdrop-filter: blur(var(--blur-amount)); */
/* -webkit-backdrop-filter: blur(var(--blur-amount)); */
border-bottom: 1px solid var(--border-color);
padding: 1rem 2rem;
display: flex;
@@ -235,14 +240,14 @@ nav a {
}
nav a:hover {
color: var(--accent-pink);
color: var(--accent);
}
.btn {
display: inline-block;
color: #fff;
padding: 0.6rem 1.2rem;
border: solid 1px var(--accent-pink);
border: solid 1px var(--accent);
border-radius: var(--radius-md);
font-weight: 600;
cursor: pointer;
@@ -260,3 +265,34 @@ nav a:hover {
cursor: not-allowed;
border-color: var(--text-muted);
}
.code-wrapper {
position: relative;
margin: 1.5rem 0;
}
.copy-button {
position: absolute;
top: 10px;
right: 10px;
padding: 10px 10px;
background: var(--container-bg);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
color: var(--text-muted);
cursor: pointer;
font-size: 0.9rem;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.copy-button:hover {
color: var(--accent);
}
.copy-button:active {
transform: translateY(2px);
}