added copy button to code blocks and tweaked some more style
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -208,6 +208,7 @@ dependencies = [
|
||||
"clap",
|
||||
"lazy_static",
|
||||
"pulldown-cmark",
|
||||
"pulldown-cmark-escape",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"syntect",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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())))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user