Select like a Boss

With this extension, you can easily select link text just like regular text, making it easier to copy. Just Select like a Boss! ;)

目前為 2023-07-20 提交的版本,檢視 最新版本

// ==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);

})();