Mouseless Link Opener

Open links and click buttons easily using only the keyboard!

  1. // ==UserScript==
  2. // @name Mouseless Link Opener
  3. // @description Open links and click buttons easily using only the keyboard!
  4. // @version 0.4.0
  5. // @author sllypper
  6. // @homepage https://gf.qytechs.cn/en/users/55535-sllypper
  7. // @namespace sllypper
  8. // @match *://*/*
  9. // @grant GM_openInTab
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // for American keyboard layout, switch it to "`" instead
  16. // so you can use it with one hand
  17. // or whatever other key you want
  18. const HOTKEY = "'"
  19.  
  20. const also_other_elements = true
  21.  
  22. // Label Colors
  23. const textColor = "#fff"
  24. const backgroundColor = "#555"
  25.  
  26. //
  27.  
  28. let elBag = [];
  29. let isSet = false;
  30. let visibleLinks = [];
  31. //let page;
  32.  
  33. function setListeners() {
  34. document.addEventListener("keydown", event => {
  35. if (!isSet) {
  36. if (event.key === HOTKEY && event.ctrlKey) {
  37. event.preventDefault()
  38. getElements()
  39. filterElements()
  40. pickTooltips()
  41. placeTooltips()
  42. isSet = true
  43. }
  44. } else {
  45. if (event.key >= 0 && event.key < 10) {
  46. event.preventDefault()
  47. if (chooseTooltip(parseInt(event.key), event.ctrlKey)) {
  48. clearTooltips()
  49. isSet = false
  50. }
  51. } else if (event.key === "Tab") {
  52. event.preventDefault()
  53. shiftTooltips()
  54. } else if (event.ctrlKey) {
  55. event.preventDefault()
  56. } else {
  57. clearTooltips()
  58. isSet = false
  59. }
  60. }
  61. })
  62. }
  63.  
  64. function chooseTooltip(num, ctrl) {
  65. if (num > visibleLinks.length || (num === 0 && visibleLinks.length < 10)) return false
  66. if (num === 0) {
  67. if (!ctrl) visibleLinks[9].click()
  68. else clickNewTab(visibleLinks[9])
  69. return true
  70. }
  71. // console.log(visibleLinks[num-1])
  72. // console.log(ctrl)
  73. if (!ctrl) visibleLinks[num-1].click()
  74. else clickNewTab(visibleLinks[num-1])
  75. return true
  76. }
  77.  
  78. function clickNewTab(el) {
  79. GM_openInTab(el.href, {'insert': true, 'setParent': true})
  80. // GM_openInTab(el.href, {'active': true, 'insert': true, 'setParent': true})
  81. }
  82.  
  83. function shiftTooltips() {
  84. visibleLinks = []
  85. clearTooltips()
  86. if (elBag.length > 0) {
  87. pickTooltips()
  88. placeTooltips()
  89. } else {
  90. getElements()
  91. filterElements()
  92. pickTooltips()
  93. placeTooltips()
  94. }
  95. }
  96.  
  97. function clearAction() {
  98. // let el
  99. for (let el of document.querySelectorAll('.displayText_container')) { el.remove() }
  100. for (let el of document.querySelectorAll('.displayText')) { el.remove() }
  101. visibleLinks = []
  102. isSet = false
  103. }
  104.  
  105. function clearTooltips() {
  106. for (let el of document.querySelectorAll('.displayText_container')) { el.remove() }
  107. for (let el of document.querySelectorAll('.displayText')) { el.remove() }
  108. visibleLinks = []
  109. }
  110.  
  111. function pickTooltips() {
  112. // gets up to 10 tooltips from elBag into visibleLink
  113. visibleLinks = []
  114. let i,
  115. len = elBag.length
  116.  
  117. for (i=0; i <= 9 && i < len; i++) {
  118. visibleLinks.push(elBag.shift())
  119. }
  120. }
  121.  
  122. function placeTooltips() {
  123. //console.log('placing '+visibleLinks.length+' tooltips', visibleLinks, elBag)
  124. for (let i = 0; i <= 8 && i < visibleLinks.length; i++) {
  125. createTooltip(visibleLinks[i], i+1)
  126. }
  127. if (visibleLinks.length === 10) {
  128. createTooltip(visibleLinks[9], 0)
  129. }
  130. }
  131.  
  132. function getElements() {
  133. if (also_other_elements) {
  134. elBag = Array.from(document.querySelectorAll('div,a,button'))
  135. return
  136. }
  137.  
  138. elBag = Array.from(document.querySelectorAll('a,button'))
  139. }
  140.  
  141. function filterElements() {
  142. elBag = elBag.filter(
  143. (el) => isInViewport(el) && !(el.offsetWidth === 0 || el.offsetHeight === 0) && !(el.nodeName === "A" && el.getAttribute('href') == null)
  144. )
  145.  
  146. if (also_other_elements) elBag = elBag.filter((el) => el.tagName == "A" || el.tagName == "BUTTON" || window.getComputedStyle(el).cursor == "pointer" || el.type == "checkbox" || el.type == "radio")
  147.  
  148. elBag = elBag.filter(isVisible)
  149. }
  150.  
  151. function createTooltip(el, num) {
  152. let oldDisplay = el.style.display
  153. el.style.display = "inline-block"
  154. const rect = getCoords(el);
  155. el.style.display = oldDisplay
  156.  
  157. const tooltip = document.createElement("div")
  158. tooltip.setAttribute("class", "displayText")
  159. tooltip.innerText = num
  160.  
  161. tooltip.style.left = rect.right + 'px'
  162. tooltip.style.top = rect.top + 'px'
  163.  
  164. document.body.appendChild(tooltip)
  165.  
  166. return tooltip
  167. }
  168.  
  169. function addCustomCSS() {
  170. let customStyles = document.createElement("style");
  171. customStyles.setAttribute("type", "text/css");
  172.  
  173. let styles = ".displayText { position: absolute; top: 0; right: -16px; margin: 0; overflow: visible !important; width: min-content; background-color: " + backgroundColor + "; color: " + textColor + "; text-align: center; border-radius: 2px; padding: 0px 2px; line-height: 20px; font-size: 14px; z-index: 9999; }"
  174.  
  175. customStyles.innerHTML = styles;
  176.  
  177. document.getElementsByTagName("head")[0].appendChild(customStyles);
  178. }
  179.  
  180. /*!
  181. * Determine if an element is in the viewport
  182. * (c) 2017 Chris Ferdinandi, MIT License, https://gomakethings.com
  183. * @param {Node} elem The element
  184. * @return {Boolean} Returns true if element is in the viewport
  185. */
  186. var isInViewport = function (elem) {
  187. var distance = elem.getBoundingClientRect();
  188. return (
  189. distance.top >= 0 &&
  190. distance.left >= 0 &&
  191. distance.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
  192. distance.right <= (window.innerWidth || document.documentElement.clientWidth)
  193. );
  194. }
  195.  
  196. var isVisible = function (elem) {
  197. const elemCenter = {
  198. x: elem.getBoundingClientRect().left + elem.offsetWidth / 2,
  199. y: elem.getBoundingClientRect().top + elem.offsetHeight / 2
  200. };
  201. let pointContainer = document.elementFromPoint(elemCenter.x, elemCenter.y);
  202. do {
  203. if (pointContainer === elem) return true;
  204. } while (pointContainer = pointContainer.parentNode);
  205. return false;
  206. }
  207.  
  208. function getCoords(elem) {
  209. let box = elem.getBoundingClientRect();
  210.  
  211. return {
  212. top: box.top + window.pageYOffset,
  213. right: box.right + window.pageXOffset,
  214. bottom: box.bottom + window.pageYOffset,
  215. left: box.left + window.pageXOffset
  216. };
  217. }
  218.  
  219. addCustomCSS()
  220. setListeners()
  221.  
  222. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址