Greasy Fork is available in English.

Open Links

Select links with Z key and open them in new tabs

  1. // ==UserScript==
  2. // @name Open Links
  3. // @namespace https://github.com/mefengl
  4. // @version 1.0.3
  5. // @description Select links with Z key and open them in new tabs
  6. // @author mefengl
  7. // @match *://*/*
  8. // @grant GM_openInTab
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. let selectionRectangle = null;
  16. let elementsInsideRectangle = [];
  17. let countLabel = null;
  18. let zKeyPressed = false;
  19. let startX, startY;
  20.  
  21. function createRectangle(x, y) {
  22. let div = document.createElement('div');
  23. div.style.border = '1px dashed red';
  24. div.style.position = 'fixed';
  25. div.style.pointerEvents = 'none';
  26. return div;
  27. }
  28.  
  29. function createCountLabel() {
  30. let div = document.createElement('div');
  31. div.style.position = 'absolute';
  32. div.style.bottom = '0px';
  33. div.style.right = '5px';
  34. div.style.backgroundColor = 'transparent';
  35. return div;
  36. }
  37.  
  38. function detectElementsInRectangle() {
  39. elementsInsideRectangle = Array.from(document.querySelectorAll('a')).filter(el => {
  40. let rect = el.getBoundingClientRect();
  41. return rect.top >= parseInt(selectionRectangle.style.top) && rect.left >= parseInt(selectionRectangle.style.left) && rect.bottom <= (parseInt(selectionRectangle.style.top) + parseInt(selectionRectangle.style.height)) && rect.right <= (parseInt(selectionRectangle.style.left) + parseInt(selectionRectangle.style.width));
  42. });
  43.  
  44. elementsInsideRectangle.forEach(el => el.style.border = '1px solid red');
  45.  
  46. countLabel.innerText = elementsInsideRectangle.length;
  47. }
  48.  
  49. function removeBordersFromLinks() {
  50. document.querySelectorAll('a').forEach(el => el.style.border = '');
  51. }
  52.  
  53. let mouseDown = false;
  54. document.addEventListener('mousedown', () => {
  55. mouseDown = true;
  56. });
  57. document.addEventListener('mouseup', () => {
  58. mouseDown = false;
  59. });
  60.  
  61. document.addEventListener('keydown', (e) => {
  62. if (e.key.toLowerCase() === 'z' && mouseDown) {
  63. e.preventDefault();
  64. } else if (e.key.toLowerCase() === 'z') {
  65. zKeyPressed = true;
  66. }
  67. });
  68.  
  69. document.addEventListener('keyup', (e) => {
  70. if (e.key.toLowerCase() === 'z') {
  71. zKeyPressed = false;
  72. openLinksAndClear();
  73. }
  74. });
  75.  
  76.  
  77. document.addEventListener('mousedown', (e) => {
  78. if (!zKeyPressed || e.buttons === 0) {
  79. clearSelection();
  80. return;
  81. }
  82. e.preventDefault();
  83. removeBordersFromLinks();
  84. startX = e.clientX;
  85. startY = e.clientY;
  86. selectionRectangle = createRectangle(startX, startY);
  87. document.body.appendChild(selectionRectangle);
  88. countLabel = createCountLabel();
  89. selectionRectangle.appendChild(countLabel);
  90. });
  91.  
  92. document.addEventListener('mousemove', (e) => {
  93. if (!zKeyPressed || !selectionRectangle || e.buttons === 0) {
  94. clearSelection();
  95. return;
  96. }
  97. e.preventDefault();
  98. removeBordersFromLinks();
  99. selectionRectangle.style.left = Math.min(e.clientX, startX) + 'px';
  100. selectionRectangle.style.top = Math.min(e.clientY, startY) + 'px';
  101. selectionRectangle.style.width = Math.abs(e.clientX - startX) + 'px';
  102. selectionRectangle.style.height = Math.abs(e.clientY - startY) + 'px';
  103. detectElementsInRectangle();
  104. });
  105.  
  106. function openLinksInBackground(urls) {
  107. urls.forEach((url) => {
  108. GM_openInTab(url, { active: false });
  109. });
  110. }
  111.  
  112. // Clear selection and open links if mouse is released before 'z'
  113. document.addEventListener('mouseup', (e) => {
  114. if (!zKeyPressed || !selectionRectangle || e.buttons !== 0) {
  115. clearSelection();
  116. return;
  117. }
  118. e.preventDefault();
  119. openLinksAndClear();
  120. });
  121.  
  122. function clearSelection() {
  123. if (selectionRectangle) {
  124. document.body.removeChild(selectionRectangle);
  125. selectionRectangle = null;
  126. }
  127. removeBordersFromLinks();
  128. }
  129.  
  130. // To open links and clear selection
  131. function openLinksAndClear() {
  132. if (selectionRectangle) {
  133. const urlsToOpen = [...new Set(elementsInsideRectangle.map(el => el.href))]
  134. openLinksInBackground(urlsToOpen);
  135. clearSelection();
  136. }
  137. }
  138. })();