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:
parent
b33162dd0b
commit
3045b5f8e0
@ -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.
|
||||
|
||||
108
docs/src/static/copy-button.css
Normal file
108
docs/src/static/copy-button.css
Normal 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;
|
||||
}
|
||||
47
docs/src/static/copy-button.js
Normal file
47
docs/src/static/copy-button.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user