// ==UserScript==
// @name AutoCopy
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @include *
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAABpElEQVR4nO3Vv2uUQRDG8c/ebSMWqay0trATAxrUSi1S2AiWFoJYpNCgoBjURsHWJKeNRfAvsDgFixQqKdPZ2ViEiCJYBOQu8f1hEXO59713j7MUfLZ6d2a/O8vMO0OzDnin9Ku2Mjvuaw07xgSAYEVXe2indMhj92zpKJLnBhF8MDeye9hn6zbN70eRiqCw02Bra3up8BBLu1FEBxsBucXqW4csz0ULe4jorSCMuPU89boRELDMHiI6Y8V65bbCUTccc70RkaOwKLOg0IkyXa9qTjOu2LAs6NZuD86hrdTyxRNTkUqqdhXlHrngGRVEZsMpJwex9DxIZSHYclesIb65LCoHgIs66UJq6btDBZHZrPh8V6YBOX66LbOkTGckBYimBW2FVTNeuOZNyrFJ236Yl4NSy5SbVm1PDvhodqgyMledTdRlAtDzqfL9tfkwUtyaRkv9LwFj9B/w7wPycXOhqlJ0yZHKPChMi5MCiM47XhsopbVJAUHfrYbmN/EToN+02eLPfz9OYyZhFJzW1Jn3lTsxaKQjCkp52jy45r1ZvSbTb9M0d4PBozGZAAAAAElFTkSuQmCC
// ==/UserScript==
let mouseDownTarget;
window.onmousedown = function (e) {
mouseDownTarget = e.target;
};
window.onmouseup = function () {
if (!inTextBox()) {
console.log('应当复制');
document.execCommand('copy');
}
};
function inTextBox () {
let ref = [],
general = ['textarea, input, *[contenteditable="true"]'];
for (let i of general) {
let t = document.querySelectorAll(i);
if (t.length != 0)
t.forEach(j => {
ref.push(j);
});
}
rules = {
'mail.google.com': 'div[aria-label= "Message Body"]',
'outlook.live.com': '#editorParent_1',
};
for (let i in rules) {
if (i == document.location.hostname) {
let t = document.querySelectorAll(rules[i]);
if (t.length != 0)
t.forEach(j => {
ref.push(j);
});
}
}
for (let i = 0; i < ref.length; i++)
if (ref[i].contains(mouseDownTarget)) return true;
return false;
}
// ==UserScript==
// @name AutoCopy
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @include *
// @grant GM_setClipboard
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAABpElEQVR4nO3Vv2uUQRDG8c/ebSMWqay0trATAxrUSi1S2AiWFoJYpNCgoBjURsHWJKeNRfAvsDgFixQqKdPZ2ViEiCJYBOQu8f1hEXO59713j7MUfLZ6d2a/O8vMO0OzDnin9Ku2Mjvuaw07xgSAYEVXe2indMhj92zpKJLnBhF8MDeye9hn6zbN70eRiqCw02Bra3up8BBLu1FEBxsBucXqW4csz0ULe4jorSCMuPU89boRELDMHiI6Y8V65bbCUTccc70RkaOwKLOg0IkyXa9qTjOu2LAs6NZuD86hrdTyxRNTkUqqdhXlHrngGRVEZsMpJwex9DxIZSHYclesIb65LCoHgIs66UJq6btDBZHZrPh8V6YBOX66LbOkTGckBYimBW2FVTNeuOZNyrFJ236Yl4NSy5SbVm1PDvhodqgyMledTdRlAtDzqfL9tfkwUtyaRkv9LwFj9B/w7wPycXOhqlJ0yZHKPChMi5MCiM47XhsopbVJAUHfrYbmN/EToN+02eLPfz9OYyZhFJzW1Jn3lTsxaKQjCkp52jy45r1ZvSbTb9M0d4PBozGZAAAAAElFTkSuQmCC
// ==/UserScript==
(function () {
'use strict';
const AutoCopy = {
mouseDownTarget: null,
init() {
window.addEventListener('mousedown', this.onMouseDown.bind(this));
window.addEventListener('mouseup', this.onMouseUp.bind(this));
},
onMouseDown(e) {
this.mouseDownTarget = e.target;
},
async onMouseUp() {
if (!this.inTextBox()) {
console.log('应当复制');
try {
this.copyTextWithFormatting();
console.log('复制成功');
} catch (err) {
console.error('复制失败', err);
}
}
},
inTextBox() {
const ref = [];
const general = ['textarea', 'input', '*[contenteditable="true"]'];
general.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(element => ref.push(element));
});
const rules = {
'mail.google.com': 'div[aria-label="Message Body"]',
'outlook.live.com': '#editorParent_1',
};
for (const domain in rules) {
if (domain === document.location.hostname) {
const elements = document.querySelectorAll(rules[domain]);
elements.forEach(element => ref.push(element));
}
}
return ref.some(element => element.contains(this.mouseDownTarget));
},
copyTextWithFormatting() {
const richTextObj = this.getSelection();
if (!richTextObj) return;
const plainText = this.getPlainText(richTextObj.html);
console.log(plainText, richTextObj.html)
const clipboardItem = new ClipboardItem({
"text/plain": new Blob(
[plainText],
{ type: "text/plain" }
),
"text/html": new Blob(
[richTextObj.html],
{ type: "text/html" }
),
});
navigator.clipboard.write([clipboardItem]);
},
getPlainText(html) {
const tempDiv = document.createElement("div");
tempDiv.innerHTML = html;
return tempDiv.textContent || tempDiv.innerText || "";
},
getSelection() {
// These are markers used to delimit the selection during processing. They
// are removed from the final rendering.
// We use noncharacter Unicode codepoints to minimize the risk of clashing
// with anything that might legitimately be present in the document.
// U+FDD0..FDEF <noncharacters>
const MARK_SELECTION_START = "\uFDD0";
const MARK_SELECTION_END = "\uFDEF";
;
var selection = window.getSelection();
var range = selection.getRangeAt(0);
var ancestorContainer = range.commonAncestorContainer;
var doc = ancestorContainer.ownerDocument;
var startContainer = range.startContainer;
var endContainer = range.endContainer;
var startOffset = range.startOffset;
var endOffset = range.endOffset;
// let the ancestor be an element
var Node = doc.defaultView.Node;
if (
ancestorContainer.nodeType == Node.TEXT_NODE ||
ancestorContainer.nodeType == Node.CDATA_SECTION_NODE
) {
ancestorContainer = ancestorContainer.parentNode;
}
// for selectAll, let's use the entire document, including <html>...</html>
// @see nsDocumentViewer::SelectAll() for how selectAll is implemented
try {
if (ancestorContainer == doc.body) {
ancestorContainer = doc.documentElement;
}
} catch (e) { }
// each path is a "child sequence" (a.k.a. "tumbler") that
// descends from the ancestor down to the boundary point
var startPath = this.getPath(ancestorContainer, startContainer);
var endPath = this.getPath(ancestorContainer, endContainer);
// clone the fragment of interest and reset everything to be relative to it
// note: it is with the clone that we operate/munge from now on. Also note
// that we clone into a data document to prevent images in the fragment from
// loading and the like. The use of importNode here, as opposed to adoptNode,
// is _very_ important.
// XXXbz wish there were a less hacky way to create an untrusted document here
var isHTML = doc.createElement("div").tagName == "DIV";
var dataDoc = isHTML
? ancestorContainer.ownerDocument.implementation.createHTMLDocument("")
: ancestorContainer.ownerDocument.implementation.createDocument(
"",
"",
null
);
ancestorContainer = dataDoc.importNode(ancestorContainer, true);
startContainer = ancestorContainer;
endContainer = ancestorContainer;
// Only bother with the selection if it can be remapped. Don't mess with
// leaf elements (such as <isindex>) that secretly use anynomous content
// for their display appearance.
var canDrawSelection = ancestorContainer.hasChildNodes();
var tmpNode;
if (canDrawSelection) {
var i;
for (i = startPath ? startPath.length - 1 : -1; i >= 0; i--) {
startContainer = startContainer.childNodes.item(startPath[i]);
}
for (i = endPath ? endPath.length - 1 : -1; i >= 0; i--) {
endContainer = endContainer.childNodes.item(endPath[i]);
}
// add special markers to record the extent of the selection
// note: |startOffset| and |endOffset| are interpreted either as
// offsets in the text data or as child indices (see the Range spec)
// (here, munging the end point first to keep the start point safe...)
if (
endContainer.nodeType == Node.TEXT_NODE ||
endContainer.nodeType == Node.CDATA_SECTION_NODE
) {
// do some extra tweaks to try to avoid the view-source output to look like
// ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
// To get a neat output, the idea here is to remap the end point from:
// 1. ...<tag>]... to ...]<tag>...
// 2. ...]</tag>... to ...</tag>]...
if (
(endOffset > 0 && endOffset < endContainer.data.length) ||
!endContainer.parentNode ||
!endContainer.parentNode.parentNode
) {
endContainer.insertData(endOffset, MARK_SELECTION_END);
} else {
tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
endContainer = endContainer.parentNode;
if (endOffset === 0) {
endContainer.parentNode.insertBefore(tmpNode, endContainer);
} else {
endContainer.parentNode.insertBefore(
tmpNode,
endContainer.nextSibling
);
}
}
} else {
tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
endContainer.insertBefore(
tmpNode,
endContainer.childNodes.item(endOffset)
);
}
if (
startContainer.nodeType == Node.TEXT_NODE ||
startContainer.nodeType == Node.CDATA_SECTION_NODE
) {
// do some extra tweaks to try to avoid the view-source output to look like
// ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
// To get a neat output, the idea here is to remap the start point from:
// 1. ...<tag>[... to ...[<tag>...
// 2. ...[</tag>... to ...</tag>[...
if (
(startOffset > 0 && startOffset < startContainer.data.length) ||
!startContainer.parentNode ||
!startContainer.parentNode.parentNode ||
startContainer != startContainer.parentNode.lastChild
) {
startContainer.insertData(startOffset, MARK_SELECTION_START);
} else {
tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
startContainer = startContainer.parentNode;
if (startOffset === 0) {
startContainer.parentNode.insertBefore(tmpNode, startContainer);
} else {
startContainer.parentNode.insertBefore(
tmpNode,
startContainer.nextSibling
);
}
}
} else {
tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
startContainer.insertBefore(
tmpNode,
startContainer.childNodes.item(startOffset)
);
}
}
// now extract and display the syntax highlighted source
tmpNode = dataDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
tmpNode.appendChild(ancestorContainer);
return {
isHTML: isHTML,
html: tmpNode.innerHTML,
drawSelection: canDrawSelection,
baseURI: doc.baseURI,
};
},
getPath(ancestor, node) {
var n = node;
var p = n.parentNode;
if (n == ancestor || !p) {
return null;
}
var path = [];
if (!path) {
return null;
}
do {
for (var i = 0; i < p.childNodes.length; i++) {
if (p.childNodes.item(i) == n) {
path.push(i);
break;
}
}
n = p;
p = n.parentNode;
} while (n != ancestor && p);
return path;
}
};
AutoCopy.init();
})();