Easy Copy Selected Text - Enhanced

Automatically copies selected text to clipboard with additional features

  1. // ==UserScript==
  2. // @name Easy Copy Selected Text - Enhanced
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.8
  5. // @description Automatically copies selected text to clipboard with additional features
  6. // @author diogoodev
  7. // @match *://*/*
  8. // @grant GM_setClipboard
  9. // @grant GM_addStyle
  10. // @run-at document-start
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. const config = {
  18. enabledByDefault: true,
  19. copyDelay: 10,
  20. selectionDelay: 500,
  21. notificationDuration: 2000,
  22. toggleKey: 'Alt',
  23. middleClickPaste: true
  24. };
  25.  
  26. let state = {
  27. enabled: config.enabledByDefault,
  28. oldSelectedText: "",
  29. copyInProgress: false,
  30. clipboard: ""
  31. };
  32.  
  33. function addStyles() {
  34. const styles = `
  35. #easy-copy-snackbar {
  36. visibility: hidden;
  37. min-width: 250px;
  38. margin-left: -125px;
  39. text-align: center;
  40. border-radius: 8px;
  41. padding: 12px;
  42. position: fixed;
  43. z-index: 999999;
  44. left: 50%;
  45. top: 30px;
  46. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  47. font-size: 14px;
  48. box-shadow: 0 4px 8px rgba(0,0,0,0.2);
  49. opacity: 0;
  50. transition: opacity 0.3s, top 0.3s;
  51. }
  52.  
  53. #easy-copy-snackbar.success {
  54. background-color: #4CAF50;
  55. color: white;
  56. }
  57.  
  58. #easy-copy-snackbar.error {
  59. background-color: #F44336;
  60. color: white;
  61. }
  62.  
  63. #easy-copy-snackbar.show {
  64. visibility: visible;
  65. opacity: 0.9;
  66. top: 30px;
  67. }
  68.  
  69. .easy-copy-textarea {
  70. position: fixed;
  71. top: 0;
  72. left: 0;
  73. width: 2em;
  74. height: 2em;
  75. padding: 0;
  76. border: none;
  77. outline: none;
  78. box-shadow: none;
  79. background: transparent;
  80. opacity: 0;
  81. }
  82. `;
  83.  
  84. if (typeof GM_addStyle !== "undefined") {
  85. GM_addStyle(styles);
  86. } else {
  87. try {
  88. const styleElement = document.createElement("style");
  89. styleElement.textContent = styles;
  90. if (document.head || document.documentElement) {
  91. (document.head || document.documentElement).appendChild(styleElement);
  92. } else {
  93. document.addEventListener('DOMContentLoaded', function() {
  94. (document.head || document.documentElement).appendChild(styleElement);
  95. });
  96. }
  97. } catch (e) {
  98. }
  99. }
  100. }
  101.  
  102. function initializeUI() {
  103. addStyles();
  104.  
  105. if (!document.getElementById("easy-copy-snackbar")) {
  106. const divSnackbar = document.createElement("div");
  107. divSnackbar.id = "easy-copy-snackbar";
  108. (document.body || document.documentElement).appendChild(divSnackbar);
  109. }
  110. }
  111.  
  112. function getSelectionText() {
  113. let text = "";
  114.  
  115. const activeEl = document.activeElement;
  116. const activeElTagName = activeEl ? activeEl.tagName.toLowerCase() : null;
  117.  
  118. if (
  119. activeElTagName === "textarea" ||
  120. (activeElTagName === "input" && /^(?:text|search|password|tel|url)$/i.test(activeEl.type)) &&
  121. (typeof activeEl.selectionStart === "number")
  122. ) {
  123. text = activeEl.value.slice(activeEl.selectionStart, activeEl.selectionEnd);
  124. }
  125. else if (window.getSelection) {
  126. text = window.getSelection().toString();
  127. }
  128.  
  129. text = text.trim();
  130.  
  131. if (text === state.oldSelectedText || text === "") {
  132. return "";
  133. }
  134.  
  135. state.oldSelectedText = text;
  136. return text;
  137. }
  138.  
  139. function showSnackbar(message, isSuccess = true) {
  140. const snackbar = document.getElementById("easy-copy-snackbar");
  141. if (!snackbar) return;
  142.  
  143. snackbar.textContent = message;
  144. snackbar.className = isSuccess ? "show success" : "show error";
  145.  
  146. setTimeout(() => {
  147. snackbar.className = snackbar.className.replace("show", "");
  148. }, config.notificationDuration);
  149. }
  150.  
  151. function copyTextToClipboard(text) {
  152. if (state.copyInProgress || !text) return;
  153.  
  154. state.copyInProgress = true;
  155.  
  156. try {
  157. if (typeof GM_setClipboard !== "undefined") {
  158. GM_setClipboard(text, "text");
  159. state.clipboard = text;
  160. showSnackbar("Text copied");
  161. state.copyInProgress = false;
  162. return;
  163. }
  164.  
  165. if (navigator.clipboard && navigator.clipboard.writeText) {
  166. navigator.clipboard.writeText(text)
  167. .then(() => {
  168. state.clipboard = text;
  169. showSnackbar("Text copied");
  170. })
  171. .catch(err => {
  172. fallbackCopyMethod(text);
  173. })
  174. .finally(() => {
  175. state.copyInProgress = false;
  176. });
  177. return;
  178. }
  179.  
  180. fallbackCopyMethod(text);
  181.  
  182. } catch (err) {
  183. state.copyInProgress = false;
  184. }
  185. }
  186.  
  187. function fallbackCopyMethod(text) {
  188. const textArea = document.createElement("textarea");
  189. textArea.value = text;
  190.  
  191. textArea.setAttribute("readonly", "");
  192. textArea.setAttribute("class", "easy-copy-textarea");
  193.  
  194. document.body.appendChild(textArea);
  195. textArea.focus();
  196. textArea.select();
  197.  
  198. try {
  199. const successful = document.execCommand('copy');
  200. if (successful) {
  201. state.clipboard = text;
  202. showSnackbar("Text copied");
  203. } else {
  204. showSnackbar("Failed to copy text", false);
  205. }
  206. } catch (err) {
  207. showSnackbar("Failed to copy text", false);
  208. }
  209.  
  210. document.body.removeChild(textArea);
  211. state.copyInProgress = false;
  212. }
  213.  
  214. function pasteText() {
  215. if (!state.clipboard) return;
  216.  
  217. const activeEl = document.activeElement;
  218. const activeElTagName = activeEl ? activeEl.tagName.toLowerCase() : null;
  219.  
  220. if (
  221. activeElTagName === "textarea" ||
  222. (activeElTagName === "input" && /^(?:text|search|password|tel|url)$/i.test(activeEl.type))
  223. ) {
  224. const start = activeEl.selectionStart || 0;
  225. const end = activeEl.selectionEnd || 0;
  226. const textBefore = activeEl.value.substring(0, start);
  227. const textAfter = activeEl.value.substring(end);
  228.  
  229. activeEl.value = textBefore + state.clipboard + textAfter;
  230. activeEl.selectionStart = activeEl.selectionEnd = start + state.clipboard.length;
  231.  
  232. const event = new Event('input', { bubbles: true });
  233. activeEl.dispatchEvent(event);
  234.  
  235. showSnackbar("Text pasted");
  236. } else if (document.execCommand) {
  237. try {
  238. document.execCommand('insertText', false, state.clipboard);
  239. showSnackbar("Text pasted");
  240. } catch (err) {
  241. showSnackbar("Failed to paste text", false);
  242. }
  243. }
  244. }
  245.  
  246. function toggleScriptState() {
  247. state.enabled = !state.enabled;
  248. showSnackbar(state.enabled ? "Auto copy enabled" : "Auto copy disabled", state.enabled);
  249. }
  250.  
  251. function initializeEvents() {
  252. document.addEventListener('keydown', function(e) {
  253. if (e.key === config.toggleKey) {
  254. toggleScriptState();
  255. }
  256. });
  257.  
  258. document.addEventListener('mouseup', function(e) {
  259. if (!state.enabled) return;
  260.  
  261. if (e.button === 1 && config.middleClickPaste) {
  262. e.preventDefault();
  263. pasteText();
  264. return;
  265. }
  266.  
  267. setTimeout(() => {
  268. const textSelected = getSelectionText();
  269. if (textSelected) {
  270. copyTextToClipboard(textSelected);
  271. }
  272. }, config.copyDelay);
  273. });
  274.  
  275. document.addEventListener('selectionchange', function() {
  276. if (!state.enabled) return;
  277.  
  278. clearTimeout(this.selectionTimer);
  279. this.selectionTimer = setTimeout(() => {
  280. if (!state.copyInProgress) {
  281. const textSelected = getSelectionText();
  282. if (textSelected) {
  283. copyTextToClipboard(textSelected);
  284. }
  285. }
  286. }, config.selectionDelay);
  287. });
  288. }
  289.  
  290. if (document.readyState === "loading") {
  291. document.addEventListener("DOMContentLoaded", function() {
  292. initializeUI();
  293. initializeEvents();
  294. });
  295. } else {
  296. initializeUI();
  297. initializeEvents();
  298. }
  299. })();