From b0615d1ed9ca5f70573f7adfce3ef75c63a4602b Mon Sep 17 00:00:00 2001 From: Mahemoff Date: Thu, 15 Sep 2011 22:25:56 +0100 Subject: [PATCH 1/9] Added twitter (@user) option --- ba-linkify.js | 254 ++++++++++++++++++++++++++------------------------ 1 file changed, 134 insertions(+), 120 deletions(-) diff --git a/ba-linkify.js b/ba-linkify.js index 4bf27ef..c73475f 100644 --- a/ba-linkify.js +++ b/ba-linkify.js @@ -68,147 +68,161 @@ // // (String) An HTML string containing links. -window.linkify = (function(){ - var - SCHEME = "[a-z\\d.-]+://", - IPV4 = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])", - HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+", - TLD = "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)", - HOST_OR_IP = "(?:" + HOSTNAME + TLD + "|" + IPV4 + ")", - PATH = "(?:[;/][^#?<>\\s]*)?", - QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?", - URI1 = "\\b" + SCHEME + "[^<>\\s]+", - URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)", - - MAILTO = "mailto:", - EMAIL = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)", - - URI_RE = new RegExp( "(?:" + URI1 + "|" + URI2 + "|" + EMAIL + ")", "ig" ), - SCHEME_RE = new RegExp( "^" + SCHEME, "i" ), - - quotes = { - "'": "`", - '>': '<', - ')': '(', - ']': '[', - '}': '{', - '»': '«', - '›': '‹' - }, - - default_options = { - callback: function( text, href ) { - return href ? '' + text + '' : text; - }, - punct_regexp: /(?:[!?.,:;'"]|(?:&|&)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/ - }; - - return function( txt, options ) { - options = options || {}; +(function() { + + var linkify = (function(){ - // Temp variables. - var arr, - i, - link, - href, + var + SCHEME = "[a-z\\d.-]+://", + IPV4 = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])", + HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+", + TLD = "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)", + HOST_OR_IP = "(?:" + HOSTNAME + TLD + "|" + IPV4 + ")", + PATH = "(?:[;/][^#?<>\\s]*)?", + QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?", + URI1 = "\\b" + SCHEME + "[^<>\\s]+", + URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)", - // Output HTML. - html = '', + MAILTO = "mailto:", + EMAIL = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)", - // Store text / link parts, in order, for re-combination. - parts = [], + URI_RE_LIST = [URI1, URI2, EMAIL], + SCHEME_RE = new RegExp( "^" + SCHEME, "i" ), - // Used for keeping track of indices in the text. - idx_prev, - idx_last, - idx, - link_last, + quotes = { + "'": "`", + '>': '<', + ')': '(', + ']': '[', + '}': '{', + '»': '«', + '›': '‹' + }, - // Used for trimming trailing punctuation and quotes from links. - matches_begin, - matches_end, - quote_begin, - quote_end; + default_options = { + callback: function( text, href ) { + return href ? '' + text + '' : text; + }, + punct_regexp: /(?:[!?.,:;'"]|(?:&|&)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/, + twitter: false + }; - // Initialize options. - for ( i in default_options ) { - if ( options[ i ] === undefined ) { - options[ i ] = default_options[ i ]; - } - } - - // Find links. - while ( arr = URI_RE.exec( txt ) ) { + return function( txt, options ) { + options = options || {}; + + if (options.twitter) URI_RE_LIST.push("@[a-zA-Z0-9_]+"); + + URI_RE = new RegExp( "(?:" + URI_RE_LIST.join("|") + ")", "ig" ); - link = arr[0]; - idx_last = URI_RE.lastIndex; - idx = idx_last - link.length; + // Temp variables. + var arr, + i, + link, + href, + + // Output HTML. + html = '', + + // Store text / link parts, in order, for re-combination. + parts = [], + + // Used for keeping track of indices in the text. + idx_prev, + idx_last, + idx, + link_last, + + // Used for trimming trailing punctuation and quotes from links. + matches_begin, + matches_end, + quote_begin, + quote_end; - // Not a link if preceded by certain characters. - if ( /[\/:]/.test( txt.charAt( idx - 1 ) ) ) { - continue; + // Initialize options. + for ( i in default_options ) { + if ( options[ i ] === undefined ) { + options[ i ] = default_options[ i ]; + } } - // Trim trailing punctuation. - do { - // If no changes are made, we don't want to loop forever! - link_last = link; + // Find links. + while ( arr = URI_RE.exec( txt ) ) { + + link = arr[0]; + idx_last = URI_RE.lastIndex; + idx = idx_last - link.length; - quote_end = link.substr( -1 ) - quote_begin = quotes[ quote_end ]; + // Not a link if preceded by certain characters. + if ( /[\/:]/.test( txt.charAt( idx - 1 ) ) ) { + continue; + } - // Ending quote character? - if ( quote_begin ) { - matches_begin = link.match( new RegExp( '\\' + quote_begin + '(?!$)', 'g' ) ); - matches_end = link.match( new RegExp( '\\' + quote_end, 'g' ) ); + // Trim trailing punctuation. + do { + // If no changes are made, we don't want to loop forever! + link_last = link; + + quote_end = link.substr( -1 ) + quote_begin = quotes[ quote_end ]; - // If quotes are unbalanced, remove trailing quote character. - if ( ( matches_begin ? matches_begin.length : 0 ) < ( matches_end ? matches_end.length : 0 ) ) { - link = link.substr( 0, link.length - 1 ); - idx_last--; + // Ending quote character? + if ( quote_begin ) { + matches_begin = link.match( new RegExp( '\\' + quote_begin + '(?!$)', 'g' ) ); + matches_end = link.match( new RegExp( '\\' + quote_end, 'g' ) ); + + // If quotes are unbalanced, remove trailing quote character. + if ( ( matches_begin ? matches_begin.length : 0 ) < ( matches_end ? matches_end.length : 0 ) ) { + link = link.substr( 0, link.length - 1 ); + idx_last--; + } } + + // Ending non-quote punctuation character? + if ( options.punct_regexp ) { + link = link.replace( options.punct_regexp, function(a){ + idx_last -= a.length; + return ''; + }); + } + } while ( link.length && link !== link_last ); + + href = link; + + // Add appropriate protocol to naked links. + if (options.twitter && href.indexOf( '@' ) == 0) + href = 'http://twitter.com/' + href.substr(1); + else if ( !SCHEME_RE.test( href ) ) { + href = ( href.indexOf( '@' ) !== -1 ? ( !href.indexOf( MAILTO ) ? '' : MAILTO ) + : !href.indexOf( 'irc.' ) ? 'irc://' + : !href.indexOf( 'ftp.' ) ? 'ftp://' + : 'http://' ) + + href; } - // Ending non-quote punctuation character? - if ( options.punct_regexp ) { - link = link.replace( options.punct_regexp, function(a){ - idx_last -= a.length; - return ''; - }); + // Push preceding non-link text onto the array. + if ( idx_prev != idx ) { + parts.push([ txt.slice( idx_prev, idx ) ]); + idx_prev = idx_last; } - } while ( link.length && link !== link_last ); - - href = link; + + // Push massaged link onto the array + parts.push([ link, href ]); + }; - // Add appropriate protocol to naked links. - if ( !SCHEME_RE.test( href ) ) { - href = ( href.indexOf( '@' ) !== -1 ? ( !href.indexOf( MAILTO ) ? '' : MAILTO ) - : !href.indexOf( 'irc.' ) ? 'irc://' - : !href.indexOf( 'ftp.' ) ? 'ftp://' - : 'http://' ) - + href; - } + // Push remaining non-link text onto the array. + parts.push([ txt.substr( idx_prev ) ]); - // Push preceding non-link text onto the array. - if ( idx_prev != idx ) { - parts.push([ txt.slice( idx_prev, idx ) ]); - idx_prev = idx_last; + // Process the array items. + for ( i = 0; i < parts.length; i++ ) { + html += options.callback.apply( null, parts[i] ); } - // Push massaged link onto the array - parts.push([ link, href ]); + // In case of catastrophic failure, return the original text; + return html || txt; }; - // Push remaining non-link text onto the array. - parts.push([ txt.substr( idx_prev ) ]); - - // Process the array items. - for ( i = 0; i < parts.length; i++ ) { - html += options.callback.apply( window, parts[i] ); - } - - // In case of catastrophic failure, return the original text; - return html || txt; - }; - + })(); + + (typeof(window)=='undefined') ? module.exports = linkify : window.linkify = linkify; + })(); From 14c50502bc65c2a09090d41f9176692e0e0f3b37 Mon Sep 17 00:00:00 2001 From: Mahemoff Date: Thu, 15 Sep 2011 22:27:18 +0100 Subject: [PATCH 2/9] Include README and test related to twitter option --- README.markdown | 11 +++++++++++ test/test.coffee | 4 ++++ 2 files changed, 15 insertions(+) create mode 100644 test/test.coffee diff --git a/README.markdown b/README.markdown index 1a20a7e..6d99889 100644 --- a/README.markdown +++ b/README.markdown @@ -1,3 +1,14 @@ + +# JavaScript Linkify: Process links in text! # + +This fork adds a twitter option: + linkify('@mahemoff', { twitter: true }) +is: + @mahemoff + +In doing so, there's some refactoring to simplify the way the uber URI regexp is built up. +There's also some basic support for Node and a tiny test (in CoffeeScript). + # JavaScript Linkify: Process links in text! # [http://benalman.com/projects/javascript-linkify/](http://benalman.com/projects/javascript-linkify/) diff --git a/test/test.coffee b/test/test.coffee new file mode 100644 index 0000000..d9390cd --- /dev/null +++ b/test/test.coffee @@ -0,0 +1,4 @@ +linkify = require '../ba-linkify.js' +console.log linkify 'abc http://def.com @cowboy ghijk good.com @mahemoff' +console.log linkify '@mahemoff', twitter: true +console.log linkify 'abc http://def.com @cowboy ghijk irc.freenode.org good.com @mahemoff', twitter: true From 73b32fa7e05d1ec8cd6ddb33bdb5ef05bf0e4c73 Mon Sep 17 00:00:00 2001 From: Michael Mahemoff Date: Thu, 15 Sep 2011 23:32:28 +0200 Subject: [PATCH 3/9] Edited README.markdown via GitHub --- README.markdown | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.markdown b/README.markdown index 6d99889..3798005 100644 --- a/README.markdown +++ b/README.markdown @@ -1,10 +1,9 @@ - # JavaScript Linkify: Process links in text! # This fork adds a twitter option: - linkify('@mahemoff', { twitter: true }) -is: - @mahemoff + linkify('@mahemoff', { twitter: true }) +becomes + @mahemoff In doing so, there's some refactoring to simplify the way the uber URI regexp is built up. There's also some basic support for Node and a tiny test (in CoffeeScript). From a8f0da16575096d2f8ab710e3173df072e3b65eb Mon Sep 17 00:00:00 2001 From: Mahemoff Date: Fri, 16 Sep 2011 00:07:14 +0100 Subject: [PATCH 4/9] add "attribs" option: arbitrary attribs on links --- README.markdown | 16 ++++++++++++---- ba-linkify.js | 22 +++++++++++++++------- test/test.coffee | 2 ++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/README.markdown b/README.markdown index 6d99889..36d9406 100644 --- a/README.markdown +++ b/README.markdown @@ -1,14 +1,22 @@ # JavaScript Linkify: Process links in text! # -This fork adds a twitter option: - linkify('@mahemoff', { twitter: true }) -is: - @mahemoff +This fork adds: +(1) a twitter option: + + linkify('@mahemoff', { twitter: true }) +becomes: + @mahemoff In doing so, there's some refactoring to simplify the way the uber URI regexp is built up. There's also some basic support for Node and a tiny test (in CoffeeScript). +(2) an attribs option: + + linkify('a.com b.com') +becomes: + a.com b.com + # JavaScript Linkify: Process links in text! # [http://benalman.com/projects/javascript-linkify/](http://benalman.com/projects/javascript-linkify/) diff --git a/ba-linkify.js b/ba-linkify.js index c73475f..31b6a6f 100644 --- a/ba-linkify.js +++ b/ba-linkify.js @@ -100,11 +100,13 @@ }, default_options = { - callback: function( text, href ) { - return href ? '' + text + '' : text; + callback: function( text, href, options ) { + return href ? ' Date: Fri, 16 Sep 2011 11:29:05 +0200 Subject: [PATCH 5/9] Edited README.markdown via GitHub --- README.markdown | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index a102559..834e894 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,5 @@ -# JavaScript Linkify: Process links in text! # +# What this fork adds: -This fork adds: (1) a twitter option: linkify('@mahemoff', { twitter: true }) @@ -12,10 +11,12 @@ There's also some basic support for Node and a tiny test (in CoffeeScript). (2) an attribs option: - linkify('a.com b.com') + linkify('a.com b.com', { target: '_blank' }) becomes: a.com b.com +(Note these links have target='_blank' as an extra attribute, in addition to the regular href and title.) + # JavaScript Linkify: Process links in text! # [http://benalman.com/projects/javascript-linkify/](http://benalman.com/projects/javascript-linkify/) From 09ab64f5807e92af4b1211ef443fa06f9b49dad1 Mon Sep 17 00:00:00 2001 From: Michael Mahemoff Date: Fri, 16 Sep 2011 11:30:08 +0200 Subject: [PATCH 6/9] Edited README.markdown via GitHub --- README.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index 834e894..67d915c 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,6 @@ # What this fork adds: -(1) a twitter option: +## (1) a twitter option: linkify('@mahemoff', { twitter: true }) becomes: @@ -9,13 +9,13 @@ becomes: In doing so, there's some refactoring to simplify the way the uber URI regexp is built up. There's also some basic support for Node and a tiny test (in CoffeeScript). -(2) an attribs option: +## (2) an attribs option: linkify('a.com b.com', { target: '_blank' }) becomes: a.com b.com -(Note these links have target='_blank' as an extra attribute, in addition to the regular href and title.) +(ote these links have target='_blank' as an extra attribute, in addition to the regular href and title. # JavaScript Linkify: Process links in text! # [http://benalman.com/projects/javascript-linkify/](http://benalman.com/projects/javascript-linkify/) From 8d26dbf094d235ec19d4b42ce3124aeca7c563ab Mon Sep 17 00:00:00 2001 From: Michael Mahemoff Date: Fri, 16 Sep 2011 11:30:34 +0200 Subject: [PATCH 7/9] Edited README.markdown via GitHub --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 67d915c..935c233 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,6 @@ # What this fork adds: -## (1) a twitter option: +## Twitter option linkify('@mahemoff', { twitter: true }) becomes: @@ -9,7 +9,7 @@ becomes: In doing so, there's some refactoring to simplify the way the uber URI regexp is built up. There's also some basic support for Node and a tiny test (in CoffeeScript). -## (2) an attribs option: +## Attribs option linkify('a.com b.com', { target: '_blank' }) becomes: From 0a4fe34b59f8bc82feebe7093c66e9c0a734bdab Mon Sep 17 00:00:00 2001 From: Michael Mahemoff Date: Fri, 16 Sep 2011 11:31:08 +0200 Subject: [PATCH 8/9] Edited README.markdown via GitHub --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 935c233..cfb5136 100644 --- a/README.markdown +++ b/README.markdown @@ -15,7 +15,7 @@ There's also some basic support for Node and a tiny test (in CoffeeScript). becomes: a.com b.com -(ote these links have target='_blank' as an extra attribute, in addition to the regular href and title. +Note these links have target='_blank' as an extra attribute, in addition to the regular href and title. # JavaScript Linkify: Process links in text! # [http://benalman.com/projects/javascript-linkify/](http://benalman.com/projects/javascript-linkify/) From e5c29c05ae3d1ffa472671fb567e28df0b0d51d0 Mon Sep 17 00:00:00 2001 From: Mahemoff Date: Tue, 4 Oct 2011 15:26:46 +0100 Subject: [PATCH 9/9] fix quotes with attribs option --- ba-linkify.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ba-linkify.js b/ba-linkify.js index 31b6a6f..d92ad62 100644 --- a/ba-linkify.js +++ b/ba-linkify.js @@ -101,7 +101,7 @@ default_options = { callback: function( text, href, options ) { - return href ? '' + text + '' : text; }, punct_regexp: /(?:[!?.,:;'"]|(?:&|&)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/, @@ -225,8 +225,9 @@ function buildAttribsString(attribs) { var s=" "; - for (var key in attribs) s+= key + '="' + attribs[key] + "' "; - return (s==" ") ? '' : s; // add leading space if there are attribs + for (var key in attribs) + s+= key + '="' + attribs[key] + '" '; + return s.replace(/ $/,'') } })();