// ==UserScript== // @namespace http://mrob.com/time/scripts-beta // @name frame-linker for OTT // @copyright 2013+, Aluisio Augusto Silva Goncalves // @copyright 2014, Robert Munafo (mrob27) // @description Detects frame references on the xkcd 1190 forums thread, formatted like 'M#2440a', and replaces them with links for the viewers of the various frame numbering standards. // @version 8026.18 // @downloadURL http://mrob.com/time/scripts-beta/frame-linker.user.js.txt // @include http://forums.xkcd.com/viewtopic.php* // @include http://fora.xkcd.com/viewtopic.php* // @include http://echochamber.me/viewtopic.php* // @include http://forums.xkcd.com/posting.php* // @include http://fora.xkcd.com/posting.php* // @include http://echochamber.me/posting.php* // @run-at document-start // ==/UserScript== /* Here is an example, of the original text (in "BBCode", really plain ASCII since there are no BBCode tags here): A mscha frame: M#2440 mscha special: M#2440a An Aubron frame: A#42 A GeekWagon frame: G#403 In HTML as normally delivered by the server, each post (not counting the signature, etc.) ends up being in a
block, like this:
A mscha frame: M#2440

mscha special: M#2440aAn Aubron frame: A#42
A GeekWagon frame: G#403
REVISION HISTORY Original version by AluisioASG: np2684.00 First version, announced in OTT:1052:2#p3403655. Compiled to JavaScript by CoffeeScript 1.6.3 Versions by Robert Munafo (mrob27): np7361.01 Formatting cleanup and block-header comments np7366.81 Convert to plain ASCII and add a few more comments np7369.27 Preserve original text, i.e. the link's text remains "M#2345" instead of being turned into "2345 [mscha]" np8026.18 Also act on posting.php np8059.23 Stricter match patterns */ (function() { var CONVERTERS, STANDARDS, findTargetNodes, generateLink, generateTooltip, getConverter, getStandards, insertNodes, mapFrameNumber, match, rewriteNode, __slice = [].slice; STANDARDS = [ { name: 'mscha', Nm: 'M', regexp: /\bM#(\d\d\d+[a-z]?)\b/i, rewrite: function(frame) { return "http://xkcd.mscha.org/viewer/" + frame; } }, { name: 'Aubron', Nm: 'A', regexp: /\bA#(\d\d\d+)\b/i, rewrite: function(frame) { return "http://xkcd.aubronwood.com/?i=" + frame + "&playing=0"; } }, { name: 'geekwagon', Nm: 'G', regexp: /\bG#(\d\d\d+)\b/i, rewrite: function(frame) { return "http://geekwagon.net/projects/xkcd1190/?frame=" + frame; } } ]; getStandards = function() { return STANDARDS; }; match = function() { var args, fallback, result, test, _i, _len, _ref; fallback = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; for (_i = 0, _len = args.length; _i < _len; _i++) { _ref = args[_i], test = _ref[0], result = _ref[1]; if (test) { return result.toString(); } } return fallback.toString(); }; CONVERTERS = { 'mscha -> geekwagon': function(frameId) { var $, _, _ref; if (/^\d+[a-z]$/.test(frameId)) { _ref = [parseInt(frameId.slice(0, -1), 10), frameId.slice(-1)], $ = _ref[0], _ = _ref[1]; return match('-', [(256 <= $ && $ <= 258), '-'], [$ === 2440, 2344 + _.charCodeAt(0)]); } else { $ = parseInt(frameId, 10); return match($, [$ >= 2441, $ + 5]); } }, 'mscha -> Aubron': function(frameId) { var $, _, _ref; if (/^\d+[a-z]$/.test(frameId)) { _ref = [parseInt(frameId.slice(0, -1), 10), frameId.slice(-1)], $ = _ref[0], _ = _ref[1]; return match('-', [(256 <= $ && $ <= 258), $], [$ === 2440, 2347 + _.charCodeAt(0)]); } else { $ = parseInt(frameId, 10); return match($, [$ > 2440, $ + 8], [$ > 255, $ + 3]); } }, 'geekwagon -> mscha': function(frameId) { var $; $ = parseInt(frameId, 10); return match($, [(2441 <= $ && $ <= 2445), 2440 + String.fromCharCode($ - 2344)], [$ > 2445, $ - 5]); }, 'Aubron -> mscha': function(frameId) { var $; $ = parseInt(frameId, 10); return match($, [(2444 <= $ && $ <= 2448), 2440 + String.fromCharCode($ - 2347)], [(256 <= $ && $ <= 258), $ + 'a'], [$ > 2448, $ - 8], [$ > 258, $ - 3]); } }; /* getConverter returns a function to convert a frame number from one system to another. If a direct conversion does not exist, it constructs a function that converts in two steps using mscha as the intermediate. */ getConverter = function(sourceStandard, targetStandard) { var hasConverter, srcName, tgtName, _ref; hasConverter = function(from, to) { return ("" + from + " -> " + to) in CONVERTERS; }; _ref = [sourceStandard.name, targetStandard.name], srcName = _ref[0], tgtName = _ref[1]; if (hasConverter(srcName, tgtName)) { return CONVERTERS["" + srcName + " -> " + tgtName]; } else if ((hasConverter(srcName, 'mscha')) && (hasConverter('mscha', tgtName))) { return function(frameId) { return CONVERTERS["mscha -> " + tgtName]( CONVERTERS["" + srcName + " -> mscha"](frameId)); }; } else { return function() { return '-'; }; } }; mapFrameNumber = function(frameNumber, sourceStandard, targetStandard) { return (getConverter(sourceStandard, targetStandard))(frameNumber); }; generateTooltip = function(frameNumber, sourceStandard, standardList) { var all, target; all = (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = standardList.length; _i < _len; _i++) { target = standardList[_i]; if (target.name !== sourceStandard.name) { _results.push("" + target.name + " " + (mapFrameNumber(frameNumber, sourceStandard, target))); } } return _results; })(); return all.join(' / '); }; generateLink = function(frameNumber, standard, text) { var link; link = document.createElement('a'); link.href = standard.rewrite(frameNumber); // link.textContent = "" + text + " [" + standard.name + "]"; link.textContent = standard.Nm + "#" + text; link.title = generateTooltip(frameNumber, standard, getStandards()); return link; }; /* findTargetNodes uses an XPath query to find all the
items, which contain the actual posts */ findTargetNodes = function() { var xpath; xpath = '//div[@class="content"]//text()'; return document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); }; /* rewriteNode does most of the work. It takes a block of text in 'node.node.textContent', splits it using the regexp of a given standard, and performs the transformation on the odd-number-indexed segments, concatenating it all together into a new text node. */ rewriteNode = function(node, standard) { var i, part, text, _i, _len, _ref, _results; text = node.textContent; if (!text) { return []; } _ref = text.split(standard.regexp); _results = []; for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { part = _ref[i]; if ((i + 1) % 2 === 0) { _results.push(generateLink(part, standard, part)); } else { _results.push(new Text(part)); } } return _results; }; insertNodes = function(originalNode, newNodes) { var node, parent, _i, _len; parent = originalNode.parentNode; for (_i = 0, _len = newNodes.length; _i < _len; _i++) { node = newNodes[_i]; parent.insertBefore(node, originalNode); } return parent.removeChild(originalNode); }; document.addEventListener('DOMContentLoaded', function() { var i, newNodes, node, standard, targetNodes, _i, _j, _len, _ref, _ref1; /* Get the specifications for each type of link we want to transform */ _ref = getStandards(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { standard = _ref[_i]; /* Get the posts' content */ targetNodes = findTargetNodes(); /* Iterate through each post. The loop is complex to deal with the possibility of snapshotLength being negative. %%% Does that ever happen? This might be CoffeeScript compiler boilerplate */ for (i = _j = 0, _ref1 = targetNodes.snapshotLength; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { node = targetNodes.snapshotItem(i); newNodes = rewriteNode(node, standard); insertNodes(node, newNodes); } } }); }).call(this);