🏠 返回首頁 

Greasy Fork is available in English.

Select text inside a link like Opera

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 -> waiting
let 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/9
if (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 event
setTimeout(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;
}