Jump to content

User:TheresNoTime/linkThings.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/**
* linkThings - a script to let you alt + click on
* [[wiki links]] and {{template links}} in the CodeMirror
* and visual source editor
*
* @version 1.4.0
* @license https://opensource.org/licenses/MIT MIT
* @author https://github.com/TheresNoGit/linkThings/graphs/contributors
* @link https://github.com/TheresNoGit/linkThings
*/
/* global $, mw, ve */
/*jshint esversion: 6 */

// Configure
let version = "1.4.0";

// Init
$(function() {
    mw.loader.using(["mediawiki.Title"], setup);
});

/**
 * Gets the URL of the page, irrespective of the wiki this is on.
 * @param {string} page The page to get the URL of.
 */
function getUrl(page = "") {
    return new URL(
        mw.config.get("wgArticlePath").replace(/\$1/g, page),
        window.location.href
    ).toString();
}

/**
 * Set up the event listener
 * 
 * @returns bool
 */
function setup() {
    // Wait for VE Source to load
    mw.hook('ve.activationComplete').add(function () {
        if ($(".ve-ui-surface").length) {
            // Get VE object
            var surface = ve.init.target.getSurface();
            // Only run in source mode
            if (surface.getMode() === 'source') {
                // Set up event listener for an alt + click
                $('.ve-ui-surface').on('click', function (event) {
                    if (event.altKey) {
                        $(".cm-mw-pagename").each((i, e) => {
                            // Click "raycasting" in order to detect the click even if the VE
                            // elemnent is overhead.
                            if (isClickAboveElement(e, event)) {
                                if (parseLink(e)) {
                                    return true;
                                } else {
                                    // Assume the user alt + clicked on something they thought would work, and give error
                                    console.error(`linkThings v${version}: Clicked element was not detected as a page or template link`);
                                    return false;
                                }
                            }
                        });
                    }
                });
                console.info(`linkThings v${version}: Initialized OK, using ${getUrl()} in VE mode`);
            } else {
                console.debug(`linkThings v${version}: VE is not in source mode`);
            }
        } else {
            console.error(`linkThings v${version}: Could not initialize script - ve-ui-surface element not found?`);
            return false;
        }
    });

    // Wait for CodeMirror to load
    mw.hook('ext.CodeMirror.switch').add(function (enabled, cm) {
        if ($(".CodeMirror").length && enabled) {
            // Set up event listener for an alt + click
            CodeMirror.on(cm[0], "mousedown", function (event) {
                if (event.altKey) {
                    if (parseLink(event.target)) {
                        return true;
                    } else {
                        // Assume the user alt + clicked on something they thought would work, and give error
                        console.error(`linkThings v${version}: Clicked element was not detected as a page or template link`);
                        return false;
                    }
                }
            });
            console.info(`linkThings v${version}: Initialized OK, using ${getUrl()} in CodeMirror mode`);
        } else {
            console.error(`linkThings v${version}: Could not initialize script - CodeMirror element not found?`);
            return false;
        }
    });
}

/**
 * Parse a ctrl clicked *anything* (CodeMirror)
 * 
 * @param {HTMLElement} element Clicked HTML element
 * @returns bool
 */
function parseLink(element) {
    // Check if this is a page/template link
    if (!element.classList.contains("cm-mw-pagename")) {
        // Neither a template link nor a page link
        return false;
    } else if (
        element.classList.contains("cm-mw-template-name")
        || element.classList.contains("cm-mw-link-pagename")
    ) {
        // Get the page link
        const page = new mw.Title(
            element.innerHTML,
            element.classList.contains("cm-mw-template-name") ?  //
                mw.config.get("wgNamespaceIds")["template"] : undefined
        );
        const url = getUrl(page.getPrefixedDb());
        console.debug(`linkThings v${version}: opening ${url}`);
        openInTab(url);
        return true;
    }
}

/**
 * Check if a click was above an element.
 * 
 * @param {HTMLElement} element The element to check for
 * @param {MouseEvent} event The event to check against
 * @returns {boolean} Whether or not the click was above the element or not
 */
function isClickAboveElement(element, event) {
    const $e = $(element), $w = $(window);
    const { clientY: cTop, clientX: cLeft } = event;
    const { top: eTop, left: eLeft } = $e.offset();
    const eHeight = $e.height(), eWidth = $e.width();
    const scrollTop = $w.scrollTop(), scrollLeft = $w.scrollLeft();

    return (
        // Within bounds, top
        eTop - scrollTop <= cTop && eTop - scrollTop + eHeight >= cTop &&
        // Within bounds, left
        eLeft - scrollLeft <= cLeft && eLeft - scrollLeft + eWidth >= cLeft
    );
}

/**
 * Opens a URL in a new tab
 * 
 * @param {string} url URL to open
 * @returns bool
 */
function openInTab(url) {
    var newTab = window.open(url, '_blank');
    if (newTab) {
        newTab.focus();
        return true;
    } else {
        console.error(`linkThings v${version}: Browser did not open new tab. Check settings?`);
        alert('Please ensure popups are enabled for this site');
        return false;
    }
}