Add existing to tracked
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Gregory Jacobs (http://greg-jacobs.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+662
@@ -0,0 +1,662 @@
|
||||
# Autolinker.js
|
||||
|
||||

|
||||
[](https://www.npmjs.com/package/autolinker)
|
||||
[](https://github.com/gregjacobs/Autolinker.js/actions/workflows/main.yml)
|
||||
[](https://codecov.io/github/gregjacobs/Autolinker.js)
|
||||
[](https://github.com/gregjacobs/Autolinker.js/blob/master/LICENSE)
|
||||
|
||||
Automatic linking of URLs, emails, phone numbers, mentions, and hashtags in text.
|
||||
|
||||
```
|
||||
Input: "Visit google.com"
|
||||
|
||||
|
|
||||
|
|
||||
v
|
||||
|
||||
Output: "Visit <a href="https://google.com">google.com</a>"
|
||||
```
|
||||
|
||||
Because I had so much trouble finding a good auto-linking implementation out in
|
||||
the wild, I decided to roll my own. It seemed that everything I found was either
|
||||
an implementation that didn't cover every case, had many false positives linked,
|
||||
or was just limited in one way or another.
|
||||
|
||||
So, this utility attempts to handle everything. It:
|
||||
|
||||
- Autolinks URLs, whether or not they start with the protocol (i.e. 'http://').
|
||||
In other words, it will automatically link the text "google.com", as well as
|
||||
"http://google.com". Will also autolink IPv4 addresses.
|
||||
- Will properly handle URLs with query params, anchors,
|
||||
and special characters, and not include chars like a trailing `.` at the end
|
||||
of a sentence, or a `)` char if the URL is inside parenenthesis.
|
||||
- Will autolink email addresses
|
||||
- Will autolink phone numbers
|
||||
- Will autolink mentions (Twitter, Instagram, Soundcloud, TikTok, Youtube)
|
||||
- Will autolink hashtags (Twitter, Instagram, Facebook, TikTok, Youtube)
|
||||
- Won't clobber URLs with hash anchors by treating them as hashtags (like some other libraries do). For example: `google.com/#anchor` is properly linked.
|
||||
- **Will properly handle HTML input.** The utility will not overwrite an `href`
|
||||
attribute inside anchor (`<a>`) tags (or any other tag/attribute), and will not
|
||||
accidentally wrap the inner text of `<a>`/`<script>`/`<style>` tags with a new
|
||||
one (which cause doubly-nested anchor tags, or mess with scripts)
|
||||
- Will do all of this in `O(n)` (linear) time with low constant factors and without the possibility of RegExp [Catastrophic Backtracking](https://www.regular-expressions.info/catastrophic.html), making it extremely fast and unsusceptible to pathological inputs.
|
||||
|
||||
<a name="benchmarks-table"></a>Quick [benchmarks](#benchmarks) comparison:
|
||||
|
||||
| Library | Ops/Sec | MOE | Compared to Fastest |
|
||||
| ----------------------------------- | ------- | ------ | --------------------|
|
||||
| **Autolinker**@4.1.5 | 3,278 | ±0.40% | Fastest ✅ |
|
||||
| [anchorme][1]@3.0.8 | 2,393 | ±0.35% | 26% (1.37x) slower |
|
||||
| [linkifyjs][2]@4.2.0 (linkify-html) | 1,875 | ±0.32% | 42% (1.75x) slower |
|
||||
| [linkify-it][3]@5.0.0 | 491 | ±0.54% | 85% (6.67x) slower |
|
||||
|
||||
(please let me know of other comparable libraries to compare to!)
|
||||
|
||||
Hope that this utility helps you as well!
|
||||
|
||||
Full API Docs: [http://gregjacobs.github.io/Autolinker.js/api/](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker)<br>
|
||||
Live Example: [http://gregjacobs.github.io/Autolinker.js/examples/live-example/](http://gregjacobs.github.io/Autolinker.js/examples/live-example/)
|
||||
|
||||
|
||||
## Upgrading from a previous major version of Autolinker?
|
||||
|
||||
See [Breaking Changes](#upgrading-from-v3x---v4x-breaking-changes) at the bottom of this readme.
|
||||
|
||||
## Installation
|
||||
|
||||
#### Installing with [npm](https://www.npmjs.org/):
|
||||
|
||||
```shell
|
||||
npm install autolinker --save
|
||||
```
|
||||
|
||||
|
||||
#### Installing with [Yarn](https://yarnpkg.com/):
|
||||
|
||||
```shell
|
||||
yarn add autolinker
|
||||
```
|
||||
|
||||
|
||||
#### Installing with [pnpm](https://pnpm.io/):
|
||||
|
||||
```shell
|
||||
pnpm add autolinker
|
||||
```
|
||||
|
||||
|
||||
#### Installing with [Bower](http://bower.io):
|
||||
|
||||
```shell
|
||||
bower install Autolinker.js --save
|
||||
```
|
||||
|
||||
|
||||
#### Direct download
|
||||
|
||||
Simply clone this repository or download a zip of the project, and link to
|
||||
either `dist/Autolinker.js` or `dist/Autolinker.min.js` with a `<script>` tag.
|
||||
|
||||
|
||||
## Importing Autolinker
|
||||
|
||||
#### ES6+/TypeScript/Webpack/Node.js ESM:
|
||||
|
||||
```ts
|
||||
import Autolinker from 'autolinker';
|
||||
```
|
||||
|
||||
#### Node.js CommonJS:
|
||||
|
||||
```javascript
|
||||
const Autolinker = require('autolinker');
|
||||
// note: npm wants an all-lowercase package name, but the utility is a class and
|
||||
// should be aliased with a capital letter
|
||||
```
|
||||
|
||||
#### Browser
|
||||
|
||||
```html
|
||||
<!-- 'Autolinker.js' or 'Autolinker.min.js' - non-minified is better for
|
||||
debugging, minified is better for users' download time -->
|
||||
<script src="path/to/autolinker/dist/Autolinker.min.js"></script>
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Using the static [link()](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-static-method-link)
|
||||
method:
|
||||
|
||||
```javascript
|
||||
const linkedText = Autolinker.link(textToAutolink[, options]);
|
||||
```
|
||||
|
||||
Using as a class:
|
||||
|
||||
```javascript
|
||||
const autolinker = new Autolinker([ options ]);
|
||||
|
||||
const linkedText = autolinker.link(textToAutoLink);
|
||||
```
|
||||
|
||||
Note: if using the same options to autolink multiple pieces of html/text, it's
|
||||
slightly more efficient to create a single Autolinker instance, and run the
|
||||
[link()](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-method-link)
|
||||
method repeatedly (i.e. use the "class" form above).
|
||||
|
||||
|
||||
#### Examples:
|
||||
|
||||
```javascript
|
||||
const linkedText = Autolinker.link("Check out google.com");
|
||||
// Produces: "Check out <a href="http://google.com" target="_blank" rel="noopener noreferrer">google.com</a>"
|
||||
|
||||
const linkedText = Autolinker.link("Check out google.com", {
|
||||
newWindow: false
|
||||
});
|
||||
// Produces: "Check out <a href="http://google.com">google.com</a>"
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
The following are the options which may be specified for linking. These are
|
||||
specified by providing an Object as the second parameter to [Autolinker.link()](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-static-method-link).
|
||||
These include:
|
||||
|
||||
- [newWindow](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-newWindow) : boolean<br />
|
||||
`true` to have the links should open in a new window when clicked, `false`
|
||||
otherwise. Defaults to `true`.
|
||||
|
||||
- [urls](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-urls) : boolean/Object<br />
|
||||
`true` to have URLs auto-linked, `false` to skip auto-linking of URLs. Defaults
|
||||
to `true`.
|
||||
|
||||
This option also accepts an Object form with 3 properties to allow for
|
||||
more customization of what exactly gets linked. All default to `true`:
|
||||
|
||||
- schemeMatches (boolean): `true` to match URLs found prefixed with a scheme,
|
||||
i.e. `http://google.com`, or `other+scheme://google.com`, `false` to
|
||||
prevent these types of matches.
|
||||
- tldMatches: `true` to match URLs with known top level domains (.com, .net,
|
||||
etc.) that are not prefixed with a scheme (i.e. 'http://'). Ex: `google.com`,
|
||||
`asdf.org/?page=1`, etc. Set to `false` to prevent these types of matches.
|
||||
- ipV4Matches (boolean): `true` to match IPv4 addresses. Ex: `192.168.0.1`.
|
||||
`false` to prevent these types of matches. Note that if the IP address had
|
||||
a prefixed scheme (such as 'http://'), and `schemeMatches` is true, it
|
||||
will still be linked.
|
||||
|
||||
Example usage: `urls: { schemeMatches: true, tldMatches: false, ipV4Matches: true }`
|
||||
|
||||
- [email](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-email) : boolean<br />
|
||||
`true` to have email addresses auto-linked, `false` to skip auto-linking of
|
||||
email addresses. Defaults to `true`.
|
||||
|
||||
- [phone](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-phone) : boolean<br />
|
||||
`true` to have phone numbers auto-linked, `false` to skip auto-linking of
|
||||
phone numbers. Defaults to `true`.
|
||||
|
||||
- [mention](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-mention) : string<br />
|
||||
A string for the service name to have mentions (@username) auto-linked to. Supported values at this time are 'twitter', 'soundcloud', 'instagram', 'tiktok', and 'youtube'. Pass `false` to skip auto-linking of mentions. Defaults to `false`.
|
||||
|
||||
- [hashtag](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-hashtag) : boolean/string<br />
|
||||
A string for the service name to have hashtags auto-linked to. Supported values at this time are 'twitter', 'facebook', 'instagram', 'tiktok', and 'youtube'. Pass `false` to skip auto-linking of hashtags. Defaults to `false`.
|
||||
|
||||
- [stripPrefix](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-stripPrefix) : boolean<br />
|
||||
`true` to have the `'http://'` (or `'https://'`) and/or the `'www.'`
|
||||
stripped from the beginning of displayed links, `false` otherwise.
|
||||
Defaults to `true`.
|
||||
|
||||
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`:
|
||||
|
||||
- scheme (boolean): `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:'`).
|
||||
- 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'`.
|
||||
|
||||
- [stripTrailingSlash](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-stripTrailingSlash) : boolean<br />
|
||||
`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`. Defaults to `true`.
|
||||
|
||||
- [truncate](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-truncate) : number/Object<br />
|
||||
A number for how many characters long URLs/emails/Twitter handles/Twitter
|
||||
hashtags should be truncated to inside the text of a link. If the match is
|
||||
over the number of characters, it will be truncated to this length by
|
||||
replacing the end of the string with a two period ellipsis ('..').
|
||||
|
||||
Example: a url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated
|
||||
to 25 characters may look like this: 'yahoo.com/some/long/pat..'
|
||||
|
||||
In the object form, both `length` and `location` may be specified to perform
|
||||
truncation. Available options for `location` are: 'end' (default), 'middle',
|
||||
or 'smart'. Example usage:
|
||||
|
||||
```javascript
|
||||
truncate: { length: 32, location: 'middle' }
|
||||
```
|
||||
|
||||
The 'smart' truncation option is for URLs where the algorithm attempts to
|
||||
strip out unnecessary parts of the URL (such as the 'www.', then URL scheme,
|
||||
hash, etc.) before trying to find a good point to insert the ellipsis if it is
|
||||
still too long. For details, see source code of:
|
||||
[TruncateSmart](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker.truncate.TruncateSmart)
|
||||
|
||||
- [className](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-className) : string<br />
|
||||
A CSS class name to add to the generated anchor tags. This class will be added
|
||||
to all links, as well as this class plus "url"/"email"/"phone"/"hashtag"/"mention"
|
||||
suffixes for styling url/email/phone/hashtag/mention links differently.
|
||||
|
||||
The name of the hashtag/mention service is also added as a CSS class for those
|
||||
types of matches.
|
||||
|
||||
For example, if this config is provided as "my-link", then:
|
||||
|
||||
- URL links will have the CSS classes: "my-link my-link-url"
|
||||
- Email links will have the CSS classes: "my-link my-link-email"
|
||||
- Phone links will have the CSS classes: "my-link my-link-phone"
|
||||
- Twitter mention links will have the CSS classes: "my-link my-link-mention my-link-twitter"
|
||||
- Instagram mention links will have the CSS classes: "my-link my-link-mention my-link-instagram"
|
||||
- Hashtag links will have the CSS classes: "my-link my-link-hashtag my-link-twitter"
|
||||
|
||||
- [decodePercentEncoding](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-decodePercentEncoding): boolean<br />
|
||||
`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é`.
|
||||
|
||||
Defaults to `true`.
|
||||
|
||||
- [replaceFn](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-replaceFn) : Function<br />
|
||||
A function to use to programmatically make replacements of matches in the
|
||||
input string, one at a time. See the section
|
||||
<a href="#custom-replacement-function">Custom Replacement Function</a> for
|
||||
more details.
|
||||
|
||||
- [sanitizeHtml](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-sanitizeHtml) : boolean<br />
|
||||
|
||||
`true` to HTML-encode the start and end brackets of existing HTML tags found
|
||||
in the input string. This will escape `<` and `>` characters to `<` and
|
||||
`>`, 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.
|
||||
|
||||
Defaults to `false`.
|
||||
|
||||
For example, if you wanted to disable links from opening in [new windows](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-newWindow), you could do:
|
||||
|
||||
```javascript
|
||||
const linkedText = Autolinker.link("Check out google.com", {
|
||||
newWindow: false
|
||||
});
|
||||
// Produces: "Check out <a href="http://google.com">google.com</a>"
|
||||
```
|
||||
|
||||
And if you wanted to truncate the length of URLs (while also not opening in a new window), you could do:
|
||||
|
||||
```javascript
|
||||
const linkedText = Autolinker.link("http://www.yahoo.com/some/long/path/to/a/file", {
|
||||
truncate: 25,
|
||||
newWindow: false
|
||||
});
|
||||
// Produces: "<a href="http://www.yahoo.com/some/long/path/to/a/file">yahoo.com/some/long/pat..</a>"
|
||||
```
|
||||
|
||||
## More Examples
|
||||
|
||||
One could update an entire DOM element that has unlinked text to auto-link them
|
||||
as such:
|
||||
|
||||
```javascript
|
||||
const myTextEl = document.getElementById('text');
|
||||
myTextEl.innerHTML = Autolinker.link(myTextEl.innerHTML);
|
||||
```
|
||||
|
||||
Using the same pre-configured [Autolinker](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker)
|
||||
instance in multiple locations of a codebase (usually by dependency injection):
|
||||
|
||||
```javascript
|
||||
const autolinker = new Autolinker({ newWindow: false, truncate: 25 });
|
||||
|
||||
//...
|
||||
|
||||
autolinker.link("Check out http://www.yahoo.com/some/long/path/to/a/file");
|
||||
// Produces: "Check out <a href="http://www.yahoo.com/some/long/path/to/a/file">yahoo.com/some/long/pat..</a>"
|
||||
|
||||
//...
|
||||
|
||||
autolinker.link( "Go to www.google.com" );
|
||||
// Produces: "Go to <a href="http://www.google.com">google.com</a>"
|
||||
|
||||
```
|
||||
|
||||
## Retrieving the List of Matches
|
||||
|
||||
If you're just interested in retrieving the list of [Matches](http://greg-jacobs.com/Autolinker.js/api/#!/api/Autolinker.match.Match) without producing a transformed string, you can use the [parse()](http://greg-jacobs.com/Autolinker.js/api/#!/api/Autolinker-static-method-parse) method.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
const matches = Autolinker.parse("Hello google.com, I am asdf@asdf.com", {
|
||||
urls: true,
|
||||
email: true
|
||||
});
|
||||
|
||||
console.log(matches.length); // 2
|
||||
console.log(matches[0].type); // 'url'
|
||||
console.log(matches[0].getUrl()); // 'google.com'
|
||||
console.log(matches[1].type); // 'email'
|
||||
console.log(matches[1].getEmail()); // 'asdf@asdf.com'
|
||||
```
|
||||
|
||||
|
||||
## Custom Replacement Function
|
||||
|
||||
A custom replacement function ([replaceFn](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker-cfg-replaceFn))
|
||||
may be provided to replace url/email/phone/mention/hashtag matches on an
|
||||
individual basis, based on the return from this function.
|
||||
|
||||
#### Full example, for purposes of documenting the API:
|
||||
|
||||
```javascript
|
||||
const input = "..."; // string with URLs, Email Addresses, Mentions (Twitter, Instagram), and Hashtags
|
||||
|
||||
const linkedText = Autolinker.link(input, {
|
||||
replaceFn : function(match) {
|
||||
console.log("href = ", match.getAnchorHref());
|
||||
console.log("text = ", match.getAnchorText());
|
||||
|
||||
switch(match.type) {
|
||||
case 'url':
|
||||
console.log("url: ", match.getUrl());
|
||||
|
||||
return true; // let Autolinker perform its normal anchor tag replacement
|
||||
|
||||
case 'email':
|
||||
const 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':
|
||||
console.log("Phone Number: ", match.getPhoneNumber());
|
||||
|
||||
return '<a href="http://newplace.to.link.phone.numbers.to/">' + match.getPhoneNumber() + '</a>';
|
||||
|
||||
case 'mention':
|
||||
console.log("Mention: ", match.getMention());
|
||||
console.log("Mention Service Name: ", match.getServiceName());
|
||||
|
||||
return '<a href="http://newplace.to.link.mention.handles.to/">' + match.getMention() + '</a>';
|
||||
|
||||
case 'hashtag':
|
||||
console.log("Hashtag: ", match.getHashtag());
|
||||
|
||||
return '<a href="http://newplace.to.link.hashtag.handles.to/">' + match.getHashtag() + '</a>';
|
||||
}
|
||||
}
|
||||
} );
|
||||
```
|
||||
|
||||
#### Modifying the default generated anchor tag
|
||||
|
||||
```javascript
|
||||
const input = "..."; // string with URLs, Email Addresses, Mentions (Twitter, Instagram), and Hashtags
|
||||
|
||||
const linkedText = Autolinker.link( input, {
|
||||
replaceFn : function( match ) {
|
||||
console.log("href = ", match.getAnchorHref());
|
||||
console.log("text = ", match.getAnchorText());
|
||||
|
||||
const tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance for an <a> tag
|
||||
tag.setAttr('rel', 'nofollow'); // adds a 'rel' attribute
|
||||
tag.addClass('external-link'); // adds a CSS class
|
||||
tag.setInnerHtml('Click here!'); // sets the inner html for the anchor tag
|
||||
|
||||
return tag;
|
||||
}
|
||||
} );
|
||||
```
|
||||
|
||||
|
||||
The `replaceFn` is provided one argument:
|
||||
|
||||
1. An [Autolinker.match.Match](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker.match.Match)
|
||||
object which details the match that is to be replaced.
|
||||
|
||||
|
||||
A replacement of the match is made based on the return value of the function.
|
||||
The following return values may be provided:
|
||||
|
||||
1. No return value (`undefined`), or `true` (boolean): Delegate back to
|
||||
Autolinker to replace the match as it normally would.
|
||||
2. `false` (boolean): Do not replace the current match at all - leave as-is.
|
||||
3. Any string: If a string is returned from the function, the string will be used
|
||||
directly as the replacement HTML for the match.
|
||||
4. An [Autolinker.HtmlTag](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker.HtmlTag)
|
||||
instance, which can be used to build/modify an HTML tag before writing out its
|
||||
HTML text.
|
||||
|
||||
|
||||
## Full API Docs
|
||||
|
||||
The full API docs for Autolinker may be referenced at:
|
||||
[http://gregjacobs.github.io/Autolinker.js/api/](http://gregjacobs.github.io/Autolinker.js/api/#!/api/Autolinker)
|
||||
|
||||
## Live Example
|
||||
|
||||
[http://gregjacobs.github.io/Autolinker.js/examples/](http://gregjacobs.github.io/Autolinker.js/examples/)
|
||||
|
||||
|
||||
## Upgrading from v3.x -> v4.x (Breaking Changes)
|
||||
|
||||
1. Internet Explorer support has been removed since its official demise in June
|
||||
2022.
|
||||
1. The `urls.wwwMatches` config has been removed. A `www.` prefix is now treated
|
||||
like any other subdomain of a top level domain (TLD) match (such as
|
||||
'subdomain.google.com').
|
||||
1. `Match.getType()` should be replaced with `Match.type`. This allows for
|
||||
TypeScript type narrowing of `Match` objects returned by the `parse()`
|
||||
method or inside the `replaceFn`.
|
||||
1. The `Matcher` classes have been removed in favor of a single finite state
|
||||
machine parser, greatly improving the performance of Autolinker (3x
|
||||
performance improvement over the 3.x branch), but removing some of the
|
||||
customizability of the old regular expressions. Will address this
|
||||
customizability in a future release.
|
||||
1. `Autolinker.AnchorTagBuilder`, `Autolinker.HtmlTag`, and `Autolinker.match.*`
|
||||
references have been removed. These shouldn't be needed as public APIs, but
|
||||
please raise a GitHub issue if these are for some reason needed.
|
||||
|
||||
## Upgrading from v2.x -> v3.x (Breaking Changes)
|
||||
|
||||
1. If you are still on v1.x, first follow the instructions in the
|
||||
[Upgrading from v1.x -> v2.x](#upgrading-from-v1x---v2x-breaking-changes)
|
||||
section below.
|
||||
2. The `HtmlParser` class has been removed in favor of an internal `parseHtml()`
|
||||
function which replaces the old regexp-based implementation with a state
|
||||
machine parser that is guaranteed to run in linear time. If you were using
|
||||
the `HtmlParser` class directly, I recommend switching to [htmlparser2](https://github.com/fb55/htmlparser2), which implements the HTML semantics
|
||||
better. The internal `parseHtml()` function that Autolinker now uses is
|
||||
fairly geared towards Autolinker's purposes, and may not be useful in a
|
||||
general HTML parsing sense.
|
||||
|
||||
## Upgrading from v1.x -> v2.x (Breaking Changes)
|
||||
|
||||
1. If you are still on v0.x, first follow the instructions in the
|
||||
[Upgrading from v0.x -> v1.x](#upgrading-from-v0x---v1x-breaking-changes)
|
||||
section below.
|
||||
2. The codebase has been converted to TypeScript, and uses ES6 exports. You can
|
||||
now use the `import` statement to pull in the `Autolinker` class and related
|
||||
entities such as `Match`:
|
||||
|
||||
```ts
|
||||
// ES6/TypeScript/Webpack
|
||||
import Autolinker, { Match } from 'autolinker';
|
||||
```
|
||||
|
||||
The `require()` interface is still supported as well for Node.js:
|
||||
|
||||
```ts
|
||||
// Node.js
|
||||
const Autolinker = require('autolinker');
|
||||
```
|
||||
|
||||
3. You will no longer need the `@types/autolinker` package as this package now
|
||||
exports its own types
|
||||
4. You will no longer be able to override the regular expressions in the
|
||||
`Matcher` classes by assigning to the prototype (for instance, something like
|
||||
`PhoneMatcher.prototype.regex = ...`). This is due to how TypeScript creates
|
||||
properties for class instances in the constructor rather than on prototypes.
|
||||
|
||||
The idea of providing your own regular expression for these classes is a
|
||||
brittle notion anyway, as the `Matcher` classes rely on capturing groups in
|
||||
the RegExp being in the right place, or even multiple capturing groups for
|
||||
the same piece of information to support a different format. These capturing
|
||||
groups and associated code are subject to change as the regular expression
|
||||
needs to be updated, and will not involve a major version release of
|
||||
Autolinker.
|
||||
|
||||
In the future you will be able to override the default `Matcher` classes
|
||||
entirely to provide your own implementation, but please raise an issue (or
|
||||
+1 an issue) if you think the library should support a currently-unsupported
|
||||
format.
|
||||
|
||||
## Upgrading from v0.x -> v1.x (Breaking Changes)
|
||||
|
||||
1. `twitter` option removed, replaced with `mention` (which accepts 'twitter',
|
||||
'instagram' and 'soundcloud' values)
|
||||
2. Matching mentions (previously the `twitter` option) now defaults to
|
||||
being turned off. Previously, Twitter handle matching was on by
|
||||
default.
|
||||
3. `replaceFn` option now called with just one argument: the `Match`
|
||||
object (previously was called with two arguments: `autolinker` and
|
||||
`match`)
|
||||
4. (Used inside the `replaceFn`) `TwitterMatch` replaced with
|
||||
`MentionMatch`, and `MentionMatch.getType()` now returns `'mention'`
|
||||
instead of `'twitter'`
|
||||
5. (Used inside the `replaceFn`) `TwitterMatch.getTwitterHandle()` ->
|
||||
`MentionMatch.getMention()`
|
||||
|
||||
|
||||
## Developing / Contributing
|
||||
|
||||
Pull requests definitely welcome. To setup the project, make sure you have
|
||||
[Node.js](https://nodejs.org) installed. Then open up a command prompt and type
|
||||
the following:
|
||||
|
||||
```sh
|
||||
npm install -g pnpm@latest # this project uses pnpm workspaces, and pnpm is a faster npm anyway :)
|
||||
|
||||
cd Autolinker.js # where you cloned the project
|
||||
pnpm install
|
||||
```
|
||||
|
||||
To run the tests:
|
||||
|
||||
```sh
|
||||
pnpm run test
|
||||
```
|
||||
|
||||
- Make sure to add tests to check your new functionality/bugfix
|
||||
- Run the `pnpm run test` command to test
|
||||
|
||||
#### Running the Live Example Page Locally
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
pnpm run devserver
|
||||
```
|
||||
|
||||
Then open your browser to: http://localhost:8080/docs/examples/index.html
|
||||
|
||||
You should be able to make a change to source files, and refresh the page to see
|
||||
the changes.
|
||||
|
||||
#### Running the benchmarks <a name="benchmarks"></a>
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
pnpm run benchmarks
|
||||
```
|
||||
|
||||
> Note: See the [Benchmarks Table](#benchmarks-table) above for current results.
|
||||
|
||||
Couple points on the benchmarks:
|
||||
* These benchmarks attempt to set up all libraries by configuring comparable features to Autolinker (e.g.: linking emails, hashtags, mentions, etc.) to try to get an apples-to-apples comparison.
|
||||
* While developing, recommend running the benchmarks a few times both before and after making any changes if developing.
|
||||
* See the [benchmarks](./benchmarks) folder and [benchmarks/input-text.ts](./benchmarks/input-text.ts) for how the benchmarks are set up and what input they are given.
|
||||
|
||||
#### Documentation Generator Notes
|
||||
|
||||
This project uses [JSDuck](https://github.com/senchalabs/jsduck) for its
|
||||
documentation generation, which produces the page at [http://gregjacobs.github.io/Autolinker.js](http://gregjacobs.github.io/Autolinker.js).
|
||||
|
||||
Unfortunately, JSDuck is a very old project that is no longer maintained. As
|
||||
such, it doesn't support TypeScript or anything from ES6 (the `class` keyword,
|
||||
arrow functions, etc). However, I have yet to find a better documentation
|
||||
generator that creates such a useful API site. (Suggestions for a new one are
|
||||
welcome though - please raise an issue.)
|
||||
|
||||
Since ES6 is not supported, we must generate the documentation from the ES5
|
||||
output. As such, a few precautions must be taken care of to make sure the
|
||||
documentation comes out right:
|
||||
|
||||
1. `@cfg` documentation tags must exist above a class property that has a
|
||||
default value, or else it won't end up in the ES5 output. For example:
|
||||
|
||||
```ts
|
||||
// Will correctly end up in the ES5 output
|
||||
|
||||
/**
|
||||
* @cfg {String} title
|
||||
*/
|
||||
readonly title: string = '';
|
||||
|
||||
|
||||
|
||||
// Will *not* end up in ES5 output, and thus, won't end up in the generated
|
||||
// documentation
|
||||
|
||||
/**
|
||||
* @cfg {String} title
|
||||
*/
|
||||
readonly title: string;
|
||||
```
|
||||
2. The `@constructor` tag must be replaced with `@method constructor`
|
||||
|
||||
To build the documentation, you will need [Ruby](https://www.ruby-lang.org)
|
||||
installed (note: Ruby comes pre-installed on MacOS), with the
|
||||
[JSDuck](https://github.com/senchalabs/jsduck) gem.
|
||||
|
||||
See https://github.com/senchalabs/jsduck#getting-it for installation
|
||||
instructions on Windows/Mac/Linux.
|
||||
|
||||
## Changelog
|
||||
|
||||
See [Releases](https://github.com/gregjacobs/Autolinker.js/releases)
|
||||
|
||||
<!-- Reference Links -->
|
||||
|
||||
[1]: https://github.com/alexcorvi/anchorme.js "anchorme.js"
|
||||
[2]: https://linkify.js.org/docs/linkify-html.html "linkify-html"
|
||||
[3]: https://github.com/markdown-it/linkify-it "linkify-it"
|
||||
+4885
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+11
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+118
@@ -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 (<a>) 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 (<a>)
|
||||
* 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
@@ -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 (<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>
|
||||
*/
|
||||
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 (<a>) 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 (<a>)
|
||||
* 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
|
||||
+1
File diff suppressed because one or more lines are too long
+608
@@ -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 (<a>) 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 <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
|
||||
* // Produces: "Go to <a href="http://google.com">google.com</a>"
|
||||
*
|
||||
* @static
|
||||
* @param {String} textOrHtml The HTML or text to find matches within (depending
|
||||
* on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #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 `<` and
|
||||
* `>`, 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
|
||||
* <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* This method finds the text around any HTML elements in the input
|
||||
* `textOrHtml`, which will be the text that is processed. Any original HTML
|
||||
* elements will be left as-is, as well as the text that is already wrapped
|
||||
* in anchor (<a>) tags.
|
||||
*
|
||||
* @param {String} textOrHtml The HTML or text to autolink matches within
|
||||
* (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #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
@@ -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 (<a>) 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 `<` and
|
||||
* `>`, 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 <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
|
||||
* // Produces: "Go to <a href="http://google.com">google.com</a>"
|
||||
*
|
||||
* @static
|
||||
* @param {String} textOrHtml The HTML or text to find matches within (depending
|
||||
* on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #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 ' ' (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 & entities
|
||||
var htmlCharacterEntitiesRegex = /( | |<|<|>|>|"|"|')/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
|
||||
* <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* This method finds the text around any HTML elements in the input
|
||||
* `textOrHtml`, which will be the text that is processed. Any original HTML
|
||||
* elements will be left as-is, as well as the text that is already wrapped
|
||||
* in anchor (<a>) tags.
|
||||
*
|
||||
* @param {String} textOrHtml The HTML or text to autolink matches within
|
||||
* (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #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, '<').replace(/>/g, '>');
|
||||
}
|
||||
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
|
||||
+1
File diff suppressed because one or more lines are too long
+132
File diff suppressed because one or more lines are too long
+170
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+30
@@ -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
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=char.js.map
|
||||
+1
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+89
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+8
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+60
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+7
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=match.js.map
|
||||
+1
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+88
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(/&/g, '&'); // any &'s in the URL should be converted back to '&' if they were displayed as & 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 '"'; // %22: '"' char
|
||||
if (match === '%26')
|
||||
return '&'; // %26: '&' char
|
||||
if (match === '%27')
|
||||
return '''; // %27: "'" char
|
||||
if (match === '%3C' || match === '%3c')
|
||||
return '<'; // %3C: '<' char
|
||||
/*if (match === '%3E' || match === '%3e')*/ return '>'; // %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
|
||||
+1
File diff suppressed because one or more lines are too long
+32
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
export * from './parse-matches';
|
||||
+5
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
export declare const tldRegex: RegExp;
|
||||
+7
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+16
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
+1204
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+21
@@ -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
@@ -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
|
||||
+1
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+9
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 = '…';
|
||||
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
|
||||
+1
@@ -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 = '…';\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
@@ -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
@@ -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 = '…';
|
||||
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
|
||||
+1
File diff suppressed because one or more lines are too long
+35
@@ -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
@@ -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 = '…';
|
||||
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
@@ -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 = '…';\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
@@ -0,0 +1 @@
|
||||
export declare const version = "4.1.5";
|
||||
+7
@@ -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
@@ -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
@@ -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 (<a>) 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 (<a>)
|
||||
* 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
@@ -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 (<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>
|
||||
*/
|
||||
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 (<a>) 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 (<a>)
|
||||
* 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
|
||||
+1
File diff suppressed because one or more lines are too long
+608
@@ -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 (<a>) 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 <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
|
||||
* // Produces: "Go to <a href="http://google.com">google.com</a>"
|
||||
*
|
||||
* @static
|
||||
* @param {String} textOrHtml The HTML or text to find matches within (depending
|
||||
* on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #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 `<` and
|
||||
* `>`, 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
|
||||
* <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* This method finds the text around any HTML elements in the input
|
||||
* `textOrHtml`, which will be the text that is processed. Any original HTML
|
||||
* elements will be left as-is, as well as the text that is already wrapped
|
||||
* in anchor (<a>) tags.
|
||||
*
|
||||
* @param {String} textOrHtml The HTML or text to autolink matches within
|
||||
* (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #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
@@ -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 (<a>) 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 `<` and
|
||||
* `>`, 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 <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
|
||||
* // Produces: "Go to <a href="http://google.com">google.com</a>"
|
||||
*
|
||||
* @static
|
||||
* @param {String} textOrHtml The HTML or text to find matches within (depending
|
||||
* on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #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 ' ' (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 & entities
|
||||
var htmlCharacterEntitiesRegex = /( | |<|<|>|>|"|"|')/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
|
||||
* <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
||||
*
|
||||
* This method finds the text around any HTML elements in the input
|
||||
* `textOrHtml`, which will be the text that is processed. Any original HTML
|
||||
* elements will be left as-is, as well as the text that is already wrapped
|
||||
* in anchor (<a>) tags.
|
||||
*
|
||||
* @param {String} textOrHtml The HTML or text to autolink matches within
|
||||
* (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #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, '<').replace(/>/g, '>');
|
||||
}
|
||||
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
|
||||
+1
File diff suppressed because one or more lines are too long
+132
File diff suppressed because one or more lines are too long
+157
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+30
@@ -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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user