MediaWiki:Gadget-tgcLinks.js

From Terraria Mods Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
// Change the 'href' attributes of links that are redirects to the Terraria Wiki
// to make them go directly to their destination. Add a CSS class to those links
// which applies a slightly different color to them.


var cssClass = 'tgclink';


$(document).ready(function() {
    var allRedirectLinksOnPage = collectAllRedirectLinks();
    // modify the title and href attributes of a link and add the class for coloring
    handleDirectInterwikiLinks(allRedirectLinksOnPage.direct);
    handleIndirectInterwikiLinks(allRedirectLinksOnPage.indirect);
});


function makeNewHovertext(title) {
    return 'Terraria Wiki: ' + title + ' (this link is a redirect to the main Terraria Wiki)';
}


function collectAllRedirectLinks() {
    // a direct interwiki link is e.g. "[[tgc:Foo]]";
    // an indirect interwiki link is e.g. "[[Foo]]" where the page "Foo"
    // contains the interwiki redirect: "#REDIRECT [[tgc:Foo]]"
	var directInterwikiLinks = [];
	var indirectInterwikiLinks = [];
	$('#mw-content-text a.mw-redirect, #mw-content-text a.extiw').each(function() {
	    var thislink = $(this);
	    if (thislink.attr('title').slice(0, 4) === 'tgc:') {
	    	directInterwikiLinks.push(thislink);
	    } else {
	        indirectInterwikiLinks.push(thislink);
	    }
	});
	return { direct: directInterwikiLinks, indirect: indirectInterwikiLinks };
}


function handleDirectInterwikiLinks(links) {
    // these `links` are direct interwiki links, their href attribute is correct
    // already, and they don't need coloring, so simply modify their title attribute
	$.each(links, function() {
		var thislink = $(this);
	    var newHovertext = makeNewHovertext(thislink.attr('title').slice(4)); // strip the 'tgc:' part
	    thislink.attr('title', newHovertext);
	});
}


function handleIndirectInterwikiLinks(links) {
    // these `links` are indirect interwiki links, so their href and title attributes
    // must be modified and they need coloring

    // get the link targets for each of the links (uniquely)
	var linkTargets = [];
	$.each(links, function() {
	    var linkTarget = getTargetOfLink($(this));
	    if ($.inArray(linkTarget, linkTargets) === -1) {
	        linkTargets.push(linkTarget);
	    }
	});
	
	if (linkTargets.length === 0) {
		// there are no redirect links to handle
		return;
	}

    // get the tgc wiki URL for each link target
    getRedirectUrls(linkTargets).done(function(redirectUrls) {
        if (redirectUrls === undefined) {
            return;
        }
        // modify the links
        $.each(links, function() {
            var thislink = $(this);
            var linkTarget = getTargetOfLink(thislink);
            var tgcUrl = redirectUrls[linkTarget];
            if (tgcUrl === undefined) {
            	return;
            }
            // if the link has a fragment, then make sure to keep it
            var hashIndex = thislink.attr('href').indexOf('#');
            if (hashIndex > -1) {
            	tgcUrl += thislink.attr('href').slice(hashIndex);
            }
            thislink
                .attr('href', tgcUrl) // modify the URL of this link
                .attr('title', makeNewHovertext(linkTarget)) // modify the hovertext of this link
                .addClass(['extiw', cssClass]); // apply coloring to this link
        });
    });
}


function getTargetOfLink(linkElement) {
	// get the target page name of the `linkElement` by stripping "/" and "/wiki/"
	// at the beginning and removing the first "#" and everything after it
    return decodeURIComponent(linkElement.attr('href')
        .replace(/_/g, ' ')
        .replace(/^\/(wiki\/)?/, '')
        .replace(/#.+$/, '')
    );
}


function getRedirectUrls(linkTargets) {
    // query the API and return an object where each of the `linkTargets` has a tgc wiki URL
    var deferred = new $.Deferred();
    new mw.Api().post({  // not .get() because the `linkTargets` can be quite long
        action: 'query',
        format: 'json',
        titles: linkTargets.join('|'),
        redirects: true,
        iwurl: true
    }).done(function(data) {
        if (data.query.redirects === undefined || data.query.interwiki === undefined) {
            deferred.resolve();
            return;
        }
        var redirectUrls = {};
        // the `data.query` is two arrays, e.g. for titles="Mount|Accessory" like this:
        // (https://terrariamods.wiki.gg/api.php?action=query&redirects=&iwurl=&titles=Mount|Accessory)
        /* {
            "redirects": [
                { "from": "Accessory", "to": "tgc:Accessories", "tointerwiki": "tgc" },
                { "from": "Mount", "to": "tgc:Mounts", "tointerwiki": "tgc" }
            ],
            "interwiki": [
                { "title": "tgc:Accessories", "iw": "tgc", "url": "https://terraria.wiki.gg/wiki/Accessories" },
                { "title": "tgc:Mounts", "iw": "tgc", "url": "https://terraria.wiki.gg/wiki/Mounts" }
            ]
        } */
        // we want to connect the `redirects.from` strings with the `interwiki.url` strings;
        // we iterate over each object in `redirects`, but we skip the objects
        // where `tointerwiki` is not 'tgc' (e.g. 'clm', or none at all)
        var redirectObjects = data.query.redirects.filter(function(r) { return r.tointerwiki === 'tgc'; });
        redirectObjects.forEach(function(redirect) {
            // connect: find the corresponding object in `interwiki`
            var iwObject = data.query.interwiki.find(function (i) {
                return i.iw === 'tgc' && i.title === redirect.to;
            });
            if (iwObject !== undefined) {
                // found it
                redirectUrls[redirect.from] = iwObject.url;
            }
        });
        // now `redirectUrls` is e.g.:
        /* {
            "Accessory": "https://terraria.wiki.gg/wiki/Accessories",
            "Mount": "https://terraria.wiki.gg/wiki/Mounts"
        } */
        deferred.resolve(redirectUrls);
    }).fail(function() {
        // the API threw an error
        var error = arguments;
        if (error[0] === 'toomanyvalues') {
            // there are too many `linkTargets` for one query (e.g. more than
            // 50 for a logged-out user), the exact limit in this case is this:
            var limit = error[1].error.limit;

            // split the `linkTargets` into multiple sub-arrays, each of which
            // is no larger than allowed by `limit`
            // (e.g. if `linkTargets` has 53 elements and `limit` is 50, then
            // split it into `[...(50)]` and `[...(3)]`)
            // then repeat the API query for each of those sub-arrays and combine
            // the results
            var deferreds = [];
            for (var index = 0; index < Math.ceil(linkTargets.length / limit); index++) {
                // split into sub-array
                var linkTargetsSlice = linkTargets.slice(index * limit, (index + 1) * limit);
                // do API query
                deferreds.push(getRedirectUrls(linkTargetsSlice));
            }
            // wait for all queries to complete; for an explanation of this method see
            // https://stackoverflow.com/questions/5627284/pass-in-an-array-of-deferreds-to-when
            // https://stackoverflow.com/questions/4878887/how-do-you-work-with-an-array-of-jquery-deferreds
            $.when.apply(null, deferreds).done(function() {
                // the arguments to this function are our desired result objects,
                // so we need to combine them into one
                // iterate over the arguments and merge them into one object
                // (named `redirectUrlsCombined` in the callback of reduce() below)
                var redirectUrls = Array.from(arguments).reduce(function(redirectUrlsCombined, redirectUrlsSlice) {
                    // `redirectUrlsSlice` is the result of the API query for one
                    // of the sub-arrays
                    if (redirectUrlsSlice !== undefined) {
                        // merge objects
                        Object.keys(redirectUrlsSlice).forEach(function(key) {
                            redirectUrlsCombined[key] = redirectUrlsSlice[key];
                        });
                    }
                    return redirectUrlsCombined;
                }, {});
                // now `redirectUrls` is merged and we can return it like normal
                deferred.resolve(redirectUrls);
            });
        } else {
            console.error(error);
            deferred.resolve();
        }
    });
    return deferred;
}