Add copy button feature for code blocks in documentation

- Implemented a one-click copy button for all code blocks in documentation
- Added copy-button.js for clipboard functionality
- Added copy-button.css for hover, icon, and dark mode styling
- Integrated JS and CSS via conf.py (html_js_files, html_css_files)
- Tested successfully with ./test-copy-button.sh (quick and build modes)
This commit is contained in:
Parth 2025-11-10 21:58:43 +05:30
parent b33162dd0b
commit 3045b5f8e0
3 changed files with 159 additions and 0 deletions

View File

@ -149,6 +149,10 @@ html_favicon = 'static/favicon.ico'
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['static']
# Custom CSS and JS files
html_css_files = ['copy-button.css']
html_js_files = ['copy-button.js']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.

View File

@ -0,0 +1,108 @@
.code-block-wrapper {
position: relative;
margin: 0;
display: block;
}
.copy-button {
position: absolute;
top: 0.5rem;
right: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.375rem;
color: #6b7280;
cursor: pointer;
transition: all 0.15s ease;
z-index: 10;
opacity: 0;
pointer-events: none;
}
.code-block-wrapper:hover .copy-button,
.highlight-default:hover .copy-button {
opacity: 1;
pointer-events: auto;
}
.copy-button:hover {
background-color: rgba(255, 255, 255, 0.95);
border-color: rgba(0, 0, 0, 0.2);
color: #374151;
transform: scale(1.1);
}
.copy-button:active {
transform: scale(1.05);
}
.copy-button:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
opacity: 1;
pointer-events: auto;
}
.copy-button:focus:not(:focus-visible) {
outline: none;
}
.copy-button svg {
width: 1rem;
height: 1rem;
display: block;
}
.copy-button.copied {
background-color: #10b981;
border-color: #10b981;
color: white;
opacity: 1;
pointer-events: auto;
}
.copy-button.copied:hover {
background-color: #059669;
border-color: #059669;
transform: scale(1.1);
}
@media (prefers-color-scheme: dark) {
.copy-button {
background-color: rgba(55, 65, 81, 0.7);
border-color: rgba(255, 255, 255, 0.15);
color: #d1d5db;
}
.copy-button:hover {
background-color: rgba(55, 65, 81, 0.95);
border-color: rgba(255, 255, 255, 0.3);
color: #f3f4f6;
}
}
@media (max-width: 640px) {
.copy-button {
opacity: 1;
pointer-events: auto;
}
}
.code-block-wrapper div.highlight {
margin: 0;
}
.code-block-wrapper > .highlight-default,
.code-block-wrapper > .highlight {
margin: 0;
}
div.highlight-default.notranslate,
div.highlight-c.notranslate,
div.highlight-python.notranslate {
position: relative;
}

View File

@ -0,0 +1,47 @@
document.addEventListener('DOMContentLoaded', function() {
const codeBlocks = document.querySelectorAll('div.highlight');
codeBlocks.forEach(function(codeBlock) {
const parent = codeBlock.parentElement;
if (parent && parent.classList.contains('code-block-wrapper')) {
return;
}
const button = document.createElement('button');
button.className = 'copy-button';
button.type = 'button';
button.setAttribute('aria-label', 'Copy code to clipboard');
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
button.addEventListener('click', function() {
const pre = codeBlock.querySelector('pre');
const code = pre ? pre.textContent : codeBlock.textContent;
navigator.clipboard.writeText(code).then(function() {
button.classList.add('copied');
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
button.setAttribute('aria-label', 'Code copied to clipboard');
setTimeout(function() {
button.classList.remove('copied');
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
button.setAttribute('aria-label', 'Copy code to clipboard');
}, 2000);
}).catch(function(err) {
console.error('Failed to copy code: ', err);
});
});
if (parent && (parent.classList.contains('highlight-default') || parent.classList.contains('highlight-c') || parent.classList.contains('highlight-python'))) {
parent.style.position = 'relative';
parent.appendChild(button);
} else {
const wrapper = document.createElement('div');
wrapper.className = 'code-block-wrapper';
codeBlock.parentNode.insertBefore(wrapper, codeBlock);
wrapper.appendChild(button);
wrapper.appendChild(codeBlock);
}
});
});