PageSourceSearch

https://customer.nationalgrid.co.uk/public/js-loader/~0/~2-1.8.2/~…?575.MainSite.20260731.2343.2

js nationalgrid.co.uk collected 2026-09-24 09:01:21 UTC 540,392 bytes, 9,279 lines download raw bytes

vendor: 635 bytes, lines 1-8
1/** include: angular.min.js **/
2window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular-1.8.2/angular.min.js');
3/*
4 AngularJS v1.8.2
5 (c) 2010-2020 Google LLC. http://angularjs.org
6 License: MIT
7*/
8(function(z){'use strict';function ve(a){if(D(a))w(a.objectMaxDepth)&&(Xb.objectMaxDepth=Yb(a.objectMaxDepth)?a.objectMaxDepth:NaN),w(a.urlErrorParamsEnabled)&&Ga(a.urlErrorParamsEnabled)&&(Xb.urlErrorParamsEnabled=a.urlErrorParamsEnabled);else return Xb}function Yb(a){return X(a)&&0<a}function F(a,b){b=b||Error;return function(){var d=arguments[0],c;c="["+(a?a+":":"")+d+"] http://errors.angularjs.org/1.8.2
vendor: 13,480 bytes, lines 8-34
8/"+(a?a+"/":"")+d;for(d=1;d<arguments.length;d++){c=c+(1==d?"?":"&")+"p"+(d-1)+"=";var e=encodeURIComponent,
9f;f=arguments[d];f="function"==typeof f?f.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof f?"undefined":"string"!=typeof f?JSON.stringify(f):f;c+=e(f)}return new b(c)}}function za(a){if(null==a||$a(a))return!1;if(H(a)||C(a)||x&&a instanceof x)return!0;var b="length"in Object(a)&&a.length;return X(b)&&(0<=b&&b-1 in a||"function"===typeof a.item)}function r(a,b,d){var c,e;if(a)if(B(a))for(c in a)"prototype"!==c&&"length"!==c&&"name"!==c&&a.hasOwnProperty(c)&&b.call(d,a[c],c,a);else if(H(a)||
10za(a)){var f="object"!==typeof a;c=0;for(e=a.length;c<e;c++)(f||c in a)&&b.call(d,a[c],c,a)}else if(a.forEach&&a.forEach!==r)a.forEach(b,d,a);else if(Pc(a))for(c in a)b.call(d,a[c],c,a);else if("function"===typeof a.hasOwnProperty)for(c in a)a.hasOwnProperty(c)&&b.call(d,a[c],c,a);else for(c in a)ta.call(a,c)&&b.call(d,a[c],c,a);return a}function Qc(a,b,d){for(var c=Object.keys(a).sort(),e=0;e<c.length;e++)b.call(d,a[c[e]],c[e]);return c}function Zb(a){return function(b,d){a(d,b)}}function we(){return++qb}
11function $b(a,b,d){for(var c=a.$$hashKey,e=0,f=b.length;e<f;++e){var g=b[e];if(D(g)||B(g))for(var k=Object.keys(g),h=0,l=k.length;h<l;h++){var m=k[h],p=g[m];d&&D(p)?ha(p)?a[m]=new Date(p.valueOf()):ab(p)?a[m]=new RegExp(p):p.nodeName?a[m]=p.cloneNode(!0):ac(p)?a[m]=p.clone():"__proto__"!==m&&(D(a[m])||(a[m]=H(p)?[]:{}),$b(a[m],[p],!0)):a[m]=p}}c?a.$$hashKey=c:delete a.$$hashKey;return a}function S(a){return $b(a,Ha.call(arguments,1),!1)}function xe(a){return $b(a,Ha.call(arguments,1),!0)}function fa(a){return parseInt(a,
1210)}function bc(a,b){return S(Object.create(a),b)}function E(){}function Ta(a){return a}function ia(a){return function(){return a}}function cc(a){return B(a.toString)&&a.toString!==la}function A(a){return"undefined"===typeof a}function w(a){return"undefined"!==typeof a}function D(a){return null!==a&&"object"===typeof a}function Pc(a){return null!==a&&"object"===typeof a&&!Rc(a)}function C(a){return"string"===typeof a}function X(a){return"number"===typeof a}function ha(a){return"[object Date]"===la.call(a)}
13function H(a){return Array.isArray(a)||a instanceof Array}function dc(a){switch(la.call(a)){case "[object Error]":return!0;case "[object Exception]":return!0;case "[object DOMException]":return!0;default:return a instanceof Error}}function B(a){return"function"===typeof a}function ab(a){return"[object RegExp]"===la.call(a)}function $a(a){return a&&a.window===a}function bb(a){return a&&a.$evalAsync&&a.$watch}function Ga(a){return"boolean"===typeof a}function ye(a){return a&&X(a.length)&&ze.test(la.call(a))}
14function ac(a){return!(!a||!(a.nodeName||a.prop&&a.attr&&a.find))}function Ae(a){var b={};a=a.split(",");var d;for(d=0;d<a.length;d++)b[a[d]]=!0;return b}function ua(a){return K(a.nodeName||a[0]&&a[0].nodeName)}function cb(a,b){var d=a.indexOf(b);0<=d&&a.splice(d,1);return d}function Ia(a,b,d){function c(a,b,c){c--;if(0>c)return"...";var d=b.$$hashKey,f;if(H(a)){f=0;for(var g=a.length;f<g;f++)b.push(e(a[f],c))}else if(Pc(a))for(f in a)b[f]=e(a[f],c);else if(a&&"function"===typeof a.hasOwnProperty)for(f in a)a.hasOwnProperty(f)&&
15(b[f]=e(a[f],c));else for(f in a)ta.call(a,f)&&(b[f]=e(a[f],c));d?b.$$hashKey=d:delete b.$$hashKey;return b}function e(a,b){if(!D(a))return a;var d=g.indexOf(a);if(-1!==d)return k[d];if($a(a)||bb(a))throw oa("cpws");var d=!1,e=f(a);void 0===e&&(e=H(a)?[]:Object.create(Rc(a)),d=!0);g.push(a);k.push(e);return d?c(a,e,b):e}function f(a){switch(la.call(a)){case "[object Int8Array]":case "[object Int16Array]":case "[object Int32Array]":case "[object Float32Array]":case "[object Float64Array]":case "[object Uint8Array]":case "[object Uint8ClampedArray]":case "[object Uint16Array]":case "[object Uint32Array]":return new a.constructor(e(a.buffer),
16a.byteOffset,a.length);case "[object ArrayBuffer]":if(!a.slice){var b=new ArrayBuffer(a.byteLength);(new Uint8Array(b)).set(new Uint8Array(a));return b}return a.slice(0);case "[object Boolean]":case "[object Number]":case "[object String]":case "[object Date]":return new a.constructor(a.valueOf());case "[object RegExp]":return b=new RegExp(a.source,a.toString().match(/[^/]*$/)[0]),b.lastIndex=a.lastIndex,b;case "[object Blob]":return new a.constructor([a],{type:a.type})}if(B(a.cloneNode))return a.cloneNode(!0)}
17var g=[],k=[];d=Yb(d)?d:NaN;if(b){if(ye(b)||"[object ArrayBuffer]"===la.call(b))throw oa("cpta");if(a===b)throw oa("cpi");H(b)?b.length=0:r(b,function(a,c){"$$hashKey"!==c&&delete b[c]});g.push(a);k.push(b);return c(a,b,d)}return e(a,d)}function ec(a,b){return a===b||a!==a&&b!==b}function va(a,b){if(a===b)return!0;if(null===a||null===b)return!1;if(a!==a&&b!==b)return!0;var d=typeof a,c;if(d===typeof b&&"object"===d)if(H(a)){if(!H(b))return!1;if((d=a.length)===b.length){for(c=0;c<d;c++)if(!va(a[c],
18b[c]))return!1;return!0}}else{if(ha(a))return ha(b)?ec(a.getTime(),b.getTime()):!1;if(ab(a))return ab(b)?a.toString()===b.toString():!1;if(bb(a)||bb(b)||$a(a)||$a(b)||H(b)||ha(b)||ab(b))return!1;d=T();for(c in a)if("$"!==c.charAt(0)&&!B(a[c])){if(!va(a[c],b[c]))return!1;d[c]=!0}for(c in b)if(!(c in d)&&"$"!==c.charAt(0)&&w(b[c])&&!B(b[c]))return!1;return!0}return!1}function db(a,b,d){return a.concat(Ha.call(b,d))}function Va(a,b){var d=2<arguments.length?Ha.call(arguments,2):[];return!B(b)||b instanceof
19RegExp?b:d.length?function(){return arguments.length?b.apply(a,db(d,arguments,0)):b.apply(a,d)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}}function Sc(a,b){var d=b;"string"===typeof a&&"$"===a.charAt(0)&&"$"===a.charAt(1)?d=void 0:$a(b)?d="$WINDOW":b&&z.document===b?d="$DOCUMENT":bb(b)&&(d="$SCOPE");return d}function eb(a,b){if(!A(a))return X(b)||(b=b?2:null),JSON.stringify(a,Sc,b)}function Tc(a){return C(a)?JSON.parse(a):a}function fc(a,b){a=a.replace(Be,"");var d=Date.parse("Jan 01, 1970 00:00:00 "+
20a)/6E4;return Y(d)?b:d}function Uc(a,b){a=new Date(a.getTime());a.setMinutes(a.getMinutes()+b);return a}function gc(a,b,d){d=d?-1:1;var c=a.getTimezoneOffset();b=fc(b,c);return Uc(a,d*(b-c))}function Aa(a){a=x(a).clone().empty();var b=x("<div></div>").append(a).html();try{return a[0].nodeType===Pa?K(b):b.match(/^(<[^>]+>)/)[1].replace(/^<([\w-]+)/,function(a,b){return"<"+K(b)})}catch(d){return K(b)}}function Vc(a){try{return decodeURIComponent(a)}catch(b){}}function hc(a){var b={};r((a||"").split("&"),
21function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=Vc(e),w(e)&&(f=w(f)?Vc(f):!0,ta.call(b,e)?H(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Ce(a){var b=[];r(a,function(a,c){H(a)?r(a,function(a){b.push(ba(c,!0)+(!0===a?"":"="+ba(a,!0)))}):b.push(ba(c,!0)+(!0===a?"":"="+ba(a,!0)))});return b.length?b.join("&"):""}function ic(a){return ba(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ba(a,
22b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function De(a,b){var d,c,e=Qa.length;for(c=0;c<e;++c)if(d=Qa[c]+b,C(d=a.getAttribute(d)))return d;return null}function Ee(a,b){var d,c,e={};r(Qa,function(b){b+="app";!d&&a.hasAttribute&&a.hasAttribute(b)&&(d=a,c=a.getAttribute(b))});r(Qa,function(b){b+="app";var e;!d&&(e=a.querySelector("["+b.replace(":","\\:")+"]"))&&(d=e,c=e.getAttribute(b))});
23d&&(Fe?(e.strictDi=null!==De(d,"strict-di"),b(d,c?[c]:[],e)):z.console.error("AngularJS: disabling automatic bootstrap. <script> protocol indicates an extension, document.location.href does not match."))}function Wc(a,b,d){D(d)||(d={});d=S({strictDi:!1},d);var c=function(){a=x(a);if(a.injector()){var c=a[0]===z.document?"document":Aa(a);throw oa("btstrpd",c.replace(/</,"&lt;").replace(/>/,"&gt;"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",
24function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=fb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;z&&e.test(z.name)&&(d.debugInfoEnabled=!0,z.name=z.name.replace(e,""));if(z&&!f.test(z.name))return c();z.name=z.name.replace(f,"");ca.resumeBootstrap=function(a){r(a,function(a){b.push(a)});return c()};B(ca.resumeDeferredBootstrap)&&
25ca.resumeDeferredBootstrap()}function Ge(){z.name="NG_ENABLE_DEBUG_INFO!"+z.name;z.location.reload()}function He(a){a=ca.element(a).injector();if(!a)throw oa("test");return a.get("$$testability")}function Xc(a,b){b=b||"_";return a.replace(Ie,function(a,c){return(c?b:"")+a.toLowerCase()})}function Je(){var a;if(!Yc){var b=rb();(sb=A(b)?z.jQuery:b?z[b]:void 0)&&sb.fn.on?(x=sb,S(sb.fn,{scope:Wa.scope,isolateScope:Wa.isolateScope,controller:Wa.controller,injector:Wa.injector,inheritedData:Wa.inheritedData})):
26x=U;a=x.cleanData;x.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=(x._data(f)||{}).events)&&c.$destroy&&x(f).triggerHandler("$destroy");a(b)};ca.element=x;Yc=!0}}function Ke(){U.legacyXHTMLReplacement=!0}function gb(a,b,d){if(!a)throw oa("areq",b||"?",d||"required");return a}function tb(a,b,d){d&&H(a)&&(a=a[a.length-1]);gb(B(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ja(a,b){if("hasOwnProperty"===a)throw oa("badname",
27b);}function Le(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g<f;g++)c=b[g],a&&(a=(e=a)[c]);return!d&&B(a)?Va(e,a):a}function ub(a){for(var b=a[0],d=a[a.length-1],c,e=1;b!==d&&(b=b.nextSibling);e++)if(c||a[e]!==b)c||(c=x(Ha.call(a,0,e))),c.push(b);return c||a}function T(){return Object.create(null)}function jc(a){if(null==a)return"";switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=!cc(a)||H(a)||ha(a)?eb(a):a.toString()}return a}function Me(a){function b(a,
28b,c){return a[b]||(a[b]=c())}var d=F("$injector"),c=F("ng");a=b(a,"angular",Object);a.$$minErr=a.$$minErr||F;return b(a,"module",function(){var a={};return function(f,g,k){var h={};if("hasOwnProperty"===f)throw c("badname","module");g&&a.hasOwnProperty(f)&&(a[f]=null);return b(a,f,function(){function a(b,c,d,f){f||(f=e);return function(){f[d||"push"]([b,c,arguments]);return t}}function b(a,c,d){d||(d=e);return function(b,e){e&&B(e)&&(e.$$moduleName=f);d.push([a,c,arguments]);return t}}if(!g)throw d("nomod",
29f);var e=[],n=[],s=[],G=a("$injector","invoke","push",n),t={_invokeQueue:e,_configBlocks:n,_runBlocks:s,info:function(a){if(w(a)){if(!D(a))throw c("aobj","value");h=a;return this}return h},requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide","decorator",n),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider",
30"register"),directive:b("$compileProvider","directive"),component:b("$compileProvider","component"),config:G,run:function(a){s.push(a);return this}};k&&G(k);return t})}})}function ja(a,b){if(H(a)){b=b||[];for(var d=0,c=a.length;d<c;d++)b[d]=a[d]}else if(D(a))for(d in b=b||{},a)if("$"!==d.charAt(0)||"$"!==d.charAt(1))b[d]=a[d];return b||a}function Ne(a,b){var d=[];Yb(b)&&(a=ca.copy(a,null,b));return JSON.stringify(a,function(a,b){b=Sc(a,b);if(D(b)){if(0<=d.indexOf(b))return"...";d.push(b)}return b})}
31function Oe(a){S(a,{errorHandlingConfig:ve,bootstrap:Wc,copy:Ia,extend:S,merge:xe,equals:va,element:x,forEach:r,injector:fb,noop:E,bind:Va,toJson:eb,fromJson:Tc,identity:Ta,isUndefined:A,isDefined:w,isString:C,isFunction:B,isObject:D,isNumber:X,isElement:ac,isArray:H,version:Pe,isDate:ha,callbacks:{$$counter:0},getTestability:He,reloadWithDebugInfo:Ge,UNSAFE_restoreLegacyJqLiteXHTMLReplacement:Ke,$$minErr:F,$$csp:Ba,$$encodeUriSegment:ic,$$encodeUriQuery:ba,$$lowercase:K,$$stringify:jc,$$uppercase:vb});
32lc=Me(z);lc("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:Qe});a.provider("$compile",Zc).directive({a:Re,input:$c,textarea:$c,form:Se,script:Te,select:Ue,option:Ve,ngBind:We,ngBindHtml:Xe,ngBindTemplate:Ye,ngClass:Ze,ngClassEven:$e,ngClassOdd:af,ngCloak:bf,ngController:cf,ngForm:df,ngHide:ef,ngIf:ff,ngInclude:gf,ngInit:hf,ngNonBindable:jf,ngPluralize:kf,ngRef:lf,ngRepeat:mf,ngShow:nf,ngStyle:of,ngSwitch:pf,ngSwitchWhen:qf,ngSwitchDefault:rf,ngOptions:sf,ngTransclude:tf,ngModel:uf,
33ngList:vf,ngChange:wf,pattern:ad,ngPattern:ad,required:bd,ngRequired:bd,minlength:cd,ngMinlength:cd,maxlength:dd,ngMaxlength:dd,ngValue:xf,ngModelOptions:yf}).directive({ngInclude:zf,input:Af}).directive(wb).directive(ed);a.provider({$anchorScroll:Bf,$animate:Cf,$animateCss:Df,$$animateJs:Ef,$$animateQueue:Ff,$$AnimateRunner:Gf,$$animateAsyncRun:Hf,$browser:If,$cacheFactory:Jf,$controller:Kf,$document:Lf,$$isDocumentHidden:Mf,$exceptionHandler:Nf,$filter:fd,$$forceReflow:Of,$interpolate:Pf,$interval:Qf,
34$$intervalFactory:Rf,$http:Sf,$httpParamSerializer:Tf,$httpParamSerializerJQLike:Uf,$httpBackend:Vf,$xhrFactory:Wf,$jsonpCallbacks:Xf,$location:Yf,$log:Zf,$parse:$f,$rootScope:ag,$q:bg,$$q:cg,$sce:dg,$sceDelegate:eg,$sniffer:fg,$$taskTrackerFactory:gg,$templateCache:hg,$templateRequest:ig,$$testability:jg,$timeout:kg,$window:lg,$$rAF:mg,$$jqLite:ng,$$Map:og,$$cookieReader:pg})}]).info({angularVersion:"1.8.
342
vendor: 89,049 bytes, lines 34-209
34"})}function xb(a,b){return b.toUpperCase()}function yb(a){return a.replace(qg,xb)}function mc(a){a=
35a.nodeType;return 1===a||!a||9===a}function gd(a,b){var d,c,e,f=b.createDocumentFragment(),g=[],k;if(nc.test(a)){d=f.appendChild(b.createElement("div"));c=(rg.exec(a)||["",""])[1].toLowerCase();e=U.legacyXHTMLReplacement?a.replace(sg,"<$1></$2>"):a;if(10>wa)for(c=hb[c]||hb._default,d.innerHTML=c[1]+e+c[2],k=c[0];k--;)d=d.firstChild;else{c=qa[c]||[];for(k=c.length;-1<--k;)d.appendChild(z.document.createElement(c[k])),d=d.firstChild;d.innerHTML=e}g=db(g,d.childNodes);d=f.firstChild;d.textContent=""}else g.push(b.createTextNode(a));
36f.textContent="";f.innerHTML="";r(g,function(a){f.appendChild(a)});return f}function U(a){if(a instanceof U)return a;var b;C(a)&&(a=V(a),b=!0);if(!(this instanceof U)){if(b&&"<"!==a.charAt(0))throw oc("nosel");return new U(a)}if(b){b=z.document;var d;a=(d=tg.exec(a))?[b.createElement(d[1])]:(d=gd(a,b))?d.childNodes:[];pc(this,a)}else B(a)?hd(a):pc(this,a)}function qc(a){return a.cloneNode(!0)}function zb(a,b){!b&&mc(a)&&x.cleanData([a]);a.querySelectorAll&&x.cleanData(a.querySelectorAll("*"))}function id(a){for(var b in a)return!1;
37return!0}function jd(a){var b=a.ng339,d=b&&Ka[b],c=d&&d.events,d=d&&d.data;d&&!id(d)||c&&!id(c)||(delete Ka[b],a.ng339=void 0)}function kd(a,b,d,c){if(w(c))throw oc("offargs");var e=(c=Ab(a))&&c.events,f=c&&c.handle;if(f){if(b){var g=function(b){var c=e[b];w(d)&&cb(c||[],d);w(d)&&c&&0<c.length||(a.removeEventListener(b,f),delete e[b])};r(b.split(" "),function(a){g(a);Bb[a]&&g(Bb[a])})}else for(b in e)"$destroy"!==b&&a.removeEventListener(b,f),delete e[b];jd(a)}}function rc(a,b){var d=a.ng339;if(d=
38d&&Ka[d])b?delete d.data[b]:d.data={},jd(a)}function Ab(a,b){var d=a.ng339,d=d&&Ka[d];b&&!d&&(a.ng339=d=++ug,d=Ka[d]={events:{},data:{},handle:void 0});return d}function sc(a,b,d){if(mc(a)){var c,e=w(d),f=!e&&b&&!D(b),g=!b;a=(a=Ab(a,!f))&&a.data;if(e)a[yb(b)]=d;else{if(g)return a;if(f)return a&&a[yb(b)];for(c in b)a[yb(c)]=b[c]}}}function Cb(a,b){return a.getAttribute?-1<(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+b+" "):!1}function Db(a,b){if(b&&a.setAttribute){var d=
39(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," "),c=d;r(b.split(" "),function(a){a=V(a);c=c.replace(" "+a+" "," ")});c!==d&&a.setAttribute("class",V(c))}}function Eb(a,b){if(b&&a.setAttribute){var d=(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," "),c=d;r(b.split(" "),function(a){a=V(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});c!==d&&a.setAttribute("class",V(c))}}function pc(a,b){if(b)if(b.nodeType)a[a.length++]=b;else{var d=b.length;if("number"===typeof d&&b.window!==b){if(d)for(var c=
400;c<d;c++)a[a.length++]=b[c]}else a[a.length++]=b}}function ld(a,b){return Fb(a,"$"+(b||"ngController")+"Controller")}function Fb(a,b,d){9===a.nodeType&&(a=a.documentElement);for(b=H(b)?b:[b];a;){for(var c=0,e=b.length;c<e;c++)if(w(d=x.data(a,b[c])))return d;a=a.parentNode||11===a.nodeType&&a.host}}function md(a){for(zb(a,!0);a.firstChild;)a.removeChild(a.firstChild)}function Gb(a,b){b||zb(a);var d=a.parentNode;d&&d.removeChild(a)}function vg(a,b){b=b||z;if("complete"===b.document.readyState)b.setTimeout(a);
41else x(b).on("load",a)}function hd(a){function b(){z.document.removeEventListener("DOMContentLoaded",b);z.removeEventListener("load",b);a()}"complete"===z.document.readyState?z.setTimeout(a):(z.document.addEventListener("DOMContentLoaded",b),z.addEventListener("load",b))}function nd(a,b){var d=Hb[b.toLowerCase()];return d&&od[ua(a)]&&d}function wg(a,b){var d=function(c,d){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=b[d||c.type],g=f?f.length:0;if(g){if(A(c.immediatePropagationStopped)){var k=
42c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();k&&k.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};var h=f.specialHandlerWrapper||xg;1<g&&(f=ja(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||h(a,c,f[l])}};d.elem=a;return d}function xg(a,b,d){d.call(a,b)}function yg(a,b,d){var c=b.relatedTarget;c&&(c===a||zg.call(a,c))||d.call(a,b)}function ng(){this.$get=
43function(){return S(U,{hasClass:function(a,b){a.attr&&(a=a[0]);return Cb(a,b)},addClass:function(a,b){a.attr&&(a=a[0]);return Eb(a,b)},removeClass:function(a,b){a.attr&&(a=a[0]);return Db(a,b)}})}}function La(a,b){var d=a&&a.$$hashKey;if(d)return"function"===typeof d&&(d=a.$$hashKey()),d;d=typeof a;return d="function"===d||"object"===d&&null!==a?a.$$hashKey=d+":"+(b||we)():d+":"+a}function pd(){this._keys=[];this._values=[];this._lastKey=NaN;this._lastIndex=-1}function qd(a){a=Function.prototype.toString.call(a).replace(Ag,
44"");return a.match(Bg)||a.match(Cg)}function Dg(a){return(a=qd(a))?"function("+(a[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function fb(a,b){function d(a){return function(b,c){if(D(b))r(b,Zb(a));else return a(b,c)}}function c(a,b){Ja(a,"service");if(B(b)||H(b))b=n.instantiate(b);if(!b.$get)throw Ca("pget",a);return p[a+"Provider"]=b}function e(a,b){return function(){var c=t.invoke(b,this);if(A(c))throw Ca("undef",a);return c}}function f(a,b,d){return c(a,{$get:!1!==d?e(a,b):b})}function g(a){gb(A(a)||
45H(a),"modulesToLoad","not an array");var b=[],c;r(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=n.get(e[0]);f[e[1]].apply(f,e[2])}}if(!m.get(a)){m.set(a,!0);try{C(a)?(c=lc(a),t.modules[a]=c,b=b.concat(g(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)):B(a)?b.push(n.invoke(a)):H(a)?b.push(n.invoke(a)):tb(a,"module")}catch(e){throw H(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1===e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ca("modulerr",
46a,e.stack||e.message||e);}}});return b}function k(a,c){function d(b,e){if(a.hasOwnProperty(b)){if(a[b]===h)throw Ca("cdep",b+" <- "+l.join(" <- "));return a[b]}try{return l.unshift(b),a[b]=h,a[b]=c(b,e),a[b]}catch(f){throw a[b]===h&&delete a[b],f;}finally{l.shift()}}function e(a,c,f){var g=[];a=fb.$$annotate(a,b,f);for(var h=0,k=a.length;h<k;h++){var l=a[h];if("string"!==typeof l)throw Ca("itkn",l);g.push(c&&c.hasOwnProperty(l)?c[l]:d(l,f))}return g}return{invoke:function(a,b,c,d){"string"===typeof c&&
47(d=c,c=null);c=e(a,c,d);H(a)&&(a=a[a.length-1]);d=a;if(wa||"function"!==typeof d)d=!1;else{var f=d.$$ngIsClass;Ga(f)||(f=d.$$ngIsClass=/^class\b/.test(Function.prototype.toString.call(d)));d=f}return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=H(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:fb.$$annotate,has:function(b){return p.hasOwnProperty(b+"Provider")||a.hasOwnProperty(b)}}}
48b=!0===b;var h={},l=[],m=new Ib,p={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,ia(b),!1)}),constant:d(function(a,b){Ja(a,"constant");p[a]=b;s[a]=b}),decorator:function(a,b){var c=n.get(a+"Provider"),d=c.$get;c.$get=function(){var a=t.invoke(d,c);return t.invoke(b,null,{$delegate:a})}}}},n=p.$injector=k(p,function(a,b){ca.isString(b)&&l.push(b);throw Ca("unpr",l.join(" <- "));}),s={},
49G=k(s,function(a,b){var c=n.get(a+"Provider",b);return t.invoke(c.$get,c,void 0,a)}),t=G;p.$injectorProvider={$get:ia(G)};t.modules=n.modules=T();var N=g(a),t=G.get("$injector");t.strictDi=b;r(N,function(a){a&&t.invoke(a)});t.loadNewModules=function(a){r(g(a),function(a){a&&t.invoke(a)})};return t}function Bf(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window","$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===
50ua(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView();var c;c=g.yOffset;B(c)?c=c():ac(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):X(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=C(a)?a:X(a)?a.toString():d.hash();var b;a?(b=k.getElementById(a))?f(b):(b=e(k.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var k=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===
51b&&""===a||vg(function(){c.$evalAsync(g)})});return g}]}function ib(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;H(a)&&(a=a.join(" "));H(b)&&(b=b.join(" "));return a+" "+b}function Eg(a){C(a)&&(a=a.split(" "));var b=T();r(a,function(a){a.length&&(b[a]=!0)});return b}function ra(a){return D(a)?a:{}}function Fg(a,b,d,c,e){function f(){pa=null;k()}function g(){t=y();t=A(t)?null:t;va(t,P)&&(t=P);N=P=t}function k(){var a=N;g();if(v!==h.url()||a!==t)v=h.url(),N=t,r(J,function(a){a(h.url(),t)})}
52var h=this,l=a.location,m=a.history,p=a.setTimeout,n=a.clearTimeout,s={},G=e(d);h.isMock=!1;h.$$completeOutstandingRequest=G.completeTask;h.$$incOutstandingRequestCount=G.incTaskCount;h.notifyWhenNoOutstandingRequests=G.notifyWhenNoPendingTasks;var t,N,v=l.href,kc=b.find("base"),pa=null,y=c.history?function(){try{return m.state}catch(a){}}:E;g();h.url=function(b,d,e){A(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=N===e;b=ga(b).href;if(v===b&&(!c.history||f))return h;
53var k=v&&Da(v)===Da(b);v=b;N=e;!c.history||k&&f?(k||(pa=b),d?l.replace(b):k?(d=l,e=b,f=e.indexOf("#"),e=-1===f?"":e.substr(f),d.hash=e):l.href=b,l.href!==b&&(pa=b)):(m[d?"replaceState":"pushState"](e,"",b),g());pa&&(pa=b);return h}return(pa||l.href).replace(/#$/,"")};h.state=function(){return t};var J=[],I=!1,P=null;h.onUrlChange=function(b){if(!I){if(c.history)x(a).on("popstate",f);x(a).on("hashchange",f);I=!0}J.push(b);return b};h.$$applicationDestroyed=function(){x(a).off("hashchange popstate",
54f)};h.$$checkUrlChange=k;h.baseHref=function(){var a=kc.attr("href");return a?a.replace(/^(https?:)?\/\/[^/]*/,""):""};h.defer=function(a,b,c){var d;b=b||0;c=c||G.DEFAULT_TASK_TYPE;G.incTaskCount(c);d=p(function(){delete s[d];G.completeTask(a,c)},b);s[d]=c;return d};h.defer.cancel=function(a){if(s.hasOwnProperty(a)){var b=s[a];delete s[a];n(a);G.completeTask(E,b);return!0}return!1}}function If(){this.$get=["$window","$log","$sniffer","$document","$$taskTrackerFactory",function(a,b,d,c,e){return new Fg(a,
55c,b,d,e)}]}function Jf(){this.$get=function(){function a(a,c){function e(a){a!==p&&(n?n===a&&(n=a.n):n=a,f(a.n,a.p),f(a,p),p=a,p.n=null)}function f(a,b){a!==b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw F("$cacheFactory")("iid",a);var g=0,k=S({},c,{id:a}),h=T(),l=c&&c.capacity||Number.MAX_VALUE,m=T(),p=null,n=null;return b[a]={put:function(a,b){if(!A(b)){if(l<Number.MAX_VALUE){var c=m[a]||(m[a]={key:a});e(c)}a in h||g++;h[a]=b;g>l&&this.remove(n.key);return b}},get:function(a){if(l<Number.MAX_VALUE){var b=
56m[a];if(!b)return;e(b)}return h[a]},remove:function(a){if(l<Number.MAX_VALUE){var b=m[a];if(!b)return;b===p&&(p=b.p);b===n&&(n=b.n);f(b.n,b.p);delete m[a]}a in h&&(delete h[a],g--)},removeAll:function(){h=T();g=0;m=T();p=n=null},destroy:function(){m=k=h=null;delete b[a]},info:function(){return S({},k,{size:g})}}}var b={};a.info=function(){var a={};r(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}}function hg(){this.$get=["$cacheFactory",function(a){return a("templates")}]}
57function Zc(a,b){function d(a,b,c){var d=/^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/,e=T();r(a,function(a,f){a=a.trim();if(a in p)e[f]=p[a];else{var g=a.match(d);if(!g)throw $("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f};g[4]&&(p[a]=e[f])}});return e}function c(a){var b=a.charAt(0);if(!b||b!==K(b))throw $("baddir",a);if(a!==a.trim())throw $("baddir",a);}function e(a){var b=a.require||a.controller&&
58a.name;!H(b)&&D(b)&&r(b,function(a,c){var d=a.match(l);a.substring(d[0].length)||(b[c]=d[0]+c)});return b}var f={},g=/^\s*directive:\s*([\w-]+)\s+(.*)$/,k=/(([\w-]+)(?::([^;]+))?;?)/,h=Ae("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,m=/^(on[a-z]+|formaction)$/,p=T();this.directive=function pa(b,d){gb(b,"name");Ja(b,"directive");C(b)?(c(b),gb(d,"directiveFactory"),f.hasOwnProperty(b)||(f[b]=[],a.factory(b+"Directive",["$injector","$exceptionHandler",function(a,c){var d=[];r(f[b],function(f,
59g){try{var h=a.invoke(f);B(h)?h={compile:ia(h)}:!h.compile&&h.link&&(h.compile=ia(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||b;h.require=e(h);var k=h,l=h.restrict;if(l&&(!C(l)||!/[EACM]/.test(l)))throw $("badrestrict",l,b);k.restrict=l||"EA";h.$$moduleName=f.$$moduleName;d.push(h)}catch(m){c(m)}});return d}])),f[b].push(d)):r(b,Zb(pa));return this};this.component=function y(a,b){function c(a){function e(b){return B(b)||H(b)?function(c,d){return a.invoke(b,this,{$element:c,$attrs:d})}:
60b}var f=b.template||b.templateUrl?b.template:"",g={controller:d,controllerAs:Gg(b.controller)||b.controllerAs||"$ctrl",template:e(f),templateUrl:e(b.templateUrl),transclude:b.transclude,scope:{},bindToController:b.bindings||{},restrict:"E",require:b.require};r(b,function(a,b){"$"===b.charAt(0)&&(g[b]=a)});return g}if(!C(a))return r(a,Zb(Va(this,y))),this;var d=b.controller||function(){};r(b,function(a,b){"$"===b.charAt(0)&&(c[b]=a,B(d)&&(d[b]=a))});c.$inject=["$injector"];return this.directive(a,
61c)};this.aHrefSanitizationTrustedUrlList=function(a){return w(a)?(b.aHrefSanitizationTrustedUrlList(a),this):b.aHrefSanitizationTrustedUrlList()};Object.defineProperty(this,"aHrefSanitizationWhitelist",{get:function(){return this.aHrefSanitizationTrustedUrlList},set:function(a){this.aHrefSanitizationTrustedUrlList=a}});this.imgSrcSanitizationTrustedUrlList=function(a){return w(a)?(b.imgSrcSanitizationTrustedUrlList(a),this):b.imgSrcSanitizationTrustedUrlList()};Object.defineProperty(this,"imgSrcSanitizationWhitelist",
62{get:function(){return this.imgSrcSanitizationTrustedUrlList},set:function(a){this.imgSrcSanitizationTrustedUrlList=a}});var n=!0;this.debugInfoEnabled=function(a){return w(a)?(n=a,this):n};var s=!1;this.strictComponentBindingsEnabled=function(a){return w(a)?(s=a,this):s};var G=10;this.onChangesTtl=function(a){return arguments.length?(G=a,this):G};var t=!0;this.commentDirectivesEnabled=function(a){return arguments.length?(t=a,this):t};var N=!0;this.cssClassDirectivesEnabled=function(a){return arguments.length?
63(N=a,this):N};var v=T();this.addPropertySecurityContext=function(a,b,c){var d=a.toLowerCase()+"|"+b.toLowerCase();if(d in v&&v[d]!==c)throw $("ctxoverride",a,b,v[d],c);v[d]=c;return this};(function(){function a(b,c){r(c,function(a){v[a.toLowerCase()]=b})}a(W.HTML,["iframe|srcdoc","*|innerHTML","*|outerHTML"]);a(W.CSS,["*|style"]);a(W.URL,"area|href area|ping a|href a|ping blockquote|cite body|background del|cite input|src ins|cite q|cite".split(" "));a(W.MEDIA_URL,"audio|src img|src img|srcset source|src source|srcset track|src video|src video|poster".split(" "));
64a(W.RESOURCE_URL,"*|formAction applet|code applet|codebase base|href embed|src frame|src form|action head|profile html|manifest iframe|src link|href media|src object|codebase object|data script|src".split(" "))})();this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$sce","$animate",function(a,b,c,e,p,M,L,u,R){function q(){try{if(!--Ja)throw Ua=void 0,$("infchng",G);L.$apply(function(){for(var a=0,b=Ua.length;a<b;++a)try{Ua[a]()}catch(d){c(d)}Ua=
65void 0})}finally{Ja++}}function ma(a,b){if(!a)return a;if(!C(a))throw $("srcset",b,a.toString());for(var c="",d=V(a),e=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,e=/\s/.test(d)?e:/(,)/,d=d.split(e),e=Math.floor(d.length/2),f=0;f<e;f++)var g=2*f,c=c+u.getTrustedMediaUrl(V(d[g])),c=c+(" "+V(d[g+1]));d=V(d[2*f]).split(/\s/);c+=u.getTrustedMediaUrl(V(d[0]));2===d.length&&(c+=" "+V(d[1]));return c}function w(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr=
66{};this.$$element=a}function O(a,b,c){Fa.innerHTML="<span "+b+">";b=Fa.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function sa(a,b){try{a.addClass(b)}catch(c){}}function da(a,b,c,d,e){a instanceof x||(a=x(a));var f=Xa(a,b,a,c,d,e);da.$$addScopeClass(a);var g=null;return function(b,c,d){if(!a)throw $("multilink");gb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var h=d.parentBoundTranscludeFn,k=d.transcludeControllers;d=d.futureParentElement;
67h&&h.$$boundTransclude&&(h=h.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==ua(d)&&la.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==g?x(ja(g,x("<div></div>").append(a).html())):c?Wa.clone.call(a):a;if(k)for(var l in k)d.data("$"+l+"Controller",k[l].instance);da.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,h);c||(a=f=null);return d}}function Xa(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,p,I,t;if(n)for(t=Array(c.length),m=0;m<h.length;m+=3)f=h[m],t[f]=c[f];else t=c;m=0;for(p=h.length;m<
68p;)k=t[h[m++]],c=h[m++],f=h[m++],c?(c.scope?(l=a.$new(),da.$$addScopeInfo(x(k),l)):l=a,I=c.transcludeOnThisElement?ka(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?ka(a,b):null,c(f,l,k,d,I)):f&&f(a,k.childNodes,void 0,e)}for(var h=[],k=H(a)||a instanceof x,l,m,p,I,n,t=0;t<a.length;t++){l=new w;11===wa&&jb(a,t,k);m=tc(a[t],[],l,0===t?d:void 0,e);(f=m.length?aa(m,a[t],l,b,c,null,[],[],f):null)&&f.scope&&da.$$addScopeClass(l.$$element);l=f&&f.terminal||!(p=a[t].childNodes)||!p.length?null:Xa(p,
69f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||l)h.push(t,f,l),I=!0,n=n||f;f=null}return I?g:null}function jb(a,b,c){var d=a[b],e=d.parentNode,f;if(d.nodeType===Pa)for(;;){f=e?d.nextSibling:a[b+1];if(!f||f.nodeType!==Pa)break;d.nodeValue+=f.nodeValue;f.parentNode&&f.parentNode.removeChild(f);c&&f===a[b+1]&&a.splice(b+1,1)}}function ka(a,b,c){function d(e,f,g,h,k){e||(e=a.$new(!1,k),e.$$transcluded=!0);return b(e,f,{parentBoundTranscludeFn:c,transcludeControllers:g,
70futureParentElement:h})}var e=d.$$slots=T(),f;for(f in b.$$slots)e[f]=b.$$slots[f]?ka(a,b.$$slots[f],c):null;return d}function tc(a,b,d,e,f){var g=d.$attr,h;switch(a.nodeType){case 1:h=ua(a);Y(b,xa(h),"E",e,f);for(var l,m,n,t,J,s=a.attributes,v=0,G=s&&s.length;v<G;v++){var P=!1,N=!1,r=!1,y=!1,u=!1,M;l=s[v];m=l.name;t=l.value;n=xa(m.toLowerCase());(J=n.match(Ra))?(r="Attr"===J[1],y="Prop"===J[1],u="On"===J[1],m=m.replace(rd,"").toLowerCase().substr(4+J[1].length).replace(/_(.)/g,function(a,b){return b.toUpperCase()})):
71(M=n.match(Sa))&&ca(M[1])&&(P=m,N=m.substr(0,m.length-5)+"end",m=m.substr(0,m.length-6));if(y||u)d[n]=t,g[n]=l.name,y?Ea(a,b,n,m):b.push(sd(p,L,c,n,m,!1));else{n=xa(m.toLowerCase());g[n]=m;if(r||!d.hasOwnProperty(n))d[n]=t,nd(a,n)&&(d[n]=!0);Ia(a,b,t,n,r);Y(b,n,"A",e,f,P,N)}}"input"===h&&"hidden"===a.getAttribute("type")&&a.setAttribute("autocomplete","off");if(!Qa)break;g=a.className;D(g)&&(g=g.animVal);if(C(g)&&""!==g)for(;a=k.exec(g);)n=xa(a[2]),Y(b,n,"C",e,f)&&(d[n]=V(a[3])),g=g.substr(a.index+
72a[0].length);break;case Pa:na(b,a.nodeValue);break;case 8:if(!Oa)break;F(a,b,d,e,f)}b.sort(ia);return b}function F(a,b,c,d,e){try{var f=g.exec(a.nodeValue);if(f){var h=xa(f[1]);Y(b,h,"M",d,e)&&(c[h]=V(f[2]))}}catch(k){}}function U(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw $("uterdir",b,c);1===a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return x(d)}function W(a,b,c){return function(d,e,f,g,h){e=U(e[0],
73b,c);return a(d,e,f,g,h)}}function Z(a,b,c,d,e,f){var g;return a?da(b,c,d,e,f):function(){g||(g=da(b,c,d,e,f),b=c=f=null);return g.apply(this,arguments)}}function aa(a,b,d,e,f,g,h,k,l){function m(a,b,c,d){if(a){c&&(a=W(a,c,d));a.require=u.require;a.directiveName=Q;if(s===u||u.$$isolateScope)a=Ba(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=W(b,c,d));b.require=u.require;b.directiveName=Q;if(s===u||u.$$isolateScope)b=Ba(b,{isolateScope:!0});k.push(b)}}function p(a,e,f,g,l){function m(a,b,c,d){var e;bb(a)||
74(d=c,c=b,b=a,a=void 0);N&&(e=P);c||(c=N?Q.parent():Q);if(d){var f=l.$$slots[d];if(f)return f(a,b,e,c,R);if(A(f))throw $("noslot",d,Aa(Q));}else return l(a,b,e,c,R)}var n,u,L,y,G,P,M,Q;b===f?(g=d,Q=d.$$element):(Q=x(f),g=new w(Q,d));G=e;s?y=e.$new(!0):t&&(G=e.$parent);l&&(M=m,M.$$boundTransclude=l,M.isSlotFilled=function(a){return!!l.$$slots[a]});J&&(P=ea(Q,g,M,J,y,e,s));s&&(da.$$addScopeInfo(Q,y,!0,!(v&&(v===s||v===s.$$originalDirective))),da.$$addScopeClass(Q,!0),y.$$isolateBindings=s.$$isolateBindings,
75u=Da(e,g,y,y.$$isolateBindings,s),u.removeWatches&&y.$on("$destroy",u.removeWatches));for(n in P){u=J[n];L=P[n];var Hg=u.$$bindings.bindToController;L.instance=L();Q.data("$"+u.name+"Controller",L.instance);L.bindingInfo=Da(G,g,L.instance,Hg,u)}r(J,function(a,b){var c=a.require;a.bindToController&&!H(c)&&D(c)&&S(P[b].instance,X(b,c,Q,P))});r(P,function(a){var b=a.instance;if(B(b.$onChanges))try{b.$onChanges(a.bindingInfo.initialChanges)}catch(d){c(d)}if(B(b.$onInit))try{b.$onInit()}catch(e){c(e)}B(b.$doCheck)&&
76(G.$watch(function(){b.$doCheck()}),b.$doCheck());B(b.$onDestroy)&&G.$on("$destroy",function(){b.$onDestroy()})});n=0;for(u=h.length;n<u;n++)L=h[n],Ca(L,L.isolateScope?y:e,Q,g,L.require&&X(L.directiveName,L.require,Q,P),M);var R=e;s&&(s.template||null===s.templateUrl)&&(R=y);a&&a(R,f.childNodes,void 0,l);for(n=k.length-1;0<=n;n--)L=k[n],Ca(L,L.isolateScope?y:e,Q,g,L.require&&X(L.directiveName,L.require,Q,P),M);r(P,function(a){a=a.instance;B(a.$postLink)&&a.$postLink()})}l=l||{};for(var n=-Number.MAX_VALUE,
77t=l.newScopeDirective,J=l.controllerDirectives,s=l.newIsolateScopeDirective,v=l.templateDirective,L=l.nonTlbTranscludeDirective,G=!1,P=!1,N=l.hasElementTranscludeDirective,y=d.$$element=x(b),u,Q,M,R=e,q,ma=!1,Jb=!1,O,sa=0,C=a.length;sa<C;sa++){u=a[sa];var E=u.$$start,jb=u.$$end;E&&(y=U(b,E,jb));M=void 0;if(n>u.priority)break;if(O=u.scope)u.templateUrl||(D(O)?(ba("new/isolated scope",s||t,u,y),s=u):ba("new/isolated scope",s,u,y)),t=t||u;Q=u.name;if(!ma&&(u.replace&&(u.templateUrl||u.template)||u.transclude&&
78!u.$$tlb)){for(O=sa+1;ma=a[O++];)if(ma.transclude&&!ma.$$tlb||ma.replace&&(ma.templateUrl||ma.template)){Jb=!0;break}ma=!0}!u.templateUrl&&u.controller&&(J=J||T(),ba("'"+Q+"' controller",J[Q],u,y),J[Q]=u);if(O=u.transclude)if(G=!0,u.$$tlb||(ba("transclusion",L,u,y),L=u),"element"===O)N=!0,n=u.priority,M=y,y=d.$$element=x(da.$$createComment(Q,d[Q])),b=y[0],oa(f,Ha.call(M,0),b),R=Z(Jb,M,e,n,g&&g.name,{nonTlbTranscludeDirective:L});else{var ka=T();if(D(O)){M=z.document.createDocumentFragment();var Xa=
79T(),F=T();r(O,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Xa[a]=b;ka[b]=null;F[b]=c});r(y.contents(),function(a){var b=Xa[xa(ua(a))];b?(F[b]=!0,ka[b]=ka[b]||z.document.createDocumentFragment(),ka[b].appendChild(a)):M.appendChild(a)});r(F,function(a,b){if(!a)throw $("reqslot",b);});for(var K in ka)ka[K]&&(R=x(ka[K].childNodes),ka[K]=Z(Jb,R,e));M=x(M.childNodes)}else M=x(qc(b)).contents();y.empty();R=Z(Jb,M,e,void 0,void 0,{needsNewScope:u.$$isolateScope||u.$$newScope});R.$$slots=ka}if(u.template)if(P=
80!0,ba("template",v,u,y),v=u,O=B(u.template)?u.template(y,d):u.template,O=Na(O),u.replace){g=u;M=nc.test(O)?td(ja(u.templateNamespace,V(O))):[];b=M[0];if(1!==M.length||1!==b.nodeType)throw $("tplrt",Q,"");oa(f,y,b);C={$attr:{}};O=tc(b,[],C);var Ig=a.splice(sa+1,a.length-(sa+1));(s||t)&&fa(O,s,t);a=a.concat(O).concat(Ig);ga(d,C);C=a.length}else y.html(O);if(u.templateUrl)P=!0,ba("template",v,u,y),v=u,u.replace&&(g=u),p=ha(a.splice(sa,a.length-sa),y,d,f,G&&R,h,k,{controllerDirectives:J,newScopeDirective:t!==
81u&&t,newIsolateScopeDirective:s,templateDirective:v,nonTlbTranscludeDirective:L}),C=a.length;else if(u.compile)try{q=u.compile(y,d,R);var Y=u.$$originalDirective||u;B(q)?m(null,Va(Y,q),E,jb):q&&m(Va(Y,q.pre),Va(Y,q.post),E,jb)}catch(ca){c(ca,Aa(y))}u.terminal&&(p.terminal=!0,n=Math.max(n,u.priority))}p.scope=t&&!0===t.scope;p.transcludeOnThisElement=G;p.templateOnThisElement=P;p.transclude=R;l.hasElementTranscludeDirective=N;return p}function X(a,b,c,d){var e;if(C(b)){var f=b.match(l);b=b.substring(f[0].length);
82var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e="^^"===g&&c[0]&&9===c[0].nodeType?null:g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw $("ctreq",b,a);}else if(H(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=X(a,b[g],c,d);else D(b)&&(e={},r(b,function(b,f){e[f]=X(a,b,c,d)}));return e||null}function ea(a,b,c,d,e,f,g){var h=T(),k;for(k in d){var l=d[k],m={$scope:l===g||l.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},p=l.controller;"@"===
83p&&(p=b[l.name]);m=M(p,m,!0,l.controllerAs);h[l.name]=m;a.data("$"+l.name+"Controller",m.instance)}return h}function fa(a,b,c){for(var d=0,e=a.length;d<e;d++)a[d]=bc(a[d],{$$isolateScope:b,$$newScope:c})}function Y(b,c,e,g,h,k,l){if(c===h)return null;var m=null;if(f.hasOwnProperty(c)){h=a.get(c+"Directive");for(var p=0,n=h.length;p<n;p++)if(c=h[p],(A(g)||g>c.priority)&&-1!==c.restrict.indexOf(e)){k&&(c=bc(c,{$$start:k,$$end:l}));if(!c.$$bindings){var I=m=c,t=c.name,u={isolateScope:null,bindToController:null};
84D(I.scope)&&(!0===I.bindToController?(u.bindToController=d(I.scope,t,!0),u.isolateScope={}):u.isolateScope=d(I.scope,t,!1));D(I.bindToController)&&(u.bindToController=d(I.bindToController,t,!0));if(u.bindToController&&!I.controller)throw $("noctrl",t);m=m.$$bindings=u;D(m.isolateScope)&&(c.$$isolateBindings=m.isolateScope)}b.push(c);m=c}}return m}function ca(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d<e;d++)if(b=c[d],b.multiElement)return!0;return!1}function ga(a,b){var c=
85b.$attr,d=a.$attr;r(a,function(d,e){"$"!==e.charAt(0)&&(b[e]&&b[e]!==d&&(d=d.length?d+(("style"===e?";":" ")+b[e]):b[e]),a.$set(e,d,!0,c[e]))});r(b,function(b,e){a.hasOwnProperty(e)||"$"===e.charAt(0)||(a[e]=b,"class"!==e&&"style"!==e&&(d[e]=c[e]))})}function ha(a,b,d,f,g,h,k,l){var m=[],p,n,t=b[0],u=a.shift(),J=bc(u,{templateUrl:null,transclude:null,replace:null,$$originalDirective:u}),s=B(u.templateUrl)?u.templateUrl(b,d):u.templateUrl,L=u.templateNamespace;b.empty();e(s).then(function(c){var e,
86I;c=Na(c);if(u.replace){c=nc.test(c)?td(ja(L,V(c))):[];e=c[0];if(1!==c.length||1!==e.nodeType)throw $("tplrt",u.name,s);c={$attr:{}};oa(f,b,e);var v=tc(e,[],c);D(u.scope)&&fa(v,!0);a=v.concat(a);ga(d,c)}else e=t,b.html(c);a.unshift(J);p=aa(a,e,d,g,b,u,h,k,l);r(f,function(a,c){a===e&&(f[c]=b[0])});for(n=Xa(b[0].childNodes,g);m.length;){c=m.shift();I=m.shift();var y=m.shift(),P=m.shift(),v=b[0];if(!c.$$destroyed){if(I!==t){var G=I.className;l.hasElementTranscludeDirective&&u.replace||(v=qc(e));oa(y,
87x(I),v);sa(x(v),G)}I=p.transcludeOnThisElement?ka(c,p.transclude,P):P;p(n,c,v,f,I)}}m=null}).catch(function(a){dc(a)&&c(a)});return function(a,b,c,d,e){a=e;b.$$destroyed||(m?m.push(b,c,d,a):(p.transcludeOnThisElement&&(a=ka(b,p.transclude,e)),p(n,b,c,d,a)))}}function ia(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function ba(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw $("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),
88a,Aa(d));}function na(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=a.parent();var b=!!a.length;b&&da.$$addBindingClass(a);return function(a,c){var e=c.parent();b||da.$$addBindingClass(e);da.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function ja(a,b){a=K(a||"html");switch(a){case "svg":case "math":var c=z.document.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function qa(a,b){if("srcdoc"===
89b)return u.HTML;if("src"===b||"ngSrc"===b)return-1===["img","video","audio","source","track"].indexOf(a)?u.RESOURCE_URL:u.MEDIA_URL;if("xlinkHref"===b)return"image"===a?u.MEDIA_URL:"a"===a?u.URL:u.RESOURCE_URL;if("form"===a&&"action"===b||"base"===a&&"href"===b||"link"===a&&"href"===b)return u.RESOURCE_URL;if("a"===a&&("href"===b||"ngHref"===b))return u.URL}function ya(a,b){var c=b.toLowerCase();return v[a+"|"+c]||v["*|"+c]}function za(a){return ma(u.valueOf(a),"ng-prop-srcset")}function Ea(a,b,c,
90d){if(m.test(d))throw $("nodomevents");a=ua(a);var e=ya(a,d),f=Ta;"srcset"!==d||"img"!==a&&"source"!==a?e&&(f=u.getTrusted.bind(u,e)):f=za;b.push({priority:100,compile:function(a,b){var e=p(b[c]),g=p(b[c],function(a){return u.valueOf(a)});return{pre:function(a,b){function c(){var g=e(a);b[0][d]=f(g)}c();a.$watch(g,c)}}}})}function Ia(a,c,d,e,f){var g=ua(a),k=qa(g,e),l=h[e]||f,p=b(d,!f,k,l);if(p){if("multiple"===e&&"select"===g)throw $("selmulti",Aa(a));if(m.test(e))throw $("nodomevents");c.push({priority:100,
91compile:function(){return{pre:function(a,c,f){c=f.$$observers||(f.$$observers=T());var g=f[e];g!==d&&(p=g&&b(g,!0,k,l),d=g);p&&(f[e]=p(a),(c[e]||(c[e]=[])).$$inter=!0,(f.$$observers&&f.$$observers[e].$$scope||a).$watch(p,function(a,b){"class"===e&&a!==b?f.$updateClass(a,b):f.$set(e,a)}))}}}})}}function oa(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]===d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&
92(a.context=c);break}f&&f.replaceChild(c,d);a=z.document.createDocumentFragment();for(g=0;g<e;g++)a.appendChild(b[g]);x.hasData(d)&&(x.data(c,x.data(d)),x(d).off("$destroy"));x.cleanData(a.querySelectorAll("*"));for(g=1;g<e;g++)delete b[g];b[0]=c;b.length=1}function Ba(a,b){return S(function(){return a.apply(null,arguments)},a,b)}function Ca(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,Aa(d))}}function ra(a,b){if(s)throw $("missingattr",a,b);}function Da(a,c,d,e,f){function g(b,c,e){B(d.$onChanges)&&
93!ec(c,e)&&(Ua||(a.$$postDigest(q),Ua=[]),m||(m={},Ua.push(h)),m[b]&&(e=m[b].previousValue),m[b]=new Kb(e,c))}function h(){d.$onChanges(m);m=void 0}var k=[],l={},m;r(e,function(e,h){var m=e.attrName,n=e.optional,I,t,u,s;switch(e.mode){case "@":n||ta.call(c,m)||(ra(m,f.name),d[h]=c[m]=void 0);n=c.$observe(m,function(a){if(C(a)||Ga(a))g(h,a,d[h]),d[h]=a});c.$$observers[m].$$scope=a;I=c[m];C(I)?d[h]=b(I)(a):Ga(I)&&(d[h]=I);l[h]=new Kb(uc,d[h]);k.push(n);break;case "=":if(!ta.call(c,m)){if(n)break;ra(m,
94f.name);c[m]=void 0}if(n&&!c[m])break;t=p(c[m]);s=t.literal?va:ec;u=t.assign||function(){I=d[h]=t(a);throw $("nonassign",c[m],m,f.name);};I=d[h]=t(a);n=function(b){s(b,d[h])||(s(b,I)?u(a,b=d[h]):d[h]=b);return I=b};n.$stateful=!0;n=e.collection?a.$watchCollection(c[m],n):a.$watch(p(c[m],n),null,t.literal);k.push(n);break;case "<":if(!ta.call(c,m)){if(n)break;ra(m,f.name);c[m]=void 0}if(n&&!c[m])break;t=p(c[m]);var v=t.literal,L=d[h]=t(a);l[h]=new Kb(uc,d[h]);n=a[e.collection?"$watchCollection":"$watch"](t,
95function(a,b){if(b===a){if(b===L||v&&va(b,L))return;b=L}g(h,a,b);d[h]=a});k.push(n);break;case "&":n||ta.call(c,m)||ra(m,f.name);t=c.hasOwnProperty(m)?p(c[m]):E;if(t===E&&n)break;d[h]=function(b){return t(a,b)}}});return{initialChanges:l,removeWatches:k.length&&function(){for(var a=0,b=k.length;a<b;++a)k[a]()}}}var Ma=/^\w/,Fa=z.document.createElement("div"),Oa=t,Qa=N,Ja=G,Ua;w.prototype={$normalize:xa,$addClass:function(a){a&&0<a.length&&R.addClass(this.$$element,a)},$removeClass:function(a){a&&
960<a.length&&R.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=ud(a,b);c&&c.length&&R.addClass(this.$$element,c);(c=ud(b,a))&&c.length&&R.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=nd(this.$$element[0],a),g=vd[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Xc(a,"-"));"img"===ua(this.$$element)&&"srcset"===a&&(this[a]=b=ma(b,"$set('srcset', value)"));!1!==d&&(null===b||A(b)?this.$$element.removeAttr(e):
97Ma.test(e)?f&&!1===b?this.$$element.removeAttr(e):this.$$element.attr(e,b):O(this.$$element[0],e,b));(a=this.$$observers)&&r(a[h],function(a){try{a(b)}catch(d){c(d)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=T()),e=d[a]||(d[a]=[]);e.push(b);L.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||A(c[a])||b(c[a])});return function(){cb(e,b)}}};var Ka=b.startSymbol(),La=b.endSymbol(),Na="{{"===Ka&&"}}"===La?Ta:function(a){return a.replace(/\{\{/g,Ka).replace(/}}/g,La)},Ra=
98/^ng(Attr|Prop|On)([A-Z].*)$/,Sa=/^(.+)Start$/;da.$$addBindingInfo=n?function(a,b){var c=a.data("$binding")||[];H(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:E;da.$$addBindingClass=n?function(a){sa(a,"ng-binding")}:E;da.$$addScopeInfo=n?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:E;da.$$addScopeClass=n?function(a,b){sa(a,b?"ng-isolate-scope":"ng-scope")}:E;da.$$createComment=function(a,b){var c="";n&&(c=" "+(a||"")+": ",b&&(c+=b+" "));return z.document.createComment(c)};
99return da}]}function Kb(a,b){this.previousValue=a;this.currentValue=b}function xa(a){return a.replace(rd,"").replace(Jg,function(a,d,c){return c?d.toUpperCase():d})}function ud(a,b){var d="",c=a.split(/\s+/),e=b.split(/\s+/),f=0;a:for(;f<c.length;f++){for(var g=c[f],k=0;k<e.length;k++)if(g===e[k])continue a;d+=(0<d.length?" ":"")+g}return d}function td(a){a=x(a);var b=a.length;if(1>=b)return a;for(;b--;){var d=a[b];(8===d.nodeType||d.nodeType===Pa&&""===d.nodeValue.trim())&&Kg.call(a,b,1)}return a}
100function Gg(a,b){if(b&&C(b))return b;if(C(a)){var d=wd.exec(a);if(d)return d[3]}}function Kf(){var a={};this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,d){Ja(b,"controller");D(b)?S(a,b):a[b]=d};this.$get=["$injector",function(b){function d(a,b,d,g){if(!a||!D(a.$scope))throw F("$controller")("noscp",g,b);a.$scope[b]=d}return function(c,e,f,g){var k,h,l;f=!0===f;g&&C(g)&&(l=g);if(C(c)){g=c.match(wd);if(!g)throw xd("ctrlfmt",c);h=g[1];l=l||g[3];c=a.hasOwnProperty(h)?a[h]:Le(e.$scope,
101h,!0);if(!c)throw xd("ctrlreg",h);tb(c,h,!0)}if(f)return f=(H(c)?c[c.length-1]:c).prototype,k=Object.create(f||null),l&&d(e,l,k,h||c.name),S(function(){var a=b.invoke(c,k,e,h);a!==k&&(D(a)||B(a))&&(k=a,l&&d(e,l,k,h||c.name));return k},{instance:k,identifier:l});k=b.instantiate(c,e,h);l&&d(e,l,k,h||c.name);return k}}]}function Lf(){this.$get=["$window",function(a){return x(a.document)}]}function Mf(){this.$get=["$document","$rootScope",function(a,b){function d(){e=c.hidden}var c=a[0],e=c&&c.hidden;
102a.on("visibilitychange",d);b.$on("$destroy",function(){a.off("visibilitychange",d)});return function(){return e}}]}function Nf(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function vc(a){return D(a)?ha(a)?a.toISOString():eb(a):a}function Tf(){this.$get=function(){return function(a){if(!a)return"";var b=[];Qc(a,function(a,c){null===a||A(a)||B(a)||(H(a)?r(a,function(a){b.push(ba(c)+"="+ba(vc(a)))}):b.push(ba(c)+"="+ba(vc(a))))});return b.join("&")}}}function Uf(){this.$get=
103function(){return function(a){function b(a,e,f){H(a)?r(a,function(a,c){b(a,e+"["+(D(a)?c:"")+"]")}):D(a)&&!ha(a)?Qc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):(B(a)&&(a=a()),d.push(ba(e)+"="+(null==a?"":ba(vc(a)))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function wc(a,b){if(C(a)){var d=a.replace(Lg,"").trim();if(d){var c=b("Content-Type"),c=c&&0===c.indexOf(yd),e;(e=c)||(e=(e=d.match(Mg))&&Ng[e[0]].test(d));if(e)try{a=Tc(d)}catch(f){if(!c)return a;throw Lb("baddata",a,f);}}}return a}
104function zd(a){var b=T(),d;C(a)?r(a.split("\n"),function(a){d=a.indexOf(":");var e=K(V(a.substr(0,d)));a=V(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):D(a)&&r(a,function(a,d){var f=K(d),g=V(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function Ad(a){var b;return function(d){b||(b=zd(a));return d?(d=b[K(d)],void 0===d&&(d=null),d):b}}function Bd(a,b,d,c){if(B(c))return c(a,b,d);r(c,function(c){a=c(a,b,d)});return a}function Sf(){var a=this.defaults={transformResponse:[wc],transformRequest:[function(a){return D(a)&&
105"[object File]"!==la.call(a)&&"[object Blob]"!==la.call(a)&&"[object FormData]"!==la.call(a)?eb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ja(xc),put:ja(xc),patch:ja(xc)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer",jsonpCallbackParam:"callback"},b=!1;this.useApplyAsync=function(a){return w(a)?(b=!!a,this):b};var d=this.interceptors=[],c=this.xsrfTrustedOrigins=[];Object.defineProperty(this,"xsrfWhitelistedOrigins",
106{get:function(){return this.xsrfTrustedOrigins},set:function(a){this.xsrfTrustedOrigins=a}});this.$get=["$browser","$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector","$sce",function(e,f,g,k,h,l,m,p){function n(b){function c(a,b){for(var d=0,e=b.length;d<e;){var f=b[d++],g=b[d++];a=a.then(f,g)}b.length=0;return a}function d(a,b){var c,e={};r(a,function(a,d){B(a)?(c=a(b),null!=c&&(e[d]=c)):e[d]=a});return e}function f(a){var b=S({},a);b.data=Bd(a.data,a.headers,a.status,g.transformResponse);
107a=a.status;return 200<=a&&300>a?b:l.reject(b)}if(!D(b))throw F("$http")("badreq",b);if(!C(p.valueOf(b.url)))throw F("$http")("badreq",b.url);var g=S({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer,jsonpCallbackParam:a.jsonpCallbackParam},b);g.headers=function(b){var c=a.headers,e=S({},b.headers),f,g,h,c=S({},c.common,c[K(b.method)]);a:for(f in c){g=K(f);for(h in e)if(K(h)===g)continue a;e[f]=c[f]}return d(e,ja(b))}(b);g.method=
108vb(g.method);g.paramSerializer=C(g.paramSerializer)?m.get(g.paramSerializer):g.paramSerializer;e.$$incOutstandingRequestCount("$http");var h=[],k=[];b=l.resolve(g);r(v,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&k.push(a.response,a.responseError)});b=c(b,h);b=b.then(function(b){var c=b.headers,d=Bd(b.data,Ad(c),void 0,b.transformRequest);A(d)&&r(c,function(a,b){"content-type"===K(b)&&delete c[b]});A(b.withCredentials)&&!A(a.withCredentials)&&
109(b.withCredentials=a.withCredentials);return s(b,d).then(f,f)});b=c(b,k);return b=b.finally(function(){e.$$completeOutstandingRequest(E,"$http")})}function s(c,d){function e(a){if(a){var c={};r(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function k(a,c,d,e,f){function g(){m(c,a,d,e,f)}R&&(200<=a&&300>a?R.put(O,[a,c,zd(d),e,f]):R.remove(O));b?h.$applyAsync(g):(g(),h.$$phase||h.$apply())}function m(a,b,d,e,f){b=-1<=b?b:0;(200<=b&&300>
110b?L.resolve:L.reject)({data:a,status:b,headers:Ad(d),config:c,statusText:e,xhrStatus:f})}function s(a){m(a.data,a.status,ja(a.headers()),a.statusText,a.xhrStatus)}function v(){var a=n.pendingRequests.indexOf(c);-1!==a&&n.pendingRequests.splice(a,1)}var L=l.defer(),u=L.promise,R,q,ma=c.headers,x="jsonp"===K(c.method),O=c.url;x?O=p.getTrustedResourceUrl(O):C(O)||(O=p.valueOf(O));O=G(O,c.paramSerializer(c.params));x&&(O=t(O,c.jsonpCallbackParam));n.pendingRequests.push(c);u.then(v,v);!c.cache&&!a.cache||
111!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(R=D(c.cache)?c.cache:D(a.cache)?a.cache:N);R&&(q=R.get(O),w(q)?q&&B(q.then)?q.then(s,s):H(q)?m(q[1],q[0],ja(q[2]),q[3],q[4]):m(q,200,{},"OK","complete"):R.put(O,u));A(q)&&((q=kc(c.url)?g()[c.xsrfCookieName||a.xsrfCookieName]:void 0)&&(ma[c.xsrfHeaderName||a.xsrfHeaderName]=q),f(c.method,O,d,k,ma,c.timeout,c.withCredentials,c.responseType,e(c.eventHandlers),e(c.uploadEventHandlers)));return u}function G(a,b){0<b.length&&(a+=(-1===a.indexOf("?")?
112"?":"&")+b);return a}function t(a,b){var c=a.split("?");if(2<c.length)throw Lb("badjsonp",a);c=hc(c[1]);r(c,function(c,d){if("JSON_CALLBACK"===c)throw Lb("badjsonp",a);if(d===b)throw Lb("badjsonp",b,a);});return a+=(-1===a.indexOf("?")?"?":"&")+b+"=JSON_CALLBACK"}var N=k("$http");a.paramSerializer=C(a.paramSerializer)?m.get(a.paramSerializer):a.paramSerializer;var v=[];r(d,function(a){v.unshift(C(a)?m.get(a):m.invoke(a))});var kc=Og(c);n.pendingRequests=[];(function(a){r(arguments,function(a){n[a]=
113function(b,c){return n(S({},c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){r(arguments,function(a){n[a]=function(b,c,d){return n(S({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");n.defaults=a;return n}]}function Wf(){this.$get=function(){return function(){return new z.XMLHttpRequest}}}function Vf(){this.$get=["$browser","$jsonpCallbacks","$document","$xhrFactory",function(a,b,d,c){return Pg(a,c,a.defer,b,d[0])}]}function Pg(a,b,d,c,e){function f(a,b,d){a=a.replace("JSON_CALLBACK",
114b);var f=e.createElement("script"),m=null;f.type="text/javascript";f.src=a;f.async=!0;m=function(a){f.removeEventListener("load",m);f.removeEventListener("error",m);e.body.removeChild(f);f=null;var g=-1,s="unknown";a&&("load"!==a.type||c.wasCalled(b)||(a={type:"error"}),s=a.type,g="error"===a.type?404:200);d&&d(g,s)};f.addEventListener("load",m);f.addEventListener("error",m);e.body.appendChild(f);return m}return function(e,k,h,l,m,p,n,s,G,t){function N(a){J="timeout"===a;pa&&pa();y&&y.abort()}function v(a,
115b,c,e,f,g){w(P)&&d.cancel(P);pa=y=null;a(b,c,e,f,g)}k=k||a.url();if("jsonp"===K(e))var q=c.createCallback(k),pa=f(k,q,function(a,b){var d=200===a&&c.getResponse(q);v(l,a,d,"",b,"complete");c.removeCallback(q)});else{var y=b(e,k),J=!1;y.open(e,k,!0);r(m,function(a,b){w(a)&&y.setRequestHeader(b,a)});y.onload=function(){var a=y.statusText||"",b="response"in y?y.response:y.responseText,c=1223===y.status?204:y.status;0===c&&(c=b?200:"file"===ga(k).protocol?404:0);v(l,c,b,y.getAllResponseHeaders(),a,"complete")};
116y.onerror=function(){v(l,-1,null,null,"","error")};y.ontimeout=function(){v(l,-1,null,null,"","timeout")};y.onabort=function(){v(l,-1,null,null,"",J?"timeout":"abort")};r(G,function(a,b){y.addEventListener(b,a)});r(t,function(a,b){y.upload.addEventListener(b,a)});n&&(y.withCredentials=!0);if(s)try{y.responseType=s}catch(I){if("json"!==s)throw I;}y.send(A(h)?null:h)}if(0<p)var P=d(function(){N("timeout")},p);else p&&B(p.then)&&p.then(function(){N(w(p.$$timeoutId)?"timeout":"abort")})}}function Pf(){var a=
117"{{",b="}}";this.startSymbol=function(b){return b?(a=b,this):a};this.endSymbol=function(a){return a?(b=a,this):b};this.$get=["$parse","$exceptionHandler","$sce",function(d,c,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(p,a).replace(n,b)}function k(a,b,c,d){var e=a.$watch(function(a){e();return d(a)},b,c);return e}function h(f,h,n,p){function v(a){try{return a=n&&!r?e.getTrusted(n,a):e.valueOf(a),p&&!w(a)?a:jc(a)}catch(b){c(Ma.interr(f,b))}}var r=n===e.URL||n===e.MEDIA_URL;if(!f.length||
118-1===f.indexOf(a)){if(h)return;h=g(f);r&&(h=e.getTrusted(n,h));h=ia(h);h.exp=f;h.expressions=[];h.$$watchDelegate=k;return h}p=!!p;for(var q,y,J=0,I=[],P,Q=f.length,M=[],L=[],u;J<Q;)if(-1!==(q=f.indexOf(a,J))&&-1!==(y=f.indexOf(b,q+l)))J!==q&&M.push(g(f.substring(J,q))),J=f.substring(q+l,y),I.push(J),J=y+m,L.push(M.length),M.push("");else{J!==Q&&M.push(g(f.substring(J)));break}u=1===M.length&&1===L.length;var R=r&&u?void 0:v;P=I.map(function(a){return d(a,R)});if(!h||I.length){var x=function(a){for(var b=
1190,c=I.length;b<c;b++){if(p&&A(a[b]))return;M[L[b]]=a[b]}if(r)return e.getTrusted(n,u?M[0]:M.join(""));n&&1<M.length&&Ma.throwNoconcat(f);return M.join("")};return S(function(a){var b=0,d=I.length,e=Array(d);try{for(;b<d;b++)e[b]=P[b](a);return x(e)}catch(g){c(Ma.interr(f,g))}},{exp:f,expressions:I,$$watchDelegate:function(a,b){var c;return a.$watchGroup(P,function(d,e){var f=x(d);b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=a.length,m=b.length,p=new RegExp(a.replace(/./g,f),"g"),n=new RegExp(b.replace(/./g,
120f),"g");h.startSymbol=function(){return a};h.endSymbol=function(){return b};return h}]}function Qf(){this.$get=["$$intervalFactory","$window",function(a,b){var d={},c=function(a){b.clearInterval(a);delete d[a]},e=a(function(a,c,e){a=b.setInterval(a,c);d[a]=e;return a},c);e.cancel=function(a){if(!a)return!1;if(!a.hasOwnProperty("$$intervalId"))throw Qg("badprom");if(!d.hasOwnProperty(a.$$intervalId))return!1;a=a.$$intervalId;var b=d[a],e=b.promise;e.$$state&&(e.$$state.pur=!0);b.reject("canceled");
121c(a);return!0};return e}]}function Rf(){this.$get=["$browser","$q","$$q","$rootScope",function(a,b,d,c){return function(e,f){return function(g,k,h,l){function m(){p?g.apply(null,n):g(s)}var p=4<arguments.length,n=p?Ha.call(arguments,4):[],s=0,G=w(l)&&!l,t=(G?d:b).defer(),r=t.promise;h=w(h)?h:0;r.$$intervalId=e(function(){G?a.defer(m):c.$evalAsync(m);t.notify(s++);0<h&&s>=h&&(t.resolve(s),f(r.$$intervalId));G||c.$apply()},k,t,G);return r}}}]}function Cd(a,b){var d=ga(a);b.$$protocol=d.protocol;b.$$host=
122d.hostname;b.$$port=fa(d.port)||Rg[d.protocol]||null}function Dd(a,b,d){if(Sg.test(a))throw kb("badpath",a);var c="/"!==a.charAt(0);c&&(a="/"+a);a=ga(a);for(var c=(c&&"/"===a.pathname.charAt(0)?a.pathname.substring(1):a.pathname).split("/"),e=c.length;e--;)c[e]=decodeURIComponent(c[e]),d&&(c[e]=c[e].replace(/\//g,"%2F"));d=c.join("/");b.$$path=d;b.$$search=hc(a.search);b.$$hash=decodeURIComponent(a.hash);b.$$path&&"/"!==b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function yc(a,b){return a.slice(0,
123b.length)===b}function ya(a,b){if(yc(b,a))return b.substr(a.length)}function Da(a){var b=a.indexOf("#");return-1===b?a:a.substr(0,b)}function zc(a,b,d){this.$$html5=!0;d=d||"";Cd(a,this);this.$$parse=function(a){var d=ya(b,a);if(!C(d))throw kb("ipthprfx",a,b);Dd(d,this,!0);this.$$path||(this.$$path="/");this.$$compose()};this.$$normalizeUrl=function(a){return b+a.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;w(f=ya(a,c))?(g=f,g=d&&w(f=ya(d,f))?
124b+(ya("/",f)||f):a+g):w(f=ya(b,c))?g=b+f:b===c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function Ac(a,b,d){Cd(a,this);this.$$parse=function(c){var e=ya(a,c)||ya(b,c),f;A(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",A(e)&&(a=c,this.replace())):(f=ya(d,e),A(f)&&(f=e));Dd(f,this,!1);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;yc(f,e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$normalizeUrl=function(b){return a+(b?d+b:"")};this.$$parseLinkUrl=function(b,
125d){return Da(a)===Da(b)?(this.$$parse(b),!0):!1}}function Ed(a,b,d){this.$$html5=!0;Ac.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a===Da(c)?f=c:(g=ya(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$normalizeUrl=function(b){return a+d+b}}function Mb(a){return function(){return this[a]}}function Fd(a,b){return function(d){if(A(d))return this[a];this[a]=b(d);this.$$compose();return this}}function Yf(){var a="!",
126b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return w(b)?(a=b,this):a};this.html5Mode=function(a){if(Ga(a))return b.enabled=a,this;if(D(a)){Ga(a.enabled)&&(b.enabled=a.enabled);Ga(a.requireBase)&&(b.requireBase=a.requireBase);if(Ga(a.rewriteLinks)||C(a.rewriteLinks))b.rewriteLinks=a.rewriteLinks;return this}return b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function k(a,b){return a===b||ga(a).href===ga(b).href}function h(a,
127b,d){var e=m.url(),f=m.$$state;try{c.url(a,b,d),m.$$state=c.state()}catch(g){throw m.url(e),m.$$state=f,g;}}function l(a,b){d.$broadcast("$locationChangeSuccess",m.absUrl(),a,m.$$state,b)}var m,p;p=c.baseHref();var n=c.url(),s;if(b.enabled){if(!p&&b.requireBase)throw kb("nobase");s=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(p||"/");p=e.history?zc:Ed}else s=Da(n),p=Ac;var r=s.substr(0,Da(s).lastIndexOf("/")+1);m=new p(s,r,"#"+a);m.$$parseLinkUrl(n,n);m.$$state=c.state();var t=/^\s*(javascript|mailto):/i;
128f.on("click",function(a){var e=b.rewriteLinks;if(e&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!==a.which&&2!==a.button){for(var g=x(a.target);"a"!==ua(g[0]);)if(g[0]===f[0]||!(g=g.parent())[0])return;if(!C(e)||!A(g.attr(e))){var e=g.prop("href"),h=g.attr("href")||g.attr("xlink:href");D(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=ga(e.animVal).href);t.test(e)||!e||g.attr("target")||a.isDefaultPrevented()||!m.$$parseLinkUrl(e,h)||(a.preventDefault(),m.absUrl()!==c.url()&&d.$apply())}}});m.absUrl()!==
129n&&c.url(m.absUrl(),!0);var N=!0;c.onUrlChange(function(a,b){yc(a,r)?(d.$evalAsync(function(){var c=m.absUrl(),e=m.$$state,f;m.$$parse(a);m.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;m.absUrl()===a&&(f?(m.$$parse(c),m.$$state=e,h(c,!1,e)):(N=!1,l(c,e)))}),d.$$phase||d.$digest()):g.location.href=a});d.$watch(function(){if(N||m.$$urlUpdatedByLocation){m.$$urlUpdatedByLocation=!1;var a=c.url(),b=m.absUrl(),f=c.state(),g=m.$$replace,n=!k(a,b)||m.$$html5&&e.history&&f!==
130m.$$state;if(N||n)N=!1,d.$evalAsync(function(){var b=m.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,m.$$state,f).defaultPrevented;m.absUrl()===b&&(c?(m.$$parse(a),m.$$state=f):(n&&h(b,g,f===m.$$state?null:m.$$state),l(a,f)))})}m.$$replace=!1});return m}]}function Zf(){var a=!0,b=this;this.debugEnabled=function(b){return w(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){dc(a)&&(a.stack&&f?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&
131(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||E;return function(){var a=[];r(arguments,function(b){a.push(c(b))});return Function.prototype.apply.call(e,b,a)}}var f=wa||/\bEdge\//.test(d.navigator&&d.navigator.userAgent);return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Tg(a){return a+""}function Ug(a,b){return"undefined"!==typeof a?a:
132b}function Gd(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function Vg(a,b){switch(a.type){case q.MemberExpression:if(a.computed)return!1;break;case q.UnaryExpression:return 1;case q.BinaryExpression:return"+"!==a.operator?1:!1;case q.CallExpression:return!1}return void 0===b?Hd:b}function Z(a,b,d){var c,e,f=a.isPure=Vg(a,d);switch(a.type){case q.Program:c=!0;r(a.body,function(a){Z(a.expression,b,f);c=c&&a.expression.constant});a.constant=c;break;case q.Literal:a.constant=!0;a.toWatch=
133[];break;case q.UnaryExpression:Z(a.argument,b,f);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case q.BinaryExpression:Z(a.left,b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case q.LogicalExpression:Z(a.left,b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case q.ConditionalExpression:Z(a.test,b,f);Z(a.alternate,b,f);Z(a.consequent,b,f);a.constant=a.test.constant&&
134a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case q.Identifier:a.constant=!1;a.toWatch=[a];break;case q.MemberExpression:Z(a.object,b,f);a.computed&&Z(a.property,b,f);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=a.constant?[]:[a];break;case q.CallExpression:c=d=a.filter?!b(a.callee.name).$stateful:!1;e=[];r(a.arguments,function(a){Z(a,b,f);c=c&&a.constant;e.push.apply(e,a.toWatch)});a.constant=c;a.toWatch=d?e:[a];break;case q.AssignmentExpression:Z(a.left,
135b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case q.ArrayExpression:c=!0;e=[];r(a.elements,function(a){Z(a,b,f);c=c&&a.constant;e.push.apply(e,a.toWatch)});a.constant=c;a.toWatch=e;break;case q.ObjectExpression:c=!0;e=[];r(a.properties,function(a){Z(a.value,b,f);c=c&&a.value.constant;e.push.apply(e,a.value.toWatch);a.computed&&(Z(a.key,b,!1),c=c&&a.key.constant,e.push.apply(e,a.key.toWatch))});a.constant=c;a.toWatch=e;break;case q.ThisExpression:a.constant=
136!1;a.toWatch=[];break;case q.LocalsExpression:a.constant=!1,a.toWatch=[]}}function Id(a){if(1===a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function Jd(a){return a.type===q.Identifier||a.type===q.MemberExpression}function Kd(a){if(1===a.body.length&&Jd(a.body[0].expression))return{type:q.AssignmentExpression,left:a.body[0].expression,right:{type:q.NGValueParameter},operator:"="}}function Ld(a){this.$filter=a}function Md(a){this.$filter=a}function Nb(a,b,d){this.ast=
137new q(a,d);this.astCompiler=d.csp?new Md(b):new Ld(b)}function Bc(a){return B(a.valueOf)?a.valueOf():Wg.call(a)}function $f(){var a=T(),b={"true":!0,"false":!1,"null":null,undefined:void 0},d,c;this.addLiteral=function(a,c){b[a]=c};this.setIdentifierFns=function(a,b){d=a;c=b;return this};this.$get=["$filter",function(e){function f(b,c){var d,f;switch(typeof b){case "string":return f=b=b.trim(),d=a[f],d||(d=new Ob(G),d=(new Nb(d,e,G)).parse(b),a[f]=p(d)),s(d,c);case "function":return s(b,c);default:return s(E,
138c)}}function g(a,b,c){return null==a||null==b?a===b:"object"!==typeof a||(a=Bc(a),"object"!==typeof a||c)?a===b||a!==a&&b!==b:!1}function k(a,b,c,d,e){var f=d.inputs,h;if(1===f.length){var k=g,f=f[0];return a.$watch(function(a){var b=f(a);g(b,k,f.isPure)||(h=d(a,void 0,void 0,[b]),k=b&&Bc(b));return h},b,c,e)}for(var l=[],m=[],n=0,p=f.length;n<p;n++)l[n]=g,m[n]=null;return a.$watch(function(a){for(var b=!1,c=0,e=f.length;c<e;c++){var k=f[c](a);if(b||(b=!g(k,l[c],f[c].isPure)))m[c]=k,l[c]=k&&Bc(k)}b&&
139(h=d(a,void 0,void 0,m));return h},b,c,e)}function h(a,b,c,d,e){function f(){h(m)&&k()}function g(a,b,c,d){m=u&&d?d[0]:n(a,b,c,d);h(m)&&a.$$postDigest(f);return s(m)}var h=d.literal?l:w,k,m,n=d.$$intercepted||d,s=d.$$interceptor||Ta,u=d.inputs&&!n.inputs;g.literal=d.literal;g.constant=d.constant;g.inputs=d.inputs;p(g);return k=a.$watch(g,b,c,e)}function l(a){var b=!0;r(a,function(a){w(a)||(b=!1)});return b}function m(a,b,c,d){var e=a.$watch(function(a){e();return d(a)},b,c);return e}function p(a){a.constant?
140a.$$watchDelegate=m:a.oneTime?a.$$watchDelegate=h:a.inputs&&(a.$$watchDelegate=k);return a}function n(a,b){function c(d){return b(a(d))}c.$stateful=a.$stateful||b.$stateful;c.$$pure=a.$$pure&&b.$$pure;return c}function s(a,b){if(!b)return a;a.$$interceptor&&(b=n(a.$$interceptor,b),a=a.$$intercepted);var c=!1,d=function(d,e,f,g){d=c&&g?g[0]:a(d,e,f,g);return b(d)};d.$$intercepted=a;d.$$interceptor=b;d.literal=a.literal;d.oneTime=a.oneTime;d.constant=a.constant;b.$stateful||(c=!a.inputs,d.inputs=a.inputs?
141a.inputs:[a],b.$$pure||(d.inputs=d.inputs.map(function(a){return a.isPure===Hd?function(b){return a(b)}:a})));return p(d)}var G={csp:Ba().noUnsafeEval,literals:Ia(b),isIdentifierStart:B(d)&&d,isIdentifierContinue:B(c)&&c};f.$$getAst=function(a){var b=new Ob(G);return(new Nb(b,e,G)).getAst(a).ast};return f}]}function bg(){var a=!0;this.$get=["$rootScope","$exceptionHandler",function(b,d){return Nd(function(a){b.$evalAsync(a)},d,a)}];this.errorOnUnhandledRejections=function(b){return w(b)?(a=b,this):
142a}}function cg(){var a=!0;this.$get=["$browser","$exceptionHandler",function(b,d){return Nd(function(a){b.defer(a)},d,a)}];this.errorOnUnhandledRejections=function(b){return w(b)?(a=b,this):a}}function Nd(a,b,d){function c(){return new e}function e(){var a=this.promise=new f;this.resolve=function(b){h(a,b)};this.reject=function(b){m(a,b)};this.notify=function(b){n(a,b)}}function f(){this.$$state={status:0}}function g(){for(;!w&&x.length;){var a=x.shift();if(!a.pur){a.pur=!0;var c=a.value,c="Possibly unhandled rejection: "+
143("function"===typeof c?c.toString().replace(/ \{[\s\S]*$/,""):A(c)?"undefined":"string"!==typeof c?Ne(c,void 0):c);dc(a.value)?b(a.value,c):b(c)}}}function k(c){!d||c.pending||2!==c.status||c.pur||(0===w&&0===x.length&&a(g),x.push(c));!c.processScheduled&&c.pending&&(c.processScheduled=!0,++w,a(function(){var e,f,k;k=c.pending;c.processScheduled=!1;c.pending=void 0;try{for(var l=0,n=k.length;l<n;++l){c.pur=!0;f=k[l][0];e=k[l][c.status];try{B(e)?h(f,e(c.value)):1===c.status?h(f,c.value):m(f,c.value)}catch(p){m(f,
144p),p&&!0===p.$$passToExceptionHandler&&b(p)}}}finally{--w,d&&0===w&&a(g)}}))}function h(a,b){a.$$state.status||(b===a?p(a,v("qcycle",b)):l(a,b))}function l(a,b){function c(b){g||(g=!0,l(a,b))}function d(b){g||(g=!0,p(a,b))}function e(b){n(a,b)}var f,g=!1;try{if(D(b)||B(b))f=b.then;B(f)?(a.$$state.status=-1,f.call(b,c,d,e)):(a.$$state.value=b,a.$$state.status=1,k(a.$$state))}catch(h){d(h)}}function m(a,b){a.$$state.status||p(a,b)}function p(a,b){a.$$state.value=b;a.$$state.status=2;k(a.$$state)}function n(c,
145d){var e=c.$$state.pending;0>=c.$$state.status&&e&&e.length&&a(function(){for(var a,c,f=0,g=e.length;f<g;f++){c=e[f][0];a=e[f][3];try{n(c,B(a)?a(d):d)}catch(h){b(h)}}})}function s(a){var b=new f;m(b,a);return b}function G(a,b,c){var d=null;try{B(c)&&(d=c())}catch(e){return s(e)}return d&&B(d.then)?d.then(function(){return b(a)},s):b(a)}function t(a,b,c,d){var e=new f;h(e,a);return e.then(b,c,d)}function q(a){if(!B(a))throw v("norslvr",a);var b=new f;a(function(a){h(b,a)},function(a){m(b,a)});return b}
146var v=F("$q",TypeError),w=0,x=[];S(f.prototype,{then:function(a,b,c){if(A(a)&&A(b)&&A(c))return this;var d=new f;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&k(this.$$state);return d},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return G(b,y,a)},function(b){return G(b,s,a)},b)}});var y=t;q.prototype=f.prototype;q.defer=c;q.reject=s;q.when=t;q.resolve=y;q.all=function(a){var b=new f,c=
1470,d=H(a)?[]:{};r(a,function(a,e){c++;t(a).then(function(a){d[e]=a;--c||h(b,d)},function(a){m(b,a)})});0===c&&h(b,d);return b};q.race=function(a){var b=c();r(a,function(a){t(a).then(b.resolve,b.reject)});return b.promise};return q}function mg(){this.$get=["$window","$timeout",function(a,b){var d=a.requestAnimationFrame||a.webkitRequestAnimationFrame,c=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||a.webkitCancelRequestAnimationFrame,e=!!d,f=e?function(a){var b=d(a);return function(){c(b)}}:
148function(a){var c=b(a,16.66,!1);return function(){b.cancel(c)}};f.supported=e;return f}]}function ag(){function a(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++qb;this.$$ChildScope=null;this.$$suspended=!1}b.prototype=a;return b}var b=10,d=F("$rootScope"),c=null,e=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$exceptionHandler","$parse","$browser",
149function(f,g,k){function h(a){a.currentScope.$$destroyed=!0}function l(a){9===wa&&(a.$$childHead&&l(a.$$childHead),a.$$nextSibling&&l(a.$$nextSibling));a.$parent=a.$$nextSibling=a.$$prevSibling=a.$$childHead=a.$$childTail=a.$root=a.$$watchers=null}function m(){this.$id=++qb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$suspended=this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=
1500;this.$$isolateBindings=null}function p(a){if(v.$$phase)throw d("inprog",v.$$phase);v.$$phase=a}function n(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function s(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function G(){}function t(){for(;y.length;)try{y.shift()()}catch(a){f(a)}e=null}function q(){null===e&&(e=k.defer(function(){v.$apply(t)},null,"$applyAsync"))}m.prototype={constructor:m,$new:function(b,c){var d;c=c||this;b?(d=new m,
151d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=a(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(b||c!==this)&&d.$on("$destroy",h);return d},$watch:function(a,b,d,e){var f=g(a);b=B(b)?b:E;if(f.$$watchDelegate)return f.$$watchDelegate(this,b,d,f,a);var h=this,k=h.$$watchers,l={fn:b,last:G,get:f,exp:e||a,eq:!!d};c=null;k||(k=h.$$watchers=[],k.$$digestWatchIndex=-1);k.unshift(l);
152k.$$digestWatchIndex++;n(this,1);return function(){var a=cb(k,l);0<=a&&(n(h,-1),a<k.$$digestWatchIndex&&k.$$digestWatchIndex--);c=null}},$watchGroup:function(a,b){function c(){h=!1;try{k?(k=!1,b(e,e,g)):b(e,d,g)}finally{for(var f=0;f<a.length;f++)d[f]=e[f]}}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});r(a,function(a,
153b){var d=g.$watch(a,function(a){e[b]=a;h||(h=!0,g.$evalAsync(c))});f.push(d)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!A(e)){if(D(e))if(za(e))for(f!==n&&(f=n,t=f.length=0,l++),a=e.length,t!==a&&(l++,f.length=t=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==p&&(f=p={},t=0,l++);a=0;for(b in e)ta.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(t++,f[b]=g,l++));if(t>a)for(b in l++,
154f)ta.call(e,b)||(t--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$$pure=g(a).literal;c.$stateful=!c.$$pure;var d=this,e,f,h,k=1<b.length,l=0,m=g(a,c),n=[],p={},s=!0,t=0;return this.$watch(m,function(){s?(s=!1,b(e,e,d)):b(e,h,d);if(k)if(D(e))if(za(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h={},e)ta.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var a,g,h,l,m,n,s,r=b,q,y=w.length?v:this,N=[],A,z;p("$digest");k.$$checkUrlChange();this===v&&null!==e&&(k.defer.cancel(e),
155t());c=null;do{s=!1;q=y;for(n=0;n<w.length;n++){try{z=w[n],l=z.fn,l(z.scope,z.locals)}catch(C){f(C)}c=null}w.length=0;a:do{if(n=!q.$$suspended&&q.$$watchers)for(n.$$digestWatchIndex=n.length;n.$$digestWatchIndex--;)try{if(a=n[n.$$digestWatchIndex])if(m=a.get,(g=m(q))!==(h=a.last)&&!(a.eq?va(g,h):Y(g)&&Y(h)))s=!0,c=a,a.last=a.eq?Ia(g,null):g,l=a.fn,l(g,h===G?g:h,q),5>r&&(A=4-r,N[A]||(N[A]=[]),N[A].push({msg:B(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:h}));else if(a===c){s=
156!1;break a}}catch(E){f(E)}if(!(n=!q.$$suspended&&q.$$watchersCount&&q.$$childHead||q!==y&&q.$$nextSibling))for(;q!==y&&!(n=q.$$nextSibling);)q=q.$parent}while(q=n);if((s||w.length)&&!r--)throw v.$$phase=null,d("infdig",b,N);}while(s||w.length);for(v.$$phase=null;J<x.length;)try{x[J++]()}catch(D){f(D)}x.length=J=0;k.$$checkUrlChange()},$suspend:function(){this.$$suspended=!0},$isSuspended:function(){return this.$$suspended},$resume:function(){this.$$suspended=!1},$destroy:function(){if(!this.$$destroyed){var a=
157this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===v&&k.$$applicationDestroyed();n(this,-this.$$watchersCount);for(var b in this.$$listenerCount)s(this,this.$$listenerCount[b],b);a&&a.$$childHead===this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail===this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=
158this.$evalAsync=this.$applyAsync=E;this.$on=this.$watch=this.$watchGroup=function(){return E};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){v.$$phase||w.length||k.defer(function(){w.length&&v.$digest()},null,"$evalAsync");w.push({scope:this,fn:g(a),locals:b})},$$postDigest:function(a){x.push(a)},$apply:function(a){try{p("$apply");try{return this.$eval(a)}finally{v.$$phase=null}}catch(b){f(b)}finally{try{v.$digest()}catch(c){throw f(c),
159c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&y.push(b);a=g(a);q()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(delete c[d],s(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=
160!0},defaultPrevented:!1},k=db([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(n){f(n)}else d.splice(l,1),l--,m--;if(g)break;e=e.$parent}while(e);h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var g=db([e],arguments,1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||
161[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){f(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var v=new m,w=v.$$asyncQueue=[],x=v.$$postDigestQueue=[],y=v.$$applyAsyncQueue=[],J=0;return v}]}function Qe(){var a=/^\s*(https?|s?ftp|mailto|tel|file):/,b=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationTrustedUrlList=function(b){return w(b)?
162(a=b,this):a};this.imgSrcSanitizationTrustedUrlList=function(a){return w(a)?(b=a,this):b};this.$get=function(){return function(d,c){var e=c?b:a,f=ga(d&&d.trim()).href;return""===f||f.match(e)?d:"unsafe:"+f}}}function Xg(a){if("self"===a)return a;if(C(a)){if(-1<a.indexOf("***"))throw Ea("iwcard",a);a=Od(a).replace(/\\\*\\\*/g,".*").replace(/\\\*/g,"[^:/.?&;]*");return new RegExp("^"+a+"$")}if(ab(a))return new RegExp("^"+a.source+"$");throw Ea("imatcher");}function Pd(a){var b=[];w(a)&&r(a,function(a){b.push(Xg(a))});
163return b}function eg(){this.SCE_CONTEXTS=W;var a=["self"],b=[];this.trustedResourceUrlList=function(b){arguments.length&&(a=Pd(b));return a};Object.defineProperty(this,"resourceUrlWhitelist",{get:function(){return this.trustedResourceUrlList},set:function(a){this.trustedResourceUrlList=a}});this.bannedResourceUrlList=function(a){arguments.length&&(b=Pd(a));return b};Object.defineProperty(this,"resourceUrlBlacklist",{get:function(){return this.bannedResourceUrlList},set:function(a){this.bannedResourceUrlList=
164a}});this.$get=["$injector","$$sanitizeUri",function(d,c){function e(a,b){var c;"self"===a?(c=Cc(b,Qd))||(z.document.baseURI?c=z.document.baseURI:(Na||(Na=z.document.createElement("a"),Na.href=".",Na=Na.cloneNode(!1)),c=Na.href),c=Cc(b,c)):c=!!a.exec(b.href);return c}function f(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};
165return b}var g=function(a){throw Ea("unsafe");};d.has("$sanitize")&&(g=d.get("$sanitize"));var k=f(),h={};h[W.HTML]=f(k);h[W.CSS]=f(k);h[W.MEDIA_URL]=f(k);h[W.URL]=f(h[W.MEDIA_URL]);h[W.JS]=f(k);h[W.RESOURCE_URL]=f(h[W.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw Ea("icontext",a,b);if(null===b||A(b)||""===b)return b;if("string"!==typeof b)throw Ea("itype",a);return new c(b)},getTrusted:function(d,f){if(null===f||A(f)||""===f)return f;var k=h.hasOwnProperty(d)?
166h[d]:null;if(k&&f instanceof k)return f.$$unwrapTrustedValue();B(f.$$unwrapTrustedValue)&&(f=f.$$unwrapTrustedValue());if(d===W.MEDIA_URL||d===W.URL)return c(f.toString(),d===W.MEDIA_URL);if(d===W.RESOURCE_URL){var k=ga(f.toString()),n,s,r=!1;n=0;for(s=a.length;n<s;n++)if(e(a[n],k)){r=!0;break}if(r)for(n=0,s=b.length;n<s;n++)if(e(b[n],k)){r=!1;break}if(r)return f;throw Ea("insecurl",f.toString());}if(d===W.HTML)return g(f);throw Ea("unsafe");},valueOf:function(a){return a instanceof k?a.$$unwrapTrustedValue():
167a}}}]}function dg(){var a=!0;this.enabled=function(b){arguments.length&&(a=!!b);return a};this.$get=["$parse","$sceDelegate",function(b,d){if(a&&8>wa)throw Ea("iequirks");var c=ja(W);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Ta);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,f=c.getTrusted,g=c.trustAs;r(W,
168function(a,b){var d=K(b);c[("parse_as_"+d).replace(Dc,xb)]=function(b){return e(a,b)};c[("get_trusted_"+d).replace(Dc,xb)]=function(b){return f(a,b)};c[("trust_as_"+d).replace(Dc,xb)]=function(b){return g(a,b)}});return c}]}function fg(){this.$get=["$window","$document",function(a,b){var d={},c=!((!a.nw||!a.nw.process)&&a.chrome&&(a.chrome.app&&a.chrome.app.runtime||!a.chrome.app&&a.chrome.runtime&&a.chrome.runtime.id))&&a.history&&a.history.pushState,e=fa((/android (\d+)/.exec(K((a.navigator||{}).userAgent))||
169[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},k=g.body&&g.body.style,h=!1,l=!1;k&&(h=!!("transition"in k||"webkitTransition"in k),l=!!("animation"in k||"webkitAnimation"in k));return{history:!(!c||4>e||f),hasEvent:function(a){if("input"===a&&wa)return!1;if(A(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ba(),transitions:h,animations:l,android:e}}]}function gg(){this.$get=ia(function(a){return new Yg(a)})}function Yg(a){function b(){var a=e.pop();return a&&
170a.cb}function d(a){for(var b=e.length-1;0<=b;--b){var c=e[b];if(c.type===a)return e.splice(b,1),c.cb}}var c={},e=[],f=this.ALL_TASKS_TYPE="$$all$$",g=this.DEFAULT_TASK_TYPE="$$default$$";this.completeTask=function(e,h){h=h||g;try{e()}finally{var l;l=h||g;c[l]&&(c[l]--,c[f]--);l=c[h];var m=c[f];if(!m||!l)for(l=m?d:b;m=l(h);)try{m()}catch(p){a.error(p)}}};this.incTaskCount=function(a){a=a||g;c[a]=(c[a]||0)+1;c[f]=(c[f]||0)+1};this.notifyWhenNoPendingTasks=function(a,b){b=b||f;c[b]?e.push({type:b,cb:a}):
171a()}}function ig(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$exceptionHandler","$templateCache","$http","$q","$sce",function(b,d,c,e,f){function g(k,h){g.totalPendingRequests++;if(!C(k)||A(d.get(k)))k=f.getTrustedResourceUrl(k);var l=c.defaults&&c.defaults.transformResponse;H(l)?l=l.filter(function(a){return a!==wc}):l===wc&&(l=null);return c.get(k,S({cache:d,transformResponse:l},a)).finally(function(){g.totalPendingRequests--}).then(function(a){return d.put(k,a.data)},
172function(a){h||(a=Zg("tpload",k,a.status,a.statusText),b(a));return e.reject(a)})}g.totalPendingRequests=0;return g}]}function jg(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var c=ca.element(a).data("$binding");c&&r(c,function(c){d?(new RegExp("(^|\\s)"+Od(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!==c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-",
173"data-ng-","ng\\:"],k=0;k<g.length;++k){var h=a.querySelectorAll("["+g[k]+"model"+(d?"=":"*=")+'"'+b+'"]');if(h.length)return h}},getLocation:function(){return d.url()},setLocation:function(b){b!==d.url()&&(d.url(b),a.$digest())},whenStable:function(a){b.notifyWhenNoOutstandingRequests(a)}}}]}function kg(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(a,b,d,c,e){function f(f,h,l){B(f)||(l=h,h=f,f=E);var m=Ha.call(arguments,3),p=w(l)&&!l,n=(p?c:d).defer(),s=n.promise,r;
174r=b.defer(function(){try{n.resolve(f.apply(null,m))}catch(b){n.reject(b),e(b)}finally{delete g[s.$$timeoutId]}p||a.$apply()},h,"$timeout");s.$$timeoutId=r;g[r]=n;return s}var g={};f.cancel=function(a){if(!a)return!1;if(!a.hasOwnProperty("$$timeoutId"))throw $g("badprom");if(!g.hasOwnProperty(a.$$timeoutId))return!1;a=a.$$timeoutId;var c=g[a],d=c.promise;d.$$state&&(d.$$state.pur=!0);c.reject("canceled");delete g[a];return b.defer.cancel(a)};return f}]}function ga(a){if(!C(a))return a;wa&&(aa.setAttribute("href",
175a),a=aa.href);aa.setAttribute("href",a);a=aa.hostname;!ah&&-1<a.indexOf(":")&&(a="["+a+"]");return{href:aa.href,protocol:aa.protocol?aa.protocol.replace(/:$/,""):"",host:aa.host,search:aa.search?aa.search.replace(/^\?/,""):"",hash:aa.hash?aa.hash.replace(/^#/,""):"",hostname:a,port:aa.port,pathname:"/"===aa.pathname.charAt(0)?aa.pathname:"/"+aa.pathname}}function Og(a){var b=[Qd].concat(a.map(ga));return function(a){a=ga(a);return b.some(Cc.bind(null,a))}}function Cc(a,b){a=ga(a);b=ga(b);return a.protocol===
176b.protocol&&a.host===b.host}function lg(){this.$get=ia(z)}function Rd(a){function b(a){try{return decodeURIComponent(a)}catch(b){return a}}var d=a[0]||{},c={},e="";return function(){var a,g,k,h,l;try{a=d.cookie||""}catch(m){a=""}if(a!==e)for(e=a,a=e.split("; "),c={},k=0;k<a.length;k++)g=a[k],h=g.indexOf("="),0<h&&(l=b(g.substring(0,h)),A(c[l])&&(c[l]=b(g.substring(h+1))));return c}}function pg(){this.$get=Rd}function fd(a){function b(d,c){if(D(d)){var e={};r(d,function(a,c){e[c]=b(c,a)});return e}return a.factory(d+
177"Filter",c)}this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];b("currency",Sd);b("date",Td);b("filter",bh);b("json",ch);b("limitTo",dh);b("lowercase",eh);b("number",Ud);b("orderBy",Vd);b("uppercase",fh)}function bh(){return function(a,b,d,c){if(!za(a)){if(null==a)return a;throw F("filter")("notarray",a);}c=c||"$";var e;switch(Ec(b)){case "function":break;case "boolean":case "null":case "number":case "string":e=!0;case "object":b=gh(b,d,c,e);break;default:return a}return Array.prototype.filter.call(a,
178b)}}function gh(a,b,d,c){var e=D(a)&&d in a;!0===b?b=va:B(b)||(b=function(a,b){if(A(a))return!1;if(null===a||null===b)return a===b;if(D(b)||D(a)&&!cc(a))return!1;a=K(""+a);b=K(""+b);return-1!==a.indexOf(b)});return function(f){return e&&!D(f)?Fa(f,a[d],b,d,!1):Fa(f,a,b,d,c)}}function Fa(a,b,d,c,e,f){var g=Ec(a),k=Ec(b);if("string"===k&&"!"===b.charAt(0))return!Fa(a,b.substring(1),d,c,e);if(H(a))return a.some(function(a){return Fa(a,b,d,c,e)});switch(g){case "object":var h;if(e){for(h in a)if(h.charAt&&
179"$"!==h.charAt(0)&&Fa(a[h],b,d,c,!0))return!0;return f?!1:Fa(a,b,d,c,!1)}if("object"===k){for(h in b)if(f=b[h],!B(f)&&!A(f)&&(g=h===c,!Fa(g?a:a[h],f,d,c,g,g)))return!1;return!0}return d(a,b);case "function":return!1;default:return d(a,b)}}function Ec(a){return null===a?"null":typeof a}function Sd(a){var b=a.NUMBER_FORMATS;return function(a,c,e){A(c)&&(c=b.CURRENCY_SYM);A(e)&&(e=b.PATTERNS[1].maxFrac);var f=c?/\u00A4/g:/\s*\u00A4\s*/g;return null==a?a:Wd(a,b.PATTERNS[1],b.GROUP_SEP,b.DECIMAL_SEP,e).replace(f,
180c)}}function Ud(a){var b=a.NUMBER_FORMATS;return function(a,c){return null==a?a:Wd(a,b.PATTERNS[0],b.GROUP_SEP,b.DECIMAL_SEP,c)}}function hh(a){var b=0,d,c,e,f,g;-1<(c=a.indexOf(Xd))&&(a=a.replace(Xd,""));0<(e=a.search(/e/i))?(0>c&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)===Fc;e++);if(e===(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)===Fc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Yd&&(d=d.splice(0,Yd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function ih(a,
181b,d,c){var e=a.d,f=e.length-a.i;b=A(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0<d){e.splice(Math.max(a.i,d));for(var g=d;g<e.length;g++)e[g]=0}else for(f=Math.max(0,f),a.i=1,e.length=Math.max(1,d=b+1),e[0]=0,g=1;g<d;g++)e[g]=0;if(5<=c)if(0>d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;f<Math.max(0,b);f++)e.push(0);if(b=e.reduceRight(function(a,b,c,d){b+=a;d[c]=b%10;return Math.floor(b/10)},0))e.unshift(b),a.i++}function Wd(a,b,d,c,e){if(!C(a)&&!X(a)||isNaN(a))return"";
182var f=!isFinite(a),g=!1,k=Math.abs(a)+"",h="";if(f)h="\u221e";else{g=hh(k);ih(g,e,b.minFrac,b.maxFrac);h=g.d;k=g.i;e=g.e;f=[];for(g=h.reduce(function(a,b){return a&&!b},!0);0>k;)h.unshift(0),k++;0<k?f=h.splice(k,h.length):(f=h,h=[0]);k=[];for(h.length>=b.lgSize&&k.unshift(h.splice(-b.lgSize,h.length).join(""));h.length>b.gSize;)k.unshift(h.splice(-b.gSize,h.length).join(""));h.length&&k.unshift(h.join(""));h=k.join(d);f.length&&(h+=c+f.join(""));e&&(h+="e+"+e)}return 0>a&&!g?b.negPre+h+b.negSuf:b.posPre+
183h+b.posSuf}function Pb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length<b;)a=Fc+a;d&&(a=a.substr(a.length-b));return e+a}function ea(a,b,d,c,e){d=d||0;return function(f){f=f["get"+a]();if(0<d||f>-d)f+=d;0===f&&-12===d&&(f=12);return Pb(f,b,c,e)}}function lb(a,b,d){return function(c,e){var f=c["get"+a](),g=vb((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Zd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function $d(a){return function(b){var d=
184Zd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Pb(b,a)}}function Gc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Td(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,k=b[8]?a.setUTCFullYear:a.setFullYear,h=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=fa(b[9]+b[10]),g=fa(b[9]+b[11]));k.call(a,fa(b[1]),fa(b[2])-1,fa(b[3]));f=fa(b[4]||0)-f;g=fa(b[5]||0)-g;k=fa(b[6]||0);b=Math.round(1E3*parseFloat("0."+
185(b[7]||0)));h.call(a,f,g,k,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",k=[],h,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;C(c)&&(c=jh.test(c)?fa(c):b(c));X(c)&&(c=new Date(c));if(!ha(c)||!isFinite(c.getTime()))return c;for(;d;)(l=kh.exec(d))?(k=db(k,l,1),d=k.pop()):(k.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=fc(f,m),c=gc(c,f,!0));r(k,function(b){h=lh[b];g+=h?h(c,a.DATETIME_FORMATS,
186m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function ch(){return function(a,b){A(b)&&(b=2);return eb(a,b)}}function dh(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):fa(b);if(Y(b))return a;X(a)&&(a=a.toString());if(!za(a))return a;d=!d||isNaN(d)?0:fa(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?Hc(a,d,d+b):0===d?Hc(a,b,a.length):Hc(a,Math.max(0,d+b),d)}}function Hc(a,b,d){return C(a)?a.slice(b,d):Ha.call(a,b,d)}function Vd(a){function b(b){return b.map(function(b){var c=
1871,d=Ta;if(B(b))d=b;else if(C(b)){if("+"===b.charAt(0)||"-"===b.charAt(0))c="-"===b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e=d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}function c(a,b){var c=0,d=a.type,h=b.type;if(d===h){var h=a.value,l=b.value;"string"===d?(h=h.toLowerCase(),l=l.toLowerCase()):"object"===d&&(D(h)&&(h=a.index),D(l)&&(l=b.index));h!==l&&(c=
188h<l?-1:1)}else c="undefined"===d?1:"undefined"===h?-1:"null"===d?1:"null"===h?-1:d<h?-1:1;return c}return function(a,f,g,k){if(null==a)return a;if(!za(a))throw F("orderBy")("notarray",a);H(f)||(f=[f]);0===f.length&&(f=["+"]);var h=b(f),l=g?-1:1,m=B(k)?k:c;a=Array.prototype.map.call(a,function(a,b){return{value:a,tieBreaker:{value:b,type:"number",index:b},predicateValues:h.map(function(c){var e=c.get(a);c=typeof e;if(null===e)c="null";else if("object"===c)a:{if(B(e.valueOf)&&(e=e.valueOf(),d(e)))break a;
189cc(e)&&(e=e.toString(),d(e))}return{value:e,type:c,index:b}})}});a.sort(function(a,b){for(var d=0,e=h.length;d<e;d++){var f=m(a.predicateValues[d],b.predicateValues[d]);if(f)return f*h[d].descending*l}return(m(a.tieBreaker,b.tieBreaker)||c(a.tieBreaker,b.tieBreaker))*l});return a=a.map(function(a){return a.value})}}function Ra(a){B(a)&&(a={link:a});a.restrict=a.restrict||"AC";return ia(a)}function Qb(a,b,d,c,e){this.$$controls=[];this.$error={};this.$$success={};this.$pending=void 0;this.$name=e(b.name||
190b.ngForm||"")(d);this.$dirty=!1;this.$valid=this.$pristine=!0;this.$submitted=this.$invalid=!1;this.$$parentForm=mb;this.$$element=a;this.$$animate=c;ae(this)}function ae(a){a.$$classCache={};a.$$classCache[be]=!(a.$$classCache[nb]=a.$$element.hasClass(nb))}function ce(a){function b(a,b,c){c&&!a.$$classCache[b]?(a.$$animate.addClass(a.$$element,b),a.$$classCache[b]=!0):!c&&a.$$classCache[b]&&(a.$$animate.removeClass(a.$$element,b),a.$$classCache[b]=!1)}function d(a,c,d){c=c?"-"+Xc(c,"-"):"";b(a,nb+
191c,!0===d);b(a,be+c,!1===d)}var c=a.set,e=a.unset;a.clazz.prototype.$setValidity=function(a,g,k){A(g)?(this.$pending||(this.$pending={}),c(this.$pending,a,k)):(this.$pending&&e(this.$pending,a,k),de(this.$pending)&&(this.$pending=void 0));Ga(g)?g?(e(this.$error,a,k),c(this.$$success,a,k)):(c(this.$error,a,k),e(this.$$success,a,k)):(e(this.$error,a,k),e(this.$$success,a,k));this.$pending?(b(this,"ng-pending",!0),this.$valid=this.$invalid=void 0,d(this,"",null)):(b(this,"ng-pending",!1),this.$valid=
192de(this.$error),this.$invalid=!this.$valid,d(this,"",this.$valid));g=this.$pending&&this.$pending[a]?void 0:this.$error[a]?!1:this.$$success[a]?!0:null;d(this,a,g);this.$$parentForm.$setValidity(a,g,this)}}function de(a){if(a)for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}function Ic(a){a.$formatters.push(function(b){return a.$isEmpty(b)?b:b.toString()})}function Sa(a,b,d,c,e,f){var g=K(b[0].type);if(!e.android){var k=!1;b.on("compositionstart",function(){k=!0});b.on("compositionupdate",
193function(a){if(A(a.data)||""===a.data)k=!1});b.on("compositionend",function(){k=!1;l()})}var h,l=function(a){h&&(f.defer.cancel(h),h=null);if(!k){var e=b.val();a=a&&a.type;"password"===g||d.ngTrim&&"false"===d.ngTrim||(e=V(e));(c.$viewValue!==e||""===e&&c.$$hasNativeValidators)&&c.$setViewValue(e,a)}};if(e.hasEvent("input"))b.on("input",l);else{var m=function(a,b,c){h||(h=f.defer(function(){h=null;b&&b.value===c||l(a)}))};b.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||
194m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut drop",m)}b.on("change",l);if(ee[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!h){var b=this.validity,c=b.badInput,d=b.typeMismatch;h=f.defer(function(){h=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Rb(a,b){return function(d,c){var e,f;if(ha(d))return d;if(C(d)){'"'===d.charAt(0)&&'"'===d.charAt(d.length-
1951)&&(d=d.substring(1,d.length-1));if(mh.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},r(e,function(a,c){c<b.length&&(f[b[c]]=+a)}),e=new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0),100>f.yyyy&&e.setFullYear(f.yyyy),e}return NaN}}function ob(a,b,d,c){return function(e,f,g,k,h,l,m,
196p){function n(a){return a&&!(a.getTime&&a.getTime()!==a.getTime())}function s(a){return w(a)&&!ha(a)?r(a)||void 0:a}function r(a,b){var c=k.$options.getOption("timezone");v&&v!==c&&(b=Uc(b,fc(v)));var e=d(a,b);!isNaN(e)&&c&&(e=gc(e,c));return e}Jc(e,f,g,k,a);Sa(e,f,g,k,h,l);var t="time"===a||"datetimelocal"===a,q,v;k.$parsers.push(function(c){if(k.$isEmpty(c))return null;if(b.test(c))return r(c,q);k.$$parserName=a});k.$formatters.push(function(a){if(a&&!ha(a))throw pb("datefmt",a);if(n(a)){q=a;var b=
197k.$options.getOption("timezone");b&&(v=b,q=gc(q,b,!0));var d=c;t&&C(k.$options.getOption("timeSecondsFormat"))&&(d=c.replace("ss.sss",k.$options.getOption("timeSecondsFormat")).replace(/:$/,""));a=m("date")(a,d,b);t&&k.$options.getOption("timeStripZeroSeconds")&&(a=a.replace(/(?::00)?(?:\.000)?$/,""));return a}v=q=null;return""});if(w(g.min)||g.ngMin){var x=g.min||p(g.ngMin)(e),z=s(x);k.$validators.min=function(a){return!n(a)||A(z)||d(a)>=z};g.$observe("min",function(a){a!==x&&(z=s(a),x=a,k.$validate())})}if(w(g.max)||
198g.ngMax){var y=g.max||p(g.ngMax)(e),J=s(y);k.$validators.max=function(a){return!n(a)||A(J)||d(a)<=J};g.$observe("max",function(a){a!==y&&(J=s(a),y=a,k.$validate())})}}}function Jc(a,b,d,c,e){(c.$$hasNativeValidators=D(b[0].validity))&&c.$parsers.push(function(a){var d=b.prop("validity")||{};if(d.badInput||d.typeMismatch)c.$$parserName=e;else return a})}function fe(a){a.$parsers.push(function(b){if(a.$isEmpty(b))return null;if(nh.test(b))return parseFloat(b);a.$$parserName="number"});a.$formatters.push(function(b){if(!a.$isEmpty(b)){if(!X(b))throw pb("numfmt",
199b);b=b.toString()}return b})}function na(a){w(a)&&!X(a)&&(a=parseFloat(a));return Y(a)?void 0:a}function Kc(a){var b=a.toString(),d=b.indexOf(".");return-1===d?-1<a&&1>a&&(a=/e-(\d+)$/.exec(b))?Number(a[1]):0:b.length-d-1}function ge(a,b,d){a=Number(a);var c=(a|0)!==a,e=(b|0)!==b,f=(d|0)!==d;if(c||e||f){var g=c?Kc(a):0,k=e?Kc(b):0,h=f?Kc(d):0,g=Math.max(g,k,h),g=Math.pow(10,g);a*=g;b*=g;d*=g;c&&(a=Math.round(a));e&&(b=Math.round(b));f&&(d=Math.round(d))}return 0===(a-b)%d}function he(a,b,d,c,e){if(w(c)){a=
200a(c);if(!a.constant)throw pb("constexpr",d,c);return a(b)}return e}function Lc(a,b){function d(a,b){if(!a||!a.length)return[];if(!b||!b.length)return a;var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],m=0;m<b.length;m++)if(e===b[m])continue a;c.push(e)}return c}function c(a){if(!a)return a;var b=a;H(a)?b=a.map(c).join(" "):D(a)?b=Object.keys(a).filter(function(b){return a[b]}).join(" "):C(a)||(b=a+"");return b}a="ngClass"+a;var e;return["$parse",function(f){return{restrict:"AC",link:function(g,
201k,h){function l(a,b){var c=[];r(a,function(a){if(0<b||p[a])p[a]=(p[a]||0)+b,p[a]===+(0<b)&&c.push(a)});return c.join(" ")}function m(a){if(a===b){var c=s,c=l(c&&c.split(" "),1);h.$addClass(c)}else c=s,c=l(c&&c.split(" "),-1),h.$removeClass(c);n=a}var p=k.data("$classCounts"),n=!0,s;p||(p=T(),k.data("$classCounts",p));"ngClass"!==a&&(e||(e=f("$index",function(a){return a&1})),g.$watch(e,m));g.$watch(f(h[a],c),function(a){if(n===b){var c=s&&s.split(" "),e=a&&a.split(" "),f=d(c,e),c=d(e,c),f=l(f,-1),
202c=l(c,1);h.$addClass(c);h.$removeClass(f)}s=a})}}}]}function sd(a,b,d,c,e,f){return{restrict:"A",compile:function(g,k){var h=a(k[c]);return function(a,c){c.on(e,function(c){var e=function(){h(a,{$event:c})};if(b.$$phase)if(f)a.$evalAsync(e);else try{e()}catch(g){d(g)}else a.$apply(e)})}}}}function Sb(a,b,d,c,e,f,g,k,h){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=void 0;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=
203[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=void 0;this.$name=h(d.name||"",!1)(a);this.$$parentForm=mb;this.$options=Tb;this.$$updateEvents="";this.$$updateEventHandler=this.$$updateEventHandler.bind(this);this.$$parsedNgModel=e(d.ngModel);this.$$parsedNgModelAssign=this.$$parsedNgModel.assign;this.$$ngModelGet=this.$$parsedNgModel;this.$$ngModelSet=this.$$parsedNgModelAssign;this.$$pendingDebounce=
204null;this.$$parserValid=void 0;this.$$parserName="parse";this.$$currentValidationRunId=0;this.$$scope=a;this.$$rootScope=a.$root;this.$$attr=d;this.$$element=c;this.$$animate=f;this.$$timeout=g;this.$$parse=e;this.$$q=k;this.$$exceptionHandler=b;ae(this);oh(this)}function oh(a){a.$$scope.$watch(function(b){b=a.$$ngModelGet(b);b===a.$modelValue||a.$modelValue!==a.$modelValue&&b!==b||a.$$setModelValue(b);return b})}function Mc(a){this.$$options=a}function ie(a,b){r(b,function(b,c){w(a[c])||(a[c]=b)})}
205function Oa(a,b){a.prop("selected",b);a.attr("selected",b)}function je(a,b,d){if(a){C(a)&&(a=new RegExp("^"+a+"$"));if(!a.test)throw F("ngPattern")("noregexp",b,a,Aa(d));return a}}function Ub(a){a=fa(a);return Y(a)?-1:a}var Xb={objectMaxDepth:5,urlErrorParamsEnabled:!0},ke=/^\/(.+)\/([a-z]*)$/,ta=Object.prototype.hasOwnProperty,K=function(a){return C(a)?a.toLowerCase():a},vb=function(a){return C(a)?a.toUpperCase():a},wa,x,sb,Ha=[].slice,Kg=[].splice,ph=[].push,la=Object.prototype.toString,Rc=Object.getPrototypeOf,
206oa=F("ng"),ca=z.angular||(z.angular={}),lc,qb=0;wa=z.document.documentMode;var Y=Number.isNaN||function(a){return a!==a};E.$inject=[];Ta.$inject=[];var ze=/^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/,V=function(a){return C(a)?a.trim():a},Od=function(a){return a.replace(/([-()[\]{}+?*.$^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Ba=function(){if(!w(Ba.rules)){var a=z.document.querySelector("[ng-csp]")||z.document.querySelector("[data-ng-csp]");if(a){var b=
207a.getAttribute("ng-csp")||a.getAttribute("data-ng-csp");Ba.rules={noUnsafeEval:!b||-1!==b.indexOf("no-unsafe-eval"),noInlineStyle:!b||-1!==b.indexOf("no-inline-style")}}else{a=Ba;try{new Function(""),b=!1}catch(d){b=!0}a.rules={noUnsafeEval:b,noInlineStyle:!1}}}return Ba.rules},rb=function(){if(w(rb.name_))return rb.name_;var a,b,d=Qa.length,c,e;for(b=0;b<d;++b)if(c=Qa[b],a=z.document.querySelector("["+c.replace(":","\\:")+"jq]")){e=a.getAttribute(c+"jq");break}return rb.name_=e},Be=/:/g,Qa=["ng-",
208"data-ng-","ng:","x-ng-"],Fe=function(a){var b=a.currentScript;if(!b)return!0;if(!(b instanceof z.HTMLScriptElement||b instanceof z.SVGScriptElement))return!1;b=b.attributes;return[b.getNamedItem("src"),b.getNamedItem("href"),b.getNamedItem("xlink:href")].every(function(b){if(!b)return!0;if(!b.value)return!1;var c=a.createElement("a");c.href=b.value;if(a.location.origin===c.origin)return!0;switch(c.protocol){case "http:":case "https:":case "ftp:":case "blob:":case "file:":case "data:":return!0;default:return!1}})}(z.document),
209Ie=/[A-Z]/g,Yc=!1,Pa=3,Pe={full:"1.8.
2092",major:1,minor:8,dot:2,codeName:"meteoric-mining
vendor: 74,250 bytes, lines 209-353
209"};U.expando="ng339";var Ka=U.cache={},ug=1;U._data=function(a){return this.cache[a[this.expando]]||{}};var qg=/-([a-z])/g,qh=/^-ms-/,Bb={mouseleave:"mouseout",mouseenter:"mouseover"},oc=F("jqLite"),tg=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,nc=/<|&#?\w+;/,rg=/<([\w:-]+)/,sg=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,qa={thead:["table"],col:["colgroup","table"],tr:["tbody","table"],td:["tr",
210"tbody","table"]};qa.tbody=qa.tfoot=qa.colgroup=qa.caption=qa.thead;qa.th=qa.td;var hb={option:[1,'<select multiple="multiple">',"</select>"],_default:[0,"",""]},Nc;for(Nc in qa){var le=qa[Nc],me=le.slice().reverse();hb[Nc]=[me.length,"<"+me.join("><")+">","</"+le.join("></")+">"]}hb.optgroup=hb.option;var zg=z.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Wa=U.prototype={ready:hd,toString:function(){var a=[];r(this,function(b){a.push(""+b)});return"["+a.join(", ")+
211"]"},eq:function(a){return 0<=a?x(this[a]):x(this[this.length+a])},length:0,push:ph,sort:[].sort,splice:[].splice},Hb={};r("multiple selected checked disabled readOnly required open".split(" "),function(a){Hb[K(a)]=a});var od={};r("input select option textarea button form details".split(" "),function(a){od[a]=!0});var vd={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern",ngStep:"step"};r({data:sc,removeData:rc,hasData:function(a){for(var b in Ka[a.ng339])return!0;
212return!1},cleanData:function(a){for(var b=0,d=a.length;b<d;b++)rc(a[b]),kd(a[b])}},function(a,b){U[b]=a});r({data:sc,inheritedData:Fb,scope:function(a){return x.data(a,"$scope")||Fb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return x.data(a,"$isolateScope")||x.data(a,"$isolateScopeNoTemplate")},controller:ld,injector:function(a){return Fb(a,"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:Cb,css:function(a,b,d){b=yb(b.replace(qh,"ms-"));if(w(d))a.style[b]=
213d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Pa&&2!==c&&8!==c&&a.getAttribute){var c=K(b),e=Hb[c];if(w(d))null===d||!1===d&&e?a.removeAttribute(b):a.setAttribute(b,e?c:d);else return a=a.getAttribute(b),e&&null!==a&&(a=c),null===a?void 0:a}},prop:function(a,b,d){if(w(d))a[b]=d;else return a[b]},text:function(){function a(a,d){if(A(d)){var c=a.nodeType;return 1===c||c===Pa?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(A(b)){if(a.multiple&&"select"===
214ua(a)){var d=[];r(a.options,function(a){a.selected&&d.push(a.value||a.text)});return d}return a.value}a.value=b},html:function(a,b){if(A(b))return a.innerHTML;zb(a,!0);a.innerHTML=b},empty:md},function(a,b){U.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==md&&A(2===a.length&&a!==Cb&&a!==ld?b:c)){if(D(b)){for(e=0;e<g;e++)if(a===sc)a(this[e],b);else for(f in b)a(this[e],f,b[f]);return this}e=a.$dv;g=A(e)?Math.min(g,1):g;for(f=0;f<g;f++){var k=a(this[f],b,c);e=e?e+k:k}return e}for(e=0;e<g;e++)a(this[e],
215b,c);return this}});r({removeData:rc,on:function(a,b,d,c){if(w(c))throw oc("onargs");if(mc(a)){c=Ab(a,!0);var e=c.events,f=c.handle;f||(f=c.handle=wg(a,e));c=0<=b.indexOf(" ")?b.split(" "):[b];for(var g=c.length,k=function(b,c,g){var k=e[b];k||(k=e[b]=[],k.specialHandlerWrapper=c,"$destroy"===b||g||a.addEventListener(b,f));k.push(d)};g--;)b=c[g],Bb[b]?(k(Bb[b],yg),k(b,void 0,!0)):k(b)}},off:kd,one:function(a,b,d){a=x(a);a.on(b,function e(){a.off(b,d);a.off(b,e)});a.on(b,d)},replaceWith:function(a,
216b){var d,c=a.parentNode;zb(a);r(new U(b),function(b){d?c.insertBefore(b,d.nextSibling):c.replaceChild(b,a);d=b})},children:function(a){var b=[];r(a.childNodes,function(a){1===a.nodeType&&b.push(a)});return b},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,b){var d=a.nodeType;if(1===d||11===d){b=new U(b);for(var d=0,c=b.length;d<c;d++)a.appendChild(b[d])}},prepend:function(a,b){if(1===a.nodeType){var d=a.firstChild;r(new U(b),function(b){a.insertBefore(b,d)})}},
217wrap:function(a,b){var d=x(b).eq(0).clone()[0],c=a.parentNode;c&&c.replaceChild(d,a);d.appendChild(a)},remove:Gb,detach:function(a){Gb(a,!0)},after:function(a,b){var d=a,c=a.parentNode;if(c){b=new U(b);for(var e=0,f=b.length;e<f;e++){var g=b[e];c.insertBefore(g,d.nextSibling);d=g}}},addClass:Eb,removeClass:Db,toggleClass:function(a,b,d){b&&r(b.split(" "),function(b){var e=d;A(e)&&(e=!Cb(a,b));(e?Eb:Db)(a,b)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},
218find:function(a,b){return a.getElementsByTagName?a.getElementsByTagName(b):[]},clone:qc,triggerHandler:function(a,b,d){var c,e,f=b.type||b,g=Ab(a);if(g=(g=g&&g.events)&&g[f])c={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:E,type:f,target:a},b.type&&(c=S(c,
219b)),b=ja(g),e=d?[c].concat(d):[c],r(b,function(b){c.isImmediatePropagationStopped()||b.apply(a,e)})}},function(a,b){U.prototype[b]=function(b,c,e){for(var f,g=0,k=this.length;g<k;g++)A(f)?(f=a(this[g],b,c,e),w(f)&&(f=x(f))):pc(f,a(this[g],b,c,e));return w(f)?f:this}});U.prototype.bind=U.prototype.on;U.prototype.unbind=U.prototype.off;var rh=Object.create(null);pd.prototype={_idx:function(a){a!==this._lastKey&&(this._lastKey=a,this._lastIndex=this._keys.indexOf(a));return this._lastIndex},_transformKey:function(a){return Y(a)?
220rh:a},get:function(a){a=this._transformKey(a);a=this._idx(a);if(-1!==a)return this._values[a]},has:function(a){a=this._transformKey(a);return-1!==this._idx(a)},set:function(a,b){a=this._transformKey(a);var d=this._idx(a);-1===d&&(d=this._lastIndex=this._keys.length);this._keys[d]=a;this._values[d]=b},delete:function(a){a=this._transformKey(a);a=this._idx(a);if(-1===a)return!1;this._keys.splice(a,1);this._values.splice(a,1);this._lastKey=NaN;this._lastIndex=-1;return!0}};var Ib=pd,og=[function(){this.$get=
221[function(){return Ib}]}],Bg=/^([^(]+?)=>/,Cg=/^[^(]*\(\s*([^)]*)\)/m,sh=/,/,th=/^\s*(_?)(\S+?)\1\s*$/,Ag=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ca=F("$injector");fb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw C(d)&&d||(d=a.name||Dg(a)),Ca("strictdi",d);b=qd(a);r(b[1].split(sh),function(a){a.replace(th,function(a,b,d){c.push(d)})})}a.$inject=c}}else H(a)?(b=a.length-1,tb(a[b],"fn"),c=a.slice(0,b)):tb(a,"fn",!0);return c};var ne=F("$animate"),
222Ef=function(){this.$get=E},Ff=function(){var a=new Ib,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=C(b)?b.split(" "):H(b)?b:[],r(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){r(b,function(b){var c=a.get(b);if(c){var d=Eg(b.attr("class")),e="",f="";r(c,function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});r(b,function(a){e&&Eb(a,e);f&&Db(a,f)});a.delete(b)}});b.length=0}return{enabled:E,on:E,off:E,pin:E,push:function(g,
223k,h,l){l&&l();h=h||{};h.from&&g.css(h.from);h.to&&g.css(h.to);if(h.addClass||h.removeClass)if(k=h.addClass,l=h.removeClass,h=a.get(g)||{},k=e(h,k,!0),l=e(h,l,!1),k||l)a.set(g,h),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},Cf=["$provide",function(a){var b=this,d=null,c=null;this.$$registeredAnimations=Object.create(null);this.register=function(c,d){if(c&&"."!==c.charAt(0))throw ne("notcsel",c);var g=c+"-animation";b.$$registeredAnimations[c.substr(1)]=g;a.factory(g,
224d)};this.customFilter=function(a){1===arguments.length&&(c=B(a)?a:null);return c};this.classNameFilter=function(a){if(1===arguments.length&&(d=a instanceof RegExp?a:null)&&/[(\s|\/)]ng-animate[(\s|\/)]/.test(d.toString()))throw d=null,ne("nongcls","ng-animate");return d};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var e;a:{for(e=0;e<d.length;e++){var f=d[e];if(1===f.nodeType){e=f;break a}}e=void 0}!e||e.parentNode||e.previousElementSibling||(d=null)}d?d.after(a):c.prepend(a)}return{on:a.on,
225off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.cancel&&a.cancel()},enter:function(c,d,h,l){d=d&&x(d);h=h&&x(h);d=d||h.parent();b(c,d,h);return a.push(c,"enter",ra(l))},move:function(c,d,h,l){d=d&&x(d);h=h&&x(h);d=d||h.parent();b(c,d,h);return a.push(c,"move",ra(l))},leave:function(b,c){return a.push(b,"leave",ra(c),function(){b.remove()})},addClass:function(b,c,d){d=ra(d);d.addClass=ib(d.addclass,c);return a.push(b,"addClass",d)},removeClass:function(b,c,d){d=ra(d);d.removeClass=ib(d.removeClass,
226c);return a.push(b,"removeClass",d)},setClass:function(b,c,d,f){f=ra(f);f.addClass=ib(f.addClass,c);f.removeClass=ib(f.removeClass,d);return a.push(b,"setClass",f)},animate:function(b,c,d,f,m){m=ra(m);m.from=m.from?S(m.from,c):c;m.to=m.to?S(m.to,d):d;m.tempClasses=ib(m.tempClasses,f||"ng-inline-animate");return a.push(b,"animate",m)}}}]}],Hf=function(){this.$get=["$$rAF",function(a){function b(b){d.push(b);1<d.length||a(function(){for(var a=0;a<d.length;a++)d[a]();d=[]})}var d=[];return function(){var a=
227!1;b(function(){a=!0});return function(d){a?d():b(d)}}}]},Gf=function(){this.$get=["$q","$sniffer","$$animateAsyncRun","$$isDocumentHidden","$timeout",function(a,b,d,c,e){function f(a){this.setHost(a);var b=d();this._doneCallbacks=[];this._tick=function(a){c()?e(a,0,!1):b(a)};this._state=0}f.chain=function(a,b){function c(){if(d===a.length)b(!0);else a[d](function(a){!1===a?b(!1):(d++,c())})}var d=0;c()};f.all=function(a,b){function c(f){e=e&&f;++d===a.length&&b(e)}var d=0,e=!0;r(a,function(a){a.done(c)})};
228f.prototype={setHost:function(a){this.host=a||{}},done:function(a){2===this._state?a():this._doneCallbacks.push(a)},progress:E,getPromise:function(){if(!this.promise){var b=this;this.promise=a(function(a,c){b.done(function(b){!1===b?c():a()})})}return this.promise},then:function(a,b){return this.getPromise().then(a,b)},"catch":function(a){return this.getPromise()["catch"](a)},"finally":function(a){return this.getPromise()["finally"](a)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&
229this.host.resume()},end:function(){this.host.end&&this.host.end();this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel();this._resolve(!1)},complete:function(a){var b=this;0===b._state&&(b._state=1,b._tick(function(){b._resolve(a)}))},_resolve:function(a){2!==this._state&&(r(this._doneCallbacks,function(b){b(a)}),this._doneCallbacks.length=0,this._state=2)}};return f}]},Df=function(){this.$get=["$$rAF","$q","$$AnimateRunner",function(a,b,d){return function(b,e){function f(){a(function(){g.addClass&&
230(b.addClass(g.addClass),g.addClass=null);g.removeClass&&(b.removeClass(g.removeClass),g.removeClass=null);g.to&&(b.css(g.to),g.to=null);k||h.complete();k=!0});return h}var g=e||{};g.$$prepared||(g=Ia(g));g.cleanupStyles&&(g.from=g.to=null);g.from&&(b.css(g.from),g.from=null);var k,h=new d;return{start:f,end:f}}}]},$=F("$compile"),uc=new function(){};Zc.$inject=["$provide","$$sanitizeUriProvider"];Kb.prototype.isFirstChange=function(){return this.previousValue===uc};var rd=/^((?:x|data)[:\-_])/i,Jg=
231/[:\-_]+(.)/g,xd=F("$controller"),wd=/^(\S+)(\s+as\s+([\w$]+))?$/,Of=function(){this.$get=["$document",function(a){return function(b){b?!b.nodeType&&b instanceof x&&(b=b[0]):b=a[0].body;return b.offsetWidth+1}}]},yd="application/json",xc={"Content-Type":yd+";charset=utf-8"},Mg=/^\[|^\{(?!\{)/,Ng={"[":/]$/,"{":/}$/},Lg=/^\)]\}',?\n/,Lb=F("$http"),Ma=ca.$interpolateMinErr=F("$interpolate");Ma.throwNoconcat=function(a){throw Ma("noconcat",a);};Ma.interr=function(a,b){return Ma("interr",a,b.toString())};
232var Qg=F("$interval"),Xf=function(){this.$get=function(){function a(a){var b=function(a){b.data=a;b.called=!0};b.id=a;return b}var b=ca.callbacks,d={};return{createCallback:function(c){c="_"+(b.$$counter++).toString(36);var e="angular.callbacks."+c,f=a(c);d[e]=b[c]=f;return e},wasCalled:function(a){return d[a].called},getResponse:function(a){return d[a].data},removeCallback:function(a){delete b[d[a].id];delete d[a]}}}},uh=/^([^?#]*)(\?([^#]*))?(#(.*))?$/,Rg={http:80,https:443,ftp:21},kb=F("$location"),
233Sg=/^\s*[\\/]{2,}/,vh={$$absUrl:"",$$html5:!1,$$replace:!1,$$compose:function(){for(var a=this.$$path,b=this.$$hash,d=Ce(this.$$search),b=b?"#"+ic(b):"",a=a.split("/"),c=a.length;c--;)a[c]=ic(a[c].replace(/%2F/g,"/"));this.$$url=a.join("/")+(d?"?"+d:"")+b;this.$$absUrl=this.$$normalizeUrl(this.$$url);this.$$urlUpdatedByLocation=!0},absUrl:Mb("$$absUrl"),url:function(a){if(A(a))return this.$$url;var b=uh.exec(a);(b[1]||""===a)&&this.path(decodeURIComponent(b[1]));(b[2]||b[1]||""===a)&&this.search(b[3]||
234"");this.hash(b[5]||"");return this},protocol:Mb("$$protocol"),host:Mb("$$host"),port:Mb("$$port"),path:Fd("$$path",function(a){a=null!==a?a.toString():"";return"/"===a.charAt(0)?a:"/"+a}),search:function(a,b){switch(arguments.length){case 0:return this.$$search;case 1:if(C(a)||X(a))a=a.toString(),this.$$search=hc(a);else if(D(a))a=Ia(a,{}),r(a,function(b,c){null==b&&delete a[c]}),this.$$search=a;else throw kb("isrcharg");break;default:A(b)||null===b?delete this.$$search[a]:this.$$search[a]=b}this.$$compose();
235return this},hash:Fd("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};r([Ed,Ac,zc],function(a){a.prototype=Object.create(vh);a.prototype.state=function(b){if(!arguments.length)return this.$$state;if(a!==zc||!this.$$html5)throw kb("nostate");this.$$state=A(b)?null:b;this.$$urlUpdatedByLocation=!0;return this}});var Ya=F("$parse"),Wg={}.constructor.prototype.valueOf,Vb=T();r("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Vb[a]=
236!0});var wh={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Ob=function(a){this.options=a};Ob.prototype={constructor:Ob,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdentifierStart(this.peekMultichar()))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,
237text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var b=a+this.peek(),d=b+this.peek(2),c=Vb[b],e=Vb[d];Vb[a]||c||e?(a=e?d:c?b:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,b){return-1!==b.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===
238typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue?this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},
239isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=
240w(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw Ya("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index<this.text.length;){var d=K(this.text.charAt(this.index));if("."===d||this.isNumber(d))a+=d;else{var c=this.peek();if("e"===d&&this.isExpOperator(c))a+=d;else if(this.isExpOperator(d)&&c&&this.isNumber(c)&&"e"===a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||c&&this.isNumber(c)||"e"!==a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:b,
241text:a,constant:!0,value:Number(a)})},readIdent:function(){var a=this.index;for(this.index+=this.peekMultichar().length;this.index<this.text.length;){var b=this.peekMultichar();if(!this.isIdentifierContinue(b))break;this.index+=b.length}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var b=this.index;this.index++;for(var d="",c=a,e=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),c=c+f;if(e)"u"===f?(e=this.text.substring(this.index+
2421,this.index+5),e.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+e+"]"),this.index+=4,d+=String.fromCharCode(parseInt(e,16))):d+=wh[f]||f,e=!1;else if("\\"===f)e=!0;else{if(f===a){this.index++;this.tokens.push({index:b,text:c,constant:!0,value:d});return}d+=f}this.index++}this.throwError("Unterminated quote",b)}};var q=function(a,b){this.lexer=a;this.options=b};q.Program="Program";q.ExpressionStatement="ExpressionStatement";q.AssignmentExpression="AssignmentExpression";q.ConditionalExpression=
243"ConditionalExpression";q.LogicalExpression="LogicalExpression";q.BinaryExpression="BinaryExpression";q.UnaryExpression="UnaryExpression";q.CallExpression="CallExpression";q.MemberExpression="MemberExpression";q.Identifier="Identifier";q.Literal="Literal";q.ArrayExpression="ArrayExpression";q.Property="Property";q.ObjectExpression="ObjectExpression";q.ThisExpression="ThisExpression";q.LocalsExpression="LocalsExpression";q.NGValueParameter="NGValueParameter";q.prototype={ast:function(a){this.text=
244a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:q.Program,body:a}},expressionStatement:function(){return{type:q.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a},
245expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();if(this.expect("=")){if(!Jd(a))throw Ya("lval");a={type:q.AssignmentExpression,left:a,right:this.assignment(),operator:"="}}return a},ternary:function(){var a=this.logicalOR(),b,d;return this.expect("?")&&(b=this.expression(),this.consume(":"))?(d=this.expression(),{type:q.ConditionalExpression,test:a,alternate:b,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:q.LogicalExpression,
246operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:q.LogicalExpression,operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),b;b=this.expect("==","!=","===","!==");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),b;b=this.expect("<",">","<=",">=");)a={type:q.BinaryExpression,operator:b.text,
247left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:q.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},
248primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=Ia(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:q.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",
249this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:q.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:q.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:q.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:q.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());
250return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:q.Identifier,name:a.text}},constant:function(){return{type:q.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");
251return{type:q.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:q.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")?(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),
252b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");return{type:q.ObjectExpression,properties:a}},throwError:function(a,b){throw Ya("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw Ya("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw Ya("ueoe",
253this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:q.ThisExpression},$locals:{type:q.LocalsExpression}}};var Hd=2;Ld.prototype={compile:function(a){var b=this;this.state={nextId:0,filters:{},fn:{vars:[],
254body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};Z(a,b.$filter);var d="",c;this.stage="assign";if(c=Kd(a))this.state.computing="assign",d=this.nextId(),this.recurse(c,d),this.return_(d),d="fn.assign="+this.generateFunction("assign","s,v,l");c=Id(a.body);b.stage="inputs";r(c,function(a,c){var d="fn"+c;b.state[d]={vars:[],body:[],own:{}};b.state.computing=d;var k=b.nextId();b.recurse(a,k);b.return_(k);b.state.inputs.push({name:d,isPure:a.isPure});a.watchId=c});this.state.computing="fn";this.stage=
255"main";this.recurse(a);a='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+d+this.watchFns()+"return fn;";a=(new Function("$filter","getStringValue","ifDefined","plus",a))(this.$filter,Tg,Ug,Gd);this.state=this.stage=void 0;return a},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;r(b,function(b){a.push("var "+b.name+"="+d.generateFunction(b.name,"s"));b.isPure&&a.push(b.name,".isPure="+JSON.stringify(b.isPure)+
256";")});b.length&&a.push("fn.inputs=["+b.map(function(a){return a.name}).join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;r(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},
257recurse:function(a,b,d,c,e,f){var g,k,h=this,l,m,p;c=c||E;if(!f&&w(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case q.Program:r(a.body,function(b,c){h.recurse(b.expression,void 0,void 0,function(a){k=a});c!==a.body.length-1?h.current().body.push(k,";"):h.return_(k)});break;case q.Literal:m=this.escape(a.value);this.assign(b,m);c(b||m);break;case q.UnaryExpression:this.recurse(a.argument,void 0,
258void 0,function(a){k=a});m=a.operator+"("+this.ifDefined(k,0)+")";this.assign(b,m);c(m);break;case q.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){k=a});m="+"===a.operator?this.plus(g,k):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(k,0):"("+g+")"+a.operator+"("+k+")";this.assign(b,m);c(m);break;case q.LogicalExpression:b=b||this.nextId();h.recurse(a.left,b);h.if_("&&"===a.operator?b:h.not(b),h.lazyRecurse(a.right,
259b));c(b);break;case q.ConditionalExpression:b=b||this.nextId();h.recurse(a.test,b);h.if_(b,h.lazyRecurse(a.alternate,b),h.lazyRecurse(a.consequent,b));c(b);break;case q.Identifier:b=b||this.nextId();d&&(d.context="inputs"===h.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);h.if_("inputs"===h.stage||h.not(h.getHasOwnProperty("l",a.name)),function(){h.if_("inputs"===h.stage||"s",function(){e&&1!==e&&h.if_(h.isNull(h.nonComputedMember("s",a.name)),
260h.lazyAssign(h.nonComputedMember("s",a.name),"{}"));h.assign(b,h.nonComputedMember("s",a.name))})},b&&h.lazyAssign(b,h.nonComputedMember("l",a.name)));c(b);break;case q.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();h.recurse(a.object,g,void 0,function(){h.if_(h.notNull(g),function(){a.computed?(k=h.nextId(),h.recurse(a.property,k),h.getStringValue(k),e&&1!==e&&h.if_(h.not(h.computedMember(g,k)),h.lazyAssign(h.computedMember(g,k),"{}")),m=h.computedMember(g,k),h.assign(b,
261m),d&&(d.computed=!0,d.name=k)):(e&&1!==e&&h.if_(h.isNull(h.nonComputedMember(g,a.property.name)),h.lazyAssign(h.nonComputedMember(g,a.property.name),"{}")),m=h.nonComputedMember(g,a.property.name),h.assign(b,m),d&&(d.computed=!1,d.name=a.property.name))},function(){h.assign(b,"undefined")});c(b)},!!e);break;case q.CallExpression:b=b||this.nextId();a.filter?(k=h.filter(a.callee.name),l=[],r(a.arguments,function(a){var b=h.nextId();h.recurse(a,b);l.push(b)}),m=k+"("+l.join(",")+")",h.assign(b,m),c(b)):
262(k=h.nextId(),g={},l=[],h.recurse(a.callee,k,g,function(){h.if_(h.notNull(k),function(){r(a.arguments,function(b){h.recurse(b,a.constant?void 0:h.nextId(),void 0,function(a){l.push(a)})});m=g.name?h.member(g.context,g.name,g.computed)+"("+l.join(",")+")":k+"("+l.join(",")+")";h.assign(b,m)},function(){h.assign(b,"undefined")});c(b)}));break;case q.AssignmentExpression:k=this.nextId();g={};this.recurse(a.left,void 0,g,function(){h.if_(h.notNull(g.context),function(){h.recurse(a.right,k);m=h.member(g.context,
263g.name,g.computed)+a.operator+k;h.assign(b,m);c(b||m)})},1);break;case q.ArrayExpression:l=[];r(a.elements,function(b){h.recurse(b,a.constant?void 0:h.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(b||m);break;case q.ObjectExpression:l=[];p=!1;r(a.properties,function(a){a.computed&&(p=!0)});p?(b=b||this.nextId(),this.assign(b,"{}"),r(a.properties,function(a){a.computed?(g=h.nextId(),h.recurse(a.key,g)):g=a.key.type===q.Identifier?a.key.name:""+a.key.value;k=h.nextId();
264h.recurse(a.value,k);h.assign(h.member(b,g,a.computed),k)})):(r(a.properties,function(b){h.recurse(b.value,a.constant?void 0:h.nextId(),void 0,function(a){l.push(h.escape(b.key.type===q.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case q.ThisExpression:this.assign(b,"s");c(b||"s");break;case q.LocalsExpression:this.assign(b,"l");c(b||"l");break;case q.NGValueParameter:this.assign(b,"v"),c(b||"v")}},getHasOwnProperty:function(a,b){var d=a+"."+
265b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,
266b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),d(),c.push("}"))}},not:function(a){return"!("+a+")"},isNull:function(a){return a+"==null"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,
267b)},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},lazyRecurse:function(a,b,d,c,e,f){var g=this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(C(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(X(a))return a.toString();if(!0===a)return"true";if(!1===
268a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw Ya("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};Md.prototype={compile:function(a){var b=this;Z(a,b.$filter);var d,c;if(d=Kd(a))c=this.recurse(d);d=Id(a.body);var e;d&&(e=[],r(d,function(a,c){var d=b.recurse(a);d.isPure=a.isPure;a.input=d;e.push(d);a.watchId=c}));var f=[];r(a.body,
269function(a){f.push(b.recurse(a.expression))});a=0===a.body.length?E:1===a.body.length?f[0]:function(a,b){var c;r(f,function(d){c=d(a,b)});return c};c&&(a.assign=function(a,b,d){return c(a,d,b)});e&&(a.inputs=e);return a},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case q.Literal:return this.value(a.value,b);case q.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case q.BinaryExpression:return c=this.recurse(a.left),
270e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case q.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case q.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case q.Identifier:return f.identifier(a.name,b,d);case q.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,
271e,b,d):this.nonComputedMember(c,e,b,d);case q.CallExpression:return g=[],r(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var p=[],n=0;n<g.length;++n)p.push(g[n](a,c,d,f));a=e.apply(void 0,p,f);return b?{context:void 0,name:void 0,value:a}:a}:function(a,c,d,f){var p=e(a,c,d,f),n;if(null!=p.value){n=[];for(var s=0;s<g.length;++s)n.push(g[s](a,c,d,f));n=p.value.apply(p.context,n)}return b?
272{value:n}:n};case q.AssignmentExpression:return c=this.recurse(a.left,!0,1),e=this.recurse(a.right),function(a,d,f,g){var p=c(a,d,f,g);a=e(a,d,f,g);p.context[p.name]=a;return b?{value:a}:a};case q.ArrayExpression:return g=[],r(a.elements,function(a){g.push(f.recurse(a))}),function(a,c,d,e){for(var f=[],n=0;n<g.length;++n)f.push(g[n](a,c,d,e));return b?{value:f}:f};case q.ObjectExpression:return g=[],r(a.properties,function(a){a.computed?g.push({key:f.recurse(a.key),computed:!0,value:f.recurse(a.value)}):
273g.push({key:a.key.type===q.Identifier?a.key.name:""+a.key.value,computed:!1,value:f.recurse(a.value)})}),function(a,c,d,e){for(var f={},n=0;n<g.length;++n)g[n].computed?f[g[n].key(a,c,d,e)]=g[n].value(a,c,d,e):f[g[n].key]=g[n].value(a,c,d,e);return b?{value:f}:f};case q.ThisExpression:return function(a){return b?{value:a}:a};case q.LocalsExpression:return function(a,c){return b?{value:c}:c};case q.NGValueParameter:return function(a,c,d){return b?{value:d}:d}}},"unary+":function(a,b){return function(d,
274c,e,f){d=a(d,c,e,f);d=w(d)?+d:0;return b?{value:d}:d}},"unary-":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?-d:-0;return b?{value:d}:d}},"unary!":function(a,b){return function(d,c,e,f){d=!a(d,c,e,f);return b?{value:d}:d}},"binary+":function(a,b,d){return function(c,e,f,g){var k=a(c,e,f,g);c=b(c,e,f,g);k=Gd(k,c);return d?{value:k}:k}},"binary-":function(a,b,d){return function(c,e,f,g){var k=a(c,e,f,g);c=b(c,e,f,g);k=(w(k)?k:0)-(w(c)?c:0);return d?{value:k}:k}},"binary*":function(a,b,
275d){return function(c,e,f,g){c=a(c,e,f,g)*b(c,e,f,g);return d?{value:c}:c}},"binary/":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)/b(c,e,f,g);return d?{value:c}:c}},"binary%":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)%b(c,e,f,g);return d?{value:c}:c}},"binary===":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)===b(c,e,f,g);return d?{value:c}:c}},"binary!==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!==b(c,e,f,g);return d?{value:c}:c}},"binary==":function(a,b,d){return function(c,
276e,f,g){c=a(c,e,f,g)==b(c,e,f,g);return d?{value:c}:c}},"binary!=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!=b(c,e,f,g);return d?{value:c}:c}},"binary<":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<b(c,e,f,g);return d?{value:c}:c}},"binary>":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=
277a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,k){e=a(e,f,g,k)?b(e,f,g,k):d(e,f,g,k);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0,name:void 0,value:a}:a}},identifier:function(a,b,d){return function(c,e,f,g){c=
278e&&a in e?e:c;d&&1!==d&&c&&null==c[a]&&(c[a]={});e=c?c[a]:void 0;return b?{context:c,name:a,value:e}:e}},computedMember:function(a,b,d,c){return function(e,f,g,k){var h=a(e,f,g,k),l,m;null!=h&&(l=b(e,f,g,k),l+="",c&&1!==c&&h&&!h[l]&&(h[l]={}),m=h[l]);return d?{context:h,name:l,value:m}:m}},nonComputedMember:function(a,b,d,c){return function(e,f,g,k){e=a(e,f,g,k);c&&1!==c&&e&&null==e[b]&&(e[b]={});f=null!=e?e[b]:void 0;return d?{context:e,name:b,value:f}:f}},inputs:function(a,b){return function(d,
279c,e,f){return f?f[b]:a(d,c,e)}}};Nb.prototype={constructor:Nb,parse:function(a){a=this.getAst(a);var b=this.astCompiler.compile(a.ast),d=a.ast;b.literal=0===d.body.length||1===d.body.length&&(d.body[0].expression.type===q.Literal||d.body[0].expression.type===q.ArrayExpression||d.body[0].expression.type===q.ObjectExpression);b.constant=a.ast.constant;b.oneTime=a.oneTime;return b},getAst:function(a){var b=!1;a=a.trim();":"===a.charAt(0)&&":"===a.charAt(1)&&(b=!0,a=a.substring(2));return{ast:this.ast.ast(a),
280oneTime:b}}};var Ea=F("$sce"),W={HTML:"html",CSS:"css",MEDIA_URL:"mediaUrl",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},Dc=/_([a-z])/g,Zg=F("$templateRequest"),$g=F("$timeout"),aa=z.document.createElement("a"),Qd=ga(z.location.href),Na;aa.href="http://[::1]";var ah="[::1]"===aa.hostname;Rd.$inject=["$document"];fd.$inject=["$provide"];var Yd=22,Xd=".",Fc="0";Sd.$inject=["$locale"];Ud.$inject=["$locale"];var lh={yyyy:ea("FullYear",4,0,!1,!0),yy:ea("FullYear",2,0,!0,!0),y:ea("FullYear",1,0,!1,!0),
281MMMM:lb("Month"),MMM:lb("Month",!0),MM:ea("Month",2,1),M:ea("Month",1,1),LLLL:lb("Month",!1,!0),dd:ea("Date",2),d:ea("Date",1),HH:ea("Hours",2),H:ea("Hours",1),hh:ea("Hours",2,-12),h:ea("Hours",1,-12),mm:ea("Minutes",2),m:ea("Minutes",1),ss:ea("Seconds",2),s:ea("Seconds",1),sss:ea("Milliseconds",3),EEEE:lb("Day"),EEE:lb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Pb(Math[0<a?"floor":"ceil"](a/60),2)+Pb(Math.abs(a%60),2))},
282ww:$d(2),w:$d(1),G:Gc,GG:Gc,GGG:Gc,GGGG:function(a,b){return 0>=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},kh=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/,jh=/^-?\d+$/;Td.$inject=["$locale"];var eh=ia(K),fh=ia(vb);Vd.$inject=["$parse"];var Re=ia({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===la.call(b.prop("href"))?"xlink:href":"href";
283b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),wb={};r(Hb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!==a){var c=xa("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});wb[c]=function(){return{restrict:"A",priority:100,link:e}}}});r(vd,function(a,b){wb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"===e.ngPattern.charAt(0)&&(c=e.ngPattern.match(ke))){e.$set("ngPattern",new RegExp(c[1],
284c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});r(["src","srcset","href"],function(a){var b=xa("ng-"+a);wb[b]=["$sce",function(d){return{priority:99,link:function(c,e,f){var g=a,k=a;"href"===a&&"[object SVGAnimatedString]"===la.call(e.prop("href"))&&(k="xlinkHref",f.$attr[k]="xlink:href",g=null);f.$set(b,d.getTrustedMediaUrl(f[b]));f.$observe(b,function(b){b?(f.$set(k,b),wa&&g&&e.prop(g,f[k])):"href"===a&&f.$set(k,null)})}}}]});var mb={$addControl:E,$getControls:ia([]),$$renameControl:function(a,
285b){a.$name=b},$removeControl:E,$setValidity:E,$setDirty:E,$setPristine:E,$setSubmitted:E,$$setSubmitted:E};Qb.$inject=["$element","$attrs","$scope","$animate","$interpolate"];Qb.prototype={$rollbackViewValue:function(){r(this.$$controls,function(a){a.$rollbackViewValue()})},$commitViewValue:function(){r(this.$$controls,function(a){a.$commitViewValue()})},$addControl:function(a){Ja(a.$name,"input");this.$$controls.push(a);a.$name&&(this[a.$name]=a);a.$$parentForm=this},$getControls:function(){return ja(this.$$controls)},
286$$renameControl:function(a,b){var d=a.$name;this[d]===a&&delete this[d];this[b]=a;a.$name=b},$removeControl:function(a){a.$name&&this[a.$name]===a&&delete this[a.$name];r(this.$pending,function(b,d){this.$setValidity(d,null,a)},this);r(this.$error,function(b,d){this.$setValidity(d,null,a)},this);r(this.$$success,function(b,d){this.$setValidity(d,null,a)},this);cb(this.$$controls,a);a.$$parentForm=mb},$setDirty:function(){this.$$animate.removeClass(this.$$element,Za);this.$$animate.addClass(this.$$element,
287Wb);this.$dirty=!0;this.$pristine=!1;this.$$parentForm.$setDirty()},$setPristine:function(){this.$$animate.setClass(this.$$element,Za,Wb+" ng-submitted");this.$dirty=!1;this.$pristine=!0;this.$submitted=!1;r(this.$$controls,function(a){a.$setPristine()})},$setUntouched:function(){r(this.$$controls,function(a){a.$setUntouched()})},$setSubmitted:function(){for(var a=this;a.$$parentForm&&a.$$parentForm!==mb;)a=a.$$parentForm;a.$$setSubmitted()},$$setSubmitted:function(){this.$$animate.addClass(this.$$element,
288"ng-submitted");this.$submitted=!0;r(this.$$controls,function(a){a.$$setSubmitted&&a.$$setSubmitted()})}};ce({clazz:Qb,set:function(a,b,d){var c=a[b];c?-1===c.indexOf(d)&&c.push(d):a[b]=[d]},unset:function(a,b,d){var c=a[b];c&&(cb(c,d),0===c.length&&delete a[b])}});var oe=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||E}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Qb,compile:function(d,f){d.addClass(Za).addClass(nb);
289var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var p=f[0];if(!("action"in e)){var n=function(b){a.$apply(function(){p.$commitViewValue();p.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",n);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",n)},0,!1)})}(f[1]||p.$$parentForm).$addControl(p);var s=g?c(p.$name):E;g&&(s(a,p),e.$observe(g,function(b){p.$name!==b&&(s(a,void 0),p.$$parentForm.$$renameControl(p,b),s=c(p.$name),s(a,p))}));
290d.on("$destroy",function(){p.$$parentForm.$removeControl(p);s(a,void 0);S(p,mb)})}}}}}]},Se=oe(),df=oe(!0),mh=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,xh=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,yh=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,
291nh=/^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,pe=/^(\d{4,})-(\d{2})-(\d{2})$/,qe=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Oc=/^(\d{4,})-W(\d\d)$/,re=/^(\d{4,})-(\d\d)$/,se=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,ee=T();r(["date","datetime-local","month","time","week"],function(a){ee[a]=!0});var te={text:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Ic(c)},date:ob("date",pe,Rb(pe,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":ob("datetimelocal",qe,Rb(qe,"yyyy MM dd HH mm ss sss".split(" ")),
292"yyyy-MM-ddTHH:mm:ss.sss"),time:ob("time",se,Rb(se,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:ob("week",Oc,function(a,b){if(ha(a))return a;if(C(a)){Oc.lastIndex=0;var d=Oc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,k=0,h=Zd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),k=b.getMilliseconds());return new Date(c,0,h.getDate()+e,d,f,g,k)}}return NaN},"yyyy-Www"),month:ob("month",re,Rb(re,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f,g,k){Jc(a,b,d,c,"number");fe(c);Sa(a,
293b,d,c,e,f);var h;if(w(d.min)||d.ngMin){var l=d.min||k(d.ngMin)(a);h=na(l);c.$validators.min=function(a,b){return c.$isEmpty(b)||A(h)||b>=h};d.$observe("min",function(a){a!==l&&(h=na(a),l=a,c.$validate())})}if(w(d.max)||d.ngMax){var m=d.max||k(d.ngMax)(a),p=na(m);c.$validators.max=function(a,b){return c.$isEmpty(b)||A(p)||b<=p};d.$observe("max",function(a){a!==m&&(p=na(a),m=a,c.$validate())})}if(w(d.step)||d.ngStep){var n=d.step||k(d.ngStep)(a),s=na(n);c.$validators.step=function(a,b){return c.$isEmpty(b)||
294A(s)||ge(b,h||0,s)};d.$observe("step",function(a){a!==n&&(s=na(a),n=a,c.$validate())})}},url:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Ic(c);c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||xh.test(d)}},email:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Ic(c);c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||yh.test(d)}},radio:function(a,b,d,c){var e=!d.ngTrim||"false"!==V(d.ngTrim);A(d.name)&&b.attr("name",++qb);b.on("change",function(a){var g;b[0].checked&&(g=d.value,e&&(g=
295V(g)),c.$setViewValue(g,a&&a.type))});c.$render=function(){var a=d.value;e&&(a=V(a));b[0].checked=a===c.$viewValue};d.$observe("value",c.$render)},range:function(a,b,d,c,e,f){function g(a,c){b.attr(a,d[a]);var e=d[a];d.$observe(a,function(a){a!==e&&(e=a,c(a))})}function k(a){p=na(a);Y(c.$modelValue)||(m?(a=b.val(),p>a&&(a=p,b.val(a)),c.$setViewValue(a)):c.$validate())}function h(a){n=na(a);Y(c.$modelValue)||(m?(a=b.val(),n<a&&(b.val(n),a=n<p?p:n),c.$setViewValue(a)):c.$validate())}function l(a){s=
296na(a);Y(c.$modelValue)||(m?c.$viewValue!==b.val()&&c.$setViewValue(b.val()):c.$validate())}Jc(a,b,d,c,"range");fe(c);Sa(a,b,d,c,e,f);var m=c.$$hasNativeValidators&&"range"===b[0].type,p=m?0:void 0,n=m?100:void 0,s=m?1:void 0,r=b[0].validity;a=w(d.min);e=w(d.max);f=w(d.step);var q=c.$render;c.$render=m&&w(r.rangeUnderflow)&&w(r.rangeOverflow)?function(){q();c.$setViewValue(b.val())}:q;a&&(p=na(d.min),c.$validators.min=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||A(p)||b>=p},g("min",k));
297e&&(n=na(d.max),c.$validators.max=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||A(n)||b<=n},g("max",h));f&&(s=na(d.step),c.$validators.step=m?function(){return!r.stepMismatch}:function(a,b){return c.$isEmpty(b)||A(s)||ge(b,p||0,s)},g("step",l))},checkbox:function(a,b,d,c,e,f,g,k){var h=he(k,a,"ngTrueValue",d.ngTrueValue,!0),l=he(k,a,"ngFalseValue",d.ngFalseValue,!1);b.on("change",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=
298function(a){return!1===a};c.$formatters.push(function(a){return va(a,h)});c.$parsers.push(function(a){return a?h:l})},hidden:E,button:E,submit:E,reset:E,file:E},$c=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,k){k[0]&&(te[K(g.type)]||te.text)(e,f,g,k[0],b,a,d,c)}}}}],Af=function(){var a={configurable:!0,enumerable:!1,get:function(){return this.getAttribute("value")||""},set:function(a){this.setAttribute("value",a)}};
299return{restrict:"E",priority:200,compile:function(b,d){if("hidden"===K(d.type))return{pre:function(b,d,f,g){b=d[0];b.parentNode&&b.parentNode.insertBefore(b,b.nextSibling);Object.defineProperty&&Object.defineProperty(b,"value",a)}}}}},zh=/^(true|false|\d+)$/,xf=function(){function a(a,d,c){var e=w(c)?c:9===wa?"":null;a.prop("value",e);d.$set("value",c)}return{restrict:"A",priority:100,compile:function(b,d){return zh.test(d.ngValue)?function(b,d,f){b=b.$eval(f.ngValue);a(d,f,b)}:function(b,d,f){b.$watch(f.ngValue,
300function(b){a(d,f,b)})}}}},We=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=jc(a)})}}}}],Ye=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=A(a)?"":a})}}}}],
301Xe=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],wf=ia({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Ze=Lc("",!0),af=Lc("Odd",0),$e=Lc("Even",1),bf=Ra({compile:function(a,
302b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),cf=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],ed={},Ah={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=xa("ng-"+a);ed[b]=["$parse","$rootScope","$exceptionHandler",function(d,c,e){return sd(d,c,e,b,a,Ah[a])}]});var ff=["$animate","$compile",function(a,b){return{multiElement:!0,
303transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var k,h,l;d.$watch(e.ngIf,function(d){d?h||g(function(d,f){h=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);k={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),h&&(h.$destroy(),h=null),k&&(l=ub(k.clone),a.leave(l).done(function(a){!1!==a&&(l=null)}),k=null))})}}}],gf=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",
304controller:ca.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",k=e.autoscroll;return function(c,e,m,p,n){var r=0,q,t,x,v=function(){t&&(t.remove(),t=null);q&&(q.$destroy(),q=null);x&&(d.leave(x).done(function(a){!1!==a&&(t=null)}),t=x,x=null)};c.$watch(f,function(f){var m=function(a){!1===a||!w(k)||k&&!c.$eval(k)||b()},t=++r;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===r){var b=c.$new();p.template=a;a=n(b,function(a){v();d.enter(a,null,e).done(m)});q=b;x=a;q.$emit("$includeContentLoaded",
305f);c.$eval(g)}},function(){c.$$destroyed||t!==r||(v(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(v(),p.template=null)})}}}}],zf=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){la.call(d[0]).match(/SVG/)?(d.empty(),a(gd(e.template,z.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],hf=Ra({priority:450,compile:function(){return{pre:function(a,
306b,d){a.$eval(d.ngInit)}}}}),vf=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=d.ngList||", ",f="false"!==d.ngTrim,g=f?V(e):e;c.$parsers.push(function(a){if(!A(a)){var b=[];a&&r(a.split(g),function(a){a&&b.push(f?V(a):a)});return b}});c.$formatters.push(function(a){if(H(a))return a.join(e)});c.$isEmpty=function(a){return!a||!a.length}}}},nb="ng-valid",be="ng-invalid",Za="ng-pristine",Wb="ng-dirty",pb=F("ngModel");Sb.$inject="$scope $exceptionHandler $attrs $element $parse $animate $timeout $q $interpolate".split(" ");
307Sb.prototype={$$initGetterSetters:function(){if(this.$options.getOption("getterSetter")){var a=this.$$parse(this.$$attr.ngModel+"()"),b=this.$$parse(this.$$attr.ngModel+"($$$p)");this.$$ngModelGet=function(b){var c=this.$$parsedNgModel(b);B(c)&&(c=a(b));return c};this.$$ngModelSet=function(a,c){B(this.$$parsedNgModel(a))?b(a,{$$$p:c}):this.$$parsedNgModelAssign(a,c)}}else if(!this.$$parsedNgModel.assign)throw pb("nonassign",this.$$attr.ngModel,Aa(this.$$element));},$render:E,$isEmpty:function(a){return A(a)||
308""===a||null===a||a!==a},$$updateEmptyClasses:function(a){this.$isEmpty(a)?(this.$$animate.removeClass(this.$$element,"ng-not-empty"),this.$$animate.addClass(this.$$element,"ng-empty")):(this.$$animate.removeClass(this.$$element,"ng-empty"),this.$$animate.addClass(this.$$element,"ng-not-empty"))},$setPristine:function(){this.$dirty=!1;this.$pristine=!0;this.$$animate.removeClass(this.$$element,Wb);this.$$animate.addClass(this.$$element,Za)},$setDirty:function(){this.$dirty=!0;this.$pristine=!1;this.$$animate.removeClass(this.$$element,
309Za);this.$$animate.addClass(this.$$element,Wb);this.$$parentForm.$setDirty()},$setUntouched:function(){this.$touched=!1;this.$untouched=!0;this.$$animate.setClass(this.$$element,"ng-untouched","ng-touched")},$setTouched:function(){this.$touched=!0;this.$untouched=!1;this.$$animate.setClass(this.$$element,"ng-touched","ng-untouched")},$rollbackViewValue:function(){this.$$timeout.cancel(this.$$pendingDebounce);this.$viewValue=this.$$lastCommittedViewValue;this.$render()},$validate:function(){if(!Y(this.$modelValue)){var a=
310this.$$lastCommittedViewValue,b=this.$$rawModelValue,d=this.$valid,c=this.$modelValue,e=this.$options.getOption("allowInvalid"),f=this;this.$$runValidators(b,a,function(a){e||d===a||(f.$modelValue=a?b:void 0,f.$modelValue!==c&&f.$$writeModelToScope())})}},$$runValidators:function(a,b,d){function c(){var c=!0;r(h.$validators,function(d,e){var g=Boolean(d(a,b));c=c&&g;f(e,g)});return c?!0:(r(h.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;r(h.$asyncValidators,function(e,
311g){var h=e(a,b);if(!h||!B(h.then))throw pb("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?h.$$q.all(c).then(function(){g(d)},E):g(!0)}function f(a,b){k===h.$$currentValidationRunId&&h.$setValidity(a,b)}function g(a){k===h.$$currentValidationRunId&&d(a)}this.$$currentValidationRunId++;var k=this.$$currentValidationRunId,h=this;(function(){var a=h.$$parserName;if(A(h.$$parserValid))f(a,null);else return h.$$parserValid||(r(h.$validators,function(a,
312b){f(b,null)}),r(h.$asyncValidators,function(a,b){f(b,null)})),f(a,h.$$parserValid),h.$$parserValid;return!0})()?c()?e():g(!1):g(!1)},$commitViewValue:function(){var a=this.$viewValue;this.$$timeout.cancel(this.$$pendingDebounce);if(this.$$lastCommittedViewValue!==a||""===a&&this.$$hasNativeValidators)this.$$updateEmptyClasses(a),this.$$lastCommittedViewValue=a,this.$pristine&&this.$setDirty(),this.$$parseAndValidate()},$$parseAndValidate:function(){var a=this.$$lastCommittedViewValue,b=this;this.$$parserValid=
313A(a)?void 0:!0;this.$setValidity(this.$$parserName,null);this.$$parserName="parse";if(this.$$parserValid)for(var d=0;d<this.$parsers.length;d++)if(a=this.$parsers[d](a),A(a)){this.$$parserValid=!1;break}Y(this.$modelValue)&&(this.$modelValue=this.$$ngModelGet(this.$$scope));var c=this.$modelValue,e=this.$options.getOption("allowInvalid");this.$$rawModelValue=a;e&&(this.$modelValue=a,b.$modelValue!==c&&b.$$writeModelToScope());this.$$runValidators(a,this.$$lastCommittedViewValue,function(d){e||(b.$modelValue=
314d?a:void 0,b.$modelValue!==c&&b.$$writeModelToScope())})},$$writeModelToScope:function(){this.$$ngModelSet(this.$$scope,this.$modelValue);r(this.$viewChangeListeners,function(a){try{a()}catch(b){this.$$exceptionHandler(b)}},this)},$setViewValue:function(a,b){this.$viewValue=a;this.$options.getOption("updateOnDefault")&&this.$$debounceViewValueCommit(b)},$$debounceViewValueCommit:function(a){var b=this.$options.getOption("debounce");X(b[a])?b=b[a]:X(b["default"])&&-1===this.$options.getOption("updateOn").indexOf(a)?
315b=b["default"]:X(b["*"])&&(b=b["*"]);this.$$timeout.cancel(this.$$pendingDebounce);var d=this;0<b?this.$$pendingDebounce=this.$$timeout(function(){d.$commitViewValue()},b):this.$$rootScope.$$phase?this.$commitViewValue():this.$$scope.$apply(function(){d.$commitViewValue()})},$overrideModelOptions:function(a){this.$options=this.$options.createChild(a);this.$$setUpdateOnEvents()},$processModelValue:function(){var a=this.$$format();this.$viewValue!==a&&(this.$$updateEmptyClasses(a),this.$viewValue=this.$$lastCommittedViewValue=
316a,this.$render(),this.$$runValidators(this.$modelValue,this.$viewValue,E))},$$format:function(){for(var a=this.$formatters,b=a.length,d=this.$modelValue;b--;)d=a[b](d);return d},$$setModelValue:function(a){this.$modelValue=this.$$rawModelValue=a;this.$$parserValid=void 0;this.$processModelValue()},$$setUpdateOnEvents:function(){this.$$updateEvents&&this.$$element.off(this.$$updateEvents,this.$$updateEventHandler);if(this.$$updateEvents=this.$options.getOption("updateOn"))this.$$element.on(this.$$updateEvents,
317this.$$updateEventHandler)},$$updateEventHandler:function(a){this.$$debounceViewValueCommit(a&&a.type)}};ce({clazz:Sb,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]}});var uf=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Sb,priority:1,compile:function(b){b.addClass(Za).addClass("ng-untouched").addClass(nb);return{pre:function(a,b,e,f){var g=f[0];b=f[1]||g.$$parentForm;if(f=f[2])g.$options=f.$options;g.$$initGetterSetters();b.$addControl(g);
318e.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},post:function(b,c,e,f){function g(){k.$setTouched()}var k=f[0];k.$$setUpdateOnEvents();c.on("blur",function(){k.$touched||(a.$$phase?b.$evalAsync(g):b.$apply(g))})}}}}}],Tb,Bh=/(\s+|^)default(\s+|$)/;Mc.prototype={getOption:function(a){return this.$$options[a]},createChild:function(a){var b=!1;a=S({},a);r(a,function(d,c){"$inherit"===d?"*"===c?b=!0:(a[c]=
319this.$$options[c],"updateOn"===c&&(a.updateOnDefault=this.$$options.updateOnDefault)):"updateOn"===c&&(a.updateOnDefault=!1,a[c]=V(d.replace(Bh,function(){a.updateOnDefault=!0;return" "})))},this);b&&(delete a["*"],ie(a,this.$$options));ie(a,Tb.$$options);return new Mc(a)}};Tb=new Mc({updateOn:"",updateOnDefault:!0,debounce:0,getterSetter:!1,allowInvalid:!1,timezone:null});var yf=function(){function a(a,d){this.$$attrs=a;this.$$scope=d}a.$inject=["$attrs","$scope"];a.prototype={$onInit:function(){var a=
320this.parentCtrl?this.parentCtrl.$options:Tb,d=this.$$scope.$eval(this.$$attrs.ngModelOptions);this.$options=a.createChild(d)}};return{restrict:"A",priority:10,require:{parentCtrl:"?^^ngModelOptions"},bindToController:!0,controller:a}},jf=Ra({terminal:!0,priority:1E3}),Ch=F("ngOptions"),Dh=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([$\w][$\w]*)|(?:\(\s*([$\w][$\w]*)\s*,\s*([$\w][$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
321sf=["$compile","$document","$parse",function(a,b,d){function c(a,b,c){function e(a,b,c,d,f){this.selectValue=a;this.viewValue=b;this.label=c;this.group=d;this.disabled=f}function f(a){var b;if(!r&&za(a))b=a;else{b=[];for(var c in a)a.hasOwnProperty(c)&&"$"!==c.charAt(0)&&b.push(c)}return b}var p=a.match(Dh);if(!p)throw Ch("iexp",a,Aa(b));var n=p[5]||p[7],r=p[6];a=/ as /.test(p[0])&&p[1];var q=p[9];b=d(p[2]?p[1]:n);var t=a&&d(a)||b,w=q&&d(q),v=q?function(a,b){return w(c,b)}:function(a){return La(a)},
322x=function(a,b){return v(a,B(a,b))},A=d(p[2]||p[1]),y=d(p[3]||""),J=d(p[4]||""),I=d(p[8]),z={},B=r?function(a,b){z[r]=b;z[n]=a;return z}:function(a){z[n]=a;return z};return{trackBy:q,getTrackByValue:x,getWatchables:d(I,function(a){var b=[];a=a||[];for(var d=f(a),e=d.length,g=0;g<e;g++){var k=a===d?g:d[g],l=a[k],k=B(l,k),l=v(l,k);b.push(l);if(p[2]||p[1])l=A(c,k),b.push(l);p[4]&&(k=J(c,k),b.push(k))}return b}),getOptions:function(){for(var a=[],b={},d=I(c)||[],g=f(d),k=g.length,n=0;n<k;n++){var p=d===
323g?n:g[n],r=B(d[p],p),s=t(c,r),p=v(s,r),w=A(c,r),z=y(c,r),r=J(c,r),s=new e(p,s,w,z,r);a.push(s);b[p]=s}return{items:a,selectValueMap:b,getOptionFromViewValue:function(a){return b[x(a)]},getViewValueFromOption:function(a){return q?Ia(a.viewValue):a.viewValue}}}}}var e=z.document.createElement("option"),f=z.document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","ngModel"],link:{pre:function(a,b,c,d){d[0].registerOption=E},post:function(d,k,h,l){function m(a){var b=(a=v.getOptionFromViewValue(a))&&
324a.element;b&&!b.selected&&(b.selected=!0);return a}function p(a,b){a.element=b;b.disabled=a.disabled;a.label!==b.label&&(b.label=a.label,b.textContent=a.label);b.value=a.selectValue}var n=l[0],q=l[1],A=h.multiple;l=0;for(var t=k.children(),z=t.length;l<z;l++)if(""===t[l].value){n.hasEmptyOption=!0;n.emptyOption=t.eq(l);break}k.empty();l=!!n.emptyOption;x(e.cloneNode(!1)).val("?");var v,B=c(h.ngOptions,k,d),C=b[0].createDocumentFragment();n.generateUnknownOptionValue=function(a){return"?"};A?(n.writeValue=
325function(a){if(v){var b=a&&a.map(m)||[];v.items.forEach(function(a){a.element.selected&&-1===Array.prototype.indexOf.call(b,a)&&(a.element.selected=!1)})}},n.readValue=function(){var a=k.val()||[],b=[];r(a,function(a){(a=v.selectValueMap[a])&&!a.disabled&&b.push(v.getViewValueFromOption(a))});return b},B.trackBy&&d.$watchCollection(function(){if(H(q.$viewValue))return q.$viewValue.map(function(a){return B.getTrackByValue(a)})},function(){q.$render()})):(n.writeValue=function(a){if(v){var b=k[0].options[k[0].selectedIndex],
326c=v.getOptionFromViewValue(a);b&&b.removeAttribute("selected");c?(k[0].value!==c.selectValue&&(n.removeUnknownOption(),k[0].value=c.selectValue,c.element.selected=!0),c.element.setAttribute("selected","selected")):n.selectUnknownOrEmptyOption(a)}},n.readValue=function(){var a=v.selectValueMap[k.val()];return a&&!a.disabled?(n.unselectEmptyOption(),n.removeUnknownOption(),v.getViewValueFromOption(a)):null},B.trackBy&&d.$watch(function(){return B.getTrackByValue(q.$viewValue)},function(){q.$render()}));
327l&&(a(n.emptyOption)(d),k.prepend(n.emptyOption),8===n.emptyOption[0].nodeType?(n.hasEmptyOption=!1,n.registerOption=function(a,b){""===b.val()&&(n.hasEmptyOption=!0,n.emptyOption=b,n.emptyOption.removeClass("ng-scope"),q.$render(),b.on("$destroy",function(){var a=n.$isEmptyOptionSelected();n.hasEmptyOption=!1;n.emptyOption=void 0;a&&q.$render()}))}):n.emptyOption.removeClass("ng-scope"));d.$watchCollection(B.getWatchables,function(){var a=v&&n.readValue();if(v)for(var b=v.items.length-1;0<=b;b--){var c=
328v.items[b];w(c.group)?Gb(c.element.parentNode):Gb(c.element)}v=B.getOptions();var d={};v.items.forEach(function(a){var b;if(w(a.group)){b=d[a.group];b||(b=f.cloneNode(!1),C.appendChild(b),b.label=null===a.group?"null":a.group,d[a.group]=b);var c=e.cloneNode(!1);b.appendChild(c);p(a,c)}else b=e.cloneNode(!1),C.appendChild(b),p(a,b)});k[0].appendChild(C);q.$render();q.$isEmpty(a)||(b=n.readValue(),(B.trackBy||A?va(a,b):a===b)||(q.$setViewValue(b),q.$render()))})}}}}],kf=["$locale","$interpolate","$log",
329function(a,b,d){var c=/{}/g,e=/^when(Minus)?(.+)$/;return{link:function(f,g,k){function h(a){g.text(a||"")}var l=k.count,m=k.$attr.when&&g.attr(k.$attr.when),p=k.offset||0,n=f.$eval(m)||{},q={},w=b.startSymbol(),t=b.endSymbol(),x=w+l+"-"+p+t,v=ca.noop,z;r(k,function(a,b){var c=e.exec(b);c&&(c=(c[1]?"-":"")+K(c[2]),n[c]=g.attr(k.$attr[b]))});r(n,function(a,d){q[d]=b(a.replace(c,x))});f.$watch(l,function(b){var c=parseFloat(b),e=Y(c);e||c in n||(c=a.pluralCat(c-p));c===z||e&&Y(z)||(v(),e=q[c],A(e)?
330(null!=b&&d.debug("ngPluralize: no rule defined for '"+c+"' in "+m),v=E,h()):v=f.$watch(e,h),z=c)})}}}],ue=F("ngRef"),lf=["$parse",function(a){return{priority:-1,restrict:"A",compile:function(b,d){var c=xa(ua(b)),e=a(d.ngRef),f=e.assign||function(){throw ue("nonassign",d.ngRef);};return function(a,b,h){var l;if(h.hasOwnProperty("ngRefRead"))if("$element"===h.ngRefRead)l=b;else{if(l=b.data("$"+h.ngRefRead+"Controller"),!l)throw ue("noctrl",h.ngRefRead,d.ngRef);}else l=b.data("$"+c+"Controller");l=
331l||b;f(a,l);b.on("$destroy",function(){e(a)===l&&f(a,null)})}}}}],mf=["$parse","$animate","$compile",function(a,b,d){var c=F("ngRepeat"),e=function(a,b,c,d,e,f,g){a[c]=d;e&&(a[e]=f);a.$index=b;a.$first=0===b;a.$last=b===g-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(b&1))},f=function(a,b,c){return La(c)},g=function(a,b){return b};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(k,h){var l=h.ngRepeat,m=d.$$createComment("end ngRepeat",
332l),p=l.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!p)throw c("iexp",l);var n=p[1],q=p[2],w=p[3],t=p[4],p=n.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);if(!p)throw c("iidexp",n);var x=p[3]||p[1],v=p[2];if(w&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(w)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(w)))throw c("badident",w);var A;if(t){var z={$id:La},y=a(t);A=function(a,b,c,d){v&&
333(z[v]=b);z[x]=c;z.$index=d;return y(a,z)}}return function(a,d,h,k,n){var p=T();a.$watchCollection(q,function(h){var k,q,t=d[0],s,y=T(),B,C,E,D,H,F,K;w&&(a[w]=h);if(za(h))H=h,q=A||f;else for(K in q=A||g,H=[],h)ta.call(h,K)&&"$"!==K.charAt(0)&&H.push(K);B=H.length;K=Array(B);for(k=0;k<B;k++)if(C=h===H?k:H[k],E=h[C],D=q(a,C,E,k),p[D])F=p[D],delete p[D],y[D]=F,K[k]=F;else{if(y[D])throw r(K,function(a){a&&a.scope&&(p[a.id]=a)}),c("dupes",l,D,E);K[k]={id:D,scope:void 0,clone:void 0};y[D]=!0}z&&(z[x]=void 0);
334for(s in p){F=p[s];D=ub(F.clone);b.leave(D);if(D[0].parentNode)for(k=0,q=D.length;k<q;k++)D[k].$$NG_REMOVED=!0;F.scope.$destroy()}for(k=0;k<B;k++)if(C=h===H?k:H[k],E=h[C],F=K[k],F.scope){s=t;do s=s.nextSibling;while(s&&s.$$NG_REMOVED);F.clone[0]!==s&&b.move(ub(F.clone),null,t);t=F.clone[F.clone.length-1];e(F.scope,k,x,E,v,C,B)}else n(function(a,c){F.scope=c;var d=m.cloneNode(!1);a[a.length++]=d;b.enter(a,null,t);t=d;F.clone=a;y[F.id]=F;e(F.scope,k,x,E,v,C,B)});p=y})}}}}],nf=["$animate",function(a){return{restrict:"A",
335multiElement:!0,link:function(b,d,c){b.$watch(c.ngShow,function(b){a[b?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],ef=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngHide,function(b){a[b?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],of=Ra(function(a,b,d){a.$watchCollection(d.ngStyle,function(a,d){d&&a!==d&&r(d,function(a,c){b.css(c,"")});a&&b.css(a)})}),pf=["$animate","$compile",function(a,
336b){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(d,c,e,f){var g=[],k=[],h=[],l=[],m=function(a,b){return function(c){!1!==c&&a.splice(b,1)}};d.$watch(e.ngSwitch||e.on,function(c){for(var d,e;h.length;)a.cancel(h.pop());d=0;for(e=l.length;d<e;++d){var q=ub(k[d].clone);l[d].$destroy();(h[d]=a.leave(q)).done(m(h,d))}k.length=0;l.length=0;(g=f.cases["!"+c]||f.cases["?"])&&r(g,function(c){c.transclude(function(d,e){l.push(e);var f=c.element;d[d.length++]=b.$$createComment("end ngSwitchWhen");
337k.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],qf=Ra({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){a=d.ngSwitchWhen.split(d.ngSwitchWhenSeparator).sort().filter(function(a,b,c){return c[b-1]!==a});r(a,function(a){c.cases["!"+a]=c.cases["!"+a]||[];c.cases["!"+a].push({transclude:e,element:b})})}}),rf=Ra({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["?"]=c.cases["?"]||[];c.cases["?"].push({transclude:e,
338element:b})}}),Eh=F("ngTransclude"),tf=["$compile",function(a){return{restrict:"EAC",compile:function(b){var d=a(b.contents());b.empty();return function(a,b,f,g,k){function h(){d(a,function(a){b.append(a)})}if(!k)throw Eh("orphan",Aa(b));f.ngTransclude===f.$attr.ngTransclude&&(f.ngTransclude="");f=f.ngTransclude||f.ngTranscludeSlot;k(function(a,c){var d;if(d=a.length)a:{d=0;for(var f=a.length;d<f;d++){var g=a[d];if(g.nodeType!==Pa||g.nodeValue.trim()){d=!0;break a}}d=void 0}d?b.append(a):(h(),c.$destroy())},
339null,f);f&&!k.isSlotFilled(f)&&h()}}}}],Te=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(b,d){"text/ng-template"===d.type&&a.put(d.id,b[0].text)}}}],Fh={$setViewValue:E,$render:E},Gh=["$element","$scope",function(a,b){function d(){g||(g=!0,b.$$postDigest(function(){g=!1;e.ngModelCtrl.$render()}))}function c(a){k||(k=!0,b.$$postDigest(function(){b.$$destroyed||(k=!1,e.ngModelCtrl.$setViewValue(e.readValue()),a&&e.ngModelCtrl.$render())}))}var e=this,f=new Ib;e.selectValueMap=
340{};e.ngModelCtrl=Fh;e.multiple=!1;e.unknownOption=x(z.document.createElement("option"));e.hasEmptyOption=!1;e.emptyOption=void 0;e.renderUnknownOption=function(b){b=e.generateUnknownOptionValue(b);e.unknownOption.val(b);a.prepend(e.unknownOption);Oa(e.unknownOption,!0);a.val(b)};e.updateUnknownOption=function(b){b=e.generateUnknownOptionValue(b);e.unknownOption.val(b);Oa(e.unknownOption,!0);a.val(b)};e.generateUnknownOptionValue=function(a){return"? "+La(a)+" ?"};e.removeUnknownOption=function(){e.unknownOption.parent()&&
341e.unknownOption.remove()};e.selectEmptyOption=function(){e.emptyOption&&(a.val(""),Oa(e.emptyOption,!0))};e.unselectEmptyOption=function(){e.hasEmptyOption&&Oa(e.emptyOption,!1)};b.$on("$destroy",function(){e.renderUnknownOption=E});e.readValue=function(){var b=a.val(),b=b in e.selectValueMap?e.selectValueMap[b]:b;return e.hasOption(b)?b:null};e.writeValue=function(b){var c=a[0].options[a[0].selectedIndex];c&&Oa(x(c),!1);e.hasOption(b)?(e.removeUnknownOption(),c=La(b),a.val(c in e.selectValueMap?
342c:b),Oa(x(a[0].options[a[0].selectedIndex]),!0)):e.selectUnknownOrEmptyOption(b)};e.addOption=function(a,b){if(8!==b[0].nodeType){Ja(a,'"option value"');""===a&&(e.hasEmptyOption=!0,e.emptyOption=b);var c=f.get(a)||0;f.set(a,c+1);d()}};e.removeOption=function(a){var b=f.get(a);b&&(1===b?(f.delete(a),""===a&&(e.hasEmptyOption=!1,e.emptyOption=void 0)):f.set(a,b-1))};e.hasOption=function(a){return!!f.get(a)};e.$hasEmptyOption=function(){return e.hasEmptyOption};e.$isUnknownOptionSelected=function(){return a[0].options[0]===
343e.unknownOption[0]};e.$isEmptyOptionSelected=function(){return e.hasEmptyOption&&a[0].options[a[0].selectedIndex]===e.emptyOption[0]};e.selectUnknownOrEmptyOption=function(a){null==a&&e.emptyOption?(e.removeUnknownOption(),e.selectEmptyOption()):e.unknownOption.parent().length?e.updateUnknownOption(a):e.renderUnknownOption(a)};var g=!1,k=!1;e.registerOption=function(a,b,f,g,k){if(f.$attr.ngValue){var q,r;f.$observe("value",function(a){var d,f=b.prop("selected");w(r)&&(e.removeOption(q),delete e.selectValueMap[r],
344d=!0);r=La(a);q=a;e.selectValueMap[r]=a;e.addOption(a,b);b.attr("value",r);d&&f&&c()})}else g?f.$observe("value",function(a){e.readValue();var d,f=b.prop("selected");w(q)&&(e.removeOption(q),d=!0);q=a;e.addOption(a,b);d&&f&&c()}):k?a.$watch(k,function(a,d){f.$set("value",a);var g=b.prop("selected");d!==a&&e.removeOption(d);e.addOption(a,b);d&&g&&c()}):e.addOption(f.value,b);f.$observe("disabled",function(a){if("true"===a||a&&b.prop("selected"))e.multiple?c(!0):(e.ngModelCtrl.$setViewValue(null),e.ngModelCtrl.$render())});
345b.on("$destroy",function(){var a=e.readValue(),b=f.value;e.removeOption(b);d();(e.multiple&&a&&-1!==a.indexOf(b)||a===b)&&c(!0)})}}],Ue=function(){return{restrict:"E",require:["select","?ngModel"],controller:Gh,priority:1,link:{pre:function(a,b,d,c){var e=c[0],f=c[1];if(f){if(e.ngModelCtrl=f,b.on("change",function(){e.removeUnknownOption();a.$apply(function(){f.$setViewValue(e.readValue())})}),d.multiple){e.multiple=!0;e.readValue=function(){var a=[];r(b.find("option"),function(b){b.selected&&!b.disabled&&
346(b=b.value,a.push(b in e.selectValueMap?e.selectValueMap[b]:b))});return a};e.writeValue=function(a){r(b.find("option"),function(b){var c=!!a&&(-1!==Array.prototype.indexOf.call(a,b.value)||-1!==Array.prototype.indexOf.call(a,e.selectValueMap[b.value]));c!==b.selected&&Oa(x(b),c)})};var g,k=NaN;a.$watch(function(){k!==f.$viewValue||va(g,f.$viewValue)||(g=ja(f.$viewValue),f.$render());k=f.$viewValue});f.$isEmpty=function(a){return!a||0===a.length}}}else e.registerOption=E},post:function(a,b,d,c){var e=
347c[1];if(e){var f=c[0];e.$render=function(){f.writeValue(e.$viewValue)}}}}}},Ve=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(b,d){var c,e;w(d.ngValue)||(w(d.value)?c=a(d.value,!0):(e=a(b.text(),!0))||d.$set("value",b.text()));return function(a,b,d){var h=b.parent();(h=h.data("$selectController")||h.parent().data("$selectController"))&&h.registerOption(a,b,d,c,e)}}}}],bd=["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f=
348c.hasOwnProperty("required")||a(c.ngRequired)(b);c.ngRequired||(c.required=!0);e.$validators.required=function(a,b){return!f||!e.$isEmpty(b)};c.$observe("required",function(a){f!==a&&(f=a,e.$validate())})}}}}],ad=["$parse",function(a){return{restrict:"A",require:"?ngModel",compile:function(b,d){var c,e;d.ngPattern&&(c=d.ngPattern,e="/"===d.ngPattern.charAt(0)&&ke.test(d.ngPattern)?function(){return d.ngPattern}:a(d.ngPattern));return function(a,b,d,h){if(h){var l=d.pattern;d.ngPattern?l=e(a):c=d.pattern;
349var m=je(l,c,b);d.$observe("pattern",function(a){var d=m;m=je(a,c,b);(d&&d.toString())!==(m&&m.toString())&&h.$validate()});h.$validators.pattern=function(a,b){return h.$isEmpty(b)||A(m)||m.test(b)}}}}}}],dd=["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f=c.maxlength||a(c.ngMaxlength)(b),g=Ub(f);c.$observe("maxlength",function(a){f!==a&&(g=Ub(a),f=a,e.$validate())});e.$validators.maxlength=function(a,b){return 0>g||e.$isEmpty(b)||b.length<=g}}}}}],cd=
350["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f=c.minlength||a(c.ngMinlength)(b),g=Ub(f)||-1;c.$observe("minlength",function(a){f!==a&&(g=Ub(a)||-1,f=a,e.$validate())});e.$validators.minlength=function(a,b){return e.$isEmpty(b)||b.length>=g}}}}}];z.angular.bootstrap?z.console&&console.log("WARNING: Tried to load AngularJS more than once."):(Je(),Oe(ca),ca.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==
351b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),
352WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,
353c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),x(function(){Ee(z.document,Wc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(window.angular.element("<style>").text('@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}'));
354
355
356;
357/** include: angular.patch.js **/
358window.core&&core.registerScript&&core.registerScript('/public/angular/javascripts/angular.patch.js');
359!function(n){var a=[],r={},u=!1;$("body[ng-app]").length||($(document).ready(function(){angular.element(document).ready(function(){var n;if(r.core){for(;n=a.shift();)r.core.requires.push(n);angular.bootstrap($("body#admin-body")[0],["core"])}u=!0})}),angular.module=function(o,e){return e&&a.push(o),u?angular:r[o]=n.apply(null,arguments)})}(angular.module);
360;
361/** include: angular-cookies.min.js **/
362window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular-1.8.2/angular-cookies.min.js');
363/*
364 AngularJS v1.8.2
365 (c) 2010-2020 Google LLC. http://angularjs.org
366 License: MIT
367*/
368(function(n,e){'use strict';function m(d,k,l){var a=l.baseHref(),h=d[0];return function(f,b,c){var d,g;c=c||{};g=c.expires;d=e.isDefined(c.path)?c.path:a;e.isUndefined(b)&&(g="Thu, 01 Jan 1970 00:00:00 GMT",b="");e.isString(g)&&(g=new Date(g));b=encodeURIComponent(f)+"="+encodeURIComponent(b);b=b+(d?";path="+d:"")+(c.domain?";domain="+c.domain:"");b+=g?";expires="+g.toUTCString():"";b+=c.secure?";secure":"";b+=c.samesite?";samesite="+c.samesite:"";c=b.length+1;4096<c&&k.warn("Cookie '"+f+"' possibly not set or overflowed because it was too large ("+
369c+" > 4096 bytes)!");h.cookie=b}}e.module("ngCookies",["ng"]).info({angularVersion:"1.8.2"}).provider("$cookies",[function(){var d=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(k,l){return{get:function(a){return k()[a]},getObject:function(a){return(a=this.get(a))?e.fromJson(a):a},getAll:function(){return k()},put:function(a,h,f){l(a,h,f?e.extend({},d,f):d)},putObject:function(a,d,f){this.put(a,e.toJson(d),f)},remove:function(a,h){l(a,void 0,h?e.extend({},d,h):d)}}}]}]);m.$inject=
370["$document","$log","$browser"];e.module("ngCookies").provider("$$cookieWriter",function(){this.$get=m})})(window,window.angular);
371
372
373;
374/** include: angular.page.js **/
375window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.page.js');
376angular.module('page', [])
377.factory('$zIndex', function() {
378	return function(selector) {
379        Math.max(0, Math.max.apply(null, $.map(((selector || "*") === "*")? $.makeArray(document.getElementsByTagName("*")) : $(selector),
380            function (v) {
381                return parseFloat($(v).css("z-index")) || null;
382            }
383        )))+1;
384    }
385})
386.factory('$at', ['$http', function($http) {
387    var started,queue = [];
388    var $at = function(fn) {
389        if (CORE.AT) { return fn(CORE.AT) }
390        
391        queue.push(fn);
392        if (CORE.ATF && !started) {
393            var fn;
394            started = true;
395            $http.post(CORE.ATF).then(function(response) { 
396                CORE.AT = response.headers('X-XSRF-TOKEN');
397                $('input[name="authenticityToken"]').val(CORE.AT);
398                while (fn = queue.shift()) { fn(CORE.AT); }
399            }, function() {});
400        }
401    }
402
403    CORE.onTokenLoad = function() { 
404        for (var fn,i=0;(fn=arguments[i]);i++) $at(fn);
405    }
406    
407    return $at;
408}])
409.factory('$coreConfig', [ '$requester', '$q', function($requester, $q) {
410    let $json = $requester({ get: 'PageFront.getCoreConfig_JSON' });
411    
412    return function() {
413        let deferred = $q.defer();
414        let keys = [];
415
416        Object.values(arguments).forEach(key => keys.push(key));
417        
418        $json.get({ keys }, function(response) {
419            deferred.resolve(response);
420        });
421        
422        return deferred.promise;
423    }
424}])
425.factory('$navigation', [ '$requester', '$location', '$debounce', '$rootScope', function($requester, $location, $debounce, $rootScope) {
426    var scopes = { }
427    var byHandle = { }
428    var $json = $requester({ trail: 'PageFront.getTrail_JSON' });
429    var loaded = true;
430    var cache = {}
431    var install = function(handle, trail) {
432        byHandle[handle] && $.each(byHandle[handle], function(i, result) {
433            result.trail = trail.slice();
434            result.selected = trail.length && trail[trail.length - 1];
435        })
436    }
437    var fetch = function() { 
438        var allHandles = []; 
439        var fetchHandles = [];
440        var path = $location.path() || $location.absUrl().replace(/^https?:\/\/[^\/]+\//, '/');
441        
442        if (path) {
443            $.each(scopes, function(k, s) { allHandles = allHandles.concat(s.refHandles) })
444
445            $.each(allHandles, function(i, h) {
446                if ((cache[path] = cache[path] || {})[h]) {
447                    install(h, cache[path][h])
448                } else if (fetchHandles.indexOf(h) < 0) {
449                    fetchHandles.push(h);
450                }
451            })
452
453            loaded && fetchHandles.length && $json.trail({ handles: fetchHandles, currentUrl: path }, function(result){
454                $.each(result, function(handle, res) {
455                    (cache[path] = cache[path] || {})[handle] = res;
456                    install(handle, res);
457                })
458            })
459        }
460    }.$debounce(100);
461    
462    var removeWatcher = $rootScope.$watch(function() { return $location.path(); }, fetch);
463    var watcher = function(key) { return this[key] }
464    var service = function($scope, refHandles) {
465        var result = { }
466        
467        if (typeof(refHandles) == 'string') {
468            refHandles = [refHandles];
469        }
470        scopes[$scope.$id] = scopes[$scope.$id] || { scope: $scope, refHandles: refHandles, result: result }
471        $.each(refHandles, function(i, h) {
472            (byHandle[h] = byHandle[h] || []).push(result);
473        })
474        $scope.$on('$destroy', function() { delete scopes[$scope.$id]; })
475        fetch();
476        
477        result.$watch = function(key, fn) { return $scope.$watch(watcher.bind(result, key), fn); }
478        
479        return result;
480    }
481    var oldBeforeUnload = window.onbeforeunload;
482    
483    window.onbeforeunload = function() { loaded = false; removeWatcher && removeWatcher(); removeWatcher = null; oldBeforeUnload && oldBeforeUnload(); }
484
485    return service;
486}])
487.factory('$i18n', [ '$requester', '$timeout', '$q', function($requester, $timeout, $q) {
488    var queued = [], i18n = {};
489    var $json = $requester({ get: 'PageFront.i18n_JSON'});
490    
491    var service = function(str, p) {
492        var i=1,a,result = i18n[str];
493
494        if (!result) {
495            service.get(str);
496            result = i18n[str] = str;
497        }
498        for (i=1; i<arguments.length; i++) {
499            var undef;
500            if (undef != arguments[i]) {
501                result = result.replace('%s', arguments[i]);
502            }
503        }
504
505        return result;
506    }
507
508    service.promise = function(str) {
509        var q = $q.defer(), originals = {}, original = str;
510        
511        str = str.replace(/ +/g, '-')
512
513        if (i18n && i18n[str]) {
514            q.resolve(i18n[str]); 
515        } else {
516            if (queued.indexOf(str) < 0) {
517                service.get(str).then(function(result) {
518                    var value = result && result[0] && result[0].value || str;
519                    
520                    value = (value!=str)?value:original;
521                    
522                    i18n[str] = value || i18n[str] || str;
523                    
524                    q.resolve(value); 
525                })
526            }
527        } 
528
529        return q.promise;
530    }
531
532    var requestTimeoutId, qMap = {}, keyMap = {}, nextMapKey =0;
533    service.get = function() {
534        var q = $q.defer(), keys = [], mapKey = ++nextMapKey;
535        
536        qMap[mapKey] = q;
537
538        $.each(arguments, function(i, e) {
539            if (typeof(e) == 'string' && queued.indexOf(e) == -1) {
540                e = (e[e.length-1]=='.') ? e + '*' : e;
541
542                queued.push(e);
543                keyMap[e] = mapKey;
544            }
545        })
546        
547        requestTimeoutId && $timeout.cancel(requestTimeoutId);
548        
549        requestTimeoutId = $timeout(function() {
550            var fnIndex, items = queued,fn, key;
551
552            queued = [];
553            var qKeys = Object.keys(qMap);
554
555            if (items.length) $json.get({ i18n: items }, function(response) {
556                var qResults = {}
557                
558                $.each(response.i18n, function(k, v) { 
559                    var qKey = keyMap[k];
560                    
561                    (qResults[qKey] = qResults[qKey] || []).push({ key: k, value: v })
562                    i18n[k] = v; 
563                })
564                
565                while (key = qKeys.pop()) {
566                    if (qMap[key]) {
567                        qMap[key].resolve(qResults[key] || { key: key, value: key });
568
569                        delete qMap[key];
570                    }
571                }
572            }, null, true);
573            
574        }, 50);
575        
576        return q.promise;
577    }
578
579    service.set = function(defs) {
580        i18n = i18n||{}
581        $.each(defs, function(key, value) {
582            i18n[key] = value;
583        })
584    }
585
586    return service;
587}])
588
589
590.factory('$ajax', ['$rootScope', '$loading', '$at', '$timeout', function($rootScope, $loading, $at, $timeout) {
591   return function(url, data, success, error, background, loader) {
592            var on = { 
593                successFN: success,
594                errorFN: error,
595                success: function(fn) { this.successFN = fn; return this; },
596                error: function(fn) { this.errorFN = fn; return this; }
597            }
598            
599            var method = 'POST';
600            var headers = { 'Cache-Control': 'no-cache' }
601            var cache = false;
602            var installMenu = function(html) {
603                $('#admin-menu-holder').replaceWith($(html));
604            }
605            
606            if ($.isPlainObject(url)) {
607                method = url.method || 'POST';
608                if (method == 'GET') {
609                    delete headers['Cache-Control'];
610                    cache = true;
611                }
612
613                url = url.url;
614            }
615            if (CORE && CORE.getters && CORE.getters[url]) url = CORE.getters[url];
616            
617            var ajaxCall = function() {
618                var successFn = function(data, textStatus, jqXHR) {
619                        if (!background) $loading.clear(loader);
620                        if (jqXHR.getResponseHeader('REQUIRES_AUTH')==='1') return doLogin();
621                        else on.successFN&&on.successFN(data);
622                        $rootScope.$digest();
623                    }
624                    
625                if (!background) $loading(loader);
626                if (method != 'GET'||(data&&CORE&&CORE.requesterLegacyMode)) data.authenticityToken =  CORE.AT;
627
628                var sendData = (method == 'GET') ? { body: JSON.stringify(data) } : JSON.stringify(data);
629                
630                $.ajax({ cache: cache, headers:headers, contentType: "application/json", type: method, url: url, data: sendData, dataType: 'JSON',
631                    success: successFn,
632                    error: function(jqXHR, textStatus, errorThrown) { 
633                        if (textStatus == 'parsererror' && jqXHR.responseText[0] == '{') {
634                            try {
635                                var data = JSON.parse(jqXHR.responseText);
636                                successFn(data, "", jqXHR);
637                                return;
638                            } catch(e) { }
639                        }
640                        if (!background) {
641                            $loading.clear();
642                        }
643                        
644                        on.errorFN&&on.errorFN(jqXHR, textStatus, errorThrown);
645                        if (!on.errorFN) {
646                            console.log('XHR Error', textStatus, errorThrown)
647                        }
648                        $rootScope.$digest();
649                    }
650                })
651            }
652            
653            $at(ajaxCall);
654            
655            return on;
656        }
657}])
658
659.factory('$loading', ['$timeout', '$zIndex', function($timeout, $zIndex) {
660    var depth = 0, stage=0, 
661        ele= $('<div class="admin-loading"><img src="/public/images/spinner2.gif" /><div>').appendTo($('body')),
662        cloud = $('<div class="admin-loading-cloud"></div>'),
663        timeout,
664        clearAnimations = function() { ele.stop(true, false); cloud.stop(true, false); timeout = 0; },
665        defaultLoader = {
666            load: function() {
667                clearAnimations();
668                cloud.css({ opacity: 0 });
669                cloud.appendTo($('body'));
670                cloud.css('z-index', $zIndex());
671                ele.css('z-index', $zIndex());
672                ele.show();
673                stage = 0;
674
675                cloud.delay(500).animate({ opacity: .2 }, 500, 'swing');
676                ele.delay(500).animate({ left: -10 }, 500);
677            },
678            clear: function() {
679                clearAnimations();
680                ele.animate( { left: -50 }, 500, 'swing');
681                cloud.animate({ opacity: 0 }, 300, 'swing', function() { cloud.remove();  ele.css('z-index', 0); } )
682            },
683            depth: 0
684        }
685        
686        
687    
688    var result = function(loader) {
689        loader = loader || defaultLoader;
690        loader.depth = loader.depth||0;
691        
692        if (loader.depth++) return;
693        
694        if (loader && loader.load) loader.load();
695    }
696    
697    var clear = function(loader) {
698        loader = loader || defaultLoader;
699        loader.depth = loader.depth||0;
700
701        if (!loader.depth || (loader.depth = Math.max(loader.depth-1, 0))) return;
702        
703        if (loader && loader.clear) loader.clear();
704    }
705    
706    result.setDefaultLoader = function(loader) {
707        defaultLoader = loader || { }
708        
709        defaultLoader.load = defaultLoader.load || function() {}
710        defaultLoader.clear = defaultLoader.clear || function() {}
711        defaultLoader.depth = 0;
712    }
713    
714    result.clear = function(loader) { $timeout(function() { clear(loader); }); }
715
716    return result;
717}])
718
719
720.factory('$requester', ['$http', '$rootScope', '$timeout', '$ajax', '$q', function($http, $rootScope, $timeout, $ajax, $q) {
721    var timeoutId, allRoutes = { },
722        pending = { routes: {}, i18n: [] }, callbacks=[],
723        getRoutes = function(routes, i18n, callback) {
724            var cbKey = callbacks.length;
725            
726            callbacks.push(callback);
727            
728            for (var i,n;(n=(i18n||[])[i]);
728i++) pending.i18n.push(n);
729            
730            routes && $.each(routes, function(key, r) {
731                (pending.routes[r] = pending.routes[r] || []).push({ cbKey: cbKey, key: key });
732            })
733            
734            timeoutId && $timeout.cancel(timeoutId);
735            timeoutId = $timeout(function() {
736                var routes = {}, params = {}, i18n = pending.i18n, i=0, storedCallbacks = callbacks;
737                
738                $.each(pending.routes, function(r, cb) {
739                    routes[i] = r;
740                    params[i] = cb;
741                    i++;
742                })
743                
744                callbacks = [];
745                pending = { routes: {}, i18n: [] };
746                
747                $ajax(CORE.JI, { routes: routes, i18n: i18n }, function(response) {
748                    var index=0,i,p,calls = $.map(storedCallbacks, function(fn) { return { fn: fn, routes: { }, methods: { } } });
749
750                    response.i18n && $.each(response.i18n, function(k,v) { CORE.i18n[k] = v; } );
751
752                    $.each(response.routes, function(key, r) {
753                        allRoutes[routes[index]] = r;
754                        if (CORE.lang && CORE.lang != '') r = '/' + CORE.lang + r;
755                        if (params[key]) {
756                            for (i=0;(p=params[key][i]);i++) {
757                                calls[p.cbKey].routes[p.key] = r;
758                                calls[p.cbKey].methods[p.key] = (response.methods) ? response.methods[key] : 'POST';
759                            }
760                        }
761                    })
762                    
763                    for (i=0;(p=calls[i]);i++) p.fn({ routes: p.routes, methods: p.methods });
764                    
765                }, null, true);
766                
767            })
768        }
769    var counter = 0;
770    var premade = {},
771        create = function(routes, i18n, onReady) {
772            var service = { },
773                ready = false,
774                queue = [],
775                readyQueue = [],
776                routeRequest = {},
777                thisId = ++counter,
778                key,
779                loader,
780                methods = { },
781                inited;
782
783                onReady && readyQueue.push(onReady);
784                
785            var initServices = function(response) {
786                var rq, fn;
787                
788                routes = response.routes||[];
789                methods = response.methods||[]
790                ready = true;
791
792                for (key in response.i18n) {
793                    CORE.i18n[key] = response.i18n[key];
794                }
795
796                while (fn = readyQueue.shift()) {
797                    fn();
798                }
799
800                while (rq = queue.shift()) {
801                    callService.apply(this, rq);
802                }
803                
804                inited = true;
805            }
806
807            var callService = function(key, params, response, background) {
808                if (!ready) {
809                    return queue.push([key, params, response, background]);
810                }
811
812                params = params || {};
813
814                var route = routes[key]&&$ajax({ url: routes[key], method: methods[key]||'POST'}, params, 0, 0, background, response.loader||loader);
815                
816                route && route.success(function() { 
817                    response.success&&response.success.apply(this, arguments);
818                    $rootScope.$digest(); 
819                })
820                
821                if (response.failure) 
822                    route.error(function() { 
823                        response.failure.apply(this, arguments);
824                        $rootScope.$digest(); 
825                    });
826            }
827
828            var createService = function(key) {
829                return function(params, callback, background) { 
830                    var response = { success: callback }
831
832                    $timeout(function() { callService(key, params, response, background); });
833
834                    return { 
835                        onSuccess: function(fn) { response.success = fn; },
836                        onFailure: function(fn) { response.failure = fn; },
837                        setLoader: function(loader) { response.loader = loader; }
838                    }
839                }
840            }
841
842            var getRoute = function(key) {
843                return routes[key];
844            }
845            
846            var setLoader = function(ldr) {
847                loader = ldr;
848                return this;
849            }
850        
851        service.$getRoute = getRoute;
852        service.setLoader = setLoader;
853        
854        service.$onReady = service.onReady = function(fn) { if (ready) $timeout(fn); else readyQueue.push(fn); return service; };
855        
856        for (key in routes) {
857            service[key] = createService(key);
858            if (routes[key].indexOf('_JSON') < 0)
859                routes[key] = routes[key]+'_JSON';
860        } 
861        if (!inited) {
862            getRoutes(routes, i18n, initServices);
863        }
864
865        return service;
866    }
867    
868    var requesterService = function(routes, i18n, onReady, handle) {
869        if (handle && premade[handle]) {
870            return premade[handle];
871        }
872        
873        var service = create(routes, i18n, onReady, handle);
874        
875        if (handle) premade[handle] = service;
876        
877        return service;
878    }
879    
880        /**
881     * Determines a Play! route given the controller.method pair (e.g 'CorePages.list_JSON') as a jet
882     * It returns a promise, which will deliver the key / route pair in the fullness of time.
883     * 
884     * Unlike $json.$getRoute, if the route hasn't already been asked for, this will prompt the router
885     * to go and get it. If the call fails, it will reject the promise, and supply the key in the rejection
886     * @param {type} key
887     * @returns {.$q@call;defer.promise}
888     */
889    requesterService.getRoute = function(key) {
890        var deferred = $q.defer();
891        
892        if (allRoutes[key]) {
893            var result = {}
894            
895            result[key] = allRoutes[key];
896            
897            deferred.resolve(result);
898        } else {
899            var query = {}
900            
901            query[key] = key;
902            getRoutes(query, {}, function(routes) { 
903                if (routes.routes && routes.routes[key]) {        
904                    deferred.resolve(routes.routes);
905                } else {
906                    deferred.reject(key);
907                }
908            });
909        }
910        
911        return deferred.promise;
912    }
913
914    return requesterService;
915    
916}])
917
918
919/**
920 * @param {type} fn
921 * @returns {undefined}
922 * 
923 * Debouncer service leveraging $timeout. Will call the supplied function on a timeout, but if it's used again
924 * before the end of the timeout, cancels the first call and replaces it with the second.
925 * 
926 * Usage:
927 * 
928 * $debounce(myFn, 1000); // Use with a one second delay.
929 * $debounce(myFn, 'my-timeout-id', 500); // Use with a half second delay, and a custom timeout ID.
930 * $debounce(function() { myStuff(); }, 'my-timeout-id', 500);
931 * 
932 * Never do this:
933 * $debounce(function() { myStuff(); }, 500); - as the function is freshly created on each call, $debounce has no way of matching it with previous calls, and will fail to cancel earlier timeouts
934 * 
935 */
936.factory('$debounce', [ '$timeout', function($timeout) {
937    var timeouts = { }, nextId = 0;
938    
939    return function(fn) {
940        var timeoutPeriod = arguments[(arguments.length > 2) ? 2 : 1];
941        var id = (arguments.length == 3) ? arguments[1] : (fn._debounce_id=(fn._debounce_id || ('_db.auto.'+(++nextId))));
942        
943        timeouts[id] && $timeout.cancel(timeouts[id]);
944        timeouts[id] = $timeout(function() { fn(); delete timeouts[id]; }, timeoutPeriod);
945    }
946}])
947
948.factory('$session', function() {
949    var undef,fn = function(key, value) {
950        if (!window.sessionStorage) return null;
951        var result = window.sessionStorage.getItem(key);
952        
953        if (value !== undef) {
954            window.sessionStorage.setItem(key, JSON.stringify({ value: value }));
955        }
956        
957        return result && JSON.parse(result).value;
958    }
959    
960    fn.remove = function(key) {
961        window.sessionStorage && window.sessionStorage.removeItem(key);
962    }
963    return fn;
964})
965
966.factory('$data', function() {
967    var values = {};
968    
969    return {
970        set: function(handle, value) {
971            values[handle] = value;
972        },
973        get: function(handle, defaultValue) {
974            if (typeof(values[handle])=='undefined') return defaultValue;
975            return values[handle];
976        }
977    }
978})
979.filter('html', ['$sce', function ($sce) {
980    return function (val) {
981        return $sce.trustAsHtml(val);
982    };
983}])
984.directive('filterFn', ['$parse', function($parse) {
985    return {
986        restrict: 'A',
987        required: 'ng-model',
988        link: function($scope, ele, attrs) {
989            var modelGetter = $parse(attrs.ngModel),
990                fnGetter = $parse(attrs.filterFn);
991            
992            $scope.$watch(attrs.ngModel, function(val, old) {
993                var undef;
994                
995                if (val != undef && val != old) {
996                    modelGetter.assign($scope, fnGetter($scope)(val, old));
997                }
998            })
999        }
1000    }
1001
1002}])
1003
1004.directive('ngBindHtmlUnsafe', function() {
1005  return function(scope, element, attr) {
1006    element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe);
1007    scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) {
1008      element.html(value || '');
1009    });
1010  };
1011})
1012
1013/*
1014This directive allows us to pass a function in on an enter key to do what we want.
1015 */
1016.directive('ngEnter', function () {
1017    return function (scope, element, attrs) {
1018        element.bind("keydown keypress", function (event) {
1019            if(event.which === 13) {
1020                scope.$apply(function (){
1021                    scope.$eval(attrs.ngEnter);
1022                });
1023 
1024                event.preventDefault();
1025            }
1026        });
1027    };
1028})
1029
1030.directive('simpleTemplate', [ '$compile', '$parse', '$templateCache', function($compile, $parse, $templateCache) {
1031    return {
1032        link: function($scope, ele, attrs) {
1033            var id = $parse(attrs.rugsMenuTemplate)($scope);
1034            var t = $templateCache.get(id);
1035
1036            $compile(t)($scope, function(clone, scope) { ele.append(clone); });
1037        }
1038    }
1039}])
1040
1041.directive('ngBindI18n', [ '$i18n', function($i18n) {
1042    return {
1043        link: function($scope, $ele, $attrs) {
1044            $i18n.promise($attrs.ngBindI18n).then(function(str) {
1045                $ele.html(str);
1046            })
1047        }
1048    }
1049}])
1050/**
1051 * This is a slightly dirty hack, to get around the fact that a base href of 
1052 * /products/ 
1053 * will not be happy with a url of
1054 * /products
1055 * 
1056 * The Angular guys have marked this bug as "won't fix" - so we're tapping into Things With Which We Shouldn't Mess With.
1057 * To wit: $browser
1058 * 
1059 * Watch out for this one - it could easily break in a future Angular release.
1060 */
1061.config(['$provide', function($provide) {
1062    $provide.decorator('$browser', [ '$delegate', function($delegate) {
1063        var baseHrefFn = $delegate.baseHref;
1064        
1065        $delegate.baseHref = function() {
1066            var result = baseHrefFn.apply($delegate, arguments);
1067            var url = $delegate.url().replace(/^(https?:)?\/\/[^/]*(.*)\?.*/, '$2');
1068            
1069            return (url + '/' == result) ? url : result;
1070        }
1071
1072        return $delegate;
1073    }])
1074}])
1075
1076/**
1077 * @param {type} fn
1078 * @returns {undefined}
1079 * 
1080 * Debouncer service leveraging $timeout. Will call the supplied function on a timeout, but if it's used again
1081 * before the end of the timeout, cancels the first call and replaces it with the second.
1082 * 
1083 * Usage:
1084 * 
1085 * $debounce(myFn, 1000); // Use with a one second delay.
1086 * $debounce(myFn, 'my-timeout-id', 500); // Use with a half second delay, and a custom timeout ID.
1087 * $debounce(function() { myStuff(); }, 'my-timeout-id', 500);
1088 * $debounce(true, myFn, 1000) // return a debounced version of myFn. Call the result with whatever args you like, and they will be passed to myFn when it runs. 
1089 * 
1090 * and for the piece de resistence - if you've included the service in a controller or some such, you have this:
1091 * myFunc.$debounce(1000)
1092 *    this one applies to *all* functions.
1093 * 
1094 * Never do this:
1095 * $debounce(function() { myStuff(); }, 500); - as the function is freshly created on each call, $debounce has no way of matching it with previous calls, and will fail to cancel earlier timeouts
1096 * 
1097 */
1098.factory('$debounce', [ '$timeout', function($timeout) {
1099    var timeouts = { }, nextId = 0;
1100    
1101    var makeDebounceId = function(fn) {
1102        return fn._debounce_id = fn._debounce_id || ('_db.auto.'+(++nextId));
1103    }
1104
1105    var createFunction = function(fn, timeout) {
1106        makeDebounceId(fn);
1107        
1108        return function() {
1109            var i,a = [];
1110            
1111            for (i=0;i<arguments.length;i++) {
1112                a.push(arguments[i]);
1113            }
1114
1115            service(function() { fn.apply(this, a); }, fn._debounce_id, timeout)
1116        }
1117    }
1118    var service = function(fn) {
1119        if (fn === true) {
1120            var i,args = [];
1121            
1122            for (i=1;i<arguments.length;i++) {
1123                args.push(arguments[i])
1124            }
1125            return createFunction.apply(this, args)
1126        }
1127        var timeoutPeriod = arguments[(arguments.length > 2) ? 2 : 1];
1128        var id = (typeof(arguments[1]) != 'number') ? arguments[1] : makeDebounceId(fn);
1129        
1130        timeouts[id] && $timeout.cancel(timeouts[id]);
1131        timeouts[id] = $timeout(function() { fn(); delete timeouts[id]; }, timeoutPeriod);
1132    }
1133    
1134    service.fn = function(fn, a, b) {
1135        return service.bind(service, fn, a, b);
1136    }
1137    
1138    Function.prototype.$debounce = function(a) {
1139        return service(true, this, a);
1140    }
1141    
1142    return service;
1143}])
1144$(document).ready((function(a){a.topZIndex=function(b){return Math.max(0,Math.max.apply(null,a.map((b||"*")==="*"?a.makeArray(document.getElementsByTagName("*")):a(b),function(b){return parseFloat(a(b).css("z-index"))||null})))};a.fn.topZIndex=function(b){if(this.length===0)return this;b=a.extend({increment:1},b);var c=a.topZIndex(b.selector),d=b.increment;return this.each(function(){this.style.zIndex=c+=d})}})(jQuery));
1145
1146;
1147/** include: angular.menus.js **/
1148window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.menus.js');
1149var menus = angular.module('menus', []);
1150
1151menus.service('siteNavigation', function () {
1152    var linkIds = null;
1153    var selected = null;
1154    var selectedWatcher = null;
1155
1156    this.setLinkIds = function (val) {
1157        linkIds = val;
1158        if (angular.isArray(linkIds) && linkIds.length) {
1159            this.setSelected(linkIds[linkIds.length - 1]);
1160        }
1161    };
1162
1163    this.getLinkIds = function () {
1164        return linkIds;
1165    };
1166
1167    this.setSelected = function (val) {
1168        selected = val;
1169        (selectedWatcher || angular.noop)(val);
1170    };
1171
1172    this.getSelected = function () {
1173        return selected;
1174    };
1175
1176    this.watchSelected = function (fn) {
1177        selectedWatcher = fn;
1178    };
1179});
1180
1181menus.controller('mainMenuCTRL', [
1182    '$timeout',
1183    function ($timeout) {
1184        var vm = this;
1185
1186        var linkIds = [];
1187
1188        vm.getLinkClass = function (id) {
1189            return linkIds.indexOf(id) >= 0 ? 'selected' : '';
1190        };
1191
1192        vm.init = function (handle, trail, refHandles, linkCount) {
1193            linkIds = trail.map(function (t) {
1194                return t.id;
1195            });
1196            
1197            //initialise the array to keep track of what submenu's are open
1198            vm.showSubmenu = new Array(linkCount);
1199        };
1200
1201        vm.toggleBurger = function (show) {
1202            vm.burgerOpen = show || !vm.burgerOpen;
1203            if (vm.burgerOpen) {
1204                console.log(angular.element('nav#wpd-main-navigation-wrapper'));
1205                $timeout(function () {
1206                    angular
1207                        .element('nav#wpd-main-navigation-wrapper')
1208                        .eq(0)
1209                        .focus();
1210                });
1211            }
1212            if (!show) {
1213                vm.closeSearch();
1214            }
1215        };
1216
1217        vm.toggleSearch = function (show) {
1218            vm.searchOpen = show || !vm.searchOpen;
1219            if (vm.searchOpen) {
1220                $timeout(function () {
1221                    angular
1222                        .element('input#search')
1223                        .eq(0)
1224                        .focus();
1225                });
1226            }
1227            if (!show) {
1228                vm.closeBurger();
1229            }
1230        };
1231
1232        vm.closeBurger = function () {
1233            vm.burgerOpen = false;
1234        };
1235
1236        vm.closeSearch = function () {
1237            vm.searchOpen = false;
1238        };
1239
1240        vm.burgerOpen = false;
1241        vm.searchOpen = false;
1242        
1243        //On mouseover/mouseleave close any toggled submenus
1244        //The toggle is primarily for keyboard users
1245        //mask this functionality for mouse users
1246        //This does not interfere with the css based hover functionality of the submenus
1247        vm.closeAllToggledMenus = function()
1248        {
1249        	untoggleSubmenus(-1);
1250        };
1251        
1252        //Boolean array to control toggled visibility of the megamenu submenus
1253        //Additional control for use with the chevrons (down triangles) - default hover behaviour untouched
1254        vm.showSubmenu = []; 
1255        
1256        //Method for toggling the submenu on the main megamenu
1257        vm.toggleSubmenu = function(linkIndex)
1258        {            
1259            //Toggle the boolean
1260            vm.showSubmenu[linkIndex] = !vm.showSubmenu[linkIndex];
1261            
1262            //Belt and braces
1263            //Set all other values to false
1264            //Incase we're jumping to other menu items without nicely untoggling the existing one
1265            untoggleSubmenus(linkIndex);
1266        };
1267        
1268        //Boolean array to control toggled visibility of the megamenu submenus
1269        //Additional control for use with the chevrons (down triangles) - default hover behaviour untouched
1270        vm.hoverSubmenu = function(linkIndex)
1271        {    
1272            //This is a hover event - which we do not want to happen on mobile devices
1273            //Problem occurs because IOS translates mouseover events into clicks
1274            //So the user clicks and gets a mouseover event - which is undesireable
1275            if (!iOS())
1276            {
1277                //Toggle the boolean
1278                vm.showSubmenu[linkIndex] = true;
1279
1280                //Belt and braces
1281                //Set all other values to false
1282                //Incase we're jumping to other menu items without nicely untoggling the existing one
1283                untoggleSubmenus(linkIndex);
1284            }
1285        };
1286        
1287        //Is this an IOS device?
1288        function iOS() {
1289            return [
1290              'iPad Simulator',
1291              'iPhone Simulator',
1292              'iPod Simulator',
1293              'iPad',
1294              'iPhone',
1295              'iPod'
1296            ].includes(navigator.platform)
1297            // iPad on iOS 13 detection
1298            || (navigator.userAgent.includes("Mac") && "ontouchend" in document)
1299          }
1300        
1301        
1302        //Untoggle all submenu items except "skipindex".
1303        var untoggleSubmenus = function(skipIndex)
1304        {
1305           var i;
1306            for (i = 0; i < vm.showSubmenu.length; i++) {
1307              if (i !== skipIndex)
1308              {
1309                  vm.showSubmenu[i] = false;
1310              }
1311            }  
1312        };
1313    }
1314]);
1315
1316/**
1317 * Sidebar controller that is displayed on each child page
1318 * responsible for setting active item and controlling menu
1319 * visibility
1320 * @author keiranc, lewisl
1321 */
1322menus.controller('sidebarCTRL', [
1323    'siteNavigation',
1324    function (siteNavigation) {
1325        var vm = this;
1326
1327        vm.open = false;
1328
1329        /**
1330         * Toggles menu open or closed
1331         */
1332        vm.toggleOpen = function () {
1333            vm.open = !vm.open;
1334        };
1335
1336        /**
1337         * Initialiser function called in the view
1338         * @param {String} handle the handle of the navigation menu
1339         * @param {Array} trail list of navigation objects in the trail
1340         */
1341        vm.init = function (handle, trail) {
1342            siteNavigation.setLinkIds(
1343                trail.map(function (t) {
1344                    return t.id;
1345                })
1346            );
1347        };
1348
1349        /**
1350         * Used in view to determine selected link
1351         * @param {*} id the id of the link
1352         */
1353        vm.getLinkClass = function (id) {
1354            var linkIds = siteNavigation.getLinkIds();
1355            if (!linkIds.length) return;
1356            if (linkIds[linkIds.length - 1] === id) {
1357                return 'current';
1358            }
1359            return '';
1360        };
1361    }
1362]);
1363
1364/**
1365 * Used to control the 'section hints' at the bottom of content section pages
1366 * @author lewisl
1367 */
1368menus.controller('sectionHintCTRL', [
1369    '$timeout',
1370    'siteNavigation',
1371    function ($timeout, siteNavigation) {
1372        var vm = this;
1373
1374        var prevLink, nextLink;
1375        var $ = angular.element;
1376        var sidebarLinks = $('.sidebar-menu li');
1377        var heading = $('.content-sidebar h2');
1378
1379        vm.initLinks = function (data, headingData) {
1380            // If we get supplied some data relating to the nav structure
1381            // (i.e. being used as a page.navigation item) then we can
1382            // reconstruct the DOM structure this page hints functionality
1383            // relies on to save us rewriting it (for now...)
1384            if (data && data.length) {
1385                var sidebarEl = $('<ul class="sidebar-menu" />');
1386                sidebarEl.css({"display": "none"});
1387                $('body').append(sidebarEl);
1388                data.forEach(function (link) {
1389                    var currentLink = $('<li />');
1390                    currentLink.attr('id', 'sidebar-link-' + link.targetId);
1391                    currentLink.append('<a href="' + link.fullURL + '">' + link.linkTitle + '</a>');
1392                    sidebarEl.append(currentLink);
1393
1394                    if (link.isSelected) {
1395                        siteNavigation.setSelected(link.targetId);
1396                    }
1397                });
1398                sidebarLinks = sidebarEl.find('li');
1399            }
1400            if (headingData) {
1401                var headingEl = $("<h2 />");
1402                headingEl.css({"display": "none"});
1403                headingEl.attr('id', 'section-heading-' + headingData.targetId);
1404                if (headingData.isSelected) {
1405                    headingEl.addClass('selected');
1406                }
1407                headingEl.append('<a href="' + headingData.fullURL + '">' + headingData.linkTitle + '</a>');
1408                $('body').append(headingEl);
1409                heading = headingEl;
1410            }
1411        };
1412
1413        var getPrevTerm = function (selected) {
1414            var prev = $('#sidebar-link-' + selected).prev();
1415            if (prev.length === 0) {
1416                return 'Overview';
1417            }
1418            if (prev.index() === 0) {
1419                return 'First';
1420            }
1421            return 'Previous';
1422        };
1423
1424        var getNextTerm = function (selected) {
1425            if (heading.filter('.selected').length) {
1426                return 'First';
1427            }
1428            var next = $('#sidebar-link-' + selected).next();
1429            if (next.index() === sidebarLinks.length - 1) {
1430                return 'Final';
1431            }
1432            return 'Next';
1433        };
1434
1435        var getPrevLink = function (selected) {
1436            if (prevLink) {
1437                return prevLink;
1438            }
1439
1440            var prev = $('#sidebar-link-' + selected).prev();
1441
1442            if (!prev.length) {
1443                prev = $('.sidebar-menu li.current').prev();
1444            }
1445
1446            if (prev && prev.length) {
1447                prevLink = {
1448                    text: prev.text(),
1449                    href: prev.find('a').attr('href')
1450                };
1451                return prevLink;
1452            }
1453
1454            if (heading.length && !heading.hasClass('selected')) {
1455                var prevElement = heading.eq(0);
1456                prevLink = {
1457                    text: prevElement.text(),
1458                    href: prevElement.find('a').attr('href')
1459                };
1460                return prevLink;
1461            }
1462            return false;
1463        };
1464
1465        var getNextLink = function (selected) {
1466            if (nextLink) {
1467                return nextLink;
1468            }
1469
1470            if (heading.filter('.selected').length) {
1471                var next = sidebarLinks.eq(0);
1472                nextLink = {
1473                    text: next.text(),
1474                    href: next.find('a').attr('href')
1475                };
1476                return nextLink;
1477            }
1478
1479            var nextElement = $('#sidebar-link-' + selected).next();
1480            if (nextElement && nextElement.length) {
1481                nextLink = {
1482                    text: nextElement.text(),
1483                    href: nextElement.find('a').attr('href')
1484                };
1485                return nextLink;
1486            }
1487            return false;
1488        };
1489
1490        siteNavigation.watchSelected(function (selected) {
1491            vm.sectionHeading =
1492                $('[id^=section-heading-]')
1493                    .first()
1494                    .text()
1495                    .trim() || 'this section';
1496
1497            $timeout(function () {
1498                vm.prevTerm = getPrevTerm(selected);
1499                vm.nextTerm = getNextTerm(selected);
1500                vm.prevLink = getPrevLink(selected);
1501                vm.nextLink = getNextLink(selected);
1502            }, 0);
1503
1504        });
1505    }
1506]);
1507
1508menus.controller('socialIconsCtrl', [
1509    '$scope', '$templateCache', function ($scope, $templateCache) {
1510        $scope.openDialog = function ($event) {
1511            $event.preventDefault();
1512
1513            // Gets the template from the templateCache.
1514            var template = $templateCache.get('whatsapp-dialog-template.html');
1515
1516            // Checks if the template is legit and no broken, or empty, or null, or undefined,
1517            // I think you got the point :D Leave now, have a great day.
1518            if (!template) {
1519                console.error('Something got wrong with the template "whatsapp-dialog-template.html". Keep calm and... exterminate, exterminate...');
1520
1521                return;
1522            }
1523
1524            // Opens the dialog, it's that simple.
1525            vex.dialog.open({
1526                input: template,
1527                contentClassName: 'whatsapp-dialog',
1528                buttons: [],
1529                showCloseButton: true
1530            });
1531        };
1532    }
1533]);
1534
1535;
1536/** include: angular.wpd-cookie-notice.js **/
1537window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpd-cookie-notice.js');
1538/**
1539 * AngularJS Cookie notice module
1540 * @author keiranc
1541 */
1542var wpdCookieNotice = angular.module('wpd-cookie-notice', ['ngCookies']);
1543
1544wpdCookieNotice.service('CookieNotice', [
1545    '$cookies',
1546    '$rootScope',
1547    function($cookies, $rootScope) {
1548        /**
1549         * Gets the current cookie level cookie
1550         * @param {String} cookieName the name of the cookie
1551         * @returns {Object} the current cookie
1552         */
1553        this.getCurrentCookie = function(cookieName) {
1554            return $cookies.getObject(cookieName);
1555        };
1556
1557        /**
1558         * Gets the allowed cookie level
1559         * @param {String} cookieName the name of the cookie
1560         * @returns {Number} the current cookie level
1561         */
1562        this.getAllowedCookieLevel = function(cookieName) {
1563            var currentCookie = this.getCurrentCookie(cookieName);
1564            return currentCookie ? currentCookie : 0;
1565        };
1566
1567        /**
1568         * Checks whether a certain level is allowed
1569         * @param {String} cookieName the name of the cookie
1570         * @param {Number} level the level to match
1571         * @returns {Boolean} whether that level is allowed
1572         */
1573        this.isAllowedAt = function(cookieName, level) {
1574            var cookie = this.getCurrentCookie(cookieName);
1575            if (cookie === null || !angular.isDefined(cookie)) return false;
1576            return parseInt(cookie) >= level;
1577        };
1578
1579        /**
1580         * Accepts a certain level of cookies
1581         * @param {String} cookieName the name of the cookie
1582         * @param {Number} level level to accept
1583         * @param {String} domain the domain to set the cookie for
1584         * @param {Date|undefined} expires
1585         */
1586        this.accept = function(cookieName, level, domain, expires) {
1587            if (expires === undefined) {
1588                expires = new Date();
1589                expires.setFullYear(expires.getFullYear() + 1);
1590            }
1591
1592            $cookies.putObject(cookieName, level, {
1593                path: '/',
1594                domain: domain,
1595                expires: expires
1596            });
1597            $rootScope.$broadcast('cookie.notice.accepted');
1598        };
1599    }
1600]);
1601
1602wpdCookieNotice.controller('CookieNoticeController', [
1603    'CookieNotice',
1604    '$scope',
1605    function(CookieNotice, $scope) {
1606        var vm = this;
1607
1608        vm.visible = false;
1609
1610        /**
1611         * Called by angular button click
1612         * uses CookieNotice service to accept cookie notice
1613         * @param {Number} level the level to accept
1614         */
1615        vm.accept = function() {
1616            vm.visible = false;
1617
1618            if (vm.disableautohide) {
1619                CookieNotice.accept(vm.key, vm.level, vm.domain, vm.expires);
1620            }
1621        };
1622
1623        vm.checkAll = function() {
1624          for(key in vm.ticked) {
1625
1626              vm.ticked[key] = true;
1627          }
1628
1629          this.accept();
1630
1631        };
1632
1633        // accept default level
1634        vm.init = function() {
1635            if (!vm.cmsMode && !CookieNotice.isAllowedAt(vm.key, vm.level)) {
1636                vm.visible = true;
1637            }
1638
1639            if (isNaN(vm.expires)) {
1640                vm.expires = undefined;
1641            } else {
1642                var expiresDate = new Date();
1643                expiresDate.setHours(expiresDate.getHours() + vm.expires);
1644
1645                vm.expires = expiresDate;
1646            }
1647
1648            if (!vm.disableautohide) {
1649                CookieNotice.accept(vm.key, vm.level, vm.domain, vm.expires);
1650            }
1651        };
1652
1653        // broadcast event when visibility changes
1654        $scope.$watch('vm.visible', function () {
1655            $scope.$emit('header.height.changed');
1656        });
1657    }
1658]);
1659
1660wpdCookieNotice.directive('wpdCookieNotice', [
1661    function() {
1662        return {
1663            scope: {
1664                domain: '@',
1665                level: '<',
1666                key: '@',
1667                expires: '<?',
1668                disableautohide: '<?',
1669                cmsMode: '=mode'
1670            },
1671            controllerAs: 'vm',
1672            bindToController: true,
1673            controller: 'CookieNoticeController',
1674            transclude: true,
1675            template:
1676                '<div data-ng-show="vm.visible" data-ng-init="vm.init(
1676)">\
1677                        <ng-transclude></ng-transclude> \
1678                    </div>'
1679        };
1680    }
1681]);
1682;
1683/** include: angular.wpdrecitemeReciteMe.js **/
1684window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpdrecitemeReciteMe.js');
1685/**
1686 * There's still some cur
1687 *
1688 * AngularJS recite-me module
1689 * @author keiranc, noahm
1690 */
1691var wpdrecitemeReciteMeModule = angular.module('wpdrecitemeReciteMe', ['ngCookies']);
1692
1693// this is a provider and not a config as when
1694// this file is executed, Recite js has not loaded
1695// but when this provider is initialized, Recite has
1696wpdrecitemeReciteMeModule.provider('wpdrecitemeReciteMeConfig', [
1697    function() {
1698        var storageEngine = window.Recite ? window.Recite.Storage.Api : {};     // cannot inject $window, hence use of window
1699        return {
1700            $get: function() {
1701                return {
1702                    autoEnable: false,
1703                    Tracker: {
1704                        Ga: {
1705                            enabled: true
1706                        }
1707                    },
1708                    Preferences: {
1709                        storageEngine: storageEngine
1710                    },
1711                    Parser: {
1712                        Html: {
1713                            skipElements: [
1714                                'INPUT',
1715                                'SCRIPT',
1716                                'STYLE',
1717                                'SELECT',
1718                                'OPTION',
1719                                'IMG',
1720                                'FRAMESET',
1721                                'FRAME',
1722                                'IFRAME',
1723                                'NOSCRIPT',
1724                                'TEXTAREA'
1725                            ]
1726                        }
1727                    },
1728                    Language: {
1729                        Translate: {
1730                            disallowedTags: [
1731                                'SCRIPT',
1732                                'STYLE',
1733                                'INPUT',
1734                                'TEXTAREA',
1735                                'SELECT'
1736                            ]
1737                        }
1738                    }
1739                };
1740            }
1741        };
1742    }
1743]);
1744
1745// directive for any object that wants access to ReciteMeLangController
1746wpdrecitemeReciteMeModule.directive('wpdrecitemeReciteMeToolbarButton', [
1747    function() {
1748        return {
1749            restrict: 'A',
1750            scope: true,            // good to know: you cannot have an isolate scope bound to a directive which does not have a template or templateUrl!
1751            controllerAs: 'vm',
1752            controller: [
1753                'wpdrecitemeReciteMeInitService',
1754                '$window',
1755                '$cookies',
1756                function(ReciteMeInitService, $window, $cookies) {
1757                    var vm = this;
1758
1759                    /**
1760                    * @return {undefined}
1761                    */
1762                    vm.enableToolbar = function() {
1763                        ReciteMeInitService.init().then(() => {
1764                            if ($window.Recite) {
1765                                // FYI this calls Recite.Language.Translate.init() too
1766                                $window.Recite.enable();
1767                            }
1768                        });
1769                    };
1770
1771                    // do toolbar enable straight away if our cookies say so
1772                    var recitePersist = $cookies.get("Recite.Persist");
1773                    if (recitePersist == "true"){
1774                        vm.enableToolbar();
1775                    }
1776                }
1777            ]
1778        };
1779    }
1780]);
1781
1782// directive for a language changer button
1783wpdrecitemeReciteMeModule.directive('wpdrecitemeReciteMeLanguageButton', [
1784    function() {
1785        return {
1786            restrict: 'A',
1787            scope: true,
1788            controllerAs: 'vm',
1789            controller: [
1790                'wpdrecitemeReciteMe_RECITE_ME_LANGUAGES',
1791                'wpdrecitemeReciteMeInitService',
1792                'wpdrecitemeReciteMeLanguageService',
1793                '$timeout',
1794                '$cookies',
1795                function(RECITE_ME_LANGUAGES, ReciteMeInitService, ReciteMeLanguageService, $timeout, $cookies) {
1796                    var vm = this;
1797
1798                    /**
1799                    * Previously `vm.getNextLanguage`
1800                    *
1801                    * @return {string} - the name of the next language to be switched to
1802                    */
1803                    vm.getNextLanguageName = function() {
1804                        if (!ReciteMeLanguageService.currentLanguageIsEnglish()) {
1805                            return RECITE_ME_LANGUAGES['en'].name;
1806                        } else {
1807                            return RECITE_ME_LANGUAGES.cy.name;
1808                        }
1809                    };
1810
1811                    /**
1812                    * @return {undefined}
1813                    */
1814                    vm.switchLanguage = function() {
1815                        ReciteMeInitService.init().then(() => {
1816                            $timeout(function() {
1817                                ReciteMeLanguageService.switchLanguage();
1818                            });
1819                        });
1820                    };
1821
1822                    // do change to Welsh straight away if our cookies say so
1823                    var welshPersist = $cookies.get("Recite.Welsh");
1824                    if (welshPersist == "true"){
1825                        ReciteMeInitService.init();
1826                    }
1827                }
1828            ]
1829        };
1830    }
1831]);
1832
1833/**
1834* @constant
1835*/
1836wpdrecitemeReciteMeModule.constant('wpdrecitemeReciteMe_RECITE_ME_LANGUAGES', {
1837    'en': {
1838        code: 'en',
1839        name: 'English'
1840    },
1841    'cy': {
1842        code: 'cy',
1843        name: 'Cymraeg'
1844    }
1845});
1846
1847/**
1848* @constant
1849*/
1850wpdrecitemeReciteMeModule.constant('wpdrecitemeReciteMe_RECITE_ME_ENDPOINT', "https://api.reciteme.com/asset/js?key=");
1851
1852/**
1853* @service
1854*
1855* A service concerned with managing the current language being used in the page
1856* @author kieranc, noahm
1857*/
1858wpdrecitemeReciteMeModule.service('wpdrecitemeReciteMeLanguageService', [
1859    'wpdrecitemeReciteMeConfig',
1860    'wpdrecitemeReciteMe_RECITE_ME_LANGUAGES',
1861    'wpdrecitemeReciteMe_RECITE_ME_ENDPOINT',
1862    '$rootScope',
1863    '$timeout',
1864    '$window',
1865    '$cookies',
1866    function(RECITE_ME_CONFIG, RECITE_ME_LANGUAGES, RECITE_ME_ENDPOINT, $rootScope, $timeout, $window, $cookies) {
1867        var service = this;
1868
1869        // @private
1870        var _currentLanguage = RECITE_ME_LANGUAGES['en'];
1871
1872        /**
1873        * @param {object} o
1874        * @param {boolean} andApply - if true will also apply this language setting to the ReciteMe preferences
1875        * @return {undefined}
1876        */
1877        service.setCurrentLanguage = function(o, andApply) {
1878            _currentLanguage = o && typeof o === 'object' && o.code ? o : RECITE_ME_LANGUAGES['en'];
1879            if (andApply) {
1880                if ($window.Recite) {
1881                    var newCode = _currentLanguage.code || 'en';
1882                    $window.Recite.Preferences.set('language', newCode);
1883                }
1884                else {
1885                    console.warn("Could not apply the language preference because window.Recite is unavailable");
1886                }
1887            }
1888        };
1889
1890        /**
1891        * @return {object}
1892        */
1893        service.getCurrentLanguage = function() {
1894            return _currentLanguage;
1895        };
1896
1897        /**
1898        * @return {boolean} - true if the `_currentLanguage` is an English variant (en, en-gb, en-us, etc.)
1899        */
1900        service.currentLanguageIsEnglish = function() {
1901            return _currentLanguage.code && _currentLanguage.code.startsWith('en');
1902        };
1903
1904        /**
1905        * Triggers a translation of all the text in the page to the specified language, also setting a cookie indicating if the
1906        *   new language is Welsh (so that this may trigger an automatic translate on page navigation/refresh)
1907        *
1908        * @author kieranc, noahm
1909        *
1910        * @param {string} language - the language code for the language you wish to translate the page to
1911        * @return {undefined}
1912        */
1913        service.translate = function(language) {
1914            if ($window.Recite) {
1915                if (_currentLanguage.code === language) return;
1916
1917                $window.Recite.Storage.Cookie.save("Recite.Welsh", language === 'cy', 365);
1918
1919                service.setCurrentLanguage({ code: language }, true);
1920                $window.Recite.Language.Translate.translate(document.body, language);
1921            }
1922            else {
1923                console.warn('window.Recite is not loaded!');
1924            }
1925        };
1926
1927        /**
1928        * Changes over the `_currentLanguage` from English to Welsh, and then triggers a translation of the page
1929        *
1930        * @author kieranc
1931        *
1932        * @return {undefined}
1933        */
1934        service.switchLanguage = function() {
1935            if ($window.Recite) {
1936                var newCode = RECITE_ME_LANGUAGES['en'].code;
1937
1938                if (service.currentLanguageIsEnglish()) {
1939                    newCode = RECITE_ME_LANGUAGES['cy'].code;
1940                }
1941
1942                // translate will update _currentLanguage and the ReciteMe preferences
1943                service.translate(newCode);
1944            }
1945            else {
1946                console.warn('window.Recite is not loaded!');
1947            }
1948        }
1949    }
1950]);
1951
1952/**
1953* @service
1954*
1955* A service concerned with the initial one-time load and setup of the ReciteMe script
1956* @author kieranc, noahm
1957*/
1958wpdrecitemeReciteMeModule.service('wpdrecitemeReciteMeInitService', [
1959    'wpdrecitemeReciteMeConfig',
1960    'wpdrecitemeReciteMe_RECITE_ME_LANGUAGES',
1961    'wpdrecitemeReciteMe_RECITE_ME_ENDPOINT',
1962    'wpdrecitemeReciteMeLanguageService',
1963    '$rootScope',
1964    '$timeout',
1965    '$window',
1966    '$cookies',
1967    function(RECITE_ME_CONFIG, RECITE_ME_LANGUAGES, RECITE_ME_ENDPOINT, ReciteMeLanguageService, $rootScope, $timeout, $window, $cookies) {
1968        var service = this;
1969
1970        // @private
1971        var _inited = false;
1972
1973        const RECITE_ME_INITED = 1;
1974        const RECITE_ME_INITING = 2;        // purpose of this is to prevent any other init calls from occurring once the first init call has started.
1975
1976        /**
1977        * Handy: https://www.educative.io/answers/how-to-dynamically-load-a-js-file-in-javascript
1978        *
1979        * @private
1980        * @param {String} key - the ReciteMe API key
1981        * @param {String} version - the particular version of the ReciteMe script to try and load
1982        * @return {Promise} - a Promise in which the resolve will occur when the ReciteMe script has
1983        *                       loaded successfully. Reject will occur if there is an error in loading
1984        *                       the script.
1985        */
1986        function _addReciteMeScript(key, version) {
1987            return new Promise((resolve, reject) => {
1988                let reciteEndpoint = RECITE_ME_ENDPOINT + key;
1989
1990                // do not attempt to load multiple ReciteMe scripts into the page, even if they differ in version
1991                if (document.querySelector(`[src*="${reciteEndpoint}"]`)) {
1992                    console.warn("Will not load multiple ReciteMe scripts into the page");
1993                    resolve();
1994                }
1995
1996                if (version) reciteEndpoint += ('&v=' + version);
1997
1998                let scriptElem = document.createElement('script');
1999                scriptElem.setAttribute("async", true);
2000                scriptElem.setAttribute("type", "text/javascript");
2001                scriptElem.setAttribute("src", reciteEndpoint);
2002
2003                scriptElem.addEventListener("load", (e) => resolve({event: e}));
2004                scriptElem.addEventListener("error", (e) => reject({event: e}));
2005
2006                document.body.appendChild(scriptElem);
2007            });
2008        };
2009
2010        /**
2011        * Main loading/initialization function in which it will asynchronously load
2012        *   the ReciteMe script into the page from the ReciteMe API, and then perform
2013        *   some one-time setup stuff.
2014        *
2015        * @private
2016        * @async
2017        * @return {Promise}
2018        */
2019        async function _init() {
2020            if (_inited) return;
2021
2022            if (!$window.WPDReciteMe) {
2023                console.warn("window.WPDReciteMe is not available. Are you sure it's been loaded?");
2024                return;
2025            }
2026
2027            _inited = RECITE_ME_INITING;
2028
2029            // technically I think this should be part of the module configuration via a service, but with Alpine
2030            //  now being the successor in 2.4, I don't think we care too much...
2031            const key = $window.WPDReciteMe.key;
2032            const version = $window.WPDReciteMe.version;
2033
2034            try {
2035                await _addReciteMeScript(key, version);
2036
2037                if (!$window.Recite) {
2038                    console.warn('ReciteMe is not loaded!');
2039                    return;
2040                }
2041
2042                // let us use the proper storageEngine
2043                RECITE_ME_CONFIG.Preferences.storageEngine = $window.Recite.Storage.Api;
2044                $window.Recite.load(RECITE_ME_CONFIG);
2045
2046                $window.Recite.Event.subscribe('Recite:ModuleEvent', function() {
2047                    angular.element($window).trigger('wpd.resize');
2048                    $rootScope.$broadcast('header.height.changed');
2049                });
2050
2051                $window.Recite.Event.subscribe('Recite:enable', function() {
2052                    $window.Recite.Preferences.load();
2053                    $rootScope.$broadcast('header.height.changed');
2054                });
2055
2056                $window.Recite.Event.subscribe('Preferences:load', function() {
2057                    $rootScope.$broadcast('header.height.changed');
2058                });
2059
2060                angular
2061                    .element(document.body)
2062                    .on('click', '.recite-close', function(e) {
2063                        e.preventDefault();
2064                        if ($window.Recite && $window.Recite.disable) {
2065                            $window.Recite.disable();
2066                        }
2067                    })
2068                    .on('click', '.recite-sprite-button-back', function(e) {
2069                        e.preventDefault();
2070                        if (
2071                            $window.Recite &&
2072                            $window.Recite.PlayerControls &&
2073                            $window.Recite.PlayerControls.previous
2074                        ) {
2075                            $window.Recite.PlayerControls.previous();
2076                        }
2077                    })
2078                    .on('click', '.recite', function(e) {
2079                        e.preventDefault();
2080
2081                    })
2082                    .on('click', '.recite-sprite-button-reset', function(e) {
2083                        e.preventDefault();
2084                        $window.Recite.Storage.Cookie.save("Recite.Welsh",!1,365)
2085                    })
2086                    .on('click', '.recite-sprite-button-forward', function(e) {
2087                        e.preventDefault();
2088                        if (
2089                            $window.Recite &&
2090                            $window.Recite.PlayerControls &&
2091                            $window.Recite.PlayerControls.next
2092                        ) {
2093                            $window.Recite.PlayerControls.next();
2094                        }
2095                    });
2096
2097                _inited = RECITE_ME_INITED;
2098            }
2099            catch (e) {
2100                console.error("Couldn't load the ReciteMe script...");
2101                _inited = false;
2102            }
2103        }
2104
2105        /**
2106        * @return {boolean|int}
2107        */
2108        service.isInited = function() {
2109            return _inited;
2110        };
2111
2112        /**
2113        * @see _init
2114        */
2115        service.init = _init;
2116    }
2117]);
2118
2119/**
2120* @run
2121*
2122*/
2123wpdrecitemeReciteMeModule.run([
2124    '$window',
2125    '$cookies',
2126    '$timeout',
2127    'wpdrecitemeReciteMeLanguageService',
2128    'wpdrecitemeReciteMeInitService',
2129    'wpdrecitemeReciteMe_RECITE_ME_LANGUAGES',
2130    function($window, $cookies, $timeout, ReciteMeLanguageService, ReciteMeInitService, RECITE_ME_LANGUAGES) {
2131
2132        // If redirected from welsh domain or Recite.Welsh cookie is set to true.
2133        var currentUrl = $window.location.href;
2134        var langParam = /lang=([^&]+)/.exec(currentUrl);
2135        var welshPersist = $cookies.get("Recite.Welsh");
2136        if (welshPersist == "true"){
2137            langParam = ["0","cy"];
2138        }
2139        if ((langParam != null && langParam.length === 2) || welshPersist == "true") {
2140            langParamValue = langParam[1];
2141            if (langParamValue == RECITE_ME_LANGUAGES['cy'].code) {
2142                ReciteMeInitService.init().then(() => {
2143                    $timeout(function() {
2144                        ReciteMeLanguageService.translate(langParamValue);
2145                    });
2146                });
2147            }
2148        }
2149    }
2150]);
2151;
2152/** include: angular.wpd.js **/
2153window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpd.js');
2154/**
2155 * @author keiranc
2156 * global angularjs wpd module
2157 */
2158var wpd = angular.module('wpd', []);
2159
2160wpd.config([
2161    '$locationProvider',
2162    function($locationProvider) {
2163        $locationProvider.html5Mode(true);
2164        $locationProvider.hashPrefix('!');
2165    }
2166]);
2167
2168
2169/**
2170 * Angular service that **should** be used for reporting any metrics stuff
2171 * @author keiranc
2172 * 
2173 * GB - The google analytics element of this has never worked - ! DO NOT USE TO LOG GOOGLE ANALYTICS EVENTS !
2174 * Only use for "internal" metrics logging.
2175 */
2176wpd.service('Analytics', [
2177    '$http',
2178    '$log',
2179    '$window',
2180    function($http, $log, $window) {
2181        // Google Analytics may not be loaded, so use angular.noop in this case (no-ga mode)
2182        this.ga = $window.ga || angular.noop;
2183
2184        this.metricsUrl = '/__metrics/log';
2185        
2186        // Utility method to ensure the GA event is sent - see https://developers.google.com/analytics/devguides/collection/analyticsjs/sending-hits#hitcallback
2187        function createFunctionWithTimeout(callback, opt_timeout) {
2188              var called = false;
2189              function fn() {
2190              if (!called) {
2191                  called = true;
2192                  callback();
2193              }
2194          }
2195          setTimeout(fn, opt_timeout || 1000);
2196          return fn;
2197        }
2198
2199        /**
2200         * Sends a custom event to google analytics
2201         * @param {String} category typically the object that was interacted with
2202         * @param {String} action the type of interaction
2203         * @param {String} label useful for categorising events
2204         * @param {Number} value a numeric value associated with the event (integer)
2205         */
2206        this.logEvent = function(category, action, label, value) {
2207            if (!category || !action) {
2208                $log.error(
2209                    'Analytics.logEvent must have at least a valid category and action parameter'
2210                );
2211                return;
2212            }
2213
2214            var _this = this;
2215
2216            //!!!GB - As far as I can tell this has never worked
2217            //We will just treat this module as an "internal" logger and deal with google analytics seperately.
2218            //this.ga('send', category, action, label, value);
2219
2220            // send to metrics
2221            var url = $window.location.href;
2222
2223            var payload = {
2224                category: category,
2225                action: action,
2226                URL: url,
2227                authenticityToken: CORE.AT
2228            };
2229
2230            if (angular.isDefined(label) && label != null) {
2231                payload.label = label;
2232            }
2233
2234            if (angular.isDefined(value) && value != null) {
2235                payload.value = value;
2236            }
2237
2238            if (navigator.sendBeacon) {
2239                var payloadFormData = new FormData();
2240                for (var key in payload) {
2241                    if (payload.hasOwnProperty(key)) {
2242                        payloadFormData.append(key, payload[key]);
2243                    }
2244                }
2245                navigator.sendBeacon(_this.metricsUrl, payloadFormData);
2246            } else {
2247                $http.post(_this.metricsUrl, payload);
2248            }
2249            return this;
2250        };
2251
2252        /**
2253         * Sends a page view event to google analytics for the url
2254         * @param {String} url url of the page
2255         * @returns {Analytics} this
2256         */
2257        this.logPageView = function(url) {
2258            this.ga('send', {
2259                hitType: 'pageview',
2260                page: url
2261            });
2262            return this;
2263        };
2264
2265        /**
2266         * Sends a page view event to google analytics for the current page
2267         * @returns {Analytics} this
2268         */
2269        this.logCurrentPageView = function() {
2270            var currentUrl = $window.location.href;
2271            this.logPageView(currentUrl);
2272            return this;
2273        };
2274    }
2275]);
2276
2277wpd.controller('alertDismissableCTRL', [
2278    '$element',
2279    '$scope',
2280    function($element, $scope) {
2281        $scope.close = function() {
2282            angular.element($element).fadeOut();
2283        };
2284    }
2285]);
2286
2287wpd.controller('youtubeCTRL', [
2288    '$scope',
2289    '$window',
2290    function($scope, $window) {
2291        // DEBUG && console.log(angular.element('.yt_holder'));
2292    }
2293]);
2294
2295
2296 //GBDEVELOPMENT - my new directive to check dates
2297/**
2298 * check if the date is valid
2299 */
2300wpd.directive('invalidDate', [
2301    function() {
2302        return {
2303            require: 'ngModel',
2304            restrict: 'A',
2305            link: function(scope, element, attrs, ngModel) {
2306                ngModel.$validators.invalidDate = function(value) {
2307                    if(value != undefined){
2308                        return (true); //all dates are invalid
2309                    }else{
2310                        return false;
2311                    }
2312                };
2313            }
2314        };
2315    }
2316]);
2317
2318// For Ability Net refinement of [WSME-586]
2319/**
2320* Use on a structure in which role=tablist, and it has role=tab children.
2321* Will set things up so that an Enter or Space press will trigger click() for each tab child.
2322* Will set up movement between tabs using left and right arrow keys.
2323* TODO: should be namespaced
2324*
2325*  @author noahm
2326*/
2327// For Ability Net refinement of [WSME-586]
2328/* Use on a structure in which role=tablist, and it has role=tab children.
2329 * Will set things up so that an Enter or Space press will trigger click() for each tab child.
2330 * Will set up movement between tabs using left and right arrow keys.
2331*/
2332wpd.directive('accessibleTabs', ['accessibilityKeyCodes', '$timeout',
2333    function(accessibilityKeyCodes, $timeout) {
2334        return {
2335            restrict: 'AE',
2336//            scope: {
2337//                initialSelectedTabIndex: '<?'
2338//            },
2339            /**
2340            * Note that this directive has no explicit template (TODO - but is this worth it?)
2341            * Interesting: https://stackoverflow.com/questions/20878830/angularjs-isolated-scope-for-directives-without-own-template
2342            * and: https://stackoverflow.com/questions/36211733/angular-directive-with-no-template-assign-ng-click-function-from-directive
2343            */
2344            link: function($scope, $element, $attrs, $ctrl, $transcludeFn) {
2345
2346                $scope.selectedTabIndex = parseInt($attrs.initialSelectedTabIndex) || 0;
2347
2348                /**
2349                * Given as a function, despite its simplicity, to allow people overriding the ng-click to have a slightly easier time setting the tab
2350                *
2351                * @param {number|string} tabIndex - the tab to set as the currently showing tab
2352                * @return {undefined}
2353                */
2354                $scope.selectTab = function(tabIndex) {
2355                    $scope.selectedTabIndex = parseInt(tabIndex) || 0;
2356                };
2357
2358                /**
2359                * @param {number|string} tabIndex - the tab to check whether we should be able to show or not
2360                * @return {boolean}
2361                */
2362                $scope.showTabPanel = function(tabIndex) {
2363                    return $scope.selectedTabIndex === parseInt(tabIndex);
2364                };
2365
2366                $timeout(function() {
2367                    var $tabChildren = $element.find('[role="tab"]');
2368
2369                    $tabChildren.each(function(idx, tabChild) {
2370                        tabChild = angular.element(tabChild);
2371
2372                        /**
2373                        * So this is how it should work: you can focus on each tab sequentially by navigating through them with the left and right arrow keys.
2374                        * Changing focus does not actually 'click' the tab to change the tab panel, pressing enter or space will though.
2375                        */
2376                        tabChild.on('keyup', function(event) {
2377                            if (event.keyCode === accessibilityKeyCodes.ENTE
2377R || event.keyCode === accessibilityKeyCodes.SPACE) {
2378                                tabChild.click();
2379                            }
2380                            else if (event.keyCode === accessibilityKeyCodes.LEFT_ARROW) {
2381                                if (idx === 0) {    //wrap to last
2382                                    $tabChildren.last().focus();
2383                                }
2384                                else {
2385                                    $tabChildren[idx-1].focus();
2386                                }
2387                            }
2388                            else if (event.keyCode === accessibilityKeyCodes.RIGHT_ARROW) {
2389                                if (idx === $tabChildren.length - 1) {    //wrap to first
2390                                    $tabChildren.first().focus();
2391                                }
2392                                else {
2393                                    $tabChildren[idx+1].focus();
2394                                }
2395                            }
2396                        });
2397                    });
2398                });
2399            }
2400        }
2401    }
2402]);
2403
2404/**
2405* TODO: make this the only version of accessibleTabs and ditch the other one.
2406*   Can't address this now because short on time and the change to use an isolate scope means that HTML bits will need
2407*   to be updated!
2408*
2409* @author noahm
2410*/
2411wpd.directive('wpdutilsAccessibleTabs', ['accessibilityKeyCodes', '$timeout',
2412    function(accessibilityKeyCodes, $timeout) {
2413        return {
2414            restrict: 'AE',
2415            scope: {
2416                initialSelectedTabIndex: '<?wpdutilsInitialSelectedTabIndex',
2417                deferSetup: '<?wpdutilsAccessibleTabsDeferSetup',
2418                tabChildren: '=?wpdutilsAccessibleTabsTabChildren',
2419                watchTabChildren: '<?wpdutilsAccessibleTabsWatchTabChildren',
2420                onSetupFn: '&?wpdutilsAccessibleTabsOnSetupFn',
2421                onTearDownFn: '&?wpdutilsAccessibleTabsOnTearDownFn',
2422                disableDirective: '<?wpdutilsAccessibleTabsDisableDirective'
2423            },
2424            /**
2425            * Note that this directive has no explicit template (TODO - but is this worth it?)
2426            * Interesting: https://stackoverflow.com/questions/20878830/angularjs-isolated-scope-for-directives-without-own-template
2427            * and: https://stackoverflow.com/questions/36211733/angular-directive-with-no-template-assign-ng-click-function-from-directive
2428            */
2429            link: function($scope, $element, $attrs, $ctrl, $transcludeFn) {
2430                const DEBUG = false;
2431                const EVENT_NAMESPACE = 'wpdutilsAccessibleTabs';
2432
2433                $scope.selectedTabIndex = $scope.initialSelectedTabIndex || parseInt($attrs.initialSelectedTab) || 0;
2434
2435                /**
2436                * Given as a function, despite its simplicity, to allow people overriding the ng-click to have a slightly easier time setting the tab
2437                *
2438                * @param {number|string} tabIndex - the tab to set as the currently showing tab
2439                * @return {undefined}
2440                */
2441                $scope.selectTab = function(tabIndex) {
2442                    $scope.selectedTabIndex = parseInt(tabIndex) || 0;
2443                };
2444
2445                /**
2446                * @param {number|string} tabIndex - the tab to check whether we should be able to show or not
2447                * @return {boolean}
2448                */
2449                $scope.showTabPanel = function(tabIndex) {
2450                    return $scope.selectedTabIndex === parseInt(tabIndex);
2451                };
2452
2453                /**
2454                 * As the name implies, will only attach a particular handler for an event once, no matter how many times
2455                 *  it's told to attach that handler to the specified event.
2456                 *
2457                 * @param {jQuery} $targetElem - the element in the DOM triggering the named event
2458                 * @param {string} eventName - the name of the event being triggered
2459                 * @param {string} eventNamespace - the namespace of the event being triggered
2460                 * @param {function} handler - the handler function to provide as a callback
2461                 * @returns {undefined}
2462                 */
2463                var attachHandlerOnce = function($targetElem, eventName, eventNamespace, handler) {
2464                    var han = $._data($targetElem.get(0), "events");
2465                    if (!han || !han[eventName] || han[eventName].map(function(a) {return a.handler.name + '.' + a.namespace}).indexOf(handler.name + '.' + eventNamespace) < 0) {
2466                        let finalEventName = eventName + (eventNamespace ? '.' + eventNamespace : '');
2467                        $targetElem.on(finalEventName, handler);
2468                    }
2469                };
2470
2471                /**
2472                * So this is how it should work: you can focus on each tab sequentially by navigating through them with the left and right arrow keys.
2473                * Changing focus does not actually 'click' the tab to change the tab panel, pressing enter or space will though.
2474                */
2475                var _tabKeyupEvent = function(event, tabChild, idx, $tabChildren) {
2476                    if (event.keyCode === accessibilityKeyCodes.ENTE
2476R || event.keyCode === accessibilityKeyCodes.SPACE) {
2477                        // `event.currentTarget` should == tabChild anyway
2478                        tabChild.click();
2479                    }
2480                    else if (event.keyCode === accessibilityKeyCodes.LEFT_ARROW) {
2481                        if (idx === 0) {    // wrap to last
2482                            $tabChildren.last().focus();
2483                        }
2484                        else {
2485                            $tabChildren[idx-1].focus();
2486                        }
2487                    }
2488                    else if (event.keyCode === accessibilityKeyCodes.RIGHT_ARROW) {
2489                        if (idx === $tabChildren.length - 1) {    // wrap to first
2490                            $tabChildren.first().focus();
2491                        }
2492                        else {
2493                            $tabChildren[idx+1].focus();
2494                        }
2495                    }
2496                };
2497
2498                $scope.setupTabBehaviour = function(callbackFn, ignoreDefaultCallbackFn) {
2499                    DEBUG && console.log($scope.tabChildren);
2500
2501                    if ($scope.tabChildren) {
2502                        $scope.tabChildren.each(function(idx, tabChild) {
2503                            tabChild = angular.element(tabChild);
2504                            attachHandlerOnce(
2505                                tabChild,
2506                                'keyup',
2507                                `${EVENT_NAMESPACE}__tabKeyupEvent`,
2508                                (event) => _tabKeyupEvent(event, tabChild, idx, $scope.tabChildren)
2509                            );
2510                        });
2511                    }
2512
2513                    callbackFn && callbackFn();
2514
2515                    if (!ignoreDefaultCallbackFn) {
2516                        $scope.onSetupFn && $scope.onSetupFn($scope, context);
2517                    }
2518                };
2519
2520                $scope.tearDownTabBehaviour = function(callbackFn, ignoreDefaultCallbackFn) {
2521                    DEBUG && console.log($scope.tabChildren);
2522
2523                    if ($scope.tabChildren) {
2524                        $scope.tabChildren.each(function(idx, tabChild) {
2525                            tabChild = angular.element(tabChild);
2526                            tabChild.off(`keyup.${EVENT_NAMESPACE}__tabKeyupEvent`);
2527                        });
2528                    }
2529
2530                    callbackFn && callbackFn();
2531
2532                    if (!ignoreDefaultCallbackFn) {
2533                        $scope.onTearDownFn && $scope.onTearDownFn($scope, context);
2534                    }
2535                };
2536
2537                $scope.initUndefinedTabChildren = function() {
2538                    if ($scope.tabChildren === undefined) {
2539                        $scope.tabChildren = $element.find('[role="tab"]');
2540                    }
2541                };
2542
2543                $scope.$on('wpdutilsAccessibleTabs__setup', (event, data) => {
2544                    if ($scope.disableDirective) return;
2545                    $scope.setupTabBehaviour(data?.callbackFn, data?.ignoreDefaultCallbackFn);
2546                });
2547
2548                $scope.$on('wpdutilsAccessibleTabs__tearDown', (event, data) => {
2549                    $scope.tearDownTabBehaviour(data?.callbackFn, data?.ignoreDefaultCallbackFn);
2550                });
2551//
2552//                if ($scope.watchDisableDirective) {
2553//                    $scope.$watch('disableDirective', function(newVal, oldVal) {
2554//                        if (newVal) $scope.tearDownTabBehaviour();
2555//                        else $scope.setupTabBehaviour();
2556//                    });
2557//                }
2558
2559                // only set this watcher up if we were told to
2560                if ($scope.watchTabChildren) {
2561                    $scope.$watchCollection('tabChildren', function(newVal, oldVal) {
2562                        // both newVal and oldVal will be null on initialization for watchCollection.
2563                        if (newVal !== oldVal) {
2564                            if ($scope.disableDirective) $scope.tearDownTabBehaviour();
2565                            else $scope.setupTabBehaviour();
2566                        }
2567                    });
2568                }
2569
2570                // initialize
2571                if (!($scope.deferSetup || $scope.disableDirective)) {
2572                    $timeout(() => {
2573                        $scope.initUndefinedTabChildren();
2574                        $scope.setupTabBehaviour();
2575                    });
2576                }
2577            }
2578        }
2579    }
2580]);
2581
2582/**
2583 * WPD site wide datePicker directive
2584 * for options see https://xdsoft.net/jqplugins/datetimepicker/
2585 * @author keiranc
2586 */
2587wpd.directive('datePicker', [
2588    function() {
2589        return {
2590            require: 'ngModel',
2591            scope: {
2592                formatFn: '&', // custom formatter
2593                parseFn: '&', // custom parser
2594                options: '=' // xdsoft datetimepicker options
2595            },
2596            link: function(scope, element, attrs, ngModelController) {
2597                const DEBUG = false;
2598                // Object.values polyfill for IE 9+
2599                if (!Object.values) {
2600                    Object.values = function(o) {
2601                        return Object.keys(o).map(function(k) {
2602                            return o[k];
2603                        });
2604                    };
2605                }
2606
2607
2608                // NOTE: this application of datetimepicker on the jQuery object is different to $.fn.datetimepicker!
2609                // This only appears in jquery.datetimepicker.full.js, not the standard jquery.datetimepicker.js that comes with CORE,
2610                //      so make sure you have used #{wpd.utils.loadDatePicker /} in your template somewhere.
2611                $.datetimepicker.setDateFormatter('moment');
2612
2613                var format = 'DD/MM/YYYY';
2614
2615                if (scope.options && scope.options.format) {
2616                    format = scope.options.format;
2617                }
2618
2619                var hasParseFn = !!attrs.parseFn;
2620                var hasFormatFn = !!attrs.formatFn;
2621
2622                // format the date from the model value to a value to display in the input box
2623                ngModelController.$formatters.push(function(modelValue) {
2624                    if (modelValue == null) {
2625                        return null;
2626                    }
2627                    if (hasFormatFn) {
2628                        return scope.formatFn({
2629                            modelValue: modelValue,
2630                            format: format
2631                        });
2632                    }
2633                    var date = moment(modelValue);
2634
2635                    return date.isValid() ? date.format(format) : '';
2636                });
2637
2638                // format the date from the input box to a value to store in the model
2639                ngModelController.$parsers.push(function(viewValue) {
2640                    if (viewValue == null) {
2641                        return null;
2642                    }
2643                    if (hasParseFn) {
2644                        return scope.parseFn({
2645                            viewValue: viewValue,
2646                            format: format
2647                        });
2648                    }
2649                    var date = moment(viewValue, format);
2650                    return date.isValid() ? date.toDate() : null;
2651                });
2652
2653//GBDEVELOPMENT - Switched off validateOnBlur as that was the built in validation changing the invalid date to "today"
2654                angular.element(element).datetimepicker(
2655                    angular.extend(
2656                        {
2657                            format: format,
2658                            timepicker: false,
2659                            autoHide: true,
2660                            validateOnBlur: false,
2661
2662                            // remove scroll date change
2663                            scrollInput: false, 
2664                        },
2665                        scope.options || {}
2666                    )
2667                );
2668
2669//GBDEVELOPMENT - My new onblur that I don't think is a great solution.
2670//                angular.element(element).on('blur', function() {
2671//                    DEBUG && console.log(element);
2672//                    var dateToCheck = angular.element(element).val();
2673//                    var valid = true;
2674//                    if (dateToCheck == 'f') valid = false;
2675//                    if (!valid) {
2676//                        element.after('<span class="error" id="' + element.ID + 'clientError' + '">You have entered an invalid date</span>');
2677//                    }
2678//                    else
2679//                    {
2680//                        var toDel = document.getElementById(element.ID + 'clientError');
2681//                        toDel.parentNode.removeChild(toDel);
2682//                    }
2683//                });
2684            }
2685        };
2686    }
2687]);
2688
2689wpd.directive('trackEvent', ['Analytics', function(Analytics) {
2690    return {
2691        scope: {
2692            trackEvent: '@?',
2693            category: '@',
2694            action: '@',
2695            label: '@?',
2696            value: '@?'
2697        },
2698        link: function(scope, element, attrs) {
2699            if (scope.category && scope.action) {
2700                angular.element(element).on(scope.trackEvent || 'click', function() {
2701                    Analytics.logEvent(scope.category, scope.action, scope.label || attrs.href || '', parseInt(scope.value, 10) || null);
2702                });
2703            }
2704        }
2705    }
2706}]);
2707
2708wpd.factory('scrollTo', [
2709    '$q',
2710    '$timeout',
2711    function($q, $timeout) {
2712        return function(selector, offset, duration) {
2713            return $q(function(resolve) {
2714                $timeout(function() {
2715                    var element = angular.element(selector);
2716                    if (element && element.offset()) {
2717                        angular.element('html, body').animate(
2718                            {
2719                                scrollTop:
2720                                    element.offset().top -
2721                                    (offset ? offset : 50) -
2722                                    (window.fixedHeaderOffset || 0)
2723                            },
2724                            duration,
2725                            'swing',
2726                            resolve
2727                        );
2728                    }
2729                });
2730            });
2731        };
2732    }
2733]);
2734
2735wpd.controller('BlocksCtrl', [
2736    '$scope',
2737    function($scope) {
2738        $scope.selected = 1;
2739
2740        $scope.select = function(index) {
2741            $scope.selected = parseInt(index, 10);
2742        };
2743    }
2744]);
2745
2746/**
2747 * Angular service for adding accessibility functionality when opening a pop up with vex 
2748 * @author petert
2749 */
2750wpd.service('vexAccessibility', [
2751    '$timeout',
2752    function($timeout) {
2753        
2754
2755        /**
2756         * Hides elements from screen readers that are in the background and indexes elements created by vex so screen readers can access them.
2757         * @param {List} elements, classes, ids to hide from the screen reader
2758         * @param {List} classes and ids to index
2759         * @param {String} element to give focus to
2760         */
2761        this.openVex = function(elementsToHide, elementsToIndex, elementToFocus) {
2762            var e;
2763            var tag;
2764            for(e = 0; e < elementsToHide.length; e++){
2765                tag = angular.element(document).find(elementsToHide[e]);
2766                tag.attr('aria-hidden',true);
2767                tag.attr('tabindex',-1);
2768            }
2769            $timeout(function() {
2770                var e;
2771                var tag;
2772                
2773                // make the vex-close button accessible and respond to enter keypresses.
2774                $('.vex-content').each(function(idx, elem) {
2775                    if ($(this).find('.vex-close').length) {
2776                        $(this).prepend(
2777                                $(this).find('.vex-close')
2778                                    .attr('tabindex',0).attr('role','button').attr('aria-label','Close dialog')
2779                                    .addClass('focusable-element')
2780                                    .on('keyup', 
2781                                        function(event) {
2782                                            if (event.keyCode === 13) {
2783                                                event.preventDefault();
2784                                                this.click();
2785                                            }
2786                                        }
2787                                    )
2788                        );
2789                    }
2790                    
2791                });
2792                
2793                for(e = 0; e < elementsToIndex.length; e++){
2794                    tag = angular.element(document).find(elementsToIndex[e]);
2795                    tag.attr('tabindex',0);                                
2796                }
2797                angular.element(document).find(elementToFocus).focus();
2798            });
2799        };
2800
2801        /**
2802         * Unhides elements that were hidden with openVex
2803         * @param {List} elements, classes, ids to unhide from the screen reader
2804         * @param {String} element to give focus to
2805         */
2806        this.closeVex = function(elementsToAdd, elementToFocus) {
2807            var e;
2808            var tag;
2809            $timeout(function() {
2810                for(e = 0; e < elementsToAdd.length; e++){
2811                    tag = angular.element(document).find(elementsToAdd[e]);
2812                    tag.attr('aria-hidden',false);
2813                    tag.attr('tabindex',0);
2814                }
2815                angular.element(document)?.find(elementToFocus)?.focus()?.get(0)?.scrollIntoView(
2816                    {
2817                        behavior: "auto", 
2818                        block: "center", 
2819                        inline: "nearest"
2820                    });
2821            });
2822        };
2823    }
2824]);
2825
2826/**
2827* A service that will configure a vex dialog object's settings such that it can render an ngTemplate as its view.
2828*
2829* @author noahm
2830*/
2831wpd.service('vexNgTemplate', [
2832    '$templateRequest', '$templateCache', '$timeout', '$compile',
2833    function($templateRequest, $templateCache, $timeout, $compile) {
2834
2835        var service = this;
2836
2837        // Stolen from https://www.w3resource.com/javascript-exercises/javascript-math-exercise-23.php
2838        var _createUUID = function() {
2839            var dt = new Date().getTime();
2840            var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
2841                var r = (dt + Math.random()*16)%16 | 0;
2842                dt = Math.floor(dt/16);
2843                return (c=='x' ? r :(r&0x3|0x8)).toString(16);
2844            });
2845            return uuid;
2846        };
2847
2848        /**
2849        * @param {String} rawHTML
2850        * @param {Object} existingSettings
2851        * @param {Object} templateScope
2852        * @param {boolean} removeVexForm
2853        * @param {String|null} compileTargetId
2854        *
2855        * @return {boolean|Object}
2856        */
2857        service.setRawTemplate = function(rawHTML, existingSettings, templateScope, removeVexForm, compileTargetId) {
2858            var vexSettings = existingSettings || {};
2859
2860            if (rawHTML) {
2861                var targetId = compileTargetId || _createUUID();
2862                vexSettings.input = '<div id="' + targetId + '">' + rawHTML + '</div>';
2863                var currentAfterOpenFn = vexSettings.afterOpen || angular.noop;
2864
2865                vexSettings.afterOpen = function() {
2866                    // find the DOM node of the content we added into vex (our vexSettings.input). You want this to have a unique ID so if you supply the compileTargetId make sure it is.
2867                    var $vexHtml = angular.element('#' + targetId);
2868
2869                    // Vex popups wrap their content in a form element, which means any form element within your template will not function.
2870                    // If you choose to remove the vex form, then think about the repercussions on the vex buttons (if you're using them)
2871                    if (removeVexForm) {
2872                        var $vexForm = $vexHtml.closest('form.vex-dialog-form');
2873
2874                        if ($vexForm) {
2875                            $vexForm.parent().append($vexForm.children());
2876                            $vexForm.remove();
2877                        }
2878                    }
2879
2880                    // compile the template using the supplied templateScope, so it can access vm or whatever, despite no longer being within vm's root DOM node
2881                    $compile($vexHtml)(templateScope);
2882
2883                    // execute the original afterOpen function
2884                    currentAfterOpenFn();
2885                };
2886            }
2887            else {
2888                console.error('Could not apply the HTML template');
2889                return false;
2890            }
2891
2892            // return the new augmented settings
2893            return existingSettings;
2894        };
2895
2896        /**
2897        * @asynchronous
2898        *
2899        * @param {String} ngTemplateName
2900        * @param {Object} existingSettings
2901        * @param {Object} templateScope
2902        * @param {boolean} removeVexForm
2903        * @param {String|null} compileTargetId
2904        *
2905        * @return {Promise} - a Promise that will resolve to an object of Vex settings ready to do with as you wish
2906        */
2907        service.setNgTemplate = function(ngTemplateName, existingSettings, templateScope, removeVexForm, compileTargetId) {
2908            return new Promise(function(resolve, reject) {
2909                // look in templateCache first
2910                var existingTemplate = $templateCache.get(ngTemplateName);
2911
2912                if (existingTemplate) {
2913                    var setTemplateResult = service.setRawTemplate(existingTemplate, existingSettings, templateScope, removeVexForm, compileTargetId);
2914
2915                    if (setTemplateResult) {
2916                        // return the augmented settings
2917                        resolve(setTemplateResult);
2918                    }
2919                    else {
2920                        // return the original settings
2921                        console.error('Could not fetch template for ' + ngTemplateName);
2922                        reject(existingSettings);
2923                    }
2924                }
2925                else {
2926                    $templateRequest(ngTemplateName).then(
2927                        function(html) {
2928                            var setTemplateResult = service.setRawTemplate(html, existingSettings, templateScope, removeVexForm, compileTargetId);
2929
2930                            if (setTemplateResult) {
2931                                // return the augmented settings
2932                                resolve(setTemplateResult);
2933                            }
2934                            else {
2935                                // return the original settings
2936                                console.error('Could not fetch template for ' + ngTemplateName);
2937                                reject(existingSettings);
2938                            }
2939                        }
2940                    );
2941                }
2942            });
2943        };
2944    }
2945]);
2946
2947
2948/**
2949* Little directive to help CMS users discern areas of a template which on the frontend would normally be visually mutually exclusive, but have been
2950*   made visible all the time in the CMS in order to edit stuff within them like slots.
2951* Will outline the directive annotated element with randomly coloured dashed border, and supply a label for the area at the top.
2952*
2953* @author noahm
2954*/
2955wpd.directive('cmsShowBounds', function() {
2956    return {
2957        restrict: 'A',
2958        scope: {
2959            cmsBoundLabel: '@'
2960        },
2961        link: function($scope, $elem, $attrs) {
2962            if ($attrs.cmsShowBounds === 'true') {
2963                var randomColor = 'hsl(' + Math.floor(Math.random() * 360) + ', 70%, 50%)';     // percentages are for saturation and lightness
2964
2965                $($elem).css({outline: ('2px dashed ' + randomColor), borderRadius: '3px', outlineOffset: '6px'});
2966
2967                if ($scope.cmsBoundLabel) {
2968                    var $label = $('<span>' + $scope.cmsBoundLabel + '</span>');
2969                    $label.css({fontSize: '0.8em', color: randomColor, display: 'inline-block', margin: '18px 0px 12px'});
2970                    $($elem).before($label);
2971                }
2972            }
2973        }
2974    }
2975});
2976
2977/**
2978* TODO: think about creating a super class rather than mixin?
2979*/
2980wpd.service('wpdutilsCustomInputMixinService', ['$compile', function($compile) {
2981    /**
2982    * A quick setup function that instantiates watches between a custom input directive's inner FormController
2983    *   and syncs the status of that FormController to the status of the NgModelController of the input's ngModel
2984    *
2985    * @param {Scope} $scope - the $scope of the directive this mixin is being applied to
2986    * @param {string} formCtrlName - the name of the inner form of the directive that this mixin is being applied to
2987    * @param {NgModelController} ngModelCtrl - the ngModel controller attached to the directive that this mixin is being applied to
2988    * @param {string} directiveKey - the name of the directive so that uit may be identifiable within ngModelCtrl's $error object
2989    *
2990    * @return {object} - an object possessing the watcher cancel functions
2991    */
2992    this.initStatusSyncers = function($scope, formCtrlName, ngModelCtrl, directiveKey) {
2993        // sync touch/dirty/valid states of our inner form with the outer ngControl
2994        var cancelFormValidWatch = $scope.$watch(() => $scope[formCtrlName].$valid, function() {
2995            ngModelCtrl.$setValidity(directiveKey, $scope[formCtrlName].$valid);
2996        });
2997
2998        var cancelFormDirtyWatch = $scope.$watch(() => $scope[formCtrlName].$dirty, function() {
2999            $scope[formCtrlName].$dirty ? ngModelCtrl.$setDirty() : ngModelCtrl.$setPristine();
3000        });
3001
3002        // a form doesn't have a $touched field (though feels like it should do). we need to check each of our controls
3003        var cancelFormDirtyWatch = $scope.$watch(() =>
3003 $scope[formCtrlName].$$controls.find((control) => control.$touched), function(newVal, oldVal) {
3004            newVal ? ngModelCtrl.$setTouched() : ngModelCtrl.$setUntouched();
3005        });
3006
3007        return {
3008            cancelFormValidWatch: cancelFormValidWatch,
3009            cancelFormDirtyWatch: cancelFormDirtyWatch,
3010            cancelFormDirtyWatch: cancelFormDirtyWatch
3011        }
3012    };
3013
3014    /**
3015     * Inject a loading spinner into the given parent element, that activates on the given Angular condition
3016     * @param {object} $scope - the scope for the condition, typically $scope
3017     * @param {element} parent - the element to add the wrapper class to, and to add the spinner overlay to, typically $elem.parent()
3018     * @param {string} condition - the condition to use for showing the spinner, e.g. 'loading' or 'vm.loading'
3019     */
3020    this.injectLoadingSpinner = function($scope, parent, condition = 'loading') {
3021        // Bail if the parent already has a matching spinner
3022        if (parent.find('.universal-loading-spinner[data-ng-show="' + condition + '"]').length > 0) {
3023            return;
3024        }
3025        // Add the spinner to the parent and give it the wrapper class
3026        const spinner = $compile('<div class="universal-loading-spinner" data-ng-show="' + condition + '"></div>')($scope);
3027        parent.prepend(spinner);
3028        parent.addClass('universal-loading-spinner-wrapper');
3029    }
3030}]);
3031
3032// TODO: these parsers need a complement "unparser" in order to take an existing value from an external model and return it to a format that the input can respond to
3033//  not sure if a formatter will cut it?
3034
3035/**
3036* A simple little parser that returns the boolean interpretation of a value.
3037*   In the case of a string, all values except 'true', 'y' or 'yes' will count as false.
3038*
3039* @author noahm
3040*/
3041wpd.directive('wpdutilsAsBoolean', [function() {
3042    return {
3043        require: 'ngModel',
3044        restrict: 'A',
3045        link: function($scope, $elem, $attrs, ctrl) {
3046            var parse = function(value) {
3047                if (typeof value === 'string') {
3048                    let v = value.toLowerCase();
3049                    return (v === 'true' || v === 'y' || v === 'yes');
3050                }
3051
3052                return !!value;
3053            };
3054
3055            ctrl.$parsers.push(parse);
3056        }
3057    }
3058}]);
3059
3060/**
3061* Might seem unnecessary, but certain inputs (like checkboxes) don't necessarily return what's in their value attribute
3062*
3063* @author noahm
3064*/
3065wpd.directive('wpdutilsAsValueAttr', [function() {
3066    return {
3067        require: 'ngModel',
3068        restrict: 'A',
3069        link: function($scope, $elem, $attrs, ctrl) {
3070            // this is of no use if we are on an element that has no value attribute.
3071            if (!($elem[0] instanceof HTMLInputElement)) return;
3072
3073            var parse = function(value) {
3074                if (value) {
3075                    return $elem.attr('value');
3076                }
3077
3078                return null;
3079            };
3080
3081            ctrl.$parsers.push(parse);
3082        }
3083    }
3084}]);
3085
3086/**
3087* Since angularJS-eqsue '{{}}' strings get sanitized out of WYSIWYG content in the CMS, use '#{}' instead.
3088* If an element has this directive attached to it, any instances of '#{}' will be changed to '{{}}' bindings
3089*
3090* @directive
3091* @author noahm
3092*/
3093wpd.directive('wpdutilsCompilable', [function() {
3094    return {
3095        restrict: 'AC',
3096        scope: false,
3097        compile: function(tElem, tAttrs) {
3098            const SEARCH_REGEX = /#\{(.*?)\}/gi;
3099
3100            tElem.html(function(idx, oldHTML) {
3101                return oldHTML.replaceAll(SEARCH_REGEX, '{{ $1 }}');
3102            });
3103        }
3104    }
3105}]);
3106
3107wpd.directive('wpdutilsHref', ['$window','$location', function($window, $location) {
3108    return {
3109        scope: {
3110            preserveQueryString: '<?',
3111            href: '@?wpdutilsHref'
3112        },
3113        link: function(scope, iElem, iAttrs) {
3114            const MATCH_REGEX = /^([^?#]*)?(?<query>\?.*)?(?<hash>#.*)?$/gmi;
3115            scope.preserveQueryString = scope.preserveQueryString !== undefined ? scope.preserveQueryString : true;
3116
3117            // preference for an existing href attribute over one given to us, as this may be an href
3118            //  created by something like ngHref
3119            let href = iAttrs.href || scope.href;
3120
3121            const replaceFn = function() {
3122                let href = iAttrs.href || scope.href;
3123                let curSearchParams = $window.location.search;
3124                let newHref = href.replace(MATCH_REGEX, '$1' + curSearchParams + '$<hash>');
3125
3126                if (newHref.startsWith('?')) {
3127                    newHref = $window.location.pathname + newHref;
3128                }
3129                iElem.attr('href', newHref);
3130            };
3131
3132            if (!scope.preserveQueryString) {
3133                iElem.attr('href', href);
3134            }
3135            else {
3136                replaceFn();
3137
3138                // TODO: watch query string and update href automatically?
3139            }
3140        }
3141    }
3142}])
3143
3144/**
3145* TODO: Map option will not behave as you expect with the ng-model definition on the checkboxes! map['key'] is wrong, you need to use map.set('key')
3146*
3147*
3148*/
3149wpd.directive('wpdutilsCheckboxNgCollectionField', ['wpdutilsCustomInputMixinService', function(customInputMixin) {
3150    return {
3151        require: ['ngModel'],
3152        restrict: 'AE',
3153        scope: {
3154            readonly: '<',
3155            asType: '<',        // ('array'|'map'|undefined|null)
3156            fieldId: '<',
3157            disabled: '<',
3158            required: '<'
3159        },
3160        templateUrl: 'wpdutilsCheckboxNgCollectionField.html',
3161        link: function($scope, $elem, $attrs, ctrl) {
3162            const DIRECTIVE_KEY = 'wpdutilsCheckboxNgCollectionField';
3163            const INNER_FORM_NAME = 'checkboxCollectionForm';
3164
3165            // TODO: not sure why in this instance ctrl is an array, even though it has just one member
3166            var ngModelCtrl = Array.isArray(ctrl) ? ctrl[0] : ctrl;
3167            
3168            var updatedViaNgChange = false;
3169
3170            var unsetFormData = function() {
3171                $scope.formData = {
3172                    checkboxCollection: ($scope.asType === 'array' ? [] : $scope.asType === 'map' ? new Map() : {})
3173                };
3174            };
3175            unsetFormData();
3176
3177            /**
3178            * Updates the passed in ngModel with the checkboxCollectionForm formData.
3179            * Will only perform the update if all of checkboxCollectionForm's fields are valid.
3180            *
3181            * @return {undefined}
3182            */
3183            $scope.updateNgModel = function() {
3184                updatedViaNgChange = true;
3185                
3186                if ($scope[INNER_FORM_NAME].$valid) {
3187                    ngModelCtrl.$setViewValue($scope.formData.checkboxCollection);
3188                }
3189
3190                if ($scope.required) checkValidity();
3191            };
3192
3193
3194            // TODO: This should be its own validator directive
3195            function checkValidity() {
3196                var validateArray = [];
3197                if ($scope?.formData?.checkboxCollection?.length > 0) {
3198                    $scope.formData.checkboxCollection.forEach(item => {
3199                        if (item) {
3200                            validateArray.push(item);
3201                        }
3202                    });
3203                }
3204                if (validateArray.length <= 0) {
3205                    ngModelCtrl.$setValidity("required-empty", false);
3206                }
3207                else {
3208                    ngModelCtrl.$setValidity("required-empty", true);
3209                }
3210            }
3211                
3212
3213            var updateCheckedProperties = function() {
3214                // TODO: you need to "recheck" the checkboxes which have values that are found in our internal model
3215            };
3216
3217            // TODO: needs more thorough testing
3218            var updateInternalModel = function(value) {
3219                if (value === undefined || value === null) {
3220                    unsetFormData();
3221                }
3222                else if (Array.isArray(value)) {
3223                    switch ($scope.asType) {
3224                        case 'map':
3225                            let m = new Map();
3226                            value.forEach((elem) => m.set(elem, elem));        // TODO: think about how a user would actually like a map/object to be utilised
3227                            $scope.formData.checkboxCollection = m;
3228                            break;
3229                        case 'array':
3230                            // NOTE: this is a shallow copy!
3231                            $scope.formData.checkboxCollection = angular.copy(value);
3232                            break;
3233                        default:
3234                            let o = {};
3235                            value.forEach((elem) => o[elem] = elem);
3236                            $scope.formData.checkboxCollection = o;
3237                            break;
3238                    }
3239                }
3240                else if (value instanceof Map) {
3241                    switch ($scope.asType) {
3242                        case 'array':
3243                            let a = [];
3244                            for (const [k,v] of value) a.push(v);
3245                            $scope.formData.checkboxCollection = a;
3246                            break;
3247                        case 'map':
3248                            // NOTE: this is a shallow copy!
3249                            $scope.formData.checkboxCollection = angular.copy(value);
3250                            break;
3251                        default:
3252                            let o = {};
3253                            for (const [k,v] of value) o[k] = v;
3254                            $scope.formData.checkboxCollection = o;
3255                            break;
3256                    }
3257                }
3258                else if (typeof value === 'object') {
3259                    switch ($scope.asType) {
3260                        case 'array':
3261                            let a = [];
3262                            Object.entries(value).forEach(([k,v]) => a.push(v));
3263                            $scope.formData.checkboxCollection = a;
3264                            break;
3265                        case 'map':
3266                            let m = new Map();
3267                            Object.entries(value).forEach(([k,v]) =>
3267 m.set(k, v));
3268                            break;
3269                        default:
3270                            // NOTE: this is a shallow copy!
3271                            $scope.formData.checkboxCollection = angular.copy(value);
3272                            break;
3273                    }
3274                }
3275            };
3276
3277            var cancelWatch = $scope.$watch(() => ngModelCtrl.$modelValue, function(newVal, oldVal) {
3278                if (!updatedViaNgChange) updateInternalModel(newVal);
3279                updatedViaNgChange = false;
3280                if ($scope.required) checkValidity();
3281            });
3282
3283            var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ngModelCtrl, DIRECTIVE_KEY);
3284
3285        }
3286    }
3287}]);
3288
3289/**
3290* Hooks together a "button" functional element with a checkbox or radio input and passes on a click on the button to a click on the input
3291*
3292* @param {string} inputId - the id attribute of the input to link to
3293* @param {string|function} checkedClass - a string or string output expression which represents a CSS class to apply to the "button" when the input is in a checked state
3294* @param {function} extraClickFn - a callback to execute when the input is toggled. It will be supplied the input and the "button" element
3295*
3296* @author noahm
3297*/
3298wpd.directive('wpdutilsInputToggleButton', [function() {
3299    return {
3300        restrict: 'A',
3301        scope: {
3302            inputId: '<',
3303            checkedClass: '@',
3304            extraClickFn: '&'
3305        },
3306        link: function($scope, $elem, $attrs) {
3307            if (!$scope.inputId) return;
3308
3309            const $input = angular.element('#' + $scope.inputId.replace('#', ''));
3310
3311            if ($input && $input.length) {
3312                let t = $input.attr('type');
3313
3314                if (t === 'checkbox' || t === 'radio') {
3315                    $scope.toggleInput = function() {
3316                        $input.click();
3317                        $scope.extraClickFn && $scope.extraClickFn($input, $elem);
3318                    };
3319
3320                    // apply/remove the checkedClass from the button based on the checked property of its input
3321                    $scope.$watch(() => $input.prop('checked'), function() {
3322                        $input.prop('checked') ? $elem.addClass($scope.checkedClass) : $elem.removeClass($scope.checkedClass);
3323                    });
3324
3325                    $elem.on('click', $scope.toggleInput);
3326                }
3327            }
3328            else {
3329                console.error('A checkbox or radio input with ID ' + $scope.inputId + ' couldn\'t be found');
3330            }
3331        }
3332    }
3333}]);
3334
3335/**
3336* Template: /app/views/ng_templates/wpd/utils/mpan-field.html
3337* Style: ???
3338*
3339* @author noahm
3340*/
3341wpd.directive('wpdutilsMpanField', ['$timeout', 'wpdutilsCustomInputMixinService', function($timeout, customInputMixin) {
3342    return {
3343        require: 'ngModel',
3344        restrict: 'AE',
3345        scope: {
3346            isReadonly: '<',
3347            asType: '<',        // ('string'|'number'|undefined|null)
3348            fieldId: '<',
3349            isDisabled: '<',
3350            isRequired: '<'
3351        },
3352        templateUrl: 'wpdutilsMpanField.html',
3353        link: function($scope, $elem, $attrs, ctrl) {
3354            const DIRECTIVE_KEY = 'wpdutilsMpanField';
3355            const INNER_FORM_NAME = 'mpanForm';
3356
3357            var updatedViaNgChange = false;
3358
3359            if ($scope.asType !== 'string' && $scope.asType !== 'number') {
3360                $scope.asType = null;
3361            }
3362
3363            // we store the mpan value internally as an object, regardless of the asString setting
3364            var unsetFormData = function() {
3365                $scope.formData = {
3366                    distributorId: null,
3367                    meterPointId: null,
3368                    checkDigit: null
3369                };
3370            }
3371            unsetFormData();
3372
3373            /**
3374            * @return {String}
3375            */
3376            var formDataToString = function() {
3377                return $scope.formData.distributorId + $scope.formData.meterPointId + $scope.formData.checkDigit;
3378            };
3379
3380            /**
3381            * Updates the passed in ngModel with the mpanForm formData.
3382            * Will only perform the update if all of mpanForm's fields are valid.
3383            *
3384            * @return {undefined}
3385            */
3386            $scope.updateNgModel = function() {
3387                updatedViaNgChange = true;
3388
3389                if ($scope[INNER_FORM_NAME].$valid) {
3390                    switch ($scope.asType) {
3391                        case 'string':
3392                            ctrl.$setViewValue(formDataToString());
3393                            break;
3394                        case 'number':
3395                            // NOTE: if casting to a number, no leading 0s will be preserved!
3396                            ctrl.$setViewValue(parseInt(formDataToString()));
3397                            break;
3398                        default:
3399                            ctrl.$setViewValue($scope.formData);
3400                    }
3401                }
3402                else {
3403                    ctrl.$setViewValue(undefined);
3404                }
3405            };
3406
3407            var updateInternalModel = function(value) {
3408                if (value === undefined || value === null) {
3409                    unsetFormData();
3410                }
3411                else if (typeof value === 'object') {
3412                    $scope.formData = {
3413                        distributorId: value.distributorId,
3414                        meterPointId: value.meterPointId,
3415                        checkDigit: value.checkDigit
3416                    };
3417                }
3418                else if (typeof value === 'number' || typeof value === 'string') {
3419                    var x = '';
3420
3421                    // transform into string
3422                    if (typeof value === 'number') x += value;
3423                    else x = value;
3424
3425                    // try to split string into the individual digits
3426                    try {
3427                        $scope.formData = {
3428                            distributorId: x.substring(0,2),
3429                            meterPointId: x.substring(2,10),
3430                            checkDigit: x.substring(10,13),
3431                        };
3432                    }
3433                    catch (e) {
3434                        console.error(e);
3435                    }
3436                }
3437            };
3438
3439            /**
3440            * Interesting in that watching 'ctrl.$modelValue' doesn't seem to fire when you change the underlying model, but watching () => ctrl.$modelValue does.
3441            *   You can also watch $attrs['ngModel'] which is a reference to the object being written to, and not the ngModelController itself (ctrl).
3442            *
3443            *   In this case, we're wanting to look for a programmatic external update to the ngModel
3444            */
3445            var cancelWatch = $scope.$watch(() => ctrl.$modelValue, function(newVal, oldVal) {
3446                // an ngChange call that we've internally set up will execute before this watch fires. We don't want to waste time writing back an identical value
3447                //      to our internal model if the change to $modelValue came from us.
3448                if (!updatedViaNgChange) {
3449                    updateInternalModel(newVal);
3450                }
3451
3452                updatedViaNgChange = false;
3453            });
3454
3455            var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY);
3456        }
3457    }
3458}]);
3459
3460/**
3461* Template: /app/views/ng_templates/wpd/utils/numeric-unit-field.html
3462* Style: ???
3463*
3464* Motivation behind this directive is introducing built-in automatic conversion of the value when units change (set up a standalone angular service for this)
3465*
3466* @author noahm
3467*/
3468wpd.directive('wpdutilsNumericUnitField', ['wpdutilsCustomInputMixinService', function(customInputMixin) {
3469    return {
3470        require: 'ngModel',
3471        restrict: 'AE',
3472        transclude: true,
3473        scope: {
3474            isReadonly: '<',        // {boolean}
3475            asType: '<',            // {string} ('string'|undefined|null)
3476            fieldId: '<',           // {string}
3477            isDisabled: '<',        // {boolean}
3478            isRequired: '<',        // {boolean}
3479            units: '<',             // {(string|object)[]}
3480            minValue: '<',          // {number}
3481            maxValue: '<',          // {number}
3482            defaultUnit: '<'        // {string}
3483        },
3484        templateUrl: 'wpdutilsNumericUnitField.html',
3485        link: function($scope, $elem, $attrs, ctrl) {
3486            const DIRECTIVE_KEY = 'wpdutilsNumericUnitField';
3487            const INNER_FORM_NAME = 'numericUnitForm';
3488
3489            var updatedViaNgChange = false;
3490
3491            if ($scope.asType !== 'string') {
3492                $scope.asType = null;
3493            }
3494
3495            // if the supplied defaultUnit isn't in the units array, take the first element of the units array instead.
3496            var defaultUnit = null; // $scope.units?.includes($scope.defaultUnit) ? $scope.defaultUnit : ($scope.units?.length ? $scope.units[0] : null);
3497
3498            $scope.unitStrings = [];
3499            $scope.unitValidators = {};
3500
3501            var _extractUnitValidator = function(validator, unitKey, validatorKey) {
3502                if (!$scope.unitValidators[unitKey]) $scope.unitValidators[unitKey] = {};
3503                // treat all validation objects as functions to be executed.
3504                $scope.unitValidators[unitKey][validatorKey] = typeof validator === 'function' ? validator : () => { return validator; };
3505            };
3506
3507            var _extractUnitData = function(unitObj, unitKey) {
3508                if (typeof unitObj === 'string') {
3509                    $scope.unitStrings.push(unitObj);
3510                    if (!defaultUnit) defaultUnit = unitObj;
3511                }
3512                else if (typeof unitObj === 'object') {
3513                    $scope.unitStrings.push(unitKey);
3514                    $scope.unitValidators[unitKey] = {};
3515                    if (!defaultUnit) defaultUnit = unitKey;
3516
3517                    if (unitObj['validators']) {
3518                        if (Array.isArray(unitObj['validators'])) {
3519                            unitObj['validators'].forEach(function(elem, validatorKey) {
3520                                _extractUnitValidator(elem, unitKey, validatorKey);
3521                            });
3522                        }
3523                        else {
3524                            for (const validatorKey in unitObj['validators']) {
3525                                _extractUnitValidator(unitObj['validators'][validatorKey], unitKey, validatorKey);
3526                            }
3527                        }
3528                    }
3529                }
3530            };
3531
3532            if ($scope.units) {
3533                if (Array.isArray($scope.units)) {
3534                    $scope.units.forEach(_extractUnitData);
3535                }
3536                else {      // assume to be an object
3537                    for (const unitKey in $scope.units) {
3538                        _extractUnitData($scope.units[unitKey], unitKey)
3539                    }
3540                }
3541            }
3542
3543            // we store the mpan value internally as an object, regardless of the asString setting
3544            var unsetFormData = function() {
3545                $scope.formData = {
3546                    value: null,
3547                    unit: defaultUnit
3548                };
3549            }
3550            unsetFormData();
3551
3552            /**
3553            * @return {String}
3554            */
3555            var formDataToString = function() {
3556                return $scope.formData.value + $scope.formData.unit;
3557            };
3558
3559            /**
3560            * Updates the passed in ngModel with the numericUnitForm formData.
3561            * Will only perform the update if all of numericUnitForm's fields are valid.
3562            *
3563            * @return {undefined}
3564            */
3565            $scope.updateNgModel = function() {
3566                updatedViaNgChange = true;
3567
3568                if ($scope[INNER_FORM_NAME].$valid) {
3569                    switch ($scope.asType) {
3570                        case 'string':
3571                            ctrl.$setViewValue(formDataToString());
3572                            break;
3573                        default:
3574                            ctrl.$setViewValue($scope.formData);
3575                    }
3576                }
3577                else {
3578                    ctrl.$setViewValue(undefined);
3579                }
3580            };
3581
3582            var updateInternalModel = function(value) {
3583                if (value === undefined || value === null) {
3584                    unsetFormData();
3585                }
3586                else if (typeof value === 'object') {
3587                    $scope.formData = {
3588                        value: ctrl.$modelValue.value,
3589                        unit: ctrl.$modelValue.unit
3590                    };
3591                }
3592                else if (typeof value === 'number') {
3593                    $scope.formData.value = ctrl.$modelValue;
3594                }
3595                else if (typeof value === 'string') {
3596                    // given a string that appears to be a representation of a numerical value plus a unit, try to split them
3597                    // e.g. 5.65ppm => 5.65 and 'ppm'
3598
3599                    const regex = /(?<value>\d*\.?\d*)(?<unit>.*)/gm;
3600                    const results = regex.exec(ctrl.$modelValue);
3601
3602                    $scope.formData = {
3603                        value: results.groups.value,
3604                        unit: results.groups.unit?.trim()
3605                    };
3606                }
3607            };
3608
3609            var cancelWatch = $scope.$watch(() => ctrl.$modelValue, function(newVal, oldVal) {
3610                // an ngChange call that we've internally set up will execute before this watch fires. We don't want to waste time writing back an identical value
3611                //      to our internal model if the change to $modelValue came from us.
3612                if (!updatedViaNgChange) {
3613                    updateInternalModel(newVal);
3614                }
3615
3616                updatedViaNgChange = false;
3617            });
3618
3619            var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY);
3620        }
3621    }
3622}]);
3623
3624/**
3625* Template: /app/views/ng_templates/wpd/utils/telephone-with-type-field.html
3626* Style: ???
3627*
3628* @author noahm
3629*/
3630wpd.directive('wpdutilsTelephoneWithTypeField', ['wpdutilsCustomInputMixinService', '$compile', '$parse', function(customInputMixin, $compile, $parse) {
3631    return {
3632        require: 'ngModel',
3633        restrict: 'AE',
3634        scope: {
3635            isReadonly: '<',            // {boolean}
3636            asType: '<',                // {string} ('string'|undefined|null)
3637            fieldId: '<',               // {string}
3638            fieldName: '<',             // {string}
3639            isDisabled: '<',            // {boolean}
3640            isRequired: '<',            // {boolean}
3641            telephoneTypes: '<',        // {Object[]}
3642            placeholder: '<',            // {string}
3643        },
3644        templateUrl: 'wpdutilsTelephoneWithTypeField.html',
3645        compile: function($elem, $attrs) {
3646            // Add the validator attribute prior to compile
3647
3648            // This function modifies the template itself, not the instance,
3649            // so if this isn't the first use of this directive, we need to clean it up
3650            const telephoneField = $elem.find('input[type="tel"]');
3651            telephoneField.removeAttr('wpdutils-is-telephone-mobile wpdutils-is-telephone-tight wpdutils-is-telephone');
3652
3653            // Which validator to use (telephone-number-validator attribute)?
3654            // Because we're pre-compile, the $attrs haven't been parsed yet :(
3655            let validatorAttr = $parse($attrs.telephoneNumberValidator)();
3656            if (validatorAttr) {
3657                // Add the data- prefix if necessary
3658                if (!validatorAttr.startsWith('data-')) {
3659                    validatorAttr = 'data-' + validatorAttr;
3660                }
3661            }
3662            else {
3663                // Default to the standard telephone validator
3664                validatorAttr = 'wpdutils-is-telephone';
3665            }
3666
3667            // Add the selected validator to the telephone number field
3668            telephoneField.attr(validatorAttr, '');
3669
3670
3671            // Standard post-link function
3672            return function($scope, $elem, $attrs, ctrl) {
3673                const DIRECTIVE_KEY = 'wpdutilsTelephoneWithTypeField';
3674                const INNER_FORM_NAME = 'telephoneWithTypeForm';
3675
3676                var updatedViaNgChange = false;
3677
3678                const defaultType = $scope.telephoneTypes?.length ? $scope.telephoneTypes[0]?.value : null;
3679
3680                // we store the value internally as an object, regardless of the asString setting
3681                var unsetFormData = function() {
3682                    $scope.formData = {
3683                        telephone: null,
3684                        type: defaultType
3685                    };
3686                }
3687                unsetFormData();
3688
3689                if ($scope?.$parent?.vm?.formData?.hasOwnProperty($scope.fieldName)) {
3690                    $scope.formData = $scope?.$parent?.vm?.formData[$scope.fieldName];
3691                }
3692
3693                /**
3694                * Updates the passed in ngModel with the telephoneWithTypeForm formData.
3695                * Will only perform the update if all of telephoneWithTypeForm's fields are valid.
3696                *
3697                * @return {undefined}
3698                */
3699                $scope.updateNgModel = function() {
3700                    updatedViaNgChange = true;
3701
3702                    if ($scope[INNER_FORM_NAME].$valid) {
3703                        ctrl.$setViewValue($scope.formData);
3704                    }
3705                    else {
3706                        ctrl.$setViewValue(undefined);
3707                    }
3708                };
3709
3710                var updateInternalModel = function(value) {
3711                    if (value === undefined || value === null) {
3712                        unsetFormData();
3713                    }
3714                    else if (typeof value === 'object') {
3715                        $scope.formData = {
3716                            telephone: value.telephone,
3717                            type: value.type
3718                        };
3719                    }
3720                    else if (typeof value === 'number' || typeof value === 'string') {
3721                        $scope.formData.telephone = value + '';
3722                    }
3723                }
3724
3725                var cancelWatch = $scope.$watch(() => ctrl.$modelValue, function(newVal, oldVal) {
3726                    if (!updatedViaNgChange) updateInternalModel(newVal);
3727                    updatedViaNgChange = false;
3728                });
3729
3730                var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY);
3731            };
3732        }
3733    };
3734}]);
3735
3736
3737// ===========================================================================================
3738// ------------------------------ Form validation directives ---------------------------------
3739// ===========================================================================================
3740
3741/**
3742 * @return {boolean} - true if viewValue is a postcode that exists within the shared data service (has a dno).
3743 */
3744wpd.directive('registeredPostcodeCheck', ['$requester', '$q', function($requester, $q) {
3745    var json = $requester({
3746        registeredPostcodeCheck: 'WPDUtilsFront.registeredPostcode_JSON'
3747    });
3748
3749    return {
3750        require: 'ngModel',
3751        link: function($scope, $elem, $attrs, ctrl) {
3752            const DIRECTIVE_KEY = 'registeredPostcodeCheck';
3753
3754            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
3755
3756            // Set up the custom error message for this ngModel on failure of this validator
3757            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
3758
3759            ctrl.$asyncValidators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
3760                return $q(function(resolve, reject) {
3761                    if (ctrl.$isEmpty(modelValue)) {
3762                        // consider empty models to be valid
3763                        ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
3764                        resolve();
3765                    }
3766
3767                    if (viewValue) {
3768                        json.registeredPostcodeCheck({postcode: viewValue}, function(response) {
3769                            if (response && response.areaStatus) {
3770                                ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
3771                                resolve();
3772                            }
3773                            else {
3774                                ctrl.customErrorMessages[DIRECTIVE_KEY] = "Sorry, this postcode isn't registered.";
3775                                reject();
3776                            }
3777                        });
3778                    }
3779                    else {
3780                        ctrl.customErrorMessages[DIRECTIVE_KEY] = "Please enter a valid postcode";
3781                        reject();
3782                    }
3783                });
3784            };
3785        }
3786    };
3787}]);
3788
3789
3790
3791// Author: @ianbe
3792wpd.directive('wpdutilsCheckboxGroupField',
3793    ['wpdutilsCustomInputMixinService', '$timeout', function(customInputMixin, $timeout) {
3794    return {
3795        require: 'ngModel',
3796        restrict: 'AE',
3797        scope: {
3798            isReadonly: '<',            // {boolean}
3799            fieldId: '<',               // {string}
3800            fieldName: '<',             // {string}
3801            disabled: '<',            // {boolean}
3802            required: '<',            // {boolean}
3803            placeholder: '<',           // {string}
3804            wrapperCssClasses: '@',       // {string}
3805
3806            options: '<',               // {Object[]}
3807
3808            type: '<',                  // {string}
3809
3810            // Indexed types only
3811            trueForChecked: '<',        // {boolean}
3812
3813            // Ignored if type is 'array'
3814            falseForUnchecked: '<',       // {boolean} // Takes priority over nullForUnchecked
3815            nullForUnchecked: '<',      // {boolean}
3816        },
3817        templateUrl: 'wpdutilsCheckboxGroupField.html',
3818        link: function($scope, $elem, $attrs, ctrl) {
3819            var i;
3820
3821            const DIRECTIVE_KEY = 'wpdUtilsCheckboxField';
3822            const INNER_FORM_NAME = 'checkboxCollectionForm';
3823
3824            var COLLECTION_TYPE = 'indexObject';
3825            var TRUE_FOR_CHECKED = false;
3826            var FALSE_VALUE = undefined;
3827
3828            const ngModelCtrl = Array.isArray(ctrl) ? ctrl[0] : ctrl;
3829
3830            // this version of the internal model is just going to be the object as built by the vanilla behaviour (similar to indexObject, but with undefined/null)
3831            // the ngmodel with our desired structure will be built separately from it
3832            $scope.formData = {
3833                checkboxCollection: {}
3834            };
3835
3836            switch ($scope.type) {
3837                case 'array':
3838                    // Array type only contains checked values, from top to bottom. Indexes are lost but there is no empty space.
3839                    // Good for storing a simple list of selected options.
3840                    // Example: [ "VALUE2", "VALUE4" ]
3841                    COLLECTION_TYPE = 'array';
3842                    break;
3843                case 'object':
3844                    // Object type uses the option value as the key and the checked state (true/false) as the value.
3845                    // This allows you to treat each option almost as its own separate boolean variable.
3846                    // If falseForUnchecked or nullForUnchecked are set, all boxes will be present, otherwise only checked boxes will be present.
3847                    // Example: { "VALUE1": FALSE_VALUE, "VALUE2": true, "VALUE3": FALSE_VALUE, "VALUE4": true }
3848                    COLLECTION_TYPE = 'object';
3849                    break;
3850                case 'indexArray':
3851                    // IndexArray type is almost identical to IndexObject type, but it uses an array instead of an object.
3852                    // Example: [ 0: undefined, 1: FALSE_VALUE, 2: "VALUE2", 3: FALSE_VALUE, 4: "VALUE4" ]
3853                    COLLECTION_TYPE = 'indexArray';
3854                    break;
3855                default:
3856                    // indexObject (default):
3857                    // IndexObject is similar to vanilla checkbox behaviour. The index is the key, and checked boxes will have their option value as the value.
3858                    // falseForUnchecked or nullForUnchecked are available as de
3858scribed above.
3859                    // trueForChecked will set the value to true instead of the option value.
3860                    // Example: { 1: FALSE_VALUE, 2: "VALUE2", 3: FALSE_VALUE, 4: "VALUE4" }
3861                    break;
3862            }
3863
3864            if (COLLECTION_TYPE !== 'array') {
3865                if ($scope.falseForUnchecked) {
3866                    FALSE_VALUE = false;
3867                }
3868                else if ($scope.nullForUnchecked) {
3869                    FALSE_VALUE = null;
3870                }
3871
3872                if (COLLECTION_TYPE !== 'object') {
3873                    TRUE_FOR_CHECKED = ($scope.trueForChecked) ? true : false;
3874                }
3875            }
3876
3877
3878
3879            function checkValidity() {
3880                const validCount = Object.values($scope?.formData?.checkboxCollection).filter(o => typeof o === 'string').length;
3881                ngModelCtrl.$setValidity("required-empty", validCount > 0);
3882            }
3883
3884
3885            var directiveCausedUpdate = false;
3886            $scope.updateNgModel = function() {
3887                if ($scope[INNER_FORM_NAME].$valid) {
3888                    directiveCausedUpdate = true;
3889                    let viewValue = generateViewModel($scope.formData.checkboxCollection);
3890                    ngModelCtrl.$setViewValue(viewValue);
3891                }
3892
3893                if ($scope.required) checkValidity();
3894            };
3895
3896            var generateViewModel = function(input) {
3897                let output;
3898                if (COLLECTION_TYPE === 'array') {
3899                    output = Object.values(input).filter(val => typeof val === 'string');
3900                }
3901                else if (COLLECTION_TYPE === 'object') {
3902                    output = {};
3903                    if (FALSE_VALUE !== undefined) {
3904                        for (const k of $scope.options) {
3905                            output[k.value] = FALSE_VALUE;
3906                        }
3907                    }
3908                    Object.entries(input).forEach(([k, v]) => {
3909                        if (typeof v === 'string') {
3910                            output[v] = true;
3911                        }
3912                    });
3913                }
3914                else if (['indexArray', 'indexObject'].includes(COLLECTION_TYPE)) {
3915                    output = (COLLECTION_TYPE === 'indexArray') ? [] : {};
3916                    if (FALSE_VALUE !== undefined) {
3917                        for (i = 1; i <= $scope.options.length; i++) {
3918                            output[i] = FALSE_VALUE;
3919                        }
3920                    }
3921                    Object.entries(input).forEach(([k, v]) => {
3922                        let value;
3923                        if (typeof v === 'string') {
3924                            value = TRUE_FOR_CHECKED ? true : v;
3925                        }
3926                        else value = FALSE_VALUE;
3927                        output[k] = value;
3928                    });
3929                }
3930                return output;
3931            }
3932
3933
3934            var fieldsByIndex = [];
3935            var fieldsByValue = {};
3936            i = 1;
3937            for (let o of $scope.options) {
3938                fieldsByIndex[i] = o.value;
3939                fieldsByValue[o.value] = i;
3940                i++;
3941            }
3942
3943
3944            var generateInternalModel = function(input) {
3945                let output = {};
3946                
3947                if (COLLECTION_TYPE === 'indexObject') {
3948                    Object.entries(input).forEach(([k, v]) => {
3949                        if (TRUE_FOR_CHECKED) {
3950                            if (v === true) {
3951                                output[k] = fieldsByIndex[k];
3952                            };
3953                        }
3954                        else {
3955                            output[k] = v;
3956                        }
3957                    });
3958                }
3959                else if (COLLECTION_TYPE === 'indexArray') {
3960                    input.forEach((v, k) => {
3961                        if (TRUE_FOR_CHECKED) {
3962                            if (v === true) {
3963                                output[k] = fieldsByIndex[k];
3964                            }
3965                        }
3966                        else {
3967                            output[k] = v;
3968                        }
3969                    });
3970                }
3971                else if (COLLECTION_TYPE === 'object') {
3972                    Object.entries(input).forEach(([k, v]) => {
3973                        if (v === true) {
3974                            output[fieldsByValue[k]] = k;
3975                        }
3976                    });
3977                }
3978                else if (COLLECTION_TYPE === 'array') {
3979                    input.forEach((v, k) => {
3980                        output[fieldsByValue[v]] = v;
3981                    });
3982                }
3983                return output;
3984            }
3985
3986            var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ngModelCtrl, DIRECTIVE_KEY);
3987
3988
3989            // Wait a digest cycle to link to existing form data if present, and then rebuild the internal model from it
3990            $timeout(function() {
3991                var existingData = ngModelCtrl.$viewValue;
3992                if (existingData) {
3993                    $scope.formData.checkboxCollection = generateInternalModel(existingData);
3994                }
3995            });
3996
3997        }
3998    }
3999    }]
4000)
4001
4002/**
4003 * Uses the WIMS API's postcodeLookup endpoint to get address data for a given postcode, and populates the given fields
4004 * 
4005 * @author ianbe
4006 */
4007wpd.directive('wpdutilsAddressLookup',
4008    ['wpdutilsCustomInputMixinService', 'wpdutilsAddressLookupService', '$timeout', '$interpolate', function(customInputMixin, AddressLookupService, $timeout, $interpolate) {
4009    return {
4010        require: 'ngModel',
4011        restrict: 'AE',
4012        scope: {
4013            fieldId: '<',           // {string} - the ID of the address lookup field as a whole
4014            fieldName: '<',         // {string:null} - if provided, the field name within the parent form to use as the object c
4014ontaining the final address data
4015                                    //                 if not provided, the address data will be bound to the parent directly
4016            formName: '<',          // {string:null} - if provided, the name of the inner form containing the search section
4017                                    //                 if not provided, "wpdutilsAddressLookup_" followed by a UUID will be used instead
4018
4019            required: '<',          // {boolean:false} - whether the address is required
4020                                    //                   in practice, the main address fields are outside of this form and have their own validation,
4021                                    //                   so this is already satisfied if the address section is shown
4022            inArea: '<',            // {boolean:false} - whether the postcode needs to satisfy postcodeAreaCheck validation
4023            label: '<',             // {string} - the label to display above the postcode search field
4024            loqate: '<',            // {string} - the handle of the API key preference to use for the address lookup service
4025            maxLengthLine1: '<',    // {number} - the maximum length of the address line 1 field, only used if the address line 1 field is not provided by the API
4026            maxLengthLine2: '<',    // {number} - the maximum length of the address line 2 field, only used if the address line 1 field is not provided by the API
4027        },
4028        templateUrl: 'wpdutilsAddressLookup.html',
4029        transclude: true,
4030        link: function($scope, $elem, $attrs, ctrl, transclude) {
4031
4032            transclude($scope.$parent, function(clone) {
4033                $elem.find('[data-wpdutilsaddressfield-address-fields]').prepend(clone);
4034            });
4035
4036            const DIRECTIVE_KEY = 'wpdutilsAddressLookup';
4037            
4038            //const INNER_FORM_NAME = $scope.formName ?? DIRECTIVE_KEY + '_' + crypto.randomUUID().replace('-', '');
4039            const INNER_FORM_NAME = DIRECTIVE_KEY;
4040            // $scope.innerFormName = INNER_FORM_NAME;
4041            
4042            const FIELDS = ['addressLine1', 'addressLine2', 'town', 'county', 'postcode'];
4043            
4044            $scope.required = ($scope.required == true);
4045            $scope.inArea = ($scope.inArea == true);
4046            $scope.label = $scope.label ?? 'Address';
4047
4048            // Interpolate the fieldId in case it contains variables
4049            $scope.fieldId = $interpolate($scope.fieldId)($scope.$parent);
4050
4051            var updatedViaNgChange = false;
4052
4053
4054            const getAddressField = function(handle) {
4055                const field = $elem.find('[data-wpdutilsaddresslookup-address=' + handle + ']');
4056                return field ? field.controller('ngModel') : null;
4057            }
4058            
4059            // Setup the loquate address lookup service
4060            const ADDRESS_LOOKUP_SERVICE = new AddressLookupService($scope.loqate);
4061
4062            // Populate the select list from a postcode search
4063            $scope.addressLookupList = null;
4064            $scope.currentlySearching = false;
4065            $scope.searchPostcode = function() {
4066                let lookupPostcode;
4067                var postcodeValid = false;
4068
4069                var postcodeSearchField = $scope[INNER_FORM_NAME].postcodeSearch;
4070
4071                if (postcodeSearchField?.$valid)
4072                {
4073                    lookupPostcode = postcodeSearchField.$viewValue;
4074                    postcodeValid = Object.keys(postcodeSearchField.$error)?.length === 0;
4075                }
4076    
4077                if (postcodeValid && (lookupPostcode?.length >= 4)) {
4078                    $scope.currentlySearching = true;
4079                    ADDRESS_LOOKUP_SERVICE.addressFind(lookupPostcode).then(
4080                        function(response) {
4081                            $scope.currentlySearching = false;
4082                            if (response?.result?.items) {
4083                                $scope.addressLookupList = response.result.items;
4084                                for (let item of $scope.addressLookupList) {
4085                                    item.label = `${item.text}, ${item.description}`;
4086                                }
4087                            }
4088                            else {
4089                                _noResultFromAddressLookup();
4090                            }
4091                        },
4092                        _noResultFromAddressLookup.bind(null)
4093                    );
4094    
4095                }
4096                else {
4097                    $scope.addressLookupList = [];
4098                }
4099
4100                checkValidity();
4101            };
4102
4103            const _noResultFromAddressLookup = function() {
4104                $scope.currentlySearching = false;
4105                $scope.addressLookupList = [];
4106            }
4107
4108            // Control the visibility of the manual site address fields 
4109            $scope.showAddressSection = false;
4110            $scope.setShowAddressSection = function(bool)
4111            {
4112                $timeout(function() {
4113                    $scope.showAddressSection = bool;
4114                    checkValidity();
4115
4116                    // If the address section is shown, don't care about validation on the (invisible) postcode search
4117                    if (bool) {
4118                        $scope[INNER_FORM_NAME].postcodeSearch.$setValidity('isPostcode', true);
4119                    }
4120                });
4121            }
4122
4123            $scope.currentlyGettingAddress = false;
4124            $scope.setAddress = function() {
4125                const barcode = $scope[INNER_FORM_NAME].selectedAddress.$modelValue?.id;
4126
4127                if (barcode !== null) {
4128                    $scope.currentlyGettingAddress = true;
4129                    ADDRESS_LOOKUP_SERVICE.addressRetrieve(barcode).then(
4130                        function(response) {
4131                            $scope.currentlyGettingAddress = false;
4132                            if (response?.result?.address) {
4133                                const address = response.result.address;
4134
4135                                // If the address line 1 (and 2) is not provided by the API, we need to construct it ourselves
4136                                // Adapted from Lloyd's PCR lookup
4137                                if (!address.line1) {
4138                                    // Filter out any empty fields
4139                                    const fieldList = [address.company, address.subBuilding, address.buildingName, address.buildingNumber, address.street]
4140                                        .filter((field) => field != null && field != '');
4141                                    // If exactly two fields are present, just use them as line 1 and line 2
4142                                    if (fieldList.length == 2) {
4143                                        [address.line1, address.line2] = fieldList;
4144                                    }
4145                                    // Otherwise, join the non-empty fields with a comma
4146                                    else {
4147                                        const separator = ', ';
4148                                        let line1 = fieldList.join(separator);
4149                                        // If too long for the field, trim down to last comma and move the rest to line 2
4150                                        const line1MaxLength = $scope.maxLengthLine1 || 255;
4151                                        const line2MaxLength = $scope.maxLengthLine2 || 255;
4152                                        if (line1.length > line1MaxLength) {
4153                                            const lastCommaAt = line1.lastIndexOf(separator, line1MaxLength);
4154                                            let line2 = line1.substring(lastCommaAt + separator.length);
4155                                            line1 = line1.substring(0, lastCommaAt);
4156                                            if (line2.length > line2MaxLength) {
4157                                                line2 = line2.substring(0, line2MaxLength);
4158                                            }
4159                                            address.line2 = line2;
4160                                        }
4161                                        address.line1 = (line1.length > 0) ? line1 : 'No first line of address found.';
4162                                    }
4163                                }
4164
4165                                // sometimes county is not provided by the API (e.g. London addresses), so add a placeholder if required
4166                                if ((address?.county === "") && (getAddressField('county').$$attr.required === true)) {
4167                                    address.county = '-';
4168                                }
4169
4170                                $scope.setShowAddressSection(true);
4171
4172                                const fields = {
4173                                    addressLine1: address.line1,
4174                                    addressLine2: address.line2,
4175                                    town: address.city,
4176                                    county: address.county,
4177                                    postcode: address.postcode,
4178                                };
4179                                for (const [field, value] of Object.entries(fields)) {
4180                                    const addressField = getAddressField(field);
4181                                    if (addressField) {
4182                                        addressField.$setViewValue(value);
4183                                        addressField.$setTouched();
4184                                        addressField.$render();
4185                                    }
4186                                }
4187
4188                                // Set the lookup field in case we come back
4189                                const postcodeSearchField = $scope[INNER_FORM_NAME].postcodeSearch;
4190                                const previousPostcodeValue = postcodeSearchField.$viewValue;
4191                                postcodeSearchField.$setViewValue(address.postcode);
4192                                if (postcodeSearchField.$invalid) {
4193                                    postcodeSearchField.$setViewValue(previousPostcodeValue);
4194                                }
4195                                postcodeSearchField.$render();
4196
4197                            }
4198                        },
4199                        function() {
4200                            $scope.currentlyGettingAddress = false;
4201                            $scope.setShowAddressSection(true);
4202                        }
4203                    );
4204                }
4205
4206                checkValidity();
4207            };
4208
4209            // Repopulate the internal model with the existing data if it exists
4210            if ($scope?.$parent?.vm?.formData) {
4211                // If the fieldName is a dot notation path or array notation path, split it and reduce to get the value
4212                //  - filter(Boolean) is just a truthiness test equivalent to !! to get rid of empty strings from the split
4213                let repopulated = false;
4214                let existingData = $scope.$parent.vm.formData;
4215                if ($scope.fieldName) {
4216                    existingData = $scope.fieldName.split(/\.|\[|\]/).filter(
4216Boolean).reduce((acc, key) => acc && acc[key], existingData);
4217                }
4218                for (const field of FIELDS) {
4219                    const addressField = getAddressField(field);
4220                    if (addressField) {
4221                        // Get the last part of the field name
4222                        const existingName = addressField.$name.split('.').pop();
4223                        if (existingData && existingData.hasOwnProperty(existingName)) {
4224                            addressField.$setViewValue(existingData[existingName]);
4225                            addressField.$render();
4226                            repopulated = true;
4227                        }
4228                    }
4229                }
4230                if (repopulated) {
4231                    $scope.setShowAddressSection(true);
4232                }
4233            }
4234
4235            const checkValidity = function() {
4236                if ($scope.required) {
4237                    let ok = true;
4238
4239                    // The lookup is valid if the address section is shown
4240                    if (!$scope.showAddressSection) {
4241                        ok = false;
4242                    }
4243                    
4244                    $scope[INNER_FORM_NAME].$setValidity('required', ok);
4245                }
4246            }
4247
4248            $scope[INNER_FORM_NAME].customErrorMessages = {
4249                required: "Required",
4250            };
4251
4252            // Set the initial valid state
4253            $timeout(() => {
4254                const statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY);
4255                checkValidity();
4256            });
4257        }
4258    };
4259}]);
4260
4261
4262/**
4263 * Uses the WIMS API's postcodeLookup endpoint to get address data for a given postcode, and populates the given fields
4264 * Special version :)
4265 * 
4266 * @author ianbe, michals
4267 */
4268wpd.directive('wpdutilsAddressLookupWims',
4269    ['wpdutilsCustomInputMixinService', 'wpdutilsAddressLookupService', '$timeout', '$interpolate',
4270    function(customInputMixin, AddressLookupService, $timeout, $interpolate) {
4271    return {
4272        require: 'ngModel',
4273        restrict: 'AE',
4274        scope: {
4275            fieldId: '<',           // {string}         - ID of the address lookup field as a whole
4276            fieldName: '<',         // {string|null}    - field name within the parent form for the address data object;
4277                                    //                    if omitted, address data binds to the parent directly
4278            formName: '<',          // {string|null}    - name of the inner form containing the search section;
4279                                    //                    if omitted, defaults to "wpdutilsAddressLookup_<UUID>"
4280            required: '<',          // {boolean:false}  - whether an address is required
4281            inArea: '<',            // {boolean:false}  - whether the postcode must pass postcodeAreaCheck validation
4282            label: '<',             // {string}         - label displayed above the postcode search field
4283            loqate: '<',            // {string}         - API key preference handle for the address lookup service
4284            maxLengthLine1: '<',    // {number}         - max length of address line 1 (used when the API omits it)
4285            maxLengthLine2: '<',    // {number}         - max length of address line 2 (used when the API omits it)
4286            lockAddressFields: '<', // {boolean:false}  - lock address fields after a successful WIMS lookup
4287            allowWimsLookup: '<',
4288            allowLoqateLookup: '<',
4289            allowIdnoLookup: '<'
4290        },
4291        templateUrl: 'wpdutilsAddressLookupWIMS.html',
4292        transclude: true,
4293        link: function($scope, $elem, $attrs, ctrl, transclude) {
4294
4295            // ─── Constants ────────────────────────────────────────────────────────────
4296
4297            const DEBUG          = false;
4298            const DIRECTIVE_KEY  = 'wpdutilsAddressLookupWIMS';
4299            const INNER_FORM     = DIRECTIVE_KEY;                   // alias for $scope[INNER_FORM]
4300            const ADDRESS_FIELDS = ['addressLine1', 'addressLine2', 'town', 'county', 'postcode'];
4301
4302            const ADDRESS_SOURCE = {
4303                MANUAL: 'MANUAL',   // Manual entry or Loqate fallback
4304                LOQATE: 'LOQATE',   // Loqate lookup (editable, same as MANUAL in practice)
4305                WIMS:   'WIMS',     // WIMS lookup - allows MPAN retrieval later
4306            };
4307
4308            // ─── Scope defaults ───────────────────────────────────────────────────────
4309
4310            $scope.addressSourceEnum      = ADDRESS_SOURCE;
4311            $scope.label                  = $scope.label ?? 'Address';
4312            $scope.required               = $scope.required        == true;
4313            $scope.lockAddressFields      = $scope.lockAddressFields == true;
4314            $scope.allowWimsLookup        = $scope.allowWimsLookup  != false;
4315            $scope.allowLoqateLookup      = $scope.allowLoqateLookup != false;
4316            $scope.inArea                 = $scope.inArea           == true;
4317            $scope.fieldId                = $interpolate($scope.fieldId)($scope.$parent);
4318            $scope.allowIdnoLookup        = $scope.allowIdnoLookup != false;
4319
4320            $scope.addressSource          = null;
4321            $scope.addressLookupList      = null;
4322            $scope.showAddressSection     = false;
4323            $scope.currentlySearching     = false;
4324            $scope.currentlyGettingAddress = false;     
4325            $scope.postcodeAreaCheckResult = null;
4326
4327            const ADDRESS_LOOKUP_SERVICE = new AddressLookupService($scope.loqate);
4328
4329            transclude($scope.$parent, function(clone) {
4330                $elem.find('[data-wpdutilsaddressfield-address-fields]').prepend(clone);
4331            });
4332
4333            // ─── Helpers ──────────────────────────────────────────────────────────────
4334
4335            const resetData = function(keepPostcode = false) {
4336                const savedValue = keepPostcode 
4337                    ? $scope[INNER_FORM]?.postcodeSearch?.$viewValue 
4338                    : null;
4339                // Clear address fields, unlock, and reset validation state
4340                ADDRESS_FIELDS.forEach(function(field) {
4341                    const addressField = getAddressField(field);
4342                    if (!addressField) return;
4343                    addressField.$setViewValue(null);
4344                    addressField.$setPristine();
4345                    addressField.$setUntouched();
4346                    addressField.$render();
4347                    addressField.$$element.prop('disabled', false);
4348                    addressField.$$element.removeAttr('disabled');
4349                });
4350
4351                // Clear postcode search field
4352                const postcodeSearch = $scope[INNER_FORM]?.postcodeSearch;
4353                if (postcodeSearch) {
4354                    postcodeSearch.$setViewValue(null);
4355                    postcodeSearch.$setPristine();
4356                    postcodeSearch.$setUntouched();
4357                    postcodeSearch.$render();
4358                }
4359
4360                // Wipe parent form data for this field
4361                const formData = $scope?.$parent?.vm?.formData;
4362                if (formData && $scope.fieldName) {
4363                    formData[$scope.fieldName]                              = null;
4364                    formData[$scope.fieldName + 'AddressSource']            = null;
4365                    formData[$scope.fieldName + 'MPAN']                     = null;
4366                }
4367
4368                // Reset all directive state
4369                $scope.addressSource              = null;
4370                $scope.addressLookupList          = null;
4371                $scope.showAddressSection         = false;
4372                $scope.currentlySearching         = false;
4373                $scope.currentlyGettingAddress    = false;
4374                $scope.postcodeAreaCheckResult    = null;
4375
4376                checkValidity();
4377
4378                if (savedValue) {
4379                    const postcodeSearch = $scope[INNER_FORM]?.postcodeSearch;
4380                    postcodeSearch.$setViewValue(savedValue);
4381                    postcodeSearch.$render();
4382                }
4383            };
4384
4385            $scope.resetData = resetData;
4386
4387            /** Returns the ngModel controller for a named address field, or null. */
4388            const getAddressField = function(handle) {
4389                const field = $elem.find('[data-wpdutilsaddresslookupwims-address=' + handle + ']');
4390                return field ? field.controller('ngModel') : null;
4391            };
4392
4393            /** Returns the current postcode search value. */
4394            const getPostcodeValue = function() {
4395                return cleanPostcode($scope[INNER_FORM]?.postcodeSearch?.$viewValue);
4396            };
4397
4398            /** Returns true only if the postcode field is valid and long enough to search. */
4399            const isPostcodeValid = function(postcode) {
4400                const field = $scope[INNER_FORM]?.postcodeSearch;
4401                return field?.$valid
4402                    && Object.keys(field.$error ?? {}).length === 0
4403                    && postcode?.length >= 4;
4404            };
4405
4406            /** Re-runs form validity so that the required error reflects the current state. */
4407            const checkValidity = function() {
4408                if ($scope.required) {
4409                    let ok = true;
4410                    if(!$scope.showAddressSection) {
4411                        ok = false;
4412                    }
4413                    $scope[INNER_FORM]?.$setValidity('required', ok);
4414                }
4415            };
4416
4417            // ─── Field locking ────────────────────────────────────────────────────────
4418
4419            /**
4420             * Returns true when address fields should be disabled.
4421             */
4422            $scope.addressFieldsLocked = function() {
4423                return $scope.showAddressSection
4424                    && $scope.lockAddressFields
4425                    && $scope.addressSource === ADDRESS_SOURCE.WIMS;
4426            };
4427
4428            /** Applies or removes the disabled attribute on all address fields to match the lock state. */
4429            const applyFieldLocking = function() {
4430                const locked = $scope.addressFieldsLocked();
4431                ADDRESS_FIELDS.forEach(function(field) {
4432                    const addressField = getAddressField(field);
4433                    if (!addressField) return;
4434                    addressField.$$element.prop('disabled', locked);
4435                    if (!locked) addressField.$$element.removeAttr('disabled');
4436                });
4437            };
4438
4439            // ─── Address section visibility ───────────────────────────────────────────
4440
4441            /** Programmatic show/hide - also broadcast via $on('showAddressSection') below. */
4442            $scope.setShowAddressSection = function(show) {
4443                $timeout(function() {
4444                    $scope.showAddressSection = show;
4445                    checkValidity();
4446
4447                    if (show) {
4448                        // Suppress postcode-search validation while address fields are visible
4449                        $scope[INNER_FORM].postcodeSearch.$setValidity('isPostcode', true);
4450                        applyFieldLocking();
4451                        return;
4452                    }
4453
4454                    // ── Hiding: clear fields, reset state ────────────────────────────
4455                    $scope.$parent.vm.formData[$scope.fieldName + 'AddressSource'] = null;
4456                    $scope.$parent.vm.formData[$scope.fieldName + 'MPAN']          = null;
4457
4458                    ADDRESS_FIELDS.forEach(function(field) {
4459                        const addressField = getAddressField(field);
4460                        if (!addressField) return;
4461                        addressField.$setViewValue(null);
4462                        addressField.$setPristine();
4463                        addressField.$setUntouched();
4464                        addressField.$render();
4465                        // Always unlock when hiding
4466                        addressField.$$element.prop('disabled', false);
4467                        addressField.$$element.removeAttr('disabled');
4468                    });
4469
4470                    // Restore postcode into both the address field and the search f
4470ield
4471                    if ($scope[INNER_FORM]?.postcodeSearch?.$viewValue) {
4472                        const addressPostcodeField = getAddressField('postcode');
4473                        if (addressPostcodeField) {
4474                            addressPostcodeField.$setViewValue(getPostcodeValue());
4475                            addressPostcodeField.$render();
4476                        }
4477                        const postcodeSearch = $scope[INNER_FORM]?.postcodeSearch;
4478                        if (postcodeSearch) {
4479                            postcodeSearch.$setViewValue(getPostcodeValue());
4480                            postcodeSearch.$render();
4481                        }
4482                    }
4483
4484                    $scope.addressLookupList      = null;
4485                    $scope.postcodeAreaCheckResult = null;
4486                });
4487            };
4488
4489            $scope.$on('showAddressSection', function(event, bool) {
4490                if (!bool) {
4491                    resetData();  
4492                    return;
4493                }
4494                $scope.setShowAddressSection(bool);
4495            });
4496
4497            $scope.setShowAddressSectionManual = function() {
4498                $scope.addressSource = ADDRESS_SOURCE.MANUAL;
4499                _persistAddressMeta();
4500                $scope.setShowAddressSection(true);
4501            };
4502
4503            // ─── Postcode search ──────────────────────────────────────────────────────
4504
4505            $scope.searchPostcode = function() {
4506                resetData(true);
4507                $scope.currentlySearching = true;
4508
4509                const lookupPostcode = getPostcodeValue();
4510                if (!isPostcodeValid(lookupPostcode)) {
4511                    DEBUG && console.log(DIRECTIVE_KEY + ': searchPostcode: postcode invalid, not searching');
4512                    $scope.addressLookupList  = [];
4513                    $scope.currentlySearching = false;
4514                    checkValidity();
4515                    return;
4516                }
4517
4518                ADDRESS_LOOKUP_SERVICE.postcodeLookup(lookupPostcode)
4519                    .then(response => _handleWimsResponse(response, lookupPostcode, $scope.allowWimsLookup))
4520                    .then(response => _handleLoqateResponse(response, lookupPostcode, $scope.allowLoqateLookup))
4521                    .catch(err     => _handleLookupError(err))
4522                    .finally(()    => _finaliseLookup());
4523            };
4524
4525            // ─── Lookup response handlers ─────────────────────────────────────────────
4526
4527            function cleanPostcode(value) {
4528                if(!value) return value;
4529                const formatted = (value + '')
4530                    .replace(/\s/g, '')
4531                    .toUpperCase();
4532                return formatted.substring(0,formatted.length - 3) + ' ' + formatted.substring(formatted.length - 3);
4533            }
4534
4535            function _handleWimsResponse(response, lookupPostcode, allowLookup) {
4536                if (!allowLookup) {
4537                    DEBUG && console.log(DIRECTIVE_KEY + ': skipping WIMS, falling through to Loqate');
4538                    return ADDRESS_LOOKUP_SERVICE.addressFind(lookupPostcode);
4539                }
4540
4541                DEBUG && console.log(DIRECTIVE_KEY + ': WIMS response:', response);
4542
4543                if ((response.inArea === false && response.success) && $scope.inArea) {
4544                    DEBUG && console.log(DIRECTIVE_KEY + ': WIMS out of area');
4545                    _outOfAreaResult(response);
4546                    return;
4547                }
4548
4549                if (response?.results) {
4550                    $scope.postcodeAreaCheckResult = null;
4551                    $scope.addressLookupList = response.results.map(_mapWimsResult);
4552                    $scope.addressSource     = ADDRESS_SOURCE.WIMS;
4553                    DEBUG && console.log(DIRECTIVE_KEY + ': WIMS mapped results:', $scope.addressLookupList);
4554                } else {
4555                    $scope.addressSource = ADDRESS_SOURCE.LOQATE;
4556                    DEBUG && console.log(DIRECTIVE_KEY + ': no WIMS results, falling through to Loqate');
4557                }
4558
4559                if (!$scope.addressLookupList?.length) {
4560                    DEBUG && console.log(DIRECTIVE_KEY + ': starting Loqate lookup');
4561                    return ADDRESS_LOOKUP_SERVICE.addressFind(lookupPostcode);
4562                }
4563            }
4564
4565            function _handleLoqateResponse(response,lookupPostcode, allowLookup) {
4566                if (!allowLookup) {
4567                    DEBUG && console.log(DIRECTIVE_KEY + ': skipping Loqate');
4568                    return;
4569                }
4570                if (!response) {
4571                    DEBUG && console.log(DIRECTIVE_KEY + ': no Loqate response, ending');
4572                    return;
4573                }
4574
4575                DEBUG && console.log(DIRECTIVE_KEY + ': Loqate response:', response);
4576
4577                if (response?.result?.items) {
4578                    $scope.postcodeAreaCheckResult = null;
4579                    $scope.addressLookupList = response.result.items
4580                        .map(_mapLoqateResult)
4581                        .filter(item => {
4582                            const label = item.label.replace(/\s/g, '').toUpperCase();
4583                            return label.includes(lookupPostcode.replace(/\s/g, '').toUpperCase());
4584                        });
4585                    if($scope.addressLookupList.length > 0) {
4586                        $scope.addressSource     = ADDRESS_SOURCE.LOQATE;
4587                        DEBUG && console.log(DIRECTIVE_KEY + ': Loqate mapped results:', $scope.addressLookupList);
4588                    } else {
4589                        $scope.addressSource = ADDRESS_SOURCE.MANUAL;
4590                        DEBUG && console.log(DIRECTIVE_KEY + ': Loqate mapped no matching results, falling through to manaul');
4591                        _noResults();
4592                    }
4593                } else {
4594                    $scope.addressSource = ADDRESS_SOURCE.MANUAL;
4595                    DEBUG && console.log(DIRECTIVE_KEY + ': Loqate no results, falling through to manual');
4596                    _noResults();
4597                }
4598            }
4599
4600            function _handleLookupError(err) {
4601                $scope.addressSource = ADDRESS_SOURCE.MANUAL;
4602                console.error(DIRECTIVE_KEY + ':' + getPostcodeValue() + ':address lookup error:', err);
4603                _noResults();
4604            }
4605
4606            function _finaliseLookup() {
4607                DEBUG && console.log(DIRECTIVE_KEY + ':' + getPostcodeValue() + ':lookup done - list:', $scope.addressLookupList, '| source:', $scope.addressSource);
4608
4609                $scope.addressLookupList = $scope.addressLookupList.slice().sort(function(a, b) {
4610                    var strA = a.addressLine2 || a.text || a.addressLine1 || a.label || '';
4611                    var strB = b.addressLine2 || b.text || b.addressLine1 || b.label || '';
4612
4613                    // Pull out the last number in the string to use as house number
4614                    function houseNum(str) {
4615                        if (!str) return Infinity;
4616                        if (/^\d+$/.test(str)) return parseInt(str, 10);
4617                        var nums = str.match(/\b(\d+)(?:-\d+)?\b/g);
4618                        return nums ? parseInt(nums[nums.length - 1], 10) : Infinity;
4619                    }
4620
4621                    // Pull out 'street name'
4622                    // FLAT 1 COOL HOUSE STREET -> COOL HOUSE STREET
4623                    // HOUSE COOL HOUSE STREET - > HOUSE COOL HOUSE STREET
4624                    function street(str) {
4625                        if (!str) return '';
4626                        var m = str.match(/^(.*?\b\d+[\w-]*\b)\s+(.+
4626)$/);
4627                        return m ? m[2].trim().toUpperCase() : str.trim().toUpperCase();
4628                    }
4629
4630                    return street(strA).localeCompare(street(strB)) ||
4631                        houseNum(a.addressLine1 || strA) - houseNum(b.addressLine1 || strB);
4632                });
4633
4634                $scope.currentlySearching = false;
4635                checkValidity();
4636            }
4637
4638            function _outOfAreaResult(data) {
4639                DEBUG && console.log(DIRECTIVE_KEY + ':' + getPostcodeValue() + ':out of area result:', data);
4640                _noResults();
4641            }
4642
4643            function _noResults() {
4644                $scope.currentlySearching = false;
4645                $scope.addressLookupList  = [];
4646                _persistAddressMeta();
4647                $scope.setShowAddressSectionManual();
4648            }
4649
4650            // ─── Result mapping ───────────────────────────────────────────────────────
4651
4652            function _mapWimsResult(item) {
4653                const lines = Object.keys(item)
4654                    .filter(k => k.indexOf('addressline') === 0)
4655                    .sort()
4656                    .map(k => item[k]);
4657                const county       = ('addressline9' in item) ? lines.pop() : '';
4658                const town         = lines.pop();
4659                const addressLine1 = lines.shift();
4660                const addressLine2 = lines.length ? lines.join(', ') : '';
4661                return {
4662                    id: item.TempID,
4663                    county,
4664                    town,
4665                    addressLine1,
4666                    addressLine2,
4667                    label: [addressLine1, addressLine2, town, county].filter(Boolean).join(', '),
4668                };
4669            }
4670
4671            function _mapLoqateResult(item) {
4672                return { ...item, label: [item.text, item.description].filter(Boolean).join(', ') };
4673            }
4674
4675            // ─── Set address fields from selection ────────────────────────────────────
4676
4677            /**
4678             * Writes values into address fields and syncs the postcode search field.
4679             * If the postcode would make the search field invalid, the previous value is restored.
4680             */
4681            const _applyFieldValues = function(fields) {
4682                for (const [field, value] of Object.entries(fields)) {
4683                    const addressField = getAddressField(field);
4684                    const maxLength = parseInt(addressField.$$element.attr('maxlength'), 10) || 255;
4685                    if (addressField) {
4686                        addressField.$setViewValue(value.substring(0,maxLength));
4687                        addressField.$setTouched();
4688                        addressField.$render();
4689                    }
4690                }
4691
4692                const postcodeSearch  = $scope[INNER_FORM].postcodeSearch;
4693                const previousPostcode = postcodeSearch.$viewValue;
4694                postcodeSearch.$setViewValue(fields.postcode);
4695                if (postcodeSearch.$invalid) {
4696                    DEBUG && console.log(DIRECTIVE_KEY + ': postcode search invalid after update, reverting');
4697                    postcodeSearch.$setViewValue(previousPostcode);
4698                }
4699                postcodeSearch.$render();
4700            };
4701
4702            /** Persists address metadata (source, id) to the parent form data. */
4703            const _persistAddressMeta = function(extraData) {
4704                const formData = $scope?.$parent?.vm?.formData;
4705                const fieldName = $scope.fieldName;
4706                if (formData && fieldName) {
4707                    if (!formData[fieldName]) {
4708                        formData[fieldName] = {};
4709                    }
4710                    formData[fieldName].addressSource = $scope.addressSource;
4711                    if (extraData) {
4712                        Object.assign(formData[fieldName], extraData);
4713                    }
4714                } else {
4715                    console.error(DIRECTIVE_KEY + ': failed - missing formData or fieldName');
4716                }
4717            };
4718
4719            $scope.setAddress = function() {
4720                DEBUG && console.log(DIRECTIVE_KEY + ': setAddress, source =', $scope.addressSource);
4721
4722                if ($scope.addressSource === ADDRESS_SOURCE.WIMS) {
4723                    _setAddressFromWims();
4724                } else if ($scope.addressSource === ADDRESS_SOURCE.LOQATE) {
4725                    _setAddressFromLoqate();
4726                }
4727
4728                // Always persist the source, even for LOQATE (meta only - fields set async above)
4729                _persistAddressMeta();
4730                checkValidity();
4731            };
4732
4733            function _setAddressFromWims() {
4734                const selected = $scope.addressLookupList.find(
4735                    item => item.id === $scope[INNER_FORM].selectedAddress.$modelValue?.id
4736                );
4737                if (!selected) return;
4738                DEBUG && console.log(DIRECTIVE_KEY + ': WIMS selected:', selected);
4739
4740                $scope.currentlyGettingAddress = true;
4741                $scope.postcodeAreaCheckResult = null;
4742
4743                if (!$scope.allowIdnoLookup) {
4744                    // No IDNO check needed (e.g. correspondence address) - skip straight to populating
4745                    $scope.currentlyGettingAddress = false;
4746                    _populateWimsFields(selected);
4747                    return;
4748                }
4749
4750                ADDRESS_LOOKUP_SERVICE.checkMPANForIDNO(selected.id, getPostcodeValue())
4751                    .then(response => {
4752                        $scope.currentlyGettingAddress = false;
4753                        console.log(response);
4754                        if (!response?.wpd && response.success) {
4755                            // Address is outside WPD area - show DNO info and block progression
4756                            $scope.addressLookupList = null;
4757                            $scope.postcodeAreaCheckResult = {
4758                                manual:  true,
4759                                message: "Your postcode is maintained by an Independent Network Operator (iDNO)",
4760                                dnos: response?.success ? [{
4761                                    name:      (response.companyName || response.dno || '').trim(),
4762                                    dno:       (response.dno || '').trim(),
4763                                    telephone: (response.companyPhoneNumber || '').trim(),
4764                                    isWPD:     '0',
4765                                    url:       null,
4766                                    prefixNo:  response.prefixNo
4767                                }] : []
4768                            };
4769                            const postcodeEl = $elem.find('[data-postcode-area-check]');
4770                            if (postcodeEl.length) {
4771                                const postcodeNgModelCtrl = postcodeEl.controller('ngModel');
4772                                if (postcodeNgModelCtrl) {
4773                                    postcodeNgModelCtrl.customErrorMessages['postcodeAreaCheck'] = $scope.postcodeAreaCheckResult.message;
4774                                    postcodeNgModelCtrl.$setValidity('postcodeAreaCheck', false);
4775                                    postcodeNgModelCtrl.$setTouched();
4776                                }
4777                            }
4778                            return; // stop here, don't populate fields
4779                        }
4780                        // In area - proceed to populate
4781                        _populateWimsFields(selected);
4782                    })
4783                    // If the lookup for iDNO fails, just populate the fields as if it succeeded (we are permissive here)
4784                    .catch(function(err) {console.error("iDNO check error", err); _populateWimsFields(selected)});
4785            }
4786
4787            function _populateWimsFields(selected) {
4788                const fields = {
4789                    addressLine1: selected.addressLine1,
4790                    addressLine2: selected.addressLine2,
4791                    town:         selected.town,
4792                    county:       selected.county,
4793                    postcode:     selected.postcode,
4794                };
4795                if (!fields.county   && getAddressField('county')?.$$attr.required   === true) fields.county   = '-';
4796                if (!fields.postcode && getAddressField('postcode')?.$$attr.required === true) {
4797                    fields.postcode = $scope[INNER_FORM].postcodeSearch.$viewValue.toUpperCase();
4798                }
4799                // Clear any previous out-of-area error state on the postcode field
4800                const postcodeEl = $elem.find('[data-postcode-area-check]');
4801                if (postcodeEl.length) {
4802                    const postcodeNgModelCtrl = postcodeEl.controller('ngModel');
4803                    if (postcodeNgModelCtrl) {
4804                        postcodeNgModelCtrl.customErrorMessages['postcodeAreaCheck'] = null;
4805                        postcodeNgModelCtrl.$setValidity('postcodeAreaCheck', true);
4806                    }
4807                }
4808                $scope.setShowAddressSection(true);
4809                $scope.postcodeAreaCheckResult = null;
4810                _applyFieldValues(fields);
4811                _persistAddressMeta(selected.id ? { id: selected.id } : { id: null });
4812                $timeout(applyFieldLocking);
4813            }
4814
4815            $scope.$watch(
4816                function() { return ctrl.$modelValue; },
4817                function(newVal, oldVal) {
4818                    if (newVal !== oldVal && $scope.postcodeAreaCheckResult?.manual) {
4819                        $scope.postcodeAreaCheckResult = null;
4820                    }
4821                }
4822            );
4823
4824            function _setAddressFromLoqate() {
4825                const barcode = $scope[INNER_FORM].selectedAddress.$modelValue?.id;
4826                if (barcode === null) return;
4827
4828                $scope.currentlyGettingAddress = true;
4829                DEBUG && console.log(DIRECTIVE_KEY + ': doing Loqate retrieve for', barcode);
4830
4831                ADDRESS_LOOKUP_SERVICE.addressRetrieve(barcode).then(
4832                    function(response) {
4833                        $scope.currentlyGettingAddress = false;
4834                        DEBUG && console.log(DIRECTIVE_KEY + ': Loqate retrieve response:', response);
4835
4836                        if (!response?.result?.address) return;
4837                        const address = response.result.address;
4838
4839                        // Build line1 (and optionally line2) when the API doesn't provide them
4840                        if (!address.line1) {
4841                            _buildLoqateAddressLines(address);
4842                        }
4843
4844                        // Fill placeholder for required county when the API returns empty string
4845                        if (address?.county === '' && getAddressField('county').$$attr.required === true) {
4846                            address.county = '-';
4847                        }
4848
4849                        $scope.setShowAddressSection(true);
4850                        $scope.postcodeAreaCheckResult = null;
4851
4852                        _applyFieldValues({
4853                            addressLine1: address.line1,
4854                            addressLine2: address.line2,
4855                            town:         address.city,
4856                            county:       address.county,
4857                            postcode:     address.postcode,
4858                        });
4859
4860                        _persistAddressMeta();
4861
4862                        // Loqate addresses are never locked - no applyFieldLocking call here
4863                    },
4864                    function() {
4865                        $scope.currentlyGettingAddress = false;
4866                        $scope.setShowAddressSection(true);
4867                    }
4868                );
4869            }
4870
4871            /**
4872             * Mutates `address` to populate line1/line2 from sub-fields when the API omits them.
4873             * Adapted from Lloyd's PCR lookup.
4874             */
4875            function _buildLoqateAddressLines(address) {
4876                const line1MaxLength = $scope.maxLengthLine1 || 255;
4877                const line2MaxLength = $scope.maxLengthLine2 || 255;
4878                const separator      = ', ';
4879
4880                const parts = [address.company, address.subBuilding, address.buildingName, address.buildingNumber, address.street]
4881                    .filter(f => f != null && f !== '');
4882
4883                if (parts.length === 2) {
4884                    [address.line1, address.line2] = parts;
4885                    return;
4886                }
4887
4888                let line1 = parts.join(separator);
4889
4890                if (line1.length > line1MaxLength) {
4891                    const splitAt = line1.lastIndexOf(separator, line1MaxLength);
4892                    let   line2   = line1.substring(splitAt + separator.length);
4893                    line1         = line1.substring(0, splitAt);
4894                    if (line2.length > line2MaxLength) line2 = line2.substring(0, line2MaxLength);
4895                    address.line2 = line2;
4896                }
4897
4898                address.line1 = line1.length > 0 ? line1 : 'No first line of address found.';
4899            }
4900
4901            // ─── Pre-populate from existing form data ─────────────────────────────────
4902
4903            (function repopulateFromExistingData() {
4904                if (!$scope?.$parent?.vm?.formData) return;
4905
4906                DEBUG && console.log(DIRECTIVE_KEY + ': repopulating address fields from existing form data');
4907
4908                let existingData = $scope.$parent.vm.formData;
4909                if ($scope.fieldName) {
4910                    existingData = $scope.fieldName
4911                        .split(/\.|\[|\]/)
4912                        .filter(Boolean)
4913                        .reduce((acc, key) => acc && acc[key], existingData);
4914                }
4915
4916                let repopulated = false;
4917                ADDRESS_FIELDS.forEach(function(field) {
4918                    const addressField = getAddressField(field);
4919                    if (!addressField) return;
4920                    const existingKey = addressField.$name.split('.').pop();
4921                    if (existingData?.hasOwnProperty(existingKey)) {
4922                        addressField.$setViewValue(existingData[existingKey]);
4923                        addressField.$render();
4924                        repopulated = true;
4925                    }
4926                });
4927
4928                if (repopulated) $scope.setShowAddressSection(true);
4929            })();
4930
4931            // ─── Initialisation ───────────────────────────────────────────────────────
4932
4933            $scope[INNER_FORM].customErrorMessages = { required: 'Required' };
4934
4935            $timeout(function() {
4936                customInputMixin.initStatusSyncers($scope, INNER_FORM, ctrl, DIRECTIVE_KEY);
4937                // Repopulate address source
4938                if ($scope?.$parent?.vm?.formData) {
4939                    const data = $scope.fieldName
4940                        ? $scope.$parent.vm.formData[$scope.fieldName]
4941                        : $scope.$parent.vm.formData;
4942                    if (data?.addressSource) {
4943                        $scope.addressSource = data.addressSource;
4944                    }
4945                }
4946                $scope.$watch(
4947                    () => $scope[INNER_FORM]?.postcodeSearch?.postcodeAreaCheckResult,
4948                    (newVal) => { $scope.postcodeAreaCheckResult = newVal; },
4949                    true
4950                );
4951                checkValidity();
4952                applyFieldLocking();
4953            });
4954        }
4955    };
4956}]);
4957
4958
4959
4960/**
4961 * Template: /app/views/ng_templates/wpd/utils/upload-field.html
4962 * Style: ???
4963 *
4964 * Bringing the uploadField into 2025, so the controller doesn't need to handle the actual upload behaviour
4965 *
4966 * @author ianbe
4967 */
4968wpd.directive('wpdutilsUploadField',
4969    [ 'wpdutilsCustomInputMixinService', '$asset_upload', '$q', function(customInputMixin, $asset_upload, $q) {
4970        return {
4971            require: 'ngModel',
4972            restrict: 'AE',
4973            scope: {
4974                isReadonly: '<',            // {boolean}
4975                asType: '<',                // {string} ('string'|undefined|null)
4976                fieldId: '<',               // {string}
4977                fieldName: '<',             // {string}
4978                isDisabled: '<',            // {boolean}
4979                isRequired: '<',            // {boolean}
4980                placeholder: '<',           // {string}
4981                allowedTypes: '<',          // {Object[]}
4982				singleFile: '<',            // {boolean}
4983                minFiles: '<',
4984                maxFiles: '<',
4985                multiSelection: '<',        // {boolean}
4986                privateAsset: '<',          // {boolean}
4987                hideMaxFiles: '<',
4988
4989                onStart: '=',               // {function}
4990                onProgress: '=',            // {function}
4991                onSuccess: '=',             // {function}
4992                onInvalid: '=',             // {function}
4993
4994                buttonLabel: '<',           // {string}
4995                deleteButtonLabel: '<',     // {string|boolean}
4996                // TODO unimplemented
4997                deleteButtonClass: '<',
4998                browseButtonClass: '<',
4999            },
5000            templateUrl: 'wpdutilsUploadField.html',
5001            link: function($scope, $elem, $attrs, ctrl) {
5002                const DIRECTIVE_KEY = 'wpdutilsUploadField';
5003                const INNER_FORM_NAME = $scope.fieldName.replace('.', '_') + '_' + DIRECTIVE_KEY;
5004
5005                $scope.singleFile = ($scope.singleFile == true);
5006				$scope.maxFiles = $scope.singleFile ? 1 : ($scope.maxFiles ?? 5);
5007				$scope.multiSelection = $scope.singleFile ? false : ($scope.multiSelection == true);
5008                $scope.isRequired = ($scope.isRequired == true);
5009
5010                $scope.browseButtonId = $scope.fieldId + "__browse-button";
5011                $scope.deleteSectionId = $scope.fieldId + "__delete";
5012
5013                if ($scope.deleteButtonLabel === true) {
5014                    $scope.deleteButtonLabel = 'Remove';
5015                }
5016				
5017				const filesOverMaximum = [];
5018
5019                var unsetFormData = function() {
5020                    $scope.formData = [];
5021                    filesOverMaximum.length = 0;
5022                };
5023                unsetFormData();
5024
5025                // Repopulate the internal model with the existing data if it exists
5026                if ($scope?.$parent?.vm?.formData) {
5027                    // If the fieldName is a dot notation path or array notation path, split it and reduce to get the value
5028                    //  - filter(Boolean) is just a truthiness test equivalent to !! to get rid of empty strings from the split
5029                    //  - not sure how this works if the target value is falsish, so be careful if you want to apply this to another field type
5030                    const existingData = $scope.fieldName.split(/\.|\[|\]/).filter(Boolean).reduce((acc, key) => acc && acc[key], $scope.$parent.vm.formData);
5031                    if (existingData !== undefined) {
5032						if ($scope.singleFile) {
5033							$scope.formData.push(existingData);
5034						} else {
5035							$scope.formData.push(...existingData);
5036						}
5037                    }
5038                }
5039
5040                $scope.uploading = false;
5041
5042                var uploadOnSuccess = function(files) {
5043                    if ($scope.onSuccess) {
5044                        $scope.onSuccess.call(this, files, _uploadOnSuccess);
5045                    }
5046                    else {
5047                        _uploadOnSuccess(files);
5048                    }
5049                };
5050
5051                var _uploadOnSuccess = function(files) {
5052                    filesOverMaximum.length = 0;
5053
5054                    if (!files) return;
5055
5056                    for (var i in files) {
5057                        if ($scope.formData.length < $scope.maxFiles) {
5058                            filesOverMaximum.push(files[i]);
5059                        }
5060                        else {
5061                            $scope.filesOverMaximum.push(files[i]);
5062                        }
5063                    }
5064
5065                    if (filesOverMaximum.length) {
5066                        // TODO trigger the over-max message
5067                    }
5068
5069                    $scope.updateNgModel();
5070                    ctrl.$validate();
5071
5072                    uploadOnFinally();
5073                }
5074
5075                var _uploadOnProgress = function(progress) {
5076                };
5077
5078                $scope.previousUploadPercentage = 0;
5079                $scope.uploadPercentage = 0;
5080                var uploadOnProgress = function(progress) {
5081                    if (progress?.total) {
5082                        $scope.previousUploadPercentage = $scope.uploadPercentage;
5083                        $scope.uploadPercentage = progress.total.percent;
5084                    }
5085
5086                    if ($scope.onProgress) {
5087                        $scope.onProgress.call(this, progress, _uploadOnProgress);
5088                    }
5089                    else {
5090                        _uploadOnProgress(progress);
5091                    }
5092                };
5093
5094                var _uploadOnInvalid = function(data, error) {
5095                    let message = "There was a problem uploading your file";
5096                    const messageExtra = error.response ?? error.message;
5097                    message += (messageExtra) ? ":\n\n" + messageExtra : ".";
5098                    window.alert(message);
5099                };
5100
5101                var uploadOnInvalid = function(data, error) {
5102                    if ($scope.onInvalid) {
5103                        $scope.onInvalid.call(this, data, error, _uploadOnInvalid);
5104                    }
5105                    else {
5106                        _uploadOnInvalid(data, error);
5107                    }
5108
5109                    uploadOnFinally();
5110                }
5111
5112                var _uploadOnStart = function(files) {
5113
5114                };
5115
5116                var uploadOnStart = function(files) {
5117                    $scope.uploading = true;
5118                    $scope.previousUploadPercentage = 0;
5119                    $scope.uploadPercentage = 0;
5120
5121                    if ($scope.onStart) {
5122                        $scope.onStart.call(this, files, _uploadOnStart);
5123                    }
5124                    else {
5125                        _uploadOnStart(files);
5126                    }
5127                }
5128
5129                var uploadOnFinally = function() {
5130                    $scope.uploading = false;
5131                }
5132
5133                $scope.removeFile = function(index) {
5134                    $scope.formData.splice(index, 1);
5135                    $scope.updateNgModel();
5136                    ctrl.$validate();
5137                };
5138
5139                var numberOfValidFiles = function() {
5140                    const value = $scope.formData;
5141                    if (typeof value != 'object') {
5142                        return false;
5143                    }
5144                    return value.filter(o => o.result == 'success').length;
5145                }
5146
5147                ctrl.$validators.required = function(modelValue, viewValue) {
5148                    if (!$scope.isRequired) return true;
5149
5150                    if ((ctrl.$isEmpty(viewValue)) || (typeof viewValue != 'object')) {
5151                        return false;
5152                    }
5153                    
5154					if ($scope.singleFile) {
5155						if (viewValue.result != 'success') {
5156							return false;
5157						}
5158					}
5159					else if (!viewValue.some(o => o.result == 'success')) {
5160                        return false;
5161                    }
5162
5163                    return true;
5164                };
5165
5166                ctrl.$validators.min = function(modelValue, viewValue) {
5167                    if (($scope.minFiles == null) || ($scope.minFiles < 1)) return true;
5168                    return (numberOfValidFiles() >= $scope.minFiles);
5169                }
5170
5171                ctrl.$validators.max = function(modelValue, viewValue) {
5172                    if (($scope.maxFiles == null) || ($scope.maxFiles < 1)) return true;
5173                    return (numberOfValidFiles() <= $scope.maxFiles);
5174                }
5175
5176                var updatedViaNgChange = false;
5177                $scope.updateNgModel = function() {
5178                    updatedViaNgChange = true;
5179
5180                    if ($scope[INNER_FORM_NAME].$valid) {
5181                        ctrl.$setViewValue($scope.singleFile ? ($scope.formData[0] ?? null) : $scope.formData);
5182                    }
5183                    else {
5184                        ctrl.$setViewValue(undefined);
5185                    }
5186                };
5187
5188                var cancelWatch = $scope.$watch(() => ctrl.$modelValue, function(newVal, oldVal) {
5189                    updatedViaNgChange = false;
5190                });
5191
5192                var statusSyncerCancelFns = customInputMixin.initStatusSyncers($scope, INNER_FORM_NAME, ctrl, DIRECTIVE_KEY);
5193
5194                var options = {
5195                    browse_button: $scope.browseButtonId,
5196                    drop_element: $scope.browseButtonId,
5197                    types: $scope.allowedTypes ?? ['image', 'document'],
5198                    action: 'CoreAssetFrontUploader.upload_JSON',
5199                    multi_selection: $scope.multiSelection ?? false,
5200                    paramsFn: function () {
5201                        return {
5202                            privateAsset: $scope.privateAsset ?? false
5203                        };
5204                    }
5205                };
5206                $scope.asset_upload = $asset_upload(options, $scope);
5207                $scope.asset_upload.onOK(uploadOnSuccess);
5208                $scope.asset_upload.onProgress(uploadOnProgress);
5209                $scope.asset_upload.onInvalid(uploadOnInvalid);
5210                $scope.asset_upload.onStart(uploadOnStart);
5211
5212            }
5213
5214        };
5215    }]);
5216
5217
5218
5219// ===========================================================================================
5220// ------------------------------ Form validation directives ---------------------------------
5221// ===========================================================================================
5222
5223wpd.service('wpdValidationService', ['$requester', '$q', function($requester, $q) {
5224
5225    this.isPostcode = function(value) {
5226        const PATTERN = /^\s*([a-z]{1,2}\d(\d|[a-z])?)\s*(\d[a-z]{2})\s*$/gi;
5227        return PATTERN.test(value);
5228    };
5229}]);
5230
5231/**
5232 * @return {boolean} - true if the supplied {string} viewValue is a postcode within WPD's area of operation. This validator is asynchronous.
5233 *
5234 * @author noahm
5235 */
5236wpd.directive('postcodeAreaCheck', ['wpdutilsRequester', '$q', 'wpdutilsCustomInputMixinService', function(wpdutilsRequester, $q, customInputMixin) {
5237    return {
5238        require: 'ngModel',
5239        link: function($scope, $elem, $attrs, ctrl) {
5240            const json = wpdutilsRequester({
5241                areaCheck: 'WPDUtilsFront.wimsPostcodeLookup_JSON'
5242            });
5243            const DEBUG = false;
5244            const DIRECTIVE_KEY = 'postcodeAreaCheck';
5245
5246            const loadingSpinnerProperty = setupLoadingSpinner($scope, $elem, DIRECTIVE_KEY, customInputMixin);
5247
5248            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5249            if (!ctrl.postcodeAreaCheckResult) ctrl.postcodeAreaCheckResult = {};
5250
5251            $scope[loadingSpinnerProperty] = false;
5252
5253            // Set up the custom error message for this ngModel on failure of this validator
5254            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5255            ctrl.postcodeAreaCheckResult = null;
5256
5257            ctrl.$asyncValidators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5258                return $q(function(resolve, reject) {
5259
5260                    if ($scope.postcodeAreaCheckResult?.manual == true) {
5261                        console.log("postcode area check not triggering as we're going manual mode");
5262                        resolve();
5263                        return;
5264                    }
5265
5266                    if (ctrl.$isEmpty(modelValue) || $scope.$eval($attrs.ngDisabled)) {
5267                        // consider empty models to be valid
5268                        ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5269                        ctrl.postcodeAreaCheckResult = null;
5270                        resolve();
5271                    }
5272
5273                    if (viewValue) {
5274                        const request = json.areaCheck({postcode: viewValue}, function(response) {
5275                            DEBUG && console.log("postcodeAreaCheck response:", response);
5276                            // Success here means 'is this a postcode that we successfully found' during lookup
5277                            const isSuccess = response?.success === true;
5278                            const isValid   = response?.postcodeValid === true;
5279                            const inArea    = response?.inArea === true;
5280                            if (isSuccess && isValid && inArea) {
5281                                // valid + in area
5282                                ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5283                                ctrl.postcodeAreaCheckResult = null;
5284                                $scope.postcodeAreaCheckResult = null;
5285                                resolve();
5286                            }
5287                            else if (isSuccess && isValid && !inArea) {
5288                                // valid postcode, but outside area
5289                                ctrl.customErrorMessages[DIRECTIVE_KEY] = "Postcode " + viewValue + " is out of area";
5290                                ctrl.postcodeAreaCheckResult = {
5291                                    message: ctrl.customErrorMessages[DIRECTIVE_KEY],
5292                                    dnos: response?.dnos
5293                                };
5294                                $scope.postcodeAreaCheckResult = ctrl.postcodeAreaCheckResult; 
5295                                ctrl.$setTouched();
5296                                reject();
5297                            }
5298                            else {
5299                                // invalid / unrecognised postcode
5300                                ctrl.customErrorMessages[DIRECTIVE_KEY] = "Please enter a valid postcode";
5301                                ctrl.postcodeAreaCheckResult = {
5302                                    message: ctrl.customErrorMessages[DIRECTIVE_KEY],
5303                                    dnos: response?.dnos
5304                                };
5305                                $scope.postcodeAreaCheckResult = ctrl.postcodeAreaCheckResult;
5306                                ctrl.$setTouched();
5307                                reject();
5308                            }
5309                            return { cache: true };
5310                        });
5311                        request.setLoader({
5312                            load: () => { $scope[loadingSpinnerProperty] = true; },
5313                            clear: () => { $scope[loadingSpinnerProperty] = false; }
5314                        })
5315                    }
5316                    else {
5317                        ctrl.customErrorMessages[DIRECTIVE_KEY] = "Please enter a valid postcode";
5318                        reject();
5319                    }
5320                });
5321            };
5322        }
5323    };
5324}]);
5325
5326/**
5327* @return {boolean} - true if the supplied {number} viewValue is greater than the supplied 'greaterThan' attribute value, or if the underlying model value is unassigned
5328*
5329* @author noahm
5330*/
5331wpd.directive('greaterThan', function() {
5332    return {
5333        require: 'ngModel',
5334        restrict: 'A',
5335        link: function($scope, $elem, $attrs, ctrl) {
5336            var DIRECTIVE_KEY = 'greaterThan';
5337
5338            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5339
5340            // Set up the custom error message for this ngModel on failure of this validator
5341            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5342
5343            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5344                if (ctrl.$isEmpty(modelValue)) {
5345                    // consider empty models to be valid
5346                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5347                    return true;
5348                }
5349
5350                // grab value supplied to attribute to compare.
5351                var greaterThanWhat = Number.parseFloat($attrs[DIRECTIVE_KEY]);
5352
5353                if (viewValue > greaterThanWhat) {
5354                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5355                    return true;
5356                }
5357
5358                ctrl.customErrorMessages[DIRECTIVE_KEY] = "Must be greater than " + greaterThanWhat;
5359                return false;
5360            };
5361        }
5362    };
5363});
5364
5365/**
5366 * @return {boolean} - true if the supplied {number} viewValue is less than the supplied 'lessThan' attribute value, or if the underlying model value is unassigned
5367 *
5368 * @author noahm
5369 */
5370wpd.directive('lessThan', function() {
5371    return {
5372        require: 'ngModel',
5373        restrict: 'A',
5374        link: function($scope, $elem, $attrs, ctrl) {
5375            var DIRECTIVE_KEY = 'lessThan';
5376
5377            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5378
5379            // Set up the custom error message for this ngModel on failure of this validator
5380            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5381
5382            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5383                if (ctrl.$isEmpty(modelValue)) {
5384                    // consider empty models to be valid
5385                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5386                    return true;
5387                }
5388
5389                // grab value supplied to attribute to compare.
5390                var lessThanWhat = Number.parseFloat($attrs[DIRECTIVE_KEY]);
5391
5392                if (viewValue < lessThanWhat) {
5393                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5394                    return true;
5395                }
5396
5397                ctrl.customErrorMessages[DIRECTIVE_KEY] = "Must be less than " + lessThanWhat;
5398                return false;
5399            };
5400        }
5401    };
5402});
5403
5404/**
5405 * @return {boolean} - true if the supplied {number} viewValue is an integer
5406 *
5407 * @author noahm
5408 */
5409wpd.directive('isInteger', function() {
5410    return {
5411        require: 'ngModel',
5412        restrict: 'A',
5413        link: function($scope, $elem, $attrs, ctrl) {
5414            var DIRECTIVE_KEY = 'isInteger';
5415            //var NUMBER_REGEX = /^-?[0-9]+\.?[0-9]*$/g;        // general number
5416            var NUMBER_REGEX = /^-?[0-9]+$/g;
5417
5418            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5419
5420            // Set up the custom error message for this ngModel on failure of this validator
5421            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5422
5423            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5424                // consider empty models to be valid
5425                if (ctrl.$isEmpty(modelValue) || viewValue.match(NUMBER_REGEX)) {
5426                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5427                    return true;
5428                }
5429
5430                ctrl.customErrorMessages[DIRECTIVE_KEY] = "Value must be an integer";
5431                return false;
5432            };
5433        }
5434    };
5435});
5436
5437
5438wpd.directive('isValidDate', function() {
5439    return {
5440        require: 'ngModel',
5441        restrict: 'A',
5442        link: function($scope, $elem, $attrs, ctrl) {
5443            var DIRECTIVE_KEY = 'isValidDate';
5444            var format = "DD/MM/YYYY";
5445
5446            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5447
5448                // EMPTY MODEL - consider empty models to be valid - drop out of the check
5449                if (ctrl.$isEmpty(modelValue)) {
5450                    return true;
5451                }
5452
5453                // ELSE - Continue with rest of valid date checks
5454                //Check if the date is in a valid format
5455                //Do we meet the correct pattern?
5456                var datePatternRegex = RegExp('\\d{2}\\/\\d{2}\\/\\d{4}'); //Only tests for integers and slashes (xx/xx/xxxx) - not date format
5457                valid = datePatternRegex.test(viewValue) && viewValue.length <= 10;
5458                if (valid)
5459                {
5460                    //Now we know it's the correct format, is it a valid date?
5461                    var date = moment(viewValue, format);
5462                    returnVal = date.isValid() ? date.valueOf() : viewValue;                 //NOTE: [WSME-577] - changed ternary false bit from null to viewValue to stop it totally clearing.
5463                    valid = date.isValid();
5464                }
5465                return valid;
5466            }
5467        }
5468    }
5469})
5470
5471wpd.directive('isValidPastDate', function() {
5472    return {
5473        require: 'ngModel',
5474        restrict: 'A',
5475        link: function($scope, $elem, $attrs, ctrl) {
5476            var DIRECTIVE_KEY = 'isValidPastDate';
5477            var format = "DD/MM/YYYY";
5478
5479            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5480
5481                // EMPTY MODEL - consider empty models to be valid - drop out of the check
5482                if (ctrl.$isEmpty(modelValue)) {
5483                    return true;
5484                }
5485
5486                // ELSE - Continue with rest of valid date checks
5487                //Check if the date is in a valid format
5488                //Do we meet the correct pattern?
5489                var datePatternRegex = RegExp('\\d{2}\\/\\d{2}\\/\\d{4}'); //Only tests for integers and slashes (xx/xx/xxxx) - not date format
5490                valid = datePatternRegex.test(viewValue) && viewValue.length <= 10;
5491                if (valid)
5492                {
5493                    //Now we know it's the correct format, is it a valid date?
5494                    var date = moment(viewValue, format);
5495                    returnVal = date.isValid() ? date.valueOf() : viewValue;                 //NOTE: [WSME-577] - changed ternary false bit from null to viewValue to stop it totally clearing.
5496                    valid = date.isValid();
5497                }
5498
5499                if (valid)
5500                {
5501                    var now = new Date();
5502                    var viewDate = moment(viewValue, format).toDate();
5503
5504                    //If date is in past we're okay
5505                    valid = (viewDate <= now);
5506                }
5507
5508                return valid;
5509            }
5510        }
5511    }
5512})
5513
5514/**
5515* @return {boolean} - true if the supplied {date} is a valid date in the correct format and in the future
5516*
5517* @author kerianc, noahm
5518*/
5519wpd.directive('isValidFutureDate', function() {
5520    return {
5521        require: 'ngModel',
5522        restrict: 'A',
5523        link: function($scope, $elem, $attrs, ctrl) {
5524            var DIRECTIVE_KEY = 'isValidFutureDate';
5525            var format = "DD/MM/YYYY";
5526
5527            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5528
5529            // Set up the custom error message for this ngModel on failure of this validator
5530            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5531
5532            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5533
5534                // EMPTY MODEL - consider empty models to be valid - drop out of the check
5535                if (ctrl.$isEmpty(modelValue)) {
5536                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5537                    return true;
5538                }
5539
5540                // ELSE - Continue with rest of valid date checks
5541
5542                var errMessage = null;
5543                var valid = false;
5544
5545                //Check if the date is in a valid format
5546                //Do we meet the correct pattern?
5547                var datePatternRegex = RegExp('\\d{2}\\/\\d{2}\\/\\d{4}'); //Only tests for integers and slashes (xx/xx/xxxx) - not date format
5548                valid = datePatternRegex.test(viewValue);
5549                if (valid)
5550                {
5551                    //Now we know it's the correct format, is it a valid date?
5552                    var date = moment(viewValue, format);
5553                    returnVal = date.isValid() ? date.valueOf() : viewValue;                 //NOTE: [WSME-577] - changed ternary false bit from null to viewValue to stop it totally clearing.
5554                    valid = date.isValid();
5555                }
5556                if (!valid)
5557                {
5558                    errMessage = "Date must be in format DD/MM/YYYY";
5559                }
5560
5561                //Now we have a valid date - is it in the future?
5562                if (valid)
5563                {
5564                    var now = new Date();
5565                    var viewDate = moment(viewValue, format).toDate();
5566
5567                    //If date is in future we're okay
5568                    valid = (viewDate > now);
5569
5570                    if (!valid)
5571                    {
5572                        errMessage = "Date must be in the future";
5573                    }
5574                }
5575
5576                ctrl.customErrorMessages[DIRECTIVE_KEY] = errMessage;
5577                return valid;
5578            };
5579
5580        }
5581    };
5582});
5583
5584/**
5585 * @return {boolean} - true if the field's date is before the supplied date-before value
5586 * 
5587 * @author ianbe (adapted from noahm)
5588 */
5589wpd.directive('dateBefore', function() {
5590    return {
5591        require: 'ngModel',
5592        restrict: 'A',
5593        link: function($scope, $elem, $attrs, ctrl) {
5594            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5595
5596            // Set up the custom error message for this ngModel on failure of this validator
5597            ctrl.customErrorMessages.dateBefore = null;
5598
5599            var errMsgFormat = "Must be before $1";      // IE doesn't support template literals so this will have to do
5600
5601            ctrl.$validators.dateBefore = function(modelValue, viewValue) {
5602
5603                if (ctrl.$isEmpty(modelValue)) {
5604                    // consider empty models to be valid
5605                    ctrl.customErrorMessages.dateBefore = null;
5606                    return true;
5607                }
5608
5609                // grab value supplied to attribute to compare.
5610                var comparisonDate = parseFloat($attrs.dateBefore);
5611                var date, result;
5612
5613                // unset any unit smaller than day, since that's only as granular as we're getting with our comparison
5614                date = new Date(modelValue);
5615                date.setHours(0);
5616                date.setMinutes(0);
5617                date.setSeconds(0);
5618                date.setMilliseconds(0);
5619
5620                compare = new Date(comparisonDate);
5621                compare.setHours(0);
5622                compare.setMinutes(0);
5623                compare.setSeconds(0);
5624                compare.setMilliseconds(0);
5625
5626                result = date.valueOf() < compare.valueOf();
5627
5628                // Set error message if necessary
5629                ctrl.customErrorMessages.dateBefore = !result ?
5630                    errMsgFormat.replace('$1', compare.toLocaleDateString('en-GB')) :
5631                    null;
5632                return result;
5633
5634            };
5635        }
5636    }
5637});
5638
5639/**
5640 * @return {boolean} - true if the field's date is after the supplied date-before value
5641 * 
5642 * @author ianbe (adapted from noahm)
5643 */
5644wpd.directive('dateAfter', function() {
5645    return {
5646        require: 'ngModel',
5647        restrict: 'A',
5648        link: function($scope, $elem, $attrs, ctrl) {
5649            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5650
5651            // Set up the custom error message for this ngModel on failure of this validator
5652            ctrl.customErrorMessages.dateAfter = null;
5653
5654            var errMsgFormat = "Must be after $1";      // IE doesn't support template literals so this will have to do
5655
5656            ctrl.$validators.dateAfter = function(modelValue, viewValue) {
5657
5658                if (ctrl.$isEmpty(modelValue)) {
5659                    // consider empty models to be valid
5660                    ctrl.customErrorMessages.dateAfter = null;
5661                    return true;
5662                }
5663
5664                // grab value supplied to attribute to compare.
5665                var comparisonDate = parseFloat($attrs.dateAfter);
5666                var date, result;
5667
5668                // unset any unit smaller than day, since that's only as granular as we're getting with our comparison
5669                date = new Date(modelValue);
5670                date.setHours(0);
5671                date.setMinutes(0);
5672                date.setSeconds(0);
5673                date.setMilliseconds(0);
5674
5675                compare = new Date(comparisonDate);
5676                compare.setHours(0);
5677                compare.setMinutes(0);
5678                compare.setSeconds(0);
5679                compare.setMilliseconds(0);
5680
5681                result = date.valueOf() > compare.valueOf();
5682
5683                // Set error message if necessary
5684                ctrl.customErrorMessages.dateAfter = !result ?
5685                    errMsgFormat.replace('$1', compare.toLocaleDateString('en-GB')) :
5686                    null;
5687                return result;
5688
5689            };
5690        }
5691    }
5692});
5693
5694/**
5695 * @return {boolean} - true if the supplied {Date} viewValue is between now and now + X years (both inclusive, to the granularity of day), or if the underlying model is unassigned
5696 *
5697 * @author noahm
5698 */
5699wpd.directive('dateWithinXYearsOfNow', function() {
5700    return {
5701        require: 'ngModel',
5702        restrict: 'A',
5703        link: function($scope, $elem, $attrs, ctrl) {
5704            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5705
5706            // Set up the custom error message for this ngModel on failure of this validator
5707            ctrl.customErrorMessages.dateWithinXYearsOfNow = null;
5708
5709            var errMsgFormat = "Must be between today's date ($1) and $2 years from now ($3)";      // IE doesn't support template literals so this will have to do
5710
5711            ctrl.$validators.dateWithinXYearsOfNow = function(modelValue, viewValue) {
5712
5713                if (ctrl.$isEmpty(modelValue)) {
5714                    // consider empty models to be valid
5715                    ctrl.customErrorMessages.dateWithinXYearsOfNow = null;
5716                    return true;
5717                }
5718
5719                // grab value supplied to attribute to compare.
5720                var xYears = parseFloat($attrs.dateWithinXYearsOfNow);
5721                var date, now, xYearsFromNow, result;
5722
5723                // if we have access to moment.js, use that.
5724                if (typeof moment === 'function') {
5725                    date = moment(modelValue);
5726                    now = moment();
5727                    xYearsFromNow = now.clone().add(xYears, 'years');
5728
5729                    // https://momentjs.com/docs/#/query/is-between/
5730                    // Note the 'day' granularity, and the inclusivity string '[]'
5731                    result = date.isBetween(now, xYearsFromNow, 'day', '[]');
5732
5733                    // Set error message if necessary
5734                    ctrl.customErrorMessages.dateWithinXYearsOfNow = !result ?
5735                        errMsgFormat.replace('$1', now.format('DD/MM/YYYY')).replace('$2', xYears).replace('$3', xYearsFromNow.format('DD/MM/YYYY')) :
5736                        null;
5737                    return result;
5738                }
5739                else {
5740                    // unset any unit smaller than day, since that's only as granular as we're getting with our comparison
5741                    date = new Date(modelValue);
5742                    date.setHours(0);
5743                    date.setMinutes(0);
5744                    date.setSeconds(0);
5745                    date.setMilliseconds(0);
5746
5747                    now = new Date(Date.now());
5748                    now.setHours(0);
5749                    now.setMinutes(0);
5750                    now.setSeconds(0);
5751                    now.setMilliseconds(0);
5752
5753                    xYearsFromNow = new Date(new Date(now).setFullYear(now.getFullYear() + xYears));
5754
5755                    result = date.valueOf() >= now.valueOf() && date.valueOf() <= xYearsFromNow.valueOf();
5756
5757                    // Set error message if necessary
5758                    ctrl.customErrorMessages.dateWithinXYearsOfNow = !result ?
5759                        errMsgFormat.replace('$1', now.toLocaleDateString('en-GB')).replace('$2', xYears).replace('$3', xYearsFromNow.toLocaleDateString('en-GB')) :
5760                        null;
5761                    return result;
5762                }
5763            };
5764        }
5765    };
5766});
5767
5768/**
5769 * @return {boolean} - Do as is required check only if the passed in value is true
5770 *
5771 * @author grahamb
5772 */
5773wpd.directive('conditionallyRequired', function() {
5774    return {
5775        require: 'ngModel',
5776        restrict: 'A',
5777        link: function($scope, $elem, $attrs, ctrl) {
5778            var DIRECTIVE_KEY = 'conditionallyRequired';
5779
5780            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5781
5782            // Set up the custom error message for this ngModel on failure of this validator
5783            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5784
5785            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5786
5787                var isRequired = ($attrs[DIRECTIVE_KEY]);
5788
5789                if (isRequired)
5790                {
5791                    //IF empty then fail
5792                    if (ctrl.$isEmpty(modelValue)) {
5793                        ctrl.customErrorMessages[DIRECTIVE_KEY] = "Required";
5794                        return false;
5795                    }
5796                    else
5797                    {
5798                        //else Okay
5799                        ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5800                        return true;
5801                    }
5802                }
5803                else
5804                {
5805                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5806                    return true;
5807                }
5808            };
5809        }
5810    };
5811});
5812
5813/**
5814* TODO: wpdUtils service has isValidPostcode...
5815* @return {boolean} - true if the supplied {String} viewValue matches the format for a UK postcode
5816*
5817* @author noahm
5818*/
5819wpd.directive('isPostcode', ['wpdValidationService', '$timeout', function(wpdValidationService, $timeout) {
5820    return {
5821        require: 'ngModel',
5822        restrict: 'A',
5823        link: function($scope, $elem, $attrs, ctrl) {
5824            var DIRECTIVE_KEY = 'isPostcode';
5825
5826            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5827
5828            // Set up the custom error message for this ngModel on failure of this validator
5829            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5830
5831            function cleanPostcode(value) {
5832                if (!value) return value;
5833                const stripped = (value + '').replace(/\s/g, '').toUpperCase();
5834                if (stripped.length <= 3) return stripped;
5835                return stripped.slice(0, -3) + ' ' + stripped.slice(-3);
5836            }
5837
5838            ctrl.$parsers.push(function(viewValue) {
5839                return cleanPostcode(viewValue);
5840            });
5841
5842            $scope.$watch(
5843                () => ctrl.$modelValue,
5844                function(newVal, oldVal) {
5845                    if (newVal && newVal !== oldVal) {
5846                        const formatted = cleanPostcode(newVal);
5847                        if (formatted !== ctrl.$viewValue) {
5848                            ctrl.$viewValue = formatted;
5849                            ctrl.$render();
5850                        }
5851                    }
5852                }
5853            );
5854
5855            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5856                if (ctrl.$isEmpty(modelValue) || $scope.$eval($attrs.ngDisabled)) {
5857                    // consider empty models to be valid
5858                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5859                    ctrl.postcodeAreaCheckResult = null;
5860                    return true;
5861                }
5862
5863                let result = wpdValidationService.isPostcode(viewValue);
5864                ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid UK postcode";
5865                ctrl.postcodeAreaCheckResult = null;
5866                return result;
5867            };
5868        }
5869    };
5870}]);
5871
5872/**
5873* @return {boolean} - true if the supplied {String|number} viewValue is in a telephone format
5874*   Treat the following sample variants as valid:
5875*       123 456 789
5876*       123456789
5877*       +44 123456789
5878*       (+44) 123456789
5879*       (0)123 456 789
5880*
5881* @author noahm
5882*/
5883wpd.directive('wpdutilsIsTelephone', ['wpdValidationService', function (wpdValidationService) {
5884    return {
5885        require: 'ngModel',
5886        restrict: 'A',
5887        link: function ($scope, $elem, $attrs, ctrl) {
5888
5889            const DEBUG = false;
5890            const DIRECTIVE_KEY = 'wpdutilsIsTelephone';
5891            const TEL_REGEX = /^(\+|\(\+?\d+\))?\d{3,15}$/;
5892            if (!ctrl.customErrorMessages) { ctrl.customErrorMessages = {}; }
5893            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5894
5895            ctrl.$parsers.push(function (viewValue) {
5896                DEBUG && console.log(DIRECTIVE_KEY + ":parsing '" + viewValue + "'");
5897                if (!viewValue) return viewValue;
5898
5899                let cleaned = (viewValue + '').replace(/\s/g, '');
5900                return cleaned;
5901            });
5902
5903            ctrl.$validators[DIRECTIVE_KEY] = function (modelValue) {
5904                DEBUG && console.log(DIRECTIVE_KEY + ":validating '" + modelValue + "'");
5905                if (ctrl.$isEmpty(modelValue)) {
5906                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5907                    return true;
5908                }
5909                let isValid = TEL_REGEX.test(modelValue);
5910                ctrl.customErrorMessages[DIRECTIVE_KEY] =  isValid ? null : "Must be a valid telephone number";
5911                return isValid;
5912            };
5913
5914            ctrl.$formatters.push(function (modelValue) {
5915                return modelValue;
5916            });
5917        }
5918    };
5919}]);
5920
5921/**
5922* @return {boolean} - true if the supplied {String|number} viewValue is in a telephone format
5923*   Treat the following sample variants as valid:
5924*       01234567890
5925*       07712345678
5926*       02081234567
5927*       01908765432
5928*       01619876543
5929*   Treat the following sample variants as invalid:
5930*       1234567890 (missing leading 0)
5931*       012345678901 (too many characters)
5932*       0123a567890 (contains non-numeric characters)
5933*       0123-567-890 (contains dashes)
5934*       +441234567890 (contains country code)
5935*
5936*       Nged requested as it matches their internal validation rules.
5937*       Ours might be better but it would let through phone numbers their system would reject
5938*       This validation differs from the telephone annotation used on the back end
5939 * @author noahm, konstanting, grahamb
5940 * -KG tightened up NMs original directive
5941 * -Moved to utils by GB
5942*/
5943wpd.directive('wpdutilsIsTelephoneTight', ['wpdValidationService', function(wpdValidationService) {
5944    return {
5945        require: 'ngModel',
5946        restrict: 'A',
5947        link: function($scope, $elem, $attrs, ctrl) {
5948            const DIRECTIVE_KEY = 'wpdutilsIsTelephoneTight';
5949            const TEL_REGEX = /^0\d{10}$/g;
5950
5951            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
5952
5953            // Set up the custom error message for this ngModel on failure of this validator
5954            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5955
5956            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
5957                if (ctrl.$isEmpty(modelValue)) {
5958                    // consider empty models to be valid
5959                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
5960                    return true;
5961                }
5962
5963                let result = false;
5964
5965                if (viewValue && (typeof viewValue === 'string' || typeof viewValue === 'number')) {
5966                    let testValue = viewValue + '';     // convert to string
5967                    testValue = testValue.replaceAll(/\s/g, '');       // remove any whitespace-like characters for easier testing
5968                    result = testValue.match(TEL_REGEX);
5969                }      
5970                ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid telephone number";
5971                return result;
5972            };
5973        }
5974    };
5975}]);
5976
5977/**
5978 * @return {boolean} - true if the supplied {String|number} viewValue is in a telephone format
5979 * 
5980 * This is a more specific yet accomodating version of wpdutilsIsTelephoneTight.
5981 * wpdutilsIsTelephoneTight doesn't make allowances for some landlines that can have 10 digits.
5982 * Built for more stringent validation rules for the PCR Form Contact Details step, rules based off feedback in NGEDSME-728
5983 * Any mobile number (assuming UK) must start with 07, and be 11 digits long.
5984 * Any other number must be either 10 or 11 digits long, and start with 0.
5985 * Non-UK phone numbers may not be valid in this format, and any international numbers with country codes will not be valid at all.
5986 * 
5987 * Treat the following sample variants as valid:
5988 *      07855548895
5989 *      02147825748
5990 *      01915887744
5991 *      0169772584 (one of the valid 10-digit landline area codes '016977' - there are 12 in total, but we will only validate them length-wise, not checking explicity for the correct area codes)
5992 * Treat the following sample variants as invalid:
5993 *      1234567890 (missing leading 0)
5994 *      012345678901 (too many characters)
5995 *      0123a567890 (contains non-numeric characters)
5996 *      0123-567-890 (contains dashes)
5997 *      0712345678 (not enough digits for starting with 07)
5998 *      012345678 (not enough digits for any number)
5999 *      +447851122114 (contains country code, too long)
6000 * 
6001 * @author lloydc
6002 */
6003wpd.directive('wpdutilsIsTelephoneMobile', ['wpdValidationService', function(wpdValidationService) {
6004    return {
6005        require: 'ngModel',
6006        restrict: 'A',
6007        link: function($scope, $elem, $attrs, ctrl) {
6008            const DIRECTIVE_KEY = 'wpdutilsIsTelephoneMobile';
6009            const TEL_REGEX = /^0(7\d{9}|[1-689]\d{8,9})$/g; // Allows 10 or 11 digit numbers starting with 0
6010
6011            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
6012
6013            // Set up the custom error message for this ngModel on failure of this validator
6014            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6015
6016            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
6017                if (ctrl.$isEmpty(modelValue)) {
6018                    // consider empty models to be valid
6019                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6020                    return true;
6021                }
6022
6023                let result = false;
6024
6025                if (viewValue && (typeof viewValue === 'string' || typeof viewValue === 'number')) {
6026                    let testValue = viewValue + '';     // convert to string
6027                    testValue = testValue.replaceAll(/\s/g, '');       // remove any whitespace-like characters for easier testing
6028                    result = testValue.match(TEL_REGEX);
6029                }      
6030                ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid UK telephone number (11 digits for mobile, 10 or 11 for landline)";
6031                return result;
6032            };
6033        }
6034    };
6035}]);
6036
6037/**
6038 * Validates that the input is a valid UK telephone number using Loqate's telephone validation API.
6039 *
6040 * Performs an initial regex check before calling the API - the number must start with 0 and
6041 * be either 10 or 11 digits long.
6042 * 
6043 * If the API response is credible and the number is valid, the cleaned value is written back to the model.
6044 *
6045 * @author GrahamB
6046 * @author BradleyM
6047 */
6048wpd.directive('wpdutilsIsTelUkLoqate', [
6049    '$requester',
6050    'wpdutilsCustomInputMixinService',
6051    'wpdutilsLoqateValidationService',
6052    function($requester, customInputMixin, loqateValidation) {
6053
6054    const DEBUG = false;
6055    const DIRECTIVE_KEY = 'wpdutilsIsTelUkLoqate';
6056    const VALIDATION_TIMEOUT_MS = 10000;
6057    const TEL_REGEX = /^0(7\d{9}|[1-689]\d{8,9})$/; // Allows 10 or 11 digit numbers starting with 0
6058
6059    return {
6060        require: 'ngModel',
6061        restrict: 'A',
6062        link: function($scope, $elem, $attrs, ctrl) {
6063            const json = $requester({
6064                telCheck: 'WPDUtilsFront.validateUKTelephoneLoqate_JSON'
6065            });
6066
6067            DEBUG && console.log(`[${DIRECTIVE_KEY}] Initialised for element: ${$elem.attr('id')}`);
6068
6069            // Coerce to string and strip whitespace
6070            ctrl.$parsers.push(function(viewValue) {
6071                if (!viewValue) return viewValue;
6072                return (viewValue + '').replace(/\s/g, '');
6073            });
6074
6075            function telRegexCheck(val) {
6076                return !!val.match(TEL_REGEX);
6077            }
6078
6079            const loadingSpinnerProperty = setupLoadingSpinner($scope, $elem, DIRECTIVE_KEY, customInputMixin);
6080
6081            function buildTelApiCall(json, apiKeyHandle) {
6082                return function({ viewValue, resolve, reject, setError, markDefinitive, onNetworkFailure }) {
6083                    json.telCheck({ telephoneNumber: viewValue, apiKeyHandle }, function(response) {
6084                        const item = response?.result?.items?.[0];
6085
6086                        if (!response?.success || !item) {
6087                            setError(null);
6088                            resolve();
6089                            return;
6090                        }
6091
6092                        markDefinitive();
6093
6094                        if (item.IsCredible) {
6095                            if (item.IsValid?.toUpperCase() === "NO") {
6096                                setError(`Telephone number ${viewValue} is not a valid UK telephone number`);
6097                                reject();
6098                            } else {
6099                                ctrl.$setViewValue(viewValue);
6100                                ctrl.$render();
6101                                setError(null);
6102                                resolve();
6103                            }
6104                        } else {
6105                            // Response not credible - default to accepting the number
6106                            setError(null);
6107                            resolve();
6108                        }
6109                    }
6109).onFailure(onNetworkFailure);
6110                };
6111            }
6112
6113            ctrl.$asyncValidators[DIRECTIVE_KEY] = loqateValidation.createValidator({
6114                validatorType: DIRECTIVE_KEY,
6115                ctrl, $elem, $scope,
6116                customInputMixin, json,
6117                apiKeyHandle: $attrs[DIRECTIVE_KEY],
6118                regexCheck: telRegexCheck,
6119                regexFailMessage: "Must be a valid UK telephone number",
6120                emptyMessage: "Please enter a valid UK telephone number",
6121                timeoutMs: VALIDATION_TIMEOUT_MS,
6122                debug: DEBUG,
6123                buildApiCall: buildTelApiCall,
6124                onLoadStart: function() { $scope[loadingSpinnerProperty] = true; },
6125                onLoadEnd: function() { $scope[loadingSpinnerProperty] = false; },
6126                onDestroy: function(cleanupFn) { $scope.$on('$destroy', cleanupFn); }
6127            });
6128        }
6129    };
6130}]);
6131
6132/**
6133 * Validates that the input is a valid email address using Loqate's email validation API.
6134 *
6135 * Performs an initial regex check before calling the API - the email must have a valid
6136 * format including a TLD after the @ symbol. If the API response marks the address as
6137 * invalid, the error is written back to the ngModel controller.
6138 *
6139 * @author GrahamB
6140 * @author BradleyM
6141 */
6142wpd.directive('wpdutilsIsEmailLoqate', [
6143    '$requester',
6144    'wpdutilsCustomInputMixinService',
6145    'wpdutilsLoqateValidationService',
6146    function($requester, customInputMixin, loqateValidation) {
6147
6148    const DEBUG = false;
6149    const DIRECTIVE_KEY = 'wpdutilsIsEmailLoqate';
6150    const VALIDATION_TIMEOUT_MS = 10000;
6151    const EMAIL_REGEX = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)+$/i;
6152
6153    return {
6154        require: 'ngModel',
6155        restrict: 'A',
6156        link: function($scope, $elem, $attrs, ctrl) {
6157            const json = $requester({
6158                emailCheck: 'WPDUtilsFront.validateEmailLoqate_JSON'
6159            });
6160
6161            DEBUG && console.log(`[${DIRECTIVE_KEY}] Initialised for element: ${$elem.attr('id')}`);
6162
6163            // Angular adds its own email validator on type="email" inputs.
6164            // Remove it so Loqate is the sole email validity authority.
6165            // Safe on re-link: delete on a non-existent property is a no-op.
6166            if ($attrs.type === 'email' && ctrl && ctrl.$validators) {
6167                delete ctrl.$validators.email;
6168            }
6169
6170            // Coerce to string and strip whitespace
6171            ctrl.$parsers.push(function(viewValue) {
6172                if (!viewValue) return viewValue;
6173                return (viewValue + '').trim();
6174            });
6175
6176            const loadingSpinnerProperty = setupLoadingSpinner($scope, $elem, DIRECTIVE_KEY, customInputMixin);
6177
6178            function buildEmailApiCall(json, apiKeyHandle) {
6179                return function({ viewValue, resolve, reject, setError, markDefinitive, onNetworkFailure }) {
6180                    json.emailCheck({ emailAddress: viewValue, apiKeyHandle }, function(response) {
6181                        const item = response?.result?.items?.[0];
6182
6183                        if (!response?.success || !item) {
6184                            setError(null);
6185                            resolve();
6186                            return;
6187                        }
6188
6189                        markDefinitive();
6190
6191                        if (item.ResponseCode?.toUpperCase() === "INVALID") {
6192                            setError(`Email address ${viewValue} is not a valid email address`);
6193                            reject();
6194                        } else {
6195                            setError(null);
6196                            resolve();
6197                        }
6198                    }).onFailure(onNetworkFailure);
6199                };
6200            }
6201
6202            function emailRegexCheck(val) {
6203                return typeof val === 'string' && !!val.match(EMAIL_REGEX);
6204            }
6205
6206            ctrl.$asyncValidators[DIRECTIVE_KEY] = loqateValidation.createValidator({
6207                validatorType: DIRECTIVE_KEY,
6208                ctrl,
6209                $elem,
6210                $scope,
6211                customInputMixin,
6212                json,
6213                apiKeyHandle: $attrs[DIRECTIVE_KEY],
6214                regexCheck: emailRegexCheck,
6215                regexFailMessage: "Must be a valid email address",
6216                emptyMessage: "Please enter a valid email address",
6217                timeoutMs: VALIDATION_TIMEOUT_MS,
6218                debug: DEBUG,
6219                buildApiCall: buildEmailApiCall,
6220                onLoadStart: function() { $scope[loadingSpinnerProperty] = true; },
6221                onLoadEnd: function() { $scope[loadingSpinnerProperty] = false; },
6222                onDestroy: function(cleanupFn) { $scope.$on('$destroy', cleanupFn); }
6223            });
6224        }
6225    };
6226}]);
6227
6228/**
6229* @return {boolean} - true if the supplied String viewValue is in an email format
6230*   Ensures that an email has a TLD after the @ symbol
6231* @author lloydc
6232*/
6233wpd.directive('wpdutilsIsEmail', ['wpdValidationService', function(wpdValidationService) {
6234    return {
6235        require: 'ngModel',
6236        restrict: 'A',
6237        link: function($scope, $elem, $attrs, ctrl) {
6238            const DIRECTIVE_KEY = 'wpdutilsIsEmail';
6239            const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/g;         // Accepts "[email protected]" but not "test@test"
6240
6241            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
6242
6243            // Set up the custom error message for this ngModel on failure of this validator
6244            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6245
6246            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
6247                if (ctrl.$isEmpty(modelValue)) {
6248                    // consider empty models to be valid
6249                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6250                    return true;
6251                }
6252
6253                let result = false;
6254
6255                if (viewValue && typeof viewValue === 'string') {
6256                    result = viewValue.match(EMAIL_REGEX);
6257                }
6258
6259                ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid email in the format '[email protected]'";
6260                return result;
6261            };
6262        }
6263    };
6264}]);
6265
6266/**
6267 * Adaptation of wpdutilsIsTelephone directive, using a JavaScript compatible 
6268 * version of the regex in CoreTelephone's isValid method.
6269 * 
6270 * This should be used in conjunction with the @Telephone annotation, as
6271 * CoreTelephone's isValid method is used when validating attributes with this 
6272 * annotation on form submission.
6273 * 
6274 * @return {boolean} - true if the supplied {String|number} viewValue is in a 
6275 * telephone format
6276 * 
6277 * Treat the following sample variants as valid:
6278 * 123 456 789
6279 * 123456789
6280 * +44 123456789
6281 * +44123456789
6282 * (020) 1234 1234
6283 * 
6284 * @author bradleym, noahm (wpdutilsIsTelephone)
6285 */
6286wpd.directive('wpdutilsIsCoreTelephone', ['wpdValidationService', function(wpdValidationService) {
6287    return {
6288        require: 'ngModel',
6289        restrict: 'A',
6290        link: function($scope, $elem, $attrs, ctrl) {
6291            const DIRECTIVE_KEY = 'wpdutilsIsCoreTelephone';
6292            const TEL_REGEX = /^([\\+][0-9]{1,3})?([ \\.\\-])?([\\(]{1}[0-9]{2,6}[\\)])?([0-9 \\.\\\\-\\\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/g;
6293            // Adapted from CoreTelephone's isValid method
6294            // Original: ^([\\+][0-9]{1,3})?([ \\.\\-])?([\\(]{1}[0-9]{2,6}[\\)])?([0-9 \\.\\-/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$
6295
6296            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
6297
6298            // Set up the custom error message for this ngModel on failure of this validator
6299            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6300
6301            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
6302                if (ctrl.$isEmpty(modelValue)) {
6303                    // consider empty models to be valid
6304                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6305                    return true;
6306                }
6307
6308                let result = false;
6309
6310                if (viewValue && (typeof viewValue === 'string' || typeof viewValue === 'number')) {
6311                    let testValue = viewValue + '';     // convert to string
6312                    testValue = testValue.replaceAll(/\s/g, '');       // remove any whitespace-like characters for easier testing
6313                    result = testValue.match(TEL_REGEX);
6314                }
6315
6316                ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid telephone number";
6317                return result;
6318            };
6319        }
6320    };
6321}]);
6322
6323
6324wpd.directive('wpdutilsIsPowercutsTelephone', ['wpdValidationService', function(wpdValidationService) {
6325    return {
6326        require: 'ngModel',
6327        restrict: 'A',
6328        link: function($scope, $elem, $attrs, ctrl) {
6329            const DIRECTIVE_KEY = 'wpdutilsIsCoreTelephone';
6330            const TEL_REGEX = /^([\\+][0-9]{1,3})?([ \\.\\-])?([\\(]{1}[0-9]{2,6}[\\)])?([0-9 \\.\\\\-\\\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/g;
6331            // Adapted from CoreTelephone's isValid method
6332            // Original: ^([\\+][0-9]{1,3})?([ \\.\\-])?([\\(]{1}[0-9]{2,6}[\\)])?([0-9 \\.\\-/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$
6333
6334            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
6335
6336            // Set up the custom error message for this ngModel on failure of this validator
6337            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6338
6339            ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
6340                if (ctrl.$isEmpty(modelValue)) {
6341                    // consider empty models to be valid
6342                    ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6343                    return true;
6344                }
6345
6346                let result = false;
6347
6348                if (viewValue && (typeof viewValue === 'string' || typeof viewValue === 'number')) {
6349                    let testValue = viewValue + '';     // convert to string
6350                    testValue = testValue.replaceAll(/\s/g, '');       // remove any whitespace-like characters for easier testing
6351                    result = testValue.match(TEL_REGEX);
6352                }
6353
6354                ctrl.customErrorMessages[DIRECTIVE_KEY] = result ? null : "Must be a valid telephone number";
6355                return result;
6356            };
6357        }
6358    };
6359}]);
6360
6361/**
6362* @return {boolean} - true if the supplied {String} is prefixed with NGED area codes
6363* Does not check if the MPAN number if valid, only if it is in area.
6364*
6365* @author grahamb
6366*/
6367wpd.directive('wpdutilsIsNgedMpan', ['wpdValidationService', function(wpdValidationService) {
6368        return {
6369            require: 'ngModel',
6370            restrict: 'A',
6371            link: function($scope, $elem, $attrs, ctrl) {
6372                const DIRECTIVE_KEY = 'wpdutilsIsNgedMpan';
6373
6374                if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
6375
6376                // Set up the custom error message for this ngModel on failure of this validator
6377                ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6378
6379                ctrl.$validators[DIRECTIVE_KEY] = function(modelValue, viewValue) {
6380                    if (ctrl.$isEmpty(modelValue) || modelValue.toString().length < 2) {
6381                        // consider empty models to be valid
6382                        ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6383                        return true;
6384                    }
6385
6386                    if (!Number.isInteger(Number(modelValue))) {
6387                        ctrl.customErrorMessages[DIRECTIVE_KEY] = "Must contain only numeric digits";
6388                        return false;
6389                    }
6390
6391                    var checkValue = modelValue.toString().substr(0, 2);
6392                    var hasRightStart = (checkValue == "21" || checkValue == "22" || checkValue == "11" || checkValue == "14");
6393
6394                    ctrl.customErrorMessages[DIRECTIVE_KEY] = (hasRightStart) ? null : "This MPAN does not belong to National Grid. Cannot proceed with application.";
6395                    return hasRightStart;
6396                };
6397            }
6398        };
6399    }
6400]);
6401
6402
6403/**
6404* Form directive that passes through the value of the parent form's stepSubmitted property
6405* TODO maybe consider making this part of wpdutilsCustomInputMixinService instead (or as well?)
6406*
6407* @author ianbe
6408* @since 2025-06-27
6409*/
6410wpd.directive('wpdutilsParentFormStatus', [function() {
6411    return {
6412        restrict: 'A',
6413        require: 'form',
6414        link: function($scope, $elem, $attrs, $ctrls) {
6415            const thisForm = $scope[$attrs.ngForm];
6416            if (thisForm && thisForm.$$parentForm) {
6417                Object.defineProperty(thisForm, 'stepSubmitted', {
6418                    get: function() {
6419                        return thisForm.$$parentForm?.stepSubmitted;
6420                    }
6421                });
6422            }
6423        }
6424    };
6425}]);
6426
6427
6428/**
6429 * Directive to validate the maximum length of a string, after encoding it in the manner of the backend
6430 * 
6431 * @author ianbe
6432 * @since 2025-07-02
6433 */
6434wpd.directive('wpdutilsMaxLength', ['wpdutilsRequester', function(wpdutilsRequester) {
6435
6436  // static object to hold the aliases for emojis, so that they can be shared across multiple instances of this directive
6437  const ALIASES = { state: 'uninitialised', aliases: {} };
6438
6439  return {
6440    restrict: 'A',
6441    require: 'ngModel',
6442    link: function($scope, $elem, $attrs, $ctrl) {
6443        const DIRECTIVE_KEY = 'wpdutilsMaxLength';
6444
6445        const FITZPATRICK_TYPES = {
6446            "\uD83C\uDFFB": "type_1_2",
6447            "\uD83C\uDFFC": "type_3",
6448            "\uD83C\uDFFD": "type_4",
6449            "\uD83C\uDFFE": "type_5",
6450            "\uD83C\uDFFF": "type_6"
6451        };
6452
6453        // Modes - these match the CoreEmojiSanitiser behaviours for the corresponding modes
6454        //  - parse (default): Create HTML entities for emojis, strip Fitzpatrick modifiers (e.g. &#128103;)
6455        //  - alias: Create aliases for emojis, use aliases for Fitzpatrick modifiers if provided (e.g. :person: or :person|type_3:)
6456        //           TODO: Not fully implemented yet - emoji aliases are currently mapped to their code points, not their aliases
6457        //  - strip: Strip all emojis and Fitzpatrick modifiers, leaving no content
6458        const max = parseInt($attrs[DIRECTIVE_KEY], 10);
6459        let emojiMode = $attrs[DIRECTIVE_KEY + 'EmojiMode'] || 'parse'; // default to parse mode if not specified
6460        
6461        if (!['parse', 'alias', 'strip'].includes(emojiMode)) {
6462            emojiMode = 'parse'; // default to parse mode
6463        }
6464
6465        if (emojiMode === 'alias') {
6466            // Set the shared aliases asynchronously if this is the first directive to request them
6467            if (['uninitialised', 'failed'].includes(ALIASES.state)) {
6468                ALIASES.state = 'loading';
6469                const json = wpdutilsRequester({
6470                    getEmojiAliases: 'WPDUtilsFront.getEmojiAliases_JSON'
6471                });
6472                const request = json.getEmojiAliases({}, function(response) {
6473                    if (response && response.success) {
6474                        ALIASES.state = 'success';
6475                        ALIASES.aliases = response.aliases;
6476                    }
6477                });
6478                request.onFailure(function() {
6479                    // Mark aliases as failed so a later directive can try again
6480                    ALIASES.state = 'failed';
6481                })
6482            }
6483        }
6484
6485
6486        $ctrl.customErrorMessages = $ctrl.customErrorMessages || {};
6487
6488
6489        function stripFitzpatrick(str) {
6490            return str.replace(/\p{Emoji_Modifier}/ug, '');
6491        }
6492
6493        function encodeEmoji(str) {
6494            switch (emojiMode) {
6495                case 'alias':
6496                    str = str.replace(/(\p{Extended_Pictographic})(\p{Emoji_Modifier})?/ug, (matches, emoji, modifier) => {
6497                        const emojiStr = ALIASES.aliases[emoji] || 'unknown';
6498                        if (modifier === undefined) {
6499                            return `:${emojiStr}:`;
6500                        }
6501                        else {
6502                            const modifierStr = FITZPATRICK_TYPES[modifier];
6503                            return `:${emojiStr}|${modifierStr}:`;
6504                        }
6505                    });
6506                    break;
6507                case 'strip':
6508                    str = stripFitzpatrick(str);
6509                    str = str.replace(/\p{Extended_Pictographic}/ug, '');
6510                    break;
6511                case 'parse':
6512                default:
6513                    str = stripFitzpatrick(str);
6514                    str = str.replace(/\p{Extended_Pictographic}/ug, emoji => '&#' + emoji.codePointAt(0) + ';');
6515                    break;
6516            }
6517
6518            return str;
6519        }
6520
6521
6522        $ctrl.$validators[DIRECTIVE_KEY] = function(modelValue) {
6523            if (!modelValue) return true;
6524
6525            const encoded = encodeEmoji(modelValue);
6526            const charCount = encoded.length;
6527
6528            // DEBUG && console.log(`"${modelValue}" has ${charCount} characters after encoding - ${encoded}`);
6529
6530            if (charCount <= max) {
6531                return true;
6532            }
6533            else {
6534                $ctrl.customErrorMessages[DIRECTIVE_KEY] = "Must not be longer than " + max + " characters";
6535                if (modelValue < encoded) {
6536                    $ctrl.customErrorMessages[DIRECTIVE_KEY] += ". Note that emoji use more than one character.";
6537                }
6538                return false;
6539            }
6540        };
6541    }
6542  };
6543}]);
6544
6545/**
6546 * Use this when you have a fieldset of checkboxes that are grouped together
6547 * and you want to make sure at least one checkbox in the group is checked.
6548 * 
6549 * Add the directive to the fieldset element, and supply it with the "group name"
6550 * checkbox-group-required="claimReasons"
6551 * 
6552 * All the input boxes need to have the data-group attribute set to the same group name:
6553 * <input type="checkbox" data-group="claimReasons" ... />
6554 * 
6555 * @return {boolean} - true if at least one checkbox in the group is checked
6556 * 
6557 * @author grahamb
6558 */
6559wpd.directive('checkboxGroupRequired', function() {
6560    return {
6561        restrict: 'A',
6562        require: '^form',
6563        link: function($scope, $elem, $attrs, ctrl) {
6564            const groupName = $attrs.checkboxGroupRequired;
6565            const DIRECTIVE_KEY = 'checkboxGroupRequired_' + groupName;  // Unique per group
6566
6567            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
6568
6569            // Set up the custom error message for this form on failure of this validator
6570            ctrl.customErrorMessages[DIRECTIVE_KEY] = null;
6571
6572            ctrl.$validators[DIRECTIVE_KEY] = function() {
6573                const checkboxes = $elem.querySelectorAll('input[type="checkbox"][data-group="' + groupName + '"]');
6574                return Array.from(checkboxes).some(cb => cb.checked);
6575            };
6576      
6577            // Watch for changes
6578            $scope.$watch(function() {
6579                const checkboxes = $elem.querySelectorAll('input[type="checkbox"][data-group="' + groupName + '"]');
6580                return Array.from(checkboxes).map(cb => cb.checked).join(',');
6581            }, function() {
6582                ctrl.$validate();
6583            });
6584        }
6585    };
6586});
6587
6588
6589
6590// ===========================================================================================
6591// ----------------------------------- Helpful filters ---------------------------------------
6592// ===========================================================================================
6593
6594/**
6595* Return a string representation of a number, formatted to a particular locale's rules
6596* See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleStr
6596ing
6597*
6598* For the Java version, see {module:Utils} /app/wpd/utils/NumberHelper#numberLocaleString
6599*
6600* @param {number|string} input - the number to format, or a string parsable to a number
6601* @param {string} locale - the locale code to use, i.e. 'en-ie'. If left blank, will use the system locale.
6602* @param {object} options - a map of optional arguments to pass in which influence the returned string. See the Mozilla link for examples.
6603* @return {string}
6604*
6605* @author noahm
6606*/
6607wpd.filter('numberLocaleString', function() {
6608    const NAME = 'numberLocaleString';
6609
6610    return function(input, locale, options) {
6611        let workingInput = input;
6612
6613        if (typeof workingInput === 'string') {
6614            try {
6615                workingInput = parseFloat(workingInput);
6616            }
6617            catch (e) {
6618                console.warn(`${NAME} could not parse a Number from the supplied string value "${input}"`);
6619            }
6620        }
6621
6622        if (typeof workingInput !== 'number') {
6623            console.warn(`${NAME} cannot work on anything other than a Number type`);
6624        }
6625        else {
6626            workingInput = workingInput.toLocaleString(locale, options);
6627        }
6628
6629        return workingInput;
6630    }
6631});
6632
6633/**
6634* Return a string representation of a number padded with some amount of leading 0s.
6635*
6636* @param {number} input
6637* @param {number} padCount
6638* @return {string}
6639*
6640* @author noahm
6641*/
6642wpd.filter('zeroPad', function() {
6643    return function(input, padCount) {
6644        input += '';
6645        if (!input || !padCount || typeof padCount !== 'number') return input;
6646        return input.padStart(padCount, '0');
6647    };
6648});
6649
6650// Stolen from angular.stormmode.js/angular.powercuts-reporting.js
6651// It's okay for this to take precedence over the other two since they're all exactly the same function
6652// TODO: remove the ordinal filters in those two files - let them use this one.
6653wpd.filter('ordinal', function() {
6654    return function(input) {
6655        if (!input && input !== 0) return '';
6656        var s = ['th', 'st', 'nd', 'rd'],
6657            v = input % 100;
6658        return input + (s[(v - 20) % 10] || s[v] || s[0]);
6659    };
6660});
6661
6662/**
6663* An augmentation of the normal angularjs date filter
6664*
6665* @return {string} - the given date in the specified angularjs date filter format, but with the ability to have an ordinal applied
6666*                       to any of the returned numerical portions, using '$ORD'.
6667*                       e.g. 'EEEE d$ORD MMMM, yyyy' => 'Thursday 13th May, 2022'
6668*
6669* @author noahm
6670*/
6671wpd.filter('datePlus', ['$filter', function($filter) {
6672    var ordinalMarker = '$ORD';
6673
6674    return function(date, format, timezone) {
6675        var ret = $filter('date')(date, format, timezone);
6676
6677        if (ret && ret.indexOf(ordinalMarker) >= 0) {
6678            ret = ret.replaceAll(new RegExp('(\\d{1,2})\\'+ordinalMarker, 'ig'), function(match, p1) {
6679                return $filter('ordinal')(Number.parseInt(p1));
6680            });
6681        }
6682
6683        return ret;
6684    };
6685}]);
6686
6687/**
6688* A filter that attempts to capitalize words in a string in a title-like format, such as:
6689*   "Structural evaluations of houses in Gateshead" => "Structural Evaluations of Houses in Gateshead"
6690*
6691* @author noahm
6692*/
6693wpd.filter('smartCapitalize', function() {
6694    // Feel free to add to this list if you spot more examples
6695    var noCapitalize = ['of', 'the', 'do', 'with', 'to', 'from', 'by', 'upon', 'under', 'a', 'in', 'out', 'and', 'at', 'above', 'below'];
6696
6697    // TODO: give option to provide noCapitalize works that _should_ still be capitalized.
6698    return function(input) {
6699        if (!input) return '';
6700
6701        var output = input.toLowerCase().replaceAll(/\b([^\s\r\n\t]*)\b/gi, function(match) {
6702            if (noCapitalize.indexOf(match) < 0) {
6703                match = match.charAt(0).toUpperCase() + match.slice(1);
6704            }
6705
6706            // TODO: you still need to capitalize noCapitalize words if they are the start of a sentence!
6707
6708            return match;
6709        });
6710
6711        // we still want to capitalize the very first letter, no matter what it is
6712        return output.charAt(0).toUpperCase() + output.slice(1);
6713    };
6714});
6715
6716/**
6717* A filter that takes in a string containing substitution tokens, and swaps those tokens out with the relevant string which maps to it
6718*   in the replaceMap. The input string may look like this:
6719*       "My $vehicle is $color"
6720*   and the map may look like this:
6721*       {
6722*           vehicle: 'motorbike',
6723*           color: 'red'
6724*       }
6725*
6726* @author noahm
6727*/
6728wpd.filter('stringSubstitute', function() {
6729    return function(inputString, replaceMap) {
6730        if (!inputString) return '';
6731        if (!replaceMap) return inputString;
6732
6733        return inputString.replaceAll(/\$[a-bA-B0-9_]+/gi, function(match) {
6734            match = match.slice(1);     // remove the $ at the start
6735            return replaceMap[match] !== undefined ? replaceMap[match] : match;
6736        });
6737    };
6738});
6739
6740/**
6741* A filter which returns the given input if it is neither null or undefined, else returns the supplied `alternativeValue`
6742*
6743* @author noahm
6744*/
6745wpd.filter('wpdutilsIfExistsOr', function() {
6746    return function(input, alternativeValue) {
6747        return input !== null && input !== undefined ? input : alternativeValue;
6748    }
6749});
6750
6751
6752// ===========================================================================================
6753// ------------------------------ General Utility Services -----------------------------------
6754// ===========================================================================================
6755
6756/**
6757* Dumping ground of helpful math functions
6758*
6759* @author noahm
6760*/
6761wpd.service('wpdutilsMathUtilities', [
6762    function() {
6763        /**
6764         * Stolen from Powercuts
6765         * Rounds a number to a precision
6766         * @author keiranc
6767         * @param {Number} value the value to round
6768         * @param {Number} precision the number of decimal places
6769         * @returns {Number} the rounded value
6770         */
6771        this.roundWithPrecision = function(value, precision) {
6772            var multiplier = Math.pow(10, precision || 0);
6773            return Math.round(value * multiplier) / multiplier;
6774        };
6775
6776        /**
6777         * Stolen from Powercuts
6778         * Converts latitude to radians
6779         * @author keiranc
6780         * @param {Number} lat latitude
6781         * @returns {Number} the amount in radians
6782         */
6783        this.lat2rad = function(lat) {
6784            var sin = Math.sin((lat * Math.PI) / 180);
6785            var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
6786            return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
6787        };
6788    }
6789]);
6790
6791/**
6792* Dumping ground of helpful miscellaneous functions
6793*
6794* @author noahm
6795*/
6796wpd.service('wpdutilsMiscUtilities', [
6797    function() {
6798        /**
6799         * Stolen from the Powercuts site
6800         *
6801         * finds an object by property value and returns said object,
6802         * or null if there is no object, or if initial objects was null
6803         *
6804         * @author keiranc
6805         * @param {String} property the property to filter on
6806         * @param {Object} value the value to filter on
6807         * @param {Array} objects the objects to filter
6808         * @returns object with property matching value in objects
6809         */
6810        this.getBy = function(property, value, objects) {
6811            if (!objects) {
6812                return null;
6813            }
6814            var _object = objects.filter(function(object) {
6815                return object[property] === value;
6816            });
6817            return _object.length ? _object[0] : null;
6818        };
6819
6820
6821        /**
6822         * Returns a simple key-value store (mostly for factories to use to share data between instances)
6823         * @author ianbe
6824         * @since 2025-07-30
6825         */
6826        this.newKeyValueStore = () => function() {
6827            const store = {};
6828
6829            /**
6830             * Check whether a value exists in the store.
6831             * @param {string} key Cache key
6832             * @returns {boolean} True if the store has a value at that key, false otherwise
6833             */
6834            this.has = function(key) {
6835                return store.hasOwnProperty(key);
6836            };
6837
6838            /**
6839             * Get a value from the store, or if not found, set it to the return value of a callback function and return it.
6840             * @param {string} key Cache key
6841             * @param {boolean|function} setDefault Determines the default value to set if the key is not found in the store.
6842             *                           If true, an empty object will be set at the key.
6843             *                           If a function, its return value will be set at the key.
6844             *                           Functions are highly recommended to return a reference type such as an object, if you want to share data without having to re-fetch it.
6845             *                           
6846             * @returns {*} The value from the store or the result of the callback function
6847             */
6848            this.get = function(key, setDefault = null) {
6849                if (setDefault && !this.has(key)) {
6850                    if (setDefault === true) {
6851                        store[key] = {};
6852                    }
6853                    else if (typeof setDefault === 'function') {
6854                        store[key] = setDefault();
6855                    }
6856                }
6857                return store[key];
6858            };
6859
6860            /**
6861             * Set a value in the store.
6862             * Note that if the key already contains a reference type, any existing references will not be updated to the new value.
6863             * @param {string} key Cache key
6864             * @param {*} value The value to store at the key
6865             */
6866            this.set = function(key, value) {
6867                store[key] = value;
6868            }
6869        };
6870
6871    }
6872])
6873
6874
6875
6876/**
6877 * Wrapper for the $requester service to add extra functionality:
6878 * 
6879 * Check if a request in the requester - isLoading() - or a specific route - isLoading(route) - is currently in progress.
6880 * 
6881 * Caching. If the success callback returns an object with a truthy `cache` property, the response will be cached
6882 * and returned on subsequent calls with the same parameters. Use for read-only requests where the response is expected to be static.
6883 * 
6884 * Should be interchangeable with the $requester syntax, so, sorry, you still gotta call onFailure separately!
6885 * Also $requester doesn't use promises, so we won't either.
6886  * 
6887 * @author ianbe
6888 * @created 2025-06-24
6889 */
6890wpd.factory('wpdutilsRequester', ['$requester', function($requester) {
6891
6892    /* Store for the optional request cache */
6893    const requestCache = {};
6894
6895    /**
6896     * Looks in the request cache for a stored response to the given route and parameters.
6897     * @param {string} route The $requester route, for the cache key
6898     * @param {object} params The request parameters, for the cache key
6899     * @returns the response object if found, otherwise undefined
6900     */
6901    const getCache = function(route, params) {
6902        const paramsKey = JSON.stringify(params);
6903        if (!requestCache[route]) {
6904            return undefined;
6905        }
6906        return requestCache[route][paramsKey];
6907    }
6908
6909    /**
6910     * Stores a response in the request cache for the given route and parameters.
6911     * @param {string} route The $requester route, for the cache key
6912     * @param {object} params The request parameters, for the cache key
6913     * @param {object} value The response object to store
6914     */
6915    const setCache = function(route, params, value) {
6916        const paramsKey = JSON.stringify(params);
6917        if (!requestCache[route]) {
6918            requestCache[route] = {};
6919        }
6920        requestCache[route][paramsKey] = value;
6921    }
6922
6923    /**
6924     * Clears the request cache, optionally only for the given route and/or parameters.
6925     * @param {string|null} route The $requester route to clear from the cache, or null to clear the whole cache
6926     * @param {object|null} params The request parameters within the (required) route to clear from the cache, or null to clear the whole route
6927     */
6928    const clearCache = function(route = null, params = null) {
6929        if (!route) {
6930            requestCache = {};
6931            return;
6932        }
6933        if (params) {
6934            const paramsKey = JSON.stringify(params);
6935            delete requestCache[route][paramsKey];
6936        }
6937        else {
6938            delete requestCache[route];
6939        }
6940    }
6941
6942    return function(routes, ...args) {
6943        // Original requester object
6944        var requester = $requester(routes, ...args);
6945        
6946        // Tracker for loading state
6947        var loading = 0;
6948        var loadingRoutes = {};
6949        
6950        // Wrapper that will be returned to the caller
6951        var wrapper = {};
6952        
6953        /**
6954         * Outward-facing function to check if a request is currently in progress.
6955         * @returns {boolean} - true if a request is currently in progress, false otherwise
6956         */
6957        wrapper.isLoading = function(route = null) {
6958            if (route) {
6959                return (loadingRoutes[route] && loadingRoutes[route] > 0);
6960            }
6961            else {
6962                return (loading > 0);
6963            }
6964        };
6965
6966        /**
6967         * Outward-facing function to reset the loading state.
6968         */
6969        wrapper.unblock = function() {
6970            loading = 0;
6971        }
6972
6973        /**
6974         * Internal function to increment the loading counter at the start of a request.
6975         * @param {string|null} route The request route to use as a key
6976         */
6977        const incrementLoading = function(route = null) {
6978            loading = Math.max(1, loading + 1);
6979            if (route) {
6980                loadingRoutes[route] = Math.max(1, (loadingRoutes[route] ?? 0) + 1);
6981            }
6982        }
6983
6984        /**
6985         * Internal function to decrement the loading counter at the end of a request.
6986         * @param {string|null} route The request route to use as a key
6987         */
6988        const decrementLoading = function(route = null) {
6989            loading = Math.max(0, loading - 1);
6990            if (route) {
6991                loadingRoutes[route] = Math.max(0, (loadingRoutes[route] ?? 0) - 1);
6992            }
6993        }
6994        
6995        // Wrap each route's call function to add the extra functionality
6996        Object.keys(routes).forEach(function(key) {
6997            const original = requester[key];
6998            
6999            /**
7000             * wpdutilsRequester wrapper around the $requester function to call a route
7001             * @param {object} params Set of HTTP parameters to send with the request
7002             * @param {function} successFn Success callback function that can optionally return { cache: true } to cache the response
7003             * @param {boolean} background 
7004             * @param {object} opts 
7005             */
7006            wrapper[key] = function(params, successFn, background, opts = {}) {
7007                // Try to find a cached response, and bail out with it if found
7008                const cachedResponse = getCache(key, params);
7009                if (cachedResponse !== undefined) {
7010                    successFn(cachedResponse);
7011                    return {
7012                        onSuccess: function(fn) {},
7013                        onFailure: function(fn) {},
7014                        setLoader: function(obj) {},
7015                    }
7016                }
7017
7018                // Increment the loading counter
7019                incrementLoading(key);
7020
7021                // Call original function
7022                const result = original(params, successFn, background);
7023
7024                // Modify the other functions for loading state changes
7025                const originalOnSuccess = result.onSuccess;
7026
7027                /**
7028                 * wpdutilsRequester wrapper for the $requester onSuccess function
7029                 * @param {function} successFn Success callback function that can optionally return { cache: true } to cache the response
7030                 */
7031                result.onSuccess = function(successFn) {
7032                    originalOnSuccess(function(...args) {
7033                        const successFnResult = successFn(...args);
7034                        if ((successFnResult instanceof Object) && (successFnResult.cache)) {
7035                            // If the success function returns an object with a trueish cache property, store the response
7036                            delete params.authenticityToken;
7037                            setCache(key, params, args[0]);
7038                        }
7039                        decrementLoading(key);
7040                    });
7041                };
7042                result.onSuccess(successFn);
7043
7044                const originalOnFailure = result.onFailure;
7045
7046                /**
7047                 * wpdutilsRequester wrapper for the $requester onFailure function
7048                 * @param {function} failureFn Failure callback function
7049                 */
7050                result.onFailure = function(failureFn) {
7051                    originalOnFailure(function(...args) {
7052                        failureFn(...args);
7053                        decrementLoading(key);
7054                    });
7055                }
7056
7057                // Set an empty failure function to ensure that the loading state is decremented, even if not set
7058                result.onFailure(() => {});
7059
7060                return result;
7061            };
7062        });
7063        
7064        // Return a proxy that returns wrapper properties if they exist, and falls back to the original requester if not
7065        return new Proxy({}, {
7066            get(_, property) {
7067                return (wrapper[property] !== undefined) ? wrapper[property] : requester[property];
7068            }
7069        });
7070    };
7071}]);
7072
7073wpd.service('wpdutilsLoqateValidationService', [
7074    '$q',
7075    '$timeout',
7076    '$window',
7077    function($q, $timeout, $window) {
7078        // Loqate *requires* we don't re-use stored results over 30 days old, though recommends we check for fresh 
7079        // results every hour or so. Below is a practical TTL that meets both this requirement & recommendation.
7080        const LOQATE_RESULT_TTL_MS = 60 * 60 * 1000;
7081
7082        const memoisedValidationResults = new Map(); // Memoised results
7083        const pendingValidationRequests = new Map(); // In-flight requests
7084
7085        /**
7086         * @returns unique string key for a given validator type & input value.
7087         */
7088        function validationKey(validatorType, viewValue) {
7089            return JSON.stringify([validatorType, viewValue]);
7090        }
7091
7092        function getPreviousResult(key) {
7093            const result = memoisedValidationResults.get(key);
7094            if (!result) return null;
7095            
7096            // Remove entry if it's exceeded our TTL - we need to do a fresh Loqate call
7097            if (Date.now() - result.settledTime >
7097 LOQATE_RESULT_TTL_MS) {
7098                memoisedValidationResults.delete(key);
7099                return null;
7100            }
7101            return result.promise;
7102        }
7103
7104        /**
7105         * Sets up all shared directive infrastructure and returns a ready to assign $asyncValidators function.
7106         *
7107         * @param {object} options
7108         * @param {string} options.validatorType - Unique key for this validator (DIRECTIVE_KEY)
7109         * @param {object} options.ctrl - ngModel controller
7110         * @param {object} options.$elem - jQuery-wrapped element
7111         * @param {Function} options.onLoadStart - Called when a validation request begins
7112         * @param {Function} options.onLoadEnd - Called when a validation request completes
7113         * @param {Function} options.onDestroy - Accepts a cleanup function and registers it for execution on directive 
7114         * destroy
7115         * @param {object} options.customInputMixin - wpdutilsCustomInputMixinService
7116         * @param {object} options.json - $requester result with setLoader
7117         * @param {Function} options.regexCheck - Pre-flight check run before the API call - return false to reject the 
7118         * value immediately
7119         * @param {string} options.regexFailMessage - Error message shown on regex failure
7120         * @param {string} options.emptyMessage - Error message shown when value is empty
7121         * @param {number} options.timeoutMs - Milliseconds before a pending validation request is abandoned
7122         * @param {Function} options.buildApiCall - Constructs the API call function for this validator, given the json 
7123         * requester and API key handle
7124         * @param {string} options.apiKeyHandle - API key handle from directive attribute
7125         * @param {boolean} options.debug - Enable debug logging
7126         */
7127        this.createValidator = function(options) {
7128            if (!options || !options.validatorType || !options.ctrl || !options.$elem ||
7129                !options.customInputMixin || !options.json || !options.regexCheck || !options.buildApiCall) {
7130                throw new Error('wpdutilsLoqateValidationService.createValidator: missing required options');
7131            }
7132
7133            const { 
7134                validatorType, ctrl, $elem, customInputMixin, json, regexCheck, regexFailMessage, emptyMessage,
7135                timeoutMs, buildApiCall, apiKeyHandle, debug
7136            } = options;
7137
7138            json.setLoader({
7139                load:  options.onLoadStart,
7140                clear: options.onLoadEnd
7141            });
7142
7143            /**
7144             * If Loqate gave us a definitive result, memoise a lightweight already-settled promise rather than the
7145             * original - to avoid retaining the full validation closure in memory - and store a settledTime.
7146             * 
7147             * @param {*} resolved - whether the promise was resolved (true) or rejected (false)
7148             * @param {*} lookupKey - key to store the validation result under
7149             * @param {*} isLoqateResultDefinitive - whether the result from loqate was definitive
7150             */
7151            function memoiseDefinitiveResult(resolved, lookupKey, isLoqateResultDefinitive) {
7152                if (isLoqateResultDefinitive) {
7153                    memoisedValidationResults.set(lookupKey, {
7154                        promise: resolved ? $q.resolve() : $q.reject(),
7155                        // We need to know when we performed this lookup, see LOQATE_RESULT_TTL_MS
7156                        settledTime: Date.now()
7157                    });
7158                }
7159            }
7160
7161            function setError(msg) {
7162                ctrl.customErrorMessages[validatorType] = msg;
7163            }
7164
7165            // Error message initialisation
7166            if (!ctrl.customErrorMessages) ctrl.customErrorMessages = {};
7167            ctrl.customErrorMessages[validatorType] = null;
7168
7169            const apiCall = buildApiCall(json, apiKeyHandle);
7170
7171            // Creating cleanup function and passing to options onDestroy fn.
7172            function cleanup() {
7173                const keyPrefix = JSON.stringify([validatorType, '']).slice(0, -3);
7174                pendingValidationRequests.forEach(function(promise, key) {
7175                    if (key.startsWith(keyPrefix)) pendingValidationRequests.delete(key);
7176                });
7177            }
7178            options.onDestroy(cleanup);
7179
7180            return function $asyncValidator(modelValue, viewValue) {
7181
7182                if (ctrl.$isEmpty(modelValue)) {
7183                    setError(null); // (Remove error)
7184                    return $q.resolve();
7185                }
7186
7187                const lookupKey = validationKey(validatorType, viewValue);
7188
7189                // If we have a pending promise for the given viewValue, return that promise instead of creating a
7190                // new one.
7191                const pendingRequest = pendingValidationRequests.get(lookupKey);
7192                if (pendingRequest) {
7193                    debug && console.log(`[${validatorType}] Skipping - ${viewValue} already pending`);
7194                    return pendingRequest;
7195                }
7196
7197                // If we've already validated this viewValue - with a definitive yes/no from Loqate - return the
7198                // associated promise instead of creating a new one.
7199                const previousResult = getPreviousResult(lookupKey);
7200                if (previousResult) {
7201                    debug && console.log(`[${validatorType}] Skipping - ${viewValue} already validated`);
7202                    return previousResult;
7203                }
7204
7205                // Perform supplied regex check on value
7206                if (!regexCheck(viewValue)) {
7207                    debug && console.log(`[${validatorType}] Failed regex check for: ${viewValue}`);
7208                    setError(regexFailMessage);
7209                    return $q.reject();
7210                }
7211
7212                let isLoqateResultDefinitive = false;
7213
7214                const validationPromise = $q(function(origResolve, origReject) {
7215
7216                    let settled = false;
7217                    let validationTimeout;
7218
7219                    // Helper wrappers to allow pre/post logic before resolving/rejecting
7220                    function resolve() {
7221                        // Place any pre-resolve logic here
7222                        if (settled) return;
7223                        settled = true;
7224                        $timeout.cancel(validationTimeout);
7225                        $elem.removeAttr('disabled');
7226                        origResolve();
7227                    }
7228
7229                    function reject() {
7230                        // Place any pre-reject logic here
7231                        if (settled) return;
7232                        settled = true;
7233                        $timeout.cancel(validationTimeout);
7234                        $elem.removeAttr('disabled');
7235                        origReject();
7236                    }
7237
7238                    // Disable the element while validation is in progress
7239                    $elem.attr('disabled', 'disabled');
7240
7241                    // Prevent hung validation requests from leaving the validator pending indefinitely.
7242                    validationTimeout = $timeout(function() {
7243                        debug && console.warn(`[${validatorType}] Validation timed out for: ${viewValue}`);
7244                        options.onLoadEnd();
7245                        setError(null);
7246                        resolve();
7247                    }, timeoutMs);
7248
7249                    apiCall({
7250                        viewValue,
7251                        resolve,
7252                        reject,
7253                        setError,
7254                        markDefinitive: function() {
7255                            isLoqateResultDefinitive = true;
7256                        },
7257                        onNetworkFailure: function() {
7258                            // Loader clear not called on this path so need to do so ourselves
7259                            options.onLoadEnd();
7260
7261                            if (false === $window.navigator.onLine) {
7262                                // The user is offline, according to their browser
7263                                setError("You appear to be offline. Please check your internet connection.");
7264                                reject();
7265                            } else {
7266                                // The user isn't offline, so something else has gone wrong.
7267                                // We'll have to let this telephone number through.
7268                                debug && console.warn(`[${validatorType}] Request failed for: ${viewValue}`);
7269                                setError(null);
7270                                resolve();
7271                            }
7272                        }
7273                    });
7274                });
7275
7276                // Guard duplicate in-flight requests for the same value
7277                pendingValidationRequests.set(lookupKey, validationPromise);
7278
7279                validationPromise.finally(function() {
7280                    pendingValidationRequests.delete(lookupKey); // Remove viewValue from pending map - now settled
7281                });
7282
7283                validationPromise.then(
7284                    function() { memoiseDefinitiveResult(true, lookupKey, isLoqateResultDefinitive); },
7285                    function() { memoiseDefinitiveResult(false, lookupKey, isLoqateResultDefinitive); }
7286                );
7287
7288                return validationPromise;
7289            };
7290        };
7291    }
7292]);
7293
7294/**
7295 * Sets up the loading spinner for a validation directive.
7296 * Injects the spinner into the directive's parent element, initialises it to hidden,
7297 * and returns the scope property name.
7298 *
7299 * @param {object} $scope - The directive's scope
7300 * @param {object} $elem - jQuery-wrapped directive element
7301 * @param {string} directiveKey - The directive's unique key
7302 * @param {object} customInputMixin - wpdutilsCustomInputMixinService
7303 * @returns {string} The scope property name used to control spinner visibility
7304 */
7305function setupLoadingSpinner($scope, $elem, directiveKey, customInputMixin) {
7306    // The replaceAll needs to be here as stray '-' chars mess up the selection for the universal loading spinner
7307    // Selection is also too broad without $elem.attr('id')
7308    const loadingSpinnerProperty = (directiveKey + 'Loading_' + $elem.attr('id')).replaceAll('-', '');
7309    // Inject a loading spinner into the parent element
7310    customInputMixin.injectLoadingSpinner($scope, $elem.parent(), loadingSpinnerProperty);
7311    $scope[loadingSpinnerProperty] = false;
7312    return loadingSpinnerProperty;
7313}
7314;
7315/** include: angular.wpdsearchAutoComplete.js **/
7316window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpdsearchAutoComplete.js');
7317var autocomplete = angular.module('wpdsearchAutoComplete', []);
7318        
7319/**
7320 * Service for search AutoComplete calls
7321 */
7322autocomplete.service('wpdsearchAutoCompleteService', ['$loading', '$requester', '$q', function($loading, $requester, $q) {
7323    // private vars
7324    var _json = $requester({
7325        'autoComplete': 'SearchFront.autoComplete',
7326        'promotedResults': 'SearchFront.promotedSearchResultsAutoComplete'
7327    }),
7328    _loader = {load: function () { }, clear: function () { }},
7329    _searchTerm = null,
7330    _successCb = [],
7331    _errorCb = angular.noop,
7332    _timeout = null;
7333
7334    _json.setLoader(_loader);
7335    
7336    
7337    var callRefresh = function() {
7338        var deferred = $q.defer();
7339        
7340        _json.autoComplete(
7341            {
7342                searchTerm: _searchTerm,
7343            }, function(response) {
7344                deferred.resolve({data: response});
7345            }.bind(this),
7346            deferred.reject
7347        );
7348
7349        return deferred.promise;
7350    };
7351    
7352    /**
7353     * Promoted serach results
7354     */
7355    var promotedResultsSearch = function() {
7356        var deferred = $q.defer();
7357        
7358        _json.promotedResults(
7359            {
7360                searchTerm: _searchTerm
7361            },function(response) {
7362                deferred.resolve({success: true, data: response});
7363            }.bind(this),
7364            deferred.reject
7365        );
7366
7367        return deferred.promise;
7368    };
7369    
7370    this.refresh = function() {
7371        $q.all({searchData: callRefresh(),
7372                promotedData: promotedResultsSearch()})
7373            .then(beforeSuccess)
7374            .catch(_errorCb);
7375    };
7376    
7377    /**
7378     * Function to set the current search term
7379     * @param {String} searchTerm search term to try and auto complete
7380     */
7381    this.setSearchTerm = function(searchTerm) {
7382        _searchTerm = searchTerm;
7383
7384        //clear timeout if exists
7385        if(_timeout !== null) clearTimeout(_timeout);
7386        //set timeout before handling - auto complete.
7387        _timeout = setTimeout(this.refresh, 250);
7388    }
7389
7390    /**
7391     * Sets the success callback for auto complete
7392     * @param {Function} successCallback the success callback
7393     * @returns {AutoComplete} this
7394     */
7395    this.onSuccess = function(successCallback) {
7396        _successCb.push(successCallback);
7397        return this;
7398    };
7399
7400    /**
7401     * Sets the error callback for auto complete
7402     * @param {Function} errorCallback the error callback
7403     * @returns {AutoComplete} this
7404     */
7405    this.onFailure = function(errorCallback) {
7406        _errorCb = errorCallback;
7407        return this;
7408    };
7409
7410    /**
7411     * Trusts the hit description as html before
7412     * sending the response to the controller
7413     * 
7414     * @param {searchResult} searchResult the response
7415     */
7416    var beforeSuccess = function(searchResult) {
7417        var result = {};
7418        
7419        if (searchResult && searchResult.promotedData.data.promotedResults) {
7420            var promotedResults = JSON.parse(searchResult.promotedData.data.promotedResults);
7421            var uniquePromotedResults = [];
7422            
7423            if (Array.isArray(promotedResults) && Array.isArray(searchResult.searchData.data.options)) {
7424                
7425                // Ensure no duplicates in promoted result suggestions.
7426                promotedResults.forEach(function(promotedResult) {
7427                    if (!doesArrayContainPromotedResult(uniquePromotedResults, promotedResult)) {
7428                        uniquePromotedResults.push(promotedResult);
7429                    }
7430                });
7431                
7432                uniquePromotedResults.forEach(function(pr) {
7433                    pr.text = pr.title;
7434                });
7435                result.promotedData = uniquePromotedResults;
7436            }
7437        }
7438        
7439        if (searchResult && searchResult.searchData.data.options) {
7440            result.searchData = searchResult.searchData.data.options;
7441        }
7442        
7443        _successCb.forEach(function(fn) {
7444            fn(result);
7445        });
7446    };
7447    
7448    
7449    /**
7450     * Check whether or not an item already exists within an array.
7451     * 
7452     * @param {array} uniqueArray an array of unique values.
7453     * @param {object} item the item that might already exist in the unique array.
7454     * @returns {Boolean} whether or not an item already exists in the unique array.
7455     */
7456    function doesArrayContainPromotedResult(uniqueArray, item) {
7457        return (uniqueArray.filter(function(uniqueItem) { return uniqueItem.id === item.id && uniqueItem.className === item.className; } )).length > 0;
7458    }
7459}]);
7460;
7461/** include: angular.wpdsearchSearchForm.js **/
7462window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpdsearchSearchForm.js');
7463/**
7464* Adapted from code within the wpdcore style module's angular.wpd.js.
7465* This is separated from the wpdsearchSearch module because we do not want to have to pull in needless dependencies that
7466*   only apply to the search result page.
7467*
7468*/
7469var searchForm = angular.module('wpdsearchSearchForm', [
7470    'wpd',
7471    'wpdsearchAutoComplete',
7472]);
7473
7474/**
7475* Moved here from the wpdcore style module angular.wpd.js
7476*
7477* TODO: This and the normal SearchController do pretty much the same thing, but the SearchController also handles pagination and the like,
7478*       make the move to splitting out this behaviour.
7479*/
7480searchForm.controller('wpdsearchSiteSearchController', [
7481    '$scope',
7482    '$timeout',
7483    '$element',
7484    'wpdsearchAutoCompleteService',
7485    'Analytics',
7486    function($scope, $timeout, $element, AutoComplete, Analytics) {
7487        var vm = this;
7488
7489        vm.baseUrl = null;
7490        vm.searchPageUrl = null;
7491        vm.enableAutoSuggestions = false;
7492        vm.searchFocused = false;
7493        vm.selectedAutoCompleteOption = false;
7494        vm.lastKeyUpSearchTerm = null;
7495        vm.autoCompleteLoadingText = '';
7496        vm.searchTypeFilter = null;
7497        vm.searchForm = null;                   // The implicit FormController on the <form> element.
7498        
7499        var appendedId = '';
7500        var focusEventName = '';
7501        var preBlurEventName = '';
7502        var postBlurEventName = '';
7503
7504        /**
7505        * Initialize the controller
7506        *
7507        * @param {string} baseUrl - the site baseUrl
7508        * @param {string} searchPageUrl - the controller action route that performs the search
7509        * @param {boolean} enableAutoSuggestions - enable or disable autosuggestions?
7510        * @param {String} id - the unique appended identifier for this form's inputs
7511        * @return {undefined}
7512        */
7513        vm.init = function(baseUrl, searchPageUrl, enableAutoSuggestions, id) {
7514            vm.baseUrl = baseUrl;
7515            vm.searchPageUrl = searchPageUrl;
7516            vm.enableAutoSuggestions = enableAutoSuggestions;
7517            appendedId = id;
7518            focusEventName = 'searchFocused_' + appendedId;
7519            preBlurEventName = 'searchPreBlurred_' + appendedId;
7520            postBlurEventName = 'searchPostBlurred_' + appendedId;
7521        };
7522
7523        /**
7524        * @return {string} - the form action endpoint, based on the search controller's action plus the search term.
7525        */
7526        vm.getAction = function() {
7527            return vm.baseUrl + '/' + vm.searchPageUrl + (vm.searchTerm ? '/' + encodeURIComponent(vm.searchTerm) : '');
7528        };
7529
7530        /**
7531        * @param {String} url - the url to assess
7532        * @return {String} - the url, appended with the baseUrl if appropriate
7533        */
7534        vm.getPromotedResultURL = function(url) {
7535            if (url) {
7536                // if we aren't prepended with http(s):// or www., then we have to assume this is a site url.
7537                return url.match(/^(http(s)?:\/\/|www[0-9]?\.)/gi) ? url : (vm.baseUrl + (url.startsWith('/') ? '' : '/') + url);
7538            }
7539
7540            return '#';
7541        };
7542
7543        /**
7544        * TODO: would be nice to have a generic service that can properly assess the visibility of any element in the page.
7545        * @return {boolean} - check to see if the filter is hidden via CSS
7546        */
7547        vm.searchTypeFilterVisible = function() {
7548            var $filterElem = $element.find('.search-type-filter');
7549
7550            return $filterElem && $filterElem.css('display') !== 'none';
7551        };
7552
7553        /**
7554        * @return {boolean} - the disabled state of the search form's button. Will be disabled if there is no searchTerm
7555        */
7556        vm.searchDisabled = function() {
7557            return !angular.isDefined(vm.searchTerm) || vm.searchTerm === null ||
7558                    vm.searchTerm.replace(/^\s+/, '').replace(/\s+$/, '') === '';
7559        };
7560
7561        /**
7562        * Intercept the submit and do nothing if the search form is empty.
7563        *
7564        * @return {undefined}
7565        */
7566        vm.onSubmit = function(event) {
7567            if (vm.searchDisabled()) {
7568                event.preventDefault();
7569            }
7570        };
7571
7572        /**
7573        * Trigger the form's submission
7574        *
7575        * @return {undefined}
7576        */
7577        vm.search = function(event) {
7578            $timeout(function() {
7579                $element.find('form').submit();
7580            });
7581        };
7582
7583        var keyupAutoTimeoutTop = null;     // don't know if it matters, but I don't want to accidentally conflict with variables used for the Search page's version of this code.
7584
7585        vm.searchKeyup = function(event) {
7586            if (event.which === 13) {
7587                // was enter key
7588                vm.search();
7589            } else {
7590                if (vm.searchTerm && vm.searchTerm.length > 2 && vm.lastKeyUpSearchTerm !== vm.searchTerm) {    //only let autocomplete reassign the suggestions list if the input actually changed.
7591                    vm.lastKeyUpSearchTerm = vm.searchTerm;
7592                    vm.autoComplete = [];
7593                    vm.promotedAutoComplete = [];
7594
7595                    // you need the timeout to be able to 'cancel' a previously lined up setSearchTerm, otherwise you'll be in a position where they start overwriting each other once the first one populates the list
7596                    // and shows it (this is the same as the original problem).
7597                    clearTimeout(keyupAutoTimeoutTop);
7598
7599                    keyupAutoTimeoutTop = setTimeout(function() {
7600                        vm.autoCompleteLoadingText = 'Loading autocomplete results';
7601                        AutoComplete.setSearchTerm(vm.searchTerm);
7602                        //console.log('search keyup w/ auto');
7603                    }, 500);        // timeout interval needs to be set at a decent enough pace that you can assume when the person has paused typing, but not close enough that you risk multiple autocompletes lining themselves up.
7604                    //console.log('settimeout');
7605                }
7606            }
7607        };
7608
7609        vm.useAutoComplete = function(autoComplete) {
7610            vm.selectedAutoCompleteOption = true;
7611            vm.searchTerm = autoComplete;
7612            vm.autoComplete = [];
7613            vm.promotedAutoComplete = [];
7614            vm.search();
7615        };
7616
7617        vm.logPromotedResultSelected = function(promotedResultId) {
7618            Analytics.logEvent('Site Search', 'AutoCompletePromotedHeader', promotedResultId);
7619        };
7620
7621        vm.focus = function() {
7622            vm.searchFocused = true;
7623            $scope.$broadcast(focusEventName);
7624            $scope.$emit(focusEventName);
7625        };
7626
7627        vm.defocus = function() {
7628            $timeout(function() {
7629                // only close the suggestions box if none of these have focus
7630                if (!$element.find('input#search_' + appendedId + ':focus, div.search-auto-suggestions:focus, div.search-auto-suggestions:focus-within, .search-suggestion-link:focus').length) {
7631                    vm.searchFocused = false;
7632                    $scope.$broadcast(postBlurEventName);
7633                    $scope.$emit(postBlurEventName);
7634                }
7635            }, 500);    // any reason for timeout here?
7636
7637            $scope.$broadcast(preBlurEventName);
7638            $scope.$emit(preBlurEventName);
7639        };
7640
7641        vm.showAutoSuggestions = function() {
7642            return vm.autoComplete && vm.autoComplete.length && vm.searchFocused && vm.searchTerm && vm.enableAutoSuggestions;
7643        };
7644
7645        AutoComplete.onSuccess(function(result) {
7646            vm.promotedAutoComplete = result && result.promotedData ? result.promotedData : [];
7647            vm.autoComplete = result && result.searchData ? result.searchData : [];
7648            vm.autoCompleteLoadingText = 'Autocomplete results loaded';
7649        });
7650
7651        AutoComplete.onFailure(function(error) {
7652            // TODO: error handling
7653            vm.autoCompleteLoadingText = 'No autocomplete results';
7654            throw error;
7655        });
7656    }
7657]);
7658;
7659/** include: angular.universal-header.js **/
7660window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.universal-header.js');
7661/**
7662* OVERRIDE of the foundation_base one
7663*
7664*/
7665angular.module("universal-header", [])
7666    .controller("universalHeaderCtrl", ["$scope", function ($scope) {
7667      }])
7668    .directive("universalSiteHeader", ["$window", function($window) {
7669      return {
7670        restrict: "C",
7671        link: function(scope, element, attributes) {
7672            scope.megaMenuOpen = false;
7673            scope.topMenuOpen = false;
7674          
7675            if (typeof attributes['closeOnOther'] !== "undefined") {
7676              angular.element(window).on("header-link", function(e, data) {
7677                if (data.type !== "mega-menu" && data.open) {
7678                  scope.megaMenuOpen = false;
7679                }
7680                if (data.type !== "search-form" && data.open) {
7681                  scope.searchFormOpen = false;
7682                }
7683              });
7684            }
7685            
7686            scope.toggleMegaMenu = function() {
7687                scope.megaMenuOpen = !scope.megaMenuOpen;
7688                
7689                angular.element(window).trigger("header-link", { open: scope.megaMenuOpen, type: "mega-menu" });
7690            }
7691
7692            scope.toggleTopMenu = function() {
7693              scope.topMenuOpen = !scope.topMenuOpen;
7694              
7695              angular.element(window).trigger("header-link", { open: scope.megaMenuOpen, type: "mega-menu" });
7696          }
7697
7698            // NEW
7699            scope.toggleSearchForm = function() {
7700                scope.searchFormOpen = !scope.searchFormOpen;
7701
7702                angular.element(window).trigger("header-link", { open: scope.searchFormOpen, type: "search-form" });
7703            }
7704        }
7705     }
7706    }])
7707    .directive("universalSiteHeaderSticky", [
7708    "$window",
7709    function ($window) {
7710      return {
7711        restrict: "C",
7712        scope: {
7713          stickyContainerSelector: "@",
7714          hideHeight: "@"
7715        },
7716        link: function (scope, element) {
7717          var $ = angular.element,
7718            // The ticking flag ensures we only do potentially
7719            // expensive operations when we need to. We also
7720            // make use of requestAnimationFrame to limit updates
7721            // to the device's refresh rate for maximum performance
7722            // vs smoothness.
7723            ticking = false;
7724
7725          /**
7726           * This takes care of what should happen if the header's
7727           * height changes due to a screen resize.
7728           */
7729          $($window).on("resize", function () {
7730            // Only do an update if we've not got one ready to go
7731            // in this frame.
7732            if (!ticking) {
7733              $window.requestAnimationFrame(function () {
7734                updateHeaderClearance();
7735                // Once we've done our work, switch the flag back
7736                // to false to allow another run.
7737                ticking = false;
7738              });
7739              // Set our flag to true to prevent this being called
7740              // more than once per frame.
7741              ticking = true;
7742            }
7743          });
7744
7745          /**
7746           * This is our main 'hot' event listener for pinning the
7747           * header. The same 'ticking' flag is used here.
7748           */
7749          $($window).on("scroll", function () {
7750            if (!ticking) {
7751              $window.requestAnimationFrame(function () {
7752                updateScroll();  
7753                ticking = false;
7754              });
7755              ticking = true;
7756            }
7757          });
7758          
7759          /**
7760           * Store some initial state for our sticky calcs.
7761           */
7762          var el = element[0], // the element itself separate from its jq wrapper
7763            stickyHeight = 0, // the header height
7764            stickyTop = 0, // the header's current position
7765            scrollTop = 0, // the window's current scroll position
7766            lastScroll = 0, // the previous update's scroll position
7767            scrollDelta = 0, // the window scroll delta
7768            hideHeight = parseInt(scope.hideHeight || 0, 10); // how far up to scroll before revealing the header
7769            
7770
7771          /**
7772           * Refactored for speed - this uses (mostly) pure JS DOM manipulation
7773           * to update the position of the sticky header given the current
7774           * status of the window scroll position vs the header's height.
7775           */
7776          function updateScroll() {
7777            stickyHeight = el.offsetHeight + hideHeight;
7778            scrollTop = $window.pageYOffset;
7779            scrollDelta = lastScroll - scrollTop;
7780            stickyTop =
7781              element.position().top +
7782              scrollDelta;
7783
7784            if (scrollTop <= 0) {
7785              // Scrolled to the very top; just set the value to 0
7786              el.style.top = "0px";
7787            }
7788            else if (scrollDelta > 0) {
7789              // Scrolling up; reveal the element until it reaches the top
7790              el.style.top = (stickyTop > 0 ? 0 : stickyTop) + "px";
7791            } else if (scrollDelta < 0) {
7792              // Scrolling down; hide the element until it scrolls out of view
7793              el.style.top = (Math.abs(stickyTop) > stickyHeight ? -stickyHeight : stickyTop) + "px";
7794            }
7795            // Update the previous scroll position
7796            lastScroll = scrollTop;
7797          }
7798
7799          /**
7800           * This simply ensures that there's enough top padding on
7801           * an element so that the header 'clears' it when the page
7802           * is scrolled all the way to the top.
7803           */
7804          function updateHeaderClearance() {
7805            $(scope.stickyContainerSelector || "body")[0].style.paddingTop = element[0].offsetHeight + "px";
7806          }
7807
7808          // Do both of our checks on initialisation.
7809          updateHeaderClearance();
7810          updateScroll();
7811          
7812          // Double check header clearance in 250ms
7813          setTimeout(updateHeaderClearance, 250);
7814        }
7815      };
7816    }
7817]);
7818
7819;
7820/** include: angular.universal-collapsing-menu.js **/
7821window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.universal-collapsing-menu.js');
7822/**
7823 * Stolen from Connections site
7824 * Adapted from lewisl's topMenuCTRL controller in angular.menus.js in the wpdcore style module.
7825 *
7826 * Used to control the visibility of top menu links via a 'more' item responsively
7827 * @author lewisl, noahm
7828 *
7829 * TODO
7830 */
7831angular.module('universal-collapsing-menu', []).controller('universal-collapsingMenuCtrl', [
7832    '$window',
7833    '$element',
7834    '$timeout',
7835    function ($window, $element, $timeout) {
7836        var vm = this;
7837
7838        // Grab some of the elements we need via jQuery (yuck, I know!)
7839        var topMenuItems = $element.find('ul > li:not(.universal-collapsing-menu__show-more,.in-dropdown)');
7840        var topMenuHiddenItems = $element.find('ul > li.in-dropdown');
7841        var topMenuShow = $element.find('.universal-collapsing-menu__show-more');
7842
7843        // The threshold height above which the menu is considered to be wrapping
7844        var wrapHeight = 50;
7845
7846        // Whether the menu is shown or not
7847        vm.showMore = false;
7848
7849        // Whether the Show More button is focused or not
7850        vm.isFocused = false;
7851
7852        // Our list of hidden links (not elements, just objects with href and text)
7853        vm.hiddenLinks = [];
7854
7855        var prevWindowWidth = angular.element($window).innerWidth();
7856
7857        /**
7858         * Init function to tell which collapsing menu this is (mobile or desktop version)
7859         */
7860        vm.init = function(mobileMenu) {
7861            vm.mobileMenu = mobileMenu;
7862            if (vm.mobileMenu) {
7863                window.addEventListener('click', function(e) {
7864                    if (!this.document.getElementById("universal-collapsing-menu__show-more").contains(e.target)) {
7865                        $timeout(function () {
7866                            vm.showMore = false;
7867                        }, 1);
7868                    }
7869                });
7870            }
7871        }
7872
7873        /**
7874         * Unhides all of the menu items, then if the element is wrapping
7875         * (determined by its height being above the wrapHeight) it hides them one
7876         * at a time until the height reduces to or below the wrapHeight
7877         *
7878         * @param {Boolean} force if true, skip the window width check
7879         */
7880        vm.check = function (force) {
7881            if (!force) {
7882                var checkedWindowWidth = angular.element($window).innerWidth();
7883                if (checkedWindowWidth === prevWindowWidth) {
7884                    return;
7885                }
7886            }
7887            prevWindowWidth = checkedWindowWidth;
7888            topMenuItems.show();
7889            if ($element.height() > wrapHeight) {
7890                var index = topMenuItems.length - 1;
7891                while ($element.height() > wrapHeight) {
7892                    topMenuItems.eq(index--).hide();
7893                    if (!vm.mobileMenu && index < 1) {
7894                        break;
7895                    } else if (vm.mobileMenu && index < 0) {
7896                        break;
7897                    }
7898                }
7899            }
7900            $timeout(function() {
7901                vm.populateHidden();
7902            });
7903        };
7904
7905        /**
7906         * Moves hidden links into the hiddenLinks array
7907         */
7908        vm.populateHidden = function () {
7909            vm.hiddenLinks = [];
7910            var hidden = topMenuItems.filter(':hidden');
7911            if (!hidden) {
7912                return;
7913            }
7914            hidden.each(function (i, link) {
7915                link = angular.element(link).find('a');
7916                vm.hiddenLinks.push({
7917                    href: link.attr('href'),
7918                    text: link.text()
7919                });
7920            });
7921            topMenuHiddenItems.each(function (i, link) {
7922                topMenuHiddenItems.eq(i).hide();
7923                link = angular.element(link).find('a');
7924                vm.hiddenLinks.push({
7925                    href: link.attr('href'),
7926                    text: link.text()
7927                });
7928            });
7929        };
7930
7931        /**
7932         * Toggles whether the 'more' menu is open or not
7933         */
7934        vm.toggleOpen = function (doOnMobile) {
7935            if (doOnMobile == vm.mobileMenu) {
7936                vm.showMore = !vm.showMore;
7937            }
7938        };
7939
7940        /**
7941             * Function that gets called when the enter key is pressed, checks for focus and then opens the menu - for accessibility reasons
7942             */
7943        vm.accessibleToggle = function(event) {
7944            if (vm.isFocused && event.which === 13) {
7945                vm.showMore = !vm.showMore;
7946            }
7947        };
7948
7949        /**
7950             * Simple function that sets a flag to check if the show more button is focused
7951             */
7952        vm.focusButton = function() {
7953            vm.isFocused = true;
7954        }
7955
7956        /**
7957         * Clears isFocused flag when the focus shifts to another element
7958         */
7959        vm.blurButton = function() {
7960            vm.isFocused = false;
7961        }
7962
7963        /**
7964         * Closes the 'more' menu
7965         */
7966        vm.close = function () {
7967            if (!vm.mobileMenu) {
7968                vm.showMore = false;
7969            }
7970        };
7971
7972        // Watch the window resize event - note we do not debounce this function
7973        // as we want this to be as instantaneous as possible
7974        angular.element($window).bind('resize', vm.check);
7975
7976        angular.element($window).bind('wpd.resize', function () {
7977            vm.check(true);
7978        });
7979
7980        vm.check(true);
7981        $timeout(function () {
7982            vm.check(true);
7983        }, 1);
7984        
7985    }
7986]);
7987;
7988/** include: angular.universal-mega-menu.js **/
7989window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.universal-mega-menu.js');
7990/**
7991* OVERRIDE of the foundation_base version
7992*/
7993angular.module("universal-mega-menu", [])
7994//NEW constant
7995.constant('EVENT_MEGA_MENU_OPEN', 'megaMenuOpen')
7996.controller("universalMegaMenuCtrl", ["$scope", function($scope) {}])
7997.directive("megaMenuItem", ["$window", 'EVENT_MEGA_MENU_OPEN', function($window, EVENT_MEGA_MENU_OPEN) {
7998    return {
7999        restrict: "C",
8000        scope: true,
8001        link: function(scope, element, attrs) {
8002            scope.isOpen = false;
8003            scope.isFocused = false;
8004            scope.expanderColour = "#ffffff";
8005
8006            var childMenu = element.find(".mega-menu__child").first();
8007            var animationSpeed = 200;
8008            // TODO: ask Lew to make the property to target configurable from the system prefs of something
8009            var breakpoint = parseInt(getComputedStyle(document.body).getPropertyValue("--universal-mega-menu-breakpoint"), 10) || 1024;
8010
8011            var setState = function(state) {
8012                if (angular.element($window).outerWidth() < breakpoint) {
8013                    return;
8014                }
8015                scope.isOpen = !!state;
8016
8017                scope.$emit(EVENT_MEGA_MENU_OPEN, scope.isOpen);
8018                scope.$broadcast(EVENT_MEGA_MENU_OPEN, scope.isOpen);
8019                scope.isOpen ? $('html').addClass('mega-menu--open') : $('html').removeClass('mega-menu--open');
8020            };
8021
8022            var doAnimation = function() {
8023                if (scope.isOpen) {
8024                    childMenu.stop().slideDown(animationSpeed);
8025                }
8026                else {
8027                    childMenu.stop().slideUp(animationSpeed);
8028                }
8029            };
8030
8031            scope.toggle = function() {
8032                // This condition is here to prevent click events working on desktop size, as they don't behave properly due to other event listeners
8033                if (angular.element($window).outerWidth() < breakpoint) {
8034                    scope.isOpen = !scope.isOpen;
8035                    if (scope.isOpen) {
8036                        scope.expanderColour = "#00148C";
8037                    } else {
8038                        scope.expanderColour = "#ffffff";
8039                    }
8040                    doAnimation();
8041                }
8042            };
8043
8044            /**
8045             * Function that gets called when the enter key is pressed, checks for focus and then opens the menu - for accessibility reasons
8046             */
8047            scope.accessibleToggle = function(event) {
8048                if (scope.isFocused && event.which === 13) {
8049                    scope.isOpen = !scope.isOpen;
8050                }
8051            };
8052
8053            /**
8054             * Simple function that sets a flag to enable scope.accessibleToggle to check which expander button is focused
8055             */
8056            scope.focusExpander = function() {
8057                scope.isFocused = true;
8058            }
8059
8060            /**
8061             * Clears isFocused flag when the focus shifts to another element
8062             */
8063            scope.blurExpander = function() {
8064                scope.isFocused = false;
8065            }
8066
8067            /**
8068             * Function to open the mega menu on desktop if it is tabbed into for accessibility reasons
8069             */
8070            scope.toggleDesktop = function() {
8071                if (angular.element($window).outerWidth() >= breakpoint) {
8072                    setState(!scope.isOpen);
8073                }
8074            }
8075
8076            scope.open = function() {
8077                setState(true);
8078            };
8079
8080            scope.close = function() {
8081                setState(false);
8082            };
8083
8084            if (breakpoint) {
8085                let mq = $window.matchMedia('(min-width: ' + breakpoint + 'px)');
8086                $($window).on('resize', function() {
8087                    if (mq.matches) {
8088                        // unset the dynamic height
8089                        $('nav.mega-menu').css('height', '');
8090                        // remove the styles introduced as a result of the animations in the mobile menu child toggles
8091                        $('nav.mega-menu .mega-menu__child').attr('style', null);
8092                        scope.isOpen = false;
8093                    }
8094                });
8095            }
8096        }
8097    }
8098}]);
8099;
8100/** include: angular.wpd-accessibility.js **/
8101window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.wpd-accessibility.js');
8102/**
8103 * @author noahm
8104 */
8105var accessibility = angular.module('wpd-accessibility', []);
8106
8107//class SelfResetRegex  {
8108//    constructor(reg) {
8109//        this.reg = reg;
8110//    }
8111//    exec(str) {
8112//        var result = this.reg.exec(str);
8113//        this.reg.lastIndex = 0;
8114//        return result;
8115//    }
8116//}
8117//
8118//class WPDColorObject {
8119//    constructor(R, G, B, A) {
8120//        this.R = R;
8121//        this.G = G;
8122//        this.B = B;
8123//        this.A = A;
8124//    }
8125//}
8126
8127/**
8128 * @constructor
8129 * @param {RegExp} reg
8130 * @returns {SelfResetRegex}
8131 */
8132function SelfResetRegex(reg) {
8133    this.reg = reg;
8134};
8135SelfResetRegex.prototype.exec = function(str) {
8136    var result = this.reg.exec(str);
8137    this.reg.lastIndex = 0;
8138    return result;
8139};
8140
8141/**
8142 * @constructor
8143 * @param {Number} R  (0-255)
8144 * @param {Number} G
8145 * @param {Number} B
8146 * @param {Number} A  (0.0-1.0)
8147 * @returns {WPDColorObject}
8148 */
8149function WPDColorObject(R, G, B, A) {
8150    this.R = R;
8151    this.G = G;
8152    this.B = B;
8153    this.A = A;
8154};
8155
8156accessibility.constant('accessibilityKeyCodes', {
8157   'ENTER'          : 13,
8158   'SPACE'          : 32,
8159   'LEFT_ARROW'     : 37,
8160   'UP_ARROW'       : 38,
8161   'RIGHT_ARROW'    : 39,
8162   'DOWN_ARROW'     : 40
8163});
8164accessibility.constant('baseFocusableElements', [
8165    'button',
8166    'a',
8167    '.focusable-element',
8168    'select',
8169    'input',
8170    'textarea'
8171]);
8172accessibility.constant('baseFocusableExceptions', [
8173   'a.skiptocontent'
8174]);
8175accessibility.constant('focusVariables', {
8176    transparencyThreshold: 0.1,                                     //anything <= this consider to be transparent
8177    opacityThreshold: 0.9,                                          //anything >= this consider to be opaque
8178    blendLevels: 2,                                                 //how deep should recursion go to blend with parent(s)
8179    defaultDarkFocus: (new WPDColorObject(48, 124, 127, 1)),        // #307c7f green
8180    defaultLightFocus: (new WPDColorObject(168, 229, 240, 1)),      // #a8e5f0 cold-blue
8181    transparentObj: {effectiveColor: (new WPDColorObject(255, 255, 255, 0))},
8182    whiteObj: {effectiveColor: (new WPDColorObject(255, 255, 255, 1))},
8183    minimumContrastReqLarge: 3,                                     // https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
8184    minimumContrastReqSmall: 4.5,
8185    textBoldSize: 18.5,
8186    textLargeSize: 24,
8187    rgbaRegex: (new SelfResetRegex(/^rgba?\(([0-9]{1,3})\s?,\s?([0-9]{1,3})\s?,\s?([0-9]{1,3})\s?(,\s?([0-9]{1,3}\.?[0-9]*))?\)$/gi)),
8188    hexaRegex: (new SelfResetRegex(/^#?([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})([0-9A-Fa-f]{2})?$/gi))         // this one assumes that it won't see an invalid hex value with the format RGBaa (using hexa means you need RRGGBBaa)
8189
8190});
8191
8192/**
8193 * Inject accessibilitySettingsProvider into your config() function to make setting changes.
8194 *
8195 * NOTE: If your focusColors in your CSS differ from the default ones above, you need to set them to match your CSS values,
8196 *  otherwise this script will be comparing luminance for the wrong colors!
8197 */
8198accessibility.provider('accessibilitySettings', ['focusVariables', function(focusVariables) {
8199        var self = this;
8200
8201        self.focusableElements = [];
8202        self.focusableExceptions = [];
8203        self.darkFocus = focusVariables.defaultDarkFocus;
8204        self.lightFocus = focusVariables.defaultLightFocus;
8205
8206        /**
8207         *
8208         * @param {String[] | String} what
8209         * @returns {undefined}
8210         */
8211        self.addFocusableElement = function(what) {
8212            if (typeof what === 'string') {
8213                this.focusableElements.push(what);
8214            }
8215            else if (Array.isArray(what)) {
8216                for(var i = 0; i < what.length; i++) {
8217                    if (typeof what[i] === 'string') {
8218                        this.focusableElements.push(what[i]);
8219                    }
8220                }
8221            }
8222        };
8223
8224        /**
8225         *
8226         * @param {String[] | String} what
8227         * @returns {undefined}
8228         */
8229        self.addFocusableException = function(what) {
8230            if (typeof what === 'string') {
8231                this.focusableExceptions.push(what);
8232            }
8233            else if (Array.isArray(what)) {
8234                for(var i = 0; i < what.length; i++) {
8235                    if (typeof what[i] === 'string') {
8236                        this.focusableExceptions.push(what[i]);
8237                    }
8238                }
8239            }
8240        };
8241
8242        /**
8243         *
8244         * @param {Number} r
8245         * @param {Number} g
8246         * @param {Number} b
8247         * @param {Number} a
8248         * @returns {undefined}
8249         */
8250        self.setLightFocus = function(r,g,b,a) {
8251            this.lightFocus = new WPDColorObject(r,g,b,a);
8252        };
8253
8254        /**
8255         *
8256         * @param {Number} r
8257         * @param {Number} g
8258         * @param {Number} b
8259         * @param {Number} a
8260         * @returns {undefined}
8261         */
8262        self.setDarkFocus = function(r,g,b,a) {
8263            this.darkFocus = new WPDColorObject(r,g,b,a);
8264        };
8265
8266        // Service object (not available for use in config() functions)
8267        this.$get = [
8268            'baseFocusableElements',
8269            'baseFocusableExceptions',
8270            function(baseFocusableElements, baseFocusableExceptions) {
8271                return {
8272                    getFocusableElements: function() {
8273                        return baseFocusableElements.concat(self.focusableElements);
8274                    },
8275                    getFocusableExceptions: function() {
8276                        return baseFocusableExceptions.concat(self.focusableExceptions);
8277                    },
8278                    getLightFocusColor: function() {
8279                        return self.lightFocus;
8280                    },
8281                    getDarkFocusColor: function() {
8282                        return self.darkFocus;
8283                    }
8284                };
8285            }
8286        ];
8287    }]
8288);
8289
8290// [WSME-590]
8291/**
8292 * @author noahm
8293 */
8294accessibility.service('colorService', ['focusVariables', function(focusVariables){
8295    var service = {};
8296
8297    /**
8298     * Take a hex or 0-255 and determine its worth in a 0-1 range
8299     * @param {String | Number} value
8300     * @returns {null | Number}
8301     */
8302    service.normaliseValue = function(value) {
8303        if (value !== null && value !== undefined) {
8304            var isHex = /^[0-9a-fA-f]+$/gi;
8305            if (isHex.test(value)) {
8306                value = parseInt('0x'+value);
8307            }
8308            if (!isNaN(parseFloat(value))) {
8309                var max = 255;
8310                var min = 0;
8311                var value = parseFloat(value);
8312
8313                return (value - min) / (max - min);
8314            }
8315        }
8316        return null;
8317    };
8318
8319    /**
8320     *
8321     * @param {Number} alpha
8322     * @param {Number} col1
8323     * @param {Number} col2
8324     * @returns {Number}
8325     */
8326    service.blendColorValue = function(alpha, col1, col2) {
8327        alpha = parseFloat(alpha);
8328        col1 = parseFloat(col1);
8329        col2 = parseFloat(col2);
8330        return (alpha * col1) + ((1-alpha) * col2);
8331    };
8332
8333    /**
8334     *
8335     * @param {WPDColorObject} back
8336     * @param {WPDColorObject} fore
8337     * @returns {WPDColorObject}
8338     */
8339    service.blendColorObj = function(back, fore) {
8340        return new WPDColorObject(
8341                service.blendColorValue(fore.A, fore.R, back.R),
8342                service.blendColorValue(fore.A, fore.G, back.G),
8343                service.blendColorValue(fore.A, fore.B, back.B),
8344                (parseFloat(fore.A) + (parseFloat(back.A) * (1-parseFloat(fore.A))))
8345            );
8346    };
8347
8348
8349    /**
8350     * This is not foolproof! It can't take into consideration background images, or the visual contents of img elements.
8351     *
8352     * Returns null if the element is deemed to be invisible/transparent according to display, visibility, opacity and bgColor alpha value (if below threshold)
8353     * Colours above opacityThreahold are returned as-is.
8354     * Colours deemed semi-transparent will attempt to blend with their parent's colours (up to blendLevel times) to get an approximate colour of what the user is actually seeing on screen.
8355     *      This action is recursive, and will terminate when the blendDepth is reached, or the document object has been reached.
8356     *
8357     *  The returned object's effectiveColour.A value is for the purpose of blending within this function.
8358     *      For all intents and purposes the alpha of the ultimately returned effective colour should always be considered 1.
8359     *
8360     * @param {jQuery} $elem
8361     * @param {Number} blendDepth
8362     * @returns {null | Object}
8363     */
8364    service.getEffectiveColor = function($elem, blendDepth) {
8365        //console.log($elem);
8366
8367        if ($elem[0] === document) return null;
8368
8369        if (blendDepth === undefined || blendDepth === null || blendDepth > focusVariables.blendLevels) {
8370            blendDepth = focusVariables.blendLevels;
8371        }
8372        //console.log('blendDepth: '+blendDepth);
8373
8374        var display = $elem.css('display');
8375        if (display === 'none') {
8376            return null;
8377        }
8378
8379        var visibility = $elem.css('visibility');
8380        if (visibility === 'hidden') {
8381            return null;
8382        }
8383
8384        // Technically I should be doing the blending thing with this value too, but considering this opacity change will propagate to any text inside the element,
8385        // I'm making the assumption that it isn't supposed to be focusable anyway.
8386        var opacity = $elem.css('opacity');
8387        if (opacity !== '' && opacity <= focusVariables.transparencyThreshold) {
8388            return null;
8389        }
8390
8391        var bgColor = $elem.css('background-color');
8392        //console.log(bgColor);
8393        if (bgColor === null || bgColor === undefined || bgColor === '' || bgColor === 'transparent') {
8394            return null;
8395        }
8396
8397        var rgbaMatches = focusVariables.rgbaRegex.exec(bgColor);
8398        var hexaMatches = focusVariables.hexaRegex.exec(bgColor);
8399
8400        var bgColorAlpha = 1;
8401        if (rgbaMatches && rgbaMatches.length > 1) {
8402            if (rgbaMatches[5] !== null && rgbaMatches[5] !== undefined) bgColorAlpha = rgbaMatches[5];     //apparently the rgba value .css() returns normalises the alpha value by itself.
8403        }
8404        else if (hexaMatches && hexaMatches.length > 1) {
8405            var x = service.normaliseValue(hexaMatches[2]);
8406            if (x !== null) bgColorAlpha = x;
8407        }
8408
8409        if (bgColorAlpha !== null && bgColorAlpha !== undefined && bgColorAlpha !== '') {
8410            if (bgColorAlpha <= focusVariables.transparencyThreshold) {
8411                return null;
8412            }
8413        }
8414
8415        var trueColorObj = null;
8416        if (rgbaMatches && rgbaMatches.length > 1) {
8417            trueColorObj = new WPDColorObject(
8418                    (rgbaMatches[1] || 0),
8419                    (rgbaMatches[2] || 0),
8420                    (rgbaMatches[3] || 0),
8421                    bgColorAlpha
8422                );
8423        }
8424        else if (hexaMatches && hexaMatches.length > 1) {
8425            if (hexaMatches[1] !== null && hexaMatches[1] !== undefined) {
8426                if (hexaMatches[1].length === 3) {
8427                    trueColorObj = new WPDColorObject(
8428                        (parseInt('0x' + hexaMatches[1][0] + hexaMatches[1][0]) || 0),
8429                        (parseInt('0x' + hexaMatches[1][1] + hexaMatches[1][1]) || 0),
8430                        (parseInt('0x' + hexaMatches[1][2] + hexaMatches[1][2]) || 0),
8431                        bgColorAlpha
8432                    );
8433                }
8434                else if (hexaMatches[1].length === 6) {
8435                    trueColorObj = new WPDColorObject(
8436                        (parseInt('0x' + hexaMatches[1][0] + hexaMatches[1][1]) || 0),
8437                        (parseInt('0x' + hexaMatches[1][2] + hexaMatches[1][3]) || 0),
8438                        (parseInt('0x' + hexaMatches[1][4] + hexaMatches[1][5]) || 0),
8439                        bgColorAlpha
8440                    );
8441                }
8442            }
8443        }
8444
8445        if (trueColorObj === null) return null;
8446
8447        var effectiveColor = trueColorObj;
8448
8449        //start blending with parent bg Colors if this isn't deemed to be opaque enough.
8450        if (bgColorAlpha < focusVariables.opacityThreshold && $elem.parent()[0] !== document && blendDepth > 0) {
8451            //console.log('blend with parent');
8452            var $parent = $elem.parent();
8453            //console.log($parent);
8454            var parentCol;
8455            //attempt to skip over transparent parents.
8456            while (true) {
8457                if ($parent[0] === document) {
8458                    parentCol = focusVariables.whiteObj;
8459                    break;
8460                }
8461                parentCol = service.getEffectiveColor($parent, blendDepth - 1); //recurse
8462                if (parentCol) break;
8463                //console.log('parent was transparent, try again for depth ' + blendDepth );
8464                $parent = $parent.parent();
8465                //console.log($parent);
8466            }
8467
8468            parentCol = parentCol || focusVariables.transparentObj;
8469            if (parentCol) {
8470                effectiveColor = service.blendColorObj(parentCol.effectiveColor, trueColorObj);
8471            }
8472        }
8473
8474       // console.log('returned effectiveColor for depth '+blendDepth);
8475        //console.log(effectiveColor);
8476
8477        return {
8478            trueColor: bgColor,
8479            trueColorObj: trueColorObj,
8480            effectiveColor: effectiveColor
8481        };
8482    };
8483
8484    /**
8485     * Traverse up parents and return the first *effectively determined* background-colour that isn't transparent.
8486     * If all parents are transparent, you'll eventually hit the document object, which will return white as its bgColor.
8487     *
8488     * Again, not foolproof! This assumes that the elements have static positioning and are visually nested in their containers.
8489     *
8490     * @param {jQuery} $elem
8491     * @param {boolean} includingElem
8492     * @returns {WPDColorObj}
8493     */
8494    service.determineBgColor = function($elem, includingElem) {
8495        if (includingElem) {
8496            var elemCol = service.getEffectiveColor($elem);
8497            if (elemCol) {
8498                return elemCol;
8499            }
8500        }
8501        var $parent = $elem.parent();
8502        while (true) {
8503            if ($parent[0] === document) return focusVariables.whiteObj;
8504            var parentCol = service.getEffectiveColor($parent);
8505            if (parentCol) {
8506                return parentCol;
8507            }
8508            $parent = $parent.parent();
8509        }
8510    };
8511
8512
8513    /**
8514     * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
8515     *
8516     * Calculate the luminance value of an rgb colour, according to the definition in the link above.
8517     *
8518     * @param {WPDColorObj} colorObj
8519     * @returns {Number}
8520     */
8521    service.determineLuminance = function(colorObj) {
8522        var r, g, b;
8523        var srgbColObj = {
8524            R: colorObj.R / 255,
8525            G: colorObj.G / 255,
8526            B: colorObj.B / 255,
8527            calculate: function(what) {
8528                if (what <= 0.03928) {
8529                    return what / 12.92;
8530                }
8531                else {
8532                    return Math.pow(((what + 0.055) / 1.055), 2.4);
8533                }
8534            }
8535        };
8536
8537        r = srgbColObj.calculate(srgbColObj.R);
8538        g = srgbColObj.calculate(srgbColObj.G);
8539        b = srgbColObj.calculate(srgbColObj.B);
8540
8541        return (0.2126 * r) + (0.7152 * g) + (0.0722 * b);
8542    };
8543
8544    /**
8545     * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
8546     *
8547     * Calculate the contrast ratio between two luminance values, accordiong to the definition in the link above.
8548     *
8549     * @param {Number} luminance1
8550     * @param {Number} luminance2
8551     * @returns {Number}
8552     */
8553    service.contrastRatio = function(luminance1, luminance2) {
8554        var lighter = Math.max(luminance1, luminance2);
8555        var darker = Math.min(luminance1, luminance2);
8556        return (lighter + 0.05) / (darker + 0.05);
8557    };
8558
8559    return service;
8560}]);
8561
8562accessibility.run([
8563    'colorService',
8564    'accessibilitySettings',
8565    'focusVariables',
8566    'DOMObserver',
8567    '$timeout',
8568    function(colorService, accessibilitySettings, focusVariables, DOMObserver, $timeout) {
8569        var lightFocusLum = colorService.determineLuminance(accessibilitySettings.getLightFocusColor());
8570        var darkFocusLum = colorService.determineLuminance(accessibilitySettings.getDarkFocusColor());
8571        var whiteLum = 1;
8572        var blackLum = 0;
8573
8574         /**
8575         *
8576         * @param {jQuery} $elements
8577         * @returns {undefined}
8578         */
8579        var setupEventListeners = function($elements) {
8580            $elements.each(function(idx, $elem) {
8581                $elem = angular.element($elem);
8582                $elem.on('focus', function(){
8583                    var backgroundColor = colorService.determineBgColor($elem);
8584                    //console.log(backgroundColor);
8585                    var luminance = colorService.determineLuminance(backgroundColor.effectiveColor);
8586
8587                    var contrastWithLight = colorService.contrastRatio(luminance, lightFocusLum);
8588                    var contrastWithDark = colorService.contrastRatio(luminance, darkFocusLum);
8589
8590                    //console.log(contrastWithLight);
8591                    //console.log(contrastWithDark);
8592
8593                    // https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
8594                    var finalContrastReq = (parseFloat($elem.css('font-size')) >= focusVariables.textLargeSize ||
8595                                                ((parseFloat($elem.css('font-size')) >= focusVariables.textBoldSize) &&
8596                                                    ($elem.css('font-weight') === 'bold' ||
8597                                                    $elem.css('font-weight') === 'bolder' ||
8598                                                    $elem.css('font-weight') === '700' ||
8599                                                    $elem.css('font-weight') === '800' ||
8600                                                    $elem.css('font-weight') === '900')
8601                                                )
8602                                            ) ? focusVariables.minimumContrastReqLarge : focusVariables.minimumContrastReqSmall;
8603                    var finalClass = 'focusable-element-';
8604
8605                   // console.log(finalContrastReq);
8606
8607                    //light is passes, dark doesn't
8608                    if (contrastWithLight >= finalContrastReq && contrastWithDark < finalContrastReq) finalClass += 'light';
8609                    //dark passes, light doesn't
8610                    else if (contrastWithDark >= finalContrastReq && contrastWithLight < finalContrastReq) finalClass += 'dark';
8611                    //both pass, so pick the better of the two
8612                    else if (contrastWithLight >= finalContrastReq && contrastWithDark >= finalContrastReq) {
8613                        finalClass += (contrastWithLight >= contrastWithDark ? 'light' : 'dark');
8614                    }
8615                    //neither custom colours cut it so go straight to either black or white outline.
8616                    else {
8617                        contrastWithLight = colorService.contrastRatio(luminance, whiteLum);
8618                        contrastWithDark = colorService.contrastRatio(luminance, blackLum);
8619
8620                        finalClass += (contrastWithLight >= contrastWithDark ? 'white' : 'black');
8621                    }
8622                    $elem.removeClass('focusable-element-light', 'focusable-element-dark', 'focusable-element-white', 'focusable-element-black');
8623                    $elem.addClass(finalClass);
8624                });
8625                $elem.on('blur', function() {
8626                    $elem.removeClass('focusable-element-light', 'focusable-element-dark', 'focusable-element-white', 'focusable-element-black');
8627                });
8628            });
8629        };
8630
8631        $timeout(function() {
8632            //console.log('accessibility run!');
8633            // it might have been nicer to watch document.activeElement, but apparently this doesn't get assigned for anything other than text type inputs for iOS.
8634            var $allFocusableElements = angular.element(document).find(accessibilitySettings.getFocusableElements().join(', ')).not(accessibilitySettings.getFocusableExceptions().join(', '));
8635            //console.log($allFocusableElements);
8636            setupEventListeners($allFocusableElements);
8637
8638            //setup something to add more listeners for focusable elements added to the DOM later on.
8639            DOMObserver.addObserver(document, function(mutations) {
8640                for(var i = 0; i < mutations.length; i ++) {
8641                    if (mutations[i].addedNodes.length) {
8642                        var $newFocusables = angular.element(Array.prototype.slice.call(mutations[i].addedNodes).filter(function(element) {
8643                            return element.nodeType === 1;
8644                        })).find(accessibilitySettings.getFocusableElements().join(', ')).not(accessibilitySettings.getFocusableExceptions().join(', '));
8645                        //console.log($newFocusables);
8646                        setupEventListeners($newFocusables);
8647                    }
8648                }
8649            });
8650        });
8651    }
8652]);
8653
8654
8655//https://stackoverflow.com/questions/3219758/detect-changes-in-the-dom
8656accessibility.service('DOMObserver', ['$timeout', function($timeout) {
8657    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
8658
8659    this.addObserver = function(obj, callback) {
8660        if( !obj || (obj.nodeType !== 1 && obj.nodeType !== 9)) return null;    //allow Elements and Document
8661
8662        if (MutationObserver) {
8663            var observer = new MutationObserver(function(mutations, observer){
8664                $timeout(function() {
8665                    callback(mutations);
8666                });
8667            });
8668            // have the observer observe obj for changes in children
8669            observer.observe( obj, { childList:true, subtree:true });
8670        }
8671        else if(window.addEventListener){
8672            //console.log('window.addeventListener');
8673            var f = function(mutations){
8674                $timeout(function() {
8675                    callback(mutations);
8676                });
8677            };
8678            obj.addEventListener('DOMNodeInserted', f, false);
8679            obj.addEventListener('DOMNodeRemoved', f, false);
8680        }
8681    };
8682}]);
8683
8684
8685accessibility.controller('MaxLengthNotice', [
8686    '$scope',
8687    '$element',
8688    '$timeout',
8689    function($scope, $element, $timeout) {
8690        var mln = this;
8691        var timeoutQueued = false;
8692        var timeoutValue = 1000;
8693
8694        mln.setAriaLive = function(remainingCount, $message) {
8695            if (remainingCount > mln.politeThreshold) {
8696                $message.attr('aria-live', 'off');
8697
8698                if (timeoutValue !== null && timeoutValue !== undefined) {
8699                    timeoutValue = 1000;
8700                }
8701            }
8702            else {
8703                if (remainingCount > mln.assertiveThreshold) {
8704                    $message.attr('aria-live', 'polite');
8705
8706                    if (timeoutValue !== null && timeoutValue !== undefined) {
8707
8708                        if (remainingCount > mln.assertiveThreshold + 50) {
8709                            timeoutValue = 500;
8710                        }
8711                        else {
8712                            timeoutValue = 200;
8713                        }
8714                    }
8715                }
8716                else {
8717                    $message.attr('aria-live', 'assertive');
8718
8719                    if (timeoutValue !== null && timeoutValue !== undefined) {
8720                        timeoutValue = 0;
8721                    }
8722                }
8723            }
8724        };
8725
8726        mln.init = function() {
8727            // we have to wait until our template is rendered (how $timeout works here I have no clue)
8728            $timeout(function() {
8729                var $message = $element.find('[data-mln-message]');
8730                var $messageTagon = $('<span data-mln-refresh-message></span>');
8731                $message.append($messageTagon);
8732                var $textField = $element.find('[data-mln-field]');
8733                var maxCharacters = $textField.attr('maxlength');
8734                var toggleTagon = false;
8735
8736                if (mln.makeAtomic) {
8737                    $message.attr('aria-atomic','true');
8738                }
8739
8740                //initally get textfield char limit and set aria-live
8741                mln.setAriaLive(maxCharacters - $textField.val().length, $message);
8742
8743                var remainingCount = maxCharacters - $textField.val().length;
8744
8745                $textField.on('keyup', function() {
8746                    //don't spam evaluating this on every input event, so use timeouts
8747                    // let us know if we already have one queued so we don't end up stacking them.
8748                    if (!timeoutQueued) {
8749                        $timeout(function() {
8750                            remainingCount = maxCharacters - $textField.val().length;
8751
8752                            mln.setAriaLive(remainingCount, $message);
8753
8754                            // reiterate message if they keep trying to type once limit is reached
8755                            if (remainingCount <= 0) {
8756                                toggleTagon = !toggleTagon;
8757                                $messageTagon.html(toggleTagon ? '': '&nbsp;');      // This is enough to get the screenreader to repeat the outer message.
8758                            }                                                   // if you're wondering why we can't just tale and repaste the .text()
8759                                                                                // of the $message, it's because we'd lose the angular expression generating it.
8760
8761
8762                            timeoutQueued = false;
8763                        }, timeoutValue);
8764
8765                        timeoutQueued = true;
8766                    }
8767                });
8768            });
8769        };
8770    }
8771]);
8772
8773accessibility.directive('maxLengthNotice', [
8774    function() {
8775        return {
8776            scope: {
8777                makeAtomic: '<',
8778                politeThreshold: '<',
8779                assertiveThreshold: '<'
8780            },
8781            transclude: true,
8782            bindToController: true,
8783            controllerAs: 'mln',
8784            controller: 'MaxLengthNotice',
8785            template: '<div ng-init="mln.init()"><ng-transclude></ng-transclude></div>'
8786        };
8787    }
8788]);
8789
8790;
8791/** include: angular.autocomplete.js **/
8792window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.autocomplete.js');
8793var autocomplete = angular.module('autocomplete', []);
8794        
8795/**
8796 * Service for search AutoComplete calls
8797 */
8798autocomplete.service('AutoComplete', ['$loading', '$requester', '$q', function($loading, $requester, $q) {
8799    // private vars
8800    var _json = $requester({
8801        'autoComplete': 'SearchFront.autoComplete',
8802        'promotedResults': 'SearchFront.promotedSearchResultsAutoComplete'
8803    }),
8804    _loader = {load: function () { }, clear: function () { }},
8805    _searchTerm = null,
8806    _successCb = [],
8807    _errorCb = angular.noop,
8808    _timeout = null;
8809
8810    _json.setLoader(_loader);
8811    
8812    
8813    var callRefresh = function() {
8814        var deferred = $q.defer();
8815        
8816        _json.autoComplete(
8817            {
8818                searchTerm: _searchTerm,
8819            }, function(response) {
8820                deferred.resolve({data: response});
8821            }.bind(this),
8822            deferred.reject
8823        );
8824
8825        return deferred.promise;
8826    };
8827    
8828    /**
8829     * Promoted serach results
8830     */
8831    var promotedResultsSearch = function() {
8832        var deferred = $q.defer();
8833        
8834        _json.promotedResults(
8835            {
8836                searchTerm: _searchTerm
8837            },function(response) {
8838                deferred.resolve({success: true, data: response});
8839            }.bind(this),
8840            deferred.reject
8841        );
8842
8843        return deferred.promise;
8844    };
8845    
8846    this.refresh = function() {
8847        $q.all({searchData: callRefresh(),
8848                promotedData: promotedResultsSearch()})
8849            .then(beforeSuccess)
8850            .catch(_errorCb);
8851    };
8852    
8853    /**
8854     * Function to set the current search term
8855     * @param {String} searchTerm search term to try and auto complete
8856     */
8857    this.setSearchTerm = function(searchTerm) {
8858        _searchTerm = searchTerm;
8859
8860        //clear timeout if exists
8861        if(_timeout !== null) clearTimeout(_timeout);
8862        //set timeout before handling - auto complete.
8863        _timeout = setTimeout(this.refresh, 250);
8864    }
8865
8866    /**
8867     * Sets the success callback for auto complete
8868     * @param {Function} successCallback the success callback
8869     * @returns {AutoComplete} this
8870     */
8871    this.onSuccess = function(successCallback) {
8872        _successCb.push(successCallback);
8873        return this;
8874    };
8875
8876    /**
8877     * Sets the error callback for auto complete
8878     * @param {Function} errorCallback the error callback
8879     * @returns {AutoComplete} this
8880     */
8881    this.onFailure = function(errorCallback) {
8882        _errorCb = errorCallback;
8883        return this;
8884    };
8885
8886    /**
8887     * Trusts the hit description as html before
8888     * sending the response to the controller
8889     * 
8890     * @param {searchResult} searchResult the response
8891     */
8892    var beforeSuccess = function(searchResult) {
8893        var result = {};
8894        
8895        if (searchResult && searchResult.promotedData.data.promotedResults) {
8896            var promotedResults = JSON.parse(searchResult.promotedData.data.promotedResults);
8897            var uniquePromotedResults = [];
8898            
8899            if (Array.isArray(promotedResults) && Array.isArray(searchResult.searchData.data.options)) {
8900                
8901                // Ensure no duplicates in promoted result suggestions.
8902                promotedResults.forEach(function(promotedResult) {
8903                    if (!doesArrayContainPromotedResult(uniquePromotedResults, promotedResult)) {
8904                        uniquePromotedResults.push(promotedResult);
8905                    }
8906                });
8907                
8908                uniquePromotedResults.forEach(function(pr) {
8909                    pr.text = pr.title;
8910                });
8911                result.promotedData = uniquePromotedResults;
8912            }
8913        }
8914        
8915        if (searchResult && searchResult.searchData.data.options) {
8916            result.searchData = searchResult.searchData.data.options;
8917        }
8918        
8919        _successCb.forEach(function(fn) {
8920            fn(result);
8921        });
8922    };
8923    
8924    
8925    /**
8926     * Check whether or not an item already exists within an array.
8927     * 
8928     * @param {array} uniqueArray an array of unique values.
8929     * @param {object} item the item that might already exist in the unique array.
8930     * @returns {Boolean} whether or not an item already exists in the unique array.
8931     */
8932    function doesArrayContainPromotedResult(uniqueArray, item) {
8933        return (uniqueArray.filter(function(uniqueItem) { return uniqueItem.id === item.id && uniqueItem.className === item.className; } )).length > 0;
8934    }
8935}]);
8936;
8937/** include: angular.find-my-supplier.js **/
8938window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.find-my-supplier.js');
8939var findMySupplier = angular.module('find-my-supplier', []);
8940
8941findMySupplier.directive('findMySupplierLink', ['$timeout','vexAccessibility',function ($timeout,vexAccessibility) {
8942    return {
8943        restrict: 'EC',
8944        transclude: true,
8945        scope: {
8946            cssClasses: '@'
8947        },
8948        template: '<button data-ng-click="openDialog()" tabindex="0" aria-pressed="false" class="find-my-supplier-btn" data-ng-class="cssClasses">' +
8949                        '<ng-transclude>' +
8950                            '<span>Find out more</span>' +
8951                        '</ng-transclude>' +
8952                    '</button>',
8953        controller: ['$scope', function($scope) {
8954            var template = '<iframe src="//supplier.westernpower.co.uk/" width="820" height="600" frameBorder="0"></iframe>';
8955            $scope.openDialog = function() {
8956                vex.dialog.open({
8957                    input: template,
8958                    contentClassName: 'find-my-supplier-dialog',
8959                    showCloseButton: true,
8960                    buttons: [
8961                        $.extend({}, vex.dialog.buttons.NO, {text: 'Close'})
8962                    ],
8963                    afterOpen: function () {
8964                        var elementsToHide = ['header', 'footer', 'section', '.breadcrumb-container', '.zopim', '#maincontent','.header-hero-panel-container'];
8965                        var elementsToIndex = [];
8966                        vexAccessibility.openVex(elementsToHide, elementsToIndex, '');
8967                    },
8968                    afterClose: function (){
8969                        var elementsToAdd = ['header', 'footer', 'section', '.breadcrumb-container', '.zopim', '#maincontent','.header-hero-panel-container'];
8970                        vexAccessibility.closeVex(elementsToAdd, '.find-my-supplier-btn');
8971                    }
8972                });
8973                angular.element('.find-my-supplier-dialog').css('min-width', '860px');
8974            };
8975        }]
8976    };
8977}]);
8978;
8979/** include: angular.cookie-notice.js **/
8980window.core&&core.registerScript&&core.registerScript('/public/javascripts/angular.cookie-notice.js');
8981/**
8982 * AngularJS Cookie notice module
8983 * @author keiranc
8984 */
8985var cookieNotice = angular.module('cookie-notice', ['ngCookies']);
8986
8987cookieNotice.service('CookieNotice', ['$cookies', '$window', '$timeout', '$requester', function($cookies, $window, $timeout, $requester) {
8988        
8989        var $json = $requester({'set': 'CoreCookieFront.set_JSON'}),
8990            loader = {
8991                    load: function () {},
8992                    clear: function () {}
8993                };
8994              
8995        $json.setLoader(loader);
8996        
8997        
8998
8999        /**
9000         * Gets the current cookie level cookie
9001         * @param {String} cookieName the name of the cookie
9002         * @returns {Object} the current cookie
9003         */
9004        this.getCurrentCookie = function(cookieName) {
9005            return $cookies.getObject(cookieName);
9006        };
9007
9008        /**
9009         * Gets the allowed cookie level
9010         * @param {String} cookieName the name of the cookie
9011         * @returns {Number} the current cookie level
9012         */
9013        this.getAllowedCookieLevel = function(cookieName) {
9014            var currentCookie = this.getCurrentCookie(cookieName);
9015            if (currentCookie === null || !angular.isDefined(currentCookie)) return 0;
9016            return parseInt(currentCookie);
9017        };
9018        
9019        
9020        
9021        /**
9022         * Method to get the acceted hash
9023         * 
9024         * @param {String} cookieName
9025         * @returns {String} The hash value that the user accepted
9026         */
9027        this.getAcceptedHash = function(cookieName) {
9028            return $cookies.getObject(cookieName+"_hash");
9029        }
9030        
9031        /**
9032         * A function to test the accpeted hash matches what we now exppect
9033         * 
9034         * @param {String} expectedHash
9035         * @returns {Boolean} True if the accpeted hash matches our stored hash
9036         */
9037        this.testAcceptedHash = function(expectedHash) {
9038            var acceptedHash = this.getAcceptedHash();
9039            
9040            var allow = acceptedHash === expectedHash;
9041            
9042            //Clear cookies if hash does not match
9043            if(!allow) {
9044                this.reject();
9045            }
9046            
9047            return allow;
9048        }
9049
9050        /**
9051         * Checks whether a certain level is allowed
9052         * @param {String} cookieName the name of the cookie
9053         * @param {Number} level the level to match
9054         * @returns {Boolean} whether that level is allowed
9055         */
9056        this.isAllowedAt = function(cookieName, level) {
9057            var cookie = this.getCurrentCookie(cookieName);
9058            return (parseInt(cookie) & level) > 0;
9059        };
9060        
9061        /**
9062         * A little cross-browser gimmickary to create events under IE 11
9063         * 
9064         * @param {type} type
9065         * @returns {angular.cookie-noticeL#7.createEvent.event}
9066         */
9067        function createEvent(type) {
9068            var event;
9069            
9070            if (typeof(Event) === 'function') {
9071                event = new Event('submit');
9072            } else{
9073                event = document.createEvent('Event');
9074                event.initEvent('submit', true, true);
9075            }            
9076            
9077            return event
9078        }
9079        /**
9080         * Accepts a certain level of cookies
9081         * @param {String} cookieName the name of the cookie
9082         * @param {Number} level level to accept
9083         * @param {String} domain the domain to set the cookie for
9084         */
9085        this.accept = function(cookieName, level, domain, hash) {
9086            var expires = new Date();
9087            expires = new Date(expires.getFullYear()+1, expires.getMonth(), expires.getDate());
9088            
9089            $cookies.putObject(cookieName, level, {
9090                domain: domain,
9091                expires: expires,
9092                path: "/"
9093            });
9094            $cookies.putObject(cookieName+"_hash", hash, {
9095                domain: domain,
9096                expires: expires,
9097                path: "/"
9098            });
9099            
9100            $json.set({ level: level, hash: hash } )
9101            
9102            $timeout(function() {
9103                $window.dispatchEvent(createEvent('resize'));
9104            }, 500);
9105        };
9106        
9107        /**
9108         * Function to reject if the hash no longer matches
9109         * @param {String} cookieName the name of the cookie
9110         * @param {String} domain the domain to set the cookie for
9111         */
9112        this.reject = function(cookieName, domain) {
9113            $cookies.remove(cookieName, {
9114                domain: domain,
9115                path: "/"
9116            });
9117            
9118            $cookies.remove(cookieName+"_hash", {
9119                domain: domain,
9120                path: "/"
9121            });
9122        } 
9123        
9124        /**
9125         * This is used to generate a hash of the current cookie levels, so if these change consent can be re-requested
9126         * @param {Map} cookieLevels All levels plus bit wise value
9127         * @returns {String} Hash of the cookie levels and their consent level
9128         */
9129        this.generateHash = function(cookieLevels) {
9130            var cookie = "";
9131            
9132            for (key in cookieLevels) {
9133                cookie += key + "-" + cookieLevels[key] + ";";
9134            }
9135            
9136            return cookie.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
9137        }
9138
9139}]);
9140
9141cookieNotice.controller('CookieNoticeController', ['CookieNotice', function(CookieNotice) {
9142        var vm = this;
9143        
9144        vm.visible = false;
9145
9146        /**
9147         * Function to load script into page
9148         * @param {String} src The URL or body of the script to add to the site
9149         */
9150        var addScript = function(src) {
9151            var nonce = document.querySelector('script[nonce]');
9152            
9153            if(nonce != null) {
9154                nonce = nonce.getAttribute('nonce');
9155            }
9156
9157            var script = document.createElement('script');
9158            script.type = 'text/javascript';
9159            script.setAttribute("nonce", nonce);
9160            
9161            //test if script is a URL
9162            if(!src.indexOf("/") || !src.indexOf("http")) {
9163                script.src = src;
9164            } else {
9165                script.appendChild(document.createTextNode(src));
9166            }
9167            
9168            document.getElementsByTagName('head')[0].appendChild(script);
9169        }
9170
9171        /**
9172         * Called by angular button click
9173         * uses CookieNotice service to accept cookie notice
9174         */
9175        vm.accept = function() {
9176            
9177            vm.visible = false;
9178            vm.level = 0;
9179            
9180            //calculate level
9181            for(key in vm.ticked) {
9182                if(vm.ticked[key]) {
9183                    vm.level += vm.cookieLevels[key];
9184
9185                    //load scripts
9186                    var scripts = vm.cookieScripts[key];
9187                    for(script in scripts) {
9188                        addScript(scripts[script]);
9189                    }
9190                    
9191                    //fire GTM events
9192                    if(typeof dataLayer !== 'undefined') {
9193                        dataLayer.push({'event': 'cookie_accepted_'+key});
9194                    }
9195                }
9196            }
9197            //store value for subsequent pages
9198            CookieNotice.accept(vm.key, vm.level, vm.domain, CookieNotice.generateHash(vm.cookieLevels));
9199
9200        };
9201        
9202        vm.checkAll = function() { 
9203          for(key in vm.ticked) {
9204              
9205              vm.ticked[key] = true;
9206          }
9207          
9208          this.accept();
9209          
9210        };
9211
9212        // accept default level
9213        vm.init = function() {
9214            vm.cookieLevels = angular.fromJson(window.core.cookieLevels);
9215            vm.cookieScripts = angular.fromJson(window.core.cookieScripts);
9216            vm.ticked = {};
9217
9218            //make sure domain does not have a leading or trailing space
9219            vm.domain = vm.domain.trim();
9220            
9221            //If not in CMS and we haven't accepted the current cookie groups show preference
9222            if (!vm.cmsMode && (typeof CookieNotice.getAcceptedHash(vm.key) === 'undefined' || CookieNotice.testAcceptedHash(CookieNotice.generateHash(vm.cookieLevels)))) {
9223                for(key in vm.cookieLevels) {
9224                    
9225                    vm.ticked[key] = vm.allTicked == 'true';
9226                    console.log(vm.ticked[key], vm.allTicked);
9227                }
9228                vm.visible = true;
9229            } else {
9230                //Cookie preference has already been set
9231                for(key in vm.cookieLevels) {
9232                    if(CookieNotice.isAllowedAt(vm.key, vm.cookieLevels[key])) {
9233                        //load scripts
9234                        var scripts = vm.cookieScripts[key];
9235                        for(script in scripts) {
9236                            addScript(scripts[script]);
9237                        }
9238
9239                        //fire GTM events
9240                        if(typeof dataLayer !== 'undefined') {
9241                            dataLayer.push({'event': 'cookie_accepted_'+key});
9242                        }
9243                    }
9244                }
9245            }
9246        };
9247        
9248        window.core = window.core || {}
9249        
9250        /**
9251         * Method which returns if your allowed to set cookies for current level
9252         * 
9253         * @param {type} level The level to check i.e. "Tracking"
9254         * @returns {boolean} True if you can set the cookie
9255         */
9256        window.core.cookieAllowedForLevel = function(level) {
9257            return CookieNotice.isAllowedAt(vm.key, vm.cookieLevels[level]);
9258        }
9259}]);
9260
9261cookieNotice.directive('cookieNotice', [function() {
9262        return {
9263            scope: {
9264                domain: '@',
9265                key: '@',
9266                cmsMode: '=mode',
9267                allTicked: '@'
9268            },
9269            controllerAs: 'vm',
9270            bindToController: true,
9271            controller: 'CookieNoticeController',
9272            transclude: true,
9273            template:   '<div data-ng-show="vm.visible" data-ng-init="vm.init()">\
9274                            <ng-transclude></ng-transclude> \
9275                        </div>'
9276        };
9277}]);
9278
9279;

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.