Select text inside a link like Opera

Disable link dragging and select text.

当前为 2019-12-22 提交的版本,查看 最新版本

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