Select text inside a link like Opera

Disable link dragging and select text.

目前为 2019-06-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Select text inside a link like Opera
  3. // @version 5.0.1
  4. // @description Disable link dragging and select text.
  5. // @homepageURL https://github.com/eight04/select-text-inside-a-link-like-opera#readme
  6. // @supportURL https://github.com/eight04/select-text-inside-a-link-like-opera/issues
  7. // @license MIT
  8. // @author eight <eight04@gmail.com> (http://eight04.blogspot.tw)
  9. // @namespace eight04.blogspot.com
  10. // @include *
  11. // @grant GM_addStyle
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. const IS_FIREFOX = typeof InstallTrigger !== 'undefined';
  16. const movementTracker = IS_FIREFOX && createMovementTracker();
  17.  
  18. document.addEventListener("mousedown", e => {
  19. // only Firefox supports multiple range?
  20. if (e.shiftKey || e.altKey || e.button || e.ctrlKey && !IS_FIREFOX) {
  21. return;
  22. }
  23. if (e.target.nodeName === "IMG" || e.target.nodeName === "img") {
  24. return;
  25. }
  26. const target = findLinkTarget(e.target);
  27. if (!target || !target.href) {
  28. return;
  29. }
  30. const initX = e.pageX;
  31. const initY = e.pageY;
  32. let posX = initX;
  33. let posY = initY;
  34. let selection;
  35. let mouseMoves = 0;
  36. const events = {
  37. mousedown: uninit,
  38. mousemove: e => {
  39. posX = e.pageX;
  40. posY = e.pageY;
  41. if (!selection) {
  42. mouseMoves++;
  43. // dragstart may not fire all the time
  44. // https://github.com/eight04/select-text-inside-a-link-like-opera/issues/9
  45. if (mouseMoves >= 3) {
  46. startSelectText();
  47. }
  48. return;
  49. }
  50. const caretPos = caretPositionFromPoint(
  51. posX - window.scrollX,
  52. posY - window.scrollY
  53. );
  54. selection.extend(caretPos.offsetNode, caretPos.offset);
  55. },
  56. mouseup: () => {
  57. // delay uninit to cancel click event
  58. setTimeout(uninit);
  59. },
  60. click: e => {
  61. if (!selection) {
  62. return;
  63. }
  64. // fix browser clicking issue. Cancel click event if we have selected
  65. // something.
  66. const clickedTarget = findLinkTarget(e.target);
  67. if (clickedTarget === target) {
  68. e.preventDefault();
  69. e.stopImmediatePropagation();
  70. }
  71. },
  72. dragstart: e => {
  73. if (startSelectText()) {
  74. e.preventDefault();
  75. }
  76. }
  77. };
  78. for (const key of Object.keys(events)) {
  79. document.addEventListener(key, events[key], true);
  80. }
  81. function uninit() {
  82. target.classList.remove("select-text-inside-a-link");
  83. for (const key of Object.keys(events)) {
  84. document.removeEventListener(key, events[key], true);
  85. }
  86. }
  87. function startSelectText() {
  88. const delta = movementTracker || {deltaX: posX - initX, deltaY: posY - initY};
  89. if (Math.abs(delta.deltaX) < Math.abs(delta.deltaY)) {
  90. uninit();
  91. return false;
  92. }
  93. selection = window.getSelection();
  94. const caretPos = caretPositionFromPoint(initX - window.scrollX, initY - window.scrollY);
  95. if (!selection.isCollapsed && inSelect(caretPos, selection)) {
  96. uninit();
  97. return false;
  98. }
  99. if (!e.ctrlKey) {
  100. selection.collapse(caretPos.offsetNode, caretPos.offset);
  101. } else {
  102. const range = new Range;
  103. range.setStart(caretPos.offsetNode, caretPos.offset);
  104. selection.addRange(range);
  105. }
  106. target.classList.add("select-text-inside-a-link");
  107. return true;
  108. }
  109. }, true);
  110.  
  111. if (!document.contentType || !document.contentType.endsWith("/xml")) {
  112. document.addEventListener("DOMContentLoaded", function(){
  113. GM_addStyle(".select-text-inside-a-link{ -moz-user-select: text!important; }");
  114. });
  115. }
  116.  
  117. function createMovementTracker() {
  118. // we always have to track mouse movement so we can use the delta in dragstart
  119. // event.
  120. // it is possible to calculate the movement between mousedown and dragstart
  121. // events in Chrome. In Firefox, the two events are fired in the same time.
  122. const tracker = {
  123. posX: 0,
  124. posY: 0,
  125. deltaX: 0,
  126. deltaY: 0
  127. };
  128. document.addEventListener("mousemove", onMouseMove);
  129. return tracker;
  130. function onMouseMove(e) {
  131. if (Math.abs(e.pageX - tracker.posX) < 5 && Math.abs(e.pageY - tracker.posY) < 5) {
  132. return;
  133. }
  134. tracker.deltaX = e.pageX - tracker.posX;
  135. tracker.deltaY = e.pageY - tracker.posY;
  136. tracker.posX = e.pageX;
  137. tracker.posY = e.pageY;
  138. }
  139. }
  140.  
  141. function caretPositionFromPoint(x, y) {
  142. if (document.caretPositionFromPoint) {
  143. return document.caretPositionFromPoint(x, y);
  144. }
  145. var r = document.caretRangeFromPoint(x, y);
  146. return {
  147. offsetNode: r.startContainer,
  148. offset: r.startOffset
  149. };
  150. }
  151.  
  152. function inSelect(caretPos, selection){
  153. var i, len = selection.rangeCount, range;
  154. for (i = 0; i < len; i++) {
  155. range = selection.getRangeAt(i);
  156. if (range.isPointInRange(caretPos.offsetNode, caretPos.offset)) {
  157. return true;
  158. }
  159. }
  160. return false;
  161. }
  162.  
  163. function findLinkTarget(target) {
  164. while (target && target.nodeName !== "A" && target.nodeName !== "a") {
  165. target = target.parentNode;
  166. }
  167. return target;
  168. }