diff --git a/src/_includes/global/baselayout.njk b/src/_includes/global/baselayout.njk
index 659b8247..613df906 100644
--- a/src/_includes/global/baselayout.njk
+++ b/src/_includes/global/baselayout.njk
@@ -37,6 +37,9 @@
{% if toc %}
{% endif %}
+ {% if hasCodeBlock %}
+
+ {% endif %}
{% if hasTooltips %}
{% endif %}
diff --git a/src/articles/myarticles/accessible-footnotes.md b/src/articles/myarticles/accessible-footnotes.md
index f299ea89..7becde19 100644
--- a/src/articles/myarticles/accessible-footnotes.md
+++ b/src/articles/myarticles/accessible-footnotes.md
@@ -5,6 +5,7 @@ updated: 2024-12-03T23:51:27+0800
desc: "How I implement accessible footnotes, at least to the best of my ability. Written for 32-Bit Cafe's Community Code Jam #5."
categories: ["32-bit cafe", "accessibility", "html", "css", "eleventy", "markdown-it"]
toc: true
+hasCodeBlock: true
---
[](https://32bit.cafe/~xandra/events/codejam5/){.inline-img}
diff --git a/src/assets/css/components.css b/src/assets/css/components.css
index f44e669d..0ed1190d 100644
--- a/src/assets/css/components.css
+++ b/src/assets/css/components.css
@@ -103,6 +103,20 @@
a.link-btn[href^="http"]:not([href*="leilukin.com"]) { padding-right: calc(var(--btn-right-padding) + var(--sz-external-link)); }
+/* Button to copy code snippets */
+.cc-btn {
+ font-size: 0.8em;
+ display: flex;
+ align-items: center;
+ gap: 0.2em;
+ margin-left: auto;
+ margin-right: 0;
+ border: 0.15em solid var(--clr-code-border);
+ border-radius: 0.2em;
+ background: var(--clr-link-btn-bg);
+ color: var(--clr-link-btn-txt);
+}
+
/* Content Disclosure */
* + .content-disclosure { margin-top: var(--sz-paragraph-margin); }
.content-disclosure__summary { font-weight: 700; }
diff --git a/src/assets/js/copycode.js b/src/assets/js/copycode.js
new file mode 100644
index 00000000..576abe7a
--- /dev/null
+++ b/src/assets/js/copycode.js
@@ -0,0 +1,58 @@
+function createCopyBtn(blockIndex) {
+ return `
`;
+}
+
+async function copyCode(block) {
+ const code = document.querySelector(`[data-block-id="${block}"]`);
+ const doCopy = async() => await navigator.clipboard.writeText(code?.innerText ?? '');
+
+ if (!navigator.userAgent.includes('Firefox')) {
+ const result = await navigator.permissions.query({ name: 'clipboard-write' });
+
+ if (result.state === 'granted' || result.state === 'prompt') {
+ doCopy();
+ }
+ } else {
+ doCopy();
+ }
+}
+
+async function handleCopyBtnClick(event) {
+ const btn = event?.target;
+ const btnTarget = btn?.getAttribute('data-target');
+
+ if (btn && btnTarget) {
+ const originalText = btn.innerHTML;
+
+ await copyCode(btnTarget);
+
+ btn.innerHTML = ' Copied!';
+
+ setTimeout(() => {
+ btn.innerHTML = originalText;
+ }, 1500);
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ const allCodeBlocks = Array.from(document.querySelectorAll('pre[class^="language-"]'));
+
+ allCodeBlocks.forEach((b, i) => {
+ const code = b.childNodes[0];
+ const codeBlockIndex = `cb-${i}`;
+
+ b.insertAdjacentHTML('afterend', createCopyBtn(codeBlockIndex));
+ code.setAttribute('data-block-id', codeBlockIndex);
+ })
+
+ const allCopyBtns = Array.from(document.querySelectorAll('.cc-btn'));
+
+ allCopyBtns.forEach((btn) => {
+ btn.addEventListener('click', handleCopyBtnClick);
+ })
+})
\ No newline at end of file
diff --git a/src/changelogs/logs/2025/2025-02-17.md b/src/changelogs/logs/2025/2025-02-17.md
new file mode 100644
index 00000000..daa9cd4d
--- /dev/null
+++ b/src/changelogs/logs/2025/2025-02-17.md
@@ -0,0 +1,5 @@
+---
+date: 2025-02-17T18:10:52+0800
+---
+
+* Add buttons to copy code snippets to pages with code blocks.
\ No newline at end of file
diff --git a/src/projects/snippets/snippets.11tydata.js b/src/projects/snippets/snippets.11tydata.js
index 13f5d2f3..7fc18c20 100644
--- a/src/projects/snippets/snippets.11tydata.js
+++ b/src/projects/snippets/snippets.11tydata.js
@@ -1,3 +1,4 @@
export default {
- tags: ["code snippets"]
+ tags: ["code snippets"],
+ hasCodeBlock: true
}
\ No newline at end of file
diff --git a/src/shrines/starwarskotor/articles/juhani-lesbian-evidence.md b/src/shrines/starwarskotor/articles/juhani-lesbian-evidence.md
index d76052d9..771a13ed 100644
--- a/src/shrines/starwarskotor/articles/juhani-lesbian-evidence.md
+++ b/src/shrines/starwarskotor/articles/juhani-lesbian-evidence.md
@@ -5,6 +5,7 @@ updated: 2024-05-10
desc: "Juhani is a canon lesbian character and she has always been intended as such by the developers of Knights of the Old Republic. Here I am presenting evidence from the game files to prove it."
tags: kotor 1 articles
categories: ["star wars kotor"]
+hasCodeBlock: true
---
(Note: This article was originally published on Tumblr)