Select text inside a link like Opera

Disable link dragging and select text.

当前为 2019-01-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Select text inside a link like Opera
  3. // @version 5.0.0
  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") {
  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. const events = {
  36. mousemove: e => {
  37. posX = e.pageX;
  38. posY = e.pageY;
  39. if (!selection) {
  40. return;
  41. }
  42. const caretPos = caretPositionFromPoint(
  43. posX - window.scrollX,
  44. posY - window.scrollY
  45. );
  46. selection.extend(caretPos.offsetNode, caretPos.offset);
  47. },
  48. mouseup: () => {
  49. // delay uninit to cancel click event
  50. setTimeout(uninit);
  51. },
  52. click: e => {
  53. if (!selection) {
  54. return;
  55. }
  56. // fix browser clicking issue. Cancel click event if we have selected
  57. // something.
  58. const clickedTarget = findLinkTarget(e.target);
  59. if (clickedTarget === target) {
  60. e.preventDefault();
  61. e.stopImmediatePropagation();
  62. }
  63. },
  64. dragstart: e => {
  65. const delta = movementTracker || {deltaX: posX - initX, deltaY: posY - initY};
  66. if (Math.abs(delta.deltaX) < Math.abs(delta.deltaY)) {
  67. uninit();
  68. return;
  69. }
  70. selection = window.getSelection();
  71. const caretPos = caretPositionFromPoint(initX - window.scrollX, initY - window.scrollY);
  72. if (!selection.isCollapsed && inSelect(caretPos, selection)) {
  73. uninit();
  74. return;
  75. }
  76. if (!e.ctrlKey) {
  77. selection.collapse(caretPos.offsetNode, caretPos.offset);
  78. } else {
  79. const range = new Range;
  80. range.setStart(caretPos.offsetNode, caretPos.offset);
  81. selection.addRange(range);
  82. }
  83. target.classList.add("select-text-inside-a-link");
  84. e.preventDefault();
  85. }
  86. };
  87. for (const key of Object.keys(events)) {
  88. document.addEventListener(key, events[key], true);
  89. }
  90. function uninit() {
  91. target.classList.remove("select-text-inside-a-link");
  92. for (const key of Object.keys(events)) {
  93. document.removeEventListener(key, events[key], true);
  94. }
  95. }
  96. }, true);
  97.  
  98. document.addEventListener("DOMContentLoaded", function(){
  99. GM_addStyle(".select-text-inside-a-link{ -moz-user-select: text!important; }");
  100. });
  101.  
  102. function createMovementTracker() {
  103. // we always have to track mouse movement so we can use the delta in dragstart
  104. // event.
  105. // it is possible to calculate the movement between mousedown and dragstart
  106. // events in Chrome. In Firefox, the two events are fired in the same time.
  107. const tracker = {
  108. posX: 0,
  109. posY: 0,
  110. deltaX: 0,
  111. deltaY: 0
  112. };
  113. document.addEventListener("mousemove", onMouseMove);
  114. return tracker;
  115. function onMouseMove(e) {
  116. if (Math.abs(e.pageX - tracker.posX) < 5 && Math.abs(e.pageY - tracker.posY) < 5) {
  117. return;
  118. }
  119. tracker.deltaX = e.pageX - tracker.posX;
  120. tracker.deltaY = e.pageY - tracker.posY;
  121. tracker.posX = e.pageX;
  122. tracker.posY = e.pageY;
  123. }
  124. }
  125.  
  126. function caretPositionFromPoint(x, y) {
  127. if (document.caretPositionFromPoint) {
  128. return document.caretPositionFromPoint(x, y);
  129. }
  130. var r = document.caretRangeFromPoint(x, y);
  131. return {
  132. offsetNode: r.startContainer,
  133. offset: r.startOffset
  134. };
  135. }
  136.  
  137. function inSelect(caretPos, selection){
  138. var i, len = selection.rangeCount, range;
  139. for (i = 0; i < len; i++) {
  140. range = selection.getRangeAt(i);
  141. if (range.isPointInRange(caretPos.offsetNode, caretPos.offset)) {
  142. return true;
  143. }
  144. }
  145. return false;
  146. }
  147.  
  148. function findLinkTarget(target) {
  149. while (target && target.nodeName !== "A") {
  150. target = target.parentNode;
  151. }
  152. return target;
  153. }