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", "clap",
"lazy_static", "lazy_static",
"pulldown-cmark", "pulldown-cmark",
"pulldown-cmark-escape",
"reqwest", "reqwest",
"serde", "serde",
"syntect", "syntect",

View File

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

View File

@@ -1,4 +1,5 @@
use pulldown_cmark::{CodeBlockKind, CowStr, Event, Parser as MarkdownParser, Tag, TagEnd}; 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 syntect::html::highlighted_html_for_string;
use crate::{SYNTAX_SET, THEME_SET}; 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 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()))) Some(Event::Html(CowStr::Boxed(rendered_html.into_boxed_str())))
} }
} }

View File

@@ -4,6 +4,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{ title }}</title> <title>{{ title }}</title>
<link rel="stylesheet" href="/style.css"> <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> </head>
<body> <body>
<article> <article>
@@ -11,19 +13,43 @@
</article> </article>
<nav> <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> <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> </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> </body>
</html> </html>

View File

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