PageSourceSearch

https://www.webnames.ca/jsbundles/AccountLogin?v=s5M5cwmY_vKAkZ5--3eV_LTMFq1khwmLZRO1WfxmOLo1

js webnames.ca collected 2026-10-02 02:54:17 UTC 168,627 bytes, 3,991 lines download raw bytes

1(function () {
2    'use strict';
3
4    angular.module("AccountLoginApp", ['WebnamesError', 'LoginHelper', 'WebnamesLocationHelper']);
5
6})();
7(function () {
8    'use strict';
9
10    angular.module('AccountLoginApp')
11           .controller('AccountLoginController', ['$log', 'LegacyParam', '$window', 'WnErrorHandler', 'LoginHelperServices', 'Credential', 'TwoStepAuthInfo', LoginController]);
12
13    function LoginController($log, LegacyParam, $window, WnErrorHandler, LoginHelperServices, Credential, TwoStepAuthInfo) {
14        var vm = this;
15        var sRedirectURL = $window._SmartURL + (LegacyParam.Params.goto || "myaccount/domain-list");
16
17        // passed into directives
18        vm.credential = Credential || { username: '', password: '' };
19        vm.TwoStepAuthInfo = TwoStepAuthInfo || { maskedPhoneNumber: '', verificationCode: null, isSSO: false };
20        vm.isSSO = vm.TwoStepAuthInfo.isSSO;
21        // states
22        vm.bSigningIn = false;
23        vm.currentLoginProcess = 'credential';
24        vm.iErrorCode = parseInt(LegacyParam.Params.ec) || "";
25        // form submission
26        vm.SignIn = signIn;
27        
28
29        init();
30        function init() {
31            WnErrorHandler.scrollToError = false; // when there's an error, show everything rather than only the error
32
33            // Handle login errors (typically logins from the header).
34            if (vm.iErrorCode) {
35                if (vm.iErrorCode === 2002) {
36                    displayTwoStepAuthView();
37                } else {
38                    WnErrorHandler.PushError(LoginHelperServices.GetErrorMessage(vm.iErrorCode));
39                }
40            }
41        }
42
43        function signIn() {
44            vm.bSigningIn = true;
45
46            WnErrorHandler.ClearAll();
47            LoginHelperServices.Login(vm.credential.username, vm.credential.password, LegacyParam.Params.goto, vm.TwoStepAuthInfo.verificationCode, vm.isSSO).then(handleLoginResponse);
48        }
49
50        function handleLoginResponse(resp) {
51            // Property 'isError' will always be false (promise was resolved), so it's not being checked here.
52            if (resp.isLoggedIn || resp.redirect) {
53                $window.location.href = resp.redirect || sRedirectURL;
54            } else if (resp.isTwoStepAuth) {
55                displayTwoStepAuthView();
56                vm.TwoStepAuthInfo.maskedPhoneNumber = resp.maskedPhoneNumber;
57                vm.TwoStepAuthInfo.verificationCode = resp.verificationCode;
58            } else {
59                WnErrorHandler.PushError(resp.alert && resp.alert.msg || "An error has occurred during login.");
60            }
61            vm.bSigningIn = false;
62        }
63
64        function displayTwoStepAuthView() {
65            vm.currentLoginProcess = 'twoStep';
66        }
67
68        vm.SSOLogin = function (type) {
69            vm.bSigningIn = true;
70            WnErrorHandler.ClearAll();
71            let params = (new URL(document.location)).searchParams;
72            let goto = params.get("goto");
73
74            LoginHelperServices.SSOLogin("LOGIN", type, "LOGIN", goto);
75        }
76    }
77
78})();
79/**
80 * @license AngularJS v1.4.8
81 * (c) 2010-2015 Google, Inc. http://angularjs.org
82 * License: MIT
83 */
84(function(window, angular, undefined) {'use strict';
85
86/* jshint ignore:start */
87// this code is in the core, but not in angular-messages.js
88var isArray = angular.isArray;
89var forEach = angular.forEach;
90var isString = angular.isString;
91var jqLite = angular.element;
92/* jshint ignore:end */
93
94/**
95 * @ngdoc module
96 * @name ngMessages
97 * @description
98 *
99 * The `ngMessages` module provides enhanced support for displaying messages within templates
100 * (typically within forms or when rendering message objects that return key/value data).
101 * Instead of relying on JavaScript code and/or complex ng-if statements within your form template to
102 * show and hide error messages specific to the state of an input field, the `ngMessages` and
103 * `ngMessage` directives are designed to handle the complexity, inheritance and priority
104 * sequencing based on the order of how the messages are defined in the template.
105 *
106 * Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
107 * `ngMessage` and `ngMessageExp` directives.
108 *
109 * # Usage
110 * The `ngMessages` directive listens on a key/value collection which is set on the ngMessages attribute.
111 * Since the {@link ngModel ngModel} directive exposes an `$error` object, this error object can be
112 * used with `ngMessages` to display control error messages in an easier way than with just regular angular
113 * template directives.
114 *
115 * ```html
116 * <form name="myForm">
117 *   <label>
118 *     Enter text:
119 *     <input type="text" ng-model="field" name="myField" required minlength="5" />
120 *   </label>
121 *   <div ng-messages="myForm.myField.$error" role="alert">
122 *     <div ng-message="required">You did not enter a field</div>
123 *     <div ng-message="minlength, maxlength">
124 *       Your email must be between 5 and 100 characters long
125 *     </div>
126 *   </div>
127 * </form>
128 * ```
129 *
130 * Now whatever key/value entries are present within the provided object (in this case `$error`) then
131 * the ngMessages directive will render the inner first ngMessage directive (depending if the key values
132 * match the attribute value present on each ngMessage directive). In other words, if your errors
133 * object contains the following data:
134 *
135 * ```javascript
136 * <!-- keep in mind that ngModel automatically sets these error flags -->
137 * myField.$error = { minlength : true, required : true };
138 * ```
139 *
140 * Then the `required` message will be displayed first. When required is false then the `minlength` message
141 * will be displayed right after (since these messages are ordered this way in the template HTML code).
142 * The prioritization of each message is determined by what order they're present in the DOM.
143 * Therefore, instead of having custom JavaScript code determine the priority of what errors are
144 * present before others, the presentation of the errors are handled within the template.
145 *
146 * By default, ngMessages will only display one error at a time. However, if you wish to display all
147 * messages then the `ng-messages-multiple` attribute flag can be used on the element containing the
148 * ngMessages directive to make this happen.
149 *
150 * ```html
151 * <!-- attribute-style usage -->
152 * <div ng-messages="myForm.myField.$error" ng-messages-multiple>...</div>
153 *
154 * <!-- element-style usage -->
155 * <ng-messages for="myForm.myField.$error" multiple>...</ng-messages>
156 * ```
157 *
158 * ## Reusing and Overriding Messages
159 * In addition to prioritization, ngMessages also allows for including messages from a remote or an inline
160 * template. This allows for generic collection of messages to be reused across multiple parts of an
161 * application.
162 *
163 * ```html
164 * <script type="text/ng-template" id="error-messages">
165 *   <div ng-message="required">This field is required</div>
166 *   <div ng-message="minlength">This field is too short</div>
167 * </script>
168 *
169 * <div ng-messages="myForm.myField.$error" role="alert">
170 *   <div ng-messages-include="error-messages"></div>
171 * </div>
172 * ```
173 *
174 * However, including generic messages may not be useful enough to match all input fields, therefore,
175 * `ngMessages` provides the ability to override messages defined in the remote template by redefining
176 * them within the directive container.
177 *
178 * ```html
179 * <!-- a generic template of error messages known as "my-custom-messages" -->
180 * <script type="text/ng-template" id="my-custom-messages">
181 *   <div ng-message="required">This field is required</div>
182 *   <div ng-message="minlength">This field is too short</div>
183 * </script>
184 *
185 * <form name="myForm">
186 *   <label>
187 *     Email address
188 *     <input type="email"
189 *            id="email"
190 *            name="myEmail"
191 *            ng-model="email"
192 *            minlength="5"
193 *            required />
194 *   </label>
195 *   <!-- any ng-message elements that appear BEFORE the ng-messages-include will
196 *        override the messages present in the ng-messages-include template -->
197 *   <div ng-messages="myForm.myEmail.$error" role="alert">
198 *     <!-- this required message has overridden the template message -->
199 *     <div ng-message="required">You did not enter your email address</div>
200 *
201 *     <!-- this is a brand new message and will appear last in the prioritization -->
202 *     <div ng-message="email">Your email address is invalid</div>
203 *
204 *     <!-- and here are the generic error messages -->
205 *     <div ng-messages-include="my-custom-messages"></div>
206 *   </div>
207 * </form>
208 * ```
209 *
210 * In the example HTML code above the message that is set on required will override the corresponding
211 * required message defined within the remote template. Therefore, with particular input fields (such
212 * email addresses, date fields, autocomplete inputs, etc...), specialized error messages can be applied
213 * while more generic messages can be used to handle other, more general input errors.
214 *
215 * ## Dynamic Messaging
216 * ngMessages also supports using expressions to dynamically change key values. Using arrays and
217 * repeaters to list messages is also supported. This means that the code below will be able to
218 * fully adapt itself and display the appropriate message when any of the expression data changes:
219 *
220 * ```html
221 * <form name="myForm">
222 *   <label>
223 *     Email address
224 *     <input type="email"
225 *            name="myEmail"
226 *            ng-model="email"
227 *            minlength="5"
228 *            required />
229 *   </label>
230 *   <div ng-messages="myForm.myEmail.$error" role="alert">
231 *     <div ng-message="required">You did not enter your email address</div>
232 *     <div ng-repeat="errorMessage in errorMessages">
233 *       <!-- use ng-message-exp for a message whose key is given by an expression -->
234 *       <div ng-message-exp="errorMessage.type">{{ errorMessage.text }}</div>
235 *     </div>
236 *   </div>
237 * </form>
238 * ```
239 *
240 * The `errorMessage.type` expression can be a string value or it can be an array so
241 * that multiple errors can be associated with a single error message:
242 *
243 * ```html
244 *   <label>
245 *     Email address
246 *     <input type="email"
247 *            ng-model="data.email"
248 *            name="myEmail"
249 *            ng-minlength="5"
250 *            ng-maxlength="100"
251 *            required />
252 *   </label>
253 *   <div ng-messages="myForm.myEmail.$error" role="alert">
254 *     <div ng-message-exp="'required'">You did not enter your email address</div>
255 *     <div ng-message-exp="['minlength', 'maxlength']">
256 *       Your email must be between 5 and 100 characters long
257 *     </div>
258 *   </div>
259 * ```
260 *
261 * Feel free to use other structural directives such as ng-if and ng-switch to further control
262 * what messages are active and when. Be careful, if you place ng-message on the same element
263 * as these structural directives, Angular may not be able to determine if a message is active
264 * or not. Therefore it is best to place the ng-message on a child element of the structural
265 * directive.
266 *
267 * ```html
268 * <div ng-messages="myForm.myEmail.$error" role="alert">
269 *   <div ng-if="showRequiredError">
270 *     <div ng-message="required">Please enter something</div>
271 *   </div>
272 * </div>
273 * ```
274 *
275 * ## Animations
276 * If the `ngAnimate` module is active within the application then the `ngMessages`, `ngMessage` and
277 * `ngMessageExp` directives will trigger animations whenever any messages are added and removed from
278 * the DOM by the `ngMessages` directive.
279 *
280 * Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS
281 * class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no
282 * messages present. Therefore, CSS transitions and keyframes as well as JavaScript animations can
283 * hook into the animations whenever these classes are added/removed.
284 *
285 * Let's say that our HTML code for our messages container looks like so:
286 *
287 * ```html
288 * <div ng-messages="myMessages" class="my-messages" role="alert">
289 *   <div ng-message="alert" class="some-message">...</div>
290 *   <div ng-message="fail" class="some-message">...</div>
291 * </div>
292 * ```
293 *
294 * Then the CSS animation code for the message container looks like so:
295 *
296 * ```css
297 * .my-messages {
298 *   transition:1s linear all;
299 * }
300 * .my-messages.ng-active {
301 *   // messages are visible
302 * }
303 * .my-messages.ng-inactive {
304 *   // messages are hidden
305 * }
306 * ```
307 *
308 * Whenever an inner message is attached (becomes visible) or removed (becomes hidden) then the enter
309 * and leave animation is triggered for each particular element bound to the `ngMessage` directive.
310 *
311 * Therefore, the CSS code for the inner messages looks like so:
312 *
313 * ```css
314 * .some-message {
315 *   transition:1s linear all;
316 * }
317 *
318 * .some-message.ng-enter {}
319 * .some-message.ng-enter.ng-enter-active {}
320 *
321 * .some-message.ng-leave {}
322 * .some-message.ng-leave.ng-leave-active {}
323 * ```
324 *
325 * {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
326 */
327angular.module('ngMessages', [])
328
329   /**
330    * @ngdoc directive
331    * @module ngMessages
332    * @name ngMessages
333    * @restrict AE
334    *
335    * @description
336    * `ngMessages` is a directive that is designed to show and hide messages based on the state
337    * of a key/value object that it listens on. The directive itself complements error message
338    * reporting with the `ngModel` $error object (which stores a key/value state of validation errors).
339    *
340    * `ngMessages` manages the state of internal messages within its container element. The internal
341    * messages use the `ngMessage` directive and will be inserted/removed from the page depending
342    * on if they're present within the key/value object. By default, only one message will be displayed
343    * at a time and this depends on the prioritization of the messages within the template. (This can
344    * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
345    *
346    * A remote template can also be used to promote message reusability and messages can also be
347    * overridden.
348    *
349    * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
350    *
351    * @usage
352    * ```html
353    * <!-- using attribute directives -->
354    * <ANY ng-messages="expression" role="alert">
355    *   <ANY ng-message="stringValue">...</ANY>
356    *   <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
357    *   <ANY ng-message-exp="expressionValue">...</ANY>
358    * </ANY>
359    *
360    * <!-- or by using element directives -->
361    * <ng-messages for="expression" role="alert">
362    *   <ng-message when="stringValue">...</ng-message>
363    *   <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
364    *   <ng-message when-exp="expressionValue">...</ng-message>
365    * </ng-messages>
366    * ```
367    *
368    * @param {string} ngMessages an angular expression evaluating to a key/value object
369    *                 (this is typically the $error object on an ngModel instance).
370    * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
371    *
372    * @example
373    * <example name="ngMessages-directive" module="ngMessagesExample"
374    *          deps="angular-messages.js"
375    *          animations="true" fixBase="true">
376    *   <file name="index.html">
377    *     <form name="myForm">
378    *       <label>
379    *         Enter your name:
380    *         <input type="text"
381    *                name="myName"
382    *                ng-model="name"
383    *                ng-minlength="5"
384    *                ng-maxlength="20"
385    *                required />
386    *       </label>
387    *       <pre>myForm.myName.$error = {{ myForm.myName.$error | json }}</pre>
388    *
389    *       <div ng-messages="myForm.myName.$error" style="color:maroon" role="alert">
390    *         <div ng-message="required">You did not enter a field</div>
391    *         <div ng-message="minlength">Your field is too short</div>
392    *         <div ng-message="maxlength">Your field is too long</div>
393    *       </div>
394    *     </form>
395    *   </file>
396    *   <file name="script.js">
397    *     angular.module('ngMessagesExample', ['ngMessages']);
398    *   </file>
399    * </example>
400    */
401   .directive('ngMessages', ['$animate', function($animate) {
402     var ACTIVE_CLASS = 'ng-active';
403     var INACTIVE_CLASS = 'ng-inactive';
404
405     return {
406       require: 'ngMessages',
407       restrict: 'AE',
408       controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
409         var ctrl = this;
410         var latestKey = 0;
411         var nextAttachId = 0;
412
413         this.getAttachId = function getAttachId() { return nextAttachId++; };
414
415         var messages = this.messages = {};
416         var renderLater, cachedCollection;
417
418         this.render = function(collection) {
419           collection = collection || {};
420
421           renderLater = false;
422           cachedCollection = collection;
423
424           // this is true if the attribute is empty or if the attribute value is truthy
425           var multiple = isAttrTruthy($scope, $attrs.ngMessagesMultiple) ||
426                          isAttrTruthy($scope, $attrs.multiple);
427
428           var unmatchedMessages = [];
429           var matchedKeys = {};
430           var messageItem = ctrl.head;
431           var messageFound = false;
432           var totalMessages = 0;
433
434           // we use != instead of !== to allow for both undefined and null values
435           while (messageItem != null) {
436             totalMessages++;
437             var messageCtrl = messageItem.message;
438
439             var messageUsed = false;
440             if (!messageFound) {
441               forEach(collection, function(value, key) {
442                 if (!messageUsed && truthy(value) && messageCtrl.test(key)) {
443                   // this is to prevent the same error name from showing up twice
444                   if (matchedKeys[key]) return;
445                   matchedKeys[key] = true;
446
447                   messageUsed = true;
448                   messageCtrl.attach();
449                 }
450               });
451             }
452
453             if (messageUsed) {
454               // unless we want to display multiple messages then we should
455               // set a flag here to avoid displaying the next message in the list
456               messageFound = !multiple;
457             } else {
458               unmatchedMessages.push(messageCtrl);
459             }
460
461             messageItem = messageItem.next;
462           }
463
464           forEach(unmatchedMessages, function(messageCtrl) {
465             messageCtrl.detach();
466           });
467
468           unmatchedMessages.length !== totalMessages
469              ? $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS)
470              : $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
471         };
472
473         $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
474
475         this.reRender = function() {
476           if (!renderLater) {
477             renderLater = true;
478             $scope.$evalAsync(function() {
479               if (renderLater) {
480                 cachedCollection && ctrl.render(cachedCollection);
481               }
482             });
483           }
484         };
485
486         this.register = function(comment, messageCtrl) {
487           var nextKey = latestKey.toString();
488           messages[nextKey] = {
489             message: messageCtrl
490           };
491           insertMessageNode($element[0], comment, nextKey);
492           comment.$$ngMessageNode = nextKey;
493           latestKey++;
494
495           ctrl.reRender();
496         };
497
498         this.deregister = function(comment) {
499           var key = comment.$$ngMessageNode;
500           delete comment.$$ngMessageNode;
501           removeMessageNode($element[0], comment, key);
502           delete messages[key];
503           ctrl.reRender();
504         };
505
506         function findPreviousMessage(parent, comment) {
507           var prevNode = comment;
508           var parentLookup = [];
509           while (prevNode && prevNode !== parent) {
510             var prevKey = prevNode.$$ngMessageNode;
511             if (prevKey && prevKey.length) {
512               return messages[prevKey];
513             }
514
515             // dive deeper into the DOM and examine its children for any ngMessage
516             // comments that may be in an element that appears deeper in the list
517             if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) == -1) {
518               parentLookup.push(prevNode);
519               prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
520             } else {
521               prevNode = prevNode.previousSibling || prevNode.parentNode;
522             }
523           }
524         }
525
526         function insertMessageNode(parent, comment, key) {
527           var messageNode = messages[key];
528           if (!ctrl.head) {
529             ctrl.head = messageNode;
530           } else {
531             var match = findPreviousMessage(parent, comment);
532             if (match) {
533               messageNode.next = match.next;
534               match.next = messageNode;
535             } else {
536               messageNode.next = ctrl.head;
537               ctrl.head = messageNode;
538             }
539           }
540         }
541
542         function removeMessageNode(parent, comment, key) {
543           var messageNode = messages[key];
544
545           var match = findPreviousMessage(parent, comment);
546           if (match) {
547             match.next = messageNode.next;
548           } else {
549             ctrl.head = messageNode.next;
550           }
551         }
552       }]
553     };
554
555     function isAttrTruthy(scope, attr) {
556      return (isString(attr) && attr.length === 0) || //empty attribute
557             truthy(scope.$eval(attr));
558     }
559
560     function truthy(val) {
561       return isString(val) ? val.length : !!val;
562     }
563   }])
564
565   /**
566    * @ngdoc directive
567    * @name ngMessagesInclude
568    * @restrict AE
569    * @scope
570    *
571    * @description
572    * `ngMessagesInclude` is a directive with the purpose to import existing ngMessage template
573    * code from a remote template and place the downloaded template code into the exact spot
574    * that the ngMessagesInclude directive is placed within the ngMessages container. This allows
575    * for a series of pre-defined messages to be reused and also allows for the developer to
576    * determine what messages are overridden due to the placement of the ngMessagesInclude directive.
577    *
578    * @usage
579    * ```html
580    * <!-- using attribute directives -->
581    * <ANY ng-messages="expression" role="alert">
582    *   <ANY ng-messages-include="remoteTplString">...</ANY>
583    * </ANY>
584    *
585    * <!-- or by using element directives -->
586    * <ng-messages for="expression" role="alert">
587    *   <ng-messages-include src="expressionValue1">...</ng-messages-include>
588    * </ng-messages>
589    * ```
590    *
591    * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
592    *
593    * @param {string} ngMessagesInclude|src a string value corresponding to the remote template.
594    */
595   .directive('ngMessagesInclude',
596     ['$templateRequest', '$document', '$compile', function($templateRequest, $document, $compile) {
597
598     return {
599       restrict: 'AE',
600       require: '^^ngMessages', // we only require this for validation sake
601       link: function($scope, element, attrs) {
602         var src = attrs.ngMessagesInclude || attrs.src;
603         $templateRequest(src).then(function(html) {
604           $compile(html)($scope, function(contents) {
605             element.after(contents);
606
607             // the anchor is placed for debugging purposes
608             var anchor = jqLite($document[0].createComment(' ngMessagesInclude: ' + src + ' '));
609             element.after(anchor);
610
611             // we don't want to pollute the DOM anymore by keeping an empty directive element
612             element.remove();
613           });
614         });
615       }
616     };
617   }])
618
619   /**
620    * @ngdoc directive
621    * @name ngMessage
622    * @restrict AE
623    * @scope
624    *
625    * @description
626    * `ngMessage` is a directive with the purpose to show and hide a particular message.
627    * For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element
628    * must be situated since it determines which messages are visible based on the state
629    * of the provided key/value map that `ngMessages` listens on.
630    *
631    * More information about using `ngMessage` can be found in the
632    * {@link module:ngMessages `ngMessages` module documentation}.
633    *
634    * @usage
635    * ```html
636    * <!-- using attribute directives -->
637    * <ANY ng-messages="expression" role="alert">
638    *   <ANY ng-message="stringValue">...</ANY>
639    *   <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
640    * </ANY>
641    *
642    * <!-- or by using element directives -->
643    * <ng-messages for="expression" role="alert">
644    *   <ng-message when="stringValue">...</ng-message>
645    *   <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
646    * </ng-messages>
647    * ```
648    *
649    * @param {expression} ngMessage|when a string value corresponding to the message key.
650    */
651  .directive('ngMessage', ngMessageDirectiveFactory('AE'))
652
653
654   /**
655    * @ngdoc directive
656    * @name ngMessageExp
657    * @restrict AE
658    * @scope
659    *
660    * @description
661    * `ngMessageExp` is a directive with the purpose to show and hide a particular message.
662    * For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element
663    * must be situated since it determines which messages are visible based on the state
664    * of the provided key/value map that `ngMessages` listens on.
665    *
666    * @usage
667    * ```html
668    * <!-- using attribute directives -->
669    * <ANY ng-messages="expression">
670    *   <ANY ng-message-exp="expressionValue">...</ANY>
671    * </ANY>
672    *
673    * <!-- or by using element directives -->
674    * <ng-messages for="expression">
675    *   <ng-message when-exp="expressionValue">...</ng-message>
676    * </ng-messages>
677    * ```
678    *
679    * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
680    *
681    * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message 
681key.
682    */
683  .directive('ngMessageExp', ngMessageDirectiveFactory('A'));
684
685function ngMessageDirectiveFactory(restrict) {
686  return ['$animate', function($animate) {
687    return {
688      restrict: 'AE',
689      transclude: 'element',
690      terminal: true,
691      require: '^^ngMessages',
692      link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
693        var commentNode = element[0];
694
695        var records;
696        var staticExp = attrs.ngMessage || attrs.when;
697        var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
698        var assignRecords = function(items) {
699          records = items
700              ? (isArray(items)
701                    ? items
702                    : items.split(/[\s,]+/))
703              : null;
704          ngMessagesCtrl.reRender();
705        };
706
707        if (dynamicExp) {
708          assignRecords(scope.$eval(dynamicExp));
709          scope.$watchCollection(dynamicExp, assignRecords);
710        } else {
711          assignRecords(staticExp);
712        }
713
714        var currentElement, messageCtrl;
715        ngMessagesCtrl.register(commentNode, messageCtrl = {
716          test: function(name) {
717            return contains(records, name);
718          },
719          attach: function() {
720            if (!currentElement) {
721              $transclude(scope, function(elm) {
722                $animate.enter(elm, null, element);
723                currentElement = elm;
724
725                // Each time we attach this node to a message we get a new id that we can match
726                // when we are destroying the node later.
727                var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
728
729                // in the event that the parent element is destroyed
730                // by any other structural directive then it's time
731                // to deregister the message from the controller
732                currentElement.on('$destroy', function() {
733                  if (currentElement && currentElement.$$attachId === $$attachId) {
734                    ngMessagesCtrl.deregister(commentNode);
735                    messageCtrl.detach();
736                  }
737                });
738              });
739            }
740          },
741          detach: function() {
742            if (currentElement) {
743              var elm = currentElement;
744              currentElement = null;
745              $animate.leave(elm);
746            }
747          }
748        });
749      }
750    };
751  }];
752
753  function contains(collection, key) {
754    if (collection) {
755      return isArray(collection)
756          ? collection.indexOf(key) >= 0
757          : collection.hasOwnProperty(key);
758    }
759  }
760}
761
762
763})(window, window.angular);
764
765(function () {
766    "use strict";
767    angular.module('LoginHelper', ['WebnamesForm']);
768})();
769
770(function () {
771    "use strict";
772
773    angular.module('LoginHelper')
774        .directive('wnLogin', ['$log', '$window', LoginController]);
775
776    function LoginController($log, $window) {
777        return {
778            scope: {},
779            restrict: 'E',
780            templateUrl: 'wn-login.cshtml',
781            require: ['ngModel', '^form'],
782            link: function (scope, ele, attrs, arrCtrls) {
783                scope.ngModelCtrl = arrCtrls[0];
784                scope.ngFormCtrl = arrCtrls[1]; // scoped for template to use with <wn-validation-messages>
785
786                scope.credential = {};
787
788                scope.ngModelCtrl.$render = function () {
789                    scope.credential = scope.ngModelCtrl.$viewValue;
790                }; // expose the required ng-model to scope for UI
791
792                scope.PasswordRecoveryLink = $window._SmartURL + 'myaccount/passwordrecovery';
793                scope.NewAccountLink = $window._SmartURL + 'new-account';
794
795            }
796
797        }
798    }
799})();
800
801(function () {
802    "use strict";
803
804    angular.module('LoginHelper')
805        .directive('wnTwoStepAuth', ['$log', '$window', TwoStepAuthController]);
806
807    function TwoStepAuthController($log, $window) {
808        return {
809            scope: {},
810            restrict: 'E',
811            templateUrl: 'wn-two-step-auth.cshtml',
812            require: 'ngModel',
813            link: function (scope, ele, attrs, ngModelCtrl) {
814
815                ngModelCtrl.$render = function () {
816                    scope.verificationCode = ngModelCtrl.$viewValue.verificationCode || '';
817                    scope.maskedPhoneNumber = ngModelCtrl.$viewValue.maskedPhoneNumber;
818                };
819                
820                scope.$watch('verificationCode', function () {
821                    ngModelCtrl.$setViewValue({ verificationCode: scope.verificationCode });
822                });
823            }
824        }
825    }
826})();
827(function () {
828    "use strict";
829
830    angular.module('LoginHelper')
831    .service('LoginHelperServices', ['$http', '$q', '$log', '$window', function ($http, $q, $log, $window) {
832        this.Login = function (user, pass, gotoURL, verificationCode, isSSO) {
833            var sURL = $window._SmartURL + 'Home/Login';
834            var deferred = $q.defer();
835            if (gotoURL == null)
836            {
837                gotoURL = '';
838            }
839
840            // submit request
841            $http.post(sURL, { username: user, password: pass, verificationCode: verificationCode, isSSO: isSSO })
842                .then(function (resp) {
843                    var result = { isLoggedIn: false, redirect: null, alert: null, isError: false, isTwoStepAuth: false};
844                    deferred.resolve(parseLoginResult(resp.data, result, gotoURL));
845                },
846                function (resp) { deferred.reject({ isLoggedIn: false, redirect: null, alert: null, isError: true, partialUserChange: resp.data.partialUserChange }); });
847            return deferred.promise;
848        }
849
850        this.SSOLogin = function (SSOAction, type, source, sGoto) {
851            $http.post($window._SmartURL + "Login/SSOLogin", { eSSOAction: SSOAction, eSSOType: type, eSSOSource: source, sGoto: sGoto }).then(function (resp) {
852                $window.location = resp.data.url;
853            });
854        }
855
856        this.UnlinkExternalAccount = function () {
857            return $http.post($window._SmartURL + "Login/UnlinkExternalAccount");
858        }
859
860        this.GetErrorMessage = getErrorMessage;
861
862        function getErrorMessage(iErrorCode) {
863            // no login errors
864            if (!iErrorCode || iErrorCode === 2000) return null;
865            
866            switch (iErrorCode) {
867                case 1100:
868                    return "You have entered an incorrect username or password. <a href='" + $window._SmartURL + "myaccount/passwordrecovery' class='alert-link'>Forgot your credentials?</a>";
869                case 1002:
870                case 1007:
871                    return "This account could not login, Please contact Webnames.ca Support Toll Free: 1-866-221-7878.";
872                case 1006:
873                    return "Your IP is not in the allowed IP addresses for this account.";
874                case 1101:
875                    return "You have entered an incorrect verification code.";
876                case 1102:
877                    return "You have reached the maximum attempts.  Please contact Webnames.ca Support Toll Free: 1-866-221-7878.";
878                case 1200:
879                    return "An account already exists with this account.";
880                case 1201:
881                    return "Sorry, no Webnames account is linked to that Google or Microsoft account. Please ensure to <strong><a href='https://www.webnames.ca/help/wnaccount/default.htm#Resources/Articles/Webnames_Account/Information_and_Access/Single_Sign-On.htm' target='_blank'>link your accounts first</a></strong>  before using this feature.";
882                case 1205:
883                    return "Instead of a dedicated Webnames username and password, this account is configured to use Single-Sign On (SSO) from Google. Please click the <b>\"sign in with Google\"</b> button to log into this account.";
884                case 1215:
885                    return "Instead of a dedicated Webnames username and password, this account is configured to use Single-Sign On (SSO) from Microsoft. Please click the <b>\"sign in with Microsoft\"</b> button to log into this account.";
886                default:
887                    return "Error occurred during login.";
888            }
889        }
890
891        function parseLoginResult(responseData, result, gotoURL) {
892            result = result || {};
893            result.partialUserChange = responseData.partialUserChange;
894
895            if (responseData.action === 2000) {
896                result.isLoggedIn = true;
897                result.redirect = gotoURL;
898                result.multiRegistrant = responseData.multiRegistrant;
899            } else if (responseData.action === 1100) {
900                result.alert = { type: 'danger', msg: getErrorMessage(1100) };
901            } else if (responseData.action === 2001) {                
902                result.redirect = 'terms/signterms.aspx?goto=' + encodeURIComponent(gotoURL);
903            } else if (responseData.action === 2003) {
904                result.isLoggedIn = true;
905                result.redirect = (gotoURL ? $window._SmartURL + gotoURL : 'myaccount/summary');
906                result.multiRegistrant = responseData.multiRegistrant;
907            } else if (responseData.action === 2004) {
908                result.isLoggedIn = true;
909                result.redirect = $window._SmartURL + 'myaccount/account-settings#!/security-settings';
910            } else if (responseData.action === 1004) {
911                result.redirect = 'account-locked-out';
912            } else if (responseData.action === 1007 || responseData.action === 1005) {
913                result.redirect = 'denied.htm';
914            } else if (responseData.action === 1009) {
915                result.redirect = 'sc/accountRequiresManualVerification.aspx';
916            } else if (responseData.action === 1002 || responseData.action === 1007) {
917                result.alert = { type: 'danger', msg: getErrorMessage(1102) };
918            } else if (responseData.action === 1006) {
919                result.alert = { type: 'danger', msg: getErrorMessage(1006) };
920            } else if (responseData.action === 2002) {
921                result.isTwoStepAuth = true;
922                result.maskedPhoneNumber = responseData.maskedPhoneNumber;
923                result.verificationCode = responseData.verificationCode;
924            } else if (responseData.action === 1101) {
925                result.alert = { type: 'danger', msg: getErrorMessage(1101) };
926            } else if (responseData.action === 1102) {
927                result.alert = { type: 'danger', msg: getErrorMessage(1102) };
928            } else if (responseData.action === 1200) {
929                result.alert = { type: 'danger', msg: getErrorMessage(1200) };
930            } else if (responseData.action === 1201) {
931                result.alert = { type: 'danger', msg: getErrorMessage(1201) };
932            } else if (responseData.action === 1205) {
933                result.alert = { type: 'danger', msg: getErrorMessage(1205) };
934            } else if (responseData.action === 1215) {
935                result.alert = { type: 'danger', msg: getErrorMessage(1215) };
936            } else {
937                result.alert = { type: 'danger', msg: 'Error occurred during login.' };
938            }
939            return result;
940        }
941
942    }]);
943})();
944
945/*
946    Webnames Formn
947    --------------------------------
948    Module to hold common form directives and filters
949    
950    Author: Ambrose Chan 2016
951*/
952
953(function () {
954    "use strict";
955
956    angular.module('WebnamesForm', ['ngSanitize', 'ui.select', 'ngMessages','ui.mask']);
957
958})();
959(function () {
960    angular.module('WebnamesForm')
961         .filter('provinceFrom', function () {
962             return function (arProvinces, selectedCountryID) {
963                 return arProvinces.filter(function (province) {
964                     return province.countryID === selectedCountryID;
965                 });
966             }
967         })
968        .filter('formatPhoneCountry', [function () {
969             return function (phoneCountry) {
970                return phoneCountry.CountryCode + ' ' + phoneCountry.CountryName + (phoneCountry.PhoneCode ? ' +' + phoneCountry.PhoneCode : '');
971            }
972        }]);
973})();
974(function () {
975    angular.module('WebnamesForm')
976        .service('FormDataStore', ['$http', FormDataStore]);
977
978    function FormDataStore($http) {
979        var service = this;
980        var sControllerURL = window._SmartURL + 'Helper';
981
982        service.phoneCountryCodes = null;
983        service.countries = null;
984        service.filteredPhoneCountryCodes = null;
985        service.filteredCountries = null;
986        service.domainPurposeIndustries = null;
987
988        service.GetCountries = function () {
989            if (service.countries === null) {
990                service.countries = $http.get(sControllerURL + '/Locations').then(function (resp) {
991                    resp.data.countries = reorderCanadaUs(resp.data.countries);
992                    return resp;
993                });
994            }
995            return service.countries;
996        }
997
998        service.GetPhoneCountryCodes = function (lFilteredCountryIDs) {
999            if (service.phoneCountryCodes === null) {
1000                service.phoneCountryCodes = $http.get(sControllerURL + '/PhoneCountryCodes').then(function (resp) {
1001                    resp.data = reorderCanadaUs(resp.data);
1002                    return resp;
1003                });
1004            }
1005
1006            return service.phoneCountryCodes;
1007        }
1008
1009        service.GetFilteredCountries = function (lFilteredCountryIDs) {
1010            if (service.filteredCountries === null) {
1011                service.filteredCountries = $http.get(sControllerURL + '/Locations', { params: { lFilteredCountryIDs: lFilteredCountryIDs } }).then(function (resp) {
1012                    resp.data.filteredCountries = reorderCanadaUs(resp.data.countries);
1013                    return resp;
1014                });
1015            }
1016            return service.filteredCountries;
1017        }
1018
1019        service.GetFilteredPhoneCountryCodes = function (lFilteredCountryIDs) {
1020            if (service.filteredPhoneCountryCodes === null) {
1021                service.filteredPhoneCountryCodes = $http.get(sControllerURL + '/PhoneCountryCodes', { params: { lFilteredCountryIDs: lFilteredCountryIDs } }).then(function (resp) {
1022                    resp.data = reorderCanadaUs(resp.data);
1023                    return resp;
1024                });
1025            }
1026
1027            return service.filteredPhoneCountryCodes;
1028        }
1029        
1030        service.GetDomainPurposeIndustries = function () {
1031            if (service.domainPurposeIndustries === null) {
1032                service.domainPurposeIndustries = $http.get(sControllerURL + '/DomainPurposeIndustries').then(function (resp) {
1033                    return resp;
1034                });
1035            }
1036            return service.domainPurposeIndustries;
1037        }
1038
1039        service.CheckUsernameAvailability = function (sUserName, sExistingUsername) {
1040            return $http.post(sControllerURL + '/CheckUserNameAvailability', { sUserName: sUserName, sExistingUsername: sExistingUsername });
1041        }
1042
1043        // Move Canada/ US to the beginning of array
1044        function reorderCanadaUs(arCountries) {
1045            var iIndexOfUSA = _.findIndex(arCountries, function (c) { return c.CountryID === "USA" });
1046            if (iIndexOfUSA !== -1) {
1047                arCountries.unshift(arCountries.splice(iIndexOfUSA, 1)[0]);
1048            }
1049
1050            var iIndexOfCanada = _.findIndex(arCountries, function (c) { return c.CountryID === "CAN" });
1051            if (iIndexOfCanada !== -1) {
1052                arCountries.unshift(arCountries.splice(iIndexOfCanada, 1)[0]);
1053            }
1054
1055            return arCountries;
1056        }
1057
1058    }
1059})();
1060/*!
1061 * angular-ui-mask
1062 * https://github.com/angular-ui/ui-mask
1063 * Version: 1.6.7 - 2015-12-30T03:32:53.475Z
1064 * License: MIT
1065 */
1066
1067
1068(function () { 
1069'use strict';
1070/*
1071 Attaches input mask onto input element
1072 */
1073angular.module('ui.mask', [])
1074        .value('uiMaskConfig', {
1075            maskDefinitions: {
1076                '9': /\d/,
1077                'A': /[a-zA-Z]/,
1078                '*': /[a-zA-Z0-9]/
1079            },
1080            clearOnBlur: true,
1081            eventsToHandle: ['input', 'keyup', 'click', 'focus']
1082        })
1083        .directive('uiMask', ['uiMaskConfig', function(maskConfig) {
1084                function isFocused (elem) {
1085                  return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
1086                }
1087
1088                return {
1089                    priority: 100,
1090                    require: 'ngModel',
1091                    restrict: 'A',
1092                    compile: function uiMaskCompilingFunction() {
1093                        var options = maskConfig;
1094
1095                        return function uiMaskLinkingFunction(scope, iElement, iAttrs, controller) {
1096                            var maskProcessed = false, eventsBound = false,
1097                                    maskCaretMap, maskPatterns, maskPlaceholder, maskComponents,
1098                                    // Minimum required length of the value to be considered valid
1099                                    minRequiredLength,
1100                                    value, valueMasked, isValid,
1101                                    // Vars for initializing/uninitializing
1102                                    originalPlaceholder = iAttrs.placeholder,
1103                                    originalMaxlength = iAttrs.maxlength,
1104                                    // Vars used exclusively in eventHandler()
1105                                    oldValue, oldValueUnmasked, oldCaretPosition, oldSelectionLength;
1106
1107                            var originalIsEmpty = controller.$isEmpty;
1108	                        controller.$isEmpty = function(value) {
1109		                        if (maskProcessed) {
1110			                        return originalIsEmpty(unmaskValue(value || ''));
1111		                        } else {
1112			                        return originalIsEmpty(value);
1113		                        }
1114	                        };
1115
1116                            function initialize(maskAttr) {
1117                                if (!angular.isDefined(maskAttr)) {
1118                                    return uninitialize();
1119                                }
1120                                processRawMask(maskAttr);
1121                                if (!maskProcessed) {
1122                                    return uninitialize();
1123                                }
1124                                initializeElement();
1125                                bindEventListeners();
1126                                return true;
1127                            }
1128
1129                            function initPlaceholder(placeholderAttr) {
1130                                if ( ! placeholderAttr) {
1131                                    return;
1132                                }
1133
1134                                maskPlaceholder = placeholderAttr;
1135
1136                                // If the mask is processed, then we need to update the value
1137                                // but don't set the value if there is nothing entered into the element
1138                                // and there is a placeholder attribute on the element because that
1139                                // will only set the value as the blank maskPlaceholder
1140                                // and override the placeholder on the element
1141                                if (maskProcessed && !(iElement.val().length === 0 && angular.isDefined(iAttrs.placeholder))) {
1142                                    iElement.val(maskValue(unmaskValue(iElement.val())));
1143                                }
1144                            }
1145
1146                            function initPlaceholderChar() {
1147                                return initialize(iAttrs.uiMask);
1148                            }
1149
1150                            var modelViewValue = false;
1151                            iAttrs.$observe('modelViewValue', function(val) {
1152                                if (val === 'true') {
1153                                    modelViewValue = true;
1154                                }
1155                            });
1156
1157                            function formatter(fromModelValue) {
1158                                if (!maskProcessed) {
1159                                    return fromModelValue;
1160                                }
1161                                value = unmaskValue(fromModelValue || '');
1162                                isValid = validateValue(value);
1163                                controller.$setValidity('mask', isValid);
1164                                return isValid && value.length ? maskValue(value) : undefined;
1165                            }
1166
1167                            function parser(fromViewValue) {
1168                                if (!maskProcessed) {
1169                                    return fromViewValue;
1170                                }
1171                                value = unmaskValue(fromViewValue || '');
1172                                isValid = validateValue(value);
1173                                // We have to set viewValue manually as the reformatting of the input
1174                                // value performed by eventHandler() doesn't happen until after
1175                                // this parser is called, which causes what the user sees in the input
1176                                // to be out-of-sync with what the controller's $viewValue is set to.
1177                                controller.$viewValue = value.length ? maskValue(value) : '';
1178                                controller.$setValidity('mask', isValid);
1179                                if (isValid) {
1180                                    return modelViewValue ? controller.$viewValue : value;
1181                                } else {
1182                                    return undefined;
1183                                }
1184                            }
1185
1186                            var linkOptions = {};
1187
1188                            if (iAttrs.uiOptions) {
1189                                linkOptions = scope.$eval('[' + iAttrs.uiOptions + ']');
1190                                if (angular.isObject(linkOptions[0])) {
1191                                    // we can't use angular.copy nor angular.extend, they lack the power to do a deep merge
1192                                    linkOptions = (function(original, current) {
1193                                        for (var i in original) {
1194                                            if (Object.prototype.hasOwnProperty.call(original, i)) {
1195                                                if (current[i] === undefined) {
1196                                                    current[i] = angular.copy(original[i]);
1197                                                } else {
1198                                                    if (angular.isObject(current[i]) && !angular.isArray(current[i])) {
1199                                                        current[i] = angular.extend({}, original[i], current[i]);
1200                                                    }
1201                                                }
1202                                            }
1203                                        }
1204                                        return current;
1205                                    })(options, linkOptions[0]);
1206                                } else {
1207                                    linkOptions = options;  //gotta be a better way to do this..
1208                                }
1209                            } else {
1210                                linkOptions = options;
1211                            }
1212
1213                            iAttrs.$observe('uiMask', initialize);
1214                            if (angular.isDefined(iAttrs.uiMaskPlaceholder)) {
1215                                iAttrs.$observe('uiMaskPlaceholder', initPlaceholder);
1216                            }
1217                            else {
1218                                iAttrs.$observe('placeholder', initPlaceholder);
1219                            }
1220                            if (angular.isDefined(iAttrs.uiMaskPlaceholderChar)) {
1221                                iAttrs.$observe('uiMaskPlaceholderChar', initPlaceholderChar);
1222                            }
1223
1224                            controller.$formatters.push(formatter);
1225                            controller.$parsers.unshift(parser);
1226
1227                            function uninitialize() {
1228                                maskProcessed = false;
1229                                unbindEventListeners();
1230
1231                                if (angular.isDefined(originalPlaceholder)) {
1232                                    iElement.attr('placeholder', originalPlaceholder);
1233                                } else {
1234                                    iElement.removeAttr('placeholder');
1235                                }
1236
1237                                if (angular.isDefined(originalMaxlength)) {
1238                                    iElement.attr('maxlength', originalMaxlength);
1239                                } else {
1240                                    iElement.removeAttr('maxlength');
1241                                }
1242
1243                                iElement.val(controller.$modelValue);
1244                                controller.$viewValue = controller.$modelValue;
1245                                return false;
1246                            }
1247
1248                            function initializeElement() {
1249                                value = oldValueUnmasked = unmaskValue(controller.$modelValue || '');
1250                                valueMasked = oldValue = maskValue(value);
1251                                isValid = validateValue(value);
1252                                if (iAttrs.maxlength) { // Double maxlength to allow pasting new val at end of mask
1253                                    iElement.attr('maxlength', maskCaretMap[maskCaretMap.length - 1] * 2);
1254                                }
1255                                if ( ! originalPlaceholder) {
1256                                    iElement.attr('placeholder', maskPlaceholder);
1257                                }
1258                                var viewValue = controller.$modelValue;
1259                                var idx = controller.$formatters.length;
1260                                while(idx--) {
1261                                    viewValue = controller.$formatters[idx](viewValue);
1262                                }
1263                                controller.$viewValue = viewValue || '';
1264                                controller.$render();
1265                                // Not using $setViewValue so we don't clobber the model value and dirty the form
1266                                // without any kind of user interaction.
1267                            }
1268
1269                            function bindEventListeners() {
1270                                if (eventsBound) {
1271                                    return;
1272                                }
1273                                iElement.bind('blur', blurHandler);
1274                                iElement.bind('mousedown mouseup', mouseDownUpHandler);
1275                                iElement.bind(linkOptions.eventsToHandle.join(' '), eventHandler);
1276                                eventsBound = true;
1277                            }
1278
1279                            function unbindEventListeners() {
1280                                if (!eventsBound) {
1281                                    return;
1282                                }
1283                                iElement.unbind('blur', blurHandler);
1284                                iElement.unbind('mousedown', mouseDownUpHandler);
1285                                iElement.unbind('mouseup', mouseDownUpHandler);
1286                                iElement.unbind('input', eventHandler);
1287                                iElement.unbind('keyup', eventHandler);
1288                                iElement.unbind('click', eventHandler);
1289                                iElement.unbind('focus', eventHandler);
1290                                eventsBound = false;
1291                            }
1292
1293                            function validateValue(value) {
1294                                // Zero-length value validity is ngRequired's determination
1295                                return value.length ? value.length >= minRequiredLength : true;
1296                            }
1297
1298                            function unmaskValue(value) {
1299                                var valueUnmasked = '',
1300                                        maskPatternsCopy = maskPatterns.slice();
1301                                // Preprocess by stripping mask components from value
1302                                value = value.toString();
1303                                angular.forEach(maskComponents, function(component) {
1304                                    value = value.replace(component, '');
1305                                });
1306                                angular.forEach(value.split(''), function(chr) {
1307                                    if (maskPatternsCopy.length && maskPatternsCopy[0].test(chr)) {
1308                                        valueUnmasked += chr;
1309                                        maskPatternsCopy.shift();
1310                                    }
1311                                });
1312
1313                                return valueUnmasked;
1314                            }
1315
1316                            function maskValue(unmaskedValue) {
1317                                var valueMasked = '',
1318                                        maskCaretMapCopy = maskCaretMap.slice();
1319
1320                                angular.forEach(maskPlaceholder.split(''), function(chr, i) {
1321                                    if (unmaskedValue.length && i === maskCaretMapCopy[0]) {
1322                                        valueMasked += unmaskedValue.charAt(0) || '_';
1323                                        unmaskedValue = unmaskedValue.substr(1);
1324                                        maskCaretMapCopy.shift();
1325                                    }
1326                                    else {
1327                                        valueMasked += chr;
1328                                    }
1329                                });
1330                                return valueMasked;
1331                            }
1332
1333                            function getPlaceholderChar(i) {
1334                                var placeholder = angular.isDefined(iAttrs.uiMaskPlaceholder) ? iAttrs.uiMaskPlaceholder : iAttrs.placeholder,
1335                                    defaultPlaceholderChar;
1336
1337                                if (angular.isDefined(placeholder) && placeholder[i]) {
1338                                    return placeholder[i];
1339                                } else {
1340                                    defaultPlaceholderChar = angular.isDefined(iAttrs.uiMaskPlaceholderChar) && iAttrs.uiMaskPlaceholderChar ? iAttrs.uiMaskPlaceholderChar : '_';
1341                                    return (defaultPlaceholderChar.toLowerCase() === 'space') ? ' ' : defaultPlaceholderChar[0];
1342                                }
1343                            }
1344
1345                            // Generate array of mask components that will be stripped from a masked value
1346                            // before processing to prevent mask components from being added to the unmasked value.
1347                            // E.g., a mask pattern of '+7 9999' won't have the 7 bleed into the unmasked value.
1348                            // If a maskable char is followed by a mask char and has a mask
1349                            // char behind it, we'll split it into 
1349it's own component so if
1350                            // a user is aggressively deleting in the input and a char ahead
1351                            // of the maskable char gets deleted, we'll still be able to strip
1352                            // it in the unmaskValue() preprocessing.
1353                            function getMaskComponents() {
1354                                var maskPlaceholderChars = maskPlaceholder.split(''),
1355                                        maskPlaceholderCopy;
1356
1357                                //maskCaretMap can have bad values if the input has the ui-mask attribute implemented as an obversable property, i.e. the demo page
1358                                if (maskCaretMap && !isNaN(maskCaretMap[0])) {
1359                                    //Instead of trying to manipulate the RegEx based on the placeholder characters
1360                                    //we can simply replace the placeholder characters based on the already built
1361                                    //maskCaretMap to underscores and leave the original working RegEx to get the proper
1362                                    //mask components
1363                                    angular.forEach(maskCaretMap, function(value) {
1364                                        maskPlaceholderChars[value] = '_';
1365                                    });
1366                                }
1367                                maskPlaceholderCopy = maskPlaceholderChars.join('');
1368                                return maskPlaceholderCopy.replace(/[_]+/g, '_').replace(/([^_]+)([a-zA-Z0-9])([^_])/g, '$1$2_$3').split('_');
1369                            }
1370
1371                            function processRawMask(mask) {
1372                                var characterCount = 0;
1373
1374                                maskCaretMap = [];
1375                                maskPatterns = [];
1376                                maskPlaceholder = '';
1377
1378                                if (angular.isString(mask)) {
1379                                    minRequiredLength = 0;
1380
1381                                    var isOptional = false,
1382                                            numberOfOptionalCharacters = 0,
1383                                            splitMask = mask.split('');
1384
1385                                    angular.forEach(splitMask, function(chr, i) {
1386                                        if (linkOptions.maskDefinitions[chr]) {
1387
1388                                            maskCaretMap.push(characterCount);
1389
1390                                            maskPlaceholder += getPlaceholderChar(i - numberOfOptionalCharacters);
1391                                            maskPatterns.push(linkOptions.maskDefinitions[chr]);
1392
1393                                            characterCount++;
1394                                            if (!isOptional) {
1395                                                minRequiredLength++;
1396                                            }
1397
1398                                            isOptional = false;
1399                                        }
1400                                        else if (chr === '?') {
1401                                            isOptional = true;
1402                                            numberOfOptionalCharacters++;
1403                                        }
1404                                        else {
1405                                            maskPlaceholder += chr;
1406                                            characterCount++;
1407                                        }
1408                                    });
1409                                }
1410                                // Caret position immediately following last position is valid.
1411                                maskCaretMap.push(maskCaretMap.slice().pop() + 1);
1412
1413                                maskComponents = getMaskComponents();
1414                                maskProcessed = maskCaretMap.length > 1 ? true : false;
1415                            }
1416
1417                            var prevValue = iElement.val();
1418                            function blurHandler() {
1419                                if (linkOptions.clearOnBlur) {
1420                                    oldCaretPosition = 0;
1421                                    oldSelectionLength = 0;
1422                                    if (!isValid || value.length === 0) {
1423                                        valueMasked = '';
1424                                        iElement.val('');
1425                                        scope.$apply(function() {
1426                                            //don't call $setViewValue to avoid changing $pristine state.
1427                                            controller.$viewValue = '';
1428                                        });
1429                                    }
1430                                }
1431                                //Check for different value and trigger change.
1432                                if (value !== prevValue) {
1433                                    triggerChangeEvent(iElement[0]);
1434                                }
1435                                prevValue = value;
1436                            }
1437
1438                            function triggerChangeEvent(element) {
1439                                var change;
1440                                if (angular.isFunction(window.Event) && !element.fireEvent) {
1441                                    // modern browsers and Edge
1442                                    change = new Event('change', {
1443                                        view: window,
1444                                        bubbles: true,
1445                                        cancelable: false
1446                                    });
1447                                    element.dispatchEvent(change);
1448                                } else if ('createEvent' in document) {
1449                                    // older browsers
1450                                    change = document.createEvent('HTMLEvents');
1451                                    change.initEvent('change', false, true);
1452                                    element.dispatchEvent(change);
1453                                }
1454                                else if (element.fireEvent) {
1455                                    // IE <= 11
1456                                    element.fireEvent('onchange');
1457                                }
1458                            }
1459
1460                            function mouseDownUpHandler(e) {
1461                                if (e.type === 'mousedown') {
1462                                    iElement.bind('mouseout', mouseoutHandler);
1463                                } else {
1464                                    iElement.unbind('mouseout', mouseoutHandler);
1465                                }
1466                            }
1467
1468                            iElement.bind('mousedown mouseup', mouseDownUpHandler);
1469
1470                            function mouseoutHandler() {
1471                                /*jshint validthis: true */
1472                                oldSelectionLength = getSelectionLength(this);
1473                                iElement.unbind('mouseout', mouseoutHandler);
1474                            }
1475
1476                            function eventHandler(e) {
1477                                /*jshint validthis: true */
1478                                e = e || {};
1479                                // Allows more efficient minification
1480                                var eventWhich = e.which,
1481                                        eventType = e.type;
1482
1483                                // Prevent shift and ctrl from mucking with old values
1484                                if (eventWhich === 16 || eventWhich === 91) {
1485                                    return;
1486                                }
1487
1488                                var val = iElement.val(),
1489                                        valOld = oldValue,
1490                                        valMasked,
1491                                        valAltered = false,
1492                                        valUnmasked = unmaskValue(val),
1493                                        valUnmaskedOld = oldValueUnmasked,
1494                                        caretPos = getCaretPosition(this) || 0,
1495                                        caretPosOld = oldCaretPosition || 0,
1496                                        caretPosDelta = caretPos - caretPosOld,
1497                                        caretPosMin = maskCaretMap[0],
1498                                        caretPosMax = maskCaretMap[valUnmasked.length] || maskCaretMap.slice().shift(),
1499                                        selectionLenOld = oldSelectionLength || 0,
1500                                        isSelected = getSelectionLength(this) > 0,
1501                                        wasSelected = selectionLenOld > 0,
1502                                        // Case: Typing a character to overwrite a selection
1503                                        isAddition = (val.length > valOld.length) || (selectionLenOld && val.length > valOld.length - selectionLenOld),
1504                                        // Case: Delete and backspace behave identically on a selection
1505                                        isDeletion = (val.length < valOld.length) || (selectionLenOld && val.length === valOld.length - selectionLenOld),
1506                                        isSelection = (eventWhich >= 37 && eventWhich <= 40) && e.shiftKey, // Arrow key codes
1507
1508                                        isKeyLeftArrow = eventWhich === 37,
1509                                        // Necessary due to "input" event not providing a key code
1510                                        isKeyBackspace = eventWhich === 8 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === -1)),
1511                                        isKeyDelete = eventWhich === 46 || (eventType !== 'keyup' && isDeletion && (caretPosDelta === 0) && !wasSelected),
1512                                        // Handles cases where caret is moved and placed in front of invalid maskCaretMap position. Logic below
1513                                        // ensures that, on click or leftward caret placement, caret is moved leftward until directly right of
1514                                        // non-mask character. Also applied to click since users are (arguably) more likely to backspace
1515                                        // a character when clicking within a filled input.
1516                                        caretBumpBack = (isKeyLeftArrow || isKeyBackspace || eventType === 'click') && caretPos > caretPosMin;
1517
1518                                oldSelectionLength = getSelectionLength(this);
1519
1520                                // These events don't require any action
1521                                if (isSelection || (isSelected && (eventType === 'click' || eventType === 'keyup'))) {
1522                                    return;
1523                                }
1524
1525                                // Value Handling
1526                                // ==============
1527
1528                                // User attempted to delete but raw value was unaffected--correct this grievous offense
1529                                if ((eventType === 'input') && isDeletion && !wasSelected && valUnmasked === valUnmaskedOld) {
1530                                    while (isKeyBackspace && caretPos > caretPosMin && !isValidCaretPosition(caretPos)) {
1531                                        caretPos--;
1532                                    }
1533                                    while (isKeyDelete && caretPos < caretPosMax && maskCaretMap.indexOf(caretPos) === -1) {
1534                                        caretPos++;
1535                                    }
1536                                    var charIndex = maskCaretMap.indexOf(caretPos);
1537                                    // Strip out non-mask character that user would have deleted if mask hadn't been in the way.
1538                                    valUnmasked = valUnmasked.substring(0, charIndex) + valUnmasked.substring(charIndex + 1);
1539                                    valAltered = true;
1540                                }
1541
1542                                // Update values
1543                                valMasked = maskValue(valUnmasked);
1544
1545                                oldValue = valMasked;
1546                                oldValueUnmasked = valUnmasked;
1547
1548                                //additional check to fix the problem where the viewValue is out of sync with the value of the element.
1549                                //better fix for commit 2a83b5fb8312e71d220a497545f999fc82503bd9 (I think)
1550                                if (!valAltered && val.length > valMasked.length)
1551                                    valAltered = true;
1552
1553                                iElement.val(valMasked);
1554
1555                                //we need this check.  What could happen if you don't have it is that you'll set the model value without the user
1556                                //actually doing anything.  Meaning, things like pristine and touched will be set.
1557                                if (valAltered) {
1558                                    scope.$apply(function () {
1559                                        controller.$setViewValue(valUnmasked); // $setViewValue should be run in angular context, otherwise the changes will be invisible to angular and user code.
1560                                    });
1561                                }
1562
1563                                // Caret Repositioning
1564                                // ===================
1565
1566                                // Ensure that typing always places caret ahead of typed character in cases where the first char of
1567                                // the input is a mask char and the caret is placed at the 0 position.
1568                                if (isAddition && (caretPos <= caretPosMin)) {
1569                                    caretPos = caretPosMin + 1;
1570                                }
1571
1572                                if (caretBumpBack) {
1573                                    caretPos--;
1574                                }
1575
1576                                // Make sure caret is within min and max position limits
1577                                caretPos = caretPos > caretPosMax ? caretPosMax : caretPos < caretPosMin ? caretPosMin : caretPos;
1578
1579                                // Scoot the caret back or forth until it's in a non-mask position and within min/max position limits
1580                                while (!isValidCaretPosition(caretPos) && caretPos > caretPosMin && caretPos < caretPosMax) {
1581                                    caretPos += caretBumpBack ? -1 : 1;
1582                                }
1583
1584                                if ((caretBumpBack && caretPos < caretPosMax) || (isAddition && !isValidCaretPosition(caretPosOld))) {
1585                                    caretPos++;
1586                                }
1587                                oldCaretPosition = caretPos;
1588                                setCaretPosition(this, caretPos);
1589                            }
1590
1591                            function isValidCaretPosition(pos) {
1592                                return maskCaretMap.indexOf(pos) > -1;
1593                            }
1594
1595                            function getCaretPosition(input) {
1596                                if (!input)
1597                                    return 0;
1598                                if (input.selectionStart !== undefined) {
1599                                    return input.selectionStart;
1600                                } else if (document.selection) {
1601                                    if (isFocused(iElement[0])) {
1602                                        // Curse you IE
1603                                        input.focus();
1604                                        var selection = document.selection.createRange();
1605                                        selection.moveStart('character', input.value ? -input.value.length : 0);
1606                                        return selection.text.length;
1607                                    }
1608                                }
1609                                return 0;
1610                            }
1611
1612                            function setCaretPosition(input, pos) {
1613                                if (!input)
1614                                    return 0;
1615                                if (input.offsetWidth === 0 || input.offsetHeight === 0) {
1616                                    return; // Input's hidden
1617                                }
1618                                if (input.setSelectionRange) {
1619                                    if (isFocused(iElement[0])) {
1620                                        input.focus();
1621                                        input.setSelectionRange(pos, pos);
1622                                    }
1623                                }
1624                                else if (input.createTextRange) {
1625                                    // Curse you IE
1626                                    var range = input.createTextRange();
1627                                    range.collapse(true);
1628                                    range.moveEnd('character', pos);
1629                                    range.moveStart('character', pos);
1630                                    range.select();
1631                                }
1632                            }
1633
1634                            function getSelectionLength(input) {
1635                                if (!input)
1636                                    return 0;
1637                                if (input.selectionStart !== undefined) {
1638                                    return (input.selectionEnd - input.selectionStart);
1639                                }
1640                                if (document.selection) {
1641                                    return (document.selection.createRange().text.length);
1642                                }
1643                                return 0;
1644                            }
1645
1646                            // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
1647                            if (!Array.prototype.indexOf) {
1648                                Array.prototype.indexOf = function(searchElement /*, fromIndex */) {
1649                                    if (this === null) {
1650                                        throw new TypeError();
1651                                    }
1652                                    var t = Object(this);
1653                                    var len = t.length >>> 0;
1654                                    if (len === 0) {
1655                                        return -1;
1656                                    }
1657                                    var n = 0;
1658                                    if (arguments.length > 1) {
1659                                        n = Number(arguments[1]);
1660                                        if (n !== n) { // shortcut for verifying if it's 
1660NaN
1661                                            n = 0;
1662                                        } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
1663                                            n = (n > 0 || -1) * Math.floor(Math.abs(n));
1664                                        }
1665                                    }
1666                                    if (n >= len) {
1667                                        return -1;
1668                                    }
1669                                    var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
1670                                    for (; k < len; k++) {
1671                                        if (k in t && t[k] === searchElement) {
1672                                            return k;
1673                                        }
1674                                    }
1675                                    return -1;
1676                                };
1677                            }
1678
1679                        };
1680                    }
1681                };
1682            }
1683        ]);
1684
1685}());
1686(function () {
1687    angular.module('WebnamesForm')
1688        .directive('wnValidationMessages', ['$log', '$timeout', function ($log, $timeout) {
1689            return {
1690                scope: {
1691                    validateFor: '=',
1692                    fieldName: '@',
1693                    maxLength: '@',
1694                    minLength: '@',
1695                    minValue: '@'
1696                },
1697                transclude: true,
1698                restrict: 'E',
1699                require: '^form',
1700                templateUrl: 'wn-validation-messages.cshtml',
1701                link: function (scope, ele, attrs, formCtrl) {
1702                    scope.isDisplayMultipleErrors = attrs.hasOwnProperty('ngMessagesMultiple');
1703
1704                    // max length may be passed in
1705                    $timeout(function () {
1706                        scope.maxLength = angular.element('[name=' + scope.validateFor.$name + ']').attr('ng-maxlength');
1707                        scope.minLength = angular.element('[name=' + scope.validateFor.$name + ']').attr('ng-minlength');
1708                        scope.minValue = angular.element('[name=' + scope.validateFor.$name + ']').attr('ng-min');
1709                    });
1710                    // determines if form has been submitted and whether or not messages should show
1711                    scope.isSubmitted = function () { return formCtrl.$submitted }
1712                }
1713            }
1714
1715        }]);
1716})();
1717
1718(function () {
1719	angular.module('WebnamesForm')
1720        .directive('wnPinValidator', function () {
1721            var INTEGER_REGEXP = /^[0-9]*$/;
1722            return {
1723                scope: {
1724                    wnPhoneNumber: '@',
1725                    wnAddress: '@'
1726                },
1727        		require: 'ngModel',
1728        		link: function (scope, elm, attrs, ctrl) {
1729        		    var ngModelController = ctrl[0];
1730        		    ctrl.$validators.integer = function (modelValue, viewValue) {
1731        		        // consider empty models to be valid
1732        				if (ctrl.$isEmpty(modelValue)) {
1733        					return true;
1734        				}
1735        		        // valid format
1736        				if (INTEGER_REGEXP.test(viewValue)) {
1737        					return true;
1738        				}
1739        				// invalid format
1740        				return false;
1741        		    };
1742
1743        		    ctrl.$validators.emptyPIN = function (modelValue, viewValue) {
1744        		        if (!modelValue || modelValue === null) {
1745        		            return false;
1746        		        }
1747        		        var value = modelValue.trim();
1748        		        if (ctrl.$isEmpty(value)) {
1749        		            return false;
1750        		        }
1751        		        return true;
1752        		    };
1753
1754        		    // PIN can't contain restricted sequence
1755        		    ctrl.$validators.containsRestrictedPIN = function (modelValue, viewValue) {
1756        		        var restrictedSequence = ["1111", "1234"];
1757        		        if (!modelValue) {
1758        		            return true;
1759        		        }
1760        		        var value = modelValue.trim();                        
1761        		        // consider empty models to be valid
1762        				if (ctrl.$isEmpty(value)) {
1763        					return true;
1764        				}
1765
1766        		        for (index = 0; index < restrictedSequence.length; index++) {
1767        		            if (value.indexOf(restrictedSequence[index]) > -1) {
1768        		                return false;
1769        		            }
1770        		        }
1771        		        return true;
1772        		    };
1773
1774        		    // PIN can't contain restricted sequence
1775        		    ctrl.$validators.containsPhoneAddressSequence = function (modelValue, viewValue) {
1776        		        if (!modelValue) {
1777        		            return true;
1778        		        }
1779
1780                        // take only digits from phone/address
1781        		        var phone = (scope.wnPhoneNumber ? scope.wnPhoneNumber.trim().toLowerCase() : "").replace(/[^\d]/g, '');
1782        		        var address = (scope.wnAddress ? scope.wnAddress.trim().toLowerCase() : "").replace(/[^\d]/g, '');                  
1783
1784        		        var value = modelValue.trim().toLowerCase();
1785        		        var restrictedFields = [phone, address];
1786        		        if (ctrl.$isEmpty(value)) {
1787        		            return true;
1788        		        }
1789        		        for (index = 0; index < restrictedFields.length; index++) {
1790                            // cannot contain any four sequential digits of restricted fields
1791        		            for (charIndex = 0; charIndex < restrictedFields[index].length - 3; charIndex++)
1792        		            {
1793        		                var segment = restrictedFields[index].substring(charIndex, charIndex + 4);
1794        		                if (segment != "" && value.indexOf(segment) > -1) {
1795        		                    return false;
1796        		                }
1797        		            }
1798        		        }
1799        		        return true;
1800        		    };
1801        		}
1802        	}
1803        }
1804	)})();
1805(function () {
1806    angular.module('WebnamesForm')
1807        .directive('wnEmailValidator', function () {
1808            var EMAILFORMAT_REGEXP = /^[A-Za-z0-9._%+-]+@(?:[A-Za-z0-9-]){1,63}(?:\.[A-Za-z0-9-]{2,63})+$/;
1809            return {
1810                require: 'ngModel',
1811                link: function (scope, elm, attrs, ctrl) {
1812                    ctrl.$validators.emailFormat = function (modelValue, viewValue) {
1813                        if (ctrl.$isEmpty(modelValue)) {
1814                            // consider empty models to be valid
1815                            return true;
1816                        }
1817                        if (EMAILFORMAT_REGEXP.test(viewValue)) {
1818                            // it is valid
1819                            return true;
1820                        }
1821                        // it is invalid
1822                        return false;
1823                    };
1824                }
1825            }
1826        }
1827	)
1828})();
1829(function () {
1830    angular.module('WebnamesForm')
1831        .directive('wnUrlValidator', function () {
1832            var URLFORMAT_REGEXP = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&%'\(\)\*\+,;=.]+$/;
1833            return {
1834                require: 'ngModel',
1835                link: function (scope, elm, attrs, ctrl) {
1836                    ctrl.$validators.URLFormat = function (modelValue, viewValue) {
1837                        if (ctrl.$isEmpty(modelValue)) {
1838                            // consider empty models to be valid
1839                            return true;
1840                        }
1841                        if (URLFORMAT_REGEXP.test(viewValue)) {
1842                            // it is valid
1843                            return true;
1844                        }
1845                        var validURIPrefixes = [
1846                            '#',
1847                            'aaa:',
1848                            'aaas:',
1849                            'about:',
1850                            'acap:',
1851                            'acct:',
1852                            'acd:',
1853                            'acr:',
1854                            'adiumxtra:',
1855                            'afp:',
1856                            'afs:',
1857                            'aim:',
1858                            'amss:',
1859                            'android:',
1860                            'appdata:',
1861                            'apt:',
1862                            'attachment:',
1863                            'aw:',
1864                            'barion:',
1865                            'beshare:',
1866                            'bitcoin:',
1867                            'bitcoincash:',
1868                            'blob:',
1869                            'bolo:',
1870                            'browserext:',
1871                            'calculator:',
1872                            'callto:',
1873                            'cap:',
1874                            'cast:',
1875                            'casts:',
1876                            'chrome:',
1877                            'chrome-extension:',
1878                            'cid:',
1879                            'coap:',
1880                            'coap+tcp:',
1881                            'coap+ws:',
1882                            'coaps:',
1883                            'coaps+tcp:',
1884                            'coaps+ws:',
1885                            'com-eventbrite-attendee:',
1886                            'content:',
1887                            'conti:',
1888                            'crid:',
1889                            'cvs:',
1890                            'dab:',
1891                            'data:',
1892                            'dav:',
1893                            'diaspora:',
1894                            'dict:',
1895                            'did:',
1896                            'dis:',
1897                            'dlna-playcontainer:',
1898                            'dlna-playsingle:',
1899                            'dns:',
1900                            'dntp:',
1901                            'dpp:',
1902                            'drm:',
1903                            'drop:',
1904                            'dtn:',
1905                            'dvb:',
1906                            'ed2k:',
1907                            'elsi:',
1908                            'example:',
1909                            'facetime:',
1910                            'fax:',
1911                            'feed:',
1912                            'feedready:',
1913                            'file:',
1914                            'filesystem:',
1915                            'finger:',
1916                            'first-run-pen-experience:',
1917                            'fish:',
1918                            'fm:',
1919                            'ftp:',
1920                            'fuchsia-pkg:',
1921                            'geo:',
1922                            'gg:',
1923                            'git:',
1924                            'gizmoproject:',
1925                            'go:',
1926                            'gopher:',
1927                            'graph:',
1928                            'gtalk:',
1929                            'h323:',
1930                            'ham:',
1931                            'hcap:',
1932                            'hcp:',
1933                            'hxxp:',
1934                            'hxxps:',
1935                            'hydrazone:',
1936                            'iax:',
1937                            'icap:',
1938                            'icon:',
1939                            'im:',
1940                            'imap:',
1941                            'info:',
1942                            'iotdisco:',
1943                            'ipn:',
1944                            'ipp:',
1945                            'ipps:',
1946                            'irc:',
1947                            'irc6:',
1948                            'ircs:',
1949                            'iris:',
1950                            'iris.beep:',
1951                            'iris.lwz:',
1952                            'iris.xpc:',
1953                            'iris.xpcs:',
1954                            'isostore:',
1955                            'itms:',
1956                            'jabber:',
1957                            'jar:',
1958                            'jms:',
1959                            'keyparc:',
1960                            'lastfm:',
1961                            'ldap:',
1962                            'ldaps:',
1963                            'leaptofrogans:',
1964                            'lorawan:',
1965                            'lvlt:',
1966                            'magnet:',
1967                            'mailserver:',
1968                            'mailto:',
1969                            'maps:',
1970                            'market:',
1971                            'message:',
1972                            'microsoft.windows.camera:',
1973                            'microsoft.windows.camera.multipicker:',
1974                            'microsoft.windows.camera.picker:',
1975                            'mid:',
1976                            'mms:',
1977                            'modem:',
1978                            'mongodb:',
1979                            'moz:',
1980                            'ms-access:',
1981                            'ms-browser-extension:',
1982                            'ms-calculator:',
1983                            'ms-drive-to:',
1984                            'ms-enrollment:',
1985                            'ms-excel:',
1986                            'ms-eyecontrolspeech:',
1987                            'ms-gamebarservices:',
1988                            'ms-gamingoverlay:',
1989                            'ms-getoffice:',
1990                            'ms-help:',
1991                            'ms-infopath:',
1992                            'ms-inputapp:',
1993                            'ms-lockscreencomponent-config:',
1994                            'ms-media-stream-id:',
1995                            'ms-mixedrealitycapture:',
1996                            'ms-mobileplans:',
1997                            'ms-officeapp:',
1998                            'ms-people:',
1999                            'ms-project:',
2000                            'ms-powerpoint:',
2001                            'ms-publisher:',
2002                            'ms-restoretabcompanion:',
2003                            'ms-screenclip:',
2004                            'ms-screensketch:',
2005                            'ms-search:',
2006                            'ms-search-repair:',
2007                            'ms-secondary-screen-controller:',
2008                            'ms-secondary-screen-setup:',
2009                            'ms-settings:',
2010                            'ms-settings-airplanemode:',
2011                            'ms-settings-bluetooth:',
2012                            'ms-settings-camera:',
2013                            'ms-settings-cellular:',
2014                            'ms-settings-cloudstorage:',
2015                            'ms-settings-connectabledevices:',
2016                            'ms-settings-displays-topology:',
2017                            'ms-settings-emailandaccounts:',
2018                            'ms-settings-language:',
2019                            'ms-settings-location:',
2020                            'ms-settings-lock:',
2021                            'ms-settings-nfctransactions:',
2022                            'ms-settings-notifications:',
2023                            'ms-settings-power:',
2024                            'ms-settings-privacy:',
2025                            'ms-settings-proximity:',
2026                            'ms-settings-screenrotation:',
2027                            'ms-settings-wifi:',
2028                            'ms-settings-workplace:',
2029                            'ms-spd:',
2030                            'ms-sttoverlay:',
2031                            'ms-transit-to:',
2032                            'ms-useractivityset:',
2033                            'ms-virtualtouchpad:',
2034                            'ms-visio:',
2035                            'ms-walk-to:',
2036                            'ms-whiteboard:',
2037                            'ms-whiteboard-cmd:',
2038                            'ms-word:',
2039                            'msnim:',
2040                            'msrp:',
2041                            'msrps:',
2042                            'mss:',
2043                            'mtqp:',
2044                            'mumble:',
2045                            'mupdate:',
2046                            'mvn:',
2047                            'news:',
2048                            'nfs:',
2049                            'ni:',
2050                            'nih:',
2051                            'nntp:',
2052                            'notes:',
2053                            'ocf:',
2054                            'oid:',
2055                            'onenote:',
2056                            'onenote-cmd:',
2057                            'opaquelocktoken:',
2058                            'openpgp4fpr:',
2059                            'pack:',
2060                            'palm:',
2061                            'paparazzi:',
2062                            'payto:',
2063                            'pkcs11:',
2064                            'platform:',
2065                            'pop:',
2066                            'pres:',
2067                            'prospero:',
2068                            'proxy:',
2069                            'pwid:',
2070                            'psyc:',
2071                            'qb:',
2072                            'query:',
2073                            'redis:',
2074                            'rediss:',
2075                            'reload:',
2076                            'res:',
2077                            'resource:',
2078                            'rmi:',
2079                            'rsync:',
2080                            'rtmfp:',
2081                            'rtmp:',
2082                            'rtsp:',
2083                            'rtsps:',
2084                            'rtspu:',
2085                            'secondlife:',
2086                            'service:',
2087                            'session:',
2088                            'sftp:',
2089                            'sgn:',
2090                            'shttp:',
2091                            'sieve:',
2092                            'simpleledger:',
2093                            'sip:',
2094                            'sips:',
2095                            'skype:',
2096                            'smb:',
2097                            'sms:',
2098                            'smtp:',
2099                            'snews:',
2100                            'snmp:',
2101                            'soap.beep:',
2102                            'soap.beeps:',
2103                            'soldat:',
2104                            'spiffe:',
2105                            'spotify:',
2106                            'ssh:',
2107                            'steam:',
2108                            'stun:',
2109                            'stuns:',
2110                            'submit:',
2111                            'svn:',
2112                            'tag:',
2113                            'teamspeak:',
2114                            'tel:',
2115                            'teliaeid:',
2116                            'telnet:',
2117                            'tftp:',
2118                            'things:',
2119                            'thismessage:',
2120                            'tip:',
2121                            'tn3270:',
2122                            'tool:',
2123                            'turn:',
2124                            'turns:',
2125                            'tv:',
2126                            'udp:',
2127                            'unreal:',
2128                            'urn:',
2129                            'ut2004:',
2130                            'v-event:',
2131                            'vemmi:',
2132                            'ventrilo:',
2133                            'videotex:',
2134                            'vnc:',
2135                            'view-source:',
2136                            'wais:',
2137                            'webcal:',
2138                            'wpid:',
2139                            'ws:',
2140                            'wss:',
2141                            'wtai:',
2142                            'wyciwyg:',
2143                            'xcon:',
2144                            'xcon-userid:',
2145                            'xfire:',
2146                            'xmlrpc.beep:',
2147                            'xmlrpc.beeps:',
2148                            'xmpp:',
2149                            'xri:',
2150                            'ymsgr:',
2151                            'z39.50:',
2152                            'z39.50r:',
2153                            'z39.50s:'
2154                        ];
2155                        for (var i=0; i < validURIPrefixes.length; i++) {
2156                            if (viewValue.startsWith(validURIPrefixes[i])) {
2157                                return true;
2158                            }
2159                        }
2160                        // it is invalid
2161                        return false;
2162                    };
2163                }
2164            }
2165        }
2166	)
2167})();
2168(function () {
2169    angular.module('WebnamesForm')
2170        .directive('wnColorCodeValidator', function () {
2171            var COLORCODEFORMAT_REGEXP = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
2172            return {
2173                require: 'ngModel',
2174                link: function (scope, elm, attrs, ctrl) {
2175                    ctrl.$validators.colorCodeFormat = function (modelValue, viewValue) {
2176                        if (ctrl.$isEmpty(modelValue)) {
2177                            // consider empty models to be valid
2178                            return true;
2179                        }
2180                        if (COLORCODEFORMAT_REGEXP.test(viewValue)) {
2181                            // it is valid
2182                            return true;
2183                        }
2184                        // it is invalid
2185                        return false;
2186                    };
2187                }
2188            }
2189        }
2190	)
2191})();
2192(function () {
2193    angular.module('WebnamesForm')
2194        .directive('wnPasswordValidator', function () {
2195            return {
2196                scope: {
2197                    wnUsername: '@',
2198                    wnFirstName: '@',
2199                    wnLastName: '@',
2200                    wnOrganization: '@'
2201                },  
2202                require: ['ngModel', '^form'],
2203                link: function (scope, elm, attrs, ctrl) {
2204                    var ngModelController = ctrl[0];
2205
2206                    // password can't contain restricted words
2207                    ngModelController.$validators.containsRestrictedWords = function (modelValue, viewValue) {
2208                        if (!modelValue)
2209                        {
2210                            return true;
2211                        }
2212                        var value = modelValue.trim().toLowerCase();
2213                        var restrictedWords = ["password", "1234", "qwerty"];
2214
2215                        if (ngModelController.$isEmpty(value)) {
2216                            return true;
2217                        }
2218
2219                        for (index = 0; index < restrictedWords.length; index++) {
2220                            if (value.indexOf(restrictedWords[index]) > -1) {
2221                                return false;
2222                            }
2223                        }
2224                        return true;
2225                    };
2226
2227                    // password can't contain username, first name, last name, organization
2228                    ngModelController.$validators.containsRestrictedContactFields = function (modelValue, viewValue) {
2229                        if (!modelValue) {
2230                            return true;
2231                        }
2232                        var value = modelValue.trim().toLowerCase();
2233                        var restrictedFields = [
2234                            (scope.wnUsername ? scope.wnUsername.trim().toLowerCase() : ""),
2235                            (scope.wnFirstName ? scope.wnFirstName.trim().toLowerCase() : ""),
2236                            (scope.wnLastName ? scope.wnLastName.trim().toLowerCase() : ""),
2237                            (scope.wnOrganization ? scope.wnOrganization.trim().toLowerCase() : "")
2238                        ];
2239                        if (ngModelController.$isEmpty(value)) {
2240                            return true;
2241                        }
2242                        for (index = 0; index < restrictedFields.length; index++) {
2243                            if (restrictedFields[index] != "" && value.indexOf(restrictedFields[index]) > -1) {
2244                                return false;
2245                            }
2246                        }
2247                        return true;
2248                    };
2249
2250                    // password must have lowercase
2251                    ngModelController.$validators.requiresLower = function (modelValue, viewValue) {
2252                        if (!modelValue) {
2253                            return true;
2254                        }
2255                        var value = modelValue.trim();
2256                        var hasLower = false;
2257                        if (ngModelController.$isEmpty(value)) {
2258                            return true;
2259                        }
2260                        for (var i = 0; i < value.length; i++) {
2261                            var cc = value.charCodeAt(i)
2262                            if (cc >= 97 && cc <= 122) {
2263                                hasLower = true;
2264                            }
2265                        }
2266                        return hasLower;
2267                    };
2268
2269                    // password must have uppercase
2270                    ngModelController.$validators.requiresUpper = function (modelValue, viewValue) {
2271                        if (!modelValue) {
2272                            return true;
2273                        }
2274                        var value = modelValue.trim();
2275                        if (!modelValue)
2276                        {
2277                            return true;
2278                        }
2279                        var hasUpper = false;
2280
2281                        if (ngModelController.$isEmpty(value)) {
2282                            return true;
2283                        }
2284                        for (var i = 0; i < value.length; i++) {
2285                            var cc = value.charCodeAt(i)
2286                            if (cc >= 65 && cc <= 90) {
2287                                hasUpper = true;
2288                            }
2289                        }
2290                        return hasUpper;
2291                    };
2292
2293                    // password must have numbers or symbols
2294                    ngModelController.$validators.requiresNonLetters = function (modelValue, viewValue) {
2295                        if (!modelValue) {
2296                            return true;
2297                        }
2298                        var value = modelValue.trim();
2299                        var hasNonLetters = false;
2300
2301                        if (ngModelController.$isEmpty(value)) {
2302                            return true;
2303                        }
2304                        for (var i = 0; i < value.length; i++) {
2305                            var cc = value.charCodeAt(i)
2306                            if (!(cc >= 65 && cc <= 90) && !(cc >= 97 && cc <= 122)) {
2307                                hasNonLetters = true;
2308                            }
2309                        }
2310                        return hasNonLetters;
2311                    };
2312                }
2313            }
2314        }
2315	)
2316})();
2317(function () {
2318    angular.module('WebnamesForm')
2319        .directive('wnUsernameValidator', ['$q', '$timeout', 'FormDataStore', function ($q, $timeout, FormDataStore) {
2320            return {
2321                scope: { wnExistingUsername: '@' },  
2322                require: 'ngModel',
2323                link: function (scope, elm, attrs, ctrl) {
2324                    // must be made up of numbers and letters
2325                    ctrl.$validators.usernameFormat = function (modelValue, viewValue) {
2326                        var USERNAMEFORMAT_REGEXP = /^[A-Za-z0-9._%+-@$!()*/]+$/;
2327
2328                        // consider empty models to be valid
2329                        if (ctrl.$isEmpty(modelValue)) {
2330                            return true;
2331                        }
2332                        // valid format
2333                        if (USERNAMEFORMAT_REGEXP.test(viewValue)) {
2334                            return true;
2335                        }
2336                        // invalid format
2337                        return false;
2338                    };
2339
2340                    // username must not be taken
2341                    ctrl.$asyncValidators.usernameUnavailable = function (modelValue, viewValue) {
2342                        var value = modelValue.trim();
2343                        var def = $q.defer();
2344
2345                        FormDataStore.CheckUsernameAvailability(value, scope.wnExistingUsername).then(
2346                            function () { def.resolve(); }, function () { def.reject(); });
2347                        return def.promise;
2348                    };
2349                }
2350            }
2351        }]
2352	)
2353})();
2354(function () {
2355    angular.module('WebnamesForm')
2356        .directive('wnPasswordMatch', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
2357            return {
2358                scope: { wnUsername: '@' },
2359                require: 'ngModel',
2360                link: function (scope, elm, attrs, ctrl) {
2361                    // password must match existing password
2362                    ctrl.$asyncValidators.passwordMismatch = function (modelValue, viewValue) {
2363                        var value = modelValue.trim();
2364                        var def = $q.defer();
2365                        $http.post(window._SmartURL + 'Helper' + '/CheckPasswordMatch', { currentPassword: value, username: scope.wnUsername }).then(
2366                            function () { def.resolve(); }, function () { def.reject(); });
2367                        return def.promise;
2368                    };
2369                }
2370            }
2371        }]
2372	)
2373})();
2374(function () {
2375    angular.module('WebnamesForm')
2376        .directive('wnUserInvitePasswordMatch', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
2377            return {
2378                scope: {
2379                    wnUserInviteGuid: '@'
2380                },  
2381                require: 'ngModel',
2382                link: function (scope, elm, attrs, ctrl) {
2383                    // password must match existing password
2384                    ctrl.$asyncValidators.userInvitePasswordMismatch = function (modelValue, viewValue) {
2385                        var def = $q.defer();
2386                        $http.post(window._SmartURL + 'Helper' + '/CheckUserInvitePasswordMatch', { currentPassword: modelValue, wnUserInviteGuid: scope.wnUserInviteGuid }).then(
2387                            function () { def.resolve(); }, function () { def.reject(); });
2388                        return def.promise;
2389                    };
2390                }
2391            }
2392        }]
2393	)
2394})();
2395(function () {
2396    angular.module('WebnamesForm')
2397        .directive('wnUserInviteValidator', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
2398            return {
2399                require: 'ngModel',
2400                link: function (scope, elm, attrs, ctrl) {
2401                    // user invite cannot be duplicate email
2402                    ctrl.$asyncValidators.duplicateInviteEmail = function (modelValue, viewValue) {
2403                        var value = modelValue.trim();
2404                        var def = $q.defer();
2405                        $http.post(window._SmartURL + 'Helper' + '/CheckDuplicateInviteEmail', { inviteEmail: value }).then(
2406                            function () { def.resolve(); }, function () { def.reject(); });
2407                        return def.promise;
2408                    };
2409                }
2410            }
2411        }]
2412	)
2413})();
2414(function () {
2415    angular.module('WebnamesForm')
2416        .directive('wnExternalServiceValidator', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
2417            return {
2418                scope: { wnExternalServiceModel: '=' },
2419                require: 'ngModel',
2420                link: function (scope, elm, attrs, ctrl) {
2421                    // external service cannot be duplicate
2422                    ctrl.$asyncValidators.duplicateExternalService = function (modelValue, viewValue) {
2423                        var def = $q.defer();
2424                        $http.post(window._SmartURL + 'Helper' + '/CheckDuplicateExternalService', { ExternalServiceModel: scope.wnExternalServiceModel, Description: modelValue }).then(
2425                            function () { def.resolve(); }, function () { def.reject(); });
2426                        return def.promise;
2427                    };
2428                }
2429            }
2430        }]
2431	)
2432})();
2433/**
2434 * @license AngularJS v1.8.2
vendor: 25,829 bytes, lines 2434-3124
2434
2435 * (c) 2010-2020 Google LLC. http://angularjs.org
2436 * License: MIT
2437 */
2438(function(window, angular) {'use strict';
2439
2440/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2441 *     Any commits to this file should be reviewed with security in mind.  *
2442 *   Changes to this file can potentially create security vulnerabilities. *
2443 *          An approval from 2 Core members with history of modifying      *
2444 *                         this file is required.                          *
2445 *                                                                         *
2446 *  Does the change somehow allow for arbitrary javascript to be executed? *
2447 *    Or allows for someone to change the prototype of built-in objects?   *
2448 *     Or gives undesired access to variables likes document or window?    *
2449 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2450
2451var $sanitizeMinErr = angular.$$minErr('$sanitize');
2452var bind;
2453var extend;
2454var forEach;
2455var isArray;
2456var isDefined;
2457var lowercase;
2458var noop;
2459var nodeContains;
2460var htmlParser;
2461var htmlSanitizeWriter;
2462
2463/**
2464 * @ngdoc module
2465 * @name ngSanitize
2466 * @description
2467 *
2468 * The `ngSanitize` module provides functionality to sanitize HTML.
2469 *
2470 * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
2471 */
2472
2473/**
2474 * @ngdoc service
2475 * @name $sanitize
2476 * @kind function
2477 *
2478 * @description
2479 *   Sanitizes an html string by stripping all potentially dangerous tokens.
2480 *
2481 *   The input is sanitized by parsing the HTML into tokens. All safe tokens (from a trusted URI list) are
2482 *   then serialized back to a properly escaped HTML string. This means that no unsafe input can make
2483 *   it into the returned string.
2484 *
2485 *   The trusted URIs for URL sanitization of attribute values is configured using the functions
2486 *   `aHrefSanitizationTrustedUrlList` and `imgSrcSanitizationTrustedUrlList` of {@link $compileProvider}.
2487 *
2488 *   The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
2489 *
2490 * @param {string} html HTML input.
2491 * @returns {string} Sanitized HTML.
2492 *
2493 * @example
2494   <example module="sanitizeExample" deps="angular-sanitize.js" name="sanitize-service">
2495   <file name="index.html">
2496     <script>
2497         angular.module('sanitizeExample', ['ngSanitize'])
2498           .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {
2499             $scope.snippet =
2500               '<p style="color:blue">an html\n' +
2501               '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
2502               'snippet</p>';
2503             $scope.deliberatelyTrustDangerousSnippet = function() {
2504               return $sce.trustAsHtml($scope.snippet);
2505             };
2506           }]);
2507     </script>
2508     <div ng-controller="ExampleController">
2509        Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
2510       <table>
2511         <tr>
2512           <td>Directive</td>
2513           <td>How</td>
2514           <td>Source</td>
2515           <td>Rendered</td>
2516         </tr>
2517         <tr id="bind-html-with-sanitize">
2518           <td>ng-bind-html</td>
2519           <td>Automatically uses $sanitize</td>
2520           <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
2521           <td><div ng-bind-html="snippet"></div></td>
2522         </tr>
2523         <tr id="bind-html-with-trust">
2524           <td>ng-bind-html</td>
2525           <td>Bypass $sanitize by explicitly trusting the dangerous value</td>
2526           <td>
2527           <pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
2528&lt;/div&gt;</pre>
2529           </td>
2530           <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
2531         </tr>
2532         <tr id="bind-default">
2533           <td>ng-bind</td>
2534           <td>Automatically escapes</td>
2535           <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
2536           <td><div ng-bind="snippet"></div></td>
2537         </tr>
2538       </table>
2539       </div>
2540   </file>
2541   <file name="protractor.js" type="protractor">
2542     it('should sanitize the html snippet by default', function() {
2543       expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
2544         toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
2545     });
2546
2547     it('should inline raw snippet if bound to a trusted value', function() {
2548       expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).
2549         toBe("<p style=\"color:blue\">an html\n" +
2550              "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
2551              "snippet</p>");
2552     });
2553
2554     it('should escape snippet without any filter', function() {
2555       expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).
2556         toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
2557              "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
2558              "snippet&lt;/p&gt;");
2559     });
2560
2561     it('should update', function() {
2562       element(by.model('snippet')).clear();
2563       element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
2564       expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
2565         toBe('new <b>text</b>');
2566       expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe(
2567         'new <b onclick="alert(1)">text</b>');
2568       expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe(
2569         "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
2570     });
2571   </file>
2572   </example>
2573 */
2574
2575
2576/**
2577 * @ngdoc provider
2578 * @name $sanitizeProvider
2579 * @this
2580 *
2581 * @description
2582 * Creates and configures {@link $sanitize} instance.
2583 */
2584function $SanitizeProvider() {
2585  var hasBeenInstantiated = false;
2586  var svgEnabled = false;
2587
2588  this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
2589    hasBeenInstantiated = true;
2590    if (svgEnabled) {
2591      extend(validElements, svgElements);
2592    }
2593    return function(html) {
2594      var buf = [];
2595      htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
2596        return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
2597      }));
2598      return buf.join('');
2599    };
2600  }];
2601
2602
2603  /**
2604   * @ngdoc method
2605   * @name $sanitizeProvider#enableSvg
2606   * @kind function
2607   *
2608   * @description
2609   * Enables a subset of svg to be supported by the sanitizer.
2610   *
2611   * <div class="alert alert-warning">
2612   *   <p>By enabling this setting without taking other precautions, you might expose your
2613   *   application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
2614   *   outside of the containing element and be rendered over other elements on the page (e.g. a login
2615   *   link). Such behavior can then result in phishing incidents.</p>
2616   *
2617   *   <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
2618   *   tags within the sanitized content:</p>
2619   *
2620   *   <br>
2621   *
2622   *   <pre><code>
2623   *   .rootOfTheIncludedContent svg {
2624   *     overflow: hidden !important;
2625   *   }
2626   *   </code></pre>
2627   * </div>
2628   *
2629   * @param {boolean=} flag Enable or disable SVG support in the sanitizer.
2630   * @returns {boolean|$sanitizeProvider} Returns the currently configured value if called
2631   *    without an argument or self for chaining otherwise.
2632   */
2633  this.enableSvg = function(enableSvg) {
2634    if (isDefined(enableSvg)) {
2635      svgEnabled = enableSvg;
2636      return this;
2637    } else {
2638      return svgEnabled;
2639    }
2640  };
2641
2642
2643  /**
2644   * @ngdoc method
2645   * @name $sanitizeProvider#addValidElements
2646   * @kind function
2647   *
2648   * @description
2649   * Extends the built-in lists of valid HTML/SVG elements, i.e. elements that are considered safe
2650   * and are not stripped off during sanitization. You can extend the following lists of elements:
2651   *
2652   * - `htmlElements`: A list of elements (tag names) to extend the current list of safe HTML
2653   *   elements. HTML elements considered safe will not be removed during sanitization. All other
2654   *   elements will be stripped off.
2655   *
2656   * - `htmlVoidElements`: This is similar to `htmlElements`, but marks the elements as
2657   *   "void elements" (similar to HTML
2658   *   [void elements](https://rawgit.com/w3c/html/html5.1-2/single-page.html#void-elements)). These
2659   *   elements have no end tag and cannot have content.
2660   *
2661   * - `svgElements`: This is similar to `htmlElements`, but for SVG elements. This list is only
2662   *   taken into account if SVG is {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for
2663   *   `$sanitize`.
2664   *
2665   * <div class="alert alert-info">
2666   *   This method must be called during the {@link angular.Module#config config} phase. Once the
2667   *   `$sanitize` service has been instantiated, this method has no effect.
2668   * </div>
2669   *
2670   * <div class="alert alert-warning">
2671   *   Keep in mind that extending the built-in lists of elements may expose your app to XSS or
2672   *   other vulnerabilities. Be very mindful of the elements you add.
2673   * </div>
2674   *
2675   * @param {Array<String>|Object} elements - A list of valid HTML elements or an object with one or
2676   *   more of the following properties:
2677   *   - **htmlElements** - `{Array<String>}` - A list of elements to extend the current list of
2678   *     HTML elements.
2679   *   - **htmlVoidElements** - `{Array<String>}` - A list of elements to extend the current list of
2680   *     void HTML elements; i.e. elements that do not have an end tag.
2681   *   - **svgElements** - `{Array<String>}` - A list of elements to extend the current list of SVG
2682   *     elements. The list of SVG elements is only taken into account if SVG is
2683   *     {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for `$sanitize`.
2684   *
2685   * Passing an array (`[...]`) is equivalent to passing `{htmlElements: [...]}`.
2686   *
2687   * @return {$sanitizeProvider} Returns self for chaining.
2688   */
2689  this.addValidElements = function(elements) {
2690    if (!hasBeenInstantiated) {
2691      if (isArray(elements)) {
2692        elements = {htmlElements: elements};
2693      }
2694
2695      addElementsTo(svgElements, elements.svgElements);
2696      addElementsTo(voidElements, elements.htmlVoidElements);
2697      addElementsTo(validElements, elements.htmlVoidElements);
2698      addElementsTo(validElements, elements.htmlElements);
2699    }
2700
2701    return this;
2702  };
2703
2704
2705  /**
2706   * @ngdoc method
2707   * @name $sanitizeProvider#addValidAttrs
2708   * @kind function
2709   *
2710   * @description
2711   * Extends the built-in list of valid attributes, i.e. attributes that are considered safe and are
2712   * not stripped off during sanitization.
2713   *
2714   * **Note**:
2715   * The new attributes will not be treated as URI attributes, which means their values will not be
2716   * sanitized as URIs using `$compileProvider`'s
2717   * {@link ng.$compileProvider#aHrefSanitizationTrustedUrlList aHrefSanitizationTrustedUrlList} and
2718   * {@link ng.$compileProvider#imgSrcSanitizationTrustedUrlList imgSrcSanitizationTrustedUrlList}.
2719   *
2720   * <div class="alert alert-info">
2721   *   This method must be called during the {@link angular.Module#config config} phase. Once the
2722   *   `$sanitize` service has been instantiated, this method has no effect.
2723   * </div>
2724   *
2725   * <div class="alert alert-warning">
2726   *   Keep in mind that extending the built-in list of attributes may expose your app to XSS or
2727   *   other vulnerabilities. Be very mindful of the attributes you add.
2728   * </div>
2729   *
2730   * @param {Array<String>} attrs - A list of valid attributes.
2731   *
2732   * @returns {$sanitizeProvider} Returns self for chaining.
2733   */
2734  this.addValidAttrs = function(attrs) {
2735    if (!hasBeenInstantiated) {
2736      extend(validAttrs, arrayToMap(attrs, true));
2737    }
2738    return this;
2739  };
2740
2741  //////////////////////////////////////////////////////////////////////////////////////////////////
2742  // Private stuff
2743  //////////////////////////////////////////////////////////////////////////////////////////////////
2744
2745  bind = angular.bind;
2746  extend = angular.extend;
2747  forEach = angular.forEach;
2748  isArray = angular.isArray;
2749  isDefined = angular.isDefined;
2750  lowercase = angular.$$lowercase;
2751  noop = angular.noop;
2752
2753  htmlParser = htmlParserImpl;
2754  htmlSanitizeWriter = htmlSanitizeWriterImpl;
2755
2756  nodeContains = window.Node.prototype.contains || /** @this */ function(arg) {
2757    // eslint-disable-next-line no-bitwise
2758    return !!(this.compareDocumentPosition(arg) & 16);
2759  };
2760
2761  // Regular Expressions for parsing tags and attributes
2762  var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2763    // Match everything outside of normal chars and " (quote character)
2764    NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g;
2765
2766
2767  // Good source of info about elements and attributes
2768  // http://dev.w3.org/html5/spec/Overview.html#semantics
2769  // http://simon.html5.org/html-elements
2770
2771  // Safe Void Elements - HTML5
2772  // http://dev.w3.org/html5/spec/Overview.html#void-elements
2773  var voidElements = stringToMap('area,br,col,hr,img,wbr');
2774
2775  // Elements that you can, intentionally, leave open (and which close themselves)
2776  // http://dev.w3.org/html5/spec/Overview.html#optional-tags
2777  var optionalEndTagBlockElements = stringToMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'),
2778      optionalEndTagInlineElements = stringToMap('rp,rt'),
2779      optionalEndTagElements = extend({},
2780                                              optionalEndTagInlineElements,
2781                                              optionalEndTagBlockElements);
2782
2783  // Safe Block Elements - HTML5
2784  var blockElements = extend({}, optionalEndTagBlockElements, stringToMap('address,article,' +
2785          'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' +
2786          'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul'));
2787
2788  // Inline Elements - HTML5
2789  var inlineElements = extend({}, optionalEndTagInlineElements, stringToMap('a,abbr,acronym,b,' +
2790          'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' +
2791          'samp,small,span,strike,strong,sub,sup,time,tt,u,var'));
2792
2793  // SVG Elements
2794  // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
2795  // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
2796  // They can potentially allow for arbitrary javascript to be executed. See #11290
2797  var svgElements = stringToMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' +
2798          'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' +
2799          'radialGradient,rect,stop,svg,switch,text,title,tspan');
2800
2801  // Blocked Elements (will be stripped)
2802  var blockedElements = stringToMap('script,style');
2803
2804  var validElements = extend({},
2805                                     voidElements,
2806                                     blockElements,
2807                                     inlineElements,
2808                                     optionalEndTagElements);
2809
2810  //Attributes that have href and hence need to be sanitized
2811  var uriAttrs = stringToMap('background,cite,href,longdesc,src,xlink:href,xml:base');
2812
2813  var htmlAttrs = stringToMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
2814      'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
2815      'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
2816      'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
2817      'valign,value,vspace,width');
2818
2819  // SVG attributes (without "id" and "name" attributes)
2820  // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
2821  var svgAttrs = stringToMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
2822      'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
2823      'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
2824      'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
2825      'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
2826      'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
2827      'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
2828      'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
2829      'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
2830      'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
2831      'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
2832      'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
2833      'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
2834      'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
2835      'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
2836
2837  var validAttrs = extend({},
2838                                  uriAttrs,
2839                                  svgAttrs,
2840                                  htmlAttrs);
2841
2842  function stringToMap(str, lowercaseKeys) {
2843    return arrayToMap(str.split(','), lowercaseKeys);
2844  }
2845
2846  function arrayToMap(items, lowercaseKeys) {
2847    var obj = {}, i;
2848    for (i = 0; i < items.length; i++) {
2849      obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
2850    }
2851    return obj;
2852  }
2853
2854  function addElementsTo(elementsMap, newElements) {
2855    if (newElements && newElements.length) {
2856      extend(elementsMap, arrayToMap(newElements));
2857    }
2858  }
2859
2860  /**
2861   * Create an inert document that contains the dirty HTML that needs sanitizing.
2862   * We use the DOMParser API by default and fall back to createHTMLDocument if DOMParser is not
2863   * available.
2864   */
2865  var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) {
2866    if (isDOMParserAvailable()) {
2867      return getInertBodyElement_DOMParser;
2868    }
2869
2870    if (!document || !document.implementation) {
2871      throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document');
2872    }
2873    var inertDocument = document.implementation.createHTMLDocument('inert');
2874    var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body');
2875    return getInertBodyElement_InertDocument;
2876
2877    function isDOMParserAvailable() {
2878      try {
2879        return !!getInertBodyElement_DOMParser('');
2880      } catch (e) {
2881        return false;
2882      }
2883    }
2884
2885    function getInertBodyElement_DOMParser(html) {
2886      // We add this dummy element to ensure that the rest of the content is parsed as expected
2887      // e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
2888      html = '<remove></remove>' + html;
2889      try {
2890        var body = new window.DOMParser().parseFromString(html, 'text/html').body;
2891        body.firstChild.remove();
2892        return body;
2893      } catch (e) {
2894        return undefined;
2895      }
2896    }
2897
2898    function getInertBodyElement_InertDocument(html) {
2899      inertBodyElement.innerHTML = html;
2900
2901      // Support: IE 9-11 only
2902      // strip custom-namespaced attributes on IE<=11
2903      if (document.documentMode) {
2904        stripCustomNsAttrs(inertBodyElement);
2905      }
2906
2907      return inertBodyElement;
2908    }
2909  })(window, window.document);
2910
2911  /**
2912   * @example
2913   * htmlParser(htmlString, {
2914   *     start: function(tag, attrs) {},
2915   *     end: function(tag) {},
2916   *     chars: function(text) {},
2917   *     comment: function(text) {}
2918   * });
2919   *
2920   * @param {string} html string
2921   * @param {object} handler
2922   */
2923  function htmlParserImpl(html, handler) {
2924    if (html === null || html === undefined) {
2925      html = '';
2926    } else if (typeof html !== 'string') {
2927      html = '' + html;
2928    }
2929
2930    var inertBodyElement = getInertBodyElement(html);
2931    if (!inertBodyElement) return '';
2932
2933    //mXSS protection
2934    var mXSSAttempts = 5;
2935    do {
2936      if (mXSSAttempts === 0) {
2937        throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable');
2938      }
2939      mXSSAttempts--;
2940
2941      // trigger mXSS if it is going to happen by reading and writing the innerHTML
2942      html = inertBodyElement.innerHTML;
2943      inertBodyElement = getInertBodyElement(html);
2944    } while (html !== inertBodyElement.innerHTML);
2945
2946    var node = inertBodyElement.firstChild;
2947    while (node) {
2948      switch (node.nodeType) {
2949        case 1: // ELEMENT_NODE
2950          handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
2951          break;
2952        case 3: // TEXT NODE
2953          handler.chars(node.textContent);
2954          break;
2955      }
2956
2957      var nextNode;
2958      if (!(nextNode = node.firstChild)) {
2959        if (node.nodeType === 1) {
2960          handler.end(node.nodeName.toLowerCase());
2961        }
2962        nextNode = getNonDescendant('nextSibling', node);
2963        if (!nextNode) {
2964          while (nextNode == null) {
2965            node = getNonDescendant('parentNode', node);
2966            if (node === inertBodyElement) break;
2967            nextNode = getNonDescendant('nextSibling', node);
2968            if (node.nodeType === 1) {
2969              handler.end(node.nodeName.toLowerCase());
2970            }
2971          }
2972        }
2973      }
2974      node = nextNode;
2975    }
2976
2977    while ((node = inertBodyElement.firstChild)) {
2978      inertBodyElement.removeChild(node);
2979    }
2980  }
2981
2982  function attrToMap(attrs) {
2983    var map = {};
2984    for (var i = 0, ii = attrs.length; i < ii; i++) {
2985      var attr = attrs[i];
2986      map[attr.name] = attr.value;
2987    }
2988    return map;
2989  }
2990
2991
2992  /**
2993   * Escapes all potentially dangerous characters, so that the
2994   * resulting string can be safely inserted into attribute or
2995   * element text.
2996   * @param value
2997   * @returns {string} escaped text
2998   */
2999  function encodeEntities(value) {
3000    return value.
3001      replace(/&/g, '&amp;').
3002      replace(SURROGATE_PAIR_REGEXP, function(value) {
3003        var hi = value.charCodeAt(0);
3004        var low = value.charCodeAt(1);
3005        return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
3006      }).
3007      replace(NON_ALPHANUMERIC_REGEXP, function(value) {
3008        return '&#' + value.charCodeAt(0) + ';';
3009      }).
3010      replace(/</g, '&lt;').
3011      replace(/>/g, '&gt;');
3012  }
3013
3014  /**
3015   * create an HTML/XML writer which writes to buffer
3016   * @param {Array} buf use buf.join('') to get out sanitized html string
3017   * @returns {object} in the form of {
3018   *     start: function(tag, attrs) {},
3019   *     end: function(tag) {},
3020   *     chars: function(text) {},
3021   *     comment: function(text) {}
3022   * }
3023   */
3024  function htmlSanitizeWriterImpl(buf, uriValidator) {
3025    var ignoreCurrentElement = false;
3026    var out = bind(buf, buf.push);
3027    return {
3028      start: function(tag, attrs) {
3029        tag = lowercase(tag);
3030        if (!ignoreCurrentElement && blockedElements[tag]) {
3031          ignoreCurrentElement = tag;
3032        }
3033        if (!ignoreCurrentElement && validElements[tag] === true) {
3034          out('<');
3035          out(tag);
3036          forEach(attrs, function(value, key) {
3037            var lkey = lowercase(key);
3038            var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
3039            if (validAttrs[lkey] === true &&
3040              (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
3041              out(' ');
3042              out(key);
3043              out('="');
3044              out(encodeEntities(value));
3045              out('"');
3046            }
3047          });
3048          out('>');
3049        }
3050      },
3051      end: function(tag) {
3052        tag = lowercase(tag);
3053        if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
3054          out('</');
3055          out(tag);
3056          out('>');
3057        }
3058        // eslint-disable-next-line eqeqeq
3059        if (tag == ignoreCurrentElement) {
3060          ignoreCurrentElement = false;
3061        }
3062      },
3063      chars: function(chars) {
3064        if (!ignoreCurrentElement) {
3065          out(encodeEntities(chars));
3066        }
3067      }
3068    };
3069  }
3070
3071
3072  /**
3073   * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
3074   * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
3075   * to allow any of these custom attributes. This method strips them all.
3076   *
3077   * @param node Root element to process
3078   */
3079  function stripCustomNsAttrs(node) {
3080    while (node) {
3081      if (node.nodeType === window.Node.ELEMENT_NODE) {
3082        var attrs = node.attributes;
3083        for (var i = 0, l = attrs.length; i < l; i++) {
3084          var attrNode = attrs[i];
3085          var attrName = attrNode.name.toLowerCase();
3086          if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
3087            node.removeAttributeNode(attrNode);
3088            i--;
3089            l--;
3090          }
3091        }
3092      }
3093
3094      var nextNode = node.firstChild;
3095      if (nextNode) {
3096        stripCustomNsAttrs(nextNode);
3097      }
3098
3099      node = getNonDescendant('nextSibling', node);
3100    }
3101  }
3102
3103  function getNonDescendant(propName, node) {
3104    // An element is clobbered if its `propName` property points to one of its descendants
3105    var nextNode = node[propName];
3106    if (nextNode && nodeContains.call(node, nextNode)) {
3107      throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText);
3108    }
3109    return nextNode;
3110  }
3111}
3112
3113function sanitizeText(chars) {
3114  var buf = [];
3115  var writer = htmlSanitizeWriter(buf, noop);
3116  writer.chars(chars);
3117  return buf.join('');
3118}
3119
3120
3121// define ngSanitize module and register $sanitize service
3122angular.module('ngSanitize', [])
3123  .provider('$sanitize', $SanitizeProvider)
3124  .info({ angularVersion: '1.8.
31242
vendor: 7,335 bytes, lines 3124-3323
3124' });
3125
3126/**
3127 * @ngdoc filter
3128 * @name linky
3129 * @kind function
3130 *
3131 * @description
3132 * Finds links in text input and turns them into html links. Supports `http/https/ftp/sftp/mailto` and
3133 * plain email address links.
3134 *
3135 * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
3136 *
3137 * @param {string} text Input text.
3138 * @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in.
3139 * @param {object|function(url)} [attributes] Add custom attributes to the link element.
3140 *
3141 *    Can be one of:
3142 *
3143 *    - `object`: A map of attributes
3144 *    - `function`: Takes the url as a parameter and returns a map of attributes
3145 *
3146 *    If the map of attributes contains a value for `target`, it overrides the value of
3147 *    the target parameter.
3148 *
3149 *
3150 * @returns {string} Html-linkified and {@link $sanitize sanitized} text.
3151 *
3152 * @usage
3153   <span ng-bind-html="linky_expression | linky"></span>
3154 *
3155 * @example
3156   <example module="linkyExample" deps="angular-sanitize.js" name="linky-filter">
3157     <file name="index.html">
3158       <div ng-controller="ExampleController">
3159       Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
3160       <table>
3161         <tr>
3162           <th>Filter</th>
3163           <th>Source</th>
3164           <th>Rendered</th>
3165         </tr>
3166         <tr id="linky-filter">
3167           <td>linky filter</td>
3168           <td>
3169             <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
3170           </td>
3171           <td>
3172             <div ng-bind-html="snippet | linky"></div>
3173           </td>
3174         </tr>
3175         <tr id="linky-target">
3176          <td>linky target</td>
3177          <td>
3178            <pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
3179          </td>
3180          <td>
3181            <div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
3182          </td>
3183         </tr>
3184         <tr id="linky-custom-attributes">
3185          <td>linky custom attributes</td>
3186          <td>
3187            <pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"&gt;<br>&lt;/div&gt;</pre>
3188          </td>
3189          <td>
3190            <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
3191          </td>
3192         </tr>
3193         <tr id="escaped-html">
3194           <td>no filter</td>
3195           <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
3196           <td><div ng-bind="snippet"></div></td>
3197         </tr>
3198       </table>
3199     </file>
3200     <file name="script.js">
3201       angular.module('linkyExample', ['ngSanitize'])
3202         .controller('ExampleController', ['$scope', function($scope) {
3203           $scope.snippet =
3204             'Pretty text with some links:\n' +
3205             'http://angularjs.org/,\n' +
3206             'mailto:[email protected],\n' +
3207             '[email protected],\n' +
3208             'and one more: ftp://127.0.0.1/.';
3209           $scope.snippetWithSingleURL = 'http://angularjs.org/';
3210         }]);
3211     </file>
3212     <file name="protractor.js" type="protractor">
3213       it('should linkify the snippet with urls', function() {
3214         expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
3215             toBe('Pretty text with some links: http://angularjs.org/, [email protected], ' +
3216                  '[email protected], and one more: ftp://127.0.0.1/.');
3217         expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
3218       });
3219
3220       it('should not linkify snippet without the linky filter', function() {
3221         expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
3222             toBe('Pretty text with some links: http://angularjs.org/, mailto:[email protected], ' +
3223                  '[email protected], and one more: ftp://127.0.0.1/.');
3224         expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
3225       });
3226
3227       it('should update', function() {
3228         element(by.model('snippet')).clear();
3229         element(by.model('snippet')).sendKeys('new http://link.');
3230         expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
3231             toBe('new http://link.');
3232         expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
3233         expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
3234             .toBe('new http://link.');
3235       });
3236
3237       it('should work with the target property', function() {
3238        expect(element(by.id('linky-target')).
3239            element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
3240            toBe('http://angularjs.org/');
3241        expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
3242       });
3243
3244       it('should optionally add custom attributes', function() {
3245        expect(element(by.id('linky-custom-attributes')).
3246            element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
3247            toBe('http://angularjs.org/');
3248        expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
3249       });
3250     </file>
3251   </example>
3252 */
3253angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
3254  var LINKY_URL_REGEXP =
3255        /((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
3256      MAILTO_REGEXP = /^mailto:/i;
3257
3258  var linkyMinErr = angular.$$minErr('linky');
3259  var isDefined = angular.isDefined;
3260  var isFunction = angular.isFunction;
3261  var isObject = angular.isObject;
3262  var isString = angular.isString;
3263
3264  return function(text, target, attributes) {
3265    if (text == null || text === '') return text;
3266    if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
3267
3268    var attributesFn =
3269      isFunction(attributes) ? attributes :
3270      isObject(attributes) ? function getAttributesObject() {return attributes;} :
3271      function getEmptyAttributesObject() {return {};};
3272
3273    var match;
3274    var raw = text;
3275    var html = [];
3276    var url;
3277    var i;
3278    while ((match = raw.match(LINKY_URL_REGEXP))) {
3279      // We can not end in these as they are sometimes found at the end of the sentence
3280      url = match[0];
3281      // if we did not match ftp/http/www/mailto then assume mailto
3282      if (!match[2] && !match[4]) {
3283        url = (match[3] ? 'http://' : 'mailto:') + url;
3284      }
3285      i = match.index;
3286      addText(raw.substr(0, i));
3287      addLink(url, match[0].replace(MAILTO_REGEXP, ''));
3288      raw = raw.substring(i + match[0].length);
3289    }
3290    addText(raw);
3291    return $sanitize(html.join(''));
3292
3293    function addText(text) {
3294      if (!text) {
3295        return;
3296      }
3297      html.push(sanitizeText(text));
3298    }
3299
3300    function addLink(url, text) {
3301      var key, linkAttributes = attributesFn(url);
3302      html.push('<a ');
3303
3304      for (key in linkAttributes) {
3305        html.push(key + '="' + linkAttributes[key] + '" ');
3306      }
3307
3308      if (isDefined(target) && !('target' in linkAttributes)) {
3309        html.push('target="',
3310                  target,
3311                  '" ');
3312      }
3313      html.push('href="',
3314                url.replace(/"/g, '&quot;'),
3315                '">');
3316      addText(text);
3317      html.push('</a>');
3318    }
3319  };
3320}]);
3321
3322
3323})(window, window.angular);
3324
3325/*
3326    Webnames Error
3327    -----------------------------
3328    Module is meant to handle server and client side errors.
3329
3330Scenarios:
33311.) Unexpected error
3332	StatusCode = 500
3333
3334	Symptoms:
3335	- threw out of controller action method
3336
3337	What should happen:
3338	- gets logged as special critical error to investigate
3339	- if Angular error handler is present, let that handle the user error
3340	- if it's a production environment, allow them to log an error report
3341	- if it's local host, display stack trace etc
3342
3343	Examples:
3344	- developer forgets to handle an exception that can be thrown, fixable error occurs (null reference)
3345	- the user should be able to report that an error occurred and the error should be investigated
3346	- these prohibit the user from continuing their intended action on the site
3347
3348	Solution:
3349	- the user should log an error
3350
3351	- inline form or link to oops page
3352
3353
33542.) Expected error
3355	StatusCode = 422
3356
3357	Symptoms:
3358	- it was caught in the controller, the StatusCode was manually set to 422
3359
3360	What should happen:
3361	- should be handled in Angular controller, displaying a special message to the user (should/could use the WnErrorHandler)
3362	- handle required view state on the promise rejection
3363
3364	Examples:
3365	- server side validation of a domain name fails, return an error object and error message to display to the user
3366
3367    Author: Akos Sebestyen
3368*/
3369(function () {
3370    "use strict";
3371
3372    angular.module('WebnamesError', ['ngSanitize'])
3373
3374        .config(['$httpProvider', function ($httpProvider) {
3375            $httpProvider.interceptors.push('WnHttpInterceptor');
3376        }])
3377
3378        .factory('WnHttpInterceptor', ['$log', '$q', '$sce', 'WnErrorHandler', function ($log, $q, $sce, WnErrorHandler) {
3379            return {
3380                'request': function (config) {
3381                    // Add a custom header so our BaseController.cs can recognize that the WebnamesError module is present.
3382                    return config;
3383                },
3384
3385                'responseError': function (response) {
3386                    // Convert the array buffer data into an object
3387                    if (response.data instanceof ArrayBuffer) {
3388                        response.data = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(response.data)));
3389                    }
3390
3391                    // Push the error into the error handler directive        
3392                    if (response.status === 500 && !!response.data.wnAngularError) {
3393                        WnErrorHandler.PushError(!!response.data.debugMode ? $sce.trustAsHtml(response.data.wnAngularError) : response.data.wnAngularError, true);
3394                    }
3395
3396                    return $q.reject(response);
3397                }
3398            }
3399        }]);
3400
3401})();
3402
3403/*
3404 * Webnames Error
3405 * ----------------
3406 * Directives for displaying errors to the user from Angular. 
3407 * 
3408 * Author: Akos Sebestyen
3409 */
3410(function () {
3411    "use strict";
3412
3413    angular.module('WebnamesError')
3414
3415        // you can specify the 'timeout-length' property to determine how long the notification should stay on screen
3416        .directive('wnNotifications', wnNotifications)
3417
3418        // errors for the user to see
3419        .directive('wnErrors', wnErrors)
3420
3421        .directive('helpdeskError', helpdeskError);
3422
3423    function wnNotifications() {
3424        return {
3425            restrict: 'EA',
3426            replace: true,
3427            template: '<div class="alert {{ notification.sCSSClass || \'alert-info\' }} alert-dismissible" data-ng-repeat="notification in wnn.notifications track by notification.id" role="alert">' +
3428                '<button class="close" data-dismiss="alert" aria-label="Close"><i class="fas fa-times"></i></button>' +
3429                '<p data-ng-bind-html="notification.message"></p>' +
3430                '</div>',
3431            controllerAs: 'wnn',
3432            link: function (scope, elem, attrs) {
3433                scope.$emit('wnNotifications loaded');
3434                if (!!attrs.timeoutLength) {
3435                    scope.timeoutLength = attrs.timeoutLength;
3436                }
3437            },
3438            controller: ['$scope', '$timeout', 'WnErrorHandler', function ($scope, $timeout, WnErrorHandler) {
3439                var vm = this;
3440
3441                vm.notifications = WnErrorHandler.Notifications;
3442
3443                $scope.$on('WnNotificationPushed', function (event, id) {
3444                    if (!!$scope.timeoutLength && $scope.timeoutLength > 0) {
3445                        $timeout(function () {
3446                            WnErrorHandler.RemoveById(id);
3447                        }, $scope.timeoutLength * 1000);
3448                    }
3449                });
3450
3451                vm.clear = function (id) {
3452                    WnErrorHandler.RemoveById(id);
3453                }
3454            }]
3455        }
3456    }
3457
3458    function wnErrors() {
3459        return {
3460            restrict: 'EA',
3461            replace: true,
3462            template: '<div id="webnames-error-directive" class="alert alert-danger alert-dismissible" data-ng-repeat="error in wne.errors track by error.id" role="alert">
3462' +
3463                '<button class="close" data-dismiss="alert" aria-label="Close" data-ng-if="!error.isCritical" data-ng-click="wne.clear(error.id)"><i class="fas fa-times"></i></button>' +
3464                '<p data-ng-bind-html="error.message"></p>' +
3465                '<p data-ng-if="error.isCritical">Timestamp: {{error.dtTimestamp | date: "yyyy-MM-dd H:mm:ss.sss"}}</p>' +
3466                '</div>',
3467            link: function (scope) {
3468                scope.$emit('wnErrors loaded');
3469            },
3470            controllerAs: 'wne',
3471            controller: ['$scope', '$timeout', 'WnErrorHandler', function ($scope, $timeout, WnErrorHandler) {
3472                var vm = this;
3473
3474                vm.errors = WnErrorHandler.Errors;
3475
3476                vm.clear = function (id) {
3477                    WnErrorHandler.RemoveById(id);
3478                }
3479            }]
3480        }
3481    }
3482
3483    function helpdeskError() {
3484        return {
3485            restrict: 'EA',
3486            template: '<div class="red-tip">' +
3487                '<h1>Cart Errors</h1>' +
3488                '<h3>Message</h3>' +
3489                '<p>{{message}}</p>' +
3490                '<h3>StackTrace</h3>' +
3491                '<p>{{stackTrace}}</p>' +
3492                '<h3>Suggested Solution</h3>' +
3493                '<p>{{suggestedSolution}}</p>' +
3494                '<h3>Timestamp</h3>' +
3495                '<p>{{dtCreated}}</p>' +
3496                '</div>',
3497            scope: {
3498                error: '='
3499            },
3500            controller: [
3501                '$scope', function ($scope) {
3502                    // When the error object changes, make sure it has all attributes and then display the error
3503                    $scope.$watch('error', function () {
3504                        if ($scope.error &&
3505                            $scope.error.message &&
3506                            $scope.error.stackTrace &&
3507                            $scope.error.suggestedSolution &&
3508                            $scope.error.dtCreated) {
3509                            $scope.message = $scope.error.message;
3510                            $scope.stackTrace = $scope.error.stackTrace;
3511                            $scope.suggestedSolution = $scope.error.suggestedSolution;
3512                            $scope.dtCreated = $scope.error.dtCreated;
3513                        }
3514                    });
3515                }
3516            ]
3517        }
3518    };
3519})();
3520
3521/*
3522    WebnamesError - WnErrorHandler
3523    ------------------
3524    Service used to push messages to the directive which will display them to the user.
3525
3526    Author: Akos Sebestyen
3527 */
3528(function () {
3529    "use strict";
3530
3531    angular.module('WebnamesError')
3532        .service('WnErrorHandler', ['$rootScope', '$timeout', '$window', '$location', '$anchorScroll', 'WnMessage', '$log', function ($rootScope, $timeout, $window, $location, $anchorScroll, WnMessage, $log) {
3533            var service = this;
3534            var messageCount = 0;
3535
3536            var wnNotificationsPresent = false;
3537            var wnErrorsPresent = false;
3538
3539            service.scrollToError = true;
3540            service.Notifications = [];
3541            service.Errors = [];
3542
3543            service.ClearAll = function () {
3544                // Must clear array using length so that references aren't lost
3545                service.Notifications.length = 0;
3546                service.Errors.length = 0;
3547            }
3548
3549            // Use this to push notifications to the user
3550            service.PushNotification = function (errorString, sCSSClass) {
3551                var id = messageCount;
3552                service.Notifications.push(new WnMessage(id, errorString, false, sCSSClass));
3553                $rootScope.$broadcast('WnNotificationPushed', id);
3554                messageCount++;
3555
3556                return service.Notifications[service.Notifications.length - 1];
3557            }
3558
3559            // Use this to push errors to the user
3560            service.PushError = function (errorString, isCritical) {
3561                var id = messageCount;
3562                service.Errors.push(new WnMessage(id, errorString, !!isCritical));
3563                $rootScope.$broadcast('WnErrorPushed', id);
3564                messageCount++;
3565
3566                if (service.scrollToError) {
3567                    $location.hash("webnames-error-directive");
3568                    $anchorScroll();
3569                }
3570
3571                return service.Errors[service.Errors.length - 1];
3572            }
3573
3574            service.GetById = function (id) {
3575                return _.findWhere(service.Notifications, { id: id });
3576            }
3577
3578            service.RemoveById = function (id) {
3579                var index = _.findIndex(service.Notifications, { id: id });
3580
3581                if (index < 0) {
3582                    index = _.findIndex(service.Errors, { id: id });
3583                    if (index > -1 && !service.Errors[index].isCritical) service.Errors.splice(index, 1);
3584                } else {
3585                    service.Notifications.splice(index, 1);
3586                }
3587            }
3588
3589            // TODO: implement parameters
3590            /**
3591             * Redirects client to the robot page.
3592             * @returns {} 
3593             */
3594            service.KillNonRecoverable = function () {
3595                $window.location.href = $window._SmartURL + "error/oops/default.aspx";
3596            }
3597
3598            // Save events from link function of directives for page validation
3599            $rootScope.$on('wnNotifications loaded', function () {
3600                wnNotificationsPresent = true;
3601            });
3602
3603            $rootScope.$on('wnErrors loaded', function () {
3604                wnErrorsPresent = true;
3605            });
3606
3607            // Used for validation of the Angular page
3608            $timeout(function () {
3609                if (!wnErrorsPresent) {
3610                    throw "No 'wnErrors' directive found! You should not use this module without placing this directive on your page first!";
3611                }
3612                if (!wnNotificationsPresent) {
3613                    throw "No 'wnNotifications' directive found! You should not use this module without placing this directive on your page first!";
3614                }
3615            });
3616        }])
3617
3618        .factory('WnMessage', [function () {
3619            function WnMessage(id, message, isCritical, sCSSClass) {
3620                var model = this;
3621
3622                model.id = id;
3623                model.dtTimestamp = new Date();
3624                model.isCritical = isCritical;
3625                model.sCSSClass = sCSSClass || "";
3626
3627                model.message = message;
3628            }
3629
3630            return WnMessage;
3631        }]);
3632})();
3633
3634(function () {
3635    "use strict";
3636    angular.module("WebnamesLocationHelper", ["WebnamesCommon"]);
3637})();
3638(function () {
3639    "use strict";
3640
3641    angular
3642        .module("WebnamesLocationHelper")
3643        .service("QueryStringFilterBinder", ["$location", "$rootScope", "$filter", QueryStringFilterBinder]);
3644
3645    function QueryStringFilterBinder($location, $rootScope, $filter) {
3646        var service = this;
3647
3648        var currentBindings = [];
3649
3650        /**
3651         * Unbinds all listeners set up
3652         * @returns {undefined} 
3653         */
3654        service.UnbindAllFilters = function () {
3655            var startingLength = currentBindings.length;
3656            for (var i = 0; i < startingLength; i++) {
3657                var binding = currentBindings.pop();
3658
3659                binding.deregisterLocationListener();
3660                binding.deregisterWatch();
3661            }
3662        }
3663
3664        /**
3665         * Binds a flat object of filters to url params
3666         * @param {Object} filterObj a flat object of filters which will be bound to the url params by type
3667         * @param {Boolean} bExcludeFilterKeys a boolean flag to indicate whether it shouldn't bind to URL params if all of the filter properties are true
3668         * @param {Object}
3668 excludeObj an object containing parameters with keys that are not counted when determining if location parameters are applied
3669         * @param {Object} includeObj an object containing parameters with keys that are counted when determining if location parameters are applied
3670         * Note: if (includeObj === undefined), all parameters (even random ones) will be counted
3671         * @returns {Function} a deregister function for this binding
3672         */
3673        service.BindFilters = function (filterObj, bExcludeFilterKeys, excludeObj, includeObj) {
3674
3675            if (bExcludeFilterKeys === undefined) bExcludeFilterKeys = false;
3676            if (excludeObj === undefined) excludeObj = {};
3677
3678            if (!_.isObject(filterObj)) throw new Error("parameter being bound must be an object");
3679            
3680            // only apply location parameters if there are any in the URL 
3681            //(that aren't "excluded", and, if includeObj is defined, also are "included")
3682            var bApplyLocationParams = false;
3683            for (var sParam in $location.search()) {
3684                if ($location.search().hasOwnProperty(sParam)) {
3685                    var bDontExclude = true;
3686                    for (var sExcludeParam in excludeObj) {
3687                        if (excludeObj.hasOwnProperty(sExcludeParam)) {
3688                            if (sParam == sExcludeParam) {
3689                                bDontExclude = false;
3690                                break;
3691                            }
3692                        }
3693                    }
3694                    if (!bDontExclude) continue;
3695                    
3696                    if (includeObj === undefined) {                        
3697                        bApplyLocationParams = true;
3698                        break;
3699                    }
3700                    var bInclude = false;
3701                    for (var sIncudeParam in includeObj) {
3702                        if (includeObj.hasOwnProperty(sIncudeParam)) {
3703                            if (sParam == sIncudeParam) {
3704                                bInclude = true;
3705                                break;
3706                            }
3707                        }
3708                    }
3709                    if (bInclude) {
3710                        bApplyLocationParams = true;
3711                        break;
3712                    }
3713                }
3714            }
3715            if (bApplyLocationParams) {
3716                applyLocationParams(filterObj, bExcludeFilterKeys, true);
3717            }
3718
3719            // Set up a watch on filter object to bind each one of its properties to changing the url
3720            var deregisterWatch = $rootScope.$watch(function () { return filterObj }, function (newVal) {
3721                _.each(_.keys(newVal), function (key) {
3722                    if (_.isBoolean(newVal[key])) {
3723                        // if we are doing exclusion, reverse our set operation
3724                        if (!bExcludeFilterKeys) {
3725                            $location.search(key, newVal[key] ? true : null);
3726                        } else {
3727                            $location.search(key, !newVal[key] ? true : null);
3728                        }
3729                    }
3730                    else if (_.isNumber(newVal[key]) || _.isString(newVal[key])) {
3731                        $location.search(key, newVal[key]);
3732                    }
3733                    else {
3734                        $location.search(key, newVal[key]);   
3735                    }
3736                });
3737            }, true);
3738
3739            var deregisterLocationListener = $rootScope.$on("$locationChangeSuccess", function () {
3740                applyLocationParams(filterObj, bExcludeFilterKeys);
3741            });
3742
3743            currentBindings.push({
3744                filterObj: filterObj,
3745                deregisterLocationListener: deregisterLocationListener,
3746                deregisterWatch: deregisterWatch
3747            });
3748
3749            return function () {
3750                var bindingIndex = _.findIndex(currentBindings, function(binding) {
3751                    return binding.filterObj === filterObj;
3752                });
3753                currentBindings[bindingIndex].deregisterWatch();
3754                currentBindings[bindingIndex].deregisterLocationListener();
3755                currentBindings.splice(bindingIndex, 1);
3756            }
3757        }
3758
3759        function applyLocationParams(filterObj, allTrueDontBind) {
3760            var params = $location.search();
3761            var paramKeys = _.keys(params);
3762            // Loop through each key of the filter object and update its value based on URL parameters
3763            _.each(_.keys(filterObj), function (key) {
3764
3765                switch (typeof filterObj[key]) {
3766                    case "boolean":
3767                        filterObj[key] = ((_.indexOf(paramKeys, key) < 0) ? !!allTrueDontBind : !allTrueDontBind);
3768                        break;
3769                    case "number":
3770                        if (params.hasOwnProperty(key) && !isNaN(params[key])) {
3771                            filterObj[key] = parseInt(params[key]);
3772                        }
3773                        break;
3774                    default:
3775                        filterObj[key]= params[key];
3776                        break;
3777                }
3778            });
3779        }
3780    }
3781})();
3782
3783(function () {
3784    "use strict";
3785
3786    angular.module("WebnamesLocationHelper")
3787    .service("LegacyParam", ['$window', function($window) {
3788        var service = this;
3789
3790        service.Params = {};
3791
3792        /**
3793         * Helper to get a list of domain name parameters from the URL
3794         * @returns {Array} An array of domain parameters (params that begin with 'dom')  
3795         */
3796        service.GetDomains = function() {
3797            return _.chain(service.Params)
3798                        .map(function (value, key) {
3799                            if (key.substr(0, 3) === "dom") return value;
3800                            else return undefined;
3801                        })
3802                        .compact()
3803                        .uniq()
3804                        .value();
3805        }
3806
3807        var paramArray = $window.location.search.substr(1).split("&");
3808
3809        _.each(paramArray, function (param) {
3810            var pair = param.split("=");
3811            service.Params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
3812        });
3813    }]);
3814})();
3815(function () {
3816    "use strict";
3817
3818    angular
3819      .module("WebnamesLocationHelper")
3820      .directive("wnHref", ["$window", wnHref]);
3821
3822    function wnHref($window) {
3823        var directive = {
3824            link: link,
3825            restrict: "A"
3826        };
3827
3828        function link(scope, element, attrs) {
3829            attrs.$set("href", $window._SmartURL + attrs.wnHref);
3830        }
3831
3832        return directive;
3833    }
3834})();
3835/*
3836    Webnames Common
3837    --------------------------------
3838    Module to hold common Angular functions
3839    
3840    Author: Akos Sebestyen 2015
3841*/
3842(function () {
3843    "use strict";
3844
3845    angular.module('WebnamesCommon', []);
3846
3847})();
3848
3849
3850/*
3851    Generic Injector
3852    --------------------------------
3853    Used to inject data into Angular on pageload.
3854
3855    The provider exposes a "InjectGenericProperty" method that can be used at config time to get JSON data into the service.
3856
3857    Then you can access the data from GenericInjector.PropBag.yourProperty
3858    
3859    Author: Akos Sebestyen 2015
3860*/
3861(function () {
3862    "use strict";
3863
3864    angular.module('WebnamesCommon')
3865    .provider('GenericInjector', function () {
3866        var _propBag = {};
3867
3868        // Assign the prop bag
3869        var GenericInjector = function () {
3870            this.PropBag = _propBag;            
3871        };
3872
3873        return {
3874            $get: ['$injector', function ($injector) { return $injector.instantiate(GenericInjector) }],
3875            InjectGenericProperty: function (propName, propValue) { _propBag[propName] = propValue}
3876        }
3877    });
3878
3879})();
3880
3881
3882(function () {
3883    "use strict";
3884
3885    angular.module("WebnamesCommon")
3886    .factory("WnFocus", ['$timeout', '$document', function ($timeout, $document) {
3887        return function(id) {
3888            $timeout(function () {
3889                var element = $document.getElementById(id);
3890                if (element)
3891                    element.focus();
3892            });
3893        }                
3894    }]);
3895})();
3896// Scrolls to the top of the given selector. Use offset to offset the value by pixels
3897
3898(function () {
3899    "use strict";
3900
3901    angular.module("WebnamesCommon")
3902    .factory("WnScroll", ['$timeout', function ($timeout) {
3903        return function (selector, offset, animateTime) {
3904            if (!offset) offset = 0;
3905            if (!animateTime) animateTime = 500;
3906            $timeout(function () {
3907                $('html, body').animate({ scrollTop: ($(selector).offset().top + offset) }, animateTime);
3908            });
3909        }
3910    }]);
3911})();
3912// https://gist.github.com/tommaitland/7579618
3913(function () {
3914    angular.module('WebnamesCommon').directive('ngDebounce', ['$timeout', function ($timeout) {
3915        return {
3916            restrict: 'A',
3917            require: 'ngModel',
3918            priority: 99,
3919            link: function (scope, elm, attr, ngModelCtrl) {
3920                if (attr.type === 'radio' || attr.type === 'checkbox') return;
3921
3922                elm.unbind('input');
3923
3924                var debounce;
3925                elm.bind('input', function () {
3926                    $timeout.cancel(debounce);
3927                    debounce = $timeout(function () {
3928                        scope.$apply(function () {
3929                            ngModelCtrl.$setViewValue(elm.val());
3930                        });
3931                    }, attr.ngDebounce || 1000);
3932                });
3933                elm.bind('blur', function () {
3934                    scope.$apply(function () {
3935                        ngModelCtrl.$setViewValue(elm.val());
3936                    });
3937                });
3938            }
3939
3940        }
3941    }]);
3942})();
3943
3944(function () {
3945    "use strict";
3946
3947    angular.module('WebnamesCommon')
3948        .filter('startFrom', function () {
3949
3950            return function (input, start) {
3951                if (input !== undefined && angular.isArray(input)) {
3952                    return input.slice(start);
3953                } else {
3954                    return [];
3955                }
3956            }
3957        })
3958    .filter("getActiveKeys", [function () {
3959        /**
3960         * Takes an object and returns an array of keys where the values are truthy
3961         * @param {Object} obj Flat object of key value pairs
3962         * @returns {Array} array of keys with truthy values
3963         */
3964        return function(obj) {
3965            return _.chain(obj)
3966            .pairs() // Turn object into array of arrays as key value pairs
3967            .filter(function (kvp) { return !!kvp[1] }) // Return all Key-Value pairs where value is truthy
3968            .map(function (kvp) { return kvp[0] }) // Map into an array of just strings of keys
3969            .value();
3970        }
3971    }])
3972    .filter('nullToEnd', function () {
3973        return function (array, key) {
3974            if (!angular.isArray(array)) return;
3975
3976            var present = array.filter(function (item) {
3977                return item[key];
3978            });
3979            var empty = array.filter(function (item) {
3980                return !item[key];
3981            });
3982
3983            return present.concat(empty);
3984        }
3985    })
3986    .run(["$http", function ($http) {
3987        $http.defaults.headers.common['X-XSRF-Token'] =
3988            angular.element(document.querySelector('input[name="__RequestVerificationToken"]')).attr('value');
3989    }]);
3990})();
3991

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.