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! ;)

当前为 2023-07-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Select like a Boss
  3. // @namespace https://github.com/lcandy2/Select-like-a-Boss
  4. // @version 2023.7.33
  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. // Create and insert style element
  19. const styleElm = document.createElement('style');
  20. document.head.appendChild(styleElm);
  21. styleElm.sheet.insertRule('.ext-Select-like-a-Boss {user-select: text !important; outline-width: 0 !important;}', 0);
  22.  
  23. const getRangeFromPoint = (x, y) =>
  24. document.caretPositionFromPoint
  25. ? (() => {
  26. const range = document.createRange();
  27. const p = document.caretPositionFromPoint(x, y);
  28. range.setStart(p.offsetNode, p.offset);
  29. return range;
  30. })()
  31. : document.caretRangeFromPoint(x, y);
  32.  
  33. const stopEvent = (e) => {
  34. e.preventDefault();
  35. e.stopPropagation();
  36. return false;
  37. };
  38.  
  39. let selection = document.getSelection();
  40. let cursor = {}, userSelecting;
  41. let justSelected = false;
  42.  
  43. const handlers = {
  44. mousemove: (e) => {
  45. let x = e.clientX, y = e.clientY;
  46. let vx = Math.abs(cursor.x - x), vy = Math.abs(cursor.y - y);
  47. userSelecting = vy === 0 || vx / vy > 0.8;
  48. if (userSelecting && (vx > 3 || vy > 3)) {
  49. let range = getRangeFromPoint(x, y);
  50. if (range && selection.rangeCount > 0) { // Check if a selection range exists
  51. selection.extend(range.startContainer, range.startOffset);
  52. }
  53. }
  54. },
  55. mouseup: (e) => {
  56. ['mousemove', 'mouseup', 'dragstart', 'dragend'].forEach(event => removeEvent(event, handlers[event]));
  57. if (userSelecting) {
  58. justSelected = true;
  59. addEvent('click', handlers.click);
  60. // console.log('add click event', userSelecting)
  61. }
  62. toggleUserSelect(e.target, false);
  63. },
  64. dragstart: (e) => {
  65. if (userSelecting) return stopEvent(e);
  66. },
  67. dragend: (e) => ['dragend', 'mousemove', 'mouseup'].forEach(event => removeEvent(event, handlers[event])),
  68. click: (e) => {
  69. if (justSelected) {
  70. justSelected = false;
  71. setTimeout(() => {
  72. removeEvent('click', handlers.click);
  73. // console.log('remove click event', userSelecting)
  74. }, 50);
  75. return stopEvent(e);
  76. }
  77. },
  78. };
  79.  
  80. const addEvent = (event, handler) => document.addEventListener(event, handler, true);
  81. const removeEvent = (event, handler) => document.removeEventListener(event, handler, true);
  82.  
  83. const toggleUserSelect = (node, enable = false) => {
  84. if (node) {
  85. if (enable) node.classList.add('ext-Select-like-a-Boss');
  86. else node.classList.remove('ext-Select-like-a-Boss');
  87. }
  88. };
  89.  
  90. document.addEventListener('mousedown', (e) => {
  91. const excludeTags = e.target.closest('img, canvas, picture, svg, audio, video, object, progress, textarea')
  92. let excludeTagsCount = 1;
  93. if (excludeTags) excludeTagsCount = excludeTags.childElementCount;
  94. if (e.button !== 0 || excludeTagsCount === 0) { excludeTagsCount = 1; return; }
  95. userSelecting = false;
  96. cursor.x = e.clientX;
  97. cursor.y = e.clientY;
  98. if (selection.type === 'Range') {
  99. let range = getRangeFromPoint(cursor.x, cursor.y);
  100. if (range && selection.getRangeAt(0).isPointInRange(range.startContainer, range.startOffset)) return;
  101. }
  102. let range = getRangeFromPoint(e.clientX, e.clientY);
  103. if (range) {
  104. selection.removeAllRanges();
  105. selection.addRange(range);
  106. }
  107. toggleUserSelect(e.target, true);
  108. ['mousemove', 'mouseup', 'dragstart', 'dragend'].forEach(event => addEvent(event, handlers[event]));
  109. }, true);
  110.  
  111. })();