autolinker.js 216 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632
  1. /*!
  2. * Autolinker.js
  3. * v4.0.0
  4. *
  5. * Copyright(c) 2022 Gregory Jacobs <greg@greg-jacobs.com>
  6. * MIT License
  7. *
  8. * https://github.com/gregjacobs/Autolinker.js
  9. */
  10. (function (global, factory) {
  11. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  12. typeof define === 'function' && define.amd ? define(factory) :
  13. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Autolinker = factory());
  14. })(this, (function () { 'use strict';
  15. // Important: this file is generated from the 'build' script and should not be
  16. // edited directly
  17. var version = '4.0.0';
  18. /**
  19. * Simpler helper method to check for undefined simply for the benefit of
  20. * gaining better compression when minified by not needing to have multiple
  21. * comparisons to the `undefined` keyword in the codebase.
  22. */
  23. function isUndefined(value) {
  24. return value === undefined;
  25. }
  26. /**
  27. * Simpler helper method to check for a boolean type simply for the benefit of
  28. * gaining better compression when minified by not needing to have multiple
  29. * `typeof` comparisons in the codebase.
  30. */
  31. function isBoolean(value) {
  32. return typeof value === 'boolean';
  33. }
  34. /**
  35. * Assigns (shallow copies) the properties of `src` onto `dest`, if the
  36. * corresponding property on `dest` === `undefined`.
  37. *
  38. * @param {Object} dest The destination object.
  39. * @param {Object} src The source object.
  40. * @return {Object} The destination object (`dest`)
  41. */
  42. function defaults(dest, src) {
  43. for (var prop in src) {
  44. if (src.hasOwnProperty(prop) && isUndefined(dest[prop])) {
  45. dest[prop] = src[prop];
  46. }
  47. }
  48. return dest;
  49. }
  50. /**
  51. * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the
  52. * end of the string (by default, two periods: '..'). If the `str` length does not exceed
  53. * `len`, the string will be returned unchanged.
  54. *
  55. * @param {String} str The string to truncate and add an ellipsis to.
  56. * @param {Number} truncateLen The length to truncate the string at.
  57. * @param {String} [ellipsisChars=...] The ellipsis character(s) to add to the end of `str`
  58. * when truncated. Defaults to '...'
  59. */
  60. function ellipsis(str, truncateLen, ellipsisChars) {
  61. var ellipsisLength;
  62. if (str.length > truncateLen) {
  63. if (ellipsisChars == null) {
  64. ellipsisChars = '&hellip;';
  65. ellipsisLength = 3;
  66. }
  67. else {
  68. ellipsisLength = ellipsisChars.length;
  69. }
  70. str = str.substring(0, truncateLen - ellipsisLength) + ellipsisChars;
  71. }
  72. return str;
  73. }
  74. /**
  75. * Removes array elements by value. Mutates the input array.
  76. *
  77. * Using this instead of the ES5 Array.prototype.filter() function to prevent
  78. * creating many new arrays in memory for removing an element.
  79. *
  80. * @param arr The array to remove elements from. This array is mutated.
  81. * @param fn The element to remove.
  82. */
  83. function remove(arr, item) {
  84. for (var i = arr.length - 1; i >= 0; i--) {
  85. if (arr[i] === item) {
  86. arr.splice(i, 1);
  87. }
  88. }
  89. }
  90. /**
  91. * Removes array elements based on a filtering function. Mutates the input
  92. * array.
  93. *
  94. * Using this instead of the ES5 Array.prototype.filter() function to prevent
  95. * creating many new arrays in memory for filtering.
  96. *
  97. * @param arr The array to remove elements from. This array is mutated.
  98. * @param fn The predicate function which should return `true` to remove an
  99. * element.
  100. */
  101. function removeWithPredicate(arr, fn) {
  102. for (var i = arr.length - 1; i >= 0; i--) {
  103. if (fn(arr[i]) === true) {
  104. arr.splice(i, 1);
  105. }
  106. }
  107. }
  108. /**
  109. * Function that should never be called but is used to check that every
  110. * enum value is handled using TypeScript's 'never' type.
  111. */
  112. function assertNever(theValue) {
  113. throw new Error("Unhandled case for value: '".concat(theValue, "'"));
  114. }
  115. /*
  116. * This file builds and stores a library of the common regular expressions used
  117. * by the Autolinker utility.
  118. *
  119. * Other regular expressions may exist ad-hoc, but these are generally the
  120. * regular expressions that are shared between source files.
  121. */
  122. /**
  123. * Regular expression to match upper and lowercase ASCII letters
  124. */
  125. var letterRe = /[A-Za-z]/;
  126. /**
  127. * Regular expression to match ASCII digits
  128. */
  129. var digitRe = /[\d]/;
  130. /**
  131. * Regular expression to match whitespace
  132. */
  133. var whitespaceRe = /\s/;
  134. /**
  135. * Regular expression to match quote characters
  136. */
  137. var quoteRe = /['"]/;
  138. /**
  139. * Regular expression to match the range of ASCII control characters (0-31), and
  140. * the backspace char (127)
  141. */
  142. var controlCharsRe = /[\x00-\x1F\x7F]/;
  143. /**
  144. * The string form of a regular expression that would match all of the
  145. * alphabetic ("letter") chars in the unicode character set when placed in a
  146. * RegExp character class (`[]`). This includes all international alphabetic
  147. * characters.
  148. *
  149. * These would be the characters matched by unicode regex engines `\p{L}`
  150. * escape ("all letters").
  151. *
  152. * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)
  153. * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Letter'
  154. * regex's bmp
  155. *
  156. * VERY IMPORTANT: This set of characters is defined inside of a Regular
  157. * Expression literal rather than a string literal to prevent UglifyJS from
  158. * compressing the unicode escape sequences into their actual unicode
  159. * characters. If Uglify compresses these into the unicode characters
  160. * themselves, this results in the error "Range out of order in character
  161. * class" when these characters are used inside of a Regular Expression
  162. * character class (`[]`). See usages of this const. Alternatively, we can set
  163. * the UglifyJS option `ascii_only` to true for the build, but that doesn't
  164. * help others who are pulling in Autolinker into their own build and running
  165. * UglifyJS themselves.
  166. */
  167. // prettier-ignore
  168. var alphaCharsStr = /A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC/
  169. .source; // see note in above variable description
  170. /**
  171. * The string form of a regular expression that would match all emoji characters
  172. * Based on the emoji regex defined in this article: https://thekevinscott.com/emojis-in-javascript/
  173. */
  174. var emojiStr = /\u2700-\u27bf\udde6-\uddff\ud800-\udbff\udc00-\udfff\ufe0e\ufe0f\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0\ud83c\udffb-\udfff\u200d\u3299\u3297\u303d\u3030\u24c2\ud83c\udd70-\udd71\udd7e-\udd7f\udd8e\udd91-\udd9a\udde6-\uddff\ude01-\ude02\ude1a\ude2f\ude32-\ude3a\ude50-\ude51\u203c\u2049\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u00a9\u00ae\u2122\u2139\udc04\u2600-\u26FF\u2b05\u2b06\u2b07\u2b1b\u2b1c\u2b50\u2b55\u231a\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\udccf\u2935\u2934\u2190-\u21ff/
  175. .source;
  176. /**
  177. * The string form of a regular expression that would match all of the
  178. * combining mark characters in the unicode character set when placed in a
  179. * RegExp character class (`[]`).
  180. *
  181. * These would be the characters matched by unicode regex engines `\p{M}`
  182. * escape ("all marks").
  183. *
  184. * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)
  185. * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Mark'
  186. * regex's bmp
  187. *
  188. * VERY IMPORTANT: This set of characters is defined inside of a Regular
  189. * Expression literal rather than a string literal to prevent UglifyJS from
  190. * compressing the unicode escape sequences into their actual unicode
  191. * characters. If Uglify compresses these into the unicode characters
  192. * themselves, this results in the error "Range out of order in character
  193. * class" when these characters are used inside of a Regular Expression
  194. * character class (`[]`). See usages of this const. Alternatively, we can set
  195. * the UglifyJS option `ascii_only` to true for the build, but that doesn't
  196. * help others who are pulling in Autolinker into their own build and running
  197. * UglifyJS themselves.
  198. */
  199. // prettier-ignore
  200. var marksStr = /\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D4-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFB-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F/
  201. .source; // see note in above variable description
  202. /**
  203. * The string form of a regular expression that would match all of the
  204. * alphabetic ("letter") chars, emoji, and combining marks in the unicode character set
  205. * when placed in a RegExp character class (`[]`). This includes all
  206. * international alphabetic characters.
  207. *
  208. * These would be the characters matched by unicode regex engines `\p{L}\p{M}`
  209. * escapes and emoji characters.
  210. */
  211. var alphaCharsAndMarksStr = alphaCharsStr + emojiStr + marksStr;
  212. /**
  213. * The string form of a regular expression that would match all of the
  214. * decimal number chars in the unicode character set when placed in a RegExp
  215. * character class (`[]`).
  216. *
  217. * These would be the characters matched by unicode regex engines `\p{Nd}`
  218. * escape ("all decimal numbers")
  219. *
  220. * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)
  221. * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Decimal_Number'
  222. * regex's bmp
  223. *
  224. * VERY IMPORTANT: This set of characters is defined inside of a Regular
  225. * Expression literal rather than a string literal to prevent UglifyJS from
  226. * compressing the unicode escape sequences into their actual unicode
  227. * characters. If Uglify compresses these into the unicode characters
  228. * themselves, this results in the error "Range out of order in character
  229. * class" when these characters are used inside of a Regular Expression
  230. * character class (`[]`). See usages of this const. Alternatively, we can set
  231. * the UglifyJS option `ascii_only` to true for the build, but that doesn't
  232. * help others who are pulling in Autolinker into their own build and running
  233. * UglifyJS themselves.
  234. */
  235. // prettier-ignore
  236. var decimalNumbersStr = /0-9\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29\u1040-\u1049\u1090-\u1099\u17E0-\u17E9\u1810-\u1819\u1946-\u194F\u19D0-\u19D9\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\uA620-\uA629\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19/
  237. .source; // see note in above variable description
  238. /**
  239. * The string form of a regular expression that would match all of the
  240. * letters, combining marks, and decimal number chars in the unicode character
  241. * set when placed in a RegExp character class (`[]`).
  242. *
  243. * These would be the characters matched by unicode regex engines
  244. * `[\p{L}\p{M}\p{Nd}]` escape ("all letters, combining marks, and decimal
  245. * numbers")
  246. */
  247. var alphaNumericAndMarksCharsStr = alphaCharsAndMarksStr + decimalNumbersStr;
  248. /**
  249. * The regular expression that will match a single letter of the
  250. * {@link #alphaNumericAndMarksCharsStr}.
  251. */
  252. var alphaNumericAndMarksRe = new RegExp("[".concat(alphaNumericAndMarksCharsStr, "]"));
  253. /**
  254. * @class Autolinker.HtmlTag
  255. * @extends Object
  256. *
  257. * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically.
  258. *
  259. * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use
  260. * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}.
  261. *
  262. * ## Examples
  263. *
  264. * Example instantiation:
  265. *
  266. * var tag = new Autolinker.HtmlTag( {
  267. * tagName : 'a',
  268. * attrs : { 'href': 'http://google.com', 'class': 'external-link' },
  269. * innerHtml : 'Google'
  270. * } );
  271. *
  272. * tag.toAnchorString(); // <a href="http://google.com" class="external-link">Google</a>
  273. *
  274. * // Individual accessor methods
  275. * tag.getTagName(); // 'a'
  276. * tag.getAttr( 'href' ); // 'http://google.com'
  277. * tag.hasClass( 'external-link' ); // true
  278. *
  279. *
  280. * Using mutator methods (which may be used in combination with instantiation config properties):
  281. *
  282. * var tag = new Autolinker.HtmlTag();
  283. * tag.setTagName( 'a' );
  284. * tag.setAttr( 'href', 'http://google.com' );
  285. * tag.addClass( 'external-link' );
  286. * tag.setInnerHtml( 'Google' );
  287. *
  288. * tag.getTagName(); // 'a'
  289. * tag.getAttr( 'href' ); // 'http://google.com'
  290. * tag.hasClass( 'external-link' ); // true
  291. *
  292. * tag.toAnchorString(); // <a href="http://google.com" class="external-link">Google</a>
  293. *
  294. *
  295. * ## Example use within a {@link Autolinker#replaceFn replaceFn}
  296. *
  297. * var html = Autolinker.link( "Test google.com", {
  298. * replaceFn : function( match ) {
  299. * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text
  300. * tag.setAttr( 'rel', 'nofollow' );
  301. *
  302. * return tag;
  303. * }
  304. * } );
  305. *
  306. * // generated html:
  307. * // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
  308. *
  309. *
  310. * ## Example use with a new tag for the replacement
  311. *
  312. * var html = Autolinker.link( "Test google.com", {
  313. * replaceFn : function( match ) {
  314. * var tag = new Autolinker.HtmlTag( {
  315. * tagName : 'button',
  316. * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() },
  317. * innerHtml : 'Load URL: ' + match.getAnchorText()
  318. * } );
  319. *
  320. * return tag;
  321. * }
  322. * } );
  323. *
  324. * // generated html:
  325. * // Test <button title="Load URL: http://google.com">Load URL: google.com</button>
  326. */
  327. var HtmlTag = /** @class */ (function () {
  328. /**
  329. * @method constructor
  330. * @param {Object} [cfg] The configuration properties for this class, in an Object (map)
  331. */
  332. function HtmlTag(cfg) {
  333. if (cfg === void 0) { cfg = {}; }
  334. /**
  335. * @cfg {String} tagName
  336. *
  337. * The tag name. Ex: 'a', 'button', etc.
  338. *
  339. * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString}
  340. * is executed.
  341. */
  342. this.tagName = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  343. /**
  344. * @cfg {Object.<String, String>} attrs
  345. *
  346. * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the
  347. * values are the attribute values.
  348. */
  349. this.attrs = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
  350. /**
  351. * @cfg {String} innerHTML
  352. *
  353. * The inner HTML for the tag.
  354. */
  355. this.innerHTML = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  356. this.tagName = cfg.tagName || '';
  357. this.attrs = cfg.attrs || {};
  358. this.innerHTML = cfg.innerHtml || cfg.innerHTML || ''; // accept either the camelCased form or the fully capitalized acronym as in the DOM
  359. }
  360. /**
  361. * Sets the tag name that will be used to generate the tag with.
  362. *
  363. * @param {String} tagName
  364. * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
  365. */
  366. HtmlTag.prototype.setTagName = function (tagName) {
  367. this.tagName = tagName;
  368. return this;
  369. };
  370. /**
  371. * Retrieves the tag name.
  372. *
  373. * @return {String}
  374. */
  375. HtmlTag.prototype.getTagName = function () {
  376. return this.tagName || '';
  377. };
  378. /**
  379. * Sets an attribute on the HtmlTag.
  380. *
  381. * @param {String} attrName The attribute name to set.
  382. * @param {String} attrValue The attribute value to set.
  383. * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
  384. */
  385. HtmlTag.prototype.setAttr = function (attrName, attrValue) {
  386. var tagAttrs = this.getAttrs();
  387. tagAttrs[attrName] = attrValue;
  388. return this;
  389. };
  390. /**
  391. * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`.
  392. *
  393. * @param {String} attrName The attribute name to retrieve.
  394. * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.
  395. */
  396. HtmlTag.prototype.getAttr = function (attrName) {
  397. return this.getAttrs()[attrName];
  398. };
  399. /**
  400. * Sets one or more attributes on the HtmlTag.
  401. *
  402. * @param {Object.<String, String>} attrs A key/value Object (map) of the attributes to set.
  403. * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
  404. */
  405. HtmlTag.prototype.setAttrs = function (attrs) {
  406. Object.assign(this.getAttrs(), attrs);
  407. return this;
  408. };
  409. /**
  410. * Retrieves the attributes Object (map) for the HtmlTag.
  411. *
  412. * @return {Object.<String, String>} A key/value object of the attributes for the HtmlTag.
  413. */
  414. HtmlTag.prototype.getAttrs = function () {
  415. return this.attrs || (this.attrs = {});
  416. };
  417. /**
  418. * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag.
  419. *
  420. * @param {String} cssClass One or more space-separated CSS classes to set (overwrite).
  421. * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
  422. */
  423. HtmlTag.prototype.setClass = function (cssClass) {
  424. return this.setAttr('class', cssClass);
  425. };
  426. /**
  427. * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes.
  428. *
  429. * @param {String} cssClass One or more space-separated CSS classes to add.
  430. * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
  431. */
  432. HtmlTag.prototype.addClass = function (cssClass) {
  433. var classAttr = this.getClass(), classes = !classAttr ? [] : classAttr.split(whitespaceRe), newClasses = cssClass.split(whitespaceRe), newClass;
  434. while ((newClass = newClasses.shift())) {
  435. if (classes.indexOf(newClass) === -1) {
  436. classes.push(newClass);
  437. }
  438. }
  439. this.getAttrs()['class'] = classes.join(' ');
  440. return this;
  441. };
  442. /**
  443. * Convenience method to remove one or more CSS classes from the HtmlTag.
  444. *
  445. * @param {String} cssClass One or more space-separated CSS classes to remove.
  446. * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
  447. */
  448. HtmlTag.prototype.removeClass = function (cssClass) {
  449. var classAttr = this.getClass(), classes = !classAttr ? [] : classAttr.split(whitespaceRe), removeClasses = cssClass.split(whitespaceRe), removeClass;
  450. while (classes.length && (removeClass = removeClasses.shift())) {
  451. var idx = classes.indexOf(removeClass);
  452. if (idx !== -1) {
  453. classes.splice(idx, 1);
  454. }
  455. }
  456. this.getAttrs()['class'] = classes.join(' ');
  457. return this;
  458. };
  459. /**
  460. * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when
  461. * there are multiple.
  462. *
  463. * @return {String}
  464. */
  465. HtmlTag.prototype.getClass = function () {
  466. return this.getAttrs()['class'] || '';
  467. };
  468. /**
  469. * Convenience method to check if the tag has a CSS class or not.
  470. *
  471. * @param {String} cssClass The CSS class to check for.
  472. * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise.
  473. */
  474. HtmlTag.prototype.hasClass = function (cssClass) {
  475. return (' ' + this.getClass() + ' ').indexOf(' ' + cssClass + ' ') !== -1;
  476. };
  477. /**
  478. * Sets the inner HTML for the tag.
  479. *
  480. * @param {String} html The inner HTML to set.
  481. * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
  482. */
  483. HtmlTag.prototype.setInnerHTML = function (html) {
  484. this.innerHTML = html;
  485. return this;
  486. };
  487. /**
  488. * Backwards compatibility method name.
  489. *
  490. * @param {String} html The inner HTML to set.
  491. * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
  492. */
  493. HtmlTag.prototype.setInnerHtml = function (html) {
  494. return this.setInnerHTML(html);
  495. };
  496. /**
  497. * Retrieves the inner HTML for the tag.
  498. *
  499. * @return {String}
  500. */
  501. HtmlTag.prototype.getInnerHTML = function () {
  502. return this.innerHTML || '';
  503. };
  504. /**
  505. * Backward compatibility method name.
  506. *
  507. * @return {String}
  508. */
  509. HtmlTag.prototype.getInnerHtml = function () {
  510. return this.getInnerHTML();
  511. };
  512. /**
  513. * Generates the HTML string for the tag.
  514. *
  515. * @return {String}
  516. */
  517. HtmlTag.prototype.toAnchorString = function () {
  518. var tagName = this.getTagName(), attrsStr = this.buildAttrsStr();
  519. attrsStr = attrsStr ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes
  520. return ['<', tagName, attrsStr, '>', this.getInnerHtml(), '</', tagName, '>'].join('');
  521. };
  522. /**
  523. * Support method for {@link #toAnchorString}, returns the string space-separated key="value" pairs, used to populate
  524. * the stringified HtmlTag.
  525. *
  526. * @protected
  527. * @return {String} Example return: `attr1="value1" attr2="value2"`
  528. */
  529. HtmlTag.prototype.buildAttrsStr = function () {
  530. if (!this.attrs)
  531. return ''; // no `attrs` Object (map) has been set, return empty string
  532. var attrs = this.getAttrs(), attrsArr = [];
  533. for (var prop in attrs) {
  534. if (attrs.hasOwnProperty(prop)) {
  535. attrsArr.push(prop + '="' + attrs[prop] + '"');
  536. }
  537. }
  538. return attrsArr.join(' ');
  539. };
  540. return HtmlTag;
  541. }());
  542. /**
  543. * Date: 2015-10-05
  544. * Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)
  545. *
  546. * A truncation feature, where the ellipsis will be placed at a section within
  547. * the URL making it still somewhat human readable.
  548. *
  549. * @param {String} url A URL.
  550. * @param {Number} truncateLen The maximum length of the truncated output URL string.
  551. * @param {String} ellipsisChars The characters to place within the url, e.g. "...".
  552. * @return {String} The truncated URL.
  553. */
  554. function truncateSmart(url, truncateLen, ellipsisChars) {
  555. var ellipsisLengthBeforeParsing;
  556. var ellipsisLength;
  557. if (ellipsisChars == null) {
  558. ellipsisChars = '&hellip;';
  559. ellipsisLength = 3;
  560. ellipsisLengthBeforeParsing = 8;
  561. }
  562. else {
  563. ellipsisLength = ellipsisChars.length;
  564. ellipsisLengthBeforeParsing = ellipsisChars.length;
  565. }
  566. var parse_url = function (url) {
  567. // Functionality inspired by PHP function of same name
  568. var urlObj = {};
  569. var urlSub = url;
  570. var match = urlSub.match(/^([a-z]+):\/\//i);
  571. if (match) {
  572. urlObj.scheme = match[1];
  573. urlSub = urlSub.substr(match[0].length);
  574. }
  575. match = urlSub.match(/^(.*?)(?=(\?|#|\/|$))/i);
  576. if (match) {
  577. urlObj.host = match[1];
  578. urlSub = urlSub.substr(match[0].length);
  579. }
  580. match = urlSub.match(/^\/(.*?)(?=(\?|#|$))/i);
  581. if (match) {
  582. urlObj.path = match[1];
  583. urlSub = urlSub.substr(match[0].length);
  584. }
  585. match = urlSub.match(/^\?(.*?)(?=(#|$))/i);
  586. if (match) {
  587. urlObj.query = match[1];
  588. urlSub = urlSub.substr(match[0].length);
  589. }
  590. match = urlSub.match(/^#(.*?)$/i);
  591. if (match) {
  592. urlObj.fragment = match[1];
  593. //urlSub = urlSub.substr(match[0].length); -- not used. Uncomment if adding another block.
  594. }
  595. return urlObj;
  596. };
  597. var buildUrl = function (urlObj) {
  598. var url = '';
  599. if (urlObj.scheme && urlObj.host) {
  600. url += urlObj.scheme + '://';
  601. }
  602. if (urlObj.host) {
  603. url += urlObj.host;
  604. }
  605. if (urlObj.path) {
  606. url += '/' + urlObj.path;
  607. }
  608. if (urlObj.query) {
  609. url += '?' + urlObj.query;
  610. }
  611. if (urlObj.fragment) {
  612. url += '#' + urlObj.fragment;
  613. }
  614. return url;
  615. };
  616. var buildSegment = function (segment, remainingAvailableLength) {
  617. var remainingAvailableLengthHalf = remainingAvailableLength / 2, startOffset = Math.ceil(remainingAvailableLengthHalf), endOffset = -1 * Math.floor(remainingAvailableLengthHalf), end = '';
  618. if (endOffset < 0) {
  619. end = segment.substr(endOffset);
  620. }
  621. return segment.substr(0, startOffset) + ellipsisChars + end;
  622. };
  623. if (url.length <= truncateLen) {
  624. return url;
  625. }
  626. var availableLength = truncateLen - ellipsisLength;
  627. var urlObj = parse_url(url);
  628. // Clean up the URL
  629. if (urlObj.query) {
  630. var matchQuery = urlObj.query.match(/^(.*?)(?=(\?|\#))(.*?)$/i);
  631. if (matchQuery) {
  632. // Malformed URL; two or more "?". Removed any content behind the 2nd.
  633. urlObj.query = urlObj.query.substr(0, matchQuery[1].length);
  634. url = buildUrl(urlObj);
  635. }
  636. }
  637. if (url.length <= truncateLen) {
  638. return url;
  639. }
  640. if (urlObj.host) {
  641. urlObj.host = urlObj.host.replace(/^www\./, '');
  642. url = buildUrl(urlObj);
  643. }
  644. if (url.length <= truncateLen) {
  645. return url;
  646. }
  647. // Process and build the URL
  648. var str = '';
  649. if (urlObj.host) {
  650. str += urlObj.host;
  651. }
  652. if (str.length >= availableLength) {
  653. if (urlObj.host.length == truncateLen) {
  654. return (urlObj.host.substr(0, truncateLen - ellipsisLength) + ellipsisChars).substr(0, availableLength + ellipsisLengthBeforeParsing);
  655. }
  656. return buildSegment(str, availableLength).substr(0, availableLength + ellipsisLengthBeforeParsing);
  657. }
  658. var pathAndQuery = '';
  659. if (urlObj.path) {
  660. pathAndQuery += '/' + urlObj.path;
  661. }
  662. if (urlObj.query) {
  663. pathAndQuery += '?' + urlObj.query;
  664. }
  665. if (pathAndQuery) {
  666. if ((str + pathAndQuery).length >= availableLength) {
  667. if ((str + pathAndQuery).length == truncateLen) {
  668. return (str + pathAndQuery).substr(0, truncateLen);
  669. }
  670. var remainingAvailableLength = availableLength - str.length;
  671. return (str + buildSegment(pathAndQuery, remainingAvailableLength)).substr(0, availableLength + ellipsisLengthBeforeParsing);
  672. }
  673. else {
  674. str += pathAndQuery;
  675. }
  676. }
  677. if (urlObj.fragment) {
  678. var fragment = '#' + urlObj.fragment;
  679. if ((str + fragment).length >= availableLength) {
  680. if ((str + fragment).length == truncateLen) {
  681. return (str + fragment).substr(0, truncateLen);
  682. }
  683. var remainingAvailableLength2 = availableLength - str.length;
  684. return (str + buildSegment(fragment, remainingAvailableLength2)).substr(0, availableLength + ellipsisLengthBeforeParsing);
  685. }
  686. else {
  687. str += fragment;
  688. }
  689. }
  690. if (urlObj.scheme && urlObj.host) {
  691. var scheme = urlObj.scheme + '://';
  692. if ((str + scheme).length < availableLength) {
  693. return (scheme + str).substr(0, truncateLen);
  694. }
  695. }
  696. if (str.length <= truncateLen) {
  697. return str;
  698. }
  699. var end = '';
  700. if (availableLength > 0) {
  701. end = str.substr(-1 * Math.floor(availableLength / 2));
  702. }
  703. return (str.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(0, availableLength + ellipsisLengthBeforeParsing);
  704. }
  705. /**
  706. * Date: 2015-10-05
  707. * Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)
  708. *
  709. * A truncation feature, where the ellipsis will be placed in the dead-center of the URL.
  710. *
  711. * @param {String} url A URL.
  712. * @param {Number} truncateLen The maximum length of the truncated output URL string.
  713. * @param {String} ellipsisChars The characters to place within the url, e.g. "..".
  714. * @return {String} The truncated URL.
  715. */
  716. function truncateMiddle(url, truncateLen, ellipsisChars) {
  717. if (url.length <= truncateLen) {
  718. return url;
  719. }
  720. var ellipsisLengthBeforeParsing;
  721. var ellipsisLength;
  722. if (ellipsisChars == null) {
  723. ellipsisChars = '&hellip;';
  724. ellipsisLengthBeforeParsing = 8;
  725. ellipsisLength = 3;
  726. }
  727. else {
  728. ellipsisLengthBeforeParsing = ellipsisChars.length;
  729. ellipsisLength = ellipsisChars.length;
  730. }
  731. var availableLength = truncateLen - ellipsisLength;
  732. var end = '';
  733. if (availableLength > 0) {
  734. end = url.substr(-1 * Math.floor(availableLength / 2));
  735. }
  736. return (url.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(0, availableLength + ellipsisLengthBeforeParsing);
  737. }
  738. /**
  739. * A truncation feature where the ellipsis will be placed at the end of the URL.
  740. *
  741. * @param {String} anchorText
  742. * @param {Number} truncateLen The maximum length of the truncated output URL string.
  743. * @param {String} ellipsisChars The characters to place within the url, e.g. "..".
  744. * @return {String} The truncated URL.
  745. */
  746. function truncateEnd(anchorText, truncateLen, ellipsisChars) {
  747. return ellipsis(anchorText, truncateLen, ellipsisChars);
  748. }
  749. /**
  750. * @protected
  751. * @class Autolinker.AnchorTagBuilder
  752. * @extends Object
  753. *
  754. * Builds anchor (&lt;a&gt;) tags for the Autolinker utility when a match is
  755. * found.
  756. *
  757. * Normally this class is instantiated, configured, and used internally by an
  758. * {@link Autolinker} instance, but may actually be used indirectly in a
  759. * {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag}
  760. * instances which may be modified before returning from the
  761. * {@link Autolinker#replaceFn replaceFn}. For example:
  762. *
  763. * var html = Autolinker.link( "Test google.com", {
  764. * replaceFn : function( match ) {
  765. * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance
  766. * tag.setAttr( 'rel', 'nofollow' );
  767. *
  768. * return tag;
  769. * }
  770. * } );
  771. *
  772. * // generated html:
  773. * // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
  774. */
  775. var AnchorTagBuilder = /** @class */ (function () {
  776. /**
  777. * @method constructor
  778. * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
  779. */
  780. function AnchorTagBuilder(cfg) {
  781. if (cfg === void 0) { cfg = {}; }
  782. /**
  783. * @cfg {Boolean} newWindow
  784. * @inheritdoc Autolinker#newWindow
  785. */
  786. this.newWindow = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  787. /**
  788. * @cfg {Object} truncate
  789. * @inheritdoc Autolinker#truncate
  790. */
  791. this.truncate = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
  792. /**
  793. * @cfg {String} className
  794. * @inheritdoc Autolinker#className
  795. */
  796. this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  797. this.newWindow = cfg.newWindow || false;
  798. this.truncate = cfg.truncate || {};
  799. this.className = cfg.className || '';
  800. }
  801. /**
  802. * Generates the actual anchor (&lt;a&gt;) tag to use in place of the
  803. * matched text, via its `match` object.
  804. *
  805. * @param match The Match instance to generate an anchor tag from.
  806. * @return The HtmlTag instance for the anchor tag.
  807. */
  808. AnchorTagBuilder.prototype.build = function (match) {
  809. return new HtmlTag({
  810. tagName: 'a',
  811. attrs: this.createAttrs(match),
  812. innerHtml: this.processAnchorText(match.getAnchorText()),
  813. });
  814. };
  815. /**
  816. * Creates the Object (map) of the HTML attributes for the anchor (&lt;a&gt;)
  817. * tag being generated.
  818. *
  819. * @protected
  820. * @param match The Match instance to generate an anchor tag from.
  821. * @return A key/value Object (map) of the anchor tag's attributes.
  822. */
  823. AnchorTagBuilder.prototype.createAttrs = function (match) {
  824. var attrs = {
  825. href: match.getAnchorHref(), // we'll always have the `href` attribute
  826. };
  827. var cssClass = this.createCssClass(match);
  828. if (cssClass) {
  829. attrs['class'] = cssClass;
  830. }
  831. if (this.newWindow) {
  832. attrs['target'] = '_blank';
  833. attrs['rel'] = 'noopener noreferrer'; // Issue #149. See https://mathiasbynens.github.io/rel-noopener/
  834. }
  835. if (this.truncate) {
  836. if (this.truncate.length && this.truncate.length < match.getAnchorText().length) {
  837. attrs['title'] = match.getAnchorHref();
  838. }
  839. }
  840. return attrs;
  841. };
  842. /**
  843. * Creates the CSS class that will be used for a given anchor tag, based on
  844. * the `matchType` and the {@link #className} config.
  845. *
  846. * Example returns:
  847. *
  848. * - "" // no {@link #className}
  849. * - "myLink myLink-url" // url match
  850. * - "myLink myLink-email" // email match
  851. * - "myLink myLink-phone" // phone match
  852. * - "myLink myLink-hashtag" // hashtag match
  853. * - "myLink myLink-mention myLink-twitter" // mention match with Twitter service
  854. *
  855. * @protected
  856. * @param match The Match instance to generate an
  857. * anchor tag from.
  858. * @return The CSS class string for the link. Example return:
  859. * "myLink myLink-url". If no {@link #className} was configured, returns
  860. * an empty string.
  861. */
  862. AnchorTagBuilder.prototype.createCssClass = function (match) {
  863. var className = this.className;
  864. if (!className) {
  865. return '';
  866. }
  867. else {
  868. var returnClasses = [className], cssClassSuffixes = match.getCssClassSuffixes();
  869. for (var i = 0, len = cssClassSuffixes.length; i < len; i++) {
  870. returnClasses.push(className + '-' + cssClassSuffixes[i]);
  871. }
  872. return returnClasses.join(' ');
  873. }
  874. };
  875. /**
  876. * Processes the `anchorText` by truncating the text according to the
  877. * {@link #truncate} config.
  878. *
  879. * @private
  880. * @param anchorText The anchor tag's text (i.e. what will be
  881. * displayed).
  882. * @return The processed `anchorText`.
  883. */
  884. AnchorTagBuilder.prototype.processAnchorText = function (anchorText) {
  885. anchorText = this.doTruncate(anchorText);
  886. return anchorText;
  887. };
  888. /**
  889. * Performs the truncation of the `anchorText` based on the {@link #truncate}
  890. * option. If the `anchorText` is longer than the length specified by the
  891. * {@link #truncate} option, the truncation is performed based on the
  892. * `location` property. See {@link #truncate} for details.
  893. *
  894. * @private
  895. * @param anchorText The anchor tag's text (i.e. what will be
  896. * displayed).
  897. * @return The truncated anchor text.
  898. */
  899. AnchorTagBuilder.prototype.doTruncate = function (anchorText) {
  900. var truncate = this.truncate;
  901. if (!truncate || !truncate.length)
  902. return anchorText;
  903. var truncateLength = truncate.length, truncateLocation = truncate.location;
  904. if (truncateLocation === 'smart') {
  905. return truncateSmart(anchorText, truncateLength);
  906. }
  907. else if (truncateLocation === 'middle') {
  908. return truncateMiddle(anchorText, truncateLength);
  909. }
  910. else {
  911. return truncateEnd(anchorText, truncateLength);
  912. }
  913. };
  914. return AnchorTagBuilder;
  915. }());
  916. /*! *****************************************************************************
  917. Copyright (c) Microsoft Corporation.
  918. Permission to use, copy, modify, and/or distribute this software for any
  919. purpose with or without fee is hereby granted.
  920. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  921. REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  922. AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  923. INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  924. LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
  925. OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  926. PERFORMANCE OF THIS SOFTWARE.
  927. ***************************************************************************** */
  928. /* global Reflect, Promise */
  929. var extendStatics = function(d, b) {
  930. extendStatics = Object.setPrototypeOf ||
  931. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  932. function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
  933. return extendStatics(d, b);
  934. };
  935. function __extends(d, b) {
  936. if (typeof b !== "function" && b !== null)
  937. throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
  938. extendStatics(d, b);
  939. function __() { this.constructor = d; }
  940. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  941. }
  942. var __assign = function() {
  943. __assign = Object.assign || function __assign(t) {
  944. for (var s, i = 1, n = arguments.length; i < n; i++) {
  945. s = arguments[i];
  946. for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
  947. }
  948. return t;
  949. };
  950. return __assign.apply(this, arguments);
  951. };
  952. /**
  953. * @abstract
  954. * @class Autolinker.match.AbstractMatch
  955. *
  956. * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a
  957. * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match.
  958. *
  959. * For example:
  960. *
  961. * var input = "..."; // string with URLs, Email Addresses, and Mentions (Twitter, Instagram, Soundcloud)
  962. *
  963. * var linkedText = Autolinker.link( input, {
  964. * replaceFn : function( match ) {
  965. * console.log( "href = ", match.getAnchorHref() );
  966. * console.log( "text = ", match.getAnchorText() );
  967. *
  968. * switch( match.getType() ) {
  969. * case 'url' :
  970. * console.log( "url: ", match.getUrl() );
  971. *
  972. * case 'email' :
  973. * console.log( "email: ", match.getEmail() );
  974. *
  975. * case 'mention' :
  976. * console.log( "mention: ", match.getMention() );
  977. * }
  978. * }
  979. * } );
  980. *
  981. * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}.
  982. */
  983. var AbstractMatch = /** @class */ (function () {
  984. /**
  985. * @member Autolinker.match.Match
  986. * @method constructor
  987. * @param {Object} cfg The configuration properties for the Match
  988. * instance, specified in an Object (map).
  989. */
  990. function AbstractMatch(cfg) {
  991. /**
  992. * @cfg {Autolinker.AnchorTagBuilder} tagBuilder (required)
  993. *
  994. * Reference to the AnchorTagBuilder instance to use to generate an anchor
  995. * tag for the Match.
  996. */
  997. // @ts-ignore
  998. this._ = null; // property used just to get the above doc comment into the ES5 output and documentation generator
  999. /**
  1000. * @cfg {String} matchedText (required)
  1001. *
  1002. * The original text that was matched by the {@link Autolinker.matcher.Matcher}.
  1003. */
  1004. this.matchedText = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  1005. /**
  1006. * @cfg {Number} offset (required)
  1007. *
  1008. * The offset of where the match was made in the input string.
  1009. */
  1010. this.offset = 0; // default value just to get the above doc comment in the ES5 output and documentation generator
  1011. this.tagBuilder = cfg.tagBuilder;
  1012. this.matchedText = cfg.matchedText;
  1013. this.offset = cfg.offset;
  1014. }
  1015. /**
  1016. * Returns the original text that was matched.
  1017. *
  1018. * @return {String}
  1019. */
  1020. AbstractMatch.prototype.getMatchedText = function () {
  1021. return this.matchedText;
  1022. };
  1023. /**
  1024. * Sets the {@link #offset} of where the match was made in the input string.
  1025. *
  1026. * A {@link Autolinker.matcher.Matcher} will be fed only HTML text nodes,
  1027. * and will therefore set an original offset that is relative to the HTML
  1028. * text node itself. However, we want this offset to be relative to the full
  1029. * HTML input string, and thus if using {@link Autolinker#parse} (rather
  1030. * than calling a {@link Autolinker.matcher.Matcher} directly), then this
  1031. * offset is corrected after the Matcher itself has done its job.
  1032. *
  1033. * @private
  1034. * @param {Number} offset
  1035. */
  1036. AbstractMatch.prototype.setOffset = function (offset) {
  1037. this.offset = offset;
  1038. };
  1039. /**
  1040. * Returns the offset of where the match was made in the input string. This
  1041. * is the 0-based index of the match.
  1042. *
  1043. * @return {Number}
  1044. */
  1045. AbstractMatch.prototype.getOffset = function () {
  1046. return this.offset;
  1047. };
  1048. /**
  1049. * Returns the CSS class suffix(es) for this match.
  1050. *
  1051. * A CSS class suffix is appended to the {@link Autolinker#className} in
  1052. * the {@link Autolinker.AnchorTagBuilder} when a match is translated into
  1053. * an anchor tag.
  1054. *
  1055. * For example, if {@link Autolinker#className} was configured as 'myLink',
  1056. * and this method returns `[ 'url' ]`, the final class name of the element
  1057. * will become: 'myLink myLink-url'.
  1058. *
  1059. * The match may provide multiple CSS class suffixes to be appended to the
  1060. * {@link Autolinker#className} in order to facilitate better styling
  1061. * options for different match criteria. See {@link Autolinker.match.Mention}
  1062. * for an example.
  1063. *
  1064. * By default, this method returns a single array with the match's
  1065. * {@link #getType type} name, but may be overridden by subclasses.
  1066. *
  1067. * @return {String[]}
  1068. */
  1069. AbstractMatch.prototype.getCssClassSuffixes = function () {
  1070. return [this.type];
  1071. };
  1072. /**
  1073. * Builds and returns an {@link Autolinker.HtmlTag} instance based on the
  1074. * Match.
  1075. *
  1076. * This can be used to easily generate anchor tags from matches, and either
  1077. * return their HTML string, or modify them before doing so.
  1078. *
  1079. * Example Usage:
  1080. *
  1081. * var tag = match.buildTag();
  1082. * tag.addClass( 'cordova-link' );
  1083. * tag.setAttr( 'target', '_system' );
  1084. *
  1085. * tag.toAnchorString(); // <a href="http://google.com" class="cordova-link" target="_system">Google</a>
  1086. *
  1087. * Example Usage in {@link Autolinker#replaceFn}:
  1088. *
  1089. * var html = Autolinker.link( "Test google.com", {
  1090. * replaceFn : function( match ) {
  1091. * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance
  1092. * tag.setAttr( 'rel', 'nofollow' );
  1093. *
  1094. * return tag;
  1095. * }
  1096. * } );
  1097. *
  1098. * // generated html:
  1099. * // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
  1100. */
  1101. AbstractMatch.prototype.buildTag = function () {
  1102. return this.tagBuilder.build(this);
  1103. };
  1104. return AbstractMatch;
  1105. }());
  1106. // NOTE: THIS IS A GENERATED FILE
  1107. // To update with the latest TLD list, run `npm run update-tld-regex`
  1108. var tldRegexStr = '(?:xn--vermgensberatung-pwb|xn--vermgensberater-ctb|xn--clchc0ea0b2g2a9gcd|xn--w4r85el8fhu5dnra|northwesternmutual|travelersinsurance|vermögensberatung|xn--5su34j936bgsg|xn--bck1b9a5dre4c|xn--mgbah1a3hjkrd|xn--mgbai9azgqp6j|xn--mgberp4a5d4ar|xn--xkc2dl3a5ee0h|vermögensberater|xn--fzys8d69uvgm|xn--mgba7c0bbn0a|xn--mgbcpq6gpa1a|xn--xkc2al3hye2a|americanexpress|kerryproperties|sandvikcoromant|xn--i1b6b1a6a2e|xn--kcrx77d1x4a|xn--lgbbat1ad8j|xn--mgba3a4f16a|xn--mgbaakc7dvf|xn--mgbc0a9azcg|xn--nqv7fs00ema|americanfamily|bananarepublic|cancerresearch|cookingchannel|kerrylogistics|weatherchannel|xn--54b7fta0cc|xn--6qq986b3xl|xn--80aqecdr1a|xn--b4w605ferd|xn--fiq228c5hs|xn--h2breg3eve|xn--jlq480n2rg|xn--jlq61u9w7b|xn--mgba3a3ejt|xn--mgbaam7a8h|xn--mgbayh7gpa|xn--mgbbh1a71e|xn--mgbca7dzdo|xn--mgbi4ecexp|xn--mgbx4cd0ab|xn--rvc1e0am3e|international|lifeinsurance|travelchannel|wolterskluwer|xn--cckwcxetd|xn--eckvdtc9d|xn--fpcrj9c3d|xn--fzc2c9e2c|xn--h2brj9c8c|xn--tiq49xqyj|xn--yfro4i67o|xn--ygbi2ammx|construction|lplfinancial|scholarships|versicherung|xn--3e0b707e|xn--45br5cyl|xn--4dbrk0ce|xn--80adxhks|xn--80asehdb|xn--8y0a063a|xn--gckr3f0f|xn--mgb9awbf|xn--mgbab2bd|xn--mgbgu82a|xn--mgbpl2fh|xn--mgbt3dhd|xn--mk1bu44c|xn--ngbc5azd|xn--ngbe9e0a|xn--ogbpf8fl|xn--qcka1pmc|accountants|barclaycard|blackfriday|blockbuster|bridgestone|calvinklein|contractors|creditunion|engineering|enterprises|foodnetwork|investments|kerryhotels|lamborghini|motorcycles|olayangroup|photography|playstation|productions|progressive|redumbrella|williamhill|xn--11b4c3d|xn--1ck2e1b|xn--1qqw23a|xn--2scrj9c|xn--3bst00m|xn--3ds443g|xn--3hcrj9c|xn--42c2d9a|xn--45brj9c|xn--55qw42g|xn--6frz82g|xn--80ao21a|xn--9krt00a|xn--cck2b3b|xn--czr694b|xn--d1acj3b|xn--efvy88h|xn--fct429k|xn--fjq720a|xn--flw351e|xn--g2xx48c|xn--gecrj9c|xn--gk3at1e|xn--h2brj9c|xn--hxt814e|xn--imr513n|xn--j6w193g|xn--jvr189m|xn--kprw13d|xn--kpry57d|xn--mgbbh1a|xn--mgbtx2b|xn--mix891f|xn--nyqy26a|xn--otu796d|xn--pgbs0dh|xn--q9jyb4c|xn--rhqv96g|xn--rovu88b|xn--s9brj9c|xn--ses554g|xn--t60b56a|xn--vuq861b|xn--w4rs40l|xn--xhq521b|xn--zfr164b|சிங்கப்பூர்|accountant|apartments|associates|basketball|bnpparibas|boehringer|capitalone|consulting|creditcard|cuisinella|eurovision|extraspace|foundation|healthcare|immobilien|industries|management|mitsubishi|nextdirect|properties|protection|prudential|realestate|republican|restaurant|schaeffler|tatamotors|technology|university|vlaanderen|volkswagen|xn--30rr7y|xn--3pxu8k|xn--45q11c|xn--4gbrim|xn--55qx5d|xn--5tzm5g|xn--80aswg|xn--90a3ac|xn--9dbq2a|xn--9et52u|xn--c2br7g|xn--cg4bki|xn--czrs0t|xn--czru2d|xn--fiq64b|xn--fiqs8s|xn--fiqz9s|xn--io0a7i|xn--kput3i|xn--mxtq1m|xn--o3cw4h|xn--pssy2u|xn--q7ce6a|xn--unup4y|xn--wgbh1c|xn--wgbl6a|xn--y9a3aq|accenture|alfaromeo|allfinanz|amsterdam|analytics|aquarelle|barcelona|bloomberg|christmas|community|directory|education|equipment|fairwinds|financial|firestone|fresenius|frontdoor|furniture|goldpoint|hisamitsu|homedepot|homegoods|homesense|institute|insurance|kuokgroup|lancaster|landrover|lifestyle|marketing|marshalls|melbourne|microsoft|panasonic|passagens|pramerica|richardli|shangrila|solutions|statebank|statefarm|stockholm|travelers|vacations|xn--90ais|xn--c1avg|xn--d1alf|xn--e1a4c|xn--fhbei|xn--j1aef|xn--j1amh|xn--l1acc|xn--ngbrx|xn--nqv7f|xn--p1acf|xn--qxa6a|xn--tckwe|xn--vhquv|yodobashi|موريتانيا|abudhabi|airforce|allstate|attorney|barclays|barefoot|bargains|baseball|boutique|bradesco|broadway|brussels|builders|business|capetown|catering|catholic|cipriani|cityeats|cleaning|clinique|clothing|commbank|computer|delivery|deloitte|democrat|diamonds|discount|discover|download|engineer|ericsson|etisalat|exchange|feedback|fidelity|firmdale|football|frontier|goodyear|grainger|graphics|guardian|hdfcbank|helsinki|holdings|hospital|infiniti|ipiranga|istanbul|jpmorgan|lighting|lundbeck|marriott|maserati|mckinsey|memorial|merckmsd|mortgage|observer|partners|pharmacy|pictures|plumbing|property|redstone|reliance|saarland|samsclub|security|services|shopping|showtime|softbank|software|stcgroup|supplies|training|vanguard|ventures|verisign|woodside|xn--90ae|xn--node|xn--p1ai|xn--qxam|yokohama|السعودية|abogado|academy|agakhan|alibaba|android|athleta|auction|audible|auspost|avianca|banamex|bauhaus|bentley|bestbuy|booking|brother|bugatti|capital|caravan|careers|channel|charity|chintai|citadel|clubmed|college|cologne|comcast|company|compare|contact|cooking|corsica|country|coupons|courses|cricket|cruises|dentist|digital|domains|exposed|express|farmers|fashion|ferrari|ferrero|finance|fishing|fitness|flights|florist|flowers|forsale|frogans|fujitsu|gallery|genting|godaddy|grocery|guitars|hamburg|hangout|hitachi|holiday|hosting|hoteles|hotmail|hyundai|ismaili|jewelry|juniper|kitchen|komatsu|lacaixa|lanxess|lasalle|latrobe|leclerc|limited|lincoln|markets|monster|netbank|netflix|network|neustar|okinawa|oldnavy|organic|origins|philips|pioneer|politie|realtor|recipes|rentals|reviews|rexroth|samsung|sandvik|schmidt|schwarz|science|shiksha|singles|staples|storage|support|surgery|systems|temasek|theater|theatre|tickets|tiffany|toshiba|trading|walmart|wanggou|watches|weather|website|wedding|whoswho|windows|winners|xfinity|yamaxun|youtube|zuerich|католик|اتصالات|البحرين|الجزائر|العليان|پاکستان|كاثوليك|இந்தியா|abarth|abbott|abbvie|africa|agency|airbus|airtel|alipay|alsace|alstom|amazon|anquan|aramco|author|bayern|beauty|berlin|bharti|bostik|boston|broker|camera|career|casino|center|chanel|chrome|church|circle|claims|clinic|coffee|comsec|condos|coupon|credit|cruise|dating|datsun|dealer|degree|dental|design|direct|doctor|dunlop|dupont|durban|emerck|energy|estate|events|expert|family|flickr|futbol|gallup|garden|george|giving|global|google|gratis|health|hermes|hiphop|hockey|hotels|hughes|imamat|insure|intuit|jaguar|joburg|juegos|kaufen|kinder|kindle|kosher|lancia|latino|lawyer|lefrak|living|locker|london|luxury|madrid|maison|makeup|market|mattel|mobile|monash|mormon|moscow|museum|mutual|nagoya|natura|nissan|nissay|norton|nowruz|office|olayan|online|oracle|orange|otsuka|pfizer|photos|physio|pictet|quebec|racing|realty|reisen|repair|report|review|rocher|rogers|ryukyu|safety|sakura|sanofi|school|schule|search|secure|select|shouji|soccer|social|stream|studio|supply|suzuki|swatch|sydney|taipei|taobao|target|tattoo|tennis|tienda|tjmaxx|tkmaxx|toyota|travel|unicom|viajes|viking|villas|virgin|vision|voting|voyage|vuelos|walter|webcam|xihuan|yachts|yandex|zappos|москва|онлайн|ابوظبي|ارامكو|الاردن|المغرب|امارات|فلسطين|مليسيا|भारतम्|இலங்கை|ファッション|actor|adult|aetna|amfam|amica|apple|archi|audio|autos|azure|baidu|beats|bible|bingo|black|boats|bosch|build|canon|cards|chase|cheap|cisco|citic|click|cloud|coach|codes|crown|cymru|dabur|dance|deals|delta|drive|dubai|earth|edeka|email|epson|faith|fedex|final|forex|forum|gallo|games|gifts|gives|glass|globo|gmail|green|gripe|group|gucci|guide|homes|honda|horse|house|hyatt|ikano|irish|jetzt|koeln|kyoto|lamer|lease|legal|lexus|lilly|linde|lipsy|loans|locus|lotte|lotto|macys|mango|media|miami|money|movie|music|nexus|nikon|ninja|nokia|nowtv|omega|osaka|paris|parts|party|phone|photo|pizza|place|poker|praxi|press|prime|promo|quest|radio|rehab|reise|ricoh|rocks|rodeo|rugby|salon|sener|seven|sharp|shell|shoes|skype|sling|smart|smile|solar|space|sport|stada|store|study|style|sucks|swiss|tatar|tires|tirol|tmall|today|tokyo|tools|toray|total|tours|trade|trust|tunes|tushu|ubank|vegas|video|vodka|volvo|wales|watch|weber|weibo|works|world|xerox|yahoo|ישראל|ایران|بازار|بھارت|سودان|سورية|همراه|भारोत|संगठन|বাংলা|భారత్|ഭാരതം|嘉里大酒店|aarp|able|adac|aero|akdn|ally|amex|arab|army|arpa|arte|asda|asia|audi|auto|baby|band|bank|bbva|beer|best|bike|bing|blog|blue|bofa|bond|book|buzz|cafe|call|camp|care|cars|casa|case|cash|cbre|cern|chat|citi|city|club|cool|coop|cyou|data|date|dclk|deal|dell|desi|diet|dish|docs|dvag|erni|fage|fail|fans|farm|fast|fiat|fido|film|fire|fish|flir|food|ford|free|fund|game|gbiz|gent|ggee|gift|gmbh|gold|golf|goog|guge|guru|hair|haus|hdfc|help|here|hgtv|host|hsbc|icbc|ieee|imdb|immo|info|itau|java|jeep|jobs|jprs|kddi|kids|kiwi|kpmg|kred|land|lego|lgbt|lidl|life|like|limo|link|live|loan|loft|love|ltda|luxe|maif|meet|meme|menu|mini|mint|mobi|moda|moto|name|navy|news|next|nico|nike|ollo|open|page|pars|pccw|pics|ping|pink|play|plus|pohl|porn|post|prod|prof|qpon|read|reit|rent|rest|rich|room|rsvp|ruhr|safe|sale|sarl|save|saxo|scot|seat|seek|sexy|shaw|shia|shop|show|silk|sina|site|skin|sncf|sohu|song|sony|spot|star|surf|talk|taxi|team|tech|teva|tiaa|tips|town|toys|tube|vana|visa|viva|vivo|vote|voto|wang|weir|wien|wiki|wine|work|xbox|yoga|zara|zero|zone|дети|сайт|بارت|بيتك|ڀارت|تونس|شبكة|عراق|عمان|موقع|भारत|ভারত|ভাৰত|ਭਾਰਤ|ભારત|ଭାରତ|ಭಾರತ|ලංකා|アマゾン|グーグル|クラウド|ポイント|组织机构|電訊盈科|香格里拉|aaa|abb|abc|aco|ads|aeg|afl|aig|anz|aol|app|art|aws|axa|bar|bbc|bbt|bcg|bcn|bet|bid|bio|biz|bms|bmw|bom|boo|bot|box|buy|bzh|cab|cal|cam|car|cat|cba|cbn|cbs|ceo|cfa|cfd|com|cpa|crs|dad|day|dds|dev|dhl|diy|dnp|dog|dot|dtv|dvr|eat|eco|edu|esq|eus|fan|fit|fly|foo|fox|frl|ftr|fun|fyi|gal|gap|gay|gdn|gea|gle|gmo|gmx|goo|gop|got|gov|hbo|hiv|hkt|hot|how|ibm|ice|icu|ifm|inc|ing|ink|int|ist|itv|jcb|jio|jll|jmp|jnj|jot|joy|kfh|kia|kim|kpn|krd|lat|law|lds|llc|llp|lol|lpl|ltd|man|map|mba|med|men|mil|mit|mlb|mls|mma|moe|moi|mom|mov|msd|mtn|mtr|nab|nba|nec|net|new|nfl|ngo|nhk|now|nra|nrw|ntt|nyc|obi|one|ong|onl|ooo|org|ott|ovh|pay|pet|phd|pid|pin|pnc|pro|pru|pub|pwc|red|ren|ril|rio|rip|run|rwe|sap|sas|sbi|sbs|sca|scb|ses|sew|sex|sfr|ski|sky|soy|spa|srl|stc|tab|tax|tci|tdk|tel|thd|tjx|top|trv|tui|tvs|ubs|uno|uol|ups|vet|vig|vin|vip|wed|win|wme|wow|wtc|wtf|xin|xxx|xyz|you|yun|zip|бел|ком|қаз|мкд|мон|орг|рус|срб|укр|հայ|קום|عرب|قطر|كوم|مصر|कॉम|नेट|คอม|ไทย|ລາວ|ストア|セール|みんな|中文网|亚马逊|天主教|我爱你|新加坡|淡马锡|诺基亚|飞利浦|ac|ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw|ελ|ευ|бг|ею|рф|გე|닷넷|닷컴|삼성|한국|コム|世界|中信|中国|中國|企业|佛山|信息|健康|八卦|公司|公益|台湾|台灣|商城|商店|商标|嘉里|在线|大拿|娱乐|家電|广东|微博|慈善|手机|招聘|政务|政府|新闻|时尚|書籍|机构|游戏|澳門|点看|移动|网址|网店|网站|网络|联通|谷歌|购物|通販|集团|食品|餐厅|香港)';
  1109. var tldRegex = new RegExp('^' + tldRegexStr + '$');
  1110. /**
  1111. * The set of characters that will start a URL suffix (i.e. the path, query, and
  1112. * hash part of the URL)
  1113. */
  1114. var urlSuffixStartCharsRe = /[\/?#]/;
  1115. /**
  1116. * The set of characters that are allowed in the URL suffix (i.e. the path,
  1117. * query, and hash part of the URL) which may also form the ending character of
  1118. * the URL.
  1119. *
  1120. * The {@link #urlSuffixNotAllowedAsLastCharRe} are additional allowed URL
  1121. * suffix characters, but (generally) should not be the last character of a URL.
  1122. */
  1123. var urlSuffixAllowedSpecialCharsRe = /[-+&@#/%=~_()|'$*\[\]{}\u2713]/;
  1124. /**
  1125. * URL suffix characters (i.e. path, query, and has part of the URL) that are
  1126. * not allowed as the *last character* in the URL suffix as they would normally
  1127. * form the end of a sentence.
  1128. *
  1129. * The {@link #urlSuffixAllowedSpecialCharsRe} contains additional allowed URL
  1130. * suffix characters which are allowed as the last character.
  1131. */
  1132. var urlSuffixNotAllowedAsLastCharRe = /[?!:,.;^]/;
  1133. /**
  1134. * Regular expression to match an http:// or https:// scheme.
  1135. */
  1136. var httpSchemeRe = /https?:\/\//i;
  1137. /**
  1138. * Regular expression to match an http:// or https:// scheme as the prefix of
  1139. * a string.
  1140. */
  1141. var httpSchemePrefixRe = new RegExp('^' + httpSchemeRe.source, 'i');
  1142. var urlSuffixedCharsNotAllowedAtEndRe = new RegExp(urlSuffixNotAllowedAsLastCharRe.source + '$');
  1143. /**
  1144. * A regular expression used to determine the schemes we should not autolink
  1145. */
  1146. var invalidSchemeRe = /^(javascript|vbscript):/i;
  1147. // A regular expression used to determine if the URL is a scheme match (such as
  1148. // 'http://google.com', and as opposed to a "TLD match"). This regular
  1149. // expression is used to parse out the host along with if the URL has an
  1150. // authority component (i.e. '//')
  1151. //
  1152. // Capturing groups:
  1153. // 1. '//' if the URL has an authority component, empty string otherwise
  1154. // 2. The host (if one exists). Ex: 'google.com'
  1155. //
  1156. // See https://www.rfc-editor.org/rfc/rfc3986#appendix-A for terminology
  1157. var schemeUrlRe = /^[A-Za-z][-.+A-Za-z0-9]*:(\/\/)?([^:/]*)/;
  1158. // A regular expression used to determine if the URL is a TLD match (such as
  1159. // 'google.com', and as opposed to a "scheme match"). This regular
  1160. // expression is used to help parse out the TLD (top-level domain) of the host.
  1161. //
  1162. // See https://www.rfc-editor.org/rfc/rfc3986#appendix-A for terminology
  1163. var tldUrlHostRe = /^(?:\/\/)?([^/#?:]+)/; // optionally prefixed with protocol-relative '//' chars
  1164. /**
  1165. * Determines if the given character may start a scheme (ex: 'http').
  1166. */
  1167. function isSchemeStartChar(char) {
  1168. return letterRe.test(char);
  1169. }
  1170. /**
  1171. * Determines if the given character is a valid character in a scheme (such as
  1172. * 'http' or 'ssh+git'), but only after the start char (which is handled by
  1173. * {@link isSchemeStartChar}.
  1174. */
  1175. function isSchemeChar(char) {
  1176. return (letterRe.test(char) || digitRe.test(char) || char === '+' || char === '-' || char === '.');
  1177. }
  1178. /**
  1179. * Determines if the character can begin a domain label, which must be an
  1180. * alphanumeric character and not an underscore or dash.
  1181. *
  1182. * A domain label is a segment of a hostname such as subdomain.google.com.
  1183. */
  1184. function isDomainLabelStartChar(char) {
  1185. return alphaNumericAndMarksRe.test(char);
  1186. }
  1187. /**
  1188. * Determines if the character is part of a domain label (but not a domain label
  1189. * start character).
  1190. *
  1191. * A domain label is a segment of a hostname such as subdomain.google.com.
  1192. */
  1193. function isDomainLabelChar(char) {
  1194. return char === '_' || isDomainLabelStartChar(char);
  1195. }
  1196. /**
  1197. * Determines if the character is a path character ("pchar") as defined by
  1198. * https://tools.ietf.org/html/rfc3986#appendix-A
  1199. *
  1200. * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
  1201. *
  1202. * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  1203. * pct-encoded = "%" HEXDIG HEXDIG
  1204. * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
  1205. * / "*" / "+" / "," / ";" / "="
  1206. *
  1207. * Note that this implementation doesn't follow the spec exactly, but rather
  1208. * follows URL path characters found out in the wild (spec might be out of date?)
  1209. */
  1210. function isPathChar(char) {
  1211. return (alphaNumericAndMarksRe.test(char) ||
  1212. urlSuffixAllowedSpecialCharsRe.test(char) ||
  1213. urlSuffixNotAllowedAsLastCharRe.test(char));
  1214. }
  1215. /**
  1216. * Determines if the character given may begin the "URL Suffix" section of a
  1217. * URI (i.e. the path, query, or hash section). These are the '/', '?' and '#'
  1218. * characters.
  1219. *
  1220. * See https://tools.ietf.org/html/rfc3986#appendix-A
  1221. */
  1222. function isUrlSuffixStartChar(char) {
  1223. return urlSuffixStartCharsRe.test(char);
  1224. }
  1225. /**
  1226. * Determines if the TLD read in the host is a known TLD (Top-Level Domain).
  1227. *
  1228. * Example: 'com' would be a known TLD (for a host of 'google.com'), but
  1229. * 'local' would not (for a domain name of 'my-computer.local').
  1230. */
  1231. function isKnownTld(tld) {
  1232. return tldRegex.test(tld.toLowerCase()); // make sure the tld is lowercase for the regex
  1233. }
  1234. /**
  1235. * Determines if the given `url` is a valid scheme-prefixed URL.
  1236. */
  1237. function isValidSchemeUrl(url) {
  1238. // If the scheme is 'javascript:' or 'vbscript:', these link
  1239. // types can be dangerous. Don't link them.
  1240. if (invalidSchemeRe.test(url)) {
  1241. return false;
  1242. }
  1243. var schemeMatch = url.match(schemeUrlRe);
  1244. if (!schemeMatch) {
  1245. return false;
  1246. }
  1247. var isAuthorityMatch = !!schemeMatch[1];
  1248. var host = schemeMatch[2];
  1249. if (isAuthorityMatch) {
  1250. // Any match that has an authority ('//' chars) after the scheme is
  1251. // valid, such as 'http://anything'
  1252. return true;
  1253. }
  1254. // If there's no authority ('//' chars), check that we have a hostname
  1255. // that looks valid.
  1256. //
  1257. // The host must contain at least one '.' char and have a domain label
  1258. // with at least one letter to be considered valid.
  1259. //
  1260. // Accept:
  1261. // - git:domain.com (scheme followed by a host
  1262. // Do not accept:
  1263. // - git:something ('something' doesn't look like a host)
  1264. // - version:1.0 ('1.0' doesn't look like a host)
  1265. if (host.indexOf('.') === -1 || !letterRe.test(host)) {
  1266. return false;
  1267. }
  1268. return true;
  1269. }
  1270. /**
  1271. * Determines if the given `url` is a match with a valid TLD.
  1272. */
  1273. function isValidTldMatch(url) {
  1274. // TLD URL such as 'google.com', we need to confirm that we have a valid
  1275. // top-level domain
  1276. var tldUrlHostMatch = url.match(tldUrlHostRe);
  1277. if (!tldUrlHostMatch) {
  1278. // At this point, if the URL didn't match our TLD re, it must be invalid
  1279. // (highly unlikely to happen, but just in case)
  1280. return false;
  1281. }
  1282. var host = tldUrlHostMatch[0];
  1283. var hostLabels = host.split('.');
  1284. if (hostLabels.length < 2) {
  1285. // 0 or 1 host label, there's no TLD. Ex: 'localhost'
  1286. return false;
  1287. }
  1288. var tld = hostLabels[hostLabels.length - 1];
  1289. if (!isKnownTld(tld)) {
  1290. return false;
  1291. }
  1292. // TODO: Implement these conditions for TLD matcher:
  1293. // (
  1294. // this.longestDomainLabelLength <= 63 &&
  1295. // this.domainNameLength <= 255
  1296. // );
  1297. return true;
  1298. }
  1299. // Regular expression to confirm a valid IPv4 address (ex: '192.168.0.1')
  1300. 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]?)$/;
  1301. // Regular expression used to split the IPv4 address itself from any port/path/query/hash
  1302. var ipV4PartRe = /[:/?#]/;
  1303. /**
  1304. * Determines if the given URL is a valid IPv4-prefixed URL.
  1305. */
  1306. function isValidIpV4Address(url) {
  1307. // Grab just the IP address
  1308. var ipV4Part = url.split(ipV4PartRe, 1)[0]; // only 1 result needed
  1309. return ipV4Re.test(ipV4Part);
  1310. }
  1311. /**
  1312. * A regular expression used to remove the 'www.' from URLs.
  1313. */
  1314. var wwwPrefixRegex = /^(https?:\/\/)?(www\.)?/i;
  1315. /**
  1316. * The regular expression used to remove the protocol-relative '//' from a URL
  1317. * string, for purposes of formatting the anchor text. A protocol-relative URL
  1318. * is, for example, "//yahoo.com"
  1319. */
  1320. var protocolRelativeRegex = /^\/\//;
  1321. /**
  1322. * @class Autolinker.match.Url
  1323. * @extends Autolinker.match.AbstractMatch
  1324. *
  1325. * Represents a Url match found in an input string which should be Autolinked.
  1326. *
  1327. * See this class's superclass ({@link Autolinker.match.Match}) for more details.
  1328. */
  1329. var UrlMatch = /** @class */ (function (_super) {
  1330. __extends(UrlMatch, _super);
  1331. /**
  1332. * @method constructor
  1333. * @param {Object} cfg The configuration properties for the Match
  1334. * instance, specified in an Object (map).
  1335. */
  1336. function UrlMatch(cfg) {
  1337. var _this = _super.call(this, cfg) || this;
  1338. /**
  1339. * @public
  1340. * @property {'url'} type
  1341. *
  1342. * A string name for the type of match that this class represents. Can be
  1343. * used in a TypeScript discriminating union to type-narrow from the
  1344. * `Match` type.
  1345. */
  1346. _this.type = 'url';
  1347. /**
  1348. * @cfg {String} url (required)
  1349. *
  1350. * The url that was matched.
  1351. */
  1352. _this.url = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  1353. /**
  1354. * @cfg {"scheme"/"www"/"tld"} urlMatchType (required)
  1355. *
  1356. * The type of URL match that this class represents. This helps to determine
  1357. * if the match was made in the original text with a prefixed scheme (ex:
  1358. * 'http://www.google.com'), a prefixed 'www' (ex: 'www.google.com'), or
  1359. * was matched by a known top-level domain (ex: 'google.com').
  1360. */
  1361. _this.urlMatchType = 'scheme'; // default value just to get the above doc comment in the ES5 output and documentation generator
  1362. /**
  1363. * @cfg {Boolean} protocolRelativeMatch (required)
  1364. *
  1365. * `true` if the URL is a protocol-relative match. A protocol-relative match
  1366. * is a URL that starts with '//', and will be either http:// or https://
  1367. * based on the protocol that the site is loaded under.
  1368. */
  1369. _this.protocolRelativeMatch = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  1370. /**
  1371. * @cfg {Object} stripPrefix (required)
  1372. *
  1373. * The Object form of {@link Autolinker#cfg-stripPrefix}.
  1374. */
  1375. _this.stripPrefix = {
  1376. scheme: true,
  1377. www: true,
  1378. }; // default value just to get the above doc comment in the ES5 output and documentation generator
  1379. /**
  1380. * @cfg {Boolean} stripTrailingSlash (required)
  1381. * @inheritdoc Autolinker#cfg-stripTrailingSlash
  1382. */
  1383. _this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  1384. /**
  1385. * @cfg {Boolean} decodePercentEncoding (required)
  1386. * @inheritdoc Autolinker#cfg-decodePercentEncoding
  1387. */
  1388. _this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  1389. /**
  1390. * @private
  1391. * @property {Boolean} protocolPrepended
  1392. *
  1393. * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the
  1394. * {@link #url} did not have a protocol)
  1395. */
  1396. _this.protocolPrepended = false;
  1397. _this.urlMatchType = cfg.urlMatchType;
  1398. _this.url = cfg.url;
  1399. _this.protocolRelativeMatch = cfg.protocolRelativeMatch;
  1400. _this.stripPrefix = cfg.stripPrefix;
  1401. _this.stripTrailingSlash = cfg.stripTrailingSlash;
  1402. _this.decodePercentEncoding = cfg.decodePercentEncoding;
  1403. return _this;
  1404. }
  1405. /**
  1406. * Returns a string name for the type of match that this class represents.
  1407. * For the case of UrlMatch, returns 'url'.
  1408. *
  1409. * @return {String}
  1410. */
  1411. UrlMatch.prototype.getType = function () {
  1412. return 'url';
  1413. };
  1414. /**
  1415. * Returns a string name for the type of URL match that this class
  1416. * represents.
  1417. *
  1418. * This helps to determine if the match was made in the original text with a
  1419. * prefixed scheme (ex: 'http://www.google.com'), a prefixed 'www' (ex:
  1420. * 'www.google.com'), or was matched by a known top-level domain (ex:
  1421. * 'google.com').
  1422. *
  1423. * @return {"scheme"/"www"/"tld"}
  1424. */
  1425. UrlMatch.prototype.getUrlMatchType = function () {
  1426. return this.urlMatchType;
  1427. };
  1428. /**
  1429. * Returns the url that was matched, assuming the protocol to be 'http://' if the original
  1430. * match was missing a protocol.
  1431. *
  1432. * @return {String}
  1433. */
  1434. UrlMatch.prototype.getUrl = function () {
  1435. var url = this.url;
  1436. // if the url string doesn't begin with a scheme, assume 'http://'
  1437. if (!this.protocolRelativeMatch &&
  1438. this.urlMatchType !== 'scheme' &&
  1439. !this.protocolPrepended) {
  1440. url = this.url = 'http://' + url;
  1441. this.protocolPrepended = true;
  1442. }
  1443. return url;
  1444. };
  1445. /**
  1446. * Returns the anchor href that should be generated for the match.
  1447. *
  1448. * @return {String}
  1449. */
  1450. UrlMatch.prototype.getAnchorHref = function () {
  1451. var url = this.getUrl();
  1452. return url.replace(/&amp;/g, '&'); // any &amp;'s in the URL should be converted back to '&' if they were displayed as &amp; in the source html
  1453. };
  1454. /**
  1455. * Returns the anchor text that should be generated for the match.
  1456. *
  1457. * @return {String}
  1458. */
  1459. UrlMatch.prototype.getAnchorText = function () {
  1460. var anchorText = this.getMatchedText();
  1461. if (this.protocolRelativeMatch) {
  1462. // Strip off any protocol-relative '//' from the anchor text
  1463. anchorText = stripProtocolRelativePrefix(anchorText);
  1464. }
  1465. if (this.stripPrefix.scheme) {
  1466. anchorText = stripSchemePrefix(anchorText);
  1467. }
  1468. if (this.stripPrefix.www) {
  1469. anchorText = stripWwwPrefix(anchorText);
  1470. }
  1471. if (this.stripTrailingSlash) {
  1472. anchorText = removeTrailingSlash(anchorText); // remove trailing slash, if there is one
  1473. }
  1474. if (this.decodePercentEncoding) {
  1475. anchorText = removePercentEncoding(anchorText);
  1476. }
  1477. return anchorText;
  1478. };
  1479. return UrlMatch;
  1480. }(AbstractMatch));
  1481. // Utility Functionality
  1482. /**
  1483. * Strips the scheme prefix (such as "http://" or "https://") from the given
  1484. * `url`.
  1485. *
  1486. * @private
  1487. * @param {String} url The text of the anchor that is being generated, for
  1488. * which to strip off the url scheme.
  1489. * @return {String} The `url`, with the scheme stripped.
  1490. */
  1491. function stripSchemePrefix(url) {
  1492. return url.replace(httpSchemePrefixRe, '');
  1493. }
  1494. /**
  1495. * Strips the 'www' prefix from the given `url`.
  1496. *
  1497. * @private
  1498. * @param {String} url The text of the anchor that is being generated, for
  1499. * which to strip off the 'www' if it exists.
  1500. * @return {String} The `url`, with the 'www' stripped.
  1501. */
  1502. function stripWwwPrefix(url) {
  1503. return url.replace(wwwPrefixRegex, '$1'); // leave any scheme ($1), it one exists
  1504. }
  1505. /**
  1506. * Strips any protocol-relative '//' from the anchor text.
  1507. *
  1508. * @private
  1509. * @param {String} text The text of the anchor that is being generated, for which to strip off the
  1510. * protocol-relative prefix (such as stripping off "//")
  1511. * @return {String} The `anchorText`, with the protocol-relative prefix stripped.
  1512. */
  1513. function stripProtocolRelativePrefix(text) {
  1514. return text.replace(protocolRelativeRegex, '');
  1515. }
  1516. /**
  1517. * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed.
  1518. *
  1519. * @private
  1520. * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing
  1521. * slash ('/') that may exist.
  1522. * @return {String} The `anchorText`, with the trailing slash removed.
  1523. */
  1524. function removeTrailingSlash(anchorText) {
  1525. if (anchorText.charAt(anchorText.length - 1) === '/') {
  1526. anchorText = anchorText.slice(0, -1);
  1527. }
  1528. return anchorText;
  1529. }
  1530. /**
  1531. * Decodes percent-encoded characters from the given `anchorText`, in
  1532. * preparation for the text to be displayed.
  1533. *
  1534. * @private
  1535. * @param {String} anchorText The text of the anchor that is being
  1536. * generated, for which to decode any percent-encoded characters.
  1537. * @return {String} The `anchorText`, with the percent-encoded characters
  1538. * decoded.
  1539. */
  1540. function removePercentEncoding(anchorText) {
  1541. // First, convert a few of the known % encodings to the corresponding
  1542. // HTML entities that could accidentally be interpretted as special
  1543. // HTML characters
  1544. var preProcessedEntityAnchorText = anchorText
  1545. .replace(/%22/gi, '&quot;') // " char
  1546. .replace(/%26/gi, '&amp;') // & char
  1547. .replace(/%27/gi, '&#39;') // ' char
  1548. .replace(/%3C/gi, '&lt;') // < char
  1549. .replace(/%3E/gi, '&gt;'); // > char
  1550. try {
  1551. // Now attempt to decode the rest of the anchor text
  1552. return decodeURIComponent(preProcessedEntityAnchorText);
  1553. }
  1554. catch (e) {
  1555. // Invalid % escape sequence in the anchor text
  1556. return preProcessedEntityAnchorText;
  1557. }
  1558. }
  1559. /**
  1560. * A regular expression to match a 'mailto:' prefix on an email address.
  1561. */
  1562. var mailtoSchemePrefixRe = /^mailto:/i;
  1563. /**
  1564. * Regular expression for all of the valid characters of the local part of an
  1565. * email address.
  1566. */
  1567. var emailLocalPartCharRegex = new RegExp("[".concat(alphaNumericAndMarksCharsStr, "!#$%&'*+/=?^_`{|}~-]"));
  1568. /**
  1569. * Determines if the given character may start the "local part" of an email
  1570. * address. The local part is the part to the left of the '@' sign.
  1571. *
  1572. * Technically according to the email spec, any of the characters in the
  1573. * {@link emailLocalPartCharRegex} can start an email address (including any of
  1574. * the special characters), but this is so rare in the wild and the
  1575. * implementation is much simpler by only starting an email address with a word
  1576. * character. This is especially important when matching the '{' character which
  1577. * generally starts a brace that isn't part of the email address.
  1578. */
  1579. function isEmailLocalPartStartChar(char) {
  1580. return alphaNumericAndMarksRe.test(char);
  1581. }
  1582. /**
  1583. * Determines if the given character can be part of the "local part" of an email
  1584. * address. The local part is the part to the left of the '@' sign.
  1585. */
  1586. function isEmailLocalPartChar(char) {
  1587. return emailLocalPartCharRegex.test(char);
  1588. }
  1589. /**
  1590. * Determines if the given email address is valid. We consider it valid if it
  1591. * has a valid TLD in its host.
  1592. *
  1593. * @param emailAddress email address
  1594. * @return true is email have valid TLD, false otherwise
  1595. */
  1596. function isValidEmail(emailAddress) {
  1597. var emailAddressTld = emailAddress.split('.').pop() || '';
  1598. return isKnownTld(emailAddressTld);
  1599. }
  1600. /**
  1601. * @class Autolinker.match.Email
  1602. * @extends Autolinker.match.AbstractMatch
  1603. *
  1604. * Represents a Email match found in an input string which should be Autolinked.
  1605. *
  1606. * See this class's superclass ({@link Autolinker.match.Match}) for more details.
  1607. */
  1608. var EmailMatch = /** @class */ (function (_super) {
  1609. __extends(EmailMatch, _super);
  1610. /**
  1611. * @method constructor
  1612. * @param {Object} cfg The configuration properties for the Match
  1613. * instance, specified in an Object (map).
  1614. */
  1615. function EmailMatch(cfg) {
  1616. var _this = _super.call(this, cfg) || this;
  1617. /**
  1618. * @public
  1619. * @property {'email'} type
  1620. *
  1621. * A string name for the type of match that this class represents. Can be
  1622. * used in a TypeScript discriminating union to type-narrow from the
  1623. * `Match` type.
  1624. */
  1625. _this.type = 'email';
  1626. /**
  1627. * @cfg {String} email (required)
  1628. *
  1629. * The email address that was matched.
  1630. */
  1631. _this.email = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  1632. _this.email = cfg.email;
  1633. return _this;
  1634. }
  1635. /**
  1636. * Returns a string name for the type of match that this class represents.
  1637. * For the case of EmailMatch, returns 'email'.
  1638. *
  1639. * @return {String}
  1640. */
  1641. EmailMatch.prototype.getType = function () {
  1642. return 'email';
  1643. };
  1644. /**
  1645. * Returns the email address that was matched.
  1646. *
  1647. * @return {String}
  1648. */
  1649. EmailMatch.prototype.getEmail = function () {
  1650. return this.email;
  1651. };
  1652. /**
  1653. * Returns the anchor href that should be generated for the match.
  1654. *
  1655. * @return {String}
  1656. */
  1657. EmailMatch.prototype.getAnchorHref = function () {
  1658. return 'mailto:' + this.email;
  1659. };
  1660. /**
  1661. * Returns the anchor text that should be generated for the match.
  1662. *
  1663. * @return {String}
  1664. */
  1665. EmailMatch.prototype.getAnchorText = function () {
  1666. return this.email;
  1667. };
  1668. return EmailMatch;
  1669. }(AbstractMatch));
  1670. /**
  1671. * Determines if the given `char` is a an allowed character in a hashtag. These
  1672. * are underscores or any alphanumeric char.
  1673. */
  1674. function isHashtagTextChar(char) {
  1675. return char === '_' || alphaNumericAndMarksRe.test(char);
  1676. }
  1677. /**
  1678. * Determines if a hashtag match is valid.
  1679. */
  1680. function isValidHashtag(hashtag) {
  1681. // Max length of 140 for a hashtag ('#' char + 139 word chars)
  1682. return hashtag.length <= 140;
  1683. }
  1684. var hashtagServices = ['twitter', 'facebook', 'instagram', 'tiktok'];
  1685. /**
  1686. * @class Autolinker.match.Hashtag
  1687. * @extends Autolinker.match.AbstractMatch
  1688. *
  1689. * Represents a Hashtag match found in an input string which should be
  1690. * Autolinked.
  1691. *
  1692. * See this class's superclass ({@link Autolinker.match.Match}) for more
  1693. * details.
  1694. */
  1695. var HashtagMatch = /** @class */ (function (_super) {
  1696. __extends(HashtagMatch, _super);
  1697. /**
  1698. * @method constructor
  1699. * @param {Object} cfg The configuration properties for the Match
  1700. * instance, specified in an Object (map).
  1701. */
  1702. function HashtagMatch(cfg) {
  1703. var _this = _super.call(this, cfg) || this;
  1704. /**
  1705. * @public
  1706. * @property {'hashtag'} type
  1707. *
  1708. * A string name for the type of match that this class represents. Can be
  1709. * used in a TypeScript discriminating union to type-narrow from the
  1710. * `Match` type.
  1711. */
  1712. _this.type = 'hashtag';
  1713. /**
  1714. * @cfg {String} serviceName
  1715. *
  1716. * The service to point hashtag matches to. See {@link Autolinker#hashtag}
  1717. * for available values.
  1718. */
  1719. _this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator
  1720. /**
  1721. * @cfg {String} hashtag (required)
  1722. *
  1723. * The HashtagMatch that was matched, without the '#'.
  1724. */
  1725. _this.hashtag = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  1726. _this.serviceName = cfg.serviceName;
  1727. _this.hashtag = cfg.hashtag;
  1728. return _this;
  1729. }
  1730. /**
  1731. * Returns a string name for the type of match that this class represents.
  1732. * For the case of HashtagMatch, returns 'hashtag'.
  1733. *
  1734. * @return {String}
  1735. */
  1736. HashtagMatch.prototype.getType = function () {
  1737. return 'hashtag';
  1738. };
  1739. /**
  1740. * Returns the configured {@link #serviceName} to point the HashtagMatch to.
  1741. * Ex: 'facebook', 'twitter'.
  1742. *
  1743. * @return {String}
  1744. */
  1745. HashtagMatch.prototype.getServiceName = function () {
  1746. return this.serviceName;
  1747. };
  1748. /**
  1749. * Returns the matched hashtag, without the '#' character.
  1750. *
  1751. * @return {String}
  1752. */
  1753. HashtagMatch.prototype.getHashtag = function () {
  1754. return this.hashtag;
  1755. };
  1756. /**
  1757. * Returns the anchor href that should be generated for the match.
  1758. *
  1759. * @return {String}
  1760. */
  1761. HashtagMatch.prototype.getAnchorHref = function () {
  1762. var serviceName = this.serviceName, hashtag = this.hashtag;
  1763. switch (serviceName) {
  1764. case 'twitter':
  1765. return 'https://twitter.com/hashtag/' + hashtag;
  1766. case 'facebook':
  1767. return 'https://www.facebook.com/hashtag/' + hashtag;
  1768. case 'instagram':
  1769. return 'https://instagram.com/explore/tags/' + hashtag;
  1770. case 'tiktok':
  1771. return 'https://www.tiktok.com/tag/' + hashtag;
  1772. default:
  1773. // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case
  1774. assertNever(serviceName);
  1775. throw new Error("Invalid hashtag service: ".concat(serviceName));
  1776. }
  1777. };
  1778. /**
  1779. * Returns the anchor text that should be generated for the match.
  1780. *
  1781. * @return {String}
  1782. */
  1783. HashtagMatch.prototype.getAnchorText = function () {
  1784. return '#' + this.hashtag;
  1785. };
  1786. /**
  1787. * Returns the CSS class suffixes that should be used on a tag built with
  1788. * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for
  1789. * details.
  1790. *
  1791. * @return {String[]}
  1792. */
  1793. HashtagMatch.prototype.getCssClassSuffixes = function () {
  1794. var cssClassSuffixes = _super.prototype.getCssClassSuffixes.call(this), serviceName = this.getServiceName();
  1795. if (serviceName) {
  1796. cssClassSuffixes.push(serviceName);
  1797. }
  1798. return cssClassSuffixes;
  1799. };
  1800. return HashtagMatch;
  1801. }(AbstractMatch));
  1802. var mentionRegexes = {
  1803. twitter: /^@\w{1,15}$/,
  1804. instagram: /^@[_\w]{1,30}$/,
  1805. soundcloud: /^@[-a-z0-9_]{3,25}$/,
  1806. // TikTok usernames are 1-24 characters containing letters, numbers, underscores
  1807. // and periods, but cannot end in a period: https://support.tiktok.com/en/getting-started/setting-up-your-profile/changing-your-username
  1808. tiktok: /^@[.\w]{1,23}[\w]$/,
  1809. };
  1810. // Regex that allows for all possible mention characters for any service. We'll
  1811. // confirm the match based on the user-configured service name after a match is
  1812. // found.
  1813. var mentionTextCharRe = /[-\w.]/;
  1814. /**
  1815. * Determines if the given character can be part of a mention's text characters.
  1816. */
  1817. function isMentionTextChar(char) {
  1818. return mentionTextCharRe.test(char);
  1819. }
  1820. /**
  1821. * Determines if the given `mention` text is valid.
  1822. */
  1823. function isValidMention(mention, serviceName) {
  1824. var re = mentionRegexes[serviceName];
  1825. return re.test(mention);
  1826. }
  1827. var mentionServices = ['twitter', 'instagram', 'soundcloud', 'tiktok'];
  1828. /**
  1829. * @class Autolinker.match.Mention
  1830. * @extends Autolinker.match.AbstractMatch
  1831. *
  1832. * Represents a Mention match found in an input string which should be Autolinked.
  1833. *
  1834. * See this class's superclass ({@link Autolinker.match.Match}) for more details.
  1835. */
  1836. var MentionMatch = /** @class */ (function (_super) {
  1837. __extends(MentionMatch, _super);
  1838. /**
  1839. * @method constructor
  1840. * @param {Object} cfg The configuration properties for the Match
  1841. * instance, specified in an Object (map).
  1842. */
  1843. function MentionMatch(cfg) {
  1844. var _this = _super.call(this, cfg) || this;
  1845. /**
  1846. * @public
  1847. * @property {'mention'} type
  1848. *
  1849. * A string name for the type of match that this class represents. Can be
  1850. * used in a TypeScript discriminating union to type-narrow from the
  1851. * `Match` type.
  1852. */
  1853. _this.type = 'mention';
  1854. /**
  1855. * @cfg {String} serviceName
  1856. *
  1857. * The service to point mention matches to. See {@link Autolinker#mention}
  1858. * for available values.
  1859. */
  1860. _this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator
  1861. /**
  1862. * @cfg {String} mention (required)
  1863. *
  1864. * The Mention that was matched, without the '@' character.
  1865. */
  1866. _this.mention = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  1867. _this.mention = cfg.mention;
  1868. _this.serviceName = cfg.serviceName;
  1869. return _this;
  1870. }
  1871. /**
  1872. * Returns a string name for the type of match that this class represents.
  1873. * For the case of MentionMatch, returns 'mention'.
  1874. *
  1875. * @return {String}
  1876. */
  1877. MentionMatch.prototype.getType = function () {
  1878. return 'mention';
  1879. };
  1880. /**
  1881. * Returns the mention, without the '@' character.
  1882. *
  1883. * @return {String}
  1884. */
  1885. MentionMatch.prototype.getMention = function () {
  1886. return this.mention;
  1887. };
  1888. /**
  1889. * Returns the configured {@link #serviceName} to point the mention to.
  1890. * Ex: 'instagram', 'twitter', 'soundcloud'.
  1891. *
  1892. * @return {String}
  1893. */
  1894. MentionMatch.prototype.getServiceName = function () {
  1895. return this.serviceName;
  1896. };
  1897. /**
  1898. * Returns the anchor href that should be generated for the match.
  1899. *
  1900. * @return {String}
  1901. */
  1902. MentionMatch.prototype.getAnchorHref = function () {
  1903. switch (this.serviceName) {
  1904. case 'twitter':
  1905. return 'https://twitter.com/' + this.mention;
  1906. case 'instagram':
  1907. return 'https://instagram.com/' + this.mention;
  1908. case 'soundcloud':
  1909. return 'https://soundcloud.com/' + this.mention;
  1910. case 'tiktok':
  1911. return 'https://www.tiktok.com/@' + this.mention;
  1912. default:
  1913. // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case.
  1914. throw new Error('Unknown service name to point mention to: ' + this.serviceName);
  1915. }
  1916. };
  1917. /**
  1918. * Returns the anchor text that should be generated for the match.
  1919. *
  1920. * @return {String}
  1921. */
  1922. MentionMatch.prototype.getAnchorText = function () {
  1923. return '@' + this.mention;
  1924. };
  1925. /**
  1926. * Returns the CSS class suffixes that should be used on a tag built with
  1927. * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for
  1928. * details.
  1929. *
  1930. * @return {String[]}
  1931. */
  1932. MentionMatch.prototype.getCssClassSuffixes = function () {
  1933. var cssClassSuffixes = _super.prototype.getCssClassSuffixes.call(this), serviceName = this.getServiceName();
  1934. if (serviceName) {
  1935. cssClassSuffixes.push(serviceName);
  1936. }
  1937. return cssClassSuffixes;
  1938. };
  1939. return MentionMatch;
  1940. }(AbstractMatch));
  1941. // Regex that holds the characters used to separate segments of a phone number
  1942. var separatorCharRe = /[-. ]/;
  1943. // Regex that specifies any delimiter char that allows us to treat the number as
  1944. // a phone number rather than just any other number that could appear in text.
  1945. var hasDelimCharsRe = /[-. ()]/;
  1946. // "Pause" and "Wait" control chars
  1947. var controlCharRe = /[,;]/;
  1948. // Over the years, many people have added to this regex, but it should have been
  1949. // split up by country. Maybe one day we can break this down.
  1950. 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]+#?)*/;
  1951. // Regex for Japanese phone numbers
  1952. 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})/;
  1953. // Combined regex
  1954. var validPhoneNumberRe = new RegExp("^".concat(mostPhoneNumbers.source, "|").concat(japanesePhoneRe.source, "$"));
  1955. /**
  1956. * Determines if the character is a phone number separator character (i.e.
  1957. * '-', '.', or ' ' (space))
  1958. */
  1959. function isPhoneNumberSeparatorChar(char) {
  1960. return separatorCharRe.test(char);
  1961. }
  1962. /**
  1963. * Determines if the character is a control character in a phone number. Control
  1964. * characters are as follows:
  1965. *
  1966. * - ',': A 1 second pause. Useful for dialing extensions once the main phone number has been reached
  1967. * - ';': A "wait" that waits for the user to take action (tap something, for instance on a smart phone)
  1968. */
  1969. function isPhoneNumberControlChar(char) {
  1970. return controlCharRe.test(char);
  1971. }
  1972. /**
  1973. * Determines if the given phone number text found in a string is a valid phone
  1974. * number.
  1975. *
  1976. * Our state machine parser is simplified to grab anything that looks like a
  1977. * phone number, and this function confirms the match.
  1978. */
  1979. function isValidPhoneNumber(phoneNumberText) {
  1980. // We'll only consider the match as a phone number if there is some kind of
  1981. // delimiter character (a prefixed '+' sign, or separator chars).
  1982. //
  1983. // Accepts:
  1984. // (123) 456-7890
  1985. // +38755233976
  1986. // Does not accept:
  1987. // 1234567890 (no delimiter chars - may just be a random number that's not a phone number)
  1988. var hasDelimiters = phoneNumberText.charAt(0) === '+' || hasDelimCharsRe.test(phoneNumberText);
  1989. return hasDelimiters && validPhoneNumberRe.test(phoneNumberText);
  1990. }
  1991. /**
  1992. * @class Autolinker.match.Phone
  1993. * @extends Autolinker.match.AbstractMatch
  1994. *
  1995. * Represents a Phone number match found in an input string which should be
  1996. * Autolinked.
  1997. *
  1998. * See this class's superclass ({@link Autolinker.match.Match}) for more
  1999. * details.
  2000. */
  2001. var PhoneMatch = /** @class */ (function (_super) {
  2002. __extends(PhoneMatch, _super);
  2003. /**
  2004. * @method constructor
  2005. * @param {Object} cfg The configuration properties for the Match
  2006. * instance, specified in an Object (map).
  2007. */
  2008. function PhoneMatch(cfg) {
  2009. var _this = _super.call(this, cfg) || this;
  2010. /**
  2011. * @public
  2012. * @property {'phone'} type
  2013. *
  2014. * A string name for the type of match that this class represents. Can be
  2015. * used in a TypeScript discriminating union to type-narrow from the
  2016. * `Match` type.
  2017. */
  2018. _this.type = 'phone';
  2019. /**
  2020. * @protected
  2021. * @property {String} number (required)
  2022. *
  2023. * The phone number that was matched, without any delimiter characters.
  2024. *
  2025. * Note: This is a string to allow for prefixed 0's.
  2026. */
  2027. _this.number = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  2028. /**
  2029. * @protected
  2030. * @property {Boolean} plusSign (required)
  2031. *
  2032. * `true` if the matched phone number started with a '+' sign. We'll include
  2033. * it in the `tel:` URL if so, as this is needed for international numbers.
  2034. *
  2035. * Ex: '+1 (123) 456 7879'
  2036. */
  2037. _this.plusSign = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  2038. _this.number = cfg.number;
  2039. _this.plusSign = cfg.plusSign;
  2040. return _this;
  2041. }
  2042. /**
  2043. * Returns a string name for the type of match that this class represents.
  2044. * For the case of PhoneMatch, returns 'phone'.
  2045. *
  2046. * @return {String}
  2047. */
  2048. PhoneMatch.prototype.getType = function () {
  2049. return 'phone';
  2050. };
  2051. /**
  2052. * Returns the phone number that was matched as a string, without any
  2053. * delimiter characters.
  2054. *
  2055. * Note: This is a string to allow for prefixed 0's.
  2056. *
  2057. * @return {String}
  2058. */
  2059. PhoneMatch.prototype.getPhoneNumber = function () {
  2060. return this.number;
  2061. };
  2062. /**
  2063. * Alias of {@link #getPhoneNumber}, returns the phone number that was
  2064. * matched as a string, without any delimiter characters.
  2065. *
  2066. * Note: This is a string to allow for prefixed 0's.
  2067. *
  2068. * @return {String}
  2069. */
  2070. PhoneMatch.prototype.getNumber = function () {
  2071. return this.getPhoneNumber();
  2072. };
  2073. /**
  2074. * Returns the anchor href that should be generated for the match.
  2075. *
  2076. * @return {String}
  2077. */
  2078. PhoneMatch.prototype.getAnchorHref = function () {
  2079. return 'tel:' + (this.plusSign ? '+' : '') + this.number;
  2080. };
  2081. /**
  2082. * Returns the anchor text that should be generated for the match.
  2083. *
  2084. * @return {String}
  2085. */
  2086. PhoneMatch.prototype.getAnchorText = function () {
  2087. return this.matchedText;
  2088. };
  2089. return PhoneMatch;
  2090. }(AbstractMatch));
  2091. // For debugging: search for and uncomment other "For debugging" lines
  2092. // import CliTable from 'cli-table';
  2093. /**
  2094. * Parses URL, email, twitter, mention, and hashtag matches from the given
  2095. * `text`.
  2096. */
  2097. function parseMatches(text, args) {
  2098. var tagBuilder = args.tagBuilder;
  2099. var stripPrefix = args.stripPrefix;
  2100. var stripTrailingSlash = args.stripTrailingSlash;
  2101. var decodePercentEncoding = args.decodePercentEncoding;
  2102. var hashtagServiceName = args.hashtagServiceName;
  2103. var mentionServiceName = args.mentionServiceName;
  2104. var matches = [];
  2105. var textLen = text.length;
  2106. // An array of all active state machines. Empty array means we're in the
  2107. // "no url" state
  2108. var stateMachines = [];
  2109. // For debugging: search for and uncomment other "For debugging" lines
  2110. // const table = new CliTable({
  2111. // head: ['charIdx', 'char', 'states', 'charIdx', 'startIdx', 'reached accept state'],
  2112. // });
  2113. var charIdx = 0;
  2114. for (; charIdx < textLen; charIdx++) {
  2115. var char = text.charAt(charIdx);
  2116. if (stateMachines.length === 0) {
  2117. stateNoMatch(char);
  2118. }
  2119. else {
  2120. // Must loop through the state machines backwards for when one
  2121. // is removed
  2122. for (var stateIdx = stateMachines.length - 1; stateIdx >= 0; stateIdx--) {
  2123. var stateMachine = stateMachines[stateIdx];
  2124. switch (stateMachine.state) {
  2125. // Protocol-relative URL states
  2126. case 11 /* ProtocolRelativeSlash1 */:
  2127. stateProtocolRelativeSlash1(stateMachine, char);
  2128. break;
  2129. case 12 /* ProtocolRelativeSlash2 */:
  2130. stateProtocolRelativeSlash2(stateMachine, char);
  2131. break;
  2132. case 0 /* SchemeChar */:
  2133. stateSchemeChar(stateMachine, char);
  2134. break;
  2135. case 1 /* SchemeHyphen */:
  2136. stateSchemeHyphen(stateMachine, char);
  2137. break;
  2138. case 2 /* SchemeColon */:
  2139. stateSchemeColon(stateMachine, char);
  2140. break;
  2141. case 3 /* SchemeSlash1 */:
  2142. stateSchemeSlash1(stateMachine, char);
  2143. break;
  2144. case 4 /* SchemeSlash2 */:
  2145. stateSchemeSlash2(stateMachine, char);
  2146. break;
  2147. case 5 /* DomainLabelChar */:
  2148. stateDomainLabelChar(stateMachine, char);
  2149. break;
  2150. case 6 /* DomainHyphen */:
  2151. stateDomainHyphen(stateMachine, char);
  2152. break;
  2153. case 7 /* DomainDot */:
  2154. stateDomainDot(stateMachine, char);
  2155. break;
  2156. case 13 /* IpV4Digit */:
  2157. stateIpV4Digit(stateMachine, char);
  2158. break;
  2159. case 14 /* IpV4Dot */:
  2160. stateIPv4Dot(stateMachine, char);
  2161. break;
  2162. case 8 /* PortColon */:
  2163. statePortColon(stateMachine, char);
  2164. break;
  2165. case 9 /* PortNumber */:
  2166. statePortNumber(stateMachine, char);
  2167. break;
  2168. case 10 /* Path */:
  2169. statePath(stateMachine, char);
  2170. break;
  2171. // Email States
  2172. case 15 /* EmailMailto_M */:
  2173. stateEmailMailto_M(stateMachine, char);
  2174. break;
  2175. case 16 /* EmailMailto_A */:
  2176. stateEmailMailto_A(stateMachine, char);
  2177. break;
  2178. case 17 /* EmailMailto_I */:
  2179. stateEmailMailto_I(stateMachine, char);
  2180. break;
  2181. case 18 /* EmailMailto_L */:
  2182. stateEmailMailto_L(stateMachine, char);
  2183. break;
  2184. case 19 /* EmailMailto_T */:
  2185. stateEmailMailto_T(stateMachine, char);
  2186. break;
  2187. case 20 /* EmailMailto_O */:
  2188. stateEmailMailto_O(stateMachine, char);
  2189. break;
  2190. case 21 /* EmailMailto_Colon */:
  2191. stateEmailMailtoColon(stateMachine, char);
  2192. break;
  2193. case 22 /* EmailLocalPart */:
  2194. stateEmailLocalPart(stateMachine, char);
  2195. break;
  2196. case 23 /* EmailLocalPartDot */:
  2197. stateEmailLocalPartDot(stateMachine, char);
  2198. break;
  2199. case 24 /* EmailAtSign */:
  2200. stateEmailAtSign(stateMachine, char);
  2201. break;
  2202. case 25 /* EmailDomainChar */:
  2203. stateEmailDomainChar(stateMachine, char);
  2204. break;
  2205. case 26 /* EmailDomainHyphen */:
  2206. stateEmailDomainHyphen(stateMachine, char);
  2207. break;
  2208. case 27 /* EmailDomainDot */:
  2209. stateEmailDomainDot(stateMachine, char);
  2210. break;
  2211. // Hashtag states
  2212. case 28 /* HashtagHashChar */:
  2213. stateHashtagHashChar(stateMachine, char);
  2214. break;
  2215. case 29 /* HashtagTextChar */:
  2216. stateHashtagTextChar(stateMachine, char);
  2217. break;
  2218. // Mention states
  2219. case 30 /* MentionAtChar */:
  2220. stateMentionAtChar(stateMachine, char);
  2221. break;
  2222. case 31 /* MentionTextChar */:
  2223. stateMentionTextChar(stateMachine, char);
  2224. break;
  2225. // Phone number states
  2226. case 32 /* PhoneNumberOpenParen */:
  2227. statePhoneNumberOpenParen(stateMachine, char);
  2228. break;
  2229. case 33 /* PhoneNumberAreaCodeDigit1 */:
  2230. statePhoneNumberAreaCodeDigit1(stateMachine, char);
  2231. break;
  2232. case 34 /* PhoneNumberAreaCodeDigit2 */:
  2233. statePhoneNumberAreaCodeDigit2(stateMachine, char);
  2234. break;
  2235. case 35 /* PhoneNumberAreaCodeDigit3 */:
  2236. statePhoneNumberAreaCodeDigit3(stateMachine, char);
  2237. break;
  2238. case 36 /* PhoneNumberCloseParen */:
  2239. statePhoneNumberCloseParen(stateMachine, char);
  2240. break;
  2241. case 37 /* PhoneNumberPlus */:
  2242. statePhoneNumberPlus(stateMachine, char);
  2243. break;
  2244. case 38 /* PhoneNumberDigit */:
  2245. statePhoneNumberDigit(stateMachine, char);
  2246. break;
  2247. case 39 /* PhoneNumberSeparator */:
  2248. statePhoneNumberSeparator(stateMachine, char);
  2249. break;
  2250. case 40 /* PhoneNumberControlChar */:
  2251. statePhoneNumberControlChar(stateMachine, char);
  2252. break;
  2253. case 41 /* PhoneNumberPoundChar */:
  2254. statePhoneNumberPoundChar(stateMachine, char);
  2255. break;
  2256. default:
  2257. assertNever(stateMachine.state);
  2258. }
  2259. }
  2260. }
  2261. // For debugging: search for and uncomment other "For debugging" lines
  2262. // table.push([
  2263. // charIdx,
  2264. // char,
  2265. // stateMachines.map(machine => State[machine.state]).join('\n') || '(none)',
  2266. // charIdx,
  2267. // stateMachines.map(m => m.startIdx).join('\n'),
  2268. // stateMachines.map(m => m.acceptStateReached).join('\n'),
  2269. // ]);
  2270. }
  2271. // Capture any valid match at the end of the string
  2272. // Note: this loop must happen in reverse because
  2273. // captureMatchIfValidAndRemove() removes state machines from the array
  2274. // and we'll end up skipping every other one if we remove while looping
  2275. // forward
  2276. for (var i = stateMachines.length - 1; i >= 0; i--) {
  2277. stateMachines.forEach(function (stateMachine) { return captureMatchIfValidAndRemove(stateMachine); });
  2278. }
  2279. // For debugging: search for and uncomment other "For debugging" lines
  2280. // console.log(`\nRead string:\n ${text}`);
  2281. // console.log(table.toString());
  2282. return matches;
  2283. // Handles the state when we're not in a URL/email/etc. (i.e. when no state machines exist)
  2284. function stateNoMatch(char) {
  2285. if (char === '#') {
  2286. // Hash char, start a Hashtag match
  2287. stateMachines.push(createHashtagStateMachine(charIdx, 28 /* HashtagHashChar */));
  2288. }
  2289. else if (char === '@') {
  2290. // '@' char, start a Mention match
  2291. stateMachines.push(createMentionStateMachine(charIdx, 30 /* MentionAtChar */));
  2292. }
  2293. else if (char === '/') {
  2294. // A slash could begin a protocol-relative URL
  2295. stateMachines.push(createTldUrlStateMachine(charIdx, 11 /* ProtocolRelativeSlash1 */));
  2296. }
  2297. else if (char === '+') {
  2298. // A '+' char can start a Phone number
  2299. stateMachines.push(createPhoneNumberStateMachine(charIdx, 37 /* PhoneNumberPlus */));
  2300. }
  2301. else if (char === '(') {
  2302. stateMachines.push(createPhoneNumberStateMachine(charIdx, 32 /* PhoneNumberOpenParen */));
  2303. }
  2304. else {
  2305. if (digitRe.test(char)) {
  2306. // A digit could start a phone number
  2307. stateMachines.push(createPhoneNumberStateMachine(charIdx, 38 /* PhoneNumberDigit */));
  2308. // A digit could start an IP address
  2309. stateMachines.push(createIpV4UrlStateMachine(charIdx, 13 /* IpV4Digit */));
  2310. }
  2311. if (isEmailLocalPartStartChar(char)) {
  2312. // Any email local part. An 'm' character in particular could
  2313. // start a 'mailto:' match
  2314. var startState = char.toLowerCase() === 'm' ? 15 /* EmailMailto_M */ : 22 /* EmailLocalPart */;
  2315. stateMachines.push(createEmailStateMachine(charIdx, startState));
  2316. }
  2317. if (isSchemeStartChar(char)) {
  2318. // An uppercase or lowercase letter may start a scheme match
  2319. stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
  2320. }
  2321. if (alphaNumericAndMarksRe.test(char)) {
  2322. // A unicode alpha character or digit could start a domain name
  2323. // label for a TLD match
  2324. stateMachines.push(createTldUrlStateMachine(charIdx, 5 /* DomainLabelChar */));
  2325. }
  2326. }
  2327. // Anything else, remain in the "non-url" state by not creating any
  2328. // state machines
  2329. }
  2330. // Implements ABNF: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
  2331. function stateSchemeChar(stateMachine, char) {
  2332. if (char === ':') {
  2333. stateMachine.state = 2 /* SchemeColon */;
  2334. }
  2335. else if (char === '-') {
  2336. stateMachine.state = 1 /* SchemeHyphen */;
  2337. }
  2338. else if (isSchemeChar(char)) ;
  2339. else {
  2340. // Any other character, not a scheme
  2341. remove(stateMachines, stateMachine);
  2342. }
  2343. }
  2344. function stateSchemeHyphen(stateMachine, char) {
  2345. if (char === '-') ;
  2346. else if (char === '/') {
  2347. // Not a valid scheme match, but may be the start of a
  2348. // protocol-relative match (such as //google.com)
  2349. remove(stateMachines, stateMachine);
  2350. stateMachines.push(createTldUrlStateMachine(charIdx, 11 /* ProtocolRelativeSlash1 */));
  2351. }
  2352. else if (isSchemeChar(char)) {
  2353. stateMachine.state = 0 /* SchemeChar */;
  2354. }
  2355. else {
  2356. // Any other character, not a scheme
  2357. remove(stateMachines, stateMachine);
  2358. }
  2359. }
  2360. function stateSchemeColon(stateMachine, char) {
  2361. if (char === '/') {
  2362. stateMachine.state = 3 /* SchemeSlash1 */;
  2363. }
  2364. else if (char === '.') {
  2365. // We've read something like 'hello:.' - don't capture
  2366. remove(stateMachines, stateMachine);
  2367. }
  2368. else if (isDomainLabelStartChar(char)) {
  2369. stateMachine.state = 5 /* DomainLabelChar */;
  2370. // It's possible that we read an "introduction" piece of text,
  2371. // and the character after the current colon actually starts an
  2372. // actual scheme. An example of this is:
  2373. // "The link:http://google.com"
  2374. // Hence, start a new machine to capture this match if so
  2375. if (isSchemeStartChar(char)) {
  2376. stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
  2377. }
  2378. }
  2379. else {
  2380. remove(stateMachines, stateMachine);
  2381. }
  2382. }
  2383. function stateSchemeSlash1(stateMachine, char) {
  2384. if (char === '/') {
  2385. stateMachine.state = 4 /* SchemeSlash2 */;
  2386. }
  2387. else if (isPathChar(char)) {
  2388. stateMachine.state = 10 /* Path */;
  2389. stateMachine.acceptStateReached = true;
  2390. }
  2391. else {
  2392. captureMatchIfValidAndRemove(stateMachine);
  2393. }
  2394. }
  2395. function stateSchemeSlash2(stateMachine, char) {
  2396. if (char === '/') {
  2397. // 3rd slash, must be an absolute path (path-absolute in the
  2398. // ABNF), such as in a file:///c:/windows/etc. See
  2399. // https://tools.ietf.org/html/rfc3986#appendix-A
  2400. stateMachine.state = 10 /* Path */;
  2401. }
  2402. else if (isDomainLabelStartChar(char)) {
  2403. // start of "authority" section - see https://tools.ietf.org/html/rfc3986#appendix-A
  2404. stateMachine.state = 5 /* DomainLabelChar */;
  2405. stateMachine.acceptStateReached = true;
  2406. }
  2407. else {
  2408. // not valid
  2409. remove(stateMachines, stateMachine);
  2410. }
  2411. }
  2412. // Handles reading a '/' from the NonUrl state
  2413. function stateProtocolRelativeSlash1(stateMachine, char) {
  2414. if (char === '/') {
  2415. stateMachine.state = 12 /* ProtocolRelativeSlash2 */;
  2416. }
  2417. else {
  2418. // Anything else, cannot be the start of a protocol-relative
  2419. // URL.
  2420. remove(stateMachines, stateMachine);
  2421. }
  2422. }
  2423. // Handles reading a second '/', which could start a protocol-relative URL
  2424. function stateProtocolRelativeSlash2(stateMachine, char) {
  2425. if (isDomainLabelStartChar(char)) {
  2426. stateMachine.state = 5 /* DomainLabelChar */;
  2427. }
  2428. else {
  2429. // Anything else, not a URL
  2430. remove(stateMachines, stateMachine);
  2431. }
  2432. }
  2433. // Handles when we have read a domain label character
  2434. function stateDomainLabelChar(stateMachine, char) {
  2435. if (char === '.') {
  2436. stateMachine.state = 7 /* DomainDot */;
  2437. }
  2438. else if (char === '-') {
  2439. stateMachine.state = 6 /* DomainHyphen */;
  2440. }
  2441. else if (char === ':') {
  2442. // Beginning of a port number, end the domain name
  2443. stateMachine.state = 8 /* PortColon */;
  2444. }
  2445. else if (isUrlSuffixStartChar(char)) {
  2446. // '/', '?', or '#'
  2447. stateMachine.state = 10 /* Path */;
  2448. }
  2449. else if (isDomainLabelChar(char)) ;
  2450. else {
  2451. // Anything else, end the domain name
  2452. captureMatchIfValidAndRemove(stateMachine);
  2453. }
  2454. }
  2455. function stateDomainHyphen(stateMachine, char) {
  2456. if (char === '-') ;
  2457. else if (char === '.') {
  2458. // Not valid to have a '-.' in a domain label
  2459. captureMatchIfValidAndRemove(stateMachine);
  2460. }
  2461. else if (isDomainLabelStartChar(char)) {
  2462. stateMachine.state = 5 /* DomainLabelChar */;
  2463. }
  2464. else {
  2465. captureMatchIfValidAndRemove(stateMachine);
  2466. }
  2467. }
  2468. function stateDomainDot(stateMachine, char) {
  2469. if (char === '.') {
  2470. // domain names cannot have multiple '.'s next to each other.
  2471. // It's possible we've already read a valid domain name though,
  2472. // and that the '..' sequence just forms an ellipsis at the end
  2473. // of a sentence
  2474. captureMatchIfValidAndRemove(stateMachine);
  2475. }
  2476. else if (isDomainLabelStartChar(char)) {
  2477. stateMachine.state = 5 /* DomainLabelChar */;
  2478. stateMachine.acceptStateReached = true; // after hitting a dot, and then another domain label, we've reached an accept state
  2479. }
  2480. else {
  2481. // Anything else, end the domain name
  2482. captureMatchIfValidAndRemove(stateMachine);
  2483. }
  2484. }
  2485. function stateIpV4Digit(stateMachine, char) {
  2486. if (char === '.') {
  2487. stateMachine.state = 14 /* IpV4Dot */;
  2488. }
  2489. else if (char === ':') {
  2490. // Beginning of a port number
  2491. stateMachine.state = 8 /* PortColon */;
  2492. }
  2493. else if (digitRe.test(char)) ;
  2494. else if (isUrlSuffixStartChar(char)) {
  2495. stateMachine.state = 10 /* Path */;
  2496. }
  2497. else if (alphaNumericAndMarksRe.test(char)) {
  2498. // If we hit an alpha character, must not be an IPv4
  2499. // Example of this: 1.2.3.4abc
  2500. remove(stateMachines, stateMachine);
  2501. }
  2502. else {
  2503. captureMatchIfValidAndRemove(stateMachine);
  2504. }
  2505. }
  2506. function stateIPv4Dot(stateMachine, char) {
  2507. if (digitRe.test(char)) {
  2508. stateMachine.octetsEncountered++;
  2509. // Once we have encountered 4 octets, it's *potentially* a valid
  2510. // IPv4 address. Our IPv4 regex will confirm the match later
  2511. // though to make sure each octet is in the 0-255 range, and
  2512. // there's exactly 4 octets (not 5 or more)
  2513. if (stateMachine.octetsEncountered === 4) {
  2514. stateMachine.acceptStateReached = true;
  2515. }
  2516. stateMachine.state = 13 /* IpV4Digit */;
  2517. }
  2518. else {
  2519. captureMatchIfValidAndRemove(stateMachine);
  2520. }
  2521. }
  2522. function statePortColon(stateMachine, char) {
  2523. if (digitRe.test(char)) {
  2524. stateMachine.state = 9 /* PortNumber */;
  2525. }
  2526. else {
  2527. captureMatchIfValidAndRemove(stateMachine);
  2528. }
  2529. }
  2530. function statePortNumber(stateMachine, char) {
  2531. if (digitRe.test(char)) ;
  2532. else if (isUrlSuffixStartChar(char)) {
  2533. // '/', '?', or '#'
  2534. stateMachine.state = 10 /* Path */;
  2535. }
  2536. else {
  2537. captureMatchIfValidAndRemove(stateMachine);
  2538. }
  2539. }
  2540. function statePath(stateMachine, char) {
  2541. if (isPathChar(char)) ;
  2542. else {
  2543. captureMatchIfValidAndRemove(stateMachine);
  2544. }
  2545. }
  2546. // Handles if we're reading a 'mailto:' prefix on the string
  2547. function stateEmailMailto_M(stateMachine, char) {
  2548. if (char.toLowerCase() === 'a') {
  2549. stateMachine.state = 16 /* EmailMailto_A */;
  2550. }
  2551. else {
  2552. stateEmailLocalPart(stateMachine, char);
  2553. }
  2554. }
  2555. function stateEmailMailto_A(stateMachine, char) {
  2556. if (char.toLowerCase() === 'i') {
  2557. stateMachine.state = 17 /* EmailMailto_I */;
  2558. }
  2559. else {
  2560. stateEmailLocalPart(stateMachine, char);
  2561. }
  2562. }
  2563. function stateEmailMailto_I(stateMachine, char) {
  2564. if (char.toLowerCase() === 'l') {
  2565. stateMachine.state = 18 /* EmailMailto_L */;
  2566. }
  2567. else {
  2568. stateEmailLocalPart(stateMachine, char);
  2569. }
  2570. }
  2571. function stateEmailMailto_L(stateMachine, char) {
  2572. if (char.toLowerCase() === 't') {
  2573. stateMachine.state = 19 /* EmailMailto_T */;
  2574. }
  2575. else {
  2576. stateEmailLocalPart(stateMachine, char);
  2577. }
  2578. }
  2579. function stateEmailMailto_T(stateMachine, char) {
  2580. if (char.toLowerCase() === 'o') {
  2581. stateMachine.state = 20 /* EmailMailto_O */;
  2582. }
  2583. else {
  2584. stateEmailLocalPart(stateMachine, char);
  2585. }
  2586. }
  2587. function stateEmailMailto_O(stateMachine, char) {
  2588. if (char.toLowerCase() === ':') {
  2589. stateMachine.state = 21 /* EmailMailto_Colon */;
  2590. }
  2591. else {
  2592. stateEmailLocalPart(stateMachine, char);
  2593. }
  2594. }
  2595. function stateEmailMailtoColon(stateMachine, char) {
  2596. if (isEmailLocalPartChar(char)) {
  2597. stateMachine.state = 22 /* EmailLocalPart */;
  2598. }
  2599. else {
  2600. remove(stateMachines, stateMachine);
  2601. }
  2602. }
  2603. // Handles the state when we're currently in the "local part" of an
  2604. // email address (as opposed to the "domain part")
  2605. function stateEmailLocalPart(stateMachine, char) {
  2606. if (char === '.') {
  2607. stateMachine.state = 23 /* EmailLocalPartDot */;
  2608. }
  2609. else if (char === '@') {
  2610. stateMachine.state = 24 /* EmailAtSign */;
  2611. }
  2612. else if (isEmailLocalPartChar(char)) {
  2613. // stay in the "local part" of the email address
  2614. // Note: because stateEmailLocalPart() is called from the
  2615. // 'mailto' states (when the 'mailto' prefix itself has been
  2616. // broken), make sure to set the state to EmailLocalPart
  2617. stateMachine.state = 22 /* EmailLocalPart */;
  2618. }
  2619. else {
  2620. // not an email address character
  2621. remove(stateMachines, stateMachine);
  2622. }
  2623. }
  2624. // Handles the state where we've read
  2625. function stateEmailLocalPartDot(stateMachine, char) {
  2626. if (char === '.') {
  2627. // We read a second '.' in a row, not a valid email address
  2628. // local part
  2629. remove(stateMachines, stateMachine);
  2630. }
  2631. else if (char === '@') {
  2632. // We read the '@' character immediately after a dot ('.'), not
  2633. // an email address
  2634. remove(stateMachines, stateMachine);
  2635. }
  2636. else if (isEmailLocalPartChar(char)) {
  2637. stateMachine.state = 22 /* EmailLocalPart */;
  2638. }
  2639. else {
  2640. // Anything else, not an email address
  2641. remove(stateMachines, stateMachine);
  2642. }
  2643. }
  2644. function stateEmailAtSign(stateMachine, char) {
  2645. if (isDomainLabelStartChar(char)) {
  2646. stateMachine.state = 25 /* EmailDomainChar */;
  2647. }
  2648. else {
  2649. // Anything else, not an email address
  2650. remove(stateMachines, stateMachine);
  2651. }
  2652. }
  2653. function stateEmailDomainChar(stateMachine, char) {
  2654. if (char === '.') {
  2655. stateMachine.state = 27 /* EmailDomainDot */;
  2656. }
  2657. else if (char === '-') {
  2658. stateMachine.state = 26 /* EmailDomainHyphen */;
  2659. }
  2660. else if (isDomainLabelChar(char)) ;
  2661. else {
  2662. // Anything else, we potentially matched if the criteria has
  2663. // been met
  2664. captureMatchIfValidAndRemove(stateMachine);
  2665. }
  2666. }
  2667. function stateEmailDomainHyphen(stateMachine, char) {
  2668. if (char === '-' || char === '.') {
  2669. // Not valid to have two hyphens ("--") or hypen+dot ("-.")
  2670. captureMatchIfValidAndRemove(stateMachine);
  2671. }
  2672. else if (isDomainLabelChar(char)) {
  2673. stateMachine.state = 25 /* EmailDomainChar */;
  2674. }
  2675. else {
  2676. // Anything else
  2677. captureMatchIfValidAndRemove(stateMachine);
  2678. }
  2679. }
  2680. function stateEmailDomainDot(stateMachine, char) {
  2681. if (char === '.' || char === '-') {
  2682. // not valid to have two dots ("..") or dot+hypen (".-")
  2683. captureMatchIfValidAndRemove(stateMachine);
  2684. }
  2685. else if (isDomainLabelStartChar(char)) {
  2686. stateMachine.state = 25 /* EmailDomainChar */;
  2687. // After having read a '.' and then a valid domain character,
  2688. // we now know that the domain part of the email is valid, and
  2689. // we have found at least a partial EmailMatch (however, the
  2690. // email address may have additional characters from this point)
  2691. stateMachine.acceptStateReached = true;
  2692. }
  2693. else {
  2694. // Anything else
  2695. captureMatchIfValidAndRemove(stateMachine);
  2696. }
  2697. }
  2698. // Handles the state when we've just encountered a '#' character
  2699. function stateHashtagHashChar(stateMachine, char) {
  2700. if (isHashtagTextChar(char)) {
  2701. // '#' char with valid hash text char following
  2702. stateMachine.state = 29 /* HashtagTextChar */;
  2703. stateMachine.acceptStateReached = true;
  2704. }
  2705. else {
  2706. remove(stateMachines, stateMachine);
  2707. }
  2708. }
  2709. // Handles the state when we're currently in the hash tag's text chars
  2710. function stateHashtagTextChar(stateMachine, char) {
  2711. if (isHashtagTextChar(char)) ;
  2712. else {
  2713. captureMatchIfValidAndRemove(stateMachine);
  2714. }
  2715. }
  2716. // Handles the state when we've just encountered a '@' character
  2717. function stateMentionAtChar(stateMachine, char) {
  2718. if (isMentionTextChar(char)) {
  2719. // '@' char with valid mention text char following
  2720. stateMachine.state = 31 /* MentionTextChar */;
  2721. stateMachine.acceptStateReached = true;
  2722. }
  2723. else {
  2724. remove(stateMachines, stateMachine);
  2725. }
  2726. }
  2727. // Handles the state when we're currently in the mention's text chars
  2728. function stateMentionTextChar(stateMachine, char) {
  2729. if (isMentionTextChar(char)) ;
  2730. else if (alphaNumericAndMarksRe.test(char)) {
  2731. // Char is invalid for a mention text char, not a valid match.
  2732. // Note that ascii alphanumeric chars are okay (which are tested
  2733. // in the previous 'if' statement, but others are not)
  2734. remove(stateMachines, stateMachine);
  2735. }
  2736. else {
  2737. captureMatchIfValidAndRemove(stateMachine);
  2738. }
  2739. }
  2740. function statePhoneNumberPlus(stateMachine, char) {
  2741. if (digitRe.test(char)) {
  2742. stateMachine.state = 38 /* PhoneNumberDigit */;
  2743. }
  2744. else {
  2745. remove(stateMachines, stateMachine);
  2746. // This character may start a new match. Add states for it
  2747. stateNoMatch(char);
  2748. }
  2749. }
  2750. function statePhoneNumberOpenParen(stateMachine, char) {
  2751. if (digitRe.test(char)) {
  2752. stateMachine.state = 33 /* PhoneNumberAreaCodeDigit1 */;
  2753. }
  2754. else {
  2755. remove(stateMachines, stateMachine);
  2756. }
  2757. // It's also possible that the paren was just an open brace for
  2758. // a piece of text. Start other machines
  2759. stateNoMatch(char);
  2760. }
  2761. function statePhoneNumberAreaCodeDigit1(stateMachine, char) {
  2762. if (digitRe.test(char)) {
  2763. stateMachine.state = 34 /* PhoneNumberAreaCodeDigit2 */;
  2764. }
  2765. else {
  2766. remove(stateMachines, stateMachine);
  2767. }
  2768. }
  2769. function statePhoneNumberAreaCodeDigit2(stateMachine, char) {
  2770. if (digitRe.test(char)) {
  2771. stateMachine.state = 35 /* PhoneNumberAreaCodeDigit3 */;
  2772. }
  2773. else {
  2774. remove(stateMachines, stateMachine);
  2775. }
  2776. }
  2777. function statePhoneNumberAreaCodeDigit3(stateMachine, char) {
  2778. if (char === ')') {
  2779. stateMachine.state = 36 /* PhoneNumberCloseParen */;
  2780. }
  2781. else {
  2782. remove(stateMachines, stateMachine);
  2783. }
  2784. }
  2785. function statePhoneNumberCloseParen(stateMachine, char) {
  2786. if (digitRe.test(char)) {
  2787. stateMachine.state = 38 /* PhoneNumberDigit */;
  2788. }
  2789. else if (isPhoneNumberSeparatorChar(char)) {
  2790. stateMachine.state = 39 /* PhoneNumberSeparator */;
  2791. }
  2792. else {
  2793. remove(stateMachines, stateMachine);
  2794. }
  2795. }
  2796. function statePhoneNumberDigit(stateMachine, char) {
  2797. // For now, if we've reached any digits, we'll say that the machine
  2798. // has reached its accept state. The phone regex will confirm the
  2799. // match later.
  2800. // Alternatively, we could count the number of digits to avoid
  2801. // invoking the phone number regex
  2802. stateMachine.acceptStateReached = true;
  2803. if (isPhoneNumberControlChar(char)) {
  2804. stateMachine.state = 40 /* PhoneNumberControlChar */;
  2805. }
  2806. else if (char === '#') {
  2807. stateMachine.state = 41 /* PhoneNumberPoundChar */;
  2808. }
  2809. else if (digitRe.test(char)) ;
  2810. else if (char === '(') {
  2811. stateMachine.state = 32 /* PhoneNumberOpenParen */;
  2812. }
  2813. else if (isPhoneNumberSeparatorChar(char)) {
  2814. stateMachine.state = 39 /* PhoneNumberSeparator */;
  2815. }
  2816. else {
  2817. captureMatchIfValidAndRemove(stateMachine);
  2818. // The transition from a digit character to a letter can be the
  2819. // start of a new scheme URL match
  2820. if (isSchemeStartChar(char)) {
  2821. stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
  2822. }
  2823. }
  2824. }
  2825. function statePhoneNumberSeparator(stateMachine, char) {
  2826. if (digitRe.test(char)) {
  2827. stateMachine.state = 38 /* PhoneNumberDigit */;
  2828. }
  2829. else if (char === '(') {
  2830. stateMachine.state = 32 /* PhoneNumberOpenParen */;
  2831. }
  2832. else {
  2833. captureMatchIfValidAndRemove(stateMachine);
  2834. // This character may start a new match. Add states for it
  2835. stateNoMatch(char);
  2836. }
  2837. }
  2838. // The ";" characters is "wait" in a phone number
  2839. // The "," characters is "pause" in a phone number
  2840. function statePhoneNumberControlChar(stateMachine, char) {
  2841. if (isPhoneNumberControlChar(char)) ;
  2842. else if (char === '#') {
  2843. stateMachine.state = 41 /* PhoneNumberPoundChar */;
  2844. }
  2845. else if (digitRe.test(char)) {
  2846. stateMachine.state = 38 /* PhoneNumberDigit */;
  2847. }
  2848. else {
  2849. captureMatchIfValidAndRemove(stateMachine);
  2850. }
  2851. }
  2852. // The "#" characters is "pound" in a phone number
  2853. function statePhoneNumberPoundChar(stateMachine, char) {
  2854. if (isPhoneNumberControlChar(char)) {
  2855. stateMachine.state = 40 /* PhoneNumberControlChar */;
  2856. }
  2857. else if (digitRe.test(char)) {
  2858. // According to some of the older tests, if there's a digit
  2859. // after a '#' sign, the match is invalid. TODO: Revisit if this is true
  2860. remove(stateMachines, stateMachine);
  2861. }
  2862. else {
  2863. captureMatchIfValidAndRemove(stateMachine);
  2864. }
  2865. }
  2866. /*
  2867. * Captures a match if it is valid (i.e. has a full domain name for a
  2868. * TLD match). If a match is not valid, it is possible that we want to
  2869. * keep reading characters in order to make a full match.
  2870. */
  2871. function captureMatchIfValidAndRemove(stateMachine) {
  2872. // Remove the state machine first. There are a number of code paths
  2873. // which return out of this function early, so make sure we have
  2874. // this done
  2875. remove(stateMachines, stateMachine);
  2876. // Make sure the state machine being checked has actually reached an
  2877. // "accept" state. If it hasn't reach one, it can't be a match
  2878. if (!stateMachine.acceptStateReached) {
  2879. return;
  2880. }
  2881. var startIdx = stateMachine.startIdx;
  2882. var matchedText = text.slice(stateMachine.startIdx, charIdx);
  2883. // Handle any unbalanced braces (parens, square brackets, or curly
  2884. // brackets) inside the URL. This handles situations like:
  2885. // The link (google.com)
  2886. // and
  2887. // Check out this link here (en.wikipedia.org/wiki/IANA_(disambiguation))
  2888. //
  2889. // And also remove any punctuation chars at the end such as:
  2890. // '?', ',', ':', '.', etc.
  2891. matchedText = excludeUnbalancedTrailingBracesAndPunctuation(matchedText);
  2892. if (stateMachine.type === 'url') {
  2893. // We don't want to accidentally match a URL that is preceded by an
  2894. // '@' character, which would be an email address
  2895. var charBeforeUrlMatch = text.charAt(stateMachine.startIdx - 1);
  2896. if (charBeforeUrlMatch === '@') {
  2897. return;
  2898. }
  2899. // For the purpose of this parser, we've generalized 'www'
  2900. // matches as part of 'tld' matches. However, for backward
  2901. // compatibility, we distinguish beween TLD matches and matches
  2902. // that begin with 'www.' so that users may turn off 'www'
  2903. // matches. As such, we need to correct for that now if the
  2904. // URL begins with 'www.'
  2905. var urlMatchType = stateMachine.matchType;
  2906. if (urlMatchType === 'scheme') {
  2907. // Autolinker accepts many characters in a url's scheme (like `fake://test.com`).
  2908. // However, in cases where a URL is missing whitespace before an obvious link,
  2909. // (for example: `nowhitespacehttp://www.test.com`), we only want the match to start
  2910. // at the http:// part. We will check if the match contains a common scheme and then
  2911. // shift the match to start from there.
  2912. var httpSchemeMatch = httpSchemeRe.exec(matchedText);
  2913. if (httpSchemeMatch) {
  2914. // If we found an overmatched URL, we want to find the index
  2915. // of where the match should start and shift the match to
  2916. // start from the beginning of the common scheme
  2917. startIdx = startIdx + httpSchemeMatch.index;
  2918. matchedText = matchedText.slice(httpSchemeMatch.index);
  2919. }
  2920. if (!isValidSchemeUrl(matchedText)) {
  2921. return; // not a valid match
  2922. }
  2923. }
  2924. else if (urlMatchType === 'tld') {
  2925. if (!isValidTldMatch(matchedText)) {
  2926. return; // not a valid match
  2927. }
  2928. }
  2929. else if (urlMatchType === 'ipV4') {
  2930. if (!isValidIpV4Address(matchedText)) {
  2931. return; // not a valid match
  2932. }
  2933. }
  2934. else {
  2935. assertNever(urlMatchType);
  2936. }
  2937. matches.push(new UrlMatch({
  2938. tagBuilder: tagBuilder,
  2939. matchedText: matchedText,
  2940. offset: startIdx,
  2941. urlMatchType: urlMatchType,
  2942. url: matchedText,
  2943. protocolRelativeMatch: matchedText.slice(0, 2) === '//',
  2944. // TODO: Do these settings need to be passed to the match,
  2945. // or should we handle them here in UrlMatcher?
  2946. stripPrefix: stripPrefix,
  2947. stripTrailingSlash: stripTrailingSlash,
  2948. decodePercentEncoding: decodePercentEncoding,
  2949. }));
  2950. }
  2951. else if (stateMachine.type === 'email') {
  2952. // if the email address has a valid TLD, add it to the list of matches
  2953. if (isValidEmail(matchedText)) {
  2954. matches.push(new EmailMatch({
  2955. tagBuilder: tagBuilder,
  2956. matchedText: matchedText,
  2957. offset: startIdx,
  2958. email: matchedText.replace(mailtoSchemePrefixRe, ''),
  2959. }));
  2960. }
  2961. }
  2962. else if (stateMachine.type === 'hashtag') {
  2963. if (isValidHashtag(matchedText)) {
  2964. matches.push(new HashtagMatch({
  2965. tagBuilder: tagBuilder,
  2966. matchedText: matchedText,
  2967. offset: startIdx,
  2968. serviceName: hashtagServiceName,
  2969. hashtag: matchedText.slice(1),
  2970. }));
  2971. }
  2972. }
  2973. else if (stateMachine.type === 'mention') {
  2974. if (isValidMention(matchedText, mentionServiceName)) {
  2975. matches.push(new MentionMatch({
  2976. tagBuilder: tagBuilder,
  2977. matchedText: matchedText,
  2978. offset: startIdx,
  2979. serviceName: mentionServiceName,
  2980. mention: matchedText.slice(1), // strip off the '@' character at the beginning
  2981. }));
  2982. }
  2983. }
  2984. else if (stateMachine.type === 'phone') {
  2985. // remove any trailing spaces that were considered as "separator"
  2986. // chars by the state machine
  2987. matchedText = matchedText.replace(/ +$/g, '');
  2988. if (isValidPhoneNumber(matchedText)) {
  2989. var cleanNumber = matchedText.replace(/[^0-9,;#]/g, ''); // strip out non-digit characters exclude comma semicolon and #
  2990. matches.push(new PhoneMatch({
  2991. tagBuilder: tagBuilder,
  2992. matchedText: matchedText,
  2993. offset: startIdx,
  2994. number: cleanNumber,
  2995. plusSign: matchedText.charAt(0) === '+',
  2996. }));
  2997. }
  2998. }
  2999. else {
  3000. assertNever(stateMachine);
  3001. }
  3002. }
  3003. }
  3004. var openBraceRe = /[\(\{\[]/;
  3005. var closeBraceRe = /[\)\}\]]/;
  3006. var oppositeBrace = {
  3007. ')': '(',
  3008. '}': '{',
  3009. ']': '[',
  3010. };
  3011. /**
  3012. * Determines if a match found has unmatched closing parenthesis,
  3013. * square brackets or curly brackets. If so, these unbalanced symbol(s) will be
  3014. * removed from the URL match itself.
  3015. *
  3016. * A match may have an extra closing parenthesis/square brackets/curly brackets
  3017. * at the end of the match because these are valid URL path characters. For
  3018. * example, "wikipedia.com/something_(disambiguation)" should be auto-linked.
  3019. *
  3020. * However, an extra parenthesis *will* be included when the URL itself is
  3021. * wrapped in parenthesis, such as in the case of:
  3022. *
  3023. * "(wikipedia.com/something_(disambiguation))"
  3024. *
  3025. * In this case, the last closing parenthesis should *not* be part of the
  3026. * URL itself, and this method will exclude it from the returned URL.
  3027. *
  3028. * For square brackets in URLs such as in PHP arrays, the same behavior as
  3029. * parenthesis discussed above should happen:
  3030. *
  3031. * "[http://www.example.com/foo.php?bar[]=1&bar[]=2&bar[]=3]"
  3032. *
  3033. * The very last closing square bracket should not be part of the URL itself,
  3034. * and therefore this method will remove it.
  3035. *
  3036. * @param matchedText The full matched URL/email/hashtag/etc. from the state
  3037. * machine parser.
  3038. * @return The updated matched text with extraneous suffix characters removed.
  3039. */
  3040. function excludeUnbalancedTrailingBracesAndPunctuation(matchedText) {
  3041. var braceCounts = {
  3042. '(': 0,
  3043. '{': 0,
  3044. '[': 0,
  3045. };
  3046. for (var i = 0; i < matchedText.length; i++) {
  3047. var char_1 = matchedText.charAt(i);
  3048. if (openBraceRe.test(char_1)) {
  3049. braceCounts[char_1]++;
  3050. }
  3051. else if (closeBraceRe.test(char_1)) {
  3052. braceCounts[oppositeBrace[char_1]]--;
  3053. }
  3054. }
  3055. var endIdx = matchedText.length - 1;
  3056. var char;
  3057. while (endIdx >= 0) {
  3058. char = matchedText.charAt(endIdx);
  3059. if (closeBraceRe.test(char)) {
  3060. var oppositeBraceChar = oppositeBrace[char];
  3061. if (braceCounts[oppositeBraceChar] < 0) {
  3062. braceCounts[oppositeBraceChar]++;
  3063. endIdx--;
  3064. }
  3065. else {
  3066. break;
  3067. }
  3068. }
  3069. else if (urlSuffixedCharsNotAllowedAtEndRe.test(char)) {
  3070. // Walk back a punctuation char like '?', ',', ':', '.', etc.
  3071. endIdx--;
  3072. }
  3073. else {
  3074. break;
  3075. }
  3076. }
  3077. return matchedText.slice(0, endIdx + 1);
  3078. }
  3079. function createSchemeUrlStateMachine(startIdx, state) {
  3080. return {
  3081. type: 'url',
  3082. startIdx: startIdx,
  3083. state: state,
  3084. acceptStateReached: false,
  3085. matchType: 'scheme',
  3086. };
  3087. }
  3088. function createTldUrlStateMachine(startIdx, state) {
  3089. return {
  3090. type: 'url',
  3091. startIdx: startIdx,
  3092. state: state,
  3093. acceptStateReached: false,
  3094. matchType: 'tld',
  3095. };
  3096. }
  3097. function createIpV4UrlStateMachine(startIdx, state) {
  3098. return {
  3099. type: 'url',
  3100. startIdx: startIdx,
  3101. state: state,
  3102. acceptStateReached: false,
  3103. matchType: 'ipV4',
  3104. octetsEncountered: 1, // starts at 1 because we create this machine when encountering the first octet
  3105. };
  3106. }
  3107. function createEmailStateMachine(startIdx, state) {
  3108. return {
  3109. type: 'email',
  3110. startIdx: startIdx,
  3111. state: state,
  3112. acceptStateReached: false,
  3113. };
  3114. }
  3115. function createHashtagStateMachine(startIdx, state) {
  3116. return {
  3117. type: 'hashtag',
  3118. startIdx: startIdx,
  3119. state: state,
  3120. acceptStateReached: false,
  3121. };
  3122. }
  3123. function createMentionStateMachine(startIdx, state) {
  3124. return {
  3125. type: 'mention',
  3126. startIdx: startIdx,
  3127. state: state,
  3128. acceptStateReached: false,
  3129. };
  3130. }
  3131. function createPhoneNumberStateMachine(startIdx, state) {
  3132. return {
  3133. type: 'phone',
  3134. startIdx: startIdx,
  3135. state: state,
  3136. acceptStateReached: false,
  3137. };
  3138. }
  3139. // For debugging: search for other "For debugging" lines
  3140. // import CliTable from 'cli-table';
  3141. /**
  3142. * Parses an HTML string, calling the callbacks to notify of tags and text.
  3143. *
  3144. * ## History
  3145. *
  3146. * This file previously used a regular expression to find html tags in the input
  3147. * text. Unfortunately, we ran into a bunch of catastrophic backtracking issues
  3148. * with certain input text, causing Autolinker to either hang or just take a
  3149. * really long time to parse the string.
  3150. *
  3151. * The current code is intended to be a O(n) algorithm that walks through
  3152. * the string in one pass, and tries to be as cheap as possible. We don't need
  3153. * to implement the full HTML spec, but rather simply determine where the string
  3154. * looks like an HTML tag, and where it looks like text (so that we can autolink
  3155. * that).
  3156. *
  3157. * This state machine parser is intended just to be a simple but performant
  3158. * parser of HTML for the subset of requirements we have. We simply need to:
  3159. *
  3160. * 1. Determine where HTML tags are
  3161. * 2. Determine the tag name (Autolinker specifically only cares about <a>,
  3162. * <script>, and <style> tags, so as not to link any text within them)
  3163. *
  3164. * We don't need to:
  3165. *
  3166. * 1. Create a parse tree
  3167. * 2. Auto-close tags with invalid markup
  3168. * 3. etc.
  3169. *
  3170. * The other intention behind this is that we didn't want to add external
  3171. * dependencies on the Autolinker utility which would increase its size. For
  3172. * instance, adding htmlparser2 adds 125kb to the minified output file,
  3173. * increasing its final size from 47kb to 172kb (at the time of writing). It
  3174. * also doesn't work exactly correctly, treating the string "<3 blah blah blah"
  3175. * as an HTML tag.
  3176. *
  3177. * Reference for HTML spec:
  3178. *
  3179. * https://www.w3.org/TR/html51/syntax.html#sec-tokenization
  3180. *
  3181. * @param {String} html The HTML to parse
  3182. * @param {Object} callbacks
  3183. * @param {Function} callbacks.onOpenTag Callback function to call when an open
  3184. * tag is parsed. Called with the tagName as its argument.
  3185. * @param {Function} callbacks.onCloseTag Callback function to call when a close
  3186. * tag is parsed. Called with the tagName as its argument. If a self-closing
  3187. * tag is found, `onCloseTag` is called immediately after `onOpenTag`.
  3188. * @param {Function} callbacks.onText Callback function to call when text (i.e
  3189. * not an HTML tag) is parsed. Called with the text (string) as its first
  3190. * argument, and offset (number) into the string as its second.
  3191. */
  3192. function parseHtml(html, _a) {
  3193. var onOpenTag = _a.onOpenTag, onCloseTag = _a.onCloseTag, onText = _a.onText, onComment = _a.onComment, onDoctype = _a.onDoctype;
  3194. var noCurrentTag = new CurrentTag();
  3195. var charIdx = 0, len = html.length, state = 0 /* Data */, currentDataIdx = 0, // where the current data start index is
  3196. currentTag = noCurrentTag; // describes the current tag that is being read
  3197. // For debugging: search for other "For debugging" lines
  3198. // const table = new CliTable( {
  3199. // head: [ 'charIdx', 'char', 'state', 'currentDataIdx', 'currentOpenTagIdx', 'tag.type' ]
  3200. // } );
  3201. while (charIdx < len) {
  3202. var char = html.charAt(charIdx);
  3203. // For debugging: search for other "For debugging" lines
  3204. // ALSO: Temporarily remove the 'const' keyword on the State enum
  3205. // table.push(
  3206. // [ charIdx, char, State[ state ], currentDataIdx, currentTag.idx, currentTag.idx === -1 ? '' : currentTag.type ]
  3207. // );
  3208. switch (state) {
  3209. case 0 /* Data */:
  3210. stateData(char);
  3211. break;
  3212. case 1 /* TagOpen */:
  3213. stateTagOpen(char);
  3214. break;
  3215. case 2 /* EndTagOpen */:
  3216. stateEndTagOpen(char);
  3217. break;
  3218. case 3 /* TagName */:
  3219. stateTagName(char);
  3220. break;
  3221. case 4 /* BeforeAttributeName */:
  3222. stateBeforeAttributeName(char);
  3223. break;
  3224. case 5 /* AttributeName */:
  3225. stateAttributeName(char);
  3226. break;
  3227. case 6 /* AfterAttributeName */:
  3228. stateAfterAttributeName(char);
  3229. break;
  3230. case 7 /* BeforeAttributeValue */:
  3231. stateBeforeAttributeValue(char);
  3232. break;
  3233. case 8 /* AttributeValueDoubleQuoted */:
  3234. stateAttributeValueDoubleQuoted(char);
  3235. break;
  3236. case 9 /* AttributeValueSingleQuoted */:
  3237. stateAttributeValueSingleQuoted(char);
  3238. break;
  3239. case 10 /* AttributeValueUnquoted */:
  3240. stateAttributeValueUnquoted(char);
  3241. break;
  3242. case 11 /* AfterAttributeValueQuoted */:
  3243. stateAfterAttributeValueQuoted(char);
  3244. break;
  3245. case 12 /* SelfClosingStartTag */:
  3246. stateSelfClosingStartTag(char);
  3247. break;
  3248. case 13 /* MarkupDeclarationOpenState */:
  3249. stateMarkupDeclarationOpen();
  3250. break;
  3251. case 14 /* CommentStart */:
  3252. stateCommentStart(char);
  3253. break;
  3254. case 15 /* CommentStartDash */:
  3255. stateCommentStartDash(char);
  3256. break;
  3257. case 16 /* Comment */:
  3258. stateComment(char);
  3259. break;
  3260. case 17 /* CommentEndDash */:
  3261. stateCommentEndDash(char);
  3262. break;
  3263. case 18 /* CommentEnd */:
  3264. stateCommentEnd(char);
  3265. break;
  3266. case 19 /* CommentEndBang */:
  3267. stateCommentEndBang(char);
  3268. break;
  3269. case 20 /* Doctype */:
  3270. stateDoctype(char);
  3271. break;
  3272. default:
  3273. assertNever(state);
  3274. }
  3275. // For debugging: search for other "For debugging" lines
  3276. // ALSO: Temporarily remove the 'const' keyword on the State enum
  3277. // table.push(
  3278. // [ charIdx, char, State[ state ], currentDataIdx, currentTag.idx, currentTag.idx === -1 ? '' : currentTag.type ]
  3279. // );
  3280. charIdx++;
  3281. }
  3282. if (currentDataIdx < charIdx) {
  3283. emitText();
  3284. }
  3285. // For debugging: search for other "For debugging" lines
  3286. // console.log( '\n' + table.toString() );
  3287. // Called when non-tags are being read (i.e. the text around HTML †ags)
  3288. // https://www.w3.org/TR/html51/syntax.html#data-state
  3289. function stateData(char) {
  3290. if (char === '<') {
  3291. startNewTag();
  3292. }
  3293. }
  3294. // Called after a '<' is read from the Data state
  3295. // https://www.w3.org/TR/html51/syntax.html#tag-open-state
  3296. function stateTagOpen(char) {
  3297. if (char === '!') {
  3298. state = 13 /* MarkupDeclarationOpenState */;
  3299. }
  3300. else if (char === '/') {
  3301. state = 2 /* EndTagOpen */;
  3302. currentTag = new CurrentTag(__assign(__assign({}, currentTag), { isClosing: true }));
  3303. }
  3304. else if (char === '<') {
  3305. // start of another tag (ignore the previous, incomplete one)
  3306. startNewTag();
  3307. }
  3308. else if (letterRe.test(char)) {
  3309. // tag name start (and no '/' read)
  3310. state = 3 /* TagName */;
  3311. currentTag = new CurrentTag(__assign(__assign({}, currentTag), { isOpening: true }));
  3312. }
  3313. else {
  3314. // Any other
  3315. state = 0 /* Data */;
  3316. currentTag = noCurrentTag;
  3317. }
  3318. }
  3319. // After a '<x', '</x' sequence is read (where 'x' is a letter character),
  3320. // this is to continue reading the tag name
  3321. // https://www.w3.org/TR/html51/syntax.html#tag-name-state
  3322. function stateTagName(char) {
  3323. if (whitespaceRe.test(char)) {
  3324. currentTag = new CurrentTag(__assign(__assign({}, currentTag), { name: captureTagName() }));
  3325. state = 4 /* BeforeAttributeName */;
  3326. }
  3327. else if (char === '<') {
  3328. // start of another tag (ignore the previous, incomplete one)
  3329. startNewTag();
  3330. }
  3331. else if (char === '/') {
  3332. currentTag = new CurrentTag(__assign(__assign({}, currentTag), { name: captureTagName() }));
  3333. state = 12 /* SelfClosingStartTag */;
  3334. }
  3335. else if (char === '>') {
  3336. currentTag = new CurrentTag(__assign(__assign({}, currentTag), { name: captureTagName() }));
  3337. emitTagAndPreviousTextNode(); // resets to Data state as well
  3338. }
  3339. else if (!letterRe.test(char) && !digitRe.test(char) && char !== ':') {
  3340. // Anything else that does not form an html tag. Note: the colon
  3341. // character is accepted for XML namespaced tags
  3342. resetToDataState();
  3343. }
  3344. else ;
  3345. }
  3346. // Called after the '/' is read from a '</' sequence
  3347. // https://www.w3.org/TR/html51/syntax.html#end-tag-open-state
  3348. function stateEndTagOpen(char) {
  3349. if (char === '>') {
  3350. // parse error. Encountered "</>". Skip it without treating as a tag
  3351. resetToDataState();
  3352. }
  3353. else if (letterRe.test(char)) {
  3354. state = 3 /* TagName */;
  3355. }
  3356. else {
  3357. // some other non-tag-like character, don't treat this as a tag
  3358. resetToDataState();
  3359. }
  3360. }
  3361. // https://www.w3.org/TR/html51/syntax.html#before-attribute-name-state
  3362. function stateBeforeAttributeName(char) {
  3363. if (whitespaceRe.test(char)) ;
  3364. else if (char === '/') {
  3365. state = 12 /* SelfClosingStartTag */;
  3366. }
  3367. else if (char === '>') {
  3368. emitTagAndPreviousTextNode(); // resets to Data state as well
  3369. }
  3370. else if (char === '<') {
  3371. // start of another tag (ignore the previous, incomplete one)
  3372. startNewTag();
  3373. }
  3374. else if (char === "=" || quoteRe.test(char) || controlCharsRe.test(char)) {
  3375. // "Parse error" characters that, according to the spec, should be
  3376. // appended to the attribute name, but we'll treat these characters
  3377. // as not forming a real HTML tag
  3378. resetToDataState();
  3379. }
  3380. else {
  3381. // Any other char, start of a new attribute name
  3382. state = 5 /* AttributeName */;
  3383. }
  3384. }
  3385. // https://www.w3.org/TR/html51/syntax.html#attribute-name-state
  3386. function stateAttributeName(char) {
  3387. if (whitespaceRe.test(char)) {
  3388. state = 6 /* AfterAttributeName */;
  3389. }
  3390. else if (char === '/') {
  3391. state = 12 /* SelfClosingStartTag */;
  3392. }
  3393. else if (char === '=') {
  3394. state = 7 /* BeforeAttributeValue */;
  3395. }
  3396. else if (char === '>') {
  3397. emitTagAndPreviousTextNode(); // resets to Data state as well
  3398. }
  3399. else if (char === '<') {
  3400. // start of another tag (ignore the previous, incomplete one)
  3401. startNewTag();
  3402. }
  3403. else if (quoteRe.test(char)) {
  3404. // "Parse error" characters that, according to the spec, should be
  3405. // appended to the attribute name, but we'll treat these characters
  3406. // as not forming a real HTML tag
  3407. resetToDataState();
  3408. }
  3409. else ;
  3410. }
  3411. // https://www.w3.org/TR/html51/syntax.html#after-attribute-name-state
  3412. function stateAfterAttributeName(char) {
  3413. if (whitespaceRe.test(char)) ;
  3414. else if (char === '/') {
  3415. state = 12 /* SelfClosingStartTag */;
  3416. }
  3417. else if (char === '=') {
  3418. state = 7 /* BeforeAttributeValue */;
  3419. }
  3420. else if (char === '>') {
  3421. emitTagAndPreviousTextNode();
  3422. }
  3423. else if (char === '<') {
  3424. // start of another tag (ignore the previous, incomplete one)
  3425. startNewTag();
  3426. }
  3427. else if (quoteRe.test(char)) {
  3428. // "Parse error" characters that, according to the spec, should be
  3429. // appended to the attribute name, but we'll treat these characters
  3430. // as not forming a real HTML tag
  3431. resetToDataState();
  3432. }
  3433. else {
  3434. // Any other character, start a new attribute in the current tag
  3435. state = 5 /* AttributeName */;
  3436. }
  3437. }
  3438. // https://www.w3.org/TR/html51/syntax.html#before-attribute-value-state
  3439. function stateBeforeAttributeValue(char) {
  3440. if (whitespaceRe.test(char)) ;
  3441. else if (char === "\"") {
  3442. state = 8 /* AttributeValueDoubleQuoted */;
  3443. }
  3444. else if (char === "'") {
  3445. state = 9 /* AttributeValueSingleQuoted */;
  3446. }
  3447. else if (/[>=`]/.test(char)) {
  3448. // Invalid chars after an '=' for an attribute value, don't count
  3449. // the current tag as an HTML tag
  3450. resetToDataState();
  3451. }
  3452. else if (char === '<') {
  3453. // start of another tag (ignore the previous, incomplete one)
  3454. startNewTag();
  3455. }
  3456. else {
  3457. // Any other character, consider it an unquoted attribute value
  3458. state = 10 /* AttributeValueUnquoted */;
  3459. }
  3460. }
  3461. // https://www.w3.org/TR/html51/syntax.html#attribute-value-double-quoted-state
  3462. function stateAttributeValueDoubleQuoted(char) {
  3463. if (char === "\"") {
  3464. // end the current double-quoted attribute
  3465. state = 11 /* AfterAttributeValueQuoted */;
  3466. }
  3467. }
  3468. // https://www.w3.org/TR/html51/syntax.html#attribute-value-single-quoted-state
  3469. function stateAttributeValueSingleQuoted(char) {
  3470. if (char === "'") {
  3471. // end the current single-quoted attribute
  3472. state = 11 /* AfterAttributeValueQuoted */;
  3473. }
  3474. }
  3475. // https://www.w3.org/TR/html51/syntax.html#attribute-value-unquoted-state
  3476. function stateAttributeValueUnquoted(char) {
  3477. if (whitespaceRe.test(char)) {
  3478. state = 4 /* BeforeAttributeName */;
  3479. }
  3480. else if (char === '>') {
  3481. emitTagAndPreviousTextNode();
  3482. }
  3483. else if (char === '<') {
  3484. // start of another tag (ignore the previous, incomplete one)
  3485. startNewTag();
  3486. }
  3487. else ;
  3488. }
  3489. // https://www.w3.org/TR/html51/syntax.html#after-attribute-value-quoted-state
  3490. function stateAfterAttributeValueQuoted(char) {
  3491. if (whitespaceRe.test(char)) {
  3492. state = 4 /* BeforeAttributeName */;
  3493. }
  3494. else if (char === '/') {
  3495. state = 12 /* SelfClosingStartTag */;
  3496. }
  3497. else if (char === '>') {
  3498. emitTagAndPreviousTextNode();
  3499. }
  3500. else if (char === '<') {
  3501. // start of another tag (ignore the previous, incomplete one)
  3502. startNewTag();
  3503. }
  3504. else {
  3505. // Any other character, "parse error". Spec says to switch to the
  3506. // BeforeAttributeState and re-consume the character, as it may be
  3507. // the start of a new attribute name
  3508. state = 4 /* BeforeAttributeName */;
  3509. reconsumeCurrentCharacter();
  3510. }
  3511. }
  3512. // A '/' has just been read in the current tag (presumably for '/>'), and
  3513. // this handles the next character
  3514. // https://www.w3.org/TR/html51/syntax.html#self-closing-start-tag-state
  3515. function stateSelfClosingStartTag(char) {
  3516. if (char === '>') {
  3517. currentTag = new CurrentTag(__assign(__assign({}, currentTag), { isClosing: true }));
  3518. emitTagAndPreviousTextNode(); // resets to Data state as well
  3519. }
  3520. else {
  3521. state = 4 /* BeforeAttributeName */;
  3522. }
  3523. }
  3524. // https://www.w3.org/TR/html51/syntax.html#markup-declaration-open-state
  3525. // (HTML Comments or !DOCTYPE)
  3526. function stateMarkupDeclarationOpen(char) {
  3527. if (html.substr(charIdx, 2) === '--') {
  3528. // html comment
  3529. charIdx += 2; // "consume" characters
  3530. currentTag = new CurrentTag(__assign(__assign({}, currentTag), { type: 'comment' }));
  3531. state = 14 /* CommentStart */;
  3532. }
  3533. else if (html.substr(charIdx, 7).toUpperCase() === 'DOCTYPE') {
  3534. charIdx += 7; // "consume" characters
  3535. currentTag = new CurrentTag(__assign(__assign({}, currentTag), { type: 'doctype' }));
  3536. state = 20 /* Doctype */;
  3537. }
  3538. else {
  3539. // At this point, the spec specifies that the state machine should
  3540. // enter the "bogus comment" state, in which case any character(s)
  3541. // after the '<!' that were read should become an HTML comment up
  3542. // until the first '>' that is read (or EOF). Instead, we'll assume
  3543. // that a user just typed '<!' as part of text data
  3544. resetToDataState();
  3545. }
  3546. }
  3547. // Handles after the sequence '<!--' has been read
  3548. // https://www.w3.org/TR/html51/syntax.html#comment-start-state
  3549. function stateCommentStart(char) {
  3550. if (char === '-') {
  3551. // We've read the sequence '<!---' at this point (3 dashes)
  3552. state = 15 /* CommentStartDash */;
  3553. }
  3554. else if (char === '>') {
  3555. // At this point, we'll assume the comment wasn't a real comment
  3556. // so we'll just emit it as data. We basically read the sequence
  3557. // '<!-->'
  3558. resetToDataState();
  3559. }
  3560. else {
  3561. // Any other char, take it as part of the comment
  3562. state = 16 /* Comment */;
  3563. }
  3564. }
  3565. // We've read the sequence '<!---' at this point (3 dashes)
  3566. // https://www.w3.org/TR/html51/syntax.html#comment-start-dash-state
  3567. function stateCommentStartDash(char) {
  3568. if (char === '-') {
  3569. // We've read '<!----' (4 dashes) at this point
  3570. state = 18 /* CommentEnd */;
  3571. }
  3572. else if (char === '>') {
  3573. // At this point, we'll assume the comment wasn't a real comment
  3574. // so we'll just emit it as data. We basically read the sequence
  3575. // '<!--->'
  3576. resetToDataState();
  3577. }
  3578. else {
  3579. // Anything else, take it as a valid comment
  3580. state = 16 /* Comment */;
  3581. }
  3582. }
  3583. // Currently reading the comment's text (data)
  3584. // https://www.w3.org/TR/html51/syntax.html#comment-state
  3585. function stateComment(char) {
  3586. if (char === '-') {
  3587. state = 17 /* CommentEndDash */;
  3588. }
  3589. }
  3590. // When we we've read the first dash inside a comment, it may signal the
  3591. // end of the comment if we read another dash
  3592. // https://www.w3.org/TR/html51/syntax.html#comment-end-dash-state
  3593. function stateCommentEndDash(char) {
  3594. if (char === '-') {
  3595. state = 18 /* CommentEnd */;
  3596. }
  3597. else {
  3598. // Wasn't a dash, must still be part of the comment
  3599. state = 16 /* Comment */;
  3600. }
  3601. }
  3602. // After we've read two dashes inside a comment, it may signal the end of
  3603. // the comment if we then read a '>' char
  3604. // https://www.w3.org/TR/html51/syntax.html#comment-end-state
  3605. function stateCommentEnd(char) {
  3606. if (char === '>') {
  3607. emitTagAndPreviousTextNode();
  3608. }
  3609. else if (char === '!') {
  3610. state = 19 /* CommentEndBang */;
  3611. }
  3612. else if (char === '-') ;
  3613. else {
  3614. // Anything else, switch back to the comment state since we didn't
  3615. // read the full "end comment" sequence (i.e. '-->')
  3616. state = 16 /* Comment */;
  3617. }
  3618. }
  3619. // We've read the sequence '--!' inside of a comment
  3620. // https://www.w3.org/TR/html51/syntax.html#comment-end-bang-state
  3621. function stateCommentEndBang(char) {
  3622. if (char === '-') {
  3623. // We read the sequence '--!-' inside of a comment. The last dash
  3624. // could signify that the comment is going to close
  3625. state = 17 /* CommentEndDash */;
  3626. }
  3627. else if (char === '>') {
  3628. // End of comment with the sequence '--!>'
  3629. emitTagAndPreviousTextNode();
  3630. }
  3631. else {
  3632. // The '--!' was not followed by a '>', continue reading the
  3633. // comment's text
  3634. state = 16 /* Comment */;
  3635. }
  3636. }
  3637. /**
  3638. * For DOCTYPES in particular, we don't care about the attributes. Just
  3639. * advance to the '>' character and emit the tag, unless we find a '<'
  3640. * character in which case we'll start a new tag.
  3641. *
  3642. * Example doctype tag:
  3643. * <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  3644. *
  3645. * Actual spec: https://www.w3.org/TR/html51/syntax.html#doctype-state
  3646. */
  3647. function stateDoctype(char) {
  3648. if (char === '>') {
  3649. emitTagAndPreviousTextNode();
  3650. }
  3651. else if (char === '<') {
  3652. startNewTag();
  3653. }
  3654. else ;
  3655. }
  3656. /**
  3657. * Resets the state back to the Data state, and removes the current tag.
  3658. *
  3659. * We'll generally run this function whenever a "parse error" is
  3660. * encountered, where the current tag that is being read no longer looks
  3661. * like a real HTML tag.
  3662. */
  3663. function resetToDataState() {
  3664. state = 0 /* Data */;
  3665. currentTag = noCurrentTag;
  3666. }
  3667. /**
  3668. * Starts a new HTML tag at the current index, ignoring any previous HTML
  3669. * tag that was being read.
  3670. *
  3671. * We'll generally run this function whenever we read a new '<' character,
  3672. * including when we read a '<' character inside of an HTML tag that we were
  3673. * previously reading.
  3674. */
  3675. function startNewTag() {
  3676. state = 1 /* TagOpen */;
  3677. currentTag = new CurrentTag({ idx: charIdx });
  3678. }
  3679. /**
  3680. * Once we've decided to emit an open tag, that means we can also emit the
  3681. * text node before it.
  3682. */
  3683. function emitTagAndPreviousTextNode() {
  3684. var textBeforeTag = html.slice(currentDataIdx, currentTag.idx);
  3685. if (textBeforeTag) {
  3686. // the html tag was the first element in the html string, or two
  3687. // tags next to each other, in which case we should not emit a text
  3688. // node
  3689. onText(textBeforeTag, currentDataIdx);
  3690. }
  3691. if (currentTag.type === 'comment') {
  3692. onComment(currentTag.idx);
  3693. }
  3694. else if (currentTag.type === 'doctype') {
  3695. onDoctype(currentTag.idx);
  3696. }
  3697. else {
  3698. if (currentTag.isOpening) {
  3699. onOpenTag(currentTag.name, currentTag.idx);
  3700. }
  3701. if (currentTag.isClosing) {
  3702. // note: self-closing tags will emit both opening and closing
  3703. onCloseTag(currentTag.name, currentTag.idx);
  3704. }
  3705. }
  3706. // Since we just emitted a tag, reset to the data state for the next char
  3707. resetToDataState();
  3708. currentDataIdx = charIdx + 1;
  3709. }
  3710. function emitText() {
  3711. var text = html.slice(currentDataIdx, charIdx);
  3712. onText(text, currentDataIdx);
  3713. currentDataIdx = charIdx + 1;
  3714. }
  3715. /**
  3716. * Captures the tag name from the start of the tag to the current character
  3717. * index, and converts it to lower case
  3718. */
  3719. function captureTagName() {
  3720. var startIdx = currentTag.idx + (currentTag.isClosing ? 2 : 1);
  3721. return html.slice(startIdx, charIdx).toLowerCase();
  3722. }
  3723. /**
  3724. * Causes the main loop to re-consume the current character, such as after
  3725. * encountering a "parse error" that changed state and needs to reconsume
  3726. * the same character in that new state.
  3727. */
  3728. function reconsumeCurrentCharacter() {
  3729. charIdx--;
  3730. }
  3731. }
  3732. var CurrentTag = /** @class */ (function () {
  3733. function CurrentTag(cfg) {
  3734. if (cfg === void 0) { cfg = {}; }
  3735. this.idx = cfg.idx !== undefined ? cfg.idx : -1;
  3736. this.type = cfg.type || 'tag';
  3737. this.name = cfg.name || '';
  3738. this.isOpening = !!cfg.isOpening;
  3739. this.isClosing = !!cfg.isClosing;
  3740. }
  3741. return CurrentTag;
  3742. }());
  3743. /**
  3744. * @class Autolinker
  3745. * @extends Object
  3746. *
  3747. * Utility class used to process a given string of text, and wrap the matches in
  3748. * the appropriate anchor (&lt;a&gt;) tags to turn them into links.
  3749. *
  3750. * Any of the configuration options may be provided in an Object provided
  3751. * to the Autolinker constructor, which will configure how the {@link #link link()}
  3752. * method will process the links.
  3753. *
  3754. * For example:
  3755. *
  3756. * var autolinker = new Autolinker( {
  3757. * newWindow : false,
  3758. * truncate : 30
  3759. * } );
  3760. *
  3761. * var html = autolinker.link( "Joe went to www.yahoo.com" );
  3762. * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
  3763. *
  3764. *
  3765. * The {@link #static-link static link()} method may also be used to inline
  3766. * options into a single call, which may be more convenient for one-off uses.
  3767. * For example:
  3768. *
  3769. * var html = Autolinker.link( "Joe went to www.yahoo.com", {
  3770. * newWindow : false,
  3771. * truncate : 30
  3772. * } );
  3773. * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
  3774. *
  3775. *
  3776. * ## Custom Replacements of Links
  3777. *
  3778. * If the configuration options do not provide enough flexibility, a {@link #replaceFn}
  3779. * may be provided to fully customize the output of Autolinker. This function is
  3780. * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
  3781. * match that is encountered.
  3782. *
  3783. * For example:
  3784. *
  3785. * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
  3786. *
  3787. * var linkedText = Autolinker.link( input, {
  3788. * replaceFn : function( match ) {
  3789. * console.log( "href = ", match.getAnchorHref() );
  3790. * console.log( "text = ", match.getAnchorText() );
  3791. *
  3792. * switch( match.getType() ) {
  3793. * case 'url' :
  3794. * console.log( "url: ", match.getUrl() );
  3795. *
  3796. * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
  3797. * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
  3798. * tag.setAttr( 'rel', 'nofollow' );
  3799. * tag.addClass( 'external-link' );
  3800. *
  3801. * return tag;
  3802. *
  3803. * } else {
  3804. * return true; // let Autolinker perform its normal anchor tag replacement
  3805. * }
  3806. *
  3807. * case 'email' :
  3808. * var email = match.getEmail();
  3809. * console.log( "email: ", email );
  3810. *
  3811. * if( email === "my@own.address" ) {
  3812. * return false; // don't auto-link this particular email address; leave as-is
  3813. * } else {
  3814. * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
  3815. * }
  3816. *
  3817. * case 'phone' :
  3818. * var phoneNumber = match.getPhoneNumber();
  3819. * console.log( phoneNumber );
  3820. *
  3821. * return '<a href="http://newplace.to.link.phone.numbers.to/">' + phoneNumber + '</a>';
  3822. *
  3823. * case 'hashtag' :
  3824. * var hashtag = match.getHashtag();
  3825. * console.log( hashtag );
  3826. *
  3827. * return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
  3828. *
  3829. * case 'mention' :
  3830. * var mention = match.getMention();
  3831. * console.log( mention );
  3832. *
  3833. * return '<a href="http://newplace.to.link.mention.to/">' + mention + '</a>';
  3834. * }
  3835. * }
  3836. * } );
  3837. *
  3838. *
  3839. * The function may return the following values:
  3840. *
  3841. * - `true` (Boolean): Allow Autolinker to replace the match as it normally
  3842. * would.
  3843. * - `false` (Boolean): Do not replace the current match at all - leave as-is.
  3844. * - Any String: If a string is returned from the function, the string will be
  3845. * used directly as the replacement HTML for the match.
  3846. * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify
  3847. * an HTML tag before writing out its HTML text.
  3848. */
  3849. var Autolinker = /** @class */ (function () {
  3850. /**
  3851. * @method constructor
  3852. * @param {Object} [cfg] The configuration options for the Autolinker instance,
  3853. * specified in an Object (map).
  3854. */
  3855. function Autolinker(cfg) {
  3856. if (cfg === void 0) { cfg = {}; }
  3857. /**
  3858. * The Autolinker version number exposed on the instance itself.
  3859. *
  3860. * Ex: 0.25.1
  3861. *
  3862. * @property {String} version
  3863. */
  3864. this.version = Autolinker.version;
  3865. /**
  3866. * @cfg {Boolean/Object} [urls]
  3867. *
  3868. * `true` if URLs should be automatically linked, `false` if they should not
  3869. * be. Defaults to `true`.
  3870. *
  3871. * Examples:
  3872. *
  3873. * urls: true
  3874. *
  3875. * // or
  3876. *
  3877. * urls: {
  3878. * schemeMatches : true,
  3879. * tldMatches : true,
  3880. * ipV4Matches : true
  3881. * }
  3882. *
  3883. * As shown above, this option also accepts an Object form with 3 properties
  3884. * to allow for more customization of what exactly gets linked. All default
  3885. * to `true`:
  3886. *
  3887. * @cfg {Boolean} [urls.schemeMatches] `true` to match URLs found prefixed
  3888. * with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`,
  3889. * `false` to prevent these types of matches.
  3890. * @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
  3891. * level domains (.com, .net, etc.) that are not prefixed with a scheme
  3892. * (such as 'http://'). This option attempts to match anything that looks
  3893. * like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
  3894. * `false` to prevent these types of matches.
  3895. * @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
  3896. * that are not prefixed with a scheme (such as 'http://'). This option
  3897. * attempts to match anything that looks like an IPv4 address in text. Ex:
  3898. * `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
  3899. * of matches.
  3900. */
  3901. this.urls = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
  3902. /**
  3903. * @cfg {Boolean} [email=true]
  3904. *
  3905. * `true` if email addresses should be automatically linked, `false` if they
  3906. * should not be.
  3907. */
  3908. this.email = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  3909. /**
  3910. * @cfg {Boolean} [phone=true]
  3911. *
  3912. * `true` if Phone numbers ("(555)555-5555") should be automatically linked,
  3913. * `false` if they should not be.
  3914. */
  3915. this.phone = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  3916. /**
  3917. * @cfg {Boolean/String} [hashtag=false]
  3918. *
  3919. * A string for the service name to have hashtags (ex: "#myHashtag")
  3920. * auto-linked to. The currently-supported values are:
  3921. *
  3922. * - 'twitter'
  3923. * - 'facebook'
  3924. * - 'instagram'
  3925. *
  3926. * Pass `false` to skip auto-linking of hashtags.
  3927. */
  3928. this.hashtag = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  3929. /**
  3930. * @cfg {String/Boolean} [mention=false]
  3931. *
  3932. * A string for the service name to have mentions (ex: "@myuser")
  3933. * auto-linked to. The currently supported values are:
  3934. *
  3935. * - 'twitter'
  3936. * - 'instagram'
  3937. * - 'soundcloud'
  3938. * - 'tiktok'
  3939. *
  3940. * Defaults to `false` to skip auto-linking of mentions.
  3941. */
  3942. this.mention = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  3943. /**
  3944. * @cfg {Boolean} [newWindow=true]
  3945. *
  3946. * `true` if the links should open in a new window, `false` otherwise.
  3947. */
  3948. this.newWindow = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  3949. /**
  3950. * @cfg {Boolean/Object} [stripPrefix=true]
  3951. *
  3952. * `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
  3953. * from the beginning of URL links' text, `false` otherwise. Defaults to
  3954. * `true`.
  3955. *
  3956. * Examples:
  3957. *
  3958. * stripPrefix: true
  3959. *
  3960. * // or
  3961. *
  3962. * stripPrefix: {
  3963. * scheme : true,
  3964. * www : true
  3965. * }
  3966. *
  3967. * As shown above, this option also accepts an Object form with 2 properties
  3968. * to allow for more customization of what exactly is prevented from being
  3969. * displayed. Both default to `true`:
  3970. *
  3971. * @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
  3972. * a URL match from being displayed to the user. Example:
  3973. * `'http://google.com'` will be displayed as `'google.com'`. `false` to
  3974. * not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
  3975. * will be removed, so as not to remove a potentially dangerous scheme
  3976. * (such as `'file://'` or `'javascript:'`)
  3977. * @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
  3978. * `'www.'` part of a URL match from being displayed to the user. Ex:
  3979. * `'www.google.com'` will be displayed as `'google.com'`. `false` to not
  3980. * strip the `'www'`.
  3981. */
  3982. this.stripPrefix = {
  3983. scheme: true,
  3984. www: true,
  3985. }; // default value just to get the above doc comment in the ES5 output and documentation generator
  3986. /**
  3987. * @cfg {Boolean} [stripTrailingSlash=true]
  3988. *
  3989. * `true` to remove the trailing slash from URL matches, `false` to keep
  3990. * the trailing slash.
  3991. *
  3992. * Example when `true`: `http://google.com/` will be displayed as
  3993. * `http://google.com`.
  3994. */
  3995. this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  3996. /**
  3997. * @cfg {Boolean} [decodePercentEncoding=true]
  3998. *
  3999. * `true` to decode percent-encoded characters in URL matches, `false` to keep
  4000. * the percent-encoded characters.
  4001. *
  4002. * Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
  4003. * be displayed as `https://en.wikipedia.org/wiki/San_José`.
  4004. */
  4005. this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  4006. /**
  4007. * @cfg {Number/Object} [truncate=0]
  4008. *
  4009. * ## Number Form
  4010. *
  4011. * A number for how many characters matched text should be truncated to
  4012. * inside the text of a link. If the matched text is over this number of
  4013. * characters, it will be truncated to this length by adding a two period
  4014. * ellipsis ('..') to the end of the string.
  4015. *
  4016. * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file'
  4017. * truncated to 25 characters might look something like this:
  4018. * 'yahoo.com/some/long/pat..'
  4019. *
  4020. * Example Usage:
  4021. *
  4022. * truncate: 25
  4023. *
  4024. *
  4025. * Defaults to `0` for "no truncation."
  4026. *
  4027. *
  4028. * ## Object Form
  4029. *
  4030. * An Object may also be provided with two properties: `length` (Number) and
  4031. * `location` (String). `location` may be one of the following: 'end'
  4032. * (default), 'middle', or 'smart'.
  4033. *
  4034. * Example Usage:
  4035. *
  4036. * truncate: { length: 25, location: 'middle' }
  4037. *
  4038. * @cfg {Number} [truncate.length=0] How many characters to allow before
  4039. * truncation will occur. Defaults to `0` for "no truncation."
  4040. * @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
  4041. *
  4042. * - 'end' (default): will truncate up to the number of characters, and then
  4043. * add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
  4044. * - 'middle': will truncate and add the ellipsis in the middle. Ex:
  4045. * 'yahoo.com/s..th/to/a/file'
  4046. * - 'smart': for URLs where the algorithm attempts to strip out unnecessary
  4047. * parts first (such as the 'www.', then URL scheme, hash, etc.),
  4048. * attempting to make the URL human-readable before looking for a good
  4049. * point to insert the ellipsis if it is still too long. Ex:
  4050. * 'yahoo.com/some..to/a/file'. For more details, see
  4051. * {@link Autolinker.truncate.TruncateSmart}.
  4052. */
  4053. this.truncate = {
  4054. length: 0,
  4055. location: 'end',
  4056. }; // default value just to get the above doc comment in the ES5 output and documentation generator
  4057. /**
  4058. * @cfg {String} className
  4059. *
  4060. * A CSS class name to add to the generated links. This class will be added
  4061. * to all links, as well as this class plus match suffixes for styling
  4062. * url/email/phone/hashtag/mention links differently.
  4063. *
  4064. * For example, if this config is provided as "myLink", then:
  4065. *
  4066. * - URL links will have the CSS classes: "myLink myLink-url"
  4067. * - Email links will have the CSS classes: "myLink myLink-email", and
  4068. * - Phone links will have the CSS classes: "myLink myLink-phone"
  4069. * - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
  4070. * - Mention links will have the CSS classes: "myLink myLink-mention myLink-[type]"
  4071. * where [type] is either "instagram", "twitter" or "soundcloud"
  4072. */
  4073. this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  4074. /**
  4075. * @cfg {Function} replaceFn
  4076. *
  4077. * A function to individually process each match found in the input string.
  4078. *
  4079. * See the class's description for usage.
  4080. *
  4081. * The `replaceFn` can be called with a different context object (`this`
  4082. * reference) using the {@link #context} cfg.
  4083. *
  4084. * This function is called with the following parameter:
  4085. *
  4086. * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which
  4087. * can be used to retrieve information about the match that the `replaceFn`
  4088. * is currently processing. See {@link Autolinker.match.Match} subclasses
  4089. * for details.
  4090. */
  4091. this.replaceFn = null; // default value just to get the above doc comment in the ES5 output and documentation generator
  4092. /**
  4093. * @cfg {Object} context
  4094. *
  4095. * The context object (`this` reference) to call the `replaceFn` with.
  4096. *
  4097. * Defaults to this Autolinker instance.
  4098. */
  4099. this.context = undefined; // default value just to get the above doc comment in the ES5 output and documentation generator
  4100. /**
  4101. * @cfg {Boolean} [sanitizeHtml=false]
  4102. *
  4103. * `true` to HTML-encode the start and end brackets of existing HTML tags found
  4104. * in the input string. This will escape `<` and `>` characters to `&lt;` and
  4105. * `&gt;`, respectively.
  4106. *
  4107. * Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
  4108. * but will remove the significance of existing HTML tags in the input string. If
  4109. * you would like to maintain the significance of existing HTML tags while also
  4110. * making the output HTML string safe, leave this option as `false` and use a
  4111. * tool like https://github.com/cure53/DOMPurify (or others) on the input string
  4112. * before running Autolinker.
  4113. */
  4114. this.sanitizeHtml = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  4115. /**
  4116. * @private
  4117. * @property {Autolinker.AnchorTagBuilder} tagBuilder
  4118. *
  4119. * The AnchorTagBuilder instance used to build match replacement anchor tags.
  4120. * Note: this is lazily instantiated in the {@link #getTagBuilder} method.
  4121. */
  4122. this.tagBuilder = null;
  4123. // Note: when `this.something` is used in the rhs of these assignments,
  4124. // it refers to the default values set above the constructor
  4125. this.urls = normalizeUrlsCfg(cfg.urls);
  4126. this.email = isBoolean(cfg.email) ? cfg.email : this.email;
  4127. this.phone = isBoolean(cfg.phone) ? cfg.phone : this.phone;
  4128. this.hashtag = cfg.hashtag || this.hashtag;
  4129. this.mention = cfg.mention || this.mention;
  4130. this.newWindow = isBoolean(cfg.newWindow) ? cfg.newWindow : this.newWindow;
  4131. this.stripPrefix = normalizeStripPrefixCfg(cfg.stripPrefix);
  4132. this.stripTrailingSlash = isBoolean(cfg.stripTrailingSlash)
  4133. ? cfg.stripTrailingSlash
  4134. : this.stripTrailingSlash;
  4135. this.decodePercentEncoding = isBoolean(cfg.decodePercentEncoding)
  4136. ? cfg.decodePercentEncoding
  4137. : this.decodePercentEncoding;
  4138. this.sanitizeHtml = cfg.sanitizeHtml || false;
  4139. // Validate the value of the `mention` cfg
  4140. var mention = this.mention;
  4141. if (mention !== false && mentionServices.indexOf(mention) === -1) {
  4142. throw new Error("invalid `mention` cfg '".concat(mention, "' - see docs"));
  4143. }
  4144. // Validate the value of the `hashtag` cfg
  4145. var hashtag = this.hashtag;
  4146. if (hashtag !== false && hashtagServices.indexOf(hashtag) === -1) {
  4147. throw new Error("invalid `hashtag` cfg '".concat(hashtag, "' - see docs"));
  4148. }
  4149. this.truncate = normalizeTruncateCfg(cfg.truncate);
  4150. this.className = cfg.className || this.className;
  4151. this.replaceFn = cfg.replaceFn || this.replaceFn;
  4152. this.context = cfg.context || this;
  4153. }
  4154. /**
  4155. * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
  4156. * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs
  4157. * found within HTML tags.
  4158. *
  4159. * For instance, if given the text: `You should go to http://www.yahoo.com`,
  4160. * then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
  4161. *
  4162. * Example:
  4163. *
  4164. * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
  4165. * // Produces: "Go to <a href="http://google.com">google.com</a>"
  4166. *
  4167. * @static
  4168. * @param {String} textOrHtml The HTML or text to find matches within (depending
  4169. * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #mention},
  4170. * {@link #hashtag}, and {@link #mention} options are enabled).
  4171. * @param {Object} [options] Any of the configuration options for the Autolinker
  4172. * class, specified in an Object (map). See the class description for an
  4173. * example call.
  4174. * @return {String} The HTML text, with matches automatically linked.
  4175. */
  4176. Autolinker.link = function (textOrHtml, options) {
  4177. var autolinker = new Autolinker(options);
  4178. return autolinker.link(textOrHtml);
  4179. };
  4180. /**
  4181. * Parses the input `textOrHtml` looking for URLs, email addresses, phone
  4182. * numbers, username handles, and hashtags (depending on the configuration
  4183. * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
  4184. * objects describing those matches (without making any replacements).
  4185. *
  4186. * Note that if parsing multiple pieces of text, it is slightly more efficient
  4187. * to create an Autolinker instance, and use the instance-level {@link #parse}
  4188. * method.
  4189. *
  4190. * Example:
  4191. *
  4192. * var matches = Autolinker.parse( "Hello google.com, I am asdf@asdf.com", {
  4193. * urls: true,
  4194. * email: true
  4195. * } );
  4196. *
  4197. * console.log( matches.length ); // 2
  4198. * console.log( matches[ 0 ].getType() ); // 'url'
  4199. * console.log( matches[ 0 ].getUrl() ); // 'google.com'
  4200. * console.log( matches[ 1 ].getType() ); // 'email'
  4201. * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
  4202. *
  4203. * @static
  4204. * @param {String} textOrHtml The HTML or text to find matches within
  4205. * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
  4206. * {@link #hashtag}, and {@link #mention} options are enabled).
  4207. * @param {Object} [options] Any of the configuration options for the Autolinker
  4208. * class, specified in an Object (map). See the class description for an
  4209. * example call.
  4210. * @return {Autolinker.match.Match[]} The array of Matches found in the
  4211. * given input `textOrHtml`.
  4212. */
  4213. Autolinker.parse = function (textOrHtml, options) {
  4214. var autolinker = new Autolinker(options);
  4215. return autolinker.parse(textOrHtml);
  4216. };
  4217. /**
  4218. * Parses the input `textOrHtml` looking for URLs, email addresses, phone
  4219. * numbers, username handles, and hashtags (depending on the configuration
  4220. * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
  4221. * objects describing those matches (without making any replacements).
  4222. *
  4223. * This method is used by the {@link #link} method, but can also be used to
  4224. * simply do parsing of the input in order to discover what kinds of links
  4225. * there are and how many.
  4226. *
  4227. * Example usage:
  4228. *
  4229. * var autolinker = new Autolinker( {
  4230. * urls: true,
  4231. * email: true
  4232. * } );
  4233. *
  4234. * var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
  4235. *
  4236. * console.log( matches.length ); // 2
  4237. * console.log( matches[ 0 ].getType() ); // 'url'
  4238. * console.log( matches[ 0 ].getUrl() ); // 'google.com'
  4239. * console.log( matches[ 1 ].getType() ); // 'email'
  4240. * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
  4241. *
  4242. * @param {String} textOrHtml The HTML or text to find matches within
  4243. * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
  4244. * {@link #hashtag}, and {@link #mention} options are enabled).
  4245. * @return {Autolinker.match.Match[]} The array of Matches found in the
  4246. * given input `textOrHtml`.
  4247. */
  4248. Autolinker.prototype.parse = function (textOrHtml) {
  4249. var _this = this;
  4250. var skipTagNames = ['a', 'style', 'script'], 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
  4251. matches = [];
  4252. // Find all matches within the `textOrHtml` (but not matches that are
  4253. // already nested within <a>, <style> and <script> tags)
  4254. parseHtml(textOrHtml, {
  4255. onOpenTag: function (tagName) {
  4256. if (skipTagNames.indexOf(tagName) >= 0) {
  4257. skipTagsStackCount++;
  4258. }
  4259. },
  4260. onText: function (text, offset) {
  4261. // Only process text nodes that are not within an <a>, <style> or <script> tag
  4262. if (skipTagsStackCount === 0) {
  4263. // "Walk around" common HTML entities. An '&nbsp;' (for example)
  4264. // could be at the end of a URL, but we don't want to
  4265. // include the trailing '&' in the URL. See issue #76
  4266. // TODO: Handle HTML entities separately in parseHtml() and
  4267. // don't emit them as "text" except for &amp; entities
  4268. var htmlCharacterEntitiesRegex = /(&nbsp;|&#160;|&lt;|&#60;|&gt;|&#62;|&quot;|&#34;|&#39;)/gi; // NOTE: capturing group is significant to include the split characters in the .split() call below
  4269. var textSplit = text.split(htmlCharacterEntitiesRegex);
  4270. var currentOffset_1 = offset;
  4271. textSplit.forEach(function (splitText, i) {
  4272. // even number matches are text, odd numbers are html entities
  4273. if (i % 2 === 0) {
  4274. var textNodeMatches = _this.parseText(splitText, currentOffset_1);
  4275. matches.push.apply(matches, textNodeMatches);
  4276. }
  4277. currentOffset_1 += splitText.length;
  4278. });
  4279. }
  4280. },
  4281. onCloseTag: function (tagName) {
  4282. if (skipTagNames.indexOf(tagName) >= 0) {
  4283. skipTagsStackCount = Math.max(skipTagsStackCount - 1, 0); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
  4284. }
  4285. },
  4286. onComment: function (_offset) { },
  4287. onDoctype: function (_offset) { }, // no need to process doctype nodes
  4288. });
  4289. // After we have found all matches, remove subsequent matches that
  4290. // overlap with a previous match. This can happen for instance with URLs,
  4291. // where the url 'google.com/#link' would match '#link' as a hashtag.
  4292. matches = this.compactMatches(matches);
  4293. // And finally, remove matches for match types that have been turned
  4294. // off. We needed to have all match types turned on initially so that
  4295. // things like hashtags could be filtered out if they were really just
  4296. // part of a URL match (for instance, as a named anchor).
  4297. matches = this.removeUnwantedMatches(matches);
  4298. return matches;
  4299. };
  4300. /**
  4301. * After we have found all matches, we need to remove matches that overlap
  4302. * with a previous match. This can happen for instance with URLs, where the
  4303. * url 'google.com/#link' would match '#link' as a hashtag. Because the
  4304. * '#link' part is contained in a larger match that comes before the HashTag
  4305. * match, we'll remove the HashTag match.
  4306. *
  4307. * @private
  4308. * @param {Autolinker.match.Match[]} matches
  4309. * @return {Autolinker.match.Match[]}
  4310. */
  4311. Autolinker.prototype.compactMatches = function (matches) {
  4312. // First, the matches need to be sorted in order of offset
  4313. matches.sort(function (a, b) {
  4314. return a.getOffset() - b.getOffset();
  4315. });
  4316. var i = 0;
  4317. while (i < matches.length - 1) {
  4318. var match = matches[i], offset = match.getOffset(), matchedTextLength = match.getMatchedText().length, endIdx = offset + matchedTextLength;
  4319. if (i + 1 < matches.length) {
  4320. // Remove subsequent matches that equal offset with current match
  4321. if (matches[i + 1].getOffset() === offset) {
  4322. var removeIdx = matches[i + 1].getMatchedText().length > matchedTextLength ? i : i + 1;
  4323. matches.splice(removeIdx, 1);
  4324. continue;
  4325. }
  4326. // Remove subsequent matches that overlap with the current match
  4327. if (matches[i + 1].getOffset() < endIdx) {
  4328. matches.splice(i + 1, 1);
  4329. continue;
  4330. }
  4331. }
  4332. i++;
  4333. }
  4334. return matches;
  4335. };
  4336. /**
  4337. * Removes matches for matchers that were turned off in the options. For
  4338. * example, if {@link #hashtag hashtags} were not to be matched, we'll
  4339. * remove them from the `matches` array here.
  4340. *
  4341. * Note: we *must* use all Matchers on the input string, and then filter
  4342. * them out later. For example, if the options were `{ url: false, hashtag: true }`,
  4343. * we wouldn't want to match the text '#link' as a HashTag inside of the text
  4344. * 'google.com/#link'. The way the algorithm works is that we match the full
  4345. * URL first (which prevents the accidental HashTag match), and then we'll
  4346. * simply throw away the URL match.
  4347. *
  4348. * @private
  4349. * @param {Autolinker.match.Match[]} matches The array of matches to remove
  4350. * the unwanted matches from. Note: this array is mutated for the
  4351. * removals.
  4352. * @return {Autolinker.match.Match[]} The mutated input `matches` array.
  4353. */
  4354. Autolinker.prototype.removeUnwantedMatches = function (matches) {
  4355. if (!this.hashtag)
  4356. removeWithPredicate(matches, function (match) {
  4357. return match.getType() === 'hashtag';
  4358. });
  4359. if (!this.email)
  4360. removeWithPredicate(matches, function (match) {
  4361. return match.getType() === 'email';
  4362. });
  4363. if (!this.phone)
  4364. removeWithPredicate(matches, function (match) {
  4365. return match.getType() === 'phone';
  4366. });
  4367. if (!this.mention)
  4368. removeWithPredicate(matches, function (match) {
  4369. return match.getType() === 'mention';
  4370. });
  4371. if (!this.urls.schemeMatches) {
  4372. removeWithPredicate(matches, function (m) {
  4373. return m.getType() === 'url' && m.getUrlMatchType() === 'scheme';
  4374. });
  4375. }
  4376. if (!this.urls.tldMatches) {
  4377. removeWithPredicate(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'tld'; });
  4378. }
  4379. if (!this.urls.ipV4Matches) {
  4380. removeWithPredicate(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'ipV4'; });
  4381. }
  4382. return matches;
  4383. };
  4384. /**
  4385. * Parses the input `text` looking for URLs, email addresses, phone
  4386. * numbers, username handles, and hashtags (depending on the configuration
  4387. * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
  4388. * objects describing those matches.
  4389. *
  4390. * This method processes a **non-HTML string**, and is used to parse and
  4391. * match within the text nodes of an HTML string. This method is used
  4392. * internally by {@link #parse}.
  4393. *
  4394. * @private
  4395. * @param {String} text The text to find matches within (depending on if the
  4396. * {@link #urls}, {@link #email}, {@link #phone},
  4397. * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string.
  4398. * @param {Number} [offset=0] The offset of the text node within the
  4399. * original string. This is used when parsing with the {@link #parse}
  4400. * method to generate correct offsets within the {@link Autolinker.match.Match}
  4401. * instances, but may be omitted if calling this method publicly.
  4402. * @return {Autolinker.match.Match[]} The array of Matches found in the
  4403. * given input `text`.
  4404. */
  4405. Autolinker.prototype.parseText = function (text, offset) {
  4406. if (offset === void 0) { offset = 0; }
  4407. offset = offset || 0;
  4408. var matches = parseMatches(text, {
  4409. tagBuilder: this.getTagBuilder(),
  4410. stripPrefix: this.stripPrefix,
  4411. stripTrailingSlash: this.stripTrailingSlash,
  4412. decodePercentEncoding: this.decodePercentEncoding,
  4413. hashtagServiceName: this.hashtag,
  4414. mentionServiceName: this.mention || 'twitter',
  4415. });
  4416. // Correct the offset of each of the matches. They are originally
  4417. // the offset of the match within the provided text node, but we
  4418. // need to correct them to be relative to the original HTML input
  4419. // string (i.e. the one provided to #parse).
  4420. for (var i = 0, numTextMatches = matches.length; i < numTextMatches; i++) {
  4421. matches[i].setOffset(offset + matches[i].getOffset());
  4422. }
  4423. return matches;
  4424. };
  4425. /**
  4426. * Automatically links URLs, Email addresses, Phone numbers, Hashtags,
  4427. * and Mentions (Twitter, Instagram, Soundcloud) found in the given chunk of HTML. Does not link
  4428. * URLs found within HTML tags.
  4429. *
  4430. * For instance, if given the text: `You should go to http://www.yahoo.com`,
  4431. * then the result will be `You should go to
  4432. * &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
  4433. *
  4434. * This method finds the text around any HTML elements in the input
  4435. * `textOrHtml`, which will be the text that is processed. Any original HTML
  4436. * elements will be left as-is, as well as the text that is already wrapped
  4437. * in anchor (&lt;a&gt;) tags.
  4438. *
  4439. * @param {String} textOrHtml The HTML or text to autolink matches within
  4440. * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled).
  4441. * @return {String} The HTML, with matches automatically linked.
  4442. */
  4443. Autolinker.prototype.link = function (textOrHtml) {
  4444. if (!textOrHtml) {
  4445. return '';
  4446. } // handle `null` and `undefined` (for JavaScript users that don't have TypeScript support)
  4447. /* We would want to sanitize the start and end characters of a tag
  4448. * before processing the string in order to avoid an XSS scenario.
  4449. * This behaviour can be changed by toggling the sanitizeHtml option.
  4450. */
  4451. if (this.sanitizeHtml) {
  4452. textOrHtml = textOrHtml.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  4453. }
  4454. var matches = this.parse(textOrHtml), newHtml = [], lastIndex = 0;
  4455. for (var i = 0, len = matches.length; i < len; i++) {
  4456. var match = matches[i];
  4457. newHtml.push(textOrHtml.substring(lastIndex, match.getOffset()));
  4458. newHtml.push(this.createMatchReturnVal(match));
  4459. lastIndex = match.getOffset() + match.getMatchedText().length;
  4460. }
  4461. newHtml.push(textOrHtml.substring(lastIndex)); // handle the text after the last match
  4462. return newHtml.join('');
  4463. };
  4464. /**
  4465. * Creates the return string value for a given match in the input string.
  4466. *
  4467. * This method handles the {@link #replaceFn}, if one was provided.
  4468. *
  4469. * @private
  4470. * @param {Autolinker.match.Match} match The Match object that represents
  4471. * the match.
  4472. * @return {String} The string that the `match` should be replaced with.
  4473. * This is usually the anchor tag string, but may be the `matchStr` itself
  4474. * if the match is not to be replaced.
  4475. */
  4476. Autolinker.prototype.createMatchReturnVal = function (match) {
  4477. // Handle a custom `replaceFn` being provided
  4478. var replaceFnResult;
  4479. if (this.replaceFn) {
  4480. replaceFnResult = this.replaceFn.call(this.context, match); // Autolinker instance is the context
  4481. }
  4482. if (typeof replaceFnResult === 'string') {
  4483. return replaceFnResult; // `replaceFn` returned a string, use that
  4484. }
  4485. else if (replaceFnResult === false) {
  4486. return match.getMatchedText(); // no replacement for the match
  4487. }
  4488. else if (replaceFnResult instanceof HtmlTag) {
  4489. return replaceFnResult.toAnchorString();
  4490. }
  4491. else {
  4492. // replaceFnResult === true, or no/unknown return value from function
  4493. // Perform Autolinker's default anchor tag generation
  4494. var anchorTag = match.buildTag(); // returns an Autolinker.HtmlTag instance
  4495. return anchorTag.toAnchorString();
  4496. }
  4497. };
  4498. /**
  4499. * Returns the {@link #tagBuilder} instance for this Autolinker instance,
  4500. * lazily instantiating it if it does not yet exist.
  4501. *
  4502. * @private
  4503. * @return {Autolinker.AnchorTagBuilder}
  4504. */
  4505. Autolinker.prototype.getTagBuilder = function () {
  4506. var tagBuilder = this.tagBuilder;
  4507. if (!tagBuilder) {
  4508. tagBuilder = this.tagBuilder = new AnchorTagBuilder({
  4509. newWindow: this.newWindow,
  4510. truncate: this.truncate,
  4511. className: this.className,
  4512. });
  4513. }
  4514. return tagBuilder;
  4515. };
  4516. // NOTE: must be 'export default' here for UMD module
  4517. /**
  4518. * @static
  4519. * @property {String} version
  4520. *
  4521. * The Autolinker version number in the form major.minor.patch
  4522. *
  4523. * Ex: 3.15.0
  4524. */
  4525. Autolinker.version = version;
  4526. return Autolinker;
  4527. }());
  4528. /**
  4529. * Normalizes the {@link #urls} config into an Object with its 2 properties:
  4530. * `schemeMatches` and `tldMatches`, both booleans.
  4531. *
  4532. * See {@link #urls} config for details.
  4533. *
  4534. * @private
  4535. * @param {Boolean/Object} urls
  4536. * @return {Object}
  4537. */
  4538. function normalizeUrlsCfg(urls) {
  4539. if (urls == null)
  4540. urls = true; // default to `true`
  4541. if (isBoolean(urls)) {
  4542. return { schemeMatches: urls, tldMatches: urls, ipV4Matches: urls };
  4543. }
  4544. else {
  4545. // object form
  4546. return {
  4547. schemeMatches: isBoolean(urls.schemeMatches) ? urls.schemeMatches : true,
  4548. tldMatches: isBoolean(urls.tldMatches) ? urls.tldMatches : true,
  4549. ipV4Matches: isBoolean(urls.ipV4Matches) ? urls.ipV4Matches : true,
  4550. };
  4551. }
  4552. }
  4553. /**
  4554. * Normalizes the {@link #stripPrefix} config into an Object with 2
  4555. * properties: `scheme`, and `www` - both Booleans.
  4556. *
  4557. * See {@link #stripPrefix} config for details.
  4558. *
  4559. * @private
  4560. * @param {Boolean/Object} stripPrefix
  4561. * @return {Object}
  4562. */
  4563. function normalizeStripPrefixCfg(stripPrefix) {
  4564. if (stripPrefix == null)
  4565. stripPrefix = true; // default to `true`
  4566. if (isBoolean(stripPrefix)) {
  4567. return { scheme: stripPrefix, www: stripPrefix };
  4568. }
  4569. else {
  4570. // object form
  4571. return {
  4572. scheme: isBoolean(stripPrefix.scheme) ? stripPrefix.scheme : true,
  4573. www: isBoolean(stripPrefix.www) ? stripPrefix.www : true,
  4574. };
  4575. }
  4576. }
  4577. /**
  4578. * Normalizes the {@link #truncate} config into an Object with 2 properties:
  4579. * `length` (Number), and `location` (String).
  4580. *
  4581. * See {@link #truncate} config for details.
  4582. *
  4583. * @private
  4584. * @param {Number/Object} truncate
  4585. * @return {Object}
  4586. */
  4587. function normalizeTruncateCfg(truncate) {
  4588. if (typeof truncate === 'number') {
  4589. return { length: truncate, location: 'end' };
  4590. }
  4591. else {
  4592. // object, or undefined/null
  4593. return defaults(truncate || {}, {
  4594. length: Number.POSITIVE_INFINITY,
  4595. location: 'end',
  4596. });
  4597. }
  4598. }
  4599. return Autolinker;
  4600. }));
  4601. //# sourceMappingURL=autolinker.js.map