diff --git a/.gitignore b/.gitignore index 4ecc2a7a..f605c5fc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ Thumbs.DB node_modules bower_components +build/* demo/dist/* diff --git a/.jshintrc b/.jshintrc index d656d3f7..fad0529f 100644 --- a/.jshintrc +++ b/.jshintrc @@ -12,6 +12,7 @@ "unused": true, "strict": false, "node": true, + "browser": true, "jquery": true, "globals": { "prettyPrint": false, diff --git a/Gruntfile.js b/Gruntfile.js index 4f3bcf89..30b47502 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -21,16 +21,28 @@ module.exports = function (grunt) { // Concat definitions concat: { - dist: { + jquery: { files: { - "dist/jquery.linkify.js": [ + "build/jquery.linkify.js": [ "src/linkified.js", "src/jquery.linkify.js" ] } - }, - options: { - banner: "<%= meta.banner %>" + } + }, + + // Wrap files + wrap: { + jquery: { + expand: true, + src: ['build/jquery.linkify.js'], + dest: 'build/', + options: { + wrapper: [ + '<%= meta.banner %>\n;(function ($, window, document, undefined) {\n"use strict";', + '})(jQuery, window, document)\n' + ] + } } }, @@ -44,7 +56,7 @@ module.exports = function (grunt) { // Minify definitions uglify: { - my_target: { + dist: { src: ["dist/jquery.linkify.js"], dest: "dist/jquery.linkify.min.js" }, @@ -54,7 +66,13 @@ module.exports = function (grunt) { }, copy: { - main: { + build: { + expand: true, + flatten: true, + src: "build/build/*", + dest: "dist/" + }, + demo: { src: "dist/*", dest: "demo/" }, @@ -93,21 +111,32 @@ module.exports = function (grunt) { }, clean: [ - ".grunt/grunt-gh-pages/gh-pages" + ".grunt/grunt-gh-pages/gh-pages", + "build/*" ] }); - grunt.loadNpmTasks("grunt-contrib-concat"); grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks("grunt-contrib-concat"); grunt.loadNpmTasks("grunt-contrib-jshint"); grunt.loadNpmTasks("grunt-contrib-uglify"); grunt.loadNpmTasks("grunt-contrib-connect"); grunt.loadNpmTasks("grunt-contrib-clean"); + grunt.loadNpmTasks("grunt-wrap"); grunt.loadNpmTasks("grunt-gh-pages"); grunt.loadNpmTasks("grunt-bumper"); - grunt.registerTask("default", ["jshint", "concat", "uglify", "copy"]); + grunt.registerTask("default", [ + "jshint", + "concat", + "wrap", + "copy:build", + "uglify", + "copy:demo", + "clean" + ]); + grunt.registerTask("travis", ["jshint"]); grunt.registerTask("release", ["bumper", "clean"]); grunt.registerTask("release:minor", ["bumper:minor", "clean"]); diff --git a/bower.json b/bower.json index 8d88cf32..36a5be39 100644 --- a/bower.json +++ b/bower.json @@ -21,7 +21,7 @@ ], "license": "MIT", "dependencies": { - "jquery": ">= 1.9.0" + "jquery": "~1.10.2" }, "ignore": [ "**/.*", diff --git a/demo/index.html b/demo/index.html index 892b8f2b..da4b60c0 100644 --- a/demo/index.html +++ b/demo/index.html @@ -385,7 +385,7 @@

Options

- + diff --git a/dist/jquery.linkify.js b/dist/jquery.linkify.js index f7b5cd78..769cd78d 100644 --- a/dist/jquery.linkify.js +++ b/dist/jquery.linkify.js @@ -6,6 +6,9 @@ * Made by SoapBox Innovations, Inc. * Under MIT License */ + +;(function ($, window, document, undefined) { +"use strict"; /** A Linkified object contains a DOM node (or just plain text) whose inner text is replaced by HTML containing `` links to URLs @@ -28,403 +31,386 @@ @class Linkified */ -;(function (window, document, undefined) { - - "use strict"; - - var defaults = { - tagName: 'a', - newLine: '\n', - target: '_blank', - linkClass: null, - linkClasses: [], - linkAttributes: null - }; - - function Linkified(element, options) { - - // Setup - this._defaults = defaults; - this.element = element; - this.setOptions(options); - this.init(); - } - Linkified.prototype = { +var defaults = { + tagName: 'a', + newLine: '\n', + target: '_blank', + linkClass: null, + linkClasses: [], + linkAttributes: null +}; - constructor: Linkified, +function Linkified(element, options) { - /** - Initializer - @method init - */ - init: function () { - if (this.element.nodeType === 1) { - Linkified.linkifyNode.call(this, this.element); - } else { - this.element = Linkified.linkify.apply( - this, - this.element.toString() - ); - } - }, - - /** - Used to reset the options for this plugin - @method setOptions - @param {Object} options - */ - setOptions: function (options) { - this.settings = Linkified.extendSettings(options, this.settings); - }, - - /** - Returns the HTML of the linkified text. - @method toString - @return {String} html - */ - toString: function () { - - // Returned the linkified HTML - return this.element.toString(); - } + // Setup + this._defaults = defaults; + this.element = element; + this.setOptions(options); + this.init(); +} +Linkified.prototype = { - }; + constructor: Linkified, /** - Create an extended settings object using the default options. - Include a second hash to use those as defaults instead. + Initializer + @method init */ - Linkified.extendSettings = function (options, settings) { - var prop; - - settings = settings || {}; - - for (prop in defaults) { - if (!settings[prop]) { - settings[prop] = defaults[prop]; - } - } - - for (prop in options) { - settings[prop] = options[prop]; + init: function () { + if (this.element.nodeType === 1) { + Linkified.linkifyNode.call(this, this.element); + } else { + this.element = Linkified.linkify.apply( + this, + this.element.toString() + ); } - return settings; - }; - + }, /** - The url-matching regular expression for double-spaced text - @property linkMatch - @static - @type RegExp + Used to reset the options for this plugin + @method setOptions + @param {Object} options */ - Linkified.linkMatch = new RegExp([ - - // The groups - '(', // 1. Character before the link - '\\s|[^a-zA-Z0-9.\\+_\\/"\\>\\-]|^', - ')(?:', //Main group - '(', // 2. Email address (optional) - '[a-zA-Z0-9\\+_\\-]+', - '(?:', - '\\.[a-zA-Z0-9\\+_\\-]+', - ')*@', - ')?(', // 3. Protocol (optional) - 'http:\\/\\/|https:\\/\\/|ftp:\\/\\/', - ')?(', // 4. Domain & Subdomains - '(?:(?:[a-z0-9][a-z0-9_%\\-_+]*\\.)+)', - ')(', // 5. Top-level domain - http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains - '(?:com|ca|co|edu|gov|net|org|dev|biz|cat|int|pro|tel|mil|aero|asia|coop|info|jobs|mobi|museum|name|post|travel|local|[a-z]{2})', - ')(', // 6. Query string (optional) - '(?:', - '[\\/|\\?]', - '(?:', - '[\\-a-zA-Z0-9_%#*&+=~!?,;:.\\/]*', - ')*', - ')', - '[\\-\\/a-zA-Z0-9_%#*&+=~]', - '|', - '\\/?', - ')?', - ')(', // 7. Character after the link - '[^a-zA-Z0-9\\+_\\/"\\<\\-]|$', - ')' - ].join(''), 'g'); + setOptions: function (options) { + this.settings = Linkified.extendSettings(options, this.settings); + }, /** - The regular expression of matching email links after the - application of the initial link matcher. - - @property emailLinkMatch - @static - @type RegExp + Returns the HTML of the linkified text. + @method toString + @return {String} html */ - Linkified.emailLinkMatch = /(<[a-z]+ href=\")(http:\/\/)([a-zA-Z0-9\+_\-]+(?:\.[a-zA-Z0-9\+_\-]+)*@)/g; + toString: function () { + // Returned the linkified HTML + return this.element.toString(); + } - /** - Linkify the given text - @method linkify - @return {String} html - */ - Linkified.linkify = function (text, options) { - var attr, - settings, - linkClasses, - linkReplace = []; +}; - if (this.constructor === Linkified && this.settings) { +/** + Create an extended settings object using the default options. + Include a second hash to use those as defaults instead. +*/ +Linkified.extendSettings = function (options, settings) { + var prop; - // Called from an instance of Linkified - settings = this.settings; - if (options) { - settings = Linkified.extendSettings(options, settings); - } + settings = settings || {}; - } else { - settings = Linkified.extendSettings(options); + for (prop in defaults) { + if (!settings[prop]) { + settings[prop] = defaults[prop]; } + } - // Normalize class names - if (settings.linkClass) { - linkClasses = settings.linkClass.split(/\s+/); - } else { - linkClasses = []; + for (prop in options) { + settings[prop] = options[prop]; + } + return settings; +}; + + +/** + The url-matching regular expression for double-spaced text + @property linkMatch + @static + @type RegExp +*/ +Linkified.linkMatch = new RegExp([ + + // The groups + '(', // 1. Character before the link + '\\s|[^a-zA-Z0-9.\\+_\\/"\\>\\-]|^', + ')(?:', //Main group + '(', // 2. Email address (optional) + '[a-zA-Z0-9\\+_\\-]+', + '(?:', + '\\.[a-zA-Z0-9\\+_\\-]+', + ')*@', + ')?(', // 3. Protocol (optional) + 'http:\\/\\/|https:\\/\\/|ftp:\\/\\/', + ')?(', // 4. Domain & Subdomains + '(?:(?:[a-z0-9][a-z0-9_%\\-_+]*\\.)+)', + ')(', // 5. Top-level domain - http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains + '(?:com|ca|co|edu|gov|net|org|dev|biz|cat|int|pro|tel|mil|aero|asia|coop|info|jobs|mobi|museum|name|post|travel|local|[a-z]{2})', + ')(', // 6. Query string (optional) + '(?:', + '[\\/|\\?]', + '(?:', + '[\\-a-zA-Z0-9_%#*&+=~!?,;:.\\/]*', + ')*', + ')', + '[\\-\\/a-zA-Z0-9_%#*&+=~]', + '|', + '\\/?', + ')?', + ')(', // 7. Character after the link + '[^a-zA-Z0-9\\+_\\/"\\<\\-]|$', + ')' +].join(''), 'g'); + +/** + The regular expression of matching email links after the + application of the initial link matcher. + + @property emailLinkMatch + @static + @type RegExp +*/ +Linkified.emailLinkMatch = /(<[a-z]+ href=\")(http:\/\/)([a-zA-Z0-9\+_\-]+(?:\.[a-zA-Z0-9\+_\-]+)*@)/g; + + +/** + Linkify the given text + @method linkify + @return {String} html +*/ +Linkified.linkify = function (text, options) { + + var attr, + settings, + linkClasses, + linkReplace = []; + + if (this.constructor === Linkified && this.settings) { + + // Called from an instance of Linkified + settings = this.settings; + if (options) { + settings = Linkified.extendSettings(options, settings); } - linkClasses.push.apply(linkClasses, settings.linkClasses); + } else { + settings = Linkified.extendSettings(options); + } + + // Normalize class names + if (settings.linkClass) { + linkClasses = settings.linkClass.split(/\s+/); + } else { + linkClasses = []; + } + linkClasses.push.apply(linkClasses, settings.linkClasses); - // Get rid of tags and HTML-structure, - // Duplicate whitespace in preparation for linking - text = text - .replace(/$2$3$4$5$6$7'); - // Add other (normalized) attributes - for (attr in settings.linkAttributes) { - linkReplace.push([ - attr, - '="', - settings.linkAttributes[attr] - .replace(/\"/g, '"') - .replace(/\$/g, '$'), - '"' - ].join('')); - } + // Create the link + text = text.replace(Linkified.linkMatch, linkReplace.join(' ')); - // Finish off - linkReplace.push('>$2$3$4$5$6$7'); + // The previous line added `http://` to emails. Replace that with `mailto:` + text = text.replace(Linkified.emailLinkMatch, '$1mailto:$3'); - // Create the link - text = text.replace(Linkified.linkMatch, linkReplace.join(' ')); + // Revert whitespace characters back to a single character + text = text.replace(/(\s){2}/g, '$1'); - // The previous line added `http://` to emails. Replace that with `mailto:` - text = text.replace(Linkified.emailLinkMatch, '$1mailto:$3'); + // Trim and account for new lines + text = text.replace(/\n/g, settings.newLine); - // Revert whitespace characters back to a single character - text = text.replace(/(\s){2}/g, '$1'); + return text; - // Trim and account for new lines - text = text.replace(/\n/g, settings.newLine); +}; - return text; +/** + Given an HTML DOM node, linkify its contents +*/ +Linkified.linkifyNode = function (node) { + + var children, + childNode, + childCount, + dummyElement, + i; + + // Don't linkify anchor tags or tags that have the .linkified class + if (node && + typeof node === 'object' && + node.nodeType === 1 && + node.tagName.toLowerCase() !== 'a' && + !/[^\s]linkified[\s$]/.test(node.className) + ) { + + children = []; + dummyElement = Linkified._dummyElement || + document.createElement('div'); + + childNode = node.firstChild; + childCount = node.childElementCount; + + while (childNode) { + + if (childNode.nodeType === 3) { + + /* + Cleanup dummy node. This is to make sure that + existing nodes don't get improperly removed + */ + while (dummyElement.firstChild) { + dummyElement.removeChild(dummyElement.firstChild); + } - }; + /* + Linkify the text node, set the result to the + dummy's contents + */ + dummyElement.innerHTML = Linkified.linkify.call( + this, + childNode.textContent || childNode.innerText + ); - /** - Given an HTML DOM node, linkify its contents - */ - Linkified.linkifyNode = function (node) { - - var children, - childNode, - childCount, - dummyElement, - i; - - // Don't linkify anchor tags or tags that have the .linkified class - if (node && - typeof node === 'object' && - node.nodeType === 1 && - node.tagName.toLowerCase() !== 'a' && - !/[^\s]linkified[\s$]/.test(node.className) - ) { - - children = []; - dummyElement = Linkified._dummyElement || - document.createElement('div'); - - childNode = node.firstChild; - childCount = node.childElementCount; - - while (childNode) { - - if (childNode.nodeType === 3) { - - /* - Cleanup dummy node. This is to make sure that - existing nodes don't get improperly removed - */ - while (dummyElement.firstChild) { - dummyElement.removeChild(dummyElement.firstChild); - } - - /* - Linkify the text node, set the result to the - dummy's contents - */ - dummyElement.innerHTML = Linkified.linkify.call( - this, - childNode.textContent || childNode.innerText - ); - - /* - Parse the linkified text and append it to the - new children - */ - children.push.apply( - children, - dummyElement.childNodes - ); - - // Clean up the dummy - while (dummyElement.firstChild) { - dummyElement.removeChild(dummyElement.firstChild); - } - - } else if (childNode.nodeType === 1) { - - // This is an HTML node, linkify it and add it - children.push(Linkified.linkifyNode(childNode)); - - } else { - - // This is some other kind of node, just push it - children.push(childNode); + /* + Parse the linkified text and append it to the + new children + */ + children.push.apply( + children, + dummyElement.childNodes + ); + + // Clean up the dummy + while (dummyElement.firstChild) { + dummyElement.removeChild(dummyElement.firstChild); } - childNode = childNode.nextSibling; - } + } else if (childNode.nodeType === 1) { + // This is an HTML node, linkify it and add it + children.push(Linkified.linkifyNode(childNode)); - // Remove all existing nodes. - while (node.firstChild) { - node.removeChild(node.firstChild); - } + } else { - // Replace with all the new nodes - for (i = 0; i < children.length; i++) { - node.appendChild(children[i]); + // This is some other kind of node, just push it + children.push(childNode); } + childNode = childNode.nextSibling; } - console.log("\n", children); - return node; - }, - Linkified._dummyElement = document.createElement('div'); - if (window) { - window.Linkified = Linkified; - } + // Remove all existing nodes. + while (node.firstChild) { + node.removeChild(node.firstChild); + } - return Linkified; + // Replace with all the new nodes + for (i = 0; i < children.length; i++) { + node.appendChild(children[i]); + } -})(window, document, undefined); + } + return node; +}, -;(function ($, window, document, undefined) { +Linkified._dummyElement = document.createElement('div'); - // Create the defaults once - var Linkified = window.Linkified; +// Plugin definition +$.fn.linkify = function (options) { + return this.each(function () { - // Plugin definition - $.fn.linkify = function (options) { - return this.each(function () { + var linkified; - var linkified; + if (linkified = $.data(this, 'plugin-linkify')) { - if (linkified = $.data(this, 'plugin-linkify')) { + // Relinkify + linkified.setOptions(options); + linkified.init(); - // Relinkify - linkified.setOptions(options); - linkified.init(); + } else { - } else { + // Linkify + $.data( + this, + 'plugin-linkify', + new Linkified(this, options) + ); - // Linkify - $.data( - this, - 'plugin-linkify', - new Linkified(this, options) - ); + } + }); +}; + +// Maintain access to the constructor from the plugin +$.fn.linkify.Constructor = Linkified; + +// DOM data- API setup +$(window).on('load', function () { + $('[data-linkify]').each(function () { + var $this = $(this), + $target, + target = $this.attr('data-linkify'), + options = { + tagName: $this.attr('data-linkify-tagname') || undefined, + newLine: $this.attr('data-linkify-newline') || undefined, + target: $this.attr('data-linkify-target') || undefined, + linkClass: $this.attr('data-linkify-linkclass') || undefined + }; + + $target = target === 'this' ? $this : $this.find(target); + $target.linkify(options); - } - }); - }; - - // Maintain access to the constructor from the plugin - $.fn.linkify.Constructor = Linkified; - - // DOM data- API setup - $(window).on('load', function () { - $('[data-linkify]').each(function () { - var $this = $(this), - $target, - target = $this.attr('data-linkify'), - options = { - tagName: $this.attr('data-linkify-tagname') || undefined, - newLine: $this.attr('data-linkify-newline') || undefined, - target: $this.attr('data-linkify-target') || undefined, - linkClass: $this.attr('data-linkify-linkclass') || undefined - }; - - $target = target === 'this' ? $this : $this.find(target); - $target.linkify(options); - - }); }); +}); - // Setup click events for linkified elements - $('body').on('click', '.linkified', function () { - var $link = $(this), - url = $link.attr('href'), - isEmail = url.substr(0, 7) === 'mailto:', - target = $link.attr('target'); +// Setup click events for linkified elements +$('body').on('click', '.linkified', function () { + var $link = $(this), + url = $link.attr('href'), + isEmail = /^mailto:/i.test(url), + target = $link.attr('target'); - if (isEmail) { + if (isEmail) { - // mailto links ignore the target - window.location.href = url; + // mailto links ignore the target + window.location.href = url; - } else { - window.open(url, target); - } + } else { + window.open(url, target); + } - return false; - }); + return false; +}); -})(jQuery, window, document); +})(jQuery, window, document) diff --git a/dist/jquery.linkify.min.js b/dist/jquery.linkify.min.js index 86da1ddf..3913e95b 100644 --- a/dist/jquery.linkify.min.js +++ b/dist/jquery.linkify.min.js @@ -6,4 +6,4 @@ * Made by SoapBox Innovations, Inc. * Under MIT License */ -!function(a,b){"use strict";function c(a,b){this._defaults=d,this.element=a,this.setOptions(b),this.init()}var d={tagName:"a",newLine:"\n",target:"_blank",linkClass:null,linkClasses:[],linkAttributes:null};return c.prototype={constructor:c,init:function(){1===this.element.nodeType?c.linkifyNode.call(this,this.element):this.element=c.linkify.apply(this,this.element.toString())},setOptions:function(a){this.settings=c.extendSettings(a,this.settings)},toString:function(){return this.element.toString()}},c.extendSettings=function(a,b){var c;b=b||{};for(c in d)b[c]||(b[c]=d[c]);for(c in a)b[c]=a[c];return b},c.linkMatch=new RegExp(["(",'\\s|[^a-zA-Z0-9.\\+_\\/"\\>\\-]|^',")(?:","(","[a-zA-Z0-9\\+_\\-]+","(?:","\\.[a-zA-Z0-9\\+_\\-]+",")*@",")?(","http:\\/\\/|https:\\/\\/|ftp:\\/\\/",")?(","(?:(?:[a-z0-9][a-z0-9_%\\-_+]*\\.)+)",")(","(?:com|ca|co|edu|gov|net|org|dev|biz|cat|int|pro|tel|mil|aero|asia|coop|info|jobs|mobi|museum|name|post|travel|local|[a-z]{2})",")(","(?:","[\\/|\\?]","(?:","[\\-a-zA-Z0-9_%#*&+=~!?,;:.\\/]*",")*",")","[\\-\\/a-zA-Z0-9_%#*&+=~]","|","\\/?",")?",")(",'[^a-zA-Z0-9\\+_\\/"\\<\\-]|$',")"].join(""),"g"),c.emailLinkMatch=/(<[a-z]+ href=\")(http:\/\/)([a-zA-Z0-9\+_\-]+(?:\.[a-zA-Z0-9\+_\-]+)*@)/g,c.linkify=function(a,b){var d,e,f,g=[];this.constructor===c&&this.settings?(e=this.settings,b&&(e=c.extendSettings(b,e))):e=c.extendSettings(b),f=e.linkClass?e.linkClass.split(/\s+/):[],f.push.apply(f,e.linkClasses),a=a.replace(/$2$3$4$5$6$7"),a=a.replace(c.linkMatch,g.join(" ")),a=a.replace(c.emailLinkMatch,"$1mailto:$3"),a=a.replace(/(\s){2}/g,"$1"),a=a.replace(/\n/g,e.newLine)},c.linkifyNode=function(a){var d,e,f,g,h;if(a&&"object"==typeof a&&1===a.nodeType&&"a"!==a.tagName.toLowerCase()&&!/[^\s]linkified[\s$]/.test(a.className)){for(d=[],g=c._dummyElement||b.createElement("div"),e=a.firstChild,f=a.childElementCount;e;){if(3===e.nodeType){for(;g.firstChild;)g.removeChild(g.firstChild);for(g.innerHTML=c.linkify.call(this,e.textContent||e.innerText),d.push.apply(d,g.childNodes);g.firstChild;)g.removeChild(g.firstChild)}else 1===e.nodeType?d.push(c.linkifyNode(e)):d.push(e);e=e.nextSibling}for(;a.firstChild;)a.removeChild(a.firstChild);for(h=0;h\\-]|^',")(?:","(","[a-zA-Z0-9\\+_\\-]+","(?:","\\.[a-zA-Z0-9\\+_\\-]+",")*@",")?(","http:\\/\\/|https:\\/\\/|ftp:\\/\\/",")?(","(?:(?:[a-z0-9][a-z0-9_%\\-_+]*\\.)+)",")(","(?:com|ca|co|edu|gov|net|org|dev|biz|cat|int|pro|tel|mil|aero|asia|coop|info|jobs|mobi|museum|name|post|travel|local|[a-z]{2})",")(","(?:","[\\/|\\?]","(?:","[\\-a-zA-Z0-9_%#*&+=~!?,;:.\\/]*",")*",")","[\\-\\/a-zA-Z0-9_%#*&+=~]","|","\\/?",")?",")(",'[^a-zA-Z0-9\\+_\\/"\\<\\-]|$',")"].join(""),"g"),e.emailLinkMatch=/(<[a-z]+ href=\")(http:\/\/)([a-zA-Z0-9\+_\-]+(?:\.[a-zA-Z0-9\+_\-]+)*@)/g,e.linkify=function(a,b){var c,d,f,g=[];this.constructor===e&&this.settings?(d=this.settings,b&&(d=e.extendSettings(b,d))):d=e.extendSettings(b),f=d.linkClass?d.linkClass.split(/\s+/):[],f.push.apply(f,d.linkClasses),a=a.replace(/$2$3$4$5$6$7"),a=a.replace(e.linkMatch,g.join(" ")),a=a.replace(e.emailLinkMatch,"$1mailto:$3"),a=a.replace(/(\s){2}/g,"$1"),a=a.replace(/\n/g,d.newLine)},e.linkifyNode=function(a){var b,d,f,g,h;if(a&&"object"==typeof a&&1===a.nodeType&&"a"!==a.tagName.toLowerCase()&&!/[^\s]linkified[\s$]/.test(a.className)){for(b=[],g=e._dummyElement||c.createElement("div"),d=a.firstChild,f=a.childElementCount;d;){if(3===d.nodeType){for(;g.firstChild;)g.removeChild(g.firstChild);for(g.innerHTML=e.linkify.call(this,d.textContent||d.innerText),b.push.apply(b,g.childNodes);g.firstChild;)g.removeChild(g.firstChild)}else 1===d.nodeType?b.push(e.linkifyNode(d)):b.push(d);d=d.nextSibling}for(;a.firstChild;)a.removeChild(a.firstChild);for(h=0;h\\-]|^', - ')(?:', //Main group - '(', // 2. Email address (optional) - '[a-zA-Z0-9\\+_\\-]+', - '(?:', - '\\.[a-zA-Z0-9\\+_\\-]+', - ')*@', - ')?(', // 3. Protocol (optional) - 'http:\\/\\/|https:\\/\\/|ftp:\\/\\/', - ')?(', // 4. Domain & Subdomains - '(?:(?:[a-z0-9][a-z0-9_%\\-_+]*\\.)+)', - ')(', // 5. Top-level domain - http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains - '(?:com|ca|co|edu|gov|net|org|dev|biz|cat|int|pro|tel|mil|aero|asia|coop|info|jobs|mobi|museum|name|post|travel|local|[a-z]{2})', - ')(', // 6. Query string (optional) - '(?:', - '[\\/|\\?]', - '(?:', - '[\\-a-zA-Z0-9_%#*&+=~!?,;:.\\/]*', - ')*', - ')', - '[\\-\\/a-zA-Z0-9_%#*&+=~]', - '|', - '\\/?', - ')?', - ')(', // 7. Character after the link - '[^a-zA-Z0-9\\+_\\/"\\<\\-]|$', - ')' - ].join(''), 'g'); + setOptions: function (options) { + this.settings = Linkified.extendSettings(options, this.settings); + }, /** - The regular expression of matching email links after the - application of the initial link matcher. - - @property emailLinkMatch - @static - @type RegExp + Returns the HTML of the linkified text. + @method toString + @return {String} html */ - Linkified.emailLinkMatch = /(<[a-z]+ href=\")(http:\/\/)([a-zA-Z0-9\+_\-]+(?:\.[a-zA-Z0-9\+_\-]+)*@)/g; + toString: function () { + // Returned the linkified HTML + return this.element.toString(); + } - /** - Linkify the given text - @method linkify - @return {String} html - */ - Linkified.linkify = function (text, options) { - var attr, - settings, - linkClasses, - linkReplace = []; +}; - if (this.constructor === Linkified && this.settings) { +/** + Create an extended settings object using the default options. + Include a second hash to use those as defaults instead. +*/ +Linkified.extendSettings = function (options, settings) { + var prop; - // Called from an instance of Linkified - settings = this.settings; - if (options) { - settings = Linkified.extendSettings(options, settings); - } + settings = settings || {}; - } else { - settings = Linkified.extendSettings(options); + for (prop in defaults) { + if (!settings[prop]) { + settings[prop] = defaults[prop]; } + } - // Normalize class names - if (settings.linkClass) { - linkClasses = settings.linkClass.split(/\s+/); - } else { - linkClasses = []; + for (prop in options) { + settings[prop] = options[prop]; + } + return settings; +}; + + +/** + The url-matching regular expression for double-spaced text + @property linkMatch + @static + @type RegExp +*/ +Linkified.linkMatch = new RegExp([ + + // The groups + '(', // 1. Character before the link + '\\s|[^a-zA-Z0-9.\\+_\\/"\\>\\-]|^', + ')(?:', //Main group + '(', // 2. Email address (optional) + '[a-zA-Z0-9\\+_\\-]+', + '(?:', + '\\.[a-zA-Z0-9\\+_\\-]+', + ')*@', + ')?(', // 3. Protocol (optional) + 'http:\\/\\/|https:\\/\\/|ftp:\\/\\/', + ')?(', // 4. Domain & Subdomains + '(?:(?:[a-z0-9][a-z0-9_%\\-_+]*\\.)+)', + ')(', // 5. Top-level domain - http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains + '(?:com|ca|co|edu|gov|net|org|dev|biz|cat|int|pro|tel|mil|aero|asia|coop|info|jobs|mobi|museum|name|post|travel|local|[a-z]{2})', + ')(', // 6. Query string (optional) + '(?:', + '[\\/|\\?]', + '(?:', + '[\\-a-zA-Z0-9_%#*&+=~!?,;:.\\/]*', + ')*', + ')', + '[\\-\\/a-zA-Z0-9_%#*&+=~]', + '|', + '\\/?', + ')?', + ')(', // 7. Character after the link + '[^a-zA-Z0-9\\+_\\/"\\<\\-]|$', + ')' +].join(''), 'g'); + +/** + The regular expression of matching email links after the + application of the initial link matcher. + + @property emailLinkMatch + @static + @type RegExp +*/ +Linkified.emailLinkMatch = /(<[a-z]+ href=\")(http:\/\/)([a-zA-Z0-9\+_\-]+(?:\.[a-zA-Z0-9\+_\-]+)*@)/g; + + +/** + Linkify the given text + @method linkify + @return {String} html +*/ +Linkified.linkify = function (text, options) { + + var attr, + settings, + linkClasses, + linkReplace = []; + + if (this.constructor === Linkified && this.settings) { + + // Called from an instance of Linkified + settings = this.settings; + if (options) { + settings = Linkified.extendSettings(options, settings); } - linkClasses.push.apply(linkClasses, settings.linkClasses); + } else { + settings = Linkified.extendSettings(options); + } + + // Normalize class names + if (settings.linkClass) { + linkClasses = settings.linkClass.split(/\s+/); + } else { + linkClasses = []; + } + + linkClasses.push.apply(linkClasses, settings.linkClasses); + + // Get rid of tags and HTML-structure, + // Duplicate whitespace in preparation for linking + text = text + .replace(/$2$3$4$5$6$7'); - // Add other (normalized) attributes - for (attr in settings.linkAttributes) { - linkReplace.push([ - attr, - '="', - settings.linkAttributes[attr] - .replace(/\"/g, '"') - .replace(/\$/g, '$'), - '"' - ].join('')); - } + // Create the link + text = text.replace(Linkified.linkMatch, linkReplace.join(' ')); - // Finish off - linkReplace.push('>$2$3$4$5$6$7'); + // The previous line added `http://` to emails. Replace that with `mailto:` + text = text.replace(Linkified.emailLinkMatch, '$1mailto:$3'); - // Create the link - text = text.replace(Linkified.linkMatch, linkReplace.join(' ')); + // Revert whitespace characters back to a single character + text = text.replace(/(\s){2}/g, '$1'); - // The previous line added `http://` to emails. Replace that with `mailto:` - text = text.replace(Linkified.emailLinkMatch, '$1mailto:$3'); + // Trim and account for new lines + text = text.replace(/\n/g, settings.newLine); - // Revert whitespace characters back to a single character - text = text.replace(/(\s){2}/g, '$1'); + return text; - // Trim and account for new lines - text = text.replace(/\n/g, settings.newLine); +}; - return text; +/** + Given an HTML DOM node, linkify its contents +*/ +Linkified.linkifyNode = function (node) { + + var children, + childNode, + childCount, + dummyElement, + i; + + // Don't linkify anchor tags or tags that have the .linkified class + if (node && + typeof node === 'object' && + node.nodeType === 1 && + node.tagName.toLowerCase() !== 'a' && + !/[^\s]linkified[\s$]/.test(node.className) + ) { + + children = []; + dummyElement = Linkified._dummyElement || + document.createElement('div'); + + childNode = node.firstChild; + childCount = node.childElementCount; + + while (childNode) { + + if (childNode.nodeType === 3) { + + /* + Cleanup dummy node. This is to make sure that + existing nodes don't get improperly removed + */ + while (dummyElement.firstChild) { + dummyElement.removeChild(dummyElement.firstChild); + } - }; + /* + Linkify the text node, set the result to the + dummy's contents + */ + dummyElement.innerHTML = Linkified.linkify.call( + this, + childNode.textContent || childNode.innerText + ); - /** - Given an HTML DOM node, linkify its contents - */ - Linkified.linkifyNode = function (node) { - - var children, - childNode, - childCount, - dummyElement, - i; - - // Don't linkify anchor tags or tags that have the .linkified class - if (node && - typeof node === 'object' && - node.nodeType === 1 && - node.tagName.toLowerCase() !== 'a' && - !/[^\s]linkified[\s$]/.test(node.className) - ) { - - children = []; - dummyElement = Linkified._dummyElement || - document.createElement('div'); - - childNode = node.firstChild; - childCount = node.childElementCount; - - while (childNode) { - - if (childNode.nodeType === 3) { - - /* - Cleanup dummy node. This is to make sure that - existing nodes don't get improperly removed - */ - while (dummyElement.firstChild) { - dummyElement.removeChild(dummyElement.firstChild); - } - - /* - Linkify the text node, set the result to the - dummy's contents - */ - dummyElement.innerHTML = Linkified.linkify.call( - this, - childNode.textContent || childNode.innerText - ); - - /* - Parse the linkified text and append it to the - new children - */ - children.push.apply( - children, - dummyElement.childNodes - ); - - // Clean up the dummy - while (dummyElement.firstChild) { - dummyElement.removeChild(dummyElement.firstChild); - } - - } else if (childNode.nodeType === 1) { - - // This is an HTML node, linkify it and add it - children.push(Linkified.linkifyNode(childNode)); - - } else { - - // This is some other kind of node, just push it - children.push(childNode); + /* + Parse the linkified text and append it to the + new children + */ + children.push.apply( + children, + dummyElement.childNodes + ); + + // Clean up the dummy + while (dummyElement.firstChild) { + dummyElement.removeChild(dummyElement.firstChild); } - childNode = childNode.nextSibling; - } + } else if (childNode.nodeType === 1) { + // This is an HTML node, linkify it and add it + children.push(Linkified.linkifyNode(childNode)); - // Remove all existing nodes. - while (node.firstChild) { - node.removeChild(node.firstChild); - } + } else { - // Replace with all the new nodes - for (i = 0; i < children.length; i++) { - node.appendChild(children[i]); + // This is some other kind of node, just push it + children.push(childNode); } + childNode = childNode.nextSibling; } - console.log("\n", children); - return node; - }, - Linkified._dummyElement = document.createElement('div'); - if (window) { - window.Linkified = Linkified; - } + // Remove all existing nodes. + while (node.firstChild) { + node.removeChild(node.firstChild); + } + + // Replace with all the new nodes + for (i = 0; i < children.length; i++) { + node.appendChild(children[i]); + } - return Linkified; + } + return node; +}, -})(window, document, undefined); +Linkified._dummyElement = document.createElement('div');