cesium
Version:
Cesium is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,508 lines (1,307 loc) • 95.1 kB
JavaScript
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module unless amdModuleId is set
define([], function () {
return (root['Autolinker'] = factory());
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
root['Autolinker'] = factory();
}
}(this, function () {
/*!
* Autolinker.js
* 0.17.1
*
* Copyright(c) 2015 Gregory Jacobs <greg@greg-jacobs.com>
* MIT Licensed. http://www.opensource.org/licenses/mit-license.php
*
* https://github.com/gregjacobs/Autolinker.js
*/
/**
* @class Autolinker
* @extends Object
*
* Utility class used to process a given string of text, and wrap the matches in
* the appropriate anchor (<a>) tags to turn them into links.
*
* Any of the configuration options may be provided in an Object (map) provided
* to the Autolinker constructor, which will configure how the {@link #link link()}
* method will process the links.
*
* For example:
*
* var autolinker = new Autolinker( {
* newWindow : false,
* truncate : 30
* } );
*
* var html = autolinker.link( "Joe went to www.yahoo.com" );
* // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
*
*
* The {@link #static-link static link()} method may also be used to inline options into a single call, which may
* be more convenient for one-off uses. For example:
*
* var html = Autolinker.link( "Joe went to www.yahoo.com", {
* newWindow : false,
* truncate : 30
* } );
* // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
*
*
* ## Custom Replacements of Links
*
* 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.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, Phone #s, Twitter Handles, and Hashtags
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( autolinker, match ) {
* console.log( "href = ", match.getAnchorHref() );
* console.log( "text = ", match.getAnchorText() );
*
* switch( match.getType() ) {
* case 'url' :
* 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
* tag.setAttr( 'rel', 'nofollow' );
* tag.addClass( 'external-link' );
*
* return tag;
*
* } else {
* return true; // let Autolinker perform its normal anchor tag replacement
* }
*
* case 'email' :
* var email = match.getEmail();
* console.log( "email: ", email );
*
* if( email === "my@own.address" ) {
* return false; // don't auto-link this particular email address; leave as-is
* } else {
* return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
* }
*
* case 'phone' :
* var phoneNumber = match.getPhoneNumber();
* console.log( phoneNumber );
*
* return '<a href="http://newplace.to.link.phone.numbers.to/">' + phoneNumber + '</a>';
*
* case 'twitter' :
* var twitterHandle = match.getTwitterHandle();
* console.log( twitterHandle );
*
* return '<a href="http://newplace.to.link.twitter.handles.to/">' + twitterHandle + '</a>';
*
* case 'hashtag' :
* var hashtag = match.getHashtag();
* console.log( hashtag );
*
* return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
* }
* }
* } );
*
*
* The function may return the following values:
*
* - `true` (Boolean): Allow Autolinker to replace the match as it normally would.
* - `false` (Boolean): Do not replace the current match at all - leave as-is.
* - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for
* the match.
* - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text.
*
* @constructor
* @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map).
*/
var Autolinker = function( cfg ) {
Autolinker.Util.assign( this, cfg ); // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs.
// Validate the value of the `hashtag` cfg.
var hashtag = this.hashtag;
if( hashtag !== false && hashtag !== 'twitter' && hashtag !== 'facebook' ) {
throw new Error( "invalid `hashtag` cfg - see docs" );
}
};
Autolinker.prototype = {
constructor : Autolinker, // fix constructor property
/**
* @cfg {Boolean} urls
*
* `true` if miscellaneous URLs should be automatically linked, `false` if they should not be.
*/
urls : true,
/**
* @cfg {Boolean} email
*
* `true` if email addresses should be automatically linked, `false` if they should not be.
*/
email : true,
/**
* @cfg {Boolean} twitter
*
* `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be.
*/
twitter : true,
/**
* @cfg {Boolean} phone
*
* `true` if Phone numbers ("(555)555-5555") should be automatically linked, `false` if they should not be.
*/
phone: true,
/**
* @cfg {Boolean/String} hashtag
*
* A string for the service name to have hashtags (ex: "#myHashtag")
* auto-linked to. The currently-supported values are:
*
* - 'twitter'
* - 'facebook'
*
* Pass `false` to skip auto-linking of hashtags.
*/
hashtag : false,
/**
* @cfg {Boolean} newWindow
*
* `true` if the links should open in a new window, `false` otherwise.
*/
newWindow : true,
/**
* @cfg {Boolean} stripPrefix
*
* `true` if 'http://' or 'https://' and/or the 'www.' should be stripped
* from the beginning of URL links' text, `false` otherwise.
*/
stripPrefix : true,
/**
* @cfg {Number} truncate
*
* A number for how many characters long matched text should be truncated to inside the text of
* a link. If the matched text is over this number of characters, it will be truncated to this length by
* adding a two period ellipsis ('..') to the end of the string.
*
* For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look
* something like this: 'yahoo.com/some/long/pat..'
*/
truncate : undefined,
/**
* @cfg {String} className
*
* 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.
*
* 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"
*/
className : "",
/**
* @cfg {Function} replaceFn
*
* A function to individually process each match found in the input string.
*
* See the class's description for usage.
*
* This function is called with the following parameters:
*
* @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.
*/
/**
* @private
* @property {Autolinker.htmlParser.HtmlParser} htmlParser
*
* The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated
* in the {@link #getHtmlParser} method.
*/
htmlParser : undefined,
/**
* @private
* @property {Autolinker.matchParser.MatchParser} matchParser
*
* The MatchParser instance used to find matches in the text nodes of an input string passed to
* {@link #link}. This is lazily instantiated in the {@link #getMatchParser} method.
*/
matchParser : undefined,
/**
* @private
* @property {Autolinker.AnchorTagBuilder} tagBuilder
*
* The AnchorTagBuilder instance used to build match replacement anchor tags. Note: this is lazily instantiated
* in the {@link #getTagBuilder} method.
*/
tagBuilder : undefined,
/**
* Automatically links URLs, Email addresses, Phone numbers, Twitter
* handles, and Hashtags 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`,
* then the result will be `You should go to
* <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
*
* This method finds the text around any HTML elements in the input
* `textOrHtml`, which will be the text that is processed. Any original HTML
* elements will be left as-is, as well as the text that is already wrapped
* 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).
* @return {String} The HTML, with matches automatically linked.
*/
link : function( textOrHtml ) {
var htmlParser = this.getHtmlParser(),
htmlNodes = htmlParser.parse( textOrHtml ),
anchorTagStackCount = 0, // used to only process text around anchor tags, and any inner text/html they may have
resultHtml = [];
for( var i = 0, len = htmlNodes.length; i < len; i++ ) {
var node = htmlNodes[ i ],
nodeType = node.getType(),
nodeText = node.getText();
if( nodeType === 'element' ) {
// Process HTML nodes in the input `textOrHtml`
if( node.getTagName() === 'a' ) {
if( !node.isClosing() ) { // it's the start <a> tag
anchorTagStackCount++;
} else { // it's the end </a> tag
anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
}
}
resultHtml.push( nodeText ); // now add the text of the tag itself verbatim
} else if( nodeType === 'entity' || nodeType === 'comment' ) {
resultHtml.push( nodeText ); // append HTML entity nodes (such as ' ') or HTML comments (such as '<!-- Comment -->') verbatim
} else {
// Process text nodes in the input `textOrHtml`
if( anchorTagStackCount === 0 ) {
// If we're not within an <a> tag, process the text node to linkify
var linkifiedStr = this.linkifyStr( nodeText );
resultHtml.push( linkifiedStr );
} else {
// `text` is within an <a> tag, simply append the text - we do not want to autolink anything
// already within an <a>...</a> tag
resultHtml.push( nodeText );
}
}
}
return resultHtml.join( "" );
},
/**
* Process the text that lies in between HTML tags, performing the anchor
* tag replacements for the matches, and returns the string with the
* replacements made.
*
* This method does the actual wrapping of matches with anchor tags.
*
* @private
* @param {String} str The string of text to auto-link.
* @return {String} The text with anchor tags auto-filled.
*/
linkifyStr : function( str ) {
return this.getMatchParser().replace( str, this.createMatchReturnVal, this );
},
/**
* Creates the return string value for a given match in the input string,
* for the {@link #linkifyStr} method.
*
* This method handles the {@link #replaceFn}, if one was provided.
*
* @private
* @param {Autolinker.match.Match} match The Match object that represents the match.
* @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but
* may be the `matchStr` itself if the match is not to be replaced.
*/
createMatchReturnVal : function( match ) {
// 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
}
if( typeof replaceFnResult === 'string' ) {
return replaceFnResult; // `replaceFn` returned a string, use that
} else if( replaceFnResult === false ) {
return match.getMatchedText(); // no replacement for the match
} else if( replaceFnResult instanceof Autolinker.HtmlTag ) {
return replaceFnResult.toAnchorString();
} else { // replaceFnResult === true, or no/unknown return value from function
// Perform Autolinker's default anchor tag generation
var tagBuilder = this.getTagBuilder(),
anchorTag = tagBuilder.build( match ); // returns an Autolinker.HtmlTag instance
return anchorTag.toAnchorString();
}
},
/**
* Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance.
*
* @protected
* @return {Autolinker.htmlParser.HtmlParser}
*/
getHtmlParser : function() {
var htmlParser = this.htmlParser;
if( !htmlParser ) {
htmlParser = this.htmlParser = new Autolinker.htmlParser.HtmlParser();
}
return htmlParser;
},
/**
* Lazily instantiates and returns the {@link #matchParser} instance for this Autolinker instance.
*
* @protected
* @return {Autolinker.matchParser.MatchParser}
*/
getMatchParser : function() {
var matchParser = this.matchParser;
if( !matchParser ) {
matchParser = this.matchParser = new Autolinker.matchParser.MatchParser( {
urls : this.urls,
email : this.email,
twitter : this.twitter,
phone : this.phone,
hashtag : this.hashtag,
stripPrefix : this.stripPrefix
} );
}
return matchParser;
},
/**
* Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it
* if it does not yet exist.
*
* This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that
* 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
* tag.setAttr( 'rel', 'nofollow' );
*
* return tag;
* }
* } );
*
* // generated html:
* // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
*
* @return {Autolinker.AnchorTagBuilder}
*/
getTagBuilder : function() {
var tagBuilder = this.tagBuilder;
if( !tagBuilder ) {
tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( {
newWindow : this.newWindow,
truncate : this.truncate,
className : this.className
} );
}
return tagBuilder;
}
};
/**
* Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
* and Hashtags 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`,
* then the result will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
*
* Example:
*
* var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
* // Produces: "Go to <a href="http://google.com">google.com</a>"
*
* @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).
* @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.
* @return {String} The HTML text, with matches automatically linked.
*/
Autolinker.link = function( textOrHtml, options ) {
var autolinker = new Autolinker( options );
return autolinker.link( textOrHtml );
};
// Autolinker Namespaces
Autolinker.match = {};
Autolinker.htmlParser = {};
Autolinker.matchParser = {};
/*global Autolinker */
/*jshint eqnull:true, boss:true */
/**
* @class Autolinker.Util
* @singleton
*
* A few utility methods for Autolinker.
*/
Autolinker.Util = {
/**
* @property {Function} abstractMethod
*
* A function object which represents an abstract method.
*/
abstractMethod : function() { throw "abstract"; },
/**
* @private
* @property {RegExp} trimRegex
*
* The regular expression used to trim the leading and trailing whitespace
* from a string.
*/
trimRegex : /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
/**
* Assigns (shallow copies) the properties of `src` onto `dest`.
*
* @param {Object} dest The destination object.
* @param {Object} src The source object.
* @return {Object} The destination object (`dest`)
*/
assign : function( dest, src ) {
for( var prop in src ) {
if( src.hasOwnProperty( prop ) ) {
dest[ prop ] = src[ prop ];
}
}
return dest;
},
/**
* Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype.
*
* @param {Function} superclass The constructor function for the superclass.
* @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the
* special property `constructor`, which will be used as the new subclass's constructor function.
* @return {Function} The new subclass function.
*/
extend : function( superclass, protoProps ) {
var superclassProto = superclass.prototype;
var F = function() {};
F.prototype = superclassProto;
var subclass;
if( protoProps.hasOwnProperty( 'constructor' ) ) {
subclass = protoProps.constructor;
} else {
subclass = function() { superclassProto.constructor.apply( this, arguments ); };
}
var subclassProto = subclass.prototype = new F(); // set up prototype chain
subclassProto.constructor = subclass; // fix constructor property
subclassProto.superclass = superclassProto;
delete protoProps.constructor; // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there
Autolinker.Util.assign( subclassProto, protoProps );
return subclass;
},
/**
* Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the
* end of the string (by default, two periods: '..'). If the `str` length does not exceed
* `len`, the string will be returned unchanged.
*
* @param {String} str The string to truncate and add an ellipsis to.
* @param {Number} truncateLen The length to truncate the string at.
* @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str`
* when truncated. Defaults to '..'
*/
ellipsis : function( str, truncateLen, ellipsisChars ) {
if( str.length > truncateLen ) {
ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars;
str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars;
}
return str;
},
/**
* Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below).
*
* @param {Array} arr The array to find an element of.
* @param {*} element The element to find in the array, and return the index of.
* @return {Number} The index of the `element`, or -1 if it was not found.
*/
indexOf : function( arr, element ) {
if( Array.prototype.indexOf ) {
return arr.indexOf( element );
} else {
for( var i = 0, len = arr.length; i < len; i++ ) {
if( arr[ i ] === element ) return i;
}
return -1;
}
},
/**
* Performs the functionality of what modern browsers do when `String.prototype.split()` is called
* with a regular expression that contains capturing parenthesis.
*
* For example:
*
* // Modern browsers:
* "a,b,c".split( /(,)/ ); // --> [ 'a', ',', 'b', ',', 'c' ]
*
* // Old IE (including IE8):
* "a,b,c".split( /(,)/ ); // --> [ 'a', 'b', 'c' ]
*
* This method emulates the functionality of modern browsers for the old IE case.
*
* @param {String} str The string to split.
* @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting
* character(s) will be spliced into the array, as in the "modern browsers" example in the
* description of this method.
* Note #1: the supplied regular expression **must** have the 'g' flag specified.
* Note #2: for simplicity's sake, the regular expression does not need
* to contain capturing parenthesis - it will be assumed that any match has them.
* @return {String[]} The split array of strings, with the splitting character(s) included.
*/
splitAndCapture : function( str, splitRegex ) {
if( !splitRegex.global ) throw new Error( "`splitRegex` must have the 'g' flag set" );
var result = [],
lastIdx = 0,
match;
while( match = splitRegex.exec( str ) ) {
result.push( str.substring( lastIdx, match.index ) );
result.push( match[ 0 ] ); // push the splitting char(s)
lastIdx = match.index + match[ 0 ].length;
}
result.push( str.substring( lastIdx ) );
return result;
},
/**
* Trims the leading and trailing whitespace from a string.
*
* @param {String} str The string to trim.
* @return {String}
*/
trim : function( str ) {
return str.replace( this.trimRegex, '' );
}
};
/*global Autolinker */
/*jshint boss:true */
/**
* @class Autolinker.HtmlTag
* @extends Object
*
* Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically.
*
* Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use
* this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}.
*
* ## Examples
*
* Example instantiation:
*
* var tag = new Autolinker.HtmlTag( {
* tagName : 'a',
* attrs : { 'href': 'http://google.com', 'class': 'external-link' },
* innerHtml : 'Google'
* } );
*
* tag.toAnchorString(); // <a href="http://google.com" class="external-link">Google</a>
*
* // Individual accessor methods
* tag.getTagName(); // 'a'
* tag.getAttr( 'href' ); // 'http://google.com'
* tag.hasClass( 'external-link' ); // true
*
*
* Using mutator methods (which may be used in combination with instantiation config properties):
*
* var tag = new Autolinker.HtmlTag();
* tag.setTagName( 'a' );
* tag.setAttr( 'href', 'http://google.com' );
* tag.addClass( 'external-link' );
* tag.setInnerHtml( 'Google' );
*
* tag.getTagName(); // 'a'
* tag.getAttr( 'href' ); // 'http://google.com'
* tag.hasClass( 'external-link' ); // true
*
* tag.toAnchorString(); // <a href="http://google.com" class="external-link">Google</a>
*
*
* ## Example use within a {@link Autolinker#replaceFn replaceFn}
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( autolinker, match ) {
* var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text
* tag.setAttr( 'rel', 'nofollow' );
*
* return tag;
* }
* } );
*
* // generated html:
* // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
*
*
* ## Example use with a new tag for the replacement
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( autolinker, match ) {
* var tag = new Autolinker.HtmlTag( {
* tagName : 'button',
* attrs : { 'title': 'Load URL: ' + match.getAnchorHref() },
* innerHtml : 'Load URL: ' + match.getAnchorText()
* } );
*
* return tag;
* }
* } );
*
* // generated html:
* // Test <button title="Load URL: http://google.com">Load URL: google.com</button>
*/
Autolinker.HtmlTag = Autolinker.Util.extend( Object, {
/**
* @cfg {String} tagName
*
* The tag name. Ex: 'a', 'button', etc.
*
* Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString}
* is executed.
*/
/**
* @cfg {Object.<String, String>} attrs
*
* An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the
* values are the attribute values.
*/
/**
* @cfg {String} innerHtml
*
* The inner HTML for the tag.
*
* Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym
* naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML}
* if you prefer, but this one is recommended.
*/
/**
* @cfg {String} innerHTML
*
* Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version
* for acronym names.
*/
/**
* @protected
* @property {RegExp} whitespaceRegex
*
* Regular expression used to match whitespace in a string of CSS classes.
*/
whitespaceRegex : /\s+/,
/**
* @constructor
* @param {Object} [cfg] The configuration properties for this class, in an Object (map)
*/
constructor : function( cfg ) {
Autolinker.Util.assign( this, cfg );
this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym
},
/**
* Sets the tag name that will be used to generate the tag with.
*
* @param {String} tagName
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setTagName : function( tagName ) {
this.tagName = tagName;
return this;
},
/**
* Retrieves the tag name.
*
* @return {String}
*/
getTagName : function() {
return this.tagName || "";
},
/**
* Sets an attribute on the HtmlTag.
*
* @param {String} attrName The attribute name to set.
* @param {String} attrValue The attribute value to set.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setAttr : function( attrName, attrValue ) {
var tagAttrs = this.getAttrs();
tagAttrs[ attrName ] = attrValue;
return this;
},
/**
* Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`.
*
* @param {String} name The attribute name to retrieve.
* @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.
*/
getAttr : function( attrName ) {
return this.getAttrs()[ attrName ];
},
/**
* Sets one or more attributes on the HtmlTag.
*
* @param {Object.<String, String>} attrs A key/value Object (map) of the attributes to set.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setAttrs : function( attrs ) {
var tagAttrs = this.getAttrs();
Autolinker.Util.assign( tagAttrs, attrs );
return this;
},
/**
* Retrieves the attributes Object (map) for the HtmlTag.
*
* @return {Object.<String, String>} A key/value object of the attributes for the HtmlTag.
*/
getAttrs : function() {
return this.attrs || ( this.attrs = {} );
},
/**
* Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag.
*
* @param {String} cssClass One or more space-separated CSS classes to set (overwrite).
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setClass : function( cssClass ) {
return this.setAttr( 'class', cssClass );
},
/**
* Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes.
*
* @param {String} cssClass One or more space-separated CSS classes to add.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
addClass : function( cssClass ) {
var classAttr = this.getClass(),
whitespaceRegex = this.whitespaceRegex,
indexOf = Autolinker.Util.indexOf, // to support IE8 and below
classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ),
newClasses = cssClass.split( whitespaceRegex ),
newClass;
while( newClass = newClasses.shift() ) {
if( indexOf( classes, newClass ) === -1 ) {
classes.push( newClass );
}
}
this.getAttrs()[ 'class' ] = classes.join( " " );
return this;
},
/**
* Convenience method to remove one or more CSS classes from the HtmlTag.
*
* @param {String} cssClass One or more space-separated CSS classes to remove.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
removeClass : function( cssClass ) {
var classAttr = this.getClass(),
whitespaceRegex = this.whitespaceRegex,
indexOf = Autolinker.Util.indexOf, // to support IE8 and below
classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ),
removeClasses = cssClass.split( whitespaceRegex ),
removeClass;
while( classes.length && ( removeClass = removeClasses.shift() ) ) {
var idx = indexOf( classes, removeClass );
if( idx !== -1 ) {
classes.splice( idx, 1 );
}
}
this.getAttrs()[ 'class' ] = classes.join( " " );
return this;
},
/**
* Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when
* there are multiple.
*
* @return {String}
*/
getClass : function() {
return this.getAttrs()[ 'class' ] || "";
},
/**
* Convenience method to check if the tag has a CSS class or not.
*
* @param {String} cssClass The CSS class to check for.
* @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise.
*/
hasClass : function( cssClass ) {
return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1;
},
/**
* Sets the inner HTML for the tag.
*
* @param {String} html The inner HTML to set.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setInnerHtml : function( html ) {
this.innerHtml = html;
return this;
},
/**
* Retrieves the inner HTML for the tag.
*
* @return {String}
*/
getInnerHtml : function() {
return this.innerHtml || "";
},
/**
* Override of superclass method used to generate the HTML string for the tag.
*
* @return {String}
*/
toAnchorString : function() {
var tagName = this.getTagName(),
attrsStr = this.buildAttrsStr();
attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes
return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '</', tagName, '>' ].join( "" );
},
/**
* Support method for {@link #toAnchorString}, returns the string space-separated key="value" pairs, used to populate
* the stringified HtmlTag.
*
* @protected
* @return {String} Example return: `attr1="value1" attr2="value2"`
*/
buildAttrsStr : function() {
if( !this.attrs ) return ""; // no `attrs` Object (map) has been set, return empty string
var attrs = this.getAttrs(),
attrsArr = [];
for( var prop in attrs ) {
if( attrs.hasOwnProperty( prop ) ) {
attrsArr.push( prop + '="' + attrs[ prop ] + '"' );
}
}
return attrsArr.join( " " );
}
} );
/*global Autolinker */
/*jshint sub:true */
/**
* @protected
* @class Autolinker.AnchorTagBuilder
* @extends Object
*
* Builds anchor (<a>) tags for the Autolinker utility when a match is 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:
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( autolinker, match ) {
* var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance
* tag.setAttr( 'rel', 'nofollow' );
*
* return tag;
* }
* } );
*
* // generated html:
* // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
*/
Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, {
/**
* @cfg {Boolean} newWindow
* @inheritdoc Autolinker#newWindow
*/
/**
* @cfg {Number} truncate
* @inheritdoc Autolinker#truncate
*/
/**
* @cfg {String} className
* @inheritdoc Autolinker#className
*/
/**
* @constructor
* @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
*/
constructor : function( cfg ) {
Autolinker.Util.assign( this, cfg );
},
/**
* Generates the actual anchor (<a>) tag to use in place of the
* matched text, via its `match` object.
*
* @param {Autolinker.match.Match} match The Match instance to generate an
* anchor tag from.
* @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag.
*/
build : function( match ) {
var tag = new Autolinker.HtmlTag( {
tagName : 'a',
attrs : this.createAttrs( match.getType(), match.getAnchorHref() ),
innerHtml : this.processAnchorText( match.getAnchorText() )
} );
return tag;
},
/**
* Creates the Object (map) of the HTML attributes for the anchor (<a>)
* 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} href The href for the anchor tag.
* @return {Object} A key/value Object (map) of the anchor tag's attributes.
*/
createAttrs : function( matchType, anchorHref ) {
var attrs = {
'href' : anchorHref // we'll always have the `href` attribute
};
var cssClass = this.createCssClass( matchType );
if( cssClass ) {
attrs[ 'class' ] = cssClass;
}
if( this.newWindow ) {
attrs[ 'target' ] = "_blank";
}
return attrs;
},
/**
* Creates the CSS class that will be used for a given anchor tag, based on
* the `matchType` and the {@link #className} config.
*
* @private
* @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of
* match that an anchor tag is being generated for.
* @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 ) {
var className = this.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"
},
/**
* Processes the `anchorText` by truncating the text according to the
* {@link #truncate} config.
*
* @private
* @param {String} anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return {String} The processed `anchorText`.
*/
processAnchorText : function( anchorText ) {
anchorText = this.doTruncate( anchorText );
return anchorText;
},
/**
* Performs the truncation of the `anchorText`, if the `anchorText` is
* longer than the {@link #truncate} option. Truncates the text to 2
* characters fewer than the {@link #truncate} option, and adds ".." to the
* end.
*
* @private
* @param {String} text The anchor tag's text (i.e. what will be displayed).
* @return {String} The truncated anchor text.
*/
doTruncate : function( anchorText ) {
return Autolinker.Util.ellipsis( anchorText, this.truncate || Number.POSITIVE_INFINITY );
}
} );
/*global Autolinker */
/**
* @private
* @class Autolinker.htmlParser.HtmlParser
* @extends 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
* around" HTML tags.
*/
Autolinker.htmlParser.HtmlParser = Autolinker.Util.extend( Object, {
/**
* @private
* @property {RegExp} htmlRegex
*
* The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and
* attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html.
*
* Capturing groups:
*
* 1. The "!DOCTYPE" tag name, if a tag is a <!DOCTYPE> tag.
* 2. If it is an end tag, this group will have the '/'.
* 3. If it is a comment tag, this group will hold the comment text (i.e.
* the text inside the `<!--` and `-->`.
* 4. The tag name for all tags (other than the <!DOCTYPE> tag)
*/
htmlRegex : (function() {
var commentTagRegex = /!--([\s\S]+?)--/,
tagNameRegex = /[0-9a-zA-Z][0-9a-zA-Z:]*/,
attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char
attrValueRegex = /(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/, // double quoted, single quoted, or unquoted attribute values
nameEqualsValueRegex = attrNameRegex.source + '(?:\\s*=\\s*' + attrValueRegex.source + ')?'; // optional '=[value]'
return new RegExp( [
// for <!DOCTYPE> tag. Ex: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">)
'(?:',
'<(!DOCTYPE)', // *** Capturing Group 1 - If it's a doctype tag
// Zero or more attributes following the tag name
'(?:',
'\\s+', // one or more whitespace chars before an attribute
// Either:
// A. attr="value", or
// B. "value" alone (To cover example doctype tag: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">)
'(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')',
')*',
'>',
')',
'|',
// All other HTML tags (i.e. tags that are not <!DOCTYPE>)
'(?:',
'<(/)?', // Beginning of a tag or comment. Either '<' for a start tag, or '</' for an end tag.
// *** Capturing Group 2: The slash or an empty string. Slash ('/') for end tag, empty string for start or self-closing tag.
'(?:',
commentTagRegex.source, // *** Capturing Group 3 - A Comment Tag's Text
'|',
'(?:',
// *** Capturing Group 4 - The tag name
'(' + tagNameRegex.source + ')',
// Zero or more attributes following the tag name
'(?:',
'\\s+', // one or more whitespace chars before an attribute
nameEqualsValueRegex, // attr="value" (with optional ="value" part)
')*',
'\\s*/?', // any trailing spaces and optional '/' before the closing '>'
')',
')',
'>',
')'
].join( "" ), 'gi' );
} )(),
/**
* @private
* @property {RegExp} htmlCharacterEntitiesRegex
*
* The regular expression that matches common HTML character entities.
*
* Ignoring & as it could be part of a query string -- handling it separately.
*/
htmlCharacterEntitiesRegex: /( | |<|<|>|>|"|"|')/gi,
/**
* Parses an HTML string and returns a simple array of {@link Autolinker.htmlParser.HtmlNode HtmlNodes}
* to represent the HTML structure of the input string.
*
* @param {String} html The HTML to parse.
* @return {Autolinker.htmlParser.HtmlNode[]}
*/
parse : function( html ) {
var htmlRegex = this.htmlRegex,
currentResult,
lastIndex = 0,
textAndEntityNodes,
nodes = []; // will be the result of the method
while( ( currentResult = htmlRegex.exec( html ) ) !== null ) {
var tagText = currentResult[ 0 ],
commentText = currentResult[ 3 ], // if we've matched a comment
tagName = currentResult[ 1 ] || currentResult[ 4 ], // The <!DOCTYPE> tag (ex: "!DOCTYPE"), or another tag (ex: "a" or "img")
isClosingTag = !!currentResult[ 2 ],
inBetweenTagsText = html.substring( lastIndex, currentResult.index );
// Push TextNodes and EntityNodes for any text found between tags
if( inBetweenTagsText ) {
textAndEntityNodes = this.parseTextAndEntityNodes( inBetweenTagsText );
nodes.push.apply( nodes, textAndEntityNodes );
}
// Push the CommentNode or ElementNode
if( commentText ) {
nodes.push( this.createCommentNode( tagText, commentText ) );
} else {
nodes.push( this.createElementNode( tagText, tagName, isClosingTag ) );
}
lastIndex = currentResult.index + tagText.length;
}
// Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements.
if( lastIndex < html.length ) {
var text = html.substring( lastIndex );
// Push TextNodes and EntityNodes for any text found between tags
if( text ) {
textAndEntityNodes = this.parseTextAndEntityNodes( text );
nodes.push.apply( nodes, textAndEntityNodes );
}
}
return nodes;
},
/**
* Parses text and HTML entity nodes from a given string. The input string
* should not have any HTML tags (elements) within it.
*
* @private
* @param {String} text The text to parse.
* @return {Autolinker.htmlParser.HtmlNode[]} An array of HtmlNodes to
* represent the {@link Autolinker.htmlParser.TextNode TextNodes} and
* {@link Autolinker.htmlParser.EntityNode EntityNodes} found.
*/
parseTextAndEntityNodes : function( text ) {
var nodes = [],
textAndEntityTokens = Autolinker.Util.splitAndCapture( text, this.htmlCharacterEntitiesRegex ); // split at HTML entities, but include the HTML entities in the results array
// Every even numbered token is a TextNode, and every odd numbered token is an EntityNode
// For example: an input `text` of "Test "this" today" would turn into the
// `textAndEntityTokens`: [ 'Test ', '"', 'this', '"', ' today' ]
for( var i = 0, len = textAndEntityTokens.length; i < len; i += 2 ) {
var textToken = textAndEntityTokens[ i ],
entityToken = textAndEntityTokens[ i + 1 ];
if( textToken ) nodes.push( this.createTextNode( textToken ) );
if( entityToken ) nodes.push( this.createEntityNode( entityToken ) );
}
return nodes;
},
/**
* Factory method to create an {@link Autolinker.htmlParser.CommentNode CommentNode}.
*
* @private
* @param {String} tagText The full text of the tag (comment) that was
* matched, including its <!-- and -->.
* @param {String} comment The full text of the comment that was matched.
*/
createCommentNode : function( tagText, commentText ) {
return new Autolinker.htmlParser.CommentNode( {
text: tagText,
comment: Autolinker.Util.trim( commentText )
} );
},
/**
* Factory method to create an {@link Autolinker.htmlParser.ElementNode ElementNode}.
*
* @private
* @param {String} tagText The full text of the tag (element) that was
* matched, including its attributes.
* @param {String} tagName The name of the tag. Ex: An <img> tag would
* be passed to this method as "img".
* @param {Boolean} isClosingTag `true` if it's a closing tag, false
* otherwise.
* @return {Autolinker.htmlParser.ElementNode}
*/
createElementNode : function( tagText, tagName, isClosingTag ) {
return new Autolinker.htmlParser.ElementNode( {
text : tagText,
tagName : tagName.toLowerCase(),
closing : isClosingTag
} );
},
/**
* Factory method to create a {@link Autolinker.htmlParser.EntityNode EntityNode}.
*
* @private
* @param {String} text The text that was matched for the HTML entity (such
* as '&nbsp;').
* @return {Autolinker.htmlParser.EntityNode}
*/
createEntityNode : function( text ) {
return new Autolinker.htmlParser.EntityNode( { text: text } );
},
/**
* Factory method to create a {@link Autolinker.htmlParser.TextNode TextNode}.
*
* @private
* @param {String} text The text that was matched.
* @return {Autolinker.htmlParser.TextNode}
*/
createTextNode : function( text ) {
return new Autolinker.htmlParser.TextNode( { text: text } );
}
} );
/*global Autolinker */
/**
* @abstract
* @class Autolinker.htmlParser.HtmlNode
*
* Represents an HTML node found in an input string. An HTML node is one of the following:
*
* 1. An {@link Autolinker.htmlParser.ElementNode ElementNode}, which represents HTML tags.
* 2. A {@link Autolinker.htmlParser.TextNode TextNode}, which represents text outside or within HTML tags.
* 3. A {@link Autolinker.htmlParser.EntityNode EntityNode}, which represents one of the known HTML
* entities that Autolinker looks for. This includes common ones such as &quot; and &nbsp;
*/
Autolinker.htmlParser.HtmlNode = Autolinker.Util.extend( Object, {
/**
* @cfg {String} text (required)
*
* The original text that was matched for the HtmlNode.
*
* - In the case of an {@link Autolinker.htmlParser.ElementNode ElementNode}, this will be the tag's
* text.
* - In the case of a {@link Autolinker.htmlParser.TextNode TextNode}, this will be the text itself.
* - In the case of a {@link Autolinker.htmlParser.EntityNode EntityNode}, this will be the text of
* the HTML entity.
*/
text : "",
/**
* @constructor
* @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map).
*/
constructor : function( cfg ) {
Autolinker.Util.assign( this, cfg );
},
/**
* Returns a string name for the type of node that this class represents.
*
* @abstract
* @return {String}
*/
getType : Autolinker.Util.abstractMethod,
/**
* Retrieves the {@link #text} for the HtmlNode.
*
* @return {String}
*/
getText : function() {
return this.text;
}
} );
/*global Autolinker */
/**
* @class Autolinker.htmlParser.CommentNode
* @extends Autolinker.htmlParser.HtmlNode
*
* Represents an HTML comment node that has been parsed by the
* {@link Autolinker.htmlParser.HtmlParser}.
*
* See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more
* details.
*/
Autolinker.htmlParser.CommentNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {
/**
* @cfg {String} comment (required)
*
* The text inside the comment tag. This text is stripped of any leading or
* trailing whitespace.
*/
comment : '',
/**
* Returns a string name for the type of node that this class represents.
*
* @return {String}
*/
getType : function() {
return 'comment';
},
/**
* Returns the comment inside the comment tag.
*
* @return {String}
*/
getComment : function() {
return this.comment;
}
} );
/*global Autolinker */
/**
* @class Autolinker.htmlParser.ElementNode
* @exten