diff --git a/README.md b/README.md index 78cc3897..439089f9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ So, this utility attempts to handle everything. It: - Will properly handle URLs with query parameters or a named anchor (i.e. hash) - Will autolink email addresses. - Will autolink phone numbers. -- Will autolink Twitter handles. +- Will autolink mentions (Twitter, Instagram). - Will autolink hashtags. - Will properly handle HTML input. The utility will not change the `href` attribute inside anchor (<a>) tags (or any other tag/attribute for that @@ -27,6 +27,23 @@ Full API Docs: [http://gregjacobs.github.io/Autolinker.js/docs/](http://gregjaco Live Example: [http://gregjacobs.github.io/Autolinker.js/examples/live-example/](http://gregjacobs.github.io/Autolinker.js/examples/live-example/) +## Breaking Changes from 0.x -> 1.x + +1. `twitter` option removed, replaced with `mention` (which accepts 'twitter' + and 'instagram' values) +2. Matching mentions (previously the `twitter` option) now defaults to + being turned off. Previously, Twitter handle matching was on by + default. +3. `replaceFn` option now called with just one argument: the `Match` + object (previously was called with two arguments: `autolinker` and + `match`) +4. (Used inside the `replaceFn`) `TwitterMatch` replaced with + `MentionMatch`, and `MentionMatch.getType()` now returns `'mention'` + instead of `'twitter'` +5. (Used inside the `replaceFn`) `TwitterMatch.getTwitterHandle()` -> + `MentionMatch.getMention()` + + ## Installation #### Download @@ -109,66 +126,68 @@ providing an Object as the second parameter to [Autolinker.link()](http://gregja hashtags should be truncated to inside the text of a link. If the match is over the number of characters, it will be truncated to this length by replacing the end of the string with a two period ellipsis ('..').

- + Example: a url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters may look like this: 'yahoo.com/some/long/pat..'

- - In the object form, both `length` and `location` may be specified to perform - truncation. Available options for `location` are: 'end' (default), 'middle', - or 'smart'. Example usage: - + + In the object form, both `length` and `location` may be specified to perform + truncation. Available options for `location` are: 'end' (default), 'middle', + or 'smart'. Example usage: + ```javascript truncate: { length: 32, location: 'middle' } ``` - - The 'smart' truncation option is for URLs where the algorithm attempts to - strip out unnecessary parts of the URL (such as the 'www.', then URL scheme, - hash, etc.) before trying to find a good point to insert the ellipsis if it is - still too long. For details, see source code of: + + The 'smart' truncation option is for URLs where the algorithm attempts to + strip out unnecessary parts of the URL (such as the 'www.', then URL scheme, + hash, etc.) before trying to find a good point to insert the ellipsis if it is + still too long. For details, see source code of: [TruncateSmart](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.truncate.TruncateSmart) - [className](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-className) : String
A CSS class name to add to the generated anchor tags. This class will be added - to all links, as well as this class plus "url"/"email"/"phone"/"twitter"/"hashtag" - suffixes for styling url/email/phone/twitter/hashtag links differently. + to all links, as well as this class plus "url"/"email"/"phone"/"hashtag"/"mention"/"twitter"/"instagram" + suffixes for styling url/email/phone/hashtag/mention links differently. For example, if this config is provided as "myLink", then: 1) URL links will have the CSS classes: "myLink myLink-url"
2) Email links will have the CSS classes: "myLink myLink-email"
3) Phone links will have the CSS classes: "myLink myLink-phone"
- 4) Twitter links will have the CSS classes: "myLink myLink-twitter"
+ 4) Twitter mention links will have the CSS classes: "myLink myLink-mention myLink-twitter"
+ 5) Instagram mention links will have the CSS classes: "myLink myLink-mention myLink-instagram"
5) Hashtag links will have the CSS classes: "myLink myLink-hashtag"
- [urls](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-urls) : Boolean/Object
`true` to have URLs auto-linked, `false` to skip auto-linking of URLs. Defaults to `true`.
- + This option also accepts an Object form with 3 properties, to allow for more customization of what exactly gets linked. All default to `true`: - + - schemeMatches (Boolean): `true` to match URLs found prefixed with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`, `false` to prevent these types of matches. - wwwMatches (Boolean): `true` to match urls found prefixed with `'www.'`, - i.e. `www.google.com`. `false` to prevent these types of matches. Note - that if the URL had a prefixed scheme, and `schemeMatches` is true, it + i.e. `www.google.com`. `false` to prevent these types of matches. Note + that if the URL had a prefixed scheme, and `schemeMatches` is true, it will still be linked. - tldMatches: `true` to match URLs with known top level domains (.com, .net, - etc.) that are not prefixed with a scheme or `'www.'`. Ex: `google.com`, + etc.) that are not prefixed with a scheme or `'www.'`. Ex: `google.com`, `asdf.org/?page=1`, etc. `false` to prevent these types of matches.
- + Example usage: `urls: { schemeMatches: true, wwwMatches: true, tldMatches: false }` - + - [email](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-email) : Boolean
`true` to have email addresses auto-linked, `false` to skip auto-linking of email addresses. Defaults to `true`.

- [phone](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-phone) : Boolean
`true` to have phone numbers auto-linked, `false` to skip auto-linking of phone numbers. Defaults to `true`.

-- [twitter](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-twitter) : Boolean
- `true` to have Twitter handles auto-linked, `false` to skip auto-linking of - Twitter handles. Defaults to `true`.

+- [mention](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-mention) : String
+ A string for the service name to have mentions (@username) auto-linked to. Supported + values at this time are 'twitter', and 'instagram'. Pass `false` to skip + auto-linking of mentions. Defaults to `false`.

- [hashtag](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-hashtag) : Boolean/String
A string for the service name to have hashtags auto-linked to. Supported values at this time are 'twitter', 'facebook' and 'instagram'. Pass `false` to skip @@ -225,23 +244,23 @@ autolinker.link( "Go to www.google.com" ); ## Custom Replacement Function A custom replacement function ([replaceFn](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-replaceFn)) -may be provided to replace url/email/phone/Twitter handle/hashtag matches on an +may be provided to replace url/email/phone/mention/hashtag matches on an individual basis, based on the return from this function. #### Full example, for purposes of documenting the API: ```javascript -var input = "..."; // string with URLs, Email Addresses, Twitter Handles, and Hashtags +var input = "..."; // string with URLs, Email Addresses, Mentions (Twitter, Instagram), and Hashtags var linkedText = Autolinker.link( input, { - replaceFn : function( autolinker, match ) { + replaceFn : function( match ) { console.log( "href = ", match.getAnchorHref() ); console.log( "text = ", match.getAnchorText() ); switch( match.getType() ) { case 'url' : console.log( "url: ", match.getUrl() ); - + return true; // let Autolinker perform its normal anchor tag replacement case 'email' : @@ -259,10 +278,11 @@ var linkedText = Autolinker.link( input, { return '' + match.getNumber() + ''; - case 'twitter' : - console.log( "Twitter Handle: ", match.getTwitterHandle() ); + case 'mention' : + console.log( "Mention: ", match.getMention() ); + console.log( "Mention Service Name: ", match.getServiceName() ); - return '' + match.getTwitterHandle() + ''; + return '' + match.getMention() + ''; case 'hashtag' : console.log( "Hashtag: ", match.getHashtag() ); @@ -276,13 +296,13 @@ var linkedText = Autolinker.link( input, { #### Modifying the default generated anchor tag ```javascript -var input = "..."; // string with URLs, Email Addresses, Twitter Handles, and Hashtags +var input = "..."; // string with URLs, Email Addresses, Mentions (Twitter, Instagram), and Hashtags var linkedText = Autolinker.link( input, { - replaceFn : function( autolinker, match ) { + replaceFn : function( match ) { console.log( "href = ", match.getAnchorHref() ); console.log( "text = ", match.getAnchorText() ); - + var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance for an tag tag.setAttr( 'rel', 'nofollow' ); // adds a 'rel' attribute tag.addClass( 'external-link' ); // adds a CSS class @@ -294,10 +314,9 @@ var linkedText = Autolinker.link( input, { ``` -The `replaceFn` is provided two arguments: +The `replaceFn` is provided one argument: -1. The [Autolinker](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker) instance that is performing replacements. -2. An [Autolinker.match.Match](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.match.Match) +1. An [Autolinker.match.Match](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.match.Match) object which details the match that is to be replaced. diff --git a/bower.json b/bower.json index 8b22929c..f9e49d24 100644 --- a/bower.json +++ b/bower.json @@ -5,7 +5,7 @@ "authors": [ "Gregory Jacobs " ], - "description": "Automatically links URLs, email addresses, and Twitter handles in a string of text or HTML.", + "description": "Automatically links URLs, email addresses, mentions (Twitter, Instagram) in a string of text or HTML.", "keywords": [ "auto", "link", diff --git a/dist/Autolinker.js b/dist/Autolinker.js index 36b65531..1a2cc048 100644 --- a/dist/Autolinker.js +++ b/dist/Autolinker.js @@ -1,6 +1,6 @@ /*! * Autolinker.js - * 0.28.1 + * 1.0.0 * * Copyright(c) 2016 Gregory Jacobs * MIT License @@ -53,15 +53,15 @@ * * If the configuration options do not provide enough flexibility, a {@link #replaceFn} * may be provided to fully customize the output of Autolinker. This function is - * called once for each URL/Email/Phone#/Twitter Handle/Hashtag match that is - * encountered. + * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram) + * match that is encountered. * * For example: * - * var input = "..."; // string with URLs, Email Addresses, Phone #s, Twitter Handles, and Hashtags + * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram) * * var linkedText = Autolinker.link( input, { - * replaceFn : function( autolinker, match ) { + * replaceFn : function( match ) { * console.log( "href = ", match.getAnchorHref() ); * console.log( "text = ", match.getAnchorText() ); * @@ -70,7 +70,7 @@ * console.log( "url: ", match.getUrl() ); * * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes + * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes * tag.setAttr( 'rel', 'nofollow' ); * tag.addClass( 'external-link' ); * @@ -96,17 +96,17 @@ * * return '' + phoneNumber + ''; * - * case 'twitter' : - * var twitterHandle = match.getTwitterHandle(); - * console.log( twitterHandle ); - * - * return '' + twitterHandle + ''; - * * case 'hashtag' : * var hashtag = match.getHashtag(); * console.log( hashtag ); * * return '' + hashtag + ''; + * + * case 'mention' : + * var mention = match.getMention(); + * console.log( mention ); + * + * return '' + mention + ''; * } * } * } ); @@ -133,13 +133,19 @@ var Autolinker = function( cfg ) { this.urls = this.normalizeUrlsCfg( cfg.urls ); this.email = typeof cfg.email === 'boolean' ? cfg.email : true; - this.twitter = typeof cfg.twitter === 'boolean' ? cfg.twitter : true; this.phone = typeof cfg.phone === 'boolean' ? cfg.phone : true; this.hashtag = cfg.hashtag || false; + this.mention = cfg.mention || false; this.newWindow = typeof cfg.newWindow === 'boolean' ? cfg.newWindow : true; this.stripPrefix = typeof cfg.stripPrefix === 'boolean' ? cfg.stripPrefix : true; - // Validate the value of the `hashtag` cfg. + // Validate the value of the `mention` cfg + var mention = this.mention; + if( mention !== false && mention !== 'twitter' && mention !== 'instagram' ) { + throw new Error( "invalid `mention` cfg - see docs" ); + } + + // Validate the value of the `hashtag` cfg var hashtag = this.hashtag; if( hashtag !== false && hashtag !== 'twitter' && hashtag !== 'facebook' && hashtag !== 'instagram' ) { throw new Error( "invalid `hashtag` cfg - see docs" ); @@ -148,6 +154,7 @@ var Autolinker = function( cfg ) { this.truncate = this.normalizeTruncateCfg( cfg.truncate ); this.className = cfg.className || ''; this.replaceFn = cfg.replaceFn || null; + this.context = cfg.context || this; this.htmlParser = null; this.matchers = null; @@ -158,7 +165,7 @@ var Autolinker = function( cfg ) { /** * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles, - * and Hashtags found in the given chunk of HTML. Does not link URLs found + * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs found * within HTML tags. * * For instance, if given the text: `You should go to http://www.yahoo.com`, @@ -172,7 +179,7 @@ var Autolinker = function( cfg ) { * @static * @param {String} textOrHtml The HTML or text to find matches within (depending * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #twitter}, - * and {@link #hashtag} options are enabled). + * {@link #hashtag}, and {@link #mention} options are enabled). * @param {Object} [options] Any of the configuration options for the Autolinker * class, specified in an Object (map). See the class description for an * example call. @@ -192,7 +199,7 @@ Autolinker.link = function( textOrHtml, options ) { * * Ex: 0.25.1 */ -Autolinker.version = '0.28.1'; +Autolinker.version = '1.0.0'; Autolinker.prototype = { @@ -228,13 +235,6 @@ Autolinker.prototype = { * should not be. */ - /** - * @cfg {Boolean} [twitter=true] - * - * `true` if Twitter handles ("@example") should be automatically linked, - * `false` if they should not be. - */ - /** * @cfg {Boolean} [phone=true] * @@ -255,6 +255,18 @@ Autolinker.prototype = { * Pass `false` to skip auto-linking of hashtags. */ + /** + * @cfg {String/Boolean} [mention=false] + * + * A string for the service name to have mentions (ex: "@myuser") + * auto-linked to. The currently supported values are: + * + * - 'twitter' + * - 'instagram' + * + * Defaults to `false` to skip auto-linking of mentions. + */ + /** * @cfg {Boolean} [newWindow=true] * @@ -321,15 +333,15 @@ Autolinker.prototype = { * * A CSS class name to add to the generated links. This class will be added * to all links, as well as this class plus match suffixes for styling - * url/email/phone/twitter/hashtag links differently. + * url/email/phone/hashtag/mention links differently. * * For example, if this config is provided as "myLink", then: * * - URL links will have the CSS classes: "myLink myLink-url" * - Email links will have the CSS classes: "myLink myLink-email", and - * - Twitter links will have the CSS classes: "myLink myLink-twitter" * - Phone links will have the CSS classes: "myLink myLink-phone" * - Hashtag links will have the CSS classes: "myLink myLink-hashtag" + * - Mention links will have the CSS classes: "myLink myLink-mention myLink-" where type is "instagram"/"twitter" */ /** @@ -339,17 +351,25 @@ Autolinker.prototype = { * * See the class's description for usage. * - * This function is called with the following parameters: + * The `replaceFn` can be called with a different context object (`this` + * reference) using the {@link #context} cfg. + * + * This function is called with the following parameter: * - * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may - * be used to retrieve child objects from (such as the instance's - * {@link #getTagBuilder tag builder}). * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which * can be used to retrieve information about the match that the `replaceFn` * is currently processing. See {@link Autolinker.match.Match} subclasses * for details. */ + /** + * @cfg {Object} context + * + * The context object (`this` reference) to call the `replaceFn` with. + * + * Defaults to this Autolinker instance. + */ + /** * @property {String} version (readonly) @@ -448,7 +468,7 @@ Autolinker.prototype = { * * @param {String} textOrHtml The HTML or text to find matches within * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, - * {@link #twitter}, and {@link #hashtag} options are enabled). + * {@link #hashtag}, and {@link #mention} options are enabled). * @return {Autolinker.match.Match[]} The array of Matches found in the * given input `textOrHtml`. */ @@ -538,7 +558,7 @@ Autolinker.prototype = { if( !this.hashtag ) remove( matches, function( match ) { return match.getType() === 'hashtag'; } ); if( !this.email ) remove( matches, function( match ) { return match.getType() === 'email'; } ); if( !this.phone ) remove( matches, function( match ) { return match.getType() === 'phone'; } ); - if( !this.twitter ) remove( matches, function( match ) { return match.getType() === 'twitter'; } ); + if( !this.mention ) remove( matches, function( match ) { return match.getType() === 'mention'; } ); if( !this.urls.schemeMatches ) { remove( matches, function( m ) { return m.getType() === 'url' && m.getUrlMatchType() === 'scheme'; } ); } @@ -565,8 +585,8 @@ Autolinker.prototype = { * * @private * @param {String} text The text to find matches within (depending on if the - * {@link #urls}, {@link #email}, {@link #phone}, {@link #twitter}, and - * {@link #hashtag} options are enabled). This must be a non-HTML string. + * {@link #urls}, {@link #email}, {@link #phone}, + * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string. * @param {Number} [offset=0] The offset of the text node within the * original string. This is used when parsing with the {@link #parse} * method to generate correct offsets within the {@link Autolinker.match.Match} @@ -597,8 +617,8 @@ Autolinker.prototype = { /** - * Automatically links URLs, Email addresses, Phone numbers, Twitter - * handles, and Hashtags found in the given chunk of HTML. Does not link + * Automatically links URLs, Email addresses, Phone numbers, Hashtags, + * and Mentions (Twitter, Instagram) found in the given chunk of HTML. Does not link * URLs found within HTML tags. * * For instance, if given the text: `You should go to http://www.yahoo.com`, @@ -611,8 +631,7 @@ Autolinker.prototype = { * in anchor (<a>) tags. * * @param {String} textOrHtml The HTML or text to autolink matches within - * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, - * {@link #twitter}, and {@link #hashtag} options are enabled). + * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled). * @return {String} The HTML, with matches automatically linked. */ link : function( textOrHtml ) { @@ -652,7 +671,7 @@ Autolinker.prototype = { // Handle a custom `replaceFn` being provided var replaceFnResult; if( this.replaceFn ) { - replaceFnResult = this.replaceFn.call( this, this, match ); // Autolinker instance is the context, and the first arg + replaceFnResult = this.replaceFn.call( this.context, match ); // Autolinker instance is the context } if( typeof replaceFnResult === 'string' ) { @@ -707,7 +726,7 @@ Autolinker.prototype = { new matchersNs.Hashtag( { tagBuilder: tagBuilder, serviceName: this.hashtag } ), new matchersNs.Email( { tagBuilder: tagBuilder } ), new matchersNs.Phone( { tagBuilder: tagBuilder } ), - new matchersNs.Twitter( { tagBuilder: tagBuilder } ), + new matchersNs.Mention( { tagBuilder: tagBuilder, serviceName: this.mention } ), new matchersNs.Url( { tagBuilder: tagBuilder, stripPrefix: this.stripPrefix } ) ]; @@ -727,8 +746,8 @@ Autolinker.prototype = { * Autolinker would normally generate, and then allow for modifications before returning it. For example: * * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * replaceFn : function( match ) { + * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance * tag.setAttr( 'rel', 'nofollow' ); * * return tag; @@ -1023,7 +1042,7 @@ Autolinker.Util = { * ## Example use within a {@link Autolinker#replaceFn replaceFn} * * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { + * replaceFn : function( match ) { * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text * tag.setAttr( 'rel', 'nofollow' ); * @@ -1038,7 +1057,7 @@ Autolinker.Util = { * ## Example use with a new tag for the replacement * * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { + * replaceFn : function( match ) { * var tag = new Autolinker.HtmlTag( { * tagName : 'button', * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() }, @@ -1429,14 +1448,14 @@ Autolinker.RegexLib = (function() { * found. * * Normally this class is instantiated, configured, and used internally by an - * {@link Autolinker} instance, but may actually be retrieved in a {@link Autolinker#replaceFn replaceFn} - * to create {@link Autolinker.HtmlTag HtmlTag} instances which may be modified - * before returning from the {@link Autolinker#replaceFn replaceFn}. For - * example: + * {@link Autolinker} instance, but may actually be used indirectly in a + * {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} + * instances which may be modified before returning from the + * {@link Autolinker#replaceFn replaceFn}. For example: * * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * replaceFn : function( match ) { + * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance * tag.setAttr( 'rel', 'nofollow' ); * * return tag; @@ -1469,7 +1488,11 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). */ constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); + cfg = cfg || {}; + + this.newWindow = cfg.newWindow; + this.truncate = cfg.truncate; + this.className = cfg.className; }, @@ -1484,7 +1507,7 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { build : function( match ) { return new Autolinker.HtmlTag( { tagName : 'a', - attrs : this.createAttrs( match.getType(), match.getAnchorHref() ), + attrs : this.createAttrs( match ), innerHtml : this.processAnchorText( match.getAnchorText() ) } ); }, @@ -1495,17 +1518,16 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { * tag being generated. * * @protected - * @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of - * match that an anchor tag is being generated for. - * @param {String} anchorHref The href for the anchor tag. + * @param {Autolinker.match.Match} match The Match instance to generate an + * anchor tag from. * @return {Object} A key/value Object (map) of the anchor tag's attributes. */ - createAttrs : function( matchType, anchorHref ) { + createAttrs : function( match ) { var attrs = { - 'href' : anchorHref // we'll always have the `href` attribute + 'href' : match.getAnchorHref() // we'll always have the `href` attribute }; - var cssClass = this.createCssClass( matchType ); + var cssClass = this.createCssClass( match ); if( cssClass ) { attrs[ 'class' ] = cssClass; } @@ -1522,20 +1544,37 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { * Creates the CSS class that will be used for a given anchor tag, based on * the `matchType` and the {@link #className} config. * + * Example returns: + * + * - "" // no {@link #className} + * - "myLink myLink-url" // url match + * - "myLink myLink-email" // email match + * - "myLink myLink-phone" // phone match + * - "myLink myLink-hashtag" // hashtag match + * - "myLink myLink-mention myLink-twitter" // mention match with Twitter service + * * @private - * @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of - * match that an anchor tag is being generated for. + * @param {Autolinker.match.Match} match The Match instance to generate an + * anchor tag from. * @return {String} The CSS class string for the link. Example return: * "myLink myLink-url". If no {@link #className} was configured, returns * an empty string. */ - createCssClass : function( matchType ) { + createCssClass : function( match ) { var className = this.className; - if( !className ) + if( !className ) { return ""; - else - return className + " " + className + "-" + matchType; // ex: "myLink myLink-url", "myLink myLink-email", "myLink myLink-phone", "myLink myLink-twitter", or "myLink myLink-hashtag" + + } else { + var returnClasses = [ className ], + cssClassSuffixes = match.getCssClassSuffixes(); + + for( var i = 0, len = cssClassSuffixes.length; i < len; i++ ) { + returnClasses.push( className + '-' + cssClassSuffixes[ i ] ); + } + return returnClasses.join( ' ' ); + } }, @@ -1594,7 +1633,7 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { * An HTML parser implementation which simply walks an HTML string and returns an array of * {@link Autolinker.htmlParser.HtmlNode HtmlNodes} that represent the basic HTML structure of the input string. * - * Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, effectively ignoring / "walking + * Autolinker uses this to only link URLs/emails/mentions within text nodes, effectively ignoring / "walking * around" HTML tags. */ Autolinker.htmlParser.HtmlParser = Autolinker.Util.extend( Object, { @@ -1843,6 +1882,7 @@ Autolinker.htmlParser.HtmlParser = Autolinker.Util.extend( Object, { } } ); + /*global Autolinker */ /** * @abstract @@ -2091,10 +2131,10 @@ Autolinker.htmlParser.TextNode = Autolinker.Util.extend( Autolinker.htmlParser.H * * For example: * - * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + * var input = "..."; // string with URLs, Email Addresses, and Mentions (Twitter, Instagram) * * var linkedText = Autolinker.link( input, { - * replaceFn : function( autolinker, match ) { + * replaceFn : function( match ) { * console.log( "href = ", match.getAnchorHref() ); * console.log( "text = ", match.getAnchorText() ); * @@ -2105,8 +2145,8 @@ Autolinker.htmlParser.TextNode = Autolinker.Util.extend( Autolinker.htmlParser.H * case 'email' : * console.log( "email: ", match.getEmail() ); * - * case 'twitter' : - * console.log( "twitter: ", match.getTwitterHandle() ); + * case 'mention' : + * console.log( "mention: ", match.getMention() ); * } * } * } ); @@ -2216,6 +2256,32 @@ Autolinker.match.Match = Autolinker.Util.extend( Object, { getAnchorText : Autolinker.Util.abstractMethod, + /** + * Returns the CSS class suffix(es) for this match. + * + * A CSS class suffix is appended to the {@link Autolinker#className} in + * the {@link AnchorTagBuilder} when a match is translated into an anchor + * tag. + * + * For example, if {@link Autolinker#className} was configured as 'myLink', + * and this method returns `[ 'url' ]`, the final class name of the element + * will become: 'myLink myLink-url'. + * + * The match may provide multiple CSS class suffixes to be appended to the + * {@link Autolinker#className} in order to facilitate better styling + * options for different match criteria. See {@link Autolinker.match.Mention} + * for an example. + * + * By default, this method returns a single array with the match's + * {@link #getType type} name, but may be overridden by subclasses. + * + * @return {String[]} + */ + getCssClassSuffixes : function() { + return [ this.getType() ]; + }, + + /** * Builds and returns an {@link Autolinker.HtmlTag} instance based on the * Match. @@ -2236,6 +2302,7 @@ Autolinker.match.Match = Autolinker.Util.extend( Object, { } } ); + /*global Autolinker */ /** * @class Autolinker.match.Email @@ -2511,19 +2578,26 @@ Autolinker.match.Phone = Autolinker.Util.extend( Autolinker.match.Match, { /*global Autolinker */ /** - * @class Autolinker.match.Twitter + * @class Autolinker.match.Mention * @extends Autolinker.match.Match * - * Represents a Twitter match found in an input string which should be Autolinked. + * Represents a Mention match found in an input string which should be Autolinked. * * See this class's superclass ({@link Autolinker.match.Match}) for more details. */ -Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { +Autolinker.match.Mention = Autolinker.Util.extend( Autolinker.match.Match, { /** - * @cfg {String} twitterHandle (required) + * @cfg {String} serviceName * - * The Twitter handle that was matched, without the '@' character. + * The service to point mention matches to. See {@link Autolinker#mention} + * for available values. + */ + + /** + * @cfg {String} mention (required) + * + * The Mention that was matched, without the '@' character. */ @@ -2532,12 +2606,14 @@ Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { * @param {Object} cfg The configuration properties for the Match * instance, specified in an Object (map). */ - constructor : function( cfg) { + constructor : function( cfg ) { Autolinker.match.Match.prototype.constructor.call( this, cfg ); - if( !cfg.twitterHandle ) throw new Error( '`twitterHandle` cfg required' ); + if( !cfg.serviceName ) throw new Error( '`serviceName` cfg required' ); + if( !cfg.mention ) throw new Error( '`mention` cfg required' ); - this.twitterHandle = cfg.twitterHandle; + this.mention = cfg.mention; + this.serviceName = cfg.serviceName; }, @@ -2547,17 +2623,28 @@ Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { * @return {String} */ getType : function() { - return 'twitter'; + return 'mention'; + }, + + + /** + * Returns the mention, without the '@' character. + * + * @return {String} + */ + getMention : function() { + return this.mention; }, /** - * Returns the twitter handle, without the '@' character. + * Returns the configured {@link #serviceName} to point the mention to. + * Ex: 'instagram', 'twitter'. * * @return {String} */ - getTwitterHandle : function() { - return this.twitterHandle; + getServiceName : function() { + return this.serviceName; }, @@ -2567,7 +2654,15 @@ Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { * @return {String} */ getAnchorHref : function() { - return 'https://twitter.com/' + this.twitterHandle; + switch( this.serviceName ) { + case 'twitter' : + return 'https://twitter.com/' + this.mention; + case 'instagram' : + return 'https://instagram.com/' + this.mention; + + default : // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case. + throw new Error( 'Unknown service name to point mention to: ', this.serviceName ); + } }, @@ -2577,10 +2672,29 @@ Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { * @return {String} */ getAnchorText : function() { - return '@' + this.twitterHandle; + return '@' + this.mention; + }, + + + /** + * Returns the CSS class suffixes that should be used on a tag built with + * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for + * details. + * + * @return {String[]} + */ + getCssClassSuffixes : function() { + var cssClassSuffixes = Autolinker.match.Match.prototype.getCssClassSuffixes.call( this ), + serviceName = this.getServiceName(); + + if( serviceName ) { + cssClassSuffixes.push( serviceName ); + } + return cssClassSuffixes; } } ); + /*global Autolinker */ /** * @class Autolinker.match.Url @@ -3046,22 +3160,25 @@ Autolinker.matcher.Phone = Autolinker.Util.extend( Autolinker.matcher.Matcher, { } ); /*global Autolinker */ /** - * @class Autolinker.matcher.Twitter + * @class Autolinker.matcher.Mention * @extends Autolinker.matcher.Matcher * * Matcher to find/replace username matches in an input string. */ -Autolinker.matcher.Twitter = Autolinker.Util.extend( Autolinker.matcher.Matcher, { +Autolinker.matcher.Mention = Autolinker.Util.extend( Autolinker.matcher.Matcher, { /** - * The regular expression to match username handles. Example match: + * Hash of regular expression to match username handles. Example match: * * @asdf * * @private - * @property {RegExp} matcherRegex + * @property {Object} matcherRegexes */ - matcherRegex : new RegExp( '@[_' + Autolinker.RegexLib.alphaNumericCharsStr + ']{1,20}', 'g' ), + matcherRegexes : { + "twitter": new RegExp( '@[_' + Autolinker.RegexLib.alphaNumericCharsStr + ']{1,20}', 'g' ), + "instagram": new RegExp( '@[_.' + Autolinker.RegexLib.alphaNumericCharsStr + ']{1,50}', 'g' ) + }, /** * The regular expression to use to check the character before a username match to @@ -3075,16 +3192,33 @@ Autolinker.matcher.Twitter = Autolinker.Util.extend( Autolinker.matcher.Matcher, nonWordCharRegex : new RegExp( '[^' + Autolinker.RegexLib.alphaNumericCharsStr + ']' ), + /** + * @constructor + * @param {Object} cfg The configuration properties for the Match instance, + * specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.matcher.Matcher.prototype.constructor.call( this, cfg ); + + this.serviceName = cfg.serviceName; + }, + + /** * @inheritdoc */ parseMatches : function( text ) { - var matcherRegex = this.matcherRegex, + var matcherRegex = this.matcherRegexes[this.serviceName], nonWordCharRegex = this.nonWordCharRegex, + serviceName = this.serviceName, tagBuilder = this.tagBuilder, matches = [], match; + if (!matcherRegex) { + return matches; + } + while( ( match = matcherRegex.exec( text ) ) !== null ) { var offset = match.index, prevChar = text.charAt( offset - 1 ); @@ -3093,14 +3227,15 @@ Autolinker.matcher.Twitter = Autolinker.Util.extend( Autolinker.matcher.Matcher, // and there is a whitespace char in front of it (meaning it is not an email // address), then it is a username match. if( offset === 0 || nonWordCharRegex.test( prevChar ) ) { - var matchedText = match[ 0 ], - twitterHandle = match[ 0 ].slice( 1 ); // strip off the '@' character at the beginning + var matchedText = match[ 0 ].replace(/\.+$/g, ''), // strip off trailing . + mention = matchedText.slice( 1 ); // strip off the '@' character at the beginning - matches.push( new Autolinker.match.Twitter( { + matches.push( new Autolinker.match.Mention( { tagBuilder : tagBuilder, matchedText : matchedText, offset : offset, - twitterHandle : twitterHandle + serviceName : serviceName, + mention : mention } ) ); } } @@ -3109,6 +3244,7 @@ Autolinker.matcher.Twitter = Autolinker.Util.extend( Autolinker.matcher.Matcher, } } ); + /*global Autolinker */ /** * @class Autolinker.matcher.Url diff --git a/dist/Autolinker.min.js b/dist/Autolinker.min.js index fb80ada4..7f254c7d 100644 --- a/dist/Autolinker.min.js +++ b/dist/Autolinker.min.js @@ -1,10 +1,10 @@ /*! * Autolinker.js - * 0.28.1 + * 1.0.0 * * Copyright(c) 2016 Gregory Jacobs * MIT License * * https://github.com/gregjacobs/Autolinker.js */ -!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.Autolinker=e()}(this,function(){var t=function(e){e=e||{},this.version=t.version,this.urls=this.normalizeUrlsCfg(e.urls),this.email="boolean"!=typeof e.email||e.email,this.twitter="boolean"!=typeof e.twitter||e.twitter,this.phone="boolean"!=typeof e.phone||e.phone,this.hashtag=e.hashtag||!1,this.newWindow="boolean"!=typeof e.newWindow||e.newWindow,this.stripPrefix="boolean"!=typeof e.stripPrefix||e.stripPrefix;var r=this.hashtag;if(r!==!1&&"twitter"!==r&&"facebook"!==r&&"instagram"!==r)throw new Error("invalid `hashtag` cfg - see docs");this.truncate=this.normalizeTruncateCfg(e.truncate),this.className=e.className||"",this.replaceFn=e.replaceFn||null,this.htmlParser=null,this.matchers=null,this.tagBuilder=null};return t.link=function(e,r){var a=new t(r);return a.link(e)},t.version="0.28.1",t.prototype={constructor:t,normalizeUrlsCfg:function(t){return null==t&&(t=!0),"boolean"==typeof t?{schemeMatches:t,wwwMatches:t,tldMatches:t}:{schemeMatches:"boolean"!=typeof t.schemeMatches||t.schemeMatches,wwwMatches:"boolean"!=typeof t.wwwMatches||t.wwwMatches,tldMatches:"boolean"!=typeof t.tldMatches||t.tldMatches}},normalizeTruncateCfg:function(e){return"number"==typeof e?{length:e,location:"end"}:t.Util.defaults(e||{},{length:Number.POSITIVE_INFINITY,location:"end"})},parse:function(t){for(var e=this.getHtmlParser(),r=e.parse(t),a=0,n=[],i=0,s=r.length;ie&&(r=null==r?"..":r,t=t.substring(0,e-r.length)+r),t},indexOf:function(t,e){if(Array.prototype.indexOf)return t.indexOf(e);for(var r=0,a=t.length;r=0;r--)e(t[r])===!0&&t.splice(r,1)},splitAndCapture:function(t,e){for(var r,a=[],n=0;r=e.exec(t);)a.push(t.substring(n,r.index)),a.push(r[0]),n=r.index+r[0].length;return a.push(t.substring(n)),a},trim:function(t){return t.replace(this.trimRegex,"")}},t.HtmlTag=t.Util.extend(Object,{whitespaceRegex:/\s+/,constructor:function(e){t.Util.assign(this,e),this.innerHtml=this.innerHtml||this.innerHTML},setTagName:function(t){return this.tagName=t,this},getTagName:function(){return this.tagName||""},setAttr:function(t,e){var r=this.getAttrs();return r[t]=e,this},getAttr:function(t){return this.getAttrs()[t]},setAttrs:function(e){var r=this.getAttrs();return t.Util.assign(r,e),this},getAttrs:function(){return this.attrs||(this.attrs={})},setClass:function(t){return this.setAttr("class",t)},addClass:function(e){for(var r,a=this.getClass(),n=this.whitespaceRegex,i=t.Util.indexOf,s=a?a.split(n):[],o=e.split(n);r=o.shift();)i(s,r)===-1&&s.push(r);return this.getAttrs()["class"]=s.join(" "),this},removeClass:function(e){for(var r,a=this.getClass(),n=this.whitespaceRegex,i=t.Util.indexOf,s=a?a.split(n):[],o=e.split(n);s.length&&(r=o.shift());){var c=i(s,r);c!==-1&&s.splice(c,1)}return this.getAttrs()["class"]=s.join(" "),this},getClass:function(){return this.getAttrs()["class"]||""},hasClass:function(t){return(" "+this.getClass()+" ").indexOf(" "+t+" ")!==-1},setInnerHtml:function(t){return this.innerHtml=t,this},getInnerHtml:function(){return this.innerHtml||""},toAnchorString:function(){var t=this.getTagName(),e=this.buildAttrsStr();return e=e?" "+e:"",["<",t,e,">",this.getInnerHtml(),""].join("")},buildAttrsStr:function(){if(!this.attrs)return"";var t=this.getAttrs(),e=[];for(var r in t)t.hasOwnProperty(r)&&e.push(r+'="'+t[r]+'"');return e.join(" ")}}),t.RegexLib=function(){var t="A-Za-z\\xAA\\xB5\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢴऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛱ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",e="0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯෦-෯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧙᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꧰-꧹꩐-꩙꯰-꯹0-9",r=t+e,a=new RegExp("["+r+".\\-]*["+r+"\\-]"),n=/(?:travelersinsurance|sandvikcoromant|kerryproperties|cancerresearch|weatherchannel|kerrylogistics|spreadbetting|international|wolterskluwer|lifeinsurance|construction|pamperedchef|scholarships|versicherung|bridgestone|creditunion|kerryhotels|investments|productions|blackfriday|enterprises|lamborghini|photography|motorcycles|williamhill|playstation|contractors|barclaycard|accountants|redumbrella|engineering|management|telefonica|protection|consulting|tatamotors|creditcard|vlaanderen|schaeffler|associates|properties|foundation|republican|bnpparibas|boehringer|eurovision|extraspace|industries|immobilien|university|technology|volkswagen|healthcare|restaurant|cuisinella|vistaprint|apartments|accountant|travelers|homedepot|institute|vacations|furniture|fresenius|insurance|christmas|bloomberg|solutions|barcelona|firestone|financial|kuokgroup|fairwinds|community|passagens|goldpoint|equipment|lifestyle|yodobashi|aquarelle|marketing|analytics|education|amsterdam|statefarm|melbourne|allfinanz|directory|microsoft|stockholm|montblanc|accenture|lancaster|landrover|everbank|istanbul|graphics|grainger|ipiranga|softbank|attorney|pharmacy|saarland|catering|airforce|yokohama|mortgage|frontier|mutuelle|stcgroup|memorial|pictures|football|symantec|cipriani|ventures|telecity|cityeats|verisign|flsmidth|boutique|cleaning|firmdale|clinique|clothing|redstone|infiniti|deloitte|feedback|services|broadway|plumbing|commbank|training|barclays|exchange|computer|brussels|software|delivery|barefoot|builders|business|bargains|engineer|holdings|download|security|helsinki|lighting|movistar|discount|hdfcbank|supplies|marriott|property|diamonds|capetown|partners|democrat|jpmorgan|bradesco|budapest|rexroth|zuerich|shriram|academy|science|support|youtube|singles|surgery|alibaba|statoil|dentist|schwarz|android|cruises|cricket|digital|markets|starhub|systems|courses|coupons|netbank|country|domains|corsica|network|neustar|realtor|lincoln|limited|schmidt|yamaxun|cooking|contact|auction|spiegel|liaison|leclerc|latrobe|lasalle|abogado|compare|lanxess|exposed|express|company|cologne|college|avianca|lacaixa|fashion|recipes|ferrero|komatsu|storage|wanggou|clubmed|sandvik|fishing|fitness|bauhaus|kitchen|flights|florist|flowers|watches|weather|temasek|samsung|bentley|forsale|channel|theater|frogans|theatre|okinawa|website|tickets|jewelry|gallery|tiffany|iselect|shiksha|brother|organic|wedding|genting|toshiba|origins|philips|hyundai|hotmail|hoteles|hosting|rentals|windows|cartier|bugatti|holiday|careers|whoswho|hitachi|panerai|caravan|reviews|guitars|capital|trading|hamburg|hangout|finance|stream|family|abbott|health|review|travel|report|hermes|hiphop|gratis|career|toyota|hockey|dating|repair|google|social|soccer|reisen|global|otsuka|giving|unicom|casino|photos|center|broker|rocher|orange|bostik|garden|insure|ryukyu|bharti|safety|physio|sakura|oracle|online|jaguar|gallup|piaget|tienda|futbol|pictet|joburg|webcam|berlin|office|juegos|kaufen|chanel|chrome|xihuan|church|tennis|circle|kinder|flickr|bayern|claims|clinic|viajes|nowruz|xperia|norton|yachts|studio|coffee|camera|sanofi|nissan|author|expert|events|comsec|lawyer|tattoo|viking|estate|villas|condos|realty|yandex|energy|emerck|virgin|vision|durban|living|school|coupon|london|taobao|natura|taipei|nagoya|luxury|walter|aramco|sydney|madrid|credit|maison|makeup|schule|market|anquan|direct|design|swatch|suzuki|alsace|vuelos|dental|alipay|voyage|shouji|voting|airtel|mutual|degree|supply|agency|museum|mobily|dealer|monash|select|mormon|active|moscow|racing|datsun|quebec|nissay|rodeo|email|gifts|works|photo|chloe|edeka|cheap|earth|vista|tushu|koeln|glass|shoes|globo|tunes|gmail|nokia|space|kyoto|black|ricoh|seven|lamer|sener|epson|cisco|praxi|trust|citic|crown|shell|lease|green|legal|lexus|ninja|tatar|gripe|nikon|group|video|wales|autos|gucci|party|nexus|guide|linde|adult|parts|amica|lixil|boats|azure|loans|locus|cymru|lotte|lotto|stada|click|poker|quest|dabur|lupin|nadex|paris|faith|dance|canon|place|gives|trade|skype|rocks|mango|cloud|boots|smile|final|swiss|homes|honda|media|horse|cards|deals|watch|bosch|house|pizza|miami|osaka|tours|total|xerox|coach|sucks|style|delta|toray|iinet|tools|money|codes|beats|tokyo|salon|archi|movie|baidu|study|actor|yahoo|store|apple|world|forex|today|bible|tmall|tirol|irish|tires|forum|reise|vegas|vodka|sharp|omega|weber|jetzt|audio|promo|build|bingo|chase|gallo|drive|dubai|rehab|press|solar|sale|beer|bbva|bank|band|auto|sapo|sarl|saxo|audi|asia|arte|arpa|army|yoga|ally|zara|scor|scot|sexy|seat|zero|seek|aero|adac|zone|aarp|maif|meet|meme|menu|surf|mini|mobi|mtpc|porn|desi|star|ltda|name|talk|navy|love|loan|live|link|news|limo|like|spot|life|nico|lidl|lgbt|land|taxi|team|tech|kred|kpmg|sony|song|kiwi|kddi|jprs|jobs|sohu|java|itau|tips|info|immo|icbc|hsbc|town|host|page|toys|here|help|pars|haus|guru|guge|tube|goog|golf|gold|sncf|gmbh|gift|ggee|gent|gbiz|game|vana|pics|fund|ford|ping|pink|fish|film|fast|farm|play|fans|fail|plus|skin|pohl|fage|moda|post|erni|dvag|prod|doha|prof|docs|viva|diet|luxe|site|dell|sina|dclk|show|qpon|date|vote|cyou|voto|read|coop|cool|wang|club|city|chat|cern|cash|reit|rent|casa|cars|care|camp|rest|call|cafe|weir|wien|rich|wiki|buzz|wine|book|bond|room|work|rsvp|shia|ruhr|blue|bing|shaw|bike|safe|xbox|best|pwc|mtn|lds|aig|boo|fyi|nra|nrw|ntt|car|gal|obi|zip|aeg|vin|how|one|ong|onl|dad|ooo|bet|esq|org|htc|bar|uol|ibm|ovh|gdn|ice|icu|uno|gea|ifm|bot|top|wtf|lol|day|pet|eus|wtc|ubs|tvs|aco|ing|ltd|ink|tab|abb|afl|cat|int|pid|pin|bid|cba|gle|com|cbn|ads|man|wed|ceb|gmo|sky|ist|gmx|tui|mba|fan|ski|iwc|app|pro|med|ceo|jcb|jcp|goo|dev|men|aaa|meo|pub|jlc|bom|jll|gop|jmp|mil|got|gov|win|jot|mma|joy|trv|red|cfa|cfd|bio|moe|moi|mom|ren|biz|aws|xin|bbc|dnp|buy|kfh|mov|thd|xyz|fit|kia|rio|rip|kim|dog|vet|nyc|bcg|mtr|bcn|bms|bmw|run|bzh|rwe|tel|stc|axa|kpn|fly|krd|cab|bnl|foo|crs|eat|tci|sap|srl|nec|sas|net|cal|sbs|sfr|sca|scb|csc|edu|new|xxx|hiv|fox|wme|ngo|nhk|vip|sex|frl|lat|yun|law|you|tax|soy|sew|om|ac|hu|se|sc|sg|sh|sb|sa|rw|ru|rs|ro|re|qa|py|si|pw|pt|ps|sj|sk|pr|pn|pm|pl|sl|sm|pk|sn|ph|so|pg|pf|pe|pa|zw|nz|nu|nr|np|no|nl|ni|ng|nf|sr|ne|st|nc|na|mz|my|mx|mw|mv|mu|mt|ms|mr|mq|mp|mo|su|mn|mm|ml|mk|mh|mg|me|sv|md|mc|sx|sy|ma|ly|lv|sz|lu|lt|ls|lr|lk|li|lc|lb|la|tc|kz|td|ky|kw|kr|kp|kn|km|ki|kh|tf|tg|th|kg|ke|jp|jo|jm|je|it|is|ir|tj|tk|tl|tm|iq|tn|to|io|in|im|il|ie|ad|sd|ht|hr|hn|hm|tr|hk|gy|gw|gu|gt|gs|gr|gq|tt|gp|gn|gm|gl|tv|gi|tw|tz|ua|gh|ug|uk|gg|gf|ge|gd|us|uy|uz|va|gb|ga|vc|ve|fr|fo|fm|fk|fj|vg|vi|fi|eu|et|es|er|eg|ee|ec|dz|do|dm|dk|vn|dj|de|cz|cy|cx|cw|vu|cv|cu|cr|co|cn|cm|cl|ck|ci|ch|cg|cf|cd|cc|ca|wf|bz|by|bw|bv|bt|bs|br|bo|bn|bm|bj|bi|ws|bh|bg|bf|be|bd|bb|ba|az|ax|aw|au|at|as|ye|ar|aq|ao|am|al|yt|ai|za|ag|af|ae|zm|id)\b/;return{alphaNumericCharsStr:r,domainNameRegex:a,tldRegex:n}}(),t.AnchorTagBuilder=t.Util.extend(Object,{constructor:function(e){t.Util.assign(this,e)},build:function(e){return new t.HtmlTag({tagName:"a",attrs:this.createAttrs(e.getType(),e.getAnchorHref()),innerHtml:this.processAnchorText(e.getAnchorText())})},createAttrs:function(t,e){var r={href:e},a=this.createCssClass(t);return a&&(r["class"]=a),this.newWindow&&(r.target="_blank",r.rel="noopener noreferrer"),r},createCssClass:function(t){var e=this.className;return e?e+" "+e+"-"+t:""},processAnchorText:function(t){return t=this.doTruncate(t)},doTruncate:function(e){var r=this.truncate;if(!r||!r.length)return e;var a=r.length,n=r.location;return"smart"===n?t.truncate.TruncateSmart(e,a,".."):"middle"===n?t.truncate.TruncateMiddle(e,a,".."):t.truncate.TruncateEnd(e,a,"..")}}),t.htmlParser.HtmlParser=t.Util.extend(Object,{htmlRegex:function(){var t=/!--([\s\S]+?)--/,e=/[0-9a-zA-Z][0-9a-zA-Z:]*/,r=/[^\s"'>\/=\x00-\x1F\x7F]+/,a=/(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/,n=r.source+"(?:\\s*=\\s*"+a.source+")?";return new RegExp(["(?:","<(!DOCTYPE)","(?:","\\s+","(?:",n,"|",a.source+")",")*",">",")","|","(?:","<(/)?","(?:",t.source,"|","(?:","("+e.source+")","(?:","(?:\\s+|\\b)",n,")*","\\s*/?",")",")",">",")"].join(""),"gi")}(),htmlCharacterEntitiesRegex:/( | |<|<|>|>|"|"|')/gi,parse:function(t){for(var e,r,a=this.htmlRegex,n=0,i=[];null!==(e=a.exec(t));){var s=e[0],o=e[3],c=e[1]||e[4],h=!!e[2],l=e.index,u=t.substring(n,l);u&&(r=this.parseTextAndEntityNodes(n,u),i.push.apply(i,r)),o?i.push(this.createCommentNode(l,s,o)):i.push(this.createElementNode(l,s,c,h)),n=l+s.length}if(n0&&"@"===f||g>0&&m&&this.wordCharRegExp.test(f))){if(this.matchHasUnbalancedClosingParen(o))o=o.substr(0,o.length-1);else{var p=this.matchHasInvalidCharAfterTld(o,c);p>-1&&(o=o.substr(0,p))}var d=c?"scheme":h?"www":"tld",b=!!c;s.push(new t.match.Url({tagBuilder:i,matchedText:o,offset:g,urlMatchType:d,url:o,protocolUrlMatch:b,protocolRelativeMatch:!!m,stripPrefix:n}))}}return s},matchHasUnbalancedClosingParen:function(t){var e=t.charAt(t.length-1);if(")"===e){var r=t.match(this.openParensRe),a=t.match(this.closeParensRe),n=r&&r.length||0,i=a&&a.length||0;if(n0&&(n=t.substr(-1*Math.floor(a/2))),(t.substr(0,Math.ceil(a/2))+r+n).substr(0,e)},t.truncate.TruncateSmart=function(t,e,r){var a=function(t){var e={},r=t,a=r.match(/^([a-z]+):\/\//i);return a&&(e.scheme=a[1],r=r.substr(a[0].length)),a=r.match(/^(.*?)(?=(\?|#|\/|$))/i),a&&(e.host=a[1],r=r.substr(a[0].length)),a=r.match(/^\/(.*?)(?=(\?|#|$))/i),a&&(e.path=a[1],r=r.substr(a[0].length)),a=r.match(/^\?(.*?)(?=(#|$))/i),a&&(e.query=a[1],r=r.substr(a[0].length)),a=r.match(/^#(.*?)$/i),a&&(e.fragment=a[1]),e},n=function(t){var e="";return t.scheme&&t.host&&(e+=t.scheme+"://"),t.host&&(e+=t.host),t.path&&(e+="/"+t.path),t.query&&(e+="?"+t.query),t.fragment&&(e+="#"+t.fragment),e},i=function(t,e){var a=e/2,n=Math.ceil(a),i=-1*Math.floor(a),s="";return i<0&&(s=t.substr(i)),t.substr(0,n)+r+s};if(t.length<=e)return t;var s=e-r.length,o=a(t);if(o.query){var c=o.query.match(/^(.*?)(?=(\?|\#))(.*?)$/i);c&&(o.query=o.query.substr(0,c[1].length),t=n(o))}if(t.length<=e)return t;if(o.host&&(o.host=o.host.replace(/^www\./,""),t=n(o)),t.length<=e)return t;var h="";if(o.host&&(h+=o.host),h.length>=s)return o.host.length==e?(o.host.substr(0,e-r.length)+r).substr(0,e):i(h,s).substr(0,e);var l="";if(o.path&&(l+="/"+o.path),o.query&&(l+="?"+o.query),l){if((h+l).length>=s){if((h+l).length==e)return(h+l).substr(0,e);var u=s-h.length;return(h+i(l,u)).substr(0,e)}h+=l}if(o.fragment){var g="#"+o.fragment;if((h+g).length>=s){if((h+g).length==e)return(h+g).substr(0,e);var m=s-h.length;return(h+i(g,m)).substr(0,e)}h+=g}if(o.scheme&&o.host){var f=o.scheme+"://";if((h+f).length0&&(p=h.substr(-1*Math.floor(s/2))),(h.substr(0,Math.ceil(s/2))+r+p).substr(0,e)},t}); \ No newline at end of file +!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.Autolinker=e()}(this,function(){var t=function(e){e=e||{},this.version=t.version,this.urls=this.normalizeUrlsCfg(e.urls),this.email="boolean"!=typeof e.email||e.email,this.phone="boolean"!=typeof e.phone||e.phone,this.hashtag=e.hashtag||!1,this.mention=e.mention||!1,this.newWindow="boolean"!=typeof e.newWindow||e.newWindow,this.stripPrefix="boolean"!=typeof e.stripPrefix||e.stripPrefix;var r=this.mention;if(r!==!1&&"twitter"!==r&&"instagram"!==r)throw new Error("invalid `mention` cfg - see docs");var a=this.hashtag;if(a!==!1&&"twitter"!==a&&"facebook"!==a&&"instagram"!==a)throw new Error("invalid `hashtag` cfg - see docs");this.truncate=this.normalizeTruncateCfg(e.truncate),this.className=e.className||"",this.replaceFn=e.replaceFn||null,this.context=e.context||this,this.htmlParser=null,this.matchers=null,this.tagBuilder=null};return t.link=function(e,r){var a=new t(r);return a.link(e)},t.version="1.0.0",t.prototype={constructor:t,normalizeUrlsCfg:function(t){return null==t&&(t=!0),"boolean"==typeof t?{schemeMatches:t,wwwMatches:t,tldMatches:t}:{schemeMatches:"boolean"!=typeof t.schemeMatches||t.schemeMatches,wwwMatches:"boolean"!=typeof t.wwwMatches||t.wwwMatches,tldMatches:"boolean"!=typeof t.tldMatches||t.tldMatches}},normalizeTruncateCfg:function(e){return"number"==typeof e?{length:e,location:"end"}:t.Util.defaults(e||{},{length:Number.POSITIVE_INFINITY,location:"end"})},parse:function(t){for(var e=this.getHtmlParser(),r=e.parse(t),a=0,n=[],i=0,s=r.length;ie&&(r=null==r?"..":r,t=t.substring(0,e-r.length)+r),t},indexOf:function(t,e){if(Array.prototype.indexOf)return t.indexOf(e);for(var r=0,a=t.length;r=0;r--)e(t[r])===!0&&t.splice(r,1)},splitAndCapture:function(t,e){for(var r,a=[],n=0;r=e.exec(t);)a.push(t.substring(n,r.index)),a.push(r[0]),n=r.index+r[0].length;return a.push(t.substring(n)),a},trim:function(t){return t.replace(this.trimRegex,"")}},t.HtmlTag=t.Util.extend(Object,{whitespaceRegex:/\s+/,constructor:function(e){t.Util.assign(this,e),this.innerHtml=this.innerHtml||this.innerHTML},setTagName:function(t){return this.tagName=t,this},getTagName:function(){return this.tagName||""},setAttr:function(t,e){var r=this.getAttrs();return r[t]=e,this},getAttr:function(t){return this.getAttrs()[t]},setAttrs:function(e){var r=this.getAttrs();return t.Util.assign(r,e),this},getAttrs:function(){return this.attrs||(this.attrs={})},setClass:function(t){return this.setAttr("class",t)},addClass:function(e){for(var r,a=this.getClass(),n=this.whitespaceRegex,i=t.Util.indexOf,s=a?a.split(n):[],o=e.split(n);r=o.shift();)i(s,r)===-1&&s.push(r);return this.getAttrs()["class"]=s.join(" "),this},removeClass:function(e){for(var r,a=this.getClass(),n=this.whitespaceRegex,i=t.Util.indexOf,s=a?a.split(n):[],o=e.split(n);s.length&&(r=o.shift());){var c=i(s,r);c!==-1&&s.splice(c,1)}return this.getAttrs()["class"]=s.join(" "),this},getClass:function(){return this.getAttrs()["class"]||""},hasClass:function(t){return(" "+this.getClass()+" ").indexOf(" "+t+" ")!==-1},setInnerHtml:function(t){return this.innerHtml=t,this},getInnerHtml:function(){return this.innerHtml||""},toAnchorString:function(){var t=this.getTagName(),e=this.buildAttrsStr();return e=e?" "+e:"",["<",t,e,">",this.getInnerHtml(),""].join("")},buildAttrsStr:function(){if(!this.attrs)return"";var t=this.getAttrs(),e=[];for(var r in t)t.hasOwnProperty(r)&&e.push(r+'="'+t[r]+'"');return e.join(" ")}}),t.RegexLib=function(){var t="A-Za-z\\xAA\\xB5\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢴऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛱ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",e="0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯෦-෯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧙᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꧰-꧹꩐-꩙꯰-꯹0-9",r=t+e,a=new RegExp("["+r+".\\-]*["+r+"\\-]"),n=/(?:travelersinsurance|sandvikcoromant|kerryproperties|cancerresearch|weatherchannel|kerrylogistics|spreadbetting|international|wolterskluwer|lifeinsurance|construction|pamperedchef|scholarships|versicherung|bridgestone|creditunion|kerryhotels|investments|productions|blackfriday|enterprises|lamborghini|photography|motorcycles|williamhill|playstation|contractors|barclaycard|accountants|redumbrella|engineering|management|telefonica|protection|consulting|tatamotors|creditcard|vlaanderen|schaeffler|associates|properties|foundation|republican|bnpparibas|boehringer|eurovision|extraspace|industries|immobilien|university|technology|volkswagen|healthcare|restaurant|cuisinella|vistaprint|apartments|accountant|travelers|homedepot|institute|vacations|furniture|fresenius|insurance|christmas|bloomberg|solutions|barcelona|firestone|financial|kuokgroup|fairwinds|community|passagens|goldpoint|equipment|lifestyle|yodobashi|aquarelle|marketing|analytics|education|amsterdam|statefarm|melbourne|allfinanz|directory|microsoft|stockholm|montblanc|accenture|lancaster|landrover|everbank|istanbul|graphics|grainger|ipiranga|softbank|attorney|pharmacy|saarland|catering|airforce|yokohama|mortgage|frontier|mutuelle|stcgroup|memorial|pictures|football|symantec|cipriani|ventures|telecity|cityeats|verisign|flsmidth|boutique|cleaning|firmdale|clinique|clothing|redstone|infiniti|deloitte|feedback|services|broadway|plumbing|commbank|training|barclays|exchange|computer|brussels|software|delivery|barefoot|builders|business|bargains|engineer|holdings|download|security|helsinki|lighting|movistar|discount|hdfcbank|supplies|marriott|property|diamonds|capetown|partners|democrat|jpmorgan|bradesco|budapest|rexroth|zuerich|shriram|academy|science|support|youtube|singles|surgery|alibaba|statoil|dentist|schwarz|android|cruises|cricket|digital|markets|starhub|systems|courses|coupons|netbank|country|domains|corsica|network|neustar|realtor|lincoln|limited|schmidt|yamaxun|cooking|contact|auction|spiegel|liaison|leclerc|latrobe|lasalle|abogado|compare|lanxess|exposed|express|company|cologne|college|avianca|lacaixa|fashion|recipes|ferrero|komatsu|storage|wanggou|clubmed|sandvik|fishing|fitness|bauhaus|kitchen|flights|florist|flowers|watches|weather|temasek|samsung|bentley|forsale|channel|theater|frogans|theatre|okinawa|website|tickets|jewelry|gallery|tiffany|iselect|shiksha|brother|organic|wedding|genting|toshiba|origins|philips|hyundai|hotmail|hoteles|hosting|rentals|windows|cartier|bugatti|holiday|careers|whoswho|hitachi|panerai|caravan|reviews|guitars|capital|trading|hamburg|hangout|finance|stream|family|abbott|health|review|travel|report|hermes|hiphop|gratis|career|toyota|hockey|dating|repair|google|social|soccer|reisen|global|otsuka|giving|unicom|casino|photos|center|broker|rocher|orange|bostik|garden|insure|ryukyu|bharti|safety|physio|sakura|oracle|online|jaguar|gallup|piaget|tienda|futbol|pictet|joburg|webcam|berlin|office|juegos|kaufen|chanel|chrome|xihuan|church|tennis|circle|kinder|flickr|bayern|claims|clinic|viajes|nowruz|xperia|norton|yachts|studio|coffee|camera|sanofi|nissan|author|expert|events|comsec|lawyer|tattoo|viking|estate|villas|condos|realty|yandex|energy|emerck|virgin|vision|durban|living|school|coupon|london|taobao|natura|taipei|nagoya|luxury|walter|aramco|sydney|madrid|credit|maison|makeup|schule|market|anquan|direct|design|swatch|suzuki|alsace|vuelos|dental|alipay|voyage|shouji|voting|airtel|mutual|degree|supply|agency|museum|mobily|dealer|monash|select|mormon|active|moscow|racing|datsun|quebec|nissay|rodeo|email|gifts|works|photo|chloe|edeka|cheap|earth|vista|tushu|koeln|glass|shoes|globo|tunes|gmail|nokia|space|kyoto|black|ricoh|seven|lamer|sener|epson|cisco|praxi|trust|citic|crown|shell|lease|green|legal|lexus|ninja|tatar|gripe|nikon|group|video|wales|autos|gucci|party|nexus|guide|linde|adult|parts|amica|lixil|boats|azure|loans|locus|cymru|lotte|lotto|stada|click|poker|quest|dabur|lupin|nadex|paris|faith|dance|canon|place|gives|trade|skype|rocks|mango|cloud|boots|smile|final|swiss|homes|honda|media|horse|cards|deals|watch|bosch|house|pizza|miami|osaka|tours|total|xerox|coach|sucks|style|delta|toray|iinet|tools|money|codes|beats|tokyo|salon|archi|movie|baidu|study|actor|yahoo|store|apple|world|forex|today|bible|tmall|tirol|irish|tires|forum|reise|vegas|vodka|sharp|omega|weber|jetzt|audio|promo|build|bingo|chase|gallo|drive|dubai|rehab|press|solar|sale|beer|bbva|bank|band|auto|sapo|sarl|saxo|audi|asia|arte|arpa|army|yoga|ally|zara|scor|scot|sexy|seat|zero|seek|aero|adac|zone|aarp|maif|meet|meme|menu|surf|mini|mobi|mtpc|porn|desi|star|ltda|name|talk|navy|love|loan|live|link|news|limo|like|spot|life|nico|lidl|lgbt|land|taxi|team|tech|kred|kpmg|sony|song|kiwi|kddi|jprs|jobs|sohu|java|itau|tips|info|immo|icbc|hsbc|town|host|page|toys|here|help|pars|haus|guru|guge|tube|goog|golf|gold|sncf|gmbh|gift|ggee|gent|gbiz|game|vana|pics|fund|ford|ping|pink|fish|film|fast|farm|play|fans|fail|plus|skin|pohl|fage|moda|post|erni|dvag|prod|doha|prof|docs|viva|diet|luxe|site|dell|sina|dclk|show|qpon|date|vote|cyou|voto|read|coop|cool|wang|club|city|chat|cern|cash|reit|rent|casa|cars|care|camp|rest|call|cafe|weir|wien|rich|wiki|buzz|wine|book|bond|room|work|rsvp|shia|ruhr|blue|bing|shaw|bike|safe|xbox|best|pwc|mtn|lds|aig|boo|fyi|nra|nrw|ntt|car|gal|obi|zip|aeg|vin|how|one|ong|onl|dad|ooo|bet|esq|org|htc|bar|uol|ibm|ovh|gdn|ice|icu|uno|gea|ifm|bot|top|wtf|lol|day|pet|eus|wtc|ubs|tvs|aco|ing|ltd|ink|tab|abb|afl|cat|int|pid|pin|bid|cba|gle|com|cbn|ads|man|wed|ceb|gmo|sky|ist|gmx|tui|mba|fan|ski|iwc|app|pro|med|ceo|jcb|jcp|goo|dev|men|aaa|meo|pub|jlc|bom|jll|gop|jmp|mil|got|gov|win|jot|mma|joy|trv|red|cfa|cfd|bio|moe|moi|mom|ren|biz|aws|xin|bbc|dnp|buy|kfh|mov|thd|xyz|fit|kia|rio|rip|kim|dog|vet|nyc|bcg|mtr|bcn|bms|bmw|run|bzh|rwe|tel|stc|axa|kpn|fly|krd|cab|bnl|foo|crs|eat|tci|sap|srl|nec|sas|net|cal|sbs|sfr|sca|scb|csc|edu|new|xxx|hiv|fox|wme|ngo|nhk|vip|sex|frl|lat|yun|law|you|tax|soy|sew|om|ac|hu|se|sc|sg|sh|sb|sa|rw|ru|rs|ro|re|qa|py|si|pw|pt|ps|sj|sk|pr|pn|pm|pl|sl|sm|pk|sn|ph|so|pg|pf|pe|pa|zw|nz|nu|nr|np|no|nl|ni|ng|nf|sr|ne|st|nc|na|mz|my|mx|mw|mv|mu|mt|ms|mr|mq|mp|mo|su|mn|mm|ml|mk|mh|mg|me|sv|md|mc|sx|sy|ma|ly|lv|sz|lu|lt|ls|lr|lk|li|lc|lb|la|tc|kz|td|ky|kw|kr|kp|kn|km|ki|kh|tf|tg|th|kg|ke|jp|jo|jm|je|it|is|ir|tj|tk|tl|tm|iq|tn|to|io|in|im|il|ie|ad|sd|ht|hr|hn|hm|tr|hk|gy|gw|gu|gt|gs|gr|gq|tt|gp|gn|gm|gl|tv|gi|tw|tz|ua|gh|ug|uk|gg|gf|ge|gd|us|uy|uz|va|gb|ga|vc|ve|fr|fo|fm|fk|fj|vg|vi|fi|eu|et|es|er|eg|ee|ec|dz|do|dm|dk|vn|dj|de|cz|cy|cx|cw|vu|cv|cu|cr|co|cn|cm|cl|ck|ci|ch|cg|cf|cd|cc|ca|wf|bz|by|bw|bv|bt|bs|br|bo|bn|bm|bj|bi|ws|bh|bg|bf|be|bd|bb|ba|az|ax|aw|au|at|as|ye|ar|aq|ao|am|al|yt|ai|za|ag|af|ae|zm|id)\b/;return{alphaNumericCharsStr:r,domainNameRegex:a,tldRegex:n}}(),t.AnchorTagBuilder=t.Util.extend(Object,{constructor:function(t){t=t||{},this.newWindow=t.newWindow,this.truncate=t.truncate,this.className=t.className},build:function(e){return new t.HtmlTag({tagName:"a",attrs:this.createAttrs(e),innerHtml:this.processAnchorText(e.getAnchorText())})},createAttrs:function(t){var e={href:t.getAnchorHref()},r=this.createCssClass(t);return r&&(e["class"]=r),this.newWindow&&(e.target="_blank",e.rel="noopener noreferrer"),e},createCssClass:function(t){var e=this.className;if(e){for(var r=[e],a=t.getCssClassSuffixes(),n=0,i=a.length;n\/=\x00-\x1F\x7F]+/,a=/(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/,n=r.source+"(?:\\s*=\\s*"+a.source+")?";return new RegExp(["(?:","<(!DOCTYPE)","(?:","\\s+","(?:",n,"|",a.source+")",")*",">",")","|","(?:","<(/)?","(?:",t.source,"|","(?:","("+e.source+")","(?:","(?:\\s+|\\b)",n,")*","\\s*/?",")",")",">",")"].join(""),"gi")}(),htmlCharacterEntitiesRegex:/( | |<|<|>|>|"|"|')/gi,parse:function(t){for(var e,r,a=this.htmlRegex,n=0,i=[];null!==(e=a.exec(t));){var s=e[0],o=e[3],c=e[1]||e[4],h=!!e[2],l=e.index,u=t.substring(n,l);u&&(r=this.parseTextAndEntityNodes(n,u),i.push.apply(i,r)),o?i.push(this.createCommentNode(l,s,o)):i.push(this.createElementNode(l,s,c,h)),n=l+s.length}if(n0&&"@"===f||g>0&&m&&this.wordCharRegExp.test(f))){if(this.matchHasUnbalancedClosingParen(o))o=o.substr(0,o.length-1);else{var p=this.matchHasInvalidCharAfterTld(o,c);p>-1&&(o=o.substr(0,p))}var d=c?"scheme":h?"www":"tld",b=!!c;s.push(new t.match.Url({tagBuilder:i,matchedText:o,offset:g,urlMatchType:d,url:o,protocolUrlMatch:b,protocolRelativeMatch:!!m,stripPrefix:n}))}}return s},matchHasUnbalancedClosingParen:function(t){var e=t.charAt(t.length-1);if(")"===e){var r=t.match(this.openParensRe),a=t.match(this.closeParensRe),n=r&&r.length||0,i=a&&a.length||0;if(n0&&(n=t.substr(-1*Math.floor(a/2))),(t.substr(0,Math.ceil(a/2))+r+n).substr(0,e)},t.truncate.TruncateSmart=function(t,e,r){var a=function(t){var e={},r=t,a=r.match(/^([a-z]+):\/\//i);return a&&(e.scheme=a[1],r=r.substr(a[0].length)),a=r.match(/^(.*?)(?=(\?|#|\/|$))/i),a&&(e.host=a[1],r=r.substr(a[0].length)),a=r.match(/^\/(.*?)(?=(\?|#|$))/i),a&&(e.path=a[1],r=r.substr(a[0].length)),a=r.match(/^\?(.*?)(?=(#|$))/i),a&&(e.query=a[1],r=r.substr(a[0].length)),a=r.match(/^#(.*?)$/i),a&&(e.fragment=a[1]),e},n=function(t){var e="";return t.scheme&&t.host&&(e+=t.scheme+"://"),t.host&&(e+=t.host),t.path&&(e+="/"+t.path),t.query&&(e+="?"+t.query),t.fragment&&(e+="#"+t.fragment),e},i=function(t,e){var a=e/2,n=Math.ceil(a),i=-1*Math.floor(a),s="";return i<0&&(s=t.substr(i)),t.substr(0,n)+r+s};if(t.length<=e)return t;var s=e-r.length,o=a(t);if(o.query){var c=o.query.match(/^(.*?)(?=(\?|\#))(.*?)$/i);c&&(o.query=o.query.substr(0,c[1].length),t=n(o))}if(t.length<=e)return t;if(o.host&&(o.host=o.host.replace(/^www\./,""),t=n(o)),t.length<=e)return t;var h="";if(o.host&&(h+=o.host),h.length>=s)return o.host.length==e?(o.host.substr(0,e-r.length)+r).substr(0,e):i(h,s).substr(0,e);var l="";if(o.path&&(l+="/"+o.path),o.query&&(l+="?"+o.query),l){if((h+l).length>=s){if((h+l).length==e)return(h+l).substr(0,e);var u=s-h.length;return(h+i(l,u)).substr(0,e)}h+=l}if(o.fragment){var g="#"+o.fragment;if((h+g).length>=s){if((h+g).length==e)return(h+g).substr(0,e);var m=s-h.length;return(h+i(g,m)).substr(0,e)}h+=g}if(o.scheme&&o.host){var f=o.scheme+"://";if((h+f).length0&&(p=h.substr(-1*Math.floor(s/2))),(h.substr(0,Math.ceil(s/2))+r+p).substr(0,e)},t}); \ No newline at end of file diff --git a/examples/live-example/index.html b/examples/live-example/index.html index 611864e7..bfa1dfa3 100644 --- a/examples/live-example/index.html +++ b/examples/live-example/index.html @@ -31,7 +31,7 @@

Match Types:

-
+
diff --git a/examples/live-example/js/main.ts b/examples/live-example/js/main.ts index 37c8dd38..c9ff5585 100644 --- a/examples/live-example/js/main.ts +++ b/examples/live-example/js/main.ts @@ -19,7 +19,7 @@ $( document ).ready( function() { urlsTldOption: LiveExample.Option, emailOption: LiveExample.Option, phoneOption: LiveExample.Option, - twitterOption: LiveExample.Option, + mentionOption: LiveExample.Option, hashtagOption: LiveExample.Option, newWindowOption: LiveExample.Option, @@ -38,7 +38,7 @@ $( document ).ready( function() { urlsTldOption = new CheckboxOption( { name: 'urls.tldMatches', description: 'TLD URLs', defaultValue: true } ).onChange( autolink ); emailOption = new CheckboxOption( { name: 'email', description: 'Email Addresses', defaultValue: true } ).onChange( autolink ); phoneOption = new CheckboxOption( { name: 'phone', description: 'Phone Numbers', defaultValue: true } ).onChange( autolink ); - twitterOption = new CheckboxOption( { name: 'twitter', description: 'Twitter Handles', defaultValue: true } ).onChange( autolink ); + mentionOption = new RadioOption( { name: 'twitter', description: 'Twitter Handles', options: [ false, 'twitter', 'instagram' ], defaultValue: false } ).onChange( autolink ); hashtagOption = new RadioOption( { name: 'hashtag', description: 'Hashtags', options: [ false, 'twitter', 'facebook', 'instagram' ], defaultValue: false } ).onChange( autolink ); newWindowOption = new CheckboxOption( { name: 'newWindow', description: 'Open in new window', defaultValue: true } ).onChange( autolink ); @@ -76,7 +76,7 @@ $( document ).ready( function() { }, email : emailOption.getValue(), phone : phoneOption.getValue(), - twitter : twitterOption.getValue(), + mention : mentionOption.getValue(), hashtag : hashtagOption.getValue(), newWindow : newWindowOption.getValue(), @@ -100,7 +100,7 @@ $( document ).ready( function() { ` },`, ` email : ${ optionsObj.email },`, ` phone : ${ optionsObj.phone },`, - ` twitter : ${ optionsObj.twitter },`, + ` mention : ${ optionsObj.mention },`, ` hashtag : ${ typeof optionsObj.hashtag === 'string' ? "'" + optionsObj.hashtag + "'" : optionsObj.hashtag },`, ``, ` stripPrefix : ${ optionsObj.stripPrefix },`, diff --git a/examples/live-example/live-example.js b/examples/live-example/live-example.js index 6b3c83b1..24d6a036 100644 --- a/examples/live-example/live-example.js +++ b/examples/live-example/live-example.js @@ -258,7 +258,7 @@ var CheckboxOption = LiveExample.CheckboxOption; var RadioOption = LiveExample.RadioOption; var TextOption = LiveExample.TextOption; $(document).ready(function () { - var $inputEl = $('#input'), $outputEl = $('#output'), $optionsOutputEl = $('#options-output'), urlsSchemeOption, urlsWwwOption, urlsTldOption, emailOption, phoneOption, twitterOption, hashtagOption, newWindowOption, stripPrefixOption, truncateLengthOption, truncationLocationOption, classNameOption; + var $inputEl = $('#input'), $outputEl = $('#output'), $optionsOutputEl = $('#options-output'), urlsSchemeOption, urlsWwwOption, urlsTldOption, emailOption, phoneOption, mentionOption, hashtagOption, newWindowOption, stripPrefixOption, truncateLengthOption, truncationLocationOption, classNameOption; init(); function init() { urlsSchemeOption = new CheckboxOption({ name: 'urls.schemeMatches', description: 'Scheme:// URLs', defaultValue: true }).onChange(autolink); @@ -266,7 +266,7 @@ $(document).ready(function () { urlsTldOption = new CheckboxOption({ name: 'urls.tldMatches', description: 'TLD URLs', defaultValue: true }).onChange(autolink); emailOption = new CheckboxOption({ name: 'email', description: 'Email Addresses', defaultValue: true }).onChange(autolink); phoneOption = new CheckboxOption({ name: 'phone', description: 'Phone Numbers', defaultValue: true }).onChange(autolink); - twitterOption = new CheckboxOption({ name: 'twitter', description: 'Twitter Handles', defaultValue: true }).onChange(autolink); + mentionOption = new RadioOption({ name: 'mention', description: 'Mentions', options: [false, 'twitter', 'instagram'], defaultValue: false }).onChange(autolink); hashtagOption = new RadioOption({ name: 'hashtag', description: 'Hashtags', options: [false, 'twitter', 'facebook', 'instagram'], defaultValue: false }).onChange(autolink); newWindowOption = new CheckboxOption({ name: 'newWindow', description: 'Open in new window', defaultValue: true }).onChange(autolink); stripPrefixOption = new CheckboxOption({ name: 'stripPrefix', description: 'Strip prefix', defaultValue: true }).onChange(autolink); @@ -293,7 +293,7 @@ $(document).ready(function () { }, email: emailOption.getValue(), phone: phoneOption.getValue(), - twitter: twitterOption.getValue(), + twitter: mentionOption.getValue(), hashtag: hashtagOption.getValue(), newWindow: newWindowOption.getValue(), stripPrefix: stripPrefixOption.getValue(), diff --git a/gulpfile.js b/gulpfile.js index 5eed590a..a7b2323b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -163,17 +163,17 @@ function createSrcFilesList() { 'src/match/Email.js', 'src/match/Hashtag.js', 'src/match/Phone.js', - 'src/match/Twitter.js', + 'src/match/Mention.js', 'src/match/Url.js', 'src/matcher/Matcher.js', 'src/matcher/Email.js', 'src/matcher/Hashtag.js', 'src/matcher/Phone.js', - 'src/matcher/Twitter.js', + 'src/matcher/Mention.js', 'src/matcher/Url.js', 'src/matcher/UrlMatchValidator.js', 'src/truncate/TruncateEnd.js', 'src/truncate/TruncateMiddle.js', 'src/truncate/TruncateSmart.js' ]; -} \ No newline at end of file +} diff --git a/meteor/package.js b/meteor/package.js index 06d7d1e3..15285394 100644 --- a/meteor/package.js +++ b/meteor/package.js @@ -8,7 +8,7 @@ var packageJson = JSON.parse(Npm.require("fs").readFileSync('package.json')); Package.describe({ name: packageName, - summary: "Autolinker.js (official) - Automatically Link URLs, Email Addresses, Phone Numbers, Twitter handles, and Hashtags in a given block of text/HTML", + summary: "Autolinker.js (official) - Automatically Link URLs, Email Addresses, Phone Numbers, Mentions (Twitter, Instagram), and Hashtags in a given block of text/HTML", version: packageJson.version, git: "https://github.com/gregjacobs/Autolinker.js.git" }); diff --git a/package.json b/package.json index 25c1884d..49f98169 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "autolinker", - "version": "0.28.1", - "description": "Utility to automatically link the URLs, email addresses, and Twitter handles in a given block of text/HTML", + "version": "1.0.0", + "description": "Utility to automatically link the URLs, email addresses, phone numbers, hashtags, and mentions (Twitter, Instagram) in a given block of text/HTML", "main": "dist/Autolinker.js", "files": [ "dist" diff --git a/src/AnchorTagBuilder.js b/src/AnchorTagBuilder.js index 78bead90..e35cec61 100644 --- a/src/AnchorTagBuilder.js +++ b/src/AnchorTagBuilder.js @@ -9,14 +9,14 @@ * found. * * Normally this class is instantiated, configured, and used internally by an - * {@link Autolinker} instance, but may actually be retrieved in a {@link Autolinker#replaceFn replaceFn} - * to create {@link Autolinker.HtmlTag HtmlTag} instances which may be modified - * before returning from the {@link Autolinker#replaceFn replaceFn}. For - * example: + * {@link Autolinker} instance, but may actually be used indirectly in a + * {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} + * instances which may be modified before returning from the + * {@link Autolinker#replaceFn replaceFn}. For example: * * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * replaceFn : function( match ) { + * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance * tag.setAttr( 'rel', 'nofollow' ); * * return tag; @@ -49,7 +49,11 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). */ constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); + cfg = cfg || {}; + + this.newWindow = cfg.newWindow; + this.truncate = cfg.truncate; + this.className = cfg.className; }, @@ -64,7 +68,7 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { build : function( match ) { return new Autolinker.HtmlTag( { tagName : 'a', - attrs : this.createAttrs( match.getType(), match.getAnchorHref() ), + attrs : this.createAttrs( match ), innerHtml : this.processAnchorText( match.getAnchorText() ) } ); }, @@ -75,17 +79,16 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { * tag being generated. * * @protected - * @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of - * match that an anchor tag is being generated for. - * @param {String} anchorHref The href for the anchor tag. + * @param {Autolinker.match.Match} match The Match instance to generate an + * anchor tag from. * @return {Object} A key/value Object (map) of the anchor tag's attributes. */ - createAttrs : function( matchType, anchorHref ) { + createAttrs : function( match ) { var attrs = { - 'href' : anchorHref // we'll always have the `href` attribute + 'href' : match.getAnchorHref() // we'll always have the `href` attribute }; - var cssClass = this.createCssClass( matchType ); + var cssClass = this.createCssClass( match ); if( cssClass ) { attrs[ 'class' ] = cssClass; } @@ -102,20 +105,37 @@ Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { * Creates the CSS class that will be used for a given anchor tag, based on * the `matchType` and the {@link #className} config. * + * Example returns: + * + * - "" // no {@link #className} + * - "myLink myLink-url" // url match + * - "myLink myLink-email" // email match + * - "myLink myLink-phone" // phone match + * - "myLink myLink-hashtag" // hashtag match + * - "myLink myLink-mention myLink-twitter" // mention match with Twitter service + * * @private - * @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of - * match that an anchor tag is being generated for. + * @param {Autolinker.match.Match} match The Match instance to generate an + * anchor tag from. * @return {String} The CSS class string for the link. Example return: * "myLink myLink-url". If no {@link #className} was configured, returns * an empty string. */ - createCssClass : function( matchType ) { + createCssClass : function( match ) { var className = this.className; - if( !className ) + if( !className ) { return ""; - else - return className + " " + className + "-" + matchType; // ex: "myLink myLink-url", "myLink myLink-email", "myLink myLink-phone", "myLink myLink-twitter", or "myLink myLink-hashtag" + + } else { + var returnClasses = [ className ], + cssClassSuffixes = match.getCssClassSuffixes(); + + for( var i = 0, len = cssClassSuffixes.length; i < len; i++ ) { + returnClasses.push( className + '-' + cssClassSuffixes[ i ] ); + } + return returnClasses.join( ' ' ); + } }, diff --git a/src/Autolinker.js b/src/Autolinker.js index 4e6b75ff..7f23fc0b 100644 --- a/src/Autolinker.js +++ b/src/Autolinker.js @@ -35,15 +35,15 @@ * * If the configuration options do not provide enough flexibility, a {@link #replaceFn} * may be provided to fully customize the output of Autolinker. This function is - * called once for each URL/Email/Phone#/Twitter Handle/Hashtag match that is - * encountered. + * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram) + * match that is encountered. * * For example: * - * var input = "..."; // string with URLs, Email Addresses, Phone #s, Twitter Handles, and Hashtags + * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram) * * var linkedText = Autolinker.link( input, { - * replaceFn : function( autolinker, match ) { + * replaceFn : function( match ) { * console.log( "href = ", match.getAnchorHref() ); * console.log( "text = ", match.getAnchorText() ); * @@ -52,7 +52,7 @@ * console.log( "url: ", match.getUrl() ); * * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes + * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes * tag.setAttr( 'rel', 'nofollow' ); * tag.addClass( 'external-link' ); * @@ -78,17 +78,17 @@ * * return '' + phoneNumber + ''; * - * case 'twitter' : - * var twitterHandle = match.getTwitterHandle(); - * console.log( twitterHandle ); - * - * return '' + twitterHandle + ''; - * * case 'hashtag' : * var hashtag = match.getHashtag(); * console.log( hashtag ); * * return '' + hashtag + ''; + * + * case 'mention' : + * var mention = match.getMention(); + * console.log( mention ); + * + * return '' + mention + ''; * } * } * } ); @@ -115,13 +115,19 @@ var Autolinker = function( cfg ) { this.urls = this.normalizeUrlsCfg( cfg.urls ); this.email = typeof cfg.email === 'boolean' ? cfg.email : true; - this.twitter = typeof cfg.twitter === 'boolean' ? cfg.twitter : true; this.phone = typeof cfg.phone === 'boolean' ? cfg.phone : true; this.hashtag = cfg.hashtag || false; + this.mention = cfg.mention || false; this.newWindow = typeof cfg.newWindow === 'boolean' ? cfg.newWindow : true; this.stripPrefix = typeof cfg.stripPrefix === 'boolean' ? cfg.stripPrefix : true; - // Validate the value of the `hashtag` cfg. + // Validate the value of the `mention` cfg + var mention = this.mention; + if( mention !== false && mention !== 'twitter' && mention !== 'instagram' ) { + throw new Error( "invalid `mention` cfg - see docs" ); + } + + // Validate the value of the `hashtag` cfg var hashtag = this.hashtag; if( hashtag !== false && hashtag !== 'twitter' && hashtag !== 'facebook' && hashtag !== 'instagram' ) { throw new Error( "invalid `hashtag` cfg - see docs" ); @@ -130,6 +136,7 @@ var Autolinker = function( cfg ) { this.truncate = this.normalizeTruncateCfg( cfg.truncate ); this.className = cfg.className || ''; this.replaceFn = cfg.replaceFn || null; + this.context = cfg.context || this; this.htmlParser = null; this.matchers = null; @@ -140,7 +147,7 @@ var Autolinker = function( cfg ) { /** * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles, - * and Hashtags found in the given chunk of HTML. Does not link URLs found + * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs found * within HTML tags. * * For instance, if given the text: `You should go to http://www.yahoo.com`, @@ -154,7 +161,7 @@ var Autolinker = function( cfg ) { * @static * @param {String} textOrHtml The HTML or text to find matches within (depending * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #twitter}, - * and {@link #hashtag} options are enabled). + * {@link #hashtag}, and {@link #mention} options are enabled). * @param {Object} [options] Any of the configuration options for the Autolinker * class, specified in an Object (map). See the class description for an * example call. @@ -210,13 +217,6 @@ Autolinker.prototype = { * should not be. */ - /** - * @cfg {Boolean} [twitter=true] - * - * `true` if Twitter handles ("@example") should be automatically linked, - * `false` if they should not be. - */ - /** * @cfg {Boolean} [phone=true] * @@ -237,6 +237,18 @@ Autolinker.prototype = { * Pass `false` to skip auto-linking of hashtags. */ + /** + * @cfg {String/Boolean} [mention=false] + * + * A string for the service name to have mentions (ex: "@myuser") + * auto-linked to. The currently supported values are: + * + * - 'twitter' + * - 'instagram' + * + * Defaults to `false` to skip auto-linking of mentions. + */ + /** * @cfg {Boolean} [newWindow=true] * @@ -303,15 +315,15 @@ Autolinker.prototype = { * * A CSS class name to add to the generated links. This class will be added * to all links, as well as this class plus match suffixes for styling - * url/email/phone/twitter/hashtag links differently. + * url/email/phone/hashtag/mention links differently. * * For example, if this config is provided as "myLink", then: * * - URL links will have the CSS classes: "myLink myLink-url" * - Email links will have the CSS classes: "myLink myLink-email", and - * - Twitter links will have the CSS classes: "myLink myLink-twitter" * - Phone links will have the CSS classes: "myLink myLink-phone" * - Hashtag links will have the CSS classes: "myLink myLink-hashtag" + * - Mention links will have the CSS classes: "myLink myLink-mention myLink-" where type is "instagram"/"twitter" */ /** @@ -321,17 +333,25 @@ Autolinker.prototype = { * * See the class's description for usage. * - * This function is called with the following parameters: + * The `replaceFn` can be called with a different context object (`this` + * reference) using the {@link #context} cfg. + * + * This function is called with the following parameter: * - * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may - * be used to retrieve child objects from (such as the instance's - * {@link #getTagBuilder tag builder}). * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which * can be used to retrieve information about the match that the `replaceFn` * is currently processing. See {@link Autolinker.match.Match} subclasses * for details. */ + /** + * @cfg {Object} context + * + * The context object (`this` reference) to call the `replaceFn` with. + * + * Defaults to this Autolinker instance. + */ + /** * @property {String} version (readonly) @@ -430,7 +450,7 @@ Autolinker.prototype = { * * @param {String} textOrHtml The HTML or text to find matches within * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, - * {@link #twitter}, and {@link #hashtag} options are enabled). + * {@link #hashtag}, and {@link #mention} options are enabled). * @return {Autolinker.match.Match[]} The array of Matches found in the * given input `textOrHtml`. */ @@ -520,7 +540,7 @@ Autolinker.prototype = { if( !this.hashtag ) remove( matches, function( match ) { return match.getType() === 'hashtag'; } ); if( !this.email ) remove( matches, function( match ) { return match.getType() === 'email'; } ); if( !this.phone ) remove( matches, function( match ) { return match.getType() === 'phone'; } ); - if( !this.twitter ) remove( matches, function( match ) { return match.getType() === 'twitter'; } ); + if( !this.mention ) remove( matches, function( match ) { return match.getType() === 'mention'; } ); if( !this.urls.schemeMatches ) { remove( matches, function( m ) { return m.getType() === 'url' && m.getUrlMatchType() === 'scheme'; } ); } @@ -547,8 +567,8 @@ Autolinker.prototype = { * * @private * @param {String} text The text to find matches within (depending on if the - * {@link #urls}, {@link #email}, {@link #phone}, {@link #twitter}, and - * {@link #hashtag} options are enabled). This must be a non-HTML string. + * {@link #urls}, {@link #email}, {@link #phone}, + * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string. * @param {Number} [offset=0] The offset of the text node within the * original string. This is used when parsing with the {@link #parse} * method to generate correct offsets within the {@link Autolinker.match.Match} @@ -579,8 +599,8 @@ Autolinker.prototype = { /** - * Automatically links URLs, Email addresses, Phone numbers, Twitter - * handles, and Hashtags found in the given chunk of HTML. Does not link + * Automatically links URLs, Email addresses, Phone numbers, Hashtags, + * and Mentions (Twitter, Instagram) found in the given chunk of HTML. Does not link * URLs found within HTML tags. * * For instance, if given the text: `You should go to http://www.yahoo.com`, @@ -593,8 +613,7 @@ Autolinker.prototype = { * in anchor (<a>) tags. * * @param {String} textOrHtml The HTML or text to autolink matches within - * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, - * {@link #twitter}, and {@link #hashtag} options are enabled). + * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled). * @return {String} The HTML, with matches automatically linked. */ link : function( textOrHtml ) { @@ -634,7 +653,7 @@ Autolinker.prototype = { // Handle a custom `replaceFn` being provided var replaceFnResult; if( this.replaceFn ) { - replaceFnResult = this.replaceFn.call( this, this, match ); // Autolinker instance is the context, and the first arg + replaceFnResult = this.replaceFn.call( this.context, match ); // Autolinker instance is the context } if( typeof replaceFnResult === 'string' ) { @@ -689,7 +708,7 @@ Autolinker.prototype = { new matchersNs.Hashtag( { tagBuilder: tagBuilder, serviceName: this.hashtag } ), new matchersNs.Email( { tagBuilder: tagBuilder } ), new matchersNs.Phone( { tagBuilder: tagBuilder } ), - new matchersNs.Twitter( { tagBuilder: tagBuilder } ), + new matchersNs.Mention( { tagBuilder: tagBuilder, serviceName: this.mention } ), new matchersNs.Url( { tagBuilder: tagBuilder, stripPrefix: this.stripPrefix } ) ]; @@ -709,8 +728,8 @@ Autolinker.prototype = { * Autolinker would normally generate, and then allow for modifications before returning it. For example: * * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * replaceFn : function( match ) { + * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance * tag.setAttr( 'rel', 'nofollow' ); * * return tag; diff --git a/src/HtmlTag.js b/src/HtmlTag.js index 6df5ee8a..95aa5eb0 100644 --- a/src/HtmlTag.js +++ b/src/HtmlTag.js @@ -45,7 +45,7 @@ * ## Example use within a {@link Autolinker#replaceFn replaceFn} * * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { + * replaceFn : function( match ) { * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text * tag.setAttr( 'rel', 'nofollow' ); * @@ -60,7 +60,7 @@ * ## Example use with a new tag for the replacement * * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { + * replaceFn : function( match ) { * var tag = new Autolinker.HtmlTag( { * tagName : 'button', * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() }, diff --git a/src/htmlParser/HtmlParser.js b/src/htmlParser/HtmlParser.js index 1ee29ff2..8a4a25fe 100644 --- a/src/htmlParser/HtmlParser.js +++ b/src/htmlParser/HtmlParser.js @@ -6,7 +6,7 @@ * An HTML parser implementation which simply walks an HTML string and returns an array of * {@link Autolinker.htmlParser.HtmlNode HtmlNodes} that represent the basic HTML structure of the input string. * - * Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, effectively ignoring / "walking + * Autolinker uses this to only link URLs/emails/mentions within text nodes, effectively ignoring / "walking * around" HTML tags. */ Autolinker.htmlParser.HtmlParser = Autolinker.Util.extend( Object, { @@ -254,4 +254,4 @@ Autolinker.htmlParser.HtmlParser = Autolinker.Util.extend( Object, { return new Autolinker.htmlParser.TextNode( { offset: offset, text: text } ); } -} ); \ No newline at end of file +} ); diff --git a/src/match/Match.js b/src/match/Match.js index bec00e5e..8208434a 100644 --- a/src/match/Match.js +++ b/src/match/Match.js @@ -8,10 +8,10 @@ * * For example: * - * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + * var input = "..."; // string with URLs, Email Addresses, and Mentions (Twitter, Instagram) * * var linkedText = Autolinker.link( input, { - * replaceFn : function( autolinker, match ) { + * replaceFn : function( match ) { * console.log( "href = ", match.getAnchorHref() ); * console.log( "text = ", match.getAnchorText() ); * @@ -22,8 +22,8 @@ * case 'email' : * console.log( "email: ", match.getEmail() ); * - * case 'twitter' : - * console.log( "twitter: ", match.getTwitterHandle() ); + * case 'mention' : + * console.log( "mention: ", match.getMention() ); * } * } * } ); @@ -135,6 +135,32 @@ Autolinker.match.Match = Autolinker.Util.extend( Object, { getAnchorText : Autolinker.Util.abstractMethod, + /** + * Returns the CSS class suffix(es) for this match. + * + * A CSS class suffix is appended to the {@link Autolinker#className} in + * the {@link AnchorTagBuilder} when a match is translated into an anchor + * tag. + * + * For example, if {@link Autolinker#className} was configured as 'myLink', + * and this method returns `[ 'url' ]`, the final class name of the element + * will become: 'myLink myLink-url'. + * + * The match may provide multiple CSS class suffixes to be appended to the + * {@link Autolinker#className} in order to facilitate better styling + * options for different match criteria. See {@link Autolinker.match.Mention} + * for an example. + * + * By default, this method returns a single array with the match's + * {@link #getType type} name, but may be overridden by subclasses. + * + * @return {String[]} + */ + getCssClassSuffixes : function() { + return [ this.getType() ]; + }, + + /** * Builds and returns an {@link Autolinker.HtmlTag} instance based on the * Match. @@ -154,4 +180,4 @@ Autolinker.match.Match = Autolinker.Util.extend( Object, { return this.tagBuilder.build( this ); } -} ); \ No newline at end of file +} ); diff --git a/src/match/Mention.js b/src/match/Mention.js new file mode 100644 index 00000000..b5f9c8fc --- /dev/null +++ b/src/match/Mention.js @@ -0,0 +1,120 @@ +/*global Autolinker */ +/** + * @class Autolinker.match.Mention + * @extends Autolinker.match.Match + * + * Represents a Mention match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ +Autolinker.match.Mention = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} serviceName + * + * The service to point mention matches to. See {@link Autolinker#mention} + * for available values. + */ + + /** + * @cfg {String} mention (required) + * + * The Mention that was matched, without the '@' character. + */ + + + /** + * @constructor + * @param {Object} cfg The configuration properties for the Match + * instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.match.Match.prototype.constructor.call( this, cfg ); + + // @if DEBUG + if( !cfg.serviceName ) throw new Error( '`serviceName` cfg required' ); + if( !cfg.mention ) throw new Error( '`mention` cfg required' ); + // @endif + + this.mention = cfg.mention; + this.serviceName = cfg.serviceName; + }, + + + /** + * Returns the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'mention'; + }, + + + /** + * Returns the mention, without the '@' character. + * + * @return {String} + */ + getMention : function() { + return this.mention; + }, + + + /** + * Returns the configured {@link #serviceName} to point the mention to. + * Ex: 'instagram', 'twitter'. + * + * @return {String} + */ + getServiceName : function() { + return this.serviceName; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + switch( this.serviceName ) { + case 'twitter' : + return 'https://twitter.com/' + this.mention; + case 'instagram' : + return 'https://instagram.com/' + this.mention; + + default : // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case. + throw new Error( 'Unknown service name to point mention to: ', this.serviceName ); + } + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return '@' + this.mention; + }, + + + /** + * Returns the CSS class suffixes that should be used on a tag built with + * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for + * details. + * + * @return {String[]} + */ + getCssClassSuffixes : function() { + var cssClassSuffixes = Autolinker.match.Match.prototype.getCssClassSuffixes.call( this ), + serviceName = this.getServiceName(); + + if( serviceName ) { + cssClassSuffixes.push( serviceName ); + } + return cssClassSuffixes; + } + +} ); diff --git a/src/match/Twitter.js b/src/match/Twitter.js deleted file mode 100644 index f605594b..00000000 --- a/src/match/Twitter.js +++ /dev/null @@ -1,74 +0,0 @@ -/*global Autolinker */ -/** - * @class Autolinker.match.Twitter - * @extends Autolinker.match.Match - * - * Represents a Twitter match found in an input string which should be Autolinked. - * - * See this class's superclass ({@link Autolinker.match.Match}) for more details. - */ -Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { - - /** - * @cfg {String} twitterHandle (required) - * - * The Twitter handle that was matched, without the '@' character. - */ - - - /** - * @constructor - * @param {Object} cfg The configuration properties for the Match - * instance, specified in an Object (map). - */ - constructor : function( cfg) { - Autolinker.match.Match.prototype.constructor.call( this, cfg ); - - // @if DEBUG - if( !cfg.twitterHandle ) throw new Error( '`twitterHandle` cfg required' ); - // @endif - - this.twitterHandle = cfg.twitterHandle; - }, - - - /** - * Returns the type of match that this class represents. - * - * @return {String} - */ - getType : function() { - return 'twitter'; - }, - - - /** - * Returns the twitter handle, without the '@' character. - * - * @return {String} - */ - getTwitterHandle : function() { - return this.twitterHandle; - }, - - - /** - * Returns the anchor href that should be generated for the match. - * - * @return {String} - */ - getAnchorHref : function() { - return 'https://twitter.com/' + this.twitterHandle; - }, - - - /** - * Returns the anchor text that should be generated for the match. - * - * @return {String} - */ - getAnchorText : function() { - return '@' + this.twitterHandle; - } - -} ); \ No newline at end of file diff --git a/src/matcher/Twitter.js b/src/matcher/Mention.js similarity index 53% rename from src/matcher/Twitter.js rename to src/matcher/Mention.js index d18565bb..061d8dce 100644 --- a/src/matcher/Twitter.js +++ b/src/matcher/Mention.js @@ -1,21 +1,24 @@ /*global Autolinker */ /** - * @class Autolinker.matcher.Twitter + * @class Autolinker.matcher.Mention * @extends Autolinker.matcher.Matcher * * Matcher to find/replace username matches in an input string. */ -Autolinker.matcher.Twitter = Autolinker.Util.extend( Autolinker.matcher.Matcher, { +Autolinker.matcher.Mention = Autolinker.Util.extend( Autolinker.matcher.Matcher, { /** - * The regular expression to match username handles. Example match: + * Hash of regular expression to match username handles. Example match: * * @asdf * * @private - * @property {RegExp} matcherRegex + * @property {Object} matcherRegexes */ - matcherRegex : new RegExp( '@[_' + Autolinker.RegexLib.alphaNumericCharsStr + ']{1,20}', 'g' ), + matcherRegexes : { + "twitter": new RegExp( '@[_' + Autolinker.RegexLib.alphaNumericCharsStr + ']{1,20}', 'g' ), + "instagram": new RegExp( '@[_.' + Autolinker.RegexLib.alphaNumericCharsStr + ']{1,50}', 'g' ) + }, /** * The regular expression to use to check the character before a username match to @@ -29,16 +32,33 @@ Autolinker.matcher.Twitter = Autolinker.Util.extend( Autolinker.matcher.Matcher, nonWordCharRegex : new RegExp( '[^' + Autolinker.RegexLib.alphaNumericCharsStr + ']' ), + /** + * @constructor + * @param {Object} cfg The configuration properties for the Match instance, + * specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.matcher.Matcher.prototype.constructor.call( this, cfg ); + + this.serviceName = cfg.serviceName; + }, + + /** * @inheritdoc */ parseMatches : function( text ) { - var matcherRegex = this.matcherRegex, + var matcherRegex = this.matcherRegexes[this.serviceName], nonWordCharRegex = this.nonWordCharRegex, + serviceName = this.serviceName, tagBuilder = this.tagBuilder, matches = [], match; + if (!matcherRegex) { + return matches; + } + while( ( match = matcherRegex.exec( text ) ) !== null ) { var offset = match.index, prevChar = text.charAt( offset - 1 ); @@ -47,14 +67,15 @@ Autolinker.matcher.Twitter = Autolinker.Util.extend( Autolinker.matcher.Matcher, // and there is a whitespace char in front of it (meaning it is not an email // address), then it is a username match. if( offset === 0 || nonWordCharRegex.test( prevChar ) ) { - var matchedText = match[ 0 ], - twitterHandle = match[ 0 ].slice( 1 ); // strip off the '@' character at the beginning + var matchedText = match[ 0 ].replace(/\.+$/g, ''), // strip off trailing . + mention = matchedText.slice( 1 ); // strip off the '@' character at the beginning - matches.push( new Autolinker.match.Twitter( { + matches.push( new Autolinker.match.Mention( { tagBuilder : tagBuilder, matchedText : matchedText, offset : offset, - twitterHandle : twitterHandle + serviceName : serviceName, + mention : mention } ) ); } } @@ -62,4 +83,4 @@ Autolinker.matcher.Twitter = Autolinker.Util.extend( Autolinker.matcher.Matcher, return matches; } -} ); \ No newline at end of file +} ); diff --git a/tests/AutolinkerSpec.js b/tests/AutolinkerSpec.js index 70fa7b10..00922f38 100644 --- a/tests/AutolinkerSpec.js +++ b/tests/AutolinkerSpec.js @@ -1,4 +1,4 @@ -/*global Autolinker, _, describe, beforeEach, afterEach, it, expect */ +/*global Autolinker, _, jasmine, describe, beforeEach, afterEach, it, expect */ describe( "Autolinker", function() { describe( "instantiating and using as a class", function() { @@ -36,22 +36,67 @@ describe( "Autolinker", function() { } ); - it( "should not throw for a valid service name", function() { + it( "should not throw for the valid service name 'twitter'", function() { expect( function() { new Autolinker( { hashtag: 'twitter' } ); } ).not.toThrow(); } ); + + it( "should not throw for the valid service name 'facebook'", function() { + expect( function() { + new Autolinker( { hashtag: 'facebook' } ); + } ).not.toThrow(); + } ); + + + it( "should not throw for the valid service name 'instagram'", function() { + expect( function() { + new Autolinker( { hashtag: 'instagram' } ); + } ).not.toThrow(); + } ); + + } ); + + + describe( '`mention` cfg', function() { + + it( "should throw if `mention` is a value other than `false` or one of the valid service names", function() { + expect( function() { + new Autolinker( { mention: true } ); // `true` is an invalid value - must be a service name + } ).toThrowError( "invalid `mention` cfg - see docs" ); + + expect( function() { + new Autolinker( { mention: 'non-existent-service' } ); + } ).toThrowError( "invalid `mention` cfg - see docs" ); + } ); + + + it( "should not throw for the valid service name 'twitter'", function() { + expect( function() { + new Autolinker( { mention: 'twitter' } ); + } ).not.toThrow(); + } ); + + + it( "should not throw for the valid service name 'instagram'", function() { + expect( function() { + new Autolinker( { mention: 'instagram' } ); + } ).not.toThrow(); + } ); + } ); } ); describe( "link() method", function() { - var autolinker; + var autolinker, + twitterAutolinker; beforeEach( function() { autolinker = new Autolinker( { newWindow: false } ); // so that target="_blank" is not added to resulting autolinked URLs + twitterAutolinker = new Autolinker( { mention: 'twitter', newWindow: false } ); } ); @@ -879,64 +924,64 @@ describe( "Autolinker", function() { } ); - describe( "twitter handle linking", function() { + describe( "mention linking", function() { it( "should automatically link a twitter handle which is the only thing in the string", function() { - var result = autolinker.link( "@joe" ); + var result = twitterAutolinker.link( "@joe" ); expect( result ).toBe( '@joe' ); } ); it( "should automatically link a twitter handle with underscores in it", function() { - var result = autolinker.link( "@joe_the_man12" ); + var result = twitterAutolinker.link( "@joe_the_man12" ); expect( result ).toBe( '@joe_the_man12' ); } ); it( "should automatically link twitter handles at the beginning of a string", function() { - var result = autolinker.link( "@greg is my twitter handle" ); + var result = twitterAutolinker.link( "@greg is my twitter handle" ); expect( result ).toBe( '@greg is my twitter handle' ); } ); it( "should automatically link twitter handles in the middle of a string", function() { - var result = autolinker.link( "Joe's twitter is @joe_the_man12 today, but what will it be tomorrow?" ); + var result = twitterAutolinker.link( "Joe's twitter is @joe_the_man12 today, but what will it be tomorrow?" ); expect( result ).toBe( 'Joe\'s twitter is @joe_the_man12 today, but what will it be tomorrow?' ); } ); it( "should automatically link twitter handles at the end of a string", function() { - var result = autolinker.link( "Joe's twitter is @joe_the_man12" ); + var result = twitterAutolinker.link( "Joe's twitter is @joe_the_man12" ); expect( result ).toBe( 'Joe\'s twitter is @joe_the_man12' ); } ); it( "should automatically link twitter handles surrounded by parentheses", function() { - var result = autolinker.link( "Joe's twitter is (@joe_the_man12)" ); + var result = twitterAutolinker.link( "Joe's twitter is (@joe_the_man12)" ); expect( result ).toBe( 'Joe\'s twitter is (@joe_the_man12)' ); } ); it( "should automatically link twitter handles surrounded by braces", function() { - var result = autolinker.link( "Joe's twitter is {@joe_the_man12}" ); + var result = twitterAutolinker.link( "Joe's twitter is {@joe_the_man12}" ); expect( result ).toBe( 'Joe\'s twitter is {@joe_the_man12}' ); } ); it( "should automatically link twitter handles surrounded by brackets", function() { - var result = autolinker.link( "Joe's twitter is [@joe_the_man12]" ); + var result = twitterAutolinker.link( "Joe's twitter is [@joe_the_man12]" ); expect( result ).toBe( 'Joe\'s twitter is [@joe_the_man12]' ); } ); it( "should automatically link multiple twitter handles in a string", function() { - var result = autolinker.link( "@greg is tweeting @joe with @josh" ); + var result = twitterAutolinker.link( "@greg is tweeting @joe with @josh" ); expect( result ).toBe( '@greg is tweeting @joe with @josh' ); } ); it( "should automatically link fully capitalized twitter handles", function() { - var result = autolinker.link( "@GREG is tweeting @JOE with @JOSH" ); + var result = twitterAutolinker.link( "@GREG is tweeting @JOE with @JOSH" ); expect( result ).toBe( '@GREG is tweeting @JOE with @JOSH' ); } ); @@ -944,7 +989,7 @@ describe( "Autolinker", function() { // NOTE: Twitter itself does not accept cyrillic characters, but // other services might so linking them anyway it( "should automatically link username handles with accented characters", function() { - var result = autolinker.link( "Hello @mañana how are you?" ); + var result = twitterAutolinker.link( "Hello @mañana how are you?" ); expect( result ).toBe( 'Hello @mañana how are you?' ); } ); @@ -952,14 +997,18 @@ describe( "Autolinker", function() { // NOTE: Twitter itself does not accept cyrillic characters, but // other services might so linking them anyway it( "should automatically link username handles with cyrillic characters", function() { - var result = autolinker.link( "Hello @Кириллица how are you?" ); + var result = twitterAutolinker.link( "Hello @Кириллица how are you?" ); expect( result ).toBe( 'Hello @Кириллица how are you?' ); } ); it( "should NOT automatically link a username that is actually part of an email address **when email address linking is turned off**", function() { - var noUsernameAutolinker = new Autolinker( { email: false, twitter: true, newWindow: false } ), - result = noUsernameAutolinker.link( "asdf@asdf.com" ); + var noEmailAutolinker = new Autolinker( { + email: false, + mention: 'twitter', + newWindow: false + } ); + var result = noEmailAutolinker.link( "asdf@asdf.com" ); expect( result ).toBe( 'asdf@asdf.com' ); } ); @@ -1118,6 +1167,112 @@ describe( "Autolinker", function() { } ); + describe( "mention linking", function() { + var twitterMentionAutolinker, + instagramMentionAutolinker; + + beforeEach( function() { + twitterMentionAutolinker = new Autolinker( { mention: 'twitter', newWindow: false } ); + instagramMentionAutolinker = new Autolinker( { mention: 'instagram', newWindow: false } ); + } ); + + + it( "should not autolink mentions by default", function() { + var autolinker = new Autolinker( { newWindow: false } ); + expect( autolinker.link( "@test" ) ).toBe( '@test' ); + } ); + + + it( "should automatically link mentions to twitter when the `mention` option is 'twitter'", function() { + var result = twitterMentionAutolinker.link( "@test" ); + + expect( result ).toBe( '@test' ); + } ); + + + it( "should automatically link mentions to instagram when the `mention` option is 'instagram'", function() { + var result = instagramMentionAutolinker.link( "@test" ); + + expect( result ).toBe( '@test' ); + } ); + + + it( "should automatically link mentions which are part of a full string", function() { + var result = twitterMentionAutolinker.link( "my handle is @test these days" ); + + expect( result ).toBe( 'my handle is @test these days' ); + } ); + + + it( "should automatically link a mention with underscores", function() { + var result = twitterMentionAutolinker.link( "Yay, @hello_world" ); + expect( result ).toBe( 'Yay, @hello_world' ); + } ); + + + it( "should automatically link mentions surrounded by parentheses", function() { + var result = twitterMentionAutolinker.link( "Joe's twitter is (@joe_the_man12)" ); + expect( result ).toBe( 'Joe\'s twitter is (@joe_the_man12)' ); + } ); + + + it( "should automatically link mentions surrounded by braces", function() { + var result = twitterMentionAutolinker.link( "Joe's twitter is {@joe_the_man12}" ); + expect( result ).toBe( 'Joe\'s twitter is {@joe_the_man12}' ); + } ); + + + it( "should automatically link mentions surrounded by brackets", function() { + var result = twitterMentionAutolinker.link( "Joe's twitter is [@joe_the_man12]" ); + expect( result ).toBe( 'Joe\'s twitter is [@joe_the_man12]' ); + } ); + + + it( "should automatically link multiple mentions in a string", function() { + var result = twitterMentionAutolinker.link( "@greg is tweeting @joe with @josh" ); + expect( result ).toBe( '@greg is tweeting @joe with @josh' ); + } ); + + + it( "should automatically link fully capitalized mentions", function() { + var result = twitterMentionAutolinker.link( "@GREG is tweeting @JOE with @JOSH" ); + expect( result ).toBe( '@GREG is tweeting @JOE with @JOSH' ); + } ); + + + // NOTE: Twitter itself does not accept cyrillic characters, but + // other services might so linking them anyway + it( "should automatically link mentions with accented characters", function() { + var result = twitterMentionAutolinker.link( "Hello @mañana how are you?" ); + expect( result ).toBe( 'Hello @mañana how are you?' ); + } ); + + + // NOTE: Twitter itself does not accept cyrillic characters, but + // other services might so linking them anyway + it( "should automatically link username handles with cyrillic characters", function() { + var result = twitterMentionAutolinker.link( "Hello @Кириллица how are you?" ); + expect( result ).toBe( 'Hello @Кириллица how are you?' ); + } ); + + + it( "should NOT automatically link a mention that is actually part of an email address **when email address linking is turned off**", function() { + var noUsernameAutolinker = new Autolinker( { email: false, mention: 'twitter', newWindow: false } ), + result = noUsernameAutolinker.link( "asdf@asdf.com" ); + + expect( result ).toBe( 'asdf@asdf.com' ); + } ); + + + it( "should NOT automatically link a mention when the '@' belongs to part of another string", function() { + var result = twitterMentionAutolinker.link( "test as@df test" ); + + expect( result ).toBe( 'test as@df test' ); + } ); + + } ); + + describe( "proper handling of HTML in the input string", function() { it( "should automatically link URLs past the last HTML tag", function() { @@ -1376,12 +1531,12 @@ describe( "Autolinker", function() { } ); - it( "should properly skip over attribute values that could be interpreted as urls/emails/twitter accts, while still autolinking urls in their inner text", function() { - var html = '
google.com asdf@asdf.com @asdf
'; + it( "should properly skip over attribute values that could be interpreted as urls/emails/twitter/mention accts, while still autolinking urls in their inner text", function() { + var html = '
google.com asdf@asdf.com @asdf
'; - var result = autolinker.link( html ); + var result = twitterAutolinker.link( html ); expect( result ).toBe( [ - '
', + '
', 'google.com ', 'asdf@asdf.com ', '@asdf', @@ -1393,7 +1548,7 @@ describe( "Autolinker", function() { it( "should properly skip over attribute names and values that could be interpreted as urls/emails/twitter accts, while still autolinking urls in their inner text", function() { var html = '
google.com asdf@asdf.com @asdf
'; - var result = autolinker.link( html ); + var result = twitterAutolinker.link( html ); expect( result ).toBe( [ '
', 'google.com ', @@ -1662,8 +1817,16 @@ describe( "Autolinker", function() { it( "should add className to twitter links", function() { - var result = Autolinker.link( "hi from @iggypopschest", { newWindow: false, twitter: true, className: 'myLink' } ); - expect( result ).toBe( 'hi from @iggypopschest' ); + var result = Autolinker.link( "hi from @iggypopschest", { newWindow: false, mention: 'twitter', className: 'myLink' } ); + expect( result ).toBe( 'hi from @iggypopschest' ); + } ); + + it( "should add className to mention links", function() { + var result = Autolinker.link( "hi from @iggypopschest", { newWindow: false, mention: 'twitter', className: 'myLink' } ); + expect( result ).toBe( 'hi from @iggypopschest' ); + + result = Autolinker.link( "hi from @iggypopschest", { newWindow: false, mention: 'instagram', className: 'myLink' } ); + expect( result ).toBe( 'hi from @iggypopschest' ); } ); } ); @@ -1757,83 +1920,157 @@ describe( "Autolinker", function() { } ); - describe( "`urls` (as boolean), `email`, `phone`, and `twitter` options", function() { + describe( "`urls` (as boolean), `email`, `phone`, `twitter`, and `mention` options", function() { var inputStr = [ "Website: asdf.com", "Email: asdf@asdf.com", "Phone: (123) 456-7890", - "Twitter: @asdf", + "Mention: @asdf", "Hashtag: #asdf" ].join( ", " ); - it( "should link all 5 types if all 5 urls/email/phone/twitter/hashtag options are enabled", function() { - var result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter' } ); + it( "should link all 5 types if all 5 urls/email/phone/mention/hashtag options are enabled", function() { + var result = Autolinker.link( inputStr, { + hashtag: 'twitter', + mention: 'twitter', + newWindow: false + } ); + expect( result ).toBe( [ + 'Website: asdf.com', + 'Email: asdf@asdf.com', + 'Phone: (123) 456-7890', + 'Mention: @asdf', + 'Hashtag: #asdf' + ].join( ", " ) ); + } ); + + + it( "should link mentions based on value provided to mention option", function() { + var result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter', mention: 'twitter' } ); + expect( result ).toBe( [ + 'Website: asdf.com', + 'Email: asdf@asdf.com', + 'Phone: (123) 456-7890', + 'Mention: @asdf', + 'Hashtag: #asdf' + ].join( ", " ) ); + + result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter', mention: 'instagram' } ); + expect( result ).toBe( [ + 'Website: asdf.com', + 'Email: asdf@asdf.com', + 'Phone: (123) 456-7890', + 'Mention: @asdf', + 'Hashtag: #asdf' + ].join( ", " ) ); + } ); + + + it( "should ignore twitter option if mention option is set", function() { + var result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter', twitter: false, mention: 'twitter' } ); + expect( result ).toBe( [ + 'Website: asdf.com', + 'Email: asdf@asdf.com', + 'Phone: (123) 456-7890', + 'Mention: @asdf', + 'Hashtag: #asdf' + ].join( ", " ) ); + + result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter', twitter: true, mention: 'instagram' } ); expect( result ).toBe( [ 'Website: asdf.com', 'Email: asdf@asdf.com', 'Phone: (123) 456-7890', - 'Twitter: @asdf', + 'Mention: @asdf', 'Hashtag: #asdf' ].join( ", " ) ); } ); it( "should not link urls when they are disabled", function() { - var result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter', urls: false } ); + var result = Autolinker.link( inputStr, { + mention: 'twitter', + hashtag: 'twitter', + urls: false, + newWindow: false + } ); + expect( result ).toBe( [ 'Website: asdf.com', 'Email: asdf@asdf.com', 'Phone: (123) 456-7890', - 'Twitter: @asdf', + 'Mention: @asdf', 'Hashtag: #asdf' ].join( ", " ) ); } ); it( "should not link email addresses when they are disabled", function() { - var result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter', email: false } ); + var result = Autolinker.link( inputStr, { + mention: 'twitter', + hashtag: 'twitter', + email: false, + newWindow: false + } ); + expect( result ).toBe( [ 'Website: asdf.com', 'Email: asdf@asdf.com', 'Phone: (123) 456-7890', - 'Twitter: @asdf', + 'Mention: @asdf', 'Hashtag: #asdf' ].join( ", " ) ); } ); it( "should not link phone numbers when they are disabled", function() { - var result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter', phone: false } ); + var result = Autolinker.link( inputStr, { + hashtag : 'twitter', + mention : 'twitter', + phone : false, + newWindow : false + } ); + expect( result ).toBe( [ 'Website: asdf.com', 'Email: asdf@asdf.com', 'Phone: (123) 456-7890', - 'Twitter: @asdf', + 'Mention: @asdf', 'Hashtag: #asdf' ].join( ", " ) ); } ); - it( "should not link Twitter handles when they are disabled", function() { - var result = Autolinker.link( inputStr, { newWindow: false, hashtag: 'twitter', twitter: false } ); + it( "should not link mention handles when they are disabled", function() { + var result = Autolinker.link( inputStr, { + newWindow: false, + hashtag: 'twitter', + mention: false + } ); + expect( result ).toBe( [ 'Website: asdf.com', 'Email: asdf@asdf.com', 'Phone: (123) 456-7890', - 'Twitter: @asdf', + 'Mention: @asdf', 'Hashtag: #asdf' ].join( ", " ) ); } ); it( "should not link Hashtags when they are disabled", function() { - var result = Autolinker.link( inputStr, { newWindow: false, hashtag: false } ); + var result = Autolinker.link( inputStr, { + mention : 'twitter', + hashtag : false, + newWindow : false + } ); + expect( result ).toBe( [ 'Website: asdf.com', 'Email: asdf@asdf.com', 'Phone: (123) 456-7890', - 'Twitter: @asdf', + 'Mention: @asdf', 'Hashtag: #asdf' ].join( ", " ) ); } ); @@ -1843,13 +2080,46 @@ describe( "Autolinker", function() { describe( "`replaceFn` option", function() { var returnTrueFn = function() { return true; }, - returnFalseFn = function() { return false; }; + returnFalseFn = function() { return false; }, + replaceFnSpy; + + beforeEach( function() { + replaceFnSpy = jasmine.createSpy( 'replaceFnSpy' ); + } ); + + + it( "by default, should be called with with the `Autolinker` instance " + + "as the context object (`this` reference)", + function() { + var replaceFnAutolinker = new Autolinker( { + replaceFn: replaceFnSpy + } ); + replaceFnAutolinker.link( "asdf.com" ); // will call the `replaceFn` + + expect( replaceFnSpy ).toHaveBeenCalled(); + expect( replaceFnSpy.calls.first().object ).toBe( replaceFnAutolinker ); + } ); + + + it( "when provided a `context` option, should be called with with " + + "that object as the context object (`this` reference)", + function() { + var contextObj = { prop: 'value' }; + var replaceFnAutolinker = new Autolinker( { + replaceFn : replaceFnSpy, + context : contextObj + } ); + replaceFnAutolinker.link( "asdf.com" ); // will call the `replaceFn` + + expect( replaceFnSpy ).toHaveBeenCalled(); + expect( replaceFnSpy.calls.first().object ).toBe( contextObj ); + } ); it( "should populate a UrlMatch object with the appropriate properties", function() { var replaceFnCallCount = 0; var result = Autolinker.link( "Website: asdf.com ", { // purposeful trailing space - replaceFn : function( autolinker, match ) { + replaceFn : function( match ) { replaceFnCallCount++; expect( match.getMatchedText() ).toBe( 'asdf.com' ); @@ -1864,7 +2134,7 @@ describe( "Autolinker", function() { it( "should populate an EmailMatch object with the appropriate properties", function() { var replaceFnCallCount = 0; var result = Autolinker.link( "Email: asdf@asdf.com ", { // purposeful trailing space - replaceFn : function( autolinker, match ) { + replaceFn : function( match ) { replaceFnCallCount++; expect( match.getMatchedText() ).toBe( 'asdf@asdf.com' ); @@ -1880,7 +2150,7 @@ describe( "Autolinker", function() { var replaceFnCallCount = 0; var result = Autolinker.link( "Hashtag: #myHashtag ", { // purposeful trailing space hashtag: 'twitter', - replaceFn : function( autolinker, match ) { + replaceFn : function( match ) { replaceFnCallCount++; expect( match.getType() ).toBe( 'hashtag' ); @@ -1896,11 +2166,28 @@ describe( "Autolinker", function() { it( "should populate a TwitterMatch object with the appropriate properties", function() { var replaceFnCallCount = 0; var result = Autolinker.link( "Twitter: @myTwitter ", { // purposeful trailing space - replaceFn : function( autolinker, match ) { + mention: 'twitter', + replaceFn : function( match ) { + replaceFnCallCount++; + + expect( match.getMatchedText() ).toBe( '@myTwitter' ); + expect( match.getMention() ).toBe( 'myTwitter' ); + } + } ); + + expect( replaceFnCallCount ).toBe( 1 ); // make sure the replaceFn was called + } ); + + + it( "should populate a MentionMatch object with the appropriate properties", function() { + var replaceFnCallCount = 0; + var result = Autolinker.link( "Mention: @myTwitter ", { // purposeful trailing space + mention: 'twitter', + replaceFn : function( match ) { replaceFnCallCount++; expect( match.getMatchedText() ).toBe( '@myTwitter' ); - expect( match.getTwitterHandle() ).toBe( 'myTwitter' ); + expect( match.getMention() ).toBe( 'myTwitter' ); } } ); @@ -1910,6 +2197,7 @@ describe( "Autolinker", function() { it( "should replace the match as Autolinker would normally do when `true` is returned from the `replaceFn`", function() { var result = Autolinker.link( "Website: asdf.com, Email: asdf@asdf.com, Twitter: @asdf", { + mention : 'twitter', newWindow : false, // just to suppress the target="_blank" from the output for this test replaceFn : returnTrueFn } ); @@ -1924,6 +2212,7 @@ describe( "Autolinker", function() { it( "should replace the match as Autolinker would normally do when there is no return value (i.e. `undefined` is returned) from the `replaceFn`", function() { var result = Autolinker.link( "Website: asdf.com, Email: asdf@asdf.com, Twitter: @asdf", { + mention : 'twitter', newWindow : false, // just to suppress the target="_blank" from the output for this test replaceFn : function() {} // no return value (`undefined` is returned) } ); @@ -1938,6 +2227,7 @@ describe( "Autolinker", function() { it( "should leave the match as-is when `false` is returned from the `replaceFn`", function() { var result = Autolinker.link( "Website: asdf.com, Email: asdf@asdf.com, Twitter: @asdf", { + mention : 'twitter', replaceFn : returnFalseFn } ); @@ -1951,6 +2241,7 @@ describe( "Autolinker", function() { it( "should use a string returned from the `replaceFn` as the HTML that is replaced in the input", function() { var result = Autolinker.link( "Website: asdf.com, Email: asdf@asdf.com, Twitter: @asdf", { + mention : 'twitter', replaceFn : function() { return "test"; } } ); @@ -1962,7 +2253,7 @@ describe( "Autolinker", function() { var result = Autolinker.link( "Website: asdf.com", { newWindow : false, - replaceFn : function( autolinker, match ) { + replaceFn : function( match ) { var tag = match.buildTag(); tag.setInnerHtml( 'asdf!' ); // just to check that we're replacing with the returned `tag` instance return tag; @@ -1977,7 +2268,7 @@ describe( "Autolinker", function() { var result = Autolinker.link( "Website: asdf.com", { newWindow : false, - replaceFn : function( autolinker, match ) { + replaceFn : function( match ) { var tag = match.buildTag(); tag.addClass( 'test' ); tag.addClass( 'test2' ); @@ -2006,7 +2297,7 @@ describe( "Autolinker", function() { var result = Autolinker.link( "@asdf", { replaceFn: returnFalseFn } ); expect( result ).toBe( "@asdf" ); - var result2 = Autolinker.link( "Twitter: @asdf", { replaceFn: returnFalseFn } ); + var result2 = Autolinker.link( "Twitter: @asdf", { mention: 'twitter', replaceFn: returnFalseFn } ); expect( result2 ).toBe( "Twitter: @asdf" ); } ); @@ -2053,7 +2344,7 @@ describe( "Autolinker", function() { unlinked : 'asdf@asdf.com', linked : 'asdf@asdf.com' }, - twitter : { + mention : { unlinked : '@asdf', linked : '@asdf' }, @@ -2074,7 +2365,7 @@ describe( "Autolinker", function() { 'Check link 2: <%= wwwUrl %>.', 'Check link 3: <%= tldUrl %>.', 'My email is: <%= email %>.', - 'My twitter username is <%= twitter %>.', + 'My mention (twitter) username is <%= mention %>.', 'My phone number is <%= phone %>.', 'Hashtag <%= hashtag %>.' ].join( '\n' ) ); @@ -2084,7 +2375,7 @@ describe( "Autolinker", function() { wwwUrl : testCases.wwwUrl.unlinked, tldUrl : testCases.tldUrl.unlinked, email : testCases.email.unlinked, - twitter : testCases.twitter.unlinked, + mention : testCases.mention.unlinked, phone : testCases.phone.unlinked, hashtag : testCases.hashtag.unlinked } ); @@ -2102,7 +2393,7 @@ describe( "Autolinker", function() { wwwMatches : !!( i & parseInt( '00000010', 2 ) ), tldMatches : !!( i & parseInt( '00000100', 2 ) ), email : !!( i & parseInt( '00001000', 2 ) ), - twitter : !!( i & parseInt( '00010000', 2 ) ), + mention : !!( i & parseInt( '00010000', 2 ) ) ? 'twitter' : false, phone : !!( i & parseInt( '00100000', 2 ) ), hashtag : !!( i & parseInt( '01000000', 2 ) ) ? 'twitter' : false }; @@ -2114,7 +2405,7 @@ describe( "Autolinker", function() { tldMatches : cfg.tldMatches }, email : cfg.email, - twitter : cfg.twitter, + mention : cfg.mention, phone : cfg.phone, hashtag : cfg.hashtag, @@ -2143,7 +2434,7 @@ describe( "Autolinker", function() { wwwUrl : cfg.wwwMatches ? testCases.wwwUrl.linked : testCases.wwwUrl.unlinked, tldUrl : cfg.tldMatches ? testCases.tldUrl.linked : testCases.tldUrl.unlinked, email : cfg.email ? testCases.email.linked : testCases.email.unlinked, - twitter : cfg.twitter ? testCases.twitter.linked : testCases.twitter.unlinked, + mention : cfg.mention ? testCases.mention.linked : testCases.mention.unlinked, phone : cfg.phone ? testCases.phone.linked : testCases.phone.unlinked, hashtag : cfg.hashtag ? testCases.hashtag.linked : testCases.hashtag.unlinked } ); diff --git a/tests/index.html b/tests/index.html index 9780740d..edf8a876 100644 --- a/tests/index.html +++ b/tests/index.html @@ -26,13 +26,13 @@ - + - + @@ -50,7 +50,7 @@ - + diff --git a/tests/match/MatchChecker.js b/tests/match/MatchChecker.js index 416432ab..ee657ce6 100644 --- a/tests/match/MatchChecker.js +++ b/tests/match/MatchChecker.js @@ -59,21 +59,24 @@ Autolinker.match.MatchChecker = { expect( match.getNumber() ).toBe( number ); expect( match.getOffset() ).toBe( offset ); }, - + /** - * Expects a {@link Autolinker.match.Twitter Twitter} match. + * Expects a {@link Autolinker.match.Mention Mention} match. * - * @param {Autolinker.match.Twitter} match The Match object to check. - * @param {String} twitterHandle The Twitter handle to expect, without the + * @param {Autolinker.match.Mention} match The Match object to check. + * @param {String} serviceName The service name to expect of where to direct + * clicks to the mention to. Ex: 'twitter', 'instagram'. + * @param {String} mention The mention to expect, without the * prefixed '@' character. * @param {Number} offset The offset for the match in the original string to * expect. */ - expectTwitterMatch : function( match, twitterHandle, offset ) { - this.expectMatchType( match, 'Twitter' ); + expectMentionMatch : function( match, serviceName, mention, offset ) { + this.expectMatchType( match, 'Mention' ); - expect( match.getTwitterHandle() ).toBe( twitterHandle ); + expect( match.getServiceName() ).toBe( serviceName ); + expect( match.getMention() ).toBe( mention ); expect( match.getOffset() ).toBe( offset ); }, @@ -113,4 +116,4 @@ Autolinker.match.MatchChecker = { } } -}; \ No newline at end of file +}; diff --git a/tests/matcher/TwitterSpec.js b/tests/matcher/MentionSpec.js similarity index 54% rename from tests/matcher/TwitterSpec.js rename to tests/matcher/MentionSpec.js index 8127c9cc..9d341e13 100644 --- a/tests/matcher/TwitterSpec.js +++ b/tests/matcher/MentionSpec.js @@ -1,11 +1,12 @@ /*global Autolinker, _, describe, beforeEach, afterEach, it, expect, jasmine */ -describe( "Autolinker.matcher.Twitter", function() { +describe( "Autolinker.matcher.Mention", function() { var MatchChecker = Autolinker.match.MatchChecker, matcher; beforeEach( function() { - matcher = new Autolinker.matcher.Twitter( { - tagBuilder : new Autolinker.AnchorTagBuilder() + matcher = new Autolinker.matcher.Mention( { + tagBuilder : new Autolinker.AnchorTagBuilder(), + serviceName: 'twitter' } ); } ); @@ -24,7 +25,7 @@ describe( "Autolinker.matcher.Twitter", function() { var matches = matcher.parseMatches( '@asdf' ); expect( matches.length ).toBe( 1 ); - MatchChecker.expectTwitterMatch( matches[ 0 ], 'asdf', 0 ); + MatchChecker.expectMentionMatch( matches[ 0 ], 'twitter', 'asdf', 0 ); } ); @@ -32,7 +33,7 @@ describe( "Autolinker.matcher.Twitter", function() { var matches = matcher.parseMatches( 'Hello @asdf my good friend' ); expect( matches.length ).toBe( 1 ); - MatchChecker.expectTwitterMatch( matches[ 0 ], 'asdf', 6 ); + MatchChecker.expectMentionMatch( matches[ 0 ], 'twitter', 'asdf', 6 ); } ); @@ -40,7 +41,7 @@ describe( "Autolinker.matcher.Twitter", function() { var matches = matcher.parseMatches( 'Hello @asdf' ); expect( matches.length ).toBe( 1 ); - MatchChecker.expectTwitterMatch( matches[ 0 ], 'asdf', 6 ); + MatchChecker.expectMentionMatch( matches[ 0 ], 'twitter', 'asdf', 6 ); } ); @@ -48,8 +49,8 @@ describe( "Autolinker.matcher.Twitter", function() { var matches = matcher.parseMatches( 'Talk to @asdf or @fdsa' ); expect( matches.length ).toBe( 2 ); - MatchChecker.expectTwitterMatch( matches[ 0 ], 'asdf', 8 ); - MatchChecker.expectTwitterMatch( matches[ 1 ], 'fdsa', 17 ); + MatchChecker.expectMentionMatch( matches[ 0 ], 'twitter', 'asdf', 8 ); + MatchChecker.expectMentionMatch( matches[ 1 ], 'twitter', 'fdsa', 17 ); } ); @@ -57,9 +58,33 @@ describe( "Autolinker.matcher.Twitter", function() { var matches = matcher.parseMatches( 'Hello (@asdf)' ); expect( matches.length ).toBe( 1 ); - MatchChecker.expectTwitterMatch( matches[ 0 ], 'asdf', 7 ); + MatchChecker.expectMentionMatch( matches[ 0 ], 'twitter', 'asdf', 7 ); + } ); + + + it( 'an Instagram username with period not at boundaries should be parsed correctly', function() { + var instagramMatcher = new Autolinker.matcher.Mention( { + tagBuilder : new Autolinker.AnchorTagBuilder(), + serviceName: 'instagram' + } ); + var matches = instagramMatcher.parseMatches( 'Hello (@as.df)' ); + + expect( matches.length ).toBe( 1 ); + MatchChecker.expectMentionMatch( matches[ 0 ], 'instagram', 'as.df', 7 ); + } ); + + + it( 'an Instagram username with period at end of string should ignore period', function() { + var instagramMatcher = new Autolinker.matcher.Mention( { + tagBuilder : new Autolinker.AnchorTagBuilder(), + serviceName: 'instagram' + } ); + var matches = instagramMatcher.parseMatches( 'Hello (@asdf.)' ); + + expect( matches.length ).toBe( 1 ); + MatchChecker.expectMentionMatch( matches[ 0 ], 'instagram', 'asdf', 7 ); } ); } ); -} ); \ No newline at end of file +} );