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><div ng-bind-html="snippet"><br/></div></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><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> 2528</div></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><div ng-bind="snippet"><br/></div></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("<p style=\"color:blue\">an html\n" + 2557 "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 2558 "snippet</p>"); 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 <b onclick=\"alert(1)\">text</b>"); 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, '&'). 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, '<'). 3011 replace(/>/g, '>'); 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><div ng-bind-html="snippet | linky"><br></div></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><div ng-bind-html="snippetWithSingleURL | linky:'_blank'"><br></div></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><div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"><br></div></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><div ng-bind="snippet"><br></div></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, '"'), 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.