DeepWiki Code Copy

Add copy button to code blocks on DeepWiki

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
  }
})();