Add existing to tracked

This commit is contained in:
Jay
2026-08-11 09:53:42 -04:00
parent afe07f3055
commit ffd6e3d73c
8531 changed files with 4396230 additions and 0 deletions
+4885
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+118
View File
@@ -0,0 +1,118 @@
import { HtmlTag } from './html-tag';
import { TruncateConfigObj } from './autolinker';
import { AbstractMatch } from './match/abstract-match';
/**
* @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 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(match) {
* var tag = match.buildTag(); // 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>
*/
export declare class AnchorTagBuilder {
/**
* @cfg {Boolean} newWindow
* @inheritdoc Autolinker#newWindow
*/
private readonly newWindow;
/**
* @cfg {Object} truncate
* @inheritdoc Autolinker#truncate
*/
private readonly truncate;
/**
* @cfg {String} className
* @inheritdoc Autolinker#className
*/
private readonly className;
/**
* @method constructor
* @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
*/
constructor(cfg?: AnchorTagBuilderCfg);
/**
* Generates the actual anchor (&lt;a&gt;) tag to use in place of the
* matched text, via its `match` object.
*
* @param match The Match instance to generate an anchor tag from.
* @return The HtmlTag instance for the anchor tag.
*/
build(match: AbstractMatch): HtmlTag;
/**
* Creates the Object (map) of the HTML attributes for the anchor (&lt;a&gt;)
* tag being generated.
*
* @protected
* @param match The Match instance to generate an anchor tag from.
* @return A key/value Object (map) of the anchor tag's attributes.
*/
protected createAttrs(match: AbstractMatch): {
[attrName: string]: string;
};
/**
* 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
*
* @protected
* @param match The Match instance to generate an
* anchor tag from.
* @return The CSS class string for the link. Example return:
* "myLink myLink-url". If no {@link #className} was configured, returns
* an empty string.
*/
protected createCssClass(match: AbstractMatch): string;
/**
* Processes the `anchorText` by truncating the text according to the
* {@link #truncate} config.
*
* @private
* @param anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return The processed `anchorText`.
*/
private processAnchorText;
/**
* Performs the truncation of the `anchorText` based on the {@link #truncate}
* option. If the `anchorText` is longer than the length specified by the
* {@link #truncate} option, the truncation is performed based on the
* `location` property. See {@link #truncate} for details.
*
* @private
* @param anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return The truncated anchor text.
*/
private doTruncate;
}
export interface AnchorTagBuilderCfg {
newWindow?: boolean;
truncate?: TruncateConfigObj;
className?: string;
}
+174
View File
@@ -0,0 +1,174 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnchorTagBuilder = void 0;
var html_tag_1 = require("./html-tag");
var truncate_smart_1 = require("./truncate/truncate-smart");
var truncate_middle_1 = require("./truncate/truncate-middle");
var truncate_end_1 = require("./truncate/truncate-end");
/**
* @protected
* @class Autolinker.AnchorTagBuilder
* @extends Object
*
* Builds anchor (&lt;a&gt;) 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 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(match) {
* var tag = match.buildTag(); // 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>
*/
var AnchorTagBuilder = /** @class */ (function () {
/**
* @method constructor
* @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
*/
function AnchorTagBuilder(cfg) {
if (cfg === void 0) { cfg = {}; }
/**
* @cfg {Boolean} newWindow
* @inheritdoc Autolinker#newWindow
*/
this.newWindow = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Object} truncate
* @inheritdoc Autolinker#truncate
*/
this.truncate = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {String} className
* @inheritdoc Autolinker#className
*/
this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
this.newWindow = cfg.newWindow || false;
this.truncate = cfg.truncate || {};
this.className = cfg.className || '';
}
/**
* Generates the actual anchor (&lt;a&gt;) tag to use in place of the
* matched text, via its `match` object.
*
* @param match The Match instance to generate an anchor tag from.
* @return The HtmlTag instance for the anchor tag.
*/
AnchorTagBuilder.prototype.build = function (match) {
return new html_tag_1.HtmlTag({
tagName: 'a',
attrs: this.createAttrs(match),
innerHtml: this.processAnchorText(match.getAnchorText()),
});
};
/**
* Creates the Object (map) of the HTML attributes for the anchor (&lt;a&gt;)
* tag being generated.
*
* @protected
* @param match The Match instance to generate an anchor tag from.
* @return A key/value Object (map) of the anchor tag's attributes.
*/
AnchorTagBuilder.prototype.createAttrs = function (match) {
var attrs = {
href: match.getAnchorHref(), // we'll always have the `href` attribute
};
var cssClass = this.createCssClass(match);
if (cssClass) {
attrs['class'] = cssClass;
}
if (this.newWindow) {
attrs['target'] = '_blank';
attrs['rel'] = 'noopener noreferrer'; // Issue #149. See https://mathiasbynens.github.io/rel-noopener/
}
if (this.truncate.length && this.truncate.length < match.getAnchorText().length) {
attrs['title'] = match.getAnchorHref();
}
return attrs;
};
/**
* 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
*
* @protected
* @param match The Match instance to generate an
* anchor tag from.
* @return The CSS class string for the link. Example return:
* "myLink myLink-url". If no {@link #className} was configured, returns
* an empty string.
*/
AnchorTagBuilder.prototype.createCssClass = function (match) {
var className = this.className;
if (!className) {
return '';
}
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(' ');
}
};
/**
* Processes the `anchorText` by truncating the text according to the
* {@link #truncate} config.
*
* @private
* @param anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return The processed `anchorText`.
*/
AnchorTagBuilder.prototype.processAnchorText = function (anchorText) {
anchorText = this.doTruncate(anchorText);
return anchorText;
};
/**
* Performs the truncation of the `anchorText` based on the {@link #truncate}
* option. If the `anchorText` is longer than the length specified by the
* {@link #truncate} option, the truncation is performed based on the
* `location` property. See {@link #truncate} for details.
*
* @private
* @param anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return The truncated anchor text.
*/
AnchorTagBuilder.prototype.doTruncate = function (anchorText) {
var truncate = this.truncate;
if (!truncate.length)
return anchorText;
var truncateLength = truncate.length, truncateLocation = truncate.location;
if (truncateLocation === 'smart') {
return (0, truncate_smart_1.truncateSmart)(anchorText, truncateLength);
}
else if (truncateLocation === 'middle') {
return (0, truncate_middle_1.truncateMiddle)(anchorText, truncateLength);
}
else {
return (0, truncate_end_1.truncateEnd)(anchorText, truncateLength);
}
};
return AnchorTagBuilder;
}());
exports.AnchorTagBuilder = AnchorTagBuilder;
//# sourceMappingURL=anchor-tag-builder.js.map
File diff suppressed because one or more lines are too long
+608
View File
@@ -0,0 +1,608 @@
import { Match } from './match/match';
import { HtmlTag } from './html-tag';
import { MentionService } from './parser/mention-utils';
import { HashtagService } from './parser/hashtag-utils';
/**
* @class Autolinker
* @extends Object
*
* Utility class used to process a given string of text, and wrap the matches in
* the appropriate anchor (&lt;a&gt;) tags to turn them into links.
*
* Any of the configuration options may be provided in an Object 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#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
* match that is encountered.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( 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 = match.buildTag(); // 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 'hashtag' :
* var hashtag = match.getHashtag();
* console.log( hashtag );
*
* return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
*
* case 'mention' :
* var mention = match.getMention();
* console.log( mention );
*
* return '<a href="http://newplace.to.link.mention.to/">' + mention + '</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.
*/
export default class Autolinker {
/**
* @static
* @property {String} version
*
* The Autolinker version number in the form major.minor.patch
*
* Ex: 3.15.0
*/
static readonly version = "4.1.5";
/**
* Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
* 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`,
* then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* 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 #mention},
* {@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.
* @return {String} The HTML text, with matches automatically linked.
*/
static link(textOrHtml: string, options?: AutolinkerConfig): string;
/**
* Parses the input `textOrHtml` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches (without making any replacements).
*
* Note that if parsing multiple pieces of text, it is slightly more efficient
* to create an Autolinker instance, and use the instance-level {@link #parse}
* method.
*
* Example:
*
* var matches = Autolinker.parse("Hello google.com, I am asdf@asdf.com", {
* urls: true,
* email: true
* });
*
* console.log(matches.length); // 2
* console.log(matches[0].getType()); // 'url'
* console.log(matches[0].getUrl()); // 'google.com'
* console.log(matches[1].getType()); // 'email'
* console.log(matches[1].getEmail()); // 'asdf@asdf.com'
*
* @static
* @param {String} textOrHtml The HTML or text to find matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@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.
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `textOrHtml`.
*/
static parse(textOrHtml: string, options?: AutolinkerConfig): Match[];
/**
* The Autolinker version number exposed on the instance itself.
*
* Ex: 0.25.1
*
* @property {String} version
*/
readonly version = "4.1.5";
/**
* @cfg {Boolean/Object} [urls]
*
* `true` if URLs should be automatically linked, `false` if they should not
* be. Defaults to `true`.
*
* Examples:
*
* urls: true
*
* // or
*
* urls: {
* schemeMatches : true,
* tldMatches : true,
* ipV4Matches : true
* }
*
* As shown above, this option also accepts an Object form with 3 properties
* to allow for more customization of what exactly gets linked. All default
* to `true`:
*
* @cfg {Boolean} [urls.schemeMatches] `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.
* @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
* level domains (.com, .net, etc.) that are not prefixed with a scheme
* (such as 'http://'). This option attempts to match anything that looks
* like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
* `false` to prevent these types of matches.
* @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
* that are not prefixed with a scheme (such as 'http://'). This option
* attempts to match anything that looks like an IPv4 address in text. Ex:
* `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
* of matches.
*/
private readonly urls;
/**
* @cfg {Boolean} [email=true]
*
* `true` if email addresses should be automatically linked, `false` if they
* should not be.
*/
private readonly email;
/**
* @cfg {Boolean} [phone=true]
*
* `true` if Phone numbers ("(555)555-5555") should be automatically linked,
* `false` if they should not be.
*/
private readonly phone;
/**
* @cfg {Boolean/String} [hashtag=false]
*
* A string for the service name to have hashtags (ex: "#myHashtag")
* auto-linked to. The currently-supported values are:
*
* - 'twitter'
* - 'facebook'
* - 'instagram'
* - 'tiktok'
* - 'youtube'
*
* Pass `false` to skip auto-linking of hashtags.
*/
private readonly hashtag;
/**
* @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'
* - 'soundcloud'
* - 'tiktok'
* - 'youtube'
*
* Defaults to `false` to skip auto-linking of mentions.
*/
private readonly mention;
/**
* @cfg {Boolean} [newWindow=true]
*
* `true` if the links should open in a new window, `false` otherwise.
*/
private readonly newWindow;
/**
* @cfg {Boolean/Object} [stripPrefix=true]
*
* `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
* from the beginning of URL links' text, `false` otherwise. Defaults to
* `true`.
*
* Examples:
*
* stripPrefix: true
*
* // or
*
* stripPrefix: {
* scheme : true,
* www : true
* }
*
* As shown above, this option also accepts an Object form with 2 properties
* to allow for more customization of what exactly is prevented from being
* displayed. Both default to `true`:
*
* @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
* a URL match from being displayed to the user. Example:
* `'http://google.com'` will be displayed as `'google.com'`. `false` to
* not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
* will be removed, so as not to remove a potentially dangerous scheme
* (such as `'file://'` or `'javascript:'`)
* @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
* `'www.'` part of a URL match from being displayed to the user. Ex:
* `'www.google.com'` will be displayed as `'google.com'`. `false` to not
* strip the `'www'`.
*/
private readonly stripPrefix;
/**
* @cfg {Boolean} [stripTrailingSlash=true]
*
* `true` to remove the trailing slash from URL matches, `false` to keep
* the trailing slash.
*
* Example when `true`: `http://google.com/` will be displayed as
* `http://google.com`.
*/
private readonly stripTrailingSlash;
/**
* @cfg {Boolean} [decodePercentEncoding=true]
*
* `true` to decode percent-encoded characters in URL matches, `false` to keep
* the percent-encoded characters.
*
* Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
* be displayed as `https://en.wikipedia.org/wiki/San_José`.
*/
private readonly decodePercentEncoding;
/**
* @cfg {Number/Object} [truncate=0]
*
* ## Number Form
*
* A number for how many characters 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..'
*
* Example Usage:
*
* truncate: 25
*
*
* Defaults to `0` for "no truncation."
*
*
* ## Object Form
*
* An Object may also be provided with two properties: `length` (Number) and
* `location` (String). `location` may be one of the following: 'end'
* (default), 'middle', or 'smart'.
*
* Example Usage:
*
* truncate: { length: 25, location: 'middle' }
*
* @cfg {Number} [truncate.length=0] How many characters to allow before
* truncation will occur. Defaults to `0` for "no truncation."
* @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
*
* - 'end' (default): will truncate up to the number of characters, and then
* add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
* - 'middle': will truncate and add the ellipsis in the middle. Ex:
* 'yahoo.com/s..th/to/a/file'
* - 'smart': for URLs where the algorithm attempts to strip out unnecessary
* parts first (such as the 'www.', then URL scheme, hash, etc.),
* attempting to make the URL human-readable before looking for a good
* point to insert the ellipsis if it is still too long. Ex:
* 'yahoo.com/some..to/a/file'. For more details, see
* {@link Autolinker.truncate.TruncateSmart}.
*/
private readonly truncate;
/**
* @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/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
* - 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-[type]"
* where [type] is either "instagram", "twitter" or "soundcloud"
*/
private readonly className;
/**
* @cfg {Function} replaceFn
*
* A function to individually process each match found in the input string.
*
* See the class's description for usage.
*
* 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.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 readonly replaceFn;
/**
* @cfg {Object} context
*
* The context object (`this` reference) to call the `replaceFn` with.
*
* Defaults to this Autolinker instance.
*/
private readonly context;
/**
* @cfg {Boolean} [sanitizeHtml=false]
*
* `true` to HTML-encode the start and end brackets of existing HTML tags found
* in the input string. This will escape `<` and `>` characters to `&lt;` and
* `&gt;`, respectively.
*
* Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
* but will remove the significance of existing HTML tags in the input string. If
* you would like to maintain the significance of existing HTML tags while also
* making the output HTML string safe, leave this option as `false` and use a
* tool like https://github.com/cure53/DOMPurify (or others) on the input string
* before running Autolinker.
*/
private readonly sanitizeHtml;
/**
* @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.
*/
private tagBuilder;
/**
* @method constructor
* @param {Object} [cfg] The configuration options for the Autolinker instance,
* specified in an Object (map).
*/
constructor(cfg?: AutolinkerConfig);
/**
* Parses the input `textOrHtml` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches (without making any replacements).
*
* This method is used by the {@link #link} method, but can also be used to
* simply do parsing of the input in order to discover what kinds of links
* there are and how many.
*
* Example usage:
*
* var autolinker = new Autolinker( {
* urls: true,
* email: true
* } );
*
* var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
*
* console.log( matches.length ); // 2
* console.log( matches[ 0 ].getType() ); // 'url'
* console.log( matches[ 0 ].getUrl() ); // 'google.com'
* console.log( matches[ 1 ].getType() ); // 'email'
* console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
*
* @param {String} textOrHtml The HTML or text to find matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@link #hashtag}, and {@link #mention} options are enabled).
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `textOrHtml`.
*/
parse(textOrHtml: string): Match[];
/**
* After we have found all matches, we need to remove matches that overlap
* with a previous match. This can happen for instance with an
* email address where the local-part of the email is also a top-level
* domain, such as in "google.com@aaa.com". In this case, the entire email
* address should be linked rather than just the 'google.com' part.
*
* @private
* @param {Autolinker.match.Match[]} matches
* @return {Autolinker.match.Match[]}
*/
private compactMatches;
/**
* Removes matches for matchers that were turned off in the options. For
* example, if {@link #hashtag hashtags} were not to be matched, we'll
* remove them from the `matches` array here.
*
* Note: we *must* use all Matchers on the input string, and then filter
* them out later. For example, if the options were `{ url: false, hashtag: true }`,
* we wouldn't want to match the text '#link' as a HashTag inside of the text
* 'google.com/#link'. The way the algorithm works is that we match the full
* URL first (which prevents the accidental HashTag match), and then we'll
* simply throw away the URL match.
*
* @private
* @param {Autolinker.match.Match[]} matches The array of matches to remove
* the unwanted matches from. Note: this array is mutated for the
* removals.
* @return {Autolinker.match.Match[]} The mutated input `matches` array.
*/
private removeUnwantedMatches;
/**
* Parses the input `text` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches.
*
* This method processes a **non-HTML string**, and is used to parse and
* match within the text nodes of an HTML string. This method is used
* internally by {@link #parse}.
*
* @private
* @param {String} text The text to find matches within (depending on if the
* {@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}
* instances, but may be omitted if calling this method publicly.
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `text`.
*/
private parseText;
/**
* Automatically links URLs, Email addresses, Phone numbers, Hashtags,
* and Mentions (Twitter, Instagram, Soundcloud) 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
* &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* 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 (&lt;a&gt;) tags.
*
* @param {String} textOrHtml The HTML or text to autolink matches within
* (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(textOrHtml: string): string;
/**
* Creates the return string value for a given match in the input string.
*
* 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.
*/
private createMatchReturnVal;
/**
* Returns the {@link #tagBuilder} instance for this Autolinker instance,
* lazily instantiating it if it does not yet exist.
*
* @private
* @return {Autolinker.AnchorTagBuilder}
*/
private getTagBuilder;
}
export interface AutolinkerConfig {
urls?: UrlsConfig;
email?: boolean;
phone?: boolean;
hashtag?: HashtagConfig;
mention?: MentionConfig;
newWindow?: boolean;
stripPrefix?: StripPrefixConfig;
stripTrailingSlash?: boolean;
truncate?: TruncateConfig;
className?: string;
replaceFn?: ReplaceFn | null;
context?: object;
sanitizeHtml?: boolean;
decodePercentEncoding?: boolean;
}
export type UrlsConfig = boolean | UrlsConfigObj;
export interface UrlsConfigObj {
schemeMatches?: boolean;
tldMatches?: boolean;
ipV4Matches?: boolean;
}
export type StripPrefixConfig = boolean | StripPrefixConfigObj;
export interface StripPrefixConfigObj {
scheme?: boolean;
www?: boolean;
}
export type TruncateConfig = number | TruncateConfigObj;
export interface TruncateConfigObj {
length?: number;
location?: 'end' | 'middle' | 'smart';
}
export type HashtagConfig = false | HashtagService;
export type MentionConfig = false | MentionService;
export type ReplaceFn = (match: Match) => ReplaceFnReturn;
export type ReplaceFnReturn = boolean | string | HtmlTag | null | undefined | void;
+900
View File
@@ -0,0 +1,900 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var version_1 = require("./version");
var utils_1 = require("./utils");
var anchor_tag_builder_1 = require("./anchor-tag-builder");
var html_tag_1 = require("./html-tag");
var parse_matches_1 = require("./parser/parse-matches");
var parse_html_1 = require("./htmlParser/parse-html");
var mention_utils_1 = require("./parser/mention-utils");
var hashtag_utils_1 = require("./parser/hashtag-utils");
/**
* @class Autolinker
* @extends Object
*
* Utility class used to process a given string of text, and wrap the matches in
* the appropriate anchor (&lt;a&gt;) tags to turn them into links.
*
* Any of the configuration options may be provided in an Object 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#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
* match that is encountered.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( 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 = match.buildTag(); // 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 'hashtag' :
* var hashtag = match.getHashtag();
* console.log( hashtag );
*
* return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
*
* case 'mention' :
* var mention = match.getMention();
* console.log( mention );
*
* return '<a href="http://newplace.to.link.mention.to/">' + mention + '</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.
*/
var Autolinker = /** @class */ (function () {
/**
* @method constructor
* @param {Object} [cfg] The configuration options for the Autolinker instance,
* specified in an Object (map).
*/
function Autolinker(cfg) {
if (cfg === void 0) { cfg = {}; }
/**
* The Autolinker version number exposed on the instance itself.
*
* Ex: 0.25.1
*
* @property {String} version
*/
this.version = Autolinker.version;
/**
* @cfg {Boolean/Object} [urls]
*
* `true` if URLs should be automatically linked, `false` if they should not
* be. Defaults to `true`.
*
* Examples:
*
* urls: true
*
* // or
*
* urls: {
* schemeMatches : true,
* tldMatches : true,
* ipV4Matches : true
* }
*
* As shown above, this option also accepts an Object form with 3 properties
* to allow for more customization of what exactly gets linked. All default
* to `true`:
*
* @cfg {Boolean} [urls.schemeMatches] `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.
* @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
* level domains (.com, .net, etc.) that are not prefixed with a scheme
* (such as 'http://'). This option attempts to match anything that looks
* like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
* `false` to prevent these types of matches.
* @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
* that are not prefixed with a scheme (such as 'http://'). This option
* attempts to match anything that looks like an IPv4 address in text. Ex:
* `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
* of matches.
*/
this.urls = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [email=true]
*
* `true` if email addresses should be automatically linked, `false` if they
* should not be.
*/
this.email = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [phone=true]
*
* `true` if Phone numbers ("(555)555-5555") should be automatically linked,
* `false` if they should not be.
*/
this.phone = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean/String} [hashtag=false]
*
* A string for the service name to have hashtags (ex: "#myHashtag")
* auto-linked to. The currently-supported values are:
*
* - 'twitter'
* - 'facebook'
* - 'instagram'
* - 'tiktok'
* - 'youtube'
*
* Pass `false` to skip auto-linking of hashtags.
*/
this.hashtag = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @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'
* - 'soundcloud'
* - 'tiktok'
* - 'youtube'
*
* Defaults to `false` to skip auto-linking of mentions.
*/
this.mention = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [newWindow=true]
*
* `true` if the links should open in a new window, `false` otherwise.
*/
this.newWindow = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean/Object} [stripPrefix=true]
*
* `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
* from the beginning of URL links' text, `false` otherwise. Defaults to
* `true`.
*
* Examples:
*
* stripPrefix: true
*
* // or
*
* stripPrefix: {
* scheme : true,
* www : true
* }
*
* As shown above, this option also accepts an Object form with 2 properties
* to allow for more customization of what exactly is prevented from being
* displayed. Both default to `true`:
*
* @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
* a URL match from being displayed to the user. Example:
* `'http://google.com'` will be displayed as `'google.com'`. `false` to
* not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
* will be removed, so as not to remove a potentially dangerous scheme
* (such as `'file://'` or `'javascript:'`)
* @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
* `'www.'` part of a URL match from being displayed to the user. Ex:
* `'www.google.com'` will be displayed as `'google.com'`. `false` to not
* strip the `'www'`.
*/
this.stripPrefix = {
scheme: true,
www: true,
}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [stripTrailingSlash=true]
*
* `true` to remove the trailing slash from URL matches, `false` to keep
* the trailing slash.
*
* Example when `true`: `http://google.com/` will be displayed as
* `http://google.com`.
*/
this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [decodePercentEncoding=true]
*
* `true` to decode percent-encoded characters in URL matches, `false` to keep
* the percent-encoded characters.
*
* Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
* be displayed as `https://en.wikipedia.org/wiki/San_José`.
*/
this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Number/Object} [truncate=0]
*
* ## Number Form
*
* A number for how many characters 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..'
*
* Example Usage:
*
* truncate: 25
*
*
* Defaults to `0` for "no truncation."
*
*
* ## Object Form
*
* An Object may also be provided with two properties: `length` (Number) and
* `location` (String). `location` may be one of the following: 'end'
* (default), 'middle', or 'smart'.
*
* Example Usage:
*
* truncate: { length: 25, location: 'middle' }
*
* @cfg {Number} [truncate.length=0] How many characters to allow before
* truncation will occur. Defaults to `0` for "no truncation."
* @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
*
* - 'end' (default): will truncate up to the number of characters, and then
* add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
* - 'middle': will truncate and add the ellipsis in the middle. Ex:
* 'yahoo.com/s..th/to/a/file'
* - 'smart': for URLs where the algorithm attempts to strip out unnecessary
* parts first (such as the 'www.', then URL scheme, hash, etc.),
* attempting to make the URL human-readable before looking for a good
* point to insert the ellipsis if it is still too long. Ex:
* 'yahoo.com/some..to/a/file'. For more details, see
* {@link Autolinker.truncate.TruncateSmart}.
*/
this.truncate = {
length: 0,
location: 'end',
}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @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/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
* - 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-[type]"
* where [type] is either "instagram", "twitter" or "soundcloud"
*/
this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Function} replaceFn
*
* A function to individually process each match found in the input string.
*
* See the class's description for usage.
*
* 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.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.
*/
this.replaceFn = null; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Object} context
*
* The context object (`this` reference) to call the `replaceFn` with.
*
* Defaults to this Autolinker instance.
*/
this.context = undefined; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [sanitizeHtml=false]
*
* `true` to HTML-encode the start and end brackets of existing HTML tags found
* in the input string. This will escape `<` and `>` characters to `&lt;` and
* `&gt;`, respectively.
*
* Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
* but will remove the significance of existing HTML tags in the input string. If
* you would like to maintain the significance of existing HTML tags while also
* making the output HTML string safe, leave this option as `false` and use a
* tool like https://github.com/cure53/DOMPurify (or others) on the input string
* before running Autolinker.
*/
this.sanitizeHtml = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @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.
*/
this.tagBuilder = null;
// Note: when `this.something` is used in the rhs of these assignments,
// it refers to the default values set above the constructor
this.urls = normalizeUrlsCfg(cfg.urls);
this.email = (0, utils_1.isBoolean)(cfg.email) ? cfg.email : this.email;
this.phone = (0, utils_1.isBoolean)(cfg.phone) ? cfg.phone : this.phone;
this.hashtag = cfg.hashtag || this.hashtag;
this.mention = cfg.mention || this.mention;
this.newWindow = (0, utils_1.isBoolean)(cfg.newWindow) ? cfg.newWindow : this.newWindow;
this.stripPrefix = normalizeStripPrefixCfg(cfg.stripPrefix);
this.stripTrailingSlash = (0, utils_1.isBoolean)(cfg.stripTrailingSlash)
? cfg.stripTrailingSlash
: this.stripTrailingSlash;
this.decodePercentEncoding = (0, utils_1.isBoolean)(cfg.decodePercentEncoding)
? cfg.decodePercentEncoding
: this.decodePercentEncoding;
this.sanitizeHtml = cfg.sanitizeHtml || false;
// Validate the value of the `mention` cfg
var mention = this.mention;
if (mention !== false && mention_utils_1.mentionServices.indexOf(mention) === -1) {
throw new Error("invalid `mention` cfg '".concat(mention, "' - see docs"));
}
// Validate the value of the `hashtag` cfg
var hashtag = this.hashtag;
if (hashtag !== false && hashtag_utils_1.hashtagServices.indexOf(hashtag) === -1) {
throw new Error("invalid `hashtag` cfg '".concat(hashtag, "' - see docs"));
}
this.truncate = normalizeTruncateCfg(cfg.truncate);
this.className = cfg.className || this.className;
this.replaceFn = cfg.replaceFn || this.replaceFn;
this.context = cfg.context || this;
}
/**
* Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
* 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`,
* then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* 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 #mention},
* {@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.
* @return {String} The HTML text, with matches automatically linked.
*/
Autolinker.link = function (textOrHtml, options) {
var autolinker = new Autolinker(options);
return autolinker.link(textOrHtml);
};
/**
* Parses the input `textOrHtml` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches (without making any replacements).
*
* Note that if parsing multiple pieces of text, it is slightly more efficient
* to create an Autolinker instance, and use the instance-level {@link #parse}
* method.
*
* Example:
*
* var matches = Autolinker.parse("Hello google.com, I am asdf@asdf.com", {
* urls: true,
* email: true
* });
*
* console.log(matches.length); // 2
* console.log(matches[0].getType()); // 'url'
* console.log(matches[0].getUrl()); // 'google.com'
* console.log(matches[1].getType()); // 'email'
* console.log(matches[1].getEmail()); // 'asdf@asdf.com'
*
* @static
* @param {String} textOrHtml The HTML or text to find matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@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.
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `textOrHtml`.
*/
Autolinker.parse = function (textOrHtml, options) {
var autolinker = new Autolinker(options);
return autolinker.parse(textOrHtml);
};
/**
* Parses the input `textOrHtml` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches (without making any replacements).
*
* This method is used by the {@link #link} method, but can also be used to
* simply do parsing of the input in order to discover what kinds of links
* there are and how many.
*
* Example usage:
*
* var autolinker = new Autolinker( {
* urls: true,
* email: true
* } );
*
* var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
*
* console.log( matches.length ); // 2
* console.log( matches[ 0 ].getType() ); // 'url'
* console.log( matches[ 0 ].getUrl() ); // 'google.com'
* console.log( matches[ 1 ].getType() ); // 'email'
* console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
*
* @param {String} textOrHtml The HTML or text to find matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@link #hashtag}, and {@link #mention} options are enabled).
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `textOrHtml`.
*/
Autolinker.prototype.parse = function (textOrHtml) {
var _this = this;
var skipTagNames = ['a', 'style', 'script'];
var skipTagsStackCount = 0; // used to only Autolink text outside of anchor/script/style tags. We don't want to autolink something that is already linked inside of an <a> tag, for instance
var matches = [];
// Find all matches within the `textOrHtml` (but not matches that are
// already nested within <a>, <style> and <script> tags)
(0, parse_html_1.parseHtml)(textOrHtml, {
onOpenTag: function (tagName) {
if (skipTagNames.indexOf(tagName) >= 0) {
skipTagsStackCount++;
}
},
onText: function (text, offset) {
// Only process text nodes that are not within an <a>, <style> or <script> tag
if (skipTagsStackCount === 0) {
// "Walk around" common HTML entities. An '&nbsp;' (for example)
// could be at the end of a URL, but we don't want to
// include the trailing '&' in the URL. See issue #76
// TODO: Handle HTML entities separately in parseHtml() and
// don't emit them as "text" except for &amp; entities
var htmlCharacterEntitiesRegex = /(&nbsp;|&#160;|&lt;|&#60;|&gt;|&#62;|&quot;|&#34;|&#39;)/gi; // NOTE: capturing group is significant to include the split characters in the .split() call below
var textSplit = text.split(htmlCharacterEntitiesRegex);
var currentOffset_1 = offset;
textSplit.forEach(function (splitText, i) {
// even number matches are text, odd numbers are html entities
if (i % 2 === 0) {
var textNodeMatches = _this.parseText(splitText, currentOffset_1);
matches.push.apply(matches, tslib_1.__spreadArray([], tslib_1.__read(textNodeMatches), false));
}
currentOffset_1 += splitText.length;
});
}
},
onCloseTag: function (tagName) {
if (skipTagNames.indexOf(tagName) >= 0) {
skipTagsStackCount = Math.max(skipTagsStackCount - 1, 0); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
}
},
onComment: function ( /*_offset: number*/) { }, // no need to process comment nodes
onDoctype: function ( /*_offset: number*/) { }, // no need to process doctype nodes
});
// After we have found all matches, remove subsequent matches that
// overlap with a previous match. This can happen for instance with an
// email address where the local-part of the email is also a top-level
// domain, such as in "google.com@aaa.com". In this case, the entire
// email address should be linked rather than just the 'google.com'
// part.
matches = this.compactMatches(matches);
// And finally, remove matches for match types that have been turned
// off. We needed to have all match types turned on initially so that
// things like hashtags could be filtered out if they were really just
// part of a URL match (for instance, as a named anchor).
matches = this.removeUnwantedMatches(matches);
return matches;
};
/**
* After we have found all matches, we need to remove matches that overlap
* with a previous match. This can happen for instance with an
* email address where the local-part of the email is also a top-level
* domain, such as in "google.com@aaa.com". In this case, the entire email
* address should be linked rather than just the 'google.com' part.
*
* @private
* @param {Autolinker.match.Match[]} matches
* @return {Autolinker.match.Match[]}
*/
Autolinker.prototype.compactMatches = function (matches) {
// First, the matches need to be sorted in order of offset in the input
// string
matches.sort(byMatchOffset);
var i = 0;
while (i < matches.length - 1) {
var match = matches[i];
var offset = match.getOffset();
var matchedTextLength = match.getMatchedText().length;
if (i + 1 < matches.length) {
// Remove subsequent matches that equal offset with current match
// This can happen when matching the text "google.com@aaa.com"
// where we have both a URL ('google.com') and an email. We
// should only keep the email match in this case.
if (matches[i + 1].getOffset() === offset) {
// Remove the shorter match
var removeIdx = matches[i + 1].getMatchedText().length > matchedTextLength ? i : i + 1;
matches.splice(removeIdx, 1);
continue;
}
// Remove subsequent matches that overlap with the current match
//
// NOTE: This was a fundamental snippet of the Autolinker.js v3
// algorithm where we had multiple regular expressions searching
// the input string for matches. The regexes would sometimes
// overlap such as in the case of "google.com/#link", where we
// would have both a URL match and a hashtag match.
//
// However, the Autolinker.js v4 algorithm uses a state machine
// parser and knows that the '#link' part of 'google.com/#link'
// is part of the URL that precedes it, so we don't need this
// piece of code any more. Keeping it here commented for now in
// case we need to put it back at some point, but none of the
// test cases are currently able to trigger the need for it.
// const endIdx = offset + matchedTextLength;
// if (matches[i + 1].getOffset() < endIdx) {
// matches.splice(i + 1, 1);
// continue;
// }
}
i++;
}
return matches;
};
/**
* Removes matches for matchers that were turned off in the options. For
* example, if {@link #hashtag hashtags} were not to be matched, we'll
* remove them from the `matches` array here.
*
* Note: we *must* use all Matchers on the input string, and then filter
* them out later. For example, if the options were `{ url: false, hashtag: true }`,
* we wouldn't want to match the text '#link' as a HashTag inside of the text
* 'google.com/#link'. The way the algorithm works is that we match the full
* URL first (which prevents the accidental HashTag match), and then we'll
* simply throw away the URL match.
*
* @private
* @param {Autolinker.match.Match[]} matches The array of matches to remove
* the unwanted matches from. Note: this array is mutated for the
* removals.
* @return {Autolinker.match.Match[]} The mutated input `matches` array.
*/
Autolinker.prototype.removeUnwantedMatches = function (matches) {
if (!this.hashtag)
(0, utils_1.removeWithPredicate)(matches, function (match) {
return match.getType() === 'hashtag';
});
if (!this.email)
(0, utils_1.removeWithPredicate)(matches, function (match) {
return match.getType() === 'email';
});
if (!this.phone)
(0, utils_1.removeWithPredicate)(matches, function (match) {
return match.getType() === 'phone';
});
if (!this.mention)
(0, utils_1.removeWithPredicate)(matches, function (match) {
return match.getType() === 'mention';
});
if (!this.urls.schemeMatches) {
(0, utils_1.removeWithPredicate)(matches, function (m) {
return m.getType() === 'url' && m.getUrlMatchType() === 'scheme';
});
}
if (!this.urls.tldMatches) {
(0, utils_1.removeWithPredicate)(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'tld'; });
}
if (!this.urls.ipV4Matches) {
(0, utils_1.removeWithPredicate)(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'ipV4'; });
}
return matches;
};
/**
* Parses the input `text` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches.
*
* This method processes a **non-HTML string**, and is used to parse and
* match within the text nodes of an HTML string. This method is used
* internally by {@link #parse}.
*
* @private
* @param {String} text The text to find matches within (depending on if the
* {@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}
* instances, but may be omitted if calling this method publicly.
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `text`.
*/
Autolinker.prototype.parseText = function (text, offset) {
offset = offset || 0;
var matches = (0, parse_matches_1.parseMatches)(text, {
tagBuilder: this.getTagBuilder(),
stripPrefix: this.stripPrefix,
stripTrailingSlash: this.stripTrailingSlash,
decodePercentEncoding: this.decodePercentEncoding,
hashtagServiceName: this.hashtag,
mentionServiceName: this.mention || 'twitter',
});
// Correct the offset of each of the matches. They are originally
// the offset of the match within the provided text node, but we
// need to correct them to be relative to the original HTML input
// string (i.e. the one provided to #parse).
for (var i = 0, numTextMatches = matches.length; i < numTextMatches; i++) {
matches[i].setOffset(offset + matches[i].getOffset());
}
return matches;
};
/**
* Automatically links URLs, Email addresses, Phone numbers, Hashtags,
* and Mentions (Twitter, Instagram, Soundcloud) 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
* &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* 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 (&lt;a&gt;) tags.
*
* @param {String} textOrHtml The HTML or text to autolink matches within
* (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.
*/
Autolinker.prototype.link = function (textOrHtml) {
if (!textOrHtml) {
return '';
} // handle `null` and `undefined` (for JavaScript users that don't have TypeScript support), and nothing to do with an empty string too
/* We would want to sanitize the start and end characters of a tag
* before processing the string in order to avoid an XSS scenario.
* This behaviour can be changed by toggling the sanitizeHtml option.
*/
if (this.sanitizeHtml) {
textOrHtml = textOrHtml.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
var matches = this.parse(textOrHtml);
var newHtml = new Array(matches.length * 2 + 1);
var lastIndex = 0;
for (var i = 0, len = matches.length; i < len; i++) {
var match = matches[i];
newHtml.push(textOrHtml.substring(lastIndex, match.getOffset()));
newHtml.push(this.createMatchReturnVal(match));
lastIndex = match.getOffset() + match.getMatchedText().length;
}
newHtml.push(textOrHtml.substring(lastIndex)); // handle the text after the last match
return newHtml.join('');
};
/**
* Creates the return string value for a given match in the input string.
*
* 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.
*/
Autolinker.prototype.createMatchReturnVal = function (match) {
// Handle a custom `replaceFn` being provided
var replaceFnResult;
if (this.replaceFn) {
replaceFnResult = this.replaceFn.call(this.context, match); // Autolinker instance is the context
}
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 html_tag_1.HtmlTag) {
return replaceFnResult.toAnchorString();
}
else {
// replaceFnResult === true, or no/unknown return value from function
// Perform Autolinker's default anchor tag generation
var anchorTag = match.buildTag(); // returns an Autolinker.HtmlTag instance
return anchorTag.toAnchorString();
}
};
/**
* Returns the {@link #tagBuilder} instance for this Autolinker instance,
* lazily instantiating it if it does not yet exist.
*
* @private
* @return {Autolinker.AnchorTagBuilder}
*/
Autolinker.prototype.getTagBuilder = function () {
var tagBuilder = this.tagBuilder;
if (!tagBuilder) {
tagBuilder = this.tagBuilder = new anchor_tag_builder_1.AnchorTagBuilder({
newWindow: this.newWindow,
truncate: this.truncate,
className: this.className,
});
}
return tagBuilder;
};
// NOTE: must be 'export default' here for UMD module
/**
* @static
* @property {String} version
*
* The Autolinker version number in the form major.minor.patch
*
* Ex: 3.15.0
*/
Autolinker.version = version_1.version;
return Autolinker;
}());
exports.default = Autolinker;
/**
* Normalizes the {@link #urls} config into an Object with its 2 properties:
* `schemeMatches` and `tldMatches`, both booleans.
*
* See {@link #urls} config for details.
*
* @private
* @param {Boolean/Object} urls
* @return {Object}
*/
function normalizeUrlsCfg(urls) {
if (urls == null)
urls = true; // default to `true`
if ((0, utils_1.isBoolean)(urls)) {
return { schemeMatches: urls, tldMatches: urls, ipV4Matches: urls };
}
else {
// object form
return {
schemeMatches: (0, utils_1.isBoolean)(urls.schemeMatches) ? urls.schemeMatches : true,
tldMatches: (0, utils_1.isBoolean)(urls.tldMatches) ? urls.tldMatches : true,
ipV4Matches: (0, utils_1.isBoolean)(urls.ipV4Matches) ? urls.ipV4Matches : true,
};
}
}
/**
* Normalizes the {@link #stripPrefix} config into an Object with 2
* properties: `scheme`, and `www` - both Booleans.
*
* See {@link #stripPrefix} config for details.
*
* @private
* @param {Boolean/Object} stripPrefix
* @return {Object}
*/
function normalizeStripPrefixCfg(stripPrefix) {
if (stripPrefix == null)
stripPrefix = true; // default to `true`
if ((0, utils_1.isBoolean)(stripPrefix)) {
return { scheme: stripPrefix, www: stripPrefix };
}
else {
// object form
return {
scheme: (0, utils_1.isBoolean)(stripPrefix.scheme) ? stripPrefix.scheme : true,
www: (0, utils_1.isBoolean)(stripPrefix.www) ? stripPrefix.www : true,
};
}
}
/**
* Normalizes the {@link #truncate} config into an Object with 2 properties:
* `length` (Number), and `location` (String).
*
* See {@link #truncate} config for details.
*
* @private
* @param {Number/Object} truncate
* @return {Object}
*/
function normalizeTruncateCfg(truncate) {
if (typeof truncate === 'number') {
return { length: truncate, location: 'end' };
}
else {
// object, or undefined/null
return tslib_1.__assign({ length: Number.POSITIVE_INFINITY, location: 'end' }, truncate);
}
}
/**
* Helper function for Array.prototype.sort() to sort the Matches by
* their offset in the input string.
*/
function byMatchOffset(a, b) {
return a.getOffset() - b.getOffset();
}
//# sourceMappingURL=autolinker.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+30
View File
@@ -0,0 +1,30 @@
/**
* Common UTF-16 character codes used in the program.
*
* This is a 'const' enum, meaning that the numerical value will be inlined into
* the code when TypeScript is compiled.
*/
export declare const enum Char {
A = 65,
Z = 90,
a = 97,
z = 122,
DoubleQuote = 34,// char code for "
SingleQuote = 39,// char code for '
Zero = 48,// char code for '0'
Nine = 57,// char code for '9'
Space = 32,// U+0020 Space <SP> Normal space
NumberSign = 35,// '#' char
OpenParen = 40,// '(' char
CloseParen = 41,// ')' char
Plus = 43,// '+' char
Comma = 44,// ',' char
Dash = 45,// '-' char
Dot = 46,// '.' char
Slash = 47,// '/' char
Colon = 58,// ':' char
SemiColon = 59,// ';' char
Question = 63,// '?' char
AtSign = 64,// '@' char
Underscore = 95
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=char.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"char.js","sourceRoot":"","sources":["../../src/char.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Common UTF-16 character codes used in the program.\n *\n * This is a 'const' enum, meaning that the numerical value will be inlined into\n * the code when TypeScript is compiled.\n */\n// prettier-ignore\nexport const enum Char {\n // Letter chars (usually used for scheme testing)\n A = 65,\n Z = 90,\n a = 97,\n z = 122,\n\n // Quote chars (used for HTML parsing)\n DoubleQuote = 34, // char code for \"\n SingleQuote = 39, // char code for '\n\n // Digit chars (used for parsing matches)\n Zero = 48, // char code for '0'\n Nine = 57, // char code for '9'\n\n // Semantically meaningful characters for HTML and Match parsing\n Space = 32, // U+0020 Space <SP> Normal space\n NumberSign = 35, // '#' char\n OpenParen = 40, // '(' char\n CloseParen = 41, // ')' char\n Plus = 43, // '+' char\n Comma = 44, // ',' char\n Dash = 45, // '-' char\n Dot = 46, // '.' char\n Slash = 47, // '/' char\n Colon = 58, // ':' char\n SemiColon = 59, // ';' char\n Question = 63, // '?' char\n AtSign = 64, // '@' char\n Underscore = 95, // '_' char\n}\n"]}
+232
View File
@@ -0,0 +1,232 @@
export declare const whitespaceRe: RegExp;
/**
* @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( match ) {
* var tag = match.buildTag(); // 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( 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>
*/
export declare class HtmlTag {
/**
* @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.
*/
private tagName;
/**
* @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.
*/
private attrs;
/**
* @cfg {String} innerHTML
*
* The inner HTML for the tag.
*/
private innerHTML;
/**
* @method constructor
* @param {Object} [cfg] The configuration properties for this class, in an Object (map)
*/
constructor(cfg?: HtmlTagCfg);
/**
* 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(tagName: string): this;
/**
* Retrieves the tag name.
*
* @return {String}
*/
getTagName(): string;
/**
* 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(attrName: string, attrValue: string): this;
/**
* Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`.
*
* @param {String} attrName The attribute name to retrieve.
* @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.
*/
getAttr(attrName: string): string;
/**
* 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(attrs: {
[attr: string]: string;
}): this;
/**
* Retrieves the attributes Object (map) for the HtmlTag.
*
* @return {Object.<String, String>} A key/value object of the attributes for the HtmlTag.
*/
getAttrs(): {
[key: string]: string;
};
/**
* 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(cssClass: string): this;
/**
* 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(cssClass: string): 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(cssClass: string): 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(): string;
/**
* 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(cssClass: string): boolean;
/**
* 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(html: string): this;
/**
* Backwards compatibility method name.
*
* @param {String} html The inner HTML to set.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
setInnerHtml(html: string): this;
/**
* Retrieves the inner HTML for the tag.
*
* @return {String}
*/
getInnerHTML(): string;
/**
* Backward compatibility method name.
*
* @return {String}
*/
getInnerHtml(): string;
/**
* Generates the HTML string for the tag.
*
* @return {String}
*/
toAnchorString(): string;
/**
* 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"`
*/
protected buildAttrsStr(): string;
}
export interface HtmlTagCfg {
tagName?: string;
attrs?: {
[key: string]: string;
};
innerHtml?: string;
innerHTML?: string;
}
+302
View File
@@ -0,0 +1,302 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HtmlTag = exports.whitespaceRe = void 0;
var utils_1 = require("./utils");
// Regular expression to match whitespace
exports.whitespaceRe = /\s+/;
/**
* @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( match ) {
* var tag = match.buildTag(); // 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( 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>
*/
var HtmlTag = /** @class */ (function () {
/**
* @method constructor
* @param {Object} [cfg] The configuration properties for this class, in an Object (map)
*/
function HtmlTag(cfg) {
if (cfg === void 0) { cfg = {}; }
/**
* @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.
*/
this.tagName = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @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.
*/
this.attrs = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {String} innerHTML
*
* The inner HTML for the tag.
*/
this.innerHTML = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
this.tagName = cfg.tagName || '';
this.attrs = cfg.attrs || {};
this.innerHTML = cfg.innerHtml || cfg.innerHTML || ''; // accept either the camelCased form or the fully capitalized acronym as in the DOM
}
/**
* 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.
*/
HtmlTag.prototype.setTagName = function (tagName) {
this.tagName = tagName;
return this;
};
/**
* Retrieves the tag name.
*
* @return {String}
*/
HtmlTag.prototype.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.
*/
HtmlTag.prototype.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} attrName The attribute name to retrieve.
* @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.
*/
HtmlTag.prototype.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.
*/
HtmlTag.prototype.setAttrs = function (attrs) {
Object.assign(this.getAttrs(), 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.
*/
HtmlTag.prototype.getAttrs = function () {
return 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.
*/
HtmlTag.prototype.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.
*/
HtmlTag.prototype.addClass = function (cssClass) {
var classAttr = this.getClass();
var classes = !classAttr ? [] : classAttr.split(exports.whitespaceRe);
var newClasses = cssClass.split(exports.whitespaceRe);
var newClass;
while ((newClass = newClasses.shift())) {
if (classes.indexOf(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.
*/
HtmlTag.prototype.removeClass = function (cssClass) {
var classAttr = this.getClass();
var classes = !classAttr ? [] : classAttr.split(exports.whitespaceRe);
var removeClasses = cssClass.split(exports.whitespaceRe);
var removeClass;
while (classes.length && (removeClass = removeClasses.shift())) {
var idx = classes.indexOf(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}
*/
HtmlTag.prototype.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.
*/
HtmlTag.prototype.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.
*/
HtmlTag.prototype.setInnerHTML = function (html) {
this.innerHTML = html;
return this;
};
/**
* Backwards compatibility method name.
*
* @param {String} html The inner HTML to set.
* @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
*/
HtmlTag.prototype.setInnerHtml = function (html) {
return this.setInnerHTML(html);
};
/**
* Retrieves the inner HTML for the tag.
*
* @return {String}
*/
HtmlTag.prototype.getInnerHTML = function () {
return this.innerHTML || '';
};
/**
* Backward compatibility method name.
*
* @return {String}
*/
HtmlTag.prototype.getInnerHtml = function () {
return this.getInnerHTML();
};
/**
* Generates the HTML string for the tag.
*
* @return {String}
*/
HtmlTag.prototype.toAnchorString = function () {
var tagName = this.getTagName();
var 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"`
*/
HtmlTag.prototype.buildAttrsStr = function () {
var attrs = this.getAttrs(), attrsArr = [];
for (var prop in attrs) {
if (utils_1.hasOwnProperty.call(attrs, prop)) {
attrsArr.push(prop + '="' + attrs[prop] + '"');
}
}
return attrsArr.join(' ');
};
return HtmlTag;
}());
exports.HtmlTag = HtmlTag;
//# sourceMappingURL=html-tag.js.map
File diff suppressed because one or more lines are too long
+89
View File
@@ -0,0 +1,89 @@
/**
* The callback functions that can be provided to {@link #parseHtml}.
*/
export interface ParseHtmlCallbacks {
onOpenTag: (tagName: string, offset: number) => void;
onCloseTag: (tagName: string, offset: number) => void;
onText: (text: string, offset: number) => void;
onComment: (offset: number) => void;
onDoctype: (offset: number) => void;
}
/**
* Parses an HTML string, calling the callbacks to notify of tags and text.
*
* ## History
*
* This file previously used a regular expression to find html tags in the input
* text. Unfortunately, we ran into a bunch of catastrophic backtracking issues
* with certain input text, causing Autolinker to either hang or just take a
* really long time to parse the string.
*
* The current code is intended to be a O(n) algorithm that walks through
* the string in one pass, and tries to be as cheap as possible. We don't need
* to implement the full HTML spec, but rather simply determine where the string
* looks like an HTML tag, and where it looks like text (so that we can autolink
* that).
*
* This state machine parser is intended just to be a simple but performant
* parser of HTML for the subset of requirements we have. We simply need to:
*
* 1. Determine where HTML tags are
* 2. Determine the tag name (Autolinker specifically only cares about <a>,
* <script>, and <style> tags, so as not to link any text within them)
*
* We don't need to:
*
* 1. Create a parse tree
* 2. Auto-close tags with invalid markup
* 3. etc.
*
* The other intention behind this is that we didn't want to add external
* dependencies on the Autolinker utility which would increase its size. For
* instance, adding htmlparser2 adds 125kb to the minified output file,
* increasing its final size from 47kb to 172kb (at the time of writing). It
* also doesn't work exactly correctly, treating the string "<3 blah blah blah"
* as an HTML tag.
*
* Reference for HTML spec:
*
* https://www.w3.org/TR/html51/syntax.html#sec-tokenization
*
* @param {String} html The HTML to parse
* @param {Object} callbacks
* @param {Function} callbacks.onOpenTag Callback function to call when an open
* tag is parsed. Called with the tagName as its argument.
* @param {Function} callbacks.onCloseTag Callback function to call when a close
* tag is parsed. Called with the tagName as its argument. If a self-closing
* tag is found, `onCloseTag` is called immediately after `onOpenTag`.
* @param {Function} callbacks.onText Callback function to call when text (i.e
* not an HTML tag) is parsed. Called with the text (string) as its first
* argument, and offset (number) into the string as its second.
*/
export declare function parseHtml(html: string, callbacks: ParseHtmlCallbacks): void;
/**
* The subset of the parser states defined in https://www.w3.org/TR/html51/syntax.html
* which are useful for Autolinker.
*/
export declare const enum State {
Data = 0,
TagOpen = 1,
EndTagOpen = 2,
TagName = 3,
BeforeAttributeName = 4,
AttributeName = 5,
AfterAttributeName = 6,
BeforeAttributeValue = 7,
AttributeValueDoubleQuoted = 8,
AttributeValueSingleQuoted = 9,
AttributeValueUnquoted = 10,
AfterAttributeValueQuoted = 11,
SelfClosingStartTag = 12,
MarkupDeclarationOpenState = 13,// When the sequence '<!' is read for an HTML comment or doctype
CommentStart = 14,
CommentStartDash = 15,
Comment = 16,
CommentEndDash = 17,
CommentEnd = 18,
CommentEndBang = 19,
Doctype = 20
}
+681
View File
@@ -0,0 +1,681 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseHtml = parseHtml;
var tslib_1 = require("tslib");
var char_utils_1 = require("../char-utils");
var utils_1 = require("../utils");
// For debugging: search for other "For debugging" lines
// import CliTable from 'cli-table';
var CurrentTag = /** @class */ (function () {
function CurrentTag(cfg) {
if (cfg === void 0) { cfg = {}; }
this.idx = cfg.idx !== undefined ? cfg.idx : -1;
this.type = cfg.type || 'tag';
this.name = cfg.name || '';
this.isOpening = !!cfg.isOpening;
this.isClosing = !!cfg.isClosing;
}
return CurrentTag;
}());
var noCurrentTag = new CurrentTag(); // shared reference for when there is no current tag currently being read
/**
* Context object containing all the state needed by the HTML parsing state
* machine function.
*
* ## Historical note
*
* In v4.1.5, we used nested functions to handle the context via closures, but
* this necessitated re-creating the functions for each call to `parseHtml()`,
* which made them difficult for v8 to JIT optimize. In v4.1.6, we lifted all of
* the functions to the top-level scope and passed the context object between
* them, which allows the functions to be JIT compiled once and reused.
*/
var ParseHtmlContext = /** @class */ (function () {
function ParseHtmlContext(html, callbacks) {
this.charIdx = 0; // Current character index being processed
this.state = 0 /* State.Data */; // begin in the Data state
this.currentDataIdx = 0; // where the current data start index is
this.currentTag = noCurrentTag; // describes the current tag that is being read
this.html = html;
this.callbacks = callbacks;
}
return ParseHtmlContext;
}());
/**
* Parses an HTML string, calling the callbacks to notify of tags and text.
*
* ## History
*
* This file previously used a regular expression to find html tags in the input
* text. Unfortunately, we ran into a bunch of catastrophic backtracking issues
* with certain input text, causing Autolinker to either hang or just take a
* really long time to parse the string.
*
* The current code is intended to be a O(n) algorithm that walks through
* the string in one pass, and tries to be as cheap as possible. We don't need
* to implement the full HTML spec, but rather simply determine where the string
* looks like an HTML tag, and where it looks like text (so that we can autolink
* that).
*
* This state machine parser is intended just to be a simple but performant
* parser of HTML for the subset of requirements we have. We simply need to:
*
* 1. Determine where HTML tags are
* 2. Determine the tag name (Autolinker specifically only cares about <a>,
* <script>, and <style> tags, so as not to link any text within them)
*
* We don't need to:
*
* 1. Create a parse tree
* 2. Auto-close tags with invalid markup
* 3. etc.
*
* The other intention behind this is that we didn't want to add external
* dependencies on the Autolinker utility which would increase its size. For
* instance, adding htmlparser2 adds 125kb to the minified output file,
* increasing its final size from 47kb to 172kb (at the time of writing). It
* also doesn't work exactly correctly, treating the string "<3 blah blah blah"
* as an HTML tag.
*
* Reference for HTML spec:
*
* https://www.w3.org/TR/html51/syntax.html#sec-tokenization
*
* @param {String} html The HTML to parse
* @param {Object} callbacks
* @param {Function} callbacks.onOpenTag Callback function to call when an open
* tag is parsed. Called with the tagName as its argument.
* @param {Function} callbacks.onCloseTag Callback function to call when a close
* tag is parsed. Called with the tagName as its argument. If a self-closing
* tag is found, `onCloseTag` is called immediately after `onOpenTag`.
* @param {Function} callbacks.onText Callback function to call when text (i.e
* not an HTML tag) is parsed. Called with the text (string) as its first
* argument, and offset (number) into the string as its second.
*/
function parseHtml(html, callbacks) {
var context = new ParseHtmlContext(html, callbacks);
// For debugging: search for other "For debugging" lines
// const table = new CliTable( {
// head: [ 'charIdx', 'char', 'state', 'currentDataIdx', 'currentOpenTagIdx', 'tag.type' ]
// } );
var len = html.length;
while (context.charIdx < len) {
var char = html.charAt(context.charIdx);
var charCode = html.charCodeAt(context.charIdx);
// For debugging: search for other "For debugging" lines
// ALSO: Temporarily remove the 'const' keyword on the State enum
// table.push([
// String(charIdx),
// char,
// State[state],
// String(currentDataIdx),
// String(currentTag.idx),
// currentTag.idx === -1 ? '' : currentTag.type
// ]);
switch (context.state) {
case 0 /* State.Data */:
stateData(context, char);
break;
case 1 /* State.TagOpen */:
stateTagOpen(context, char, charCode);
break;
case 2 /* State.EndTagOpen */:
stateEndTagOpen(context, char, charCode);
break;
case 3 /* State.TagName */:
stateTagName(context, char, charCode);
break;
case 4 /* State.BeforeAttributeName */:
stateBeforeAttributeName(context, char, charCode);
break;
case 5 /* State.AttributeName */:
stateAttributeName(context, char, charCode);
break;
case 6 /* State.AfterAttributeName */:
stateAfterAttributeName(context, char, charCode);
break;
case 7 /* State.BeforeAttributeValue */:
stateBeforeAttributeValue(context, char, charCode);
break;
case 8 /* State.AttributeValueDoubleQuoted */:
stateAttributeValueDoubleQuoted(context, char);
break;
case 9 /* State.AttributeValueSingleQuoted */:
stateAttributeValueSingleQuoted(context, char);
break;
case 10 /* State.AttributeValueUnquoted */:
stateAttributeValueUnquoted(context, char, charCode);
break;
case 11 /* State.AfterAttributeValueQuoted */:
stateAfterAttributeValueQuoted(context, char, charCode);
break;
case 12 /* State.SelfClosingStartTag */:
stateSelfClosingStartTag(context, char);
break;
case 13 /* State.MarkupDeclarationOpenState */:
stateMarkupDeclarationOpen(context);
break;
case 14 /* State.CommentStart */:
stateCommentStart(context, char);
break;
case 15 /* State.CommentStartDash */:
stateCommentStartDash(context, char);
break;
case 16 /* State.Comment */:
stateComment(context, char);
break;
case 17 /* State.CommentEndDash */:
stateCommentEndDash(context, char);
break;
case 18 /* State.CommentEnd */:
stateCommentEnd(context, char);
break;
case 19 /* State.CommentEndBang */:
stateCommentEndBang(context, char);
break;
case 20 /* State.Doctype */:
stateDoctype(context, char);
break;
/* istanbul ignore next */
default:
(0, utils_1.assertNever)(context.state);
}
// For debugging: search for other "For debugging" lines
// ALSO: Temporarily remove the 'const' keyword on the State enum
// table.push([
// String(context.charIdx),
// char,
// State[context.state],
// String(context.currentDataIdx),
// String(context.currentTag.idx),
// context.currentTag.idx === -1 ? '' : context.currentTag.type
// ]);
context.charIdx++;
}
if (context.currentDataIdx < context.charIdx) {
emitText(context);
}
// For debugging: search for other "For debugging" lines
// console.log( '\n' + table.toString() );
}
// Called when non-tags are being read (i.e. the text around HTML †ags)
// https://www.w3.org/TR/html51/syntax.html#data-state
function stateData(context, char) {
if (char === '<') {
startNewTag(context);
}
}
// Called after a '<' is read from the Data state
// https://www.w3.org/TR/html51/syntax.html#tag-open-state
function stateTagOpen(context, char, charCode) {
if (char === '!') {
context.state = 13 /* State.MarkupDeclarationOpenState */;
}
else if (char === '/') {
context.state = 2 /* State.EndTagOpen */;
context.currentTag = new CurrentTag(tslib_1.__assign(tslib_1.__assign({}, context.currentTag), { isClosing: true }));
}
else if (char === '<') {
// start of another tag (ignore the previous, incomplete one)
startNewTag(context);
}
else if ((0, char_utils_1.isAsciiLetterChar)(charCode)) {
// tag name start (and no '/' read)
context.state = 3 /* State.TagName */;
context.currentTag = new CurrentTag(tslib_1.__assign(tslib_1.__assign({}, context.currentTag), { isOpening: true }));
}
else {
// Any other
context.state = 0 /* State.Data */;
context.currentTag = noCurrentTag;
}
}
// After a '<x', '</x' sequence is read (where 'x' is a letter character),
// this is to continue reading the tag name
// https://www.w3.org/TR/html51/syntax.html#tag-name-state
function stateTagName(context, char, charCode) {
if ((0, char_utils_1.isWhitespaceChar)(charCode)) {
context.currentTag = new CurrentTag(tslib_1.__assign(tslib_1.__assign({}, context.currentTag), { name: captureTagName(context) }));
context.state = 4 /* State.BeforeAttributeName */;
}
else if (char === '<') {
// start of another tag (ignore the previous, incomplete one)
startNewTag(context);
}
else if (char === '/') {
context.currentTag = new CurrentTag(tslib_1.__assign(tslib_1.__assign({}, context.currentTag), { name: captureTagName(context) }));
context.state = 12 /* State.SelfClosingStartTag */;
}
else if (char === '>') {
context.currentTag = new CurrentTag(tslib_1.__assign(tslib_1.__assign({}, context.currentTag), { name: captureTagName(context) }));
emitTagAndPreviousTextNode(context); // resets to Data state as well
}
else if (!(0, char_utils_1.isAsciiLetterChar)(charCode) && !(0, char_utils_1.isDigitChar)(charCode) && char !== ':') {
// Anything else that does not form an html tag. Note: the colon
// character is accepted for XML namespaced tags
resetToDataState(context);
}
else {
// continue reading tag name
}
}
// Called after the '/' is read from a '</' sequence
// https://www.w3.org/TR/html51/syntax.html#end-tag-open-state
function stateEndTagOpen(context, char, charCode) {
if (char === '>') {
// parse error. Encountered "</>". Skip it without treating as a tag
resetToDataState(context);
}
else if ((0, char_utils_1.isAsciiLetterChar)(charCode)) {
context.state = 3 /* State.TagName */;
}
else {
// some other non-tag-like character, don't treat this as a tag
resetToDataState(context);
}
}
// https://www.w3.org/TR/html51/syntax.html#before-attribute-name-state
function stateBeforeAttributeName(context, char, charCode) {
if ((0, char_utils_1.isWhitespaceChar)(charCode)) {
// stay in BeforeAttributeName state - continue reading chars
}
else if (char === '/') {
context.state = 12 /* State.SelfClosingStartTag */;
}
else if (char === '>') {
emitTagAndPreviousTextNode(context); // resets to Data state as well
}
else if (char === '<') {
// start of another tag (ignore the previous, incomplete one)
startNewTag(context);
}
else if (char === "=" || (0, char_utils_1.isQuoteChar)(charCode) || (0, char_utils_1.isControlChar)(charCode)) {
// "Parse error" characters that, according to the spec, should be
// appended to the attribute name, but we'll treat these characters
// as not forming a real HTML tag
resetToDataState(context);
}
else {
// Any other char, start of a new attribute name
context.state = 5 /* State.AttributeName */;
}
}
// https://www.w3.org/TR/html51/syntax.html#attribute-name-state
function stateAttributeName(context, char, charCode) {
if ((0, char_utils_1.isWhitespaceChar)(charCode)) {
context.state = 6 /* State.AfterAttributeName */;
}
else if (char === '/') {
context.state = 12 /* State.SelfClosingStartTag */;
}
else if (char === '=') {
context.state = 7 /* State.BeforeAttributeValue */;
}
else if (char === '>') {
emitTagAndPreviousTextNode(context); // resets to Data state as well
}
else if (char === '<') {
// start of another tag (ignore the previous, incomplete one)
startNewTag(context);
}
else if ((0, char_utils_1.isQuoteChar)(charCode)) {
// "Parse error" characters that, according to the spec, should be
// appended to the attribute name, but we'll treat these characters
// as not forming a real HTML tag
resetToDataState(context);
}
else {
// anything else: continue reading attribute name
}
}
// https://www.w3.org/TR/html51/syntax.html#after-attribute-name-state
function stateAfterAttributeName(context, char, charCode) {
if ((0, char_utils_1.isWhitespaceChar)(charCode)) {
// ignore the character - continue reading
}
else if (char === '/') {
context.state = 12 /* State.SelfClosingStartTag */;
}
else if (char === '=') {
context.state = 7 /* State.BeforeAttributeValue */;
}
else if (char === '>') {
emitTagAndPreviousTextNode(context);
}
else if (char === '<') {
// start of another tag (ignore the previous, incomplete one)
startNewTag(context);
}
else if ((0, char_utils_1.isQuoteChar)(charCode)) {
// "Parse error" characters that, according to the spec, should be
// appended to the attribute name, but we'll treat these characters
// as not forming a real HTML tag
resetToDataState(context);
}
else {
// Any other character, start a new attribute in the current tag
context.state = 5 /* State.AttributeName */;
}
}
// https://www.w3.org/TR/html51/syntax.html#before-attribute-value-state
function stateBeforeAttributeValue(context, char, charCode) {
if ((0, char_utils_1.isWhitespaceChar)(charCode)) {
// ignore the character - continue reading
}
else if (char === "\"") {
context.state = 8 /* State.AttributeValueDoubleQuoted */;
}
else if (char === "'") {
context.state = 9 /* State.AttributeValueSingleQuoted */;
}
else if (/[>=`]/.test(char)) {
// Invalid chars after an '=' for an attribute value, don't count
// the current tag as an HTML tag
resetToDataState(context);
}
else if (char === '<') {
// start of another tag (ignore the previous, incomplete one)
startNewTag(context);
}
else {
// Any other character, consider it an unquoted attribute value
context.state = 10 /* State.AttributeValueUnquoted */;
}
}
// https://www.w3.org/TR/html51/syntax.html#attribute-value-double-quoted-state
function stateAttributeValueDoubleQuoted(context, char) {
if (char === "\"") {
// end the current double-quoted attribute
context.state = 11 /* State.AfterAttributeValueQuoted */;
}
else {
// consume the character as part of the double-quoted attribute value
}
}
// https://www.w3.org/TR/html51/syntax.html#attribute-value-single-quoted-state
function stateAttributeValueSingleQuoted(context, char) {
if (char === "'") {
// end the current single-quoted attribute
context.state = 11 /* State.AfterAttributeValueQuoted */;
}
else {
// consume the character as part of the double-quoted attribute value
}
}
// https://www.w3.org/TR/html51/syntax.html#attribute-value-unquoted-state
function stateAttributeValueUnquoted(context, char, charCode) {
if ((0, char_utils_1.isWhitespaceChar)(charCode)) {
context.state = 4 /* State.BeforeAttributeName */;
}
else if (char === '>') {
emitTagAndPreviousTextNode(context);
}
else if (char === '<') {
// start of another tag (ignore the previous, incomplete one)
startNewTag(context);
}
else {
// Any other character, treat it as part of the attribute value
}
}
// Called after a double-quoted or single-quoted attribute value is read
// (i.e. after the closing quote character)
// https://www.w3.org/TR/html51/syntax.html#after-attribute-value-quoted-state
function stateAfterAttributeValueQuoted(context, char, charCode) {
if ((0, char_utils_1.isWhitespaceChar)(charCode)) {
context.state = 4 /* State.BeforeAttributeName */;
}
else if (char === '/') {
context.state = 12 /* State.SelfClosingStartTag */;
}
else if (char === '>') {
emitTagAndPreviousTextNode(context);
}
else if (char === '<') {
// start of another tag (ignore the previous, incomplete one)
startNewTag(context);
}
else {
// Any other character, "parse error". Spec says to switch to the
// BeforeAttributeState and re-consume the character, as it may be
// the start of a new attribute name
context.state = 4 /* State.BeforeAttributeName */;
reconsumeCurrentChar(context);
}
}
// A '/' has just been read in the current tag (presumably for '/>'), and
// this handles the next character
// https://www.w3.org/TR/html51/syntax.html#self-closing-start-tag-state
function stateSelfClosingStartTag(context, char) {
if (char === '>') {
context.currentTag = new CurrentTag(tslib_1.__assign(tslib_1.__assign({}, context.currentTag), { isClosing: true }));
emitTagAndPreviousTextNode(context); // resets to Data state as well
}
else {
// Note: the spec calls for a character after a '/' within a start
// tag to go back into the BeforeAttributeName state (in order to
// read more attributes, but for the purposes of Autolinker, this is
// most likely not a valid HTML tag. For example: "<something / other>"
// state = State.BeforeAttributeName;
// Instead, just treat as regular text
resetToDataState(context);
}
}
// https://www.w3.org/TR/html51/syntax.html#markup-declaration-open-state
// (HTML Comments or !DOCTYPE)
function stateMarkupDeclarationOpen(context) {
var html = context.html, charIdx = context.charIdx;
if (html.slice(charIdx, charIdx + 2) === '--') {
// html comment
context.charIdx++; // "consume" the second '-' character. Next loop iteration will consume the character after the '<!--' sequence
context.currentTag = new CurrentTag(tslib_1.__assign(tslib_1.__assign({}, context.currentTag), { type: 'comment' }));
context.state = 14 /* State.CommentStart */;
}
else if (html.slice(charIdx, charIdx + 7).toUpperCase() === 'DOCTYPE') {
context.charIdx += 6; // "consume" the characters "OCTYPE" (the current loop iteraction consumed the 'D'). Next loop iteration will consume the character after the '<!DOCTYPE' sequence
context.currentTag = new CurrentTag(tslib_1.__assign(tslib_1.__assign({}, context.currentTag), { type: 'doctype' }));
context.state = 20 /* State.Doctype */;
}
else {
// At this point, the spec specifies that the state machine should
// enter the "bogus comment" state, in which case any character(s)
// after the '<!' that were read should become an HTML comment up
// until the first '>' that is read (or EOF). Instead, we'll assume
// that a user just typed '<!' as part of some piece of non-html
// text
resetToDataState(context);
}
}
// Handles after the sequence '<!--' has been read
// https://www.w3.org/TR/html51/syntax.html#comment-start-state
function stateCommentStart(context, char) {
if (char === '-') {
// We've read the sequence '<!---' at this point (3 dashes)
context.state = 15 /* State.CommentStartDash */;
}
else if (char === '>') {
// At this point, we'll assume the comment wasn't a real comment
// so we'll just emit it as data. We basically read the sequence
// '<!-->'
resetToDataState(context);
}
else {
// Any other char, take it as part of the comment
context.state = 16 /* State.Comment */;
}
}
// We've read the sequence '<!---' at this point (3 dashes)
// https://www.w3.org/TR/html51/syntax.html#comment-start-dash-state
function stateCommentStartDash(context, char) {
if (char === '-') {
// We've read '<!----' (4 dashes) at this point
context.state = 18 /* State.CommentEnd */;
}
else if (char === '>') {
// At this point, we'll assume the comment wasn't a real comment
// so we'll just emit it as data. We basically read the sequence
// '<!--->'
resetToDataState(context);
}
else {
// Anything else, take it as a valid comment
context.state = 16 /* State.Comment */;
}
}
// Currently reading the comment's text (data)
// https://www.w3.org/TR/html51/syntax.html#comment-state
function stateComment(context, char) {
if (char === '-') {
context.state = 17 /* State.CommentEndDash */;
}
else {
// Any other character, stay in the Comment state
}
}
// When we we've read the first dash inside a comment, it may signal the
// end of the comment if we read another dash
// https://www.w3.org/TR/html51/syntax.html#comment-end-dash-state
function stateCommentEndDash(context, char) {
if (char === '-') {
context.state = 18 /* State.CommentEnd */;
}
else {
// Wasn't a dash, must still be part of the comment
context.state = 16 /* State.Comment */;
}
}
// After we've read two dashes inside a comment, it may signal the end of
// the comment if we then read a '>' char
// https://www.w3.org/TR/html51/syntax.html#comment-end-state
function stateCommentEnd(context, char) {
if (char === '>') {
emitTagAndPreviousTextNode(context);
}
else if (char === '!') {
context.state = 19 /* State.CommentEndBang */;
}
else if (char === '-') {
// A 3rd '-' has been read: stay in the CommentEnd state
}
else {
// Anything else, switch back to the comment state since we didn't
// read the full "end comment" sequence (i.e. '-->')
context.state = 16 /* State.Comment */;
}
}
// We've read the sequence '--!' inside of a comment
// https://www.w3.org/TR/html51/syntax.html#comment-end-bang-state
function stateCommentEndBang(context, char) {
if (char === '-') {
// We read the sequence '--!-' inside of a comment. The last dash
// could signify that the comment is going to close
context.state = 17 /* State.CommentEndDash */;
}
else if (char === '>') {
// End of comment with the sequence '--!>'
emitTagAndPreviousTextNode(context);
}
else {
// The '--!' was not followed by a '>', continue reading the
// comment's text
context.state = 16 /* State.Comment */;
}
}
/**
* For DOCTYPES in particular, we don't care about the attributes. Just
* advance to the '>' character and emit the tag, unless we find a '<'
* character in which case we'll start a new tag.
*
* Example doctype tag:
* <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
*
* Actual spec: https://www.w3.org/TR/html51/syntax.html#doctype-state
*/
function stateDoctype(context, char) {
if (char === '>') {
emitTagAndPreviousTextNode(context);
}
else if (char === '<') {
startNewTag(context);
}
else {
// stay in the Doctype state
}
}
/**
* Resets the state back to the Data state, and removes the current tag.
*
* We'll generally run this function whenever a "parse error" is
* encountered, where the current tag that is being read no longer looks
* like a real HTML tag.
*/
function resetToDataState(context) {
context.state = 0 /* State.Data */;
context.currentTag = noCurrentTag;
}
/**
* Starts a new HTML tag at the current index, ignoring any previous HTML
* tag that was being read.
*
* We'll generally run this function whenever we read a new '<' character,
* including when we read a '<' character inside of an HTML tag that we were
* previously reading.
*/
function startNewTag(context) {
context.state = 1 /* State.TagOpen */;
context.currentTag = new CurrentTag({ idx: context.charIdx });
}
/**
* Once we've decided to emit an open tag, that means we can also emit the
* text node before it.
*/
function emitTagAndPreviousTextNode(context) {
var textBeforeTag = context.html.slice(context.currentDataIdx, context.currentTag.idx);
if (textBeforeTag) {
// the html tag was the first element in the html string, or two
// tags next to each other, in which case we should not emit a text
// node
context.callbacks.onText(textBeforeTag, context.currentDataIdx);
}
var currentTag = context.currentTag;
if (currentTag.type === 'comment') {
context.callbacks.onComment(currentTag.idx);
}
else if (currentTag.type === 'doctype') {
context.callbacks.onDoctype(currentTag.idx);
}
else {
if (currentTag.isOpening) {
context.callbacks.onOpenTag(currentTag.name, currentTag.idx);
}
if (currentTag.isClosing) {
// note: self-closing tags will emit both opening and closing
context.callbacks.onCloseTag(currentTag.name, currentTag.idx);
}
}
// Since we just emitted a tag, reset to the data state for the next char
resetToDataState(context);
context.currentDataIdx = context.charIdx + 1;
}
function emitText(context) {
var text = context.html.slice(context.currentDataIdx, context.charIdx);
context.callbacks.onText(text, context.currentDataIdx);
context.currentDataIdx = context.charIdx + 1;
}
/**
* Captures the tag name from the start of the tag to the current character
* index, and converts it to lower case
*/
function captureTagName(context) {
var startIdx = context.currentTag.idx + (context.currentTag.isClosing ? 2 : 1);
return context.html.slice(startIdx, context.charIdx).toLowerCase();
}
/**
* Causes the main loop to re-consume the current character, such as after
* encountering a "parse error" that changed state and needs to reconsume
* the same character in that new state.
*/
function reconsumeCurrentChar(context) {
context.charIdx--;
}
//# sourceMappingURL=parse-html.js.map
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
import Autolinker from './autolinker';
export default Autolinker;
export { Autolinker };
export * from './autolinker';
export * from './anchor-tag-builder';
export * from './html-tag';
export * from './match/index';
export * from './parser/index';
+19
View File
@@ -0,0 +1,19 @@
"use strict";
// Note: the following line is added by scripts/fix-common-js-output.ts to allow require('autolinker') to work correctly
exports = module.exports = require('./autolinker').default; // redefine 'exports' object as the Autolinker class itself
// WARNING: This file is modified a bit when it is compiled into index.js in
// order to support nodejs interoperability with require('autolinker') directly.
// This is done by the buildSrcFixCommonJsIndexTask() function in the gulpfile.
// See that function for more details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.Autolinker = void 0;
var tslib_1 = require("tslib");
var autolinker_1 = tslib_1.__importDefault(require("./autolinker"));
exports.Autolinker = autolinker_1.default;
exports.default = autolinker_1.default;
tslib_1.__exportStar(require("./autolinker"), exports);
tslib_1.__exportStar(require("./anchor-tag-builder"), exports);
tslib_1.__exportStar(require("./html-tag"), exports);
tslib_1.__exportStar(require("./match/index"), exports);
tslib_1.__exportStar(require("./parser/index"), exports);
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,+EAA+E;AAC/E,sCAAsC;;;;AAEtC,oEAAsC;AAG7B,qBAHF,oBAAU,CAGE;AADnB,kBAAe,oBAAU,CAAC;AAG1B,uDAA6B;AAC7B,+DAAqC;AACrC,qDAA2B;AAC3B,wDAA8B;AAC9B,yDAA+B","sourcesContent":["// WARNING: This file is modified a bit when it is compiled into index.js in\n// order to support nodejs interoperability with require('autolinker') directly.\n// This is done by the buildSrcFixCommonJsIndexTask() function in the gulpfile.\n// See that function for more details.\n\nimport Autolinker from './autolinker';\n\nexport default Autolinker;\nexport { Autolinker };\n\nexport * from './autolinker';\nexport * from './anchor-tag-builder';\nexport * from './html-tag';\nexport * from './match/index';\nexport * from './parser/index';\n"]}
+179
View File
@@ -0,0 +1,179 @@
import { AnchorTagBuilder } from '../anchor-tag-builder';
import { HtmlTag } from '../html-tag';
import { MatchType } from './match';
/**
* @abstract
* @class Autolinker.match.AbstractMatch
*
* Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a
* {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, and Mentions (Twitter, Instagram, Soundcloud)
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( match ) {
* console.log( "href = ", match.getAnchorHref() );
* console.log( "text = ", match.getAnchorText() );
*
* switch( match.getType() ) {
* case 'url' :
* console.log( "url: ", match.getUrl() );
*
* case 'email' :
* console.log( "email: ", match.getEmail() );
*
* case 'mention' :
* console.log( "mention: ", match.getMention() );
* }
* }
* } );
*
* See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}.
*/
export declare abstract class AbstractMatch {
/**
* @public
* @property {'url'/'email'/'hashtag'/'mention'/'phone'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
abstract readonly type: MatchType;
/**
* @cfg {Autolinker.AnchorTagBuilder} tagBuilder (required)
*
* Reference to the AnchorTagBuilder instance to use to generate an anchor
* tag for the Match.
*/
private _;
private readonly tagBuilder;
/**
* @cfg {String} matchedText (required)
*
* The original text that was matched by the {@link Autolinker.matcher.Matcher}.
*/
protected readonly matchedText: string;
/**
* @cfg {Number} offset (required)
*
* The offset of where the match was made in the input string.
*/
private offset;
/**
* @member Autolinker.match.Match
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
constructor(cfg: AbstractMatchConfig);
/**
* Returns a string name for the type of match that this class represents.
*
* @deprecated Use {@link #type} instead which can assist in type-narrowing
* for TypeScript.
* @abstract
* @return {String}
*/
abstract getType(): MatchType;
/**
* Returns the original text that was matched.
*
* @return {String}
*/
getMatchedText(): string;
/**
* Sets the {@link #offset} of where the match was made in the input string.
*
* A {@link Autolinker.matcher.Matcher} will be fed only HTML text nodes,
* and will therefore set an original offset that is relative to the HTML
* text node itself. However, we want this offset to be relative to the full
* HTML input string, and thus if using {@link Autolinker#parse} (rather
* than calling a {@link Autolinker.matcher.Matcher} directly), then this
* offset is corrected after the Matcher itself has done its job.
*
* @private
* @param {Number} offset
*/
setOffset(offset: number): void;
/**
* Returns the offset of where the match was made in the input string. This
* is the 0-based index of the match.
*
* @return {Number}
*/
getOffset(): number;
/**
* Returns the anchor href that should be generated for the match.
*
* @abstract
* @return {String}
*/
abstract getAnchorHref(): string;
/**
* Returns the anchor text that should be generated for the match.
*
* @abstract
* @return {String}
*/
abstract getAnchorText(): string;
/**
* Returns the CSS class suffix(es) for this match.
*
* A CSS class suffix is appended to the {@link Autolinker#className} in
* the {@link Autolinker.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(): string[];
/**
* Builds and returns an {@link Autolinker.HtmlTag} instance based on the
* Match.
*
* This can be used to easily generate anchor tags from matches, and either
* return their HTML string, or modify them before doing so.
*
* Example Usage:
*
* var tag = match.buildTag();
* tag.addClass( 'cordova-link' );
* tag.setAttr( 'target', '_system' );
*
* tag.toAnchorString(); // <a href="http://google.com" class="cordova-link" target="_system">Google</a>
*
* Example Usage in {@link Autolinker#replaceFn}:
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( match ) {
* var tag = match.buildTag(); // 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>
*/
buildTag(): HtmlTag;
}
export interface AbstractMatchConfig {
tagBuilder: AnchorTagBuilder;
matchedText: string;
offset: number;
}
+159
View File
@@ -0,0 +1,159 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractMatch = void 0;
/**
* @abstract
* @class Autolinker.match.AbstractMatch
*
* Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a
* {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, and Mentions (Twitter, Instagram, Soundcloud)
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( match ) {
* console.log( "href = ", match.getAnchorHref() );
* console.log( "text = ", match.getAnchorText() );
*
* switch( match.getType() ) {
* case 'url' :
* console.log( "url: ", match.getUrl() );
*
* case 'email' :
* console.log( "email: ", match.getEmail() );
*
* case 'mention' :
* console.log( "mention: ", match.getMention() );
* }
* }
* } );
*
* See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}.
*/
var AbstractMatch = /** @class */ (function () {
/**
* @member Autolinker.match.Match
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
function AbstractMatch(cfg) {
/**
* @cfg {Autolinker.AnchorTagBuilder} tagBuilder (required)
*
* Reference to the AnchorTagBuilder instance to use to generate an anchor
* tag for the Match.
*/
// @ts-expect-error Property used just to get the above doc comment into the ES5 output and documentation generator
this._ = null;
/**
* @cfg {String} matchedText (required)
*
* The original text that was matched by the {@link Autolinker.matcher.Matcher}.
*/
this.matchedText = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Number} offset (required)
*
* The offset of where the match was made in the input string.
*/
this.offset = 0; // default value just to get the above doc comment in the ES5 output and documentation generator
this.tagBuilder = cfg.tagBuilder;
this.matchedText = cfg.matchedText;
this.offset = cfg.offset;
}
/**
* Returns the original text that was matched.
*
* @return {String}
*/
AbstractMatch.prototype.getMatchedText = function () {
return this.matchedText;
};
/**
* Sets the {@link #offset} of where the match was made in the input string.
*
* A {@link Autolinker.matcher.Matcher} will be fed only HTML text nodes,
* and will therefore set an original offset that is relative to the HTML
* text node itself. However, we want this offset to be relative to the full
* HTML input string, and thus if using {@link Autolinker#parse} (rather
* than calling a {@link Autolinker.matcher.Matcher} directly), then this
* offset is corrected after the Matcher itself has done its job.
*
* @private
* @param {Number} offset
*/
AbstractMatch.prototype.setOffset = function (offset) {
this.offset = offset;
};
/**
* Returns the offset of where the match was made in the input string. This
* is the 0-based index of the match.
*
* @return {Number}
*/
AbstractMatch.prototype.getOffset = function () {
return this.offset;
};
/**
* Returns the CSS class suffix(es) for this match.
*
* A CSS class suffix is appended to the {@link Autolinker#className} in
* the {@link Autolinker.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[]}
*/
AbstractMatch.prototype.getCssClassSuffixes = function () {
return [this.type];
};
/**
* Builds and returns an {@link Autolinker.HtmlTag} instance based on the
* Match.
*
* This can be used to easily generate anchor tags from matches, and either
* return their HTML string, or modify them before doing so.
*
* Example Usage:
*
* var tag = match.buildTag();
* tag.addClass( 'cordova-link' );
* tag.setAttr( 'target', '_system' );
*
* tag.toAnchorString(); // <a href="http://google.com" class="cordova-link" target="_system">Google</a>
*
* Example Usage in {@link Autolinker#replaceFn}:
*
* var html = Autolinker.link( "Test google.com", {
* replaceFn : function( match ) {
* var tag = match.buildTag(); // 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>
*/
AbstractMatch.prototype.buildTag = function () {
return this.tagBuilder.build(this);
};
return AbstractMatch;
}());
exports.AbstractMatch = AbstractMatch;
//# sourceMappingURL=abstract-match.js.map
File diff suppressed because one or more lines are too long
+60
View File
@@ -0,0 +1,60 @@
import { AbstractMatchConfig, AbstractMatch } from './abstract-match';
/**
* @class Autolinker.match.Email
* @extends Autolinker.match.AbstractMatch
*
* Represents a Email match found in an input string which should be Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more details.
*/
export declare class EmailMatch extends AbstractMatch {
/**
* @public
* @property {'email'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
readonly type: "email";
/**
* @cfg {String} email (required)
*
* The email address that was matched.
*/
private readonly email;
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
constructor(cfg: EmailMatchConfig);
/**
* Returns a string name for the type of match that this class represents.
* For the case of EmailMatch, returns 'email'.
*
* @return {String}
*/
getType(): 'email';
/**
* Returns the email address that was matched.
*
* @return {String}
*/
getEmail(): string;
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref(): string;
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText(): string;
}
export interface EmailMatchConfig extends AbstractMatchConfig {
email: string;
}
+77
View File
@@ -0,0 +1,77 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmailMatch = void 0;
var tslib_1 = require("tslib");
var abstract_match_1 = require("./abstract-match");
/**
* @class Autolinker.match.Email
* @extends Autolinker.match.AbstractMatch
*
* Represents a Email match found in an input string which should be Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more details.
*/
var EmailMatch = /** @class */ (function (_super) {
tslib_1.__extends(EmailMatch, _super);
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
function EmailMatch(cfg) {
var _this = _super.call(this, cfg) || this;
/**
* @public
* @property {'email'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
_this.type = 'email';
/**
* @cfg {String} email (required)
*
* The email address that was matched.
*/
_this.email = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
_this.email = cfg.email;
return _this;
}
/**
* Returns a string name for the type of match that this class represents.
* For the case of EmailMatch, returns 'email'.
*
* @return {String}
*/
EmailMatch.prototype.getType = function () {
return 'email';
};
/**
* Returns the email address that was matched.
*
* @return {String}
*/
EmailMatch.prototype.getEmail = function () {
return this.email;
};
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
EmailMatch.prototype.getAnchorHref = function () {
return 'mailto:' + this.email;
};
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
EmailMatch.prototype.getAnchorText = function () {
return this.email;
};
return EmailMatch;
}(abstract_match_1.AbstractMatch));
exports.EmailMatch = EmailMatch;
//# sourceMappingURL=email-match.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"email-match.js","sourceRoot":"","sources":["../../../src/match/email-match.ts"],"names":[],"mappings":";;;;AAAA,mDAAsE;AAEtE;;;;;;;GAOG;AACH;IAAgC,sCAAa;IAkBzC;;;;OAIG;IACH,oBAAY,GAAqB;QAC7B,YAAA,MAAK,YAAC,GAAG,CAAC,SAAC;QAvBf;;;;;;;WAOG;QACa,UAAI,GAAG,OAAgB,CAAC;QAExC;;;;WAIG;QACc,WAAK,GAAW,EAAE,CAAC,CAAC,gGAAgG;QAUjI,KAAI,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;;IAC3B,CAAC;IAED;;;;;OAKG;IACH,4BAAO,GAAP;QACI,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,6BAAQ,GAAR;QACI,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACH,kCAAa,GAAb;QACI,OAAO,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACH,kCAAa,GAAb;QACI,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IACL,iBAAC;AAAD,CAAC,AAjED,CAAgC,8BAAa,GAiE5C;AAjEY,gCAAU","sourcesContent":["import { AbstractMatchConfig, AbstractMatch } from './abstract-match';\n\n/**\n * @class Autolinker.match.Email\n * @extends Autolinker.match.AbstractMatch\n *\n * Represents a Email match found in an input string which should be Autolinked.\n *\n * See this class's superclass ({@link Autolinker.match.Match}) for more details.\n */\nexport class EmailMatch extends AbstractMatch {\n /**\n * @public\n * @property {'email'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public readonly type = 'email' as const;\n\n /**\n * @cfg {String} email (required)\n *\n * The email address that was matched.\n */\n private readonly email: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: EmailMatchConfig) {\n super(cfg);\n\n this.email = cfg.email;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n * For the case of EmailMatch, returns 'email'.\n *\n * @return {String}\n */\n getType(): 'email' {\n return 'email';\n }\n\n /**\n * Returns the email address that was matched.\n *\n * @return {String}\n */\n getEmail() {\n return this.email;\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorHref() {\n return 'mailto:' + this.email;\n }\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorText() {\n return this.email;\n }\n}\n\nexport interface EmailMatchConfig extends AbstractMatchConfig {\n email: string;\n}\n"]}
+86
View File
@@ -0,0 +1,86 @@
import { HashtagService } from '../parser/hashtag-utils';
import { AbstractMatch, AbstractMatchConfig } from './abstract-match';
/**
* @class Autolinker.match.Hashtag
* @extends Autolinker.match.AbstractMatch
*
* Represents a Hashtag match found in an input string which should be
* Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more
* details.
*/
export declare class HashtagMatch extends AbstractMatch {
/**
* @public
* @property {'hashtag'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
readonly type: "hashtag";
/**
* @cfg {String} serviceName
*
* The service to point hashtag matches to. See {@link Autolinker#hashtag}
* for available values.
*/
private readonly serviceName;
/**
* @cfg {String} hashtag (required)
*
* The HashtagMatch that was matched, without the '#'.
*/
private readonly hashtag;
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
constructor(cfg: HashtagMatchConfig);
/**
* Returns a string name for the type of match that this class represents.
* For the case of HashtagMatch, returns 'hashtag'.
*
* @return {String}
*/
getType(): 'hashtag';
/**
* Returns the configured {@link #serviceName} to point the HashtagMatch to.
* Ex: 'facebook', 'twitter'.
*
* @return {String}
*/
getServiceName(): HashtagService;
/**
* Returns the matched hashtag, without the '#' character.
*
* @return {String}
*/
getHashtag(): string;
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref(): string;
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText(): string;
/**
* 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(): string[];
}
export interface HashtagMatchConfig extends AbstractMatchConfig {
serviceName: HashtagService;
hashtag: string;
}
+127
View File
@@ -0,0 +1,127 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HashtagMatch = void 0;
var tslib_1 = require("tslib");
var utils_1 = require("../utils");
var abstract_match_1 = require("./abstract-match");
/**
* @class Autolinker.match.Hashtag
* @extends Autolinker.match.AbstractMatch
*
* Represents a Hashtag match found in an input string which should be
* Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more
* details.
*/
var HashtagMatch = /** @class */ (function (_super) {
tslib_1.__extends(HashtagMatch, _super);
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
function HashtagMatch(cfg) {
var _this = _super.call(this, cfg) || this;
/**
* @public
* @property {'hashtag'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
_this.type = 'hashtag';
/**
* @cfg {String} serviceName
*
* The service to point hashtag matches to. See {@link Autolinker#hashtag}
* for available values.
*/
_this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {String} hashtag (required)
*
* The HashtagMatch that was matched, without the '#'.
*/
_this.hashtag = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
_this.serviceName = cfg.serviceName;
_this.hashtag = cfg.hashtag;
return _this;
}
/**
* Returns a string name for the type of match that this class represents.
* For the case of HashtagMatch, returns 'hashtag'.
*
* @return {String}
*/
HashtagMatch.prototype.getType = function () {
return 'hashtag';
};
/**
* Returns the configured {@link #serviceName} to point the HashtagMatch to.
* Ex: 'facebook', 'twitter'.
*
* @return {String}
*/
HashtagMatch.prototype.getServiceName = function () {
return this.serviceName;
};
/**
* Returns the matched hashtag, without the '#' character.
*
* @return {String}
*/
HashtagMatch.prototype.getHashtag = function () {
return this.hashtag;
};
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
HashtagMatch.prototype.getAnchorHref = function () {
var serviceName = this.serviceName, hashtag = this.hashtag;
switch (serviceName) {
case 'twitter':
return 'https://twitter.com/hashtag/' + hashtag;
case 'facebook':
return 'https://www.facebook.com/hashtag/' + hashtag;
case 'instagram':
return 'https://instagram.com/explore/tags/' + hashtag;
case 'tiktok':
return 'https://www.tiktok.com/tag/' + hashtag;
case 'youtube':
return 'https://youtube.com/hashtag/' + hashtag;
/* istanbul ignore next */
default:
// Should never happen because Autolinker's constructor should block any invalid values, but just in case
(0, utils_1.assertNever)(serviceName);
}
};
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
HashtagMatch.prototype.getAnchorText = function () {
return '#' + this.hashtag;
};
/**
* 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[]}
*/
HashtagMatch.prototype.getCssClassSuffixes = function () {
var cssClassSuffixes = _super.prototype.getCssClassSuffixes.call(this), serviceName = this.getServiceName();
if (serviceName) {
cssClassSuffixes.push(serviceName);
}
return cssClassSuffixes;
};
return HashtagMatch;
}(abstract_match_1.AbstractMatch));
exports.HashtagMatch = HashtagMatch;
//# sourceMappingURL=hashtag-match.js.map
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
export * from './match';
export * from './email-match';
export * from './hashtag-match';
export * from './abstract-match';
export * from './mention-match';
export * from './phone-match';
export * from './url-match';
+11
View File
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
tslib_1.__exportStar(require("./match"), exports);
tslib_1.__exportStar(require("./email-match"), exports);
tslib_1.__exportStar(require("./hashtag-match"), exports);
tslib_1.__exportStar(require("./abstract-match"), exports);
tslib_1.__exportStar(require("./mention-match"), exports);
tslib_1.__exportStar(require("./phone-match"), exports);
tslib_1.__exportStar(require("./url-match"), exports);
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/match/index.ts"],"names":[],"mappings":";;;AAAA,kDAAwB;AACxB,wDAA8B;AAC9B,0DAAgC;AAChC,2DAAiC;AACjC,0DAAgC;AAChC,wDAA8B;AAC9B,sDAA4B","sourcesContent":["export * from './match';\nexport * from './email-match';\nexport * from './hashtag-match';\nexport * from './abstract-match';\nexport * from './mention-match';\nexport * from './phone-match';\nexport * from './url-match';\n"]}
+7
View File
@@ -0,0 +1,7 @@
import { EmailMatch } from './email-match';
import { HashtagMatch } from './hashtag-match';
import { MentionMatch } from './mention-match';
import { PhoneMatch } from './phone-match';
import { UrlMatch } from './url-match';
export type Match = EmailMatch | HashtagMatch | MentionMatch | PhoneMatch | UrlMatch;
export type MatchType = 'email' | 'hashtag' | 'mention' | 'phone' | 'url';
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=match.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../../src/match/match.ts"],"names":[],"mappings":"","sourcesContent":["import { EmailMatch } from './email-match';\nimport { HashtagMatch } from './hashtag-match';\nimport { MentionMatch } from './mention-match';\nimport { PhoneMatch } from './phone-match';\nimport { UrlMatch } from './url-match';\n\nexport type Match = EmailMatch | HashtagMatch | MentionMatch | PhoneMatch | UrlMatch;\nexport type MatchType = 'email' | 'hashtag' | 'mention' | 'phone' | 'url';\n"]}
+84
View File
@@ -0,0 +1,84 @@
import { MentionService } from '../parser/mention-utils';
import { AbstractMatch, AbstractMatchConfig } from './abstract-match';
/**
* @class Autolinker.match.Mention
* @extends Autolinker.match.AbstractMatch
*
* 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.
*/
export declare class MentionMatch extends AbstractMatch {
/**
* @public
* @property {'mention'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
readonly type: "mention";
/**
* @cfg {String} serviceName
*
* The service to point mention matches to. See {@link Autolinker#mention}
* for available values.
*/
private readonly serviceName;
/**
* @cfg {String} mention (required)
*
* The Mention that was matched, without the '@' character.
*/
private readonly mention;
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
constructor(cfg: MentionMatchConfig);
/**
* Returns a string name for the type of match that this class represents.
* For the case of MentionMatch, returns 'mention'.
*
* @return {String}
*/
getType(): 'mention';
/**
* Returns the mention, without the '@' character.
*
* @return {String}
*/
getMention(): string;
/**
* Returns the configured {@link #serviceName} to point the mention to.
* Ex: 'instagram', 'twitter', 'soundcloud'.
*
* @return {String}
*/
getServiceName(): MentionService;
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref(): string;
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText(): string;
/**
* 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(): string[];
}
export interface MentionMatchConfig extends AbstractMatchConfig {
serviceName: MentionService;
mention: string;
}
+124
View File
@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MentionMatch = void 0;
var tslib_1 = require("tslib");
var utils_1 = require("../utils");
var abstract_match_1 = require("./abstract-match");
/**
* @class Autolinker.match.Mention
* @extends Autolinker.match.AbstractMatch
*
* 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.
*/
var MentionMatch = /** @class */ (function (_super) {
tslib_1.__extends(MentionMatch, _super);
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
function MentionMatch(cfg) {
var _this = _super.call(this, cfg) || this;
/**
* @public
* @property {'mention'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
_this.type = 'mention';
/**
* @cfg {String} serviceName
*
* The service to point mention matches to. See {@link Autolinker#mention}
* for available values.
*/
_this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {String} mention (required)
*
* The Mention that was matched, without the '@' character.
*/
_this.mention = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
_this.mention = cfg.mention;
_this.serviceName = cfg.serviceName;
return _this;
}
/**
* Returns a string name for the type of match that this class represents.
* For the case of MentionMatch, returns 'mention'.
*
* @return {String}
*/
MentionMatch.prototype.getType = function () {
return 'mention';
};
/**
* Returns the mention, without the '@' character.
*
* @return {String}
*/
MentionMatch.prototype.getMention = function () {
return this.mention;
};
/**
* Returns the configured {@link #serviceName} to point the mention to.
* Ex: 'instagram', 'twitter', 'soundcloud'.
*
* @return {String}
*/
MentionMatch.prototype.getServiceName = function () {
return this.serviceName;
};
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
MentionMatch.prototype.getAnchorHref = function () {
switch (this.serviceName) {
case 'twitter':
return 'https://twitter.com/' + this.mention;
case 'instagram':
return 'https://instagram.com/' + this.mention;
case 'soundcloud':
return 'https://soundcloud.com/' + this.mention;
case 'tiktok':
return 'https://www.tiktok.com/@' + this.mention;
case 'youtube':
return 'https://youtube.com/@' + this.mention;
/* istanbul ignore next */
default:
// Should never happen because Autolinker's constructor should block any invalid values, but just in case.
(0, utils_1.assertNever)(this.serviceName);
}
};
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
MentionMatch.prototype.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[]}
*/
MentionMatch.prototype.getCssClassSuffixes = function () {
var cssClassSuffixes = _super.prototype.getCssClassSuffixes.call(this), serviceName = this.getServiceName();
if (serviceName) {
cssClassSuffixes.push(serviceName);
}
return cssClassSuffixes;
};
return MentionMatch;
}(abstract_match_1.AbstractMatch));
exports.MentionMatch = MentionMatch;
//# sourceMappingURL=mention-match.js.map
File diff suppressed because one or more lines are too long
+88
View File
@@ -0,0 +1,88 @@
import { AbstractMatch, AbstractMatchConfig } from './abstract-match';
/**
* @class Autolinker.match.Phone
* @extends Autolinker.match.AbstractMatch
*
* Represents a Phone number match found in an input string which should be
* Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more
* details.
*/
export declare class PhoneMatch extends AbstractMatch {
/**
* @public
* @property {'phone'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
readonly type: "phone";
/**
* @protected
* @property {String} number (required)
*
* The phone number that was matched, without any delimiter characters.
*
* Note: This is a string to allow for prefixed 0's.
*/
private readonly number;
/**
* @protected
* @property {Boolean} plusSign (required)
*
* `true` if the matched phone number started with a '+' sign. We'll include
* it in the `tel:` URL if so, as this is needed for international numbers.
*
* Ex: '+1 (123) 456 7879'
*/
private readonly plusSign;
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
constructor(cfg: PhoneMatchConfig);
/**
* Returns a string name for the type of match that this class represents.
* For the case of PhoneMatch, returns 'phone'.
*
* @return {String}
*/
getType(): 'phone';
/**
* Returns the phone number that was matched as a string, without any
* delimiter characters.
*
* Note: This is a string to allow for prefixed 0's.
*
* @return {String}
*/
getPhoneNumber(): string;
/**
* Alias of {@link #getPhoneNumber}, returns the phone number that was
* matched as a string, without any delimiter characters.
*
* Note: This is a string to allow for prefixed 0's.
*
* @return {String}
*/
getNumber(): string;
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref(): string;
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText(): string;
}
export interface PhoneMatchConfig extends AbstractMatchConfig {
number: string;
plusSign: boolean;
}
+107
View File
@@ -0,0 +1,107 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PhoneMatch = void 0;
var tslib_1 = require("tslib");
var abstract_match_1 = require("./abstract-match");
/**
* @class Autolinker.match.Phone
* @extends Autolinker.match.AbstractMatch
*
* Represents a Phone number match found in an input string which should be
* Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more
* details.
*/
var PhoneMatch = /** @class */ (function (_super) {
tslib_1.__extends(PhoneMatch, _super);
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
function PhoneMatch(cfg) {
var _this = _super.call(this, cfg) || this;
/**
* @public
* @property {'phone'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
_this.type = 'phone';
/**
* @protected
* @property {String} number (required)
*
* The phone number that was matched, without any delimiter characters.
*
* Note: This is a string to allow for prefixed 0's.
*/
_this.number = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @protected
* @property {Boolean} plusSign (required)
*
* `true` if the matched phone number started with a '+' sign. We'll include
* it in the `tel:` URL if so, as this is needed for international numbers.
*
* Ex: '+1 (123) 456 7879'
*/
_this.plusSign = false; // default value just to get the above doc comment in the ES5 output and documentation generator
_this.number = cfg.number;
_this.plusSign = cfg.plusSign;
return _this;
}
/**
* Returns a string name for the type of match that this class represents.
* For the case of PhoneMatch, returns 'phone'.
*
* @return {String}
*/
PhoneMatch.prototype.getType = function () {
return 'phone';
};
/**
* Returns the phone number that was matched as a string, without any
* delimiter characters.
*
* Note: This is a string to allow for prefixed 0's.
*
* @return {String}
*/
PhoneMatch.prototype.getPhoneNumber = function () {
return this.number;
};
/**
* Alias of {@link #getPhoneNumber}, returns the phone number that was
* matched as a string, without any delimiter characters.
*
* Note: This is a string to allow for prefixed 0's.
*
* @return {String}
*/
PhoneMatch.prototype.getNumber = function () {
return this.getPhoneNumber();
};
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
PhoneMatch.prototype.getAnchorHref = function () {
return 'tel:' + (this.plusSign ? '+' : '') + this.number;
};
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
PhoneMatch.prototype.getAnchorText = function () {
return this.matchedText;
};
return PhoneMatch;
}(abstract_match_1.AbstractMatch));
exports.PhoneMatch = PhoneMatch;
//# sourceMappingURL=phone-match.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"phone-match.js","sourceRoot":"","sources":["../../../src/match/phone-match.ts"],"names":[],"mappings":";;;;AAAA,mDAAsE;AAEtE;;;;;;;;;GASG;AACH;IAAgC,sCAAa;IAgCzC;;;;OAIG;IACH,oBAAY,GAAqB;QAC7B,YAAA,MAAK,YAAC,GAAG,CAAC,SAAC;QArCf;;;;;;;WAOG;QACa,UAAI,GAAG,OAAgB,CAAC;QAExC;;;;;;;WAOG;QACc,YAAM,GAAW,EAAE,CAAC,CAAC,gGAAgG;QAEtI;;;;;;;;WAQG;QACc,cAAQ,GAAY,KAAK,CAAC,CAAC,gGAAgG;QAUxI,KAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QACzB,KAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;;IACjC,CAAC;IAED;;;;;OAKG;IACH,4BAAO,GAAP;QACI,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;;;;OAOG;IACH,mCAAc,GAAd;QACI,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;IAED;;;;;;;OAOG;IACH,8BAAS,GAAT;QACI,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACH,kCAAa,GAAb;QACI,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IAC7D,CAAC;IAED;;;;OAIG;IACH,kCAAa,GAAb;QACI,OAAO,IAAI,CAAC,WAAW,CAAC;IAC5B,CAAC;IACL,iBAAC;AAAD,CAAC,AA/FD,CAAgC,8BAAa,GA+F5C;AA/FY,gCAAU","sourcesContent":["import { AbstractMatch, AbstractMatchConfig } from './abstract-match';\n\n/**\n * @class Autolinker.match.Phone\n * @extends Autolinker.match.AbstractMatch\n *\n * Represents a Phone number match found in an input string which should be\n * Autolinked.\n *\n * See this class's superclass ({@link Autolinker.match.Match}) for more\n * details.\n */\nexport class PhoneMatch extends AbstractMatch {\n /**\n * @public\n * @property {'phone'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public readonly type = 'phone' as const;\n\n /**\n * @protected\n * @property {String} number (required)\n *\n * The phone number that was matched, without any delimiter characters.\n *\n * Note: This is a string to allow for prefixed 0's.\n */\n private readonly number: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @protected\n * @property {Boolean} plusSign (required)\n *\n * `true` if the matched phone number started with a '+' sign. We'll include\n * it in the `tel:` URL if so, as this is needed for international numbers.\n *\n * Ex: '+1 (123) 456 7879'\n */\n private readonly plusSign: boolean = false; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: PhoneMatchConfig) {\n super(cfg);\n\n this.number = cfg.number;\n this.plusSign = cfg.plusSign;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n * For the case of PhoneMatch, returns 'phone'.\n *\n * @return {String}\n */\n getType(): 'phone' {\n return 'phone';\n }\n\n /**\n * Returns the phone number that was matched as a string, without any\n * delimiter characters.\n *\n * Note: This is a string to allow for prefixed 0's.\n *\n * @return {String}\n */\n getPhoneNumber(): string {\n return this.number;\n }\n\n /**\n * Alias of {@link #getPhoneNumber}, returns the phone number that was\n * matched as a string, without any delimiter characters.\n *\n * Note: This is a string to allow for prefixed 0's.\n *\n * @return {String}\n */\n getNumber(): string {\n return this.getPhoneNumber();\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorHref(): string {\n return 'tel:' + (this.plusSign ? '+' : '') + this.number;\n }\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorText(): string {\n return this.matchedText;\n }\n}\n\nexport interface PhoneMatchConfig extends AbstractMatchConfig {\n number: string;\n plusSign: boolean;\n}\n"]}
+121
View File
@@ -0,0 +1,121 @@
import { AbstractMatch, AbstractMatchConfig } from './abstract-match';
import type { StripPrefixConfigObj } from '../autolinker';
/**
* @class Autolinker.match.Url
* @extends Autolinker.match.AbstractMatch
*
* Represents a Url match found in an input string which should be Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more details.
*/
export declare class UrlMatch extends AbstractMatch {
/**
* @public
* @property {'url'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
readonly type: "url";
/**
* @cfg {String} url (required)
*
* The url that was matched.
*/
private url;
/**
* @cfg {"scheme"/"www"/"tld"} urlMatchType (required)
*
* The type of URL match that this class represents. This helps to determine
* if the match was made in the original text with a prefixed scheme (ex:
* 'http://www.google.com'), a prefixed 'www' (ex: 'www.google.com'), or
* was matched by a known top-level domain (ex: 'google.com').
*/
private readonly urlMatchType;
/**
* @cfg {Boolean} protocolRelativeMatch (required)
*
* `true` if the URL is a protocol-relative match. A protocol-relative match
* is a URL that starts with '//', and will be either http:// or https://
* based on the protocol that the site is loaded under.
*/
private readonly protocolRelativeMatch;
/**
* @cfg {Object} stripPrefix (required)
*
* The Object form of {@link Autolinker#cfg-stripPrefix}.
*/
private readonly stripPrefix;
/**
* @cfg {Boolean} stripTrailingSlash (required)
* @inheritdoc Autolinker#cfg-stripTrailingSlash
*/
private readonly stripTrailingSlash;
/**
* @cfg {Boolean} decodePercentEncoding (required)
* @inheritdoc Autolinker#cfg-decodePercentEncoding
*/
private readonly decodePercentEncoding;
/**
* @private
* @property {Boolean} protocolPrepended
*
* Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the
* {@link #url} did not have a protocol)
*/
private protocolPrepended;
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
constructor(cfg: UrlMatchConfig);
/**
* Returns a string name for the type of match that this class represents.
* For the case of UrlMatch, returns 'url'.
*
* @return {String}
*/
getType(): 'url';
/**
* Returns a string name for the type of URL match that this class
* represents.
*
* This helps to determine if the match was made in the original text with a
* prefixed scheme (ex: 'http://www.google.com'), a prefixed 'www' (ex:
* 'www.google.com'), or was matched by a known top-level domain (ex:
* 'google.com').
*
* @return {"scheme"/"www"/"tld"}
*/
getUrlMatchType(): UrlMatchType;
/**
* Returns the url that was matched, assuming the protocol to be 'http://' if the original
* match was missing a protocol.
*
* @return {String}
*/
getUrl(): string;
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
getAnchorHref(): string;
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
getAnchorText(): string;
}
export interface UrlMatchConfig extends AbstractMatchConfig {
url: string;
urlMatchType: UrlMatchType;
protocolRelativeMatch: boolean;
stripPrefix: Required<StripPrefixConfigObj>;
stripTrailingSlash: boolean;
decodePercentEncoding: boolean;
}
export type UrlMatchType = 'scheme' | 'tld' | 'ipV4';
+280
View File
@@ -0,0 +1,280 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UrlMatch = void 0;
var tslib_1 = require("tslib");
var abstract_match_1 = require("./abstract-match");
var uri_utils_1 = require("../parser/uri-utils");
/**
* A regular expression used to remove the 'www.' from URLs.
*/
var wwwPrefixRegex = /^(https?:\/\/)?(?:www\.)?/i;
/**
* The regular expression used to remove the protocol-relative '//' from a URL
* string, for purposes of formatting the anchor text. A protocol-relative URL
* is, for example, "//yahoo.com"
*/
var protocolRelativeRegex = /^\/\//;
/**
* @class Autolinker.match.Url
* @extends Autolinker.match.AbstractMatch
*
* Represents a Url match found in an input string which should be Autolinked.
*
* See this class's superclass ({@link Autolinker.match.Match}) for more details.
*/
var UrlMatch = /** @class */ (function (_super) {
tslib_1.__extends(UrlMatch, _super);
/**
* @method constructor
* @param {Object} cfg The configuration properties for the Match
* instance, specified in an Object (map).
*/
function UrlMatch(cfg) {
var _this = _super.call(this, cfg) || this;
/**
* @public
* @property {'url'} type
*
* A string name for the type of match that this class represents. Can be
* used in a TypeScript discriminating union to type-narrow from the
* `Match` type.
*/
_this.type = 'url';
/**
* @cfg {String} url (required)
*
* The url that was matched.
*/
_this.url = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {"scheme"/"www"/"tld"} urlMatchType (required)
*
* The type of URL match that this class represents. This helps to determine
* if the match was made in the original text with a prefixed scheme (ex:
* 'http://www.google.com'), a prefixed 'www' (ex: 'www.google.com'), or
* was matched by a known top-level domain (ex: 'google.com').
*/
_this.urlMatchType = 'scheme'; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} protocolRelativeMatch (required)
*
* `true` if the URL is a protocol-relative match. A protocol-relative match
* is a URL that starts with '//', and will be either http:// or https://
* based on the protocol that the site is loaded under.
*/
_this.protocolRelativeMatch = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Object} stripPrefix (required)
*
* The Object form of {@link Autolinker#cfg-stripPrefix}.
*/
_this.stripPrefix = {
scheme: true,
www: true,
}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} stripTrailingSlash (required)
* @inheritdoc Autolinker#cfg-stripTrailingSlash
*/
_this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} decodePercentEncoding (required)
* @inheritdoc Autolinker#cfg-decodePercentEncoding
*/
_this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @private
* @property {Boolean} protocolPrepended
*
* Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the
* {@link #url} did not have a protocol)
*/
_this.protocolPrepended = false;
_this.urlMatchType = cfg.urlMatchType;
_this.url = cfg.url;
_this.protocolRelativeMatch = cfg.protocolRelativeMatch;
_this.stripPrefix = cfg.stripPrefix;
_this.stripTrailingSlash = cfg.stripTrailingSlash;
_this.decodePercentEncoding = cfg.decodePercentEncoding;
return _this;
}
/**
* Returns a string name for the type of match that this class represents.
* For the case of UrlMatch, returns 'url'.
*
* @return {String}
*/
UrlMatch.prototype.getType = function () {
return 'url';
};
/**
* Returns a string name for the type of URL match that this class
* represents.
*
* This helps to determine if the match was made in the original text with a
* prefixed scheme (ex: 'http://www.google.com'), a prefixed 'www' (ex:
* 'www.google.com'), or was matched by a known top-level domain (ex:
* 'google.com').
*
* @return {"scheme"/"www"/"tld"}
*/
UrlMatch.prototype.getUrlMatchType = function () {
return this.urlMatchType;
};
/**
* Returns the url that was matched, assuming the protocol to be 'http://' if the original
* match was missing a protocol.
*
* @return {String}
*/
UrlMatch.prototype.getUrl = function () {
var url = this.url;
// if the url string doesn't begin with a scheme, assume 'http://'
if (!this.protocolRelativeMatch &&
this.urlMatchType !== 'scheme' &&
!this.protocolPrepended) {
url = this.url = 'http://' + url;
this.protocolPrepended = true;
}
return url;
};
/**
* Returns the anchor href that should be generated for the match.
*
* @return {String}
*/
UrlMatch.prototype.getAnchorHref = function () {
var url = this.getUrl();
return url.replace(/&amp;/g, '&'); // any &amp;'s in the URL should be converted back to '&' if they were displayed as &amp; in the source html
};
/**
* Returns the anchor text that should be generated for the match.
*
* @return {String}
*/
UrlMatch.prototype.getAnchorText = function () {
var anchorText = this.getMatchedText();
if (this.protocolRelativeMatch) {
// Strip off any protocol-relative '//' from the anchor text
anchorText = stripProtocolRelativePrefix(anchorText);
}
if (this.stripPrefix.scheme) {
anchorText = stripSchemePrefix(anchorText);
}
if (this.stripPrefix.www) {
anchorText = stripWwwPrefix(anchorText);
}
if (this.stripTrailingSlash) {
anchorText = removeTrailingSlash(anchorText); // remove trailing slash, if there is one
}
if (this.decodePercentEncoding) {
anchorText = removePercentEncoding(anchorText);
}
return anchorText;
};
return UrlMatch;
}(abstract_match_1.AbstractMatch));
exports.UrlMatch = UrlMatch;
// Utility Functionality
/**
* Strips the scheme prefix (such as "http://" or "https://") from the given
* `url`.
*
* @private
* @param {String} url The text of the anchor that is being generated, for
* which to strip off the url scheme.
* @return {String} The `url`, with the scheme stripped.
*/
function stripSchemePrefix(url) {
return url.replace(uri_utils_1.httpSchemePrefixRe, '');
}
/**
* Strips the 'www' prefix from the given `url`.
*
* @private
* @param {String} url The text of the anchor that is being generated, for
* which to strip off the 'www' if it exists.
* @return {String} The `url`, with the 'www' stripped.
*/
function stripWwwPrefix(url) {
// If the URL doesn't actually include 'www.' in it, skip running the
// .replace() regexp on it, which is fairly slow even just to check the
// string for the 'www.'s existence. Most URLs these days do not have 'www.'
// in it, so most of the time we skip running the .replace(). One other
// option in the future is to run a state machine on the `url` string
if (!url.includes('www.')) {
return url;
}
else {
return url.replace(wwwPrefixRegex, '$1'); // leave any scheme ($1), it one exists
}
}
/**
* Strips any protocol-relative '//' from the anchor text.
*
* @private
* @param {String} text The text of the anchor that is being generated, for which to strip off the
* protocol-relative prefix (such as stripping off "//")
* @return {String} The `anchorText`, with the protocol-relative prefix stripped.
*/
function stripProtocolRelativePrefix(text) {
return text.replace(protocolRelativeRegex, '');
}
/**
* Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed.
*
* @private
* @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing
* slash ('/') that may exist.
* @return {String} The `anchorText`, with the trailing slash removed.
*/
function removeTrailingSlash(anchorText) {
if (anchorText.charAt(anchorText.length - 1) === '/') {
anchorText = anchorText.slice(0, -1);
}
return anchorText;
}
/**
* Decodes percent-encoded characters from the given `anchorText`, in
* preparation for the text to be displayed.
*
* @private
* @param {String} anchorText The text of the anchor that is being
* generated, for which to decode any percent-encoded characters.
* @return {String} The `anchorText`, with the percent-encoded characters
* decoded.
*/
function removePercentEncoding(anchorText) {
// First, convert a few of the known % encodings to the corresponding
// HTML entities that could accidentally be interpretted as special
// HTML characters
// NOTE: This used to be written as 5 separate .replace() calls, but that
// was 25% slower than the current form below according to jsperf
var preProcessedEntityAnchorText = anchorText.replace(/%(?:22|26|27|3C|3E)/gi, function (match) {
if (match === '%22')
return '&quot;'; // %22: '"' char
if (match === '%26')
return '&amp;'; // %26: '&' char
if (match === '%27')
return '&#39;'; // %27: "'" char
if (match === '%3C' || match === '%3c')
return '&lt;'; // %3C: '<' char
/*if (match === '%3E' || match === '%3e')*/ return '&gt;'; // %3E: '>' char
});
// Now attempt to URL-decode the rest of the anchor text. However,
// decodeURIComponent() is a slow function. Only call it if we have
// remaining %-encoded entities. Adding this check added ~300 ops/sec to
// benchmark
if (preProcessedEntityAnchorText.includes('%')) {
try {
return decodeURIComponent(preProcessedEntityAnchorText);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
catch (error) {
// Invalid % escape sequence in the anchor text, we'll simply return
// the preProcessedEntityAnchorText below
}
}
return preProcessedEntityAnchorText;
}
//# sourceMappingURL=url-match.js.map
File diff suppressed because one or more lines are too long
+32
View File
@@ -0,0 +1,32 @@
import { isAlphaNumericOrMarkChar } from '../char-utils';
/**
* A regular expression to match a 'mailto:' prefix on an email address.
*/
export declare const mailtoSchemePrefixRe: RegExp;
/**
* Determines if the given character may start the "local part" of an email
* address. The local part is the part to the left of the '@' sign.
*
* Technically according to the email spec, any of the characters in the
* {@link emailLocalPartCharRegex} can start an email address (including any of
* the special characters), but this is so rare in the wild and the
* implementation is much simpler by only starting an email address with a word
* character. This is especially important when matching the '{' character which
* generally starts a brace that isn't part of the email address.
*/
export declare const isEmailLocalPartStartChar: typeof isAlphaNumericOrMarkChar;
/**
* Determines if the given character can be part of the "local part" of an email
* address. The local part is the part to the left of the '@' sign.
*
* Checking for an email address's start char is handled with {@link #isEmailLocalPartStartChar}
*/
export declare function isEmailLocalPartChar(charCode: number): boolean;
/**
* Determines if the given email address is valid. We consider it valid if it
* has a valid TLD in its host.
*
* @param emailAddress email address
* @return true is email have valid TLD, false otherwise
*/
export declare function isValidEmail(emailAddress: string): boolean;
+44
View File
@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEmailLocalPartStartChar = exports.mailtoSchemePrefixRe = void 0;
exports.isEmailLocalPartChar = isEmailLocalPartChar;
exports.isValidEmail = isValidEmail;
var char_utils_1 = require("../char-utils");
var uri_utils_1 = require("./uri-utils");
/**
* A regular expression to match a 'mailto:' prefix on an email address.
*/
exports.mailtoSchemePrefixRe = /^mailto:/i;
/**
* Determines if the given character may start the "local part" of an email
* address. The local part is the part to the left of the '@' sign.
*
* Technically according to the email spec, any of the characters in the
* {@link emailLocalPartCharRegex} can start an email address (including any of
* the special characters), but this is so rare in the wild and the
* implementation is much simpler by only starting an email address with a word
* character. This is especially important when matching the '{' character which
* generally starts a brace that isn't part of the email address.
*/
exports.isEmailLocalPartStartChar = char_utils_1.isAlphaNumericOrMarkChar; // alias for clarity
/**
* Determines if the given character can be part of the "local part" of an email
* address. The local part is the part to the left of the '@' sign.
*
* Checking for an email address's start char is handled with {@link #isEmailLocalPartStartChar}
*/
function isEmailLocalPartChar(charCode) {
return (0, exports.isEmailLocalPartStartChar)(charCode) || (0, char_utils_1.isValidEmailLocalPartSpecialChar)(charCode);
}
/**
* Determines if the given email address is valid. We consider it valid if it
* has a valid TLD in its host.
*
* @param emailAddress email address
* @return true is email have valid TLD, false otherwise
*/
function isValidEmail(emailAddress) {
var emailAddressTld = emailAddress.split('.').pop(); // as long as we have a valid string (as opposed to null or undefined), we will always get at least one element in the .split('.') array
return (0, uri_utils_1.isKnownTld)(emailAddressTld);
}
//# sourceMappingURL=email-utils.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"email-utils.js","sourceRoot":"","sources":["../../../src/parser/email-utils.ts"],"names":[],"mappings":";;;AA2BA,oDAEC;AASD,oCAIC;AA1CD,4CAA2F;AAC3F,yCAAyC;AAEzC;;GAEG;AACU,QAAA,oBAAoB,GAAG,WAAW,CAAC;AAEhD;;;;;;;;;;GAUG;AACU,QAAA,yBAAyB,GAAG,qCAAwB,CAAC,CAAC,oBAAoB;AAEvF;;;;;GAKG;AACH,SAAgB,oBAAoB,CAAC,QAAgB;IACjD,OAAO,IAAA,iCAAyB,EAAC,QAAQ,CAAC,IAAI,IAAA,6CAAgC,EAAC,QAAQ,CAAC,CAAC;AAC7F,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,YAAY,CAAC,YAAoB;IAC7C,IAAM,eAAe,GAAW,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,CAAC,wIAAwI;IAExM,OAAO,IAAA,sBAAU,EAAC,eAAe,CAAC,CAAC;AACvC,CAAC","sourcesContent":["import { isAlphaNumericOrMarkChar, isValidEmailLocalPartSpecialChar } from '../char-utils';\nimport { isKnownTld } from './uri-utils';\n\n/**\n * A regular expression to match a 'mailto:' prefix on an email address.\n */\nexport const mailtoSchemePrefixRe = /^mailto:/i;\n\n/**\n * Determines if the given character may start the \"local part\" of an email\n * address. The local part is the part to the left of the '@' sign.\n *\n * Technically according to the email spec, any of the characters in the\n * {@link emailLocalPartCharRegex} can start an email address (including any of\n * the special characters), but this is so rare in the wild and the\n * implementation is much simpler by only starting an email address with a word\n * character. This is especially important when matching the '{' character which\n * generally starts a brace that isn't part of the email address.\n */\nexport const isEmailLocalPartStartChar = isAlphaNumericOrMarkChar; // alias for clarity\n\n/**\n * Determines if the given character can be part of the \"local part\" of an email\n * address. The local part is the part to the left of the '@' sign.\n *\n * Checking for an email address's start char is handled with {@link #isEmailLocalPartStartChar}\n */\nexport function isEmailLocalPartChar(charCode: number): boolean {\n return isEmailLocalPartStartChar(charCode) || isValidEmailLocalPartSpecialChar(charCode);\n}\n\n/**\n * Determines if the given email address is valid. We consider it valid if it\n * has a valid TLD in its host.\n *\n * @param emailAddress email address\n * @return true is email have valid TLD, false otherwise\n */\nexport function isValidEmail(emailAddress: string): boolean {\n const emailAddressTld: string = emailAddress.split('.').pop()!; // as long as we have a valid string (as opposed to null or undefined), we will always get at least one element in the .split('.') array\n\n return isKnownTld(emailAddressTld);\n}\n"]}
+11
View File
@@ -0,0 +1,11 @@
/**
* Determines if the given `char` is a an allowed character in a hashtag. These
* are underscores or any alphanumeric char.
*/
export declare function isHashtagTextChar(charCode: number): boolean;
/**
* Determines if a hashtag match is valid.
*/
export declare function isValidHashtag(hashtag: string): boolean;
export type HashtagService = 'twitter' | 'facebook' | 'instagram' | 'tiktok' | 'youtube';
export declare const hashtagServices: HashtagService[];
+28
View File
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hashtagServices = void 0;
exports.isHashtagTextChar = isHashtagTextChar;
exports.isValidHashtag = isValidHashtag;
var char_utils_1 = require("../char-utils");
/**
* Determines if the given `char` is a an allowed character in a hashtag. These
* are underscores or any alphanumeric char.
*/
function isHashtagTextChar(charCode) {
return charCode === 95 /* Char.Underscore */ || (0, char_utils_1.isAlphaNumericOrMarkChar)(charCode);
}
/**
* Determines if a hashtag match is valid.
*/
function isValidHashtag(hashtag) {
// Max length of 140 for a hashtag ('#' char + 139 word chars)
return hashtag.length <= 140;
}
exports.hashtagServices = [
'twitter',
'facebook',
'instagram',
'tiktok',
'youtube',
];
//# sourceMappingURL=hashtag-utils.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"hashtag-utils.js","sourceRoot":"","sources":["../../../src/parser/hashtag-utils.ts"],"names":[],"mappings":";;;AAOA,8CAEC;AAKD,wCAGC;AAhBD,4CAAyD;AAEzD;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,QAAgB;IAC9C,OAAO,QAAQ,6BAAoB,IAAI,IAAA,qCAAwB,EAAC,QAAQ,CAAC,CAAC;AAC9E,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,OAAe;IAC1C,8DAA8D;IAC9D,OAAO,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC;AACjC,CAAC;AAGY,QAAA,eAAe,GAAqB;IAC7C,SAAS;IACT,UAAU;IACV,WAAW;IACX,QAAQ;IACR,SAAS;CACZ,CAAC","sourcesContent":["import { Char } from '../char';\nimport { isAlphaNumericOrMarkChar } from '../char-utils';\n\n/**\n * Determines if the given `char` is a an allowed character in a hashtag. These\n * are underscores or any alphanumeric char.\n */\nexport function isHashtagTextChar(charCode: number): boolean {\n return charCode === Char.Underscore || isAlphaNumericOrMarkChar(charCode);\n}\n\n/**\n * Determines if a hashtag match is valid.\n */\nexport function isValidHashtag(hashtag: string): boolean {\n // Max length of 140 for a hashtag ('#' char + 139 word chars)\n return hashtag.length <= 140;\n}\n\nexport type HashtagService = 'twitter' | 'facebook' | 'instagram' | 'tiktok' | 'youtube';\nexport const hashtagServices: HashtagService[] = [\n 'twitter',\n 'facebook',\n 'instagram',\n 'tiktok',\n 'youtube',\n];\n"]}
+1
View File
@@ -0,0 +1 @@
export * from './parse-matches';
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
tslib_1.__exportStar(require("./parse-matches"), exports);
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/parser/index.ts"],"names":[],"mappings":";;;AAAA,0DAAgC","sourcesContent":["export * from './parse-matches';\n"]}
+1
View File
@@ -0,0 +1 @@
export declare const tldRegex: RegExp;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
/**
* Determines if the given character can be part of a mention's text characters.
*
* Accepts characters that match the RegExp `/[-\w.]/`, which are the possible
* mention characters for any service.
*
* We'll confirm the match based on the user-configured service name after the
* match is found.
*/
export declare function isMentionTextChar(charCode: number): boolean;
/**
* Determines if the given `mention` text is valid.
*/
export declare function isValidMention(mention: string, serviceName: MentionService): boolean;
export type MentionService = 'twitter' | 'instagram' | 'soundcloud' | 'tiktok' | 'youtube';
export declare const mentionServices: MentionService[];
+49
View File
@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mentionServices = void 0;
exports.isMentionTextChar = isMentionTextChar;
exports.isValidMention = isValidMention;
var char_utils_1 = require("../char-utils");
var mentionRegexes = {
twitter: /^@\w{1,15}$/,
instagram: /^@[_\w]{1,30}$/,
soundcloud: /^@[-a-z0-9_]{3,25}$/,
// TikTok usernames are 1-24 characters containing letters, numbers, underscores
// and periods, but cannot end in a period: https://support.tiktok.com/en/getting-started/setting-up-your-profile/changing-your-username
tiktok: /^@[.\w]{1,23}[\w]$/,
// Youtube usernames are 3-30 characters containing letters, numbers, underscores,
// dashes, or latin middle dots ('·').
// https://support.google.com/youtube/answer/11585688?hl=en&co=GENIE.Platform%3DAndroid#tns
youtube: /^@[-.·\w]{3,30}$/,
};
/**
* Determines if the given character can be part of a mention's text characters.
*
* Accepts characters that match the RegExp `/[-\w.]/`, which are the possible
* mention characters for any service.
*
* We'll confirm the match based on the user-configured service name after the
* match is found.
*/
function isMentionTextChar(charCode) {
return (charCode === 45 /* Char.Dash */ || // '-'
charCode === 46 /* Char.Dot */ || // '.'
charCode === 95 /* Char.Underscore */ || // '_'
(0, char_utils_1.isAsciiLetterChar)(charCode) ||
(0, char_utils_1.isDigitChar)(charCode));
}
/**
* Determines if the given `mention` text is valid.
*/
function isValidMention(mention, serviceName) {
var re = mentionRegexes[serviceName];
return re.test(mention);
}
exports.mentionServices = [
'twitter',
'instagram',
'soundcloud',
'tiktok',
'youtube',
];
//# sourceMappingURL=mention-utils.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"mention-utils.js","sourceRoot":"","sources":["../../../src/parser/mention-utils.ts"],"names":[],"mappings":";;;AA2BA,8CAQC;AAKD,wCAIC;AA3CD,4CAA+D;AAE/D,IAAM,cAAc,GAAgD;IAChE,OAAO,EAAE,aAAa;IACtB,SAAS,EAAE,gBAAgB;IAC3B,UAAU,EAAE,qBAAqB;IAEjC,gFAAgF;IAChF,wIAAwI;IACxI,MAAM,EAAE,oBAAoB;IAE5B,kFAAkF;IAClF,sCAAsC;IACtC,2FAA2F;IAC3F,OAAO,EAAE,kBAAkB;CAC9B,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAgB,iBAAiB,CAAC,QAAgB;IAC9C,OAAO,CACH,QAAQ,uBAAc,IAAI,MAAM;QAChC,QAAQ,sBAAa,IAAI,MAAM;QAC/B,QAAQ,6BAAoB,IAAI,MAAM;QACtC,IAAA,8BAAiB,EAAC,QAAQ,CAAC;QAC3B,IAAA,wBAAW,EAAC,QAAQ,CAAC,CACxB,CAAC;AACN,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,OAAe,EAAE,WAA2B;IACvE,IAAM,EAAE,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;IAEvC,OAAO,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC5B,CAAC;AAGY,QAAA,eAAe,GAAqB;IAC7C,SAAS;IACT,WAAW;IACX,YAAY;IACZ,QAAQ;IACR,SAAS;CACZ,CAAC","sourcesContent":["import { Char } from '../char';\nimport { isDigitChar, isAsciiLetterChar } from '../char-utils';\n\nconst mentionRegexes: { [serviceName in MentionService]: RegExp } = {\n twitter: /^@\\w{1,15}$/,\n instagram: /^@[_\\w]{1,30}$/,\n soundcloud: /^@[-a-z0-9_]{3,25}$/,\n\n // TikTok usernames are 1-24 characters containing letters, numbers, underscores\n // and periods, but cannot end in a period: https://support.tiktok.com/en/getting-started/setting-up-your-profile/changing-your-username\n tiktok: /^@[.\\w]{1,23}[\\w]$/,\n\n // Youtube usernames are 3-30 characters containing letters, numbers, underscores,\n // dashes, or latin middle dots ('·').\n // https://support.google.com/youtube/answer/11585688?hl=en&co=GENIE.Platform%3DAndroid#tns\n youtube: /^@[-.·\\w]{3,30}$/,\n};\n\n/**\n * Determines if the given character can be part of a mention's text characters.\n *\n * Accepts characters that match the RegExp `/[-\\w.]/`, which are the possible\n * mention characters for any service.\n *\n * We'll confirm the match based on the user-configured service name after the\n * match is found.\n */\nexport function isMentionTextChar(charCode: number): boolean {\n return (\n charCode === Char.Dash || // '-'\n charCode === Char.Dot || // '.'\n charCode === Char.Underscore || // '_'\n isAsciiLetterChar(charCode) ||\n isDigitChar(charCode)\n );\n}\n\n/**\n * Determines if the given `mention` text is valid.\n */\nexport function isValidMention(mention: string, serviceName: MentionService): boolean {\n const re = mentionRegexes[serviceName];\n\n return re.test(mention);\n}\n\nexport type MentionService = 'twitter' | 'instagram' | 'soundcloud' | 'tiktok' | 'youtube';\nexport const mentionServices: MentionService[] = [\n 'twitter',\n 'instagram',\n 'soundcloud',\n 'tiktok',\n 'youtube',\n];\n"]}
+48
View File
@@ -0,0 +1,48 @@
import { Match } from '../match/match';
import { HashtagService } from './hashtag-utils';
import { MentionService } from './mention-utils';
import { AnchorTagBuilder } from '../anchor-tag-builder';
import type { StripPrefixConfigObj } from '../autolinker';
/**
* Parses URL, email, twitter, mention, and hashtag matches from the given
* `text`.
*/
export declare function parseMatches(text: string, args: ParseMatchesArgs): Match[];
export interface ParseMatchesArgs {
tagBuilder: AnchorTagBuilder;
stripPrefix: Required<StripPrefixConfigObj>;
stripTrailingSlash: boolean;
decodePercentEncoding: boolean;
hashtagServiceName: HashtagService;
mentionServiceName: MentionService;
}
/**
* Determines if a match found has unmatched closing parenthesis,
* square brackets or curly brackets. If so, these unbalanced symbol(s) will be
* removed from the URL match itself.
*
* A match may have an extra closing parenthesis/square brackets/curly brackets
* at the end of the match because these are valid URL path characters. For
* example, "wikipedia.com/something_(disambiguation)" should be auto-linked.
*
* However, an extra parenthesis *will* be included when the URL itself is
* wrapped in parenthesis, such as in the case of:
*
* "(wikipedia.com/something_(disambiguation))"
*
* In this case, the last closing parenthesis should *not* be part of the
* URL itself, and this method will exclude it from the returned URL.
*
* For square brackets in URLs such as in PHP arrays, the same behavior as
* parenthesis discussed above should happen:
*
* "[http://www.example.com/foo.php?bar[]=1&bar[]=2&bar[]=3]"
*
* The very last closing square bracket should not be part of the URL itself,
* and therefore this method will remove it.
*
* @param matchedText The full matched URL/email/hashtag/etc. from the state
* machine parser.
* @return The updated matched text with extraneous suffix characters removed.
*/
export declare function excludeUnbalancedTrailingBracesAndPunctuation(matchedText: string): string;
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
/**
* Determines if the character is a phone number separator character (i.e.
* '-', '.', or ' ' (space))
*/
export declare function isPhoneNumberSeparatorChar(charCode: number): boolean;
/**
* Determines if the character is a control character in a phone number. Control
* characters are as follows:
*
* - ',': A 1 second pause. Useful for dialing extensions once the main phone number has been reached
* - ';': A "wait" that waits for the user to take action (tap something, for instance on a smart phone)
*/
export declare function isPhoneNumberControlChar(charCode: number): boolean;
/**
* Determines if the given phone number text found in a string is a valid phone
* number.
*
* Our state machine parser is simplified to grab anything that looks like a
* phone number, and this function confirms the match.
*/
export declare function isValidPhoneNumber(phoneNumberText: string): boolean;
+57
View File
@@ -0,0 +1,57 @@
"use strict";
// Regex that specifies any delimiter char that allows us to treat the number as
Object.defineProperty(exports, "__esModule", { value: true });
exports.isPhoneNumberSeparatorChar = isPhoneNumberSeparatorChar;
exports.isPhoneNumberControlChar = isPhoneNumberControlChar;
exports.isValidPhoneNumber = isValidPhoneNumber;
// a phone number rather than just any other number that could appear in text.
var hasDelimCharsRe = /[-. ()]/;
// Over the years, many people have added to this regex, but it should have been
// split up by country. Maybe one day we can break this down.
var mostPhoneNumbers = /(?:(?:(?:(\+)?\d{1,3}[-. ]?)?\(?\d{3}\)?[-. ]?\d{3}[-. ]?\d{4})|(?:(\+)(?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-. ]?(?:\d[-. ]?){6,12}\d+))([,;]+[0-9]+#?)*/;
// Regex for Japanese phone numbers
var japanesePhoneRe = /(0([1-9]-?[1-9]\d{3}|[1-9]{2}-?\d{3}|[1-9]{2}\d{1}-?\d{2}|[1-9]{2}\d{2}-?\d{1})-?\d{4}|0[789]0-?\d{4}-?\d{4}|050-?\d{4}-?\d{4})/;
// Combined regex
var validPhoneNumberRe = new RegExp("^".concat(mostPhoneNumbers.source, "|").concat(japanesePhoneRe.source, "$"));
/**
* Determines if the character is a phone number separator character (i.e.
* '-', '.', or ' ' (space))
*/
function isPhoneNumberSeparatorChar(charCode) {
return (charCode === 45 /* Char.Dash */ || // '-'
charCode === 46 /* Char.Dot */ || // '.'
charCode === 32 /* Char.Space */ // ' '
);
}
/**
* Determines if the character is a control character in a phone number. Control
* characters are as follows:
*
* - ',': A 1 second pause. Useful for dialing extensions once the main phone number has been reached
* - ';': A "wait" that waits for the user to take action (tap something, for instance on a smart phone)
*/
function isPhoneNumberControlChar(charCode) {
return (charCode === 44 /* Char.Comma */ || // ','
charCode === 59 /* Char.SemiColon */ // ';'
);
}
/**
* Determines if the given phone number text found in a string is a valid phone
* number.
*
* Our state machine parser is simplified to grab anything that looks like a
* phone number, and this function confirms the match.
*/
function isValidPhoneNumber(phoneNumberText) {
// We'll only consider the match as a phone number if there is some kind of
// delimiter character (a prefixed '+' sign, or separator chars).
//
// Accepts:
// (123) 456-7890
// +38755233976
// Does not accept:
// 1234567890 (no delimiter chars - may just be a random number that's not a phone number)
var hasDelimiters = phoneNumberText.charAt(0) === '+' || hasDelimCharsRe.test(phoneNumberText);
return hasDelimiters && validPhoneNumberRe.test(phoneNumberText);
}
//# sourceMappingURL=phone-number-utils.js.map
@@ -0,0 +1 @@
{"version":3,"file":"phone-number-utils.js","sourceRoot":"","sources":["../../../src/parser/phone-number-utils.ts"],"names":[],"mappings":";AAAA,gFAAgF;;AAuBhF,gEAMC;AASD,4DAKC;AASD,gDAaC;AA7DD,8EAA8E;AAC9E,IAAM,eAAe,GAAG,SAAS,CAAC;AAElC,gFAAgF;AAChF,6DAA6D;AAC7D,IAAM,gBAAgB,GAClB,uQAAuQ,CAAC;AAE5Q,mCAAmC;AACnC,IAAM,eAAe,GACjB,iIAAiI,CAAC;AAEtI,iBAAiB;AACjB,IAAM,kBAAkB,GAAG,IAAI,MAAM,CAAC,WAAI,gBAAgB,CAAC,MAAM,cAAI,eAAe,CAAC,MAAM,MAAG,CAAC,CAAC;AAEhG;;;GAGG;AACH,SAAgB,0BAA0B,CAAC,QAAgB;IACvD,OAAO,CACH,QAAQ,uBAAc,IAAI,MAAM;QAChC,QAAQ,sBAAa,IAAI,MAAM;QAC/B,QAAQ,wBAAe,CAAC,MAAM;KACjC,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,wBAAwB,CAAC,QAAgB;IACrD,OAAO,CACH,QAAQ,wBAAe,IAAI,MAAM;QACjC,QAAQ,4BAAmB,CAAC,MAAM;KACrC,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,eAAuB;IACtD,2EAA2E;IAC3E,iEAAiE;IACjE,EAAE;IACF,WAAW;IACX,qBAAqB;IACrB,mBAAmB;IACnB,mBAAmB;IACnB,+FAA+F;IAC/F,IAAM,aAAa,GACf,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAE/E,OAAO,aAAa,IAAI,kBAAkB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;AACrE,CAAC","sourcesContent":["// Regex that specifies any delimiter char that allows us to treat the number as\n\nimport { Char } from '../char';\n\n// a phone number rather than just any other number that could appear in text.\nconst hasDelimCharsRe = /[-. ()]/;\n\n// Over the years, many people have added to this regex, but it should have been\n// split up by country. Maybe one day we can break this down.\nconst mostPhoneNumbers =\n /(?:(?:(?:(\\+)?\\d{1,3}[-. ]?)?\\(?\\d{3}\\)?[-. ]?\\d{3}[-. ]?\\d{4})|(?:(\\+)(?:9[976]\\d|8[987530]\\d|6[987]\\d|5[90]\\d|42\\d|3[875]\\d|2[98654321]\\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-. ]?(?:\\d[-. ]?){6,12}\\d+))([,;]+[0-9]+#?)*/;\n\n// Regex for Japanese phone numbers\nconst japanesePhoneRe =\n /(0([1-9]-?[1-9]\\d{3}|[1-9]{2}-?\\d{3}|[1-9]{2}\\d{1}-?\\d{2}|[1-9]{2}\\d{2}-?\\d{1})-?\\d{4}|0[789]0-?\\d{4}-?\\d{4}|050-?\\d{4}-?\\d{4})/;\n\n// Combined regex\nconst validPhoneNumberRe = new RegExp(`^${mostPhoneNumbers.source}|${japanesePhoneRe.source}$`);\n\n/**\n * Determines if the character is a phone number separator character (i.e.\n * '-', '.', or ' ' (space))\n */\nexport function isPhoneNumberSeparatorChar(charCode: number): boolean {\n return (\n charCode === Char.Dash || // '-'\n charCode === Char.Dot || // '.'\n charCode === Char.Space // ' '\n );\n}\n\n/**\n * Determines if the character is a control character in a phone number. Control\n * characters are as follows:\n *\n * - ',': A 1 second pause. Useful for dialing extensions once the main phone number has been reached\n * - ';': A \"wait\" that waits for the user to take action (tap something, for instance on a smart phone)\n */\nexport function isPhoneNumberControlChar(charCode: number): boolean {\n return (\n charCode === Char.Comma || // ','\n charCode === Char.SemiColon // ';'\n );\n}\n\n/**\n * Determines if the given phone number text found in a string is a valid phone\n * number.\n *\n * Our state machine parser is simplified to grab anything that looks like a\n * phone number, and this function confirms the match.\n */\nexport function isValidPhoneNumber(phoneNumberText: string): boolean {\n // We'll only consider the match as a phone number if there is some kind of\n // delimiter character (a prefixed '+' sign, or separator chars).\n //\n // Accepts:\n // (123) 456-7890\n // +38755233976\n // Does not accept:\n // 1234567890 (no delimiter chars - may just be a random number that's not a phone number)\n const hasDelimiters =\n phoneNumberText.charAt(0) === '+' || hasDelimCharsRe.test(phoneNumberText);\n\n return hasDelimiters && validPhoneNumberRe.test(phoneNumberText);\n}\n"]}
+82
View File
@@ -0,0 +1,82 @@
/**
* Regular expression to match an http:// or https:// scheme.
*/
export declare const httpSchemeRe: RegExp;
/**
* Regular expression to match an http:// or https:// scheme as the prefix of
* a string.
*/
export declare const httpSchemePrefixRe: RegExp;
/**
* A regular expression used to determine the schemes we should not autolink
*/
export declare const invalidSchemeRe: RegExp;
export declare const schemeUrlRe: RegExp;
export declare const tldUrlHostRe: RegExp;
/**
* Determines if the given character code represents a character that may start
* a scheme (ex: the 'h' in 'http')
*/
export declare const isSchemeStartChar: (code: number) => boolean;
/**
* Determines if the given character is a valid character in a scheme (such as
* 'http' or 'ssh+git'), but only after the start char (which is handled by
* {@link isSchemeStartChar}.
*/
export declare function isSchemeChar(charCode: number): boolean;
/**
* Determines if the character can begin a domain label, which must be an
* alphanumeric character and not an underscore or dash.
*
* A domain label is a segment of a hostname such as subdomain.google.com.
*/
export declare const isDomainLabelStartChar: (charCode: number) => boolean;
/**
* Determines if the character is part of a domain label (but not a domain label
* start character).
*
* A domain label is a segment of a hostname such as subdomain.google.com.
*/
export declare function isDomainLabelChar(charCode: number): boolean;
/**
* Determines if the character is a path character ("pchar") as defined by
* https://tools.ietf.org/html/rfc3986#appendix-A
*
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*
* Note that this implementation doesn't follow the spec exactly, but rather
* follows URL path characters found out in the wild (spec might be out of date?)
*/
export declare function isPathChar(charCode: number): boolean;
/**
* Determines if the character given may begin the "URL Suffix" section of a
* URI (i.e. the path, query, or hash section). These are the '/', '?' and '#'
* characters.
*
* See https://tools.ietf.org/html/rfc3986#appendix-A
*/
export declare function isUrlSuffixStartChar(charCode: number): boolean;
/**
* Determines if the top-level domain (TLD) read in the host is a known TLD.
*
* Example: 'com' would be a known TLD (for a host of 'google.com'), but
* 'local' would not (for a domain name of 'my-computer.local').
*/
export declare function isKnownTld(tld: string): boolean;
/**
* Determines if the given `url` is a valid scheme-prefixed URL.
*/
export declare function isValidSchemeUrl(url: string): boolean;
/**
* Determines if the given `url` is a match with a valid TLD.
*/
export declare function isValidTldMatch(url: string): boolean;
/**
* Determines if the given URL is a valid IPv4-prefixed URL.
*/
export declare function isValidIpV4Address(url: string): boolean;
+200
View File
@@ -0,0 +1,200 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isDomainLabelStartChar = exports.isSchemeStartChar = exports.tldUrlHostRe = exports.schemeUrlRe = exports.invalidSchemeRe = exports.httpSchemePrefixRe = exports.httpSchemeRe = void 0;
exports.isSchemeChar = isSchemeChar;
exports.isDomainLabelChar = isDomainLabelChar;
exports.isPathChar = isPathChar;
exports.isUrlSuffixStartChar = isUrlSuffixStartChar;
exports.isKnownTld = isKnownTld;
exports.isValidSchemeUrl = isValidSchemeUrl;
exports.isValidTldMatch = isValidTldMatch;
exports.isValidIpV4Address = isValidIpV4Address;
var char_utils_1 = require("../char-utils");
var known_tlds_1 = require("./known-tlds");
/**
* Regular expression to match an http:// or https:// scheme.
*/
exports.httpSchemeRe = /https?:\/\//i;
/**
* Regular expression to match an http:// or https:// scheme as the prefix of
* a string.
*/
exports.httpSchemePrefixRe = new RegExp('^' + exports.httpSchemeRe.source, 'i');
/**
* A regular expression used to determine the schemes we should not autolink
*/
exports.invalidSchemeRe = /^(javascript|vbscript):/i;
// A regular expression used to determine if the URL is a scheme match (such as
// 'http://google.com', and as opposed to a "TLD match"). This regular
// expression is used to parse out the host along with if the URL has an
// authority component (i.e. '//')
//
// Capturing groups:
// 1. '//' if the URL has an authority component, empty string otherwise
// 2. The host (if one exists). Ex: 'google.com'
//
// See https://www.rfc-editor.org/rfc/rfc3986#appendix-A for terminology
exports.schemeUrlRe = /^[A-Za-z][-.+A-Za-z0-9]*:(\/\/)?([^:/]*)/;
// A regular expression used to determine if the URL is a TLD match (such as
// 'google.com', and as opposed to a "scheme match"). This regular
// expression is used to help parse out the TLD (top-level domain) of the host.
//
// See https://www.rfc-editor.org/rfc/rfc3986#appendix-A for terminology
exports.tldUrlHostRe = /^(?:\/\/)?([^/#?:]+)/; // optionally prefixed with protocol-relative '//' chars
/**
* Determines if the given character code represents a character that may start
* a scheme (ex: the 'h' in 'http')
*/
exports.isSchemeStartChar = char_utils_1.isAsciiLetterChar; // Equivalent to checking the RegExp `/[A-Za-z]/`, but aliased for clarity and maintainability
/**
* Determines if the given character is a valid character in a scheme (such as
* 'http' or 'ssh+git'), but only after the start char (which is handled by
* {@link isSchemeStartChar}.
*/
function isSchemeChar(charCode) {
return ((0, char_utils_1.isAsciiLetterChar)(charCode) ||
(0, char_utils_1.isDigitChar)(charCode) ||
charCode === 43 /* Char.Plus */ || // '+'
charCode === 45 /* Char.Dash */ || // '-'
charCode === 46 /* Char.Dot */ // '.'
);
}
/**
* Determines if the character can begin a domain label, which must be an
* alphanumeric character and not an underscore or dash.
*
* A domain label is a segment of a hostname such as subdomain.google.com.
*/
exports.isDomainLabelStartChar = char_utils_1.isAlphaNumericOrMarkChar; // alias function for clarity
/**
* Determines if the character is part of a domain label (but not a domain label
* start character).
*
* A domain label is a segment of a hostname such as subdomain.google.com.
*/
function isDomainLabelChar(charCode) {
return charCode === 95 /* Char.Underscore */ || (0, exports.isDomainLabelStartChar)(charCode);
}
/**
* Determines if the character is a path character ("pchar") as defined by
* https://tools.ietf.org/html/rfc3986#appendix-A
*
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*
* Note that this implementation doesn't follow the spec exactly, but rather
* follows URL path characters found out in the wild (spec might be out of date?)
*/
function isPathChar(charCode) {
return ((0, char_utils_1.isAlphaNumericOrMarkChar)(charCode) ||
(0, char_utils_1.isUrlSuffixAllowedSpecialChar)(charCode) ||
(0, char_utils_1.isUrlSuffixNotAllowedAsFinalChar)(charCode) // characters in addition to those allowed by isUrlSuffixAllowedSpecialChar()
);
}
/**
* Determines if the character given may begin the "URL Suffix" section of a
* URI (i.e. the path, query, or hash section). These are the '/', '?' and '#'
* characters.
*
* See https://tools.ietf.org/html/rfc3986#appendix-A
*/
function isUrlSuffixStartChar(charCode) {
return (charCode === 47 /* Char.Slash */ || // '/'
charCode === 63 /* Char.Question */ || // '?'
charCode === 35 /* Char.NumberSign */ // '#'
);
}
/**
* Determines if the top-level domain (TLD) read in the host is a known TLD.
*
* Example: 'com' would be a known TLD (for a host of 'google.com'), but
* 'local' would not (for a domain name of 'my-computer.local').
*/
function isKnownTld(tld) {
return known_tlds_1.tldRegex.test(tld.toLowerCase()); // make sure the tld is lowercase for the regex
}
/**
* Determines if the given `url` is a valid scheme-prefixed URL.
*/
function isValidSchemeUrl(url) {
// If the scheme is 'javascript:' or 'vbscript:', these link
// types can be dangerous. Don't link them.
if (exports.invalidSchemeRe.test(url)) {
return false;
}
var schemeMatch = url.match(exports.schemeUrlRe);
if (!schemeMatch) {
return false;
}
var isAuthorityMatch = !!schemeMatch[1];
var host = schemeMatch[2];
if (isAuthorityMatch) {
// Any match that has an authority ('//' chars) after the scheme is
// valid, such as 'http://anything'
return true;
}
// If there's no authority ('//' chars), check that we have a hostname
// that looks valid.
//
// The host must contain at least one '.' char and have a domain label
// with at least one letter to be considered valid.
//
// Accept:
// - git:domain.com (scheme followed by a host
// Do not accept:
// - git:something ('something' doesn't look like a host)
// - version:1.0 ('1.0' doesn't look like a host)
if (host.indexOf('.') === -1 || !/[A-Za-z]/.test(host)) {
// `letterRe` RegExp checks for a letter anywhere in the host string
return false;
}
return true;
}
/**
* Determines if the given `url` is a match with a valid TLD.
*/
function isValidTldMatch(url) {
// TLD URL such as 'google.com', we need to confirm that we have a valid
// top-level domain
var tldUrlHostMatch = url.match(exports.tldUrlHostRe);
if (!tldUrlHostMatch) {
// At this point, if the URL didn't match our TLD re, it must be invalid
// (highly unlikely to happen, but just in case)
return false;
}
var host = tldUrlHostMatch[0];
var hostLabels = host.split('.');
if (hostLabels.length < 2) {
// 0 or 1 host label, there's no TLD. Ex: 'localhost'
return false;
}
var tld = hostLabels[hostLabels.length - 1];
if (!isKnownTld(tld)) {
return false;
}
// TODO: Implement these conditions for TLD matcher:
// (
// this.longestDomainLabelLength <= 63 &&
// this.domainNameLength <= 255
// );
return true;
}
// Regular expression to confirm a valid IPv4 address (ex: '192.168.0.1')
// TODO: encode this into the state machine so that we don't need to run this
// regexp separately to confirm the match
var ipV4Re = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
// Regular expression used to split the IPv4 address itself from any port/path/query/hash
var ipV4PartRe = /[:/?#]/;
/**
* Determines if the given URL is a valid IPv4-prefixed URL.
*/
function isValidIpV4Address(url) {
// Grab just the IP address
var ipV4Part = url.split(ipV4PartRe, 1)[0]; // only 1 result needed
return ipV4Re.test(ipV4Part);
}
//# sourceMappingURL=uri-utils.js.map
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
/**
* A truncation feature where the ellipsis will be placed at the end of the URL.
*
* @param {String} anchorText
* @param {Number} truncateLen The maximum length of the truncated output URL string.
* @param {String} ellipsisChars The characters to place within the url, e.g. "..".
* @return {String} The truncated URL.
*/
export declare function truncateEnd(anchorText: string, truncateLen: number, ellipsisChars?: string): string;
+16
View File
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.truncateEnd = truncateEnd;
var utils_1 = require("../utils");
/**
* A truncation feature where the ellipsis will be placed at the end of the URL.
*
* @param {String} anchorText
* @param {Number} truncateLen The maximum length of the truncated output URL string.
* @param {String} ellipsisChars The characters to place within the url, e.g. "..".
* @return {String} The truncated URL.
*/
function truncateEnd(anchorText, truncateLen, ellipsisChars) {
return (0, utils_1.ellipsis)(anchorText, truncateLen, ellipsisChars);
}
//# sourceMappingURL=truncate-end.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"truncate-end.js","sourceRoot":"","sources":["../../../src/truncate/truncate-end.ts"],"names":[],"mappings":";;AAUA,kCAEC;AAZD,kCAAoC;AAEpC;;;;;;;GAOG;AACH,SAAgB,WAAW,CAAC,UAAkB,EAAE,WAAmB,EAAE,aAAsB;IACvF,OAAO,IAAA,gBAAQ,EAAC,UAAU,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;AAC5D,CAAC","sourcesContent":["import { ellipsis } from '../utils';\n\n/**\n * A truncation feature where the ellipsis will be placed at the end of the URL.\n *\n * @param {String} anchorText\n * @param {Number} truncateLen The maximum length of the truncated output URL string.\n * @param {String} ellipsisChars The characters to place within the url, e.g. \"..\".\n * @return {String} The truncated URL.\n */\nexport function truncateEnd(anchorText: string, truncateLen: number, ellipsisChars?: string) {\n return ellipsis(anchorText, truncateLen, ellipsisChars);\n}\n"]}
+12
View File
@@ -0,0 +1,12 @@
/**
* Date: 2015-10-05
* Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)
*
* A truncation feature, where the ellipsis will be placed in the dead-center of the URL.
*
* @param {String} url A URL.
* @param {Number} truncateLen The maximum length of the truncated output URL string.
* @param {String} ellipsisChars The characters to place within the url, e.g. "..".
* @return {String} The truncated URL.
*/
export declare function truncateMiddle(url: string, truncateLen: number, ellipsisChars?: string): string;
+37
View File
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.truncateMiddle = truncateMiddle;
/**
* Date: 2015-10-05
* Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)
*
* A truncation feature, where the ellipsis will be placed in the dead-center of the URL.
*
* @param {String} url A URL.
* @param {Number} truncateLen The maximum length of the truncated output URL string.
* @param {String} ellipsisChars The characters to place within the url, e.g. "..".
* @return {String} The truncated URL.
*/
function truncateMiddle(url, truncateLen, ellipsisChars) {
if (url.length <= truncateLen) {
return url;
}
var ellipsisLengthBeforeParsing;
var ellipsisLength;
if (ellipsisChars == null) {
ellipsisChars = '&hellip;';
ellipsisLengthBeforeParsing = 8;
ellipsisLength = 3;
}
else {
ellipsisLengthBeforeParsing = ellipsisChars.length;
ellipsisLength = ellipsisChars.length;
}
var availableLength = truncateLen - ellipsisLength;
var end = '';
if (availableLength > 0) {
end = url.substr(-1 * Math.floor(availableLength / 2));
}
return (url.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(0, availableLength + ellipsisLengthBeforeParsing);
}
//# sourceMappingURL=truncate-middle.js.map
@@ -0,0 +1 @@
{"version":3,"file":"truncate-middle.js","sourceRoot":"","sources":["../../../src/truncate/truncate-middle.ts"],"names":[],"mappings":";;AAWA,wCA0BC;AArCD;;;;;;;;;;GAUG;AACH,SAAgB,cAAc,CAAC,GAAW,EAAE,WAAmB,EAAE,aAAsB;IACnF,IAAI,GAAG,CAAC,MAAM,IAAI,WAAW,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC;IACf,CAAC;IAED,IAAI,2BAAmC,CAAC;IACxC,IAAI,cAAsB,CAAC;IAE3B,IAAI,aAAa,IAAI,IAAI,EAAE,CAAC;QACxB,aAAa,GAAG,UAAU,CAAC;QAC3B,2BAA2B,GAAG,CAAC,CAAC;QAChC,cAAc,GAAG,CAAC,CAAC;IACvB,CAAC;SAAM,CAAC;QACJ,2BAA2B,GAAG,aAAa,CAAC,MAAM,CAAC;QACnD,cAAc,GAAG,aAAa,CAAC,MAAM,CAAC;IAC1C,CAAC;IAED,IAAM,eAAe,GAAG,WAAW,GAAG,cAAc,CAAC;IACrD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;QACtB,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG,GAAG,CAAC,CAAC,MAAM,CAC/E,CAAC,EACD,eAAe,GAAG,2BAA2B,CAChD,CAAC;AACN,CAAC","sourcesContent":["/**\n * Date: 2015-10-05\n * Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)\n *\n * A truncation feature, where the ellipsis will be placed in the dead-center of the URL.\n *\n * @param {String} url A URL.\n * @param {Number} truncateLen The maximum length of the truncated output URL string.\n * @param {String} ellipsisChars The characters to place within the url, e.g. \"..\".\n * @return {String} The truncated URL.\n */\nexport function truncateMiddle(url: string, truncateLen: number, ellipsisChars?: string) {\n if (url.length <= truncateLen) {\n return url;\n }\n\n let ellipsisLengthBeforeParsing: number;\n let ellipsisLength: number;\n\n if (ellipsisChars == null) {\n ellipsisChars = '&hellip;';\n ellipsisLengthBeforeParsing = 8;\n ellipsisLength = 3;\n } else {\n ellipsisLengthBeforeParsing = ellipsisChars.length;\n ellipsisLength = ellipsisChars.length;\n }\n\n const availableLength = truncateLen - ellipsisLength;\n let end = '';\n if (availableLength > 0) {\n end = url.substr(-1 * Math.floor(availableLength / 2));\n }\n return (url.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(\n 0,\n availableLength + ellipsisLengthBeforeParsing\n );\n}\n"]}
+13
View File
@@ -0,0 +1,13 @@
/**
* Date: 2015-10-05
* Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)
*
* A truncation feature, where the ellipsis will be placed at a section within
* the URL making it still somewhat human readable.
*
* @param {String} url A URL.
* @param {Number} truncateLen The maximum length of the truncated output URL string.
* @param {String} ellipsisChars The characters to place within the url, e.g. "...".
* @return {String} The truncated URL.
*/
export declare function truncateSmart(url: string, truncateLen: number, ellipsisChars?: string): string;
+184
View File
@@ -0,0 +1,184 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.truncateSmart = truncateSmart;
/**
* Date: 2015-10-05
* Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)
*
* A truncation feature, where the ellipsis will be placed at a section within
* the URL making it still somewhat human readable.
*
* @param {String} url A URL.
* @param {Number} truncateLen The maximum length of the truncated output URL string.
* @param {String} ellipsisChars The characters to place within the url, e.g. "...".
* @return {String} The truncated URL.
*/
function truncateSmart(url, truncateLen, ellipsisChars) {
var ellipsisLengthBeforeParsing;
var ellipsisLength;
if (ellipsisChars == null) {
ellipsisChars = '&hellip;';
ellipsisLength = 3;
ellipsisLengthBeforeParsing = 8;
}
else {
ellipsisLength = ellipsisChars.length;
ellipsisLengthBeforeParsing = ellipsisChars.length;
}
// If the URL is shorter than the truncate length, return it as is
if (url.length <= truncateLen) {
return url;
}
var availableLength = truncateLen - ellipsisLength;
var urlObj = parseUrl(url);
// Clean up the URL by removing any malformed query string
// (e.g. "?foo=bar?ignorethis")
if (urlObj.query) {
var matchQuery = urlObj.query.match(/^(.*?)(?=(\?|#))(.*?)$/i);
if (matchQuery) {
// Malformed URL; two or more "?". Removed any content behind the 2nd.
urlObj.query = urlObj.query.substr(0, matchQuery[1].length);
url = buildUrl(urlObj);
}
}
if (url.length <= truncateLen) {
return url; // removing a malformed query string brought the URL under the truncateLength
}
// Clean up the URL by removing 'www.' from the host if it exists
if (urlObj.host) {
urlObj.host = urlObj.host.replace(/^www\./, '');
url = buildUrl(urlObj);
}
if (url.length <= truncateLen) {
return url; // removing 'www.' brought the URL under the truncateLength
}
// Process and build the truncated URL, starting with the hostname
var truncatedUrl = '';
if (urlObj.host) {
truncatedUrl += urlObj.host;
}
if (truncatedUrl.length >= availableLength) {
if (urlObj.host.length === truncateLen) {
return (urlObj.host.substr(0, truncateLen - ellipsisLength) + ellipsisChars).substr(0, availableLength + ellipsisLengthBeforeParsing);
}
return buildSegment(truncatedUrl, availableLength, ellipsisChars).substr(0, availableLength + ellipsisLengthBeforeParsing);
}
// If we still have available chars left, add the path and query string
var pathAndQuery = '';
if (urlObj.path) {
pathAndQuery += '/' + urlObj.path;
}
if (urlObj.query) {
pathAndQuery += '?' + urlObj.query;
}
if (pathAndQuery) {
if ((truncatedUrl + pathAndQuery).length >= availableLength) {
if ((truncatedUrl + pathAndQuery).length == truncateLen) {
return (truncatedUrl + pathAndQuery).substr(0, truncateLen);
}
var remainingAvailableLength = availableLength - truncatedUrl.length;
return (truncatedUrl + buildSegment(pathAndQuery, remainingAvailableLength, ellipsisChars)).substr(0, availableLength + ellipsisLengthBeforeParsing);
}
else {
truncatedUrl += pathAndQuery;
}
}
// If we still have available chars left, add the fragment
if (urlObj.fragment) {
var fragment = '#' + urlObj.fragment;
if ((truncatedUrl + fragment).length >= availableLength) {
if ((truncatedUrl + fragment).length == truncateLen) {
return (truncatedUrl + fragment).substr(0, truncateLen);
}
var remainingAvailableLength2 = availableLength - truncatedUrl.length;
return (truncatedUrl + buildSegment(fragment, remainingAvailableLength2, ellipsisChars)).substr(0, availableLength + ellipsisLengthBeforeParsing);
}
else {
truncatedUrl += fragment;
}
}
// If we still have available chars left, add the scheme
if (urlObj.scheme && urlObj.host) {
var scheme = urlObj.scheme + '://';
if ((truncatedUrl + scheme).length < availableLength) {
return (scheme + truncatedUrl).substr(0, truncateLen);
}
}
if (truncatedUrl.length <= truncateLen) {
return truncatedUrl;
}
var end = '';
if (availableLength > 0) {
end = truncatedUrl.substr(-1 * Math.floor(availableLength / 2));
}
return (truncatedUrl.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(0, availableLength + ellipsisLengthBeforeParsing);
}
/**
* Parses a URL into its components: scheme, host, path, query, and fragment.
*/
function parseUrl(url) {
// Functionality inspired by PHP function of same name
var urlObj = {};
var urlSub = url;
// Parse scheme
var match = urlSub.match(/^([a-z]+):\/\//i);
if (match) {
urlObj.scheme = match[1];
urlSub = urlSub.slice(match[0].length);
}
// Parse host
match = urlSub.match(/^(.*?)(?=(\?|#|\/|$))/i);
if (match) {
urlObj.host = match[1];
urlSub = urlSub.slice(match[0].length);
}
// Parse path
match = urlSub.match(/^\/(.*?)(?=(\?|#|$))/i);
if (match) {
urlObj.path = match[1];
urlSub = urlSub.slice(match[0].length);
}
// Parse query
match = urlSub.match(/^\?(.*?)(?=(#|$))/i);
if (match) {
urlObj.query = match[1];
urlSub = urlSub.slice(match[0].length);
}
// Parse fragment
match = urlSub.match(/^#(.*?)$/i);
if (match) {
urlObj.fragment = match[1];
//urlSub = urlSub.slice(match[0].length); -- not used. Uncomment if adding another block.
}
return urlObj;
}
function buildUrl(urlObj) {
var url = '';
if (urlObj.scheme && urlObj.host) {
url += urlObj.scheme + '://';
}
if (urlObj.host) {
url += urlObj.host;
}
if (urlObj.path) {
url += '/' + urlObj.path;
}
if (urlObj.query) {
url += '?' + urlObj.query;
}
if (urlObj.fragment) {
url += '#' + urlObj.fragment;
}
return url;
}
function buildSegment(segment, remainingAvailableLength, ellipsisChars) {
var remainingAvailableLengthHalf = remainingAvailableLength / 2;
var startOffset = Math.ceil(remainingAvailableLengthHalf);
var endOffset = -1 * Math.floor(remainingAvailableLengthHalf);
var end = '';
if (endOffset < 0) {
end = segment.substr(endOffset);
}
return segment.substr(0, startOffset) + ellipsisChars + end;
}
//# sourceMappingURL=truncate-smart.js.map
File diff suppressed because one or more lines are too long
+35
View File
@@ -0,0 +1,35 @@
export declare const hasOwnProperty: (v: PropertyKey) => boolean;
/**
* Simpler helper method to check for a boolean type simply for the benefit of
* gaining better compression when minified by not needing to have multiple
* `typeof` comparisons in the codebase.
*/
export declare function isBoolean(value: unknown): value is boolean;
/**
* 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 '...'
*/
export declare function ellipsis(str: string, truncateLen: number, ellipsisChars?: string): string;
/**
* Removes array elements based on a filtering function. Mutates the input
* array.
*
* Using this instead of the ES5 Array.prototype.filter() function to prevent
* creating many new arrays in memory for filtering.
*
* @param arr The array to remove elements from. This array is mutated.
* @param fn The predicate function which should return `true` to remove an
* element.
*/
export declare function removeWithPredicate<T>(arr: T[], fn: (item: T) => boolean): void;
/**
* Function that should never be called but is used to check that every
* enum value is handled using TypeScript's 'never' type.
*/
export declare function assertNever(theValue: never): never;
+67
View File
@@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasOwnProperty = void 0;
exports.isBoolean = isBoolean;
exports.ellipsis = ellipsis;
exports.removeWithPredicate = removeWithPredicate;
exports.assertNever = assertNever;
exports.hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Simpler helper method to check for a boolean type simply for the benefit of
* gaining better compression when minified by not needing to have multiple
* `typeof` comparisons in the codebase.
*/
function isBoolean(value) {
return typeof value === 'boolean';
}
/**
* 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 '...'
*/
function ellipsis(str, truncateLen, ellipsisChars) {
var ellipsisLength;
if (str.length > truncateLen) {
if (ellipsisChars == null) {
ellipsisChars = '&hellip;';
ellipsisLength = 3;
}
else {
ellipsisLength = ellipsisChars.length;
}
str = str.substring(0, truncateLen - ellipsisLength) + ellipsisChars;
}
return str;
}
/**
* Removes array elements based on a filtering function. Mutates the input
* array.
*
* Using this instead of the ES5 Array.prototype.filter() function to prevent
* creating many new arrays in memory for filtering.
*
* @param arr The array to remove elements from. This array is mutated.
* @param fn The predicate function which should return `true` to remove an
* element.
*/
function removeWithPredicate(arr, fn) {
for (var i = arr.length - 1; i >= 0; i--) {
if (fn(arr[i]) === true) {
arr.splice(i, 1);
}
}
}
/**
* Function that should never be called but is used to check that every
* enum value is handled using TypeScript's 'never' type.
*/
/* istanbul ignore next */
function assertNever(theValue) {
throw new Error("Unhandled case for value: '".concat(theValue, "'"));
}
//# sourceMappingURL=utils.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":";;;AAOA,8BAEC;AAYD,4BAcC;AAaD,kDAMC;AAOD,kCAEC;AA/DY,QAAA,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC;AAE9D;;;;GAIG;AACH,SAAgB,SAAS,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC;AACtC,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,QAAQ,CAAC,GAAW,EAAE,WAAmB,EAAE,aAAsB;IAC7E,IAAI,cAAsB,CAAC;IAE3B,IAAI,GAAG,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;QAC3B,IAAI,aAAa,IAAI,IAAI,EAAE,CAAC;YACxB,aAAa,GAAG,UAAU,CAAC;YAC3B,cAAc,GAAG,CAAC,CAAC;QACvB,CAAC;aAAM,CAAC;YACJ,cAAc,GAAG,aAAa,CAAC,MAAM,CAAC;QAC1C,CAAC;QAED,GAAG,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,GAAG,aAAa,CAAC;IACzE,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,mBAAmB,CAAI,GAAQ,EAAE,EAAwB;IACrE,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACtB,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrB,CAAC;IACL,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,0BAA0B;AAC1B,SAAgB,WAAW,CAAC,QAAe;IACvC,MAAM,IAAI,KAAK,CAAC,qCAA8B,QAAQ,MAAG,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["export const hasOwnProperty = Object.prototype.hasOwnProperty;\n\n/**\n * Simpler helper method to check for a boolean type simply for the benefit of\n * gaining better compression when minified by not needing to have multiple\n * `typeof` comparisons in the codebase.\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === 'boolean';\n}\n\n/**\n * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the\n * end of the string (by default, two periods: '..'). If the `str` length does not exceed\n * `len`, the string will be returned unchanged.\n *\n * @param {String} str The string to truncate and add an ellipsis to.\n * @param {Number} truncateLen The length to truncate the string at.\n * @param {String} [ellipsisChars=...] The ellipsis character(s) to add to the end of `str`\n * when truncated. Defaults to '...'\n */\nexport function ellipsis(str: string, truncateLen: number, ellipsisChars?: string) {\n let ellipsisLength: number;\n\n if (str.length > truncateLen) {\n if (ellipsisChars == null) {\n ellipsisChars = '&hellip;';\n ellipsisLength = 3;\n } else {\n ellipsisLength = ellipsisChars.length;\n }\n\n str = str.substring(0, truncateLen - ellipsisLength) + ellipsisChars;\n }\n return str;\n}\n\n/**\n * Removes array elements based on a filtering function. Mutates the input\n * array.\n *\n * Using this instead of the ES5 Array.prototype.filter() function to prevent\n * creating many new arrays in memory for filtering.\n *\n * @param arr The array to remove elements from. This array is mutated.\n * @param fn The predicate function which should return `true` to remove an\n * element.\n */\nexport function removeWithPredicate<T>(arr: T[], fn: (item: T) => boolean) {\n for (let i = arr.length - 1; i >= 0; i--) {\n if (fn(arr[i]) === true) {\n arr.splice(i, 1);\n }\n }\n}\n\n/**\n * Function that should never be called but is used to check that every\n * enum value is handled using TypeScript's 'never' type.\n */\n/* istanbul ignore next */\nexport function assertNever(theValue: never): never {\n throw new Error(`Unhandled case for value: '${theValue}'`);\n}\n"]}
+1
View File
@@ -0,0 +1 @@
export declare const version = "4.1.5";
+7
View File
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = void 0;
// Important: this file is generated from the 'build' script and should not be
// edited directly
exports.version = '4.1.5';
//# sourceMappingURL=version.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";;;AAAA,8EAA8E;AAC9E,kBAAkB;AACL,QAAA,OAAO,GAAG,OAAO,CAAC","sourcesContent":["// Important: this file is generated from the 'build' script and should not be\n// edited directly\nexport const version = '4.1.5';\n"]}
+118
View File
@@ -0,0 +1,118 @@
import { HtmlTag } from './html-tag';
import { TruncateConfigObj } from './autolinker';
import { AbstractMatch } from './match/abstract-match';
/**
* @protected
* @class Autolinker.AnchorTagBuilder
* @extends Object
*
* Builds anchor (&lt;a&gt;) 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 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(match) {
* var tag = match.buildTag(); // 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>
*/
export declare class AnchorTagBuilder {
/**
* @cfg {Boolean} newWindow
* @inheritdoc Autolinker#newWindow
*/
private readonly newWindow;
/**
* @cfg {Object} truncate
* @inheritdoc Autolinker#truncate
*/
private readonly truncate;
/**
* @cfg {String} className
* @inheritdoc Autolinker#className
*/
private readonly className;
/**
* @method constructor
* @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
*/
constructor(cfg?: AnchorTagBuilderCfg);
/**
* Generates the actual anchor (&lt;a&gt;) tag to use in place of the
* matched text, via its `match` object.
*
* @param match The Match instance to generate an anchor tag from.
* @return The HtmlTag instance for the anchor tag.
*/
build(match: AbstractMatch): HtmlTag;
/**
* Creates the Object (map) of the HTML attributes for the anchor (&lt;a&gt;)
* tag being generated.
*
* @protected
* @param match The Match instance to generate an anchor tag from.
* @return A key/value Object (map) of the anchor tag's attributes.
*/
protected createAttrs(match: AbstractMatch): {
[attrName: string]: string;
};
/**
* 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
*
* @protected
* @param match The Match instance to generate an
* anchor tag from.
* @return The CSS class string for the link. Example return:
* "myLink myLink-url". If no {@link #className} was configured, returns
* an empty string.
*/
protected createCssClass(match: AbstractMatch): string;
/**
* Processes the `anchorText` by truncating the text according to the
* {@link #truncate} config.
*
* @private
* @param anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return The processed `anchorText`.
*/
private processAnchorText;
/**
* Performs the truncation of the `anchorText` based on the {@link #truncate}
* option. If the `anchorText` is longer than the length specified by the
* {@link #truncate} option, the truncation is performed based on the
* `location` property. See {@link #truncate} for details.
*
* @private
* @param anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return The truncated anchor text.
*/
private doTruncate;
}
export interface AnchorTagBuilderCfg {
newWindow?: boolean;
truncate?: TruncateConfigObj;
className?: string;
}
+171
View File
@@ -0,0 +1,171 @@
import { HtmlTag } from './html-tag';
import { truncateSmart } from './truncate/truncate-smart';
import { truncateMiddle } from './truncate/truncate-middle';
import { truncateEnd } from './truncate/truncate-end';
/**
* @protected
* @class Autolinker.AnchorTagBuilder
* @extends Object
*
* Builds anchor (&lt;a&gt;) 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 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(match) {
* var tag = match.buildTag(); // 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>
*/
var AnchorTagBuilder = /** @class */ (function () {
/**
* @method constructor
* @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
*/
function AnchorTagBuilder(cfg) {
if (cfg === void 0) { cfg = {}; }
/**
* @cfg {Boolean} newWindow
* @inheritdoc Autolinker#newWindow
*/
this.newWindow = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Object} truncate
* @inheritdoc Autolinker#truncate
*/
this.truncate = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {String} className
* @inheritdoc Autolinker#className
*/
this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
this.newWindow = cfg.newWindow || false;
this.truncate = cfg.truncate || {};
this.className = cfg.className || '';
}
/**
* Generates the actual anchor (&lt;a&gt;) tag to use in place of the
* matched text, via its `match` object.
*
* @param match The Match instance to generate an anchor tag from.
* @return The HtmlTag instance for the anchor tag.
*/
AnchorTagBuilder.prototype.build = function (match) {
return new HtmlTag({
tagName: 'a',
attrs: this.createAttrs(match),
innerHtml: this.processAnchorText(match.getAnchorText()),
});
};
/**
* Creates the Object (map) of the HTML attributes for the anchor (&lt;a&gt;)
* tag being generated.
*
* @protected
* @param match The Match instance to generate an anchor tag from.
* @return A key/value Object (map) of the anchor tag's attributes.
*/
AnchorTagBuilder.prototype.createAttrs = function (match) {
var attrs = {
href: match.getAnchorHref(), // we'll always have the `href` attribute
};
var cssClass = this.createCssClass(match);
if (cssClass) {
attrs['class'] = cssClass;
}
if (this.newWindow) {
attrs['target'] = '_blank';
attrs['rel'] = 'noopener noreferrer'; // Issue #149. See https://mathiasbynens.github.io/rel-noopener/
}
if (this.truncate.length && this.truncate.length < match.getAnchorText().length) {
attrs['title'] = match.getAnchorHref();
}
return attrs;
};
/**
* 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
*
* @protected
* @param match The Match instance to generate an
* anchor tag from.
* @return The CSS class string for the link. Example return:
* "myLink myLink-url". If no {@link #className} was configured, returns
* an empty string.
*/
AnchorTagBuilder.prototype.createCssClass = function (match) {
var className = this.className;
if (!className) {
return '';
}
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(' ');
}
};
/**
* Processes the `anchorText` by truncating the text according to the
* {@link #truncate} config.
*
* @private
* @param anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return The processed `anchorText`.
*/
AnchorTagBuilder.prototype.processAnchorText = function (anchorText) {
anchorText = this.doTruncate(anchorText);
return anchorText;
};
/**
* Performs the truncation of the `anchorText` based on the {@link #truncate}
* option. If the `anchorText` is longer than the length specified by the
* {@link #truncate} option, the truncation is performed based on the
* `location` property. See {@link #truncate} for details.
*
* @private
* @param anchorText The anchor tag's text (i.e. what will be
* displayed).
* @return The truncated anchor text.
*/
AnchorTagBuilder.prototype.doTruncate = function (anchorText) {
var truncate = this.truncate;
if (!truncate.length)
return anchorText;
var truncateLength = truncate.length, truncateLocation = truncate.location;
if (truncateLocation === 'smart') {
return truncateSmart(anchorText, truncateLength);
}
else if (truncateLocation === 'middle') {
return truncateMiddle(anchorText, truncateLength);
}
else {
return truncateEnd(anchorText, truncateLength);
}
};
return AnchorTagBuilder;
}());
export { AnchorTagBuilder };
//# sourceMappingURL=anchor-tag-builder.js.map
File diff suppressed because one or more lines are too long
+608
View File
@@ -0,0 +1,608 @@
import { Match } from './match/match';
import { HtmlTag } from './html-tag';
import { MentionService } from './parser/mention-utils';
import { HashtagService } from './parser/hashtag-utils';
/**
* @class Autolinker
* @extends Object
*
* Utility class used to process a given string of text, and wrap the matches in
* the appropriate anchor (&lt;a&gt;) tags to turn them into links.
*
* Any of the configuration options may be provided in an Object 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#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
* match that is encountered.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( 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 = match.buildTag(); // 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 'hashtag' :
* var hashtag = match.getHashtag();
* console.log( hashtag );
*
* return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
*
* case 'mention' :
* var mention = match.getMention();
* console.log( mention );
*
* return '<a href="http://newplace.to.link.mention.to/">' + mention + '</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.
*/
export default class Autolinker {
/**
* @static
* @property {String} version
*
* The Autolinker version number in the form major.minor.patch
*
* Ex: 3.15.0
*/
static readonly version = "4.1.5";
/**
* Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
* 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`,
* then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* 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 #mention},
* {@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.
* @return {String} The HTML text, with matches automatically linked.
*/
static link(textOrHtml: string, options?: AutolinkerConfig): string;
/**
* Parses the input `textOrHtml` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches (without making any replacements).
*
* Note that if parsing multiple pieces of text, it is slightly more efficient
* to create an Autolinker instance, and use the instance-level {@link #parse}
* method.
*
* Example:
*
* var matches = Autolinker.parse("Hello google.com, I am asdf@asdf.com", {
* urls: true,
* email: true
* });
*
* console.log(matches.length); // 2
* console.log(matches[0].getType()); // 'url'
* console.log(matches[0].getUrl()); // 'google.com'
* console.log(matches[1].getType()); // 'email'
* console.log(matches[1].getEmail()); // 'asdf@asdf.com'
*
* @static
* @param {String} textOrHtml The HTML or text to find matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@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.
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `textOrHtml`.
*/
static parse(textOrHtml: string, options?: AutolinkerConfig): Match[];
/**
* The Autolinker version number exposed on the instance itself.
*
* Ex: 0.25.1
*
* @property {String} version
*/
readonly version = "4.1.5";
/**
* @cfg {Boolean/Object} [urls]
*
* `true` if URLs should be automatically linked, `false` if they should not
* be. Defaults to `true`.
*
* Examples:
*
* urls: true
*
* // or
*
* urls: {
* schemeMatches : true,
* tldMatches : true,
* ipV4Matches : true
* }
*
* As shown above, this option also accepts an Object form with 3 properties
* to allow for more customization of what exactly gets linked. All default
* to `true`:
*
* @cfg {Boolean} [urls.schemeMatches] `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.
* @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
* level domains (.com, .net, etc.) that are not prefixed with a scheme
* (such as 'http://'). This option attempts to match anything that looks
* like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
* `false` to prevent these types of matches.
* @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
* that are not prefixed with a scheme (such as 'http://'). This option
* attempts to match anything that looks like an IPv4 address in text. Ex:
* `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
* of matches.
*/
private readonly urls;
/**
* @cfg {Boolean} [email=true]
*
* `true` if email addresses should be automatically linked, `false` if they
* should not be.
*/
private readonly email;
/**
* @cfg {Boolean} [phone=true]
*
* `true` if Phone numbers ("(555)555-5555") should be automatically linked,
* `false` if they should not be.
*/
private readonly phone;
/**
* @cfg {Boolean/String} [hashtag=false]
*
* A string for the service name to have hashtags (ex: "#myHashtag")
* auto-linked to. The currently-supported values are:
*
* - 'twitter'
* - 'facebook'
* - 'instagram'
* - 'tiktok'
* - 'youtube'
*
* Pass `false` to skip auto-linking of hashtags.
*/
private readonly hashtag;
/**
* @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'
* - 'soundcloud'
* - 'tiktok'
* - 'youtube'
*
* Defaults to `false` to skip auto-linking of mentions.
*/
private readonly mention;
/**
* @cfg {Boolean} [newWindow=true]
*
* `true` if the links should open in a new window, `false` otherwise.
*/
private readonly newWindow;
/**
* @cfg {Boolean/Object} [stripPrefix=true]
*
* `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
* from the beginning of URL links' text, `false` otherwise. Defaults to
* `true`.
*
* Examples:
*
* stripPrefix: true
*
* // or
*
* stripPrefix: {
* scheme : true,
* www : true
* }
*
* As shown above, this option also accepts an Object form with 2 properties
* to allow for more customization of what exactly is prevented from being
* displayed. Both default to `true`:
*
* @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
* a URL match from being displayed to the user. Example:
* `'http://google.com'` will be displayed as `'google.com'`. `false` to
* not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
* will be removed, so as not to remove a potentially dangerous scheme
* (such as `'file://'` or `'javascript:'`)
* @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
* `'www.'` part of a URL match from being displayed to the user. Ex:
* `'www.google.com'` will be displayed as `'google.com'`. `false` to not
* strip the `'www'`.
*/
private readonly stripPrefix;
/**
* @cfg {Boolean} [stripTrailingSlash=true]
*
* `true` to remove the trailing slash from URL matches, `false` to keep
* the trailing slash.
*
* Example when `true`: `http://google.com/` will be displayed as
* `http://google.com`.
*/
private readonly stripTrailingSlash;
/**
* @cfg {Boolean} [decodePercentEncoding=true]
*
* `true` to decode percent-encoded characters in URL matches, `false` to keep
* the percent-encoded characters.
*
* Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
* be displayed as `https://en.wikipedia.org/wiki/San_José`.
*/
private readonly decodePercentEncoding;
/**
* @cfg {Number/Object} [truncate=0]
*
* ## Number Form
*
* A number for how many characters 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..'
*
* Example Usage:
*
* truncate: 25
*
*
* Defaults to `0` for "no truncation."
*
*
* ## Object Form
*
* An Object may also be provided with two properties: `length` (Number) and
* `location` (String). `location` may be one of the following: 'end'
* (default), 'middle', or 'smart'.
*
* Example Usage:
*
* truncate: { length: 25, location: 'middle' }
*
* @cfg {Number} [truncate.length=0] How many characters to allow before
* truncation will occur. Defaults to `0` for "no truncation."
* @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
*
* - 'end' (default): will truncate up to the number of characters, and then
* add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
* - 'middle': will truncate and add the ellipsis in the middle. Ex:
* 'yahoo.com/s..th/to/a/file'
* - 'smart': for URLs where the algorithm attempts to strip out unnecessary
* parts first (such as the 'www.', then URL scheme, hash, etc.),
* attempting to make the URL human-readable before looking for a good
* point to insert the ellipsis if it is still too long. Ex:
* 'yahoo.com/some..to/a/file'. For more details, see
* {@link Autolinker.truncate.TruncateSmart}.
*/
private readonly truncate;
/**
* @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/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
* - 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-[type]"
* where [type] is either "instagram", "twitter" or "soundcloud"
*/
private readonly className;
/**
* @cfg {Function} replaceFn
*
* A function to individually process each match found in the input string.
*
* See the class's description for usage.
*
* 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.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 readonly replaceFn;
/**
* @cfg {Object} context
*
* The context object (`this` reference) to call the `replaceFn` with.
*
* Defaults to this Autolinker instance.
*/
private readonly context;
/**
* @cfg {Boolean} [sanitizeHtml=false]
*
* `true` to HTML-encode the start and end brackets of existing HTML tags found
* in the input string. This will escape `<` and `>` characters to `&lt;` and
* `&gt;`, respectively.
*
* Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
* but will remove the significance of existing HTML tags in the input string. If
* you would like to maintain the significance of existing HTML tags while also
* making the output HTML string safe, leave this option as `false` and use a
* tool like https://github.com/cure53/DOMPurify (or others) on the input string
* before running Autolinker.
*/
private readonly sanitizeHtml;
/**
* @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.
*/
private tagBuilder;
/**
* @method constructor
* @param {Object} [cfg] The configuration options for the Autolinker instance,
* specified in an Object (map).
*/
constructor(cfg?: AutolinkerConfig);
/**
* Parses the input `textOrHtml` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches (without making any replacements).
*
* This method is used by the {@link #link} method, but can also be used to
* simply do parsing of the input in order to discover what kinds of links
* there are and how many.
*
* Example usage:
*
* var autolinker = new Autolinker( {
* urls: true,
* email: true
* } );
*
* var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
*
* console.log( matches.length ); // 2
* console.log( matches[ 0 ].getType() ); // 'url'
* console.log( matches[ 0 ].getUrl() ); // 'google.com'
* console.log( matches[ 1 ].getType() ); // 'email'
* console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
*
* @param {String} textOrHtml The HTML or text to find matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@link #hashtag}, and {@link #mention} options are enabled).
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `textOrHtml`.
*/
parse(textOrHtml: string): Match[];
/**
* After we have found all matches, we need to remove matches that overlap
* with a previous match. This can happen for instance with an
* email address where the local-part of the email is also a top-level
* domain, such as in "google.com@aaa.com". In this case, the entire email
* address should be linked rather than just the 'google.com' part.
*
* @private
* @param {Autolinker.match.Match[]} matches
* @return {Autolinker.match.Match[]}
*/
private compactMatches;
/**
* Removes matches for matchers that were turned off in the options. For
* example, if {@link #hashtag hashtags} were not to be matched, we'll
* remove them from the `matches` array here.
*
* Note: we *must* use all Matchers on the input string, and then filter
* them out later. For example, if the options were `{ url: false, hashtag: true }`,
* we wouldn't want to match the text '#link' as a HashTag inside of the text
* 'google.com/#link'. The way the algorithm works is that we match the full
* URL first (which prevents the accidental HashTag match), and then we'll
* simply throw away the URL match.
*
* @private
* @param {Autolinker.match.Match[]} matches The array of matches to remove
* the unwanted matches from. Note: this array is mutated for the
* removals.
* @return {Autolinker.match.Match[]} The mutated input `matches` array.
*/
private removeUnwantedMatches;
/**
* Parses the input `text` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches.
*
* This method processes a **non-HTML string**, and is used to parse and
* match within the text nodes of an HTML string. This method is used
* internally by {@link #parse}.
*
* @private
* @param {String} text The text to find matches within (depending on if the
* {@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}
* instances, but may be omitted if calling this method publicly.
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `text`.
*/
private parseText;
/**
* Automatically links URLs, Email addresses, Phone numbers, Hashtags,
* and Mentions (Twitter, Instagram, Soundcloud) 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
* &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* 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 (&lt;a&gt;) tags.
*
* @param {String} textOrHtml The HTML or text to autolink matches within
* (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(textOrHtml: string): string;
/**
* Creates the return string value for a given match in the input string.
*
* 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.
*/
private createMatchReturnVal;
/**
* Returns the {@link #tagBuilder} instance for this Autolinker instance,
* lazily instantiating it if it does not yet exist.
*
* @private
* @return {Autolinker.AnchorTagBuilder}
*/
private getTagBuilder;
}
export interface AutolinkerConfig {
urls?: UrlsConfig;
email?: boolean;
phone?: boolean;
hashtag?: HashtagConfig;
mention?: MentionConfig;
newWindow?: boolean;
stripPrefix?: StripPrefixConfig;
stripTrailingSlash?: boolean;
truncate?: TruncateConfig;
className?: string;
replaceFn?: ReplaceFn | null;
context?: object;
sanitizeHtml?: boolean;
decodePercentEncoding?: boolean;
}
export type UrlsConfig = boolean | UrlsConfigObj;
export interface UrlsConfigObj {
schemeMatches?: boolean;
tldMatches?: boolean;
ipV4Matches?: boolean;
}
export type StripPrefixConfig = boolean | StripPrefixConfigObj;
export interface StripPrefixConfigObj {
scheme?: boolean;
www?: boolean;
}
export type TruncateConfig = number | TruncateConfigObj;
export interface TruncateConfigObj {
length?: number;
location?: 'end' | 'middle' | 'smart';
}
export type HashtagConfig = false | HashtagService;
export type MentionConfig = false | MentionService;
export type ReplaceFn = (match: Match) => ReplaceFnReturn;
export type ReplaceFnReturn = boolean | string | HtmlTag | null | undefined | void;
+898
View File
@@ -0,0 +1,898 @@
import { __assign, __read, __spreadArray } from "tslib";
import { version } from './version';
import { isBoolean, removeWithPredicate } from './utils';
import { AnchorTagBuilder } from './anchor-tag-builder';
import { HtmlTag } from './html-tag';
import { parseMatches } from './parser/parse-matches';
import { parseHtml } from './htmlParser/parse-html';
import { mentionServices } from './parser/mention-utils';
import { hashtagServices } from './parser/hashtag-utils';
/**
* @class Autolinker
* @extends Object
*
* Utility class used to process a given string of text, and wrap the matches in
* the appropriate anchor (&lt;a&gt;) tags to turn them into links.
*
* Any of the configuration options may be provided in an Object 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#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
* match that is encountered.
*
* For example:
*
* var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
*
* var linkedText = Autolinker.link( input, {
* replaceFn : function( 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 = match.buildTag(); // 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 'hashtag' :
* var hashtag = match.getHashtag();
* console.log( hashtag );
*
* return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
*
* case 'mention' :
* var mention = match.getMention();
* console.log( mention );
*
* return '<a href="http://newplace.to.link.mention.to/">' + mention + '</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.
*/
var Autolinker = /** @class */ (function () {
/**
* @method constructor
* @param {Object} [cfg] The configuration options for the Autolinker instance,
* specified in an Object (map).
*/
function Autolinker(cfg) {
if (cfg === void 0) { cfg = {}; }
/**
* The Autolinker version number exposed on the instance itself.
*
* Ex: 0.25.1
*
* @property {String} version
*/
this.version = Autolinker.version;
/**
* @cfg {Boolean/Object} [urls]
*
* `true` if URLs should be automatically linked, `false` if they should not
* be. Defaults to `true`.
*
* Examples:
*
* urls: true
*
* // or
*
* urls: {
* schemeMatches : true,
* tldMatches : true,
* ipV4Matches : true
* }
*
* As shown above, this option also accepts an Object form with 3 properties
* to allow for more customization of what exactly gets linked. All default
* to `true`:
*
* @cfg {Boolean} [urls.schemeMatches] `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.
* @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
* level domains (.com, .net, etc.) that are not prefixed with a scheme
* (such as 'http://'). This option attempts to match anything that looks
* like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
* `false` to prevent these types of matches.
* @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
* that are not prefixed with a scheme (such as 'http://'). This option
* attempts to match anything that looks like an IPv4 address in text. Ex:
* `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
* of matches.
*/
this.urls = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [email=true]
*
* `true` if email addresses should be automatically linked, `false` if they
* should not be.
*/
this.email = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [phone=true]
*
* `true` if Phone numbers ("(555)555-5555") should be automatically linked,
* `false` if they should not be.
*/
this.phone = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean/String} [hashtag=false]
*
* A string for the service name to have hashtags (ex: "#myHashtag")
* auto-linked to. The currently-supported values are:
*
* - 'twitter'
* - 'facebook'
* - 'instagram'
* - 'tiktok'
* - 'youtube'
*
* Pass `false` to skip auto-linking of hashtags.
*/
this.hashtag = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @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'
* - 'soundcloud'
* - 'tiktok'
* - 'youtube'
*
* Defaults to `false` to skip auto-linking of mentions.
*/
this.mention = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [newWindow=true]
*
* `true` if the links should open in a new window, `false` otherwise.
*/
this.newWindow = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean/Object} [stripPrefix=true]
*
* `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
* from the beginning of URL links' text, `false` otherwise. Defaults to
* `true`.
*
* Examples:
*
* stripPrefix: true
*
* // or
*
* stripPrefix: {
* scheme : true,
* www : true
* }
*
* As shown above, this option also accepts an Object form with 2 properties
* to allow for more customization of what exactly is prevented from being
* displayed. Both default to `true`:
*
* @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
* a URL match from being displayed to the user. Example:
* `'http://google.com'` will be displayed as `'google.com'`. `false` to
* not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
* will be removed, so as not to remove a potentially dangerous scheme
* (such as `'file://'` or `'javascript:'`)
* @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
* `'www.'` part of a URL match from being displayed to the user. Ex:
* `'www.google.com'` will be displayed as `'google.com'`. `false` to not
* strip the `'www'`.
*/
this.stripPrefix = {
scheme: true,
www: true,
}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [stripTrailingSlash=true]
*
* `true` to remove the trailing slash from URL matches, `false` to keep
* the trailing slash.
*
* Example when `true`: `http://google.com/` will be displayed as
* `http://google.com`.
*/
this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [decodePercentEncoding=true]
*
* `true` to decode percent-encoded characters in URL matches, `false` to keep
* the percent-encoded characters.
*
* Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
* be displayed as `https://en.wikipedia.org/wiki/San_José`.
*/
this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Number/Object} [truncate=0]
*
* ## Number Form
*
* A number for how many characters 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..'
*
* Example Usage:
*
* truncate: 25
*
*
* Defaults to `0` for "no truncation."
*
*
* ## Object Form
*
* An Object may also be provided with two properties: `length` (Number) and
* `location` (String). `location` may be one of the following: 'end'
* (default), 'middle', or 'smart'.
*
* Example Usage:
*
* truncate: { length: 25, location: 'middle' }
*
* @cfg {Number} [truncate.length=0] How many characters to allow before
* truncation will occur. Defaults to `0` for "no truncation."
* @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
*
* - 'end' (default): will truncate up to the number of characters, and then
* add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
* - 'middle': will truncate and add the ellipsis in the middle. Ex:
* 'yahoo.com/s..th/to/a/file'
* - 'smart': for URLs where the algorithm attempts to strip out unnecessary
* parts first (such as the 'www.', then URL scheme, hash, etc.),
* attempting to make the URL human-readable before looking for a good
* point to insert the ellipsis if it is still too long. Ex:
* 'yahoo.com/some..to/a/file'. For more details, see
* {@link Autolinker.truncate.TruncateSmart}.
*/
this.truncate = {
length: 0,
location: 'end',
}; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @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/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
* - 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-[type]"
* where [type] is either "instagram", "twitter" or "soundcloud"
*/
this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Function} replaceFn
*
* A function to individually process each match found in the input string.
*
* See the class's description for usage.
*
* 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.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.
*/
this.replaceFn = null; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Object} context
*
* The context object (`this` reference) to call the `replaceFn` with.
*
* Defaults to this Autolinker instance.
*/
this.context = undefined; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @cfg {Boolean} [sanitizeHtml=false]
*
* `true` to HTML-encode the start and end brackets of existing HTML tags found
* in the input string. This will escape `<` and `>` characters to `&lt;` and
* `&gt;`, respectively.
*
* Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
* but will remove the significance of existing HTML tags in the input string. If
* you would like to maintain the significance of existing HTML tags while also
* making the output HTML string safe, leave this option as `false` and use a
* tool like https://github.com/cure53/DOMPurify (or others) on the input string
* before running Autolinker.
*/
this.sanitizeHtml = false; // default value just to get the above doc comment in the ES5 output and documentation generator
/**
* @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.
*/
this.tagBuilder = null;
// Note: when `this.something` is used in the rhs of these assignments,
// it refers to the default values set above the constructor
this.urls = normalizeUrlsCfg(cfg.urls);
this.email = isBoolean(cfg.email) ? cfg.email : this.email;
this.phone = isBoolean(cfg.phone) ? cfg.phone : this.phone;
this.hashtag = cfg.hashtag || this.hashtag;
this.mention = cfg.mention || this.mention;
this.newWindow = isBoolean(cfg.newWindow) ? cfg.newWindow : this.newWindow;
this.stripPrefix = normalizeStripPrefixCfg(cfg.stripPrefix);
this.stripTrailingSlash = isBoolean(cfg.stripTrailingSlash)
? cfg.stripTrailingSlash
: this.stripTrailingSlash;
this.decodePercentEncoding = isBoolean(cfg.decodePercentEncoding)
? cfg.decodePercentEncoding
: this.decodePercentEncoding;
this.sanitizeHtml = cfg.sanitizeHtml || false;
// Validate the value of the `mention` cfg
var mention = this.mention;
if (mention !== false && mentionServices.indexOf(mention) === -1) {
throw new Error("invalid `mention` cfg '".concat(mention, "' - see docs"));
}
// Validate the value of the `hashtag` cfg
var hashtag = this.hashtag;
if (hashtag !== false && hashtagServices.indexOf(hashtag) === -1) {
throw new Error("invalid `hashtag` cfg '".concat(hashtag, "' - see docs"));
}
this.truncate = normalizeTruncateCfg(cfg.truncate);
this.className = cfg.className || this.className;
this.replaceFn = cfg.replaceFn || this.replaceFn;
this.context = cfg.context || this;
}
/**
* Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
* 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`,
* then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* 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 #mention},
* {@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.
* @return {String} The HTML text, with matches automatically linked.
*/
Autolinker.link = function (textOrHtml, options) {
var autolinker = new Autolinker(options);
return autolinker.link(textOrHtml);
};
/**
* Parses the input `textOrHtml` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches (without making any replacements).
*
* Note that if parsing multiple pieces of text, it is slightly more efficient
* to create an Autolinker instance, and use the instance-level {@link #parse}
* method.
*
* Example:
*
* var matches = Autolinker.parse("Hello google.com, I am asdf@asdf.com", {
* urls: true,
* email: true
* });
*
* console.log(matches.length); // 2
* console.log(matches[0].getType()); // 'url'
* console.log(matches[0].getUrl()); // 'google.com'
* console.log(matches[1].getType()); // 'email'
* console.log(matches[1].getEmail()); // 'asdf@asdf.com'
*
* @static
* @param {String} textOrHtml The HTML or text to find matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@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.
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `textOrHtml`.
*/
Autolinker.parse = function (textOrHtml, options) {
var autolinker = new Autolinker(options);
return autolinker.parse(textOrHtml);
};
/**
* Parses the input `textOrHtml` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches (without making any replacements).
*
* This method is used by the {@link #link} method, but can also be used to
* simply do parsing of the input in order to discover what kinds of links
* there are and how many.
*
* Example usage:
*
* var autolinker = new Autolinker( {
* urls: true,
* email: true
* } );
*
* var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
*
* console.log( matches.length ); // 2
* console.log( matches[ 0 ].getType() ); // 'url'
* console.log( matches[ 0 ].getUrl() ); // 'google.com'
* console.log( matches[ 1 ].getType() ); // 'email'
* console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
*
* @param {String} textOrHtml The HTML or text to find matches within
* (depending on if the {@link #urls}, {@link #email}, {@link #phone},
* {@link #hashtag}, and {@link #mention} options are enabled).
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `textOrHtml`.
*/
Autolinker.prototype.parse = function (textOrHtml) {
var _this = this;
var skipTagNames = ['a', 'style', 'script'];
var skipTagsStackCount = 0; // used to only Autolink text outside of anchor/script/style tags. We don't want to autolink something that is already linked inside of an <a> tag, for instance
var matches = [];
// Find all matches within the `textOrHtml` (but not matches that are
// already nested within <a>, <style> and <script> tags)
parseHtml(textOrHtml, {
onOpenTag: function (tagName) {
if (skipTagNames.indexOf(tagName) >= 0) {
skipTagsStackCount++;
}
},
onText: function (text, offset) {
// Only process text nodes that are not within an <a>, <style> or <script> tag
if (skipTagsStackCount === 0) {
// "Walk around" common HTML entities. An '&nbsp;' (for example)
// could be at the end of a URL, but we don't want to
// include the trailing '&' in the URL. See issue #76
// TODO: Handle HTML entities separately in parseHtml() and
// don't emit them as "text" except for &amp; entities
var htmlCharacterEntitiesRegex = /(&nbsp;|&#160;|&lt;|&#60;|&gt;|&#62;|&quot;|&#34;|&#39;)/gi; // NOTE: capturing group is significant to include the split characters in the .split() call below
var textSplit = text.split(htmlCharacterEntitiesRegex);
var currentOffset_1 = offset;
textSplit.forEach(function (splitText, i) {
// even number matches are text, odd numbers are html entities
if (i % 2 === 0) {
var textNodeMatches = _this.parseText(splitText, currentOffset_1);
matches.push.apply(matches, __spreadArray([], __read(textNodeMatches), false));
}
currentOffset_1 += splitText.length;
});
}
},
onCloseTag: function (tagName) {
if (skipTagNames.indexOf(tagName) >= 0) {
skipTagsStackCount = Math.max(skipTagsStackCount - 1, 0); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
}
},
onComment: function ( /*_offset: number*/) { }, // no need to process comment nodes
onDoctype: function ( /*_offset: number*/) { }, // no need to process doctype nodes
});
// After we have found all matches, remove subsequent matches that
// overlap with a previous match. This can happen for instance with an
// email address where the local-part of the email is also a top-level
// domain, such as in "google.com@aaa.com". In this case, the entire
// email address should be linked rather than just the 'google.com'
// part.
matches = this.compactMatches(matches);
// And finally, remove matches for match types that have been turned
// off. We needed to have all match types turned on initially so that
// things like hashtags could be filtered out if they were really just
// part of a URL match (for instance, as a named anchor).
matches = this.removeUnwantedMatches(matches);
return matches;
};
/**
* After we have found all matches, we need to remove matches that overlap
* with a previous match. This can happen for instance with an
* email address where the local-part of the email is also a top-level
* domain, such as in "google.com@aaa.com". In this case, the entire email
* address should be linked rather than just the 'google.com' part.
*
* @private
* @param {Autolinker.match.Match[]} matches
* @return {Autolinker.match.Match[]}
*/
Autolinker.prototype.compactMatches = function (matches) {
// First, the matches need to be sorted in order of offset in the input
// string
matches.sort(byMatchOffset);
var i = 0;
while (i < matches.length - 1) {
var match = matches[i];
var offset = match.getOffset();
var matchedTextLength = match.getMatchedText().length;
if (i + 1 < matches.length) {
// Remove subsequent matches that equal offset with current match
// This can happen when matching the text "google.com@aaa.com"
// where we have both a URL ('google.com') and an email. We
// should only keep the email match in this case.
if (matches[i + 1].getOffset() === offset) {
// Remove the shorter match
var removeIdx = matches[i + 1].getMatchedText().length > matchedTextLength ? i : i + 1;
matches.splice(removeIdx, 1);
continue;
}
// Remove subsequent matches that overlap with the current match
//
// NOTE: This was a fundamental snippet of the Autolinker.js v3
// algorithm where we had multiple regular expressions searching
// the input string for matches. The regexes would sometimes
// overlap such as in the case of "google.com/#link", where we
// would have both a URL match and a hashtag match.
//
// However, the Autolinker.js v4 algorithm uses a state machine
// parser and knows that the '#link' part of 'google.com/#link'
// is part of the URL that precedes it, so we don't need this
// piece of code any more. Keeping it here commented for now in
// case we need to put it back at some point, but none of the
// test cases are currently able to trigger the need for it.
// const endIdx = offset + matchedTextLength;
// if (matches[i + 1].getOffset() < endIdx) {
// matches.splice(i + 1, 1);
// continue;
// }
}
i++;
}
return matches;
};
/**
* Removes matches for matchers that were turned off in the options. For
* example, if {@link #hashtag hashtags} were not to be matched, we'll
* remove them from the `matches` array here.
*
* Note: we *must* use all Matchers on the input string, and then filter
* them out later. For example, if the options were `{ url: false, hashtag: true }`,
* we wouldn't want to match the text '#link' as a HashTag inside of the text
* 'google.com/#link'. The way the algorithm works is that we match the full
* URL first (which prevents the accidental HashTag match), and then we'll
* simply throw away the URL match.
*
* @private
* @param {Autolinker.match.Match[]} matches The array of matches to remove
* the unwanted matches from. Note: this array is mutated for the
* removals.
* @return {Autolinker.match.Match[]} The mutated input `matches` array.
*/
Autolinker.prototype.removeUnwantedMatches = function (matches) {
if (!this.hashtag)
removeWithPredicate(matches, function (match) {
return match.getType() === 'hashtag';
});
if (!this.email)
removeWithPredicate(matches, function (match) {
return match.getType() === 'email';
});
if (!this.phone)
removeWithPredicate(matches, function (match) {
return match.getType() === 'phone';
});
if (!this.mention)
removeWithPredicate(matches, function (match) {
return match.getType() === 'mention';
});
if (!this.urls.schemeMatches) {
removeWithPredicate(matches, function (m) {
return m.getType() === 'url' && m.getUrlMatchType() === 'scheme';
});
}
if (!this.urls.tldMatches) {
removeWithPredicate(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'tld'; });
}
if (!this.urls.ipV4Matches) {
removeWithPredicate(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'ipV4'; });
}
return matches;
};
/**
* Parses the input `text` looking for URLs, email addresses, phone
* numbers, username handles, and hashtags (depending on the configuration
* of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
* objects describing those matches.
*
* This method processes a **non-HTML string**, and is used to parse and
* match within the text nodes of an HTML string. This method is used
* internally by {@link #parse}.
*
* @private
* @param {String} text The text to find matches within (depending on if the
* {@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}
* instances, but may be omitted if calling this method publicly.
* @return {Autolinker.match.Match[]} The array of Matches found in the
* given input `text`.
*/
Autolinker.prototype.parseText = function (text, offset) {
offset = offset || 0;
var matches = parseMatches(text, {
tagBuilder: this.getTagBuilder(),
stripPrefix: this.stripPrefix,
stripTrailingSlash: this.stripTrailingSlash,
decodePercentEncoding: this.decodePercentEncoding,
hashtagServiceName: this.hashtag,
mentionServiceName: this.mention || 'twitter',
});
// Correct the offset of each of the matches. They are originally
// the offset of the match within the provided text node, but we
// need to correct them to be relative to the original HTML input
// string (i.e. the one provided to #parse).
for (var i = 0, numTextMatches = matches.length; i < numTextMatches; i++) {
matches[i].setOffset(offset + matches[i].getOffset());
}
return matches;
};
/**
* Automatically links URLs, Email addresses, Phone numbers, Hashtags,
* and Mentions (Twitter, Instagram, Soundcloud) 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
* &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
*
* 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 (&lt;a&gt;) tags.
*
* @param {String} textOrHtml The HTML or text to autolink matches within
* (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.
*/
Autolinker.prototype.link = function (textOrHtml) {
if (!textOrHtml) {
return '';
} // handle `null` and `undefined` (for JavaScript users that don't have TypeScript support), and nothing to do with an empty string too
/* We would want to sanitize the start and end characters of a tag
* before processing the string in order to avoid an XSS scenario.
* This behaviour can be changed by toggling the sanitizeHtml option.
*/
if (this.sanitizeHtml) {
textOrHtml = textOrHtml.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
var matches = this.parse(textOrHtml);
var newHtml = new Array(matches.length * 2 + 1);
var lastIndex = 0;
for (var i = 0, len = matches.length; i < len; i++) {
var match = matches[i];
newHtml.push(textOrHtml.substring(lastIndex, match.getOffset()));
newHtml.push(this.createMatchReturnVal(match));
lastIndex = match.getOffset() + match.getMatchedText().length;
}
newHtml.push(textOrHtml.substring(lastIndex)); // handle the text after the last match
return newHtml.join('');
};
/**
* Creates the return string value for a given match in the input string.
*
* 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.
*/
Autolinker.prototype.createMatchReturnVal = function (match) {
// Handle a custom `replaceFn` being provided
var replaceFnResult;
if (this.replaceFn) {
replaceFnResult = this.replaceFn.call(this.context, match); // Autolinker instance is the context
}
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 HtmlTag) {
return replaceFnResult.toAnchorString();
}
else {
// replaceFnResult === true, or no/unknown return value from function
// Perform Autolinker's default anchor tag generation
var anchorTag = match.buildTag(); // returns an Autolinker.HtmlTag instance
return anchorTag.toAnchorString();
}
};
/**
* Returns the {@link #tagBuilder} instance for this Autolinker instance,
* lazily instantiating it if it does not yet exist.
*
* @private
* @return {Autolinker.AnchorTagBuilder}
*/
Autolinker.prototype.getTagBuilder = function () {
var tagBuilder = this.tagBuilder;
if (!tagBuilder) {
tagBuilder = this.tagBuilder = new AnchorTagBuilder({
newWindow: this.newWindow,
truncate: this.truncate,
className: this.className,
});
}
return tagBuilder;
};
// NOTE: must be 'export default' here for UMD module
/**
* @static
* @property {String} version
*
* The Autolinker version number in the form major.minor.patch
*
* Ex: 3.15.0
*/
Autolinker.version = version;
return Autolinker;
}());
export default Autolinker;
/**
* Normalizes the {@link #urls} config into an Object with its 2 properties:
* `schemeMatches` and `tldMatches`, both booleans.
*
* See {@link #urls} config for details.
*
* @private
* @param {Boolean/Object} urls
* @return {Object}
*/
function normalizeUrlsCfg(urls) {
if (urls == null)
urls = true; // default to `true`
if (isBoolean(urls)) {
return { schemeMatches: urls, tldMatches: urls, ipV4Matches: urls };
}
else {
// object form
return {
schemeMatches: isBoolean(urls.schemeMatches) ? urls.schemeMatches : true,
tldMatches: isBoolean(urls.tldMatches) ? urls.tldMatches : true,
ipV4Matches: isBoolean(urls.ipV4Matches) ? urls.ipV4Matches : true,
};
}
}
/**
* Normalizes the {@link #stripPrefix} config into an Object with 2
* properties: `scheme`, and `www` - both Booleans.
*
* See {@link #stripPrefix} config for details.
*
* @private
* @param {Boolean/Object} stripPrefix
* @return {Object}
*/
function normalizeStripPrefixCfg(stripPrefix) {
if (stripPrefix == null)
stripPrefix = true; // default to `true`
if (isBoolean(stripPrefix)) {
return { scheme: stripPrefix, www: stripPrefix };
}
else {
// object form
return {
scheme: isBoolean(stripPrefix.scheme) ? stripPrefix.scheme : true,
www: isBoolean(stripPrefix.www) ? stripPrefix.www : true,
};
}
}
/**
* Normalizes the {@link #truncate} config into an Object with 2 properties:
* `length` (Number), and `location` (String).
*
* See {@link #truncate} config for details.
*
* @private
* @param {Number/Object} truncate
* @return {Object}
*/
function normalizeTruncateCfg(truncate) {
if (typeof truncate === 'number') {
return { length: truncate, location: 'end' };
}
else {
// object, or undefined/null
return __assign({ length: Number.POSITIVE_INFINITY, location: 'end' }, truncate);
}
}
/**
* Helper function for Array.prototype.sort() to sort the Matches by
* their offset in the input string.
*/
function byMatchOffset(a, b) {
return a.getOffset() - b.getOffset();
}
//# sourceMappingURL=autolinker.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+30
View File
@@ -0,0 +1,30 @@
/**
* Common UTF-16 character codes used in the program.
*
* This is a 'const' enum, meaning that the numerical value will be inlined into
* the code when TypeScript is compiled.
*/
export declare const enum Char {
A = 65,
Z = 90,
a = 97,
z = 122,
DoubleQuote = 34,// char code for "
SingleQuote = 39,// char code for '
Zero = 48,// char code for '0'
Nine = 57,// char code for '9'
Space = 32,// U+0020 Space <SP> Normal space
NumberSign = 35,// '#' char
OpenParen = 40,// '(' char
CloseParen = 41,// ')' char
Plus = 43,// '+' char
Comma = 44,// ',' char
Dash = 45,// '-' char
Dot = 46,// '.' char
Slash = 47,// '/' char
Colon = 58,// ':' char
SemiColon = 59,// ';' char
Question = 63,// '?' char
AtSign = 64,// '@' char
Underscore = 95
}
+2
View File
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=char.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"char.js","sourceRoot":"","sources":["../../src/char.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Common UTF-16 character codes used in the program.\n *\n * This is a 'const' enum, meaning that the numerical value will be inlined into\n * the code when TypeScript is compiled.\n */\n// prettier-ignore\nexport const enum Char {\n // Letter chars (usually used for scheme testing)\n A = 65,\n Z = 90,\n a = 97,\n z = 122,\n\n // Quote chars (used for HTML parsing)\n DoubleQuote = 34, // char code for \"\n SingleQuote = 39, // char code for '\n\n // Digit chars (used for parsing matches)\n Zero = 48, // char code for '0'\n Nine = 57, // char code for '9'\n\n // Semantically meaningful characters for HTML and Match parsing\n Space = 32, // U+0020 Space <SP> Normal space\n NumberSign = 35, // '#' char\n OpenParen = 40, // '(' char\n CloseParen = 41, // ')' char\n Plus = 43, // '+' char\n Comma = 44, // ',' char\n Dash = 45, // '-' char\n Dot = 46, // '.' char\n Slash = 47, // '/' char\n Colon = 58, // ':' char\n SemiColon = 59, // ';' char\n Question = 63, // '?' char\n AtSign = 64, // '@' char\n Underscore = 95, // '_' char\n}\n"]}

Some files were not shown because too many files have changed in this diff Show More