// ==UserScript==
// @name Select like a Boss
// @namespace https://github.com/lcandy2/Select-like-a-Boss
// @version 2023.7.33
// @license MPL-2.0
// @description With this extension, you can easily select link text just like regular text, making it easier to copy. Just Select like a Boss! ;)
// @author seril🍋
// @match *
// @run-at document-end
// @homepageURL https://lcandy2.github.io/Select-like-a-Boss/
// @icon https://raw.githubusercontent.com/lcandy2/Select-like-a-Boss/main/src/icons/icon16.png
// @supportURL https://github.com/lcandy2/Select-like-a-Boss/issues
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Create and insert style element
const styleElm = document.createElement('style');
document.head.appendChild(styleElm);
styleElm.sheet.insertRule('.ext-Select-like-a-Boss {user-select: text !important; outline-width: 0 !important;}', 0);
const getRangeFromPoint = (x, y) =>
document.caretPositionFromPoint
? (() => {
const range = document.createRange();
const p = document.caretPositionFromPoint(x, y);
range.setStart(p.offsetNode, p.offset);
return range;
})()
: document.caretRangeFromPoint(x, y);
const stopEvent = (e) => {
e.preventDefault();
e.stopPropagation();
return false;
};
let selection = document.getSelection();
let cursor = {}, userSelecting;
let justSelected = false;
const handlers = {
mousemove: (e) => {
let x = e.clientX, y = e.clientY;
let vx = Math.abs(cursor.x - x), vy = Math.abs(cursor.y - y);
userSelecting = vy === 0 || vx / vy > 0.8;
if (userSelecting && (vx > 3 || vy > 3)) {
let range = getRangeFromPoint(x, y);
if (range && selection.rangeCount > 0) { // Check if a selection range exists
selection.extend(range.startContainer, range.startOffset);
}
}
},
mouseup: (e) => {
['mousemove', 'mouseup', 'dragstart', 'dragend'].forEach(event => removeEvent(event, handlers[event]));
if (userSelecting) {
justSelected = true;
addEvent('click', handlers.click);
// console.log('add click event', userSelecting)
}
toggleUserSelect(e.target, false);
},
dragstart: (e) => {
if (userSelecting) return stopEvent(e);
},
dragend: (e) => ['dragend', 'mousemove', 'mouseup'].forEach(event => removeEvent(event, handlers[event])),
click: (e) => {
if (justSelected) {
justSelected = false;
setTimeout(() => {
removeEvent('click', handlers.click);
// console.log('remove click event', userSelecting)
}, 50);
return stopEvent(e);
}
},
};
const addEvent = (event, handler) => document.addEventListener(event, handler, true);
const removeEvent = (event, handler) => document.removeEventListener(event, handler, true);
const toggleUserSelect = (node, enable = false) => {
if (node) {
if (enable) node.classList.add('ext-Select-like-a-Boss');
else node.classList.remove('ext-Select-like-a-Boss');
}
};
document.addEventListener('mousedown', (e) => {
const excludeTags = e.target.closest('img, canvas, picture, svg, audio, video, object, progress, textarea')
let excludeTagsCount = 1;
if (excludeTags) excludeTagsCount = excludeTags.childElementCount;
if (e.button !== 0 || excludeTagsCount === 0) { excludeTagsCount = 1; return; }
userSelecting = false;
cursor.x = e.clientX;
cursor.y = e.clientY;
if (selection.type === 'Range') {
let range = getRangeFromPoint(cursor.x, cursor.y);
if (range && selection.getRangeAt(0).isPointInRange(range.startContainer, range.startOffset)) return;
}
let range = getRangeFromPoint(e.clientX, e.clientY);
if (range) {
selection.removeAllRanges();
selection.addRange(range);
}
toggleUserSelect(e.target, true);
['mousemove', 'mouseup', 'dragstart', 'dragend'].forEach(event => addEvent(event, handlers[event]));
}, true);
})();