// ==UserScript== // @name Linkify Plus // @version 2.1.4 // @namespace http://arantius.com/misc/greasemonkey/ // @description Turn plain text URLs into links. Supports http, https, ftp, email addresses. // @include http* // @exclude http://www.google.tld/search* // @exclude https://www.google.tld/search* // @exclude http://music.google.com/* // @exclude https://music.google.com/* // @exclude http://mail.google.com/* // @exclude https://mail.google.com/* // @exclude http://docs.google.com/* // @exclude https://docs.google.com/* // @exclude http://mxr.mozilla.org/* // @downloadURL none // ==/UserScript== // // Copyright (c) 2011, Anthony Lieuallen // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of Anthony Lieuallen nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // /******************************************************************************* Loosely based on the Linkify script located at: http://downloads.mozdev.org/greasemonkey/linkify.user.js Version history: Version 2.1.4 (Aug 12, 2012): - Bug fix for when (only some) nodes have been removed from the document. Version 2.1.3 (Oct 24, 2011): - More excludes. Version 2.1.2: - Some bug fixes. Version 2.1.1: - Ignore certain "highlighter" script containers. Version 2.1: - Rewrite the regular expression to be more readable. - Fix trailing "." characters. Version 2.0.3: - Fix infinite recursion on X(HT)ML pages. Version 2.0.2: - Limit @include, for greater site/plugin compatibility. Version 2.0.1: - Fix aberrant 'mailto:' where it does not belong. Version 2.0: - Apply incrementally, so the browser does not hang on large pages. - Continually apply to new content added to the page (i.e. AJAX). Version 1.1.4: - Basic "don't screw up xml pretty printing" exception case Version 1.1.3: - Include "+" in the username of email addresses. Version 1.1.2: - Include "." in the username of email addresses. Version 1.1: - Fixed a big that caused the first link in a piece of text to be skipped (i.e. not linkified). *******************************************************************************/ var notInTags = [ 'a', 'code', 'head', 'noscript', 'option', 'script', 'style', 'title', 'textarea']; var textNodeXpath = ".//text()[not(ancestor::"+notInTags.join(') and not(ancestor::')+")]"; // Built based on: // - http://en.wikipedia.org/wiki/URI_scheme // - http://www.regular-expressions.info/regexbuddy/email.html var urlRE = new RegExp( '(' // leading scheme:// or "www." + '\\b([a-z][-a-z0-9+.]+://|www\\.)' // everything until non-URL character + '[^\\s\'"<>()]+' + '|' // email + '\\b[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}\\b' + ')', 'gi'); var queue = []; /******************************************************************************/ linkifyContainer(document.body); document.body.addEventListener('DOMNodeInserted', function(event) { linkifyContainer(event.target); }, false); /******************************************************************************/ function linkifyContainer(container) { // Prevent infinite recursion, in case X(HT)ML documents with namespaces // break the XPath's attempt to do so. (Don't evaluate spans we put our // classname into.) if (container.className && container.className.match(/\blinkifyplus\b/)) { return; } var xpathResult = document.evaluate( textNodeXpath, container, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); var i = 0; function continuation() { var node = null, counter = 0; while (node = xpathResult.snapshotItem(i++)) { var parent = node.parentNode; if (!parent) continue; // Skip styled
 -- often highlighted by script.
		  if ('PRE' == parent.tagName && parent.className) continue;
		  
			linkifyTextNode(node);

			if (++counter > 50) {
				return setTimeout(continuation, 0);
			}
		}
	}
	setTimeout(continuation, 0);
}

function linkifyTextNode(node) {
	var i, l, m;
	var txt = node.textContent;
	var span = null;
	var p = 0;
	while (m = urlRE.exec(txt)) {
		if (null == span) {
			// Create a span to hold the new text with links in it.
			span = document.createElement('span');
			span.className = 'linkifyplus';
		}

		//get the link without trailing dots
		l = m[0].replace(/\.*$/, '');
		var lLen = l.length;
		//put in text up to the link
		span.appendChild(document.createTextNode(txt.substring(p, m.index)));
		//create a link and put it in the span
		a = document.createElement('a');
		a.className = 'linkifyplus';
		a.appendChild(document.createTextNode(l));
		if (l.indexOf(":/") < 0) {
			if (l.indexOf("@") > 0) {
				l = "mailto:" + l;
			} else {
				l = "http://" + l;
		  }
		}
		a.setAttribute('href', l);
		span.appendChild(a);
		//track insertion point
		p = m.index+lLen;
	}
	if (span) {
		//take the text after the last link
		span.appendChild(document.createTextNode(txt.substring(p, txt.length)));
		//replace the original text with the new span
		try {
			node.parentNode.replaceChild(span, node);
		} catch (e) {
			console.error(e);
			console.log(node);
		}
	}
}