Greasy Fork is available in English.
Disable link dragging and select text.
// ==UserScript==// @name Select text inside a link like Opera// @version 6.0.0// @description Disable link dragging and select text.// @homepageURL https://github.com/eight04/select-text-inside-a-link-like-opera#readme// @supportURL https://github.com/eight04/select-text-inside-a-link-like-opera/issues// @license MIT// @author eight <[email protected]> (http://eight04.blogspot.tw)// @namespace eight04.blogspot.com// @include *// @grant GM_addStyle// @run-at document-start// ==/UserScript==// const IS_FIREFOX = typeof InstallTrigger !== 'undefined';// const tracker = IS_FIREFOX && createMovementTracker();const tracker = createMovementTracker();const selection = window.getSelection();// waiting -> starting -> started -> ending -> waitinglet state = "WAITING";let preState;let mousemoves = 0;let linkTarget;const initPos = [0, 0];let selectType;const EVENTS = {mousedown: e => {if (state === "WAITING") {if (e.altKey || e.button) {return;}if (/img/i.test(e.target.nodeName)) {return;}const target = findLinkTarget(e.target);if (!target || !target.href) {return;}selectType = e.ctrlKey ? "add" :e.shiftKey ? "extend" : "new";initPos[0] = e.pageX;initPos[1] = e.pageY;if (selectType === "new") {if (!selection.isCollapsed && inSelect(getInitPos(), selection)) {return;}}mousemoves = 0;state = "STARTING";linkTarget = target;linkTarget.classList.add("select-text-inside-a-link");}},mousemove: e => {if (state === "STARTING") {mousemoves++;// dragstart event may not fire all the time// https://github.com/eight04/select-text-inside-a-link-like-opera/issues/9if (mousemoves >= 3) {startSelecting(e);}}if (state === "STARTED") {const caretPos = caretPositionFromPoint(e.pageX - window.scrollX,e.pageY - window.scrollY);selection.extend(caretPos.offsetNode, caretPos.offset);}},mouseup: () => {if (state !== "WAITING") {preState = state;state = "ENDING";// delay uninit to cancel click eventsetTimeout(startWaiting);}},click: e => {if (state === "ENDING") {if (preState === "STARTED") {// fix browser clicking issue. Cancel click event if we have selected// something.const clickedTarget = findLinkTarget(e.target);if (clickedTarget === linkTarget) {e.preventDefault();e.stopImmediatePropagation();}}startWaiting();}},dragstart: e => {if (state === "STARTED") {e.preventDefault();return;}if (state === "STARTING") {startSelecting(e);}}};for (const key in EVENTS) {document.addEventListener(key, EVENTS[key], true);}if (!document.contentType || !document.contentType.endsWith("/xml")) {document.addEventListener("DOMContentLoaded", function(){GM_addStyle(".select-text-inside-a-link{ -moz-user-select: text!important; }");});}function startSelecting(e) {if (!shouldStart(e)) {startWaiting();return;}if (e.type === "dragstart") {e.preventDefault();}if (selectType === "new") {const pos = getInitPos();selection.collapse(pos.offsetNode, pos.offset);} else if (selectType === "add") {const range = new Range;const pos = getInitPos();range.setStart(pos.offsetNode, pos.offset);selection.addRange(range);}state = "STARTED";}function getInitPos() {return caretPositionFromPoint(initPos[0] - window.scrollX, initPos[1] - window.scrollY);}function shouldStart(e) {const delta = tracker ? tracker() :[Math.abs(e.pageX - initPos[0]), Math.abs(e.pageY - initPos[1])];return delta[0] >= delta[1];}function startWaiting() {if (linkTarget) {linkTarget.classList.remove("select-text-inside-a-link");}state = "WAITING";linkTarget = null;}function createMovementTracker() {// we always have to track mouse movement so we can use the delta on dragstart// event.// it is possible to calculate the movement between mousedown and dragstart// events in Chrome. In Firefox, the two events are fired at the same time.const moves = [[0, 0], [0, 0], [0, 0]];let index = 0;document.addEventListener("mousemove", e => {moves[index][0] = e.pageX;moves[index][1] = e.pageY;index = (index + 1) % 3;});return () => {const output = [];for (let i = 0; i < 2; i++) {// FIXME: should we assume that the array contains initial values [0, 0]?output.push(Math.abs(moves[index][i] - moves[(index + 1) % 3][i]) +Math.abs(moves[(index + 1) % 3][i] - moves[(index + 2) % 3][i]));}return output;};}function caretPositionFromPoint(x, y) {if (document.caretPositionFromPoint) {return document.caretPositionFromPoint(x, y);}var r = document.caretRangeFromPoint(x, y);return {offsetNode: r.startContainer,offset: r.startOffset};}function inSelect(caretPos, selection){var i, len = selection.rangeCount, range;for (i = 0; i < len; i++) {range = selection.getRangeAt(i);if (range.isPointInRange(caretPos.offsetNode, caretPos.offset)) {return true;}}return false;}function findLinkTarget(target) {while (target && target.nodeName !== "A" && target.nodeName !== "a") {target = target.parentNode;}return target;}