Easy Keyboard Link Opener

Open links easily using only the keyboard

目前為 2021-03-21 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Easy Keyboard Link Opener
// @description  Open links easily using only the keyboard
// @version      0.2.1
// @namespace    https://greasyfork.org/en/users/55535-sllypper
// @author       sllypper
// @match        *://*/*
// @grant        none
// @todo         add TAB to cycle between links
// ==/UserScript==

(function() {
    'use strict';

    // on the American keyboard layout, switch it to "`" instead
    // or whatever other key you want
    const HOTKEY = "'"

    // allow or deny links that start with #
    const ALLOW_ANCHOR_LINKS = true
    // allow or deny buttons
    const ALLOW_BUTTONS = true

    //

    let isSet = false;
    let visibleLinks = [];
    let page;

    const tooltipColor = "#fff"
    const tooltipBgColor = "#555"

    function setHot() {
        document.addEventListener("keydown", event => {
            if (!isSet) {
                if (event.key === HOTKEY && event.ctrlKey) {
                    actionHot();
                }
            } else {
                if (event.key >= 0 && event.key < 10) {
                    let chosen
                    chosen = event.key === "0" ? visibleLinks[9] : visibleLinks[parseInt(event.key)-1]
                    chosen.click()
                    //window.location = chosen.getAttribute('href')
                    //console.log(chosen.getAttribute('href'))
                }
                /*else if (event.key === "Tab") {

                }*/
                //console.log(event.key)
                clearAction()
            }
        })
    }

    function clearAction() {
        // let el
        for (let el of document.querySelectorAll('.displayText_container')) { el.remove() }
        for (let el of document.querySelectorAll('.displayText')) { el.remove() }
        visibleLinks = []
        isSet = false
    }

    function actionHot() {
        let selector = ALLOW_BUTTONS ? 'a,button' : 'a'
        let links = Array.from(document.querySelectorAll(selector)).filter((el) => {
            // separating check to only check the href of anchors
            // so it doesn't exclude all buttons, if they're enabled
            if (el.nodeName === "A" && el.getAttribute('href') == null) return false;

            return !(el.offsetWidth === 0 || el.offsetHeight === 0) && isInViewport(el)
        })

        if (!ALLOW_ANCHOR_LINKS) links = links.filter((el) => el.getAttribute('href')[0] != '#' )

        let numlinks = 0

        for (const el of links) {
            // line below was duplicated inside and outside the if check
            visibleLinks.push(el)

            // only create up to 9 tooltips
            // then create tooltip 0 and break
            if (numlinks >= 9) {
                createTooltip(el, 0)
                break;
            }
            numlinks += 1
            createTooltip(el, numlinks)
        }
        isSet = true
    }

    // Creates tooltip and makes sure it is visible
    // Avoids creating tooltip on body because they don't follow fixed elements
    function createTooltip(el, num) {
        let tooltip = showTooltip(el, num)
        if (!isInViewport(tooltip)) {
            tooltip.remove()
            showTooltipOnBody(el, num)
        }
    }

    function showTooltip(el, num) {
        const superEl = document.createElement("span")
        superEl.setAttribute('style','position: relative;')
        superEl.setAttribute("class", "displayText_container")

        let tooltip = document.createElement("span")
        tooltip.setAttribute("class", "displayText")
        tooltip.innerText = num
        superEl.appendChild(tooltip)
        el.appendChild(superEl)

        return superEl
    }

    function showTooltipOnBody(el, num) {
        const rect = el.getBoundingClientRect();
        console.log(rect.top, rect.right, rect.bottom, rect.left);

        const tooltip = document.createElement("span")
        tooltip.setAttribute("class", "displayText")
        tooltip.innerText = num
        tooltip.style.margin = '0'
        tooltip.style.bottom = 'initial'
        tooltip.style.top = rect.top+'px'
        tooltip.style.left = rect.right+'px'
        document.body.appendChild(tooltip)

        return tooltip
    }

    function addCustomCSS() {
        let customStyles = document.createElement("style");
        customStyles.setAttribute("type", "text/css");

        let styles = ".displayText { position: absolute; bottom: 100%; left: 100%; margin-left: 0; width: initial; background-color: "+tooltipBgColor+"; color: "+tooltipColor+"; text-align: center; border-radius: 2px; padding: 0px 2px; margin-bottom: -20px; line-height: 20px; font-size: 14px; z-index: 9999; }"
        customStyles.innerHTML = styles;

        document.getElementsByTagName("head")[0].appendChild(customStyles);
    }

    /*!
     * Determine if an element is in the viewport
     * (c) 2017 Chris Ferdinandi, MIT License, https://gomakethings.com
     * @param  {Node}    elem The element
     * @return {Boolean}      Returns true if element is in the viewport
     */
    var isInViewport = function (elem) {
        var distance = elem.getBoundingClientRect();
        return (
            distance.top >= 0 &&
            distance.left >= 0 &&
            distance.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
            distance.right <= (window.innerWidth || document.documentElement.clientWidth)
        );
    }

    addCustomCSS()
    setHot()

})();