Linkify Plus Plus

Based on Linkify Plus. Turn plain text URLs into links.

As of 07.05.2015. See апошняя версія.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Linkify Plus Plus
// @version     4.0.1
// @namespace   eight04.blogspot.com
// @description Based on Linkify Plus. Turn plain text URLs into links.
// @include     http*
// @exclude     http://www.google.*/search*
// @exclude     https://www.google.*/search*
// @exclude     http://www.google.*/webhp*
// @exclude     https://www.google.*/webhp*
// @exclude     http://music.google.*/*
// @exclude     https://music.google.*/*
// @exclude     http://mail.google.*/*
// @exclude     https://mail.google.*/*
// @exclude     http://docs.google.*/*
// @exclude     https://docs.google.*/*
// @exclude     http://mxr.mozilla.org/*
// @require 	https://greatest.deepsurf.us/scripts/7212-gm-config-eight-s-version/code/GM_config%20(eight's%20version).js?version=46603
// @grant       GM_addStyle
// @grant       GM_registerMenuCommand
// @grant       GM_getValue
// @grant       GM_setValue
// ==/UserScript==

"use strict";

var config, re;

GM_config.init(
	"Linkify Plus Plus",
	{
		useImg: {
			label: "Embed images",
			type: "checkbox",
			default: true
		},
		classBlackList: {
			label: "Add classes to black-list (Separate by space)",
			type: "textarea",
			default: ""
		},
		classWhiteList: {
			label: "Add classes to white-list (Separate by space)",
			type: "textarea",
			default: ""
		},
		generateLog: {
			label: "Generate log (useful for debugging)",
			type: "checkbox",
			default: false
		},
		openInNewTab: {
			label: "Open link in new tab",
			type: "checkbox",
			default: false
		},
		useYT: {
			label: "Embed youtube video",
			type: "checkbox",
			default: false
		},
		ytWidth: {
			label: "Video width",
			type: "text",
			default: "560"
		},
		ytHeight: {
			label: "Video height",
			type: "text",
			default: "315"
		},
		embedAll: {
			label: "Enable embedding globally (It might mess up your page!)",
			type: "checkbox",
			default: false
		}
	}
);

GM_config.onclose = loadConfig;

loadConfig();

// 1=protocol, 2=user, 3=domain, 4=port, 5=path, 6=angular source
var urlRE = /\b([-a-z*]+:\/\/)?(?:([\w:.+-]+)@)?([a-z0-9-.]+\.[a-z0-9-]+)\b(:\d+)?([/?#][\w-.~!$&*+;=:@%/?#(),]*)?|\{\{(.+?)\}\}/gi;

var tlds = {
	"AC":true,"ACADEMY":false,"ACTIVE":false,"AD":true,"AE":true,"AERO":true,"AF":true,"AG":true,"AGENCY":false,"AI":true,"AL":true,"ALSACE":false,"AM":true,"AN":true,"AO":true,"AQ":true,"AR":true,"ARCHI":false,"ARMY":false,"ARPA":true,"AS":true,"ASIA":true,"ASSOCIATES":false,"AT":true,"AU":true,"AUCTION":false,"AUDIO":false,"AUTOS":false,"AW":true,"AX":true,"AXA":false,"AZ":true,"BA":true,"BAR":false,"BARGAINS":false,"BAYERN":false,"BB":true,"BD":true,"BE":true,"BEER":false,"BERLIN":false,"BEST":false,"BF":true,"BG":true,"BH":true,"BI":true,"BID":false,"BIKE":false,"BIO":false,"BIZ":true,"BJ":true,"BLACK":false,"BLACKFRIDAY":false,"BLUE":false,"BM":true,"BN":true,"BNPPARIBAS":false,"BO":true,"BOUTIQUE":false,"BR":true,"BRUSSELS":false,"BS":true,"BT":true,"BUDAPEST":false,"BUILD":false,"BUILDERS":false,"BUSINESS":false,"BUZZ":false,"BV":false,"BW":true,"BY":true,"BZ":true,"BZH":false,"CA":true,"CAB":false,"CAMERA":false,"CAMP":true,"CAPETOWN":false,"CAPITAL":false,"CARAVAN":false,"CARDS":false,"CARE":false,"CAREERS":false,"CASA":false,"CASH":false,"CAT":true,"CATERING":false,"CC":true,"CD":true,"CENTER":true,"CEO":false,"CERN":false,"CF":true,"CG":false,"CH":true,"CHANNEL":false,"CHRISTMAS":false,"CHURCH":false,"CI":true,"CITY":false,"CK":true,"CL":true,"CLICK":false,"CLINIC":false,"CLOTHING":false,"CLUB":true,"CM":true,"CN":true,"CO":true,"CODES":false,"COFFEE":false,"COLOGNE":false,"COM":true,"COMMUNITY":false,"COMPANY":false,"COMPUTER":false,"CONDOS":false,"CONSTRUCTION":false,"CONSULTING":false,"CONTRACTORS":false,"COOKING":false,"COOL":false,"COOP":true,"COUNTRY":false,"CR":true,"CREDIT":false,"CREDITCARD":false,"CU":true,"CV":true,"CW":true,"CX":true,"CY":true,"CYMRU":false,"CZ":true,"DANCE":false,"DATING":false,"DAY":false,"DE":true,"DEALS":false,"DENTAL":false,"DESI":false,"DIAMONDS":false,"DIET":false,"DIGITAL":false,"DIRECT":false,"DIRECTORY":false,"DISCOUNT":false,"DJ":true,"DK":true,"DM":true,"DO":true,"DOMAINS":false,"DZ":true,"EC":true,"EDU":true,"EDUCATION":false,"EE":true,"EG":true,"EMAIL":true,"ENGINEER":false,"ENGINEERING":false,"ENTERPRISES":false,"EQUIPMENT":false,"ER":true,"ES":true,"ESTATE":false,"ET":true,"EU":true,"EUS":false,"EVENTS":false,"EXCHANGE":true,"EXPERT":false,"EXPOSED":false,"FAIL":false,"FARM":false,"FEEDBACK":false,"FI":true,"FISH":false,"FISHING":false,"FITNESS":false,"FJ":true,"FK":true,"FLORIST":false,"FLY":false,"FM":true,"FO":true,"FOO":false,"FORSALE":false,"FOUNDATION":false,"FR":true,"FRL":false,"FROGANS":false,"FUND":false,"FURNITURE":false,"FUTBOL":false,"GA":true,"GAL":false,"GALLERY":false,"GB":false,"GD":true,"GE":true,"GENT":false,"GF":true,"GG":true,"GH":true,"GI":true,"GIFT":false,"GIFTS":false,"GL":true,"GLASS":false,"GLOBAL":true,"GM":true,"GN":false,"GOP":false,"GOV":true,"GP":true,"GQ":true,"GR":true,"GRAPHICS":false,"GREEN":false,"GS":true,"GT":true,"GU":false,"GUIDE":false,"GURU":true,"GW":false,"GY":true,"HAMBURG":false,"HAUS":false,"HELP":false,"HERE":false,"HIV":false,"HK":true,"HM":true,"HN":true,"HOLDINGS":false,"HOLIDAY":false,"HOMES":false,"HORSE":false,"HOST":true,"HOSTING":true,"HOUSE":false,"HR":true,"HT":true,"HU":true,"ID":true,"IE":true,"IL":true,"IM":true,"IMMO":false,"IN":true,"INDUSTRIES":false,"INFO":true,"INK":false,"INSTITUTE":false,"INSURE":false,"INT":true,"INTERNATIONAL":false,"IO":true,"IQ":false,"IR":true,"IS":true,"IT":true,"JE":true,"JETZT":false,"JM":true,"JO":true,"JOBS":false,"JP":true,"KAUFEN":false,"KE":true,"KG":true,"KH":true,"KI":false,"KIM":false,"KITCHEN":false,"KIWI":false,"KM":false,"KN":false,"KOELN":false,"KP":false,"KR":true,"KRED":false,"KW":true,"KY":true,"KZ":true,"LA":true,"LAND":false,"LB":true,"LC":true,"LEASE":false,"LGBT":false,"LI":true,"LIFE":false,"LIGHTING":false,"LIMITED":false,"LIMO":false,"LINK":true,"LK":true,"LOANS":false,"LONDON":true,"LOTTO":false,"LR":true,"LS":true,"LT":true,"LTDA":false,"LU":true,"LUXE":false,"LV":true,"LY":true,"MA":true,"MAISON":false,"MANAGEMENT":false,"MARKET":false,"MARKETING":false,"MC":true,"MD":true,"ME":true,"MEDIA":false,"MEET":false,"MELBOURNE":false,"MENU":false,"MG":true,"MH":true,"MIAMI":false,"MIL":true,"MK":true,"ML":true,"MM":true,"MN":true,"MO":true,"MOBI":true,"MODA":false,"MOE":true,"MOSCOW":false,"MOTORCYCLES":false,"MP":false,"MQ":false,"MR":false,"MS":true,"MT":true,"MU":true,"MUSEUM":false,"MV":true,"MW":true,"MX":true,"MY":true,"MZ":true,"NA":true,"NAGOYA":false,"NAME":true,"NC":true,"NE":true,"NET":true,"NETWORK":true,"NEUSTAR":false,"NEW":false,"NEXUS":false,"NF":true,"NG":true,"NI":true,"NINJA":true,"NL":true,"NO":true,"NP":true,"NR":true,"NRA":false,"NRW":false,"NU":true,"NYC":false,"NZ":true,"OM":true,"ONG":false,"ONL":false,"OOO":false,"ORG":true,"ORGANIC":false,"OVH":true,"PA":true,"PARIS":false,"PARTNERS":false,"PARTS":false,"PE":true,"PF":true,"PG":true,"PH":true,"PHARMACY":false,"PHOTO":false,"PHOTOGRAPHY":false,"PHOTOS":false,"PICS":false,"PICTURES":false,"PINK":false,"PK":true,"PL":true,"PLACE":false,"PLUMBING":false,"PM":true,"PN":false,"POST":false,"PR":true,"PRAXI":false,"PRESS":false,"PRO":true,"PROD":true,"PRODUCTIONS":false,"PROPERTIES":false,"PS":true,"PT":true,"PUB":false,"PW":true,"PY":true,"QA":true,"QPON":false,"QUEBEC":false,"RE":true,"REALTOR":false,"RECIPES":false,"RED":true,"REISE":false,"REISEN":false,"RENTALS":false,"REPAIR":false,"REPORT":false,"REPUBLICAN":false,"REST":false,"RESTAURANT":false,"REVIEWS":false,"RICH":false,"RIO":false,"RO":true,"ROCKS":true,"RODEO":false,"RS":true,"RU":true,"RUHR":false,"RW":true,"SA":true,"SAARLAND":false,"SARL":false,"SB":true,"SC":true,"SCB":false,"SCOT":false,"SD":true,"SE":true,"SERVICES":false,"SEXY":false,"SG":true,"SH":true,"SHIKSHA":false,"SHOES":false,"SI":true,"SINGLES":false,"SK":true,"SL":true,"SM":true,"SN":true,"SO":true,"SOCIAL":false,"SOFTWARE":false,"SOLAR":false,"SOLUTIONS":true,"SOY":false,"SPACE":false,"SR":true,"ST":true,"SU":true,"SUPPLY":false,"SUPPORT":false,"SURF":false,"SV":true,"SX":true,"SY":true,"SYSTEMS":true,"SZ":true,"TATAR":false,"TATTOO":false,"TAX":false,"TC":true,"TD":false,"TECHNOLOGY":true,"TEL":false,"TF":true,"TG":true,"TH":true,"TIPS":false,"TIROL":false,"TJ":true,"TK":true,"TL":true,"TM":true,"TN":true,"TO":true,"TODAY":false,"TOKYO":false,"TOOLS":false,"TOWN":false,"TP":true,"TR":true,"TRADE":false,"TRAINING":false,"TRAVEL":true,"TT":true,"TV":true,"TW":true,"TZ":true,"UA":true,"UG":true,"UK":true,"UNIVERSITY":false,"UNKNOWN":true,"UNO":false,"US":true,"UY":true,"UZ":true,"VA":true,"VC":true,"VE":true,"VEGAS":false,"VENTURES":false,"VERSICHERUNG":false,"VG":true,"VI":true,"VILLAS":false,"VISION":false,"VLAANDEREN":false,"VN":true,"VODKA":false,"VOTE":false,"VOTING":false,"VOTO":false,"VOYAGE":false,"VU":true,"WANG":false,"WATCH":false,"WEBCAM":false,"WEBSITE":true,"WED":false,"WF":true,"WHOSWHO":false,"WIEN":false,"WIKI":false,"WILLIAMHILL":false,"WORK":false,"WORKS":false,"WORLD":false,"WS":true,"WTF":false,"XN--3DS443G":false,"XN--4GBRIM":false,"XN--55QX5D":false,"XN--6FRZ82G":false,"XN--80ADXHKS":false,"XN--80AO21A":false,"XN--80ASEHDB":false,"XN--80ASWG":false,"XN--C1AVG":false,"XN--D1ACJ3B":false,"XN--FIQ228C5HS":false,"XN--FIQS8S":false,"XN--FIQZ9S":false,"XN--I1B6B1A6A2E":false,"XN--J1AMH":false,"XN--J6W193G":false,"XN--KPRY57D":false,"XN--KPUT3I":false,"XN--MGBAAM7A8H":false,"XN--MGBERP4A5D4AR":false,"XN--NGBC5AZD":false,"XN--NQV7F":false,"XN--NQV7FS00EMA":false,"XN--P1ACF":false,"XN--P1AI":true,"XN--Q9JYB4C":false,"XN--RHQV96G":false,"XN--SES554G":false,"XXX":true,"XYZ":true,"YACHTS":false,"YANDEX":false,"YE":true,"YOKOHAMA":false,"YT":false,"ZA":true,"ZM":true,"ZONE":true,"ZW":true
};

function valid(node) {
	// Deal with SVGAnimatedString
	// http://x.co/9Rleu
	var className = node.className;
	if (typeof className == "object") {
		className = className.baseVal;
	}
	return node.nodeType == 1 &&
		(!re.excludingTag.test(node.nodeName) &&
		!re.excludingClass.test(className) ||
		re.includingClass.test(className)) &&
		node.contentEditable != "true" &&
		className.indexOf("linkifyplus") < 0;
}

function validRoot(node) {
	var cache = node;
	while (node.parentNode != document.documentElement) {
		// Check if the node is valid
		if (!valid(node) || node.LINKIFY_INVALID) {
			cache.LINKIFY_INVALID = true;
			return false;
		}
		// The node was detached from DOM tree
		if (!node.parentNode) {
			return false;
		}
		node = node.parentNode;
	}
	return true;
}

function isWbrOrText(node) {
	return node && (node.nodeType == 3 || node.nodeName == "WBR");
}

var traverser = {
	queue: [],
	running: false,
	start: function(){
		if (traverser.running) {
			return;
		}
		traverser.running = true;
		traverser.container();
	},
	container: function(){
		var root = traverser.queue.shift();

		if (!root) {
			traverser.running = false;
			return;
		}

		if (!root.childNodes || !root.childNodes.length || !validRoot(root)) {
			root.inTraverserQueue = false;
			setTimeout(traverser.container, 0);
			return;
		}

		var state = {
			currentNode: root.childNodes[0],
			lastMove: 1,	// 1=down, 2=left, 3=up
			loopCount: 0,
			timeStart: Date.now()
		};

		function traverse(){
			var i = 0, child, sibling, parent, range;

			while (state.currentNode != root) {
				// Create range to select textNode/wbr
				if (state.currentNode.nodeType == 3) {
					range = document.createRange();
					range.setStartBefore(state.currentNode);
					while (isWbrOrText(state.currentNode.nextSibling)) {
						state.currentNode = state.currentNode.nextSibling;
					}
					range.setEndAfter(state.currentNode);
				}

				// Cache next node for transfer
				child = state.currentNode.childNodes[0];
				sibling = state.currentNode.nextSibling;
				parent = state.currentNode.parentNode;

				if (range) {
					linkifyTextNode(range);
					range.detach();
					range = null;
				}

				// State transfer
				if (valid(state.currentNode) && state.lastMove != 3 && child) {
					state.currentNode = child;
					state.lastMove = 1;
				} else if (sibling) {
					state.currentNode = sibling;
					state.lastMove = 2;
				} else if (parent) {
					state.currentNode = parent;
					state.lastMove = 3;
				} else {
					break;
				}

				// Loop counter
				i++;
				state.loopCount++;
				if (i > 50) {
					setTimeout(traverse, 0);
					return;
				}
			}

			if (config.generateLog) {
				if (state.currentNode != root) {
					console.log("Traversal terminated! Last node: ", state.currentNode);
				} else {
					console.log(
						"Traversal end! Traversed %d nodes in %dms.",
						state.loopCount + 1,
						Date.now() - state.timeStart
					);
				}
			}

			root.inTraverserQueue = false;
			setTimeout(traverser.container);
		}
		setTimeout(traverse, 0);
	},
	add: function(node) {
		if (node.inTraverserQueue) {
			return;
		}
		node.inTraverserQueue = true;
		traverser.queue.push(node);
	}
};

function loadConfig(){
	config = GM_config.get();

	var excludingTag = [
		'a', 'code', 'head', 'noscript', 'option', 'script', 'style',
		'title', 'textarea', "svg", "canvas", "button", "select", "template",
		"meter", "progress", "math", "h1", "h2", "h3", "h4", "h5", "h6", "time"
	];

	var excludingClass = [
		"highlight", "editbox", "code", "brush:", "bdsug", "spreadsheetinfo"
	].concat(getArray(config.classBlackList));

	var includingClass = [
		"bbcode_code"
	].concat(getArray(config.classWhiteList));

	re = {
		excludingTag: new RegExp("^(" + excludingTag.join("|") + ")$", "i"),
		excludingClass: new RegExp("(^|\\s)(" + excludingClass.join("|") + ")($|\\s)"),
		includingClass: new RegExp("(^|\\s)(" + includingClass.join("|") + ")($|\\s)")
	};
}

function getArray(s) {
	s = s.trim();
	if (!s) {
		return [];
	}
	return s.split(/\s+/);
}

function isIP(s) {
	var m, i;
	if (!(m = s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))) {
		return false;
	}
	for (i = 1; i < m.length; i++) {
		if (m[i] * 1 > 255 || (m[i].length > 1 && m[i][0] == "0")) {
			return false;
		}
	}
	return true;
}

function stripSingleParenthesis(str) {
	var i, pos, c = ")";
	for (i = 0; i < str.length; i++) {
		if (str[i] == "(") {
			if (c != ")") {
				return str.substring(0, pos);
			}
			pos = i;
			c = "(";
		}
		if (str[i] == ")") {
			if (c != "(") {
				return str.substring(0, i);
			}
			pos = i;
			c = ")";
		}
	}
	if (c != ")") {
		return str.substring(0, pos);
	}
	return str;
}

function getYoutubeId(url) {
	var match =
		url.match(/https?:\/\/www\.youtube\.com\/watch\?v=([^&]+)/) ||
		url.match(/https?:\/\/youtu\.be\/([^?]+)/);
	return match && match[1];
}

function createLink(url, text) {
	var cont;

	cont = document.createElement("a");
	cont.href = url;
	if (config.openInNewTab) {
		cont.target = "_blank";
	}
	cont.appendChild(text);
	cont.className = "linkifyplus";

	return cont;
}

function replaceRange(range, nodes) {
	var i, j;

	// Get text targets
	var targets = [],
		list = range.startContainer.childNodes,
		offset = 0,
		endOffset = 0;
	for (i = range.startOffset; i < range.endOffset; i++) {
		if (list[i].nodeType == 3) {
			endOffset = offset + list[i].nodeValue.length;
		}
		targets.push({
			offset: offset,
			endOffset: endOffset,
			node: list[i]
		});
		offset = endOffset;
	}

	// Compare offset with range position
	var subRange = document.createRange(),
		frag = document.createDocumentFragment(),
		text;
	for (i = 0, j = 0; i < nodes.length; i++) {
		// Create sub range
		while (nodes[i].start >= targets[j].endOffset) {
			j++;
		}
		subRange.setStart(targets[j].node, nodes[i].start - targets[j].offset);
		while (nodes[i].end > targets[j].endOffset) {
			j++;
		}
		subRange.setEnd(targets[j].node, nodes[i].end - targets[j].offset);

		// Create text and link
		text = subRange.cloneContents();
		if (nodes[i].type == "string") {
			frag.appendChild(text);
		} else {
			frag.appendChild(createLink(nodes[i].url, text));
		}
	}
	subRange.detach();

	// Replace range
	range.deleteContents();
	range.insertNode(frag);
}

function linkifyTextNode(range) {
	var m, mm,
		txt = range.toString(),
		nodes = [],
		lastIndex = 0;
	var face, protocol, user, domain, port, path, angular;
	var url, linkified = false;

	while (m = urlRE.exec(txt)) {
		face = m[0];
		protocol = m[1] || "";
		user = m[2] || "";
		domain = m[3] || "";
		port = m[4] || "";
		path = m[5] || "";
		angular = m[6];

		// Skip angular source
		if (angular) {
			if (!unsafeWindow.angular) {
				urlRE.lastIndex = m.index + 2;
			}
			continue;
		}

		// domain shouldn't contain connected dots
		if (domain.indexOf("..") > -1) {
			continue;
		}

		// valid IP address
		if (!isIP(domain) && (mm = domain.match(/\.([a-z0-9-]+)$/i)) && !(mm[1].toUpperCase() in tlds)) {
			continue;
		}

		// Insert text
		if (m.index > lastIndex) {
			nodes.push({
				start: lastIndex,
				end: m.index,
				type: "string"
			});
		}

		if (path) {
			// get the link without trailing dots and comma
			face = face.replace(/[.,]*$/, '');
			path = path.replace(/[.,]*$/, '');

			// Get the link without single parenthesis
			face = stripSingleParenthesis(face);
			path = stripSingleParenthesis(path);
		}

		// Guess protocol
		if (!protocol && user && (mm = user.match(/^mailto:(.+)/))) {
			protocol = "mailto:";
			user = mm[1];
		}

		if (protocol && protocol.match(/^(hxxp|h\*\*p|ttp)/)) {
			protocol = "http://";
		}

		if (!protocol) {
			if (mm = domain.match(/^(ftp|irc)/)) {
				protocol = mm[0] + "://";
			} else if (domain.match(/^(www|web)/)) {
				protocol = "http://";
			} else if (user && user.indexOf(":") < 0 && !path) {
				protocol = "mailto:";
			} else {
				protocol = "http://";
			}
		}

		// Create URL
		url = protocol + (user && user + "@") + domain + port + path;

		urlRE.lastIndex = m.index + face.length;
		lastIndex = urlRE.lastIndex;

		nodes.push({
			start: m.index,
			end: lastIndex,
			type: "anchor",
			url: url
		});

		linkified = true;
	}

	if (!linkified) {
		return;
	}

	if (txt.length > lastIndex) {
		nodes.push({
			start: lastIndex,
			end: txt.length,
			type: "string"
		});
	}

	replaceRange(range, nodes);
}

function template(text, option) {
	var key;
	for (key in option) {
		text = text.split("@" + key).join(option[key]);
	}
	return text;
}

function observeDocument(callback) {

	callback(document.body);

	new MutationObserver(function(mutations){
		var i;
		for (i = 0; i < mutations.length; i++) {
			if (!mutations[i].addedNodes.length) {
				continue;
			}
			callback(mutations[i].target);
		}
	}).observe(document.body, {
		childList: true,
		subtree: true
	});
}

function loop(list, callback) {
	var i = 0,
		len = list.length;

	function chunk() {
		var j;
		for (j = 0; j < 50 && i < len; i++, j++) {
			callback(list[i]);
		}
		if (i < len) {
			setTimeout(chunk);
		}
	}

	setTimeout(chunk);
}

var embedFunction = {
	image: function(url, element) {
		var obj;
		if (!config.useImg || !/^[^?#]+\.(jpg|png|gif|jpeg)($|[?#])/i.test(url)) {
			return null;
		}
		obj = document.createElement("img");
		obj.className = "embedme-image";
		obj.alt = url;
		obj.src = url;
		element = element.cloneNode(false);
		element.appendChild(obj);
		return element;
	},
	youtube: function(url) {
		var id, cont, wrap, obj;
		if (!config.useYT || !(id = getYoutubeId(url))) {
			return null;
		}
		cont = document.createElement("div");
		cont.className = "embedme-video";

		wrap = document.createElement("div");
		wrap.className = "embedme-video-wrap";
		cont.appendChild(wrap);

		obj = document.createElement("iframe");
		obj.className = "embedme-video-iframe";
		obj.src = "https://www.youtube.com/embed/" + id;
		obj.setAttribute("allowfullscreen", "true");
		obj.setAttribute("frameborder", "0");
		wrap.appendChild(obj);

		return cont;
	}
};

function embedContent(element) {
	var url = element.href, key, embed;

	if (!element.parentNode) {
		return;
	}

	for (key in embedFunction) {
		embed = embedFunction[key](url, element);
		if (embed) {
			embed.classList.add("embedme");
			element.parentNode.replaceChild(embed, element);
			return;
		}
	}
//	element.classList.add("embedme-fail");
}

function embedMe(node) {
	var result, nodes = [], i, xpath;

	if (config.embedAll) {
		xpath = ".//a[not(*) and text() and @href and not(contains(@class, 'embedme'))]";
	} else {
		xpath = ".//a[not(*) and text() and @href and not(contains(@class, 'embedme')) and contains(@class, 'linkifyplus')]";
	}

	result = document.evaluate(xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

	for (i = 0; i < result.snapshotLength; i++) {
		nodes.push(result.snapshotItem(i));
	}

	loop(nodes, embedContent);
}

GM_addStyle(template(".embedme-image{max-width:100%}.embedme-video{max-width:@ytWidthpx}.embedme-video-wrap{position:relative;padding-top:30px;padding-bottom:@ytRatio%}.embedme-video-iframe{position:absolute;top:0;left:0;width:100%;height:100%}", {
	ytWidth: config.ytWidth,
	ytRatio: (config.ytHeight / config.ytWidth) * 100
}));

GM_registerMenuCommand("Linkify Plus Plus - Configure", function(){
	GM_config.open();
});

observeDocument(function(node){
	traverser.add(node);
	traverser.start();
});

observeDocument(embedMe);