Select like a Boss

With this extension, you can easily select link text just like regular text, making it easier to copy. Just Select like a Boss! ;)

  1. // ==UserScript==
  2. // @name Select like a Boss
  3. // @namespace https://github.com/lcandy2/Select-like-a-Boss
  4. // @version 2024.3.16
  5. // @license MPL-2.0
  6. // @description With this extension, you can easily select link text just like regular text, making it easier to copy. Just Select like a Boss! ;)
  7. // @author seril🍋
  8. // @match *://*/*
  9. // @run-at document-end
  10. // @homepageURL https://lcandy2.github.io/Select-like-a-Boss/
  11. // @icon https://raw.githubusercontent.com/lcandy2/Select-like-a-Boss/main/src/icons/icon16.png
  12. // @supportURL https://github.com/lcandy2/Select-like-a-Boss/issues
  13. // @grant none
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18. const _bind = (evt, bind = true) => {
  19. const events = Array.isArray(evt) ? evt : [evt];
  20. const method = bind ? 'addEventListener' : 'removeEventListener';
  21. events.forEach(e => document[method](e, handlers[e], true));
  22. };
  23. const _unbind = (evt) => _bind(evt, false);
  24.  
  25. function getCurrentAnchor(n) {
  26. while (n && n !== document.body) {
  27. if (n instanceof HTMLAnchorElement || n instanceof HTMLButtonElement) return n;
  28. n = n.parentNode;
  29. }
  30. return null;
  31. }
  32.  
  33. const stopEvent = (e) => {
  34. return e.preventDefault(), e.stopPropagation(), false;
  35. };
  36.  
  37. // browser compatibility
  38. const getRangeFromPoint = (x, y) => {
  39. if (document.caretPositionFromPoint) {
  40. let range = document.createRange();
  41. let p = document.caretPositionFromPoint(x, y);
  42. range.setStart(p.offsetNode, p.offset);
  43. return range;
  44. } else return document.caretRangeFromPoint(x, y);
  45. }
  46.  
  47. // user style
  48. const _letUserSelect = (function () {
  49. let n,
  50. styleElm = document.createElement('style');
  51. let _className = 'ext-Select-like-a-Boss',
  52. _property = '-webkit-user-select:text!important;outline-width:0!important;';
  53. document.head.appendChild(styleElm);
  54. styleElm.sheet.insertRule(`.${_className}{${_property}}`, 0);
  55. return (node) => {
  56. if (node) {
  57. (n = node).classList.add(_className);
  58. } else if (n) {
  59. n.classList.remove(_className);
  60. n = null;
  61. }
  62. };
  63. })();
  64.  
  65. let selection = document.getSelection();
  66. let cursor = {},
  67. movable,
  68. needDetermineUserSelection,
  69. needCreateStartSelection,
  70. needStopClick,
  71. userSelecting,
  72. regexTDTH = /T[HD]/;
  73.  
  74. const mainMouseDownHandler = (e) => {
  75. let t = e.target
  76. // console.log(t)
  77. if (e.button !== 0) return; // LMB only
  78. // resetVars
  79. needDetermineUserSelection = needCreateStartSelection = true;
  80. userSelecting = needStopClick = false;
  81. cursor.x = e.clientX;
  82. cursor.y = e.clientY;
  83. if (selection.type === 'Range') {
  84. let range = getRangeFromPoint(cursor.x, cursor.y);
  85. if (range && selection.getRangeAt(0).isPointInRange(range.startContainer, range.startOffset)
  86. ) return;
  87. }
  88. _letUserSelect();
  89. if (t.nodeType === 3) t = t.parentNode
  90. if (e.ctrlKey && regexTDTH.test(t.tagName) || e.altKey) return;
  91. let n = getCurrentAnchor(t);
  92. // console.log(n)
  93. if (['HTMLTextAreaElement', 'HTMLCanvasElement'].includes(t.constructor.name) || t.textContent === '' || !n) return;
  94. let rect = n.getBoundingClientRect();
  95. movable = { n: n, x: Math.round(rect.left), y: Math.round(rect.top), c: 0 };
  96. _bind(['mousemove', 'mouseup', 'dragend', 'dragstart']);
  97. _letUserSelect(n);
  98. };
  99.  
  100. // detection range setting
  101. let D = 3,
  102. K = 0.8;
  103. function getOutFromMoveHandler() {
  104. _unbind(['mousemove', 'mouseup', 'dragend', 'dragstart', 'click']);
  105. _letUserSelect();
  106. selection.removeAllRanges();
  107. }
  108.  
  109. const handlers = {
  110. mousemove: (e) => {
  111. if (movable) {
  112. if (movable.n.constructor !== HTMLAnchorElement && movable.n.draggable) {
  113. movable = null;
  114. return getOutFromMoveHandler();
  115. }
  116. if (movable.c++ < 12) {
  117. let rect = movable.n.getBoundingClientRect();
  118. if (
  119. Math.round(rect.left) !== movable.x ||
  120. Math.round(rect.top) !== movable.y
  121. ) {
  122. _unbind(['mousemove', 'mouseup', 'dragend', 'dragstart', 'click']);
  123. _letUserSelect();
  124. selection.removeAllRanges();
  125. return;
  126. }
  127. } else movable = null;
  128. }
  129. let x = e.clientX;
  130. let y = e.clientY;
  131. if (needCreateStartSelection) {
  132. if (!e.altKey || !e.ctrlKey) selection.removeAllRanges();
  133. let correct = x > cursor.x ? -2 : 2;
  134. let range = getRangeFromPoint(x + correct, y);
  135. if (range) {
  136. selection.addRange(range);
  137. needCreateStartSelection = false;
  138. }
  139. }
  140. if (needDetermineUserSelection) {
  141. let vx = Math.abs(cursor.x - x),
  142. vy = Math.abs(cursor.y - y);
  143. userSelecting = vy === 0 || vx / vy > K;
  144. if (vx > D || vy > D) {
  145. needDetermineUserSelection = false;
  146. if (userSelecting) {
  147. needStopClick = true;
  148. _bind('click');
  149. }
  150. }
  151. }
  152. if (userSelecting) {
  153. let range = getRangeFromPoint(x, y);
  154. if (range) selection.extend(range.startContainer, range.startOffset);
  155. }
  156. },
  157. dragstart: (e) => {
  158. _unbind('dragstart');
  159. if (userSelecting) return stopEvent(e);
  160. },
  161. mouseup: (e) => {
  162. _unbind(['mousemove', 'mouseup', 'dragstart', 'dragend']);
  163. if (!userSelecting && needStopClick) needStopClick = false;
  164. setTimeout(() => _unbind('click'), 111);
  165. if (selection.type !== 'Range') _letUserSelect();
  166. },
  167. dragend: () => {
  168. _unbind(['dragend', 'mousemove', 'mouseup']);
  169. },
  170. click: function (e) {
  171. _unbind('click');
  172. if (selection.type !== 'Range') _letUserSelect();
  173. if (needStopClick) return stopEvent(e);
  174. },
  175. };
  176.  
  177. document.addEventListener('mousedown', mainMouseDownHandler, true);
  178.  
  179. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址