// ==UserScript==
// @name Linky Square
// @author eight <eight04@gmail.com>
// @version 0.2.1
// @namespace eight04.blogspot.com
// @description Grab links by dragging a square.
// @include *
// @grant GM_openInTab
// @grant GM_setClipboard
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @compatible firefox
// @compatible chrome
// @compatible opera
// @require https://greasyfork.org/scripts/7212-gm-config-eight-s-version/code/GM_config%20(eight's%20version).js?version=156587
// ==/UserScript==
function createLinky(o){
var delay = function(){
function wrap(target) {
target();
target.delay = false;
}
return function(target) {
if (!target.delay) {
target.delay = true;
setTimeout(wrap, 0, target);
}
};
}();
var tracker = function(o){
var ox = 0, oy = 0, x = 0, y = 0,
traceStart = false,
linkCount = 0,
enable = false;
var ui = function(){
GM_addStyle(".linky-info-box,.linky-select-box{position:absolute;z-index:65534;display:none}.linky .linky-anchor-box{background:#ff0}.linky .linky-anchor-box img{filter:url(data:image/svg+xml;charset=utf8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KCTxmaWx0ZXIgaWQ9ImZpbHRlciI+DQoJCTxmZUZsb29kIHJlc3VsdD0iZmxvb2RGaWxsIiB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIg0KCQkJCWZsb29kLWNvbG9yPSJ5ZWxsb3ciIGZsb29kLW9wYWNpdHk9IjEiLz4NCgkJPGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZmxvb2RGaWxsIiBtb2RlPSJtdWx0aXBseSIvPg0KCTwvZmlsdGVyPg0KPC9zdmc+DQo=#filter)}.linky-select-box{border:2px dashed red;box-sizing:border-box}.linky-info-box{color:#000;border:1px solid grey;background:#fff;padding:.3em .6em}.linky body{-moz-user-select:none;-webkit-user-select:none;pointer-events:none}.linky .linky-info-box,.linky .linky-select-box{display:block}");
var selectBox = document.createElement("div");
selectBox.className = "linky-select-box";
var infoBox = document.createElement("div");
infoBox.className = "linky-info-box";
var body = document.body;
body.appendChild(selectBox);
body.appendChild(infoBox);
function updateSelectBox(x, y, w, h){
var s = selectBox.style;
s.left = x + "px";
s.top = y + "px";
s.width = w + "px";
s.height = h + "px";
}
function updateInfoBox(x, y, text) {
var s = infoBox.style;
s.left = x + 16 + "px";
s.top = y + 16 + "px";
infoBox.textContent = text;
}
function on(){
document.documentElement.classList.add("linky");
}
function off(){
document.documentElement.classList.remove("linky");
}
return {
on: on,
off: off,
updateSelectBox: updateSelectBox,
updateInfoBox: updateInfoBox
};
}();
function getOffset(node){
var rect = node.getBoundingClientRect();
return {
x: window.pageXOffset + rect.left,
y: window.pageYOffset + rect.top,
width: rect.width,
height: rect.height
};
}
function updateSelectBox(){
ui.updateSelectBox(
Math.min(Math.min(x, ox)),
Math.min(y, oy),
Math.abs(x - ox),
Math.abs(y - oy)
);
}
function inSelect(node){
var pos = getOffset(node);
var centerx = pos.x + pos.width / 2;
var centery = pos.y + pos.height / 2;
if (centerx < Math.min(ox, x)) {
return false;
}
if (centerx > Math.max(ox, x)) {
return false;
}
if (centery < Math.min(oy, y)) {
return false;
}
if (centery > Math.max(oy, y)) {
return false;
}
return true;
}
function isJSURL(node){
return node.href.lastIndexOf("javascript:", 0) != -1;
}
function updateLinkList(){
var l = document.querySelectorAll("a[href]"), i, k = [], len = l.length;
for (i = 0; i < len; i++) {
k.push(inSelect(l[i]) && !isJSURL(l[i]));
}
linkCount = 0;
for (i = 0; i < len; i++) {
l[i].classList.toggle("linky-anchor-box", k[i]);
linkCount += +k[i];
}
}
function updateInfoBox() {
ui.updateInfoBox(x, y, "selected " + linkCount + " link(s)");
}
function update(){
updateSelectBox();
delay(updateLinkList);
updateInfoBox();
}
function takeLinks(){
var l = document.querySelectorAll("a[href]"), i, links = [];
for (i = 0; i < l.length; i++) {
if (inSelect(l[i]) && !isJSURL(l[i])) {
links.push(l[i].href);
}
}
return links;
}
function handler(e){
if (e.type == "mousedown") {
if (traceStart) {
return;
}
if (!o.config.key.regular(e)) {
return;
}
traceStart = true;
on();
// call it directly will cause firefox to stop srcolling. why?
setTimeout(ui.on);
x = ox = e.pageX;
y = oy = e.pageY;
update();
} else if (e.type == "mousemove") {
if (!traceStart) {
return;
}
x = e.pageX;
y = e.pageY;
update();
} else if (e.type == "mouseup" || e.type == "keydown") {
if (
!traceStart ||
e.type == "keydown" && !o.config.key.copy(e) && !o.config.key.cancel(e)
) {
return;
}
traceStart = false;
off();
ui.off();
o.callback(e, takeLinks());
}
}
function on(){
if (enable) {
return;
}
window.addEventListener("mousemove", handler);
window.addEventListener("mouseup", handler);
window.addEventListener("keydown", handler);
enable = true;
}
function off(){
window.removeEventListener("mousemove", handler);
window.removeEventListener("mouseup", handler);
window.removeEventListener("keydown", handler);
enable = false;
}
function track() {
window.addEventListener("mousedown", handler);
handler(o.initEvent);
}
return {
on: on,
off: off,
track: track
};
}(o);
tracker.track();
}
function openLinks(links) {
var i;
for (i = 0; i < links.length; i++) {
GM_openInTab(links[i], true);
}
}
// do object property check
function objectProperties(a, b) {
var key;
for (key in b) {
if (a[key] !== b[key]) return false;
}
return true;
}
function createConfig() {
var config = {key: {
regular: function(e) {
return objectProperties(e, config.keyRegular);
},
copy: function(e) {
return objectProperties(e, config.keyCopy);
},
cancel: function(e) {
return objectProperties(e, config.keyCancel);
}
}};
GM_config.setup({
behavior: {
label: "Default action after selecting",
type: "select",
default: "open",
options: {
open: "Open links in new tab",
copy: "Copy URLs"
}
},
keyRegular: {
label: "Event to start selection",
type: "textarea",
default: JSON.stringify({altKey: true, ctrlKey: false, shiftKey: false, button: 0})
},
keyCopy: {
label: "Event to copy url",
type: "textarea",
default: JSON.stringify({code: "KeyC"})
},
keyCancel: {
label: "Event to cancel selection",
type: "textarea",
default: JSON.stringify({code: "Escape"})
}
}, function() {
var o = GM_config.get();
Object.assign(config, o);
for (var key in o) {
if (!key.startsWith("key")) {
continue;
}
try {
config[key] = JSON.parse(o[key]);
} catch (err) {
alert("Failed to create key config! Is your config broken?");
}
}
});
return config;
}
function copyLinks(links) {
if (!links.length) return;
GM_setClipboard(links.join("\n"));
}
var config = createConfig();
function handler(e, links) {
if (e.type == "mouseup" && config.behavior == "open") {
openLinks(links);
} else if (e.type == "mouseup" || config.key.copy(e)) {
copyLinks(links);
}
}
window.addEventListener("mousedown", function init(e){
if (config.key.regular(e)) {
window.removeEventListener("mousedown", init);
createLinky({
callback: handler,
initEvent: e,
config: config
});
}
});