Add copy button to code blocks on DeepWiki
// ==UserScript==
// @name DeepWiki Code Copy
// @namespace deepwiki_code_copy
// @description Add copy button to code blocks on DeepWiki
// @version 1.1.0
// @author roojay(https://github.com/roojay520)
// @license http://opensource.org/licenses/MIT
// @match https://deepwiki.com/*
// @run-at document-end
// @grant GM_addStyle
// ==/UserScript==
GM_addStyle(`
.code-copy-button {
position: absolute;
top: 5px;
right: 5px;
padding: 4px 8px;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 12px;
border-radius: 4px;
cursor: pointer;
opacity: 1;
z-index: 100;
border: none;
outline: none;
}
.code-copy-button:hover {
background-color: rgba(0, 0, 0, 0.8);
}
.code-copy-button.copied {
background-color: #4caf50;
}
pre {
position: relative;
}
`);
(function() {
/** Store processed code blocks to avoid duplicate processing */
const processedPre = new WeakSet();
/** Add copy buttons to code blocks */
function addCopyButtonsToCodeBlocks() {
/** Get all top-level pre elements (not nested inside other pre elements) */
const preElements = Array.from(document.querySelectorAll('pre')).filter(pre => {
// Check if this pre is nested inside another pre
const parentPre = pre.closest('pre:not(:scope)');
return !parentPre; // If no parent pre, it's a top-level pre
});
preElements.forEach(pre => {
// Skip if already processed
if (processedPre.has(pre)) {
return;
}
// Mark as processed
processedPre.add(pre);
// Create copy button
const copyButton = document.createElement('button');
copyButton.className = 'code-copy-button';
copyButton.textContent = 'Copy';
// Add copy functionality
copyButton.addEventListener('click', function(e) {
e.stopPropagation();
// Get clean code content by cloning the pre element, removing the button, and getting text
const preClone = pre.cloneNode(true);
const buttonInClone = preClone.querySelector('.code-copy-button');
if (buttonInClone) {
buttonInClone.remove();
}
const codeContent = preClone.textContent || '';
// Copy to clipboard
navigator.clipboard.writeText(codeContent).then(() => {
// Visual feedback on success
copyButton.classList.add('copied');
copyButton.textContent = 'Copied!';
// Reset after 2 seconds
setTimeout(() => {
copyButton.classList.remove('copied');
copyButton.textContent = 'Copy';
}, 2000);
}).catch(err => {
console.error('Copy failed:', err);
// Fallback copy method
const textarea = document.createElement('textarea');
textarea.value = codeContent;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
copyButton.classList.add('copied');
copyButton.textContent = 'Copied!';
setTimeout(() => {
copyButton.classList.remove('copied');
copyButton.textContent = 'Copy';
}, 2000);
} catch (err) {
console.error('Fallback copy failed:', err);
copyButton.textContent = 'Copy failed';
}
document.body.removeChild(textarea);
});
});
// Add button to code block
pre.appendChild(copyButton);
});
}
/** Initialize script */
function initScript() {
// Process initially loaded code blocks
addCopyButtonsToCodeBlocks();
// Watch for DOM changes to handle dynamically loaded code blocks
const observer = new MutationObserver((mutations) => {
let hasNewPre = false;
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
const addedNodes = Array.from(mutation.addedNodes);
// Check for new pre elements or nodes that might contain pre elements
const hasPreNodes = addedNodes.some(node => {
if (node.nodeName === 'PRE') return true;
if (node.nodeType === 1 && node.querySelector('pre')) return true;
return false;
});
if (hasPreNodes) {
hasNewPre = true;
}
}
});
if (hasNewPre) {
addCopyButtonsToCodeBlocks();
}
});
// Start observing document body
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// Start script when page is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initScript);
} else {
initScript();
}
})();