Copy NeetCode

Adds a copy button to copy content from neetcode.io

  1. // ==UserScript==
  2. // @name Copy NeetCode
  3. // @namespace tzway
  4. // @version 0.2
  5. // @description Adds a copy button to copy content from neetcode.io
  6. // @author tzway
  7. // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0-standalone.html
  8. // @match *://neetcode.io/problems/*
  9. // @grant none
  10. // @run-at document-idle
  11. // ==/UserScript==
  12.  
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Function to copy formatted content excluding buttons with class 'copy-to-clipboard-button'
  18. function copyToClipboard(button) {
  19. const contentElement = document.querySelector('.my-article-component-container');
  20.  
  21. // Create a deep clone of the content to avoid modifying the original element
  22. const contentClone = contentElement.cloneNode(true);
  23.  
  24. // Remove all elements with the class 'copy-to-clipboard-button'
  25. const buttonsToExclude = contentClone.querySelectorAll('.copy-to-clipboard-button');
  26. buttonsToExclude.forEach(button => button.remove());
  27.  
  28. // Create a temporary container to hold the cleaned content
  29. const tempDiv = document.createElement('div');
  30. tempDiv.appendChild(contentClone);
  31.  
  32. // Copy the cleaned HTML content to the clipboard
  33. navigator.clipboard.write([
  34. new ClipboardItem({
  35. 'text/html': new Blob([tempDiv.innerHTML], { type: 'text/html' }),
  36. 'text/plain': new Blob([tempDiv.innerText], { type: 'text/plain' })
  37. })
  38. ]).then(() => {
  39. // Provide non-intrusive feedback
  40. button.innerText = 'Copied!'; // Change button text
  41. button.style.backgroundColor = '#4caf50'; // Optional: Change color to indicate success
  42.  
  43. // Revert back to original after 2 seconds
  44. setTimeout(() => {
  45. button.innerText = 'Copy Content';
  46. button.style.backgroundColor = ''; // Revert color
  47. }, 1000);
  48. }).catch(err => {
  49. console.error('Failed to copy: ', err);
  50. });
  51. }
  52.  
  53. // Function to add the copy button
  54. function addButton(targetElement) {
  55. // Check if the button already exists to prevent adding duplicates
  56. if (targetElement.querySelector('.copy-to-clipboard-button')) return;
  57.  
  58. const button = document.createElement('button');
  59. button.classList.add('copy-to-clipboard-button');
  60. button.innerText = 'Copy Content';
  61. button.style.marginLeft = 'auto'; // Align to the right
  62. button.style.display = 'inline-block';
  63.  
  64. // Set the parent container to flex to align the button to the right
  65. targetElement.style.display = 'flex';
  66. targetElement.style.alignItems = 'center'; // Align content vertically
  67. targetElement.appendChild(button);
  68.  
  69. // Add the event listener to the button
  70. button.addEventListener('click', () => copyToClipboard(button));
  71. }
  72.  
  73. // Function to observe changes in the DOM and reapply the button
  74. function observePage() {
  75. const targetNode = document.body;
  76.  
  77. // Options for the observer (which mutations to observe)
  78. const config = { childList: true, subtree: true };
  79.  
  80. // Callback function to execute when mutations are observed
  81. const callback = function(mutationsList, observer) {
  82. for (let mutation of mutationsList) {
  83. if (mutation.type === 'childList') {
  84. // Re-add the button if the target element is found
  85. const targetElement = document.querySelector('.question-tab > div:nth-child(1) > div:nth-child(1)');
  86. if (targetElement) {
  87. addButton(targetElement);
  88. }
  89. }
  90. }
  91. };
  92.  
  93. // Create an observer instance linked to the callback function
  94. const observer = new MutationObserver(callback);
  95.  
  96. // Start observing the target node for configured mutations
  97. observer.observe(targetNode, config);
  98. }
  99.  
  100. // Initialize observation of the page
  101. observePage();
  102.  
  103. })();