Select text inside a link like Opera

Disable link dragging and select text.

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

  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. 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. if (!document.contentType || !document.contentType.endsWith("/xml")) {
  99. document.addEventListener("DOMContentLoaded", function(){
  100. GM_addStyle(".select-text-inside-a-link{ -moz-user-select: text!important; }");
  101. });
  102. }
  103.  
  104. function createMovementTracker() {
  105. // we always have to track mouse movement so we can use the delta in dragstart
  106. // event.
  107. // it is possible to calculate the movement between mousedown and dragstart
  108. // events in Chrome. In Firefox, the two events are fired in the same time.
  109. const tracker = {
  110. posX: 0,
  111. posY: 0,
  112. deltaX: 0,
  113. deltaY: 0
  114. };
  115. document.addEventListener("mousemove", onMouseMove);
  116. return tracker;
  117. function onMouseMove(e) {
  118. if (Math.abs(e.pageX - tracker.posX) < 5 && Math.abs(e.pageY - tracker.posY) < 5) {
  119. return;
  120. }
  121. tracker.deltaX = e.pageX - tracker.posX;
  122. tracker.deltaY = e.pageY - tracker.posY;
  123. tracker.posX = e.pageX;
  124. tracker.posY = e.pageY;
  125. }
  126. }
  127.  
  128. function caretPositionFromPoint(x, y) {
  129. if (document.caretPositionFromPoint) {
  130. return document.caretPositionFromPoint(x, y);
  131. }
  132. var r = document.caretRangeFromPoint(x, y);
  133. return {
  134. offsetNode: r.startContainer,
  135. offset: r.startOffset
  136. };
  137. }
  138.  
  139. function inSelect(caretPos, selection){
  140. var i, len = selection.rangeCount, range;
  141. for (i = 0; i < len; i++) {
  142. range = selection.getRangeAt(i);
  143. if (range.isPointInRange(caretPos.offsetNode, caretPos.offset)) {
  144. return true;
  145. }
  146. }
  147. return false;
  148. }
  149.  
  150. function findLinkTarget(target) {
  151. while (target && target.nodeName !== "A" && target.nodeName !== "a") {
  152. target = target.parentNode;
  153. }
  154. return target;
  155. }