- // ==UserScript==
- // @name Select like a Boss
- // @namespace https://github.com/lcandy2/Select-like-a-Boss
- // @version 2024.3.16
- // @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';
- const _bind = (evt, bind = true) => {
- const events = Array.isArray(evt) ? evt : [evt];
- const method = bind ? 'addEventListener' : 'removeEventListener';
- events.forEach(e => document[method](e, handlers[e], true));
- };
- const _unbind = (evt) => _bind(evt, false);
-
- function getCurrentAnchor(n) {
- while (n && n !== document.body) {
- if (n instanceof HTMLAnchorElement || n instanceof HTMLButtonElement) return n;
- n = n.parentNode;
- }
- return null;
- }
-
- const stopEvent = (e) => {
- return e.preventDefault(), e.stopPropagation(), false;
- };
-
- // browser compatibility
- const getRangeFromPoint = (x, y) => {
- if (document.caretPositionFromPoint) {
- let range = document.createRange();
- let p = document.caretPositionFromPoint(x, y);
- range.setStart(p.offsetNode, p.offset);
- return range;
- } else return document.caretRangeFromPoint(x, y);
- }
-
- // user style
- const _letUserSelect = (function () {
- let n,
- styleElm = document.createElement('style');
- let _className = 'ext-Select-like-a-Boss',
- _property = '-webkit-user-select:text!important;outline-width:0!important;';
- document.head.appendChild(styleElm);
- styleElm.sheet.insertRule(`.${_className}{${_property}}`, 0);
- return (node) => {
- if (node) {
- (n = node).classList.add(_className);
- } else if (n) {
- n.classList.remove(_className);
- n = null;
- }
- };
- })();
-
- let selection = document.getSelection();
- let cursor = {},
- movable,
- needDetermineUserSelection,
- needCreateStartSelection,
- needStopClick,
- userSelecting,
- regexTDTH = /T[HD]/;
-
- const mainMouseDownHandler = (e) => {
- let t = e.target
- // console.log(t)
- if (e.button !== 0) return; // LMB only
- // resetVars
- needDetermineUserSelection = needCreateStartSelection = true;
- userSelecting = needStopClick = 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;
- }
- _letUserSelect();
- if (t.nodeType === 3) t = t.parentNode
- if (e.ctrlKey && regexTDTH.test(t.tagName) || e.altKey) return;
- let n = getCurrentAnchor(t);
- // console.log(n)
- if (['HTMLTextAreaElement', 'HTMLCanvasElement'].includes(t.constructor.name) || t.textContent === '' || !n) return;
- let rect = n.getBoundingClientRect();
- movable = { n: n, x: Math.round(rect.left), y: Math.round(rect.top), c: 0 };
- _bind(['mousemove', 'mouseup', 'dragend', 'dragstart']);
- _letUserSelect(n);
- };
-
- // detection range setting
- let D = 3,
- K = 0.8;
- function getOutFromMoveHandler() {
- _unbind(['mousemove', 'mouseup', 'dragend', 'dragstart', 'click']);
- _letUserSelect();
- selection.removeAllRanges();
- }
-
- const handlers = {
- mousemove: (e) => {
- if (movable) {
- if (movable.n.constructor !== HTMLAnchorElement && movable.n.draggable) {
- movable = null;
- return getOutFromMoveHandler();
- }
- if (movable.c++ < 12) {
- let rect = movable.n.getBoundingClientRect();
- if (
- Math.round(rect.left) !== movable.x ||
- Math.round(rect.top) !== movable.y
- ) {
- _unbind(['mousemove', 'mouseup', 'dragend', 'dragstart', 'click']);
- _letUserSelect();
- selection.removeAllRanges();
- return;
- }
- } else movable = null;
- }
- let x = e.clientX;
- let y = e.clientY;
- if (needCreateStartSelection) {
- if (!e.altKey || !e.ctrlKey) selection.removeAllRanges();
- let correct = x > cursor.x ? -2 : 2;
- let range = getRangeFromPoint(x + correct, y);
- if (range) {
- selection.addRange(range);
- needCreateStartSelection = false;
- }
- }
- if (needDetermineUserSelection) {
- let vx = Math.abs(cursor.x - x),
- vy = Math.abs(cursor.y - y);
- userSelecting = vy === 0 || vx / vy > K;
- if (vx > D || vy > D) {
- needDetermineUserSelection = false;
- if (userSelecting) {
- needStopClick = true;
- _bind('click');
- }
- }
- }
- if (userSelecting) {
- let range = getRangeFromPoint(x, y);
- if (range) selection.extend(range.startContainer, range.startOffset);
- }
- },
- dragstart: (e) => {
- _unbind('dragstart');
- if (userSelecting) return stopEvent(e);
- },
- mouseup: (e) => {
- _unbind(['mousemove', 'mouseup', 'dragstart', 'dragend']);
- if (!userSelecting && needStopClick) needStopClick = false;
- setTimeout(() => _unbind('click'), 111);
- if (selection.type !== 'Range') _letUserSelect();
- },
- dragend: () => {
- _unbind(['dragend', 'mousemove', 'mouseup']);
- },
- click: function (e) {
- _unbind('click');
- if (selection.type !== 'Range') _letUserSelect();
- if (needStopClick) return stopEvent(e);
- },
- };
-
- document.addEventListener('mousedown', mainMouseDownHandler, true);
-
- })();