Created
April 13, 2015 05:36
-
-
Save Buildstarted/c45d64215b43b094df46 to your computer and use it in GitHub Desktop.
Directly ported content providers from http://jabbr.net to typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ContentProviders { | |
enum DeferredState { | |
Done, | |
Failed, | |
Pending | |
} | |
export class Deferred { | |
private _done: Function[]; | |
private _fail: Function[]; | |
private _always: Function[]; | |
private _state: DeferredState; | |
private _arguments: any; | |
public Result: any[]; | |
constructor(); | |
constructor(init: Function); | |
constructor(init?: Function) { | |
this._done = new Array<Function>(); | |
this._fail = new Array<Function>(); | |
this._state = DeferredState.Pending; | |
if (init) { | |
init.apply(this, [this]); | |
} | |
} | |
private Execute(actions: Function[], args: any): void { | |
var i = actions.length; | |
args = Array.prototype.slice.call(args); | |
this._arguments = args; | |
while (i--) { | |
actions[i].apply(null, args); | |
} | |
} | |
public Resolve(...args: any[]): void { | |
this._state = DeferredState.Done; | |
this.Result = args; | |
this.Execute(this._done, arguments); | |
} | |
public Reject(): void { | |
this._state = DeferredState.Failed; | |
this.Execute(this._fail, arguments); | |
} | |
public Done(callback: Function): Deferred { | |
if (this._state === DeferredState.Done) { | |
callback.apply(null, this._arguments); | |
} else { | |
this._done.push(callback); | |
} | |
return this; | |
} | |
public Fail(callback: Function): Deferred { | |
if (this._state === DeferredState.Failed) { | |
callback.apply(null, this._arguments); | |
} else { | |
this._fail.push(callback); | |
} | |
return this; | |
} | |
public Always(callback: Function): Deferred { | |
if (this._state !== DeferredState.Pending) { | |
callback.apply(null, this._arguments); | |
} else { | |
this._fail.push(callback); | |
this._done.push(callback); | |
} | |
return this; | |
} | |
public static When(callbacks: Deferred[]): Deferred { | |
var callbackCount = callbacks.length; | |
if (callbackCount === 1) { | |
return callbacks[0]; | |
} | |
var deferred = new Deferred(); | |
var i: number = callbackCount; | |
while (i--) { | |
callbacks[i].Done(() => { | |
callbackCount -= 1; | |
if (callbackCount == 0) { | |
deferred.Resolve(); | |
} | |
}); | |
} | |
return deferred; | |
} | |
} | |
export interface IContentProvider { | |
GetContent(requestUri: string): Deferred; | |
IsValidContent(uri: string): boolean; | |
} | |
export class ContentProviderResult { | |
Title: string; | |
Content: string; | |
} | |
export class Uri { | |
private _anchor: HTMLAnchorElement; | |
constructor(url: string) { | |
this._anchor = document.createElement("a"); | |
this._anchor.href = url; | |
} | |
public get Protocol(): string { return this._anchor.protocol; } | |
public get Hostname(): string { return this._anchor.hostname; } | |
public get Port(): string { return this._anchor.port; } | |
public get Pathname(): string { return this._anchor.pathname; } | |
public get Search(): string { return this._anchor.search; } | |
public get Hash(): string { return this._anchor.hash; } | |
public get Host(): string { return this._anchor.host; } | |
} | |
export class CollapsibleContentProvider implements IContentProvider { | |
private ContentFormat: string = "<div class=\"collapsible_content\"><h3 class=\"collapsible_title\">{0}</h3><div class=\"collapsible_box\">{1}</div></div>"; | |
private HtmlEscape(str) { | |
return String(str) | |
.replace(/&/g, '&') | |
.replace(/"/g, '"') | |
.replace(/'/g, ''') | |
.replace(/</g, '<') | |
.replace(/>/g, '>'); | |
} | |
public get ParameterExtractionRegex(): RegExp { | |
return new RegExp("(\d+)"); | |
} | |
public GetContent(requestUri: string): Deferred { | |
var twitter = new TwitterLinkExpander(); | |
var finalUrl = twitter.GetContent(requestUri); | |
var d = new Deferred(); | |
finalUrl.Done((url) => { | |
var result = this.GetCollapsibleContent(url); | |
result.Done((result: ContentProviderResult) => { | |
if (this.IsCollapsible && result != null) { | |
var contentTitle: string = this.HtmlEscape(result.Title); | |
//string format this later | |
result.Content = result.Content; | |
} | |
d.Resolve(result); | |
}); | |
}); | |
return d; | |
} | |
public GetCollapsibleContent(requestUri: string): Deferred { | |
throw "Must override GetCollapsibleContent"; | |
} | |
public ExtractParameters(responseUri: string): Array<string> { | |
//get final url | |
var list = responseUri.match(this.ParameterExtractionRegex); | |
var result: Array<string> = []; | |
if (list != null && list.length > 0) { | |
for (var i = 1; i < list.length; i++) { | |
if (list[i] != null) { | |
result.push(list[i]); | |
} | |
} | |
} | |
return result; | |
} | |
public IsValidContent(uri: string): boolean { | |
return false; | |
} | |
public get IsCollapsible(): boolean { | |
return true; | |
} | |
} | |
export class EmbedContentProvider extends CollapsibleContentProvider { | |
public get Domains(): Array<string> { | |
return null; | |
} | |
public get MediaFormatString(): string { | |
return null; | |
} | |
public GetCollapsibleContent(requestUri: string): Deferred { | |
var d = new Deferred(); | |
var args = this.ExtractParameters(requestUri); | |
if (args == null || !args.length) { | |
d.Resolve(null); | |
} | |
var result = new ContentProviderResult(); | |
result.Title = requestUri; | |
result.Content = ContentProviders.stringFormat(this.MediaFormatString, args); | |
d.Resolve(result); | |
return d; | |
} | |
public IsValidContent(url: string): boolean { | |
for (var i in this.Domains) { | |
if (url.lastIndexOf(this.Domains[i], 0) === 0) { | |
return true; | |
} | |
} | |
return false; | |
} | |
} | |
export class ImgurContentProvider extends CollapsibleContentProvider { | |
public GetCollapsibleContent(requestUri: string): Deferred { | |
var d = new Deferred(); | |
var id = requestUri.split("/"); | |
var result = new ContentProviderResult(); | |
result.Content = ContentProviders.stringFormat("<a rel=\"nofollow external\" target=\"_blank\" href=\"{1}\"><img src=\"https://i.imgur.com/{0}.png\" style=\"max-width: 300px; max-height: 300px;\" /></a>", id[id.length - 1], requestUri); | |
result.Title = requestUri; | |
d.Resolve(result) | |
return d; | |
} | |
private IsMatch(source: string, regex: RegExp): boolean { | |
var match = source.match(regex); | |
return match != null && match.length > 0; | |
} | |
public IsValidContent(url: string): boolean { | |
var uri = new Uri(url); | |
var isProperDomain = this.IsMatch(uri.Host, /imgur.com/i) || | |
this.IsMatch(uri.Host, /www.imgur.com/i) || | |
this.IsMatch(uri.Host, /i.imgur.com/i); | |
var result = isProperDomain && | |
//!(uri.Pathname.indexOf("/a/") == 0) && | |
!(uri.Pathname == "/") && | |
!(uri.Pathname.indexOf(".") != -1); | |
return result; | |
} | |
} | |
export class TwitterLinkExpander extends CollapsibleContentProvider { | |
public GetContent(requestUri: string): Deferred { | |
var d = new Deferred(); | |
if (this.IsValidContent(requestUri)) { | |
var http = new XMLHttpRequest(); | |
http.open('GET', '/resolve?url=' + requestUri, true); | |
http.onreadystatechange = function (e) { | |
if (http.readyState == 4) { | |
var result = JSON.parse(http.responseText); | |
d.Resolve(result.result); | |
} | |
}; | |
http.send(); | |
} else { | |
d.Resolve(requestUri); | |
} | |
return d; | |
} | |
public IsValidContent(url: string): boolean { | |
var uri = new Uri(url); | |
var matches = uri.Host.match(/t\.co/i); | |
return matches != null && matches.length > 0; | |
} | |
} | |
export class AudioContentProvider implements IContentProvider { | |
public IsValidContent(url: string): boolean { | |
return | |
url.indexOf(".mp3", url.length - 4) !== -1 || | |
url.indexOf(".wav", url.length - 4) !== -1 || | |
url.indexOf(".ogg", url.length - 4) !== -1; | |
} | |
public GetContent(requestUrl: string): Deferred { | |
var d = new Deferred(); | |
var result = new ContentProviderResult(); | |
result.Title = requestUrl; | |
result.Content = ContentProviders.stringFormat("<audio controls=\"controls\" src=\"{1}\">{0}</audio>", "Your browser does not support the audio tag.", requestUrl); | |
d.Resolve(result); | |
return d; | |
} | |
} | |
export class YoutubeContentProvider extends EmbedContentProvider { | |
public get Domains(): Array<string> { | |
return [ | |
"http://www.youtube.com", | |
"https://www.youtube.com", | |
"http://youtu.be", | |
"https://youtu.be", | |
]; | |
} | |
public get MediaFormatString(): string { | |
return "<object width=\"425\" height=\"344\"><param name=\"WMode\" value=\"transparent\"></param><param name=\"movie\" value=\"https://www.youtube.com/v/{0}fs=1\"></param><param name=\"allowFullScreen\" value=\"true\"></param><param name=\"allowScriptAccess\" value=\"always\"></param><embed src=\"https://www.youtube.com/v/{0}?fs=1\" wmode=\"transparent\" type=\"application/x-shockwave-flash\" allowfullscreen=\"true\" allowscriptaccess=\"always\" width=\"425\" height=\"344B\"></embed></object>"; | |
} | |
public ExtractParameters(requestUrl: string): Array<string> { | |
var matches = requestUrl.match(/(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?/); | |
if (matches == null || matches.length < 2 || !matches[1]) { | |
return null; | |
} | |
var videoId = matches[1]; | |
return [videoId]; | |
} | |
} | |
export class VimeoContentProvider extends EmbedContentProvider { | |
public get Domains(): Array<string> { | |
return [ | |
"http://vimeo.com", | |
"http://www.vimeo.com" | |
]; | |
} | |
public get MediaFormatString(): string { | |
return "<iframe src=\"//player.vimeo.com/video/{0}?title=0&byline=0&portrait=0&color=c9ff23\" width=\"500\" height=\"271\" frameborder=\"0\" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>"; | |
} | |
public get ParameterExtractionRegex(): RegExp { | |
return new RegExp("(\d+)"); | |
} | |
} | |
export class TwitPicContentProvider extends CollapsibleContentProvider { | |
private _twitPicUrlRegex = /^http:\/\/(www\.)?twitpic\.com\/(\w+)/i; | |
public GetCollapsibleContent(requestUrl: string): Deferred { | |
return new Deferred((d) => { | |
var result = new ContentProviderResult(); | |
var match = requestUrl.match(this._twitPicUrlRegex); | |
if (match != null) { | |
var id = match[1]; | |
result.Content = stringFormat("<a href=\"http://twitpic.com/{0}\"> <img src=\"http://twitpic.com/show/large/{0}\" style=\"max-width: 300px; max-height: 300px;\" /></a>", id); | |
result.Title = requestUrl; | |
d.Resolve(result); | |
} else { | |
d.Resolve(null); | |
} | |
}); | |
} | |
public IsValidContent(url: string): boolean { | |
var match = url.match(this._twitPicUrlRegex); | |
return match != null && match.length !== 0; | |
} | |
} | |
export class TweetContentProvider extends CollapsibleContentProvider { | |
private _tweetRegex = new RegExp(".*/(?:statuses|status)/(\d+)"); | |
private static _tweetScript = stringFormat("<div class=\"tweet_{{0}}\"><script src=\"{0}\"></script></div>", htmlEscape("https://api.twitter.com/1/statuses/oembed.json?id={0}&callback=addTweet")); | |
public GetCollapsibleContent(requestUrl: string): Deferred { | |
return new Deferred((d: Deferred) => { | |
var match = requestUrl.match(this._tweetRegex); | |
if (match != null && match.length !== 0) { | |
var status = match[1]; | |
if (status != null) { | |
var result = new ContentProviderResult(); | |
result.Content = stringFormat(TweetContentProvider._tweetScript, status); | |
result.Title = requestUrl; | |
d.Resolve(result); | |
return; | |
} | |
} | |
d.Resolve(null); | |
}); | |
} | |
public IsValidContent(url: string): boolean { | |
return | |
url.lastIndexOf("http://twitter.com/", 0) === 0 || | |
url.lastIndexOf("https://twitter.com/", 0) === 0 || | |
url.lastIndexOf("http://www.twitter.com/", 0) === 0 || | |
url.lastIndexOf("https://www.twitter.com/", 0) === 0; | |
} | |
} | |
export class SpotifyContentProvider extends CollapsibleContentProvider { | |
public GetCollapsibleContent(requestUrl: string): Deferred { | |
return new Deferred((d: Deferred) => { | |
var spotifyUrl = SpotifyContentProvider.ExtractSpotifyUri(requestUrl); | |
var result = new ContentProviderResult(); | |
result.Content = stringFormat("<iframe src=\"https://embed.spotify.com/?uri=spotify:{0}\" width=\"300\" height=\"380\" frameborder=\"0\" allowtransparency=\"true\"></iframe>", spotifyUrl); | |
result.Title = stringFormat("spotify:track:{0}", spotifyUrl); | |
d.Resolve(result); | |
}); | |
} | |
private static ExtractSpotifyUri(url: string) { | |
return url.substring(1).replace("/", ":"); | |
} | |
public IsValidContent(url: string): boolean { | |
return url.lastIndexOf("http://open.spotify.com/", 0) === 0; | |
} | |
} | |
export class PastieContentProvider extends EmbedContentProvider { | |
public get Domains(): Array<string> { | |
return [ | |
"http://vimeo.com", | |
"http://www.vimeo.com" | |
]; | |
} | |
public get ParameterExtractionRegex(): RegExp { | |
return new RegExp("(\d+)"); | |
} | |
public get IsCollapsible(): boolean { | |
return false; | |
} | |
public get MediaFormatString(): string { | |
var scriptTagId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | |
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | |
return v.toString(16); | |
}); | |
return stringFormat(this.ScriptTagFormat, scriptTagId); | |
} | |
private get ScriptTagFormat(): string { | |
return "<div id='{0}'></div><script type='text/javascript'>captureDocumentWrite('http://pastie.org/{{0}}.js', 'http://pastie.org/{{0}}', $('#{0}'));</script>"; | |
} | |
} | |
export class MixcloudContentProvider extends EmbedContentProvider { | |
public get Domains(): Array<string> { | |
return [ | |
"http://www.mixcloud.com/" | |
]; | |
} | |
public get MediaFormatString(): string { | |
return "<object width=\"100%\" height=\"120\"><param name=\"movie\" value=\"//www.mixcloud.com/media/swf/player/mixcloudLoader.swf?feed={0}&embed_uuid=06c5d381-1643-407d-80e7-2812382408e9&stylecolor=1e2671&embed_type=widget_standard\"></param><param name=\"allowFullScreen\" value=\"true\"></param><param name=\"wmode\" value=\"opaque\"></param><param name=\"allowscriptaccess\" value=\"always\"></param><embed src=\"//www.mixcloud.com/media/swf/player/mixcloudLoader.swf?feed={0}&embed_uuid=06c5d381-1643-407d-80e7-2812382408e9&stylecolor=1e2671&embed_type=widget_standard\" type=\"application/x-shockwave-flash\" wmode=\"opaque\" allowscriptaccess=\"always\" allowfullscreen=\"true\" width=\"100%\" height=\"120\"></embed></object>"; | |
} | |
public get ParameterExtractionRegex(): RegExp { | |
return new RegExp("(\d+)"); | |
} | |
public ExtractParameters(url: string): Array<string> { | |
return [url]; | |
} | |
public get IsCollapsible(): boolean { | |
return true; | |
} | |
} | |
export class JoinMeContentProvider extends CollapsibleContentProvider { | |
private static _iframedMeetingFormat = "<iframe src=\"{0}\" sandbox=\"allow-same-origin allow-scripts allow-popups allow-forms\" width=\"700\" height=\"400\"></iframe>"; | |
public GetCollapsibleContent(requestUrl: string): Deferred { | |
return new Deferred((d: Deferred) => { | |
var result = new ContentProviderResult(); | |
result.Title = requestUrl; | |
result.Content = stringFormat(JoinMeContentProvider._iframedMeetingFormat, requestUrl); | |
d.Resolve(result); | |
}); | |
} | |
public IsValidContent(url: string): boolean { | |
return url.lastIndexOf("https://join.me/", 0) === 0; | |
} | |
} | |
export class CollegeHumorContentProvider extends EmbedContentProvider { | |
public get Domains(): Array<string> { | |
return [ | |
"http://www.collegehumor.com" | |
]; | |
} | |
public get MediaFormatString(): string { | |
return "<object type=\"application/x-shockwave-flash\" data=\"http://www.collegehumor.com/moogaloop/moogaloop.swf?clip_id={0}&use_node_id=true&fullscreen=1\" width=\"600\" height=\"338\"><param name=\"allowfullscreen\" value=\"true\"/><param name=\"wmode\" value=\"transparent\"/><param name=\"allowScriptAccess\" value=\"always\"/><param name=\"movie\" quality=\"best\" value=\"http://www.collegehumor.com/moogaloop/moogaloop.swf?clip_id={0}&use_node_id=true&fullscreen=1\"/><embed src=\"http://www.collegehumor.com/moogaloop/moogaloop.swf?clip_id={0}&use_node_id=true&fullscreen=1\" type=\"application/x-shockwave-flash\" wmode=\"transparent\" width=\"600\" height=\"338\" allowScriptAccess=\"always\"></embed></object>"; | |
} | |
public get ParameterExtractionRegex(): RegExp { | |
return new RegExp(".*video/(\d+).*"); | |
} | |
public ExtractParameters(url: string): Array<string> { | |
return [url]; | |
} | |
public get IsCollapsible(): boolean { | |
return true; | |
} | |
} | |
export class GistContentProvider extends EmbedContentProvider { | |
public get Domains(): Array<string> { | |
return [ | |
"https://gist.github.com" | |
]; | |
} | |
public get MediaFormatString(): string { | |
var scriptTagId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | |
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | |
return v.toString(16); | |
}); | |
var before = stringFormat("<div id='{0}'></div><script type='text/javascript'>captureDocumentWrite('https://gist.github.com/{{0}}.js', 'https://gist.github.com/{{0}}', $('#{0}'));</script>", scriptTagId); | |
return before; | |
} | |
public get ParameterExtractionRegex(): RegExp { | |
return new RegExp("(\\w+$)"); | |
} | |
public get IsCollapsible(): boolean { | |
return false; | |
} | |
} | |
export class ImageContentProvider extends CollapsibleContentProvider { | |
public GetCollapsibleContent(requestUrl: string): Deferred { | |
return new Deferred((d: Deferred) => { | |
var result = new ContentProviderResult(); | |
result.Title = requestUrl; | |
result.Content = stringFormat("<a rel=\"nofollow external\" target=\"_blank\" href=\"{0}\"><img src=\"{0}\" style=\"max-width: 300px; max-height: 300px;\" /></a>", requestUrl); | |
d.Resolve(result); | |
}); | |
} | |
public IsValidContent(url: string): boolean { | |
url = url.toLowerCase(); | |
return url.indexOf(".png", url.length - 4) !== -1 || | |
url.indexOf(".bmp", url.length - 4) !== -1 || | |
url.indexOf(".jpg", url.length - 4) !== -1 || | |
url.indexOf(".jpeg", url.length - 5) !== -1 || | |
url.indexOf(".gif", url.length - 4) !== -1; | |
} | |
} | |
export class ResourceProcessor { | |
private _contentProviders: Array<IContentProvider>; | |
constructor() { | |
this._contentProviders = new Array<IContentProvider>(); | |
this._contentProviders.push(new ImgurContentProvider()); | |
this._contentProviders.push(new AudioContentProvider()); | |
this._contentProviders.push(new YoutubeContentProvider()); | |
this._contentProviders.push(new VimeoContentProvider()); | |
this._contentProviders.push(new TwitPicContentProvider()); | |
this._contentProviders.push(new TweetContentProvider()); | |
this._contentProviders.push(new SpotifyContentProvider()); | |
this._contentProviders.push(new PastieContentProvider()); | |
this._contentProviders.push(new MixcloudContentProvider()); | |
this._contentProviders.push(new JoinMeContentProvider()); | |
this._contentProviders.push(new CollegeHumorContentProvider()); | |
this._contentProviders.push(new GistContentProvider()); | |
this._contentProviders.push(new ImageContentProvider()); | |
} | |
public ExtractResource(url: string): Deferred { | |
var d = new Deferred(); | |
var twitter = new TwitterLinkExpander(); | |
var handler = twitter.GetContent(url); | |
handler.Done((uri) => { | |
var validProviders: Array<IContentProvider> = []; | |
for (var i in this._contentProviders) { | |
if (this._contentProviders[i].IsValidContent(uri)) { | |
validProviders.push(this._contentProviders[i]); | |
} | |
} | |
var tasks = []; | |
var completed: boolean = false; | |
var count = validProviders.length; | |
for (var i in validProviders) { | |
var task = validProviders[i].GetContent(uri); | |
tasks.push(task); | |
} | |
if (tasks.length > 0) { | |
Deferred.When(tasks) | |
.Done(function () { | |
for (var i in tasks) { | |
var task = tasks[i]; | |
if (task.Result != null && task.Result[0] != null) { | |
d.Resolve(task.Result[0]); | |
return; | |
} | |
} | |
d.Resolve(null); | |
}); | |
} else { | |
d.Resolve(null); | |
} | |
}); | |
return d; | |
} | |
} | |
export function stringFormat(formatString: string, ...args: any[]) { | |
var builder = []; | |
var st = 0; | |
var j = 0; | |
for (var i = 0; i < formatString.length; i++) { | |
switch (st) { | |
case 0: | |
{ | |
switch (formatString[i]) { | |
case "{": | |
st = 1; | |
builder.push(formatString.substr(j, i - j)); | |
j = i + 1; | |
break; | |
case "}": | |
st = 2; | |
builder.push(formatString.substr(j, i - j)); | |
j = i + 1; | |
break; | |
default: | |
st = 0; | |
break; | |
} | |
break; | |
} | |
case 1: | |
{ | |
switch (formatString[i]) { | |
case "{": | |
builder.push("{"); | |
j = i + 1; | |
st = 0; | |
break; | |
case "0": | |
case "1": | |
case "2": | |
case "3": | |
case "4": | |
case "5": | |
case "6": | |
case "7": | |
case "8": | |
case "9": | |
st = 3; | |
break; | |
default: | |
console.error("stringFormat: Pattern error processing string", formatString, "at index", i); | |
return null; | |
} | |
break; | |
} | |
case 2: | |
{ | |
switch (formatString[i]) { | |
case "}": | |
builder.push("}"); | |
j = i + 1; | |
st = 0; | |
break; | |
default: | |
console.error("stringFormat: Pattern error processing string", formatString, "at index", i); | |
return null; | |
} | |
break; | |
} | |
case 3: | |
{ | |
switch (formatString[i]) { | |
case "0": | |
case "1": | |
case "2": | |
case "3": | |
case "4": | |
case "5": | |
case "6": | |
case "7": | |
case "8": | |
case "9": | |
st = 3; | |
break; | |
case "}": | |
{ | |
var d = formatString.substr(j, i - j); | |
var n = parseInt(d); | |
if (n + 1 >= arguments.length) { | |
console.error("stringFormat: The parameter {" + n + "} was not provided. Format string:", formatString); | |
return null; | |
} | |
builder.push(arguments[n + 1]); | |
st = 0; | |
j = i + 1; | |
break; | |
} | |
default: | |
console.error("stringFormat: Pattern error processing string", formatString, "at index", i); | |
return null; | |
} | |
break; | |
} | |
} | |
} | |
if (i > j) { | |
builder.push(formatString.substr(j, i - j)); | |
} | |
return builder.join(""); | |
} | |
export function htmlEscape(str) { | |
return String(str) | |
.replace(/&/g, '&') | |
.replace(/"/g, '"') | |
.replace(/'/g, ''') | |
.replace(/</g, '<') | |
.replace(/>/g, '>'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment