Greasy Fork is available in English.

Linky Square

Grab links by dragging a square.

  1. // ==UserScript==
  2. // @name Linky Square
  3. // @author eight <eight04@gmail.com>
  4. // @version 0.2.1
  5. // @namespace eight04.blogspot.com
  6. // @description Grab links by dragging a square.
  7. // @include *
  8. // @grant GM_openInTab
  9. // @grant GM_setClipboard
  10. // @grant GM_addStyle
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_registerMenuCommand
  14. // @compatible firefox
  15. // @compatible chrome
  16. // @compatible opera
  17. // @require https://greasyfork.org/scripts/7212-gm-config-eight-s-version/code/GM_config%20(eight's%20version).js?version=156587
  18. // ==/UserScript==
  19.  
  20. function createLinky(o){
  21. var delay = function(){
  22. function wrap(target) {
  23. target();
  24. target.delay = false;
  25. }
  26. return function(target) {
  27. if (!target.delay) {
  28. target.delay = true;
  29. setTimeout(wrap, 0, target);
  30. }
  31. };
  32. }();
  33. var tracker = function(o){
  34. var ox = 0, oy = 0, x = 0, y = 0,
  35. traceStart = false,
  36. linkCount = 0,
  37. enable = false;
  38.  
  39. var ui = function(){
  40. 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}");
  41.  
  42. var selectBox = document.createElement("div");
  43. selectBox.className = "linky-select-box";
  44.  
  45. var infoBox = document.createElement("div");
  46. infoBox.className = "linky-info-box";
  47.  
  48. var body = document.body;
  49. body.appendChild(selectBox);
  50. body.appendChild(infoBox);
  51. function updateSelectBox(x, y, w, h){
  52. var s = selectBox.style;
  53. s.left = x + "px";
  54. s.top = y + "px";
  55. s.width = w + "px";
  56. s.height = h + "px";
  57. }
  58. function updateInfoBox(x, y, text) {
  59. var s = infoBox.style;
  60. s.left = x + 16 + "px";
  61. s.top = y + 16 + "px";
  62. infoBox.textContent = text;
  63. }
  64. function on(){
  65. document.documentElement.classList.add("linky");
  66. }
  67. function off(){
  68. document.documentElement.classList.remove("linky");
  69. }
  70. return {
  71. on: on,
  72. off: off,
  73. updateSelectBox: updateSelectBox,
  74. updateInfoBox: updateInfoBox
  75. };
  76. }();
  77.  
  78. function getOffset(node){
  79. var rect = node.getBoundingClientRect();
  80.  
  81. return {
  82. x: window.pageXOffset + rect.left,
  83. y: window.pageYOffset + rect.top,
  84. width: rect.width,
  85. height: rect.height
  86. };
  87. }
  88.  
  89. function updateSelectBox(){
  90. ui.updateSelectBox(
  91. Math.min(Math.min(x, ox)),
  92. Math.min(y, oy),
  93. Math.abs(x - ox),
  94. Math.abs(y - oy)
  95. );
  96. }
  97.  
  98. function inSelect(node){
  99. var pos = getOffset(node);
  100. var centerx = pos.x + pos.width / 2;
  101. var centery = pos.y + pos.height / 2;
  102.  
  103. if (centerx < Math.min(ox, x)) {
  104. return false;
  105. }
  106. if (centerx > Math.max(ox, x)) {
  107. return false;
  108. }
  109. if (centery < Math.min(oy, y)) {
  110. return false;
  111. }
  112. if (centery > Math.max(oy, y)) {
  113. return false;
  114. }
  115. return true;
  116. }
  117. function isJSURL(node){
  118. return node.href.lastIndexOf("javascript:", 0) != -1;
  119. }
  120.  
  121. function updateLinkList(){
  122. var l = document.querySelectorAll("a[href]"), i, k = [], len = l.length;
  123. for (i = 0; i < len; i++) {
  124. k.push(inSelect(l[i]) && !isJSURL(l[i]));
  125. }
  126. linkCount = 0;
  127. for (i = 0; i < len; i++) {
  128. l[i].classList.toggle("linky-anchor-box", k[i]);
  129. linkCount += +k[i];
  130. }
  131. }
  132. function updateInfoBox() {
  133. ui.updateInfoBox(x, y, "selected " + linkCount + " link(s)");
  134. }
  135.  
  136. function update(){
  137. updateSelectBox();
  138. delay(updateLinkList);
  139. updateInfoBox();
  140. }
  141.  
  142. function takeLinks(){
  143. var l = document.querySelectorAll("a[href]"), i, links = [];
  144. for (i = 0; i < l.length; i++) {
  145. if (inSelect(l[i]) && !isJSURL(l[i])) {
  146. links.push(l[i].href);
  147. }
  148. }
  149. return links;
  150. }
  151.  
  152. function handler(e){
  153. if (e.type == "mousedown") {
  154. if (traceStart) {
  155. return;
  156. }
  157. if (!o.config.key.regular(e)) {
  158. return;
  159. }
  160. traceStart = true;
  161. on();
  162. // call it directly will cause firefox to stop srcolling. why?
  163. setTimeout(ui.on);
  164. x = ox = e.pageX;
  165. y = oy = e.pageY;
  166. update();
  167. } else if (e.type == "mousemove") {
  168. if (!traceStart) {
  169. return;
  170. }
  171. x = e.pageX;
  172. y = e.pageY;
  173. update();
  174. } else if (e.type == "mouseup" || e.type == "keydown") {
  175. if (
  176. !traceStart ||
  177. e.type == "keydown" && !o.config.key.copy(e) && !o.config.key.cancel(e)
  178. ) {
  179. return;
  180. }
  181. traceStart = false;
  182. off();
  183. ui.off();
  184. o.callback(e, takeLinks());
  185. }
  186. }
  187. function on(){
  188. if (enable) {
  189. return;
  190. }
  191. window.addEventListener("mousemove", handler);
  192. window.addEventListener("mouseup", handler);
  193. window.addEventListener("keydown", handler);
  194. enable = true;
  195. }
  196. function off(){
  197. window.removeEventListener("mousemove", handler);
  198. window.removeEventListener("mouseup", handler);
  199. window.removeEventListener("keydown", handler);
  200. enable = false;
  201. }
  202. function track() {
  203. window.addEventListener("mousedown", handler);
  204. handler(o.initEvent);
  205. }
  206. return {
  207. on: on,
  208. off: off,
  209. track: track
  210. };
  211. }(o);
  212. tracker.track();
  213. }
  214.  
  215. function openLinks(links) {
  216. var i;
  217. for (i = 0; i < links.length; i++) {
  218. GM_openInTab(links[i], true);
  219. }
  220. }
  221.  
  222. // do object property check
  223. function objectProperties(a, b) {
  224. var key;
  225. for (key in b) {
  226. if (a[key] !== b[key]) return false;
  227. }
  228. return true;
  229. }
  230.  
  231. function createConfig() {
  232. var config = {key: {
  233. regular: function(e) {
  234. return objectProperties(e, config.keyRegular);
  235. },
  236. copy: function(e) {
  237. return objectProperties(e, config.keyCopy);
  238. },
  239. cancel: function(e) {
  240. return objectProperties(e, config.keyCancel);
  241. }
  242. }};
  243. GM_config.setup({
  244. behavior: {
  245. label: "Default action after selecting",
  246. type: "select",
  247. default: "open",
  248. options: {
  249. open: "Open links in new tab",
  250. copy: "Copy URLs"
  251. }
  252. },
  253. keyRegular: {
  254. label: "Event to start selection",
  255. type: "textarea",
  256. default: JSON.stringify({altKey: true, ctrlKey: false, shiftKey: false, button: 0})
  257. },
  258. keyCopy: {
  259. label: "Event to copy url",
  260. type: "textarea",
  261. default: JSON.stringify({code: "KeyC"})
  262. },
  263. keyCancel: {
  264. label: "Event to cancel selection",
  265. type: "textarea",
  266. default: JSON.stringify({code: "Escape"})
  267. }
  268. }, function() {
  269. var o = GM_config.get();
  270. Object.assign(config, o);
  271. for (var key in o) {
  272. if (!key.startsWith("key")) {
  273. continue;
  274. }
  275. try {
  276. config[key] = JSON.parse(o[key]);
  277. } catch (err) {
  278. alert("Failed to create key config! Is your config broken?");
  279. }
  280. }
  281. });
  282. return config;
  283. }
  284.  
  285. function copyLinks(links) {
  286. if (!links.length) return;
  287. GM_setClipboard(links.join("\n"));
  288. }
  289.  
  290. var config = createConfig();
  291.  
  292. function handler(e, links) {
  293. if (e.type == "mouseup" && config.behavior == "open") {
  294. openLinks(links);
  295. } else if (e.type == "mouseup" || config.key.copy(e)) {
  296. copyLinks(links);
  297. }
  298. }
  299.  
  300. window.addEventListener("mousedown", function init(e){
  301. if (config.key.regular(e)) {
  302. window.removeEventListener("mousedown", init);
  303. createLinky({
  304. callback: handler,
  305. initEvent: e,
  306. config: config
  307. });
  308. }
  309. });