PageSourceSearch

https://smractionplan.ca/libraries/wet-boew/js/wet-boew.min.js?tmb444

js smractionplan.ca collected 2026-10-04 00:01:07 UTC 627,680 bytes, 20,303 lines download raw bytes

vendor: 5,328 bytes, lines 1-153
1/*!
2 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
3 * wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4 * v4.0.88.1 - 2025-06-04
5 *
6 */
7
8/*! Modernizr (Custom Build) | MIT & BSD */
9/*! @license DOMPurify 3.2.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.6/LICENSE */
10
11(function (global, factory) {
12  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
13  typeof define === 'function' && define.amd ? define(factory) :
14  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DOMPurify = factory());
15})(this, (function () { 'use strict';
16
17  const {
18    entries,
19    setPrototypeOf,
20    isFrozen,
21    getPrototypeOf,
22    getOwnPropertyDescriptor
23  } = Object;
24  let {
25    freeze,
26    seal,
27    create
28  } = Object; // eslint-disable-line import/no-mutable-exports
29  let {
30    apply,
31    construct
32  } = typeof Reflect !== 'undefined' && Reflect;
33  if (!freeze) {
34    freeze = function freeze(x) {
35      return x;
36    };
37  }
38  if (!seal) {
39    seal = function seal(x) {
40      return x;
41    };
42  }
43  if (!apply) {
44    apply = function apply(fun, thisValue, args) {
45      return fun.apply(thisValue, args);
46    };
47  }
48  if (!construct) {
49    construct = function construct(Func, args) {
50      return new Func(...args);
51    };
52  }
53  const arrayForEach = unapply(Array.prototype.forEach);
54  const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
55  const arrayPop = unapply(Array.prototype.pop);
56  const arrayPush = unapply(Array.prototype.push);
57  const arraySplice = unapply(Array.prototype.splice);
58  const stringToLowerCase = unapply(String.prototype.toLowerCase);
59  const stringToString = unapply(String.prototype.toString);
60  const stringMatch = unapply(String.prototype.match);
61  const stringReplace = unapply(String.prototype.replace);
62  const stringIndexOf = unapply(String.prototype.indexOf);
63  const stringTrim = unapply(String.prototype.trim);
64  const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
65  const regExpTest = unapply(RegExp.prototype.test);
66  const typeErrorCreate = unconstruct(TypeError);
67  /**
68   * Creates a new function that calls the given function with a specified thisArg and arguments.
69   *
70   * @param func - The function to be wrapped and called.
71   * @returns A new function that calls the given function with a specified thisArg and arguments.
72   */
73  function unapply(func) {
74    return function (thisArg) {
75      if (thisArg instanceof RegExp) {
76        thisArg.lastIndex = 0;
77      }
78      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
79        args[_key - 1] = arguments[_key];
80      }
81      return apply(func, thisArg, args);
82    };
83  }
84  /**
85   * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
86   *
87   * @param func - The constructor function to be wrapped and called.
88   * @returns A new function that constructs an instance of the given constructor function with the provided arguments.
89   */
90  function unconstruct(func) {
91    return function () {
92      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
93        args[_key2] = arguments[_key2];
94      }
95      return construct(func, args);
96    };
97  }
98  /**
99   * Add properties to a lookup table
100   *
101   * @param set - The set to which elements will be added.
102   * @param array - The array containing elements to be added to the set.
103   * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
104   * @returns The modified set with added elements.
105   */
106  function addToSet(set, array) {
107    let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
108    if (setPrototypeOf) {
109      // Make 'in' and truthy checks like Boolean(set.constructor)
110      // independent of any properties defined on Object.prototype.
111      // Prevent prototype setters from intercepting set as a this value.
112      setPrototypeOf(set, null);
113    }
114    let l = array.length;
115    while (l--) {
116      let element = array[l];
117      if (typeof element === 'string') {
118        const lcElement = transformCaseFunc(element);
119        if (lcElement !== element) {
120          // Config presets (e.g. tags.js, attrs.js) are immutable.
121          if (!isFrozen(array)) {
122            array[l] = lcElement;
123          }
124          element = lcElement;
125        }
126      }
127      set[element] = true;
128    }
129    return set;
130  }
131  /**
132   * Clean up an array to harden against CSPP
133   *
134   * @param array - The array to be cleaned.
135   * @returns The cleaned version of the array
136   */
137  function cleanArray(array) {
138    for (let index = 0; index < array.length; index++) {
139      const isPropertyExist = objectHasOwnProperty(array, index);
140      if (!isPropertyExist) {
141        array[index] = null;
142      }
143    }
144    return array;
145  }
146  /**
147   * Shallow clone an object
148   *
149   * @param object - The object to be cloned.
150   * @returns A new object that copies the original.
151   */
152  function clone(object) {
153    const newObject = create(null);
vendor: 4,348 bytes, lines 154-205
154    for (const [property, value] of entries(object)) {
155      const isPropertyExist = objectHasOwnProperty(object, property);
156      if (isPropertyExist) {
157        if (Array.isArray(value)) {
158          newObject[property] = cleanArray(value);
159        } else if (value && typeof value === 'object' && value.constructor === Object) {
160          newObject[property] = clone(value);
161        } else {
162          newObject[property] = value;
163        }
164      }
165    }
166    return newObject;
167  }
168  /**
169   * This method automatically checks if the prop is function or getter and behaves accordingly.
170   *
171   * @param object - The object to look up the getter function in its prototype chain.
172   * @param prop - The property name for which to find the getter function.
173   * @returns The getter function found in the prototype chain or a fallback function.
174   */
175  function lookupGetter(object, prop) {
176    while (object !== null) {
177      const desc = getOwnPropertyDescriptor(object, prop);
178      if (desc) {
179        if (desc.get) {
180          return unapply(desc.get);
181        }
182        if (typeof desc.value === 'function') {
183          return unapply(desc.value);
184        }
185      }
186      object = getPrototypeOf(object);
187    }
188    function fallbackValue() {
189      return null;
190    }
191    return fallbackValue;
192  }
193
194  const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
195  const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
196  const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
197  // List of SVG elements that are disallowed by default.
198  // We still need to know them so that we can do namespace
199  // checks properly in case one wants to add them to
200  // allow-list.
201  const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
202  const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
203  // Similarly to SVG, we want to know all MathML elements,
204  // even those that we disallow by default.
205  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'n
205one']);
206  const text = freeze(['#text']);
207
208  const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
209  const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
210  const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
211  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
212
213  // eslint-disable-next-line unicorn/better-regex
214  const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
215  const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
216  const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
217  const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
218  const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
vendor: 7,928 bytes, lines 219-426
219  const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
220  );
221  const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
222  const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
223  );
224  const DOCTYPE_NAME = seal(/^html$/i);
225  const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
226
227  var EXPRESSIONS = /*#__PURE__*/Object.freeze({
228    __proto__: null,
229    ARIA_ATTR: ARIA_ATTR,
230    ATTR_WHITESPACE: ATTR_WHITESPACE,
231    CUSTOM_ELEMENT: CUSTOM_ELEMENT,
232    DATA_ATTR: DATA_ATTR,
233    DOCTYPE_NAME: DOCTYPE_NAME,
234    ERB_EXPR: ERB_EXPR,
235    IS_ALLOWED_URI: IS_ALLOWED_URI,
236    IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
237    MUSTACHE_EXPR: MUSTACHE_EXPR,
238    TMPLIT_EXPR: TMPLIT_EXPR
239  });
240
241  /* eslint-disable @typescript-eslint/indent */
242  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
243  const NODE_TYPE = {
244    element: 1,
245    attribute: 2,
246    text: 3,
247    cdataSection: 4,
248    entityReference: 5,
249    // Deprecated
250    entityNode: 6,
251    // Deprecated
252    progressingInstruction: 7,
253    comment: 8,
254    document: 9,
255    documentType: 10,
256    documentFragment: 11,
257    notation: 12 // Deprecated
258  };
259  const getGlobal = function getGlobal() {
260    return typeof window === 'undefined' ? null : window;
261  };
262  /**
263   * Creates a no-op policy for internal use only.
264   * Don't export this function outside this module!
265   * @param trustedTypes The policy factory.
266   * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
267   * @return The policy created (or null, if Trusted Types
268   * are not supported or creating the policy failed).
269   */
270  const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
271    if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
272      return null;
273    }
274    // Allow the callers to control the unique policy name
275    // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
276    // Policy creation with duplicate names throws in Trusted Types.
277    let suffix = null;
278    const ATTR_NAME = 'data-tt-policy-suffix';
279    if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
280      suffix = purifyHostElement.getAttribute(ATTR_NAME);
281    }
282    const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
283    try {
284      return trustedTypes.createPolicy(policyName, {
285        createHTML(html) {
286          return html;
287        },
288        createScriptURL(scriptUrl) {
289          return scriptUrl;
290        }
291      });
292    } catch (_) {
293      // Policy creation failed (most likely another DOMPurify script has
294      // already run). Skip creating the policy, as this will only cause errors
295      // if TT are enforced.
296      console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
297      return null;
298    }
299  };
300  const _createHooksMap = function _createHooksMap() {
301    return {
302      afterSanitizeAttributes: [],
303      afterSanitizeElements: [],
304      afterSanitizeShadowDOM: [],
305      beforeSanitizeAttributes: [],
306      beforeSanitizeElements: [],
307      beforeSanitizeShadowDOM: [],
308      uponSanitizeAttribute: [],
309      uponSanitizeElement: [],
310      uponSanitizeShadowNode: []
311    };
312  };
313  function createDOMPurify() {
314    let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
315    const DOMPurify = root => createDOMPurify(root);
316    DOMPurify.version = '3.2.6';
317    DOMPurify.removed = [];
318    if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
319      // Not running in a browser, provide a factory function
320      // so that you can pass your own Window
321      DOMPurify.isSupported = false;
322      return DOMPurify;
323    }
324    let {
325      document
326    } = window;
327    const originalDocument = document;
328    const currentScript = originalDocument.currentScript;
329    const {
330      DocumentFragment,
331      HTMLTemplateElement,
332      Node,
333      Element,
334      NodeFilter,
335      NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
336      HTMLFormElement,
337      DOMParser,
338      trustedTypes
339    } = window;
340    const ElementPrototype = Element.prototype;
341    const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
342    const remove = lookupGetter(ElementPrototype, 'remove');
343    const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
344    const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
345    const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
346    // As per issue #47, the web-components registry is inherited by a
347    // new document created via createHTMLDocument. As per the spec
348    // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
349    // a new empty registry is used when creating a template contents owner
350    // document, so we use that as our parent document to ensure nothing
351    // is inherited.
352    if (typeof HTMLTemplateElement === 'function') {
353      const template = document.createElement('template');
354      if (template.content && template.content.ownerDocument) {
355        document = template.content.ownerDocument;
356      }
357    }
358    let trustedTypesPolicy;
359    let emptyHTML = '';
360    const {
361      implementation,
362      createNodeIterator,
363      createDocumentFragment,
364      getElementsByTagName
365    } = document;
366    const {
367      importNode
368    } = originalDocument;
369    let hooks = _createHooksMap();
370    /**
371     * Expose whether this browser supports running the full DOMPurify.
372     */
373    DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
374    const {
375      MUSTACHE_EXPR,
376      ERB_EXPR,
377      TMPLIT_EXPR,
378      DATA_ATTR,
379      ARIA_ATTR,
380      IS_SCRIPT_OR_DATA,
381      ATTR_WHITESPACE,
382      CUSTOM_ELEMENT
383    } = EXPRESSIONS;
384    let {
385      IS_ALLOWED_URI: IS_ALLOWED_URI$1
386    } = EXPRESSIONS;
387    /**
388     * We consider the elements and attributes below to be safe. Ideally
389     * don't add any new ones but feel free to remove unwanted ones.
390     */
391    /* allowed element names */
392    let ALLOWED_TAGS = null;
393    const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
394    /* Allowed attribute names */
395    let ALLOWED_ATTR = null;
396    const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
397    /*
398     * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
399     * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
400     * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
401     * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
402     */
403    let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
404      tagNameCheck: {
405        writable: true,
406        configurable: false,
407        enumerable: true,
408        value: null
409      },
410      attributeNameCheck: {
411        writable: true,
412        configurable: false,
413        enumerable: true,
414        value: null
415      },
416      allowCustomizedBuiltInElements: {
417        writable: true,
418        configurable: false,
419        enumerable: true,
420        value: false
421      }
422    }));
423    /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
424    let FORBID_TAGS = null;
425    /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
426    let FORBID_ATTR = null
vendor: 9,205 bytes, lines 426-577
426;
427    /* Decide if ARIA attributes are okay */
428    let ALLOW_ARIA_ATTR = true;
429    /* Decide if custom data attributes are okay */
430    let ALLOW_DATA_ATTR = true;
431    /* Decide if unknown protocols are okay */
432    let ALLOW_UNKNOWN_PROTOCOLS = false;
433    /* Decide if self-closing tags in attributes are allowed.
434     * Usually removed due to a mXSS issue in jQuery 3.0 */
435    let ALLOW_SELF_CLOSE_IN_ATTR = true;
436    /* Output should be safe for common template engines.
437     * This means, DOMPurify removes data attributes, mustaches and ERB
438     */
439    let SAFE_FOR_TEMPLATES = false;
440    /* Output should be safe even for XML used within HTML and alike.
441     * This means, DOMPurify removes comments when containing risky content.
442     */
443    let SAFE_FOR_XML = true;
444    /* Decide if document with <html>... should be returned */
445    let WHOLE_DOCUMENT = false;
446    /* Track whether config is already set on this instance of DOMPurify. */
447    let SET_CONFIG = false;
448    /* Decide if all elements (e.g. style, script) must be children of
449     * document.body. By default, browsers might move them to document.head */
450    let FORCE_BODY = false;
451    /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
452     * string (or a TrustedHTML object if Trusted Types are supported).
453     * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
454     */
455    let RETURN_DOM = false;
456    /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
457     * string  (or a TrustedHTML object if Trusted Types are supported) */
458    let RETURN_DOM_FRAGMENT = false;
459    /* Try to return a Trusted Type object instead of a string, return a string in
460     * case Trusted Types are not supported  */
461    let RETURN_TRUSTED_TYPE = false;
462    /* Output should be free from DOM clobbering attacks?
463     * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
464     */
465    let SANITIZE_DOM = true;
466    /* Achieve full DOM Clobbering protection by isolating the namespace of named
467     * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
468     *
469     * HTML/DOM spec rules that enable DOM Clobbering:
470     *   - Named Access on Window (§7.3.3)
471     *   - DOM Tree Accessors (§3.1.5)
472     *   - Form Element Parent-Child Relations (§4.10.3)
473     *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)
474     *   - HTMLCollection (§4.2.10.2)
475     *
476     * Namespace isolation is implemented by prefixing `id` and `name` attributes
477     * with a constant string, i.e., `user-content-`
478     */
479    let SANITIZE_NAMED_PROPS = false;
480    const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
481    /* Keep element content when removing element? */
482    let KEEP_CONTENT = true;
483    /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
484     * of importing it into a new Document and returning a sanitized copy */
485    let IN_PLACE = false;
486    /* Allow usage of profiles like html, svg and mathMl */
487    let USE_PROFILES = {};
488    /* Tags to ignore content of when KEEP_CONTENT is true */
489    let FORBID_CONTENTS = null;
490    const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
491    /* Tags that are safe for data: URIs */
492    let DATA_URI_TAGS = null;
493    const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
494    /* Attributes safe for values like "javascript:" */
495    let URI_SAFE_ATTRIBUTES = null;
496    const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
497    const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
498    const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
499    const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
500    /* Document namespace */
501    let NAMESPACE = HTML_NAMESPACE;
502    let IS_EMPTY_INPUT = false;
503    /* Allowed XHTML+XML namespaces */
504    let ALLOWED_NAMESPACES = null;
505    const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
506    let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
507    let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
508    // Certain elements are allowed in both SVG and HTML
509    // namespace. We need to specify them explicitly
510    // so that they don't get erroneously deleted from
511    // HTML namespace.
512    const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
513    /* Parsing of strict XHTML documents */
514    let PARSER_MEDIA_TYPE = null;
515    const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
516    const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
517    let transformCaseFunc = null;
518    /* Keep a reference to config to pass to hooks */
519    let CONFIG = null;
520    /* Ideally, do not touch anything below this line */
521    /* ______________________________________________ */
522    const formElement = document.createElement('form');
523    const isRegexOrFunction = function isRegexOrFunction(testValue) {
524      return testValue instanceof RegExp || testValue instanceof Function;
525    };
526    /**
527     * _parseConfig
528     *
529     * @param cfg optional config literal
530     */
531    // eslint-disable-next-line complexity
532    const _parseConfig = function _parseConfig() {
533      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
534      if (CONFIG && CONFIG === cfg) {
535        return;
536      }
537      /* Shield configuration object from tampering */
538      if (!cfg || typeof cfg !== 'object') {
539        cfg = {};
540      }
541      /* Shield configuration object from prototype pollution */
542      cfg = clone(cfg);
543      PARSER_MEDIA_TYPE =
544      // eslint-disable-next-line unicorn/prefer-includes
545      SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
546      // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
547      transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
548      /* Set configuration parameters */
549      ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
550      ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
551      ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
552      URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
553      DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
554      FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
555      FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
556      FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
557      USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
558      ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
559      ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
560      ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
561      ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
562      SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
563      SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
564      WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
565      RETURN_DOM = cfg.RETURN_DOM || false; // Default false
566      RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
567      RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
568      FORCE_BODY = cfg.FORCE_BODY || false; // Default false
569      SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
570      SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
571      KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
572      IN_PLACE = cfg.IN_PLACE || false; // Default false
573      IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
574      NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
575      MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
576      HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
577      CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || 
vendor: 1,622 bytes, lines 577-616
577{};
578      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
579        CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
580      }
581      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
582        CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
583      }
584      if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
585        CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
586      }
587      if (SAFE_FOR_TEMPLATES) {
588        ALLOW_DATA_ATTR = false;
589      }
590      if (RETURN_DOM_FRAGMENT) {
591        RETURN_DOM = true;
592      }
593      /* Parse profile info */
594      if (USE_PROFILES) {
595        ALLOWED_TAGS = addToSet({}, text);
596        ALLOWED_ATTR = [];
597        if (USE_PROFILES.html === true) {
598          addToSet(ALLOWED_TAGS, html$1);
599          addToSet(ALLOWED_ATTR, html);
600        }
601        if (USE_PROFILES.svg === true) {
602          addToSet(ALLOWED_TAGS, svg$1);
603          addToSet(ALLOWED_ATTR, svg);
604          addToSet(ALLOWED_ATTR, xml);
605        }
606        if (USE_PROFILES.svgFilters === true) {
607          addToSet(ALLOWED_TAGS, svgFilters);
608          addToSet(ALLOWED_ATTR, svg);
609          addToSet(ALLOWED_ATTR, xml);
610        }
611        if (USE_PROFILES.mathMl === true) {
612          addToSet(ALLOWED_TAGS, mathMl$1);
613          addToSet(ALLOWED_ATTR, mathMl);
614          addToSet(ALLOWED_ATTR, xml);
615        }
616      }
vendor: 5,664 bytes, lines 617-744
617      /* Merge configuration parameters */
618      if (cfg.ADD_TAGS) {
619        if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
620          ALLOWED_TAGS = clone(ALLOWED_TAGS);
621        }
622        addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
623      }
624      if (cfg.ADD_ATTR) {
625        if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
626          ALLOWED_ATTR = clone(ALLOWED_ATTR);
627        }
628        addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
629      }
630      if (cfg.ADD_URI_SAFE_ATTR) {
631        addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
632      }
633      if (cfg.FORBID_CONTENTS) {
634        if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
635          FORBID_CONTENTS = clone(FORBID_CONTENTS);
636        }
637        addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
638      }
639      /* Add #text in case KEEP_CONTENT is set to true */
640      if (KEEP_CONTENT) {
641        ALLOWED_TAGS['#text'] = true;
642      }
643      /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
644      if (WHOLE_DOCUMENT) {
645        addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
646      }
647      /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
648      if (ALLOWED_TAGS.table) {
649        addToSet(ALLOWED_TAGS, ['tbody']);
650        delete FORBID_TAGS.tbody;
651      }
652      if (cfg.TRUSTED_TYPES_POLICY) {
653        if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
654          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
655        }
656        if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
657          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
658        }
659        // Overwrite existing TrustedTypes policy.
660        trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
661        // Sign local variables required by `sanitize`.
662        emptyHTML = trustedTypesPolicy.createHTML('');
663      } else {
664        // Uninitialized policy, attempt to initialize the internal dompurify policy.
665        if (trustedTypesPolicy === undefined) {
666          trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
667        }
668        // If creating the internal policy succeeded sign internal variables.
669        if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
670          emptyHTML = trustedTypesPolicy.createHTML('');
671        }
672      }
673      // Prevent further manipulation of configuration.
674      // Not available in IE8, Safari 5, etc.
675      if (freeze) {
676        freeze(cfg);
677      }
678      CONFIG = cfg;
679    };
680    /* Keep track of all possible SVG and MathML tags
681     * so that we can perform the namespace checks
682     * correctly. */
683    const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
684    const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
685    /**
686     * @param element a DOM element whose namespace is being checked
687     * @returns Return false if the element has a
688     *  namespace that a spec-compliant parser would never
689     *  return. Return true otherwise.
690     */
691    const _checkValidNamespace = function _checkValidNamespace(element) {
692      let parent = getParentNode(element);
693      // In JSDOM, if we're inside shadow DOM, then parentNode
694      // can be null. We just simulate parent in this case.
695      if (!parent || !parent.tagName) {
696        parent = {
697          namespaceURI: NAMESPACE,
698          tagName: 'template'
699        };
700      }
701      const tagName = stringToLowerCase(element.tagName);
702      const parentTagName = stringToLowerCase(parent.tagName);
703      if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
704        return false;
705      }
706      if (element.namespaceURI === SVG_NAMESPACE) {
707        // The only way to switch from HTML namespace to SVG
708        // is via <svg>. If it happens via any other tag, then
709        // it should be killed.
710        if (parent.namespaceURI === HTML_NAMESPACE) {
711          return tagName === 'svg';
712        }
713        // The only way to switch from MathML to SVG is via`
714        // svg if parent is either <annotation-xml> or MathML
715        // text integration points.
716        if (parent.namespaceURI === MATHML_NAMESPACE) {
717          return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
718        }
719        // We only allow elements that are defined in SVG
720        // spec. All others are disallowed in SVG namespace.
721        return Boolean(ALL_SVG_TAGS[tagName]);
722      }
723      if (element.namespaceURI === MATHML_NAMESPACE) {
724        // The only way to switch from HTML namespace to MathML
725        // is via <math>. If it happens via any other tag, then
726        // it should be killed.
727        if (parent.namespaceURI === HTML_NAMESPACE) {
728          return tagName === 'math';
729        }
730        // The only way to switch from SVG to MathML is via
731        // <math> and HTML integration points
732        if (parent.namespaceURI === SVG_NAMESPACE) {
733          return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
734        }
735        // We only allow elements that are defined in MathML
736        // spec. All others are disallowed in MathML namespace.
737        return Boolean(ALL_MATHML_TAGS[tagName]);
738      }
739      if (element.namespaceURI === HTML_NAMESPACE) {
740        // The only way to switch from SVG to HTML is via
741        // HTML integration points, and from MathML to HTML
742        // is via MathML text integration points
743        if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
744          return false;
745        }
746        if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
747          return false;
748        }
749        // We disallow tags that are specific for MathML
750        // or SVG and should never appear in HTML namespace
751        return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
752      }
753      // For XHTML and XML documents that support custom namespaces
754      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
755        return true;
756      }
757      // The code should never reach this place (this means
758      // that the element somehow got namespace that is not
759      // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
760      // Return false just in case.
761      return false;
762    };
763    /**
764     * _forceRemove
765     *
766     * @param node a DOM node
767     */
768    const _forceRemove = function _forceRemove(node) {
769      arrayPush(DOMPurify.removed, {
770        element: node
771      });
772      try {
vendor: 6,975 bytes, lines 773-940
773        // eslint-disable-next-line unicorn/prefer-dom-node-remove
774        getParentNode(node).removeChild(node);
775      } catch (_) {
776        remove(node);
777      }
778    };
779    /**
780     * _removeAttribute
781     *
782     * @param name an Attribute name
783     * @param element a DOM node
784     */
785    const _removeAttribute = function _removeAttribute(name, element) {
786      try {
787        arrayPush(DOMPurify.removed, {
788          attribute: element.getAttributeNode(name),
789          from: element
790        });
791      } catch (_) {
792        arrayPush(DOMPurify.removed, {
793          attribute: null,
794          from: element
795        });
796      }
797      element.removeAttribute(name);
798      // We void attribute values for unremovable "is" attributes
799      if (name === 'is') {
800        if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
801          try {
802            _forceRemove(element);
803          } catch (_) {}
804        } else {
805          try {
806            element.setAttribute(name, '');
807          } catch (_) {}
808        }
809      }
810    };
811    /**
812     * _initDocument
813     *
814     * @param dirty - a string of dirty markup
815     * @return a DOM, filled with the dirty markup
816     */
817    const _initDocument = function _initDocument(dirty) {
818      /* Create a HTML document */
819      let doc = null;
820      let leadingWhitespace = null;
821      if (FORCE_BODY) {
822        dirty = '<remove></remove>' + dirty;
823      } else {
824        /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
825        const matches = stringMatch(dirty, /^[\r\n\t ]+/);
826        leadingWhitespace = matches && matches[0];
827      }
828      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
829        // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
830        dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
831      }
832      const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
833      /*
834       * Use the DOMParser API by default, fallback later if needs be
835       * DOMParser not work for svg when has multiple root element.
836       */
837      if (NAMESPACE === HTML_NAMESPACE) {
838        try {
839          doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
840        } catch (_) {}
841      }
842      /* Use createHTMLDocument in case DOMParser is not available */
843      if (!doc || !doc.documentElement) {
844        doc = implementation.createDocument(NAMESPACE, 'template', null);
845        try {
846          doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
847        } catch (_) {
848          // Syntax error if dirtyPayload is invalid xml
849        }
850      }
851      const body = doc.body || doc.documentElement;
852      if (dirty && leadingWhitespace) {
853        body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
854      }
855      /* Work on whole document or just its body */
856      if (NAMESPACE === HTML_NAMESPACE) {
857        return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
858      }
859      return WHOLE_DOCUMENT ? doc.documentElement : body;
860    };
861    /**
862     * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
863     *
864     * @param root The root element or node to start traversing on.
865     * @return The created NodeIterator
866     */
867    const _createNodeIterator = function _createNodeIterator(root) {
868      return createNodeIterator.call(root.ownerDocument || root, root,
869      // eslint-disable-next-line no-bitwise
870      NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
871    };
872    /**
873     * _isClobbered
874     *
875     * @param element element to check for clobbering attacks
876     * @return true if clobbered, false if safe
877     */
878    const _isClobbered = function _isClobbered(element) {
879      return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
880    };
881    /**
882     * Checks whether the given object is a DOM node.
883     *
884     * @param value object to check whether it's a DOM node
885     * @return true is object is a DOM node
886     */
887    const _isNode = function _isNode(value) {
888      return typeof Node === 'function' && value instanceof Node;
889    };
890    function _executeHooks(hooks, currentNode, data) {
891      arrayForEach(hooks, hook => {
892        hook.call(DOMPurify, currentNode, data, CONFIG);
893      });
894    }
895    /**
896     * _sanitizeElements
897     *
898     * @protect nodeName
899     * @protect textContent
900     * @protect removeChild
901     * @param currentNode to check for permission to exist
902     * @return true if node was killed, false if left alive
903     */
904    const _sanitizeElements = function _sanitizeElements(currentNode) {
905      let content = null;
906      /* Execute a hook if present */
907      _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
908      /* Check if element is clobbered or can clobber */
909      if (_isClobbered(currentNode)) {
910        _forceRemove(currentNode);
911        return true;
912      }
913      /* Now let's check the element's type and name */
914      const tagName = transformCaseFunc(currentNode.nodeName);
915      /* Execute a hook if present */
916      _executeHooks(hooks.uponSanitizeElement, currentNode, {
917        tagName,
918        allowedTags: ALLOWED_TAGS
919      });
920      /* Detect mXSS attempts abusing namespace confusion */
921      if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
922        _forceRemove(currentNode);
923        return true;
924      }
925      /* Remove any occurrence of processing instructions */
926      if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
927        _forceRemove(currentNode);
928        return true;
929      }
930      /* Remove any kind of possibly harmful comments */
931      if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
932        _forceRemove(currentNode);
933        return true;
934      }
935      /* Remove element if anything forbids its presence */
936      if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
937        /* Check if we have a custom element to handle */
938        if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
939          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
940            return false;
vendor: 4,716 bytes, lines 941-1017
941          }
942          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
943            return false;
944          }
945        }
946        /* Keep content except for bad-listed elements */
947        if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
948          const parentNode = getParentNode(currentNode) || currentNode.parentNode;
949          const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
950          if (childNodes && parentNode) {
951            const childCount = childNodes.length;
952            for (let i = childCount - 1; i >= 0; --i) {
953              const childClone = cloneNode(childNodes[i], true);
954              childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
955              parentNode.insertBefore(childClone, getNextSibling(currentNode));
956            }
957          }
958        }
959        _forceRemove(currentNode);
960        return true;
961      }
962      /* Check whether element has a valid namespace */
963      if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
964        _forceRemove(currentNode);
965        return true;
966      }
967      /* Make sure that older browsers don't get fallback-tag mXSS */
968      if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
969        _forceRemove(currentNode);
970        return true;
971      }
972      /* Sanitize element content to be template-safe */
973      if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
974        /* Get the element's text content */
975        content = currentNode.textContent;
976        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
977          content = stringReplace(content, expr, ' ');
978        });
979        if (currentNode.textContent !== content) {
980          arrayPush(DOMPurify.removed, {
981            element: currentNode.cloneNode()
982          });
983          currentNode.textContent = content;
984        }
985      }
986      /* Execute a hook if present */
987      _executeHooks(hooks.afterSanitizeElements, currentNode, null);
988      return false;
989    };
990    /**
991     * _isValidAttribute
992     *
993     * @param lcTag Lowercase tag name of containing element.
994     * @param lcName Lowercase attribute name.
995     * @param value Attribute value.
996     * @return Returns true if `value` is valid, otherwise false.
997     */
998    // eslint-disable-next-line complexity
999    const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1000      /* Make sure attribute cannot clobber */
1001      if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
1002        return false;
1003      }
1004      /* Allow valid data-* attributes: At least one character after "-"
1005          (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1006          XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1007          We don't need to check the value; it's always URI safe. */
1008      if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
1009        if (
1010        // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1011        // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1012        // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1013        _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) ||
1014        // Alternative, second condition checks if it's an `is`-attribute, AND
1015        // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1016        lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
1017          return false;
vendor: 15,033 bytes, lines 1018-1395
1018        }
1019        /* Check value is safe. First, is attr inert? If so, is safe */
1020      } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
1021        return false;
1022      } else ;
1023      return true;
1024    };
1025    /**
1026     * _isBasicCustomElement
1027     * checks if at least one dash is included in tagName, and it's not the first char
1028     * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1029     *
1030     * @param tagName name of the tag of the node to sanitize
1031     * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
1032     */
1033    const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
1034      return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
1035    };
1036    /**
1037     * _sanitizeAttributes
1038     *
1039     * @protect attributes
1040     * @protect nodeName
1041     * @protect removeAttribute
1042     * @protect setAttribute
1043     *
1044     * @param currentNode to sanitize
1045     */
1046    const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1047      /* Execute a hook if present */
1048      _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
1049      const {
1050        attributes
1051      } = currentNode;
1052      /* Check if we have attributes; if not we might have a text node */
1053      if (!attributes || _isClobbered(currentNode)) {
1054        return;
1055      }
1056      const hookEvent = {
1057        attrName: '',
1058        attrValue: '',
1059        keepAttr: true,
1060        allowedAttributes: ALLOWED_ATTR,
1061        forceKeepAttr: undefined
1062      };
1063      let l = attributes.length;
1064      /* Go backwards over all attributes; safely remove bad ones */
1065      while (l--) {
1066        const attr = attributes[l];
1067        const {
1068          name,
1069          namespaceURI,
1070          value: attrValue
1071        } = attr;
1072        const lcName = transformCaseFunc(name);
1073        const initValue = attrValue;
1074        let value = name === 'value' ? initValue : stringTrim(initValue);
1075        /* Execute a hook if present */
1076        hookEvent.attrName = lcName;
1077        hookEvent.attrValue = value;
1078        hookEvent.keepAttr = true;
1079        hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
1080        _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
1081        value = hookEvent.attrValue;
1082        /* Full DOM Clobbering protection via namespace isolation,
1083         * Prefix id and name attributes with `user-content-`
1084         */
1085        if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
1086          // Remove the attribute with this value
1087          _removeAttribute(name, currentNode);
1088          // Prefix the value and later re-create the attribute with the sanitized value
1089          value = SANITIZE_NAMED_PROPS_PREFIX + value;
1090        }
1091        /* Work around a security issue with comments inside attributes */
1092        if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
1093          _removeAttribute(name, currentNode);
1094          continue;
1095        }
1096        /* Did the hooks approve of the attribute? */
1097        if (hookEvent.forceKeepAttr) {
1098          continue;
1099        }
1100        /* Did the hooks approve of the attribute? */
1101        if (!hookEvent.keepAttr) {
1102          _removeAttribute(name, currentNode);
1103          continue;
1104        }
1105        /* Work around a security issue in jQuery 3.0 */
1106        if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
1107          _removeAttribute(name, currentNode);
1108          continue;
1109        }
1110        /* Sanitize attribute content to be template-safe */
1111        if (SAFE_FOR_TEMPLATES) {
1112          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1113            value = stringReplace(value, expr, ' ');
1114          });
1115        }
1116        /* Is `value` valid for this attribute? */
1117        const lcTag = transformCaseFunc(currentNode.nodeName);
1118        if (!_isValidAttribute(lcTag, lcName, value)) {
1119          _removeAttribute(name, currentNode);
1120          continue;
1121        }
1122        /* Handle attributes that require Trusted Types */
1123        if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
1124          if (namespaceURI) ; else {
1125            switch (trustedTypes.getAttributeType(lcTag, lcName)) {
1126              case 'TrustedHTML':
1127                {
1128                  value = trustedTypesPolicy.createHTML(value);
1129                  break;
1130                }
1131              case 'TrustedScriptURL':
1132                {
1133                  value = trustedTypesPolicy.createScriptURL(value);
1134                  break;
1135                }
1136            }
1137          }
1138        }
1139        /* Handle invalid data-* attribute set by try-catching it */
1140        if (value !== initValue) {
1141          try {
1142            if (namespaceURI) {
1143              currentNode.setAttributeNS(namespaceURI, name, value);
1144            } else {
1145              /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1146              currentNode.setAttribute(name, value);
1147            }
1148            if (_isClobbered(currentNode)) {
1149              _forceRemove(currentNode);
1150            } else {
1151              arrayPop(DOMPurify.removed);
1152            }
1153          } catch (_) {
1154            _removeAttribute(name, currentNode);
1155          }
1156        }
1157      }
1158      /* Execute a hook if present */
1159      _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
1160    };
1161    /**
1162     * _sanitizeShadowDOM
1163     *
1164     * @param fragment to iterate over recursively
1165     */
1166    const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1167      let shadowNode = null;
1168      const shadowIterator = _createNodeIterator(fragment);
1169      /* Execute a hook if present */
1170      _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
1171      while (shadowNode = shadowIterator.nextNode()) {
1172        /* Execute a hook if present */
1173        _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
1174        /* Sanitize tags and elements */
1175        _sanitizeElements(shadowNode);
1176        /* Check attributes next */
1177        _sanitizeAttributes(shadowNode);
1178        /* Deep shadow DOM detected */
1179        if (shadowNode.content instanceof DocumentFragment) {
1180          _sanitizeShadowDOM(shadowNode.content);
1181        }
1182      }
1183      /* Execute a hook if present */
1184      _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
1185    };
1186    // eslint-disable-next-line complexity
1187    DOMPurify.sanitize = function (dirty) {
1188      let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1189      let body = null;
1190      let importedNode = null;
1191      let currentNode = null;
1192      let returnNode = null;
1193      /* Make sure we have a string to sanitize.
1194        DO NOT return early, as this will return the wrong type if
1195        the user has requested a DOM object rather than a string */
1196      IS_EMPTY_INPUT = !dirty;
1197      if (IS_EMPTY_INPUT) {
1198        dirty = '<!-->';
1199      }
1200      /* Stringify, in case dirty is an object */
1201      if (typeof dirty !== 'string' && !_isNode(dirty)) {
1202        if (typeof dirty.toString === 'function') {
1203          dirty = dirty.toString();
1204          if (typeof dirty !== 'string') {
1205            throw typeErrorCreate('dirty is not a string, aborting');
1206          }
1207        } else {
1208          throw typeErrorCreate('toString is not a function');
1209        }
1210      }
1211      /* Return dirty HTML if DOMPurify cannot run */
1212      if (!DOMPurify.isSupported) {
1213        return dirty;
1214      }
1215      /* Assign config vars */
1216      if (!SET_CONFIG) {
1217        _parseConfig(cfg);
1218      }
1219      /* Clean up removed elements */
1220      DOMPurify.removed = [];
1221      /* Check if dirty is correctly typed for IN_PLACE */
1222      if (typeof dirty === 'string') {
1223        IN_PLACE = false;
1224      }
1225      if (IN_PLACE) {
1226        /* Do some early pre-sanitization to avoid unsafe root nodes */
1227        if (dirty.nodeName) {
1228          const tagName = transformCaseFunc(dirty.nodeName);
1229          if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1230            throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1231          }
1232        }
1233      } else if (dirty instanceof Node) {
1234        /* If dirty is a DOM element, append to an empty document to avoid
1235           elements being stripped by the parser */
1236        body = _initDocument('<!---->');
1237        importedNode = body.ownerDocument.importNode(dirty, true);
1238        if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
1239          /* Node is already a body, use as is */
1240          body = importedNode;
1241        } else if (importedNode.nodeName === 'HTML') {
1242          body = importedNode;
1243        } else {
1244          // eslint-disable-next-line unicorn/prefer-dom-node-append
1245          body.appendChild(importedNode);
1246        }
1247      } else {
1248        /* Exit directly if we have nothing to do */
1249        if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
1250        // eslint-disable-next-line unicorn/prefer-includes
1251        dirty.indexOf('<') === -1) {
1252          return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1253        }
1254        /* Initialize the document to work on */
1255        body = _initDocument(dirty);
1256        /* Check we have a DOM node from the data */
1257        if (!body) {
1258          return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1259        }
1260      }
1261      /* Remove first element node (ours) if FORCE_BODY is set */
1262      if (body && FORCE_BODY) {
1263        _forceRemove(body.firstChild);
1264      }
1265      /* Get node iterator */
1266      const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
1267      /* Now start iterating over the created document */
1268      while (currentNode = nodeIterator.nextNode()) {
1269        /* Sanitize tags and elements */
1270        _sanitizeElements(currentNode);
1271        /* Check attributes next */
1272        _sanitizeAttributes(currentNode);
1273        /* Shadow DOM detected, sanitize it */
1274        if (currentNode.content instanceof DocumentFragment) {
1275          _sanitizeShadowDOM(currentNode.content);
1276        }
1277      }
1278      /* If we sanitized `dirty` in-place, return it. */
1279      if (IN_PLACE) {
1280        return dirty;
1281      }
1282      /* Return sanitized string or DOM */
1283      if (RETURN_DOM) {
1284        if (RETURN_DOM_FRAGMENT) {
1285          returnNode = createDocumentFragment.call(body.ownerDocument);
1286          while (body.firstChild) {
1287            // eslint-disable-next-line unicorn/prefer-dom-node-append
1288            returnNode.appendChild(body.firstChild);
1289          }
1290        } else {
1291          returnNode = body;
1292        }
1293        if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
1294          /*
1295            AdoptNode() is not used because internal state is not reset
1296            (e.g. the past names map of a HTMLFormElement), this is safe
1297            in theory but we would rather not risk another attack vector.
1298            The state that is cloned by importNode() is explicitly defined
1299            by the specs.
1300          */
1301          returnNode = importNode.call(originalDocument, returnNode, true);
1302        }
1303        return returnNode;
1304      }
1305      let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1306      /* Serialize doctype if allowed */
1307      if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
1308        serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
1309      }
1310      /* Sanitize final string template-safe */
1311      if (SAFE_FOR_TEMPLATES) {
1312        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1313          serializedHTML = stringReplace(serializedHTML, expr, ' ');
1314        });
1315      }
1316      return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1317    };
1318    DOMPurify.setConfig = function () {
1319      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1320      _parseConfig(cfg);
1321      SET_CONFIG = true;
1322    };
1323    DOMPurify.clearConfig = function () {
1324      CONFIG = null;
1325      SET_CONFIG = false;
1326    };
1327    DOMPurify.isValidAttribute = function (tag, attr, value) {
1328      /* Initialize shared config vars if necessary. */
1329      if (!CONFIG) {
1330        _parseConfig({});
1331      }
1332      const lcTag = transformCaseFunc(tag);
1333      const lcName = transformCaseFunc(attr);
1334      return _isValidAttribute(lcTag, lcName, value);
1335    };
1336    DOMPurify.addHook = function (entryPoint, hookFunction) {
1337      if (typeof hookFunction !== 'function') {
1338        return;
1339      }
1340      arrayPush(hooks[entryPoint], hookFunction);
1341    };
1342    DOMPurify.removeHook = function (entryPoint, hookFunction) {
1343      if (hookFunction !== undefined) {
1344        const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
1345        return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
1346      }
1347      return arrayPop(hooks[entryPoint]);
1348    };
1349    DOMPurify.removeHooks = function (entryPoint) {
1350      hooks[entryPoint] = [];
1351    };
1352    DOMPurify.removeAllHooks = function () {
1353      hooks = _createHooksMap();
1354    };
1355    return DOMPurify;
1356  }
1357  var purify = createDOMPurify();
1358
1359  return purify;
1360
1361}));
1362//# sourceMappingURL=purify.js.map
1363
1364/**
1365 * jQuery fixes
1366 *
1367 * Note: The following analysis and/or changes was required also to be applied in our code base:
1368 *	- Ensure there is no JSONP ajax call
1369 *	- Ensure to not use self closing tag when a closing tag is usually needed.
1370 *		ex: `<div />` need to change for `<div></div>`
1371 *		This is applicable to all tags except the one in this list: area|br|col|embed|hr|img|input|link|meta|param
1372 *	- Review how `<tr>`, `<td>`, `<script>` and `<link>` are inserted with `$()`.
1373 *		Their insertion need to be completed differently like the by using the javascript DOM interface.
1374 *	- All content that related to jQuery DOM manipulation are sanitized with DOMPurify
1375 *	- You can't use jQuery for parsing XML document, you can use DOMParser() as an alternative
1376 *	- Ensure AJAX and fetch are sanitized before they response are used
1377 */
1378/*
1379
1380// Test case for the jQuery fixes bellow
1381
1382Test: jQuery()
1383
1384	- Run in your browser console
1385	> $( "<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>" ).appendTo( "#wb-cont" );
1386	result: No iframe inserted around the heading level 1
1387
1388Test:
1389jQuery.html()
1390jQuery.append()
1391jQuery.prepend()
1392jQuery.before()
1393jQuery.after()
1394
1395	- Use the data-ajax demo page: /demos/data-ajax/data-ajax-en.html
1396	- Insert the following in the ajaxed in file: <p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>
1397	- Check if the dirty HTML are cleaned for the example: "append", "prepend", "before", "after"
1398	- The data-ajax-replace will also test the jQuery.html() function.
1399
1400Test: jQuery.replaceWith()
1401
1402	- Run in your browser console
1403	> $("#wb-cont").replaceWith( "<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>" );
1404	result: No iframe inserted around the heading level 1
1405
1406Test: jQuery.parseHTML()
1407
1408	- Run in your browser console, pass unsafe HTML and it should return sanitized HTML
1409	> jQuery.parseHTML( "<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>" );
1410	result: return an array with 1 element, [ p ]
1411
1412Test: jQuery.htmlPrefilter()
1413
1414	- Run in your browser console
1415	> jQuery.htmlPrefilter( "<tr/>" );
1416	result: return <tr/>
1417
1418Test: jQuery.extend
1419
1420	- Run in your browser console
1421	> var dataFromAPI = JSON.parse( '{ "new": "property", "__proto__": { "polluted": "true" } }' );
1422	> var myObject = jQuery.extend( '{ "new": "property" }', dataFromAPI );
1423	> console.log( myObject.polluted );
1424	result: > undefined
1425
1426*/
1427
1428( function( jQuery, DOMPurify, window ) {
1429"use strict";
1430
1431/**
1432 * Copied from: https://git.drupalcode.org/project/drupal/-/commit/d2f26902ef5d6de58caaf2a7c766eb2115b1c17e
1433 *
1434 * This is almost verbatim copied from jQuery 3.4.0.
1435 *
1436 * Now compatible with jQuery 4.
1437 *
1438 */
1439jQuery.extend = jQuery.fn.extend = function() {
1440	var options, name, src, copy, copyIsArray, clone,
1441		target = arguments[ 0 ] || {},
1442		i = 1,
1443		length = arguments.length,
1444		deep = false;
1445
1446	// Handle a deep copy situation
1447	if ( typeof target === "boolean" ) {
1448		deep = target;
1449
1450		// Skip the boolean and the target
1451		target = arguments[ i ] || {};
1452		i++;
1453	}
1454
1455	// Handle case when target is a string or something (possible in deep copy)
1456	if ( typeof target !== "object" && typeof target !== "function" ) {
1457		target = {};
1458	}
1459
1460	// Extend jQuery itself if only one argument is passed
1461	if ( i === length ) {
1462		target = this;
1463		i--;
1464	}
1465
1466	for ( ; i < length; i++ ) {
1467
1468		// Only deal with non-null/undefined values
1469		options = arguments[ i ];
1470		if ( options !== undefined || options !== null ) {
1471
1472			// Extend the base object
1473			for ( name in options ) {
1474				copy = options[ name ];
1475
1476				// Prevent Object.prototype pollution
1477				// Prevent never-ending loop
1478				if ( name === "__proto__" || target === copy ) {
1479					continue;
1480				}
1481
1482				// Recurse if we're merging plain objects or arrays
1483				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
1484					( copyIsArray = Array.isArray( copy ) ) ) ) {
1485					src = target[ name ];
1486
1487					// Ensure proper type for the source value
1488					if ( copyIsArray && !Array.isArray( src ) ) {
1489						clone = [];
1490					} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
1491						clone = {};
1492					} else {
1493						clone = src;
1494					}
1495					copyIsArray = false;
1496
1497					// Never move original objects, clone them
1498					target[ name ] = jQuery.extend( deep, clone, copy );
1499
1500					// Don't bring in undefined values
1501				} else if ( copy !== undefined ) {
1502					target[ name ] = copy;
1503				}
1504			}
1505		}
1506	}
1507
1508	// Return the modified object
1509	return target;
1510};
1511
1512/**
1513 * Security advisories: https://github.com/jquery/jquery/security/advisories/GHSA-gxr4-xjj5-5px2
1514 */
1515jQuery.htmlPrefilter = function( html ) {
1516	return html;
1517};
1518
1519/**
1520 * Security advisories: https://github.com/jquery/jquery/security/advisories/GHSA-jpcq-cgw6-v4j6
1521 *
1522 * This implementation leverage DOMPurify for filtering every string prior DOM manipulation by jQuery
1523 *
1524 */
1525
1526// START: add hooks to DOMPurify to allow external links when they meet certain conditions as defined here: https://owasp.org/www-community/attacks/Reverse_Tabnabbing
1527DOMPurify.addHook( "beforeSanitizeAttributes", function( node ) {
1528
1529	// Add "data-wb-external-link" to all <a> with a target="_blank" and rel="noreferrer"
1530	if (
1531		node.tagName === "A" &&
1532		node.getAttribute( "target" ) &&
1533		node.getAttribute( "target" ) === "_blank" &&
1534		node.getAttribute( "rel" ) &&
1535		node.relList.contains( "noreferrer" )
1536	) {
1537		node.setAttribute( "data-wb-external-link", "true" );
1538	}
1539} );
1540
1541DOMPurify.addHook( "afterSanitizeAttributes", function( node ) {
1542
1543	// Put back the target="_blank" to all <a> with attribute "data-wb-external-link"
1544	if ( node.tagName === "A" && node.getAttribute( "data-wb-external-link" ) ) {
1545		node.setAttribute( "target", "_blank" );
1546		node.removeAttribute( "data-wb-external-link" );
1547	}
1548} );
1549
1550// END
1551
1552var localParseHTML = jQuery.parseHTML,
1553	append = jQuery.fn.append,
1554	prepend = jQuery.fn.prepend,
1555	before = jQuery.fn.before,
1556	after = jQuery.fn.after,
1557	replaceWith = jQuery.fn.replaceWith,
1558	jqInit = jQuery.fn.init,
1559	dataTableAllowedTag = [
1560		"<tbody/>",
1561		"<tr/>",
1562		"<td />",
1563		"<td/>"
1564	],
1565	sanitize = function( html ) {
1566
1567		// Add an exception for DataTable plugin
1568		if ( window.DataTable && dataTableAllowedTag.indexOf( html ) !== -1 ) {
1569			return html;
1570		}
1571
1572		return DOMPurify.sanitize( html );
1573	};
1574
1575jQuery.parseHTML = function( data, context, keepScripts ) {
1576	return localParseHTML( sanitize( data ), context, keepScripts );
1577};
1578
1579jQuery.domManip = null;
1580
1581jQuery.append = jQuery.fn.append = function() {
1582	var args = arguments,
1583		value = args[ 0 ];
1584	if ( typeof value === "string" ) {
1585		value = sanitize( value );
1586		args[ 0 ] = value;
1587	}
1588	return append.apply( this, args );
1589};
1590
1591jQuery.prepend = jQuery.fn.prepend = function() {
1592	var args = arguments,
1593		value = args[ 0 ];
1594	if ( typeof value === "string" ) {
1595		value = sanitize( value );
1596		args[ 0 ] = value;
1597	}
1598	return prepend.apply( this, args );
1599};
1600
1601jQuery.before = jQuery.fn.before = function() {
1602	var args = arguments,
1603		value = args[ 0 ];
1604	if ( typeof value === "string" ) {
1605		value = sanitize( value );
1606		args[ 0 ] = value;
1607	}
1608	return before.apply( this, args );
1609};
1610
1611jQuery.after = jQuery.fn.after = function() {
1612	var args = arguments,
1613		value = args[ 0 ];
1614	if ( typeof value === "string" ) {
1615		value = sanitize( value );
1616		args[ 0 ] = value;
1617	}
1618	return after.apply( this, args );
1619};
1620
1621jQuery.replaceWith = jQuery.fn.replaceWith = function() {
1622	var args = arguments,
1623		value = args[ 0 ];
1624	if ( typeof value === "string" ) {
1625		value = sanitize( value );
1626		args[ 0 ] = value;
1627	}
1628	return replaceWith.apply( this, args );
1629};
1630
1631jQuery.fn.init = function( selector, context, root ) {
1632	if ( typeof selector === "string" ) {
1633		selector = sanitize( selector );
1634	}
1635	return new jqInit( selector, context, root );
1636};
1637
1638jQuery.html = function( value ) {
1639	return jQuery.html( sanitize( value ) );
1640};
1641
1642} )( jQuery, DOMPurify, window );
1643
1644/* Modernizr (Custom Build) | MIT & BSD
1645 * Build: https://modernizr.com/download/#-elem_details-elem_progress_meter-mathml-cors-load-mq-css3-input-inputtypes-svg-cssclasses-csstransitions-fontface-backgroundsize-borderimage-teststyles-testprops-testallprops-hasevents-prefixes-domprefixes
1646 */
1647;window.Modernizr=function(e,t,n){function L(e){f.cssText=e}function A(e,t){return L(p.join(e+";")+(t||""))}function O(e,t){return typeof e===t}function M(e,t){return!!~(""+e).indexOf(t)}function _(e,t){for(var r in e){var i=e[r];if(!M(i,"-")&&f[i]!==n)return t=="pfx"?i:!0}return!1}function D(e,t,r){for(var i in e){var s=t[e[i]];if(s!==n)return r===!1?e[i]:O(s,"function")?s.bind(r||t):s}return!1}function P(e,t,n){var r=e.charAt(0).toUpperCase()+e.slice(1),i=(e+" "+v.join(r+" ")+r).split(" ");return O(t,"string")||O(t,"undefined")?_(i,t):(i=(e+" "+m.join(r+" ")+r).split(" "),D(i,t,n))}function H(){i.input=function(n){for(var r=0,i=n.length;r<i;r++)w[n[r]]=n[r]in l;return w.list&&(w.list=!!t.createElement("datalist")&&!!e.HTMLDataListElement),w}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),i.inputtypes=function(e){for(var r=0,i,s,u,a=e.length;r<a;r++)l.setAttribute("type",s=e[r]),i=l.type!=="text",i&&(l.value=c,l.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(s)&&l.style.WebkitAppearance!==n?(o.appendChild(l),u=t.defaultView,i=u.getComputedStyle&&u.getComputedStyle(l,null).WebkitAppearance!=="textfield"&&l.offsetHeight!==0,o.removeChild(l)):/^(search|tel)$/.test(s)||(/^(url|email)$/.test(s)?i=l.checkValidity&&l.checkValidity()===!1:i=l.value!=c)),b[e[r]]=!!i;return b}("search tel url email datetime date month week time datetime-local number range color".split(" "))}
vendor: 5,597 bytes, line 1647
1647var r="2.8.3",i={},s=!0,o=t.documentElement,u="modernizr",a=t.createElement(u),f=a.style,l=t.createElement("input"),c=":)",h={}.toString,p=" -webkit- -moz- -o- -ms- ".split(" "),d="Webkit Moz O ms",v=d.split(" "),m=d.toLowerCase().split(" "),g={svg:"http://www.w3.org/2000/svg"},y={},b={},w={},E=[],S=E.slice,x,T=function(e,n,r,i){var s,a,f,l,c=t.createElement("div"),h=t.body,p=h||t.createElement("body");if(parseInt(r,10))while(r--)f=t.createElement("div"),f.id=i?i[r]:u+(r+1),c.appendChild(f);return s=["&#173;",'<style id="s',u,'">',e,"</style>"].join(""),c.id=u,(h?c:p).innerHTML+=s,p.appendChild(c),h||(p.style.background="",p.style.overflow="hidden",l=o.style.overflow,o.style.overflow="hidden",o.appendChild(p)),a=n(c,e),h?c.parentNode.removeChild(c):(p.parentNode.removeChild(p),o.style.overflow=l),!!a},N=function(t){var n=e.matchMedia||e.msMatchMedia;if(n)return n(t)&&n(t).matches||!1;var r;return T("@media "+t+" { #"+u+" { position: absolute; } }",function(t){r=(e.getComputedStyle?getComputedStyle(t,null):t.currentStyle)["position"]=="absolute"}),r},C={}.hasOwnProperty,k;!O(C,"undefined")&&!O(C.call,"undefined")?k=function(e,t){return C.call(e,t)}:k=function(e,t){return t in e&&O(e.constructor.prototype[t],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError;var r=S.call(arguments,1),i=function(){if(this instanceof i){var e=function(){};e.prototype=n.prototype;var s=new e,o=n.apply(s,r.concat(S.call(arguments)));return Object(o)===o?o:s}return n.apply(t,r.concat(S.call(arguments)))};return i}),y.backgroundsize=function(){return P("backgroundSize")},y.borderimage=function(){return P("borderImage")},y.csstransitions=function(){return P("transition")},y.fontface=function(){var e;return T('@font-face {font-family:"font";src:url("https://")}',function(n,r){var i=t.getElementById("smodernizr"),s=i.sheet||i.styleSheet,o=s?s.cssRules&&s.cssRules[0]?s.cssRules[0].cssText:s.cssText||"":"";e=/src/i.test(o)&&o.indexOf(r.split(" ")[0])===0}),e},y.svg=function(){return!!t.createElementNS&&!!t.createElementNS(g.svg,"svg").createSVGRect};for(var B in y)k(y,B)&&(x=B.toLowerCase(),i[x]=y[B](),E.push((i[x]?"":"no-")+x));return i.input||H(),i.addTest=function(e,t){if(typeof e=="object")for(var r in e)k(e,r)&&i.addTest(r,e[r]);else{e=e.toLowerCase();if(i[e]!==n)return i;t=typeof t=="function"?t():t,typeof s!="undefined"&&s&&(o.className+=" "+(t?"":"no-")+e),i[e]=t}return i},L(""),a=l=null,i._version=r,i._prefixes=p,i._domPrefixes=m,i._cssomPrefixes=v,i.mq=N,i.testProp=function(e){return _([e])},i.testAllProps=P,i.testStyles=T,o.className=o.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(s?" js "+E.join(" "):""),i}(this,this.document),function(e,t,n){function r(e){return"[object Function]"==d.call(e)}function i(e){return"string"==typeof e}function s(){}function o(e){return!e||"loaded"==e||"complete"==e||"uninitialized"==e}function u(){var e=v.shift();m=1,e?e.t?h(function(){("c"==e.t?k.injectCss:k.injectJs)(e.s,0,e.a,e.x,e.e,1)},0):(e(),u()):m=0}function a(e,n,r,i,s,a,f){function l(t){if(!d&&o(c.readyState)&&(w.r=d=1,!m&&u(),c.onload=c.onreadystatechange=null,t)){"img"!=e&&h(function(){b.removeChild(c)},50);for(var r in T[n])T[n].hasOwnProperty(r)&&T[n][r].onload()}}var f=f||k.errorTimeout,c=t.createElement(e),d=0,g=0,w={t:r,s:n,e:s,a:a,x:f};1===T[n]&&(g=1,T[n]=[]),"object"==e?c.data=n:(c.src=n,c.type=e),c.width=c.height="0",c.onerror=c.onload=c.onreadystatechange=function(){l.call(this,g)},v.splice(i,0,w),"img"!=e&&(g||2===T[n]?(b.insertBefore(c,y?null:p),h(l,f)):T[n].push(c))}function f(e,t,n,r,s){return m=0,t=t||"j",i(e)?a("c"==t?E:w,e,t,this.i++,n,r,s):(v.splice(this.i++,0,e),1==v.length&&u()),this}function l(){var e=k;return e.loader={load:f,i:0},e}var c=t.documentElement,h=e.setTimeout,p=t.getElementsByTagName("script")[0],d={}.toString,v=[],m=0,g="MozAppearance"in c.style,y=g&&!!t.createRange().compareNode,b=y?c:p.parentNode,c=e.opera&&"[object Opera]"==d.call(e.opera),c=!!t.attachEvent&&!c,w=g?"object":c?"script":"img",E=c?"script":w,S=Array.isArray||function(e){return"[object Array]"==d.call(e)},x=[],T={},N={timeout:function(e,t){return t.length&&(e.timeout=t[0]),e}},C,k;k=function(e){function t(e){var e=e.split("!"),t=x.length,n=e.pop(),r=e.length,n={url:n,origUrl:n,prefixes:e},i,s,o;for(s=0;s<r;s++)o=e[s].split("="),(i=N[o.shift()])&&(n=i(n,o));for(s=0;s<t;s++)n=x[s](n);return n}function o(e,i,s,o,u){var a=t(e),f=a.autoCallback;a.url.split(".").pop().split("?").shift(),a.bypass||(i&&(i=r(i)?i:i[e]||i[o]||i[e.split("/").pop().split("?")[0]]),a.instead?a.instead(e,i,s,o,u):(T[a.url]?a.noexec=!0:T[a.url]=1,s.load(a.url,a.forceCSS||!a.forceJS&&"css"==a.url.split(".").pop().split("?").shift()?"c":n,a.noexec,a.attrs,a.timeout),(r(i)||r(f))&&s.load(function(){l(),i&&i(a.origUrl,u,o),f&&f(a.origUrl,u,o),T[a.url]=2})))}function u(e,t){function n(e,n){if(e){if(i(e))n||(f=function(){var e=[].slice.call(arguments);l.apply(this,e),c()}),o(e,f,t,0,u);else if(Object(e)===e)for(p in h=function(){var t=0,n;for(n in e)e.hasOwnProperty(n)&&t++;return t}(),e)e.hasOwnProperty(p)&&(!n&&!--h&&(r(f)?f=function(){var e=[].slice.call(arguments);l.apply(this,e),c()}:f[p]=function(e){return function(){var t=[].slice.call(arguments);e&&e.apply(this,t),c()}}(l[p])),o(e[p],f,t,p,u))}else!n&&c()}var u=!!e.test,a=e.load||e.both,f=e.callback||s,l=f,c=e.complete||s,h,p;n(u?e.yep:e.nope,!!a),a&&n(a)}var a,f,c=this.yepnope.loader;if(i(e))o(e,0,c,0);else if(S(e))for(a=0;a<e.length;a++)f=e[a],i(f)?o(f,0,c,0):S(f)?k(f):Object(f)===f&&u(f,c);else Object(e)===e&&u(e,c)}
1647,k.addPrefix=function(e,t){N[e]=t},k.addFilter=function(e){x.push(e)},k.errorTimeout=1e4,null==t.readyState&&t.addEventListener&&(t.readyState="loading",t.addEventListener("DOMContentLoaded",C=function(){t.removeEventListener("DOMContentLoaded",C,0),t.readyState="complete"},0)),e.yepnope=l(),e.yepnope.executeStack=u,e.yepnope.injectJs=function(e,n,r,i,a,f){var l=t.createElement("script"),c,d,i=i||k.errorTimeout;l.src=e;for(d in r)l.setAttribute(d,r[d]);n=f?u:n||s,l.onreadystatechange=l.onload=function(){!c&&o(l.readyState)&&(c=1,n(),l.onload=l.onreadystatechange=null)},h(function(){c||(c=1,n(1))},i),a?l.onload():p.parentNode.insertBefore(l,p)},e.yepnope.injectCss=function(e,n,r,i,o,a){var i=t.createElement("link"),f,n=a?u:n||s;i.href=e,i.rel="stylesheet",i.type="text/css";for(f in r)i.setAttribute(f,r[f]);o||(p.parentNode.insertBefore(i,p),h(n,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))},Modernizr.addTest("details",function(){var e=document,t=e.createElement("details"),n,r,i;return"open"in t?(r=e.body||function(){var t=e.documentElement;return n=!0,t.insertBefore(e.createElement("body"),t.firstElementChild||t.firstChild)}(),t.innerHTML="<summary>a</summary>b",t.style.display="block",r.appendChild(t),i=t.offsetHeight,t.open=!0,i=i!=t.offsetHeight,r.removeChild(t),n&&r.parentNode.removeChild(r),i):!1}),Modernizr.addTest("progressbar",function(){return document.createElement("progress").max!==undefined}),Modernizr.addTest("meter",function(){return document.createElement("meter").max!==undefined}),Modernizr.addTest("mathml",function(){var e=!1;if(document.createElementNS){var t="http://www.w3.org/1998/Math/MathML",n=document.createElement("div");n.style.position="absolute";var r=n.appendChild(document.createElementNS(t,"math")).appendChild(document.createElementNS(t,"mfrac"));r.appendChild(document.createElementNS(t,"mi")).appendChild(document.createTextNode("xx")),r.appendChild(document.createElementNS(t,"mi")).appendChild(document.createTextNode("yy")),document.body.appendChild(n),e=n.offsetHeight>n.offsetWidth}return e}),Modernizr.addTest("cors",!!(window.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest));
1648
1649/**
1650 * String.prototype.includes() polyfill
1651 * @author Ricokola
1652 * @license MIT
1653 */
1654if ( !String.prototype.includes ) {
1655	String.prototype.includes = function( string ) {
1656
1657		return this.indexOf( string ) !== -1;
1658
1659	};
1660}
1661
1662/**
1663 * String.prototype.replaceAll() polyfill
1664 * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
1665 * @author Chris Ferdinandi
1666 * @license MIT
1667 */
1668if ( !String.prototype.replaceAll ) {
1669	String.prototype.replaceAll = function( str, newStr ) {
1670
1671		// If a regex pattern
1672		if ( Object.prototype.toString.call( str ).toLowerCase() === "[object regexp]" ) {
1673			return this.replace( str, newStr );
1674		}
1675
1676		// If a string
1677		return this.replace( new RegExp( str, "g" ), newStr );
1678
1679	};
1680}
1681
1682/**
1683 * @title WET-BOEW Vapour loader
1684 * @overview Helper methods for WET
1685 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
1686 * @author WET Community
1687 */
1688/*
1689 * Vapour Object that will store tombstone data for plugins to leverage
1690 */
1691( function( $, window, document, undef ) {
1692"use strict";
1693
1694/**
1695 * @method getUrlParts
1696 * @param {String} url of URL to examine
1697 * @return {Object} of the parts of passed URL
1698 */
1699var getUrlParts = function( url ) {
1700		var a = document.createElement( "a" );
1701		a.href = url;
1702		return {
1703			href: a.href,
1704			absolute: a.href,
1705			host: a.host,
1706			hostname: a.hostname,
1707			port: a.port,
1708			pathname: a.pathname.replace( /^([^/])/, "/$1" ), // Prefix pathname with a slash in browsers that don't natively do it (i.e. all versions of IE and possibly early versions of Edge). See pull request #8110.
1709			protocol: a.protocol,
1710			hash: a.hash,
1711			search: a.search,
1712
1713			// A collection of the parameters of the query string part of the URL.
1714			params: ( function() {
1715				var queryString = a.search.replace( /(^\?)/, "" );
1716				if ( !queryString ) {
1717					return {};
1718				}
1719				return queryString.split( "&" ).map( function( n ) {
1720					return ( n = n.split( "=" ), this[ n[ 0 ] ] = decodeURIComponent( n[ 1 ] ), this );
1721				}.bind( {} ) )[ 0 ];
1722			}() )
1723		};
1724	},
1725
1726	/**
1727	 * @variable seed
1728	 * @return a unique number for auto-generating ids
1729	 */
1730	seed = 0,
1731
1732	/**
1733	 * @variable $src
1734	 * @return {jQuery Element} of wb script element
1735	 */
1736	$src = $( "script[src*='wet-boew.js'],script[src*='wet-boew.min.js'],script[data-wb-core]" )
1737		.last(),
1738
1739	/**
1740	 * @variable i18n
1741	 * @return {string} of WET document language
1742	 */
1743	lang = ( function( ele ) {
1744		let lang = document.documentElement.lang;
1745		const shortLangLength = 2;
1746
1747		// Perform extra checks if the page uses a long language code
1748		if ( lang.length > shortLangLength ) {
1749			let longLangs = [ "pt-BR", "zh-Hans" ]; // Built-in long language codes
1750
1751			// Check if any custom long language codes have been specified
1752			// Specify by adding data-wb-core and data-lang-long="en-CA en-US etc" attributes to WET's script element (e.g. wet-boew.js or wet-boew.min.js)
1753			if ( ele[ 0 ].hasAttribute( "data-wb-core" ) &&  ele[ 0 ].hasAttribute( "data-lang-long" ) ) {
1754				const longLangsCustom = ele.attr( "data-lang-long" ).split( " " );
1755
1756				// Add extra language codes to the beginning of the longLangs array to match them more quickly
1757				longLangs = longLangsCustom.concat( longLangs );
1758			}
1759
1760			// Shorten the language code if it doesn't exist in the longLangs array
1761			if ( longLangs.indexOf( lang ) === -1 ) {
1762				lang = lang.substring( 0, shortLangLength );
1763			}
1764		}
1765
1766		return lang;
1767	}( $src ) ),
1768
1769	paths = ( function( ele ) {
1770		var paths = {};
1771
1772		paths.home = ele.prop( "src" )
1773			.split( "?" )[ 0 ].split( "/" )
1774			.slice( 0, -1 )
1775			.join( "/" );
1776		paths.asset = paths.home + "/../assets";
1777		paths.template = paths.home + "/assets/templates";
1778		paths.dep = paths.home + "/deps";
1779		paths.js = paths.home;
1780		paths.css = paths.home.substring( 0, paths.home.length - 2 ) + "css";
1781		paths.mode = ele.prop( "src" ).indexOf( ".min" ) < 0 ? "" : ".min";
1782
1783		if ( ele[ 0 ].hasAttribute( "data-wb-core" ) ) {
1784			$.extend( paths, {
1785				home: ele.attr( "data-home" ),
1786				asset: ele.attr( "data-asset" ),
1787				template: ele.attr( "data-template" ),
1788				dep: ele.attr( "data-dep" ),
1789				js: ele.attr( "data-js" ),
1790				css: ele.attr( "data-css" ),
1791				mode: ele.attr( "data-mode" )
1792			} );
1793		}
1794
1795		return paths;
1796	}( $src ) ),
1797
1798	/**
1799	 * @variable oldie
1800	 * @return {integer} of IE version
1801	 */
1802	oldie = ( function() {
1803		var undef,
1804			v = 3,
1805			div = document.createElement( "div" ),
1806			all = div.getElementsByTagName( "i" );
1807
1808		while ( (
1809			div.innerHTML = "<!--[if gt IE " + ( v += 1 ) + "]><i></i><![endif]-->",
1810			all[ 0 ]
1811		) ) { /* empty */ }
1812
1813		return v > 4 ? v : undef;
1814	}() ),
1815
1816	/**
1817	 * @variable currentpage
1818	 * @return {Object} of parts of the current page URL
1819	 */
1820	currentpage = getUrlParts( window.location.href ),
1821
1822	/**
1823	 * @variable disabled
1824	 * @return {boolean} of state of disabled flag
1825	 */
1826	disabled = ( function() {
1827		var disabledSaved = "false",
1828			disabled;
1829
1830		try {
1831			disabledSaved = localStorage.getItem( "wbdisable" ) || disabledSaved;
1832		} catch ( e ) {
1833
1834			/* swallow error */
1835		}
1836
1837		disabled = currentpage.params.wbdisable || disabledSaved;
1838		return ( typeof disabled === "string" ) ? ( disabled.toLowerCase() === "true" ) : Boolean( disabled );
1839	}() ),
1840
1841	/*-----------------------------
1842	 * Core Library Object
1843	 *-----------------------------
1844	 */
1845	wb = {
1846		"/": paths.home,
1847		"/assets": paths.asset,
1848		"/templates": paths.template,
1849		"/deps": paths.dep,
1850		lang: lang,
1851		mode: paths.mode,
1852		doc: $( document ),
1853		win: $( window ),
1854		html: $( "html" ),
1855		pageUrlParts: currentpage,
1856		getUrlParts: getUrlParts,
1857		isDisabled: disabled,
1858		isStarted: false,
1859		isReady: false,
1860		ignoreHashChange: false,
1861		initQueue: 0,
1862
1863		// Checks if the element <details> is supported natively.
1864		// Vérifier si l'élément <details> est supporté nativement.
1865		supportsDetails: function() {
1866			return "open" in document.createElement( "details" );
1867		},
1868
1869		getPath: function( property ) {
1870			return Object.prototype.hasOwnProperty.call( this, property ) ? this[ property ] : undef;
1871		},
1872
1873		getMode: function() {
1874			return this.mode;
1875		},
1876
1877		getId: function() {
1878			var idPrefix = "wb-auto-",
1879				ids,
1880				numberCandidate,
1881				numbers = [];
1882
1883			// Check for conflicting hardcoded IDs the first time an ID is requested
1884			if ( !seed ) {
1885				ids = document.querySelectorAll( "[id^='" + idPrefix + "']" );
1886
1887				// Loop through elements whose IDs begin with the prefix
1888				ids.forEach( function( currentElm ) {
1889					numberCandidate = currentElm.id.substring( idPrefix.length );
1890
1891					// Verify whether the ID ends with a conflicting number and add it to an array
1892					if ( numberCandidate.search( /^\d+$/ ) !== -1 ) {
1893						numbers.push( numberCandidate );
1894					}
1895
1896					console.error( "wb.getId: ID '" + currentElm.id + "' isn't supposed to be hardcoded in the page. Please remove it or change its prefix to something different than '" + idPrefix + "'." );
1897				} );
1898
1899				// Set the seed to the array's highest number (will be incremented later)
1900				seed = numbers.length ? Math.max.apply( null, numbers ) : seed;
1901			}
1902
1903			return idPrefix + ( seed += 1 );
1904		},
1905
1906		init: function( event, componentName, selector, noAutoId ) {
1907			var	eventTarget = event.target,
1908				isEvent = !!eventTarget,
1909				node = isEvent ? eventTarget : event,
1910				initedClass = componentName + "-inited",
1911				isDocumentNode = node === document;
1912
1913			// Filter out any events triggered by descendants and only initializes
1914			// the element once (if is an event and document node is not the target)
1915			if ( !isEvent || isDocumentNode || ( event.currentTarget === node &&
1916				node.className.indexOf( initedClass ) === -1 ) ) {
1917
1918				this.initQueue += 1;
1919				this.remove( selector );
1920				if ( !isDocumentNode ) {
1921					node.className += " " + initedClass;
1922
1923					if ( !noAutoId && !node.id ) {
1924						node.id = wb.getId();
1925					}
1926				}
1927
1928				return node;
1929			}
1930
1931			return undef;
1932		},
1933
1934		ready: function( $elm, componentName, context ) {
1935
1936			if ( $elm ) {
1937
1938				// Trigger any nested elements (excluding nested within nested)
1939				$elm
1940					.find( wb.allSelectors )
1941					.addClass( "wb-init" )
1942					.filter( ":not(#" + $elm.attr( "id" ) + " .wb-init .wb-init)" )
1943					.trigger( "timerpoke.wb" );
1944
1945				// Identify that the component is ready
1946				$elm.trigger( "wb-ready." + componentName, context );
1947
1948				this.initQueue -= 1;
1949			} else {
1950				this.doc.trigger( "wb-ready." + componentName, context );
1951			}
1952
1953			// Identify that global initialization is complete
1954			if ( !this.isReady && this.isStarted && this.initQueue < 1 ) {
1955
1956				// Create DOM event
1957				const DOMevent = new Event( "wet-boew-ready" );
1958
1959				this.isReady = true;
1960				this.doc.trigger( "wb-ready.wb" );
1961				this.doc[ 0 ].dispatchEvent( DOMevent ); // Trigger native DOM event
1962			}
1963		},
1964
1965		// Lets load some variables into wb for IE detection
1966		other: !oldie,
1967		desktop: ( window.orientation === undefined ),
1968		ie: !!oldie,
1969		ie6: ( oldie === 6 ),
1970		ie7: ( oldie === 7 ),
1971		ie8: ( oldie === 8 ),
1972		ie9: ( oldie === 9 ),
1973		ielt7: ( oldie < 7 ),
1974		ielt8: ( oldie < 8 ),
1975		ielt9: ( oldie < 9 ),
1976		ielt10: ( oldie < 10 ),
1977		ie11: ( navigator.userAgent.includes( "Trident/7." ) ),
1978
1979		selectors: [],
1980
1981		resizeEvents: "xxsmallview.wb xsmallview.wb smallview.wb mediumview.wb largeview.wb xlargeview.wb",
1982
1983		// For Charts and Geomap
1984		drawColours: [
1985			"#8d201c",
1986			"#EE8310",
1987			"#2a7da6",
1988			"#5a306b",
1989			"#285228",
1990			"#154055",
1991			"#555555",
1992			"#f6d200",
1993			"#d73d38",
1994			"#418541",
1995			"#87aec9",
1996			"#23447e",
1997			"#999999"
1998		],
1999
2000		// Get and generate a unique session id
2001		sessionGUID: function() {
2002			var sessionId = sessionStorage.getItem( "wb-session-GUID" );
2003			if ( !sessionId ) {
2004				sessionId = wb.guid();
2005				sessionStorage.setItem( "wb-session-GUID", sessionId );
2006			}
2007			return sessionId;
2008		},
2009
2010		// Add a selector to be targeted by timerpoke
2011		add: function( selector ) {
2012			var exists = false,
2013				len = wb.selectors.length,
2014				i;
2015
2016			// Lets ensure we are not running if things are disabled
2017			if ( wb.isDisabled && selector !== "#wb-tphp" ) {
2018				return 0;
2019			}
2020
2021			// Check to see if the selector is already targeted
2022			for ( i = 0; i !== len; i += 1 ) {
2023				if ( wb.selectors[ i ] === selector ) {
2024					exists = true;
2025					break;
2026				}
2027			}
2028
2029			// Add the selector if it isn't already targeted
2030			if ( !exists ) {
2031				wb.selectors.push( selector );
2032			}
2033		},
2034
2035		// Remove a selector targeted by timerpoke
2036		remove: function( selector ) {
2037			var len = this.selectors.length,
2038				i;
2039
2040			for ( i = 0; i !== len; i += 1 ) {
2041				if ( this.selectors[ i ] === selector ) {
2042					this.selectors.splice( i, 1 );
2043					break;
2044				}
2045			}
2046		},
2047
2048		// Handles triggering of timerpoke events
2049		timerpoke: function( initial ) {
2050			var selectorsLocal = wb.selectors.slice( 0 ),
2051				len = selectorsLocal.length,
2052				selector, $elms, $foundElms, i;
2053
2054			if ( initial ) {
2055				$foundElms = $();
2056				for ( i = 0; i !== len; i += 1 ) {
2057					selector = selectorsLocal[ i ];
2058					$elms = $( selector );
2059					if ( $elms.length !== 0 ) {
2060						$foundElms = $foundElms.add( $elms );
2061
2062					// If the selector returns no elements, remove the selector
2063					} else {
2064						wb.remove( selector );
2065					}
2066				}
2067
2068				// Keep only the non-nested plugin/polyfill elements
2069				$elms = $foundElms.filter( ":not(.wb-init .wb-init)" ).addClass( "wb-init" );
2070			} else {
2071				$elms = $( selectorsLocal.join( ", " ) );
2072			}
2073			$elms.trigger( "timerpoke.wb" );
2074		},
2075
2076		start: function() {
2077
2078			// Save a copy of all the possible selectors
2079			wb.allSelectors = wb.selectors.join( ", " );
2080
2081			// Initiate timerpoke events right way
2082			wb.timerpoke( true );
2083			this.isStarted = true;
2084			this.ready();
2085
2086			// Initiate timerpoke events again every half second
2087			setInterval( wb.timerpoke, 500 );
2088		},
2089
2090		i18nDict: {},
2091		i18n: function( key, state, mixin ) {
2092			var dictionary = wb.i18nDict,
2093
2094				// eg. 000 or 001 ie. 0 or 1
2095				truthiness = ( typeof key === "string" && key !== "" ) |
2096
2097					// eg. 000 or 010 ie. 0 or 2
2098					( typeof state === "string" && state !== "" ) << 1 |
2099
2100					// eg. 000 or 100 ie. 0 or 4
2101					( typeof mixin === "string" && mixin !== "" ) << 2;
2102
2103			switch ( truthiness ) {
2104				case 1:
2105
2106					// only key was provided
2107					return dictionary[ key ];
2108
2109				case 3:
2110
2111					// key and state were provided
2112					return dictionary[ key ][ state ];
2113
2114				case 7:
2115
2116					// key, state, and mixin were provided
2117					return dictionary[ key ][ state ].replace( "[MIXIN]", mixin );
2118				default:
2119					return "";
2120			}
2121		},
2122
2123		hashString: function( str ) {
2124
2125			// Sources:
2126			//	https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
2127			//	https://jsperf.com/hashing-strings
2128			var hash = 0,
2129				chr, i;
2130
2131			if ( str.length === 0 ) {
2132				return hash;
2133			}
2134
2135			for ( i = 0; i < str.length; i++ ) {
2136				chr = str.charCodeAt( i );
2137				hash = ( ( hash << 5 ) - hash ) + chr;
2138
2139				// Convert to 32bit integer
2140				hash = hash & hash;
2141			}
2142
2143			return hash;
2144		},
2145
2146		stripWhitespace: function( str ) {
2147			return str.replace( /\s+/g, "" );
2148		},
2149
2150		// Core function to deal with the dependency racing issue
2151		whenLibReady: function( testCallback, readyCallback ) {
2152			if ( testCallback() ) {
2153				readyCallback();
2154			} else {
2155				setTimeout( function() {
2156					wb.whenLibReady( testCallback, readyCallback );
2157				}, 50 );
2158			}
2159
2160		}
2161	};
2162
2163window.wb = wb;
2164
2165/*-----------------------------
2166 * Yepnope Prefixes
2167 *-----------------------------*/
2168/*
2169 * Establish the base path to be more flexible in terms of WCMS where JS can
2170 * reside in theme folders and not in the root of sites
2171 * @TODO: For modularity the prefixes where written independently as we are
2172 * flushing out some use cases on better grouping and optimization of polyfills.
2173 * Once this more hashed out, we could optimize the prefixes down to one or two
2174 * prefixes "site" and "disabled" to thin out the codeblock a bit more
2175 * increase performance due to redundant chaining of the prefixes.
2176 */
2177
2178/**
2179 * @prefix: site! - adds the root js directory of yepnope resources
2180 */
2181yepnope.addPrefix( "site", function( resourceObj ) {
2182	resourceObj.url = paths.js + "/" + resourceObj.url;
2183	return resourceObj;
2184} );
2185
2186/**
2187 * @prefix: plyfll! - builds the path for the polyfill resource
2188 */
2189yepnope.addPrefix( "plyfll", function( resourceObj ) {
2190	var path,
2191		url = resourceObj.url;
2192
2193	if ( disabled && url.indexOf( "svg" ) === -1 ) {
2194		resourceObj.bypass = true;
2195	} else if ( !paths.mode ) {
2196		url = url.replace( ".min", "" );
2197	}
2198
2199	if ( url.indexOf( ".css" ) !== -1 ) {
2200		resourceObj.forceCSS = true;
2201		path = paths.css;
2202	} else {
2203		path = paths.js;
2204	}
2205	resourceObj.url = path + "/polyfills/" + url;
2206
2207	return resourceObj;
2208} );
2209
2210/**
2211 * @prefix: i18n! - adds the correct document language for our i18n library
2212 */
2213yepnope.addPrefix( "i18n", function( resourceObj ) {
2214	resourceObj.url = paths.js + "/" + resourceObj.url + lang + paths.mode + ".js";
2215	return resourceObj;
2216} );
2217
2218/**
2219 * @prefix: mthjx! - adds the root directory of MathJax resources
2220 */
2221yepnope.addPrefix( "mthjx", function( resourceObj ) {
2222	resourceObj.url = paths.js + "/MathJax/" + resourceObj.url;
2223	return resourceObj;
2224} );
2225
2226/*-----------------------------
2227 * Deps loading, call "complete" callback when the deps is ready if a testReady is defined
2228 *-----------------------------*/
2229wb.modernizrLoad = Modernizr.load;
2230Modernizr.load = function( options ) {
2231	var i, i_len, i_cache,
2232		testReady, complete;
2233	if ( !Array.isArray( options ) ) {
2234		options = [ options ];
2235	}
2236	i_len = options.length;
2237	for ( i = 0; i !== i_len; i += 1 ) {
2238		i_cache = options[ i ];
2239		testReady = i_cache.testReady;
2240		complete = i_cache.complete;
2241		if ( testReady && complete ) {
2242			i_cache.complete = wb.whenLibReady( testReady, complete );
2243		}
2244	}
2245	wb.modernizrLoad( options );
2246};
2247
2248/*-----------------------------
2249 * Modernizr Polyfill Loading
2250 *-----------------------------*/
2251Modernizr.load( [
2252	{
2253		test: Modernizr.details,
2254		nope: [
2255			"plyfll!details.min.js",
2256			"plyfll!details.min.css"
2257		]
2258	}, {
2259		test: Modernizr.input.list,
2260		nope: [
2261			"plyfll!datalist.min.js",
2262			"plyfll!datalist.min.css"
2263		]
2264	}, {
2265		test: Modernizr.inputtypes.date,
2266		nope: [
2267			"plyfll!datepicker.min.js",
2268			"plyfll!datepicker.min.css"
2269		]
2270	}, {
2271		test: Modernizr.inputtypes.range,
2272		nope: [
2273			"plyfll!slider.min.js",
2274			"plyfll!slider_wrapper.min.js",
2275			"plyfll!slider.min.css"
2276		],
2277		callback: function( url ) {
2278			if ( url === "slider.min.js" ) {
2279				window.fdSlider.onDomReady();
2280			}
2281		}
2282	}, {
2283		test: Modernizr.progressbar,
2284		nope: [
2285			"plyfll!progress.min.js",
2286			"plyfll!progress.min.css"
2287		]
2288	}, {
2289		test: Modernizr.mathml,
2290
2291		// Cleanup Modernizr test and add selector to global timer
2292		complete: function() {
2293			var	componentName = "wb-math",
2294				selector = "math",
2295				math = document.getElementsByTagName( selector ),
2296				$document = wb.doc;
2297
2298			// Cleanup elements that Modernizr.mathml test leaves behind.
2299			if ( math.length ) {
2300				document.body.removeChild( math[ math.length - 1 ].parentNode );
2301			}
2302
2303			// Defer loading the polyfill till an element is detected due to the size
2304			if ( !Modernizr.mathml ) {
2305				let isTrident = new Boolean( window.navigator.msSaveOrOpenBlob );
2306
2307				// Bind the init event of the plugin
2308				$document.one( "timerpoke.wb wb-init." + componentName, selector, function() {
2309
2310					// Start initialization
2311					wb.init( document, componentName, selector );
2312
2313					// Disable MathJax's context menu to more closely mimic native MathML implementations
2314					window.MathJax = {
2315						options: {
2316							enableMenu: false
2317						}
2318					};
2319
2320					// Extra tasks for IE11
2321					if ( isTrident ) {
2322
2323						// Load an ES6 polyfill
2324						Modernizr.load( "timeout=500!https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6" );
2325
2326						// Specify the CDN's font URL
2327						// Note: IE11 is unable to resolve this on its own
2328						window.MathJax.chtml = {
2329							fontURL: "https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2"
2330						};
2331					}
2332
2333					// Load the MathML dependency. Since the polyfill is only loaded
2334					// when !Modernizr.mathml, we can skip the test here.
2335					Modernizr.load( [ {
2336
2337						// Load latest version of MathJax 3 from a CDN
2338						// Also load a CSS workaround for a MathJax 3.2.0 bug (refer to CSS file for details)
2339						load: [
2340							"timeout=500!https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js",
2341							"plyfll!mathml.min.css"
2342						],
2343						complete: function() {
2344
2345							// Wait a moment to reduce the risk of a race condition
2346							setTimeout( function() {
2347
2348								// Specify a font URL for a local copy of MathJax 3 for IE11
2349								// Note: Useful if IE11 has internet access but fails to reach the CDN
2350								if ( isTrident && !window.MathJax.startup ) {
2351									window.MathJax.chtml.fontURL = paths.js + "/MathJax/output/chtml/fonts/woff-v2";
2352								}
2353
2354								// Fall back on a local copy of MathJax 3 if the CDN is unreachable
2355								// Note: Won't work with IE11 in isolated networks (ES6 polyfill has no local fallback)
2356								Modernizr.load( [ {
2357									test: window.MathJax.startup,
2358									nope: "mthjx!mml-chtml.js",
2359									complete: function() {
2360
2361										// Try loading a local copy of MathJax 2 as a last ditch effort
2362										Modernizr.load( [ {
2363											test: window.MathJax.startup,
2364											nope: "mthjx!MathJax.js?config=Accessible",
2365											complete: function() {
2366
2367												// Identify that initialization has completed
2368												wb.ready( $document, componentName );
2369											}
2370										} ] );
2371									}
2372								} ] );
2373							}, 100 );
2374						}
2375					} ] );
2376				} );
2377
2378				wb.add( selector );
2379			}
2380		}
2381	}, {
2382		test: Modernizr.meter,
2383		nope: [
2384			"plyfll!meter.min.js",
2385			"plyfll!meter.min.css"
2386		]
2387	}, {
2388		test: Modernizr.touch,
2389		yep: "plyfll!mobile.min.js"
2390	}, {
2391		test: Modernizr.svg,
2392		nope: "plyfll!svg.min.js"
2393	}, {
2394		load: "i18n!i18n/",
2395		testReady: function() {
2396			return wb.i18nDict.tphp;
2397		},
2398		complete: function() {
2399			wb.start();
2400		}
2401	}
2402] );
2403
2404} )( jQuery, window, document );
2405
2406/**
2407 * @title WET-BOEW JQuery Helper Methods
2408 * @overview Helper methods for WET
2409 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
2410 * @author WET Community
2411 * Credits: https://web.archive.org/web/20130826230640/http://kaibun.net/blog/2013/04/19/a-fully-fledged-coffeescript-boilerplate-for-jquery-plugins/
2412 */
2413( function( $, wb ) {
2414
2415wb.getData = function( element, dataName ) {
2416	var elm = !element.jquery ? element : element[ 0 ],
2417		dataAttr = elm.getAttribute( "data-" + dataName ),
2418		dataObj;
2419
2420	if ( dataAttr ) {
2421		try {
2422			dataObj = JSON.parse( dataAttr );
2423			$.data( elm, dataName, dataObj );
2424		} catch ( error ) {
2425			console.info( elm );
2426			$.error( "Bad JSON array in data-" + dataName + " attribute" );
2427		}
2428	}
2429
2430	return dataObj;
2431};
2432
2433/*
2434 * Initiate an in-browser download from a blob
2435 * @param blob: a reference to a blob object
2436 * @param filename: a suggested file name to save as under
2437 * @param title: (Optional) a title added to the link. Its use case is for web analytics tracking.
2438*/
2439wb.download = function( blob, filename, title ) {
2440
2441	var objectURL = URL.createObjectURL( blob ),
2442		anchor = document.createElement( "a" );
2443
2444	filename = filename || "unnamed"; // Ensure a filename is defined
2445
2446	anchor.textContent = title || "";
2447	anchor.download = filename;
2448
2449	anchor.hidden = true;
2450	document.body.appendChild( anchor ); // Added to the body for the web analytic tracking use case.
2451
2452	if ( window.navigator.msSaveOrOpenBlob ) {
2453
2454		// This is for IE11 support
2455		anchor.addEventListener( "click", function( ) {
2456			window.navigator.msSaveOrOpenBlob( blob, filename );
2457		} );
2458		anchor.setAttribute( "target", "_blank" );
2459	} else {
2460		anchor.href = objectURL;
2461	}
2462
2463	anchor.click();
2464
2465	// Clean the DOM, remove the accessory anchor at the next tick
2466	setTimeout( function() {
2467		document.body.removeChild( anchor );
2468	}, 1 );
2469
2470	// Revoke the object, A setTimeout is used because Blob API don't have a download complete event.
2471	setTimeout( function() {
2472		if ( typeof objectURL === "string" ) {
2473			URL.revokeObjectURL( objectURL );
2474		} else {
2475			objectURL.remove();
2476		}
2477	}, 40000 ); // The revoking time is arbitrary
2478
2479};
2480
2481/* ---------------------------------
2482@extension: shuffleDOM
2483@returns: [list] shuffles a list of items randomly
2484-------------------------------- */
2485wb.shuffleDOM = function( $elm ) {
2486	var allElems = $elm.get(),
2487		shuffled = $.map( allElems, function() {
2488			var random = Math.floor( Math.random() * allElems.length ),
2489				randEl = $( allElems[ random ] ).clone( true )[ 0 ];
2490			allElems.splice( random, 1 );
2491			return randEl;
2492		} ),
2493		elm_len = $elm.length,
2494		i;
2495
2496	for ( i = 0; i < elm_len; i++ ) {
2497		$( $elm[ i ] ).replaceWith( $( shuffled[ i ] ) );
2498	}
2499
2500	return $( shuffled );
2501};
2502
2503/* ---------------------------------
2504@extension: pickElements
2505@returns: [collection] of random elements
2506-------------------------------- */
2507wb.pickElements = function( $elm, numOfElm ) {
2508	var nbElm = $elm.size(),
2509		elmCopies,
2510		i, swap;
2511
2512	numOfElm = numOfElm || 1;
2513
2514	// Special cases
2515	if ( numOfElm > nbElm ) {
2516		return $elm.pushStack( $elm );
2517	} else if ( numOfElm === 1 ) {
2518		return $elm.filter( ":eq(" + Math.floor( Math.random() * nbElm ) + ")" );
2519	}
2520
2521	// Create a randomized copy of the set of elements,
2522	// using Fisher-Yates sorting
2523	elmCopies = $elm.get();
2524
2525	for ( i = 0; i < nbElm - 1; i++ ) {
2526		swap = Math.floor( Math.random() * ( nbElm - i ) ) + i;
2527		elmCopies[ swap ] = elmCopies.splice( i, 1, elmCopies[ swap ] )[ 0 ];
2528	}
2529	elmCopies = elmCopies.slice( 0, numOfElm );
2530
2531	// Finally, filter jQuery stack
2532	return $elm.filter( function( idx ) {
2533		return $.inArray( $elm.get( idx ), elmCopies ) > -1;
2534	} );
2535};
2536
2537/* ---------------------------------
2538Adds a link to the Skip links navigation
2539@param text: Text to display in the anchor or button
2540@param attr: JSO with { attribute: value, ... } to add attributes to the anchor or button. Minimum is { href: "#your-anchor" } for the anchor tag
2541@param isBtn: (Optional) Bool if true element is a button, otherwise it is an anchor by default
2542@param isLast: (Optional) Bool if true element will be inserted last in the list
2543-------------------------------- */
2544wb.addSkipLink = function( text, attr, isBtn, isLast ) {
2545	var list = document.getElementById( "wb-tphp" ),
2546		li = document.createElement( "li" ),
2547		elm = document.createElement( ( isBtn ? "button" : "a" ) ),
2548		key;
2549
2550	// Add skip link's proprietary classes to new element
2551	li.className = "wb-slc";
2552	elm.className = "wb-sl";
2553
2554	// Add given attributes to element
2555	for ( key in attr ) {
2556		elm.setAttribute( key, attr[ key ] );
2557	}
2558
2559	// Append text and new element to the skip link list (after main content)
2560	elm.appendChild( document.createTextNode( text ) );
2561	li.appendChild( elm );
2562
2563	if ( isLast ) {
2564		list.appendChild( li );
2565	} else {
2566		list.insertBefore( li, list.childNodes[ 2 ] );
2567	}
2568
2569	return true;
2570};
2571
2572} )( jQuery, wb );
2573
2574( function( wb, window ) {
2575
2576"use strict";
2577
2578// Escapes the characters in a string for use in a jQuery selector
2579// Based on https://totaldev.com/content/escaping-characters-get-valid-jquery-id
2580wb.jqEscape = function( selector ) {
2581	// eslint-disable-next-line no-useless-escape
2582	return selector.replace( /([;&,\.\+\*\~':"\\\!\^\/#$%@\[\]\(\)=>\|])/g, "\\$1" );
2583};
2584
2585// RegEx used by formattedNumCompare
2586wb.formattedNumCompareRegEx = /(<[^>]*>|[^\d.])/g;
2587
2588// Compares two formatted numbers (e.g., 1.2.12 or 1,000,345)
2589wb.formattedNumCompare = function( a, b ) {
2590	var regEx = wb.formattedNumCompareRegEx,
2591		aMultiple = a.indexOf( "-" ) === -1 ? 1 : -1,
2592		aNumbers = ( ( a === "-" || a === "" ) ? "0" : a.replace( regEx, "" ) ).split( "." ),
2593		bMultiple = b.indexOf( "-" ) === -1 ? 1 : -1,
2594		bNumbers = ( ( b === "-" || b === "" ) ? "0" : b.replace( regEx, "" ) ).split( "." ),
2595		len = aNumbers.length,
2596		i, result;
2597
2598	for ( i = 0; i !== len; i += 1 ) {
2599		result = parseInt( aNumbers[ i ], 10 ) * aMultiple - parseInt( bNumbers[ i ], 10 ) * bMultiple;
2600		if ( result !== 0 ) {
2601			break;
2602		}
2603	}
2604	return result;
2605};
2606
2607// Compare two strings with special characters (e.g., Cyrillic or Chinese characters)
2608wb.i18nTextCompare = function( a, b ) {
2609	return wb.normalizeDiacritics( a ).localeCompare( wb.normalizeDiacritics( b ) );
2610};
2611
2612// Based upon https://gist.github.com/instanceofme/1731620
2613// Licensed under WTFPL v2 http://sam.zoy.org/wtfpl/COPYING
2614wb.normalizeDiacritics = function( str ) {
2615	var diacritics = {
2616			"\u24B6": "A",
2617			"\uFF21": "A",
2618			"\u00C0": "A",
2619			"\u00C1": "A",
2620			"\u00C2": "A",
2621			"\u1EA6": "A",
2622			"\u1EA4": "A",
2623			"\u1EAA": "A",
2624			"\u1EA8": "A",
2625			"\u00C3": "A",
2626			"\u0100": "A",
2627			"\u0102": "A",
2628			"\u1EB0": "A",
2629			"\u1EAE": "A",
2630			"\u1EB4": "A",
2631			"\u1EB2": "A",
2632			"\u0226": "A",
2633			"\u01E0": "A",
2634			"\u00C4": "A",
2635			"\u01DE": "A",
2636			"\u1EA2": "A",
2637			"\u00C5": "A",
2638			"\u01FA": "A",
2639			"\u01CD": "A",
2640			"\u0200": "A",
2641			"\u0202": "A",
2642			"\u1EA0": "A",
2643			"\u1EAC": "A",
2644			"\u1EB6": "A",
2645			"\u1E00": "A",
2646			"\u0104": "A",
2647			"\u023A": "A",
2648			"\u2C6F": "A",
2649			"\uA732": "AA",
2650			"\u00C6": "AE",
2651			"\u01FC": "AE",
2652			"\u01E2": "AE",
2653			"\uA734": "AO",
2654			"\uA736": "AU",
2655			"\uA738": "AV",
2656			"\uA73A": "AV",
2657			"\uA73C": "AY",
2658			"\u24B7": "B",
2659			"\uFF22": "B",
2660			"\u1E02": "B",
2661			"\u1E04": "B",
2662			"\u1E06": "B",
2663			"\u0243": "B",
2664			"\u0182": "B",
2665			"\u0181": "B",
2666			"\u24B8": "C",
2667			"\uFF23": "C",
2668			"\u0106": "C",
2669			"\u0108": "C",
2670			"\u010A": "C",
2671			"\u010C": "C",
2672			"\u00C7": "C",
2673			"\u1E08": "C",
2674			"\u0187": "C",
2675			"\u023B": "C",
2676			"\uA73E": "C",
2677			"\u24B9": "D",
2678			"\uFF24": "D",
2679			"\u1E0A": "D",
2680			"\u010E": "D",
2681			"\u1E0C": "D",
2682			"\u1E10": "D",
2683			"\u1E12": "D",
2684			"\u1E0E": "D",
2685			"\u0110": "D",
2686			"\u018B": "D",
2687			"\u018A": "D",
2688			"\u0189": "D",
2689			"\uA779": "D",
2690			"\u01F1": "DZ",
2691			"\u01C4": "DZ",
2692			"\u01F2": "Dz",
2693			"\u01C5": "Dz",
2694			"\u24BA": "E",
2695			"\uFF25": "E",
2696			"\u00C8": "E",
2697			"\u00C9": "E",
2698			"\u00CA": "E",
2699			"\u1EC0": "E",
2700			"\u1EBE": "E",
2701			"\u1EC4": "E",
2702			"\u1EC2": "E",
2703			"\u1EBC": "E",
2704			"\u0112": "E",
2705			"\u1E14": "E",
2706			"\u1E16": "E",
2707			"\u0114": "E",
2708			"\u0116": "E",
2709			"\u00CB": "E",
2710			"\u1EBA": "E",
2711			"\u011A": "E",
2712			"\u0204": "E",
2713			"\u0206": "E",
2714			"\u1EB8": "E",
2715			"\u1EC6": "E",
2716			"\u0228": "E",
2717			"\u1E1C": "E",
2718			"\u0118": "E",
2719			"\u1E18": "E",
2720			"\u1E1A": "E",
2721			"\u0190": "E",
2722			"\u018E": "E",
2723			"\u24BB": "F",
2724			"\uFF26": "F",
2725			"\u1E1E": "F",
2726			"\u0191": "F",
2727			"\uA77B": "F",
2728			"\u24BC": "G",
2729			"\uFF27": "G",
2730			"\u01F4": "G",
2731			"\u011C": "G",
2732			"\u1E20": "G",
2733			"\u011E": "G",
2734			"\u0120": "G",
2735			"\u01E6": "G",
2736			"\u0122": "G",
2737			"\u01E4": "G",
2738			"\u0193": "G",
2739			"\uA7A0": "G",
2740			"\uA77D": "G",
2741			"\uA77E": "G",
2742			"\u24BD": "H",
2743			"\uFF28": "H",
2744			"\u0124": "H",
2745			"\u1E22": "H",
2746			"\u1E26": "H",
2747			"\u021E": "H",
2748			"\u1E24": "H",
2749			"\u1E28": "H",
2750			"\u1E2A": "H",
2751			"\u0126": "H",
2752			"\u2C67": "H",
2753			"\u2C75": "H",
2754			"\uA78D": "H",
2755			"\u24BE": "I",
2756			"\uFF29": "I",
2757			"\u00CC": "I",
2758			"\u00CD": "I",
2759			"\u00CE": "I",
2760			"\u0128": "I",
2761			"\u012A": "I",
2762			"\u012C": "I",
2763			"\u0130": "I",
2764			"\u00CF": "I",
2765			"\u1E2E": "I",
2766			"\u1EC8": "I",
2767			"\u01CF": "I",
2768			"\u0208": "I",
2769			"\u020A": "I",
2770			"\u1ECA": "I",
2771			"\u012E": "I",
2772			"\u1E2C": "I",
2773			"\u0197": "I",
2774			"\u24BF": "J",
2775			"\uFF2A": "J",
2776			"\u0134": "J",
2777			"\u0248": "J",
2778			"\u24C0": "K",
2779			"\uFF2B": "K",
2780			"\u1E30": "K",
2781			"\u01E8": "K",
2782			"\u1E32": "K",
2783			"\u0136": "K",
2784			"\u1E34": "K",
2785			"\u0198": "K",
2786			"\u2C69": "K",
2787			"\uA740": "K",
2788			"\uA742": "K",
2789			"\uA744": "K",
2790			"\uA7A2": "K",
2791			"\u24C1": "L",
2792			"\uFF2C": "L",
2793			"\u013F": "L",
2794			"\u0139": "L",
2795			"\u013D": "L",
2796			"\u1E36": "L",
2797			"\u1E38": "L",
2798			"\u013B": "L",
2799			"\u1E3C": "L",
2800			"\u1E3A": "L",
2801			"\u0141": "L",
2802			"\u023D": "L",
2803			"\u2C62": "L",
2804			"\u2C60": "L",
2805			"\uA748": "L",
2806			"\uA746": "L",
2807			"\uA780": "L",
2808			"\u01C7": "LJ",
2809			"\u01C8": "Lj",
2810			"\u24C2": "M",
2811			"\uFF2D": "M",
2812			"\u1E3E": "M",
2813			"\u1E40": "M",
2814			"\u1E42": "M",
2815			"\u2C6E": "M",
2816			"\u019C": "M",
2817			"\u24C3": "N",
2818			"\uFF2E": "N",
2819			"\u01F8": "N",
2820			"\u0143": "N",
2821			"\u00D1": "N",
2822			"\u1E44": "N",
2823			"\u0147": "N",
2824			"\u1E46": "N",
2825			"\u0145": "N",
2826			"\u1E4A": "N",
2827			"\u1E48": "N",
2828			"\u0220": "N",
2829			"\u019D": "N",
2830			"\uA790": "N",
2831			"\uA7A4": "N",
2832			"\u01CA": "NJ",
2833			"\u01CB": "Nj",
2834			"\u24C4": "O",
2835			"\uFF2F": "O",
2836			"\u00D2": "O",
2837			"\u00D3": "O",
2838			"\u00D4": "O",
2839			"\u1ED2": "O",
2840			"\u1ED0": "O",
2841			"\u1ED6": "O",
2842			"\u1ED4": "O",
2843			"\u00D5": "O",
2844			"\u1E4C": "O",
2845			"\u022C": "O",
2846			"\u1E4E": "O",
2847			"\u014C": "O",
2848			"\u1E50": "O",
2849			"\u1E52": "O",
2850			"\u014E": "O",
2851			"\u022E": "O",
2852			"\u0230": "O",
2853			"\u00D6": "O",
2854			"\u022A": "O",
2855			"\u1ECE": "O",
2856			"\u0150": "O",
2857			"\u01D1": "O",
2858			"\u020C": "O",
2859			"\u020E": "O",
2860			"\u01A0": "O",
2861			"\u1EDC": "O",
2862			"\u1EDA": "O",
2863			"\u1EE0": "O",
2864			"\u1EDE": "O",
2865			"\u1EE2": "O",
2866			"\u1ECC": "O",
2867			"\u1ED8": "O",
2868			"\u01EA": "O",
2869			"\u01EC": "O",
2870			"\u00D8": "O",
2871			"\u01FE": "O",
2872			"\u0186": "O",
2873			"\u019F": "O",
2874			"\uA74A": "O",
2875			"\uA74C": "O",
2876			"\u0152": "OE",
2877			"\u01A2": "OI",
2878			"\uA74E": "OO",
2879			"\u0222": "OU",
2880			"\u24C5": "P",
2881			"\uFF30": "P",
2882			"\u1E54": "P",
2883			"\u1E56": "P",
2884			"\u01A4": "P",
2885			"\u2C63": "P",
2886			"\uA750": "P",
2887			"\uA752": "P",
2888			"\uA754": "P",
2889			"\u24C6": "Q",
2890			"\uFF31": "Q",
2891			"\uA756": "Q",
2892			"\uA758": "Q",
2893			"\u024A": "Q",
2894			"\u24C7": "R",
2895			"\uFF32": "R",
2896			"\u0154": "R",
2897			"\u1E58": "R",
2898			"\u0158": "R",
2899			"\u0210": "R",
2900			"\u0212": "R",
2901			"\u1E5A": "R",
2902			"\u1E5C": "R",
2903			"\u0156": "R",
2904			"\u1E5E": "R",
2905			"\u024C": "R",
2906			"\u2C64": "R",
2907			"\uA75A": "R",
2908			"\uA7A6": "R",
2909			"\uA782": "R",
2910			"\u24C8": "S",
2911			"\uFF33": "S",
2912			"\u015A": "S",
2913			"\u1E64": "S",
2914			"\u015C": "S",
2915			"\u1E60": "S",
2916			"\u0160": "S",
2917			"\u1E66": "S",
2918			"\u1E62": "S",
2919			"\u1E68": "S",
2920			"\u0218": "S",
2921			"\u015E": "S",
2922			"\u2C7E": "S",
2923			"\uA7A8": "S",
2924			"\uA784": "S",
2925			"\u1E9E": "SS",
2926			"\u24C9": "T",
2927			"\uFF34": "T",
2928			"\u1E6A": "T",
2929			"\u0164": "T",
2930			"\u1E6C": "T",
2931			"\u021A": "T",
2932			"\u0162": "T",
2933			"\u1E70": "T",
2934			"\u1E6E": "T",
2935			"\u0166": "T",
2936			"\u01AC": "T",
2937			"\u01AE": "T",
2938			"\u023E": "T",
2939			"\uA786": "T",
2940			"\uA728": "TZ",
2941			"\u24CA": "U",
2942			"\uFF35": "U",
2943			"\u00D9": "U",
2944			"\u00DA": "U",
2945			"\u00DB": "U",
2946			"\u0168": "U",
2947			"\u1E78": "U",
2948			"\u016A": "U",
2949			"\u1E7A": "U",
2950			"\u016C": "U",
2951			"\u00DC": "U",
2952			"\u01DB": "U",
2953			"\u01D7": "U",
2954			"\u01D5": "U",
2955			"\u01D9": "U",
2956			"\u1EE6": "U",
2957			"\u016E": "U",
2958			"\u0170": "U",
2959			"\u01D3": "U",
2960			"\u0214": "U",
2961			"\u0216": "U",
2962			"\u01AF": "U",
2963			"\u1EEA": "U",
2964			"\u1EE8": "U",
2965			"\u1EEE": "U",
2966			"\u1EEC": "U",
2967			"\u1EF0": "U",
2968			"\u1EE4": "U",
2969			"\u1E72": "U",
2970			"\u0172": "U",
2971			"\u1E76": "U",
2972			"\u1E74": "U",
2973			"\u0244": "U",
2974			"\u24CB": "V",
2975			"\uFF36": "V",
2976			"\u1E7C": "V",
2977			"\u1E7E": "V",
2978			"\u01B2": "V",
2979			"\uA75E": "V",
2980			"\u0245": "V",
2981			"\uA760": "VY",
2982			"\u24CC": "W",
2983			"\uFF37": "W",
2984			"\u1E80": "W",
2985			"\u1E82": "W",
2986			"\u0174": "W",
2987			"\u1E86": "W",
2988			"\u1E84": "W",
2989			"\u1E88": "W",
2990			"\u2C72": "W",
2991			"\u24CD": "X",
2992			"\uFF38": "X",
2993			"\u1E8A": "X",
2994			"\u1E8C": "X",
2995			"\u24CE": "Y",
2996			"\uFF39": "Y",
2997			"\u1EF2": "Y",
2998			"\u00DD": "Y",
2999			"\u0176": "Y",
3000			"\u1EF8": "Y",
3001			"\u0232": "Y",
3002			"\u1E8E": "Y",
3003			"\u0178": "Y",
3004			"\u1EF6": "Y",
3005			"\u1EF4": "Y",
3006			"\u01B3": "Y",
3007			"\u024E": "Y",
3008			"\u1EFE": "Y",
3009			"\u24CF": "Z",
3010			"\uFF3A": "Z",
3011			"\u0179": "Z",
3012			"\u1E90": "Z",
3013			"\u017B": "Z",
3014			"\u017D": "Z",
3015			"\u1E92": "Z",
3016			"\u1E94": "Z",
3017			"\u01B5": "Z",
3018			"\u0224": "Z",
3019			"\u2C7F": "Z",
3020			"\u2C6B": "Z",
3021			"\uA762": "Z",
3022			"\u24D0": "a",
3023			"\uFF41": "a",
3024			"\u1E9A": "a",
3025			"\u00E0": "a",
3026			"\u00E1": "a",
3027			"\u00E2": "a",
3028			"\u1EA7": "a",
3029			"\u1EA5": "a",
3030			"\u1EAB": "a",
3031			"\u1EA9": "a",
3032			"\u00E3": "a",
3033			"\u0101": "a",
3034			"\u0103": "a",
3035			"\u1EB1": "a",
3036			"\u1EAF": "a",
3037			"\u1EB5": "a",
3038			"\u1EB3": "a",
3039			"\u0227": "a",
3040			"\u01E1": "a",
3041			"\u00E4": "a",
3042			"\u01DF": "a",
3043			"\u1EA3": "a",
3044			"\u00E5": "a",
3045			"\u01FB": "a",
3046			"\u01CE": "a",
3047			"\u0201": "a",
3048			"\u0203": "a",
3049			"\u1EA1": "a",
3050			"\u1EAD": "a",
3051			"\u1EB7": "a",
3052			"\u1E01": "a",
3053			"\u0105": "a",
3054			"\u2C65": "a",
3055			"\u0250": "a",
3056			"\uA733": "aa",
3057			"\u00E6": "ae",
3058			"\u01FD": "ae",
3059			"\u01E3": "ae",
3060			"\uA735": "ao",
3061			"\uA737": "au",
3062			"\uA739": "av",
3063			"\uA73B": "av",
3064			"\uA73D": "ay",
3065			"\u24D1": "b",
3066			"\uFF42": "b",
3067			"\u1E03": "b",
3068			"\u1E05": "b",
3069			"\u1E07": "b",
3070			"\u0180": "b",
3071			"\u0183": "b",
3072			"\u0253": "b",
3073			"\u24D2": "c",
3074			"\uFF43": "c",
3075			"\u0107": "c",
3076			"\u0109": "c",
3077			"\u010B": "c",
3078			"\u010D": "c",
3079			"\u00E7": "c",
3080			"\u1E09": "c",
3081			"\u0188": "c",
3082			"\u023C": "c",
3083			"\uA73F": "c",
3084			"\u2184": "c",
3085			"\u24D3": "d",
3086			"\uFF44": "d",
3087			"\u1E0B": "d",
3088			"\u010F": "d",
3089			"\u1E0D": "d",
3090			"\u1E11": "d",
3091			"\u1E13": "d",
3092			"\u1E0F": "d",
3093			"\u0111": "d",
3094			"\u018C": "d",
3095			"\u0256": "d",
3096			"\u0257": "d",
3097			"\uA77A": "d",
3098			"\u01F3": "dz",
3099			"\u01C6": "dz",
3100			"\u24D4": "e",
3101			"\uFF45": "e",
3102			"\u00E8": "e",
3103			"\u00E9": "e",
3104			"\u00EA": "e",
3105			"\u1EC1": "e",
3106			"\u1EBF": "e",
3107			"\u1EC5": "e",
3108			"\u1EC3": "e",
3109			"\u1EBD": "e",
3110			"\u0113": "e",
3111			"\u1E15": "e",
3112			"\u1E17": "e",
3113			"\u0115": "e",
3114			"\u0117": "e",
3115			"\u00EB": "e",
3116			"\u1EBB": "e",
3117			"\u011B": "e",
3118			"\u0205": "e",
3119			"\u0207": "e",
3120			"\u1EB9": "e",
3121			"\u1EC7": "e",
3122			"\u0229": "e",
3123			"\u1E1D": "e",
3124			"\u0119": "e",
3125			"\u1E19": "e",
3126			"\u1E1B": "e",
3127			"\u0247": "e",
3128			"\u025B": "e",
3129			"\u01DD": "e",
3130			"\u24D5": "f",
3131			"\uFF46": "f",
3132			"\u1E1F": "f",
3133			"\u0192": "f",
3134			"\uA77C": "f",
3135			"\u24D6": "g",
3136			"\uFF47": "g",
3137			"\u01F5": "g",
3138			"\u011D": "g",
3139			"\u1E21": "g",
3140			"\u011F": "g",
3141			"\u0121": "g",
3142			"\u01E7": "g",
3143			"\u0123": "g",
3144			"\u01E5": "g",
3145			"\u0260": "g",
3146			"\uA7A1": "g",
3147			"\u1D79": "g",
3148			"\uA77F": "g",
3149			"\u24D7": "h",
3150			"\uFF48": "h",
3151			"\u0125": "h",
3152			"\u1E23": "h",
3153			"\u1E27": "h",
3154			"\u021F": "h",
3155			"\u1E25": "h",
3156			"\u1E29": "h",
3157			"\u1E2B": "h",
3158			"\u1E96": "h",
3159			"\u0127": "h",
3160			"\u2C68": "h",
3161			"\u2C76": "h",
3162			"\u0265": "h",
3163			"\u0195": "hv",
3164			"\u24D8": "i",
3165			"\uFF49": "i",
3166			"\u00EC": "i",
3167			"\u00ED": "i",
3168			"\u00EE": "i",
3169			"\u0129": "i",
3170			"\u012B": "i",
3171			"\u012D": "i",
3172			"\u00EF": "i",
3173			"\u1E2F": "i",
3174			"\u1EC9": "i",
3175			"\u01D0": "i",
3176			"\u0209": "i",
3177			"\u020B": "i",
3178			"\u1ECB": "i",
3179			"\u012F": "i",
3180			"\u1E2D": "i",
3181			"\u0268": "i",
3182			"\u0131": "i",
3183			"\u24D9": "j",
3184			"\uFF4A": "j",
3185			"\u0135": "j",
3186			"\u01F0": "j",
3187			"\u0249": "j",
3188			"\u24DA": "k",
3189			"\uFF4B": "k",
3190			"\u1E31": "k",
3191			"\u01E9": "k",
3192			"\u1E33": "k",
3193			"\u0137": "k",
3194			"\u1E35": "k",
3195			"\u0199": "k",
3196			"\u2C6A": "k",
3197			"\uA741": "k",
3198			"\uA743": "k",
3199			"\uA745": "k",
3200			"\uA7A3": "k",
3201			"\u24DB": "l",
3202			"\uFF4C": "l",
3203			"\u0140": "l",
3204			"\u013A": "l",
3205			"\u013E": "l",
3206			"\u1E37": "l",
3207			"\u1E39": "l",
3208			"\u013C": "l",
3209			"\u1E3D": "l",
3210			"\u1E3B": "l",
3211			"\u0142": "l",
3212			"\u019A": "l",
3213			"\u026B": "l",
3214			"\u2C61": "l",
3215			"\uA749": "l",
3216			"\uA781": "l",
3217			"\uA747": "l",
3218			"\u01C9": "lj",
3219			"\u24DC": "m",
3220			"\uFF4D": "m",
3221			"\u1E3F": "m",
3222			"\u1E41": "m",
3223			"\u1E43": "m",
3224			"\u0271": "m",
3225			"\u026F": "m",
3226			"\u24DD": "n",
3227			"\uFF4E": "n",
3228			"\u01F9": "n",
3229			"\u0144": "n",
3230			"\u00F1": "n",
3231			"\u1E45": "n",
3232			"\u0148": "n",
3233			"\u1E47": "n",
3234			"\u0146": "n",
3235			"\u1E4B": "n",
3236			"\u1E49": "n",
3237			"\u019E": "n",
3238			"\u0272": "n",
3239			"\u0149": "n",
3240			"\uA791": "n",
3241			"\uA7A5": "n",
3242			"\u01CC": "nj",
3243			"\u24DE": "o",
3244			"\uFF4F": "o",
3245			"\u00F2": "o",
3246			"\u00F3": "o",
3247			"\u00F4": "o",
3248			"\u1ED3": "o",
3249			"\u1ED1": "o",
3250			"\u1ED7": "o",
3251			"\u1ED5": "o",
3252			"\u00F5": "o",
3253			"\u1E4D": "o",
3254			"\u022D": "o",
3255			"\u1E4F": "o",
3256			"\u014D": "o",
3257			"\u1E51": "o",
3258			"\u1E53": "o",
3259			"\u014F": "o",
3260			"\u022F": "o",
3261			"\u0231": "o",
3262			"\u00F6": "o",
3263			"\u022B": "o",
3264			"\u1ECF": "o",
3265			"\u0151": "o",
3266			"\u01D2": "o",
3267			"\u020D": "o",
3268			"\u020F": "o",
3269			"\u01A1": "o",
3270			"\u1EDD": "o",
3271			"\u1EDB": "o",
3272			"\u1EE1": "o",
3273			"\u1EDF": "o",
3274			"\u1EE3": "o",
3275			"\u1ECD": "o",
3276			"\u1ED9": "o",
3277			"\u01EB": "o",
3278			"\u01ED": "o",
3279			"\u00F8": "o",
3280			"\u01FF": "o",
3281			"\u0254": "o",
3282			"\uA74B": "o",
3283			"\uA74D": "o",
3284			"\u0275": "o",
3285			"\u0153": "oe",
3286			"\u0276": "oe",
3287			"\u01A3": "oi",
3288			"\u0223": "ou",
3289			"\uA74F": "oo",
3290			"\u24DF": "p",
3291			"\uFF50": "p",
3292			"\u1E55": "p",
3293			"\u1E57": "p",
3294			"\u01A5": "p",
3295			"\u1D7D": "p",
3296			"\uA751": "p",
3297			"\uA753": "p",
3298			"\uA755": "p",
3299			"\u24E0": "q",
3300			"\uFF51": "q",
3301			"\u024B": "q",
3302			"\uA757": "q",
3303			"\uA759": "q",
3304			"\u24E1": "r",
3305			"\uFF52": "r",
3306			"\u0155": "r",
3307			"\u1E59": "r",
3308			"\u0159": "r",
3309			"\u0211": "r",
3310			"\u0213": "r",
3311			"\u1E5B": "r",
3312			"\u1E5D": "r",
3313			"\u0157": "r",
3314			"\u1E5F": "r",
3315			"\u024D": "r",
3316			"\u027D": "r",
3317			"\uA75B": "r",
3318			"\uA7A7": "r",
3319			"\uA783": "r",
3320			"\u24E2": "s",
3321			"\uFF53": "s",
3322			"\u015B": "s",
3323			"\u1E65": "s",
3324			"\u015D": "s",
3325			"\u1E61": "s",
3326			"\u0161": "s",
3327			"\u1E67": "s",
3328			"\u1E63": "s",
3329			"\u1E69": "s",
3330			"\u0219": "s",
3331			"\u015F": "s",
3332			"\u023F": "s",
3333			"\uA7A9": "s",
3334			"\uA785": "s",
3335			"\u017F": "s",
3336			"\u1E9B": "s",
3337			"\u00DF": "ss",
3338			"\u24E3": "t",
3339			"\uFF54": "t",
3340			"\u1E6B": "t",
3341			"\u1E97": "t",
3342			"\u0165": "t",
3343			"\u1E6D": "t",
3344			"\u021B": "t",
3345			"\u0163": "t",
3346			"\u1E71": "t",
3347			"\u1E6F": "t",
3348			"\u0167": "t",
3349			"\u01AD": "t",
3350			"\u0288": "t",
3351			"\u2C66": "t",
3352			"\uA787": "t",
3353			"\uA729": "tz",
3354			"\u24E4": "u",
3355			"\uFF55": "u",
3356			"\u00F9": "u",
3357			"\u00FA": "u",
3358			"\u00FB": "u",
3359			"\u0169": "u",
3360			"\u1E79": "u",
3361			"\u016B": "u",
3362			"\u1E7B": "u",
3363			"\u016D": "u",
3364			"\u00FC": "u",
3365			"\u01DC": "u",
3366			"\u01D8": "u",
3367			"\u01D6": "u",
3368			"\u01DA": "u",
3369			"\u1EE7": "u",
3370			"\u016F": "u",
3371			"\u0171": "u",
3372			"\u01D4": "u",
3373			"\u0215": "u",
3374			"\u0217": "u",
3375			"\u01B0": "u",
3376			"\u1EEB": "u",
3377			"\u1EE9": "u",
3378			"\u1EEF": "u",
3379			"\u1EED": "u",
3380			"\u1EF1": "u",
3381			"\u1EE5": "u",
3382			"\u1E73": "u",
3383			"\u0173": "u",
3384			"\u1E77": "u",
3385			"\u1E75": "u",
3386			"\u0289": "u",
3387			"\u24E5": "v",
3388			"\uFF56": "v",
3389			"\u1E7D": "v",
3390			"\u1E7F": "v",
3391			"\u028B": "v",
3392			"\uA75F": "v",
3393			"\u028C": "v",
3394			"\uA761": "vy",
3395			"\u24E6": "w",
3396			"\uFF57": "w",
3397			"\u1E81": "w",
3398			"\u1E83": "w",
3399			"\u0175": "w",
3400			"\u1E87": "w",
3401			"\u1E85": "w",
3402			"\u1E98": "w",
3403			"\u1E89": "w",
3404			"\u2C73": "w",
3405			"\u24E7": "x",
3406			"\uFF58": "x",
3407			"\u1E8B": "x",
3408			"\u1E8D": "x",
3409			"\u24E8": "y",
3410			"\uFF59": "y",
3411			"\u1EF3": "y",
3412			"\u00FD": "y",
3413			"\u0177": "y",
3414			"\u1EF9": "y",
3415			"\u0233": "y",
3416			"\u1E8F": "y",
3417			"\u00FF": "y",
3418			"\u1EF7": "y",
3419			"\u1E99": "y",
3420			"\u1EF5": "y",
3421			"\u01B4": "y",
3422			"\u024F": "y",
3423			"\u1EFF": "y",
3424			"\u24E9": "z",
3425			"\uFF5A": "z",
3426			"\u017A": "z",
3427			"\u1E91": "z",
3428			"\u017C": "z",
3429			"\u017E": "z",
3430			"\u1E93": "z",
3431			"\u1E95": "z",
3432			"\u01B6": "z",
3433			"\u0225": "z",
3434			"\u0240": "z",
3435			"\u2C6C": "z",
3436			"\uA763": "z",
3437			"\uFF10": "0",
3438			"\u2080": "0",
3439			"\u24EA": "0",
3440			"\u2070": "0",
3441			"\u00B9": "1",
3442			"\u2474": "1",
3443			"\u2081": "1",
3444			"\u2776": "1",
3445			"\u24F5": "1",
3446			"\u2488": "1",
3447			"\u2460": "1",
3448			"\uFF11": "1",
3449			"\u00B2": "2",
3450			"\u2777": "2",
3451			"\u2475": "2",
3452			"\uFF12": "2",
3453			"\u2082": "2",
3454			"\u24F6": "2",
3455			"\u2461": "2",
3456			"\u2489": "2",
3457			"\u00B3": "3",
3458			"\uFF13": "3",
3459			"\u248A": "3",
3460			"\u2476": "3",
3461			"\u2083": "3",
3462			"\u2778": "3",
3463			"\u24F7": "3",
3464			"\u2462": "3",
3465			"\u24F8": "4",
3466			"\u2463": "4",
3467			"\u248B": "4",
3468			"\uFF14": "4",
3469			"\u2074": "4",
3470			"\u2084": "4",
3471			"\u2779": "4",
3472			"\u2477": "4",
3473			"\u248C": "5",
3474			"\u2085": "5",
3475			"\u24F9": "5",
3476			"\u2478": "5",
3477			"\u277A": "5",
3478			"\u2464": "5",
3479			"\uFF15": "5",
3480			"\u2075": "5",
3481			"\u2479": "6",
3482			"\u2076": "6",
3483			"\uFF16": "6",
3484			"\u277B": "6",
3485			"\u2086": "6",
3486			"\u2465": "6",
3487			"\u24FA": "6",
3488			"\u248D": "6",
3489			"\uFF17": "7",
3490			"\u2077": "7",
3491			"\u277C": "7",
3492			"\u24FB": "7",
3493			"\u248E": "7",
3494			"\u2087": "7",
3495			"\u247A": "7",
3496			"\u2466": "7",
3497			"\u2467": "8",
3498			"\u248F": "8",
3499			"\u24FC": "8",
3500			"\u247B": "8",
3501			"\u2078": "8",
3502			"\uFF18": "8",
3503			"\u277D": "8",
3504			"\u2088": "8",
3505			"\u24FD": "9",
3506			"\uFF19": "9",
3507			"\u2490": "9",
3508			"\u277E": "9",
3509			"\u247C": "9",
3510			"\u2089": "9",
3511			"\u2468": "9",
3512			"\u2079": "9"
3513		},
3514		chars = str.split( "" ),
3515		len = chars.length,
3516		normalized = false,
3517		i, character;
3518	for ( i = 0; i !== len; i += 1 ) {
3519		character = chars[ i ];
3520		if ( Object.prototype.hasOwnProperty.call( diacritics, character ) ) {
3521			chars[ i ] = diacritics[ character ];
3522			normalized = true;
3523		}
3524	}
3525	return ( normalized ? chars.join( "" ) : str );
3526};
3527
3528/**
3529 * @namespace wb.string
3530 */
3531wb.string = {
3532
3533	/*
3534	 * Left-pads a number with zeros.
3535	 * @memberof wb.string
3536	 * @param {number} number The original number to pad.
3537	 * @param {number} length The width of the resulting padded number, not the number of zeros to add to the front of the string.
3538	 * @return {string} The padded string
3539	 */
3540	pad: function( number, length ) {
3541		var str = number + "",
3542			diff = length - str.length,
3543			i;
3544		for ( i = 0; i !== diff; i += 1 ) {
3545			str = "0" + str;
3546		}
3547		return str;
3548	},
3549
3550	/*
3551	 * Convert a base64 string into an ArrayBuffer (Note: this function are not fully UTF-8 supported and may create interoperability issue)
3552	 * ref. https://www.isummation.com/blog/convert-arraybuffer-to-base64-string-and-vice-versa/
3553	 * @memberof wb.string
3554	 * @param {string} Base64 browser encoded
3555	 * @return {ArrayBuffer} string converted into ArrayBuffer
3556	 */
3557	base64ToArrayBuffer: function( base64 ) {
3558		var binary_string = window.atob( base64 ),
3559			len = binary_string.length,
3560			bytes = new Uint8Array( len ),
3561			i;
3562		for ( i = 0; i < len; i++ ) {
3563			bytes[ i ] = binary_string.charCodeAt( i );
3564		}
3565		return bytes.buffer;
3566	},
3567
3568	/*
3569	 * Convert an ArrayBuffer into base64 string (Note: this function are not fully UTF-8 supported and may create interoperability issue)
3570	 * ref. https://www.isummation.com/blog/convert-arraybuffer-to-base64-string-and-vice-versa/
3571	 * @memberof wb.string
3572	 * @param {ArrayBuffer}
3573	 * @return {string} ArrayBuffer converted into base64 string
3574	 */
3575	arrayBufferToBase64: function( buffer ) {
3576		var binary = "",
3577			bytes = new Uint8Array( buffer ),
3578			len = bytes.byteLength,
3579			i;
3580		for ( i = 0; i < len; i++ ) {
3581			binary += String.fromCharCode( bytes[ i ] );
3582		}
3583		return window.btoa( binary );
3584	},
3585
3586	/*
3587	 * Convert an hexadecimal string into an ArrayBuffer
3588	 * ref. https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript/50868276#50868276
3589	 * @memberof wb.string
3590	 * @param {string} Encoded string in hexadecimal
3591	 * @return {Uint8Array} Binary array buffer
3592	 */
3593	fromHexString: function( hexString ) {
3594		return hexString === null ? null : Uint8Array.from( hexString.match( /.{1,2}/g ).map( function( byte ) {
3595			return parseInt( byte, 16 );
3596		} ) );
3597	},
3598
3599	/*
3600	 * Convert an ArrayBuffer into an hexadecimal string
3601	 * ref. https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript/50868276#50868276
3602	 * @memberof wb.string
3603	 * @param {Uint8Array} Binary array buffer
3604	 * @return {string} Encoded string in hexadecimal
3605	 */
3606	toHexString: function( bytes ) {
3607		return bytes.reduce( function( str, byte ) {
3608			return str + byte.toString( 16 ).padStart( 2, "0" );
3609		}, "" );
3610	}
3611
3612};
3613
3614/*
3615 * A suite of date related functions for easier parsing of dates
3616 * @namespace wb.date
3617 */
3618wb.date = {
3619
3620	/*
3621	 * Converts the date to a date-object. The input can be:
3622	 * <ul>
3623	 * <li>a Date object: returned without modification.</li>
3624	 * <li>an array: Interpreted as [year,month,day]. NOTE: month is 0-11.</li>
3625	 * <li>a number: Interpreted as number of milliseconds since 1 Jan 1970 (a timestamp).</li>
3626	 * <li>a string: Any format supported by the javascript engine, like 'YYYY/MM/DD', 'MM/DD/YYYY', 'Jan 31 2009' etc.</li>
3627	 * <li>an object: Interpreted as an object with year, month and date attributes. **NOTE** month is 0-11.</li>
3628	 * </ul>
3629	 * @memberof wb.date
3630	 * @param {Date | number[] | number | string | object} dateValue
3631	 * @return {Date | NaN}
3632	 */
3633	convert: function( dateValue ) {
3634		var dateConstructor = dateValue.constructor;
3635
3636		switch ( dateConstructor ) {
3637			case Date:
3638				return dateConstructor;
3639			case Array:
3640				return new Date( dateValue[ 0 ], dateValue[ 1 ], dateValue[ 2 ] );
3641			case Number:
3642			case String:
3643				return new Date( dateValue );
3644			default:
3645				return typeof dateValue === "object" ? new Date( dateValue.year, dateValue.month, dateValue.date ) : NaN;
3646		}
3647	},
3648
3649	/*
3650	 * Compares two dates (input can be any type supported by the convert function).
3651	 * @memberof wb.date
3652	 * @param {Date | number[] | number | string | object} dateValue1
3653	 * @param {Date | number[] | number | string | object} dateValue2
3654	 * @return {number | NaN}
3655	 * @example returns
3656	 * -1 if dateValue1 < dateValue2
3657	 * 0 if dateValue1 = dateValue2
3658	 * 1 if dateValue1 > dateValue2
3659	 * NaN if dateValue1 or dateValue2 is an illegal date
3660	 */
3661	compare: function( dateValue1, dateValue2 ) {
3662		var convert = wb.date.convert;
3663
3664		if ( isFinite( dateValue1 = convert( dateValue1 ).valueOf() ) && isFinite( dateValue2 = convert( dateValue2 ).valueOf() ) ) {
3665			return ( dateValue1 > dateValue2 ) - ( dateValue1 < dateValue2 );
3666		}
3667		return NaN;
3668	},
3669
3670	/*
3671	 * Cross-browser safe way of translating a date to ISO format
3672	 * @memberof wb.date
3673	 * @param {Date | number[] | number | string | object} dateValue
3674	 * @param {boolean} withTime Optional. Whether to include the time in the result, or just the date. False if blank.
3675	 * @return {string}
3676	 * @example
3677	 * toDateISO( new Date() )
3678	 * returns "2012-04-27"
3679	 * toDateISO( new Date(), true )
3680	 * returns "2012-04-27 13:46"
3681	 */
3682	toDateISO: function( dateValue, withTime ) {
3683		var date = wb.date.convert( dateValue ),
3684			pad = wb.string.pad;
3685
3686		return date.getFullYear() + "-" + pad( date.getMonth() + 1, 2, "0" ) + "-" + pad( date.getDate(), 2, "0" ) +
3687			( withTime ? " " + pad( date.getHours(), 2, "0" ) + ":" + pad( date.getMinutes(), 2, "0" ) : "" );
3688	},
3689
3690	/*
3691	 * Cross-browser safe way of creating a date object from a date string in ISO format
3692	 * @memberof wb.date
3693	 * @param {string} dateISO Date string in ISO format
3694	 * @return {Date}
3695	 */
3696	fromDateISO: function( dateISO ) {
3697		var date = null;
3698
3699		if ( dateISO && /\d{4}-\d{2}-\d{2}/.test( dateISO ) ) {
3700			date = new Date( dateISO.substr( 0, 4 ), dateISO.substr( 5, 2 ) - 1, dateISO.substr( 8, 2 ), 0, 0, 0, 0 );
3701		}
3702		return date;
3703	}
3704};
3705
3706/*
3707 * Returns a RFC4122 compliant Global Unique ID (GUID).
3708 * Originally from https://stackoverflow.com/a/2117523/455535
3709 */
3710wb.guid = function() {
3711	return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function( replacementChar ) {
3712		var rand = Math.random() * 16 | 0,
3713			newChar = replacementChar === "x" ? rand : ( rand & 0x3 | 0x8 );
3714		return newChar.toString( 16 );
3715	} );
3716};
3717
3718wb.escapeAttribute = function( str ) {
3719	return str.replace( /'/g, "&#39;" ).replace( /"/g, "&#34;" );
3720};
3721
3722/*
3723 * Returns an escaped HTML string
3724 */
3725wb.escapeHTML = function( str ) {
3726	return wb.escapeAttribute( str
3727		.replace( /&/g, "&#38;" )
3728		.replace( /</g, "&#60;" )
3729		.replace( />/g, "&#62;" ) );
3730};
3731
3732/*
3733 * Returns a UTF-8 output from Base64
3734 * Reference: https://developer.mozilla.org/fr/docs/Glossary/Base64 (To be reviewed later because escape function is deprecated)
3735 */
3736wb.decodeUTF8Base64 = function( str ) {
3737	return decodeURIComponent( escape( atob( str ) ) );
3738};
3739
3740/*
3741* Find most common Personal Identifiable Information (PII) in a string and return either the cleaned string either true/false
3742* @param {string} str (required) - the content that needs to be verified
3743*
3744* @param {boolean} scope - if true will scrub the content
3745* @param {object} (optional) the 2nd param (scope) can also be an object having the following properties (optional):
3746* 	{string} any key name of the default patterns e.g. email, digits, etc. with the value 1. The function will only scrub the content that match the regex of the default patterns passed in this object
3747* 	{regex} customCase - this param is a regex. It will search and replace the values corresponding that pattern
3748*
3749* @param {object} opts (optional) - the 3rd param of the function that can contain the following properties (optional):
3750* 	{boolean} isCustomExclusive - if true, it will scrub only the custom regex if the regex is the only property of the "scope" object
3751* 	{boolean} useFullBlock - if true, it will replace the scrubbed characters with the "█" symbol;
3752* 	{string} replaceWith - this string will replace the scrubbed content
3753*
3754
3755* @return {string | true | false}
3756* @example
3757* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", true )
3758* returns "email:, phone:",
3759*
3760* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", false )
3761* returns true
3762*
3763* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", { email:1 }{ replaceWith: [REDACTED/CAVIARDÉ] } )
3764* returns "email:[REDACTED/CAVIARDÉ], phone:123 123 1234"
3765*
3766* wb.findPotentialPII( "email:[email protected], phone:123 123 1234, numéro de cas 12345678", { "customCase":/\b(?:case[\s-]?number[\s\-\\.]?(?:\d{5,10}))|(?:numéro[\s-]?de[\s-]?cas[\s\-\\.]?(?:\d{5,10}))/ig }, { useFullBlock:1})
3767* returns "phone:████████████, email:█████████████, postalCode:██████, ██████████████████████"
3768*/
3769wb.findPotentialPII = function( str, scope, opts ) {
3770	if ( str && typeof str  !== "string" ) {
3771		return false;
3772	}
3773	var oRegEx = {
3774
3775			/*
3776			* Digits:
3777			* 9 digits or more
3778			*/
3779			digits: /\d(?:[\s\-\\.\\/]?\d){8,}(?!\d)/ig,
3780
3781			/*
3782			* Phone:
3783			* Any international phone number format
3784			*/
3785			phone: /\+?(\d{1,3})?[-._\s]?(\(?\d{3}\)?)[-._\s]?(\d{3})[-._\s]?(\d{4})/ig,
3786
3787			/*
3788			* Passport:
3789			* 2 letters followed by either a " ", a "/", a ".", or a "-" any amount of times, followed by 6 digits
3790			*/
3791			passport: /\b[A-Za-z]{2}[\s\\.-]*?\d{6}\b/ig,
3792
3793			/*
3794			* Email:
3795			* valid email format
3796			*/
3797			email: /\b(?:[a-zA-Z0-9_\-\\.]+)(?:@|%40|%2540)(?:[a-zA-Z0-9_\-\\.]+)\.(?:[a-zA-Z]{2,5})\b/ig,
3798
3799			/*
3800			* Loose email:
3801			* email address that has one or more whitespaces before the "@" sign and either a "." or "," after the domain name
3802			*/
3803			looseEmail: /([a-zA-Z0-9_\-.]+)\s*@([\sa-zA-Z0-9_\-.]+)[.,]([a-zA-Z]{1,5})/g,
3804
3805			/*
3806			* Loose email 2:
3807			* matches probable email format that the user tried to hide
3808			* any amount of letters, numbers, ".", "_", "%", "+", or "-", followed by 0 or 1 whitespace,
3809			* followed by "@", followed by 0 or 1 whitespace, followed by "gmail", "outlook", "hotmail", or "yahoo".
3810			*/
3811			looseEmail2: /([a-zA-Z0-9._%+-]+)\s?@\s?(gmail|outlook|icloud|hotmail|yahoo)(\s?\.?\s?(com|ca))?/ig,
3812
3813			/*
3814			* Postal code:
3815			* valid Canadian postal code
3816			*/
3817			postalCode: /\b[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d\b/ig,
3818
3819			/*
3820			* Username:
3821			* "username" or "user",
3822			* followed by a colon or an equals sign,
3823			* followed by any character that is not a " " or a "&"
3824			*/
3825			username: /(?:(username|user)[%20]?([:=]|(%EF%BC%9A))[^\s&]*)/ig,
3826
3827			/*
3828			* Password:
3829			* "password" or "pass",
3830			* followed by a ":" or a "=",
3831			* followed by any character that is not a " " or a "&"
3832			*/
3833			password: /(?:(password|pass)[%20]?([:=]|(%EF%BC%9A))[^\s&]*)/ig
3834		},
3835		isFound = false,
3836		txtMarker = opts && opts.replaceWith ? opts.replaceWith : "",
3837		toClean = typeof scope === "object" ? true : scope,
3838		arMatchedStr,
3839		settings = opts || {},
3840		defaultSettings = {
3841			isCustomExclusive: false,
3842			useFullBlock: false,
3843			replaceWith: ""
3844		},
3845		isFullBlock = settings.useFullBlock || false,
3846		validatedScope = typeof scope === "object" ? {} : oRegEx;
3847	//settings = $.extend( {}, defaultSettings, settings );
3848	settings = Object.assign( {}, defaultSettings, settings );
3849
3850	if ( Object.keys( validatedScope ).length === 0 ) {
3851		if ( settings.isCustomExclusive ) {
3852			for ( var key in scope ) {
3853				if ( scope[ key ] instanceof RegExp ) {
3854					validatedScope[ key ] = scope[ key ];
3855				}
3856			}
3857		} else {
3858			if ( Object.keys( scope ).length === 1 && Object.values( scope )[ 0 ] instanceof RegExp ) {
3859				validatedScope = oRegEx;
3860				validatedScope[ Object.keys( scope )[ 0 ] ] = Object.values( scope )[ 0 ];
3861			} else {
3862				for ( var keyScope in scope ) {
3863					if ( Object.prototype.hasOwnProperty.call( oRegEx, keyScope ) ) {
3864						validatedScope [ keyScope ] = oRegEx [ keyScope ];
3865					} else {
3866						if ( scope[ keyScope ]  instanceof RegExp ) {
3867							validatedScope [ keyScope ] = scope [ keyScope ];
3868						}
3869					}
3870				}
3871			}
3872		}
3873	}
3874
3875	for ( var valKey in validatedScope ) {
3876		arMatchedStr = str.match( validatedScope[ valKey ] );
3877		if ( arMatchedStr ) {
3878			isFound = true;
3879			if ( toClean ) {
3880				txtMarker = isFullBlock ? "█".repeat( arMatchedStr[ 0 ].length ) : txtMarker;
3881				str = str.replaceAll( validatedScope[ valKey ], txtMarker );
3882			}
3883		}
3884	}
3885
3886	return toClean && isFound ? str : isFound;
3887};
3888
3889} )( wb, window );
3890
3891( function( $, undef ) {
3892"use strict";
3893
3894var methods,
3895	settings = {
3896		"default": "wet-boew"
3897	};
3898
3899methods = {
3900
3901	init: function( options ) {
3902		return $.extend( settings, options || {} );
3903	},
3904
3905	show: function( onlyAria ) {
3906		$( this ).each( function() {
3907			var $elm = $( this );
3908			$elm.attr( "aria-hidden", "false" );
3909			if ( onlyAria === undef ) {
3910				$elm.removeClass( "wb-inv" );
3911			}
3912		} );
3913	},
3914
3915	hide: function( onlyAria ) {
3916		$( this )
3917			.each( function() {
3918				var $elm = $( this );
3919				$elm.attr( "aria-hidden", "true" );
3920				if ( onlyAria === undef ) {
3921					return $elm.addClass( "wb-inv" );
3922				}
3923			} );
3924	},
3925
3926	toggle: function( to, from ) {
3927		$( this )
3928			.addClass( to )
3929			.removeClass( from );
3930	}
3931};
3932
3933$.fn.wb = function( method ) {
3934
3935	if ( methods[ method ] ) {
3936		methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
3937	} else if ( typeof method === "object" || !method ) {
3938		methods.init.apply( this, arguments );
3939	} else {
3940		$.error( "Method " + method + " does not exist on jquery.wb" );
3941	}
3942};
3943
3944} )( jQuery );
3945
3946/*
3947:focusable and :tabable jQuery helper expressions - https://github.com/jquery/jquery-ui/blob/24756a978a977d7abbef5e5bce403837a01d964f/ui/jquery.ui.core.js
3948*/
3949( function( $ ) {
3950
3951"use strict";
3952
3953function focusable( element, isTabIndexNotNaN, visibility ) {
3954	var map, mapName, img,
3955		nodeName = element.nodeName.toLowerCase( );
3956	if ( nodeName === "area" ) {
3957		map = element.parentNode;
3958		mapName = map.name;
3959		if ( !element.href || !mapName || map.nodeName.toLowerCase( ) !== "map" ) {
3960			return false;
3961		}
3962		img = $( "img[usemap=#" + mapName + "]" )[ 0 ];
3963		return !!img && visible( img );
3964	}
3965	if ( visibility ) {
3966		return ( /input|select|textarea|button|object|summary/.test( nodeName ) ? !element.disabled :
3967			nodeName === "a" ?
3968				element.href || isTabIndexNotNaN :
3969				isTabIndexNotNaN ) &&
3970		visible( element ); /* the element and all of its ancestors must be visible */
3971	} else {
3972		return ( /input|select|textarea|button|object|summary/.test( nodeName ) ? !element.disabled :
3973			nodeName === "a" ?
3974				element.href || isTabIndexNotNaN :
3975				isTabIndexNotNaN );
3976	}
3977}
3978
3979function visible( element ) {
3980	return $.expr.filters.visible( element ) && !$( element )
3981		.parents( )
3982		.addBack( )
3983		.filter( function() {
3984			return $.css( this, "visibility" ) === "hidden";
3985		} )
3986		.length;
3987}
3988
3989$.extend( $.expr.pseudos, {
3990	data: function( elem, index, match ) {
3991		return !!$.data( elem, match[ 3 ] );
3992	},
3993	focusable: function( element ) {
3994		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ), true );
3995	},
3996	discoverable: function( element ) {
3997		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
3998	},
3999	tabbable: function( element ) {
4000		var tabIndex = $.attr( element, "tabindex" ),
4001			isTabIndexNaN = isNaN( tabIndex );
4002		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
4003	}
4004} );
4005
4006} )( jQuery );
4007
4008/**
4009 * @title WET-BOEW Add to calendar
4010 * @overview Create an add to calendar button for an event
4011 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4012 * @author @ricokola
4013 */
4014( function( $, wb ) {
4015"use strict";
4016
4017/*
4018 * Variable and function definitions.
4019 * These are global to the plugin - meaning that they will be initialized once per page,
4020 * not once per instance of plugin on the page. So, this is a good place to define
4021 * variables that are common to all instances of the plugin on a page.
4022 */
4023var componentName = "wb-addcal",
4024	selector = ".provisional." + componentName,
4025	initEvent = "wb-init." + componentName,
4026	$document = wb.doc,
4027
4028	/**
4029	* @method init
4030	* @param {jQuery Event} event Event that triggered the function call
4031	*/
4032	init = function( event ) {
4033
4034		// Start initialization
4035		// returns DOM object = proceed with init
4036		// returns undefined = do not proceed with init (e.g., already initialized)
4037		var elm = wb.init( event, componentName, selector ),
4038			$elm = $( elm );
4039
4040		if ( elm ) {
4041
4042			wb.ready( $( elm ), componentName );
4043
4044			var properties = elm.querySelectorAll( "[property]" ),
4045				event_details = new Object(),
4046				place_details = [],
4047				i,
4048				i_len,
4049				prop_cache,
4050				googleLink,
4051				icsFile,
4052				i18nDict = {
4053					en: {
4054						"addcal-addto": "Add to",
4055						"addcal-calendar": "calendar",
4056						"addcal-other": "Other (Outlook, Apple, etc.)"
4057					},
4058					fr: {
4059						"addcal-addto": "Ajouter au",
4060						"addcal-calendar": "calendrier",
4061						"addcal-other": "Autre (Outlook, Apple, etc.)"
4062					}
4063				};
4064
4065			// Initiate dictionary
4066			i18nDict = i18nDict[ $( "html" ).attr( "lang" ) || "en" ];
4067			i18nDict = {
4068				addto: i18nDict[ "addcal-addto" ],
4069				calendar: i18nDict[ "addcal-calendar" ],
4070				ical: i18nDict[ "addcal-other" ]
4071			};
4072
4073			// Set date stamp with the date modified
4074			event_details.dtStamp = dtToISOString( $( "time[property='dateModified']" ) );
4075
4076			i_len = properties.length;
4077			for ( i = 0; i < i_len; i++ ) {
4078				prop_cache = properties[ i ];
4079				switch ( prop_cache.getAttribute( "property" ) ) {
4080					case "name":
4081
4082						// If the property=name is inside an element with typeof=Place defined
4083						if ( $( prop_cache ).parentsUntil( ( "." + componentName ), "[typeof=Place]" ).length ) {
4084							event_details.placeName = prop_cache.textContent;
4085						} else {
4086							event_details.name = prop_cache.textContent;
4087						}
4088						break;
4089					case "description":
4090						event_details.description = prop_cache.textContent.replace( /(\r\n|\n|\r)/gm, " " );
4091						break;
4092					case "startDate":
4093						event_details.sDate = dtToISOString( $( "time[property='startDate']", $elm ) );
4094						break;
4095					case "endDate":
4096						event_details.eDate = dtToISOString( $( "time[property='endDate']", $elm ) );
4097						break;
4098					case "location":
4099
4100						// If the location doesn't have typeof defined OR has typeof=VirtualLocation without URL inside.
4101						if ( !prop_cache.getAttribute( "typeof" ) || ( prop_cache.getAttribute( "typeof" ) === "VirtualLocation" && !$( prop_cache ).find( "[property=url]" ).length ) ) {
4102							event_details.placeName = prop_cache.textContent;
4103						}
4104						break;
4105					case "streetAddress":
4106						event_details.placeAddress = prop_cache.textContent;
4107						break;
4108					case "addressLocality":
4109						event_details.placeLocality = prop_cache.textContent;
4110						break;
4111					case "addressRegion":
4112						event_details.placeRegion = prop_cache.textContent;
4113						break;
4114					case "postalCode":
4115						event_details.placePostalCode = prop_cache.textContent;
4116						break;
4117					case "url":
4118
4119						// If the property=url is inside a property=location
4120						if ( $( prop_cache ).parentsUntil( ( "." + componentName ), "[property=location]" ).length ) {
4121							event_details.placeName = prop_cache.textContent;
4122						}
4123						break;
4124				}
4125			}
4126
4127			place_details.push( ( event_details.placeName || "" ), ( event_details.placeAddress || "" ), ( event_details.placeLocality || "" ), ( event_details.placeRegion || "" ), ( event_details.placePostalCode || "" ) );
4128
4129			// Error handling
4130			if ( !event_details.name ) {
4131				throw componentName + ": Event title is missing.";
4132			} else if ( !event_details.sDate ) {
4133				throw componentName + ": Start date is missing.";
4134			} else if ( !event_details.eDate ) {
4135				throw componentName + ": End date is missing.";
4136			}
4137
4138			// Set Unique Identifier (UID) and Date Stamp (DSTAMP)
4139			event_details.uid = window.location.href.replace( /\.|-|\/|:|[G-Zg-z]/g, "" ).toUpperCase().substr( -10 ) + "-" + event_details.sDate + "-" + event_details.dtStamp;
4140
4141			// Set google calendar link
4142			googleLink = encodeURI( "https://www.google.com/calendar/render?action=TEMPLATE" +  "&text=" + event_details.name +  "&details=" +
4143			event_details.description +  "&dates=" + event_details.sDate + "/" + event_details.eDate + "&location=" + place_details.join( " " ) );
4144
4145			// Set ICS file for Outlook, Apple and other calendars
4146			icsFile = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//WET-BOEW//Add to Calendar v4.0//\n
4146BEGIN:VEVENT\nDTSTAMP:" + event_details.dtStamp + "\nSUMMARY:" + event_details.name +  "\nDESCRIPTION:" + event_details.description + "\nUID:" + event_details.uid + "\nDTSTART:" + event_details.sDate + "\nDTEND:" + event_details.eDate + "\nLOCATION:" + place_details.join( " " ) + "\nEND:VEVENT\nEND:VCALENDAR";
4147
4148			elm.dataset.icsFile = icsFile;
4149
4150			// Create and add details summary to the wb-addcal event and initiate the unordered list
4151			$elm.append( "<details class='max-content " + componentName + "-buttons'><summary>" + i18nDict.addto + " " + i18nDict.calendar +
4152			"</summary><ul class='list-unstyled mrgn-bttm-0'><li><a class='btn btn-link' href='" + googleLink.replace( /'/g, "%27" ) + "' target='_blank' rel='noreferrer noopener'>Google<span class='wb-inv'>" + i18nDict.calendar + "</span></a></li><li><button class='btn btn-link download-ics'>" + i18nDict.ical +
4153			"<span class='wb-inv'>Calendar</span></button></li></ul></details>" );
4154		}
4155
4156		wb.ready( $( elm ), componentName );
4157
4158	};
4159
4160// Convert date to ISO string and formatting for ICS file
4161var dtToISOString = function( date ) {
4162	if ( date.is( "[datetime]" ) ) {
4163		date = date.attr( "datetime" );
4164	} else {
4165		date = date.text();
4166	}
4167
4168	return new Date( date ).toISOString().replace( /\..*[0-9]/g, "" ).replace( /-|:|\./g, "" );
4169};
4170
4171$document.on( "click", ".download-ics", function( event ) {
4172	var icsFile = $( event.currentTarget ).parentsUntil( "." + componentName ).parent()[ 0 ];
4173	icsFile =  $( icsFile ).attr( "data-ics-file" );
4174	wb.download( new Blob( [ icsFile ], { type: "text/calendar;charset=utf-8" } ), "evenement-gc-event.ics" );
4175} );
4176
4177// Bind the init event of the plugin
4178$document.on( "timerpoke.wb " + initEvent, selector, init );
4179
4180// Add the timer poke to initialize the plugin
4181wb.add( selector );
4182
4183} )( jQuery, wb );
4184
4185/**
4186 * @title WET-BOEW Ajax Fetch [ ajax-fetch ]
4187 * @overview A basic AjaxLoader wrapper for WET-BOEW
4188 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4189 * @author WET Community
4190 */
4191( function( $, wb, DOMPurify ) {
4192"use strict";
4193
4194/*
4195 * Variable and function definitions.
4196 * These are global to the plugin - meaning that they will be initialized once per page,
4197 * not once per instance of plugin on the page. So, this is a good place to define
4198 * variables that are common to all instances of the plugin on a page.
4199 */
4200var $document = wb.doc;
4201
4202// Event binding
4203$document.on( "ajax-fetch.wb", function( event ) {
4204
4205	// TODO: Remove event.element in future versions
4206	var caller = event.element || event.target,
4207		fetchOpts = event.fetch,
4208		urlParts = fetchOpts.url.split( " " ),
4209		url = urlParts[ 0 ],
4210		urlSubParts = url.split( "#" ),
4211		urlHash = urlSubParts[ 1 ],
4212		selector = urlParts[ 1 ] || ( urlHash ? "#" + urlHash : false ),
4213		fetchData = {},
4214		callerId, fetchNoCacheURL, urlSub,
4215		fetchNoCache = fetchOpts.nocache,
4216		fetchNoCacheKey = fetchOpts.nocachekey || wb.cacheBustKey || "wbCacheBust";
4217
4218	// Separate the URL from the filtering criteria
4219	if ( selector ) {
4220		fetchOpts.url = urlParts[ 0 ];
4221
4222		if ( urlParts[ 1 ] ) {
4223			selector = urlParts.slice( 1 ).join( " " );
4224		}
4225	}
4226
4227	if ( fetchNoCache ) {
4228		if ( fetchNoCache === "nocache" ) {
4229			fetchNoCacheURL = wb.guid();
4230		} else {
4231			fetchNoCacheURL = wb.sessionGUID();
4232		}
4233		fetchNoCacheURL = fetchNoCacheKey + "=" + fetchNoCacheURL;
4234
4235		urlSub = urlSubParts[ 0 ];
4236		if ( urlSub.indexOf( "?" ) !== -1 ) {
4237			url = urlSub + "&" + fetchNoCacheURL + ( urlHash ? "#" + urlHash : "" );
4238		} else {
4239			url = urlSub + "?" + fetchNoCacheURL + ( urlHash ? "#" + urlHash : "" );
4240		}
4241
4242		fetchOpts.url = url;
4243	}
4244
4245	// Filter out any events triggered by descendants
4246	if ( caller === event.target || event.currentTarget === event.target ) {
4247
4248		if ( !caller.id ) {
4249			caller.id = wb.getId();
4250		}
4251		callerId = caller.id;
4252
4253		// Ensure we don't allow jsonp load
4254		if ( fetchOpts.dataType && fetchOpts.dataType === "jsonp" ) {
4255			fetchOpts.dataType = "json";
4256		}
4257		if ( fetchOpts.jsonp ) {
4258			fetchOpts.jsonp = false;
4259		}
4260
4261		$.ajax( fetchOpts )
4262			.done( function( response, status, xhr ) {
4263				var responseType = typeof response;
4264
4265				if ( selector ) {
4266					response = $( "<div>" + response + "</div>" ).find( selector );
4267				}
4268
4269				fetchData.pointer = $( "<div id='" + wb.getId() + "' data-type='" + responseType + "'></div>" )
4270					.append( responseType === "string" ? response : "" );
4271
4272				if ( !xhr.responseJSON ) {
4273					try {
4274						response = $( response );
4275					} catch ( e ) {
4276						response = DOMPurify.sanitize( xhr.responseText );
4277					}
4278				} else {
4279					response = xhr.responseText;
4280				}
4281
4282				fetchData.response = response;
4283				fetchData.hasSelector = !!selector;
4284				fetchData.status = status;
4285				fetchData.xhr = xhr;
4286
4287				$( "#" + callerId ).trigger( {
4288					type: "ajax-fetched.wb",
4289					fetch: fetchData
4290				}, this );
4291			} )
4292			.fail( function( xhr, status, error ) {
4293				$( "#" + callerId ).trigger( {
4294					type: "ajax-failed.wb",
4295					fetch: {
4296						xhr: xhr,
4297						status: status,
4298						error: error
4299					}
4300				}, this );
4301			}, this );
4302	}
4303} );
4304
4305} )( jQuery, wb, DOMPurify );
4306
4307/**
4308 * @title WET-BOEW Set background image
4309 * @overview Apply a background image or detects the change in screen width and replace the background image accordingly
4310 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4311 * @author @namjohn920, @duboisp
4312 */
4313( function( $, wb ) {
4314"use strict";
4315
4316/*
4317 * Variable and function definitions.
4318 * These are global to the plugin - meaning that they will be initialized once per page,
4319 * not once per instance of plugin on the page. So, this is a good place to define
4320 * variables that are common to all instances of the plugin on a page.
4321 */
4322var $document = wb.doc,
4323	$window = wb.win,
4324	componentName = "wb-bgimg",
4325	selector = "[data-bgimg-srcset], [data-bgimg]",
4326	bgViews = {},
4327	ids = [],
4328
4329	init = function( event ) {
4330
4331		var elm, elmId,
4332			bgImg, bgimgSrcset, bgRawViews,
4333			i, i_len, i_views,
4334			imgSrc, imgSize;
4335
4336		// Start initialization
4337		// returns DOM object = proceed with init
4338		// returns undefined = do not proceed with init (e.g., already initialized)
4339		elm = wb.init( event, componentName, selector );
4340
4341		if ( elm ) {
4342
4343			// Ensure the feature have an ID.
4344			if ( !elm.id ) {
4345				elm.id = wb.getId();
4346			}
4347			elmId = elm.id;
4348
4349			// Apply default background image
4350			bgImg = elm.dataset.bgimg;
4351			if ( bgImg ) {
4352				elm.style.backgroundImage = "url(" + bgImg + ")";
4353			}
4354
4355			// Apply background image set if defined
4356			bgimgSrcset = elm.dataset.bgimgSrcset;
4357			if ( bgimgSrcset ) {
4358				ids.push( elm.id );
4359				bgRawViews = elm.dataset.bgimgSrcset.split( "," );
4360				i_len = bgRawViews.length;
4361				bgViews[ elmId ] = [];
4362
4363				for ( i = 0; i < i_len; i++ ) {
4364					i_views = bgRawViews[ i ].trim().split( " " );
4365
4366					imgSrc = i_views[ 0 ];
4367					imgSize =  i_views[ i_views.length - 1 ];
4368
4369					imgSize = parseInt( imgSize.substring( 0, imgSize.length - 1 ) );
4370					bgViews[ elmId ].push( [ imgSrc, imgSize ] );
4371				}
4372
4373				bgViews[ elmId ].sort(
4374					function( a, b ) {
4375						return a[ 1 ] > b[ 1 ] ? 1 : -1;
4376					}
4377				);
4378
4379				selectImage();
4380
4381				// Add the resize listener
4382				$window.on( "resize", selectImage );
4383			}
4384
4385			// Identify that initialization has completed
4386			wb.ready( $( elm ), componentName );
4387		}
4388	},
4389
4390	selectImage = function() {
4391		var screenWidth = window.innerWidth,
4392			optimizedLink = {},
4393			i, i_len = ids.length, j,
4394			optimizedSize, currentId, currentId_len,
4395			currentInput,
4396			link, elm;
4397
4398		for ( i = 0; i < i_len; i++ ) {
4399			optimizedSize = Infinity;
4400			currentId = bgViews[ ids[ i ] ];
4401			currentId_len = currentId.length;
4402
4403			for ( j = 0; j < currentId_len; j++ ) {
4404				currentInput = currentId[ j ];
4405				if ( currentInput[ 1 ] >= screenWidth ) {
4406					if ( optimizedSize > currentInput[ 1 ] ) {
4407						optimizedSize = currentInput[ 1 ];
4408						optimizedLink[ ids[ i ] ] = currentInput[ 0 ];
4409					}
4410				}
4411			}
4412			if ( optimizedSize === Infinity ) {
4413				optimizedLink[ ids[ i ] ] = currentId[ currentId_len - 1 ][ 0 ];
4414			}
4415		}
4416
4417		for ( link in optimizedLink ) {
4418			elm = document.getElementById( link );
4419
4420			if ( optimizedLink[ link ] === "https://wet-boew.github.io/vocab/wb/utilities#no-image" ) {
4421				elm.style.backgroundImage = "none";
4422			} else {
4423				elm.style.backgroundImage = "url(" + optimizedLink[ link ] + ")";
4424			}
4425		}
4426	};
4427
4428// Bind the init event of the plugin
4429$document.on( "timerpoke.wb wb-init." + componentName, selector, init );
4430
4431// Add the timer poke to initialize the plugin
4432wb.add( selector );
4433
4434} )( jQuery, wb );
4435
4436/**
4437 * @title WET-BOEW Events Calendar
4438 * @overview Dynamically generates a calendar interface for navigating a list of events.
4439 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4440 * @author WET Community
4441 */
4442( function( $, window, wb ) {
4443"use strict";
4444
4445/*
4446 * Variable and function definitions.
4447 * These are global to the plugin - meaning that they will be initialized once per page,
4448 * not once per instance of plugin on the page. So, this is a good place to define
4449 * variables that are common to all instances of the plugin on a page.
4450 */
4451var componentName = "wb-calevt",
4452	selector = "." + componentName,
4453	componentEventName = componentName + "-cal",
4454	selectorEvent = "." + componentEventName,
4455	initEvent = "wb-init" + selector,
4456	evDetails = "ev-details",
4457	setFocusEvent = "focus",
4458	dataAttr = componentName,
4459	$document = wb.doc,
4460	hiddenClass = "hidden",
4461
4462	/**
4463	 * @method init
4464	 * @param {jQuery Event} event Event that triggered this handler
4465	 */
4466	init = function( event ) {
4467
4468		// Start initialization
4469		// returns DOM object = proceed with init
4470		// returns undefined = do not proceed with init (e.g., already initialized)
4471		var elm = wb.init( event, componentName, selector ),
4472			$elm;
4473
4474		if ( elm ) {
4475			$elm = $( elm );
4476
4477			// Load ajax content
4478			$.when.apply( $, $.map( $elm.find( "[data-calevt]" ), getAjax ) )
4479				.always( function() {
4480					processEvents( $elm );
4481
4482					// Identify that initialization has completed
4483					wb.ready( $elm, componentName );
4484				} );
4485		}
4486	},
4487
4488	getAjax = function( ajaxContainer ) {
4489		var $ajaxContainer = $( ajaxContainer ),
4490			urls = $ajaxContainer.data( "calevt" ).split( /\s+/ ),
4491			dfd = $.Deferred(),
4492			len = urls.length,
4493			promises = [],
4494			i, appendData;
4495
4496		appendData = function( data ) {
4497			$ajaxContainer.append( $.trim( data ) );
4498		};
4499
4500		for ( i = 0; i < len; i += 1 ) {
4501			promises.push( $.get( urls[ i ], appendData, "html" ) );
4502		}
4503
4504		$.when.apply( $, promises ).always( function() {
4505			dfd.resolve();
4506		} );
4507
4508		return dfd.promise();
4509	},
4510
4511	processEvents = function( $elm ) {
4512		var settings = $.extend( {}, window[ componentName ], $elm.data( dataAttr ) ),
4513			year, month, events, containerId, $container,
4514			minDate, maxDate, minDateTime, maxDateTime,
4515			currDate = new Date(),
4516			currDateTime = currDate.getTime();
4517
4518		events = getEvents( $elm );
4519		containerId = $elm.data( "calevtSrc" );
4520		$container = $( "#" + containerId ).addClass( componentEventName );
4521
4522		year = settings.year;
4523		month = settings.month;
4524
4525		if ( $elm.data( "calevtMinDate" ) ) {
4526			minDate = getLocaleDate( $elm.data( "calevtMinDate" ) );
4527		}
4528		if ( $elm.data( "calevtMaxDate" ) ) {
4529			maxDate = getLocaleDate( $elm.data( "calevtMaxDate" ) );
4530		}
4531
4532		if ( !minDate || ( events.minDate < minDate ) ) {
4533			minDate = events.minDate;
4534		}
4535		if ( !maxDate || ( events.maxDate > maxDate ) ) {
4536			maxDate = events.maxDate;
4537		}
4538
4539		minDateTime = minDate.getTime();
4540		maxDateTime = maxDate.getTime();
4541
4542		if ( !year && minDateTime < currDateTime && currDateTime < maxDateTime ) {
4543			year = currDate.getFullYear();
4544		} else if ( !year && currDateTime < minDateTime ) {
4545			year = minDate.getFullYear();
4546		} else if ( !year && maxDateTime < currDateTime ) {
4547			year = maxDate.getFullYear();
4548		}
4549
4550		if ( !month && minDateTime < currDateTime && currDate.getTime() < maxDateTime ) {
4551			month = currDate.getMonth();
4552		} else if ( !month && currDateTime < minDateTime ) {
4553			month = minDate.getMonth();
4554		} else if ( !month && maxDateTime < currDateTime ) {
4555			month = maxDate.getMonth();
4556		}
4557
4558		wb.calendar.create( $container, {
4559			year: year,
4560			month: month,
4561			minDate: minDate,
4562			maxDate: maxDate,
4563			daysCallback: addEvents,
4564			events: events.list,
4565			$events: $elm
4566		} );
4567	},
4568
4569	daysBetween = function( dateLow, dateHigh ) {
4570
4571		// Simplified conversion to date object
4572		var date1 = wb.date.convert( dateLow ),
4573			date2 = wb.date.convert( dateHigh ),
4574			dstAdjust = 0,
4575			oneMinute = 1000 * 60,
4576			oneDay = oneMinute * 60 * 24,
4577			diff;
4578
4579		// Equalize times in case date objects have them
4580		date1.setHours( 0 );
4581		date1.setMinutes( 0 );
4582		date1.setSeconds( 0 );
4583		date2.setHours( 0 );
4584		date2.setMinutes( 0 );
4585		date2.setSeconds( 0 );
4586
4587		// Take care of spans across Daylight Saving Time changes
4588		if ( date2 > date1 ) {
4589			dstAdjust = ( date2.getTimezoneOffset() - date1.getTimezoneOffset() ) * oneMinute;
4590		} else {
4591			dstAdjust = ( date1.getTimezoneOffset() - date2.getTimezoneOffset() ) * oneMinute;
4592		}
4593		diff = Math.abs( date2.getTime() - date1.getTime() ) - dstAdjust;
4594		return Math.ceil( diff / oneDay );
4595	},
4596
4597	///TODO: Review this code
4598	getEvents = function( obj ) {
4599		var directLinking = !( $( obj ).hasClass( "evt-anchor" ) ),
4600			events = {
4601				minDate: null,
4602				maxDate: null,
4603				iCount: 0,
4604				list: [
4605					{
4606						a: 1
4607					}
4608				]
4609			},
4610			objEventsList = obj.find( "ul, ol" ).first(),
4611			objEventsListItems = objEventsList.find( "> li:not(.wb-fltr-out)" ),
4612			iLen = objEventsListItems.length,
4613			dateTimeRegExp = /datetime\s+\{date:\s*(\d+-\d+-\d+)\}/,
4614			i, $event, event, $objTitle, title, link, href, target,
4615			linkId, date, tCollection, tCollectionTemp,	strDate1,
4616			strDate2, z, zLen, className, dateClass;
4617
4618		for ( i = 0; i !== iLen; i += 1 ) {
4619			$event = objEventsListItems.eq( i );
4620			event = $event[ 0 ];
4621			$objTitle = $event.find( "*:header:first" );
4622			className = $objTitle.attr( "class" );
4623			title = $objTitle.text();
4624			link = $event.find( "a" )[ 0 ];
4625			href = link.getAttribute( "href" );
4626			target = link.getAttribute( "target" );
4627			zLen = 1;
4628
4629			/*
4630			 * Modification direct-linking or page-linking
4631			 *	- added the ability  to have class set the behaviour of the links
4632			 *	- default is to use the link of the item as the event link in the calendar
4633			 *	- 'evt-anchor' class dynamically generates page anchors on the links it maps to the event
4634			 */
4635			if ( !directLinking ) {
4636				linkId = event.id || wb.getId();
4637				event.id = linkId;
4638				href = "#" + linkId;
4639			}
4640
4641			date = new Date();
4642			date.setHours( 0, 0, 0, 0 );
4643			tCollection = event.getElementsByTagName( "time" );
4644
4645			/*
4646			 * Date spanning capability
4647			 *   - since there may be some dates that are capable of spanning over months we need to identify them
4648			 *     the process is see how many time nodes are in the event. 2 nodes will trigger a span
4649			 */
4650			if ( tCollection.length !== 0 ) {
4651				tCollectionTemp = tCollection[ 0 ];
4652				strDate1 = tCollectionTemp.nodeName.toLowerCase() === "time" ?
4653					tCollectionTemp.getAttribute( "datetime" ).substr( 0, 10 ).split( "-" ) :
4654					tCollectionTemp.className.match( dateTimeRegExp )[ 1 ].substr( 0, 10 ).split( "-" );
4655
4656				// Convert to zero-based month
4657				strDate1[ 1 ] = strDate1[ 1 ] - 1;
4658
4659				date.setFullYear( strDate1[ 0 ], strDate1[ 1 ], strDate1[ 2 ] );
4660
4661				if ( tCollection.length !== 1 ) {
4662
4663					// This is a spanning event
4664					tCollectionTemp = tCollection[ 1 ];
4665					strDate2 = tCollectionTemp.nodeName.toLowerCase() === "time" ?
4666						tCollectionTemp.getAttribute( "datetime" ).substr( 0, 10 ).split( "-" ) :
4667						tCollectionTemp.className.match( dateTimeRegExp )[ 1 ].substr( 0, 10 ).split( "-" );
4668
4669					// Convert to zero-based month
4670					strDate2[ 1 ] = strDate2[ 1 ] - 1;
4671
4672					zLen += daysBetween( strDate1, strDate2 );
4673				}
4674
4675				// Now loop in events to load up all the days that it would be on tomorrow.setDate(tomorrow.getDate() + 1);
4676				for ( z = 0; z !== zLen; z += 1 ) {
4677					if ( z !== 0 ) {
4678						date = new Date( date.setDate( date.getDate() + 1 ) );
4679					}
4680
4681					if ( events.minDate === null || date < events.minDate ) {
4682						events.minDate = date;
4683					}
4684
4685					if ( events.maxDate === null || date > events.maxDate ) {
4686						events.maxDate = date;
4687					}
4688
4689					events.list[ events.iCount ] = {
4690						title: title,
4691						date: new Date( date.getTime() ),
4692						href: href,
4693						target: target
4694					};
4695
4696					// Add a viewfilter
4697					dateClass = "filter-" + ( date.getFullYear() ) + "-" +
4698						wb.string.pad( date.getMonth() + 1, 2 );
4699					if ( !className ) {
4700						className = dateClass;
4701					} else if ( className.indexOf( dateClass ) === -1 ) {
4702						className += " " + dateClass;
4703					}
4704					events.iCount += 1;
4705				}
4706				$objTitle.attr( "class", className );
4707			}
4708
4709		// End of loop through objects/events
4710		}
4711
4712		//Sort events
4713		events.list.sort( function( firstEvent, secondEvent ) {
4714			return firstEvent.date - secondEvent.date;
4715		} );
4716
4717		return events;
4718	},
4719
4720	addEvents = function( year, month, $days ) {
4721		var eventsList = this.events,
4722			i, eLen, date, dayIndex, $day, $dayEvents, event, eventMonth;
4723
4724		/*
4725		 * Determines for each event, if it occurs in the display month
4726		 */
4727		for ( i = 0, eLen = eventsList.length; i !== eLen; i += 1 ) {
4728			event = eventsList[ i ];
4729			date = event.date;
4730
4731			if ( date.getFullYear() === year ) {
4732				eventMonth = date.getMonth();
4733				if ( eventMonth > month ) {
4734
4735					//End the loop if the next event is in a future month because events are sorted chronologically
4736					break;
4737				} else if ( date.getMonth() === month ) {
4738					dayIndex = date.getDate() - 1;
4739					$day = $( $days[ dayIndex ] );
4740
4741					//Get the appropriate day events if a day link exists
4742					if ( $day.parent().get( 0 ).nodeName !== "A" ) {
4743						$dayEvents = $day.next();
4744					} else {
4745						$dayEvents = $day.parent().next();
4746					}
4747
4748					//Create the event list container if it doesn't exist
4749					if ( $dayEvents.length !== 1 ) {
4750						$dayEvents = $( "<ul></ul>" ).insertAfter( $day );
4751
4752						//Determine the focus based on the day before
4753						if ( dayIndex && $days[ dayIndex - 1 ].parentNode.nodeName === "A" ) {
4754							$day.wrap( "<a class='cal-evt' tabindex='-1'></a>" );
4755						} else {
4756							$day.wrap( "<a class='cal-evt'></a>" );
4757						}
4758						$day.parent().attr( "href", "javascript:;" );
4759					}
4760
4761					//Add the event to the list
4762					$dayEvents.append( "<li><a tabindex='-1' class='cal-evt-lnk' href='" + event.href + "'>" + event.title + "</a></li>" );
4763				}
4764			}
4765		}
4766	},
4767
4768	filterEvents = function( year, month ) {
4769		this.find( "li.cal-disp-onshow" )
4770			.addClass( hiddenClass )
4771			.has( ":header[class*=filter-" + year + "-" +
4772				wb.string.pad( parseInt( month, 10 ) + 1, 2 ) + "]" )
4773			.removeClass( hiddenClass );
4774	},
4775
4776	showEvents = function() {
4777		$( this )
4778			.next()
4779			.addClass( evDetails );
4780	},
4781
4782	hideEvents = function() {
4783		var $link = $( this ),
4784			$cell = $link.closest( "td" );
4785
4786		setTimeout( function() {
4787			if ( $cell.find( "a:focus" ).length === 0 ) {
4788				$cell.find( "ul" )
4789					.removeClass( evDetails )
4790					.find( "a" )
4791					.attr( "tabindex", "-1" );
4792			}
4793		}, 5 );
4794	},
4795
4796	getLocaleDate = function( dateString ) {
4797		var date = new Date(),
4798			dateComponents = dateString.split( "-" );
4799
4800		dateComponents[ 1 ] = dateComponents[ 1 ] - 1;	// Convert to zero-based month
4801		date.setFullYear( dateComponents[ 0 ], dateComponents[ 1 ], dateComponents[ 2 ] );
4802
4803		return date;
4804	};
4805
4806// Bind the init event of the plugin
4807$document.on( "timerpoke.wb " + initEvent + " wb-redraw" + selector, selector, function( event ) {
4808
4809	var eventType = event.type,
4810		$elm = $( "#" + event.target.id ),
4811		calendarId = event.currentTarget.dataset.calevtSrc;
4812
4813	switch ( eventType ) {
4814		case "timerpoke":
4815		case "wb-init":
4816			init( event );
4817			break;
4818
4819		case "wb-redraw":
4820			$( "#" + calendarId + " .wb-clndr" ).remove();
4821			processEvents( $elm );
4822			$elm.trigger( "wb-updated" + selector );
4823			break;
4824	}
4825} );
4826
4827$document.on( "wb-navigate.wb-clndr", selectorEvent, function( event ) {
4828	var lib = event.target.lib,
4829		$calEvent;
4830
4831	if ( lib ) {
4832		$calEvent = lib.$events;
4833
4834		if ( $calEvent ) {
4835			filterEvents.call( $calEvent, event.year, event.month );
4836
4837			//TODO: Added for backwards compatibility. Remove in previous versions
4838			$calEvent.trigger( "wb-updated" + selector );
4839		}
4840	}
4841} );
4842
4843$document.on( "focusin focusout keydown", selectorEvent + " .cal-evt", function( event ) {
4844	var eventType = event.type,
4845		$link;
4846
4847	switch ( eventType ) {
4848		case "focusin":
4849			showEvents.call( event.target );
4850			break;
4851		case "focusout":
4852			hideEvents.call( event.target );
4853			break;
4854		case "keydown":
4855			$link = $( event.target );
4856			if ( ( event.which === 13 || event.which === 32 ) && $link.hasClass( "cal-evt" ) ) {
4857				$( event.target ).next().find( "a:first" ).trigger( setFocusEvent );
4858			}
4859			break;
4860	}
4861} );
4862
4863$document.on( "keydown", selectorEvent + " td > ul li", function( event ) {
4864	var $item = $( event.currentTarget ),
4865		$toFocus, $itemParent;
4866
4867	switch ( event.which ) {
4868		case 38:
4869			$toFocus = $item.prev().find( "a" );
4870			if ( $toFocus.length === 0 ) {
4871				$toFocus = $item.siblings( ":last" ).find( "a" );
4872			}
4873			$toFocus.trigger( setFocusEvent );
4874			break;
4875		case 40:
4876			$toFocus = $item.next().find( "a" );
4877			if ( $toFocus.length === 0 ) {
4878				$toFocus = $item.siblings( ":first" ).find( "a" );
4879			}
4880			$toFocus.trigger( setFocusEvent );
4881			break;
4882		case 27:
4883			$itemParent = $item.closest( "td" ).children( "a" );
4884			$itemParent.trigger( setFocusEvent );
4885			break;
4886	}
4887} );
4888
4889$document.on( "focusout", selectorEvent + " td > ul", function( event ) {
4890	hideEvents.call( event.target );
4891} );
4892
4893// Add the timer poke to initialize the plugin
4894wb.add( selector );
4895
4896} )( jQuery, window, wb );
4897
4898( function( $, DOMPurify, window, document, wb, undef ) {
4899
4900var i18nText,
4901	$document = wb.doc,
4902	selector = ".wb-clndr",
4903	navigateEvent = "wb-navigate" + selector,
4904	inited = false,
4905	currDate = new Date(),
4906	currYear = currDate.getFullYear(),
4907	currMonth = currDate.getMonth(),
4908	defaults = {
4909		year: currYear,
4910		month: currMonth,
4911		minDate: new Date( currYear, 0, 1 ),
4912		maxDate: new Date( currYear, 11, 31 )
4913	},
4914	$calBase,
4915
4916	init = function() {
4917		var i18n = wb.i18n,
4918			textWeekDayNames, textMonthNames;
4919
4920		i18nText = {
4921			monthNames: i18n( "mnths" ),
4922			prevMonth: i18n( "prvMnth" ),
4923			nextMonth: i18n( "nxtMnth" ),
4924			goToYear: i18n( "cal-goToYr" ),
4925			goToMonth: i18n( "cal-goToMnth" ),
4926			dayNames: i18n( "days" ),
4927			currDay: i18n( "currDay" ),
4928			format: i18n( "cal-format" ),
4929			calendar: i18n( "cal" )
4930		};
4931
4932		textWeekDayNames = i18nText.dayNames;
4933		textMonthNames = i18nText.monthNames;
4934
4935		$calBase = $( "<div class='wb-clndr' role='application' aria-label='" + i18nText.calendar + "'>" +
4936			"<div class='cal-nav'>" +
4937				"<span class='wb-inv current-month' aria-live='polite'></span>" + // Added for screen-readers
4938				"<button type='button' class='btn pull-left cal-month-prev'>" +
4939					"<span class='glyphicon glyphicon-arrow-left' aria-hidden='true'></span>" +
4940					"<span class='wb-inv'>" + i18nText.prevMonth + "<span></span></span>" +
4941				"</button>" +
4942				"<button type='button' class='btn pull-right cal-month-next'>" +
4943					"<span class='glyphicon glyphicon-arrow-right' aria-hidden='true'></span>" +
4944					"<span class='wb-inv'>" + i18nText.nextMonth + "<span></span></span>" +
4945				"</button>" +
4946				"<div class='form-group'>" +
4947					"<label><span class='wb-inv'>" + i18nText.goToYear + "</span><select class='cal-year'></select></label>\n" +
4948					"<label><span class='wb-inv'>" + i18nText.goToMonth + "</span><select class='cal-month'>" +
4949						( function() {
4950							var months = "",
4951								m;
4952
4953							for ( m = 0; m < 12; m += 1 ) {
4954								months += "<option value='" + m + "'>" + textMonthNames[ m ] + "</option>";
4955							}
4956
4957							return months;
4958						} )() +
4959					"</select></label>" +
4960				"</div>" +
4961			"</div>" +
4962			"<table>" +
4963				"<thead>" +
4964					"<tr>" +
4965						( function() {
4966							var days = "",
4967								d;
4968
4969							for ( d = 0; d < 7; d += 1 ) {
4970								days += "<th><abbr title='" + textWeekDayNames[ d ] + "'>" + textWeekDayNames[ d ].substr( 0, 1 ) + "</abbr></th>";
4971							}
4972
4973							return days;
4974						} )() +
4975					"</tr>" +
4976				"</thead>" +
4977				"<tbody class='cal-days'></tbody>" +
4978			"</table>" +
4979		"</div>" );
4980
4981		inited = true;
4982	},
4983
4984	createCalendar = function( $container, settings ) {
4985		var calendar = {
4986				reInit: initCalendar
4987			}, $calendarObj, calendarObj;
4988
4989		if ( !inited ) {
4990			init();
4991		}
4992
4993		$calendarObj = $calBase.clone();
4994		calendarObj = $calendarObj.get( 0 );
4995
4996		calendar.$o = $calendarObj;
4997		calendar.o = calendarObj;
4998		calendarObj.lib = calendar;
4999
5000		$calendarObj.appendTo( $container );
5001
5002		calendar.reInit( settings );
5003
5004		return calendar;
5005	},
5006
5007	initCalendar = function( settings ) {
5008		var years = "",
5009			$calendarObj = this.$o,
5010			$yearField, lastYear, y, defaultsPartial;
5011
5012		settings = settings || {};
5013
5014		if ( settings.year !== undef && settings.month !== undef ) {
5015			defaultsPartial = {
5016				minDate: new Date( settings.year, 0, 1 ),
5017				maxDate: new Date( settings.year, 11, 31 )
5018			};
5019			$.extend( this, defaultsPartial, settings );
5020		} else {
5021			$.extend( this, defaults );
5022		}
5023
5024		//Generates the list of years
5025		lastYear = this.maxDate.getFullYear();
5026		$yearField = $calendarObj.find( ".cal-year" ).empty();
5027		for ( y = this.minDate.getFullYear(); y <= lastYear; y += 1 ) {
5028			years += "<option value='" + y + "'>" + y + "</option>";
5029		}
5030		$yearField.append( years );
5031
5032		$calendarObj
5033			.trigger( {
5034				type: navigateEvent,
5035				year: this.year,
5036				month: this.month,
5037				initEvent: true
5038			} );
5039	},
5040
5041	createDays = function( calendar, year, month ) {
5042		var $container = $( calendar ).find( ".cal-days" ),
5043			daysContainer = $container.get( 0 ),
5044			dayCount = 1,
5045			textCurrentDay = i18nText.currDay,
5046			lib = calendar.lib,
5047			minDate = lib.minDate,
5048			maxDate = lib.maxDate,
5049			callback = lib.daysCallback,
5050			row, cell,
5051			date, firstDay, lastDay, currYear, currMonth, currDay, week, day, className, isCurrentDate, isoDate, printDate, breakAt
5051End, days, inRange;
5052
5053		date = new Date( year, month, 1 );
5054
5055		firstDay = date.getDay();
5056		date.setMonth( month + 1, 0 );
5057		lastDay = date.getDate();
5058
5059		// Get the current date
5060		date = new Date();
5061		currYear = date.getFullYear();
5062		currMonth = date.getMonth();
5063		currDay = date.getDate();
5064
5065		// Clean all existing rows
5066		$container.empty();
5067
5068		for ( week = 1; week < 7; week += 1 ) {
5069			row = daysContainer.insertRow();
5070
5071			for ( day = 0; day < 7; day += 1 ) {
5072
5073				if ( ( week === 1 && day < firstDay ) || ( dayCount > lastDay ) ) {
5074
5075					// Creates empty cells | Cree les cellules vides
5076					cell = row.insertCell();
5077					cell.classList.add( "cal-empty" );
5078					cell.textContent = " ";
5079
5080				} else {
5081
5082					// Creates date cells | Cree les cellules de date
5083					isCurrentDate = ( dayCount === currDay && month === currMonth && year === currYear );
5084					className = "cal-index-" + dayCount + ( isCurrentDate ? " cal-curr-day " : "" );
5085
5086					date.setFullYear( year, month, dayCount );
5087					isoDate = date.toLocalISOString().substr( 0, 10 );
5088					printDate = displayDate( date ) + ( isCurrentDate ? "<span class='wb-inv'>" + textCurrentDay + "</span>" : "" );
5089
5090					cell = row.insertCell();
5091					cell.setAttribute( "class", className );
5092					cell.innerHTML = DOMPurify.sanitize( "<time datetime='" + isoDate  + "'>" + printDate + "</time>" );
5093
5094					if ( dayCount >= lastDay ) {
5095						breakAtEnd = true;
5096					}
5097
5098					dayCount += 1;
5099				}
5100			}
5101			if ( breakAtEnd ) {
5102				break;
5103			}
5104		}
5105
5106		if ( callback ) {
5107			days = $container.find( "time" );
5108			inRange = {};
5109
5110			if ( year === minDate.getFullYear() && month === minDate.getMonth() ) {
5111				inRange.min = minDate.getDate() - 1;
5112			}
5113
5114			if ( year === maxDate.getFullYear() && month === maxDate.getMonth() ) {
5115				inRange.max = maxDate.getDate() - 1;
5116			}
5117
5118			callback.call( calendar.lib, year, month, days, inRange );
5119		}
5120	},
5121
5122	displayDate = function( date ) {
5123		var textWeekDayNames = i18nText.dayNames,
5124			textMonthNames = i18nText.monthNames;
5125
5126		return i18nText.format.replace( /\{ddd\}|\{d\}|\{M\}|\{Y\}/g, function( match ) {
5127			switch ( match ) {
5128				case "{ddd}":
5129					return textWeekDayNames[ parseInt( date.getDay(), 10 ) ];
5130				case "{d}":
5131					return parseInt( date.getDate(), 10 );
5132				case "{M}":
5133					return textMonthNames[ parseInt( date.getMonth(), 10 ) ];
5134				case "{Y}":
5135					return date.getFullYear();
5136			}
5137		} );
5138	};
5139
5140wb.calendar = {
5141	create: createCalendar
5142};
5143
5144$document.on( navigateEvent, selector, function( event ) {
5145	var calendarObj = event.currentTarget,
5146		$calendar = $( calendarObj ),
5147		year = event.year,
5148		month = event.month,
5149		lib = calendarObj.lib,
5150		maxYear = lib.maxDate.getFullYear(),
5151		maxMonth = lib.maxDate.getMonth(),
5152		minYear = lib.minDate.getFullYear(),
5153		minMonth = lib.minDate.getMonth(),
5154		$prevArrow = $calendar.find( ".cal-month-prev" ),
5155		$nextArrow = $calendar.find( ".cal-month-next" ),
5156		$monthField = $calendar.find( ".cal-month" ),
5157		disabled = "disabled";
5158
5159	if ( year !== undef ) {
5160		lib.year = year;
5161	}
5162
5163	if ( month !== undef ) {
5164		lib.month = month;
5165	}
5166
5167	//Update UI
5168	$calendar.find( ".cal-year" ).val( year );
5169
5170	$monthField.val( month );
5171
5172	$monthField.children( ":" + disabled ).removeAttr( disabled );
5173
5174	if ( year < minYear || ( year === minYear && month <= minMonth ) ) {
5175		$prevArrow.attr( disabled, disabled );
5176	} else {
5177		$prevArrow.removeAttr( disabled );
5178	}
5179
5180	if ( year > maxYear || ( year === maxYear && month >= maxMonth ) ) {
5181		$nextArrow.attr( disabled, disabled );
5182	} else {
5183		$nextArrow.removeAttr( disabled );
5184	}
5185
5186	if ( year === minYear ) {
5187		$monthField.children( ":lt(" + minMonth + ")" ).attr( disabled, disabled );
5188	}
5189
5190	if ( year === maxYear ) {
5191		$monthField.children( ":gt(" + maxMonth + ")" ).attr( disabled, disabled );
5192	}
5193
5194	createDays( event.currentTarget, event.year, event.month );
5195
5196	// Added declaration of current month in aria-live="polite" for screen-readers
5197	if ( !event.initEvent ) {
5198		$calendar.find( ".current-month" ).text( i18nText.monthNames[ month ] + " " + year );
5199	}
5200} );
5201
5202$document.on( "change", selector, function( event ) {
5203	var target = event.target,
5204		calendar = event.currentTarget,
5205		year, month;
5206
5207	switch ( target.className ) {
5208		case "cal-year":
5209			year = parseInt( target.value, 10 );
5210			month = calendar.lib.month;
5211			break;
5212		case "cal-month":
5213			year = calendar.lib.year;
5214			month = parseInt( target.value, 10 );
5215			break;
5216	}
5217
5218	$( calendar ).trigger( {
5219		type: navigateEvent,
5220		year: year,
5221		month: month
5222	} );
5223} );
5224
5225$document.on( "click", ".cal-month-prev, .cal-month-next", function( event ) {
5226	var $calendar = $( event.currentTarget ).closest( selector ),
5227		calendar = $calendar.get( 0 ),
5228		className = event.currentTarget.className,
5229		modifier = className.indexOf( "cal-month-prev" ) !== -1 ? -1 : 1,
5230		date = new Date( calendar.lib.year, calendar.lib.month + modifier, 1 );
5231
5232	$calendar.trigger( {
5233		type: navigateEvent,
5234		year: date.getFullYear(),
5235		month: date.getMonth()
5236	} );
5237	if ( wb.ie11 ) {
5238		$calendar.trigger( "focusin" );
5239	}
5240} );
5241
5242$document.on( "keydown", selector, function( event ) {
5243	var calendar = event.currentTarget,
5244		$days = $( event.currentTarget ).find( ".cal-days" ),
5245		target = event.target,
5246		which = event.which,
5247		lib = calendar.lib,
5248		date = new Date( lib.year, lib.month, 1 ),
5249		minDate = lib.minDate,
5250		maxDate = lib.maxDate,
5251		currentDate = new Date( date ),
5252		navigate = true,
5253		classMatch, isDayLink, lastDay, modifier, day;
5254
5255	if ( !event.altKey && !event.metaKey && which > 32 && which < 41 ) {
5256
5257		classMatch = target.parentNode.className.match( /cal-index-(\d{1,2})/ );
5258		isDayLink = classMatch !== null;
5259
5260		//Key binding for the entire calendar
5261		switch ( which ) {
5262
5263			//page up
5264			case 33:
5265				date.setDate( minDate.getDate() );
5266
5267				//page down
5268				/* falls through */
5269			case 34:
5270				modifier = ( which === 33 ? -1 : 1 );
5271
5272				if ( event.ctrlKey || event.shiftKey || event.altKey ) {
5273					date.setYear( date.getFullYear() + modifier );
5274				} else {
5275					date.setMonth( date.getMonth() + modifier );
5276				}
5277				break;
5278		}
5279
5280		//Key binding for navigating calendar days
5281		if ( isDayLink ) {
5282			day = parseInt( classMatch[ 1 ], 10 );
5283			date.setMonth( date.getMonth() + 1, 0 );
5284			lastDay = date.getDate();
5285			date.setDate( day > lastDay ? lastDay : day );
5286
5287			switch ( which ) {
5288
5289				// end / home
5290				case 35:
5291					date.setDate( lastDay );
5292					break;
5293				case 36:
5294					date.setDate( 1 );
5295					break;
5296
5297				// left / up / right / down arrows
5298				case 37:
5299					date.setDate( day - 1 );
5300					break;
5301				case 38:
5302					date.setDate( day - 7 );
5303					break;
5304				case 39:
5305					date.setDate( day + 1 );
5306					break;
5307				case 40:
5308					date.setDate( day + 7 );
5309					break;
5310			}
5311		}
5312
5313		if ( date < minDate || date > maxDate ) {
5314			if ( which === 35 ) {
5315				date.setDate( maxDate.getDate() );
5316			} else if ( which === 36 ) {
5317				date = minDate;
5318			} else {
5319				navigate = false;
5320			}
5321		}
5322
5323		if ( navigate && ( date.getMonth() !== currentDate.getMonth() || date.getFullYear() !== currentDate.getFullYear() ) ) {
5324			$( calendar ).trigger( {
5325				type: navigateEvent,
5326				year: date.getFullYear(),
5327				month: date.getMonth()
5328			} );
5329		}
5330
5331		if ( isDayLink ) {
5332			$days.find( ".cal-index-" + date.getDate() + " a:first" ).focus();
5333		}
5334
5335		event.preventDefault();
5336		return false;
5337	}
5338
5339} );
5340
5341( function() {
5342
5343	function pad( number ) {
5344		if ( number < 10 ) {
5345			return "0" + number;
5346		}
5347		return number;
5348	}
5349
5350	Date.prototype.toLocalISOString = function() {
5351		var tz = this.getTimezoneOffset();
5352		if ( tz === 0 ) {
5353			return this.toISOString();
5354		}
5355		return this.getFullYear() +
5356			"-" + pad( this.getMonth() + 1 ) +
5357			"-" + pad( this.getDate() ) +
5358			"T" + pad( this.getHours() ) +
5359			":" + pad( this.getMinutes() ) +
5360			":" + pad( this.getSeconds() ) +
5361			"." + ( this.getMilliseconds() / 1000 ).toFixed( 3 ).slice( 2, 5 ) +
5362			( tz < 0 ? "+" : "-" ) +
5363			pad( Math.floor( Math.abs( tz / 60 ) ) ) +
5364			":" + pad( tz % 60 );
5365	};
5366
5367	if ( !Date.prototype.toISOString ) {
5368		Date.prototype.toISOString = function() {
5369			return this.getUTCFullYear() +
5370				"-" + pad( this.getUTCMonth() + 1 ) +
5371				"-" + pad( this.getUTCDate() ) +
5372				"T" + pad( this.getUTCHours() ) +
5373				":" + pad( this.getUTCMinutes() ) +
5374				":" + pad( this.getUTCSeconds() ) +
5375				"." + ( this.getUTCMilliseconds() / 1000 ).toFixed( 3 ).slice( 2, 5 ) +
5376				"Z";
5377		};
5378	}
5379}() );
5380
5381} )( jQuery, DOMPurify, window, document, wb );
5382
5383/**
5384 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
5385 * @title Charts and Graph
5386 * @overview Draw charts from an html simple and complex data table
5387 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
5388 * @author @duboisp
5389 *
5390 */
5391( function( $, window, document, wb ) {
5392"use strict";
5393
5394/**
5395 * Variable and function definitions.
5396 * These are global to the plugin - meaning that they will be initialized once per page,
5397 * not once per instance of plugin on the page. So, this is a good place to define
5398 * variables that are common to all instances of the plugin on a page.
5399 */
5400var componentName = "wb-charts",
5401	selector = "." + componentName,
5402	initEvent = "wb-init" + selector,
5403	tableParsingEvent = "passiveparse.wb-tableparser",
5404	tableParsingCompleteEvent = "parsecomplete.wb-tableparser",
5405	$document = wb.doc,
5406	i18n, i18nText,
5407
5408	/**
5409	 * Main Entry function to create the charts
5410	 * @method createCharts
5411	 * @param {jQuery DOM element} $elm table element use to create the chart
5412	 */
5413	createCharts = function( $elm ) {
5414		var allSeries = [],
5415			chartslabels = [],
5416			dataSeries = [],
5417			nbBarChart = 0,
5418			$caption = $( "caption", $elm ),
5419			captionHtml = $caption.html() || "",
5420			captionText = $caption.text() || "",
5421			valuePoint = 0,
5422			dataCellUnitRegExp = /[^+\-., 0-9]+[^\-+0-9]*/,
5423			lowestFlotDelta, $imgContainer, $placeHolder,
5424			$wetChartContainer, htmlPlaceHolder, figurehtml,
5425			cellValue, datacolgroupfound, dataGroup, header,
5426			i, iLength, j, jLength, parsedData, rIndex, currVectorOptions,
5427			currentRowGroup, reverseTblParsing, dataGroupVector,
5428			currentDataGroupVector, dataCell, previousDataCell, currDataVector,
5429			pieQuaterFlotSeries, optionFlot, optionsCharts, globalOptions,
5430			defaultsOptions = {
5431
5432				// Flot Global Options
5433				flot: {
5434					prefix: "wb-charts-",
5435					defaults: {
5436						colors: wb.drawColours,
5437						canvas: true,
5438						xaxis: {
5439							ticks: { }
5440						}
5441					},
5442					line: { },
5443					area: {
5444						lines: {
5445							show: true,
5446							fill: true
5447						}
5448					},
5449					bar: {
5450						bars: {
5451							show: true,
5452							barWidth: 1,
5453							align: "center"
5454						}
5455					},
5456					pie: {
5457						series: {
5458							pie: {
5459								show: true
5460							}
5461						},
5462						fn: {
5463							"/series/pie/label/formatter": function( label, series ) {
5464								var textlabel;
5465								if ( !optionsCharts.decimal ) {
5466									textlabel = Math.round( series.percent );
5467								} else {
5468									textlabel = Math.round( series.percent * Math.pow( 10, optionsCharts.decimal ) );
5469									textlabel = textlabel / Math.pow( 10, optionsCharts.decimal );
5470								}
5471
5472								if ( optionsCharts.nolegend ) {
5473
5474									// Add the series label
5475									textlabel = label + "<br />" + textlabel;
5476								}
5477								return textlabel + "%";
5478							}
5479						}
5480					},
5481					donut: {
5482						base: "pie",
5483						series: {
5484							pie: {
5485								radius: 1,
5486								label: {
5487									show: true,
5488									radius: 1,
5489									threshold: 0.08
5490								},
5491								tilt: 0.5,
5492								innerRadius: 0.45,
5493								startAngle: 1
5494							}
5495						},
5496						grid: {
5497							hoverable: true
5498						}
5499					},
5500					slicelegend: {
5501						base: "pie",
5502						series: {
5503							pie: {
5504								radius: 1,
5505								label: {
5506									radius: 1,
5507									show: true,
5508									threshold: 0.05
5509								},
5510								combine: {
5511									threshold: 0.05,
5512									color: "#555",
5513									label: i18nText.slicelegend
5514								}
5515							}
5516						},
5517						fn: {
5518							"/series/pie/label/formatter": function( label ) {
5519								return label;
5520							}
5521						}
5522					}
5523				},
5524
5525				// Flot Series Options
5526				series: {
5527					prefix: "wb-charts-",
5528					defaults: { },
5529					line: { },
5530					area: {
5531						lines: {
5532							show: true,
5533							fill: true
5534						}
5535					},
5536					bar: {
5537						bars: {
5538							show: true,
5539							barWidth: 1,
5540							align: "center"
5541						}
5542					},
5543					stacked: {
5544						base: "bar"
5545					}
5546				},
5547
5548				// Wet-boew Charts Options
5549				charts: {
5550					prefix: "wb-charts-",
5551					defaults: {
5552
5553						// [string] Class name added at the figure element container
5554						graphclass: "wb-graph",
5555
5556						// [boolean] Wrap or not the table in a details/summary elements
5557						noencapsulation: false,
5558
5559						// [number] false means the deepest vector will be used for labelling
5560						labelposition: false,
5561
5562						// [number] false means the deepest vector will be used for calculate the reference
5563						referencevalue: false,
5564
5565						// [boolean] false means to move the legend from inside the charts to next to it
5566						legendinline: false,
5567
5568						// [boolean] true means that the legend will be destroyed and the label for pie chart will include the legend
5569						nolegend: false,
5570
5571						// [number] Literal number of displayed decimal for a pie charts
5572						decimal: 0,
5573
5574						// [number] Provide a default width for the charts that will be rendered
5575						width: $elm.width(),
5576
5577						// [number] Provide a default height for the charts that will be rendered
5578						height: $elm.height(),
5579
5580						// [boolean] Flag for defining if the data table should be read in reverse compared to HTML spec
5581						reversettblparsing: false,
5582						fn: {
5583							"/getcellvalue": function( elem ) {
5584
5585								// Get the number from the data cell, #3267
5586								var cellValue = $.trim( elem.dataset.wbChartsValue || $( elem ).text() );
5587								return [
5588									parseFloat( cellValue.replace( /(\d{1,3}(?:(?: |,)\d{3})*)(?:(?:.|,)(\d{1,2}))?$/, function( a, b, c ) {
5589										return b.replace( / |,/g, "" ) + "." + c || "0";
5590									} ), 10 ),
5591									cellValue.match( dataCellUnitRegExp )
5592								];
5593							}
5594						}
5595					},
5596					donut: {
5597						decimal: 1
5598					}
5599				}
5600			};
5601
5602		/**
5603		 * A little function to overwrite and add preset into the default options
5604		 *
5605		 * @method overwriteDefaultsOptions
5606		 * @param {string} scopekey - Key that represent the subject of the setting, [flot, charts, series,...]
5607		 * @param {json object} target - DefaultOptions that will be overwritten
5608		 * @param {json object} object - User defined object for overwriting options
5609		 * @return {json object} - Return the new object
5610		 */
5611		function overwriteDefaultsOptions( scopekey, target, object ) {
5612			var cachedObj, key;
5613
5614			cachedObj = object[ scopekey ];
5615			if ( !cachedObj ) {
5616				return target;
5617			}
5618			for ( key in cachedObj ) {
5619				if ( !Object.prototype.hasOwnProperty.call( cachedObj, key ) ) {
5620					continue;
5621				}
5622				target[ scopekey ][ key ] = cachedObj[ key ];
5623			}
5624			return target;
5625		}
5626
5627		// User defined options
5628		if ( !window.chartsGraphOpts ) {
5629			globalOptions = window[ componentName ];
5630
5631			// Global setting
5632			if ( globalOptions ) {
5633				overwriteDefaultsOptions( "flot", defaultsOptions, globalOptions );
5634				overwriteDefaultsOptions( "series", defaultsOptions, globalOptions );
5635				overwriteDefaultsOptions( "charts", defaultsOptions, globalOptions );
5636			}
5637
5638			// Save the setting here in a case of a second graphic on the same page
5639			window.chartsGraphOpts = defaultsOptions;
5640		}
5641		defaultsOptions = window.chartsGraphOpts;
5642
5643		/**
5644		 * A little function to ease the web editor life
5645		 *
5646		 * Apply preset defined by a set of space-separated tokens from a baseline json object and at the same time extend the result by using the HTML5 data attribute
5647		 *
5648		 * @method applyPreset
5649		 * @param {json object} baseline - Base line json object that includes predefined and user defined preset
5650		 * @param {jQuery} $elem - Element on which the class attribute will be taken for a set of space-separated tokens
5651		 * @param {string} attribute - Name of the HTML5 data attribute for extending the object at the end
5652		 * @return {json object} - Return a new object build from the ```baseline``` or ```baseline.default``` object with the preset applied.
5653		 */
5654		function applyPreset( baseline, $elem, attribute ) {
5655
5656			var config = $.extend( true, {}, baseline.defaults || baseline ),
5657				fn = $.extend( true, {}, baseline.defaults && baseline.defaults.fn || { } ),
5658				tokens = $elem.attr( "class" ) || "",
5659				tblTokens, i, iLength, token, tokenLength,
5660
5661				// Prefix used in front of the token
5662				prefix, prefixLength,
5663				preset, key, tblFn, localKey, currObj;
5664
5665			if ( tokens.length ) {
5666
5667				prefix = ( baseline.prefix || "" );
5668				prefixLength = prefix.length;
5669
5670				// split the set of space-separated tokens
5671				tblTokens = tokens.split( " " );
5672
5673				for ( i = 0, iLength = tblTokens.length; i !== iLength; i += 1 ) {
5674
5675					// Get the current token
5676					token = tblTokens[ i ];
5677					tokenLength = token.length;
5678
5679					// Remove the token is used
5680					if ( tokenLength <= prefixLength || token.slice( 0, prefixLength ) !== prefix ) {
5681						continue;
5682					}
5683					token = token.slice( prefixLength, tokenLength );
5684
5685					preset = baseline[ token ];
5686
5687					// Apply the preset
5688					if ( preset ) {
5689						if ( preset.base ) {
5690
5691							// Like setting inherited from a parent config
5692							config = $.extend( true, config, baseline[ preset.base ] );
5693							fn = $.extend( true, fn, baseline[ preset.base ].fn || { } );
5694						}
5695						config = $.extend( true, config, preset );
5696						fn = $.extend( true, fn, preset.fn || { } );
5697					}
5698				}
5699			}
5700
5701			// Extend the config from the element @data attribute
5702			config = $.extend( true, config, wb.getData( $elem, attribute ) );
5703
5704			// Merge and override the function.
5705			for ( key in fn ) {
5706				if ( !Object.prototype.hasOwnProperty.call( fn, key ) ) {
5707					continue;
5708				}
5709				tblFn = key.split( "/" );
5710				currObj = config;
5711				for ( i = 0, iLength = tblFn.length - 1; i !== iLength; i += 1 ) {
5712					localKey = tblFn.shift();
5713					if ( localKey === "" ) {
5714						continue;
5715					}
5716					if ( !currObj[ localKey ] ) {
5717						currObj[ localKey ] = { };
5718					}
5719					currObj = currObj[ localKey ];
5720				}
5721				localKey = tblFn.shift();
5722				currObj[ localKey ] = fn[ key ];
5723			}
5724			return config;
5725		}
5726
5727		// Apply any preset
5728		optionFlot = applyPreset( defaultsOptions.flot, $elm, "flot" );
5729
5730		// Apply any preset
5731		optionsCharts = applyPreset( defaultsOptions.charts, $elm, componentName );
5732
5733		// Fix default width and height in case the table is hidden or too small.
5734		optionsCharts.width = ( optionsCharts.width && optionsCharts.width > 250 ? optionsCharts.width : 250 );
5735		optionsCharts.height = ( optionsCharts.height && optionsCharts.height > 250 ? optionsCharts.height : 250 );
5736
5737		/**
5738		 * @method getColumnGroupHeaderCalculateSteps
5739		 * @param {object} colGroupHead - Column Group Header Object from the table parser
5740		 * @param {number} referenceValuePosition - Vector position use as reference for defining the steps, zero based position
5741		 */
5742		function getColumnGroupHeaderCalculateSteps( colGroupHead, referenceValuePosition ) {
5743
5744			// Get the appropriate ticks
5745			var headerCell, i, iLen,
5746				calcStep = 1,
5747				colRefValue, colCurent;
5748
5749			if ( !colGroupHead ) {
5750
5751				// There is an error. Possibly the series are missing a header.
5752				return;
5753			}
5754
5755			colRefValue = colGroupHead.col[ referenceValuePosition ];
5756			colCurent = colGroupHead.col[ 0 ];
5757
5758			for ( i = 0, iLen = colRefValue.cell.length; i !== iLen; i += 1 ) {
5759
5760				headerCell = colRefValue.cell[ i ];
5761
5762				if ( i === 0 || ( i > 0 && colCurent.cell[ i - 1 ].uid !== headerCell.uid ) ) {
5763
5764					if ( headerCell.rowgroup && headerCell.rowgroup.type === 3 ) {
5765
5766						// We only process the first column data group
5767						break;
5768					}
5769
5770					if ( headerCell.type === 1 || headerCell.type === 7 ) {
5771						if ( headerCell.child.length !== 0 ) {
5772							calcStep = calcStep * groupHeaderCalculateStepsRecursive( headerCell, 1 );
5773						}
5774					}
5775				}
5776			}
5777
5778			return calcStep;
5779		}
5780
5781		/**
5782		 * @method getRowGroupHeaderCalculateSteps
5783		 * @param {object} rowGroupHead - Row Group Header Object from the table parser
5784		 * @param {number} referenceValuePosition - Vector position use as reference for defining the steps, zero based position
5785		 * @param {number} dataColgroupStart - Column position where the column data group start
5786		 */
5787		function getRowGroupHeaderCalculateSteps( rowGroupHead, referenceValuePosition, dataColgroupStart ) {
5788
5789			// Find the range of the first data colgroup
5790			var headerCell, i, iLen,
5791				calcStep = 1,
5792				rowRefValueCells = rowGroupHead[ referenceValuePosition ].elem.cells;
5793
5794			for ( i = 0, iLen = rowRefValueCells.length; i !== iLen; i += 1 ) {
5795
5796				headerCell = $( rowRefValueCells[ i ] ).data().tblparser;
5797
5798				if ( headerCell.colgroup && headerCell.colgroup.type === 3 ) {
5799
5800					// We only process the first column data group
5801					break;
5802				}
5803
5804				if ( headerCell.colpos >= dataColgroupStart && ( headerCell.type === 1 || headerCell.type === 7 ) ) {
5805					if ( headerCell.child.length !== 0 ) {
5806						calcStep = calcStep * headerCell.child.length * groupHeaderCalculateStepsRecursive( headerCell, 1 );
5807					}
5808				}
5809			}
5810
5811			return calcStep;
5812		}
5813
5814		/**
5815		 * @method groupHeaderCalculateStepsRecursive
5816		 * @param {object} headerCell - Header cell object from the table parser
5817		 * @param {number} refValue - Reference Value (Dénominateur) of headerCell
5818		 */
5819		function groupHeaderCalculateStepsRecursive( headerCell, refValue ) {
5820			var childLength = headerCell.child.length,
5821				calcStep = 1,
5822				kIndex, subRefValue, headerCellChild;
5823
5824			if ( childLength === 0 ) {
5825				return calcStep;
5826			}
5827
5828			subRefValue = childLength * refValue;
5829
5830			calcStep = calcStep * subRefValue;
5831
5832			for ( kIndex = 0; kIndex !== childLength; kIndex += 1 ) {
5833				headerCellChild = headerCell.child[ kIndex ];
5834				if ( headerCellChild.child.length !== 0 ) {
5835					calcStep = calcStep * groupHeaderCalculateStepsRecursive( headerCellChild, subRefValue );
5836				}
5837			}
5838			return calcStep;
5839		}
5840
5841		/**
5842		 * Set the inner step value (divisor) of an header cell and for his child
5843		 *
5844		 * @method setInnerStepValues
5845		 * @param {object} vectorHead - Group Header Object from the table parser
5846		 * @param {number} headerLevel - Hierarchical Level of heading
5847		 * @param {number} stepsValue - Step Value for the reference value vector
5848		 * @param {number} referenceValue - Reference Value Vector ID
5849		 * @param {number} dataColgroupStart - Column position where the column data group start
5850		 *
5851		 */
5852		function setInnerStepValues( vectorHead, headerLevel, stepsValue, referenceValue, dataColgroupStart ) {
5853			var i, iLength,
5854				headerCell,
5855				cumulativeValue = 0;
5856
5857			for ( i = 0, iLength = vectorHead.cell.length; i !== iLength; i += 1 ) {
5858				headerCell = vectorHead.cell[ i ];
5859				if ( i !== 0 && headerCell.uid === vectorHead.cell[ i - 1 ].uid || ( dataColgroupStart && headerCell.colpos < dataColgroupStart ) ) {
5860					continue;
5861				}
5862
5863				// Only process the first data group
5864				if ( !reverseTblParsing ) {
5865					if ( headerCell.colgroup && headerCell.colgroup.type === 3 ) {
5866						break;
5867					}
5868				} else {
5869					if ( headerCell.rowgroup && headerCell.rowgroup.type === 3 ) {
5870						break;
5871					}
5872				}
5873				if ( headerCell.child > 0 && headerLevel < referenceValue ) {
5874					headerCell.flotDelta = stepsValue * headerCell.child.length;
5875				} else {
5876					headerCell.flotDelta = stepsValue;
5877				}
5878				if ( headerCell.type === 1 || headerCell.type === 7  ) {
5879
5880					if ( !lowestFlotDelta || headerCell.flotDelta < lowestFlotDelta ) {
5881						lowestFlotDelta = headerCell.flotDelta;
5882					}
5883					headerCell.flotValue = cumulativeValue;
5884
5885					cumulativeValue = cumulativeValue + stepsValue;
5886
5887					if ( headerCell.child.length > 0 ) {
5888						setInnerStepValuesChildRecursive( headerCell, headerLevel, stepsValue, referenceValue );
5889					}
5890				}
5891			}
5892		}
5893
5894		/**
5895		 * Recursize - Set the inner step value (divisor) of an sub header cell
5896		 *
5897		 * @method setInnerStepValuesChildRecursive
5898		 * @param {object} headerCell - Header cell object from the table parser
5899		 * @param {number} headerLevel - Hierarchical Level of heading
5900		 * @param {number} stepsValue - Specific Step Value applied for current headerCell
5901		 * @param {number} referenceValue - Reference Value Vector ID
5902		 */
5903		function setInnerStepValuesChildRecursive( headerCell, headerLevel, stepsValue, referenceValue ) {
5904			var cumulativeValue = 0,
5905
5906				// Step Values for childs header in headerCell
5907				flotDelta,
5908				i, iLength,	currentHeaderCellChild;
5909
5910			headerLevel += 1;
5911			cumulativeValue = headerCell.flotValue;
5912			flotDelta = stepsValue / headerCell.child.length;
5913
5914			// Use to calculate the largest width for a bar in a bar chart
5915			if ( !lowestFlotDelta || flotDelta < lowestFlotDelta ) {
5916				lowestFlotDelta = flotDelta;
5917			}
5918
5919			for ( i = 0, iLength = headerCell.child.length; i !== iLength; i += 1 ) {
5920				currentHeaderCellChild = headerCell.child[ i ];
5921				if ( headerLevel < referenceValue ) {
5922					currentHeaderCellChild.flotDelta = flotDelta * currentHeaderCellChild.child.length;
5923				} else {
5924					currentHeaderCellChild.flotDelta = flotDelta;
5925				}
5926				currentHeaderCellChild.flotValue = cumulativeValue;
5927				if ( currentHeaderCellChild.child.length > 0 ) {
5928					setInnerStepValuesChildRecursive( currentHeaderCellChild, headerLevel, flotDelta, referenceValue );
5929				}
5930				cumulativeValue = cumulativeValue + flotDelta;
5931			}
5932		}
5933
5934		/**
5935		 * Set the header cell step value (flotDelta) for vector that regroup more than one reference
5936		 *
5937		 * @method setUpperStepValues
5938		 * @param {object} vectorHead - Group Header Object from the table parser
5939		 * @param {number} referenceValue - Reference Value Vector ID
5940		 */
5941		function setUpperStepValues( vectorHead, referenceValue ) {
5942			var i, k, m, kLen, mLen,
5943				cumulativeValue,
5944				currentCell,
5945				currentCellChild,
5946				currentVectorHead;
5947
5948			// Calculate upper-step for cells that are
5949			// less precise than the reference value vector
5950			for ( i = referenceValue - 1; i !== -1; i -= 1 ) {
5951				currentVectorHead = vectorHead[ i ];
5952
5953				for ( k = 0, kLen = currentVectorHead.cell.length; k !== kLen; k += 1 ) {
5954					currentCell = currentVectorHead.cell[ k ];
5955
5956					if ( currentCell.flotDelta || k > 0 &&
5957						currentCell.uid === currentVectorHead.cell[ k - 1 ].uid ) {
5958
5959						continue;
5960					}
5961
5962					if ( !( currentCell.type === 1 || currentCell.type === 7 ) ) {
5963						continue;
5964					}
5965
5966					cumulativeValue = 0;
5967					for ( m = 0, mLen = currentCell.child.length; m !== mLen; m += 1 ) {
5968						currentCellChild = currentCell.child[ m ];
5969
5970						cumulativeValue = currentCellChild.flotDelta;
5971						if ( !currentCell.flotValue ) {
5972							currentCell.flotValue = currentCellChild.flotValue;
5973						}
5974					}
5975					currentCell.flotDelta = cumulativeValue;
5976				}
5977			}
5978		}
5979
5980		/**
5981		 * Get labels for a specific vector
5982		 *
5983		 * @method getLabels
5984		 * @param {object} labelVector - Vector Header Object from the table parser
5985		 * @param {number} dataColgroupStart - Column position where the column data group start
5986		 */
5987		function getLabels( labelVector, dataColgroupStart ) {
5988			var labels = [],
5989				i, iLen, currentCell;
5990
5991			for ( i = 0, iLen = labelVector.cell.length; i !== iLen; i += 1 ) {
5992				currentCell = labelVector.cell[ i ];
5993
5994				if ( ( i !== 0 && currentCell.uid === labelVector.cell[ i - 1 ].uid ) ||
5995						( !( currentCell.type === 1 || currentCell.type === 7 ) ) ||
5996						( dataColgroupStart && currentCell.colpos < dataColgroupStart ) ) {
5997					continue;
5998				}
5999
6000				labels.push( [ currentCell.flotValue, $( currentCell.elem ).text() ] );
6001			}
6002			return labels;
6003		}
6004
6005		/**
6006		 * Get the vector that would be used for labelling x-axis
6007		 *
6008		 * @method getlabelsVectorPosition
6009		 * @param {object[]} arrVectorHeaders - Collection of vector headers
6010		 */
6011		function getlabelsVectorPosition( arrVectorHeaders ) {
6012			var labelPosition = optionsCharts.labelposition;
6013			return ( !labelPosition || ( labelPosition && labelPosition > arrVectorHeaders.length ) ?
6014				parsedData.theadRowStack.length : labelPosition ) - 1;
6015		}
6016
6017		/**
6018		 * Get the vertical label and set the appropriate header cell x-axis Value
6019		 *
6020		 * @method verticalLabels
6021		 * @param {object} parsedData - Generic object generated by the table parser
6022		 */
6023		function verticalLabels( parsedData ) {
6024
6025			// Get the appropriate ticks
6026			var headerlevel = 0,
6027				labelsVectorPosition, stepsValue, columnReferenceValue;
6028
6029			if ( !reverseTblParsing || ( reverseTblParsing && optionsCharts.referencevalue === false ) ) {
6030				columnReferenceValue = parsedData.colgrouphead.col.length;
6031			} else {
6032				columnReferenceValue = optionsCharts.referencevalue;
6033			}
6034
6035			columnReferenceValue = columnReferenceValue - 1;
6036
6037			stepsValue = getColumnGroupHeaderCalculateSteps( parsedData.colgrouphead, columnReferenceValue );
6038
6039			if ( !reverseTblParsing ) {
6040				labelsVectorPosition = parsedData.colgrouphead.col.length - 1;
6041			} else {
6042				labelsVectorPosition = getlabelsVectorPosition( parsedData.colgrouphead.col );
6043			}
6044
6045			headerlevel = columnReferenceValue;
6046
6047			// Calculate inner-step for cells that are more precise than the reference value vector
6048			setInnerStepValues( parsedData.colgrouphead.col[ columnReferenceValue ], headerlevel, stepsValue, columnReferenceValue );
6049
6050			// Calculate upper-step for cells that are less precise than the reference value vector
6051			setUpperStepValues( parsedData.colgrouphead.col, columnReferenceValue );
6052
6053			// Get the labelling
6054			return getLabels( parsedData.colgrouphead.col[ labelsVectorPosition ] );
6055		}
6056
6057		/**
6058		 * Get the horizontal label and set the appropriate header cell x-axis Value
6059		 *
6060		 * @method horizontalLabels
6061		 * @param {object} parsedData - Generic object generated by the table parser
6062		 */
6063		function horizontalLabels( parsedData ) {
6064
6065			// Find the range of the first data colgroup
6066			var dataColgroupStart = -1,
6067				headerlevel = 0,
6068				theadRowStack = parsedData.theadRowStack,
6069				i, iLength, labelsVectorPosition,
6070				stepsValue, rowReferenceValue;
6071
6072			if ( !theadRowStack ) {
6073				return;
6074			}
6075
6076			for ( i = 0, iLength = parsedData.colgroup.length; i !== iLength; i += 1 ) {
6077				if ( parsedData.colgroup[ i ].type === 2 ) {
6078					dataColgroupStart = parsedData.colgroup[ i ].start;
6079					break;
6080				}
6081			}
6082
6083			if ( ( !reverseTblParsing && optionsCharts.referencevalue === false ) || reverseTblParsing ) {
6084				rowReferenceValue = theadRowStack.length;
6085			} else {
6086				rowReferenceValue = optionsCharts.referencevalue;
6087			}
6088
6089			rowReferenceValue = rowReferenceValue - 1;
6090
6091			stepsValue = getRowGroupHeaderCalculateSteps( theadRowStack, rowReferenceValue, dataColgroupStart );
6092
6093			if ( !reverseTblParsing ) {
6094				labelsVectorPosition = getlabelsVectorPosition( theadRowStack );
6095			} else {
6096				labelsVectorPosition = theadRowStack.length - 1;
6097			}
6098
6099			headerlevel = rowReferenceValue;
6100
6101			// Calculate inner-step for cells that are more precise than the reference value vector
6102			setInnerStepValues( theadRowStack[ rowReferenceValue ], headerlevel, stepsValue, rowReferenceValue, dataColgroupStart );
6103
6104			// Calculate upper-step for cells that are less precise than the reference value vector
6105			setUpperStepValues( theadRowStack, rowReferenceValue );
6106
6107			// Get the labelling
6108			return getLabels( theadRowStack[ labelsVectorPosition ], dataColgroupStart );
6109
6110		}
6111
6112		/**
6113		 * Wrap the table into a smart details/summary element
6114		 *
6115		 * @method wrapTableIntoDetails
6116		 */
6117		function wrapTableIntoDetails() {
6118			var $summary;
6119
6120			if ( !captionHtml.length ) {
6121				return;
6122			}
6123
6124			$summary = $( "<summary>" + captionHtml + i18nText.tableMention + "</summary>" );
6125			$elm
6126				.wrap( "<details></details>" )
6127				.before( $summary );
6128
6129			$summary.trigger( "wb-init.wb-details" );
6130		}
6131
6132		function createContainer( withDimension ) {
6133			$elm
6134				.wrap( "<figure class='" + optionsCharts.graphclass + "'/>" )
6135				.before(
6136
6137					// Copy to the inner table caption
6138					( captionHtml.length ? "<figcaption>" + captionHtml + "</figcaption>" : "" ) +
6139
6140					// Image Container
6141					"<div role='img' aria-label='" + captionText + i18nText.tableFollowing + "'" +
6142
6143					// Add Dimension
6144					( withDimension ? "style='height:" + optionsCharts.height +
6145					"px; width:" + optionsCharts.width + "px'" : "" ) + "></div>"
6146				);
6147
6148			return $( "div:eq(0)", $elm.parent() );
6149		}
6150
6151		// Retrieve the parsed data
6152		parsedData = $elm.data().tblparser;
6153
6154		// Reverse table parsing
6155		reverseTblParsing = optionsCharts.reversettblparsing;
6156
6157		// first data row group
6158		currentRowGroup = parsedData.lstrowgroup[ 0 ];
6159
6160		if ( optionFlot.series && optionFlot.series.pie ) {
6161
6162			// WET Charts placeholder
6163			$wetChartContainer = createContainer( false );
6164
6165			// Flot pie chart placeholder
6166			htmlPlaceHolder = "<div style='height:" + optionsCharts.height +
6167				"px; width:" + optionsCharts.width + "px'></div>";
6168
6169			if ( !reverseTblParsing ) {
6170
6171				// If normal parsing
6172				dataGroup = parsedData.colgroup[ 0 ].type === 1 ?
6173					parsedData.colgroup[ 1 ] :
6174					parsedData.colgroup[ 0 ];
6175
6176				rIndex = currentRowGroup.row.length - 1;
6177			} else {
6178
6179				// If reverse parsing
6180				dataGroup = currentRowGroup;
6181				rIndex = ( parsedData.colgroup[ 0 ].type === 1 ?
6182					parsedData.colgroup[ 1 ].col.length :
6183					parsedData.colgroup[ 0 ].col.length ) - 1;
6184			}
6185
6186			for ( rIndex; rIndex >= 0; rIndex -= 1 ) {
6187
6188				dataGroupVector = !reverseTblParsing ? dataGroup.col : dataGroup.row;
6189
6190				// For each row or column
6191				for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) {
6192					dataSeries = [];
6193					valuePoint = 0;
6194					currentDataGroupVector = dataGroupVector[ i ];
6195
6196					// For each cells
6197					for ( j = 0, jLength = currentDataGroupVector.cell.length; j !== jLength; j += 1 ) {
6198
6199						dataCell = currentDataGroupVector.cell[ j ];
6200
6201						// Skip the column if
6202						if ( reverseTblParsing && dataCell.col.type === 1 ) {
6203							continue;
6204						}
6205
6206						previousDataCell = undefined;
6207						if ( j !== 0 ) {
6208							previousDataCell = currentDataGroupVector.cell[ j - 1 ];
6209						}
6210
6211						// Verify if the selected cell still in the scope of a data group in his another axes (eg. row/col)
6212						// Verify if we are still in the same datagroup as the previous data cell
6213						if ( ( !reverseTblParsing && ( dataCell.row.type !== 2  || ( previousDataCell &&
6214								previousDataCell.rowgroup.uid !== dataCell.rowgroup.uid ) ) ) ||
6215								( reverseTblParsing && ( dataCell.col.type !== 2 ) || ( previousDataCell &&
6216								previousDataCell.col.type !== 1 &&
6217								previousDataCell.col.groupstruct.uid !== dataCell.col.groupstruct.uid ) ) ) {
6218							break;
6219						}
6220
6221						// Gets the value
6222						header = !reverseTblParsing ? dataCell.row.header : dataCell.col.header;
6223
6224						cellValue = optionsCharts.getcellvalue( !reverseTblParsing ?
6225							currentDataGroupVector.cell[ rIndex ].elem :
6226							currentDataGroupVector.datacell[ rIndex ].elem );
6227
6228						dataSeries.push(
6229							[
6230								valuePoint,
6231								typeof cellValue === "object" ?
6232									cellValue[ 0 ] :
6233									cellValue
6234							]
6235						);
6236
6237						valuePoint += header[ header.length - 1 ].flotDelta;
6238
6239						break;
6240					}
6241
6242					pieQuaterFlotSeries = { };
6243
6244					// Get the setting from the associative cell header
6245					dataCell = !reverseTblParsing ?
6246						currentDataGroupVector.cell[ rIndex ] :
6247						currentDataGroupVector.datacell[ rIndex ];
6248					header = !reverseTblParsing ?
6249						dataCell.col.header :
6250						dataCell.row.header;
6251					header = header[ header.length - 1 ];
6252
6253					// Apply any preset
6254					pieQuaterFlotSeries = applyPreset( defaultsOptions.series, $( header.elem ), "flot" );
6255
6256					// Set the data issue from the table
6257					pieQuaterFlotSeries.data = dataSeries;
6258					pieQuaterFlotSeries.label = ( !reverseTblParsing ?
6259						$( currentDataGroupVector.dataheader[ currentDataGroupVector.dataheader.length - 1 ].elem ).text() :
6260						$( currentDataGroupVector.header[ currentDataGroupVector.header.length - 1 ].elem ).text() );
6261
6262					// Add the series
6263					allSeries.push( pieQuaterFlotSeries );
6264				}
6265
6266				// Create a sub Figure or use the main one
6267				if ( currentRowGroup.row.length === 1 &&
6268					( currentRowGroup.row[ 0 ].header[ 0 ].elem.innerHTML === captionHtml ||
6269					currentRowGroup.row[ 0 ].header.length === 0 ) ) {
6270
6271					$placeHolder = $wetChartContainer;
6272					$placeHolder.css( {
6273						height: optionsCharts.height,
6274						width: optionsCharts.width
6275					} );
6276
6277				} else {
6278
6279					header = !reverseTblParsing ? dataCell.row.header : dataCell.col.header;
6280
6281					figurehtml = "<figure><figcaption>" +
6282						header[ header.length - 1 ].elem.innerHTML +
6283						"</figcaption>" + htmlPlaceHolder + "</figure>";
6284
6285					$wetChartContainer.append( $( figurehtml ) );
6286
6287					$placeHolder = $( "div:last()", $wetChartContainer );
6288				}
6289
6290				// Create the graphic
6291				$.plot( $placeHolder, allSeries, optionFlot );
6292
6293				if ( !optionsCharts.legendinline ) {
6294
6295					// Move the legend under the graphic
6296					$( ".legend", $placeHolder ).appendTo( $wetChartContainer );
6297				}
6298
6299				allSeries = [];
6300			}
6301
6302			if ( optionsCharts.nolegend ) {
6303
6304				// Remove the legend
6305				$( ".legend", $wetChartContainer ).remove();
6306			}
6307			if ( !optionsCharts.legendinline ) {
6308
6309				// Fix the legend that appear under the graphic
6310				$( ".legend > div", $wetChartContainer ).remove();
6311				$( ".legend > table", $wetChartContainer ).removeAttr( "style" ).addClass( "font-small" );
6312				$( ".legend", $placeHolder ).appendTo( $imgContainer );
6313			}
6314
6315			// Remove any "pieLabel" ids set by the flotPie.js plugin at line #457
6316			$( ".pieLabel" ).removeAttr( "id" );
6317
6318			if ( !optionsCharts.noencapsulation ) {
6319				wrapTableIntoDetails();
6320			}
6321
6322			return;
6323		}
6324
6325		if ( !reverseTblParsing ) {
6326
6327			// If normal parsing
6328			dataGroup = currentRowGroup;
6329			rIndex = ( parsedData.colgroup[ 0 ].type === 1 ?
6330				parsedData.colgroup[ 1 ].col.length :
6331				parsedData.colgroup[ 0 ].col.length ) - 1;
6332			chartslabels = horizontalLabels( parsedData );
6333		} else {
6334
6335			// If reverse parsing
6336			dataGroup = parsedData.colgroup[ 0 ].type === 1 ?
6337				parsedData.colgroup[ 1 ] :
6338				parsedData.colgroup[ 0 ];
6339			rIndex = currentRowGroup.row.length - 1;
6340			chartslabels = verticalLabels( parsedData );
6341		}
6342
6343		// Add the labels at the Flot options
6344		optionFlot.xaxis.ticks = chartslabels;
6345
6346		dataGroupVector = !reverseTblParsing ? dataGroup.row : dataGroup.col;
6347
6348		// Count the number of bar charts,
6349		for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) {
6350			currentDataGroupVector = dataGroupVector[ i ];
6351			currDataVector = currentDataGroupVector.header[ currentDataGroupVector.header.length - 1 ];
6352
6353			// Apply any preset
6354			currVectorOptions = applyPreset( defaultsOptions.series, $( currDataVector.elem ), "flot" );
6355
6356			if ( currVectorOptions.bars || ( optionFlot.bars && !currVectorOptions.lines ) ) {
6357
6358				// Count number of bars, this number is use to calculate the bar width.
6359				nbBarChart += 1;
6360
6361				// Set a default setting specially for bar charts
6362				if ( !currVectorOptions.bars ) {
6363					currVectorOptions.bars = { show: true, barWidth: 0.9 };
6364				}
6365
6366				// Set a default order for orderBars flot plugin
6367				if ( !currVectorOptions.bars.order ) {
6368					currVectorOptions.bars.order = nbBarChart;
6369				}
6370			}
6371
6372			// cache the compiled setting
6373			currDataVector.chartOption = currVectorOptions;
6374		}
6375
6376		// First rowgroup assume is a data row group.
6377		// For all row....
6378		for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) {
6379			dataSeries = [];
6380			datacolgroupfound = 0;
6381			valuePoint = 0;
6382			currDataVector = dataGroupVector[ i ];
6383
6384			currVectorOptions = currDataVector.header[ currDataVector.header.length - 1 ].chartOption;
6385
6386			// For each cells
6387			for ( j = 0, jLength = currDataVector.cell.length; j !== jLength; j += 1 ) {
6388
6389				dataCell = currDataVector.cell[ j ];
6390
6391				if ( datacolgroupfound > 1 && dataCell.col.groupstruct.type !== 2 ) {
6392					break;
6393				}
6394
6395				if ( ( !reverseTblParsing && dataCell.col.groupstruct.type === 2 ) ||
6396						( reverseTblParsing && dataCell.row.rowgroup.type === 2 ) ) {
6397
6398					// Gets the value
6399					header = !reverseTblParsing ? dataCell.col.header : dataCell.row.header;
6400
6401					cellValue = optionsCharts.getcellvalue( dataCell.elem );
6402
6403					// Add the data point
6404					dataSeries.push(
6405						[
6406							valuePoint,
6407							typeof cellValue === "object" ?
6408								cellValue[ 0 ] :
6409								cellValue
6410						]
6411					);
6412					valuePoint += header[ header.length - 1 ].flotDelta;
6413					datacolgroupfound += 1;
6414				}
6415			}
6416
6417			currVectorOptions.data = dataSeries;
6418			currVectorOptions.label = $( currDataVector.header[ currDataVector.header.length - 1 ].elem ).text();
6419
6420			if ( currVectorOptions.bars ) {
6421
6422				// Adjust the bars width
6423				currVectorOptions.bars.barWidth = currVectorOptions.bars.barWidth * ( 1 / nbBarChart );
6424			}
6425
6426			allSeries.push( currVectorOptions );
6427
6428		}
6429
6430		if ( optionFlot.bars ) {
6431
6432			// Adjust the bars width
6433			optionFlot.bars.barWidth = optionFlot.bars.barWidth * ( 1 / nbBarChart );
6434		}
6435
6436		// WET Charts Placeholder
6437		$placeHolder = createContainer( true );
6438
6439		// Maximum width
6440		$placeHolder.css( "width", "100%" );
6441
6442		// Create the graphic
6443		$.plot( $placeHolder, allSeries, optionFlot );
6444
6445		if ( !optionsCharts.legendinline ) {
6446
6447			// Move the legend under the graphic
6448			$( ".legend > div", $placeHolder ).remove();
6449			$( ".legend > table", $placeHolder ).removeAttr( "style" ).addClass( "font-small" );
6450			$placeHolder.css( "height", "auto" );
6451		}
6452		if ( optionsCharts.nolegend ) {
6453
6454			// Remove the legend
6455			$( ".legend", $placeHolder ).remove();
6456		}
6457
6458		if ( !optionsCharts.noencapsulation ) {
6459			wrapTableIntoDetails();
6460		}
6461
6462		$( "canvas:eq(1)", $placeHolder ).css( "position", "static" );
6463		$( "canvas:eq(0)", $placeHolder ).css( "width", "100%" );
6464
6465		$elm.trigger( "wb-updated" + selector );
6466	},
6467
6468	/**
6469	 * @method init
6470	 * @param {jQuery Event} event Event that triggered this handler
6471	 */
6472	init = function( event ) {
6473
6474		// Start initialization
6475		// returns DOM object = proceed with init
6476		// returns undefined = do not proceed with init (e.g., already initialized)
6477		var elm = wb.init( event, componentName, selector ),
6478			settings = window[ componentName ],
6479			elmId, modeJS, deps;
6480
6481		if ( elm ) {
6482			elmId = elm.id;
6483			modeJS = wb.getMode() + ".js";
6484			deps = [
6485				"site!deps/jquery.flot" + modeJS,
6486				"site!deps/jquery.flot.pie" + modeJS,
6487				"site!deps/jquery.flot.canvas" + modeJS,
6488				"site!deps/jquery.flot.orderBars" + modeJS,
6489				"site!deps/tableparser" + modeJS
6490			];
6491
6492			//TODO: Revisit this in the new plugin structure
6493			if ( settings && settings.plugins ) {
6494				deps = deps.concat( settings.plugins );
6495			}
6496
6497			// Only initialize the i18nText once
6498			if ( !i18nText ) {
6499				i18n = wb.i18n;
6500				i18nText = {
6501					tableMention: i18n( "hyphen" ) + i18n( "tbl-txt" ),
6502					tableFollowing: i18n( "hyphen" ) + i18n( "tbl-dtls" ),
6503					slicelegend: i18n( "chrt-cmbslc" )
6504				};
6505			}
6506
6507			// Load the required dependencies
6508			Modernizr.load( {
6509
6510				// For loading multiple dependencies
6511				load: deps,
6512				complete: function() {
6513					var $elm = $( "#" + elmId );
6514
6515					// Let's parse the table
6516					$elm.trigger( tableParsingEvent );
6517
6518					// Identify that initialization has completed
6519					wb.ready( $elm, componentName );
6520				}
6521			} );
6522		}
6523	};
6524
6525// Bind the init event of the plugin
6526$document.on( "timerpoke.wb " + initEvent + " " + tableParsingCompleteEvent, selector, function( event ) {
6527	var eventType = event.type,
6528		elm = event.target;
6529
6530	switch ( eventType ) {
6531
6532		/*
6533		 * Init
6534		 */
6535		case "timerpoke":
6536		case "wb-init":
6537			init( event );
6538			break;
6539
6540		/*
6541		 * Data table parsed
6542		 */
6543		case "parsecomplete":
6544			if ( event.currentTarget === elm ) {
6545				createCharts( $( elm ) );
6546			}
6547			break;
6548	}
6549
6550	/*
6551	 * Since we are working with events we want to ensure that we are being passive about our control,
6552	 * so returning true allows for events to always continue
6553	 */
6554	return true;
6555} );
6556
6557// Add the timer poke to initialize the plugin
6558wb.add( selector );
6559
6560} )( jQuery, window, document, wb );
6561
6562/**
6563 * @title WET-BOEW Collapsible alerts plugin
6564 * @overview Collapsible alerts (details/summary)
6565 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
6566 * @author WET community
6567 */
6568( function( $, window, wb ) {
6569"use strict";
6570
6571/*
6572 * Variable and function definitions.
6573 * These are global to the event - meaning that they will be initialized once per page,
6574 * not once per instance of event on the page.
6575 */
6576var componentName = "wb-collapsible",
6577	selector = "details.alert",
6578	initEvent = "wb-init." + componentName,
6579	$document = wb.doc,
6580	key,
6581
6582	/**
6583	 * @method init
6584	 * @param {jQuery Event} event Event that triggered the function call
6585	 */
6586	init = function( event ) {
6587
6588		// Start initialization
6589		// returns DOM object = proceed with init
6590		// returns undefined = do not proceed with init (e.g., already initialized)
6591		var details = wb.init( event, componentName, selector ),
6592			$details;
6593
6594		if ( details ) {
6595			$details = $( details );
6596
6597			key = "alert-collapsible-state-" + details.getAttribute( "id" );
6598
6599			try {
6600				if ( localStorage.getItem( key ) ) {
6601
6602					// Set open/closed state for existing localStorage keys
6603					if ( localStorage.getItem( key ) === "open" ) {
6604						details.setAttribute( "open", "open" );
6605						details.className += " open";
6606					} else if ( localStorage.getItem( key ) === "closed" ) {
6607						details.removeAttribute( "open" );
6608						details.className = details.className.replace( " open", "" );
6609					}
6610
6611				} else {
6612
6613					// Set new localStorage values
6614					if ( details.hasAttribute( "open" ) ) {
6615						localStorage.setItem( key, "open" );
6616					} else {
6617						localStorage.setItem( key, "closed" );
6618					}
6619
6620				}
6621			} catch ( e ) {
6622				/* swallow error */}
6623
6624			// Identify that initialization has completed
6625			wb.ready( $details, componentName );
6626		}
6627	};
6628
6629// Bind the init event of the plugin
6630$document.on( "timerpoke.wb " + initEvent, selector, init );
6631
6632// Do not bind events if details polyfill is active
6633if ( Modernizr.details ) {
6634
6635	// Bind the the event handlers of the plugin
6636	$document.on( "click keydown toggle." + componentName, selector + " summary", function( event ) {
6637		var which = event.which,
6638			currentTarget = event.currentTarget,
6639			isClosed,
6640			details;
6641
6642		// Ignore middle/right mouse buttons and wb-toggle enhanced summary elements (except for toggle)
6643		if ( ( !which || which === 1 ) &&
6644			( currentTarget.className.indexOf( "wb-toggle" ) === -1 ||
6645			( event.type === "toggle" && event.namespace === componentName ) ) ) {
6646
6647			details = currentTarget.parentNode;
6648			isClosed = details.getAttribute( "open" ) === null;
6649			key = "alert-collapsible-state-" + details.getAttribute( "id" );
6650
6651			if ( isClosed ) {
6652				try {
6653					localStorage.setItem( key, "open" );
6654				} catch ( e ) {
6655					/* swallow error */}
6656			} else {
6657				try {
6658					localStorage.setItem( key, "closed" );
6659				} catch ( e ) {
6660					/* swallow error */}
6661			}
6662		} else if ( which === 13 || which === 32 ) {
6663			event.preventDefault();
6664			$( currentTarget ).trigger( "click" );
6665		}
6666
6667		/*
6668		 * Since we are working with events we want to ensure that we are being passive about our control,
6669		 * so returning true allows for events to always continue
6670		 */
6671		return true;
6672	} );
6673}
6674
6675// Add the timer poke to initialize the plugin
6676wb.add( selector );
6677
6678} )( jQuery, window, wb );
6679
6680/**
6681 * @title WET-BOEW Country Content
6682 * @overview A basic AjaxLoader wrapper that inserts AJAXed in content based on a visitors country as resolved by freegeoip.app
6683 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
6684 * @author @nschonni
6685 */
6686( function( $, window, wb ) {
6687"use strict";
6688
6689/*
6690 * Variable and function definitions.
6691 * These are global to the plugin - meaning that they will be initialized once
6692 * per page, not once per instance of plugin on the page. So, this is a good
6693 * place to define variables that are common to all instances of the plugin on a
6694 * page.
6695 */
6696var componentName = "wb-ctrycnt",
6697	selector = "[data-ctrycnt]",
6698	initEvent = "wb-init." + componentName,
6699	$document = wb.doc,
6700
6701	/**
6702	 * @method init
6703	 * @param {jQuery Event} event Event that triggered this handler
6704	 */
6705	init = function( event ) {
6706
6707		// Start initialization
6708		// returns DOM object = proceed with init
6709		// returns undefined = do not proceed with init (e.g., already initialized)
6710		var elm = wb.init( event, componentName, selector ),
6711			$elm, url;
6712
6713		if ( elm ) {
6714			$elm = $( elm );
6715			url = $elm.data( "ctrycnt" );
6716
6717			$.when( getCountry() ).then( function( countryCode ) {
6718
6719				if ( countryCode === "" ) {
6720
6721					// Leave default content since we couldn't find the country
6722					return;
6723				} else {
6724
6725					// @TODO: Handle bad country values or any whitelist of countries.
6726				}
6727
6728				url = url.replace( "{country}", countryCode.toLowerCase() );
6729
6730				$elm.load( url, function() {
6731
6732					// Identify that initialization has completed
6733					wb.ready( $elm, componentName );
6734				} );
6735			} );
6736		}
6737	},
6738	getCountry = function() {
6739		var dfd = $.Deferred(),
6740			countryCode = localStorage.getItem( "countryCode" );
6741
6742		// Couldn't find a value in the session
6743		if ( countryCode === null ) {
6744
6745			// From https://github.com/aFarkas/webshim/blob/master/src/shims/geolocation.js#L89-L127
6746			$.ajax( {
6747				url: "https://api.country.is/",
6748				dataType: "json",
6749				cache: true,
6750				success: function( data ) {
6751					if ( data ) {
6752						countryCode = data.country;
6753						try {
6754							localStorage.setItem( "countryCode", countryCode );
6755						} catch ( error ) {
6756
6757							/* swallow error */
6758						}
6759					}
6760
6761					dfd.resolve( countryCode );
6762				},
6763				error: function() {
6764					dfd.reject( "" );
6765				}
6766			} );
6767		} else {
6768			dfd.resolve( countryCode );
6769		}
6770
6771		return dfd.promise();
6772	};
6773
6774// Bind the init event of the plugin
6775$document.on( "timerpoke.wb " + initEvent, selector, init );
6776
6777// Add the timer poke to initialize the plugin
6778wb.add( selector );
6779
6780} )( jQuery, window, wb );
6781
6782/**
6783 * @title WET-BOEW Data Ajax [data-ajax-after], [data-ajax-append],
6784 * [data-ajax-before], [data-ajax-prepend] and [data-ajax-replace]
6785 * @overview A basic AjaxLoader wrapper that inserts AJAXed-in content
6786 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
6787 * @author WET Community
6788 */
6789( function( $, window, wb ) {
6790"use strict";
6791
6792/*
6793 * Variable and function definitions.
6794 * These are global to the plugin - meaning that they will be initialized once
6795 * per page, not once per instance of plugin on the page. So, this is a good
6796 * place to define variables that are common to all instances of the plugin on a
6797 * page.
6798 */
6799var componentName = "wb-data-ajax",
6800	shortName = "wb-ajax",
6801	selectors = [
6802		"[data-ajax-after]",
6803		"[data-ajax-append]",
6804		"[data-ajax-before]",
6805		"[data-ajax-prepend]",
6806		"[data-ajax-replace]",
6807		"[data-" + shortName + "]"
6808	],
6809	ajaxTypes = [
6810		"before",
6811		"replace",
6812		"after",
6813		"append",
6814		"prepend"
6815	],
6816	selectorsLength = selectors.length,
6817	selector = selectors.join( "," ),
6818	initEvent = "wb-init." + componentName,
6819	updateEvent = "wb-update." + componentName,
6820	contentUpdatedEvent = "wb-contentupdated",
6821	$document = wb.doc,
6822	s,
6823
6824	/**
6825	 * @method init
6826	 * @param {jQuery Event} event Event that triggered this handler
6827	 * @param {string} ajaxType The type of AJAX operation, either after, append, before or replace
6828	 */
6829	init = function( event ) {
6830
6831		// Start initialization
6832		// returns DOM object = proceed with init
6833		// returns undefined = do not proceed with init (e.g., already initialized)
6834		var ajxInfo = getAjxInfo( event.target ),
6835			ajaxType = ajxInfo.type,
6836			elm = wb.init( event, componentName + "-" + ajaxType, selector );
6837
6838		if ( elm && ajxInfo.url ) {
6839
6840			ajax.call( this, event, ajxInfo );
6841
6842			// Identify that initialization has completed
6843			wb.ready( $( elm ), componentName, [ ajaxType ] );
6844		}
6845	},
6846
6847	ajax = function( event, ajxInfo ) {
6848		var elm = event.target,
6849			$elm = $( elm ),
6850			settings = window[ componentName ],
6851			url,
6852			fetchObj,
6853			urlParts;
6854
6855		if ( !ajxInfo ) {
6856			ajxInfo = getAjxInfo( elm );
6857		}
6858		url = ajxInfo.url;
6859		fetchObj = {
6860			url: url,
6861			nocache: ajxInfo.nocache,
6862			nocachekey: ajxInfo.nocachekey
6863		};
6864
6865		// Detect CORS requests.
6866		if ( settings && ( url.substr( 0, 4 ) === "http" || url.substr( 0, 2 ) === "//" ) ) {
6867			urlParts = wb.getUrlParts( url );
6868			if ( ( wb.pageUrlParts.protocol !== urlParts.protocol || wb.pageUrlParts.host !== urlParts.host ) && ( !Modernizr.cors || settings.forceCorsFallback ) ) {
6869				if ( typeof settings.corsFallback === "function" ) {
6870					fetchObj.dataType = "jsonp";
6871					fetchObj.jsonp = "callback";
6872					fetchObj = settings.corsFallback( fetchObj );
6873				}
6874			}
6875		}
6876
6877		$elm.trigger( {
6878			type: "ajax-fetch.wb",
6879			fetch: fetchObj
6880		} );
6881	},
6882
6883	// Get Info and return { "url": "the/ajax/URL", "atype" }
6884	getAjxInfo = function( elm ) {
6885		var ajaxType,
6886			len = ajaxTypes.length,
6887			i, url, dtAttr, nocache, nocachekey;
6888
6889		for ( i = 0; i !== len; i += 1 ) {
6890			ajaxType = ajaxTypes[ i ];
6891			url = elm.getAttribute( "data-ajax-" + ajaxType );
6892			if ( url ) {
6893				break;
6894			}
6895		}
6896
6897		if ( !url ) {
6898			dtAttr = wb.getData( $( elm ), shortName );
6899
6900			// Abort the init when called on an invalid element (related to #8058)
6901			if ( !dtAttr ) {
6902				return {};
6903			}
6904
6905			ajaxType = dtAttr.type;
6906			if ( ajaxTypes.indexOf( ajaxType ) === -1 ) {
6907				throw "Invalid ajax type";
6908			}
6909
6910			url = getURL( dtAttr.url, dtAttr.httpref );
6911			if ( !url ) {
6912				return { "type": ajaxType };
6913			}
6914
6915			nocache = dtAttr.nocache;
6916			nocachekey = dtAttr.nocachekey;
6917		}
6918
6919		return {
6920			"url": url,
6921			"type": ajaxType,
6922			"nocache": nocache,
6923			"nocachekey": nocachekey
6924		};
6925	},
6926
6927	// Return url for conditional display if regexp match http refer
6928	getURL = function( url, referer ) {
6929		var refers, httpRef, regHttpRef,
6930			i, i_len;
6931
6932		if ( referer ) {
6933			if ( !Array.isArray( referer ) ) {
6934				refers = [];
6935				refers.push( referer );
6936			} else {
6937				refers = referer;
6938			}
6939
6940			httpRef = window.document.referrer;
6941			i_len = refers.length;
6942			for ( i = 0; i !== i_len; i += 1 ) {
6943				regHttpRef = new RegExp( refers[ i ] );
6944				if ( regHttpRef.test( httpRef ) ) {
6945					if ( Array.isArray( url ) && url.length === i_len ) {
6946						return url[ i ];
6947					} else {
6948						return url;
6949					}
6950				}
6951			}
6952		} else {
6953			return url;
6954		}
6955		return "";
6956	},
6957
6958	ajxFetched = function( elm, fetchObj ) {
6959		var $elm = $( elm ),
6960			ajxInfo = getAjxInfo( elm ),
6961			ajaxType = ajxInfo.type,
6962			content, jQueryCaching,
6963			settings = wb.getData( $( elm ), shortName ) || {},
6964			doEncode = settings.encode,
6965			hasSelector = fetchObj.hasSelector,
6966			resultString = "";
6967
6968		// ajax-fetched event
6969		content = fetchObj.response;
6970		if ( content &&  content.length > 0 ) {
6971
6972			// If the fetched content need to be encoded
6973			if ( doEncode && hasSelector ) {
6974				if ( content.length > 1 ) {
6975					content.each( function( idx, itm ) {
6976						resultString += itm.outerHTML + "\n";
6977					} );
6978
6979					content = resultString;
6980				} else {
6981					content = content.html();
6982				}
6983
6984				content = content.replaceAll( "<", "&lt;" );
6985			} else if ( doEncode && !hasSelector ) {
6986				content = fetchObj.xhr.responseText.replaceAll( "<", "&lt;" );
6987			}
6988
6989			//Prevents the force caching of nested resources
6990			jQueryCaching = jQuery.ajaxSettings.cache;
6991			jQuery.ajaxSettings.cache = true;
6992
6993			// "replace" is the only event that doesn't map to a jQuery function
6994			if ( ajaxType === "replace" ) {
6995				$elm.html( content );
6996			} else {
6997				$elm[ ajaxType ]( content );
6998			}
6999
7000			//Resets the initial jQuery caching setting
7001			jQuery.ajaxSettings.cache = jQueryCaching;
7002
7003			$elm.trigger( contentUpdatedEvent, { "ajax-type": ajaxType, "content": content } );
7004		}
7005	};
7006
7007$document.on( "timerpoke.wb " + initEvent + " " + updateEvent + " ajax-fetched.wb", selector, function( event ) {
7008	var eventTarget = event.target;
7009
7010	switch ( event.type ) {
7011
7012		case "timerpoke":
7013		case "wb-init":
7014			init( event );
7015			break;
7016		case "wb-update":
7017			ajax( event );
7018			break;
7019		default:
7020
7021			// Filter out any events triggered by descendants
7022			if ( event.currentTarget === eventTarget ) {
7023				ajxFetched( eventTarget, event.fetch );
7024			}
7025	}
7026
7027	/*
7028	 * Since we are working with events we want to ensure that we are being
7029	 * passive about our control, so returning true allows for events to always
7030	 * continue
7031	 */
7032	return true;
7033} );
7034
7035// Re-run WET for elements that have just been loaded if WET is already done initializing
7036$document.on( contentUpdatedEvent, function( event ) {
7037	if ( !wb.isDisabled ) {
7038		let updtElm = event.target;
7039
7040		$( updtElm )
7041			.find( wb.allSelectors )
7042			.addClass( "wb-init" )
7043			.filter( ":not(#" + updtElm.id + " .wb-init .wb-init)" )
7044			.trigger( "timerpoke.wb" );
7045	}
7046} );
7047
7048// Add the timerpoke to initialize the plugin
7049for ( s = 0; s !== selectorsLength; s += 1 ) {
7050	wb.add( selectors[ s ] );
7051}
7052
7053} )( jQuery, window, wb );
7054
7055/**
7056 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
7057 * @title Data Fusion Query
7058 * @overview Map a query parameter value into an input value
7059 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7060 * @author @duboisp
7061 *
7062 */
7063( function( document, $, wb ) {
7064"use strict";
7065
7066/**
7067 * Variable and function definitions.
7068 * These are global to the plugin - meaning that they will be initialized once per page,
7069 * not once per instance of plugin on the page. So, this is a good place to define
7070 * variables that are common to all instances of the plugin on a page.
7071 */
7072var componentName = "wb-data-fusion-query",
7073	selector = "[data-fusion-query][name]",
7074	initEvent = "wb-init." + componentName,
7075
7076
7077	/**
7078	 * @method init
7079	 * @param {jQuery Event} event Event that triggered the function call
7080	 */
7081	init = function( event ) {
7082
7083		// Start initialization
7084		// returns DOM object = proceed with init
7085		// returns undefined = do not proceed with init (e.g., already initialized)
7086		var elm = wb.init( event, componentName, selector ),
7087			$elm,
7088			inputName,
7089			queryParamValue;
7090
7091		if ( elm ) {
7092			$elm = $( elm );
7093
7094			// Retrieve the query parameter value and set it on the input
7095			inputName = $elm.attr( "name" );
7096			queryParamValue = wb.pageUrlParts.params[ inputName ];
7097			if ( queryParamValue ) {
7098				$elm.val( queryParamValue.replace( /\+/g, " " ) );
7099			}
7100
7101			// Identify that initialization has completed
7102			wb.ready( $elm, componentName );
7103		}
7104	};
7105
7106// Bind the init event of the plugin
7107wb.doc.on( "timerpoke.wb " + initEvent, selector, init );
7108
7109// Add the timer poke to initialize the plugin
7110wb.add( selector );
7111
7112} )( document, jQuery, wb );
7113
7114/**
7115 * @title WET-BOEW Data InView
7116 * @overview A simplified data-attribute driven plugin that responds to moving in and out of the viewport.
7117 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7118 * @author WET Community
7119 */
7120( function( $, window, wb ) {
7121"use strict";
7122
7123/*
7124 * Variable and function definitions.
7125 * These are global to the plugin - meaning that they will be initialized once per page,
7126 * not once per instance of plugin on the page. So, this is a good place to define
7127 * variables that are common to all instances of the plugin on a page.
7128 */
7129var componentName = "wb-inview",
7130	selector = "." + componentName,
7131	initEvent = "wb-init" + selector,
7132	scrollEvent = "scroll" + selector,
7133	$document = wb.doc,
7134	$window = wb.win,
7135	$elms = $(),
7136
7137	/**
7138	 * @method init
7139	 * @param {jQuery Event} event Event that triggered this handler
7140	 */
7141	init = function( event ) {
7142
7143		// Start initialization
7144		// returns DOM object = proceed with init
7145		// returns undefined = do not proceed with init (e.g., already initialized)
7146		var elm = wb.init( event, componentName, selector ),
7147			$elm;
7148
7149		if ( elm ) {
7150			$elm = $( elm );
7151			$elms = $elms.add( $elm );
7152
7153			// Allow other plugins to run first
7154			setTimeout( function() {
7155				onInView( $elm );
7156
7157				// Identify that initialization has completed
7158				wb.ready( $elm, componentName );
7159			}, 1 );
7160		}
7161	},
7162
7163	/**
7164	 * @method onInView
7165	 * @param {jQuery DOM element} $elm The plugin element
7166	 */
7167	onInView = function( $elm ) {
7168		var elementWidth = $elm.outerWidth(),
7169			elementHeight = $elm.outerHeight(),
7170			scrollTop = $window.scrollTop(),
7171			scrollBottom = scrollTop + $window.height(),
7172			scrollRight = $window.scrollLeft() + elementWidth,
7173			x1 = $elm.offset().left,
7174			x2 = x1 + elementWidth,
7175			y1 = $elm.offset().top,
7176			y2 = y1 + elementHeight,
7177			oldViewState = $elm.attr( "data-inviewstate" ),
7178			inView = ( scrollBottom < y1 || scrollTop > y2 ) || ( scrollRight < x1 || scrollRight > x2 ),
7179
7180			// this is a bit of a play on true/false to get the desired effect. In short this variable depicts
7181			// the view state of the element
7182			// all - the whole element is in the viewport
7183			// partial - part of the element is in the viewport
7184			// none - no part of the element is in the viewport
7185			viewState = ( scrollBottom > y2 && scrollTop < y1 ) ? "all" : inView ? "none" : "partial",
7186			$dataInView = $( "#" + $elm.attr( "data-inview" ) ),
7187			show;
7188
7189		// Remove any element that no longer exists in the DOM
7190		if ( elementWidth === 0 || elementHeight === 0 ) {
7191			$elms = $elms.not( $elm );
7192			$dataInView.addClass( "user-closed" );
7193			$dataInView.trigger( {
7194				type: ( "close" ),
7195				namespace: "wb-overlay",
7196				noFocus: true
7197			} );
7198
7199			return;
7200		}
7201
7202		// Link the overlay close button to the dismiss action if the inview content is dismissable
7203		if ( $elm.hasClass( "wb-dismissable" ) ) {
7204			if ( $dataInView.hasClass( "wb-overlay" ) ) {
7205				$dataInView.children( ".overlay-close" ).on( "click", function( event ) {
7206					var which = event.which;
7207
7208					// Ignore middle/right mouse buttons
7209					if ( !which || which === 1 ) {
7210						$elm.parent().siblings( ".content-dismiss" ).trigger( "click" );
7211					}
7212				} );
7213			}
7214		}
7215
7216		// Only if the view state has changed
7217		if ( viewState !== oldViewState ) {
7218
7219			// Show on "partial"/"none" (default) or just "none" (requires "show-none" class)
7220			show = inView || ( $elm.hasClass( "show-none" ) ? false : viewState === "partial" );
7221
7222			$elm.attr( "data-inviewstate", viewState );
7223
7224			if ( $dataInView.length !== 0 ) {
7225
7226				// Keep closed if the user closed the inView result
7227				if ( !$dataInView.hasClass( "user-closed" ) ) {
7228					if ( $dataInView.hasClass( "wb-overlay" ) ) {
7229						if ( !oldViewState ) {
7230							$dataInView.addClass( "outside-off" );
7231						}
7232						$dataInView.trigger( {
7233							type: ( show ? "open" : "close" ),
7234							namespace: "wb-overlay",
7235							noFocus: true
7236						} );
7237					} else {
7238						if ( !$dataInView.attr( "data-hasPlayed" ) ) {
7239							$dataInView.toggleClass( "in", !show );
7240							if ( !$dataInView.hasClass( "in-only" ) ) {
7241								$dataInView.toggleClass( "out", show );
7242							}
7243						}
7244						if ( wb.isReady && viewState === "all" && $dataInView.hasClass( "in-only" ) ) {
7245							$dataInView.attr( "data-hasPlayed", "true" );
7246						}
7247					}
7248				}
7249			}
7250
7251			// Trigger an event on the element identifying that the view state has changed
7252			// (e.g., "all.wb-inview", "partial.wb-inview", "none.wb-inview")
7253			$elm.trigger( viewState + selector );
7254		}
7255	};
7256
7257// Bind the init event of the plugin
7258$document.on( "timerpoke.wb " + initEvent + " " + scrollEvent, selector, function( event ) {
7259	var eventTarget = event.target,
7260		eventType = event.type;
7261
7262	switch ( eventType ) {
7263		case "timerpoke":
7264		case "wb-init":
7265			init( event );
7266			break;
7267
7268		case "scroll":
7269
7270			// Filter out any events triggered by descendants
7271			if ( event.currentTarget === eventTarget ) {
7272				onInView( $( eventTarget ) );
7273			}
7274			break;
7275	}
7276
7277	/*
7278	 * Since we are working with events we want to ensure that we are being passive about our control,
7279	 * so returning true allows for events to always continue
7280	 */
7281	return true;
7282} );
7283
7284$window.on( "scroll scrollstop", function() {
7285	$elms.trigger( scrollEvent );
7286} );
7287
7288$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb", function() {
7289	$elms.trigger( scrollEvent );
7290} );
7291
7292$document.on( "refresh.wb", function() {
7293	$elms.each( function() {
7294		onInView( $( this ) );
7295	} );
7296} );
7297
7298// Add the timer poke to initialize the plugin
7299wb.add( selector );
7300
7301} )( jQuery, window, wb );
7302
7303/**
7304 * @title WET-BOEW Data Picture
7305 * @overview Event driven port of the Picturefill library: https://github.com/scottjehl/picturefill
7306 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7307 * @author @patheard
7308 */
7309( function( $, window, wb ) {
7310"use strict";
7311
7312/*
7313 * Variable and function definitions.
7314 * These are global to the plugin - meaning that they will be initialized once per page,
7315 * not once per instance of plugin on the page. So, this is a good place to define
7316 * variables that are common to all instances of the plugin on a page.
7317 */
7318var imgClass,
7319	componentName = "wb-pic",
7320	selector = "[data-pic]",
7321	initEvent = "wb-init." + componentName,
7322	picturefillEvent = "picfill." + componentName,
7323	$document = wb.doc,
7324
7325	/**
7326	 * @method init
7327	 * @param {jQuery Event} event Event that triggered this handler
7328	 */
7329	init = function( event ) {
7330
7331		// Start initialization
7332		// returns DOM object = proceed with init
7333		// returns undefined = do not proceed with init (e.g., already initialized)
7334		var elm = wb.init( event, componentName, selector ),
7335			$elm;
7336
7337		if ( elm ) {
7338			$elm = $( elm );
7339
7340			// Store the class attribute of the plugin element.  It
7341			// will be added to the image created by the plugin.
7342			imgClass = $elm.data( "class" ) || "";
7343
7344			$elm.trigger( picturefillEvent );
7345
7346			// Identify that initialization has completed
7347			wb.ready( $elm, componentName );
7348		}
7349	},
7350
7351	/**
7352	 * Updates the image displayed according to media queries.
7353	 * This is the logic ported from Picturefill.
7354	 * @method picturefill
7355	 * @param {DOM element} elm The element containing the images to be updated
7356	 */
7357	picturefill = function( elm ) {
7358		var matches = [],
7359			img = elm.getElementsByTagName( "img" )[ 0 ],
7360			sources = elm.getElementsByTagName( "span" ),
7361			i, len, matchedElm, media;
7362
7363		// Loop over the data-media elements and find matching media queries
7364		for ( i = 0, len = sources.length; i !== len; i += 1 ) {
7365			media = sources[ i ].getAttribute( "data-media" );
7366
7367			// Check if the media query matches or if no media query is defined.
7368			// Vérifie si le media query correspond ou si aucun media query n'est défini.
7369			if ( !media || window.matchMedia( media ).matches ) {
7370				matches.push( sources[ i ] );
7371			}
7372		}
7373
7374		// If a media query match was found, add the image to the page
7375		if ( matches.length !== 0 ) {
7376			matchedElm = matches.pop();
7377			if ( !img ) {
7378				img = $document[ 0 ].createElement( "img" );
7379				img.alt = elm.getAttribute( "data-alt" );
7380				img.className = imgClass;
7381			}
7382			img.src = matchedElm.getAttribute( "data-src" );
7383			matchedElm.appendChild( img );
7384
7385		// No match and an image exists: delete it
7386		} else if ( img ) {
7387			img.parentNode.removeChild( img );
7388		}
7389
7390		// Identify that the picture has been updated
7391		$( elm ).trigger( "wb-updated." + componentName );
7392	};
7393
7394// Bind the init event of the plugin
7395$document.on( "timerpoke.wb " + initEvent + " " + picturefillEvent, selector, function( event ) {
7396	var eventTarget = event.target,
7397		eventType = event.type;
7398
7399	switch ( eventType ) {
7400		case "timerpoke":
7401		case "wb-init":
7402			init( event );
7403			break;
7404
7405		case "picfill":
7406
7407			// Filter out any events triggered by descendants
7408			if ( event.currentTarget === eventTarget ) {
7409				picturefill( eventTarget );
7410			}
7411			break;
7412	}
7413} );
7414
7415// Handles window resize so images can be updated as new media queries match
7416$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb", function() {
7417	$( selector ).trigger( picturefillEvent );
7418} );
7419
7420// Add the timer poke to initialize the plugin
7421wb.add( selector );
7422
7423} )( jQuery, window, wb );
7424
7425/**
7426 * @title WET-BOEW Details closed on small screen
7427 * @overview Closes details on defined viewport and down if they were not engaged, default is small
7428 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7429 * @author @thomasgohard
7430 */
7431( function( $, wb ) {
7432"use strict";
7433
7434/*
7435* Variable and function definitions.
7436* These are global to the plugin - meaning that they will be initialized once per page,
7437* not once per instance of plugin on the page. So, this is a good place to define
7438* variables that are common to all instances of the plugin on a page.
7439*/
7440var componentName = "wb-details-close",
7441	selector = ".provisional." + componentName,
7442	initEvent = "wb-init" + selector,
7443	$document = wb.doc,
7444	views = [ "xxs", "xs", "sm", "md", "lg", "xl" ],
7445	viewsClass = [ "xxsmallview", "xsmallview", "smallview", "mediumview", "largeview", "xlargeview" ],
7446	breakpoint,
7447
7448	/**
7449	 * @method init
7450	 * @param {jQuery Event} event Event that triggered the function call
7451	 */
7452	init = function( event ) {
7453
7454		// Start initialization
7455		// returns DOM object = proceed with init
7456		// returns undefined = do not proceed with init (e.g., already initialized)
7457		var elm = wb.init( event, componentName, selector ),
7458			$elm, i;
7459
7460		if ( elm ) {
7461			$elm = $( elm );
7462
7463			// Get the plugin JSON configuration set on attribute data-wb-details-close
7464			// Will define one set settings for all .wb-details-close on the page
7465			breakpoint = $elm.data( "breakpoint" ) || "sm";
7466
7467			// reset breakpoint if config is passed
7468			if ( views.length === viewsClass.length ) {
7469				i = views.indexOf( breakpoint );
7470				viewsClass = viewsClass.slice( 0, i + 1 );
7471			}
7472
7473			hideOnBreakpoint();
7474
7475			// Identify that initialization has completed
7476			wb.ready( $elm, componentName );
7477		}
7478	},
7479
7480	/**
7481	 * Toggle details depending on breakpoint
7482	 * @method hideOnBreakpoint
7483	 * @param {jQuery DOM element | jQuery Event} $elm Element targetted by this plugin, which is the details
7484	 */
7485	hideOnBreakpoint = function() {
7486		var $elm = $( selector ),
7487			viewsSelector = "html." + viewsClass.join( ", html." );
7488
7489		// If within the targetted views, keep details closed
7490		if ( $( viewsSelector ).length ) {
7491			$elm.removeAttr( "open" );
7492		} else {
7493
7494			// If not, keep opened
7495			$elm.attr( "open", "" );
7496		}
7497	};
7498
7499// Bind the init event of the plugin
7500$document.on( "timerpoke.wb " + initEvent, selector, init );
7501
7502// Add the timer poke to initialize the plugin
7503wb.add( selector );
7504
7505} )( jQuery, wb );
7506
7507/**
7508 * @title WET-BOEW Dismissable content plugin
7509 * @overview Enables content to be dismissed
7510 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7511 * @author @thomasgohard
7512 */
7513( function( $, window, wb ) {
7514"use strict";
7515
7516/*
7517 * Variable and function definitions.
7518 * These are global to the event - meaning that they will be initialized once per page,
7519 * not once per instance of event on the page.
7520 */
7521var componentName = "wb-dismissable",
7522	selector = "." + componentName,
7523	initEvent = "wb-init." + componentName,
7524	containerClass = "wb-dismissable-container",
7525	wrapperClass = "wb-dismissable-wrapper",
7526	dismissClass = "content-dismiss",
7527	idKey = "dismissable-item-id",
7528	$document = wb.doc,
7529	i18n, i18nText,
7530
7531	/**
7532	 * @method init
7533	 * @param {jQuery Event} event Event that triggered the function call
7534	 */
7535	init = function( event ) {
7536
7537		// Start initialization
7538		// returns DOM object = proceed with init
7539		// returns undefined = do not proceed with init (e.g., already initialized)
7540		var elm = wb.init( event, componentName, selector ),
7541			itemId, dismissedState, contentContainer, contentWrapper, dismissButton;
7542
7543		if ( elm ) {
7544
7545			// Only initialize the i18nText once
7546			if ( !i18nText ) {
7547				i18n = wb.i18n;
7548				i18nText = {
7549					dismiss: i18n( "dismiss" )
7550				};
7551			}
7552
7553			// Give the dismissable element a unique ID
7554			if ( elm.hasAttribute( "id" ) ) {
7555				itemId = elm.getAttribute( "id" );
7556
7557				// Ignore an ID assigned by wb
7558				if ( itemId.indexOf( "wb-auto-" ) === 0 ) {
7559					itemId = undefined;
7560				}
7561			}
7562			if ( itemId === undefined ) {
7563				itemId = wb.hashString( wb.stripWhitespace( elm.innerHTML ) );
7564			}
7565
7566			dismissedState = getDismissedState( itemId );
7567
7568			if ( dismissedState === "true" ) {
7569
7570				// Remove the element if it has been dismissed
7571				if ( elm.parentNode ) {
7572					elm.parentNode.removeChild( elm );
7573				}
7574			} else {
7575				$( elm ).wrap( "<div class='" + wrapperClass + "'>" );
7576				contentWrapper = elm.parentNode;
7577
7578				$( contentWrapper ).wrap( "<div class='" + containerClass + "'>" );
7579				contentContainer = contentWrapper.parentNode;
7580
7581				dismissButton = "<button type='button' class='mfp-close " + dismissClass +
7582					"' title='" + i18nText.dismiss + "'>&#xd7;<span class='wb-inv'> " +
7583					i18nText.dismiss + "</span></button>";
7584				$( contentContainer ).append( dismissButton );
7585
7586				contentContainer.setAttribute( "data-" + idKey, itemId );
7587			}
7588
7589			// Identify that initialization has completed
7590			wb.ready( $document, componentName );
7591		}
7592	},
7593
7594	getDismissedState = function( id ) {
7595		var dismissState = localStorage.getItem( id );
7596
7597		if ( dismissState === null ) {
7598			return false;
7599		}
7600
7601		return dismissState;
7602	},
7603
7604	dismissContent = function( elm ) {
7605		localStorage.setItem( elm.getAttribute( "data-" + idKey ), true );
7606		elm.parentNode.removeChild( elm );
7607		$document.trigger( "refresh.wb" );
7608	};
7609
7610// Bind the init event of the plugin
7611$document.on( "timerpoke.wb " + initEvent, selector, init );
7612
7613// Handler for clicking on the dismiss button
7614$document.on( "click", "." + dismissClass, function( event ) {
7615	var elm = event.currentTarget,
7616		which = event.which;
7617
7618	// Ignore middle/right mouse buttons
7619	if ( !which || which === 1 ) {
7620		dismissContent( elm.parentNode );
7621	}
7622} );
7623
7624// Add the timer poke to initialize the plugin
7625wb.add( selector );
7626
7627} )( jQuery, window, wb );
7628
7629/**
7630 * @title eqht
7631 * @overview Provide ability to have equal height containers and nested containers inside a WET-BOEW grid
7632 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7633 * @author @luc-bertrand-hrsdc
7634 */
7635( function( $, window, wb ) {
7636"use strict";
7637
7638/*
7639 * Variable and function definitions.
7640 * These are global to the plugin - meaning that they will be initialized once per page,
7641 * not once per instance of plugin on the page. So, this is a good place to define
7642 * variables that are common to all instances of the plugin on a page.
7643 */
7644var componentName = "wb-eqht-grd",
7645	selector = "." + componentName + " .eqht-trgt",
7646	initEvent = "wb-init" + selector,
7647	$document = wb.doc,
7648
7649	/**
7650	 * @method init
7651	 * @param {jQuery Event} event Event that triggered the function call
7652	 */
7653	init = function( event ) {
7654
7655		// Start initialization
7656		// returns DOM object = proceed with init
7657		// returns undefined = do not proceed with init (e.g., already initialized)
7658		var elm = wb.init( event, componentName, selector ),
7659			$elm,
7660			$eqhtParents;
7661
7662		if ( elm ) {
7663			$elm = $( elm );
7664			$elm.addClass( "hght-inhrt" );
7665			$eqhtParents = $elm.parentsUntil( "[class*='" + componentName + "']" );
7666			$eqhtParents.addClass( "hght-inhrt" );
7667
7668			// Identify that initialization has completed
7669			wb.ready( $elm, componentName );
7670		}
7671	};
7672
7673// Bind the init event of the plugin
7674$document.on( "timerpoke.wb " + initEvent, selector, init );
7675
7676// Add the timer poke to initialize the plugin
7677wb.add( selector );
7678
7679} )( jQuery, window, wb );
7680
7681/**
7682 * @title WET-BOEW Responsive equal height
7683 * @overview Sets the same height for all elements in a container that are rendered on the same baseline (row). Adapted from https://codepen.io/micahgodbolt/pen/FgqLc.
7684 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7685 * @author @thomasgohard
7686 */
7687( function( $, window, wb ) {
7688"use strict";
7689
7690/*
7691 * Variable and function definitions.
7692 * These are global to the plugin - meaning that they will be initialized once per page,
7693 * not once per instance of plugin on the page. So, this is a good place to define
7694 * variables that are common to all instances of the plugin on a page.
7695 */
7696var componentName = "wb-eqht",
7697	selector = "." + componentName,
7698	$document = wb.doc,
7699	eventTimerpoke = "timerpoke.wb",
7700	initEvent = "wb-init" + selector,
7701	vAlignCSS = "vertical-align",
7702	vAlignDefault = "top",
7703	contentUpdatedEvent = "wb-contentupdated",
7704	minHeightCSS = "min-height",
7705	minHeightDefault = "0",
7706	cssValueSeparator = ":",
7707	cssPropertySeparator = ";",
7708	regexCSSValue = " ?[^;]+",
7709	regexVAlign = new RegExp( vAlignCSS + cssValueSeparator + " ?" + regexCSSValue + cssPropertySeparator + "?", "i" ),
7710	regexMinHeight = new RegExp( minHeightCSS + cssValueSeparator + " ?" + regexCSSValue + cssPropertySeparator + "?", "i" ),
7711
7712	/**
7713	 * @method init
7714	 * @param {jQuery Event} event Event that triggered the function call
7715	 */
7716	init = function( event ) {
7717
7718		// Start initialization
7719		// returns DOM object = proceed with init
7720		// returns undefined = do not proceed with init (e.g., already initialized)
7721		var elm = wb.init( event, componentName, selector );
7722
7723		if ( elm ) {
7724
7725			// Remove the event handler since only want init fired once per page (not per element)
7726			$document.off( eventTimerpoke, selector );
7727
7728			onResize();
7729
7730			// Identify that initialization has completed
7731			wb.ready( $document, componentName );
7732		}
7733	},
7734
7735	/**
7736	 * Re-equalise any time the window/document or a child element of 'selector' is resized.
7737	 * @method onResize
7738	 */
7739	onResize = function() {
7740		var $elm, $children, $anchor, currentChild, childCSS, i, j,
7741			$elms = $( selector ),
7742			row = [],
7743			rowTop = -1,
7744			currentChildTop = -1,
7745			currentChildHeight = -1,
7746			tallestHeight = -1;
7747
7748		for ( i = $elms.length - 1; i !== -1; i -= 1 ) {
7749			$elm = $elms.eq( i );
7750			$children = $elm.find( ".eqht-trgt" );
7751			if ( !$children.length ) {
7752				$children = $elm.children();
7753			}
7754
7755			// Reinitialize the row at the beginning of each section of equal height
7756			row = [];
7757
7758			$anchor = detachElement( $elm );
7759			for ( j = $children.length - 1; j !== -1; j -= 1 ) {
7760				currentChild = $children[ j ];
7761				childCSS = currentChild.style.cssText.toLowerCase();
7762
7763				//Ensure the CSS string ends by a separator
7764				if ( childCSS.length > 0 && childCSS.substr( childCSS.length - 1 ) !== cssPropertySeparator ) {
7765					childCSS += cssPropertySeparator;
7766				}
7767
7768				// Ensure all children that are on the same baseline have the same 'top' value.
7769				if ( childCSS.indexOf( vAlignCSS ) !== -1 ) {
7770					childCSS = childCSS.replace( regexVAlign, vAlignCSS + cssValueSeparator + vAlignDefault + cssPropertySeparator );
7771				} else {
7772					childCSS += " " + vAlignCSS + cssValueSeparator + vAlignDefault + cssPropertySeparator;
7773				}
7774
7775				// Remove any previously set min height
7776				if ( childCSS.indexOf( minHeightCSS ) !== -1 ) {
7777					childCSS = childCSS.replace( regexMinHeight, minHeightCSS + cssValueSeparator + minHeightDefault + cssPropertySeparator );
7778				} else {
7779					childCSS += " " + minHeightCSS + cssValueSeparator + minHeightDefault + cssPropertySeparator;
7780				}
7781
7782				currentChild.style.cssText = childCSS;
7783				$children.eq( j ).data( minHeightCSS, minHeightDefault );
7784			}
7785			$elm = reattachElement( $anchor );
7786
7787			// set the top offset and tallest height to the first element
7788			rowTop = $children[ 0 ] ? $children[ 0 ].getBoundingClientRect().top + window.pageYOffset : 0;
7789			tallestHeight = $children[ 0 ] ? $children[ 0 ].offsetHeight : 0;
7790
7791			// first, the loop MUST be from start to end to work.
7792			for ( j = 0; j < $children.length; j++ ) {
7793				currentChild = $children[ j ];
7794
7795				currentChildTop = currentChild.getBoundingClientRect().top + window.pageYOffset;
7796				currentChildHeight = currentChild.offsetHeight;
7797
7798				// if the current element is visible...
7799				// note: hidden elements need to be excluded since they have a different top offset than visible ones
7800				if ( currentChildHeight ) {
7801
7802					// as soon as we find an element not on this row (not the same top offset)...
7803					if ( currentChildTop !== rowTop ) {
7804
7805						// we need to equalize each item in that row to align the next row
7806						equalize( row, tallestHeight );
7807
7808						// since the elements of the previous row was equalized
7809						// we need to get the new top offset of the current element
7810						currentChildTop = currentChild.getBoundingClientRect().top + window.pageYOffset;
7811
7812						// reset the row, rowTop and tallestHeight
7813						row.length = 0;
7814						rowTop = currentChildTop;
7815						tallestHeight = currentChildHeight;
7816					}
7817
7818					tallestHeight = Math.max( currentChildHeight, tallestHeight );
7819					row.push( $children.eq( j ) );
7820				}
7821			}
7822
7823			// equalize the last row
7824			equalize( row, tallestHeight );
7825
7826			// Identify that the height equalization was updated
7827			$document.trigger( "wb-updated" + selector );
7828		}
7829	},
7830
7831	/**
7832	* @method equalize
7833	* @param {array} row the array of items to be equalized
7834	* @param {int} tallestHeight the tallest height to use to equalize
7835	*/
7836	equalize = function( row, tallestHeight ) {
7837		for ( var i = 0; i < row.length; i++ ) {
7838
7839			// added a +1 because some floated element got stuck if the
7840			// shortest element was the last element in the row
7841			var minHeight = tallestHeight + 1;
7842			row[ i ][ 0 ].style.minHeight = minHeight + "px";
7843		}
7844	},
7845
7846	/**
7847	 * @method detachElement
7848	 * @param {jQuery object} $elm The element to detach
7849	 * @returns {object} The detached element
7850	 */
7851	detachElement = function( $elm ) {
7852		var $prev = $elm.prev(),
7853			$next = $elm.next(),
7854			$parent = $elm.parent();
7855
7856		if ( $prev.length ) {
7857			$elm.data( { anchor: $prev, anchorRel: "prev" } );
7858		} else if ( $next.length ) {
7859			$elm.data( { anchor: $next, anchorRel: "next" } );
7860		} else if ( $parent.length ) {
7861			$elm.data( { anchor: $parent, anchorRel: "parent" } );
7862		}
7863
7864		return $elm.detach();
7865	},
7866
7867	/**
7868	 * @method reattachElement
7869	 * @param {jQuery object} $elm The element to reattach
7870	 * @returns {object} The reattached element
7871	 */
7872	reattachElement = function( $elm ) {
7873		var $anchor = $elm.data( "anchor" ),
7874			anchorRel = $elm.data( "anchorRel" );
7875
7876		switch ( anchorRel ) {
7877			case "prev":
7878				$anchor.after( $elm );
7879				break;
7880			case "next":
7881				$anchor.before( $elm );
7882				break;
7883			case "parent":
7884				$anchor.append( $elm );
7885				break;
7886		}
7887
7888		return $elm;
7889	};
7890
7891// Bind the init event of the plugin
7892$document.on( eventTimerpoke + " " + initEvent, selector, init );
7893
7894// Handle text and window resizing
7895$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb " + contentUpdatedEvent + " wb-updated.wb-tables wb-update" + selector, onResize );
7896
7897// Add the timer poke to initialize the plugin
7898wb.add( selector );
7899
7900} )( jQuery, window, wb );
7901
7902/**
7903* @title WET-BOEW Exit script plugin
7904* @overview Plugin redirects users to non secure site
7905* @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7906* @author @ipaksc
7907*/
7908( function( $, window, wb, crypto ) {
7909"use strict";
7910var componentName = "wb-exitscript",
7911	selector = "." + componentName,
7912	initEvent = "wb-init" + selector,
7913	$document = wb.doc,
7914	exiturlparam = componentName + "-urlparam",
7915	keyForKeyHolder = componentName + "key",
7916	moDalId = componentName + "-modal",
7917	i18n,
7918	i18nDict = {
7919		en: {
7920			"msgboxHeader": "Warning",
7921			"exitMsg": "You are about to leave a secure site, do you wish to continue?",
7922			"targetWarning": "The link will open in a new browser window.",
7923			"yesBtn": "Yes",
7924			"cancelBtn": "Cancel"
7925
7926		},
7927		fr: {
7928			"msgboxHeader": "Avertissement",
7929			"exitMsg": "Vous êtes sur le point de quitter un site sécurisé. Voulez-vous continuer?",
7930			"targetWarning": "Le lien s'ouvrira dans une nouvelle fenêtre de navigateur.",
7931			"yesBtn": "Oui",
7932			"cancelBtn": "Annuler"
7933
7934		}
7935	},
7936
7937	/**
7938	 * @method init
7939	 * @param {jQuery Event} event Event that triggered the function call
7940	 */
7941	init = function( event ) {
7942		var elm = wb.init( event, componentName, selector ),
7943			settings,
7944			queryString = window.location.search,
7945			urlParams = new URLSearchParams( queryString ),
7946			counterInUrl = wb.string.fromHexString( urlParams.get( "exturl" ) ),
7947			encryptedUrl = localStorage.getItem( componentName ),
7948			jwt = JSON.parse( localStorage.getItem( keyForKeyHolder ) ),
7949			$elm;
7950		if ( elm ) {
7951			$elm = $( elm );
7952			settings = $.extend(
7953				true,
7954				window[ componentName ],
7955				wb.getData( $elm, componentName )
7956
7957			);
7958
7959			$elm.data( componentName, settings );
7960
7961			if ( settings.url && crypto ) {
7962
7963				crypto.subtle.generateKey(
7964					{
7965						name: "AES-CTR",
7966						length: 256
7967					},
7968					true,
7969					[ "encrypt", "decrypt" ]
7970				).then( function( keyToEncrypt ) {
7971
7972					var enc, messageEncoded, counter;
7973
7974					// Save the key in the anchor
7975					crypto.subtle.exportKey( "jwk", keyToEncrypt )
7976						.then( function( exportedJwtKey ) {
7977							elm[ keyForKeyHolder ] = exportedJwtKey;
7978						} );
7979
7980					// Encrypt the URL
7981					enc = new TextEncoder();
7982					messageEncoded = enc.encode( elm.href );
7983					counter = crypto.getRandomValues( new Uint8Array( 16 ) );
7984					crypto.subtle.encrypt(
7985						{
7986							name: "AES-CTR",
7987							counter: counter,
7988							length: 64
7989						},
7990						keyToEncrypt,
7991						messageEncoded
7992					).then( function( ciphertext ) {
7993						elm[ componentName ] = ciphertext;
7994					} );
7995
7996					// Change the link URL by passing the counter as a key
7997					$elm.attr( "href", settings.url + "?exturl=" + wb.string.toHexString( counter ) );
7998				} );
7999
8000			}
8001
8002			i18n = i18nDict[ wb.lang || "en" ];
8003
8004			// This conditional statement for a middle static exit page, to retrieve the URL to the non-secure site.
8005			if ( $elm.hasClass( exiturlparam ) && encryptedUrl !== null && jwt !== null ) {
8006
8007				crypto.subtle.importKey(
8008					"jwk",
8009					jwt,
8010					{
8011						name: "AES-CTR",
8012						length: 256
8013					},
8014					true,
8015					[ "decrypt" ]
8016				).then( function( key ) {
8017
8018					crypto.subtle.decrypt(
8019						{
8020							name: "AES-CTR",
8021							counter: counterInUrl,
8022							length: 64
8023						},
8024						key,
8025						wb.string.base64ToArrayBuffer( encryptedUrl )
8026					).then( function( decrypted ) {
8027
8028						var dec = new TextDecoder(),
8029							urlToRedirect = dec.decode( decrypted );
8030
8031						// Check if the decrypted message is an valid URL and silently fail if the pattern don't match
8032						if ( urlToRedirect.match( /^(http|https):\/\//g ) ) {
8033							elm.outerHTML = "<a href='" + urlToRedirect + "'>" + urlToRedirect + "</a>";
8034						}
8035
8036					} );
8037				} );
8038
8039			}
8040
8041			// Remove the plugin data and ensure it is removed from the localStorage
8042			localStorage.removeItem( componentName );
8043			localStorage.removeItem( keyForKeyHolder );
8044
8045			wb.ready( $elm, componentName );
8046		}
8047	};
8048
8049$document.on( "click", selector, function( event ) {
8050
8051	var elm = event.currentTarget,
8052		$elm = $( elm ),
8053		wrapper,
8054		targetAttribute = "",
8055		moDal = document.createDocumentFragment(),
8056		tpl = document.createElement( "div" ),
8057		settings =  $elm.data( componentName ),
8058		msgboxHeader = i18n.msgboxHeader,
8059		yesBtn = i18n.yesBtn,
8060		cancelBtn = i18n.cancelBtn,
8061		exitMsg = i18n.exitMsg,
8062		targetWarning = i18n.targetWarning;
8063
8064	if ( settings.i18n ) {
8065		msgboxHeader =  settings.i18n.msgboxHeader || i18n.msgboxHeader;
8066		yesBtn = settings.i18n.yesBtn || i18n.yesBtn;
8067		cancelBtn = settings.i18n.cancelBtn || i18n.cancelBtn;
8068		exitMsg = settings.i18n.exitMsg || i18n.exitMsg;
8069		targetWarning = settings.i18n.targetWarning || i18n.targetWarning;
8070	}
8071
8072	if ( !settings.url ) {
8073
8074		event.preventDefault();
8075	}
8076
8077	if ( this.hasAttribute( "target" ) ) {
8078		targetAttribute = "target='" + this.getAttribute( "target" ) + "'";
8079	} else {
8080		targetAttribute = "target='" + targetAttribute + "'";
8081	}
8082
8083	if ( this.getAttribute( "target" ) === "_blank" ) {
8084		exitMsg = exitMsg  + " " + targetWarning;
8085	}
8086
8087	if ( document.getElementById( moDalId ) ) {
8088		document.getElementById( moDalId ).remove();
8089
8090	}
8091
8092	if ( !settings.url ) {
8093		tpl.innerHTML = "<section id='" + moDalId + "' " + "class='mfp-hide modal-dialog modal-content overlay-def'>" +
8094			"<header class='modal-header'><h2 class='modal-title'>" + msgboxHeader + "</h2></header>" +
8095			"<div class='modal-body'>" +
8096			"<p>" + exitMsg + "</p>" +
8097			"</div>" +
8098			"<div class='modal-footer'>" +
8099			"<ul class='list-inline text-center'>" +
8100			"<li><a class='btn btn-default pull-right popup-modal-dismiss'" + targetAttribute + " href='" + this.getAttribute( "href" ) + "'>" + yesBtn + "</a></li>" +
8101			"<li><button class='btn btn-primary popup-modal-dismiss pull-left'>" + cancelBtn + "</button></li>" +
8102			"</ul></div></section>";
8103		moDal.appendChild( tpl );
8104		wrapper = moDal.firstChild;
8105		wrapper = wrapper.firstChild;
8106		document.body.appendChild( wrapper );
8107
8108		$( wrapper ).trigger( "open.wb-lbx", [
8109			[ {
8110				src: "#" + moDalId,
8111				type: "inline"
8112			} ],
8113
8114			true
8115
8116		] );
8117
8118	} else if ( crypto && this[ componentName ] ) {
8119
8120		// Save to localStorage, the plugin init will ensure this data is only used once
8121		localStorage.setItem( componentName, wb.string.arrayBufferToBase64( this[ componentName ] ) );
8122		localStorage.setItem( keyForKeyHolder, JSON.stringify( this[ keyForKeyHolder ] ) );
8123	}
8124
8125} );
8126
8127// Bind the init event of the plugin
8128$document.on( "timerpoke.wb " + initEvent, selector, init );
8129
8130// Add the timer poke to initialize the plugin
8131wb.add( selector );
8132
8133} )( jQuery, window, wb, crypto );
8134
8135/**
8136* @title WET-BOEW Facebook embedded page
8137* @overview Helps with implementing Facebook embedded pages.
8138* @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
8139* @author @pjackson28
8140*/
8141( function( $, window, wb ) {
8142"use strict";
8143
8144/*
8145* Variable and function definitions.
8146* These are global to the plugin - meaning that they will be initialized once per page,
8147* not once per instance of plugin on the page. So, this is a good place to define
8148* variables that are common to all instances of the plugin on a page.
8149*/
8150var componentName = "wb-facebook",
8151	selector = "." + componentName,
8152	initEvent = "wb-init" + selector,
8153	$document = wb.doc,
8154	fbinited = false,
8155
8156	/**
8157	* @method init
8158	* @param {jQuery Event} event Event that triggered the function call
8159	*/
8160	init = function( event ) {
8161
8162		// Start initialization
8163		// returns DOM object = proceed with init
8164		// returns undefined = do not proceed with init (e.g., already initialized)
8165		var ele = wb.init( event, componentName, selector ),
8166			protocol = wb.pageUrlParts.protocol;
8167
8168		if ( ele ) {
8169			Modernizr.load(
8170				{
8171					load: [ ( protocol.indexOf( "http" ) === -1 ? "http:" : protocol ) + "//connect.facebook.net/" + wb.lang + "_US/sdk.js" ],
8172					complete: function() {
8173						if ( !fbinited ) {
8174							window.FB.init( {
8175								version: "v2.4"
8176							} );
8177							fbinited = true;
8178						}
8179
8180						window.FB.XFBML.parse( ele[ 0 ] );
8181						wb.ready( $( ele ), componentName );
8182					}
8183				}
8184			);
8185		}
8186	};
8187
8188$document.on( "timerpoke.wb " + initEvent, selector, init );
8189
8190// Add the timer poke to initialize the plugin
8191wb.add( selector );
8192
8193} )( jQuery, window, wb );
8194
8195/**
8196 * @title WET-BOEW Favicon Plugin
8197 * @overview Provides the ability to add and update a page's favicons
8198 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
8199 * @author @patheard
8200 *
8201 * This plugin provides the ability to add and update the favicon's on a web page. Its default behaviour is to add a mobile favicon to web pages that have a favicon defined by a `<link rel='icon'>` element.
8202 *
8203 * The mobile favicon's file name, rel, path and sizes can be set with data attributes on the `<link rel='icon'/>`:
8204 *
8205 * -**data-filename:** filename of the mobile favicon (defaults to "favicon-mobile.png"). This will be appended to the favicon's path.
8206 * -**data-path:** path to the mobile favicon (defaults to using the same path as the shortcut icon).
8207 * -**data-rel:** rel attribute of the mobile favicon (defaults to "apple-touch-icon").
8208 * -**data-sizes:** sizes attribute of the mobile favicon (defaults to "57x57 72x72 114x114 144x144 150x150").
8209 *
8210 * For example, the following overrides the rel and file name attributes of the mobile favicon:
8211 *
8212 *     <link href="favicon.ico" rel='icon' data-rel="apple-touch-icon-precomposed" data-filename="my-mobile-favicon.ico"/>
8213 */
8214( function( $, document, wb ) {
8215"use strict";
8216
8217/*
8218 * Variable and function definitions.
8219 * These are global to the plugin - meaning that they will be initialized once per page,
8220 * not once per instance of plugin on the page. So, this is a good place to define
8221 * variables that are common to all instances of the plugin on a page.
8222 */
8223var componentName = "wb-favicon",
8224	selector = "link[rel='icon']",
8225	initEvent = "wb-init." + componentName,
8226	updatedEvent = "wb-updated." + componentName,
8227	mobileEvent = "mobile." + componentName,
8228	iconEvent = "icon." + componentName,
8229	$document = wb.doc,
8230
8231	/*
8232	 * Plugin users can override these defaults by setting attributes on the html elements that the
8233	 * selector matches.
8234	 * For example, adding the attribute data-option1="false", will override option1 for that plugin instance.
8235	 */
8236	defaults = {
8237		filename: "favicon-mobile.png",
8238		path: null,
8239		rel: "apple-touch-icon",
8240		sizes: "57x57 72x72 114x114 144x144 150x150"
8241	},
8242
8243	/**
8244	 * @method init
8245	 * @param {jQuery Event} event Event that triggered the function call
8246	 */
8247	init = function( event ) {
8248
8249		// Start initialization
8250		// returns DOM object = proceed with init
8251		// returns undefined = do not proceed with init (e.g., already initialized)
8252		var elm = wb.init( event, componentName, selector, true ),
8253			$favicon, settings;
8254
8255		if ( elm ) {
8256			$favicon = $( elm );
8257
8258			// Merge default settings with overrides from the selected plugin element.
8259			settings = $.extend( {}, defaults, $favicon.data() );
8260
8261			$favicon.trigger( mobileEvent, settings );
8262
8263			// Identify that initialization has completed
8264			wb.ready( $document, componentName );
8265		}
8266	},
8267
8268	/**
8269	 * Adds, or updates, the mobile favicon on a page. Mobile favicons are identified by the
8270	 * `apple` prefix in the `<link>` elements rel attribute.
8271	 * @method mobile
8272	 * @param {DOM element} favicon Favicon element
8273	 * @param {jQuery Event} event Event that triggered this handler
8274	 * @param {Object} data Key-value data object passed with the event
8275	 */
8276	mobile = function( favicon, event, data ) {
8277		var faviconPath,
8278			faviconMobile = $( "link[rel^='apple']" ),
8279			isFaviconMobile = faviconMobile.length !== 0;
8280
8281		// Create the mobile favicon if it doesn't exist
8282		if ( !isFaviconMobile ) {
8283			var lnk = document.createElement( "link" );
8284			lnk.setAttribute( "rel", data.rel  );
8285			lnk.setAttribute( "sizes", data.sizes );
8286			lnk.setAttribute( "class", componentName );
8287
8288			document.head.appendChild( lnk );
8289
8290			faviconMobile = $( lnk );
8291		}
8292
8293		// Only add/update a mobile favicon that was created by the plugin
8294		if ( faviconMobile.hasClass( componentName ) ) {
8295			faviconPath = data.path !== null ? data.path : getPath( favicon.getAttribute( "href" ) );
8296			faviconMobile.attr( "href", faviconPath + data.filename );
8297
8298			if ( !isFaviconMobile ) {
8299				favicon.parentNode.insertBefore( faviconMobile[ 0 ], favicon );
8300			}
8301		}
8302
8303		$document.trigger( updatedEvent, [ "mobile" ] );
8304	},
8305
8306	/**
8307	 * Updates the the page's shortcut icon
8308	 * @method icon
8309	 * @param {DOM element} favicon Favicon element
8310	 * @param {jQuery Event} event Event that triggered this handler
8311	 * @param {Object} data Key-value data object passed with the event
8312	 */
8313	icon = function( favicon, event, data ) {
8314		var faviconPath = data.path !== null ? data.path : getPath( favicon.getAttribute( "href" ) );
8315		favicon.setAttribute( "href", faviconPath + data.filename );
8316
8317		$document.trigger( updatedEvent, [ "icon" ] );
8318	},
8319
8320	/**
8321	 * Given a full file path, returns the path without the filename
8322	 * @method getPath
8323	 * @param {string} filepath The full path to file, including filename
8324	 * @returns {string} The path to the file
8325	 */
8326	getPath = function( filepath ) {
8327		return filepath.substring( 0, filepath.lastIndexOf( "/" ) + 1 );
8328	};
8329
8330// Bind the init event
8331$document.on( "timerpoke.wb " + initEvent, selector, init );
8332
8333// Bind the mobile and icon events
8334$document.on( mobileEvent + " " + iconEvent, selector, function( event, data ) {
8335	var eventTarget = event.target;
8336
8337	// Filter out any events triggered by descendants
8338	if ( event.currentTarget === eventTarget ) {
8339		switch ( event.type ) {
8340			case "mobile":
8341				mobile( eventTarget, event, data );
8342				break;
8343
8344			case "icon":
8345				icon( eventTarget, event, data );
8346				break;
8347		}
8348	}
8349
8350	/*
8351	 * Since we are working with events we want to ensure that we are being passive about our control,
8352	 * so returning true allows for events to always continue
8353	 */
8354	return true;
8355} );
8356
8357// Add the timer poke to initialize the plugin
8358wb.add( selector );
8359
8360} )( jQuery, document, wb );
8361
8362/**
8363 * @title WET-BOEW Feeds
8364 * @overview Aggregates and displays entries from one or more Web feeds.
8365 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
8366 * @author @pjackson28
8367 */
8368( function( $, window, wb, undef ) {
8369"use strict";
8370
8371/*
8372 * Variable and function definitions.
8373 * These are global to the plugin - meaning that they will be initialized once per page,
8374 * not once per instance of plugin on the page. So, this is a good place to define
8375 * variables that are common to all instances of the plugin on a page.
8376 */
8377var componentName = "wb-feeds",
8378	selector = "." + componentName,
8379	feedLinkSelector = "li > a",
8380	initEvent = "wb-init" + selector,
8381	$document = wb.doc,
8382	patt = /\\u([\d\w]{4})/g,
8383
8384	/**
8385	 * @object Templates
8386	 * @properties {function}
8387	 * @param {object} requires a entry object of various ATOM based properties
8388	 * @returns {string} modified string with appropriate markup/format for a entry object
8389	 */
8390	Templates = {
8391
8392		/**
8393		 * [flickr template]
8394		 * @param  {entry object} data
8395		 * @return {string}	HTML string for creating a photowall effect
8396		 */
8397		flickr: function( data ) {
8398			var media = data.media.m,
8399				flickrData = {
8400					title: data.title,
8401					thumbnail: media.replace( "_m.", "_s." ),
8402					image: media.replace( "_m", "" ),
8403					description: data.description.replace( /^\s*<p>(.*?)<\/p>\s*<p>(.*?)<\/p>/i, "" )
8404				};
8405
8406			// due to CORS we cannot default to simple ajax pulls of the image. We have to inline the content box
8407			return "<li><a class='feed-flickr' href='#' data-flickr='" +
8408				wb.escapeAttribute( JSON.stringify( flickrData ) ) + "'><img src='" + flickrData.thumbnail + "' alt='" +
8409				wb.escapeAttribute( flickrData.title ) + "' title='" + wb.escapeAttribute( flickrData.title ) +
8410				"' class='img-responsive'/></a></li>";
8411		},
8412
8413		/**
8414		 * [YouTube template]
8415		 * @param  {entry object} data
8416		 * @return {string}	HTML string for creating a photowall effect
8417		 */
8418		youtube: function( data ) {
8419			var youtubeDate = {
8420				title: data.title,
8421				videoId: data.id
8422			};
8423
8424			// Due to CORS we cannot default to simple ajax pulls of the image. We have to inline the content box
8425			return "<li class='col-md-4 col-sm-6'><button class='btn btn-lnk feed-youtube' data-youtube='" +
8426				wb.escapeAttribute( JSON.stringify( youtubeDate ) ) + "'><img src='" +
8427				wb.pageUrlParts.protocol + "//img.youtube.com/vi/" + youtubeDate.videoId + "/mqdefault.jpg' alt='" +
8428				wb.escapeAttribute( youtubeDate.title ) + "' title='" + wb.escapeAttribute( youtubeDate.title ) +
8429				"' class='img-responsive' /></button></li>";
8430		},
8431
8432		/**
8433		 * [pinterest template]
8434		 * @param  {entry object}    data
8435		 * @return {string}    HTML string of formatted using a simple list / anchor view
8436		 */
8437		pinterest: function( data ) {
8438			var content = fromCharCode( data.description ).replace( /<a href="\/pin[^"]*"><img ([^>]*)><\/a>([^<]*)(<a .*)?/, "<a href='" +
8439				data.link + "'><img alt='' class='center-block' $1><br/>$2</a>$3" );
8440			return "<li class='media'>" + content +
8441			( data.publishedDate !== "" ? " <small class='small feeds-date'><time>" +
8442			wb.date.toDateISO( data.publishedDate, true ) + "</time></small>" : "" ) + "</li>";
8443		},
8444
8445		/**
8446		 * [generic template]
8447		 * @param  {entry object}	data
8448		 * @return {string}	HTML string of formatted using a simple list / anchor view
8449		 */
8450		generic: function( data ) {
8451			var title = data.title;
8452
8453			if ( typeof( title ) === "object" ) {
8454				if ( title.content ) {
8455					title = title.content;
8456				} else if ( title.type === "xhtml" && title.div ) {
8457					title = title.div.content;
8458				}
8459			}
8460			return "<li><a href='" + data.link + "'>" + title + "</a><br />" +
8461				( data.publishedDate !== "" ? " <small class='feeds-date'><time>" +
8462				wb.date.toDateISO( data.publishedDate, true ) + "</time></small>" : "" ) + "</li>";
8463		}
8464	},
8465
8466	/**
8467	 * Helper function that returns the string representation of a unicode character
8468	 * @method decode
8469	 * @param  {regex} match  unicode pattern
8470	 * @param  {string} code  string where unicode is needed to be converted
8471	 * @return {string}	unicode string character
8472	 */
8473	decode = function( match, code ) {
8474		return String.fromCharCode( parseInt( code, 16 ) );
8475	},
8476
8477	/**
8478	 * Helper wrapper function that performs unicode decodes on a string
8479	 * @method fromCharCode
8480	 * @param  {string} s string to sanitize with escaped unicode characters
8481	 * @return {string}	sanitized string
8482	 */
8483	fromCharCode = function( s ) {
8484		return s.replace( patt, decode );
8485	},
8486
8487	/**
8488	 * Helper function that returns a class-based set limit on plugin instances
8489	 * @method getLimit
8490	 * @param {DOM object} elm The element to search for a class of the form limit-5
8491	 * @return {number} 0 if none found, which means the plugin default
8492	 */
8493	getLimit = function( elm ) {
8494		var count = elm.className.match( /\blimit-\d+/ );
8495		if ( !count ) {
8496			return 0;
8497		}
8498		return Number( count[ 0 ].replace( /limit-/i, "" ) );
8499	},
8500
8501	/**
8502	 * @method init
8503	 * @param {jQuery Event} event Event that triggered the function call
8504	 */
8505	init = function( event ) {
8506
8507		// Start initialization
8508		// returns DOM object = proceed with init
8509		// returns undefined = do not proceed with init (e.g., already initialized)
8510		var elm = wb.init( event, componentName, selector ),
8511			fetch, url, $content, limit, feeds, fType, last, i, callback, fElem, fIcon, youtubeData, $elm;
8512		if ( elm ) {
8513			$elm = $( elm );
8514			$content = $elm.find( ".feeds-cont" );
8515			limit = getLimit( elm );
8516			feeds = $content.find( feedLinkSelector );
8517			last = feeds.length - 1;
8518
8519			// Lets bind some variables to the node to ensure safe ajax thread counting
8520
8521			$content.data( "toProcess", feeds.length )
8522				.data( "feedLimit", limit )
8523				.data( "entries", [] );
8524
8525			for ( i = last; i !== -1; i -= 1 ) {
8526				fElem = feeds.eq( i );
8527				fIcon = fElem.find( "> img" );
8528
8529				fetch = {
8530					dataType: "jsonp",
8531					timeout: 10000
8532				};
8533
8534				if ( fElem.attr( "data-ajax" ) ) {
8535
8536					if ( fElem.attr( "href" ).indexOf( "flickr" ) !== -1 ) {
8537						fType =  "flickr";
8538						callback = "jsoncallback";
8539						$content.data( componentName + "-postProcess", [ ".wb-lbx" ] );
8540					} else {
8541						fType = "generic";
8542						callback = false;
8543						fetch.dataType = "json";
8544					}
8545
8546					// We need a Gallery so lets add another plugin
8547					// #TODO: Lightbox review for more abstraction we should not have to add a wb.add() for overlaying
8548					fetch.url = fElem.attr( "data-ajax" );
8549					fetch.jsonp = callback;
8550				} else if ( fElem.attr( "data-youtube" ) ) {
8551					youtubeData = wb.getData( fElem, "youtube" );
8552
8553					$content.data( componentName + "-postProcess", [ ".wb-lbx", ".wb-mltmd" ] );
8554
8555					if ( youtubeData.playlist ) {
8556						fElem.trigger( {
8557							type: "data-ready.wb-feeds",
8558							feedsData: youtubeData.playlist
8559						}, {
8560							feedType: "youtube",
8561							_content: $content
8562						} );
8563					}
8564
8565				} else {
8566
8567					url = fElem.attr( "href" );
8568					fetch.dataType = "xml";
8569					fetch.url = url;
8570
8571					// Let's bind the template to the Entries
8572					if ( url.indexOf( "pinterest.com" ) > -1  ) {
8573						fType = "pinterest";
8574					} else {
8575						fType = "generic";
8576					}
8577				}
8578
8579				fetch.jsonp = callback;
8580
8581				fetch.context = {
8582					fIcon: ( fIcon.length !== 0 ) ? fIcon.attr( "src" ) : "",
8583					feedType: fType,
8584					_content: $content
8585				};
8586
8587				fElem.trigger( {
8588					type: "ajax-fetch.wb",
8589					fetch: fetch
8590				} );
8591			}
8592		}
8593	},
8594
8595	/**
8596	 * Process Feed/JSON Entries for CORS Enabled
8597	 * @method corsEntry
8598	 */
8599	corsEntry = function( xmlDoc, limit ) {
8600		var entries = xmlDoc.getElementsByTagName( "entry" ).length,
8601			arr_entry = [],
8602			corsObj = {},
8603			jsonString = JSON.stringify( xmlToJson( xmlDoc ) ),
8604			jsonObj = JSON.parse( jsonString ),
8605			i, iCache;
8606		if ( limit > entries || limit === 0 || limit === null ) {
8607			limit = entries;
8608		}
8609		if ( entries === 1 ) {
8610			iCache = jsonObj.feed.entry;
8611			corsObj = {
8612				title: iCache.title[ "#text" ],
8613				link: ( iCache.link ? iCache.link[ "@attributes" ].href : iCache.id[ "#text" ] ),
8614				updated: iCache.updated[ "#text" ]
8615			};
8616			arr_entry.push( corsObj );
8617		} else if ( entries ) {
8618			for ( i = 0; i < limit; i++ ) {
8619				iCache = jsonObj.feed.entry[ i ];
8620				corsObj = {
8621					title: iCache.title[ "#text" ],
8622					link: ( iCache.link ? iCache.link[ "@attributes" ].href : iCache.id[ "#text" ] ),
8623					updated: iCache.updated[ "#text" ]
8624				};
8625				arr_entry.push( corsObj );
8626			}
8627		}
8628		return arr_entry;
8629	},
8630
8631	/**
8632	 * Process XML to JSON
8633	 * @method xmlToJson
8634	 * @param  {xml}
8635	 */
8636	xmlToJson = function( xml ) {
8637
8638		var obj = {},
8639			i, iCache, nodeName, old,
8640			xmlAttributes, xmlChildNodes,
8641			xmlNodeType = xml.nodeType;
8642
8643		if ( xmlNodeType === 1 ) {
8644			xmlAttributes = xml.attributes;
8645			if ( xmlAttributes.length ) {
8646				obj[ "@attributes" ] = {};
8647				for ( i = 0; i < xmlAttributes.length; i++ ) {
8648					iCache = xmlAttributes.item( i );
8649					obj[ "@attributes" ][ iCache.nodeName ] = iCache.nodeValue;
8650				}
8651			}
8652		} else if ( xmlNodeType === 3 ) {
8653			obj = xml.nodeValue;
8654		}
8655
8656		if ( xml.hasChildNodes() ) {
8657			xmlChildNodes = xml.childNodes;
8658			for ( i = 0; i < xmlChildNodes.length; i++ ) {
8659				iCache = xmlChildNodes.item( i );
8660				nodeName = iCache.nodeName;
8661				if ( typeof( obj[ nodeName ] ) === "undefined" ) {
8662					obj[ nodeName ] = xmlToJson( iCache );
8663				} else {
8664					if ( typeof( obj[ nodeName ].push ) === "undefined" ) {
8665						old = obj[ nodeName ];
8666						obj[ nodeName ] = [];
8667						obj[ nodeName ].push( old );
8668					}
8669					obj[ nodeName ].push( xmlToJson( iCache ) );
8670				}
8671			}
8672		}
8673		return obj;
8674	},
8675
8676	/**
8677	 * Process Feed/JSON Entries
8678	 * @method processEntries
8679	 * @param  {data} JSON formatted data to process
8680	 * @return {string} of HTML output
8681	 */
8682	processEntries = function( data ) {
8683		var items = data,
8684			entries = [],
8685			icon = this.fIcon,
8686			$content = this._content,
8687			toProcess = $content.data( "toProcess" ),
8688			i, len;
8689
8690		len = items.length;
8691		for ( i = 0; i !== len; i += 1 ) {
8692			items[ i ].fIcon =  icon;
8693
8694			if ( items[ i ].publishedDate === undef ) {
8695				items[ i ].publishedDate = ( items[ i ].published || items[ i ].pubDate || items[ i ].updated || "" );
8696			}
8697
8698			var link = items[ i ].link;
8699
8700			if ( link && link.href ) {
8701				items[ i ].link = link.href;
8702			}
8703
8704			entries.push( items[ i ] );
8705		}
8706
8707		// lets merge with latest entries
8708		entries = $.merge( entries, $content.data( "entries" ) );
8709
8710		if ( toProcess === 1 ) {
8711			parseEntries( entries, $content.data( "feedLimit" ), $content, this.feedType );
8712			return 0;
8713		}
8714
8715		toProcess -= 1;
8716		$content.data( {
8717			"toProcess": toProcess,
8718			"entries": entries
8719		} );
8720
8721		return toProcess;
8722	},
8723
8724	/**
8725	 * Parses the results from a JSON request and appends to an element
8726	 * @method parseEntries
8727	 * @param {object} entries Results from a JSON request.
8728	 * @param {integer} limit Limit on the number of results to append to the element.
8729	 * @param {jQuery DOM element} $elm Element to which the elements will be appended.
8730	 * @return {url} The URL for the JSON request
8731	 */
8732	parseEntries = function( entries, limit, $elm, feedtype ) {
8733		var cap = ( limit > 0 && limit < entries.length ? limit : entries.length ),
8734			result = "",
8735			compare = wb.date.compare,
8736			$details = $elm.closest( "details" ),
8737			activate = true,
8738			feedContSelector = ".feeds-cont",
8739			hasVisibilityHandler = "vis-handler",
8740			i, sorted, sortedEntry, $tabs;
8741
8742		if ( feedtype !== "youtube" ) {
8743			sorted = entries.sort( function( a, b ) {
8744				return compare( b.publishedDate, a.publishedDate );
8745			} );
8746		} else {
8747			sorted = entries;
8748		}
8749
8750		for ( i = 0; i !== cap; i += 1 ) {
8751			sortedEntry = sorted[ i ];
8752			result += Templates[ feedtype ]( sortedEntry );
8753		}
8754		$elm.data( componentName + "-result", result );
8755
8756		// Check to see if feed should be activated (only if visible)
8757		// and add handler to determine visibility
8758		if ( $details.length !== 0 ) {
8759			if ( $details.attr( "role" ) === "tabpanel" ) {
8760				if ( $details.attr( "aria-hidden" ) === "true" ) {
8761					activate = false;
8762					$elm.empty().addClass( "waiting" );
8763					$tabs = $details.closest( ".wb-tabs" );
8764					if ( !$tabs.hasClass( hasVisibilityHandler ) ) {
8765						$tabs
8766							.on( "wb-updated.wb-tabs", function( event, $newPanel ) {
8767								var $feedCont = $newPanel.find( feedContSelector );
8768								if ( !$feedCont.hasClass( "feed-active" ) ) {
8769									activateFeed( $feedCont );
8770								}
8771							} )
8772							.addClass( hasVisibilityHandler );
8773					}
8774				}
8775			} else if ( !$details.attr( "open" ) ) {
8776				activate = false;
8777				$elm.empty().addClass( "waiting" );
8778				$details
8779					.children( "summary" )
8780					.on( "click.wb-feeds", function( event ) {
8781						var $summary = $( event.currentTarget ).off( "click.wb-feeds" );
8782						activateFeed( $summary.parent().find( feedContSelector ) );
8783					} );
8784			}
8785		}
8786
8787		if ( activate ) {
8788			activateFeed( $elm );
8789		}
8790
8791		return true;
8792	},
8793
8794	/**
8795	 * Activates feed results view
8796	 * @method activateFeed
8797	 * @param = {jQuery object} $elm Feed container
8798	 */
8799	activateFeed = function( $elm ) {
8800		var result = $elm.data( componentName + "-result" ),
8801			postProcess = $elm.data( componentName + "-postProcess" ),
8802			i, postProcessSelector;
8803
8804		if ( !result ) {
8805			return;
8806		}
8807
8808		$elm.empty()
8809			.removeClass( "waiting" )
8810			.addClass( "feed-active" )
8811			.append( result );
8812
8813		if ( postProcess ) {
8814			for ( i = postProcess.length - 1; i !== -1; i -= 1 ) {
8815				postProcessSelector = postProcess[ i ];
8816				$elm.find( postProcessSelector )
8817					.trigger( "wb-init" + postProcessSelector );
8818			}
8819		}
8820
8821		// Identify that the feed has now been displayed
8822		$elm.trigger( "wb-feed-ready" + selector );
8823	};
8824
8825$document.on( "ajax-fetched.wb data-ready.wb-feeds", selector + " " + feedLinkSelector, function( event, context ) {
8826	var eventTarget = event.target,
8827		data, response, responseRaw,
8828		$emlRss, limit, results;
8829
8830	// Filter out any events triggered by descendants
8831	if ( event.currentTarget === eventTarget ) {
8832		$emlRss = $( eventTarget ).parentsUntil( selector ).parent();
8833		switch ( event.type ) {
8834			case "ajax-fetched":
8835				responseRaw = event.fetch.response;
8836				if ( typeof responseRaw === "string" ) {
8837					response = JSON.parse( responseRaw ); // Assuming we have fetch a JSON document, try to parse it.
8838				} else {
8839					response = responseRaw.get( 0 ); // fetched an HTML or XML document which has been parsed by jQuery and sanitized by DomPurify
8840				}
8841				if ( response.documentElement ) {
8842					limit = getLimit( $emlRss[ Object.keys( $emlRss )[ 0 ] ] );
8843					data = corsEntry( response, limit );
8844				} else if ( response.query ) {
8845					results = response.query.results;
8846					if ( !results ) {
8847						data = results.item; // Flicker feeds
8848						if ( !Array.isArray( data ) ) {
8849							data = [ data ];
8850						}
8851					} else {
8852						data = [];
8853					}
8854				} else {
8855					data = ( response.responseData ) ? response.responseData.feed.entries : response.items || response.feed.entry;
8856				}
8857				break;
8858			default:
8859				data = event.feedsData;
8860		}
8861
8862		// Identify that initialization has completed
8863		// if there are no entries left to process
8864		if ( processEntries.apply( context, [ data ] ) === 0 ) {
8865			wb.ready( $( eventTarget ).closest( selector ), componentName );
8866		}
8867	}
8868} );
8869
8870$document.on( "click", selector + " .feed-youtube", function( event ) {
8871	var youTubeOverlaySelector  = "#wb-feeds-youtube-lbx",
8872		$youTubeOverlay = $( youTubeOverlaySelector ),
8873		youtubeData = wb.getData( event.currentTarget, "youtube" ),
8874		videoUrl = wb.pageUrlParts.protocol + "//www.youtube.com/watch?v=" + youtubeData.videoId,
8875		videoSource = "<figure class='wb-mltmd'><video title='" + youtubeData.title + "'>" +
8876			"<source type='video/youtube' src='" + videoUrl + "'></source>" +
8877			"</video><figcaption><p>" +  youtubeData.title + "</p>" +
8878			"</figcaption></figure>";
8879
8880	if ( $youTubeOverlay.length === 0 ) {
8881		$youTubeOverlay = $( "<section id='wb-feeds-youtube-lbx' class='mfp-hide modal-dialog modal-content overlay-def'>" +
8882			"<header class='modal-header'><h2 class='modal-title'>" + youtubeData.title + "</h2></header>" +
8883			"<div class='modal-body'>" +
8884			videoSource +
8885			"</div></section>" ).insertAfter( "main" );
8886	} else {
8887
8888		//Modify lightbox
8889		$youTubeOverlay.find( ".modal-title" ).text( youtubeData.title );
8890		$youTubeOverlay.find( ".modal-body" ).empty().append( videoSource );
8891	}
8892
8893	//Temporary fix until lightbox auto initialize the multimedia player
8894	$youTubeOverlay.find( ".wb-mltmd" ).trigger( "wb-init.wb-mltmd" );
8895
8896	$( document ).trigger( "open.wb-lbx", [ {
8897		src: youTubeOverlaySelector,
8898		type: "inline"
8899	} ] );
8900} );
8901
8902$document.on( "click", selector + " .feed-flickr", function( event ) {
8903	var flickrOverlaySelector  = "#wb-feeds-flick-lbx",
8904		$flickrOverlay = $( flickrOverlaySelector ),
8905		flickrData = wb.getData( event.currentTarget, "flickr" ),
8906		body = "<img src='" + flickrData.image + "' class='thumbnail center-block' alt='" + flickrData.title + "' /><span>" +
8907			flickrData.description + "</span>";
8908
8909	if ( $flickrOverlay.length === 0 ) {
8910		$flickrOverlay = $( "<section id='wb-feeds-flick-lbx' class='mfp-hide modal-dialog modal-content overlay-def'>" +
8911			"<header class='modal-header'><h2 class='modal-title'>" + flickrData.title + "</h2></header>" +
8912			"<div class='modal-body'>" + body + "</div></section>" ).insertAfter( "main" );
8913	} else {
8914
8915		//Modify lightbox
8916		$flickrOverlay.find( ".modal-title" ).text( flickrData.title );
8917		$flickrOverlay.find( ".modal-body" ).empty().append( body );
8918	}
8919
8920	$( document ).trigger( "open.wb-lbx", [ {
8921		src: flickrOverlaySelector,
8922		type: "inline"
8923	} ] );
8924} );
8925
8926// Bind the init event to the plugin
8927$document.on( "timerpoke.wb " + initEvent, selector, init );
8928
8929// Add the timer poke to initialize the plugin
8930wb.add( selector );
8931
8932} )( jQuery, window, wb );
8933
8934( function( $, window, wb ) {
8935"use strict";
8936
8937var componentName = "wb-filter",
8938	selector = "." + componentName,
8939	initEvent = "wb-init" + selector,
8940	$document = wb.doc,
8941	filterClass = "wb-fltr-out",
8942	tagFilterClass = "wb-tgfltr-out",
8943	notFilterClassSel = ":not(." + filterClass + "):not(." + tagFilterClass + ")",
8944	inputClass = "wb-fltr-inpt",
8945	dtNameFltrArea = "wbfltrid",
8946	selectorInput = "." + inputClass,
8947	defaults = {
8948		std: {
8949			selector: "li"
8950		},
8951		grp: {
8952			selector: "li",
8953			section: ">section"
8954		},
8955		tbl: {
8956			selector: "tr",
8957			section: ">tbody"
8958		},
8959		tblgrp: {
8960			selector: " th:not([scope])" + notFilterClassSel,
8961			hdnparentuntil: "tbody",
8962			section: ">tbody"
8963		}
8964	},
8965	i18n, i18nText,
8966	wait,
8967
8968	init = function( event ) {
8969		var elm = wb.init( event, componentName, selector ),
8970			$elm, elmTagName, filterUI, prependUI,
8971			settings, setDefault,
8972			itemsObserver,
8973			inptId, totalEntries,
8974			secSelector,
8975			uiTemplate, uiInpt, uiInfo,
8976			uiNbItems, uiTotal, uiInfoID;
8977
8978		if ( elm ) {
8979			$elm = $( elm );
8980			elmTagName = elm.nodeName;
8981			uiInfoID = elm.id + "-info";
8982
8983			if ( [ "DIV", "SECTION", "ARTICLE" ].indexOf( elmTagName ) >= 0 ) {
8984				setDefault = defaults.grp;
8985				prependUI = true;
8986			} else if ( elmTagName === "TABLE" ) {
8987				if ( $elm.find( "tbody" ).length > 1 ) {
8988					setDefault = defaults.tblgrp;
8989				} else {
8990					setDefault = defaults.tbl;
8991				}
8992			} else {
8993				setDefault = defaults.std;
8994			}
8995
8996			settings = $.extend( true, {}, setDefault, window[ componentName ], wb.getData( $elm, componentName ) );
8997			$elm.data( settings );
8998
8999			if ( !i18nText ) {
9000				i18n = wb.i18n;
9001				i18nText = {
9002					filter_label: i18n( "fltr-lbl" ),
9003					fltr_info: i18n( "fltr-info" )
9004				};
9005			}
9006
9007			Modernizr.addTest( "stringnormalize", "normalize" in String );
9008			Modernizr.load( {
9009				test: Modernizr.stringnormalize,
9010				nope: [
9011					"site!deps/unorm" + wb.getMode() + ".js"
9012				]
9013			} );
9014
9015			if ( !elm.id ) {
9016				elm.id = wb.getId();
9017			}
9018
9019			if ( settings.uiTemplate ) {
9020				uiTemplate = document.querySelector( settings.uiTemplate );
9021				uiInpt = uiTemplate.querySelector( "input[type=search]" );
9022
9023				if ( uiInpt ) {
9024					uiInfo = uiTemplate.querySelector( ".wb-fltr-info" );
9025
9026					uiInpt.classList.add( inputClass );
9027					uiInpt.setAttribute( "data-" + dtNameFltrArea, elm.id );
9028					uiInpt.setAttribute( "aria-controls", elm.id );
9029
9030					if ( uiInfo ) {
9031						uiInfoID = uiInfo.id || uiInfoID;
9032						uiInfo.id = uiInfoID;
9033						uiInfo.setAttribute( "role", "status" );
9034					}
9035				} else {
9036					console.error( componentName + ": " + "an <input type=\"search\"> is required in your UI template." );
9037				}
9038
9039				if ( settings.source ) {
9040					console.warn( componentName + ": " + "the 'source' option is not compatible with the 'uiTemplate' option. If both options are defined, only 'uiTemplate' will be registered." );
9041				}
9042			} else {
9043				inptId = elm.id + "-inpt";
9044				filterUI = $( "<div class=\"input-group\">" +
9045					"<label for=\"" + inptId + "\" class=\"input-group-addon\"><span class=\"glyphicon glyphicon-filter\" aria-hidden=\"true\"></span> " + i18nText.filter_label + "</label>" +
9046					"<input id=\"" + inptId + "\" class=\"form-control " + inputClass + "\" data-" + dtNameFltrArea + "=\"" + elm.id + "\" aria-controls=\"" + elm.id + "\" type=\"search\">" +
9047					"</div>" +
9048					"<p role=\"status\" id=\"" + uiInfoID + "\">" + i18nText.fltr_info + "</p>" );
9049
9050				if ( settings.source ) {
9051					$( settings.source ).prepend( filterUI );
9052				} else if ( prependUI ) {
9053					$elm.prepend( filterUI );
9054				} else {
9055					$elm.before( filterUI );
9056				}
9057			}
9058
9059			secSelector = ( settings.section || "" ) + " ";
9060			totalEntries = $elm.find( secSelector + settings.selector ).length;
9061			uiNbItems = document.querySelector( "#" + uiInfoID + " [data-nbitem]" );
9062			uiTotal = document.querySelector( "#" + uiInfoID + " [data-total]" );
9063
9064			if ( uiNbItems ) {
9065				uiNbItems.textContent = totalEntries;
9066
9067				itemsObserver = new MutationObserver( function() {
9068					uiNbItems.textContent = $elm.find( secSelector + settings.selector + notFilterClassSel ).length;
9069				} );
9070
9071				itemsObserver.observe( elm, { attributes: true, subtree: true } );
9072			}
9073
9074			if ( uiTotal ) {
9075				uiTotal.textContent = totalEntries;
9076			}
9077
9078			wb.ready( $elm, componentName );
9079		}
9080	},
9081
9082	/*
9083	 * Takes in the text from the filter box
9084	 * Returns:
9085	 *  An array of search words
9086	 *      Special characters are escaped
9087	 *      Double and single quotes removed
9088	 */
9089	filterQueryParser = function( filter ) {
9090
9091		// Pattern to separate the filter text into "words"
9092		var pattern = /[^\s"]+|"([^"]*)"/gi;
9093
9094		// Make strings safe again for regex
9095		// Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
9096		filter = filter.replace( /[.*+?^${}()|[\]\\]/g, "\\$&" );
9097
9098		// Apply the word match pattern and return
9099		return filter.match( pattern );
9100	},
9101
9102	/**
9103	 * Build the Regular Expression that we are going
9104	 * to use to filter content
9105	 * This involves identifying the type of search being
9106	 * applied and then breaking up the search string into
9107	 * words
9108	 */
9109	buildSearchFilterRegularExp =  function( filterType, filter ) {
9110
9111		var words, wordRegExFilter = filter,
9112			i, i_len;
9113
9114		switch ( filterType ) {
9115
9116			case "and":
9117				words = filterQueryParser( filter );
9118				if ( words ) {
9119					wordRegExFilter = ".*";
9120					i_len = words.length;
9121					for ( i = 0; i < i_len; i++ ) {
9122						wordRegExFilter = wordRegExFilter + ( "(?=.*" + words[ i ] + ")" );
9123					}
9124				}
9125				break;
9126
9127			case "or": // If one word fall back on default
9128				words = filterQueryParser( filter );
9129				if ( words ) {
9130					wordRegExFilter =  words.join( "|" );
9131				}
9132				break;
9133
9134			default:
9135				break;
9136
9137		}
9138
9139		return new RegExp( wordRegExFilter, "i" );
9140
9141	},
9142
9143	filter = function( $field, $elm, settings ) {
9144		var unAccent = function( str ) {
9145				return str.normalize( "NFD" ).replace( /[\u0300-\u036f]/g, "" );
9146			},
9147			filter = unAccent( $field.val().trim() ),
9148			fCallBack = settings.filterCallback,
9149			secSelector = ( settings.section || "" )  + " ",
9150			hndParentSelector = settings.hdnparentuntil,
9151			$items = $elm.find( secSelector + settings.selector ),
9152			itemsLength = $items.length,
9153			i, $item, text, searchFilterRegularExp;
9154
9155		$elm.find( "." + filterClass ).removeClass( filterClass );
9156
9157		searchFilterRegularExp = buildSearchFilterRegularExp( settings.filterType, filter );
9158
9159		for ( i = 0; i < itemsLength; i += 1 ) {
9160			$item = $items.eq( i );
9161			text = unAccent( $item.text() );
9162
9163			if ( !searchFilterRegularExp.test( text ) ) {
9164				if ( hndParentSelector ) {
9165					$item = $item.parentsUntil( hndParentSelector );
9166				}
9167				$item.addClass( filterClass );
9168			}
9169		}
9170
9171		if ( !fCallBack || typeof fCallBack !== "function"  ) {
9172			fCallBack = filterCallback;
9173		}
9174		fCallBack.apply( this, arguments );
9175
9176		$elm.trigger( "wb-contentupdated" );
9177	},
9178	filterCallback = function( $field, $elm, settings ) {
9179		var $sections =	$elm.find( settings.section ),
9180			sectionsLength = $sections.length,
9181			fndSelector = notFilterClassSel + settings.selector,
9182			s, $section;
9183
9184		for ( s = 0; s < sectionsLength; s += 1 ) {
9185			$section = $sections.eq( s );
9186			if ( $section.find( fndSelector ).length === 0 ) {
9187				$section.addClass( filterClass );
9188			}
9189		}
9190	};
9191
9192$document.on( "keyup", selectorInput, function( event ) {
9193	var target = event.target,
9194		$input = $( target ),
9195		$elm = $( "#" + $input.data( dtNameFltrArea ) );
9196
9197	if ( wait ) {
9198		clearTimeout( wait );
9199	}
9200	wait = setTimeout( filter.bind( this, $input, $elm, $elm.data() ), 250 );
9201
9202} );
9203
9204$document.on( "timerpoke.wb " + initEvent, selector, init );
9205
9206wb.add( selector );
9207} )( jQuery, window, wb );
9208
9209/**
9210 * @title WET-BOEW Footnotes
9211 * @overview Provides a consistent, accessible way of handling footnotes across websites.
9212 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
9213 * @author @EricDunsworth
9214 */
9215( function( $, window, wb ) {
9216"use strict";
9217
9218/*
9219 * Variable and function definitions.
9220 * These are global to the plugin - meaning that they will be initialized once per page,
9221 * not once per instance of plugin on the page. So, this is a good place to define
9222 * variables that are common to all instances of the plugin on a page.
9223 */
9224var componentName = "wb-fnote",
9225	selector = "." + componentName,
9226	modFlag = "data-" + componentName,
9227	initEvent = "wb-init" + selector,
9228	setFocusEvent = "setfocus.wb",
9229	$document = wb.doc,
9230
9231	/**
9232	 * @method init
9233	 * @param {jQuery Event} event Event that triggered the function call
9234	 */
9235	init = function( event ) {
9236
9237		// Start initialization
9238		// returns DOM object = proceed with init
9239		// returns undefined = do not proceed with init (e.g., already initialized)
9240		var elm = wb.init( event, componentName, selector ),
9241			$elm, footnoteDd, footnoteDt, i, len, dd, dt;
9242
9243		if ( elm ) {
9244			$elm = $( elm );
9245			footnoteDd = elm.getElementsByTagName( "dd" );
9246			footnoteDt = elm.getElementsByTagName( "dt" );
9247
9248			// Set initial event handlers for return to referrer links
9249			len = footnoteDd.length;
9250			for ( i = 0; i !== len; i += 1 ) {
9251				dd = footnoteDd[ i ];
9252				dt = footnoteDt[ i ];
9253				dd.setAttribute( "tabindex", "-1" );
9254				dt.id = dd.id + "-dt";
9255			}
9256
9257			// Remove "first/premier/etc"-style text from certain footnote return links (via the child spans that hold those bits of text)
9258			$elm.find( "dd p.fn-rtn a span span" ).remove();
9259
9260			// Listen for footnote reference links that get clicked
9261			$document.on( "click", "main :not(" + selector + ") sup a.fn-lnk", function( event ) {
9262				var eventTarget = event.target,
9263					which = event.which,
9264					refId, $refLinkDest;
9265
9266				// Ignore middle/right mouse button
9267				if ( !which || which === 1 ) {
9268					refId = "#" + wb.jqEscape( eventTarget.getAttribute( "href" ).substring( 1 ) );
9269					$refLinkDest = $document.find( refId );
9270
9271					$refLinkDest.find( "p.fn-rtn a" )
9272						.attr( "href", "#" + eventTarget.parentNode.id )
9273						.attr( modFlag, true );
9274
9275					// Assign focus to $refLinkDest
9276					$refLinkDest.trigger( setFocusEvent );
9277					return false;
9278				}
9279			} );
9280
9281			// Listen for footnote return links that get clicked
9282			$document.on( "click", selector + " dd p.fn-rtn a", function( event ) {
9283				var which = event.which,
9284					$elmTarget = $( event.target ),
9285					ref,
9286					refId,
9287					refIdSrc,
9288					refIdDashIdx,
9289					searchRefId;
9290
9291				// Ignore middle/right mouse button
9292				if ( !which || which === 1 ) {
9293					ref = $elmTarget.attr( "href" );
9294
9295					// Focus on associated referrer link (if the return link points to an ID)
9296					if ( ref.charAt( 0 ) === "#" ) {
9297						refId = wb.jqEscape( ref.substring( 1 ) );
9298
9299						// When first clicked, ensure we send the user on the first instance when the id follow the recommend pattern
9300						refIdDashIdx = refId.indexOf( "-" );
9301						if ( refIdDashIdx !== -1 && !$elmTarget.attr( modFlag ) ) {
9302							searchRefId = refId.substring( 0, refIdDashIdx + 1 );
9303							refIdSrc = wb.jqEscape( $( "sup[id^='" + searchRefId + "']:first()" ).attr( "id" ) );
9304							if ( !refIdSrc || refId !== refIdSrc ) {
9305								console.warn( componentName + " - Relink first reference of " + ref + " for #" + refIdSrc );
9306								refId = refIdSrc;
9307								$elmTarget
9308									.attr( "href", "#" + refId )
9309									.attr( modFlag, true );
9310							}
9311						}
9312
9313						// Assign focus to the link
9314						$document.find( "#" + refId + " a" ).trigger( setFocusEvent );
9315						return false;
9316					}
9317				}
9318			} );
9319
9320			// Identify that initialization has completed
9321			wb.ready( $elm, componentName );
9322		}
9323	};
9324
9325// Bind the init event of the plugin
9326$document.on( "timerpoke.wb " + initEvent, selector, init );
9327
9328// Add the timer poke to initialize the plugin
9329wb.add( selector );
9330
9331} )( jQuery, window, wb );
9332
9333/**
9334 * @title WET-BOEW Form validation
9335 * @overview Provides generic validation and error message handling for Web forms.
9336 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
9337 * @author @pjackson28
9338 */
9339( function( $, window, document, wb ) {
9340"use strict";
9341
9342/*
9343* Variable and function definitions.
9344* These are global to the plugin - meaning that they will be initialized once per page,
9345* not once per instance of plugin on the page. So, this is a good place to define
9346* variables that are common to all instances of the plugin on a page.
9347*/
9348var componentName = "wb-frmvld",
9349	selector = "." + componentName,
9350	initEvent = "wb-init" + selector,
9351	setFocusEvent = "setfocus.wb",
9352	$document = wb.doc,
9353	idCount = 0,
9354	i18n, i18nText,
9355
9356	defaults = {
9357		hdLvl: "h2",
9358		ignore: ":hidden"
9359	},
9360
9361	/**
9362	 * @method init
9363	 * @param {jQuery Event} event Event that triggered the function call
9364	 */
9365	init = function( event ) {
9366
9367		// Start initialization
9368		// returns DOM object = proceed with init
9369		// returns undefined = do not proceed with init (e.g., already initialized)
9370		var eventTarget = wb.init( event, componentName, selector ),
9371			elmId, modeJS;
9372
9373		if ( eventTarget ) {
9374			elmId = eventTarget.id;
9375
9376			// Ensure there is a unique id on the element
9377			if ( !elmId ) {
9378				elmId = componentName + "-id-" + idCount;
9379				idCount += 1;
9380				eventTarget.id = elmId;
9381			}
9382
9383			// Read the selector node for parameters
9384			modeJS = wb.getMode() + ".js";
9385
9386			// Only initialize the i18nText once
9387			if ( !i18nText ) {
9388				i18n = wb.i18n;
9389				i18nText = {
9390					colon: i18n( "colon" ),
9391					hyphen: i18n( "hyphen" ),
9392					error: i18n( "err" ),
9393					errorFound: i18n( "err-fnd" ),
9394					errorsFound: i18n( "errs-fnd" ),
9395					formNotSubmitted: i18n( "frm-nosubmit" ),
9396					errorCorrect: i18n( "err-correct" )
9397				};
9398			}
9399
9400			Modernizr.load( {
9401				load: [ "site!deps/jquery.validate" + modeJS ],
9402				testReady: function() {
9403					return ( $.validator );
9404				},
9405				complete: function() {
9406					Modernizr.load( {
9407
9408						// For loading multiple dependencies
9409						load: [ "site!deps/additional-methods" + modeJS ],
9410						testReady: function() {
9411							return ( $.validator.methods.bic );
9412						},
9413						complete: function() {
9414							var $elm = $( "#" + elmId ),
9415								$form = $elm.find( "form" ),
9416								formDOM = $form.get( 0 ),
9417								formId = $form.attr( "id" ),
9418								labels = formDOM.getElementsByTagName( "label" ),
9419								submitted = false,
9420								showSummary = false,
9421								errorFormId = "errors-" + ( !formId ? "default" : formId ),
9422								settings = $.extend(
9423									true,
9424									{},
9425									defaults,
9426									window[ componentName ],
9427									wb.getData( $elm, componentName )
9428								),
9429								summaryHeading = settings.hdLvl,
9430								i, len, validator;
9431
9432							if ( wb.lang === "fr" ) {
9433
9434								// alphanumeric regex is changed to allow french characters;
9435								$.validator.addMethod( "alphanumeric", function( value, element ) {
9436									return this.optional( element ) || /^[a-zàâçéèêëîïôûùüÿæœ0-9_]+$/i.test( value );
9437								}, "Letters, numbers, and underscores only please." );
9438
9439								// error french text is adjusted to remove the word "spaces"
9440								$.extend( $.validator.messages, {
9441									alphanumeric: "Veuillez fournir seulement des lettres, nombres et soulignages."
9442								} );
9443							}
9444
9445							// Append the aria-live region (for provide message updates to screen readers)
9446							$elm.append( "<div class='arialive wb-inv' aria-live='polite' aria-relevant='all'></div>" );
9447
9448							// Add space to the end of the labels (so separation between label and error when CSS turned off)
9449							len = labels.length;
9450							for ( i = 0; i !== len; i += 1 ) {
9451								labels[ i ].insertAdjacentHTML( "beforeend", " " );
9452							}
9453
9454							// Hide "required" label text in older forms from screen readers
9455							// Prevents redundant "required" announcements on semantically-required fields whose labels mention they're required
9456							$form.find( "strong.required:not([aria-hidden='true'])" ).each( function() {
9457								const $requiredText = $( this ),
9458									$label = $requiredText.closest( "label" ),
9459									fieldId = $label.attr( "for" );
9460								let $field = fieldId ? $( "#" + fieldId ) : $label.find( ":input" ).first();
9461
9462								// If the label's field has yet to be found, look for fields that refer to the label via aria-labelledby
9463								if ( !$field.length ) {
9464									const labelId = $label.attr( "id" ) || $requiredText.closest( "id" );
9465									$field = $form.find( "[aria-labelledby~='" + labelId + "']:input" ).first();
9466								}
9467
9468								// Hide the "required" text if its field is semantically-required
9469								if ( $field.is( "[required], [aria-required='true']" ) ) {
9470									$requiredText.attr( "aria-hidden", "true" );
9471								}
9472							} );
9473
9474							// The jQuery validation plug-in in action
9475							validator = $form.validate( {
9476								meta: "validate",
9477								focusInvalid: false,
9478								ignore: settings.ignore,
9479
9480								// Set the element which will wrap the inline error messages
9481								errorElement: "strong",
9482
9483								// Location for the inline error messages
9484								// In this case we will place them in the associated label element
9485								errorPlacement: function( $error, $element ) {
9486									var type = $element.attr( "type" ),
9487										group = $element.attr( "data-rule-require_from_group" ),
9488										$fieldset, $legend;
9489
9490									$error.data( "element-id", $element.attr( "id" ) );
9491									if ( type ) {
9492										type = type.toLowerCase();
9493										if ( type === "radio" || type === "checkbox" ) {
9494											$fieldset = $element.closest( "fieldset" );
9495											if ( $fieldset.length !== 0 ) {
9496												$legend = $fieldset.find( "legend" ).first();
9497												if ( $legend.length !== 0 && $fieldset.find( "input[name='" + $element.attr( "name" ) + "']" ) !== 1 ) {
9498													$error.appendTo( $legend );
9499													return;
9500												}
9501											}
9502										}
9503									}
9504
9505									if ( group ) {
9506										$fieldset = $element.closest( "fieldset" );
9507										if ( $fieldset.length !== 0 ) {
9508											$legend = $fieldset.find( "legend" ).first();
9509											if ( $legend.length !== 0 && $fieldset.find( "input[name='" + $element.attr( "name" ) + "']" ) !== 1 ) {
9510												var $strong = $legend.find( "strong.error" ),
9511													id = $legend.attr( "id" );
9512
9513												if ( $strong.length > 0 ) {
9514													$strong.remove();
9515												}
9516
9517												if ( !id ) {
9518													id = "required-group-" + idCount;
9519													idCount += 1;
9520
9521													$legend.attr( "id", id );
9522												}
9523
9524												$error.data( "element-id", id );
9525												$error.attr( "id", id );
9526												$error.appendTo( $legend );
9527
9528												return;
9529											}
9530										}
9531									}
9532
9533									//Std If we have a label and the input field is inside the label
9534									// need to add a css-implicite-input
9535									if ( $form.find( "label" ).find( ".wb-server-error + input.css-implicite-input[name='" + $element.attr( "name" ) + "']" ).length > 0 ) {
9536										$error.insertBefore( $form.find( ".wb-server-error + input.css-implicite-input[name='" + $element.attr( "name" ) + "']" ) );
9537										return;
9538									}
9539
9540									$error.appendTo( $form.find( "label[for='" + $element.attr( "id" ) + "']" ) );
9541									return;
9542								},
9543
9544								// Create our error summary that will appear before the form
9545								showErrors: function( errorMap ) {
9546									this.defaultShowErrors();
9547									var $errors = $form.find( ".wb-server-error, strong.error" ).filter( ":not(:hidden)" ),
9548										$errorfields = $form.find( "input.error, select.error, textarea.error" ),
9549										prefixStart = "<span class='prefix'>" + i18nText.error + "&#160;",
9550										prefixEnd = i18nText.colon + " </span>",
9551										separator = i18nText.hyphen,
9552										ariaLive = $form.closest( ".wb-frmvld" ).find( ".arialive" )[ 0 ],
9553										$summaryContainer, summary, key, i, len, $error, prefix, $fieldName, $f
9553ieldset, label, labelString, el;
9554
9555									// Correct the colouring of fields that are no longer invalid
9556									$form
9557										.find( ".has-error [aria-invalid=false]" )
9558										.closest( ".has-error" )
9559										.removeClass( "has-error" );
9560
9561									if ( $errors.length !== 0 ) {
9562
9563										// Post process
9564										summary = "<" + summaryHeading + ">" +
9565											i18nText.formNotSubmitted + $errors.length +
9566											(
9567												$errors.length !== 1 ?
9568													i18nText.errorsFound :
9569													i18nText.errorFound
9570											) + "</" + summaryHeading + "><ul>";
9571										$errorfields
9572											.closest( ".form-group" )
9573											.addClass( "has-error" );
9574										len = $errors.length;
9575										for ( i = 0; i !== len; i += 1 ) {
9576											$error = $errors.eq( i );
9577											prefix = prefixStart + ( i + 1 ) + prefixEnd;
9578											$fieldName = $error.closest( "label" ).find( ".field-name" );
9579
9580											// Try to find the field name in the legend (if one exists)
9581											if ( $fieldName.length === 0 ) {
9582												$fieldset = $error.closest( "fieldset" );
9583												if ( $fieldset.length !== 0 ) {
9584													$fieldName = $fieldset.find( "legend .field-name" );
9585												}
9586											}
9587
9588											$error.find( "span.prefix" ).detach();
9589
9590											//Verify if it is a wb-server-error
9591											if ( $errors[ i ].classList.contains( "wb-server-error" ) ) {
9592												if ( $errors[ i ].id ) {
9593													var myParent = document.getElementById( $errors[ i ].id ).parentElement;
9594													if ( myParent === null ) {
9595														summary += "<li><a>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9596													} else {
9597														if ( myParent.hasAttribute( "for" ) && myParent.getAttribute( "for" ).length > 0 ) {
9598															summary += "<li><a href='#" + myParent.getAttribute( "for" ) + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9599														} else {
9600															if ( myParent.getElementsByTagName( "input" )[ 0 ] && myParent.getElementsByTagName( "input" )[ 0 ].name.length > 0 ) {
9601																summary += "<li><a href='#" + myParent.getElementsByTagName( "input" )[ 0 ].id + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9602															} else {
9603																if ( myParent.tagName === ( "LEGEND" ) && ( myParent.parentElement.getElementsByTagName( "input" )[ 0 ].type === "checkbox" || myParent.parentElement.getElementsByTagName( "input" )[ 0 ].type === "radio" && myParent.parentElement.getElementsByTagName( "input" )[ 0 ].name.length > 0 ) ) {
9604																	summary += "<li><a href='#" + myParent.parentElement.getElementsByTagName( "input" )[ 0 ].id + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9605																} else {
9606																	summary += "<li><a>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9607																}
9608															}
9609														}
9610													}
9611													$error.html( "<strong>" + prefix + $error.text() + "</strong>" );
9612												}
9613
9614												//Force display of summary if wb-server-error present.
9615												showSummary = true;
9616											} else {
9617												summary += "<li><a href='#" + $error.data( "element-id" ) + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + "</a></li>";
9618												$error.html( "<span class='label label-danger'>" + prefix + $error.text() + "</span>" );
9619											}
9620										}
9621										summary += "</ul>";
9622
9623										if ( !submitted ) {
9624
9625											// Update the aria-live region as necessary
9626											i = 0;
9627											for ( key in errorMap ) {
9628												if ( Object.prototype.hasOwnProperty.call( errorMap, key ) ) {
9629													i += 1;
9630													break;
9631												}
9632											}
9633											if ( i !== 0 ) {
9634												len = $errors.length;
9635												for ( i = 0; i !== len; i += 1 ) {
9636													label = $errors[ i ].parentNode;
9637													if ( label.getAttribute( "for" ) === key ) {
9638														el = document.getElementById( key );
9639														if ( el.matches( ":focus" ) ) {
9640															ariaLive.innerHTML = "";
9641														} else {
9642															labelString = label.innerHTML;
9643															if ( labelString !== ariaLive.innerHTML ) {
9644																ariaLive.innerHTML = labelString;
9645															}
9646														}
9647														break;
9648													}
9649												}
9650											} else if ( ariaLive.innerHTML.length !== 0 ) {
9651												ariaLive.innerHTML = "";
9652											}
9653										}
9654										if ( showSummary ) {
9655
9656											// Delay updating the summary container in case a summary link was clicked
9657											setTimeout( function() {
9658												$summaryContainer = $form.find( "#" + errorFormId );
9659
9660												// Output our error summary and place it in the error container
9661												// Create our container if one doesn't already exist
9662												if ( $summaryContainer.length === 0 ) {
9663													$summaryContainer = $( "<section id='" + errorFormId + "' class='alert alert-danger' tabindex='-1'>
9663" + summary + "</section>" ).prependTo( $form );
9664
9665													// Replace current error summary content only if it is different
9666													// from the incoming error summary content
9667												} else if ( $summaryContainer.html() !== summary.replace( /'/g, "\"" ).replace( /&#160;/g, "&nbsp;" ) ) {
9668
9669													// if the summary container is currently focused then
9670													// we will focus the last element in the summary
9671													// since we recreate the error list on blur/change
9672													var $isFocused = $summaryContainer.find( "a" ).is( ":focus" );
9673													$summaryContainer.empty().append( summary );
9674													if ( $isFocused ) {
9675														$summaryContainer.find( "a" ).last().trigger( "focus" );
9676													}
9677												}
9678
9679												// Put focus on the error if the errors are generated by an attempted form submission
9680												if ( submitted ) {
9681
9682													// Assign focus to $summaryContainer
9683													$summaryContainer.trigger( setFocusEvent );
9684													submitted = false;
9685												}
9686											}, 100 );
9687										}
9688									} else {
9689
9690										// Update the aria-live region as necessary
9691										if ( ariaLive.innerHTML.length !== 0 ) {
9692											ariaLive.innerHTML = "";
9693										}
9694										$form.find( "#" + errorFormId ).detach();
9695										showSummary = false;
9696									}
9697								},
9698
9699								/* End of showErrors() */
9700
9701								invalidHandler: function() {
9702									submitted = true;
9703									showSummary = true;
9704								}
9705
9706							} ); /* end of validate() */
9707
9708							/* fixes validation issue (see PR #7913) */
9709							$form.on( "change", "input[type=date], input[type=file], select", function() {
9710								$form.validate().element( this );
9711							} );
9712
9713							// Clear the form and remove error messages on reset
9714							$document.on( "click", selector + " input[type=reset]", function( event ) {
9715								var which = event.which,
9716									ariaLive;
9717
9718								// Ignore middle/right mouse buttons
9719								if ( !which || which === 1 ) {
9720									validator.resetForm();
9721									$( "#" + errorFormId ).detach();
9722
9723									ariaLive = $form.closest( ".wb-frmvld" ).find( ".arialive" )[ 0 ];
9724									if ( ariaLive.innerHTML.length !== 0 ) {
9725										ariaLive.innerHTML = "";
9726									}
9727
9728									// Correct the colouring of fields that are no longer invalid
9729									$form.find( ".has-error" ).removeClass( "has-error" );
9730								}
9731							} );
9732
9733							//Trigger validation on wb-server-error
9734							$form.find( ".wb-server-error" ).filter( ":not( :hidden )" ).parent().each( function() {
9735								if ( this.attributes.for && this.attributes.for.value.length > 0 ) {
9736									$form.validate().element( $( "[id =" + this.attributes.for.value + "]" ) );
9737								} else if ( $( this ).find( "input" )[ 0 ] ) {
9738									$form.validate().element( $( this ).find( "input" )[ 0 ] );
9739								} else if ( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ] ) {
9740									if ( $( this ).find( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ].id ) ) {
9741										$form.validate().element( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ] );
9742									}
9743								} else if ( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ] ) {
9744									if ( $( this ).find( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ].id ) ) {
9745										$form.validate().element( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ] );
9746									}
9747								}
9748							} );
9749
9750							// Tell the i18n file to execute to run any $.validator extends
9751							$form.trigger( "formLanguages.wb" );
9752
9753							// Identify that initialization has completed
9754							wb.ready( $( eventTarget ), componentName );
9755						}
9756					} );
9757				}
9758			} );
9759		}
9760	};
9761
9762// Bind the init event of the plugin
9763$document.on( "timerpoke.wb " + initEvent, selector, init );
9764
9765// Add the timer poke to initialize the plugin
9766wb.add( selector );
9767
9768} )( jQuery, window, document, wb );
9769
9770/*
9771 * @title WET-BOEW Geomap
9772 * @overview Displays a dynamic map over which information from additional sources can be overlaid.
9773 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
9774 * @author @pjackson28
9775 */
9776( function( $, wb ) {
9777"use strict";
9778
9779var componentName = "wb-geomap",
9780	selector = "." + componentName,
9781	initEvent = "wb-init" + selector,
9782	$document = wb.doc,
9783
9784	/**
9785	 * @method init
9786	 * @param {jQuery Event} event Event that triggered the function call
9787	 */
9788	init = function( event ) {
9789
9790		// Start initialization
9791		// returns DOM object = proceed with init
9792		// returns undefined = do not proceed with init (e.g., already initialized)
9793		var elm = wb.init( event, componentName, selector ),
9794			$elm, $map, $leg, modeJS;
9795
9796		if ( elm ) {
9797			$elm = $( elm );
9798			$map = $elm.find( ".wb-geomap-map" );
9799			$leg = $elm.find( ".wb-geomap-legend" );
9800
9801			// Initialize the map height for the loader
9802			$map.height( $map.width() * 0.8 );
9803			$map.append( "<div class='geomap-progress'><span class='wb-inv'>" + wb.i18n( "load" ) + "</span></div>" );
9804			$leg.append( "<div class='skeleton-lgnd-1'><span class='skeleton-lgnd-3'></span></div>" +
9805					"<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" +
9806					"<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" +
9807					"<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" +
9808					"<div  class='skeleton-lgnd-1'><span class='skeleton-lgnd-3'></span></div>" );
9809
9810			modeJS = wb.getMode() + ".js";
9811
9812			Modernizr.load( [ {
9813
9814				// For loading multiple dependencies
9815				both: [
9816					"site!deps/proj4" + modeJS,
9817					"site!deps/ol" + modeJS,
9818					"site!deps/geomap-lib" + modeJS
9819				],
9820				complete: function() {
9821					$elm.trigger( "geomap.wb" );
9822				}
9823			} ] );
9824		}
9825	};
9826
9827// Bind the init function to the timerpoke event
9828$document.on( "timerpoke.wb " + initEvent, selector, init );
9829
9830// Add the timer poke to initialize the plugin
9831wb.add( selector );
9832
9833} )( jQuery, wb );
9834
9835/**
9836 * @title WET-BOEW JSON Fetch [ json-fetch ]
9837 * @overview Load and filter data from a JSON file
9838 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
9839 * @author @duboisp
9840 */
9841/*global jsonpointer */
9842( function( $, wb, DOMPurify ) {
9843"use strict";
9844
9845/*
9846 * Variable and function definitions.
9847 * These are global to the plugin - meaning that they will be initialized once per page,
9848 * not once per instance of plugin on the page. So, this is a good place to define
9849 * variables that are common to all instances of the plugin on a page.
9850 */
9851var $document = wb.doc,
9852	component = "json-fetch",
9853	fetchEvent = component + ".wb",
9854	jsonCache = { },
9855	jsonCacheBacklog = { },
9856	completeJsonFetch = function( callerId, refId, response, status, xhr, selector, fetchedOpts ) {
9857		if ( !window.jsonpointer ) {
9858
9859			// JSON pointer library is loaded but not executed in memory yet, we need to wait a tick before to continue
9860			setTimeout( function() {
9861				completeJsonFetch( callerId, refId, response, status, xhr, selector, fetchedOpts );
9862			}, 100 );
9863			return false;
9864		}
9865		if ( selector ) {
9866			try {
9867				response = jsonpointer.get( response, selector );
9868			} catch ( ex ) {
9869				console.error( "JSON fetch - Bad JSON selector: " + selector );
9870				console.error( response );
9871				console.error( $( "#" + callerId ).get( 0 ) );
9872			}
9873		}
9874		$( "#" + callerId ).trigger( {
9875			type: "json-fetched.wb",
9876			fetch: {
9877				response: response,
9878				status: status,
9879				xhr: xhr,
9880				refId: refId,
9881				fetchedOpts: fetchedOpts
9882			}
9883		}, this );
9884	};
9885
9886// Event binding
9887$document.on( fetchEvent, function( event ) {
9888
9889	var caller = event.element || event.target,
9890		fetchOpts = event.fetch || { url: "" },
9891		urlParts = fetchOpts.url.split( "#" ),
9892		url = urlParts[ 0 ],
9893		fetchNoCache = fetchOpts.nocache,
9894		fetchNoCacheKey = fetchOpts.nocachekey || wb.cacheBustKey || "wbCacheBust",
9895		fetchNoCacheValue,
9896		fetchCacheURL,
9897		hashPart,
9898		datasetName,
9899		selector = urlParts[ 1 ] || false,
9900		callerId, refId = fetchOpts.refId,
9901		cachedResponse;
9902
9903	// Filter out any events triggered by descendants
9904	if ( caller === event.target || event.currentTarget === event.target ) {
9905
9906		if ( !caller.id ) {
9907			caller.id = wb.getId();
9908		}
9909		callerId = caller.id;
9910
9911		if ( selector ) {
9912
9913			// If a Dataset Name exist let it managed by wb-jsonpatch plugin
9914			hashPart = selector.split( "/" );
9915			datasetName = hashPart[ 0 ];
9916
9917			// A dataset name must start with "[" character, if it is a letter, then follow JSON Schema (to be implemented)
9918			if ( datasetName.charCodeAt( 0 ) === 91 ) {
9919
9920				// Let the wb-jsonpatch plugin to manage it
9921				$( "#" + callerId ).trigger( {
9922					type: "postpone.wb-jsonmanager",
9923					postpone: {
9924						callerId: callerId,
9925						refId: refId,
9926						dsname: datasetName,
9927						selector: selector.substring( datasetName.length )
9928					}
9929				} );
9930				return;
9931			}
9932			fetchOpts.url = url;
9933		}
9934
9935		if ( fetchNoCache ) {
9936			if ( fetchNoCache === "nocache" ) {
9937				fetchNoCacheValue = wb.guid();
9938			} else {
9939				fetchNoCacheValue = wb.sessionGUID();
9940			}
9941			fetchCacheURL = fetchNoCacheKey + "=" + fetchNoCacheValue;
9942
9943			if ( url.indexOf( "?" ) !== -1 ) {
9944				url = url + "&" + fetchCacheURL;
9945			} else {
9946				url = url + "?" + fetchCacheURL;
9947			}
9948			fetchOpts.url = url;
9949		}
9950
9951		Modernizr.load( {
9952			load: "site!deps/jsonpointer" + wb.getMode() + ".js",
9953			complete: function() {
9954
9955				// Ensure this fetch has an URL. There is no URL when only using dataset name (a virtual JSON file).
9956				if ( !url ) {
9957					return;
9958				}
9959
9960				if ( !fetchOpts.nocache ) {
9961					cachedResponse = jsonCache[ url ];
9962
9963					if ( cachedResponse ) {
9964						completeJsonFetch( callerId, refId, cachedResponse, "success", undefined, selector, fetchOpts );
9965						return;
9966					} else {
9967						if ( !jsonCacheBacklog[ url ] ) {
9968							jsonCacheBacklog[ url ] = [ ];
9969						} else {
9970							jsonCacheBacklog[ url ].push( {
9971								"callerId": callerId,
9972								"refId": refId,
9973								"selector": selector
9974							} );
9975							return;
9976						}
9977					}
9978				}
9979
9980				// Ensure we only receive JSON data and don't allow jsonp
9981				// jQuery will raise an error if other data format is received
9982				fetchOpts.dataType = "json";
9983				if ( fetchOpts.jsonp ) {
9984					fetchOpts.jsonp = false;
9985				}
9986
9987				// Sending Data
9988				if ( fetchOpts.data ) {
9989					try {
9990						fetchOpts.data = ( typeof fetchOpts.data === "string" ? fetchOpts.data : JSON.stringify( fetchOpts.data ) );
9991					} catch ( err ) {
9992						throw "JSON fetch - Data being sent to server - " + err;
9993					}
9994
9995					fetchOpts.method = fetchOpts.method || "POST";
9996					fetchOpts.contentType = fetchOpts.contentType || "application/json";
9997				}
9998
9999				$.ajax( fetchOpts )
10000					.done( function( response, status, xhr ) {
10001						var i, i_len, i_cache, backlog;
10002
10003						if ( !fetchOpts.nocache ) {
10004							try {
10005								jsonCache[ url ] = response;
10006							} catch ( error ) {
10007								return;
10008							}
10009						}
10010
10011						completeJsonFetch( callerId, refId, response, status, xhr, selector, fetchOpts );
10012
10013						if ( jsonCacheBacklog[ url ] ) {
10014							backlog = jsonCacheBacklog[ url ];
10015
10016							i_len = backlog.length;
10017
10018							for ( i = 0; i !== i_len; i += 1 ) {
10019								i_cache = backlog[ i ];
10020								completeJsonFetch( i_cache.callerId, i_cache.refId, response, status, xhr, i_cache.selector, fetchOpts );
10021							}
10022						}
10023
10024					} )
10025					.fail( function( xhr, status, error ) {
10026						xhr.responseText = DOMPurify.sanitize( xhr.responseText );
10027						$( "#" + callerId ).trigger( {
10028							type: "json-failed.wb",
10029							fetch: {
10030								xhr: xhr,
10031								status: status,
10032								error: error,
10033								refId: refId,
10034								fetchOpts: fetchOpts
10035							}
10036						}, this );
10037					}, this );
10038			}
10039		} );
10040	}
10041} );
10042
10043} )( jQuery, wb, DOMPurify );
10044
10045/**
10046 * @title WET-BOEW Lightbox
10047 * @overview Helps build a photo gallery on a web page.
10048 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
10049 * @author @pjackson28
10050 */
10051( function( $, DOMPurify, window, document, wb, undef ) {
10052"use strict";
10053
10054/*
10055 * Variable and function definitions.
10056 * These are global to the plugin - meaning that they will be initialized once per page,
10057 * not once per instance of plugin on the page. So, this is a good place to define
10058 * variables that are common to all instances of the plugin on a page.
10059 */
10060var componentName = "wb-lbx",
10061	selector = "." + componentName,
10062	initEvent = "wb-init" + selector,
10063	setFocusEvent = "setfocus.wb",
10064	dependenciesLoadedEvent = "deps-loaded" + selector,
10065	modalHideSelector = "#wb-tphp, body > header, body > main, body > footer",
10066	$document = wb.doc,
10067	callbacks, i18n, i18nText,
10068	defaults = {
10069
10070		// exclude 'times' from screen reader with aria-hidden span
10071		closeMarkup: "<button type='button' class='mfp-close'><span class='mfp-close' aria-hidden='true'>&times;</span><span class='wb-inv'>%title%</span></button>"
10072	},
10073
10074	/**
10075	 * @method init
10076	 * @param {jQuery Event} event Event that triggered the function call
10077	 */
10078	init = function( event ) {
10079
10080		// Start initialization
10081		// returns DOM object = proceed with init
10082		// returns undefined = do not proceed with init (e.g., already initialized)
10083		var elm = wb.init( event, componentName, selector ),
10084			elmId;
10085
10086		if ( elm ) {
10087			elmId = elm.id;
10088
10089			// Ensure the dependencies are loaded first
10090			$document.one( dependenciesLoadedEvent, function() {
10091				var elm = document.getElementById( elmId ),
10092					$elm = $( elm ),
10093					settings = {},
10094					firstLink;
10095
10096				if ( !elm ) {
10097					return;
10098				}
10099
10100				// TODO: Add swipe support
10101
10102				settings.callbacks = callbacks;
10103
10104				if ( elm.nodeName.toLowerCase() !== "a" ) {
10105					settings.delegate = "a:not(" + selector + "-skip)";
10106					firstLink = elm.getElementsByTagName( "a" )[ 0 ];
10107
10108					// Is the element a gallery?
10109					if ( elm.className.indexOf( "-gal" ) !== -1 ) {
10110						settings.gallery = {
10111							enabled: true
10112						};
10113					}
10114				} else {
10115					firstLink = elm;
10116				}
10117
10118				if ( firstLink ) {
10119					if ( firstLink.getAttribute( "href" ).charAt( 0 ) === "#" ) {
10120						settings.type = "inline";
10121					} else if ( firstLink.className.indexOf( "lbx-iframe" ) !== -1 ) {
10122						settings.type = "iframe";
10123					} else if ( firstLink.getElementsByTagName( "img" ).length === 0 ) {
10124						settings.type = "ajax";
10125					} else {
10126						settings.type = "image";
10127					}
10128
10129					if ( elm.className.indexOf( "lbx-modal" ) !== -1 ) {
10130						settings.modal = true;
10131					}
10132					if ( elm.className.indexOf( "lbx-ajax" ) !== -1 ) {
10133						settings.type = "ajax";
10134					}
10135					if ( elm.className.indexOf( "lbx-image" ) !== -1 ) {
10136						settings.type = "image";
10137					}
10138					if ( elm.className.indexOf( "lbx-inline" ) !== -1 ) {
10139						settings.type = "inline";
10140					}
10141
10142					// Extend the settings with window[ "wb-lbx" ] then data-wb-lbx
10143					settings = $.extend(
10144						true,
10145						settings,
10146						window[ componentName ],
10147						wb.getData( $elm, componentName )
10148					);
10149					$elm.magnificPopup(
10150						settings
10151					).data( "wbLbxFilter", settings.filter );
10152				}
10153
10154				// Identify that initialization has completed
10155				wb.ready( $elm, componentName );
10156			} );
10157
10158			// Load dependencies as needed
10159			setup();
10160		}
10161	},
10162
10163	/**
10164	 * @method setup
10165	 */
10166	setup = function() {
10167
10168		// Only initialize the i18nText and callbacks once
10169		if ( !i18nText ) {
10170			i18n = wb.i18n;
10171			i18nText = {
10172				close: i18n( "close" ),
10173				oClose: i18n( "overlay-close" ),
10174				tClose: i18n( "close" ) + i18n( "space" ) + i18n( "overlay-close" ) + i18n( "space" ) + i18n( "esc-key" ),
10175				tLoading: i18n( "load" ),
10176				gallery: {
10177					tPrev: i18n( "prv-l" ),
10178					tNext: i18n( "nxt-r" ),
10179					tCounter: i18n( "lb-curr" )
10180				},
10181				image: {
10182					tError: i18n( "lb-img-err" ) + " (<a href=\"url%\">)"
10183				},
10184				ajax: {
10185					tError: i18n( "lb-xhr-err" ) + " (<a href=\"url%\">)"
10186				}
10187			};
10188
10189			callbacks = {
10190				open: function() {
10191
10192					// TODO: Better if dealt with upstream by Magnific popup
10193					var $item = this.currItem,
10194						$content = this.contentContainer,
10195						$wrap = this.wrap,
10196						$container = $wrap.find( ".mfp-container" ),
10197						$containerParent = $container.parent(),
10198						$modal = $wrap.find( ".modal-dialog" ),
10199						$buttons = $wrap.find( ".mfp-arrow" ),
10200						len = $buttons.length,
10201						i, button;
10202
10203					createCloseButton( $modal );
10204
10205					$document.find( "body" ).addClass( "wb-modal" );
10206					$document.find( modalHideSelector ).attr( "aria-hidden", "true" );
10207					for ( i = 0; i !== len; i += 1 ) {
10208						button = $buttons[ i ];
10209						button.innerHTML += "<span class='wb-inv'> " + button.title + "</span>";
10210					}
10211
10212					if ( $item.type === "image" ) {
10213						$content.find( ".mfp-bottom-bar" ).attr( "id", "lbx-title" );
10214					} else {
10215						$content.attr( "role", "document" );
10216					}
10217
10218					this.contentContainer.attr( "data-pgtitle", document.getElementsByTagName( "H1" )[ 0 ].textContent );
10219
10220					trapTabbing( $wrap );
10221
10222					if ( !$containerParent.is( "dialog" ) ) {
10223						$container.wrap( "<dialog class='mfp-container' open='open'></dialog>" );
10224					} else {
10225						$containerParent.attr( "open", "open" );
10226					}
10227				},
10228				close: function() {
10229					$document.find( "body" ).removeClass( "wb-modal" );
10230					$document.find( modalHideSelector ).removeAttr( "aria-hidden" );
10231					this.wrap.find( "dialog" ).removeAttr( "open" );
10232
10233				},
10234				change: function() {
10235					var $item = this.currItem,
10236						$content = this.contentContainer,
10237						$el, $bottomBar, $source, $target,
10238						description, altTitleId, altTitle;
10239
10240					if ( $item.type === "image" ) {
10241						$el = $item.el;
10242						$target = $item.img;
10243						$bottomBar = $content.find( ".mfp-bottom-bar" );
10244
10245						if ( $el ) {
10246							$source = $el.find( "img" );
10247							$target.attr( "alt", $source.attr( "alt" ) );
10248
10249							// Replicate aria-describedby if it exists
10250							description = $source.attr( "aria-describedby" );
10251							if ( description ) {
10252								$target.attr( "aria-describedby", description );
10253							}
10254
10255							// Replicate longdesc if it exists
10256							description = $source.attr( "longdesc" );
10257							if ( description ) {
10258								$target.attr( "longdesc", description );
10259							}
10260
10261							// Handle alternate titles
10262							altTitleId = $el.attr( "data-title" );
10263							if ( altTitleId ) {
10264								altTitle = document.getElementById( altTitleId );
10265								if ( altTitle !== null ) {
10266									$bottomBar.find( ".mfp-title" ).html( altTitle.innerHTML );
10267								}
10268							}
10269						} else {
10270							$target.attr( "alt", $bottomBar.find( ".mfp-title" ).html() );
10271						}
10272					} else {
10273						$content
10274							.find( ".modal-title, h1" )
10275							.first()
10276							.attr( "id", "lbx-title" );
10277					}
10278
10279					$content.attr( "aria-labelledby", "lbx-title" );
10280				},
10281				parseAjax: function( mfpResponse ) {
10282					var currItem = this.currItem,
10283						currEl = currItem.el,
10284						urlHash = currItem.src.split( "#" )[ 1 ],
10285						filter = currEl ? currEl.data( "wbLbxFilter" ) : undef,
10286						selector = filter || ( urlHash ? "#" + urlHash : false ),
10287						$response;
10288
10289					// Sanitize the response
10290					mfpResponse.data = DOMPurify.sanitize( mfpResponse.data );
10291
10292					// Provide the ability to filter the AJAX response HTML
10293					// by the URL hash or a selector
10294					// TODO: Should be dealt with upstream by Magnific Popup
10295					if ( selector ) {
10296						$response = $( "<div>" + mfpResponse.data + "</div>" ).find( selector );
10297					} else {
10298						$response = $( mfpResponse.data );
10299					}
10300					createCloseButton( $response );
10301
10302					$response
10303						.find( ".modal-title, h1" )
10304						.first()
10305						.attr( "id", "lbx-title" );
10306
10307					mfpResponse.data = $response;
10308				},
10309				ajaxContentAdded: function() {
10310					trapTabbing( this.wrap );
10311				}
10312			};
10313		}
10314
10315		// Load Magnific Popup dependency and bind the init event handler
10316		Modernizr.load( {
10317			load: "site!deps/jquery.magnific-popup" + wb.getMode() + ".js",
10318			testReady: function() {
10319				return $.magnificPopup;
10320			},
10321			complete: function() {
10322
10323				// Set the dependency i18nText only once
10324				$.extend( true, $.magnificPopup.defaults, i18nText, defaults );
10325
10326				$document.trigger( dependenciesLoadedEvent );
10327			}
10328		} );
10329	},
10330	createCloseButton = function( $modal ) {
10331		if ( $modal !== null && $modal.hasClass( "modal-dialog" ) ) {
10332			var footer = $modal.find( ".modal-footer" ).first(),
10333				hasFooter = footer.length,
10334				hasButton = hasFooter && $( footer ).find( ".popup-modal-dismiss" ).length !== 0,
10335				closeClassFtr = "popup-modal-dismiss",
10336				closeTextFtr = i18nText.close,
10337				spanTextFtr = i18nText.oClose,
10338				overlayCloseFtr;
10339
10340			if ( !hasButton ) {
10341				if ( !hasFooter ) {
10342					footer = document.createElement( "div" );
10343					footer.setAttribute( "class", "modal-footer" );
10344				}
10345
10346				overlayCloseFtr = "<button type='button' class='btn btn-sm btn-primary pull-left " + closeClassFtr +
10347					"'>" +
10348					closeTextFtr +
10349					"<span class='wb-inv'>" + spanTextFtr + "</span></button>";
10350
10351				$( footer ).append( overlayCloseFtr );
10352				if ( !hasFooter ) {
10353					$( footer ).insertAfter( $modal.find( ".modal-body" ) );
10354				}
10355			}
10356		}
10357	},
10358	trapTabbing = function( $wrap ) {
10359
10360		$wrap.on( "keydown", function( e ) {
10361			if ( e.which === 9 ) {
10362				var tabbable = $wrap.find( ".mfp-container :tabbable" ),
10363					firstTabbable = tabbable.first()[ 0 ],
10364					lastTabbable = tabbable.last()[ 0 ],
10365					currentFocus = $( document.activeElement )[ 0 ];
10366
10367				if ( !e.shiftKey && currentFocus === lastTabbable ) {
10368					e.preventDefault();
10369					firstTabbable.focus();
10370				} else if ( e.shiftKey && ( currentFocus === firstTabbable || currentFocus === $wrap[ 0 ] ) ) {
10371					e.preventDefault();
10372					lastTabbable.focus();
10373				}
10374			}
10375		} );
10376	};
10377
10378// Bind the init event of the plugin
10379$document.on( "timerpoke.wb " + initEvent, selector, init );
10380
10381// Handler for clicking on a same page link within the overlay to outside the overlay
10382$document.on( "click", ".mfp-wrap a[href^='#']", function( event ) {
10383	var which = event.which,
10384		eventTarget = event.currentTarget,
10385		$lightbox, linkTarget;
10386
10387	// Ignore middle/right mouse buttons
10388	if ( !which || which === 1 ) {
10389		$lightbox = $( eventTarget ).closest( ".mfp-wrap" );
10390		linkTarget = document.getElementById( eventTarget.getAttribute( "href" ).substring( 1 ) );
10391
10392		// Ignore same page links to within the overlay
10393		if ( linkTarget && !$.contains( $lightbox[ 0 ], linkTarget ) ) {
10394
10395			// Stop propagation of the click event
10396			if ( event.stopPropagation ) {
10397				event.stopImmediatePropagation();
10398			} else {
10399				event.cancelBubble = true;
10400			}
10401
10402			// Close the overlay and set focus to the same page link
10403			$.magnificPopup.close();
10404			$( linkTarget ).trigger( setFocusEvent );
10405		}
10406	}
10407} );
10408
10409// Event handler for closing a modal popup via the close button
10410$( document ).on( "click", ".popup-modal-dismiss", function( event ) {
10411	if ( !this.hasAttribute( "target" ) ) {
10412		event.preventDefault();
10413	}
10414
10415	$.magnificPopup.close();
10416} );
10417
10418// Event handler for closing a modal popup via the Escape key
10419$( document ).on( "keydown", ".mfp-wrap:not(.mfp-close-btn-in)", function( event ) {
10420
10421	// If the Escape key was pressed...
10422	if ( event.key === "Escape" ) {
10423		const closeButtons = event.currentTarget.querySelectorAll( ".popup-modal-dismiss" );
10424
10425		// Trigger a "fake" click on the last close button
10426		// Notes:
10427		// -Allows Escape key presses to "piggyback" on additional functionality in close button click handlers (such as preventDefault and the session timeout plugin's confirm method)
10428		// -Targets the last close button link to accomodate plugins that use multiple close buttons (such as exit script)... the last button is more likely to represent no in those scenarios
10429		$( closeButtons[ closeButtons.length - 1 ] ).trigger( "click" );
10430	}
10431} );
10432
10433// Event handler for opening a popup via a button link and the spacebar key
10434$( document ).on( "keydown", "." + componentName, function( event ) {
10435	const sourceLink = event.currentTarget;
10436
10437	// If the link contains a role="button" attribute and the spacebar key was pressed...
10438	if ( sourceLink.getAttribute( "role" ) === "button" && event.key === " " ) {
10439
10440		// Don't scroll down (typical spacebar behaviour)
10441		event.preventDefault();
10442
10443		// Trigger a "fake" click on the button link
10444		$( sourceLink ).trigger( "click" );
10445	}
10446} );
10447
10448// Event handler for opening a popup without a link
10449$( document ).on( "open" + selector, function( event, items, modal, title, ajax ) {
10450	if ( event.namespace === componentName ) {
10451		var isGallery = items.length > 1,
10452			isModal = modal && !isGallery ? modal : false,
10453			titleSrc = title ? function() {
10454				return title[ $.magnificPopup.instance.index ];
10455			} : "title";
10456
10457		event.preventDefault();
10458
10459		// Ensure the dependencies are loaded first
10460		$document.one( dependenciesLoadedEvent, function() {
10461			$.magnificPopup.open( {
10462				items: items,
10463				modal: isModal,
10464				gallery: {
10465					enabled: isGallery
10466				},
10467				image: {
10468					titleSrc: titleSrc
10469				},
10470				callbacks: callbacks,
10471				ajax: ajax
10472			} );
10473
10474			wb.ready( undef, componentName );
10475		} );
10476
10477		// Load dependencies as needed
10478		setup();
10479	}
10480} );
10481
10482// Add the timer poke to initialize the plugin
10483wb.add( selector );
10484
10485} )( jQuery, DOMPurify, window, document, wb );
10486
10487/**
10488 * @title WET-BOEW Menu plugin
10489 * @overview A Menu plugin for WET
10490 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
10491 * @author WET community
10492 */
10493( function( $, window, document, wb ) {
10494"use strict";
10495
10496/*
10497 * Variable and function definitions.
10498 * These are global to the plugin - meaning that they will be initialized once per page,
10499 * not once per instance of plugin on the page. So, this is a good place to define
10500 * variables that are common to all instances of the plugin on a page.
10501 */
10502var componentName = "wb-menu",
10503	selector = "." + componentName,
10504	initEvent = "wb-init" + selector,
10505	breadcrumb = document.getElementById( "wb-bc" ),
10506	navCurrentEvent = "navcurr.wb",
10507	focusEvent = "setfocus.wb",
10508	detailsInitEvent = "wb-init.wb-details",
10509	menuItemSelector = "> a, > details > summary",
10510	$document = wb.doc,
10511
10512	// Used for half second delay on showing/hiding menus because of mouse hover
10513	hoverDelay = 500,
10514	menuCount = 0,
10515	globalTimeout,
10516
10517	/**
10518	 * @method init
10519	 * @param {jQuery Event} event Event that triggered the function call
10520	 */
10521	init = function( event ) {
10522
10523		// Start initialization
10524		// returns DOM object = proceed with init
10525		// returns undefined = do not proceed with init (e.g., already initialized)
10526		var elm = wb.init( event, componentName, selector ),
10527			$elm, ajaxFetch;
10528
10529		if ( elm ) {
10530			$elm = $( elm );
10531
10532			// Ensure the container has an id attribute
10533			if ( !$elm.attr( "id" ) ) {
10534				$elm.attr( "id", componentName + "-" + menuCount );
10535			}
10536			menuCount += 1;
10537
10538			// Lets test to see if we have any menus to fetch
10539			// This is required for backwards compatibility. In previous versions, the menu was not integrated with the data ajax plugin.
10540			ajaxFetch = $elm.data( "ajax-fetch" );
10541			if ( ajaxFetch ) {
10542				$elm.trigger( {
10543					type: "ajax-fetch.wb",
10544					fetch: {
10545						url: ajaxFetch
10546					}
10547				} );
10548			} else {
10549
10550				//Enhance menus that don't rely on the data-ajax plugin
10551				ajaxFetch = $elm.data( "ajax-replace" ) || $elm.data( "ajax-append" ) || $elm.data( "ajax-prepend" );
10552				if ( !ajaxFetch ) {
10553					onAjaxLoaded( $elm, $elm );
10554				}
10555			}
10556		}
10557	},
10558
10559	/**
10560	 * Lets set some aria states and attributes
10561	 * @method drizzleAria
10562	 * @param {jQuery DOM elements} $elements The collection of elements
10563	 */
10564	drizzleAria = function( $elements ) {
10565		var length = $elements.length,
10566			$elm, $subMenu, i;
10567
10568		// Lets tweak for aria
10569		for ( i = 0; i !== length; i += 1 ) {
10570			$elm = $elements.eq( i );
10571			$subMenu = $elm.siblings( "ul" );
10572
10573			$elm.attr( {
10574				"aria-posinset": ( i + 1 ),
10575				"aria-setsize": length,
10576				role: "menuitem"
10577			} );
10578
10579			// if there is a submenu lets put in the aria for it
10580			if ( $subMenu.length !== 0 ) {
10581
10582				$elm.attr( "aria-haspopup", "true" );
10583
10584				$subMenu.attr( {
10585					"aria-expanded": "false",
10586					"aria-hidden": "true"
10587				} );
10588
10589				// recurse into submenu
10590				drizzleAria( $subMenu.children( "li" ).find( menuItemSelector ) );
10591			}
10592		}
10593	},
10594
10595	/**
10596	 * @method createCollapsibleSection
10597	 * @return {string}
10598	 */
10599	createCollapsibleSection = function( section, sectionIndex, sectionsLength, $items, itemsLength ) {
10600
10601		// Use details/summary for the collapsible mechanism
10602		var k, $elm, elm, $item, $subItems, subItemsLength,
10603			$section = $( section ),
10604			posinset = "' aria-posinset='",
10605			menuitem = " role='menuitem' aria-setsize='",
10606			sectionHtml = "<li><details>" + "<summary class='mb-item" +
10607				( $section.hasClass( "wb-navcurr" ) || $section.children( ".wb-navcurr" ).length !== 0 ? " wb-navcurr'" : "'" ) +
10608				menuitem + sectionsLength + posinset + ( sectionIndex + 1 ) +
10609				"' aria-haspopup='true'>" + $section.text() + "</summary>" +
10610				"<ul class='list-unstyled mb-sm' role='menu' aria-expanded='false' aria-hidden='true'>";
10611
10612		// Convert each of the list items into WAI-ARIA menuitems
10613		for ( k = 0; k !== itemsLength; k += 1 ) {
10614			$item = $items.eq( k );
10615			$elm = $item.find( menuItemSelector );
10616			elm = $elm[ 0 ];
10617			$subItems = $elm.parent().find( "> ul > li" );
10618			subItemsLength = $subItems.length;
10619
10620			if ( elm && subItemsLength === 0 && elm.nodeName.toLowerCase() === "a" ) {
10621				sectionHtml += "<li>" + $item[ 0 ].innerHTML.replace(
10622					/(<a\s)/,
10623					"$1" + menuitem + itemsLength +
10624							posinset + ( k + 1 ) +
10625							"' tabindex='-1' "
10626				) + "</li>";
10627			} else {
10628				sectionHtml += createCollapsibleSection( elm, k, itemsLength, $subItems, $subItems.length );
10629			}
10630		}
10631
10632		return sectionHtml + "</ul></details></li>";
10633	},
10634
10635	/**
10636	 * @method createMobilePanelMenu
10637	 * @param {array} allProperties Properties used to build the menu system
10638	 * @return {string}
10639	 */
10640	createMobilePanelMenu = function( allProperties ) {
10641		var panel = "",
10642			sectionHtml, properties, sections, section, parent, $items,
10643			linkHtml, i, j, len, sectionsLength, itemsLength;
10644
10645		// Process the secondary and site menus
10646		len = allProperties.length;
10647		for ( i = 0; i !== len; i += 1 ) {
10648			properties = allProperties[ i ];
10649			sectionHtml = "";
10650			sections = properties[ 0 ];
10651			sectionsLength = sections.length;
10652			for ( j = 0; j !== sectionsLength; j += 1 ) {
10653				section = sections[ j ];
10654				$items = $( section.parentNode ).find( "> ul > li" );
10655				itemsLength = $items.length;
10656
10657				// Collapsible section
10658				if ( itemsLength !== 0 ) {
10659					sectionHtml += createCollapsibleSection( section, j, sectionsLength, $items, itemsLength );
10660				} else {
10661					parent = section.parentNode;
10662
10663					// Menu item without a section
10664					if ( parent.nodeName.toLowerCase() === "li" ) {
10665						linkHtml = parent.innerHTML;
10666
10667					// Non-list menu items without a section and that contain their own link
10668					} else if ( parent.getElementsByTagName( "a" )[ 0 ] === section.getElementsByTagName( "a" )[ 0 ] ) {
10669						linkHtml = section.innerHTML;
10670
10671					// Non-list menu item without a section and whose siblings contain a link
10672					} else {
10673						linkHtml = "<a href='" +
10674							parent.getElementsByTagName( "a" )[ 0 ].href + "'>" +
10675							section.innerHTML + "</a>";
10676					}
10677
10678					// Convert the list item to a WAI-ARIA menuitem
10679					sectionHtml += "<li class='no-sect'>" +
10680						linkHtml.replace(
10681							/(<a\s)/,
10682							"$1 class='mb-item' " + "role='menuitem' aria-setsize='" +
10683								sectionsLength + "' aria-posinset='" + ( j + 1 ) +
10684								"' tabindex='-1' "
10685						) + "</li>";
10686				}
10687			}
10688
10689			// Create the panel section
10690			panel += "<nav role='navigation' typeof='SiteNavigationElement' id='" +
10691				properties[ 1 ] + "' class='" + properties[ 1 ] + " wb-menu wb-menu-inited'>" +
10692				"<h3>" + properties[ 2 ] + "</h3>" +
10693				"<ul class='list-unstyled mb-menu' role='menu'>" +
10694				sectionHtml + "</ul></nav>";
10695		}
10696
10697		return panel.replace( /['"]?list-group-item['"]?/gi, "\"\"" );
10698	},
10699
10700	/**
10701	 * @method onAjaxLoaded
10702	 * @param {jQuery DOM element} $elm The plugin element
10703	 * @param {jQuery DOM element}
10703 $ajaxResult The AJAXed in menu content to import
10704	 */
10705	onAjaxLoaded = function( $elm, $ajaxResult ) {
10706		var $info = $( "#wb-info" ),
10707			inner = function() {
10708				var $ajaxed = $ajaxResult && $ajaxResult.attr( "data-type" ) === "string" ? $ajaxResult : $elm,
10709					$menubar = $ajaxed.find( ".menu" ),
10710					$menu = $menubar.find( "> li > a" ),
10711					target = $elm.data( "trgt" ),
10712					$secnav = $( "#wb-sec" ),
10713					$language = $( "#wb-lng" ),
10714					search = document.getElementById( "wb-srch" ),
10715					panel = "",
10716					panelDOM = document.getElementById( target ),
10717					$panel = $( panelDOM ),
10718					allProperties = [],
10719					$navCurr, $menuItem, $langItems, len, i;
10720
10721				/*
10722				 * Build the mobile panel
10723				 */
10724
10725				// Add search
10726				if ( search !== null ) {
10727					panel += "<section class='srch-pnl'>" +
10728						search.innerHTML
10729							.replace( /h2>/i, "h3>" )
10730							.replace( /(for|id)="([^"]+)"/gi, "$1='$2-imprt'" ) +
10731						"</section>";
10732				}
10733
10734				// Add active language offer
10735				if ( $language.length !== 0 ) {
10736					$langItems = $language.find( "li:not(.curr)" );
10737					len = $langItems.length;
10738					panel += "<section class='lng-ofr'>" +
10739						"<h3>" + $language.children( "h2" ).html() + "</h3>" +
10740						"<ul class='list-inline'>";
10741					for ( i = 0; i !== len; i += 1 ) {
10742						panel += $langItems[ i ].innerHTML
10743							.replace( /(<a\s.*<\/a>?)/, "<li>$1</li>" );
10744					}
10745					panel += "</ul></section>";
10746				}
10747
10748				// Create menu system
10749				if ( $secnav.length !== 0 || $menubar.length !== 0 || $info.length !== 0 ) {
10750
10751					// Add the secondary menu
10752					if ( $secnav.length !== 0 ) {
10753						allProperties.push( [
10754							$secnav.find( "ul" ).filter( ":not(li > ul)" ).find( " > li > *:first-child" ).get(),
10755							"sec-pnl",
10756							$secnav.find( "h2" ).html()
10757						] );
10758
10759						if ( $secnav.find( ".wb-navcurr" ).length === 0 ) {
10760
10761							// Trigger the navcurrent plugin
10762							$secnav.trigger( navCurrentEvent, breadcrumb );
10763						}
10764					}
10765
10766					// Add the site menu
10767					if ( $menubar.length !== 0 ) {
10768
10769						// Add the menubar role if it is missing
10770						if ( !$menubar.attr( "role" ) ) {
10771							$menubar.attr( "role", "menubar" );
10772						}
10773
10774						allProperties.push( [
10775							$menu.get(),
10776							"sm-pnl",
10777							$ajaxed.find( "h2" ).html()
10778						] );
10779					}
10780
10781					// Add the site information
10782					if ( $info.length !== 0 ) {
10783						allProperties.push( [
10784							$info.find( "h3, a" ).not( "section a" ),
10785							"info-pnl",
10786							$info.find( "h2" ).html()
10787						] );
10788
10789						if ( $info.find( ".wb-navcurr" ).length === 0 ) {
10790
10791							// Trigger the navcurrent plugin
10792							$info.trigger( navCurrentEvent, breadcrumb );
10793						}
10794					}
10795
10796					panel += createMobilePanelMenu( allProperties );
10797				}
10798
10799				// Let's now populate the DOM since we have done all the work in a documentFragment
10800				panelDOM.innerHTML = "<header class='modal-header'><div class='modal-title'>" +
10801						document.getElementById( "wb-glb-mn" )
10802							.getElementsByTagName( "h2" )[ 0 ]
10803							.innerHTML +
10804						"</div></header><div class='modal-body'>" + panel + "</div>";
10805				panelDOM.className += " wb-overlay modal-content overlay-def wb-panel-r";
10806
10807				// fix #8241
10808				if ( $.active > 0 ) {
10809					$( document ).ajaxStop( function() {
10810						initOverlay( $panel );
10811					} );
10812				} else {
10813					initOverlay( $panel );
10814				}
10815
10816				/*
10817				 * Build the regular mega menu
10818				 */
10819				$ajaxed
10820					.find( ":discoverable" )
10821					.attr( "tabindex", "-1" );
10822
10823				if ( $menu.length !== 0 ) {
10824					$menu[ 0 ].setAttribute( "tabindex", "0" );
10825					drizzleAria( $menu );
10826					$menu
10827						.filter( "[aria-haspopup=true]" )
10828						.append( "<span class='expicon glyphicon glyphicon-chevron-down'></span>" );
10829				}
10830
10831				// Replace elements
10832				$elm.html( $ajaxed.html() );
10833
10834				// Trigger the navcurrent plugin
10835				setTimeout( function() {
10836					$elm.trigger( navCurrentEvent, breadcrumb );
10837					$panel.find( "#sm-pnl" ).trigger( navCurrentEvent, breadcrumb );
10838
10839					// Ensure that wb-navcurr is reflected in the top level
10840					$navCurr = $panel.find( ".wb-navcurr" );
10841					len = $navCurr.length;
10842					for ( i = 0; i !== len; i += 1 ) {
10843						$menuItem = $navCurr.eq( i );
10844
10845						// If not at the top level, then add wb-navcurr to the top level
10846						if ( !$menuItem.hasClass( ".mb-item" ) ) {
10847							$menuItem = $menuItem
10848								.closest( "details" )
10849								.children( "summary" )
10850								.addClass( "wb-navcurr" );
10851						}
10852					}
10853
10854					// Open up the secondary menu if it has wb-navcurr and has a submenu
10855					$menuItem = $panel.find( "#sec-pnl .wb-navcurr.mb-item" );
10856					if ( $menuItem.attr( "aria-haspopup" ) === "true" ) {
10857						$menuItem
10858							.trigger( "click" )
10859							.parent()
10860							.prop( "open", "open" );
10861					}
10862
10863					// Identify that initialization has completed
10864					wb.ready( $elm, componentName );
10865				}, 1 );
10866			},
10867			$footerAjax  = $info.find( "[data-ajax-replace],[data-ajax-append],[data-ajax-prepend]" ),
10868			footerAjaxLength = $footerAjax.length,
10869			ajaxCount = 0;
10870
10871		//Delay the execution the menu until any ajaxed footer content is in
10872		if ( footerAjaxLength === 0 ) {
10873			inner();
10874		} else {
10875			$info.on( "wb-contentupdated ajax-failed.wb", function() {
10876				ajaxCount += 1;
10877				if ( ajaxCount === footerAjaxLength ) {
10878					inner();
10879				}
10880			} );
10881		}
10882	},
10883
10884	// fix #8517
10885	/**
10886	 * @method initOverlay
10887	 * @param {jQuery object} $panel Current panel
10888	 */
10889	initOverlay = function( $panel ) {
10890		$panel
10891			.trigger( "wb-init.wb-overlay" )
10892			.find( "summary" )
10893			.attr( "tabindex", "-1" )
10894			.trigger( detailsInitEvent );
10895		$panel
10896			.find( ".mb-menu > li:first-child" )
10897			.find( ".mb-item" )
10898			.attr( "tabindex", "0" );
10899	},
10900
10901	/**
10902	 * @method menuIncrement
10903	 * @param {jQuery object} $menuItems Collection of of menu items to move between
10904	 * @param {jQuery object} $current Current menu item
10905	 * @param {integer} indexChange Requested relative change to the menu item index
10906	 */
10907	menuIncrement = function( $menuItems, $current, indexChange ) {
10908		var menuItemsLength = $menuItems.length,
10909			index = $menuItems.index( $current ) + indexChange;
10910
10911		// Correct out-of-range indexes
10912		index = index === menuItemsLength ? 0 : index === -1 ? menuItemsLength - 1 : index;
10913
10914		// Move to the new menu item
10915		$menuItems.eq( index ).trigger( focusEvent );
10916	},
10917
10918	/**
10919	 * @method menuClose
10920	 * @param {jQuery DOM element} $elm Parent of the element to close
10921	 * @param {boolean} removeActive Whether or not to keep the active class
10922	 */
10923	menuClose = function( $elm, removeActive ) {
10924		$elm
10925			.removeClass( "sm-open" )
10926			.children( ".open" )
10927			.removeClass( "open" )
10928			.attr( {
10929				"aria-hidden": "true",
10930				"aria-expanded": "false"
10931			} )
10932
10933		// Close nested submenus
10934			.find( "details" )
10935			.removeAttr( "open" )
10936			.children( "ul" )
10937			.attr( {
10938				"aria-hidden": "true",
10939				"aria-expanded": "false"
10940			} );
10941
10942		if ( removeActive ) {
10943			$elm.removeClass( "active" );
10944		}
10945	},
10946
10947	/**
10948	 * @method menuDisplay
10949	 * @param {jQuery DOM element} $elm The plugin element
10950	 * @param {jQuery event} menu The menu to display
10951	 */
10952	menuDisplay = function( $elm, menu ) {
10953		var menuLink = menu.children( "a" );
10954
10955		menuClose( $elm.find( ".active" ), true );
10956
10957		menu.addClass( "active" );
10958
10959		// Ignore if doesn't have a submenu
10960		if ( menuLink.attr( "aria-haspopup" ) === "true" ) {
10961
10962			// Add the open state classes
10963			menu
10964				.addClass( "sm-open" )
10965				.children( ".sm" )
10966				.addClass( "open" )
10967				.attr( {
10968					"aria-hidden": "false",
10969					"aria-expanded": "true"
10970				} );
10971		}
10972	},
10973
10974	/**
10975	 * Searches for the next link that has link text starting with a specific letter
10976	 * @method selectByLetter
10977	 * @param {integer} charCode The charCode of the letter to search for
10978	 * @param {DOM elements} links Collection of links to search
10979	 */
10980	selectByLetter = function( charCode, links ) {
10981		var len = links.length,
10982			keyChar = String.fromCharCode( charCode ),
10983			link, i;
10984
10985		for ( i = 0; i !== len; i += 1 ) {
10986			link = links[ i ];
10987			if ( link.innerHTML.charAt( 0 ) === keyChar ) {
10988				$( link ).trigger( focusEvent );
10989				return true;
10990			}
10991		}
10992
10993		return false;
10994	};
10995
10996// Bind the events of the plugin
10997$document.on( "timerpoke.wb " + initEvent + " ajax-fetched.wb ajax-failed.wb", selector, function( event ) {
10998
10999	var eventType = event.type,
11000		elm, $elm;
11001
11002	switch ( eventType ) {
11003		case "ajax-fetched":
11004		case "ajax-failed":
11005			elm = event.target;
11006
11007			// Filter out any events triggered by descendants
11008			if ( event.currentTarget === elm ) {
11009				$elm = $( elm );
11010
11011				// Only replace the menu if there isn't an error
11012				onAjaxLoaded(
11013					$elm,
11014					eventType === "ajax-fetched" ? event.fetch.pointer : $elm
11015				);
11016			}
11017			return false;
11018
11019		case "timerpoke":
11020		case "wb-init":
11021			init( event );
11022			break;
11023	}
11024
11025	/*
11026	 * Since we are working with events we want to ensure that we are being passive about our control,
11027	 * so returning true allows for events to always continue
11028	 */
11029	return true;
11030} );
11031
11032$document.on( "mouseleave", selector + " .menu", function( event ) {
11033	var $currentTarget = $( event.currentTarget );
11034
11035	// Clear the timeout for open/closing menus
11036	clearTimeout( globalTimeout );
11037
11038	globalTimeout = setTimeout( function() {
11039		menuClose( $currentTarget.find( ".active" ), true );
11040	}, hoverDelay );
11041} );
11042
11043// Prevent opening another menu if mouse re-enters already opened menu
11044$document.on( "mouseenter", selector + " .sm", function() {
11045	if ( $( this ).attr( "aria-expanded" ) === "true" ) {
11046		clearTimeout( globalTimeout );
11047	}
11048} );
11049
11050// Touchscreen "touches" on menubar items should close the submenu if it is open
11051$document.on( "click", selector + " .item[aria-haspopup=true]", function( event ) {
11052	var which = event.which,
11053		$this, $parent;
11054
11055	// Ignore middle and right mouse buttons
11056	if ( !which || which === 1 ) {
11057		event.preventDefault();
11058		$this = $( this );
11059		$parent = $this.parent();
11060
11061		// Open the submenu if it is closed
11062		if ( !$parent.hasClass( "sm-open" ) ) {
11063			$this.trigger( "focusin" );
11064		}
11065	}
11066} );
11067
11068// Click on menu items with submenus should open and close those submenus
11069$document.on( "click", selector + " [role=menu] [aria-haspopup=true]", function( event ) {
11070	var menuItem = event.currentTarget,
11071		parent = menuItem.parentNode,
11072		submenu = parent.getElementsByTagName( "ul" )[ 0 ],
11073		isOpen = submenu.getAttribute( "aria-hidden" ) === "false",
11074		menuItemOffsetTop, menuContainer;
11075
11076	// Close any other open menus
11077	if ( !isOpen ) {
11078		$( parent )
11079			.closest( "[role^='menu']" )
11080			.find( "[aria-hidden=false]" )
11081			.parent()
11082			.find( "[aria-haspopup=true]" )
11083			.not( menuItem )
11084			.trigger( "click" );
11085
11086		// Ensure the opened menu is in view if in a mobile panel
11087		menuContainer = document.getElementById( "mb-pnl" );
11088		menuItemOffsetTop = menuItem.offsetTop;
11089		if ( $.contains( menuContainer, menuItem ) &&
11090			menuItemOffsetTop < menuContainer.scrollTop ) {
11091
11092			menuContainer.scrollTop = menuItemOffsetTop;
11093		}
11094	}
11095
11096	submenu.setAttribute( "aria-expanded", !isOpen );
11097	submenu.setAttribute( "aria-hidden", isOpen );
11098} );
11099
11100// Clicks and touches outside of menus should close any open menus
11101$document.on( "click", function( event ) {
11102	var $openMenus,
11103		which = event.which;
11104
11105	// Ignore middle and right mouse buttons
11106	if ( event.type === "" || ( !which || which === 1 ) ) {
11107		$openMenus = $( selector + " .sm-open" );
11108		if ( $openMenus.length !== 0 &&
11109			$( event.target ).closest( selector ).length === 0 ) {
11110
11111			menuClose( $openMenus, true );
11112		}
11113	}
11114} );
11115
11116$document.on( "mouseover focusin", selector + " .item", function( event ) {
11117	var $elm = $( event.currentTarget ),
11118		$parent = $elm.parent(),
11119		$container = $parent.closest( selector );
11120
11121	// Clear the timeout for open/closing menus
11122	clearTimeout( globalTimeout );
11123
11124	if ( event.type === "focusin" ) {
11125		menuDisplay( $container, $parent );
11126	} else {
11127		globalTimeout = setTimeout( function() {
11128			menuDisplay( $container, $parent );
11129		}, hoverDelay );
11130	}
11131} );
11132
11133/*
11134 * Keyboard bindings
11135 */
11136$document.on( "keydown", selector + " [role=menuitem]", function( event ) {
11137	var menuItem = event.currentTarget,
11138		which = event.which,
11139		$menuItem = $( menuItem ),
11140		hasPopup = $menuItem.attr( "aria-haspopup" ) === "true",
11141		$menu = $menuItem.parent().closest( "[role^='menu']" ),
11142		inMenuBar = $menu.attr( "role" ) === "menubar",
11143		$menuLink, $parentMenu, $parent, $subMenu, result,
11144		menuitemSelector, isOpen, menuItemOffsetTop, menuContainer;
11145
11146	// Define keycodes. (Make const when WET supports ES6)
11147	var TAB_KC = 9,
11148		ENTER_KC = 13,
11149		ESC_KC = 27,
11150		LEFT_KC = 37,
11151		UP_KC = 38,
11152		RIGHT_KC = 39,
11153		DOWN_KC = 40,
11154		SPACE_KC = 32;
11155
11156	if ( !( event.ctrlKey || event.altKey || event.metaKey ) ) {
11157
11158		// Tab key = Hide all sub-menus
11159		if ( which === TAB_KC ) {
11160			menuClose( $( selector + " .active" ), true );
11161
11162		//Enter or spacebar on a link = follow the link and close menus
11163		} else if ( menuItem.nodeName === "A" && menuItem.hasAttribute( "href" ) &&
11164			( which === ENTER_KC || which === SPACE_KC ) ) {
11165
11166			event.preventDefault();
11167			menuItem.click();
11168			menuClose( $( selector + " .active" ), true );
11169
11170		// Menu item is within a menu bar
11171		} else if ( inMenuBar ) {
11172
11173			// Left / right arrow = Previous / next menu item
11174			if ( which === LEFT_KC || which === RIGHT_KC ) {
11175				event.preventDefault();
11176				menuIncrement(
11177					$menu.find( "> li > a" ),
11178					$menuItem,
11179					which === LEFT_KC ? -1 : 1
11180				);
11181
11182			// Enter sub-menu
11183			} else if ( hasPopup && ( which === ENTER_KC || which === SPACE_KC || which === UP_KC || which === DOWN_KC ) ) {
11184				event.preventDefault();
11185				$parent = $menuItem.parent();
11186				$subMenu = $parent.find( ".sm" );
11187
11188				// Open the submenu if it is not already open
11189				if ( !$subMenu.hasClass( "open" ) ) {
11190					menuDisplay( $menu.closest( selector ), $parent );
11191				}
11192
11193				// Set focus on the first submenu item
11194				$subMenu.children( "li" ).eq( 0 ).find( menuItemSelector ).trigger( focusEvent );
11195
11196			// Hide sub-menus and set focus
11197			} else if ( which === ESC_KC ) {
11198				event.preventDefault();
11199				menuClose( $menu.closest( selector ).find( ".active" ), false );
11200
11201			// Letters only
11202			} else if ( which > 64 && which < 91 ) {
11203				event.preventDefault();
11204				selectByLetter(
11205					which,
11206					$menuItem.parent().find( "> ul > li > a, > ul > li > details > summary" ).get()
11207				);
11208			}
11209
11210		// Menu item is not within a menu bar
11211		} else {
11212			menuitemSelector = menuItemSelector;
11213
11214			// Up / down arrow = Previous / next menu item
11215			if ( which === UP_KC || which === DOWN_KC ) {
11216				event.preventDefault();
11217				menuIncrement(
11218					$menu.children( "li" ).find( menuitemSelector ),
11219					$menuItem,
11220					which === UP_KC ? -1 : 1
11221				);
11222
11223			// Enter, space, or right arrow with a submenu
11224			} else if ( hasPopup && ( which === ENTER_KC || which === SPACE_KC || which === RIGHT_KC ) ) {
11225				$parent = $menuItem.parent();
11226
11227				// Prevent handling by details.js polyfill
11228				event.stopImmediatePropagation();
11229				event.preventDefault();
11230
11231				// If the menu item is a summary element
11232				if ( menuItem.nodeName.toLowerCase( "summary" ) ) {
11233					isOpen = !!$parent.attr( "open" );
11234
11235					// Close any other open menus
11236					if ( !isOpen ) {
11237						$( parent )
11238							.closest( "[role^='menu']" )
11239							.find( "[aria-hidden=false]" )
11240							.parent()
11241							.find( "[aria-haspopup=true]" )
11242							.not( menuItem )
11243							.trigger( "click" );
11244
11245						// Ensure the opened menu is in view if in a mobile panel
11246						menuContainer = document.getElementById( "mb-pnl" );
11247						menuItemOffsetTop = menuItem.offsetTop;
11248						if ( $.contains( menuContainer, menuItem ) &&
11249							menuItemOffsetTop < menuContainer.scrollTop ) {
11250
11251							menuContainer.scrollTop = menuItemOffsetTop;
11252						}
11253
11254						// Ensure the menu is opened or stays open
11255						$menuItem.trigger( "click" );
11256					}
11257
11258					// Update the WAI-ARIA states and move focus to
11259					// the first submenu item
11260					$parent.children( "ul" )
11261						.attr( {
11262							"aria-expanded": "true",
11263							"aria-hidden": "false"
11264						} )
11265						.find( "[role=menuitem]:first" )
11266						.trigger( "setfocus.wb" );
11267				}
11268
11269			// Escape, left / right arrow without a submenu
11270			} else if ( which === ESC_KC || which === LEFT_KC || which === RIGHT_KC ) {
11271				$parent = $menu.parent();
11272				$parentMenu = $parent.closest( "[role^='menu']" );
11273				if ( which === LEFT_KC || which === RIGHT_KC ) {
11274					event.preventDefault();
11275				}
11276
11277				// If the parent menu is a menubar
11278				if ( $parentMenu.attr( "role" ) === "menubar" ) {
11279					$menuLink = $menu.siblings( "a" );
11280
11281					// Escape key = Close menu and return to menu bar item
11282					if ( which === ESC_KC ) {
11283						event.preventDefault();
11284						$menuLink.trigger( focusEvent );
11285
11286						// Close the menu but keep the referring link active
11287						setTimeout( function() {
11288							menuClose( $menuLink.parent(), false );
11289						}, 100 );
11290
11291					// Left / right key = Next / previous menu bar item
11292					} else if ( $parentMenu.attr( "role" ) === "menubar" ) {
11293						menuIncrement(
11294							$parentMenu.find( "> li > a" ),
11295							$menuLink,
11296							which === LEFT_KC ? -1 : 1
11297						);
11298					}
11299
11300				// Escape or left arrow: Go up a level if there is a higher-level
11301				// menu or close the current submenu if there isn't
11302				} else if ( which !== RIGHT_KC ) {
11303					$subMenu = $parentMenu.length !== 0 ? $menu : $menuItem;
11304
11305					// There is a higher-level menu
11306					if ( $parentMenu.length !== 0 ) {
11307						event.preventDefault();
11308						$menu.closest( "li" )
11309							.find( menuitemSelector )
11310							.trigger( "click" )
11311							.trigger( "setfocus.wb" );
11312
11313					// No higher-level menu but the current submenu is open
11314					} else if ( $menuItem.parent().children( "ul" ).attr( "aria-hidden" ) === "fal
11314se" ) {
11315						event.preventDefault();
11316						$menuItem
11317							.trigger( "click" )
11318							.trigger( "setfocus.wb" );
11319					}
11320				}
11321
11322			// Select a menu item in the current menu by the first letter
11323			} else if ( which > 64 && which < 91 ) {
11324				event.preventDefault();
11325				$parent = $menuItem.closest( "li" );
11326
11327				// Try to find a match in the next siblings
11328				result = selectByLetter(
11329					which,
11330					$parent.nextAll().find( menuitemSelector ).get()
11331				);
11332
11333				// If couldn't find a match, try the previous siblings
11334				if ( !result ) {
11335					result = selectByLetter(
11336						which,
11337						$parent.prevAll().find( menuitemSelector ).get()
11338					);
11339				}
11340			}
11341		}
11342	}
11343} );
11344
11345// Prevent Firefox from double-triggering menu behaviour
11346$document.on( "keyup", selector + " [role=menuitem]", function( event ) {
11347	event.preventDefault();
11348	return false;
11349} );
11350
11351// Close the mobile panel if switching to medium, large or extra large view
11352$document.on( "mediumview.wb largeview.wb xlargeview.wb", function() {
11353	var mobilePanel = document.getElementById( "mb-pnl" );
11354	if ( mobilePanel && mobilePanel.getAttribute( "aria-hidden" ) === "false" ) {
11355		$( mobilePanel ).trigger( "close.wb-overlay" );
11356	}
11357} );
11358
11359// Add the timer poke to initialize the plugin
11360wb.add( selector );
11361
11362} )( jQuery, window, document, wb );
11363
11364/**
11365 * @title WET-BOEW Multimedia PLayer
11366 * @overview An accessible multimedia player for <audio> and <video> tags
11367 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
11368 * @author WET Community
11369 */
11370/* globals YT */
11371( function( $, DOMPurify, window, wb, undef ) {
11372"use strict";
11373
11374/* Local scoped variables*/
11375var componentName = "wb-mltmd",
11376	selector = "." + componentName,
11377	initEvent = "wb-init" + selector,
11378	ctrls = selector + " .wb-mm-ctrls",
11379	dispCtrls = selector + " .display," + ctrls,
11380	template,
11381	i18n, i18nText,
11382	youtubeReadyEvent = "ready.youtube",
11383	captionsLoadedEvent = "ccloaded" + selector,
11384	captionsLoadFailedEvent = "ccloadfail" + selector,
11385	captionsVisibleChangeEvent = "ccvischange" + selector,
11386	renderUIEvent = "renderui" + selector,
11387	initializedEvent = "inited" + selector,
11388	youtubeEvent = "youtube" + selector,
11389	resizeEvent = "resize" + selector,
11390	templateLoadedEvent = "templateloaded" + selector,
11391	cuepointEvent = "cuepoint" + selector,
11392	captionClass = "cc_on",
11393	multimediaEvents = [
11394		"durationchange",
11395		"playing",
11396		"pause",
11397		"ended",
11398		"volumechange",
11399		"timeupdate",
11400		"waiting",
11401		"canplay",
11402		"seeked",
11403		"progress",
11404		captionsLoadedEvent,
11405		captionsLoadFailedEvent,
11406		captionsVisibleChangeEvent,
11407		cuepointEvent
11408	].join( " " ),
11409	$document = wb.doc,
11410	$window = wb.win,
11411
11412	/**
11413	 * @function init
11414	 * @param {jQuery Event} event Event that triggered the function call
11415	 */
11416	init = function( event ) {
11417
11418		// Start initialization
11419		// returns DOM object = proceed with init
11420		// returns undefined = do not proceed with init (e.g., already initialized)
11421		var eventTarget = wb.init( event, componentName, selector );
11422
11423		if ( eventTarget ) {
11424
11425			// Only initialize the i18nText once
11426			if ( !i18nText ) {
11427
11428				// YT workaround for when played inside a modal dialog, like with lightbox
11429				//
11430				// the issue is when the iFrame is moved, it reloads and then it doesn't emit the right event to
11431				// adjust the WET multimedia controller to represent its current state.
11432				//
11433				// This needs to be executed only once, that is why it is in the i18nText conditional
11434				window.addEventListener( "message", function( e ) {
11435					var data, frames, i, i_len, i_cache;
11436
11437					// Ensure we don't conflict with other postMessage listener
11438					try {
11439						data = JSON.parse( e.data );
11440
11441						// Only for a specific YT message
11442						if ( data.event && data.event === "infoDelivery" && data.info && data.info.playerState ) {
11443
11444							// Find the iFrame and evaluate if it needs to be reposted
11445							frames = document.getElementsByTagName( "iframe" );
11446
11447							i_len = frames.length;
11448							for ( i = 0; i < i_len; i++ ) {
11449								i_cache = frames[ i ];
11450								if ( i_cache.dataset.L2 && i_cache.contentWindow === e.source  ) {
11451
11452									// Prepare the event data and emulate the YT object for our event management need
11453									youTubeEvents.call( i_cache, {
11454										target: i_cache.parentElement.parentElement.object,
11455										data: data.info.playerState
11456									} );
11457									break;
11458								}
11459							}
11460						}
11461					} catch ( err ) {
11462
11463						/* swallow error */
11464					}
11465				} );
11466
11467				//
11468				// END YT workaround
11469				//
11470
11471				i18n = wb.i18n;
11472				i18nText = {
11473					play: i18n( "mmp-play" ),
11474					pause: i18n( "pause" ),
11475					volume: i18n( "volume" ),
11476					cc_on: i18n( "cc", "on" ),
11477					cc_off: i18n( "cc", "off" ),
11478					cc_error: i18n( "cc-err" ),
11479					fs: i18n( "fs" ),
11480					mute_on: i18n( "mute", "on" ),
11481					mute_off: i18n( "mute", "off" ),
11482					duration: i18n( "dur" ),
11483					position: i18n( "pos" )
11484				};
11485			}
11486
11487			if ( template === undef ) {
11488				template = "";
11489				$( eventTarget ).trigger( {
11490					type: "ajax-fetch.wb",
11491					fetch: {
11492						url: wb.getPath( "/assets" ) + "/mediacontrols.html"
11493					}
11494				} );
11495			} else if ( template !== "" ) {
11496				$( eventTarget ).trigger( templateLoadedEvent );
11497			}
11498		}
11499	},
11500
11501	/* helper functions*/
11502
11503	/**
11504	 * @method formatTime
11505	 * @description format a number of seconds to SMTPE Timecode format (HH:MM:SS.FF)
11506	 * @param {Float} time The time to format
11507	 * @returns {String} the formatted time
11508	 */
11509	formatTime = function( time ) {
11510		var index = 2,
11511			timecode = "",
11512			secondsIn, current, pad;
11513
11514		pad = function( number, digits ) {
11515			return new Array( Math.max( digits - String( number ).length + 1, 0 ) ).join( 0 ) + number;
11516		};
11517
11518		time = Math.floor( time );
11519
11520		//Loop to extract hours, minutes and seconds
11521		while ( index >= 0 ) {
11522
11523			//Get the number of seconds for the current iteration (hour, minute or second)
11524			secondsIn = Math.pow( 60, index );
11525			current = Math.floor( time / secondsIn );
11526
11527			if ( timecode !== "" ) {
11528				timecode += ":";
11529			}
11530
11531			timecode += pad( current, 2 );
11532			time -= secondsIn * current;
11533			index -= 1;
11534		}
11535		return timecode;
11536	},
11537
11538	/**
11539	 * @method parseTime
11540	 * @description parse an SMTPE Timecode string (HH:MM:SS.FF) or duration (45s) and returns the number of seconds for the timecode
11541	 * @param {String} time The timecode or duration string to parse
11542	 * @returns {Float} the number of seconds in time
11543	 */
11544	parseTime = function( time ) {
11545		var i, parts, timeStringPortion, partLength, seconds;
11546
11547		if ( time !== undef ) {
11548			if ( time.charAt( time.length - 1 ) === "s" ) {
11549
11550				//Duration parsing
11551				return parseFloat( time.substring( 0, time.length - 1 ) );
11552			} else {
11553
11554				//SMTPE Timecode Parsing
11555				parts = time.split( ":" ).reverse();
11556				seconds = 0;
11557
11558				for ( i = 0, partLength = parts.length; i < partLength; i += 1 ) {
11559					timeStringPortion = i === 0 ?
11560						parseFloat( parts[ i ] ) :
11561						parseInt( parts[ i ], 10 );
11562					seconds += timeStringPortion * Math.pow( 60, i );
11563				}
11564				return seconds;
11565			}
11566		}
11567		return -1;
11568	},
11569
11570	/*
11571	 * Performant micro templater
11572	 * @credit: https://github.com/premasagar/tim/blob/master/tinytim.js
11573	 * @todo: caching
11574	 */
11575	tmpl = ( function() {
11576		var start = "{{",
11577			end = "}}",
11578
11579			// e.g. config.person.name
11580			path = "[a-z0-9_$][\\.a-z0-9_]*",
11581			pattern = new RegExp( start + "\\s*(" + path + ")\\s*" + end, "gi" );
11582		return function( template, data ) {
11583
11584			// Merge data into the template string
11585			return template.replace( pattern, function( tag, token ) {
11586				var path = token.split( "." ),
11587					len = path.length,
11588					lookup = data,
11589					i = 0;
11590				for ( ; i < len; i += 1 ) {
11591					lookup = lookup[ path[ i ] ];
11592
11593					// Property not found
11594					if ( lookup === undef ) {
11595						throw "tim: '" + path[ i ] + "' not found in " + tag;
11596					}
11597
11598					// Return the required value
11599					if ( i === len - 1 ) {
11600						return lookup;
11601					}
11602				}
11603			} );
11604		};
11605	}() ),
11606
11607	/**
11608	 * @method parseHtml
11609	 * @description parse an HTML fragment and extract embed captions
11610	 * @param {String} content The HTML fragment containing the captions
11611	 * @returns {Array} An array of captions objects (ex: {text: "Caption", begin: 0, end :10})
11612	 */
11613	parseHtml = function( content ) {
11614		var captions = [],
11615			captionSelector = ".wb-tmtxt",
11616			captionElements = content.find( captionSelector ),
11617			len = captionElements.length,
11618			i, captionElement, json, begin, end;
11619
11620		for ( i = 0; i !== len; i += 1 ) {
11621			captionElement = $( captionElements[ i ] );
11622			begin = -1;
11623			end = -1;
11624
11625			if ( captionElement.attr( "data-begin" ) !== undef ) {
11626				begin = parseTime( captionElement.attr( "data-begin" ) );
11627				end = captionElement.attr( "data-end" ) !== undef ?
11628					parseTime( captionElement.attr( "data-end" ) ) :
11629					parseTime( captionElement.attr( "data-dur" ) ) + begin;
11630			} else if ( captionElement.attr( "data" ) !== undef ) {
11631				json = captionElement.attr( "data" )
11632					.replace( /(begin|dur|end)/g, "\"$1\"" )
11633					.replace( /'/g, "\"" );
11634				json = $.parseJSON( json );
11635				begin = parseTime( json.begin );
11636				end = json.end !== undef ?
11637					parseTime( json.end ) :
11638					parseTime( json.dur ) + begin;
11639			}
11640
11641			//Removes nested captions if an
11642			captionElement = captionElement.clone();
11643			captionElement.find( captionSelector ).detach();
11644
11645			captions[ captions.length ] = {
11646				text: captionElement.html(),
11647				begin: begin,
11648				end: end
11649			};
11650		}
11651		return captions;
11652	},
11653
11654	/**
11655	 * @method parseXml
11656	 * @description parse an TTML (Xml) document and extract captions
11657	 * @param {String} content The TTML fragment containing the captions
11658	 * @returns {Array} An array of captions objects (ex: {text: "Caption", begin: 0, end :10})
11659	 */
11660	parseXml = function( content ) {
11661		var captions = [],
11662			captionSelector = "[begin]",
11663			parser = new DOMParser(),
11664			doc = parser.parseFromString( content, "application/xml" ),
11665			captionElements = doc.querySelectorAll( captionSelector ),
11666			len = captionElements.length,
11667			i, captionElement, begin, end;
11668
11669		for ( i = 0; i !== len; i += 1 ) {
11670			captionElement = captionElements[ i ];
11671
11672			begin = parseTime( captionElement.getAttribute( "begin" ) + "" );
11673			end = captionElement.hasAttribute( "end" ) ?
11674				parseTime( captionElement.getAttribute( "end" ) + "" ) :
11675				parseTime( captionElement.getAttribute( "dur" ) + "" ) + begin;
11676
11677			captions[ captions.length ] = {
11678				text: DOMPurify.sanitize( captionElement.textContent ),
11679				begin: begin,
11680				end: end
11681			};
11682		}
11683		return captions;
11684	},
11685
11686	/**
11687	 * @method loadCaptionsExternal
11688	 * @description Loads captions from an external source (HTML embed or TTML)
11689	 * @param {Object} elm The jQuery object for the multimedia player loading the captions
11690	 * @param {String} url The url for the captions resource to load
11691	 * @fires ccloaded.wb-mltmd
11692	 * @fires ccloadfail.wb-mltmd
11693	 */
11694	loadCaptionsExternal = function( elm, url ) {
11695		$.ajax( {
11696			url: url,
11697			dataType: "html",
11698
11699			//Filters out images and objects from the content to avoid loading them
11700			dataFilter: function( data ) {
11701				return data.replace( /<img|object [^>]*>/g, "" );
11702			},
11703			success: function( data ) {
11704
11705				var captionItems;
11706
11707				if ( data.indexOf( "<html" ) !== -1 ) {
11708
11709					// Sanitize the response
11710					captionItems = parseHtml( $( DOMPurify.sanitize( data, { WHOLE_DOCUMENT: true } ) ) );
11711				} else {
11712
11713					// Response is sanitized in the XML parser function
11714					captionItems = parseXml( data );
11715				}
11716
11717				if ( captionItems.length ) {
11718					elm.trigger( {
11719						type: captionsLoadedEvent,
11720						captions: captionItems
11721					} );
11722				} else {
11723					elm.trigger( {
11724						type: captionsLoadFailedEvent
11725					} );
11726				}
11727			},
11728			error: function( response, textStatus, errorThrown ) {
11729				elm.trigger( {
11730					type: captionsLoadFailedEvent,
11731					error: errorThrown
11732				} );
11733			}
11734		} );
11735	},
11736
11737	/**
11738	 * @method loadCaptionsInternal
11739	 * @description Loads same page captions embedded in HTML
11740	 * @param {Object} elm The jQuery object for the multimedia player loading the captions
11741	 * @param {Object}
11741 obj The jQUery object containing the captions
11742	 * @fires ccloaded.wb-mltmd
11743	 */
11744	loadCaptionsInternal = function( elm, obj ) {
11745		var captionItems = parseHtml( obj );
11746
11747		if ( captionItems.length ) {
11748			elm.trigger( {
11749				type: captionsLoadedEvent,
11750				captions: captionItems
11751			} );
11752		} else {
11753			elm.trigger( {
11754				type: captionsLoadFailedEvent
11755			} );
11756		}
11757	},
11758
11759	/**
11760	 * @method updateCaptions
11761	 * @description Update the captions for a multimedia player (called from the timeupdate event of the HTML5 media API)
11762	 * @param {Object} area The jQuery object for the element where captions are displayed
11763	 * @param {Float} seconds The current time of the media (use to sync the captions)
11764	 * @param {Object} captions The JavaScript object containing the captions
11765	 */
11766	updateCaptions = function( area, seconds, captions ) {
11767		var caption, i,
11768			captionsLength = captions.length;
11769
11770		// added &#160; (non-breaking space) to prevent caption space from collapsing
11771		// Used .html() instead of .append for performance purposes
11772		// https://jsperf.com/jquery-append-vs-html-list-performance/2
11773		area.html( "&#160;" );
11774
11775		for ( i = 0; i < captionsLength; i += 1 ) {
11776			caption = captions[ i ];
11777			if ( seconds >= caption.begin && seconds <= caption.end ) {
11778				area.html( $( "<div>" + caption.text + "</div>" ) );
11779			}
11780		}
11781	},
11782
11783	/**
11784	 * @method playerApi
11785	 * @description Normalizes the calls to the HTML5 media API
11786	 * @param {String} fn The function to call
11787	 * @param {object} args The arguments to send to the function call
11788	 */
11789	playerApi = function( fn, args ) {
11790		var $this, method;
11791
11792		switch ( fn ) {
11793			case "play":
11794				try {
11795					this.object.play();
11796				} catch ( ex ) {
11797					this.object.doPlay();
11798				}
11799				break;
11800			case "pause":
11801				try {
11802					this.object.pause();
11803				} catch ( ex ) {
11804					this.object.doPause();
11805				}
11806				break;
11807			case "getCaptionsVisible":
11808				return $( this ).hasClass( captionClass );
11809			case "setCaptionsVisible":
11810				$this = $( this );
11811				if ( args ) {
11812					$this.addClass( captionClass );
11813				} else {
11814					$this.removeClass( captionClass );
11815				}
11816				$this.trigger( captionsVisibleChangeEvent );
11817				break;
11818			case "fullscreen":
11819				if ( this.object.requestFullscreen ) {
11820					this.object.requestFullscreen();
11821				} else if ( this.object.webkitRequestFullscreen ) { /* Safari */
11822					this.object.webkitRequestFullscreen();
11823				} else if ( this.object.msRequestFullscreen ) { /* IE11 */
11824					this.object.msRequestFullscreen();
11825				}
11826				break;
11827			case "getBuffering":
11828				return this.object.buffering || false;
11829			case "setBuffering":
11830				this.object.buffering = args;
11831				break;
11832			case "getPreviousTime":
11833				return this.object.previousTime;
11834			case "setPreviousTime":
11835				this.object.previousTime = args;
11836				break;
11837			default:
11838				method = fn.charAt( 3 ).toLowerCase() + fn.substr( 4 );
11839				switch ( fn.substr( 0, 3 ) ) {
11840					case "get":
11841						return typeof this.object[ method ] !== "function" ?
11842							this.object[ method ] :
11843							this.object[ method ]();
11844					case "set":
11845						typeof this.object[ method ] !== "function" ?
11846							this.object[ method ] = args :
11847							this.object[ fn ]( args );
11848				}
11849		}
11850	},
11851
11852	/**
11853	 * @method youTubeApi
11854	 * @description Normalizes the calls to the YouTube API
11855	 * @param {String} fn The function to call
11856	 * @param {object} args The arguments to send to the function call
11857	 */
11858	youTubeApi = function( fn, args ) {
11859		var $media = $( this.object.getIframe() ),
11860			state;
11861
11862		switch ( fn ) {
11863			case "play":
11864				this.object.wasMutedPlay = this.object.isMuted();
11865				return this.object.playVideo();
11866			case "pause":
11867				return this.object.pauseVideo();
11868			case "getPaused":
11869				state = this.object.getPlayerState();
11870				return state === -1 || state === 0 || state === 2 || state === 5;
11871			case "getPlayed":
11872				return this.object.getPlayerState() > -1;
11873			case "getEnded":
11874				return this.object.getPlayerState() === 0;
11875			case "getDuration":
11876				return this.object.getDuration();
11877			case "getCurrentTime":
11878				return this.object.getCurrentTime();
11879			case "setCurrentTime":
11880				return this.object.seekTo( args, true );
11881			case "fullscreen":
11882				return this.object.getIframe().requestFullscreen();
11883			case "getMuted":
11884				if ( !this.object.playedOnce && this.object.wasMutedPlay ) {
11885					state = this.object.wasMutedPlay;
11886					this.object.playedOnce = true;
11887					return state;
11888				} else {
11889					return this.object.isMuted();
11890				}
11891			case "setMuted":
11892				if ( args ) {
11893					this.object.mute();
11894				} else {
11895					this.object.unMute();
11896				}
11897				setTimeout( function() {
11898					$media.trigger( "volumechange" );
11899				}, ( wb.isReady ? 50 : 500 ) );
11900				break;
11901			case "getVolume":
11902				return this.object.getVolume() / 100;
11903			case "setVolume":
11904				this.object.setVolume( args * 100 );
11905				setTimeout( function() {
11906					$media.trigger( "volumechange" );
11907				}, 50 );
11908				break;
11909			case "getCaptionsVisible":
11910				return $( this ).hasClass( captionClass );
11911			case "setCaptionsVisible":
11912				if ( args ) {
11913					$( this ).addClass( captionClass );
11914					try {
11915						this.object.loadModule( "cc" );
11916						this.object.setOption( "cc", "track", { languageCode: this.object.getOption( "cc", "tracklist" )[ 0 ].languageCode } );
11917					} catch ( e ) {
11918						this.object.loadModule( "captions" );
11919						this.object.setOption( "captions", "track", { languageCode: this.object.getOption( "captions", "tracklist" )[ 0 ].languageCode } );
11920					}
11921				} else {
11922					$( this ).removeClass( captionClass );
11923					this.object.unloadModule( "cc" );
11924					this.object.unloadModule( "captions" );
11925				}
11926				$media.trigger( "ccvischange" );
11927		}
11928	},
11929
11930	/**
11931	 * @method youTubeEvents
11932	 * @description YouTube API event manager
11933	 * @param {object} event The event object for the triggered event
11934	 */
11935	youTubeEvents = function( event ) {
11936		var media = event.target.getIframe(),
11937			$media = $( media ),
11938			timeline = function() {
11939				$media.trigger( "timeupdate" );
11940			},
11941			$mltmPlayerElm,
11942			mltmPlayerElm,
11943			isMuted;
11944
11945		switch ( event.data ) {
11946			case null: // init
11947				$media
11948					.trigger( "canplay" )
11949					.trigger( "durationchange" );
11950
11951				// Put video on mute if the video is muted on init, run once
11952				$mltmPlayerElm = $media.parentsUntil( selector ).parent();
11953
11954				// Mute the player, GUI
11955				if ( $mltmPlayerElm.data( "putMutedOnInit" ) ) {
11956					youTubeApi.call( $mltmPlayerElm.get( 0 ), "setMuted", true );
11957					$mltmPlayerElm.data( "putMutedOnInit", false );
11958				}
11959				break;
11960			case -1:
11961				event.target.unMute();
11962				$media.trigger( "durationchange" );
11963				break;
11964			case 0:
11965				$media.trigger( "ended" );
11966				media.timeline = clearInterval( media.timeline );
11967				break;
11968			case 1: // play
11969
11970				// Get the media player
11971				$mltmPlayerElm = $media.parentsUntil( selector ).parent();
11972				mltmPlayerElm = $mltmPlayerElm.get( 0 );
11973
11974				// Need to be muted here
11975				isMuted = mltmPlayerElm.player( "getMuted" );
11976
11977				// Reset the close caption state when iframe was reloaded
11978				if ( media.dataset.L2 ) {
11979					youTubeApi.call( mltmPlayerElm, "setCaptionsVisible", $mltmPlayerElm.hasClass( captionClass ) );
11980				}
11981
11982				// Play
11983				$media
11984					.trigger( "canplay" )
11985					.trigger( "play" )
11986					.trigger( "playing" );
11987
11988				// Reset muted as needed because youtube onMute by default when playing
11989				if ( isMuted ) {
11990					youTubeApi.call( mltmPlayerElm, "setMuted", true );
11991				}
11992
11993				media.timeline = setInterval( timeline, 250 );
11994				break;
11995			case 2:
11996				$media.trigger( "pause" );
11997				media.timeline = clearInterval( media.timeline );
11998				break;
11999			case 3:
12000				media.timeline = clearInterval( media.timeline );
12001				break;
12002		}
12003	},
12004
12005	youTubeAPIReady = function() {
12006		var youTube = window.youTube;
12007		youTube.ready = true;
12008		$document.trigger( youtubeReadyEvent );
12009	},
12010
12011	onResize = function() {
12012		$( selector + " object, " + selector + " iframe, " +  selector + " video" ).trigger( resizeEvent );
12013	};
12014
12015$document.on( "timerpoke.wb " + initEvent, selector, init );
12016
12017$window.on( "resize", onResize );
12018
12019$document.on( "ajax-fetched.wb " + templateLoadedEvent, selector, function( event ) {
12020	var $this = $( this );
12021
12022	if ( event.type === "ajax-fetched" ) {
12023		template = event.fetch.pointer.html();
12024
12025		//Notify all player waiting for the controls to load
12026		$this = $( selector );
12027	}
12028
12029	$this.trigger( {
12030		type: initializedEvent
12031	} );
12032} );
12033
12034$document.on( initializedEvent, selector, function( event ) {
12035	if ( event.namespace === componentName ) {
12036		var $this = $( this ),
12037			$media = $this.children( "audio, video" ).eq( 0 ),
12038			captions = $media.children( "track[kind='captions']" ).attr( "src" ) || undef,
12039			id = $this.attr( "id" ),
12040			mId = $media.attr( "id" ) || id + "-md",
12041			type = $media.is( "audio" ) ? "audio" : "video",
12042			title = $media.attr( "title" ) || "",
12043			width = type === "video" ? $media.attr( "width" ) || $media.width() : 0,
12044			height = type === "video" ? $media.attr( "height" ) || $media.height() : 0,
12045			settings = wb.getData( $this, componentName ),
12046			data = $.extend( {
12047				media: $media,
12048				captions: captions,
12049				id: id,
12050				mId: mId,
12051				type: type,
12052				title: title,
12053				height: height,
12054				width: width
12055			}, i18nText ),
12056			media = $media.get( 0 ),
12057			youTube = window.youTube,
12058			url,
12059			i18n = wb.i18n;
12060
12061		if ( $media.attr( "id" ) === undef ) {
12062			$media.attr( "id", mId );
12063		}
12064
12065		if ( settings !== undef ) {
12066			data.shareUrl = settings.shareUrl;
12067			data.fullscreen = settings.fullscreenBtn || false;
12068		}
12069
12070		$this.addClass( type );
12071
12072		if ( $media.find( "[type='video/youtube']" ).length > 0 ) {
12073
12074			// lets tweak some variables and start the load sequence
12075			url = wb.getUrlParts( $this.find( "[type='video/youtube']" ).attr( "src" ) );
12076
12077			// lets set the flag for the call back
12078			data.youTubeId = url.params.v ? url.params.v : url.pathname.substr( 1 );
12079
12080			// Defaults config set on the video element
12081			data.isInitMuted = $media.get( 0 ).muted;
12082
12083			if ( youTube.ready === false ) {
12084				$document.one( youtubeReadyEvent, function() {
12085					$this.trigger( youtubeEvent, data );
12086				} );
12087			} else {
12088				$this.trigger( youtubeEvent, data );
12089			}
12090
12091			// finally lets load safely
12092			return Modernizr.load( {
12093				load: "https://www.youtube.com/iframe_api",
12094
12095				//possible solution for multimedia and doaction conflict in corporate network
12096				complete: function() {
12097
12098					// Ensure that YouTube API is loading the iframe and if it fails, ensure that it will show a message, like accessing the web via our GC network.
12099					setTimeout( function() {
12100						var resources, arrIframesYt, $notifText;
12101
12102						resources = window.performance.getEntriesByType( "resource" );
12103
12104						/* get all the iframe initiators that have the same YT url id */
12105						arrIframesYt  = resources.filter( function( obj ) {
12106
12107							return obj.initiatorType === "iframe" && obj.name.includes( data.youTubeId );
12108
12109						} );
12110
12111
12112						/* if none found, most probably wb is loaded in restricted network so wb.ready() is triggered for not preventing other wb components to load*/
12113						if ( arrIframesYt.length < 1 ) {
12114							if ( !wb.isReady ) {
12115
12116								// show the video notification error
12117								$notifText = $( "<div aria-live='polite' class='pstn-lft-xs bg-dark text-white'><p class='mrgn-tp-md mrgn-rght-md mrgn-bttm-md mrgn-lft-md'>" + i18n( "msgYoutubeNotLoad" ) + "</p></div>" );
12118								$this.prepend( $notifText );
12119								data.notifyText = $notifText;
12120								wb.ready( $this, componentName );
12121							}
12122						}
12123					}, 1000 );
12124
12125				}
12126			} );
12127
12128		} else if ( media.error === null && media.currentSrc !== "" && media.currentSrc !== undef ) {
12129			$this.trigger( renderUIEvent, [ type, data ] );
12130			onResize();
12131
12132			// Identify that initialization has completed
12133			wb.ready( $this, componentName );
12134		}
12135	}
12136} );
12137
12138/*
12139 *  YouTube Video mode Event
12140 */
12141$document.on( youtubeEvent, selector, function( event, data ) {
12142	if ( event.namespace === componentName ) {
12143		var mId = data.mId,
12144			$this = $( event.currentTarget ),
12145			$media, ytPlayer;
12146
12147		ytPlayer = new YT.Player( mId, {
12148			videoId: data.youTubeId,
12149			width: data.width,
12150			height: data.height,
12151			playerVars: {
12152				autoplay: 0,
12153				controls: 0,
12154				hl: wb.lang,
12155				origin: wb.pageUrlParts.host,
12156				rel: 0,
12157				cc_load_policy: 1
12158			},
12159			events: {
12160				onReady: function( event ) {
12161					if ( data.notifyText ) {
12162						data.notifyText.hide();
12163					}
12164					onResize();
12165					youTubeEvents( event );
12166					if ( !wb.isReady ) {
12167						wb.ready( $this, c
12167omponentName );
12168					}
12169				},
12170				onStateChange: youTubeEvents,
12171				onApiChange: function() {
12172
12173					//If captions were enabled before the module was ready, re-enable them
12174					var t = $this.get( 0 );
12175					t.player( "setCaptionsVisible", t.player( "getCaptionsVisible" ) );
12176				},
12177				onError: function() {
12178					console.warn( "There is an issue loading the YouTube player" );
12179				}
12180			}
12181		} );
12182
12183		$this.addClass( "youtube" );
12184
12185		$media = $this.find( "#" + mId );
12186
12187		data.media = $media;
12188		data.ytPlayer = ytPlayer;
12189
12190		// The fullscreen button is not visible by default because there are no controls when in full screen.
12191		if ( data.fullscreen ) {
12192			$this.attr( "data-fullscreen-btn", true );
12193		}
12194
12195		// Detect if the YT player reloads, like when magnific Popup show the modal, because it moves the iframe
12196		// and then the iframe gets refreshed and reloaded. So the issue is that the iframe stops emitting the event
12197		// needed to adjust the multimedia player controller, like the "onStateChange" event.
12198		$media.on( "load", function( evt ) {
12199
12200			var elm = evt.currentTarget,
12201				ds = elm.dataset;
12202
12203			// Do nothing on the first load and add a flag to indicate it is loaded a second time
12204			if ( ds.L1 ) {
12205				ds.L2 = true;
12206			} else {
12207				ds.L1 = true;
12208			}
12209		} );
12210
12211		$this.trigger( renderUIEvent, [ "youtube", data ] );
12212	}
12213} );
12214
12215$document.on( renderUIEvent, selector, function( event, type, data ) {
12216	if ( event.namespace === componentName ) {
12217		var $this = $( event.currentTarget ),
12218			captionsUrl = wb.getUrlParts( data.captions ),
12219			currentUrl = wb.getUrlParts( window.location.href ),
12220			$media = data.media,
12221			$eventReceiver;
12222
12223		$media
12224			.after( tmpl( template, data ) )
12225			.wrap( "<div class=\"display\"></div>" );
12226
12227		$eventReceiver = $media.is( "object" ) ? $media.children( ":first-child" ) : $media;
12228
12229		// Create an adapter for the event management
12230		$eventReceiver.on( multimediaEvents, function( event ) {
12231			$this.trigger( event );
12232		} );
12233
12234		this.object = data.ytPlayer || $media.get( 0 );
12235		this.player = ( data.ytPlayer ) ? youTubeApi : playerApi;
12236
12237		// Trigger the duration change for cases where the event was called before the event binding
12238		if ( type !== "youtube" && !isNaN( this.player( "getDuration" ) ) ) {
12239			$eventReceiver.trigger( "durationchange" );
12240		}
12241
12242		// Load the progress polyfill if needed
12243		$this.find( "progress" ).trigger( "wb-init.wb-progress" );
12244
12245		// Load the slider polyfill if needed
12246		$this.find( "input[type='range']" ).trigger( "wb-init.wb-slider" );
12247
12248		// Create the share widgets if needed
12249		if ( data.shareUrl !== undef ) {
12250			$( "<div class='wb-share' data-wb-share='{\"type\": \"" +
12251				( type === "audio" ? type : "video" ) + "\", \"title\": \"" +
12252				data.title.replace( /'/g, "&apos;" ) + "\", \"url\": \"" + data.shareUrl +
12253				"\", \"pnlId\": \"" + data.id + "-shr\"}'></div>" )
12254				.insertBefore( $media.parent() )
12255				.trigger( "wb-init.wb-share" );
12256		}
12257
12258		if ( data.isInitMuted ) {
12259			$this.data( "putMutedOnInit", true );
12260		} else if ( !data.ytPlayer && this.object.muted ) {
12261			$media.trigger( "volumechange" );
12262		}
12263
12264		if ( data.captions === undef ) {
12265			return 1;
12266		}
12267
12268		// Load the captions
12269		if ( currentUrl.absolute.replace( currentUrl.hash || "#", "" ) !== captionsUrl.absolute.replace( captionsUrl.hash || "#", "" ) ) {
12270			loadCaptionsExternal( $media, captionsUrl.absolute );
12271		} else {
12272			loadCaptionsInternal( $media, $( "#" + wb.jqEscape( captionsUrl.hash.substring( 1 ) ) ) );
12273		}
12274
12275		// The fullscreen button is not visible by default because there are no controls when in full screen.
12276		if ( data.fullscreen ) {
12277			$this.attr( "data-fullscreen-btn", true );
12278		}
12279	}
12280} );
12281
12282/*
12283 * UI Bindings
12284 */
12285
12286$document.on( "click", selector, function( event ) {
12287	var $target = $( event.target ),
12288		className = $target.attr( "class" ) || "";
12289
12290	// Ignore middle and right mouse buttons
12291	if ( event.which === 2 || event.which === 3 ) {
12292		return true;
12293	}
12294
12295	// Optimized multiple class tests to include child glyphicon because Safari was reporting the click event
12296	// from the child span not the parent button, forcing us to have to check for both elements
12297	// JSPerf for multiple class matching https://jsperf.com/hasclass-vs-is-stackoverflow/7
12298	if (  /playpause|-play|-pause|display/.test( className ) || $target.is( "object" ) || $target.is( "video" ) ) {
12299		this.player( "getPaused" ) || this.player( "getEnded" ) ? this.player( "play" ) : this.player( "pause" );
12300	} else if ( /(^|\s)cc\b|-subtitles/.test( className ) && !$target.attr( "disabled" ) && !$target.parent().attr( "disabled" ) ) {
12301		this.player( "setCaptionsVisible", !this.player( "getCaptionsVisible" ) );
12302	} else if ( /\bmute\b|-volume-(up|off)/.test( className ) ) {
12303		this.player( "setMuted", !this.player( "getMuted" ) );
12304	} else if ( $target.is( "progress" ) || $target.hasClass( "progress" ) || $target.hasClass( "progress-bar" ) ) {
12305		this.player( "setCurrentTime", this.player( "getDuration" ) * ( ( event.pageX - $target.offset().left ) / $target.width() ) );
12306	} else if ( /\brewind\b|-backward/.test( className ) ) {
12307		this.player( "setCurrentTime", this.player( "getCurrentTime" ) - this.player( "getDuration" ) * 0.05 );
12308	} else if ( /\bfastforward\b|-forward/.test( className ) ) {
12309		this.player( "setCurrentTime", this.player( "getCurrentTime" ) + this.player( "getDuration" ) * 0.05 );
12310	} else if ( className.includes( "cuepoint" ) ) {
12311		$( this ).trigger( { type: "cuepoint", cuepoint: $target.data( "cuepoint" ) } );
12312	} else if ( /fullscreen|fs/.test( className ) ) {
12313		this.player( "fullscreen" );
12314	}
12315} );
12316
12317$document.on( "input change", selector, function( event ) {
12318	var target = event.target;
12319
12320	if ( $( target ).hasClass( "volume" ) ) {
12321		event.currentTarget.player( "setMuted", false );
12322		event.currentTarget.player( "setVolume", target.value / 100 );
12323	}
12324} );
12325
12326$document.on( "keydown", dispCtrls, function( event ) {
12327	var playerTarget = event.currentTarget.parentNode,
12328		which = event.which,
12329		volume = 0,
12330		step = 0.05,
12331		$playerTarget = $( playerTarget );
12332
12333	if ( !( event.ctrlKey || event.altKey || event.metaKey ) ) {
12334		switch ( which ) {
12335			case 32:
12336
12337				// Mute/unmute if focused on the mute/unmute button or volume input.
12338				if ( $( event.target ).hasClass( "mute" ) || event.target.nodeName === "INPUT" ) {
12339					$playerTarget.find( ".mute" ).trigger( "click" );
12340				} else if ( $( event.target ).hasClass( "fs" ) ) {
12341
12342					// Enter full screen if focused on the full screen button
12343					$playerTarget.find( ".fs" ).trigger( "click" );
12344				} else if ( $( event.target ).hasClass( "cc" ) ) {
12345
12346					// Show/hide captions if focused on the closed captions button.
12347					$playerTarget.find( ".cc" ).trigger( "click" );
12348				} else {
12349
12350					// Play/pause if focused on anything else (i.e. the video itself, play/pause button or progress bar).
12351					$playerTarget.find( ".playpause" ).trigger( "click" );
12352				}
12353				break;
12354
12355			case 37:
12356				playerTarget.player( "setCurrentTime", this.parentNode.player( "getCurrentTime" ) - this.parentNode.player( "getDuration" ) * 0.05 );
12357				break;
12358
12359			case 39:
12360				playerTarget.player( "setCurrentTime", this.parentNode.player( "getCurrentTime" ) + this.parentNode.player( "getDuration" ) * 0.05 );
12361				break;
12362
12363			case 38:
12364				volume = Math.round( playerTarget.player( "getVolume" ) * 100 ) / 100 + step;
12365				playerTarget.player( "setVolume", volume < 1 ? volume : 1 );
12366				break;
12367
12368			case 40:
12369				volume = Math.round( playerTarget.player( "getVolume" ) * 100 ) / 100 - step;
12370				playerTarget.player( "setVolume", volume > 0 ? volume : 0 );
12371				break;
12372
12373			default:
12374				return true;
12375		}
12376		return false;
12377	}
12378} );
12379
12380$document.on( "keyup", ctrls, function( event ) {
12381	if ( event.which === 32 && !( event.ctrlKey || event.altKey || event.metaKey ) ) {
12382
12383		// Allows the spacebar to be used for play/pause without double triggering
12384		return false;
12385	}
12386} );
12387
12388$document.on( "wb-activate", selector, function() {
12389	this.player( "play" );
12390} );
12391
12392$document.on( "closed.wb-overlay", ".wb-overlay", function( event ) {
12393	var mltmdPlayer = event.currentTarget.querySelector( selector );
12394	if ( mltmdPlayer ) {
12395		mltmdPlayer.player( "pause" );
12396	}
12397} );
12398
12399$document.on( multimediaEvents, selector, function( event, simulated ) {
12400	var eventTarget = event.currentTarget,
12401		eventType = event.type,
12402		eventNamespace = event.namespace,
12403		$this = $( eventTarget ),
12404		invStart = "<span class='wb-inv'>",
12405		invEnd = "</span>",
12406		currentTime, $button, $slider, buttonData, isPlay, isMuted, isCCVisible, skipTo, volume;
12407	switch ( eventType ) {
12408		case "playing":
12409		case "pause":
12410		case "ended":
12411			isPlay = eventType === "playing";
12412			$button = $this.find( ".playpause" );
12413			buttonData = $button.data( "state-" + ( isPlay ? "off" : "on" ) );
12414			if ( isPlay ) {
12415				$this.addClass( "playing" );
12416				$this.find( ".progress" ).addClass( "active" );
12417			} else {
12418				if ( eventType === "ended" ) {
12419					this.loading = clearTimeout( this.loading );
12420				}
12421				$this.removeClass( "playing" );
12422			}
12423			$button
12424				.attr( "title", buttonData )
12425				.children( "span" )
12426				.toggleClass( "glyphicon-play", !isPlay )
12427				.toggleClass( "glyphicon-pause", isPlay )
12428				.html( invStart + buttonData + invEnd );
12429			break;
12430
12431		case "volumechange":
12432			isMuted = eventTarget.player( "getMuted" );
12433			$button = $this.find( ".mute" );
12434			buttonData = $button.data( "state-" + ( isMuted ? "off" : "on" ) );
12435			volume = eventTarget.player( "getVolume" ) * 100;
12436			$button
12437				.attr( {
12438					title: buttonData,
12439					"aria-pressed": isMuted
12440				} )
12441				.children( "span" )
12442				.toggleClass( "glyphicon-volume-up", !isMuted )
12443				.toggleClass( "glyphicon-volume-off", isMuted )
12444				.html( invStart + buttonData + invEnd );
12445			$slider = $this.find( "input[type='range']" );
12446			$slider[ 0 ].value = isMuted ? 0 : volume;
12447			$slider.trigger( "wb-update.wb-slider" );
12448			break;
12449
12450		case "timeupdate":
12451			currentTime = eventTarget.player( "getCurrentTime" );
12452			$this.find( "progress" )
12453				.attr(
12454					"value",
12455					Math.round( currentTime / eventTarget.player( "getDuration" ) * 1000 ) / 10
12456				).trigger( "wb-update.wb-progress" );
12457
12458			$this.find( ".wb-mm-tmln-crrnt span:nth-child(2)" )
12459				.text( formatTime( currentTime ) );
12460
12461			if ( $this.hasClass( captionClass ) && $.data( eventTarget, "captions" ) !== undef ) {
12462				updateCaptions(
12463					$this.find( ".wb-mm-cc" ),
12464					currentTime,
12465					$.data( eventTarget, "captions" )
12466				);
12467			}
12468			break;
12469
12470		case "durationchange":
12471			$this.find( ".wb-mm-tmln-ttl span:nth-child(2)" )
12472				.text( formatTime( eventTarget.player( "getDuration" ) ) );
12473
12474			// Skip to pointer from the querystring
12475			skipTo = wb.pageUrlParts.params[ event.target.id ];
12476			if ( skipTo ) {
12477				skipTo = parseTime( skipTo );
12478				eventTarget.player( "setCurrentTime", skipTo );
12479			}
12480			break;
12481
12482		case "ccloaded":
12483			if ( eventNamespace === componentName ) {
12484				$.data( eventTarget, "captions", event.captions );
12485			}
12486			break;
12487
12488		case "ccloadfail":
12489			if ( eventNamespace === componentName ) {
12490				if ( !$this.hasClass( "errmsg" ) ) {
12491					$this.addClass( "cc_on errmsg" )
12492						.find( ".wb-mm-cc" )
12493						.append( "<div>" + i18nText.cc_error + "</div>" )
12494						.end()
12495						.find( ".cc" )
12496						.attr( "disabled", "" )
12497						.removeAttr( "aria-pressed" );
12498				}
12499			}
12500			break;
12501
12502		case "ccvischange":
12503			if ( eventNamespace === componentName ) {
12504				isCCVisible = eventTarget.player( "getCaptionsVisible" );
12505				$button = $this.find( ".cc" );
12506				buttonData = $button.data( "state-" + ( isCCVisible ? "off" : "on" ) );
12507				$button.attr( {
12508					title: buttonData,
12509					"aria-pressed": isCCVisible
12510				} ).children( "span" ).html( invStart + buttonData + invEnd );
12511			}
12512			break;
12513
12514		case "waiting":
12515			if ( !simulated ) {
12516				$document.off( "progress", selector );
12517			}
12518			this.loading = setTimeout( function() {
12519				$this.addClass( "waiting" );
12520			}, 500 );
12521			break;
12522
12523		case "canplay":
12524		case "seeked":
12525			this.loading = clearTimeout( this.loading );
12526			$this.removeClass( "waiting" );
12527			break;
12528		case "cuepoint":
12529			eventTarget.player( "setCurrentTime", parseTime( event.cuepoint ) );
12530			break;
12531	}
12532} );
12533
12534// Fallback for browsers that don't implement the waiting events
12535$document.on( "progress", selector, function( event ) {
12536	var eventTarget = event.currentTarget,
12537		$this = $( eventTarget );
12538
12539	// Waiting detected
12540	if ( this.player( "getPaused" ) === false && this.player( "getCurrentTime" ) === this.player( "getPreviousTime" ) ) {
12541		if ( eventTarget.player( "getBuffering" ) === false ) {
12542			eventTarget.player( "setBuffering", true );
12543			$this.trigger( "waiting", true );
12544		}
12545
12546	// Waiting has ended
12547	} else if ( eventTarget.player( "getBuffering" ) === true ) {
12548		eventTarget.player( "setBuffering", false );
12549		$this.trigger( "canplay", true );
12550	}
12551	eventTarget.player( "setPreviousTime", eventTarget.player( "getCurrentTime" ) );
12552} );
12553
12554$document.on( resizeEvent, selector, function( event ) {
12555	if ( event.namespace === componentName ) {
12556		var media = event.target,
12557			$media = $( media ),
12558			figure = event.currentTarget,
12559			ratio, newHeight,
12560			heightDiff;
12561
12562		ratio = $media.attr( "height" ) / $media.attr( "width" );
12563
12564		// Calculate the new height based on the specified ratio or assume a default 16:9 ratio
12565		newHeight = Math.round( $media.width() * ( !isNaN( ratio ) ? ratio : 0.5625 ) );
12566
12567		if ( newHeight > window.innerHeight ) {
12568			heightDiff = figure.offsetHeight - window.innerHeight;
12569			newHeight = $media.height() - heightDiff;
12570		}
12571
12572		$media.css( "height", newHeight + "px" );
12573	}
12574} );
12575
12576window.onYouTubeIframeAPIReady = youTubeAPIReady;
12577
12578window.youTube = {
12579	ready: false
12580};
12581
12582wb.add( selector );
12583
12584} )( jQuery, DOMPurify, window, wb );
12585
12586/**
12587 * @title WET-BOEW NavCurrent
12588 * @overview Identify URL in a navigation system that matches current page URL or a URL in the breadcrumb trail. Call by applying .trigger( "navcurr.wb", breadcrumb ) where the breadcrumb parameter is an optional object (DOM or jQuery)
12589 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
12590 * @author @pjackson28
12591 */
12592( function( $, window, wb ) {
12593"use strict";
12594
12595/*
12596 * Variable and function definitions.
12597 * These are global to the plugin - meaning that they will be initialized once per page,
12598 * not once per instance of plugin on the page. So, this is a good place to define
12599 * variables that are common to all instances of the plugin on a page.
12600 */
12601var componentName = "wb-navcurr",
12602	selector = "." + componentName,
12603	$document = wb.doc,
12604	breadcrumbLinksArray, breadcrumbLinksUrlArray,
12605
12606	/**
12607	 * @method init
12608	 * @param {jQuery Event} event Event that triggered the function call
12609	 * @param {jQuery DOM element | DOM element} breadcrumb Optional breadcrumb element
12610	 * @param {string} classNameOverride Optional class name override (default is wb-navcurr)
12611	 */
12612	init = function( event, breadcrumb, classNameOverride ) {
12613		if ( event.namespace === "wb" ) {
12614
12615			// Start initialization
12616			// returns DOM object = proceed with init
12617			// returns undefined = do not proceed with init (e.g., already initialized)
12618			var menu = wb.init( event.target, componentName, selector ),
12619				menuLinks = menu.getElementsByTagName( "a" ),
12620				menuLinksArray = [],
12621				menuLinksUrlArray = [],
12622				windowLocation = window.location,
12623				pageUrl = windowLocation.hostname + windowLocation.pathname.replace( /^([^/])/, "/$1" ),
12624				pageUrlQuery = windowLocation.search,
12625				match = false,
12626				className = classNameOverride ? classNameOverride : componentName,
12627				child, len, i, j, link, linkHref, linkUrl, linkQuery, linkQueryLen,
12628				localBreadcrumbLinks, localBreadcrumbLinksArray, localBreadcrumbLinksUrlArray,
12629				localBreadcrumbQuery, localBreadcrumbLinkUrl;
12630
12631			if ( menu ) {
12632
12633				// Try to find a match with the page Url and cache link + Url for later if no match found
12634				// Perform the check and caching in reverse to go from more specific links to more general links
12635				for ( i = menuLinks.length - 1; i !== -1; i -= 1 ) {
12636					link = menuLinks[ i ];
12637					linkHref = link.getAttribute( "href" );
12638					if ( linkHref !== null ) {
12639						if ( linkHref.length !== 0 && linkHref.charAt( 0 ) !== "#" ) {
12640							linkUrl = link.hostname + link.pathname.replace( /^([^/])/, "/$1" );
12641							linkQuery = link.search;
12642							linkQueryLen = linkQuery.length;
12643							if ( pageUrl.slice( -linkUrl.length ) === linkUrl && ( linkQueryLen === 0 || pageUrlQuery.slice( -linkQueryLen ) === linkQuery ) ) {
12644								match = true;
12645								break;
12646							}
12647							menuLinksArray.push( link );
12648							menuLinksUrlArray.push( linkUrl );
12649						}
12650					}
12651				}
12652
12653				// No page Url match found, try a breadcrumb link match instead
12654				if ( !match && breadcrumb ) {
12655
12656					// Check to see if the data has been cached already
12657					if ( !breadcrumbLinksArray ) {
12658
12659						// Pre-process the breadcrumb links
12660						localBreadcrumbLinksArray = [];
12661						localBreadcrumbLinksUrlArray = [];
12662						localBreadcrumbLinks = ( breadcrumb.jquery ? breadcrumb[ 0 ] : breadcrumb ).getElementsByTagName( "li" );
12663						len = localBreadcrumbLinks.length;
12664						if ( len ) {
12665							link = localBreadcrumbLinks[ len - 1 ];
12666							child = link.firstChild;
12667							linkHref = ( child && child.nodeName === "A" ) ? child.getAttribute( "href" ) : "";
12668							if ( linkHref && linkHref.charAt( 0 ) !== "#" ) {
12669								localBreadcrumbLinksArray.push( child );
12670								localBreadcrumbLinksUrlArray.push( child.hostname + child.pathname.replace( /^([^/])/, "/$1" ) );
12671							}
12672						}
12673
12674						// Cache the data in case of more than one execution (e.g., site menu + secondary navigation)
12675						breadcrumbLinksArray = localBreadcrumbLinksArray;
12676						breadcrumbLinksUrlArray = localBreadcrumbLinksUrlArray;
12677					} else {
12678
12679						// Retrieve the cached data
12680						localBreadcrumbLinksArray = breadcrumbLinksArray;
12681						localBreadcrumbLinksUrlArray = breadcrumbLinksUrlArray;
12682					}
12683
12684					// Try to match each breadcrumb link
12685					len = menuLinksArray.length;
12686					for ( j = localBreadcrumbLinksArray.length - 1; j !== -1; j -= 1 ) {
12687						localBreadcrumbLinkUrl = localBreadcrumbLinksUrlArray[ j ];
12688						localBreadcrumbQuery = localBreadcrumbLinksArray[ j ].search;
12689
12690						for ( i = 0; i !== len; i += 1 ) {
12691							link = menuLinksArray[ i ];
12692							linkUrl = menuLinksUrlArray[ i ];
12693							linkQuery = link.search;
12694							linkQueryLen = linkQuery.length;
12695
12696							if ( localBreadcrumbLinkUrl.slice( -linkUrl.length ) === linkUrl && ( linkQueryLen === 0 || localBreadcrumbQuery.slice( -linkQueryLen ) === linkQuery ) ) {
12697								match = true;
12698								break;
12699							}
12700						}
12701						if ( match ) {
12702							break;
12703						}
12704					}
12705				}
12706
12707				if ( match ) {
12708					link.className += " " + className;
12709					if ( menu.className.indexOf( "wb-menu" ) !== -1 && link.className.indexOf( "item" ) === -1 ) {
12710						$( link ).closest( ".sm" ).parent().children( "a" ).addClass( className );
12711					}
12712				}
12713
12714				// Identify that initialization has completed
12715				wb.ready( $( menu ), componentName );
12716			}
12717		}
12718	};
12719
12720// Bind the navcurrent event of the plugin
12721$document.on( "navcurr.wb", init );
12722
12723} )( jQuery, window, wb );
12724
12725/**
12726 * @title WET-BOEW Overlay
12727 * @overview Provides multiple styles of overlays such as panels and pop-ups
12728 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
12729 * @author @thomasgohard, @pjackson28
12730 */
12731( function( $, window, document, wb ) {
12732"use strict";
12733
12734/*
12735 * Variable and function definitions.
12736 * These are global to the plugin - meaning that they will be initialized once per page,
12737 * not once per instance of plugin on the page. So, this is a good place to define
12738 * variables that are common to all instances of the plugin on a page.
12739 */
12740var componentName = "wb-overlay",
12741	selector = "." + componentName,
12742	initEvent = "wb-init" + selector,
12743	closeClass = "overlay-close",
12744	linkClass = "overlay-lnk",
12745	ignoreOutsideClass = "outside-off",
12746	OverlayOpenFlag = "wb-overlay-dlg",
12747	initialized = false,
12748	sourceLinks = {},
12749	setFocusEvent = "setfocus.wb",
12750	$document = wb.doc,
12751	i18n, i18nText,
12752
12753	/**
12754	 * @method init
12755	 * @param {jQuery Event} event Event that triggered the function call
12756	 */
12757	init = function( event ) {
12758
12759		// Start initialization
12760		// returns DOM object = proceed with init
12761		// returns undefined = do not proceed with init (e.g., already initialized)
12762		var elm = wb.init( event, componentName, selector ),
12763			$elm, footer, $header, closeText, overlayClose;
12764
12765		if ( elm ) {
12766			$elm = $( elm );
12767
12768			// Only initialize the i18nText once
12769			if ( !i18nText ) {
12770				i18n = wb.i18n;
12771				i18nText = {
12772					close: i18n( "close" ),
12773					colon: i18n( "colon" ),
12774					space: i18n( "space" ),
12775					esc: i18n( "esc-key" ),
12776					closeOverlay: i18n( closeClass )
12777				};
12778			}
12779
12780			// One left and right panels add close button
12781			var isPanel = ( $elm.attr( "class" ).indexOf( "wb-panel" ) > -1 ) ? true : false,
12782				isPopup = ( $elm.attr( "class" ).indexOf( "wb-popup" ) > -1 ) ? true : false;
12783			if ( isPanel || isPopup ) {
12784				footer = $elm.find( ".modal-footer" )[ 0 ];
12785
12786				var hasFooter = ( footer && footer.length !== 0 ) ? true : false,
12787					hasButton = hasFooter && $( footer ).find( "." + closeClass ).length !== 0,
12788					closeClassFtr = ( $elm.hasClass( "wb-panel-l" ) ? "pull-right " : "pull-left " )  + closeClass,
12789					closeTextFtr = i18nText.close,
12790					spanTextFtr = i18nText.closeOverlay,
12791					overlayCloseFtr;
12792
12793				if ( !hasButton ) {
12794					if ( !hasFooter ) {
12795						footer = document.createElement( "div" );
12796						footer.setAttribute( "class", "modal-footer" );
12797					}
12798
12799					if ( isPopup ) {
12800						footer.style.border = "0";
12801					}
12802
12803					overlayCloseFtr = "<button type='button' class='btn btn-sm btn-primary " + closeClassFtr +
12804						"' title='" + spanTextFtr + "'>" +
12805						closeTextFtr +
12806						"<span class='wb-inv'>" + spanTextFtr + "</span></button>";
12807
12808					$( footer ).append( overlayCloseFtr );
12809					if ( !hasFooter ) {
12810						$elm.append( footer );
12811					}
12812				}
12813			}
12814
12815			// Add close button
12816			$header = $elm.find( ".modal-title" );
12817			if ( $header.length !== 0 ) {
12818				closeText = i18nText.close + i18nText.colon + i18nText.space +
12819					$header.text() + i18nText.space + i18nText.esc;
12820			} else {
12821				closeText = i18nText.closeOverlay;
12822			}
12823			closeText = closeText.replace( "'", "&#39;" );
12824			overlayClose = "<button type='button' class='mfp-close " + closeClass +
12825				"' title='" + closeText + "'>&#xd7;<span class='wb-inv'> " +
12826				closeText + "</span></button>";
12827
12828			$elm.append( overlayClose );
12829			elm.setAttribute( "aria-hidden", "true" );
12830
12831			// Identify that initialization has completed
12832			initialized = true;
12833			wb.ready( $elm, componentName );
12834		}
12835	},
12836
12837	openOverlay = function( overlayId, noFocus ) {
12838		var $overlay = $( "#" + wb.jqEscape( overlayId ) );
12839
12840		$overlay
12841			.addClass( "open" )
12842			.attr( "role", "dialog" )
12843			.attr( "aria-hidden", "false" );
12844
12845		if ( $overlay.hasClass( "wb-popup-full" ) || $overlay.hasClass( "wb-popup-mid" ) ) {
12846			$overlay.attr( "data-pgtitle", document.getElementsByTagName( "H1" )[ 0 ].textContent );
12847			$document.find( "body" ).addClass( OverlayOpenFlag );
12848		}
12849
12850		if ( !noFocus ) {
12851			$overlay
12852				.scrollTop( 0 )
12853				.trigger( setFocusEvent );
12854		}
12855
12856		// Register the overlay if it wasn't previously registered
12857		// (only required when opening through an event)
12858		if ( !sourceLinks[ overlayId ] ) {
12859			setTimeout( function() {
12860				sourceLinks[ overlayId ] = null;
12861			}, 1 );
12862		}
12863
12864		$overlay.trigger( "opened" + selector );
12865	},
12866
12867	closeOverlay = function( overlayId, noFocus, userClosed ) {
12868		var $overlay = $( "#" + overlayId ),
12869			sourceLink = sourceLinks[ overlayId ];
12870
12871		$overlay
12872			.removeClass( "open" )
12873			.removeAttr( "role" )
12874			.attr( "aria-hidden", "true" );
12875
12876		if ( $overlay.hasClass( "wb-popup-full" ) || $overlay.hasClass( "wb-popup-mid" ) ) {
12877			$document.find( "body" ).removeClass( OverlayOpenFlag );
12878		}
12879
12880		if ( userClosed ) {
12881			$overlay.addClass( "user-closed" );
12882		}
12883
12884		if ( !noFocus && sourceLink ) {
12885
12886			// Returns focus to the source link for the overlay
12887			$( sourceLink ).trigger( setFocusEvent );
12888		}
12889
12890		// Delete the source link reference
12891		delete sourceLinks[ overlayId ];
12892
12893		$overlay.trigger( "closed" + selector );
12894	};
12895
12896$document.on( "timerpoke.wb " + initEvent + " keydown open" + selector +
12897" close" + selector, selector, function( event ) {
12898
12899	var eventType = event.type,
12900		which = event.which,
12901		eventTarget = event.target,
12902		eventCurrentTarget = event.currentTarget,
12903		overlayId = eventCurrentTarget.id,
12904		overlay, $focusable, index, length;
12905
12906	switch ( eventType ) {
12907		case "timerpoke":
12908		case "wb-init":
12909			init( event );
12910			break;
12911
12912		case "open":
12913			if ( eventCurrentTarget === eventTarget ) {
12914				openOverlay( overlayId, event.noFocus );
12915			}
12916			break;
12917
12918		case "close":
12919			if ( eventCurrentTarget === eventTarget ) {
12920				closeOverlay( overlayId, event.noFocus );
12921			}
12922			break;
12923
12924		default:
12925			overlay = document.getElementById( overlayId );
12926
12927			switch ( which ) {
12928
12929				// Tab key
12930				case 9:
12931
12932					// No special tab handling when ignoring outside activity
12933					if ( overlay.className.indexOf( ignoreOutsideClass ) === -1 ) {
12934						$focusable = $( overlay ).find( ":tabbable" );
12935						length = $focusable.length;
12936						index = $focusable.index( event.target ) + ( event.shiftKey ? -1 : 1 );
12937
12938						if ( index === -1 || index === length ) {
12939							event.preventDefault();
12940							$focusable.eq( index === -1 ? length - 1 : 0 )
12941								.trigger( setFocusEvent );
12942						}
12943					}
12944					break;
12945
12946				// Escape key
12947				case 27:
12948					if ( !event.isDefaultPrevented() ) {
12949						closeOverlay( overlayId, false, true );
12950					}
12951					break;
12952			}
12953	}
12954} );
12955
12956// Handler for clicking on the close button of the overlay
12957$document.on( "click", "." + closeClass, function( event ) {
12958	var which = event.which;
12959
12960	// Ignore if not initialized and middle/right mouse buttons
12961	if ( initialized && ( !which || which === 1 ) ) {
12962		closeOverlay(
12963			$( event.currentTarget ).closest( selector ).attr( "id" ),
12964			false,
12965			true
12966		);
12967	}
12968} );
12969
12970// Handler for clicking on a source link for the overlay
12971$document.on( "click keydown", "." + linkClass, function( event ) {
12972	var which = event.which,
12973		sourceLink = event.currentTarget,
12974		overlayId = sourceLink.hash.substring( 1 );
12975
12976	// Ignore if not initialized and middle/right mouse buttons
12977	if ( initialized && ( !which || which === 1 || which === 32 ) ) {
12978		event.preventDefault();
12979
12980		// Introduce a delay to prevent outside activity detection
12981		setTimeout( function() {
12982
12983			// Stores the source link for the overlay
12984			sourceLinks[ overlayId ] = sourceLink;
12985
12986			// Opens the overlay
12987			openOverlay( overlayId );
12988		}, 1 );
12989	}
12990} );
12991
12992// Handler for clicking on a same page link within the overlay to outside the overlay
12993$document.on( "click", selector + " a[href^='#']", function( event ) {
12994	var which = event.which,
12995		eventTarget = event.target,
12996		href, overlay, linkTarget;
12997
12998	// Ignore if not initialized and middle/right mouse buttons
12999	if ( initialized && ( !which || which === 1 ) ) {
13000		overlay = $( eventTarget ).closest( selector )[ 0 ];
13001		href = eventTarget.getAttribute( "href" );
13002		linkTarget = document.getElementById( href.substring( 1 ) );
13003
13004		// Ignore same page links to within the overlay
13005		if ( href.length > 1 && !$.contains( overlay, linkTarget ) ) {
13006
13007			// Stop propagation of the click event
13008			if ( event.stopPropagation ) {
13009				event.stopImmediatePropagation();
13010			} else {
13011				event.cancelBubble = true;
13012			}
13013
13014			// Close the overlay and set focus to the same page link
13015			closeOverlay( overlay.id, true );
13016			$( linkTarget ).trigger( setFocusEvent );
13017		}
13018	}
13019} );
13020
13021// Outside activity detection
13022$document.on( "click focusin", "body", function( event ) {
13023	var eventTarget = event.target,
13024		which = event.which,
13025		overlayId, overlay;
13026
13027	// Ignore if not initialized and middle/right mouse buttons
13028	if ( initialized && ( !which || which === 1 ) ) {
13029
13030		// Close any overlays with outside activity
13031		for ( overlayId in sourceLinks ) {
13032			overlay = document.getElementById( overlayId );
13033			if ( overlay && overlay.getAttribute( "aria-hidden" ) === "false" &&
13034				eventTarget.id !== overlayId &&
13035				overlay.className.indexOf( ignoreOutsideClass ) === -1 &&
13036				!$.contains( overlay, eventTarget ) ) {
13037
13038				// Close the overlay
13039				closeOverlay( overlayId );
13040			}
13041		}
13042	}
13043} );
13044
13045// Ensure any element in focus outside an overlay is visible
13046$document.on( "keyup", function( ) {
13047	var elmInFocus, elmInFocusRect, focusAreaBelow, focusAreaAbove,
13048		overlayId, overlay, overlayRect;
13049
13050	// Ignore if not initialized
13051	if ( initialized ) {
13052		elmInFocus = document.activeElement;
13053		elmInFocusRect = elmInFocus.getBoundingClientRect();
13054		focusAreaBelow = 0;
13055		focusAreaAbove = window.innerHeight;
13056
13057		// Ensure that at least one overlay is visible, and that the element in focus is not an overlay,
13058		// a child of an overlay, or the body element
13059		if ( $.isEmptyObject( sourceLinks ) || elmInFocus.className.indexOf( componentName ) !== -1 ||
13060			$( elmInFocus ).parents( selector ).length !== 0 || elmInFocus === document.body ) {
13061			return;
13062		}
13063
13064		// Determine the vertical portion of the viewport that is not obscured by an overlay
13065		for ( overlayId in sourceLinks ) {
13066			overlay = document.getElementById( overlayId );
13067			if ( overlay && overlay.getAttribute( "aria-hidden" ) === "false" ) {
13068				overlayRect = overlay.getBoundingClientRect();
13069				if ( overlay.className.indexOf( "wb-bar-t" ) !== -1 ) {
13070					focusAreaBelow = Math.max( overlayRect.bottom, focusAreaBelow );
13071				} else if ( overlay.className.indexOf( "wb-bar-b" ) !== -1 ) {
13072					focusAreaAbove = Math.min( overlayRect.top, focusAreaAbove );
13073				}
13074			}
13075		}
13076
13077		// Ensure the element in focus is visible
13078		// TODO: Find a solution for when there isn't enough page to scroll up or down
13079		if ( elmInFocusRect.top < focusAreaBelow ) {
13080
13081			// Scroll down till the top of the element is visible
13082			window.scrollBy( 0, focusAreaBelow - elmInFocusRect.top );
13083		} else if ( elmInFocusRect.bottom > focusAreaAbove ) {
13084
13085			// Scroll up till the bottom of the element is visible
13086			window.scrollBy( 0, elmInFocusRect.bottom - focusAreaAbove );
13087		}
13088	}
13089} );
13090
13091// Add the timer poke to initialize the plugin
13092wb.add( selector );
13093
13094} )( jQuery, window, document, wb );
13095
13096/**
13097 * @title WET-BOEW Tag filter
13098 * @overview Filter based content tagging
13099 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13100 * @author @duboisp
13101 */
13102( function( $, window, document, wb ) {
13103"use strict";
13104
13105let i18n, i18nText;
13106
13107const componentName = "wb-paginate",
13108	selector = ".provisional." + componentName,
13109	initEvent = "wb-init" + selector,
13110	$document = wb.doc,
13111	filterClass = "wb-fltr-out",
13112	pgFilterOutClass = "wb-pgfltr-out",
13113	tagFilterClass = "wb-tgfltr-out",
13114	pagerClass = "wb-paginate-pager",
13115	pageData = "data-pagination-idx",
13116	notFilterClassSel = ":not(." + filterClass + "):not(." + tagFilterClass + ")",
13117	defaults = {
13118		lst: {
13119			selector: "li"
13120		},
13121		grp: {
13122			selector: "> *"
13123		},
13124		tbl: {
13125			selector: "tr",
13126			section: ":scope > tbody"
13127		},
13128		itemsPerPage: 10
13129	},
13130
13131	init = function( event ) {
13132		const elm = wb.init( event, componentName, selector );
13133
13134		if ( elm ) {
13135			var $elm = $( elm ),
13136				paginationElm,
13137				elmTagName = elm.nodeName,
13138				setDefault,
13139				uiTargetElm;
13140
13141			if ( !i18nText ) {
13142				i18n = wb.i18n;
13143				i18nText = {
13144					prv: i18n( "prv" ),
13145					nxt: i18n( "nxt" )
13146				};
13147			}
13148
13149			// Setup configurations
13150			switch ( elmTagName ) {
13151				case "UL":
13152					setDefault = defaults.lst;
13153					break;
13154				case "TABLE":
13155					setDefault = defaults.tbl;
13156					break;
13157				default:
13158					setDefault = defaults.grp;
13159					break;
13160			}
13161
13162			elm.id = elm.id || wb.getId();
13163			elm.pgSettings = $.extend( true, {}, setDefault, window[ componentName ], wb.getData( $elm, componentName ) );
13164			elm.pgSettings.currPage = 1;
13165			elm.pgSettings.itemsPerPage = elm.pgSettings.itemsPerPage || defaults.itemsPerPage;
13166			elm.pgSettings.items = elm.querySelectorAll( ( elm.pgSettings.section || ":scope" ) + " " + elm.pgSettings.selector + notFilterClassSel );
13167
13168			// Setup pagination container
13169			paginationElm = document.createElement( "div" );
13170			paginationElm.id = componentName + "-" + elm.id;
13171			paginationElm.classList.add( pagerClass );
13172
13173			// Add pagination container
13174			if ( elm.pgSettings.uiTarget ) {
13175				uiTargetElm = document.querySelector( elm.pgSettings.uiTarget );
13176				uiTargetElm.appendChild( paginationElm );
13177			} else if ( elm.pgSettings.section ) {
13178				if ( elmTagName === "UL" || elmTagName === "TABLE" ) {
13179					elm.after( paginationElm );
13180				} else {
13181					elm.querySelector( elm.pgSettings.section ).after( paginationElm );
13182				}
13183			} else {
13184				elm.after( paginationElm );
13185			}
13186
13187			// Show/hide items and generate pagination
13188			updateItems( elm );
13189			generateUI( elm );
13190
13191			wb.ready( $( elm ), componentName );
13192		}
13193	},
13194
13195	// Set or reset pagination UI
13196	generateUI = function( elm ) {
13197		var paginationUI = "",
13198			currPage = elm.pgSettings.currPage,
13199			pagesCount = elm.pgSettings.pagesCount,
13200			paginationElm = document.querySelector( "#" + componentName + "-" + elm.id ),
13201			i = 1;
13202
13203		// Make sure the defined current page is not bigger than the total pages
13204		if ( currPage > pagesCount ) {
13205			currPage = pagesCount;
13206		}
13207
13208		// Only add pagination if there is more than one page
13209		if ( pagesCount > 1 ) {
13210			paginationUI = "<ol class=\"pagination\">";
13211
13212			// Add Previous page button
13213			var prevLI = "";
13214			prevLI += "<li" + ( i === currPage ? " class=\"disabled\"" : "" ) + ">";
13215			prevLI += "<button type=\"button\" class=\"paginate-prev\" aria-controls=\"" + elm.id + "\"><span class=\"wb-inv\">Page </span>" + i18nText.prv + "</button>";
13216			prevLI += "</li>";
13217
13218			paginationUI += prevLI;
13219
13220			// Add pages buttons
13221			for ( i; i <= pagesCount; i++ ) {
13222				var pageButtonLI = "";
13223				pageButtonLI += "<li class=\"" + returnItemClass( currPage, pagesCount, i ) + "\"" + ">";
13224				pageButtonLI += "<button type=\"button\" " + pageData + "=\"" + i + "\" aria-controls=\"" + elm.id + "\"" + ( i === currPage ? " aria-current=\"true\"" : "" ) + "><span class=\"wb-inv\">Page </span>" + i + "</button>";
13225				pageButtonLI += "</li>";
13226				paginationUI += pageButtonLI;
13227			}
13228
13229			// Add Next page button
13230			var nextLI = "";
13231			nextLI += "<li" + ( i === currPage ? " class=\"disabled\"" : "" ) + ">";
13232			nextLI += "<button type=\"button\" class=\"paginate-next\" aria-controls=\"" + elm.id + "\"><span class=\"wb-inv\">Page </span>" + i18nText.nxt + "</button>";
13233			nextLI += "</li>";
13234			paginationUI += nextLI;
13235			paginationUI += "</ol>";
13236		}
13237
13238		// Insert HTML
13239		paginationElm.innerHTML = paginationUI;
13240	},
13241
13242	// Show/hide items to reflect current page
13243	updateItems = function( elm ) {
13244		let currPage = elm.pgSettings.currPage,
13245			items = elm.pgSettings.items,
13246			itemsPerPage = elm.pgSettings.itemsPerPage;
13247
13248		items.forEach( function( item, index ) {
13249			if ( ( index < ( itemsPerPage * currPage ) ) && ( index >= ( itemsPerPage * currPage ) - itemsPerPage ) ) {
13250				item.classList.remove( pgFilterOutClass );
13251			} else {
13252				item.classList.add( pgFilterOutClass );
13253			}
13254		} );
13255
13256		elm.pgSettings.pagesCount = Math.ceil( items.length / itemsPerPage );
13257	},
13258
13259	// Update pagination to reflect current page
13260	goToPage = function( elm ) {
13261		let paginationElm = document.querySelector( "#" + componentName + "-" + elm.id ),
13262			pageItems = paginationElm.querySelectorAll( "li" ),
13263			itemClass,
13264			pageLink,
13265			currPage = elm.pgSettings.currPage,
13266			pagesCount = elm.pgSettings.pagesCount;
13267
13268		pageItems.forEach( function( pageItem, i ) {
13269			pageLink = pageItem.querySelector( "button" );
13270
13271			if ( pageLink.classList.contains( "paginate-prev" ) ) {
13272				if ( currPage > 1 ) {
13273					pageItem.classList.remove( "disabled" );
13274				} else {
13275					pageItem.classList.add( "disabled" );
13276				}
13277			} else if ( pageLink.classList.contains( "paginate-next" ) ) {
13278				if ( currPage < pagesCount ) {
13279					pageItem.classList.remove( "disabled" );
13280				} else {
13281					pageItem.classList.add( "disabled" );
13282				}
13283			} else {
13284				pageItem.className = "";
13285				pageItem.children[ 0 ].removeAttribute( "aria-current" );
13286
13287				itemClass = returnItemClass( currPage, pagesCount, i );
13288
13289				if ( i === currPage ) {
13290					pageItem.children[ 0 ].setAttribute( "aria-current", "true" );
13291				}
13292
13293				pageItem.className = itemClass;
13294			}
13295		} );
13296	},
13297
13298	// Return the list item classname
13299	returnItemClass = function( currPage, pagesCount, i ) {
13300		let itemClass = "";
13301
13302		if ( currPage > 1 && currPage < pagesCount ) {
13303			if ( Math.abs( currPage - i ) > 1 ) {
13304				itemClass += "hidden-xs hidden-sm";
13305
13306				if ( Math.abs( currPage - i ) > 2 ) {
13307					itemClass += " hidden-md";
13308				}
13309			}
13310		} else {
13311			if ( Math.abs( currPage - i ) > 2 ) {
13312				itemClass += "hidden-xs hidden-sm";
13313
13314				if ( Math.abs( currPage - i ) > 4 ) {
13315					itemClass += " hidden-md";
13316				}
13317			}
13318		}
13319
13320		if ( pagesCount > 10 ) {
13321			if ( currPage <= 5 ) {
13322				if ( i > 10 ) {
13323					itemClass += " hidden";
13324				}
13325			} else if ( ( currPage > 5 ) && ( currPage < pagesCount - 5 ) ) {
13326				if ( ( i < currPage - 4 ) || ( i > currPage + 5 ) ) {
13327					itemClass += " hidden";
13328				}
13329			} else {
13330				if ( i <= pagesCount - 10 ) {
13331					itemClass += " hidden";
13332				}
13333			}
13334		}
13335
13336		if ( i === currPage ) {
13337			itemClass += " active";
13338		}
13339
13340		return itemClass;
13341	};
13342
13343// When a page button is clicked
13344$document.on( "click", "." + pagerClass + " button", function()  {
13345	let elm = document.querySelector( "#" + this.getAttribute( "aria-controls" ) ),
13346		pageDest = ( ( this.getAttribute( pageData ) ) * 1 ) || elm.pgSettings.currPage;
13347
13348	if ( this.classList.contains( "paginate-next" ) ) {
13349		pageDest++;
13350	} else if ( this.classList.contains( "paginate-prev" ) ) {
13351		pageDest--;
13352	}
13353
13354	if ( pageDest !== elm.pgSettings.currPage ) {
13355		elm.pgSettings.currPage = pageDest;
13356
13357		updateItems( elm );
13358		goToPage( elm );
13359
13360		$( elm ).trigger( "setfocus.wb" );
13361		if ( elm.getBoundingClientRect().top < 0 ) {
13362			elm.scrollIntoView( { behavior: "smooth" }, true );
13363		}
13364	}
13365
13366
13367} );
13368
13369// Resets items and pagination
13370$document.on( "wb-contentupdated", selector, function() {
13371	this.pgSettings.currPage = 1;
13372	this.pgSettings.items = this.pgSettings.items = this.querySelectorAll( ( this.pgSettings.section || ":scope" ) + " " + this.pgSettings.selector + notFilterClassSel );
13373
13374	updateItems( this );
13375	generateUI( this );
13376} );
13377
13378$document.on( "timerpoke.wb " + initEvent, selector, init );
13379
13380wb.add( selector );
13381
13382} )( jQuery, window, document, wb );
13383
13384/**
13385 * @title WET-BOEW wb-pii-scrub
13386 * @overview This plugin delete Personal Identifiable Information (PII) from the flagged form fields before form submit
13387 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13388 * @author @polmih, @duboisp, @GormFrank
13389 **/
13390( function( $, wb ) {
13391"use strict";
13392
13393var $document = wb.doc,
13394	componentName = "wb-pii-scrub",
13395	selector = "." + componentName,
13396	initEvent = "wb-init" + selector,
13397	attrPIIBlocked = "data-wb-pii-blocked",
13398	attrScrubField = "data-scrub-field",
13399	attrScrubSubmit = "data-scrub-submit",
13400	piiModalID = componentName + "-modal",
13401	defaults = {
13402		scrubChar: "********"
13403	},
13404	i18n, i18nText,
13405	currSubmitter,
13406	btnAsInput,
13407
13408	init = function( event ) {
13409		var elm = wb.init( event, componentName, selector ),
13410			$elm = $( elm );
13411
13412		if ( elm ) {
13413			var settings = elm.getAttribute( "data-" + componentName );
13414
13415			// Initialize i18n strings
13416			if ( !i18nText ) {
13417				i18n = wb.i18n;
13418				i18nText = {
13419					header: i18n( "pii-header" ),
13420					intro: i18n( "pii-intro" ),
13421					viewMore: i18n( "pii-view-more" ),
13422					viewMoreInfo: i18n( "pii-view-more-info" ),
13423					confirmBtn: i18n( "pii-yes-btn" ),
13424					cancelBtn: i18n( "pii-cancel-btn" ),
13425					redacted: i18n( "redacted" )
13426				};
13427			}
13428
13429			// Parse settings defined on element
13430			if ( settings ) {
13431				settings = JSON.parse( settings );
13432			}
13433
13434			// Initialize settings
13435			elm.settings = { ...defaults, ...settings };
13436
13437			// Ensure the form has an ID
13438			elm.id = elm.id ? elm.id : wb.getId();
13439
13440			// Block form submission for Postback forms by default
13441			elm.setAttribute( attrPIIBlocked, "true" );
13442
13443			elm.addEventListener( "submit", function( e ) {
13444				e.preventDefault(); // This is needed because of the setTimeout
13445
13446				// Go through form values
13447				checkFormValues( elm );
13448
13449				// Check if form has validation errors before showing PII popup or submitting
13450				setTimeout( function() {
13451					let errorElm = elm.querySelector( ".error .label.label-danger" );
13452
13453					if ( !errorElm ) {
13454						currSubmitter = e.submitter;
13455
13456						// Add submitter data if it is present (only if not a Postback form as it has its own method)
13457						if ( currSubmitter.name && !elm.classList.contains( "wb-postback" ) ) {
13458							btnAsInput = document.createElement( "input" );
13459							btnAsInput.type = "hidden";
13460							btnAsInput.name = currSubmitter.name;
13461							btnAsInput.value = currSubmitter.value;
13462							elm.appendChild( btnAsInput );
13463						}
13464
13465						// Open modal
13466						if ( elm.PIIFields.length > 0 ) {
13467							generateModal( elm );
13468
13469							$( "#" + piiModalID ).trigger( "open.wb-lbx", [
13470								[ {
13471									src: "#" + piiModalID,
13472									type: "inline"
13473								} ],
13474								true
13475							] );
13476						} else {
13477							if ( elm.classList.contains( "wb-postback" ) ) {
13478								$( elm ).trigger( "wb-postback.submit", currSubmitter );
13479							} else {
13480								elm.submit();
13481							}
13482						}
13483					}
13484				}, 50 );
13485			} );
13486
13487			wb.ready( $elm, componentName );
13488		}
13489	},
13490
13491	/*
13492	* Log all PII positive fields inside the form's "PIIFields" property
13493	* @param form: a reference to the form containing PII fields
13494	*/
13495	checkFormValues = function( form ) {
13496		let fieldsToScrub = form.querySelectorAll( "[" + attrScrubField + "]" );
13497
13498		form.PIIFields = [];
13499
13500		// identify form elements that were assigned to be scrubbed
13501		fieldsToScrub.forEach( ( field ) => {
13502
13503			// If the field contains PII add field to list
13504			if ( wb.findPotentialPII( field.value, false ) ) {
13505				let fieldLabel = form.querySelector( "[for=" + field.id + "] > span.field-name" ),
13506					fieldLabelText = fieldLabel ? fieldLabel.innerText : form.querySelector( "[for=" + field.id + "]" ).innerText,
13507					scrubbedFieldValue = wb.findPotentialPII( field.value, true, { replaceWith: form.settings.scrubChar } ),
13508					scrubValHTML = wb.findPotentialPII( field.value.replace( /</g, "&lt;" ), true, { replaceWith: "<span role='img' aria-label='" + i18nText.redacted + "'>" + form.settings.scrubChar + "</span>" } );
13509
13510				form.PIIFields.push( {
13511					elm: field,
13512					scrubVal: scrubbedFieldValue,
13513					scrubValHTML: scrubValHTML,
13514					label: fieldLabelText
13515				} );
13516			}
13517		} );
13518
13519		if ( form.PIIFields.length === 1 ) {
13520			document.getElementById( form.PIIFields[ 0 ].elm.id ).focus();
13521		}
13522
13523		// If PII is found, block Postback form submission
13524		form.PIIFields.length > 0 ? form.setAttribute( attrPIIBlocked, "true" ) : form.setAttribute( attrPIIBlocked, "false" );
13525	},
13526
13527	/*
13528	* Scrub all PII positive fields
13529	* @param form: a reference to the form containing PII fields
13530	*/
13531	scrubFormValues = function( form ) {
13532
13533		// Scrub the value of each PII positive fields
13534		form.PIIFields.forEach( ( field ) => {
13535			field.elm.value = field.scrubVal;
13536		} );
13537
13538		// Clear PII fields as their value has been replaced by the scrubbed value
13539		form.PIIFields = [];
13540	},
13541
13542	/*
13543	* Generate the modal UI
13544	* @param form: a reference to the form containing PII fields
13545	*/
13546	generateModal = function( form ) {
13547		let piiModalFields = "",
13548			piiModal = document.createElement( "section" ),
13549			moreInfoContent = form.settings.moreInfo ? form.settings.moreInfo : i18nText.viewMoreInfo,
13550			modalTemplate = form.querySelector( "template" + form.settings.modalTemplate );
13551
13552		// Destroy modal if present
13553		if ( document.getElementById( piiModalID ) ) {
13554			document.getElementById( piiModalID ).remove();
13555		}
13556
13557		// Generate PII fields list
13558		if ( form.PIIFields.length > 1 ) {
13559			piiModalFields += "<dl>";
13560			form.PIIFields.forEach( ( field ) => {
13561				piiModalFields += "<dt>" + field.label + "</dt><dd class=\"well well-sm\">" + field.scrubValHTML.replace( /\n/g, "<br>
13561" ) + "</dd>";
13562			} );
13563			piiModalFields += "</dl>";
13564		} else {
13565			piiModalFields += "<div class=\"well well-sm\">" + form.PIIFields[ 0 ].scrubValHTML.replace( /\n/g, "<br>" ) + "</div>";
13566		}
13567
13568		piiModal.id = piiModalID;
13569		piiModal.className = "modal-dialog modal-content overlay-def";
13570		piiModal.setAttribute( "data-form", form.id );
13571
13572		if ( modalTemplate ) {
13573			piiModal.appendChild( modalTemplate.content.cloneNode( true ) );
13574		} else {
13575			piiModal.innerHTML = `<header class="modal-header">
13576					<h2 class="modal-title">${ i18nText.header }</h2>
13577				</header>
13578				<div class="modal-body">
13579					<p>${ i18nText.intro }</p>
13580					${ piiModalFields }
13581					<details class="mrgn-tp-md">
13582						<summary>${ i18nText.viewMore }</summary>
13583						${ moreInfoContent }
13584					</details>
13585				</div>
13586				<div class="modal-footer">
13587					<div class="row">
13588						<div class="col-xs-12 col-sm-5 mrgn-tp-sm"><button type="button" class="btn btn-link btn-block popup-modal-dismiss">${ i18nText.cancelBtn }</button></div>
13589						<div class="col-xs-12 col-sm-7 mrgn-tp-sm"><button type="button" class="btn btn-primary btn-block popup-modal-dismiss" ${ attrScrubSubmit }>${ i18nText.confirmBtn }</button></div>
13590					</div>
13591				</div>`;
13592		}
13593
13594		// Using jQuery here to pass the content through DOMpurify
13595		$( "body" ).append( piiModal );
13596
13597		// Add PII fields HTML if using a custom UI template
13598		if ( modalTemplate ) {
13599			$( "#" + piiModalID + " [data-scrub-modal-fields]" ).html( piiModalFields );
13600		}
13601	};
13602
13603// Bind the init event of the plugin
13604$document.on( "timerpoke.wb " + initEvent, selector, init );
13605
13606// Scrub the form fields on click of the "Confirm" button
13607$document.on( "click", "#" + piiModalID + " [" + attrScrubSubmit + "]", function( ) {
13608	let modal = document.getElementById( piiModalID ),
13609		form = document.getElementById( modal.dataset.form );
13610
13611	scrubFormValues( form );
13612
13613	if ( form.classList.contains( "wb-postback" ) ) {
13614		$( form ).trigger( "wb-postback.submit", currSubmitter );
13615	} else {
13616		form.submit();
13617	}
13618} );
13619
13620// Add the timer poke to initialize the plugin
13621wb.add( selector );
13622
13623} )( jQuery, wb );
13624
13625/**
13626 * @title WET-BOEW Prettify Plugin
13627 * @overview Wrapper for Google Code Prettify library: https://github.com/google/code-prettify
13628 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13629 * @author @patheard
13630 */
13631/*
13632 * Syntax highlighting of source code snippets in an html page using [google-code-prettify](https://github.com/google/code-prettify).
13633 *
13634 * 1. Apply `class="prettyprint"` to a `pre` or `code` element to apply syntax highlighting. Alternatively use `class="all-pre"` to apply syntax highlighting to all `pre` elements on the page.
13635 * 2. Apply `class="linenums"` to a `pre` or `code` element to add line numbers. Alternatively use `class="all-linenums"` to all applicable `pre` elements. Specify the starting number by adding `linenums:#` before `linenums`.
13636 * 3. Add extra language support by applying `class="lang-*"` to each applicable `pre` or `code` element. Supported language syntax CSS classes are as follows:
13637 *    - lang-apollo
13638 *    - lang-clj
13639 *    - lang-css
13640 *    - lang-dart
13641 *    - lang-go
13642 *    - lang-hs
13643 *    - lang-lisp
13644 *    - lang-lua
13645 *    - lang-ml
13646 *    - lang-n
13647 *    - lang-proto
13648 *    - lang-scala
13649 *    - lang-sql
13650 *    - lang-tex
13651 *    - lang-vb
13652 *    - lang-vhdl
13653 *    - lang-wiki
13654 *    - lang-xq
13655 *    - lang-yaml
13656 */
13657( function( $, window, wb ) {
13658"use strict";
13659
13660/*
13661 * Variable and function definitions.
13662 * These are global to the plugin - meaning that they will be initialized once per page,
13663 * not once per instance of plugin on the page. So, this is a good place to define
13664 * variables that are common to all instances of the plugin on a page.
13665 */
13666var componentName = "wb-prettify",
13667	selector = "." + componentName,
13668	initEvent = "wb-init" + selector,
13669	prettyPrintEvent = "prettyprint" + selector,
13670	$document = wb.doc,
13671
13672	/*
13673	 * Plugin users can override these defaults by setting attributes on the html elements that the
13674	 * selector matches.
13675	 */
13676	defaults = {
13677		linenums: false,
13678		allpre: false
13679	},
13680
13681	/**
13682	 * @method init
13683	 * @param {jQuery Event} event Event that triggered the function call
13684	 */
13685	init = function( event ) {
13686
13687		// Start initialization
13688		// returns DOM object = proceed with init
13689		// returns undefined = do not proceed with init (e.g., already initialized)
13690		var elm = wb.init( event, componentName, selector ),
13691			modeJS = wb.getMode() + ".js",
13692			deps = [ "site!deps/prettify" + modeJS ],
13693			$elm, classes, settings, i, len, $pre;
13694
13695		if ( elm ) {
13696			$elm = $( elm );
13697			classes = elm.className.split( " " );
13698
13699			// Merge default settings with overrides from the selected plugin element. There may be more than one, so don't override defaults globally!
13700			settings = $.extend( {}, defaults, $elm.data() );
13701
13702			// Check the element for `lang-*` syntax CSS classes
13703			for ( i = 0, len = classes.length; i !== len; i += 1 ) {
13704				if ( classes[ i ].indexOf( "lang-" ) === 0 ) {
13705					deps.push( "site!deps/" + classes[ i ] + modeJS );
13706				}
13707			}
13708
13709			// CSS class overrides of settings
13710			settings.allpre = settings.allpre || $elm.hasClass( "all-pre" );
13711			settings.linenums = settings.linenums || $elm.hasClass( "linenums" );
13712
13713			// Apply global settings
13714			if ( settings.allpre || settings.linenums ) {
13715				$pre = $document.find( "pre" );
13716				if ( settings.allpre ) {
13717					$pre.addClass( "prettyprint" );
13718				}
13719				if ( settings.linenums ) {
13720					$pre.filter( ".prettyprint" ).addClass( "linenums" );
13721				}
13722			}
13723
13724			// Load the required dependencies and prettify the code once finished
13725			Modernizr.load( {
13726				load: deps,
13727				complete: function() {
13728					$document.trigger( prettyPrintEvent );
13729				}
13730			} );
13731		}
13732	},
13733
13734	prettifyDone = function() {
13735
13736		// Identify that initialization has completed
13737		wb.ready( $document, componentName );
13738	},
13739
13740	/*
13741	 * Invoke the Google pretty print library if it has been initialized
13742	 * @method prettyprint
13743	 */
13744	prettyprint = function( event ) {
13745		if ( event.namespace === componentName &&
13746			typeof window.prettyPrint === "function" ) {
13747
13748			window.prettyPrint( prettifyDone );
13749		}
13750	};
13751
13752// Bind the plugin events
13753$document
13754	.on( "timerpoke.wb " + initEvent, selector, init )
13755	.on( prettyPrintEvent, prettyprint );
13756
13757// Add the timer poke to initialize the plugin
13758wb.add( selector );
13759
13760} )( jQuery, window, wb );
13761
13762/**
13763 * @title WET-BOEW Resize
13764 * @overview Text and window resizing event handler
13765 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13766 * @author @pjackson28
13767 */
13768( function( $, window, document, wb ) {
13769"use strict";
13770
13771/*
13772 * Variable and function definitions.
13773 * These are global to the plugin - meaning that they will be initialized once per page,
13774 * not once per instance of plugin on the page. So, this is a good place to define
13775 * variables that are common to all instances of the plugin on a page.
13776 */
13777var componentName = "wb-rsz",
13778	selector = "#" + componentName,
13779	initEvent = "wb-init" + selector,
13780	$document = wb.doc,
13781	sizes = [],
13782	events = [
13783		"txt-rsz.wb",
13784		"win-rsz-width.wb",
13785		"win-rsz-height.wb"
13786	],
13787
13788	// Breakpoint names and lower pixel limits
13789	breakpoints = {
13790		xxsmallview: 0,
13791		xsmallview: 480,
13792		smallview: 768,
13793		mediumview: 992,
13794		largeview: 1200,
13795		xlargeview: 1600
13796	},
13797	resizeTest, currentView,
13798
13799	/**
13800	 * @method init
13801	 * @param {jQuery Event} event Event that triggered the function call
13802	 */
13803	init = function( event ) {
13804
13805		// Start initialization
13806		// returns DOM object = proceed with init
13807		// returns undefined = do not proceed with init (e.g., already initialized)
13808		var elm = wb.init( event, componentName, selector ),
13809			localResizeTest;
13810
13811		if ( elm ) {
13812
13813			// Set up the DOM element used for resize testing
13814			localResizeTest = document.createElement( "span" );
13815			localResizeTest.innerHTML = "&#160;";
13816			localResizeTest.setAttribute( "id", componentName );
13817			document.body.appendChild( localResizeTest );
13818			resizeTest = localResizeTest;
13819
13820			// Get a snapshot of the current sizes
13821			sizes = [
13822				localResizeTest.offsetHeight,
13823				window.innerWidth || $document.width(),
13824				window.innerHeight || $document.height()
13825			];
13826
13827			// Determine the current view
13828			viewChange( sizes[ 1 ] );
13829
13830			// Identify that initialization has completed
13831			wb.ready( $document, componentName );
13832		}
13833	},
13834
13835	viewChange = function( viewportWidth ) {
13836		var breakpoint, viewName;
13837
13838		// Check for a change between views
13839		for ( breakpoint in breakpoints ) {
13840
13841			// Determine the current view
13842			if ( viewportWidth < breakpoints[ breakpoint ] ) {
13843				break;
13844			} else {
13845				viewName = breakpoint;
13846			}
13847		}
13848
13849		// Determine if the current view is different than the previous view
13850		if ( viewName !== currentView ) {
13851
13852			// Change the breakpoint class on the html element
13853			wb.html
13854				.removeClass( currentView || "" )
13855				.addClass( viewName );
13856
13857			// Update the current view
13858			currentView = viewName;
13859
13860			// Trigger the view event
13861			$document.trigger( viewName + ".wb" );
13862		}
13863	},
13864
13865	/**
13866	 * Tests for text size, window width and window height changes and triggers an event when a change is found
13867	 * @method test
13868	 */
13869	test = function() {
13870		var currentSizes = [
13871				resizeTest.offsetHeight,
13872				window.innerWidth || $document.width(),
13873				window.innerHeight || $document.height()
13874			],
13875			len = currentSizes.length,
13876			i;
13877
13878		// Check for a viewport or text size change
13879		for ( i = 0; i !== len; i += 1 ) {
13880			if ( currentSizes[ i ] !== sizes[ i ] ) {
13881
13882				// Change detected so trigger related event
13883				$document.trigger( events[ i ], currentSizes );
13884
13885				// Check for a view change
13886				viewChange( currentSizes[ 1 ] );
13887			}
13888		}
13889		sizes = currentSizes;
13890
13891		return;
13892	};
13893
13894// Bind the init event to the plugin
13895$document.on( initEvent, init );
13896
13897// Re-test on each timerpoke
13898$document.on( "timerpoke.wb", selector, test );
13899
13900// Initialize the resources
13901$document.trigger( initEvent );
13902
13903// Add the timer poke to initialize the plugin
13904wb.add( selector );
13905
13906} )( jQuery, window, document, wb );
13907
13908/**
13909 * @title WET-BOEW Session Timeout
13910 * @overview Helps Web asset owners to provide session timeout and inactivity timeout functionality.
13911 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13912 * @author @patheard
13913 */
13914( function( $, DOMPurify, window, document, wb ) {
13915"use strict";
13916
13917/*
13918 * Variable and function definitions.
13919 * These are global to the plugin - meaning that they will be initialized once per page,
13920 * not once per instance of plugin on the page. So, this is a good place to define
13921 * variables that are common to all instances of the plugin on a page.
13922 */
13923var $modal, $modalLink, countdownInterval, i18n, i18nText,
13924	$document = wb.doc,
13925	componentName = "wb-sessto",
13926	selector = "." + componentName,
13927	confirmClass = componentName + "-confirm",
13928	initEvent = "wb-init" + selector,
13929	resetEvent = "reset" + selector,
13930	keepaliveEvent = "keepalive" + selector,
13931	inactivityEvent = "inactivity" + selector,
13932	dataAttr = componentName,
13933
13934	/*
13935	 * Plugin users can override these defaults by setting attributes on the html elements that the
13936	 * selector matches.
13937	 * For example, adding the attribute data-option1="false", will override option1 for that plugin instance.
13938	 */
13939	defaults = {
13940		inactivity: 1200000,		/* default inactivity period 20 minutes */
13941		reactionTime: 180000,		/* default confirmation period of 3 minutes */
13942		sessionalive: 1200000,		/* default keepalive period of 20 minutes */
13943		refreshCallbackUrl: null,	/* refresh callback if using AJAX keepalive (no default) */
13944		logouturl: "./",			/* logout URL to end the session */
13945		signInUrl: null,			/* sign-in URL once the session has expired */
13946		refreshOnClick: true,		/* refresh session if user clicks on the page */
13947		refreshLimit: 120000,		/* default period of 2 minutes (ajax calls happen only once during this period) */
13948		method: "POST",				/* the request method to use */
13949		textOverrides: null,		/* text overrides (no default) */
13950		additionalData: null,		/* additional data to send with the request */
13951		refreshCallback: function( response ) {	/* callback function used to check the server response */
13952			return response.replace( /\s/g, "" ) === "true";
13953		}
13954	},
13955
13956	/**
13957	 * @function init
13958	 * @param {jQuery Event} event Event that triggered the function call
13959	 */
13960	init = function( event ) {
13961
13962		// Start initialization
13963		// returns DOM object = proceed with init
13964		// returns undefined = do not proceed with init (e.g., already initialized)
13965		var elm = wb.init( event, componentName, selector ),
13966			$elm, settings, onReady;
13967
13968		if ( elm ) {
13969			$elm = $( elm );
13970
13971			// For backwards compatibility where data-wet-boew was used instead of data-wb-sessto
13972			if ( !$elm.attr( "data-" + componentName ) ) {
13973				dataAttr = "wet-boew";
13974			}
13975
13976			// Merge default settings with overrides from the plugin element
13977			// and save back to the element for future reference
13978			settings = $.extend( {}, defaults, window[ componentName ], $elm.data( dataAttr ) );
13979			$elm.data( dataAttr, settings );
13980
13981			// Only initialize the i18nText once
13982			if ( !i18nText ) {
13983				i18n = wb.i18n;
13984				const textOverrides = settings.textOverrides;
13985				if ( textOverrides ) {
13986					i18nText = {
13987						buttonContinue: Object.hasOwn( textOverrides, "buttonContinue" ) ? DOMPurify.sanitize( textOverrides.buttonContinue ) : i18n( "st-btn-cont" ),
13988						buttonEnd: Object.hasOwn( textOverrides, "buttonEnd" ) ? DOMPurify.sanitize( textOverrides.buttonEnd ) : i18n( "st-btn-end" ),
13989						buttonSignin: Object.hasOwn( textOverrides, "buttonSignin" ) ? DOMPurify.sanitize( textOverrides.buttonSignin ) 
13989: i18n( "tmpl-signin" ),
13990						timeoutBegin: i18n( "st-to-msg-bgn" ),
13991						timeoutEnd: Object.hasOwn( textOverrides, "timeoutEnd" ) ? DOMPurify.sanitize( textOverrides.timeoutEnd ) : i18n( "st-to-msg-end" ),
13992						timeoutTitle: i18n( "st-msgbx-ttl" ),
13993						timeoutAlready: Object.hasOwn( textOverrides, "timeoutAlready" ) ? DOMPurify.sanitize( textOverrides.timeoutAlready ) : i18n( "st-alrdy-to-msg" )
13994					};
13995				} else {
13996					i18nText = {
13997						buttonContinue: i18n( "st-btn-cont" ),
13998						buttonEnd: i18n( "st-btn-end" ),
13999						buttonSignin: i18n( "tmpl-signin" ),
14000						timeoutBegin: i18n( "st-to-msg-bgn" ),
14001						timeoutEnd: i18n( "st-to-msg-end" ),
14002						timeoutTitle: i18n( "st-msgbx-ttl" ),
14003						timeoutAlready: i18n( "st-alrdy-to-msg" )
14004					};
14005				}
14006			}
14007
14008			onReady = function() {
14009
14010				// Setup the refresh on click behaviour
14011				initRefreshOnClick( $elm, settings );
14012
14013				// Initialize the keepalive and inactive timeouts of the plugin
14014				$elm.trigger( resetEvent, settings );
14015
14016				// Identify that initialization has completed
14017				wb.ready( $elm, componentName );
14018			};
14019
14020			// Create the modal dialog
14021			initModalDialog( onReady );
14022		}
14023	},
14024
14025	/**
14026	 * Initializes a timeout that triggers an event
14027	 * @function initEventTimeout
14028	 * @param {jQuery DOM Element} $elm Element to trigger the event on
14029	 * @param {string} eventName Name of the event to trigger on setTimeout
14030	 * @param {mixed} time Time to wait before triggering the event
14031	 * @param {Object} settings Key-value object
14032	 */
14033	initEventTimeout = function( $elm, eventName, time, settings ) {
14034		var duration = parseTime( time );
14035
14036		// Clear any existing timeout for the event
14037		clearTimeout( $elm.data( eventName ) );
14038
14039		// Create the new timeout that will trigger the event
14040		$elm.data( eventName, setTimeout( function() {
14041			$elm.trigger( eventName, settings );
14042		}, duration ) );
14043	},
14044
14045	/**
14046	 * Creates the modal dialog element, appends to the <body> and initializes the lightbox plugin
14047	 * that is used to create the dialog behaviour.
14048	 * @function initModalDialog
14049	 */
14050	initModalDialog = function( callback ) {
14051		var modalID = "#" + componentName + "-modal",
14052			child, modal, temp;
14053
14054		if ( $document.find( modalID ).length === 0 ) {
14055			modal = document.createDocumentFragment();
14056			temp = document.createElement( "div" );
14057
14058			// Create the modal dialog.  A temp <div> element is used so that its innerHTML can be set as a string.
14059			temp.innerHTML = "<a class='wb-lbx lbx-modal mfp-hide' href='#" + componentName + "-modal'>" + i18nText.timeoutTitle + "</a>" +
14060				"<section id='" + componentName + "-modal' class='mfp-hide modal-dialog modal-content overlay-def'>" +
14061				"<header class='modal-header'><h2 class='modal-title'>" + i18nText.timeoutTitle + "</h2></header>" +
14062				"<div class='modal-body'></div>" +
14063				"<div class='modal-footer'></div>" +
14064				"</section>";
14065
14066			// Get the temporary <div>'s top level children and append to the fragment
14067			while ( ( child = temp.firstChild ) !== null ) {
14068				modal.appendChild( child );
14069			}
14070			document.body.appendChild( modal );
14071
14072			$modal = $document.find( modalID );
14073
14074			// Get object references to the modal and its triggering link
14075			$modalLink = $modal.prev()
14076				.one( "wb-ready.wb-lbx", callback )
14077				.trigger( "wb-init.wb-lbx" );
14078		} else {
14079			callback();
14080		}
14081	},
14082
14083	/**
14084	 * Initialize the refresh on click keepalive behaviour. This will cause a `keepalive.wb-sessto`
14085	 * event to be triggered when the document is clicked, limited by the settings.refreshLimit value.
14086	 * @function initRefreshOnClick
14087	 * @param {jQuery DOM Element} $elm DOM element to trigger the event on
14088	 * @param {Object} settings Key-value object that will be passed when event is triggered.
14089	 */
14090	initRefreshOnClick = function( $elm, settings ) {
14091		if ( settings.refreshOnClick ) {
14092			$document.on( "click", function( event ) {
14093				var className = event.target.className,
14094					lastActivity, currentTime;
14095
14096				// Ignore clicks when the modal dialog is open
14097				if ( ( !className || className.indexOf( confirmClass ) === -1 ) &&
14098					$( ".mfp-ready ." + confirmClass ).length === 0 ) {
14099
14100					lastActivity = $elm.data( "lastActivity" );
14101					currentTime = getCurrentTime();
14102					if ( !lastActivity || ( currentTime - lastActivity ) > settings.refreshLimit ) {
14103						$elm
14104							.trigger( resetEvent, settings )
14105							.trigger( keepaliveEvent, settings );
14106						$elm.data( "lastActivity", currentTime );
14107					}
14108				}
14109			} );
14110		}
14111	},
14112
14113	/**
14114	 * Keepalive session event handler. Sends the POST request to determine if the session is still alive.
14115	 * @function keepalive
14116	 * @param {jQuery Event} event `keepalive.wb-sessto` event that triggered the function call
14117	 * @param {Object} settings Key-value object
14118	 */
14119	keepalive = function( event, settings ) {
14120		var $elm = $( event.target );
14121		if ( settings.refreshCallbackUrl !== null ) {
14122			$.ajax( {
14123				url: settings.refreshCallbackUrl,
14124				data: settings.additionalData,
14125				dataType: "text",
14126				method: settings.method,
14127				success: function( response ) {
14128
14129					// Sanitize the response
14130					response = DOMPurify.sanitize( response );
14131
14132					// Session is valid
14133					if ( response && settings.refreshCallback( response ) ) {
14134						$elm.trigger( resetEvent, settings );
14135
14136					// Session has timed out - let the user know they need to sign in again
14137					} else {
14138
14139						// End the inactivity timeouts since the session is already kaput
14140						clearTimeout( $elm.data( inactivityEvent ) );
14141						clearTimeout( $elm.data( keepaliveEvent ) );
14142
14143						openModal( {
14144							body: "<p>" + i18nText.timeoutAlready + "</p>",
14145							buttons: $( "<button type='button' class='" + confirmClass +
14146								" btn btn-primary popup-modal-dismiss'>" + i18nText.buttonSignin + "</button>" )
14147								.data( "logouturl", settings.logouturl )
14148						} );
14149					}
14150				}
14151			} );
14152		}
14153	},
14154
14155	/**
14156	 * Inactivity check event handler. Displays the modal dialog to allow the user to confirm their activity.
14157	 * @function inactivity
14158	 * @param {jQuery Event} event `inactivity.wb-sessto` event that triggered the function call
14159	 * @param {Object} settings Key-value object
14160	 */
14161	inactivity = function( event, settings ) {
14162		var $buttonContinue, $buttonEnd,
14163			time = getTime( settings.reactionTime ),
14164			startTime = getCurrentTime(),
14165			timeoutBegin = i18nText.timeoutBegin
14166				.replace( "#min#", "<span class='min'>" + time.minutes + "</span>" )
14167				.replace( "#sec#", "<span class='sec'>" + time.seconds + "</span>" ),
14168			buttonStart = "<button type='button' class='",
14169			buttonEnd = "</button>";
14170
14171		// Clear the keepalive timeout to avoid double firing of requests
14172		clearInterval( $( event.target ).data( keepaliveEvent ) );
14173
14174		$buttonContinue = $( buttonStart + confirmClass +
14175			" btn btn-primary popup-modal-dismiss'>" + i18nText.buttonContinue + buttonEnd )
14176			.data( settings )
14177			.data( "start", startTime );
14178		$buttonEnd = $( buttonStart + confirmClass + " btn btn-default'>" +
14179			i18nText.buttonEnd + buttonEnd )
14180			.data( "logouturl", settings.logouturl );
14181
14182		openModal( {
14183			body: "<p>" + timeoutBegin + "<br />" + i18nText.timeoutEnd + "</p>",
14184			buttons: [ $buttonContinue, $buttonEnd ],
14185			open: function() {
14186				var $minutes = $modal.find( ".min" ),
14187					$seconds = $modal.find( ".sec" ),
14188					endDuration = settings.reactionTime;
14189
14190				countdownInterval = setInterval( function() {
14191					if ( countdown( $minutes, $seconds, startTime, endDuration ) ) {
14192						clearInterval( countdownInterval );
14193
14194						// Let the user know their session has timed out
14195						$modal.find( "p" ).text( i18nText.timeoutAlready );
14196						$buttonContinue.text( i18nText.buttonSignin );
14197						$buttonEnd.hide();
14198					}
14199				}, 500 );
14200			}
14201		} );
14202	},
14203
14204	/**
14205	 * Initialize the inactivity and keepalive timeouts of the plugin
14206	 * @function reset
14207	 * @param {jQuery Event} event `reset.wb-sessto` event that triggered the function call
14208	 * @param {Object} settings Key-value object
14209	 */
14210	reset = function( event, settings ) {
14211		var $elm = $( event.target );
14212
14213		initEventTimeout( $elm, inactivityEvent, settings.inactivity, settings );
14214		if ( settings.refreshCallbackUrl !== null ) {
14215			initEventTimeout( $elm, keepaliveEvent, settings.sessionalive, settings );
14216		}
14217	},
14218
14219	/**
14220	 * Checks if the user wants to keep their session alive.
14221	 * @function inactivity
14222	 * @param {jQuery Event} event `confirm.wb-sessto` event that triggered the function call
14223	 */
14224	confirm = function( event ) {
14225		var elm = event.target,
14226			$elm = $( elm ),
14227			settings = $elm.data();
14228
14229		event.preventDefault();
14230		$.magnificPopup.close();
14231		clearInterval( countdownInterval );
14232
14233		// User wants their session maintained
14234		if ( settings.start !== undefined && ( getCurrentTime() - settings.start ) <= settings.reactionTime ) {
14235			$( selector )
14236				.trigger( resetEvent, settings )
14237				.trigger( keepaliveEvent, settings );
14238
14239		// Negative confirmation or the user took too long; logout
14240		} else {
14241
14242			// Use setTimeout() to navigate asynchronously (to support lightbox Escape key presses)
14243			setTimeout( function() {
14244				window.location.href = settings.signInUrl ? settings.signInUrl : settings.logouturl;
14245			} );
14246		}
14247	},
14248
14249	/**
14250	 * Add the modal dialog's content and display it to the user
14251	 * @function openModal
14252	 * @param {Object} data Key-value object
14253	 */
14254	openModal = function( data ) {
14255
14256		// Detach the modal to prevent reflows while updating the element
14257		$modal = $modal.detach();
14258		$modal.find( ".modal-body" ).html( data.body );
14259		$modal.find( ".modal-footer" ).empty().append( data.buttons );
14260
14261		// Re-attach the modal and open the dialog
14262		$modal = $modal.insertAfter( $modalLink );
14263		$modalLink.magnificPopup( "open" );
14264
14265		// Execute the open callback if it exists
14266		if ( data.open ) {
14267			data.open();
14268		}
14269	},
14270
14271	/**
14272	 * Returns the current time in milliseconds
14273	 * @function getCurrentTime
14274	 * @returns {integer} Current time in milliseconds
14275	 */
14276	getCurrentTime = function() {
14277		return ( new Date() ).getTime();
14278	},
14279
14280	/**
14281	 * Parses a time value into a milliseconds integer value.
14282	 * @function parseTime
14283	 * @param {Mixed} value The time value to parse (integer or string)
14284	 * @returns {integer} Millisecond integer value parsed from the time value
14285	 */
14286	parseTime = function( value ) {
14287		var result, num, mult,
14288			powers = {
14289				ms: 1,
14290				cs: 10,
14291				ds: 100,
14292				s: 1000,
14293				das: 10000,
14294				hs: 100000,
14295				ks: 1000000
14296			};
14297
14298		if ( value == null ) { //eslint-disable-line no-eq-null
14299			return null;
14300		}
14301
14302		result = /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/.exec( $.trim( value.toString() ) );
14303		if ( result[ 2 ] ) {
14304			num = parseFloat( result[ 1 ] );
14305			mult = powers[ result[ 2 ] ] || 1;
14306			return num * mult;
14307		}
14308		return value;
14309	},
14310
14311	/**
14312	 * Converts a millisecond value into minutes and seconds
14313	 * @function getTime
14314	 * @param {integer} milliseconds The time value in milliseconds
14315	 * @returns {Object} An object with a seconds and minutes property
14316	 */
14317	getTime = function( milliseconds ) {
14318		var time = { minutes: "", seconds: "" };
14319
14320		if ( milliseconds != null ) { //eslint-disable-line no-eq-null
14321			time.minutes = parseInt( ( milliseconds / ( 1000 * 60 ) ) % 60, 10 );
14322			time.seconds = parseInt( ( milliseconds / 1000 ) % 60, 10 );
14323		}
14324		return time;
14325	},
14326
14327	/**
14328	 * Given 2 elements representing minutes and seconds, decrement their time value by 1 second
14329	 * @function countdown
14330	 * @param {jQuery DOM Element} $minutes Element that contains the minute value
14331	 * @param {jQuery DOM Element} $seconds Element that contains the second value
14332	 * @param { integer } startTime The time value of when the countdown started in milliseconds
14333	 * @param { integer } endDuration The time value of the duration of the countdown in milliseconds
14334	 * @returns {boolean} Is the countdown finished?
14335	 */
14336	countdown = function( $minutes, $seconds, startTime, endDuration ) {
14337		var newTime = getTime( endDuration - ( getCurrentTime() - startTime ) );
14338
14339		// Update the DOM elements
14340		$minutes.text( newTime.minutes );
14341		$seconds.text( newTime.seconds );
14342
14343		return newTime.minutes <= 0 && newTime.seconds <= 0;
14344	};
14345
14346// Bind the plugin events
14347$document.on( "timerpoke.wb " + initEvent + " " + keepaliveEvent + " " +
14348inactivityEvent + " " + resetEvent, selector, function( event, settings ) {
14349
14350	var eventType = event.type;
14351
14352	switch ( eventType ) {
14353		case "timerpoke":
14354		case "wb-init":
14355			init( event );
14356			break;
14357
14358		case "keepalive":
14359			keepalive( event, settings );
14360			break;
14361
14362		case "inactivity":
14363			inactivity( event, settings );
14364			break;
14365
14366		case "reset":
14367			reset( event, settings );
14368			break;
14369	}
14370} );
14371
14372$document.on( "click", "." + confirmClass, confirm );
14373
14374// Add the timer poke to initialize the plugin
14375wb.add( selector );
14376
14377} )( jQuery, DOMPurify, window, document, wb );
14378
14379/**
14380 * @title WET-BOEW Share widget
14381 * @overview Facilitates sharing Web content on social media platforms.
14382 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
14383 * @author @pjackson28
14384 */
14385( function( $, window, document, wb ) {
14386"use strict";
14387
14388/*
14389 * Variable and function definitions.
14390 * These are global to the plugin - meaning that they will be initialized once per page,
14391 * not once per instance of plugin on the page. So, this is a good place to define
14392 * variables that are common to all instances of the plugin on a page.
14393 */
14394var componentName = "wb-share",
14395	selector = "." + componentName,
14396	initEvent = "wb-init" + selector,
14397	shareLink = "shr-lnk",
14398	panelCount = 0,
14399	$document = wb.doc,
14400	i18n, i18nText,
14401
14402	/*
14403	 * Plugin users can override these defaults by setting attributes on the html elements that the
14404	 * selector matches.
14405	 */
14406	defaults = {
14407		hdLvl: "h2",
14408
14409		// Supported types are: "page", "video" and "audio"
14410		type: "page",
14411
14412		// For custom types
14413		// custType = " this comment" results in "Share this comment"
14414		custType: "",
14415
14416		url: wb.pageUrlParts.href,
14417		title: document.title || $document.find( "h1:first" ).text(),
14418
14419		pnlId: "",
14420		lnkClass: "",
14421		img: "",
14422		desc: "",
14423
14424		// For filtering the sites that area displayed and controlling the order
14425		// they are displayed. Empty array displays all sites in the default order.
14426		// Otherwise, it displays the sites in the order in the array using the
14427		// keys used by the sites object.
14428		filter: [],
14429
14430		sites: {
14431
14432			// The definitions of the available bookmarking sites, in URL use
14433			// '{u}' for the page URL, '{t}' for the page title, {i} for the image, and '{d}' for the description
14434			blogger: {
14435				name: "Blogger",
14436				url: "https://www.blogger.com/blog_this.pyra?t=&amp;u={u}&amp;n={t}"
14437			},
14438			bluesky: {
14439				name: "Bluesky",
14440				url: "https://bsky.app/intent/compose?text={t}+{u}"
14441			},
14442			diigo: {
14443				name: "Diigo",
14444				url: "https://www.diigo.com/post?url={u}&amp;title={t}"
14445			},
14446			facebook: {
14447				name: "Facebook",
14448				url: "https://www.facebook.com/sharer.php?u={u}&amp;t={t}"
14449			},
14450			gmail: {
14451				name: "Gmail",
14452				url: "https://mail.google.com/mail/?view=cm&fs=1&tf=1&to=&su={t}&body={u}%0A{d}"
14453			},
14454			linkedin: {
14455				name: "LinkedIn®",
14456				url: "https://www.linkedin.com/shareArticle?mini=true&amp;url={u}&amp;title={t}&amp;ro=false&amp;summary={d}&amp;source="
14457			},
14458			myspace: {
14459				name: "MySpace",
14460				url: "https://www.myspace.com/Modules/PostTo/Pages/?u={u}&amp;t={t}"
14461			},
14462			pinterest: {
14463				name: "Pinterest",
14464				url: "https://www.pinterest.com/pin/create/button/?url={u}&amp;media={i}&amp;description={t}"
14465			},
14466			reddit: {
14467				name: "reddit",
14468				url: "https://reddit.com/submit?url={u}&amp;title={t}"
14469			},
14470			tinyurl: {
14471				name: "TinyURL",
14472				url: "https://tinyurl.com/create.php?url={u}"
14473			},
14474			tumblr: {
14475				name: "tumblr",
14476				url: "https://www.tumblr.com/share/link?url={u}&amp;name={t}&amp;description={d}"
14477			},
14478			twitter: {
14479				name: "X",
14480				url: "https://twitter.com/intent/tweet?text={t}&url={u}"
14481			},
14482			x: {
14483				name: "X",
14484				url: "https://twitter.com/intent/tweet?text={t}&url={u}"
14485			},
14486			yahoomail: {
14487				name: "Yahoo! Mail",
14488				url: "https://compose.mail.yahoo.com/?to=&subject={t}&body={u}%0A{d}"
14489			},
14490			whatsapp: {
14491				name: "WhatsApp",
14492				url: "https://api.whatsapp.com/send?text={t}%0A{d}%0A{u}"
14493			}
14494		}
14495	},
14496
14497	/**
14498	 * @method init
14499	 * @param {jQuery Event} event Event that triggered the function call
14500	 */
14501	init = function( event ) {
14502
14503		// Start initialization
14504		// returns DOM object = proceed with init
14505		// returns undefined = do not proceed with init (e.g., already initialized)
14506		var elm = wb.init( event, componentName, selector ),
14507			sites, heading, settings, panel, link, $share, $elm,
14508			pageHref, pageTitle, pageImage, pageDescription,
14509			siteProperties, url, shareText, id, pnlId, regex,
14510			filter, i, len, keys, key;
14511
14512		if ( elm ) {
14513
14514			// Only initialize the i18nText once
14515			if ( !i18nText ) {
14516				i18n = wb.i18n;
14517				i18nText = {
14518					shareText: i18n( "shr-txt" ),
14519					page: i18n( "shr-pg" ),
14520					video: i18n( "shr-vid" ),
14521					audio: i18n( "shr-aud" ),
14522					disclaimer: i18n( "shr-disc" ),
14523					email: i18n( "email" )
14524				};
14525
14526				// Add an email mailto option
14527				defaults.sites.email = {
14528					name: i18nText.email,
14529					url: "mailto:?subject={t}&body={u}%0A{d}",
14530					isMailto: true
14531				};
14532			}
14533
14534			$elm = $( elm );
14535			settings = $.extend(
14536				true,
14537				{},
14538				defaults,
14539				window[ componentName ],
14540				wb.getData( $elm, componentName )
14541			);
14542			sites = settings.sites;
14543			filter = settings.filter;
14544			heading = settings.hdLvl;
14545
14546			shareText = i18nText.shareText + ( settings.custType.length !== 0 ? settings.custType : i18nText[ settings.type ] );
14547			pnlId = settings.pnlId;
14548			id = "shr-pg" + ( pnlId.length !== 0 ? "-" + pnlId : panelCount );
14549			pageHref = encodeURIComponent( settings.url );
14550
14551			regex = /'|&#39;|&apos;/g;
14552			pageTitle = encodeURIComponent( settings.title )
14553				.replace( regex, "%27" );
14554			pageImage = encodeURIComponent( settings.img );
14555			pageDescription = encodeURIComponent( settings.desc )
14556				.replace( regex, "%27" );
14557
14558			// Don't create the panel for the second link (class="link-only")
14559			if ( elm.className.indexOf( "link-only" ) === -1 ) {
14560				panel = "<section id='" + id  + "' class='shr-pg mfp-hide modal-dialog modal-content overlay-def" +
14561					"'><header class='modal-header'><" + heading + " class='modal-title'>" +
14562					shareText + "</" + heading + "></header><div class='modal-body'>" +
14563					"<ul class='list-unstyled colcount-xs-2'>";
14564
14565				// If there is no filter array of site keys, then generate an array of site keys
14566				if ( !filter || filter.length === 0 ) {
14567					keys = [];
14568					for ( key in sites ) {
14569						if ( Object.prototype.hasOwnProperty.call( sites, key ) ) {
14570							keys.push( key );
14571						}
14572					}
14573				} else {
14574					keys = filter;
14575				}
14576
14577				// i18n-friendly sort of the site keys
14578				keys.sort( function( x, y ) {
14579					return wb.normalizeDiacritics( x ).localeCompare( wb.normalizeDiacritics( y ) );
14580				} );
14581
14582				// If Twitter and X are both present, only keep X
14583				if ( keys.includes( "twitter" ) && keys.includes( "x" ) ) {
14584					keys = keys.filter( function( item ) {
14585						return item !== "twitter";
14586					} );
14587				}
14588
14589				len = keys.length;
14590
14591				// Generate the panel
14592				for ( i = 0; i !== len; i += 1 ) {
14593					key = keys[ i ];
14594					siteProperties = sites[ key ];
14595					url = siteProperties.url
14596						.replace( /\{u\}/, pageHref )
14597						.replace( /\{t\}/, pageTitle )
14598						.replace( /\{i\}/, pageImage )
14599						.replace( /\{d\}/, pageDescription );
14600					panel += "<li><a href='" + url + "' class='" + shareLink +
14601						" " + ( siteProperties.isMailto ? "email" : key ) +
14602						" btn btn-default' target='_blank' rel='noreferrer noopener'>" +
14603						siteProperties.name + "</a></li>";
14604				}
14605
14606				panel += "</ul><p class='col-sm-12 shr-dscl'>" + i18nText.disclaimer +
14607					"</p><div class='clearfix'></div></div></section>";
14608				panelCount += 1;
14609			}
14610			link = "<a href='#" + id + "' aria-controls='" + id +
14611				"' class='shr-opn wb-lbx " + settings.lnkClass +
14612				"'><span class='glyphicon glyphicon-share'></span>" +
14613				shareText + "</a>";
14614
14615			$share = $( ( panel ? panel : "" ) + link );
14616
14617			$elm.append( $share );
14618
14619			$share
14620				.trigger( "wb-init.wb-lbx" );
14621
14622			// Identify that initialization has completed
14623			wb.ready( $elm, componentName );
14624		}
14625	};
14626
14627// Bind the init event of the plugin
14628$document.on( "timerpoke.wb " + initEvent, selector, init );
14629
14630// Add the timer poke to initialize the plugin
14631wb.add( selector );
14632
14633} )( jQuery, window, document, wb );
14634
14635/**
14636 * @title WET-BOEW step form
14637 * @overview Provide ability for a form to be broken into steps.
14638 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
14639 * @author @kodecount
14640 */
14641( function( $, window, document, wb ) {
14642"use strict";
14643
14644/*
14645 * Variable and function definitions.
14646 * These are global to the plugin - meaning that they will be initialized once per page,
14647 * not once per instance of plugin on the page. So, this is a good place to define
14648 * variables that are common to all instances of the plugin on a page.
14649 */
14650var componentName = "wb-steps",
14651	selector = ".provisional." + componentName,
14652	initEvent = "wb-init" + selector,
14653	$document = wb.doc,
14654	i18n, i18nText,
14655	btnPrevious, btnNext, btnSubmit,
14656
14657	/**
14658	 * @method init
14659	 * @param {jQuery Event} evt Event that triggered the function call
14660	 */
14661	init = function( evt ) {
14662
14663		// Start initialization
14664		// returns DOM object = proceed with init
14665		// returns undefined = do not proceed with init (e.g., already initialized)
14666		var elm = wb.init( evt, componentName, selector );
14667
14668		if ( elm ) {
14669
14670			// Ensure there is a unique id on the element
14671			if ( !elm.id ) {
14672				elm.id = wb.getId();
14673			}
14674
14675			// Only initialize the i18nText once
14676			if ( !i18nText ) {
14677				i18n = wb.i18n;
14678				i18nText = {
14679					prv: i18n( "prv" ),
14680					nxt: i18n( "nxt" )
14681				};
14682			}
14683
14684			/*
14685			 * Variable and function definitions
14686			 * These will be initialized once per instance of plugin.
14687			 */
14688			var form = elm.getElementsByTagName( "FORM" )[ 0 ],
14689				fieldsets = ( form ) ? $( form ).children( "fieldset" ) : 0,
14690				hasStepsInitialized;
14691
14692			// Initialize navigation buttons
14693			btnPrevious = createStepsButton( "prev", "mrgn-rght-sm mrgn-bttm-md", i18nText.prv );
14694			btnNext = createStepsButton( "next", "mrgn-bttm-md", i18nText.nxt );
14695			btnSubmit = form.querySelector( "input[type=submit], button[type=submit]" );
14696			btnSubmit.classList.add( "mrgn-bttm-md" );
14697
14698			/*
14699			 * Determines if html is correctly formatted and initialize all fieldsets/legend combinations into steps.
14700			 */
14701			for ( var i = 0, len = fieldsets.length; i < len; i++ ) {
14702
14703				/*
14704				 * Variable and function definitions
14705				 * These well be initialized once per instance of each fieldset.
14706				 * Determines the following business rules:
14707				 *  -Only allow steps if elements are in proper order fieldset -> legend -> div
14708				 *  -Only allow NEXT button on first step
14709				 *  -Only allow final SUBMIT button on last step
14710				 */
14711				var fieldset = fieldsets[ i ],
14712					isFirstFieldset = ( i === 0 ) ? true : false,
14713					isLastFieldset = ( i === ( len - 1 ) ) ? true : false,
14714					legend = fieldset.firstElementChild,
14715					div = ( legend && legend.tagName === "LEGEND" ) ? legend.nextElementSibling : false,
14716					buttonGroup = document.createElement( "div" ),
14717					wrapper = document.createElement( "div" ),
14718					buttonGroupClassList = buttonGroup.classList,
14719					divClassList = div.classList;
14720
14721				buttonGroupClassList.add( "buttons" );
14722				fieldset.parentNode.insertBefore( wrapper, fieldset );
14723				wrapper.appendChild( fieldset );
14724				wrapper.classList.add( "steps-wrapper" );
14725
14726				if ( div && div.tagName === "DIV" ) {
14727					var btnClone;
14728					hasStepsInitialized = true;
14729
14730					if ( !isFirstFieldset ) {
14731						btnClone = btnPrevious.cloneNode( true );
14732						setStepsBtnEvent( btnClone );
14733						buttonGroup.appendChild( btnClone );
14734						wrapper.appendChild( buttonGroup );
14735					}
14736
14737					if ( !isLastFieldset ) {
14738						btnClone = btnNext.cloneNode( true );
14739						setStepsBtnEvent( btnClone );
14740						buttonGroup.appendChild( btnClone );
14741					} else {
14742						buttonGroup.appendChild( btnSubmit );
14743					}
14744
14745					wrapper.appendChild( buttonGroup );
14746
14747					fieldset.classList.add( "wb-tggle-fildst" );
14748					divClassList.add( "hidden" );
14749					buttonGroupClassList.add( "hidden" );
14750
14751					if ( isFirstFieldset ) {
14752						legend.classList.add( "wb-steps-active" );
14753						btnClone.classList.remove( "hidden" );
14754						divClassList.remove( "hidden" );
14755						buttonGroupClassList.remove( "hidden" );
14756					}
14757				}
14758			}
14759
14760			/*
14761			 * if steps has initialized hide any precreated submit or reset buttons
14762			 */
14763			if ( form && hasStepsInitialized ) {
14764				$( form ).children( "input" ).hide();
14765				wb.ready( $( elm ), componentName );
14766			}
14767		}
14768	},
14769
14770	/**
14771	 * @method createStepsButton
14772	 * @param {string var} tagName, {string var} type, {boolean var} isPrimary, {string var} style, {string var} text
14773	 * @returns {Object} A ready-to-use button element
14774	 */
14775	createStepsButton = function( type, style, text ) {
14776		var control = document.createElement( "BUTTON" );
14777
14778		// set default attributes
14779		control.className = ( type === "prev" ? "btn btn-md btn-default" : "btn btn-md btn-primary" ) + " " + style;
14780		control.setAttribute( "type", "button" );
14781		control.innerHTML = text;
14782
14783		return control;
14784	},
14785
14786	/**
14787	 * @method setStepsBtnEvent
14788	 * @param {JavaScript element} elm
14789	 */
14790	setStepsBtnEvent = function( elm ) {
14791		elm.addEventListener( "click", function( evt ) {
14792			evt.preventDefault();
14793			var classes = ( this.className ) ? this.className : false,
14794				isNext = ( classes && classes.indexOf( "btn-primary" ) > -1 ),
14795				isFormValid = true,
14796				parentElement = this.parentElement,
14797				parentParentElement = parentElement.parentElement,
14798				parentPreviousClassList = parentElement.previousElementSibling.classList;
14799
14800			// confirm if form is valid
14801			if ( isNext && jQuery.validator && jQuery.validator !== "undefined" ) {
14802				isFormValid =  $( "#" + parentParentElement.parentElement.id ).valid();
14803			}
14804
14805			// continue if valid
14806			if ( isFormValid ) {
14807				showSteps( parentParentElement, isNext );
14808				if ( isNext ) {
14809					parentPreviousClassList.remove( "wb-steps-error" );
14810				}
14811			} else if ( isNext && !isFormValid ) {
14812				parentPreviousClassList.add( "wb-steps-error" );
14813			}
14814		} );
14815	},
14816
14817	/**
14818	 * @method showSteps
14819	 * @param {JavaScript element} elm and {boolean var} isNext
14820	 */
14821	showSteps = function( elm, isNext ) {
14822		var fieldsetElement = elm.getElementsByTagName( "FIELDSET" )[ 0 ],
14823			fields = fieldsetElement.getElementsByTagName( "div" )[ 0 ],
14824			legend = fieldsetElement.getElementsByTagName( "legend" )[ 0 ],
14825			buttonGroup = elm.querySelector( "div.buttons" ),
14826			fieldset;
14827
14828		if ( elm ) {
14829			fields.classList.add( "hidden" );
14830			buttonGroup.classList.add( "hidden" );
14831
14832			if ( legend ) {
14833				legend.classList.remove( "wb-steps-active" );
14834			}
14835
14836			fieldset = ( !isNext ) ? elm.previousElementSibling : elm.nextElementSibling;
14837			if ( fieldset ) {
14838				legend = fieldset.getElementsByTagName( "LEGEND" )[ 0 ];
14839				elm = fieldset.getElementsByTagName( "DIV" )[ 0 ];
14840				buttonGroup = fieldset.querySelector( "div.buttons" );
14841				if ( legend ) {
14842					legend.classList.add( "wb-steps-active" );
14843					legend.tabIndex = 0;
14844					legend.focus();
14845					legend.tabIndex = -1;
14846				}
14847				if ( elm ) {
14848					elm.classList.remove( "hidden" );
14849				}
14850				if ( buttonGroup ) {
14851					buttonGroup.classList.remove( "hidden" );
14852				}
14853			}
14854		}
14855	};
14856
14857// Bind the init event of the plugin
14858$document.on( "timerpoke.wb " + initEvent, selector, init );
14859
14860// Add the timer poke to initialize the plugin
14861wb.add( selector );
14862
14863} )( jQuery, window, document, wb );
14864
14865/**
14866 * @title WET-BOEW Tables
14867 * @overview Integrates the DataTables plugin into WET providing searching, sorting, filtering, pagination and other advanced features for tables.
14868 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
14869 * @author @jeresiv
14870 */
14871/*jshint scripturl:true*/
14872( function( $, window, wb ) {
14873"use strict";
14874
14875/*
14876 * Variable and function definitions.
14877 * These are global to the plugin - meaning that they will be initialized once per page,
14878 * not once per instance of plugin on the page. So, this is a good place to define
14879 * variables that are common to all instances of the plugin on a page.
14880 */
14881var componentName = "wb-tables",
14882	selector = "." + componentName,
14883	initEvent = "wb-init" + selector,
14884	$document = wb.doc,
14885	idCount = 0,
14886	i18n, i18nText, defaults,
14887
14888	/**
14889	 * @method init
14890	 * @param {jQuery Event} event Event that triggered the function call
14891	 */
14892	init = function( event ) {
14893
14894		// Start initialization
14895		// returns DOM object = proceed with init
14896		// returns undefined = do not proceed with init (e.g., already initialized)
14897		var elm = wb.init( event, componentName, selector ),
14898			elmId;
14899
14900		if ( elm ) {
14901			elmId = elm.id;
14902
14903			// Ensure there is a unique id on the element
14904			if ( !elmId ) {
14905				elmId = componentName + "-id-" + idCount;
14906				idCount += 1;
14907				elm.id = elmId;
14908			}
14909
14910			// Only initialize the i18nText once
14911			if ( !i18nText ) {
14912				i18n = wb.i18n;
14913				i18nText = {
14914					aria: {
14915						sortAscending: i18n( "sortAsc" ),
14916						sortDescending: i18n( "sortDesc" )
14917					},
14918					emptyTable: i18n( "emptyTbl" ),
14919					info: i18n( "infoEntr" ),
14920					infoEmpty: i18n( "infoEmpty" ),
14921					infoFiltered: i18n( "infoFilt" ),
14922					lengthMenu: i18n( "lenMenu" ),
14923					loadingRecords: i18n( "load" ),
14924					paginate: {
14925						first: i18n( "first" ),
14926						last: i18n( "last" ),
14927						next: i18n( "nxt" ),
14928						previous: i18n( "prv" ),
14929						page: i18n( "page" )
14930					},
14931					processing: i18n( "process" ),
14932					search: i18n( "filter" ),
14933					thousands: i18n( "info1000" ),
14934					zeroRecords: i18n( "infoEmpty" ),
14935					tblFilterInstruction: i18n( "tbFilterInst" )
14936				};
14937			}
14938
14939			defaults = {
14940				asStripeClasses: [],
14941				language: i18nText,
14942				dom: "<'top'fil>rt<'bottom'p><'clear'>"
14943			};
14944
14945			Modernizr.load( {
14946				load: [ "site!deps/jquery.dataTables" + wb.getMode() + ".js" ],
14947				testReady: function() {
14948					return ( $.fn.dataTable && $.fn.dataTable.version );
14949				},
14950				complete: function() {
14951					var $elm = $( "#" + elmId ),
14952						dataTableExt = $.fn.dataTableExt,
14953						settings = wb.getData( $elm, componentName ) || {};
14954
14955					// Explicitly deactivate the paging for the filterEmphasis provisional feature/styling when not configured
14956					if ( $elm.hasClass( "provisional" ) && $elm.hasClass( "filterEmphasis" ) ) {
14957						settings.paging = settings.paging ? settings.paging : false;
14958					}
14959
14960					/*
14961					 * Extend sorting support
14962					 */
14963					$.extend( dataTableExt.type.order, {
14964
14965						// Enable internationalization support in the sorting
14966						"html-pre": function( a ) {
14967							return wb.normalizeDiacritics(
14968								!a ? "" : a.replace ?
14969									a.replace( /<.*?>/g, "" ).toLowerCase() : a + ""
14970							);
14971						},
14972						"string-case-pre": function( a ) {
14973							return wb.normalizeDiacritics( a );
14974						},
14975						"string-pre": function( a ) {
14976							return wb.normalizeDiacritics( a );
14977						},
14978
14979						// Formatted number sorting
14980						"formatted-num-asc": function( a, b ) {
14981							return wb.formattedNumCompare( a, b );
14982						},
14983						"formatted-num-desc": function( a, b ) {
14984							return wb.formattedNumCompare( b, a );
14985						}
14986					} );
14987
14988					// Create the DataTable object
14989					$elm.dataTable( $.extend( true, {}, defaults, window[ componentName ], settings ) );
14990				}
14991			} );
14992		}
14993	},
14994	updatePaginationMarkup = function( $pagination, setFocusOnId ) {
14995
14996		var ol = document.createElement( "OL" ),
14997			li = document.createElement( "LI" ),
14998			paginate_buttons = $pagination.find( ".paginate_button" ),
14999			navFocusOnId;
15000
15001		if ( $pagination.length === 0 ) {
15002			return;
15003		}
15004
15005		// Set the element to get the focus upon navigation
15006		navFocusOnId = setFocusOnId || $pagination.get( 0 ).id;
15007
15008		// Update Pagination List
15009		for ( var i = 0; i < paginate_buttons.length; i++ ) {
15010			var item = li.cloneNode( true );
15011			item.appendChild( paginate_buttons[ i ] );
15012			ol.appendChild( item );
15013		}
15014
15015		ol.className = "pagination mrgn-tp-0 mrgn-bttm-0";
15016		$pagination.empty();
15017		$pagination.append( ol );
15018
15019		// Update the aria-pressed properties on the pagination buttons
15020		// Should be pushed upstream to DataTables
15021		$pagination.find( ".paginate_button" )
15022			.attr( {
15023				"href": "#" + navFocusOnId
15024			} )
15025
15026			// This is required to override the datatable.js (v1.10.13) behavior to cancel the event propagation on anchor element.
15027			.on( "keypress", function( evn ) {
15028				if ( evn.keyCode === 13 ) {
15029					window.location = evn.target.href;
15030				}
15031			} )
15032
15033			.not( ".previous, .next" )
15034			.attr( "aria-pressed", "false" )
15035			.html( function( index, oldHtml ) {
15036				return "<span class='wb-inv'>" + i18nText.paginate.page + " </span>" + oldHtml;
15037			} )
15038			.filter( ".current" )
15039			.attr( "aria-pressed", "true" );
15040	};
15041
15042// Bind the init event of the plugin
15043$document.on( "timerpoke.wb " + initEvent, selector, init );
15044
15045// Handle the draw.dt event
15046$document.on( "draw.dt", selector, function( event, settings ) {
15047	var $elm = $( event.target ),
15048		pagination = $elm.next( ".bottom" ).find( "div:first-child" ),
15049		pagination_top = $elm.prevAll( ".top" ).find( "div.dataTables_paginate" ),
15050		paginate_buttons = $elm.next( ".bottom" ).find( ".paginate_button" ),
15051		pbLength = paginate_buttons.length,
15052		pHasLF = pagination.find( ".last, .first" ).length === 2,
15053		pHasPN = pagination.find( ".previous, .next" ).length === 2;
15054
15055	// Handle sorting/ordering
15056	var order = $elm.dataTable( { "retrieve": true } ).api().order();
15057	$elm.find( "th" ).each( function( index ) {
15058		var $th = $( this ),
15059			$btn = $th.find( "button" );
15060		if ( order && order.length && order[ 0 ][ 0 ] === index ) {
15061			var label = ( order[ 0 ][ 1 ] === "desc" ) ? i18nText.aria.sortAscending : i18nText.aria.sortDescending;
15062			label = $btn.text() + label.replace( /&#160;/g, " " );
15063			$btn.attr( "title", label );
15064		}
15065		$th.removeAttr( "aria-label" );
15066	} );
15067
15068	// Determine if Pagination required
15069	if (
15070		pbLength === 1 ||
15071		(
15072			pbLength === 3 &&
15073			(
15074				pHasLF ||
15075				pHasPN
15076			)
15077		) ||
15078		(
15079			pbLength === 5 &&
15080			pHasLF &&
15081			pHasPN
15082		)
15083	) {
15084		pagination.addClass( "hidden" );
15085		pagination_top.addClass( "hidden" );
15086	} else {
15087
15088		// Make sure Pagination is visible
15089		pagination.removeClass( "hidden" );
15090		pagination_top.removeClass( "hidden" );
15091
15092		updatePaginationMarkup( pagination, $elm.get( 0 ).id );
15093		updatePaginationMarkup( pagination_top );
15094	}
15095
15096	// Identify that the table has been updated
15097	$elm.trigger( "wb-updated" + selector, [ settings ] );
15098} );
15099
15100// Identify that initialization has completed
15101$document.on( "init.dt", selector, function( event ) {
15102	var $elm = $( event.target ),
15103		settings = $.extend( true, {}, defaults, window[ componentName ], wb.getData( $elm, componentName ) );
15104
15105	// Handle sorting/ordering
15106	var ordering = ( settings && settings.ordering === false ) ? false : true;
15107	if ( ordering ) {
15108		$elm.find( "thead th" ).each( function() {
15109			var $th = $( this ),
15110				label = ( $th.attr( "aria-sort" ) === "ascending" ) ? i18nText.aria.sortDescending : i18nText.aria.sortAscending;
15111			if ( ( $th.attr( "data-orderable" ) !== "false" ) && !( $th.hasClass( "sorting_disabled" ) ) ) {
15112				$th.html( "<button type='button' aria-controls='" + $th.attr( "aria-controls" ) +  "' title='" + $th.text().replace( /'/g, "&#39;" ) + label + "'>" + $th.html() + "<span class='sorting-cnt'><span class='sorting-icons' aria-hidden='true'></span></span></button>" );
15113				$th.removeAttr( "aria-label tabindex aria-controls" );
15114			}
15115		} );
15116		$elm.attr( "aria-label", i18nText.tblFilterInstruction );
15117	}
15118
15119	// Apply the filter emphasis style
15120	if ( $elm.hasClass( "provisional" ) && $elm.hasClass( "filterEmphasis" ) ) {
15121		$elm.parent().addClass( "provisional filterEmphasis" );
15122	}
15123
15124	wb.ready( $( event.target ), componentName );
15125} );
15126
15127// Handle the draw.dt event
15128$document.on( "submit", ".wb-tables-filter", function( event ) {
15129
15130	event.preventDefault();
15131
15132	var $form = $( this ),
15133		$datatable = $( "#" + $form.data( "bind-to" ) ).dataTable( { "retrieve": true } ).api(),
15134		$toNumber = function stringToNumber( number ) {
15135			number = number.replace( /[^0-9\-,.]+/g, "" );
15136			if ( /[,]\d{1,2}$/.test( number ) ) {
15137				number = number.replace( /(\d{2})$/, ".$1" );
15138			}
15139			number = number.replace( /,/g, "" );
15140			return parseFloat( number );
15141		},
15142		$isDate = function isDate( date ) {
15143			return date instanceof Date && !isNaN( date );
15144		};
15145
15146	// Lets reset the search
15147	$datatable.search( "" ).columns().search( "" );
15148
15149	// Lets loop through all options
15150	var $prevCol = -1, $cachedVal = "";
15151	$form.find( "[name]" ).each( function() {
15152		var $elm = $( this ),
15153			$val = $elm.val(),
15154			$value = "",
15155			$regex = "",
15156			$column = parseInt( $elm.attr( "data-column" ), 10 ),
15157			$isAopts = $elm.data( "aopts" ),
15158			$aoptsSelector = "[data-aopts*='\"column\": \"" + $column + "\"']:checked",
15159			$aopts = $( $aoptsSelector ),
15160			$aoType = ( $aopts && $aopts.data( "aopts" ) ) ? $aopts.data( "aopts" ).type.toLowerCase() : "",
15161			$fData;
15162
15163		// Ignore the advanced options fields
15164		if ( $isAopts ) {
15165			return;
15166		}
15167
15168		// Verifies if filtering the same column
15169		if ( $column !== $prevCol || $prevCol === -1 ) {
15170			$cachedVal = "";
15171		}
15172		$prevCol = $column;
15173
15174		// Filters based on input type
15175		if ( $elm.is( "select" ) ) {
15176			$value = $elm.find( "option:selected" ).val();
15177		} else if ( $elm.is( "input[type='number']" ) ) {
15178			var $minNum, $maxNum = null;
15179
15180			// Retain minimum number (always the first number input)
15181			if ( $cachedVal === "" ) {
15182				$cachedVal = parseFloat( $val );
15183				$cachedVal = ( $cachedVal ) ? $cachedVal : "-0";
15184			} else {
15185				$maxNum = parseFloat( $val );
15186				$maxNum = ( $maxNum ) ? $maxNum : "-0";
15187			}
15188			$minNum = $cachedVal;
15189
15190			//Number filtering logic needs to be reviewed in order to remove the "-0" value (issue #9235)
15191			// Generates a list of numbers (within the min and max number)
15192			if ( !isNaN( $minNum ) && !isNaN( $maxNum ) ) {
15193				$fData = $datatable.column( $column ).data().filter( function( obj ) {
15194					var $num = $toNumber( obj.toString() );
15195
15196					if ( !isNaN( $num ) ) {
15197						if ( $aoType === "and" ) {
15198							if ( $cachedVal !== $maxNum && $cachedVal !== "-0" && $maxNum !== "0" && $num >= $minNum && $num <= $maxNum ) {
15199								return true;
15200							}
15201						} else {
15202							if ( $maxNum === null ) { // only one input number
15203								return $minNum === "-0" || $minNum === $num;
15204							} else if ( $maxNum === $cachedVal && $cachedVal === "-0" ) { // both are empty
15205								return true;
15206							} else if ( $maxNum !== "-0" && $minNum === $maxNum && $num === $maxNum ) { // min and max are the same
15207								return true;
15208							} else if ( $maxNum === "-0" && $num >= $minNum ) { // max number is missing
15209								return true;
15210							} else if ( $cachedVal === "-0" && $num <= $maxNum ) { // min number is missing
15211								return true;
15212							} else if ( $cachedVal !== "-0" && $num >= $minNum && $num <= $maxNum ) { // min and max are present
15213								return true;
15214							}
15215						}
15216					}
15217					return false;
15218				} );
15219				$fData = $fData.join( "|" );
15220
15221				// If no numbers match set as -0, so no results return
15222				$value = ( $fData ) ? $fData : "-0";
15223				$regex = "(" + $value.replace( /&nbsp;|\s/g, "\\s" ).replace( /\$/g, "\\$" ) + ")";
15224			}
15225		} else if ( $elm.is( "input[type='date']" ) && $val ) {
15226			var $minDate, $maxDate;
15227
15228			// Retain minimum date (always the first date input)
15229			if ( $cachedVal === "" ) {
15230				$cachedVal = new Date( $val );
15231				$cachedVal.setDate( $cachedVal.getDate() + 1 );
15232				$cachedVal.setHours( 0, 0, 0, 0 );
15233			}
15234			$minDate = $cachedVal;
15235
15236			// Maximum date is always the current selected date
15237			$maxDate = new Date( $val );
15238			$maxDate.setDate( $maxDate.getDate() + 1 );
15239			$maxDate.setHours( 23, 59, 59, 999 );
15240
15241			// Generates a list of date strings (within the min and max date)
15242			$fData = $datatable.column( $column ).data().filter( function( obj ) {
15243				var $date = obj.replace( /[0-9]{2}\s[0-9]{2}:/g, function( e ) {
15244					return e.replace( /\s/g, "T" );
15245				} );
15246
15247				if ( !$date.includes( "T" ) ) {
15248					$date = $date + "T00:00:00";
15249				}
15250				$date = new Date( $date );
15251				$date.setHours( 0, 0, 0, 0 );
15252
15253				if ( !$isDate( $minDate ) || !$isDate( $maxDate ) || !$isDate( $date ) ) {
15254					return;
15255				}
15256				return ( $date >= $minDate && $date <= $maxDate );
15257			} );
15258			$fData = $fData.join( "|" );
15259
15260			// If no dates match set as element value, so no results return
15261			$value = ( $fData ? $fData : $val );
15262		} else if ( $elm.is( ":checkbox" ) ) {
15263
15264			// Verifies if checkbox is checked before setting value
15265			if ( $elm.is( ":checked" ) ) {
15266				if ( $aoType === "both" ) {
15267					$cachedVal += "(?=.*\\b" + $val + "\\b)";
15268				} else {
15269					$cachedVal += ( $cachedVal.length > 0 ) ? "|" : "";
15270					$cachedVal += $val;
15271				}
15272
15273				$value = $cachedVal;
15274				$value = $value.replace( /\s/g, "\\s*" );
15275
15276				// Adjust regex based on advanced options
15277				switch ( $aoType ) {
15278					case "both":
15279						$regex = "(" + $value + ").*";
15280						break;
15281					case "either":
15282						$regex = "^(" + $value + ")$";
15283						break;
15284					case "and":
15285						$regex = ( $value.indexOf( "|" ) > -1 ) ? "^(" + $value + "|[,\\s])(" + $value + "|[,\\s])+$" : "(" + $value + ")";
15286						break;
15287					case "any":
15288					default:
15289						$regex = "(" + $value + ")";
15290						break;
15291				}
15292			}
15293		} else {
15294			$value = $val;
15295		}
15296
15297		if ( $value ) {
15298
15299			// Verifies if regex was preset, if not preset use 'contains value' as default
15300			if ( !$regex ) {
15301				if ( $elm[ 0 ].getAttribute( "data-exact" ) ) {
15302					$regex = "^" + $value + "$";
15303				} else {
15304					$value = $value.replace( /\s/g, "\\s*" );
15305					$regex = "(" + $value + ")";
15306				}
15307			}
15308
15309			$datatable.column( $column ).search( $regex, true );
15310		}
15311	} );
15312	$datatable.draw();
15313	return false;
15314} );
15315
15316$document.on( "click", ".wb-tables-filter [type='reset']", function( event ) {
15317	event.preventDefault();
15318
15319	var $form = $( this ).closest( ".wb-tables-filter" ),
15320		$datatable = $( "#" + $form.data( "bind-to" ) ).dataTable( { "retrieve": true } ).api();
15321
15322	$datatable.search( "" ).columns().search( "" ).draw();
15323
15324	$form.find( "select" ).prop( "selectedIndex", 0 );
15325	$form.find( "input:checkbox, input:radio" ).prop( "checked", false );
15326	$form.find( ":input" ).not( ":button, :submit, :reset, :hidden, :checkbox, :radio" ).val( "" );
15327	return false;
15328} );
15329
15330// Add the timer poke to initialize the plugin
15331wb.add( selector );
15332
15333} )( jQuery, window, wb );
15334
15335/**
15336 * @title WET-BOEW Tabbed interface
15337 * @overview Dynamically stacks multiple sections of content, transforming them into a tabbed interface.
15338 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
15339 * @author WET Community
15340 */
15341( function( $, window, wb ) {
15342"use strict";
15343
15344/*
15345 * Variable and function definitions.
15346 * These are global to the plugin - meaning that they will be initialized once per page,
15347 * not once per instance of plugin on the page. So, this is a good place to define
15348 * variables that are common to all instances of the plugin on a page.
15349 */
15350var componentName = "wb-tabs",
15351	selector = "." + componentName,
15352	initEvent = "wb-init" + selector,
15353	shiftEvent = "wb-shift" + selector,
15354	selectEvent = "wb-select" + selector,
15355	updatedEvent = "wb-updated" + selector,
15356	setFocusEvent = "setfocus.wb",
15357	controls = selector + " ul[role=tablist] a, " + selector + " ul[role=tablist] .tab-count",
15358	initialized = false,
15359	tabsAccordionClass = "tabs-acc",
15360	nestedTglPanelSelector = "> .tabpanels > details > .tgl-panel",
15361	activePanel = "-activePanel",
15362	activateEvent = "click keydown",
15363	pagePath = wb.pageUrlParts.pathname + "#",
15364	$document = wb.doc,
15365	i18n, i18nText,
15366
15367	// Includes "smallview", "xsmallview" and "xxsmallview"
15368	smallViewPattern = "smallview",
15369	isSmallView, oldIsSmallView,
15370
15371	defaults = {
15372		excludePlay: false,
15373		interval: 6,
15374		updateHash: false,
15375		ignoreSession: false
15376	},
15377
15378	/**
15379	 * @method init
15380	 * @param {jQuery Event} event Event that triggered the function call
15381	 */
15382	init = function( event ) {
15383
15384		// Start initialization
15385		// returns DOM object = proceed with init
15386		// returns undefined = do not proceed with init (e.g., already initialized)
15387		var elm = wb.init( event, componentName, selector ),
15388			isCarousel = true,
15389			open = "open",
15390			hashTargetLen = 0,
15391			$panels, $tablist, activeId, $openPanel, $elm, elmId, $hashTarget,
15392			settings, $panel, i, len, tablist, isOpen, hashFocus,
15393			newId, positionY, groupClass, $tabPanels, openByHash;
15394
15395		if ( elm ) {
15396			$elm = $( elm );
15397
15398			// For backwards compatibility. Should be removed in WET v4.1
15399			if ( $elm.children( ".tabpanels" ).length === 0 ) {
15400				$elm.children( "[role=tabpanel], details" ).wrapAll( "<div class='tabpanels'></div>" );
15401			}
15402
15403			$panels = $elm.find( "> .tabpanels > [role=tabpanel], > .tabpanels > details" );
15404			$tablist = $elm.children( "[role=tablist]" );
15405			isCarousel = $tablist.length !== 0;
15406
15407			// If a carousel contains only 1 panel, remove its controls, visually-hide its thumbnails and prevent it from attempting to play
15408			if ( isCarousel && $panels.length === 1 ) {
15409
15410				$elm.removeClass( "show-thumbs playing" );
15411				$elm.addClass( "exclude-controls" );
15412			}
15413
15414			activeId = wb.jqEscape( wb.pageUrlParts.hash.substring( 1 ) );
15415			hashFocus = activeId.length !== 0;
15416			$openPanel = hashFocus ? $panels.filter( "#" + activeId ) : undefined;
15417			openByHash = $openPanel && $openPanel.length !== 0;
15418			elmId = elm.id;
15419
15420			settings = $.extend(
15421				true,
15422				{},
15423				defaults,
15424				{
15425					interval: $elm.hasClass( "slow" ) ?
15426						9 : $elm.hasClass( "fast" ) ?
15427							3 : defaults.interval,
15428					excludeControls: $elm.hasClass( "exclude-controls" ),
15429					excludePlay: $elm.hasClass( "exclude-play" ),
15430					updateHash: $elm.hasClass( "update-hash" ),
15431					playing: $elm.hasClass( "playing" ),
15432					ignoreSession: $elm.hasClass( "ignore-session" )
15433				},
15434				window[ componentName ],
15435				wb.getData( $elm, componentName )
15436			);
15437
15438			try {
15439
15440				// If the panel was not set by URL hash
15441				if ( !openByHash ) {
15442					if ( hashFocus ) {
15443						$hashTarget = $panels.find( "#" + activeId );
15444						hashTargetLen = $hashTarget.length;
15445					}
15446
15447					// If the anchor target is within a panel, then open that panel
15448					if ( hashTargetLen !== 0 ) {
15449						activeId = $hashTarget.closest( "[role=tabpanel]" ).attr( "id" );
15450
15451					// Attempt to retrieve active panel from sessionStorage
15452					} else {
15453						if ( !settings.ignoreSession ) {
15454							activeId = sessionStorage.getItem( pagePath + elmId + activePanel );
15455						}
15456					}
15457
15458					if ( activeId ) {
15459						$openPanel = $panels.filter( "#" + activeId );
15460					}
15461
15462				// If the panel was set by URL hash, then store in sessionStorage
15463				} else {
15464					if ( !settings.ignoreSession ) {
15465						try {
15466							sessionStorage.setItem( pagePath + elmId + activePanel, activeId );
15467						} catch ( error ) {
15468
15469							/* swallow error */
15470						}
15471					}
15472				}
15473			} catch ( error ) {
15474
15475				/* swallow error */
15476			}
15477
15478			// Determine the current view
15479			isSmallView = document.documentElement.className.indexOf( smallViewPattern ) !== -1;
15480
15481			// Only initialize the i18nText once
15482			if ( !i18nText ) {
15483				i18n = wb.i18n;
15484				i18nText = {
15485					prev: i18n( "prv" ),
15486					next: i18n( "nxt" ),
15487					play: i18n( "tab-play" ),
15488					rotStart: i18n( "tab-rot" ).on,
15489					rotStop: i18n( "tab-rot" ).off,
15490					space: i18n( "space" ),
15491					hyphen: i18n( "hyphen" ),
15492					pause: i18n( "pause" ),
15493					tabCount: i18n( "lb-curr" )
15494				};
15495			}
15496
15497			// Build the tablist and enhance the panels as needed for details/summary
15498			if ( !isCarousel ) {
15499				$elm.addClass( tabsAccordionClass );
15500				groupClass = elmId + "-grp";
15501				$tabPanels = $elm.children( ".tabpanels" );
15502				$panels = $tabPanels.children( "details" );
15503				len = $panels.length;
15504
15505				$tabPanels.detach();
15506
15507				// Ensure there is only one panel open
15508				// Order of priority is hash, open property, first details
15509				if ( !$openPanel || $openPanel.length === 0 ) {
15510					$openPanel = $panels.filter( "[open]" ).first();
15511					if ( $openPanel.length === 0 ) {
15512						$openPanel = $panels.eq( 0 );
15513					}
15514				}
15515				$panels.removeAttr( open );
15516				$openPanel.attr( open, open );
15517
15518				// Hide the tablist in small view and the summary elements in large view
15519				tablist = "<ul role='tablist' aria-live='off' class='generated'>";
15520
15521				for ( i = 0; i !== len; i += 1 ) {
15522					$panel = $panels.eq( i );
15523					$panel
15524						.addClass( groupClass )
15525						.html(
15526							$panel.html()
15527								.replace( /(<\/summary>)/i, "$1<div class='tgl-panel'>" ) +
15528							"</div>"
15529						);
15530
15531					newId = $panel.attr( "id" );
15532					if ( !newId ) {
15533						newId = wb.getId();
15534						$panel.attr( "id", newId );
15535					}
15536					isOpen = !!$panel.attr( open );
15537
15538					if ( isSmallView ) {
15539						if ( !wb.supportsDetails ) {
15540							$panel.toggleClass( "open", isOpen );
15541						}
15542					} else {
15543						$panel.attr( {
15544							role: "tabpanel",
15545							open: open
15546						} );
15547						$panel.addClass( ( wb.supportsDetails ? "" : open + " " ) + "fade " + ( isOpen ? "in" : "noheight out wb-inv" ) );
15548					}
15549
15550					tablist += "<li" + ( isOpen ? " class='active'" : "" ) +
15551						"><a id='" + newId + "-lnk' href='#" + newId + "'>" +
15552						$panel.children( "summary" ).html() + "</a></li>";
15553				}
15554
15555				$tablist = $( tablist + "</ul>" );
15556				$tabPanels.find( "> details > summary" )
15557					.addClass( "wb-toggle tgl-tab" )
15558					.attr( "data-toggle", "{\"parent\": \"#" + elmId +
15559						"\", \"group\": \"." + groupClass + "\"}" );
15560
15561				$elm
15562					.prepend( $tablist )
15563					.append( $tabPanels )
15564					.trigger( "wb-init.wb-toggle" );
15565			} else if ( $openPanel && $openPanel.length !== 0 ) {
15566				$panels.filter( ".in" )
15567					.addClass( "out noheight" )
15568					.removeClass( "in" );
15569				$openPanel
15570					.addClass( "in" )
15571					.removeClass( "out noheight" );
15572				$tablist.find( ".active" )
15573					.removeClass( "active" );
15574				$tablist.find( "a" )
15575					.filter( "[href$='" + activeId + "']" )
15576					.parent()
15577					.addClass( "active" );
15578			}
15579
15580			drizzleAria( $panels, $tablist );
15581
15582			if ( isCarousel ) {
15583
15584				// Returns true if the tabs should be rotating automatically
15585				if ( createControls( $tablist, settings ) ) {
15586
15587					// Register this specific tabs instance for timerpoke.wb events
15588					wb.add( "#" + elmId + selector );
15589				}
15590			}
15591
15592			// If focus is being set by the URL hash, then ensure the tabs are
15593			// not above the top of the viewport (if the panel was the target),
15594			// or the anchor is not above the top of viewport (if the anchor was
15595			// the target)
15596			if ( hashFocus ) {
15597
15598				// Need a slight delay to allow for the reflow
15599				setTimeout( function() {
15600					if ( openByHash ) {
15601						positionY = $tablist.offset().top;
15602					} else if ( hashTargetLen !== 0 ) {
15603						positionY = $hashTarget.offset().top;
15604					} else {
15605						positionY = -1;
15606					}
15607
15608					if ( positionY !== -1 && positionY < document.body.scrollTop ) {
15609						document.body.scrollTop = positionY;
15610					}
15611				}, 1 );
15612			}
15613
15614			$elm.data( {
15615				"wb-tabs": {
15616					panels: $panels,
15617					tablist: $tablist,
15618					settings: settings,
15619					ctime: 0
15620				}
15621			} );
15622
15623			initialized = true;
15624			onResize( $elm );
15625
15626			// Update the URL hash if needed
15627			if ( settings.updateHash ) {
15628				updateHash( $openPanel[ 0 ] );
15629			}
15630
15631			// Identify that initialization has completed
15632			wb.ready( $elm, componentName );
15633		}
15634	},
15635
15636	/**
15637	 * @method onTimerPoke
15638	 * @param {jQuery DOM element} $elm The plugin element
15639	 */
15640	onTimerPoke = function( $elm ) {
15641		var data = $elm.data( componentName ),
15642			delayCurrent = parseFloat( data.ctime ) + 0.5;
15643
15644		// Check if we need to rotate panels
15645		if ( parseFloat( data.settings.interval ) <= delayCurrent ) {
15646			$elm.trigger( shiftEvent );
15647			delayCurrent = 0;
15648		}
15649
15650		data.ctime = delayCurrent;
15651		$elm.data( componentName, data );
15652	},
15653
15654	/**
15655	 * @method createControls
15656	 * @param {jQuery DOM element} $tablist The plugin element
15657	 * @param {object} settings Settings for the tabs instance
15658	 * @returns {boolean} Whether or not the tabs should be rotating initially
15659	 */
15660	createControls = function( $tablist, settings ) {
15661		var prevText = i18nText.prev,
15662			nextText = i18nText.next,
15663			spaceText = i18nText.space,
15664			excludeControls = settings.excludeControls,
15665			excludePlay = settings.excludePlay,
15666			isPlaying = !excludeControls && !excludePlay && settings.playing,
15667			state = isPlaying ? i18nText.pause : i18nText.play,
15668			hidden = isPlaying ? i18nText.rotStop : i18nText.rotStart,
15669			glyphiconStart = "<span class='glyphicon glyphicon-",
15670			wbInvStart = "<span class='wb-inv'>",
15671			tabsToggleStart = "<li class='control ",
15672			btnMiddle = "' href='javascript:;' role='button' title='",
15673			btnEnd = "</span></a></li> ",
15674			iconState = glyphiconStart + ( isPlaying ? "pause" : "play" ) + "'></span>",
15675			$tabs = $tablist.find( "[role=tab]" ),
15676			currentIndex = $tabs.index( $tabs.filter( "[aria-selected=true]" ) ) + 1,
15677			i18nTabCount = i18nText.tabCount,
15678			firstReplaceIndex = i18nTabCount.indexOf( "%" ),
15679			lastReplaceIndex = i18nTabCount.lastIndexOf( "%" ) + 1,
15680			prevControl = tabsToggleStart + "prv'><a class='prv" + btnMiddle +
15681				prevText + "'>" + glyphiconStart + "chevron-left'></span>" +
15682				wbInvStart + prevText + btnEnd,
15683			tabCount = tabsToggleStart + " tab-count' tabindex='0'><div>" +
15684				i18nTabCount.substring( 0, firstReplaceIndex ) +
15685				"<div class='curr-count'>" +
15686				i18nTabCount.substring( firstReplaceIndex, lastReplaceIndex )
15687					.replace( "%curr%", "<span class='curr-index'>" + currentIndex + "</span>" )
15688					.replace( "%total%", $tabs.length ) +
15689				"</div>" + i18nTabCount.substring( lastReplaceIndex ) +
15690				"</div></li>",
15691			nextControl = tabsToggleStart + "nxt'><a class='nxt" + btnMiddle +
15692				nextText + "'>" + glyphiconStart + "chevron-right'></span>" +
15693				wbInvStart + nextText + btnEnd,
15694			playControl =  tabsToggleStart + "plypause'><a class='plypause" +
15695				btnMiddle + state + "'>" + iconState + " <span>" + state +
15696				"</span>" + wbInvStart + spaceText + i18nText.hyphen + spaceText +
15697				hidden + btnEnd;
15698
15699		if ( !excludeControls ) {
15700			$tablist.prepend( prevControl + tabCount + nextControl );
15701		}
15702
15703		if ( !excludeControls && !excludePlay ) {
15704			$tablist.append( playControl );
15705		}
15706		$tablist.find( "a[role=button]" ).attr( "href", "javascript:;" );
15707
15708		return isPlaying;
15709	},
15710
15711	/**
15712	 * @method drizzleAria
15713	 * @param {jQuery DOM element} $panels Tabpanel groupings
15714	 * @param {jQuery DOM element} $tablist Pointers to the groupings
15715	 */
15716	drizzleAria = function( $panels, $tabList ) {
15717
15718		// Let's process the elements for aria
15719		var panels = $panels.get(),
15720			tabCounter = panels.length - 1,
15721			listItems = $tabList.children().get(),
15722			listCounter = listItems.length - 1,
15723			isDetails = $panels[ 0 ].nodeName.toLowerCase() === "details",
15724			isActive, item, link, panelId, activeFound;
15725
15726		for ( ; tabCounter !== -1; tabCounter -= 1 ) {
15727			item = panels[ tabCounter ];
15728			isActive = item.className.indexOf( "out" ) === -1;
15729
15730			if ( !isDetails || !isSmallView ) {
15731				item.setAttribute( "aria-hidden", isActive ? "false" : "true" );
15732				item.setAttribute( "aria-expanded", isActive ? "true" : "false" );
15733			}
15734			item.setAttribute( "aria-labelledby", item.id + "-lnk" );
15735		}
15736
15737		activeFound = false;
15738		for ( ; listCounter !== -1; listCounter -= 1 ) {
15739			item = listItems[ listCounter ];
15740			item.setAttribute( "role", "presentation" );
15741
15742			isActive = item.className.indexOf( "active" ) !== -1;
15743			if ( isActive ) {
15744				activeFound = true;
15745			} else if ( listCounter === 0 && !activeFound ) {
15746				isActive = true;
15747				item.className += " active";
15748			}
15749
15750			link = item.getElementsByTagName( "a" )[ 0 ];
15751			panelId = link.getAttribute( "href" ).substring( 1 );
15752
15753			link.tabIndex = isActive ? "0" : "-1";
15754			link.setAttribute( "role", "tab" );
15755			link.setAttribute( "aria-selected", isActive ? "true" : "false" );
15756			link.setAttribute( "aria-controls", panelId );
15757			link.id = panelId + "-lnk";
15758		}
15759		$tabList.attr( "aria-live", "off" );
15760	},
15761
15762	/**
15763	 * @method updateHash
15764	 * @param {DOM element} elm Tabpanel to be referenced in the URL hash
15765	 */
15766	updateHash = function( elm ) {
15767		var elmId = elm.id;
15768
15769		wb.ignoreHashChange = true;
15770		elm.id += "-off";
15771		window.location.hash = elmId;
15772		elm.id = elmId;
15773		wb.ignoreHashChange = false;
15774	},
15775
15776	updateNodes = function( $panels, $controls, $next, $control ) {
15777		var $tabs = $controls.find( "[role=tab]" ),
15778			newIndex = $tabs.index( $control ) + 1,
15779			$currPanel = $panels.filter( ".in" ),
15780			$container = $next.closest( selector ),
15781			tabSettings = $container.data( componentName ).settings,
15782			mPlayers = $currPanel.find( ".wb-mltmd-inited" ).get(),
15783			mPlayersLen = mPlayers.length,
15784			mPlayer, i, j, last;
15785
15786		// Handle the direction of the slide transitions
15787		if ( $currPanel[ 0 ].className.indexOf( "slide" ) !== -1 ) {
15788			i = $panels.index( $currPanel );
15789			j = $panels.index( $next );
15790			last = $panels.length - 1;
15791
15792			$panels.toggleClass(
15793				"reverse",
15794				( i > j && ( i !== last || j !== 0 ) ) || ( i === 0 && j === last )
15795			);
15796		}
15797
15798		$currPanel
15799			.removeClass( "in" )
15800			.addClass( "out noheight" )
15801			.attr( {
15802				"aria-hidden": "true",
15803				"aria-expanded": "false"
15804			} );
15805
15806		// Pause all multimedia players in the current panel
15807		for ( i = 0; i !== mPlayersLen; i += 1 ) {
15808			mPlayer = mPlayers[ i ];
15809			if ( mPlayer.player ) {
15810				mPlayer.player( "pause" );
15811			}
15812		}
15813
15814		$next
15815			.removeClass( "out noheight" )
15816			.addClass( "in" )
15817			.attr( {
15818				"aria-hidden": "false",
15819				"aria-expanded": "true"
15820			} );
15821
15822		$controls
15823			.find( ".active" )
15824			.removeClass( "active" )
15825			.children( "a" )
15826			.attr( {
15827				"aria-selected": "false",
15828				tabindex: "-1"
15829			} );
15830
15831		// Update the Item x of n
15832		$controls
15833			.find( ".curr-index" )
15834			.html( newIndex );
15835
15836		$control
15837			.attr( {
15838				"aria-selected": "true",
15839				tabindex: "0"
15840			} )
15841			.parent()
15842			.addClass( "active" );
15843
15844		// Update sessionStorage with the current active panel
15845		if ( !tabSettings.ignoreSession ) {
15846			try {
15847				sessionStorage.setItem(
15848					pagePath + $container.attr( "id" ) + activePanel,
15849					$next.attr( "id" )
15850				);
15851			} catch ( error ) {
15852
15853				/* swallow error */
15854			}
15855		}
15856
15857		// Update the URL hash if needed
15858		if ( tabSettings.updateHash ) {
15859			updateHash( $next[ 0 ] );
15860		}
15861
15862		// Identify that the tabbed interface/carousel was updated
15863		$container.trigger( updatedEvent, [ $next ] );
15864	},
15865
15866	/**
15867	 * @method onPick
15868	 * @param {jQuery DOM element} $sldr The plugin element
15869	 * @param {jQuery DOM element} $elm The selected link from the tablist
15870	 */
15871	onPick = function( $sldr, $elm ) {
15872		var data = $sldr.data( componentName ),
15873			$panels = data.panels,
15874			$controls = data.tablist,
15875			$next = $panels.filter( "#" + $elm.attr( "aria-controls" ) );
15876
15877		updateNodes( $panels, $controls, $next, $elm );
15878	},
15879
15880	/**
15881	 * @method onShift
15882	 * @param (jQuery event} event Current event
15883	 * @param {jQuery DOM element} $elm The plugin element
15884	 */
15885	onShift = function( event, $elm ) {
15886		var data = $elm.data( componentName ),
15887			$panels = data.panels,
15888			len = $panels.length,
15889			current = $elm.find( "> .tabpanels > .in" ).prevAll( "[role=tabpanel]" ).length,
15890			autoCycle = !event.shiftto,
15891			next = current > len ? 0 : current + ( autoCycle ? 1 : event.shiftto );
15892
15893		onSelect( $panels[ ( next > len - 1 ) ? 0 : ( next < 0 ) ? len - 1 : next ].id, autoCycle );
15894	},
15895
15896	/**
15897	 * @method onSelect
15898	 * @param (string) id Id attribute of the panel
15899	 * @param (boolean) autoCycle Whether change is caused by an auto cycle
15900	 */
15901	onSelect = function( id, autoCycle ) {
15902		var panelSelector = "#" + id,
15903			$panel = $( panelSelector ),
15904			$panelSelectorLink;
15905
15906		if ( isSmallView && $panel[ 0 ].nodeName.toLowerCase() === "details" ) {
15907			$panel.children( "summary" ).trigger( $panel.attr( "open" ) ? setFocusEvent : "click" );
15908		} else {
15909			$panelSelectorLink = $( panelSelector + "-lnk" );
15910			$panelSelectorLink.trigger( {
15911				type: "click",
15912				which: autoCycle ? undefined : 1
15913			} );
15914
15915			// Don't change the focus if change is cause by an auto cycle
15916			if ( !autoCycle ) {
15917				$panelSelectorLink.trigger( setFocusEvent );
15918			}
15919		}
15920	},
15921
15922	/**
15923	 * @method onCycle
15924	 * @param {jQuery DOM element} $elm The plugin element
15925	 * @param {integer} shiftto The item to shift to
15926	 */
15927	onCycle = function( $elm, shiftto ) {
15928		$elm.trigger( {
15929			type: shiftEvent,
15930			shiftto: shiftto
15931		} );
15932	},
15933
15934	/**
15935	 * @method onResize
15936	 * @param {jQuery Object} $currentElm Element being initialized (only during initialization process).
15937	 */
15938	onResize = function( $currentElm ) {
15939		var $elms, $elm, $tabPanels, $details, $detailsElm, $tablist,
15940			$openDetails, openDetailsId, activeId, $summary, $panelElm,
15941			i, len, j, len2, viewChange, isInit, isActive;
15942
15943		if ( initialized ) {
15944			isSmallView = document.documentElement.className.indexOf( smallViewPattern ) !== -1;
15945			viewChange = isSmallView !== oldIsSmallView;
15946			isInit = $currentElm.length ? true : false;
15947
15948			if ( viewChange || isInit ) {
15949				$elms = isInit ? $currentElm : $( selector );
15950				len = $elms.length;
15951
15952				for ( i = 0; i !== len; i += 1 ) {
15953					$elm = $elms.eq( i );
15954					$tabPanels = $elm.children( ".tabpanels" );
15955					$details = $tabPanels.children( "details" );
15956					len2 = $details.length;
15957
15958					if ( $details.length !== 0 ) {
15959						$tabPanels.detach();
15960						$summary = $details.children( "summary" );
15961						$tablist = $elm.children( "ul" );
15962
15963						if ( isSmallView ) {
15964
15965							// Switch to small view
15966							activeId = $tablist.find( ".active a" ).attr( "href" ).substring( 1 );
15967							for ( j = 0; j !== len2; j += 1 ) {
15968								$detailsElm = $details.eq( j );
15969								$panelElm = $detailsElm.children( ".tgl-panel" );
15970								isActive = $detailsElm.attr( "id" ) === activeId;
15971
15972								$detailsElm
15973									.removeAttr( "role aria-expanded aria-hidden" )
15974									.removeClass( "fade out noheight in" )
15975									.toggleClass( "open", isActive );
15976
15977								$panelElm
15978									.attr( "role", "tabpanel" )
15979									.removeAttr( "aria-expanded" )
15980									.removeAttr( "aria-hidden" );
15981
15982								if ( isActive ) {
15983									$detailsElm.attr( "open", "open" );
15984								} else {
15985									$detailsElm.removeAttr( "open" );
15986								}
15987
15988								if ( !isInit ) {
15989									$detailsElm
15990										.children( "summary" )
15991										.attr( {
15992											"aria-expanded": isActive,
15993											"aria-selected": isActive
15994										} );
15995								}
15996							}
15997						} else if ( oldIsSmallView ) {
15998
15999							// Switch to large view
16000							$openDetails = $details.filter( "[open]" );
16001							openDetailsId = $openDetails.attr( "id" );
16002
16003							$openDetails = ( $openDetails.length === 0 ? $details : $openDetails ).eq( 0 );
16004
16005							$details
16006								.attr( {
16007									role: "tabpanel",
16008									open: "open"
16009								} )
16010								.not( $openDetails )
16011								.addClass( "fade out noheight wb-inv" )
16012								.attr( {
16013									"aria-hidden": "true",
16014									"aria-expanded": "false"
16015								} );
16016
16017							$details.children( ".tgl-panel" ).removeAttr( "role" );
16018
16019							$openDetails
16020								.addClass( "fade in" )
16021								.attr( {
16022									"aria-hidden": "false",
16023									"aria-expanded": "true"
16024								} );
16025						}
16026
16027						$summary.attr( "aria-hidden", !isSmallView );
16028						$tablist.attr( "aria-hidden", isSmallView );
16029
16030						$elm.append( $tabPanels );
16031
16032						// Update the tablist role
16033						if ( isSmallView && !isInit ) {
16034							$elm.attr( "role", "tablist" );
16035						} else if ( oldIsSmallView ) {
16036							$elm
16037								.removeAttr( "role" )
16038								.find( nestedTglPanelSelector ).removeAttr( "role" );
16039
16040							$elm.find( "> ul [href$='" + openDetailsId + "']" ).trigger( "click" );
16041						}
16042					}
16043				}
16044
16045				// Need timeout to account for Toggle changes
16046				if ( isInit && !isSmallView && $elms.hasClass( tabsAccordionClass ) ) {
16047					setTimeout( function() {
16048						$elms
16049							.removeAttr( "role" )
16050							.find( nestedTglPanelSelector ).removeAttr( "role" );
16051					}, 1 );
16052				}
16053			}
16054
16055			oldIsSmallView = isSmallView;
16056		}
16057
16058		if ( viewChange || isInit ) {
16059
16060			// Remove wb-inv from regular tabs that were used to prevent FOUC (after 300ms delay)
16061			setTimeout( function() {
16062				$( selector + " .tabpanels > details.wb-inv" ).removeClass( "wb-inv" );
16063			}, 300 );
16064		}
16065	};
16066
16067// Bind the init event of the plugin
16068$document.on( "timerpoke.wb " + initEvent + " " + shiftEvent + " " + selectEvent, selector, function( event ) {
16069	var eventTarget = event.target,
16070		eventCurrentTarget = event.currentTarget,
16071		$elm;
16072
16073	// Filter out any events triggered by descendants
16074	if ( eventCurrentTarget === eventTarget ) {
16075		switch ( event.type ) {
16076			case "timerpoke":
16077				$elm = $( eventTarget );
16078				if ( !$elm.hasClass( componentName + "-inited" ) ) {
16079					init( event );
16080				} else if ( $elm.hasClass( "playing" ) ) {
16081					onTimerPoke( $elm );
16082				}
16083				break;
16084
16085			/*
16086			 * Init
16087			 */
16088			case "wb-init":
16089				init( event );
16090				break;
16091
16092			/*
16093			 * Change tab panels by a delta
16094			 */
16095			case "wb-shift":
16096				onShift( event, $( eventTarget ) );
16097				break;
16098
16099			/*
16100			 * Select a specific tab panel
16101			 */
16102			case "wb-select":
16103				onSelect( event.id );
16104				break;
16105		}
16106	}
16107
16108	/*
16109	 * Since we are working with events we want to ensure that we are being passive about our control,
16110	 * so returning true allows for events to always continue
16111	 */
16112	return true;
16113} );
16114
16115/*
16116 * Tabs, next, previous and play/pause
16117 */
16118$document.on( activateEvent, controls, function( event ) {
16119	var which = event.which,
16120		elm = event.currentTarget,
16121		className = elm.className,
16122		spaceText = i18nText.space,
16123		$elm, $sldr, sldrId, plypause, buttonText, data, isPlaying, isPlayPause;
16124
16125	// No control, alt or meta keys and only left mouse button, enter key,
16126	// space bar, escape key and arrow keys
16127	if ( !( event.ctrlKey || event.altKey || event.metaKey ) &&
16128			( !which || which === 1 || which === 13 || which === 27 ||
16129			which === 32 || ( which > 36 && which < 41 ) ) ) {
16130
16131		// Stop propagation of the activate event
16132		event.preventDefault();
16133		if ( event.stopPropagation ) {
16134			event.stopImmediatePropagation();
16135		} else {
16136			event.cancelBubble = true;
16137		}
16138
16139		$elm = $( elm );
16140		$sldr = $elm.closest( selector );
16141		sldrId = $sldr[ 0 ].id;
16142		isPlaying = $sldr.hasClass( "playing" );
16143		isPlayPause = className.indexOf( "plypause" ) !== -1;
16144
16145		// Reset ctime to 0
16146		data = $sldr.data( componentName );
16147		data.ctime = 0;
16148		$sldr.data( componentName, data );
16149
16150		// Stop the slider from playing unless it is already stopped
16151		// and the play button is activated
16152		if ( ( isPlaying && which ) || ( isPlayPause && !( which > 36 && which < 41 ) ) ) {
16153			if ( isPlaying ) {
16154				wb.remove( "#" + sldrId + selector );
16155			} else {
16156				wb.add( "#" + sldrId + selector );
16157			}
16158
16159			$sldr.toggleClass( "playing" );
16160			isPlaying = !isPlaying;
16161			buttonText = isPlaying ? i18nText.pause : i18nText.play;
16162
16163			plypause = $sldr.find( "a.plypause" )[ 0 ];
16164			plypause.setAttribute( "title", buttonText );
16165			plypause.innerHTML = "<span class='glyphicon glyphicon-" +
16166				( isPlaying ? "pause" : "play" ) + "'></span> " +
16167				"<span>" + buttonText + "</span><span class='wb-inv'>" +
16168				spaceText + i18nText.hyphen + spaceText +
16169				( isPlaying ? i18nText.rotStop : i18nText.rotStart ) + "</span>";
16170		}
16171
16172		// Arrow keys
16173		if ( which > 36 ) {
16174			onCycle( $sldr, which < 39 ? -1 : 1 );
16175			$sldr.find( "> [role=tablist] .active a" ).trigger( setFocusEvent );
16176
16177		// Not the escape key
16178		} else if ( which !== 27 ) {
16179
16180			// If the target is a tab
16181			if ( elm.getAttribute( "role" ) === "tab" ) {
16182
16183				// Only change the tabpanel if the tab is not currently selected
16184				if ( elm.getAttribute( "aria-selected" ) !== "true" ) {
16185					onPick( $sldr, $elm );
16186				}
16187
16188				// Put focus on the tab panel if the enter key or space bar are used
16189				if ( which === 13 || which === 32 ) {
16190					$sldr.find( elm.getAttribute( "href" ) )
16191						.trigger( setFocusEvent );
16192				}
16193
16194			// If the target is next, previous or tab count
16195			} else if ( !isPlayPause ) {
16196				onCycle( $sldr, className.indexOf( "prv" ) !== -1 ? -1 : 1 );
16197			}
16198		}
16199	}
16200
16201	/*
16202	 * Since we are working with events we want to ensure that we are being passive about our control,
16203	 * so returning true allows for events to always continue
16204	 */
16205	return true;
16206} );
16207
16208$document.on( activateEvent, selector + " [role=tabpanel]", function( event ) {
16209	var currentTarget = event.currentTarget,
16210		which = event.which,
16211		$container;
16212
16213	// Stop propagation of the click/keydown event
16214	if ( event.stopPropagation ) {
16215		event.stopImmediatePropagation();
16216	} else {
16217		event.cancelBubble = true;
16218	}
16219
16220	// Ctrl + Up arrow
16221	if ( event.ctrlKey && event.which === 38 ) {
16222
16223		// Move focus to the tab or summary element
16224		if ( isSmallView ) {
16225			$( currentTarget ).prev().trigger( setFocusEvent );
16226		} else {
16227			$( currentTarget )
16228				.closest( selector )
16229				.find( "[href$='#" + currentTarget.id + "']" )
16230				.trigger( setFocusEvent );
16231		}
16232
16233	// Left mouse button click or escape key
16234	} else if ( !which || which === 1 || which === 27 ) {
16235		$container = $( event.currentTarget ).closest( selector );
16236
16237		// Stop the carousel
16238		if ( $container.hasClass( "playing" ) ) {
16239			$container.find( ".plypause" ).trigger( "click" );
16240		}
16241	}
16242} );
16243
16244// Handling for links to tabs from within a panel
16245$document.on( "click", selector + " [role=tabpanel] a", function( event ) {
16246	var currentTarget = event.currentTarget,
16247		href = currentTarget.getAttribute( "href" ),
16248		which = event.which,
16249		$tabpanels, $panel, $summary;
16250
16251	// Ignore middle and right mouse buttons
16252	if ( ( !which || which === 1 ) && href.charAt( 0 ) === "#" ) {
16253		$tabpanels = $( currentTarget ).closest( ".tabpanels" );
16254		$panel = $tabpanels.children( "#" + wb.jqEscape( href.substring( 1 ) ) );
16255		if ( $panel.length !== 0 ) {
16256			event.preventDefault();
16257			$summary = $panel.children( "summary" );
16258			if ( $summary.length !== 0 && $summary.attr( "aria-hidden" ) !== "true" ) {
16259				$summary.trigger( "click" );
16260			} else {
16261				$tabpanels.parent().find( href + "-lnk" ).trigger( "click" );
16262			}
16263		}
16264	}
16265} );
16266
16267// These events only fire at the document level
16268$document.on( wb.resizeEvents, onResize );
16269
16270$document.on( activateEvent, selector + " > .tabpanels > details > summary", function( event ) {
16271	var which = event.which,
16272		details = event.currentTarget.parentNode,
16273		$details, $container, tabSettings;
16274
16275	if ( !( event.ctrlKey || event.altKey || event.metaKey ) &&
16276		( !which || which === 1 || which === 13 || which === 32 ) ) {
16277
16278		$container = $( details.parentNode.parentNode );
16279		$details = $( details );
16280		tabSettings = $container.data( componentName ).settings;
16281
16282		// Update sessionStorage with the current active panel
16283		if ( !tabSettings.ignoreSession ) {
16284			try {
16285				sessionStorage.setItem(
16286					pagePath + $container.attr( "id" ) + activePanel,
16287					details.id
16288				);
16289			} catch ( error ) {
16290
16291				/* swallow error */
16292			}
16293		}
16294
16295		// Update the URL hash if needed
16296		if ( tabSettings.updateHash ) {
16297			updateHash( details );
16298		}
16299
16300		// Identify that the tabbed interface accordion was updated
16301		// if the panel was not already open
16302		if ( !$details.attr( "open" ) ) {
16303			$container.trigger( updatedEvent, [ $details ] );
16304		}
16305	}
16306} );
16307
16308// Change the panel based upon an external link click
16309$document.on( "click", ".wb-tabs-ext", function( event ) {
16310	var which = event.which;
16311
16312	// Ignore middle and right mouse buttons
16313	if ( !which || which === 1 ) {
16314		event.preventDefault();
16315		onSelect( event.currentTarget.getAttribute( "href" ).substring( 1 ) );
16316	}
16317} );
16318
16319// Add the timer poke to initialize the plugin
16320wb.add( selector );
16321
16322} )( jQuery, window, wb );
16323
16324/**
16325 * @title WET-BOEW Tag filter
16326 * @overview Filter based content tagging
16327 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
16328 * @author @duboisp
16329 */
16330( function( $, window, document, wb ) {
16331"use strict";
16332
16333let wait;
16334
16335const componentName = "wb-tagfilter",
16336	selector = ".provisional." + componentName,
16337	selectorCtrl = "." + componentName + "-ctrl",
16338	initEvent = "wb-init" + selector,
16339	$document = wb.doc,
16340	filterOutClass = "wb-fltr-out",
16341	tgFilterOutClass = "wb-tgfltr-out",
16342	itemsWrapperClass = "wb-tagfilter-items",
16343	noResultWrapperClass = "wb-tagfilter-noresult",
16344
16345	init = function( event ) {
16346		const elm = wb.init( event, componentName, selector );
16347
16348		if ( elm ) {
16349			const filterControls = elm.querySelectorAll( selectorCtrl ),
16350				taggedItems = elm.querySelectorAll( "[data-wb-tags]" ),
16351				taggedItemsWrapper = elm.querySelector( "." + itemsWrapperClass ),
16352				noResultWrapper = elm.querySelector( "." + noResultWrapperClass );
16353
16354			elm.items = [];
16355			elm.filters = {};
16356			elm.activeFilters = [];
16357
16358			if ( taggedItemsWrapper ) {
16359				taggedItemsWrapper.id = taggedItemsWrapper.id || wb.getId(); // Ensure the element has an ID
16360				taggedItemsWrapper.setAttribute( "aria-live", "polite" );
16361			}
16362
16363			// Handle filters
16364			if ( filterControls.length ) {
16365				elm.filters = buildFiltersObj( filterControls );
16366
16367				filterControls.forEach( function( item ) {
16368					item.setAttribute( "aria-controls", taggedItemsWrapper.id );
16369				} );
16370			}
16371
16372			// Handle tagged items
16373			if ( taggedItems.length ) {
16374				elm.items = buildTaggedItemsArr( taggedItems );
16375			}
16376
16377			// Add accessibility to no result element
16378			if ( noResultWrapper ) {
16379				noResultWrapper.setAttribute( "role", "status" );
16380			}
16381
16382			// Update list of visible items (in case of predefined filters)
16383			update( elm );
16384
16385			wb.ready( $( elm ), componentName );
16386		}
16387	},
16388
16389	// Add every tagged item to an array of objects with their DOM ID, list of associated tags, and default isMatched attribute
16390	buildTaggedItemsArr = function( taggedItems ) {
16391		let taggedItemsArr = [];
16392
16393		taggedItems.forEach( function( taggedItem ) {
16394			let tagsList = taggedItem.dataset.wbTags.split( " " );
16395
16396			if ( !taggedItem.id ) {
16397				taggedItem.setAttribute( "id", wb.getId() );
16398			}
16399
16400			taggedItemsArr.push( {
16401				id: taggedItem.id,
16402				tags: tagsList,
16403				isMatched: true,
16404				itemText: taggedItem.innerText.toLowerCase()
16405			} );
16406		} );
16407
16408		return taggedItemsArr;
16409	},
16410
16411	// Build list of available filters using all filters grouped by filter name
16412	buildFiltersObj = function( filterControls ) {
16413		let filtersObj = {};
16414
16415		filterControls.forEach( function( control ) {
16416			if ( !control.name ) {
16417				console.error( componentName + ": Filter controls require an attribute 'name'." );
16418			}
16419
16420			switch ( control.type ) {
16421				case "checkbox":
16422				case "radio":
16423					if ( !( control.name in filtersObj ) ) {
16424						filtersObj[ control.name ] = [ ];
16425					}
16426
16427					filtersObj[ control.name ].push( {
16428						isChecked: control.checked,
16429						type: control.type,
16430						value: control.value
16431					} );
16432
16433					break;
16434				case "select-one":
16435					filtersObj[ control.name ] = [ {
16436						type: control.type,
16437						value: control.value
16438					} ];
16439					break;
16440			}
16441		} );
16442
16443		return filtersObj;
16444	},
16445
16446	// Update array of active filters according to UI selected controls
16447	refineFilters = function( instance ) {
16448		instance.activeFilters = [ ]; // Clear active filters
16449
16450		for ( let filterGroupName in instance.filters ) {
16451			let filterGroup = instance.filters[ filterGroupName ],
16452				filterGroupChkCnt = filterGroup.filter( function( o ) {
16453					return o.isChecked === true;
16454				} ).length,
16455				filterGroupActiveFilters = [ ];
16456
16457			switch ( filterGroup[ 0 ].type ) {
16458				case "checkbox":
16459					if ( filterGroupChkCnt > 0 ) {
16460						filterGroup.forEach( function( filterItem ) {
16461							if ( filterItem.isChecked ) {
16462								filterGroupActiveFilters.push( filterItem.value );
16463							}
16464						} );
16465					}
16466					break;
16467
16468				case "radio":
16469					if ( filterGroupChkCnt > 0 ) {
16470						for ( let filterItem of filterGroup ) {
16471							if ( filterItem.isChecked === true ) {
16472								if ( filterItem.value !== "" ) {
16473									filterGroupActiveFilters.push( filterItem.value );
16474								}
16475								break;
16476							}
16477						}
16478					} else {
16479						console.warn( componentName + ": Radio button groups must have a default selected value. If you want to display all items, add an option called \"All\" with an empty value." );
16480					}
16481					break;
16482
16483				case "select-one":
16484					if ( filterGroup[ 0 ].value !== "" ) {
16485						filterGroupActiveFilters.push( filterGroup[ 0 ].value );
16486					}
16487					break;
16488			}
16489
16490			instance.activeFilters.push( filterGroupActiveFilters );
16491		}
16492	},
16493
16494	// Match tagged items to active filters and only return items that have an active filter in every filter group
16495	matchItemsToFilters = function( instance ) {
16496		let filtersGroups = instance.activeFilters.length;
16497
16498		instance.items.forEach( function( item ) {
16499			let matchCount = 0;
16500
16501			instance.activeFilters.forEach( function( filterGroup ) {
16502				if ( filterGroup.length === 0 ) {
16503					matchCount++;
16504				} else {
16505					let itemIncludesFilter = filterGroup.filter( function( f ) {
16506						return item.tags.includes( f );
16507					} ).length;
16508
16509					if ( itemIncludesFilter ) {
16510						matchCount++;
16511					}
16512				}
16513			} );
16514
16515			matchCount === filtersGroups ? item.isMatched = true : item.isMatched = false;
16516		} );
16517	},
16518
16519	// Update list of visible items according to their "isMatched" property
16520	updateDOMItems = function( instance ) {
16521		const updatedItemsList = instance.items.forEach( function( item ) {
16522			let domItem = instance.querySelector( "#" + item.id ),
16523				matched = item.isMatched;
16524
16525			if ( matched ) {
16526				if ( domItem.classList.contains( tgFilterOutClass ) ) {
16527					domItem.classList.remove( tgFilterOutClass );
16528				}
16529			} else {
16530				if ( !domItem.classList.contains( tgFilterOutClass ) ) {
16531					domItem.classList.add( tgFilterOutClass );
16532				}
16533			}
16534		} );
16535
16536		return updatedItemsList;
16537	},
16538
16539	// Utility method to update stored active filters, update stored items and update visibility of tagged items
16540	update = function( instance ) {
16541		refineFilters( instance );
16542		matchItemsToFilters( instance );
16543		updateDOMItems( instance );
16544
16545		$( instance ).trigger( "wb-contentupdated", [ { source: componentName } ] );
16546	};
16547
16548// When a filter is updated
16549$document.on( "change", selectorCtrl, function( event )  {
16550	let control = event.currentTarget,
16551		filterType = control.type,
16552		filterName = control.name,
16553		filterValue = control.value,
16554		elm = control.closest( selector ),
16555		filterGroup = elm.filters[ filterName ];
16556
16557	switch ( filterType ) {
16558		case "checkbox":
16559
16560			// Update virtual filter to the new state
16561			filterGroup.find( function( filter ) {
16562				return filter.value === filterValue;
16563			} ).isChecked = !!control.checked;
16564			break;
16565
16566		case "radio":
16567
16568			// Set all virtual radio items to unchecked
16569			filterGroup.forEach( function( filterItem ) {
16570				filterItem.isChecked = false;
16571			} );
16572
16573			// Set selected radio button's associated virtual filter to checked
16574			filterGroup.find( function( filter ) {
16575				return filter.value === filterValue;
16576			} ).isChecked = true;
16577			break;
16578
16579		case "select-one":
16580
16581			// Update virtual filter to the new value
16582			filterGroup[ 0 ].value = filterValue;
16583			break;
16584	}
16585
16586	// Update list of visible items
16587	update( elm );
16588} );
16589
16590$document.on( "wb-contentupdated", selector, function( event, data )  {
16591	let that = this,
16592		supportsHas = window.getComputedStyle( document.documentElement ).getPropertyValue( "--supports-has" ); // Get "--supports-has" CSS property
16593
16594	// Reinitialize tagfilter if content on the page has been updated by another plugin
16595	if ( data && data.source !== componentName ) {
16596		if ( wait ) {
16597			clearTimeout( wait );
16598		}
16599
16600		wait = setTimeout( function() {
16601			that.classList.remove( "wb-init", componentName + "-inited" );
16602			$( that ).trigger( "wb-init." + componentName );
16603		}, 100 );
16604	}
16605
16606	// Show no result message if on Firefox -- Remove once Firefox supports ":has()"
16607	if ( supportsHas === "false" ) {
16608		let noResultItem = this.querySelector( "." + noResultWrapperClass );
16609
16610		if ( noResultItem && this.items.length > 0 ) {
16611			let visibleItems = this.querySelectorAll( "." + itemsWrapperClass + " " + "[data-wb-tags]:not(." + tgFilterOutClass + ", ." + filterOutClass + ")" );
16612
16613			if ( visibleItems.length < 1 ) {
16614				noResultItem.style.display = "block";
16615			} else {
16616				noResultItem.style.display = "none";
16617			}
16618		}
16619	}
16620} );
16621
16622$document.on( "timerpoke.wb " + initEvent, selector, init );
16623
16624wb.add( selector );
16625
16626} )( jQuery, window, document, wb );
16627
16628/**
16629 * @title WET-BOEW Text highlighting
16630 * @overview Automatically highlights certain words on a Web page. The highlighted words can be selected via the query string.
16631 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
16632 * @author @pjackson28
16633 */
16634( function( $, window, document, wb ) {
16635"use strict";
16636
16637/*
16638 * Variable and function definitions.
16639 * These are global to the plugin - meaning that they will be initialized once per page,
16640 * not once per instance of plugin on the page. So, this is a good place to define
16641 * variables that are common to all instances of the plugin on a page.
16642 */
16643var componentName = "wb-txthl",
16644	selector = "." + componentName,
16645	initEvent = "wb-init" + selector,
16646	$document = wb.doc,
16647
16648	/**
16649	 * @method init
16650	 * @param {jQuery Event} event Event that triggered the function call
16651	 */
16652	init = function( event ) {
16653
16654		// Start initialization
16655		// returns DOM object = proceed with init
16656		// returns undefined = do not proceed with init (e.g., already initialized)
16657		var elm = wb.init( event, componentName, selector ),
16658			params = wb.pageUrlParts.params,
16659			searchCriteria, newText;
16660
16661		if ( elm ) {
16662			if ( event.txthl ) {
16663				searchCriteria = Array.isArray( event.txthl ) ? event.txthl.join( "|" ) : event.txthl;
16664			} else if ( params && params.txthl ) {
16665				searchCriteria = decodeURIComponent(
16666					wb.pageUrlParts.params.txthl
16667						.replace( /^\s+|\s+$|\|+|"|\(|\)/g, "" ).replace( /\++/g, "|" )
16668				);
16669			}
16670
16671			if ( searchCriteria ) {
16672
16673				// Strip html
16674				var map = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#039;" };
16675				searchCriteria = searchCriteria.replace( /[&<>"']/g, function( m ) {
16676					return map[ m ];
16677				} );
16678
16679				// Make sure that we're not checking for text within a tag; only the text outside of tags.
16680				searchCriteria = "(?=([^>]*<))([\\s'])?(" + searchCriteria + ")(?!>)";
16681
16682				newText = elm.innerHTML.replace( new RegExp( searchCriteria, "gi" ), function( match, group1, group2, group3 ) {
16683					return ( !group2 ? "" : group2 ) + "<mark>" + group3 + "</mark>";
16684				} );
16685				elm.innerHTML = newText;
16686			}
16687
16688			// Identify that initialization has completed
16689			wb.ready( $( elm ), componentName );
16690		}
16691	};
16692
16693// Bind the init event of the plugin
16694$document.on( "timerpoke.wb " + initEvent, selector, init );
16695
16696// Add the timer poke to initialize the plugin
16697wb.add( selector );
16698
16699} )( jQuery, window, document, wb );
16700
16701/**
16702 * @title WET-BOEW Toggle
16703 * @overview Plugin that allows a link to toggle elements between on and off states.
16704 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
16705 * @author @patheard
16706 */
16707( function( $, window, wb ) {
16708"use strict";
16709
16710/*
16711 * Variable and function definitions.
16712 * These are global to the plugin - meaning that they will be initialized once per page,
16713 * not once per instance of plugin on the page. So, this is a good place to define
16714 * variables that are common to all instances of the plugin on a page.
16715 */
16716var componentName = "wb-toggle",
16717	selector = "." + componentName,
16718	selectorPanel = ".tgl-panel",
16719	selectorTab = ".tgl-tab",
16720	initEvent = "wb-init" + selector,
16721	toggleEvent = "toggle" + selector,
16722	toggledEvent = "toggled" + selector,
16723	setFocusEvent = "setfocus.wb",
16724	states = {},
16725	$document = wb.doc,
16726	$window = wb.win,
16727
16728	defaults = {
16729		stateOn: "on",
16730		stateOff: "off"
16731	},
16732
16733	/**
16734	 * @method init
16735	 * @param {jQuery Event} event Event that triggered the function call
16736	 */
16737	init = function( event ) {
16738
16739		// Start initialization
16740		// returns DOM object = proceed with init
16741		// returns undefined = do not proceed with init (e.g., already initialized)
16742		var link = wb.init( event, componentName, selector, true ),
16743			$link, data, persistState;
16744
16745		if ( link ) {
16746
16747			// Merge the elements settings with the defaults
16748			$link = $( link );
16749			data = $.extend( {}, defaults, $link.data( "toggle" ) );
16750			$link.data( "toggle", data );
16751
16752			// Add aria attributes of the toggle element
16753			initAria( link, data );
16754
16755			// Persist toggle state across page loads
16756			if ( data.persist ) {
16757				persistState = initPersist( $link, data );
16758			}
16759
16760			// Toggle behaviour when the page is printed
16761			if ( data.print ) {
16762				initPrint( $link, data );
16763			}
16764
16765			// Set the initial state if the state has been specified and
16766			// the persistent state has not been set
16767			if ( !persistState && data.state ) {
16768				setState( $link, data, data.state );
16769			}
16770
16771			// Identify that initialization has completed
16772			wb.ready( $link, componentName );
16773		}
16774	},
16775
16776	/**
16777	 * Initialize the aria attributes for a given toggle element
16778	 * @param {DOM element} link The toggle element to initialize
16779	 * @param {Object} data Simple key/value data object passed when the event was triggered
16780	 */
16781	initAria = function( link, data ) {
16782		var i, len, elm, elms, parent, tabs, tab, panel, isOpen, wrapper,
16783			ariaControls = "",
16784			hasOpen = false;
16785
16786		// Group toggle elements with a parent are assumed to be a tablist
16787		if ( data.group != null && data.parent != null ) { //eslint-disable-line no-eq-null
16788			parent = document.querySelector( data.parent );
16789
16790			// Check that the tablist widget hasn't already been initialized
16791			if ( parent.getAttribute( "role" ) !== "tablist" ) {
16792
16793				// Only apply the tablist role if the parent is not the tabbed interface container
16794				// or the page is currently in "smallview", "xsmallview" or "xxsmallview"
16795				if ( parent.className.indexOf( "wb-tabs" ) === -1 ||
16796					document.documentElement.className.indexOf( "smallview" ) !== -1 ) {
16797					parent.setAttribute( "role", "tablist" );
16798				}
16799
16800				elms = parent.querySelectorAll( data.group );
16801				tabs = parent.querySelectorAll( data.group + " " + selectorTab );
16802
16803				// Set the tab and panel aria attributes
16804				for ( i = 0, len = elms.length; i !== len; i += 1 ) {
16805					elm = elms[ i ];
16806					tab = tabs[ i ];
16807					panel = elm.querySelector( selectorPanel );
16808
16809					// Check if the element is toggled on based on the
16810					// open attribute or "on" CSS class
16811					isOpen = elm.nodeName.toLowerCase() === "details" ?
16812						!!elm.hasAttribute( "open" ) :
16813						( " " + tab.className + " " ).indexOf( " " + data.stateOn + " " );
16814					if ( isOpen ) {
16815						hasOpen = true;
16816					}
16817
16818					if ( !tab.getAttribute( "id" ) ) {
16819						tab.setAttribute( "id", wb.getId() );
16820					}
16821
16822					//Details and summary don't support aria roles and some aria attribute that is why they are wrapped in a div
16823					if ( elm.nodeName.toLowerCase() === "details" && elm.parentNode.className.toLowerCase().indexOf( "accordion" ) > -1 ) {
16824						wrapper = document.createElement( "div" );
16825						wrapper.classList.add( "tgl-tab" );
16826						wrapper.setAttribute( "role", "tab" );
16827						wrapper.setAttribute( "aria-selected", isOpen );
16828						wrapper.setAttribute( "tabindex", isOpen ? "0" : "-1" );
16829						wrapper.setAttribute( "aria-posinset", i + 1 );
16830						wrapper.setAttribute( "aria-setsize", len );
16831						parent.replaceChild( wrapper, elm );
16832						wrapper.appendChild( elm );
16833					} else {
16834						tab.setAttribute( "role", "tab" );
16835						tab.setAttribute( "aria-selected", isOpen );
16836						tab.setAttribute( "tabindex", isOpen ? "0" : "-1" );
16837						tab.setAttribute( "aria-posinset", i + 1 );
16838						tab.setAttribute( "aria-setsize", len );
16839					}
16840					panel.setAttribute( "role", "tabpanel" );
16841					panel.setAttribute( "aria-labelledby", tab.getAttribute( "id" ) );
16842					panel.setAttribute( "aria-expanded", isOpen );
16843					panel.setAttribute( "aria-hidden", !isOpen );
16844				}
16845
16846				// No open panels so put the first summary in the tab order
16847				if ( !hasOpen ) {
16848					tabs[ 0 ].setAttribute( "tabindex", "0" );
16849				}
16850			}
16851
16852		// Set the elements this link controls
16853		} else {
16854			elms = getElements( link, data );
16855			for ( i = 0, len = elms.length; i !== len; i += 1 ) {
16856				elm = elms[ i ];
16857				if ( !elm.id ) {
16858					elm.id = wb.getId();
16859				}
16860				ariaControls += elm.id + " ";
16861			}
16862			link.setAttribute( "aria-controls", ariaControls.slice( 0, -1 ) );
16863		}
16864	},
16865
16866	/**
16867	 * Initializes persistent behaviour of the toggle element
16868	 * @param {jQuery Object} $link The toggle element to initialize
16869	 * @param {Object} data Simple key/value data object passed when the event was triggered
16870	 * @returns {string} Persistent state
16871	 */
16872	initPersist = function( $link, data ) {
16873		var state,
16874			link = $link[ 0 ];
16875
16876		// Store the persistence type and key for later use
16877		data.persist = data.persist === "session" ? sessionStorage : localStorage;
16878		data.persistKey = componentName + ( data.group ? data.group : "" ) + link.id;
16879
16880		// If there's a saved toggle state, trigger the change to that state
16881		state = data.persist.getItem( data.persistKey );
16882		if ( state ) {
16883			$link.trigger( toggleEvent, $.extend( {}, data, { type: state } ) );
16884		}
16885
16886		return state;
16887	},
16888
16889	/**
16890	 * Initialize open on print behaviour of the toggle element
16891	 * @param {jQuery Object} $link The toggle element to initialize
16892	 * @param {Object} data Simple key/value data object passed when the event was triggered
16893	 */
16894	initPrint = function( $link, data ) {
16895		var mediaQuery,
16896			printEvent = "beforeprint";
16897
16898		$window.on( printEvent, function() {
16899			$link.trigger( toggleEvent, $.extend( {}, data, { type: data.print } ) );
16900		} );
16901
16902		// Fallback for browsers that don't support print events
16903		if ( window.matchMedia ) {
16904			mediaQuery = window.matchMedia( "print" );
16905			if ( mediaQuery.addListener ) {
16906				mediaQuery.addListener( function( query ) {
16907					if ( query.matches ) {
16908						$window.trigger( printEvent );
16909					}
16910				} );
16911			}
16912		}
16913	},
16914
16915	/**
16916	 * Click handler for the toggle links
16917	 * @param {jQuery Event} event The event that triggered this invocation
16918	 */
16919	click = function( event ) {
16920		var $link = $( event.currentTarget );
16921
16922		$link.trigger( toggleEvent, $link.data( "toggle" ) );
16923		event.preventDefault();
16924
16925		// Assign focus to eventTarget
16926		$link.trigger( setFocusEvent );
16927	},
16928
16929	/**
16930	 * Toggles the elements a link controls between the on and off states.
16931	 * @param {jQuery Event} event The event that triggered this invocation
16932	 * @param {Object} data Simple key/value data object passed when the event was triggered
16933	 */
16934	toggle = function( event, data ) {
16935		if ( event.namespace === componentName ) {
16936			var dataGroup, key, $elmsGroup,
16937				isGroup = !!data.group,
16938				isPersist = !!data.persist,
16939				isTablist = isGroup && !!data.parent,
16940				link = event.currentTarget,
16941				$link = $( link ),
16942				stateFrom = getState( $link, data ),
16943				isToggleOn = stateFrom === data.stateOff,
16944				stateTo = isToggleOn ? data.stateOn : data.stateOff,
16945				$elms = isTablist ?	$link.parent( data.group ) : getElements( link, data );
16946
16947			// Group toggle behaviour: only one element in the group open at a time.
16948			if ( isGroup ) {
16949
16950				// Get the grouped elements using data.group as the CSS selector
16951				dataGroup = $.extend( {}, data, { selector: data.group } );
16952				$elmsGroup = getElements( link, dataGroup );
16953
16954				// Set the toggle state to "off".  For tab lists, this is stored on the tab element
16955				setState( isTablist ? $( data.parent ).find( selectorTab ) : $elmsGroup,
16956					dataGroup, data.stateOff );
16957
16958				// Toggle all grouped elements to "off"
16959				$elmsGroup.wb( "toggle", data.stateOff, data.stateOn );
16960				$elmsGroup.trigger( toggledEvent, {
16961					isOn: false,
16962					isTablist: isTablist,
16963					elms: $elmsGroup
16964				} );
16965
16966				// Remove all grouped persistence keys
16967				if ( isPersist ) {
16968					for ( key in data.persist ) {
16969						if ( key.indexOf( componentName + data.group ) === 0 ) {
16970							data.persist.removeItem( key );
16971						}
16972					}
16973				}
16974			}
16975
16976			// Set the toggle state. For tab lists, this is set on the tab element
16977			setState( isTablist ? $link : $elms, data, stateTo );
16978
16979			// Toggle all elements to the requested state
16980			$elms.wb( "toggle", stateTo, stateFrom );
16981			$elms.trigger( toggledEvent, {
16982				isOn: isToggleOn,
16983				isTablist: isTablist,
16984				elms: $elms
16985			} );
16986
16987			// Ensure that last focused element in the accordion remains keyboard focusable
16988			// whether it is collapsed or not
16989			if ( isGroup ) {
16990				$elms.find( "summary" ).attr( { "tabindex": "0" } );
16991			}
16992
16993			// Store the toggle link's current state if persistence is turned on.
16994			// Try/catch is required to address exceptions thrown when using BB10 or
16995			// private browsing in iOS.
16996			if ( isPersist ) {
16997				try {
16998					data.persist.setItem( data.persistKey, stateTo );
16999				} catch ( error ) {
17000
17001					/* swallow error */
17002				}
17003			}
17004		}
17005	},
17006
17007	/**
17008	 * Sets the required property and attribute for toggling open/closed a details element
17009	 * @param {jQuery Event} event The event that triggered this invocation
17010	 * @param {Object} data Simple key/value data object passed when the event was triggered
17011	 */
17012	toggleDetails = function( event, data ) {
17013		if ( event.namespace === componentName && event.target === event.currentTarget ) {
17014			var top,
17015				isOn = data.isOn,
17016				$elms = data.elms,
17017				$this = $( this ),
17018				$detail = $this.is( "summary" ) ? $this.parent() : $this;
17019
17020			// Stop propagation of the toggleDetails event
17021			if ( event.stopPropagation ) {
17022				event.stopImmediatePropagation();
17023			} else {
17024				event.cancelBubble = true;
17025			}
17026
17027			$detail.prop( "open", isOn );
17028
17029			if ( data.isTablist ) {
17030
17031				// Set the required aria attributes
17032				$elms.find( selectorTab ).parents( selectorTab ).attr( {
17033					"aria-selected": isOn,
17034					tabindex: isOn ? "0" : "-1"
17035				} );
17036				$elms.find( selectorPanel ).attr( {
17037					"aria-hidden": !isOn,
17038					"aria-expanded": isOn
17039				} );
17040
17041				// Check that the top of the open element is in view.
17042				if ( isOn && $elms.length === 1 ) {
17043					top = $elms.offset().top;
17044					if ( top < $window.scrollTop() ) {
17045						$window.scrollTop( top );
17046					}
17047				}
17048			}
17049		}
17050	},
17051
17052	/**
17053	 * Returns the elements a given toggle element controls.
17054	 * @param {DOM element} link Toggle element that was clicked
17055	 * @param {Object} data Simple key/value data object passed when the event was triggered
17056	 * @returns {jQuery Object} DOM elements the toggle link controls
17057	 */
17058	getElements = function( link, data ) {
17059		var selector = data.selector || link,
17060			parent = data.parent || null;
17061
17062		return parent !== null ? $( parent ).find( selector ) : $( selector );
17063	},
17064
17065	/**
17066	 * Gets the current toggle state of elements controlled by the given link.
17067	 * @param {jQuery Object} $link Toggle link that was clicked
17068	 * @param {Object} data Simple key/value data object passed when the event was triggered
17069	 */
17070	getState = function( $link, data ) {
17071		var parent = data.parent,
17072			selector = data.selector,
17073			type = data.type;
17074
17075		// Get opposite state of the type. Toggle reverses this
17076		// to the requested state.
17077		if ( type ) {
17078			return type === "on" ? data.stateOff : data.stateOn;
17079
17080		// <details> elements use the open attribute to determine state
17081		} else if ( $link[ 0 ].nodeName.toLowerCase() === "summary" ) {
17082			return $link.parent().attr( "open" ) ? data.stateOn : data.stateOff;
17083
17084		// When no selector, use the data attribute of the link
17085		} else if ( !selector ) {
17086			return $link.data( componentName + "-state" ) || data.stateOff;
17087
17088		// When toggling multiple <details> elements, state is "off" if any are collapsed
17089		} else if ( selector === "details" && !type ) {
17090			var anyCollapsed = false;
17091			getElements( $link, data ).each( function() {
17092				if ( !$( this ).attr( "open" ) ) {
17093					anyCollapsed = true;
17094				}
17095			} );
17096			return anyCollapsed ? data.stateOff : data.stateOn;
17097
17098		// Get the current on/off state of the elements specified by the selector and parent
17099		} else if ( Object.prototype.hasOwnProperty.call( states, selector ) ) {
17100			return Object.prototype.hasOwnProperty.call( states[ selector ], parent ) ?
17101				states[ selector ][ parent ] :
17102				states[ selector ].all;
17103		}
17104		return data.stateOff;
17105	},
17106
17107	/*
17108	 * Sets the current toggle state of elements controlled by the given link.
17109	 * @param {DOM element} link Toggle link that was clicked
17110	 * @param {Object} data Simple key/value data object passed when the event was triggered
17111	 * @param {String} state The current state of the elements: "on" or "off"
17112	 */
17113	setState = function( $elms, data, state ) {
17114		var prop,
17115			parent = data.parent,
17116			selector = data.selector,
17117			elmsState = states[ selector ];
17118
17119		if ( selector ) {
17120
17121			// Check the selector object has been created
17122			if ( !elmsState ) {
17123				elmsState = { all: data.stateOff };
17124				states[ selector ] = elmsState;
17125			}
17126
17127			// If there's a parent, set its state
17128			if ( parent ) {
17129				elmsState[ parent ] = state;
17130
17131			// No parent means set all states for the given selector. This is
17132			// because toggle links that apply to the entire DOM also affect
17133			// links that are restricted by parent.
17134			} else {
17135				for ( prop in elmsState ) {
17136					if ( Object.prototype.hasOwnProperty.call( elmsState, prop ) ) {
17137						elmsState[ prop ] = state;
17138					}
17139				}
17140			}
17141		}
17142
17143		// Store the state on the elements as well. This allows a link to toggle itself.
17144		$elms.data( componentName + "-state", state );
17145	};
17146
17147// Bind the plugin's events
17148$document.on( "timerpoke.wb " + initEvent + " " + toggleEvent +
17149" click", selector, function( event, data ) {
17150
17151	var eventType = event.type;
17152
17153	switch ( eventType ) {
17154		case "click":
17155			click( event );
17156			break;
17157
17158		case "toggle":
17159			toggle( event, data );
17160			break;
17161
17162		case "timerpoke":
17163		case "wb-init":
17164			init( event );
17165			break;
17166	}
17167} );
17168
17169$document.on( toggledEvent, "summary, details", toggleDetails );
17170
17171// Keyboard handling for the accordion
17172$document.on( "keydown", selectorTab, function( event ) {
17173	var which = event.which,
17174		data, $elm, $parent, $group, $newPanel, index;
17175
17176	if ( !event.ctrlKey && which > 34 && which < 41 ) {
17177		event.preventDefault();
17178		$elm = $( event.currentTarget );
17179		data = $elm.data( "toggle" );
17180		$parent = $document.find( data.parent );
17181		$group = $parent.find( data.group );
17182		index = $group.index( $elm.parent() );
17183
17184		switch ( which ) {
17185
17186			// End
17187			case 35:
17188				$newPanel = $group.last();
17189				break;
17190
17191			// Home
17192			case 36:
17193				$newPanel = $group.first();
17194				break;
17195
17196			// Left / up arrow
17197			case 37:
17198			case 38:
17199				if ( index === 0 ) {
17200					$newPanel = $group.last();
17201				} else {
17202					$newPanel = $group.eq( index - 1 );
17203				}
17204				break;
17205
17206			// Right / down arrow
17207			case 39:
17208			case 40:
17209				if ( index === $group.length - 1 ) {
17210					$newPanel = $group.first();
17211				} else {
17212					$newPanel = $group.eq( index + 1 );
17213				}
17214				break;
17215		}
17216
17217		$newPanel
17218			.children( "summary" )
17219			.trigger( setFocusEvent );
17220	}
17221} );
17222
17223$document.on( "keydown", selectorPanel, function( event ) {
17224
17225	// Ctrl + Up arrow
17226	if ( event.ctrlKey && event.which === 38 ) {
17227
17228		// Move focus to the summary element
17229		$( event.currentTarget )
17230			.prev()
17231			.trigger( setFocusEvent );
17232	}
17233} );
17234
17235// Add the timer poke to initialize the plugin
17236wb.add( selector );
17237
17238} )( jQuery, window, wb );
17239
17240/**
17241 * @title WET-BOEW Twitter embedded timeline
17242 * @overview Helps with implementing Twitter embedded timelines.
17243 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
17244 * @author @pjackson28
17245 */
17246( function( $, window, wb ) {
17247"use strict";
17248
17249/*
17250 * Variable and function definitions.
17251 * These are global to the plugin - meaning that they will be initialized once per page,
17252 * not once per instance of plugin on the page. So, this is a good place to define
17253 * variables that are common to all instances of the plugin on a page.
17254 */
17255var componentName = "wb-twitter",
17256	selector = "." + componentName,
17257	initEvent = "wb-init" + selector,
17258	$document = wb.doc,
17259	i18n, i18nText,
17260
17261	/**
17262	 * @method init
17263	 * @param {jQuery Event} event Event that triggered the function call
17264	 */
17265	init = function( event ) {
17266
17267		// Start initialization
17268		// returns DOM object = proceed with init
17269		// returns undefined = do not proceed with init (e.g., already initialized)
17270		var eventTarget = wb.init( event, componentName, selector ),
17271			protocol = wb.pageUrlParts.protocol;
17272
17273		if ( eventTarget ) {
17274			const twitterLink = eventTarget.querySelector( "a.twitter-timeline" );
17275
17276			// Ignore IE11
17277			// Note: Twitter's widget no longer supports it...
17278			if ( wb.ie11 ) {
17279				wb.ready( $( eventTarget ), componentName );
17280				return;
17281			}
17282
17283			// Process the Twitter link
17284			if ( twitterLink ) {
17285				const loadingDiv = document.createElement( "div" );
17286				let observer;
17287
17288				// Only initialize the i18nText once
17289				if ( !i18nText ) {
17290					i18n = wb.i18n;
17291					i18nText = {
17292						startNotice: i18n( "twitter-start-notice" ),
17293						endNotice: i18n( "twitter-end-notice" ),
17294						skipEnd: i18n( "twitter-skip-end" ),
17295						skipStart: i18n( "twitter-skip-start" ),
17296						timelineTitle: i18n( "twitter-timeline-title" )
17297					};
17298				}
17299
17300				// Show a warning if the timelineTitle variable isn't a string
17301				if ( typeof i18nText.startNotice !== "string" ) {
17302					console.warn( componentName + ": i18n text is missing. Iframe title override and skip links will be disabled." );
17303				}
17304
17305				// Set Chinese (Simplified)'s language code to "zh-cn"
17306				// If the link doesn't specify a widget language and its "in-page" language code is "zh-Hans"...
17307				// Notes:
17308				// -WET uses "zh-Hans", Twitter uses "zh-ch" and falls back to English if the former is used
17309				// -Language code sourced from https://developer.twitter.com/en/docs/twitter-for-websites/supported-languages
17310				if ( !twitterLink.dataset.lang && twitterLink.closest( "[lang='zh-Hans']" ) ) {
17311					twitterLink.dataset.lang = "zh-cn";
17312				}
17313
17314				// Match the Facebook page plugin's default height
17315				// If data-height is set to "fb-page" OR the widget has a tweet limit and lacks a custom height...
17316				// Notes:
17317				// -Counteracts enormous default widget heights that can reach tens of thousands of pixels without a scrollbar
17318				// -Timeline widgets stopped honouring tweet limits on July 21, 2023 and began showing up to 100 tweets at a time ("verified" accounts only)
17319				// -Facebook page plugin's default height is documented in https://developers.facebook.com/docs/plugins/page-plugin#settings
17320				if ( twitterLink.dataset.height === "fb-page" || ( twitterLink.dataset.tweetLimit && !twitterLink.dataset.height ) ) {
17321					twitterLink.dataset.height = "500";
17322				}
17323
17324				// Add a "do not track" parameter (i.e. data-dnt="true" attribute) unless it's already been set
17325				// Note: Covered in https://developer.twitter.com/en/docs/twitter-for-websites/webpage-properties
17326				if ( !twitterLink.dataset.dnt ) {
17327					twitterLink.dataset.dnt = "true";
17328				}
17329
17330				// Add a loading icon below the link
17331				loadingDiv.className = "twitter-timeline-loading";
17332				twitterLink.after( loadingDiv );
17333
17334				// Observe DOM mutations
17335				observer = new MutationObserver( function( mutations ) {
17336					mutations.forEach( function( mutation ) {
17337						switch ( mutation.type ) {
17338
17339							// Check for attribute changes
17340							case "attributes": {
17341								const mutationTarget = mutation.target;
17342
17343								// Override the timeline iframe's title right after Twitter's widget script adds it
17344								// Notes:
17345								// -The timeline's iframe title is English-only, uses "Twitter" and is written in title case ("Twitter Timeline")... This replaces it with an i18n version that uses "X" and is written in sentence case.
17346								// -Only proceed if the i18n variable is a string... otherwise this'll trigger an infinite loop of attribute mutations
17347								if ( mutationTarget.nodeName === "IFRAME" && mutationTarget.title !== i18nText.timelineTitle && typeof i18nText.timelineTitle === "string" ) {
17348									mutationTarget.title = i18nText.timelineTitle;
17349								}
17350								break;
17351							}
17352
17353							// Check for node removals
17354							case "childList": {
17355								mutation.removedNodes.forEach( function( removedNode ) {
17356
17357									// If the removed node was a Twitter link, remove its adjacent loading icon, add skip links and stop observing
17358									// Note: Twitter's widget script removes "a.twitter-timeline" upon displaying the timeline iframe's content... at which point the loading icon is no longer useful
17359									if ( removedNode === twitterLink && mutation.nextSibling === loadingDiv ) {
17360										const iframeContainer = loadingDiv.previousElementSibling;
17361
17362										loadingDiv.remove();
17363										addSkipLinks( iframeContainer );
17364
17365										// The following 2 lines were added as a workaround in Safari where the iFrame is not displayed
17366										eventTarget.style.opacity = 1;
17367										eventTarget.style.opacity = "";
17368
17369										observer.disconnect();
17370									}
17371								} );
17372							}
17373						}
17374					} );
17375				} );
17376
17377				// Observe changes to the plugin container's child elements and title attributes
17378				observer.observe( eventTarget, {
17379					attributeFilter: [ "title" ],
17380					childList: true,
17381					subtree: true
17382				} );
17383			}
17384
17385			Modernizr.load( {
17386				load: ( protocol.indexOf( "http" ) === -1 ? "http:" : protocol ) + "//platform.twitter.com/widgets.js",
17387				complete: function() {
17388
17389					// Identify that initialization has completed
17390					wb.ready( $( eventTarget ), componentName );
17391				}
17392			} );
17393		}
17394	},
17395
17396	// Add skip links immediately before and after the timeline widget
17397	// Note: Verified account timelines may contain several hundred interactive elements... this provides a mechanism to spare keyboard-only users from needing to tab through everything to move past the widget.
17398	addSkipLinks = function( iframeContainer ) {
17399		const timelineIframe = iframeContainer.getElementsByTagName( "iframe" )[ 0 ];
17400		const username = getTwitterUsername( timelineIframe.src );
17401		const noticeClass = componentName + "-" + "notice";
17402		const skipClass = componentName + "-" + "skip";
17403		const startText = "start";
17404		const endText = "end";
17405		let startNotice;
17406		let endNotice;
17407		let skipToEndLink;
17408		let skipToStartLink;
17409
17410		// Abort if Twitter username is falsy or i18n variables aren't strings
17411		if ( !username || typeof i18nText.timelineTitle !== "string" ) {
17412			return;
17413		}
17414
17415		// Add a start of timeline notice
17416		startNotice = createNotice( i18nText.startNotice, username, timelineIframe.id, noticeClass, startText );
17417		iframeContainer.before( startNotice );
17418
17419		// Add an end of timeline notice
17420		endNotice = createNotice( i18nText.endNotice, username, timelineIframe.id, noticeClass, endText );
17421		iframeContainer.after( endNotice );
17422
17423		// Add a skip to end link
17424		skipToEndLink = createSkipLink( i18nText.skipEnd, username, endNotice.id, skipClass, endText );
17425		startNotice.after( skipToEndLink );
17426
17427		// Add a skip to start link
17428		skipToStartLink = createSkipLink( i18nText.skipStart, username, startNotice.id, skipClass, startText );
17429		endNotice.before( skipToStartLink );
17430	},
17431
17432	// Extract a Twitter username from the iframe's timeline URL
17433	getTwitterUsername = function( iframeSrc ) {
17434		let username = iframeSrc.match( /\/screen-name\/([^?]+)/ );
17435		username = username ? username[ 1 ] : null;
17436
17437		return username;
17438	},
17439
17440	// Create a timeline notice
17441	createNotice = function( textTemplate, username, iframeId, noticeClass, position ) {
17442		const spanElm = document.createElement( "span" );
17443		const pElm = document.createElement( "p" );
17444
17445		spanElm.innerHTML = textTemplate.replace( "%username%", username );
17446
17447		pElm.id = iframeId + "-" + position;
17448		pElm.className = noticeClass + "-" + position;
17449		pElm.prepend( spanElm );
17450
17451		// Hide the notice upon losing focus
17452		// Removes its tabindex attribute to make its CSS hide it from screen readers
17453		pElm.addEventListener( "blur", function( e ) {
17454			e.target.removeAttribute( "tabindex" );
17455		} );
17456
17457		return pElm;
17458	},
17459
17460	// Create a skip link
17461	createSkipLink = function( textTemplate, username, linkDestId, skipClass, linkDir ) {
17462		const spanElm = document.createElement( "span" );
17463		const aElm = document.createElement( "a" );
17464		const pElm = document.createElement( "p" );
17465
17466		spanElm.innerHTML = textTemplate.replace( "%username%", username );
17467
17468		aElm.href = "#" + linkDestId;
17469		aElm.prepend( spanElm );
17470
17471		// Focus onto the destination of a clicked link
17472		$( aElm ).on( "click", function( event ) {
17473			const currentTarget = event.currentTarget;
17474			const linkDestId = "#" + wb.jqEscape( currentTarget.getAttribute( "href" ).substring( 1 ) );
17475			const $linkDest = $document.find( linkDestId );
17476
17477			// Assign focus to the link's destination
17478			// Note: The focus event's scrolling behaviour is more graceful than "jumping" to an anchor link's destination
17479			$linkDest.trigger( "setfocus.wb" );
17480
17481			// Don't engage normal link navigation behaviour (i.e. "jumping" to the link destination, changing address/navigation history)
17482			return false;
17483		} );
17484
17485		pElm.className = skipClass + " " + skipClass + "-" + linkDir;
17486		pElm.prepend( aElm );
17487
17488		return pElm;
17489	};
17490
17491$document.on( "timerpoke.wb " + initEvent, selector, init );
17492
17493// Add the timer poke to initialize the plugin
17494wb.add( selector );
17495
17496} )( jQuery, window, wb );
17497
17498/**
17499 * @title WET-BOEW Data Json [data-json-after], [data-json-append],
17500 * [data-json-before], [data-json-prepend], [data-json-replace], [data-json-replacewith] and [data-wb-json]
17501 * @overview Insert content extracted from JSON file.
17502 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
17503 * @author @duboisp
17504 */
17505/*global jsonpointer */
17506( function( $, window, wb ) {
17507"use strict";
17508
17509/*
17510 * Variable and function definitions.
17511 * These are global to the plugin - meaning that they will be initialized once per page,
17512 * not once per instance of plugin on the page. So, this is a good place to define
17513 * variables that are common to all instances of the plugin on a page.
17514 */
17515var componentName = "wb-data-json",
17516	shortName = "wb-json",
17517	selectors = [
17518		"[data-json-after]",
17519		"[data-json-append]",
17520		"[data-json-before]",
17521		"[data-json-prepend]",
17522		"[data-json-replace]",
17523		"[data-json-replacewith]",
17524		"[data-" + shortName + "]"
17525	],
17526	allowJsonTypes = [ "after", "append", "before", "prepend", "val" ],
17527	allowAttrNames = /(href|src|data-*|aria-*|role|pattern|min|max|step|low|high|lang|hreflang|action)/,
17528	allowPropNames = /(checked|selected|disabled|required|readonly|multiple|hidden)/,
17529	selectorsLength = selectors.length,
17530	selector = selectors.join( "," ),
17531	initEvent = "wb-init." + componentName,
17532	updateEvent = "wb-update." + componentName,
17533	contentUpdatedEvent = "wb-contentupdated",
17534	dataQueue = componentName + "-queue",
17535	$document = wb.doc,
17536	isExtensionRegistered,
17537	s,
17538
17539	/**
17540	 * @method init
17541	 * @param {jQuery Event} event Event that triggered this handler
17542	 * @param {string} ajaxType The type of JSON operation, either after, append, before or replace
17543	 */
17544	init = function( event ) {
17545
17546		// Start initialization
17547		// returns DOM object = proceed with init
17548		// returns undefined = do not proceed with init (e.g., already initialized)
17549		var elm = wb.init( event, componentName, selector ),
17550			jsSettings = window[ componentName ] || { },
17551			prop,
17552			$elm;
17553
17554		if ( elm ) {
17555
17556			var jsonCoreTypes = [
17557					"before",
17558					"replace",
17559					"replacewith",
17560					"after",
17561					"append",
17562					"prepend"
17563				],
17564				jsonType, jsondata,
17565				i, i_len = jsonCoreTypes.length, i_cache,
17566				lstCall = [],
17567				url;
17568
17569			$elm = $( elm );
17570
17571			for ( i = 0; i !== i_len; i += 1 ) {
17572				jsonType = jsonCoreTypes[ i ];
17573				url = elm.getAttribute( "data-json-" + jsonType );
17574				if ( url !== null ) {
17575					lstCall.push( {
17576						type: jsonType,
17577						url: url
17578					} );
17579				}
17580			}
17581
17582			// Extend but not overwrite the functionTest and the functionForOperand if some was added
17583			if ( !isExtensionRegistered ) {
17584				if ( jsSettings.functionForTest ) {
17585					for ( prop in jsSettings.functionForTest ) {
17586						if ( !functionForTest[ prop ] ) {
17587							functionForTest[ prop ] = jsSettings.functionForTest[ prop ];
17588						}
17589					}
17590				}
17591				if ( jsSettings.functionForOperand ) {
17592					for ( prop in jsSettings.functionForOperand ) {
17593						if ( !functionForOperand[ prop ] ) {
17594							functionForOperand[ prop ] = jsSettings.functionForOperand[ prop ];
17595						}
17596					}
17597				}
17598				isExtensionRegistered = true;
17599			}
17600
17601			// Identify that initialization has completed
17602			wb.ready( $elm, componentName );
17603
17604			jsondata = wb.getData( $elm, shortName );
17605
17606			if ( jsondata && jsondata.url ) {
17607				lstCall.push( jsondata );
17608			} else if ( jsondata && Array.isArray( jsondata ) ) {
17609				i_len = jsondata.length;
17610				for ( i = 0; i !== i_len; i += 1 ) {
17611					lstCall.push( jsondata[ i ] );
17612				}
17613			}
17614
17615			// Save it to the dataJSON object.
17616			$elm.data( dataQueue, lstCall );
17617
17618			i_len = lstCall.length;
17619			for ( i = 0; i !== i_len; i += 1 ) {
17620				i_cache = lstCall[ i ];
17621				loadJSON( elm, i_cache.url, i, i_cache.nocache, i_cache.nocachekey, i_cache.data, i_cache.contenttype, i_cache.method );
17622			}
17623
17624		}
17625	},
17626
17627	loadJSON = function( elm, url, refId, nocache, nocachekey, data, contentType, method ) {
17628		var $elm = $( elm ),
17629			fetchObj = {
17630				url: url,
17631				refId: refId,
17632				nocache: nocache,
17633				nocachekey: nocachekey,
17634				data: data,
17635				contentType: contentType,
17636				method: method
17637			};
17638
17639		$elm.trigger( {
17640			type: "json-fetch.wb",
17641			fetch: fetchObj
17642		} );
17643	},
17644
17645
17646	// Manage JSON value After the json data has been fetched. This function can deal with array.
17647	jsonFetched = function( event ) {
17648
17649		var elm = event.target,
17650			$elm = $( elm ),
17651			lstCall = $elm.data( dataQueue ),
17652			fetchObj = event.fetch,
17653			itmSettings = lstCall[ fetchObj.refId ],
17654			jsonType = itmSettings.type,
17655			attrname = itmSettings.prop || itmSettings.attr,
17656			showEmpty = itmSettings.showempty,
17657			content = fetchObj.response,
17658			typeOfContent = typeof content,
17659			jQueryCaching;
17660
17661		if ( showEmpty || typeOfContent !== "undefined" ) {
17662
17663			if ( showEmpty && typeOfContent === "undefined" ) {
17664				content = "";
17665			}
17666
17667			//Prevents the force caching of nested resources
17668			jQueryCaching = jQuery.ajaxSettings.cache;
17669			jQuery.ajaxSettings.cache = true;
17670
17671			// "replace" and "replaceWith" doesn't map to a jQuery function
17672			if ( !jsonType ) {
17673				jsonType = "template";
17674				applyTemplate( elm, itmSettings, content );
17675
17676				// Trigger wet
17677				if ( itmSettings.trigger && !wb.isDisabled ) {
17678					$elm
17679						.find( wb.allSelectors )
17680						.addClass( "wb-init" )
17681						.filter( ":not(#" + elm.id + " .wb-init .wb-init)" )
17682						.trigger( "timerpoke.wb" );
17683				}
17684			} else if ( jsonType === "replace" ) {
17685				$elm.html( content );
17686			} else if ( jsonType === "replacewith" ) {
17687				$elm.replaceWith( content );
17688			} else if ( jsonType === "addclass" ) {
17689				$elm.addClass( content );
17690			} else if ( jsonType === "removeclass" ) {
17691				$elm.removeClass( content );
17692			} else if ( jsonType === "prop" && attrname && allowPropNames.test( attrname ) ) {
17693				$elm.prop( attrname, content );
17694			} else if ( jsonType === "attr" && attrname && allowAttrNames.test( attrname ) ) {
17695				$elm.attr( attrname, content );
17696			} else if ( typeof $elm[ jsonType ] === "function" && allowJsonTypes.indexOf( jsonType ) !== -1 ) {
17697				$elm[ jsonType ]( content );
17698			} else {
17699				throw componentName + " do not support type: " + jsonType;
17700			}
17701
17702			//Resets the initial jQuery caching setting
17703			jQuery.ajaxSettings.cache = jQueryCaching;
17704
17705			$elm.trigger( contentUpdatedEvent, { "json-type": jsonType, "content": content } );
17706		}
17707	},
17708
17709	// Apply the template as per the configuration
17710	applyTemplate = function( elm, settings, content ) {
17711
17712		var elmClass = elm.className,
17713			dataTable,
17714			dataTableAddRow,
17715			template = settings.source ? document.querySelector( settings.source ) : elm.querySelector( "template" );
17716
17717		// If combined with wb-tables plugin
17718		if ( elm.tagName === "TABLE" && elmClass.indexOf( "wb-tables" ) !== -1 ) {
17719
17720			//  Wait for its initialization before to applyTemplate
17721			if ( elmClass.indexOf( "wb-tables-inited" ) === -1 || !$.fn.dataTable || !$.fn.dataTable.isDataTable( elm ) ) {
17722				$( elm ).one( "wb-ready.wb-tables", function( ) {
17723					applyTemplate( elm, settings, content );
17724				} );
17725				return;
17726			}
17727
17728			dataTable = $( elm ).dataTable( { "retrieve": true } ).api();
17729			dataTableAddRow = dataTable.row.add;
17730			settings.tobeclone = "tr"; // Only table row can be added
17731		}
17732
17733		if ( !template ) {
17734			return;
17735		}
17736
17737		// Needed when executing sub-template that wasn't polyfill, like in IE11
17738		if ( !template.content ) {
17739			wb.tmplPolyfill( template );
17740		}
17741
17742		if ( !settings.streamline ) {
17743			dataIterator( elm, content, settings );
17744		} else {
17745			processMapping( elm, elm, content, settings );
17746		}
17747
17748		// Refresh the dataTable display
17749		if ( dataTableAddRow ) {
17750			dataTable.draw();
17751		}
17752	},
17753
17754	// Iterate over the dataset
17755	dataIterator = function( elm, content, mappingConfig, useClone ) {
17756
17757		var i, i_len, i_cache,
17758			elmAppendTo = elm,
17759			clone, template,
17760			dataTable, dataTableAddRow;
17761
17762		if ( mappingConfig.appendto ) {
17763			elmAppendTo = $( mappingConfig.appendto ).get( 0 );
17764		}
17765
17766		// Connection with data table plugin
17767		if ( elm.tagName === "TABLE" && elm.className.indexOf( "wb-tables" ) !== -1 ) {
17768			dataTable = $( elm ).dataTable( { "retrieve": true } ).api();
17769			dataTableAddRow = dataTable.row.add;
17770			mappingConfig.tobeclone = "tr";
17771		}
17772
17773
17774		// if content is object, transform into array @id and @value
17775		if ( !Array.isArray( content ) ) {
17776			if ( typeof content !== "object" ) {
17777				content = [ content ];
17778			} else {
17779				content = $.map( content, function( val, index ) {
17780					if ( val && typeof val === "object" && !Array.isArray( val ) ) {
17781						if ( !val[ "@id" ] ) {
17782							val[ "@id" ] = index;
17783						}
17784					} else {
17785						val = {
17786							"@id": index,
17787							"@value": val
17788						};
17789					}
17790					return [ val ];
17791				} );
17792			}
17793		}
17794		i_len = content.length;
17795
17796		// Get the template to be iterated.
17797		if ( !useClone && mappingConfig.source ) {
17798			template = document.querySelector( mappingConfig.source );
17799		} else if ( !useClone && mappingConfig.template ) {
17800			template = elm.querySelector( mappingConfig.template );
17801		} else if ( !useClone ) {
17802			template = elm.querySelectorAll( ":scope > template" );
17803			if ( template.length === 1 || template[ 0 ].attributes.length === 0 ) {
17804
17805				// Only when there is one choice or take the first one only if only there is no attribute set on the element
17806				template = template[ 0 ];
17807			} else {
17808
17809				// let the mapping instructions to define which template to use
17810				template = false;
17811			}
17812		}
17813
17814		// Iterate the data array
17815		for ( i = 0; i < i_len; i += 1 ) {
17816			i_cache = content[ i ];
17817
17818
17819			// If the data are filtered. This is deprecated and are only for backward compatible purpose
17820			if ( !filterPassJSON( i_cache, mappingConfig.filter, mappingConfig.filternot ) ) {
17821				continue;
17822			}
17823
17824			// Get the template (if applicable)
17825			if ( !clone && useClone ) {
17826				clone = useClone;
17827			}
17828
17829			// Create a clone if one unique template is found
17830			if ( !useClone && template && !mappingConfig.tobeclone ) {
17831				clone = template.content.cloneNode( true );
17832			} else if ( !useClone && template ) {
17833				clone = template.content.querySelector( mappingConfig.tobeclone ).cloneNode( true );
17834			}
17835
17836			// process the mapping, return value is the new clone object if applicable
17837			var tmpClone;
17838			tmpClone = processMapping( elm, clone, i_cache, mappingConfig );
17839
17840			// Remove the template flag, to ensure we do reuse it for the subsequent iteration
17841			if ( tmpClone ) {
17842				delete mappingConfig.template;
17843				clone = tmpClone;
17844			}
17845
17846			// Add the clone object
17847			if ( dataTableAddRow ) {
17848				dataTableAddRow( $( clone ) ); // If wb-tables, use its API to add rows
17849			} else if ( !useClone && template ) {
17850				elmAppendTo.appendChild( clone );
17851			}
17852		}
17853
17854		// Refresh the dataTable display (if applicable)
17855		if ( dataTableAddRow ) {
17856			dataTable.draw();
17857		}
17858
17859	},
17860
17861	// Check if the mapping are met or not
17862	canProcessMapping = function( content, mappingConfig ) {
17863
17864		var rawValue, value,
17865			testableData,
17866			operand = mappingConfig.operand || "softEq",
17867			operandOutcome;
17868
17869		if ( !mappingConfig.test ) {
17870			return;
17871		}
17872
17873		// Get the value to be tested
17874		try {
17875			rawValue = getRawValue( content, mappingConfig.assess || mappingConfig.value );
17876			value = getValue( rawValue );
17877		} catch ( ex ) {
17878
17879			// If this is an error, the path probably don't exist
17880			rawValue = undefined;
17881			value = undefined;
17882		}
17883
17884		// Get the function to use
17885		if ( !functionForTest[ mappingConfig.test ] ) {
17886			console.error( "The test function '" + mappingConfig.test + "' don't exist. Default to false test result" );
17887			console.error( mappingConfig );
17888			return false;
17889		}
17890		testableData = functionForTest[ mappingConfig.test ].call( mappingConfig, value, rawValue );
17891
17892		// Run the operand
17893		if ( !functionForOperand[ operand ] ) {
17894			console.error( "The operand '" + operand + "' don't exist" );
17895			console.error( mappingConfig );
17896			operand = "softEq";
17897		}
17898		operandOutcome = functionForOperand[ operand ].call( mappingConfig, testableData, mappingConfig.expect );
17899
17900		// If not true, go next
17901		if ( !operandOutcome ) {
17902			return false;
17903		}
17904
17905		// Run mapping if satisfied
17906		return true;
17907	},
17908
17909	// Special mapping typed function
17910	functionForTypedMapping = {
17911
17912		"rdf:Alt": function( elm, clone, content, mappingConfig ) {
17913
17914			var mapping = mappingConfig.mapping,
17915				i, i_cache,
17916				i_len = mapping.length,
17917				value = content;
17918
17919			for ( i = 0; i < i_len || i === 0; i += 1 ) {
17920				i_cache = mapping[ i ];
17921
17922				if ( canProcessMapping( content, i_cache ) ) {
17923
17924					// Clone the object to avoid conflict when it is reused for other data
17925					i_cache = $.extend( true, {}, i_cache );
17926
17927					// Remove the test, because it was checked
17928					delete i_cache.test;
17929
17930					// Navigate the content if specified
17931					if ( i_cache.value ) {
17932						value = getValue( content, i_cache.value );
17933					}
17934
17935					// Process the mapping
17936					processMapping( elm, clone, value, i_cache );
17937
17938					// End
17939					return;
17940				}
17941			}
17942		}
17943	},
17944
17945	// Function called for testing the mapping condition, which can be extend via the js configuration
17946	functionForTest = {
17947
17948		"fn:isArray": function( value ) {
17949			return Array.isArray( value );
17950		},
17951
17952		"fn:isLiteral": function( value ) {
17953
17954			// Check if the value are set under the JSON-LD parameter @value
17955			if ( value && value[ "@value" ] ) {
17956				value = value[ "@value" ];
17957			}
17958
17959			if ( value && typeof value !== "object" ) {
17960				return true;
17961			}
17962
17963			return false;
17964		},
17965
17966		"fn:getType": function( value, rawValue ) {
17967
17968			var tp = value[ "@type" ] || rawValue[ "@type" ];
17969
17970			if ( tp === "@json" ) {
17971				return "rdf:JSON";
17972			} else if ( Array.isArray( tp ) && tp.indexOf( "@json" ) !== -1 ) {
17973				tp[ tp.indexOf( "@json" ) ] = "rdf:JSON";
17974			}
17975
17976			if ( tp ) {
17977				return tp;
17978			} else {
17979				return typeof value;
17980			}
17981		},
17982
17983		"fn:getValue": function( value ) {
17984
17985			return value;
17986
17987		},
17988
17989		"fn:guessType": function( value, rawValue ) {
17990
17991			var guessType;
17992
17993			if ( !value ) {
17994				guessType = "undefined";
17995			} else if ( value[ "@type" ] ) {
17996				guessType = value[ "@type" ];
17997			} else if ( rawValue[ "@type" ] ) {
17998				guessType = rawValue[ "@type" ];
17999			} else if ( value[ "@value" ] ) {
18000
18001				// Only if we are in JSON ld mode
18002				// Check if the value are set under the JSON-LD parameter @value
18003				value = value[ "@value" ];
18004			}
18005
18006			// Edge case to convert the @json data type into its RDF form
18007			if ( guessType && guessType !== "undefined" ) {
18008				if ( guessType === "@json" ) {
18009					guessType = "rdf:JSON";
18010				} else if ( Array.isArray( guessType ) && guessType.indexOf( "@json" ) !== -1 ) {
18011					guessType[ guessType.indexOf( "@json" ) ] = "rdf:JSON";
18012				}
18013			}
18014
18015			if ( !guessType ) {
18016				if ( typeof value === "string" && value.match( /^([a-z][a-z0-9+\-.]*):/i ) ) {
18017					guessType = [ "xsd:anyURI", "rdfs:Literal" ];
18018				} else if ( typeof value === "string" ) {
18019					guessType = [ "xsd:string", "rdfs:Literal" ];
18020				} else if ( typeof value === "boolean" ) {
18021					guessType = [ "xsd:boolean", "rdfs:Literal" ];
18022				} else if ( typeof value === "number" ) {
18023					guessType = [ "xsd:double", "rdfs:Literal" ];
18024				} else if ( typeof value === "undefined" ) {
18025					guessType = "undefined";
18026				} else if ( Array.isArray( value ) ) {
18027					guessType = "rdfs:Container";
18028				} else {
18029
18030					// The type is a generic Object
18031					guessType = "rdfs:Resource";
18032				}
18033			}
18034
18035			return guessType;
18036		}
18037
18038	},
18039
18040	// Operand used to evaluate the testable output from functionForTest to determine if the mapping condition is met or not, which can be extend via the js configuration
18041	functionForOperand = {
18042
18043		"softEq": function( value, expect ) {
18044			var i, i_len;
18045
18046			if ( Array.isArray( value ) && !Array.isArray( expect ) && value.indexOf( expect ) !== -1 ) {
18047				return true;
18048			} else if ( Array.isArray( value ) &&  Array.isArray( expect ) ) {
18049				i_len = expect.length;
18050				for ( i = 0; i !== i_len; i++ ) {
18051					if ( value.indexOf( expect[ i ] ) ) {
18052						return true;
18053					}
18054				}
18055			} else if ( expect && value === expect ) {
18056				return true;
18057			} else if ( !expect && value ) {
18058				return true;
18059			}
18060
18061			return false;
18062
18063		},
18064
18065		"eq": function( value, expect ) {
18066
18067			if ( _equalsJSON( value, expect ) ) {
18068				return true;
18069			}
18070
18071			return false;
18072		},
18073
18074		"neq": function( value, expect ) {
18075			if ( !_equalsJSON( value, expect ) ) {
18076				return true;
18077			}
18078
18079			return false;
18080		},
18081
18082		"in": function( value, expect ) {
18083
18084			var i;
18085
18086			if ( !expect ) {
18087				console.error( "Expected value is missing. Defaulting to false." );
18088				console.error( this );
18089				return false;
18090			}
18091
18092			if ( Array.isArray( value ) && !Array.isArray( expect ) && value.indexOf( expect ) !== -1 ) {
18093				return true;
18094			} else if ( Array.isArray( value ) &&  Array.isArray( expect ) ) {
18095				for ( i = 0; i !== expect.length; i++ ) {
18096					if ( value.indexOf( expect[ i ] ) ) {
18097						return true;
18098					}
18099				}
18100			} else if ( !Array.isArray( value ) &&  Array.isArray( expect ) && expect.indexOf( value ) !== -1  ) {
18101				return true;
18102			} else if ( value === expect ) {
18103				return true;
18104			}
18105
18106			return false;
18107		},
18108
18109		"nin": function( value, expect ) {
18110			return !functionForOperand.in.call( this, value, expect );
18111		}
18112	},
18113
18114	// Mapping the data into a template or into a node
18115	processMapping = function( elm, clone, content, mappingConfig ) {
18116
18117		var j, j_cache,
18118			cached_node, cached_value,
18119			cached_value_is_HTML, cached_value_is_JSON, cached_value_is_IRI,
18120			queryAll = mappingConfig.queryall,
18121			selElements,
18122			mapping = mappingConfig.mapping,
18123			mapping_len,
18124			upstreamClone, template;
18125
18126
18127		// Is this mapping a special mapping type?
18128		if ( mappingConfig[ "@type" ] ) {
18129			functionForTypedMapping[ mappingConfig[ "@type" ] ].call( content, elm, clone, content, mappingConfig );
18130			return;
18131		}
18132
18133		// Can we proceed?
18134		if ( mappingConfig.test && !canProcessMapping( content, mappingConfig ) ) {
18135			return;
18136		}
18137
18138		// Check if there is some mapping configuration
18139		if ( !mapping && !queryAll && !mappingConfig.template && typeof mapping !== "object" ) {
18140			return;
18141		}
18142
18143		// Clone mappingConfig to ensure it don't interfere with subsequent data iteration
18144		mappingConfig = $.extend( true, {}, mappingConfig );
18145
18146		// If there is no clone, let use the element (parent)
18147		clone = clone || elm;
18148
18149		// If there is a "template" property, get the inner template
18150		if ( mappingConfig.template ) {
18151			template = clone.querySelector( mappingConfig.template );
18152
18153			upstreamClone = clone; // Keep reference of the top clone
18154
18155			clone = template.content.cloneNode( true );
18156
18157			// Ensure we don't recreated it if during a subsequent iteration
18158			delete mappingConfig.template;
18159		}
18160
18161
18162		// Is content an array? then iterate the content
18163		if ( Array.isArray( content ) ) {
18164
18165
18166			dataIterator( clone, content, mappingConfig, clone );
18167
18168			// Case of where a template is associated with this mapping action
18169			if ( template ) {
18170				if ( template.parentNode ) {
18171
18172					if ( !mappingConfig.append ) {
18173						template.parentNode.insertBefore( clone, template );
18174					} else {
18175						template.parentNode.appendChild( clone );
18176					}
18177				} else {
18178					upstreamClone.appendChild( clone );
18179				}
18180
18181				return elm;
18182
18183			}
18184			return;
18185		}
18186
18187		// Prepare the mapping object to be iterated
18188		if ( !mapping ) {
18189			mapping = [ {} ];
18190		}
18191		if ( !Array.isArray( mapping ) ) {
18192			mapping = [ mapping ];
18193		}
18194		mapping_len = mapping.length;
18195
18196		// Ensure the mapping is an array of Mapping Object
18197		for ( j = 0; j < mapping_len || j === 0; j += 1 ) {
18198			if ( typeof mapping[ j ] === "string" ) {
18199				mapping[ j ] = {
18200					value: mapping[ j ]
18201				};
18202			}
18203		}
18204
18205		if ( queryAll ) {
18206			selElements = clone.querySelectorAll( queryAll );
18207
18208			// Replicate this setting the in the mapping
18209			for ( j = 0; j < selElements.length || j === 0; j += 1 ) {
18210				if ( !mapping[ j ].selector && queryAll.indexOf( "nth-child" ) === -1 ) {
18211					mapping[ j ].selector = queryAll + ":nth-child(" + ( j + 1 ) + ")";
18212				} else if ( !mapping[ j ].selector ) {
18213					mapping[ j ].selector = queryAll;
18214				}
18215			}
18216		}
18217
18218
18219		//
18220		// Process the mapping
18221		//
18222		for ( j = 0; j < mapping_len || j === 0; j += 1 ) {
18223			j_cache = mapping[ j ];
18224
18225			// Reset the cache value special type flag
18226			cached_value_is_IRI = false;
18227			cached_value_is_HTML = false;
18228			cached_value_is_JSON = false;
18229
18230			// Get the element to be updated
18231			if ( j_cache.selector ) {
18232				cached_node = clone.querySelector( j_cache.selector );
18233			} else {
18234				cached_node = clone;
18235			}
18236
18237			// Get the value to be set
18238			try {
18239				cached_value = getRawValue( content, j_cache );
18240			} catch ( ex ) {
18241
18242				// The path don't exist, let continue to the next mapping item
18243				console.info( "JSON selector path for mapping don't exist in content" );
18244				console.info( j_cache );
18245				console.info( content );
18246				continue;
18247			}
18248
18249			// Go to the next mapping if the value of JSON node don't exist to ensure we keep the default text set in the template, but move ahead if empty or null
18250			if ( typeof cached_value === "undefined" ) {
18251				continue;
18252			}
18253
18254			// Do the cache value contain special @type
18255			if ( cached_value && cached_value[ "@value" ] && cached_value[ "@type" ] ) {
18256				if ( !Array.isArray( cached_value[ "@type" ] ) ) {
18257					cached_value[ "@type" ] = [ cached_value[ "@type" ] ];
18258				}
18259				cached_value_is_IRI = cached_value[ "@type" ].indexOf( "@id" ) !== -1;
18260				cached_value_is_HTML = cached_value[ "@type" ].indexOf( "rdf:HTML" ) !== -1;
18261				cached_value_is_JSON = cached_value[ "@type" ].indexOf( "rdf:JSON" ) !== -1 || cached_value[ "@type" ].indexOf( "@json" ) !== -1;
18262			}
18263
18264			// Action the value
18265			if ( Array.isArray( cached_value ) && ( j_cache.mapping || j_cache.queryall ) ) {
18266
18267				// Deep dive into the content if a mapping exist
18268				dataIterator( cached_node, cached_value, j_cache );
18269
18270			} else if ( j_cache.mapping || j_cache.queryall || !j_cache.mapping && typeof j_cache.mapping === "object" ) {
18271				try {
18272
18273					// Map the inner mapping
18274					processMapping( template || elm, cached_node, cached_value, j_cache );
18275				} catch ( ex ) {
18276
18277					if ( ex === "cached_node: null" && typeof cached_value === "object" ) {
18278
18279						// If it fail, let iterate the cached_value object
18280						dataIterator( cached_node, cached_value, j_cache );
18281					} else {
18282						throw ex;
18283					}
18284				}
18285			} else if ( cached_value_is_IRI && cached_value_is_HTML ) {
18286
18287				// The import file type are expected to be HTML
18288				// Add the data-ajax instruction so the content would be added once the JSON mapping is completed and added on the page.
18289				cached_node.dataset.wbAjax = JSON.stringify( {
18290					url: cached_value[ "@value" ],
18291					type: "replace",
18292					dataType: cached_value_is_JSON ? "json" : null,
18293					encode: j_cache.encode
18294				} );
18295			} else if ( cached_value_is_HTML && cached_value_is_JSON && !cached_value_is_IRI ) {
18296
18297				// Get content from the "@value" property which contain JSON value and use it as a string value
18298				cached_value = JSON.stringify( cached_value[ "@value" ] );
18299
18300				// Map the value in the element
18301				mapValue( cached_node, cached_value, j_cache );
18302
18303			} else if ( !cached_node && typeof cached_value === "object" ) {
18304				throw "cached_node: null";
18305			} else if ( mappingConfig.mapping !== null ) {
18306
18307				cached_value = getValue( cached_value );
18308
18309				// Serialize the value if it is an JS object and its not a null object
18310				if ( typeof cached_value === "object" &&  cached_value !== null ) {
18311					cached_value = JSON.stringify( cached_value );
18312				}
18313
18314				// Map the value in the element
18315				mapValue( cached_node, cached_value, j_cache );
18316			}
18317
18318		}
18319
18320		// Add the template, if applicable
18321		if ( template ) {
18322			if ( template.parentNode ) {
18323
18324				if ( !mappingConfig.append ) {
18325					template.parentNode.insertBefore( clone, template );
18326				} else {
18327					template.parentNode.appendChild( clone );
18328				}
18329			} else {
18330				upstreamClone.appendChild( clone );
18331			}
18332
18333			return elm;
18334		}
18335
18336	},
18337
18338	// Extract the value of an JS object
18339	getValue = function( source, pointer ) {
18340
18341		var value = getRawValue( source, pointer );
18342
18343		// for JSON-LD @value support
18344		if ( typeof value === "object" && value !== null && Object.prototype.hasOwnProperty.call( value, "@value" ) ) {
18345			value = value[ "@value" ];
18346		}
18347
18348		return value;
18349	},
18350
18351	// Extract the value without considering it possible JSON-LD value
18352	getRawValue = function( source, pointer ) {
18353
18354		var value;
18355		pointer = pointer || false; // Ensure pointer is defined
18356
18357		// Get the value if source is string or pointer is pointing to root
18358		if ( typeof source === "string" || pointer === "/" || pointer === "/@value" || pointer.value === "/" || pointer.value === "/@value" ) {
18359			value = source;
18360		} else if ( typeof pointer === "string" ) {
18361			value = jsonpointer.get( source, pointer );
18362		} else if ( pointer.value ) {
18363			value = jsonpointer.get( source, pointer.value );
18364		} else {
18365			value = source;
18366		}
18367
18368		return value;
18369	},
18370
18371	// Map a value into an HTML element or attribute
18372	mapValue = function( element, value, mappingConfig ) {
18373
18374		var attributeName, placeholderText;
18375
18376		attributeName = mappingConfig.attr;
18377		if ( attributeName ) {
18378			if ( !element.hasAttribute( attributeName ) ) {
18379				element.setAttribute( attributeName, "" );
18380			}
18381			element = element.getAttributeNode( attributeName );
18382		}
18383
18384		// Placeholder text replacement if any
18385		if ( mappingConfig.placeholder ) {
18386			placeholderText = element.textContent || "";
18387			value = placeholderText.replace( mappingConfig.placeholder, value );
18388		}
18389
18390		// Exclude null values and replace with default text
18391		if ( value !== null ) {
18392			if ( mappingConfig.isHTML ) {
18393				element.innerHTML = value;
18394			} else {
18395				element.textContent = value;
18396			}
18397		}
18398	},
18399
18400
18401	// Filtering a JSON
18402	// Return true if trueness && falseness
18403	// Return false if !( trueness && falseness )
18404	// trueness and falseness is an array of { "path": "", "value": "" } object
18405	filterPassJSON = function( obj, trueness, falseness ) {
18406		var i, i_cache,
18407			trueness_len = trueness ? trueness.length : 0,
18408			falseness_len = falseness ? falseness.length : 0,
18409			compareResult = false,
18410			isEqual;
18411
18412		if ( trueness_len || falseness_len ) {
18413
18414			for ( i = 0; i < trueness_len; i += 1 ) {
18415				i_cache = trueness[ i ];
18416				isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value );
18417
18418				if ( i_cache.optional ) {
18419					compareResult = compareResult || isEqual;
18420				} else if ( !isEqual ) {
18421					return false;
18422				} else {
18423					compareResult = true;
18424				}
18425			}
18426			if ( trueness_len && !compareResult ) {
18427				return false;
18428			}
18429
18430			for ( i = 0; i < falseness_len; i += 1 ) {
18431				i_cache = falseness[ i ];
18432				isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value );
18433
18434				if ( isEqual && !i_cache.optional || isEqual && i_cache.optional ) {
18435					return false;
18436				}
18437			}
18438
18439		}
18440		return true;
18441	},
18442
18443	//
18444	_equalsJSON = function( a, b ) {
18445		switch ( typeof a ) {
18446			case "undefined":
18447				return false;
18448			case "boolean":
18449			case "string":
18450			case "number":
18451				return a === b;
18452			case "object":
18453				if ( a === null ) {
18454					return b === null;
18455				}
18456				var i, l;
18457				if ( Array.isArray( a ) ) {
18458					if (  !Array.isArray( b ) || a.length !== b.length ) {
18459						return false;
18460					}
18461					for ( i = 0, l = a.length; i < l; i++ ) {
18462						if ( !_equalsJSON( a[ i ], b[ i ] ) ) {
18463							return false;
18464						}
18465					}
18466					return true;
18467				}
18468				var bKeys = _objectKeys( b ),
18469					bLength = bKeys.length;
18470				if ( _objectKeys( a ).length !== bLength ) {
18471					return false;
18472				}
18473				for ( i in a ) {
18474					if ( !_equalsJSON( a[ i ], b[ i ] ) ) {
18475						return false;
18476					}
18477				}
18478				return true;
18479			default:
18480				return false;
18481		}
18482	},
18483	_objectKeys = function( obj ) {
18484		var keys;
18485		if ( Array.isArray( obj ) ) {
18486			keys = new Array( obj.length );
18487			for ( var k = 0; k < keys.length; k++ ) {
18488				keys[ k ] = "" + k;
18489			}
18490			return keys;
18491		}
18492		if ( Object.keys ) {
18493			return Object.keys( obj );
18494		}
18495		keys = [];
18496		for ( var i in obj ) {
18497			if ( Object.prototype.hasOwnProperty.call( obj, i ) ) {
18498				keys.push( i );
18499			}
18500		}
18501		return keys;
18502	},
18503
18504	// Manage JSON value After the json data has been fetched
18505	jsonUpdate = function( event ) {
18506		var elm = event.target,
18507			$elm = $( elm ),
18508			lstCall = $elm.data( dataQueue ),
18509			refId = lstCall.length,
18510			wbJsonConfig = event[ "wb-json" ];
18511
18512		if ( !( wbJsonConfig.url && ( wbJsonConfig.type || wbJsonConfig.source ) ) ) {
18513			throw "Data JSON update not configured properly";
18514		}
18515
18516		lstCall.push( wbJsonConfig );
18517		$elm.data( dataQueue, lstCall );
18518
18519		loadJSON( elm, wbJsonConfig.url, refId );
18520	};
18521
18522$document.on( "json-failed.wb", selector, function( event ) {
18523
18524	var elm = event.currentTarget,
18525		$elm = $( elm ),
18526		lstCall = $elm.data( dataQueue ),
18527		fetchObj = event.fetch,
18528		xhrResponse = fetchObj.xhr,
18529		itmSettings = lstCall[ fetchObj.refId ],
18530		failSettings = itmSettings.fail;
18531
18532	if ( failSettings ) {
18533
18534		// Mapping is always streamline because the data structure is a static object not an array
18535		failSettings.streamline = true;
18536
18537		// apply the template to display an error message
18538		applyTemplate( elm, failSettings, {
18539			error: fetchObj.error.message || xhrResponse.statusText,
18540			status: fetchObj.status,
18541			url: fetchObj.fetchOpts.url,
18542			response: {
18543				text: xhrResponse.responseText || "",
18544				status: xhrResponse.status,
18545				statusText: xhrResponse.statusText
18546			}
18547		} );
18548	}
18549
18550	console.info( event.currentTarget );
18551	console.error( "Error or bad JSON Fetched from url in " + componentName );
18552} );
18553
18554// Load template polyfill
18555Modernizr.load( {
18556	test: ( "content" in document.createElement( "template" ) ),
18557	nope: "site!deps/template" + wb.getMode() + ".js"
18558} );
18559
18560$document.on( "timerpoke.wb " + initEvent + " " + updateEvent + " json-fetched.wb", selector, function( event ) {
18561
18562	if ( event.currentTarget === event.target ) {
18563		switch ( event.type ) {
18564
18565			case "timerpoke":
18566			case "wb-init":
18567				init( event );
18568				break;
18569			case "wb-update":
18570				jsonUpdate( event );
18571				break;
18572			default:
18573				jsonFetched( event );
18574				break;
18575		}
18576	}
18577
18578	return true;
18579} );
18580
18581// Add the timerpoke to initialize the plugin
18582for ( s = 0; s !== selectorsLength; s += 1 ) {
18583	wb.add( selectors[ s ] );
18584}
18585
18586} )( jQuery, window, wb );
18587
18588/**
18589 * @title WET-BOEW Disable Event
18590 * @overview Event creates the active offer for users that have disabled the event.
18591 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
18592 * @author @gc
18593 */
18594( function( $, window, wb ) {
18595"use strict";
18596
18597/*
18598 * Variable and function definitions.
18599 * These are global to the event - meaning that they will be initialized once per page,
18600 * not once per instance of event on the page.
18601 */
18602var componentName = "wb-disable",
18603	selector = "#wb-tphp",
18604	$document = wb.doc,
18605	allowOnDisableClass = "wb-disable-allow",
18606	allowedPlugins = [
18607		{
18608			selectors: "[data-wb-jsonmanager]",
18609			initEvent: "wb-jsonmanager"
18610		},
18611		{
18612			selectors: "[data-wb-postback]",
18613			initEvent: "wb-postback"
18614		},
18615		{
18616			selectors: [
18617				"[data-ajax-after]",
18618				"[data-ajax-append]",
18619				"[data-ajax-before]",
18620				"[data-ajax-prepend]",
18621				"[data-ajax-replace]",
18622				"[data-wb-ajax]"
18623			],
18624			initEvent: "wb-data-ajax"
18625		},
18626		{
18627			selectors: [
18628				"[data-json-after]",
18629				"[data-json-append]",
18630				"[data-json-before]",
18631				"[data-json-prepend]",
18632				"[data-json-replace]",
18633				"[data-json-replacewith]",
18634				"[data-wb-json]"
18635			],
18636			initEvent: "wb-data-json"
18637		}
18638	],
18639
18640	/**
18641	 * @method init
18642	 * @param {jQuery Event} event Event that triggered the function call
18643	 */
18644	init = function( event ) {
18645
18646		// Start initialization
18647		// returns DOM object = proceed with init
18648		// returns undefined = do not proceed with init (e.g., already initialized)
18649		var elm = wb.init( event, componentName, selector, true ),
18650			nQuery = "?",
18651			$html = wb.html,
18652			i18n = wb.i18n,
18653			pageUrl = wb.pageUrlParts,
18654			param,
18655			noticeHeader = i18n( "disable-notice-h" ),
18656			noticeBody = i18n( "disable-notice" ),
18657			noticehtml = "<section",
18658			noticehtmlend = "</a>.</p></section>",
18659			canonicalUrl,
18660			canonicalLink;
18661
18662		if ( elm ) {
18663
18664			// Rebuild the query string
18665			for ( param in pageUrl.params ) {
18666				if ( param && Object.prototype.hasOwnProperty.call( pageUrl.params, param ) && param !== "wbdisable" ) {
18667					nQuery += param + "=" + pageUrl.params[ param ] + "&";
18668				}
18669			}
18670
18671			try {
18672				if ( wb.isDisabled || wb.ie ) {
18673					$html.addClass( "wb-disable" );
18674
18675					try {
18676
18677						// Store preference for WET plugins and polyfills to be disabled in localStorage
18678						localStorage.setItem( "wbdisable", "true" );
18679					} catch ( e ) {
18680
18681						/* swallow error */
18682					}
18683
18684					// Trigger initialization of plugins that are needed in basic mode
18685					runAllowedPlugins();
18686
18687					// Add canonical link if not already present
18688					if ( !document.querySelector( "link[rel=canonical]" ) ) {
18689
18690						// Remove wbdisable from URL
18691						canonicalUrl = window.location.href.replace( /&?wbdisable=true/gi, "" ).replace( "?&", "?" ).replace( "?#", "#" );
18692
18693						if ( canonicalUrl.indexOf( "?" ) === ( canonicalUrl.length - 1 ) ) {
18694							canonicalUrl = canonicalUrl.replace( "?", "" );
18695						}
18696
18697						canonicalLink = document.createElement( "link" );
18698						canonicalLink.rel = "canonical";
18699						canonicalLink.href = canonicalUrl;
18700
18701						document.head.appendChild( canonicalLink );
18702					}
18703
18704					// Add notice and link to re-enable WET plugins and polyfills
18705					let significantLinkId = wb.getId();
18706					noticehtml = noticehtml + " class='container-fluid bg-warning text-center mrgn-tp-sm py-4'><h2 class='mrgn-tp-0'>" + noticeHeader + "</h2><p>" + noticeBody + "</p><p><a id='" + significantLinkId + "' rel='alternate' href='" + nQuery + "wbdisable=false'>" + i18n( "wb-enable" ) + noticehtmlend;
18707					$( elm ).after( noticehtml );
18708					document.querySelector( "#" + significantLinkId ).setAttribute( "property", "significantLink" );
18709					return true;
18710				} else {
18711					$html.addClass( "wb-enable" );
18712
18713					if ( localStorage ) {
18714
18715						// Store preference for WET plugins and polyfills to be enabled in localStorage
18716						localStorage.setItem( "wbdisable", "false" );
18717					}
18718
18719					// Remove variable from URL
18720					var lc = window.location.href.replace( /&?wbdisable=false/gi, "" ).replace( "?&", "?" ).replace( "?#", "#" );
18721					if ( lc.indexOf( "?" ) === ( lc.length - 1 ) ) {
18722						lc = lc.replace( "?", "" );
18723					}
18724					window.history.replaceState( "", "", lc );
18725				}
18726			} catch ( error ) {
18727
18728				/* swallow error */
18729			}
18730
18731			// Append the Basic HTML version link version
18732			// Add link to disable WET plugins and polyfills
18733			wb.addSkipLink( i18n( "wb-disable" ), {
18734				href: nQuery + "wbdisable=true",
18735				rel: "alternate"
18736			}, false, true );
18737
18738			// Identify that initialization has completed
18739			wb.ready( $document, componentName );
18740		}
18741	},
18742
18743	// Trigger initialization of plugins that are needed in basic mode
18744	// TODO: Remove once basic initialization is implemented at the core level
18745	runAllowedPlugins = function() {
18746		allowedPlugins.forEach( allowedPlugin => {
18747			if ( typeof( allowedPlugin.selectors ) === "object" ) {
18748				allowedPlugin.selectors = allowedPlugin.selectors.join( "." + allowOnDisableClass + "," );
18749			}
18750
18751			allowedPlugin.selectors = allowedPlugin.selectors + "." + allowOnDisableClass;
18752
18753			$( allowedPlugin.selectors ).trigger( "wb-init." + allowedPlugin.initEvent );
18754		} );
18755	};
18756
18757// Bind the events
18758$document.on( "timerpoke.wb", selector, init );
18759
18760// Go through allowed plugins once again when content has been added dynamically
18761$document.on( "wb-contentupdated", function() {
18762	runAllowedPlugins();
18763} );
18764
18765// Add the timer poke to initialize the plugin
18766wb.add( selector );
18767
18768} )( jQuery, window, wb );
18769
18770/**
18771 * @title WET-BOEW Focus
18772 * @overview User agent safe way of assigning focus to an element
18773 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
18774 * @author @pjackson28
18775 */
18776( function( $, wb ) {
18777"use strict";
18778
18779var $document = wb.doc,
18780	$window = wb.win,
18781	clickEvents = "click",
18782	setFocusEvent = "setfocus.wb",
18783	linkSelector = "a[href]",
18784	$linkTarget,
18785
18786	/**
18787	 * @method processHash
18788	 */
18789	processHash = function() {
18790		var hash = wb.pageUrlParts.hash;
18791
18792		if ( hash && ( $linkTarget = $( "#" + wb.jqEscape( hash.substring( 1 ) ) ) ).length !== 0 ) {
18793			$linkTarget.trigger( setFocusEvent );
18794		}
18795	};
18796
18797// Bind the setfocus event
18798$document.on( setFocusEvent, function( event ) {
18799	if ( event.namespace === "wb" ) {
18800		var elm = event.target,
18801			$elm = $( elm ),
18802			$closedParents = $elm.not( "summary" ).parents( "details, [role='tabpanel']" ),
18803			$closedPanels, $closedPanel, len, i;
18804
18805		if ( $closedParents.length !== 0 ) {
18806
18807			// Open any closed ancestor details elements
18808			$closedParents.not( "[open]" ).children( "summary" ).trigger( "click" );
18809
18810			// Open any closed tabpanels
18811			$closedPanels = $closedParents.filter( "[aria-hidden='true']" );
18812			len = $closedPanels.length;
18813			for ( i = 0; i !== len; i += 1 ) {
18814				$closedPanel = $closedPanels.eq( i );
18815				$closedPanel.closest( ".wb-tabs" )
18816					.find( "#" + $closedPanel.attr( "aria-labelledby" ) )
18817					.trigger( "click" );
18818			}
18819		}
18820
18821		// Assigns focus to an element (delay allows for revealing of hidden content)
18822		setTimeout( function() {
18823			let addedTabIndexAttr = false;
18824
18825			$elm.trigger( "focus" );
18826
18827			// If the element hasn't gained focus, retry with a tabindex attribute...
18828			if ( elm !== document.activeElement && elm.getAttribute( "tabindex" ) === null ) {
18829
18830				// Add a tabindex="-1" attribute and keep track of it for later
18831				elm.setAttribute( "tabindex", "-1" );
18832				addedTabIndexAttr = true;
18833
18834				// Try re-focusing
18835				$elm.trigger( "focus" );
18836			}
18837
18838			// If the element has gained focus...
18839			if ( elm === document.activeElement ) {
18840				const $topBar = $( ".wb-bar-t[aria-hidden=false]" );
18841
18842				// Ensure the top bar overlay does not conceal the focus target
18843				if ( $topBar.length !== 0 ) {
18844					document.documentElement.scrollTop -= $topBar.outerHeight();
18845				}
18846			} else {
18847
18848				// Remove the element's tabindex attribute (no point in keeping it if it didn't help)
18849				if ( addedTabIndexAttr ) {
18850					elm.removeAttribute( "tabindex" );
18851				}
18852
18853				// Show a console error
18854				console.error( setFocusEvent + ": Unable to focus onto the destination element... maybe it's hidden?" );
18855				console.error( elm );
18856			}
18857
18858			return $elm;
18859		}, 100 );
18860	}
18861} );
18862
18863// Set focus to the target of a deep link from a different page
18864// (helps browsers that can't set the focus on their own)
18865$document.on( "wb-ready.wb", processHash );
18866
18867// Handle any changes to the URL hash after the page has loaded
18868$window.on( "hashchange", function() {
18869	wb.pageUrlParts.hash = window.location.hash;
18870	if ( !wb.ignoreHashChange ) {
18871		processHash();
18872	}
18873} );
18874
18875// Helper for browsers that can't change keyboard and/or event focus on a same page link click
18876$document.on( clickEvents, linkSelector, function( event ) {
18877	var testHref = event.currentTarget.getAttribute( "href" );
18878
18879	// Same page links only
18880	if ( testHref.length > 1 && testHref.charAt( 0 ) === "#" && !event.isDefaultPrevented() &&
18881		( $linkTarget = $( "#" + wb.jqEscape( testHref.substring( 1 ) ) ) ).length !== 0 ) {
18882		wb.ignoreHashChange = true;
18883		$linkTarget.trigger( setFocusEvent );
18884	}
18885} );
18886
18887} )( jQuery, wb );
18888
18889/**
18890 * @title WET-BOEW JSON Manager
18891 * @overview Manage JSON dataset, execute JSON patch operation.
18892 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
18893 * @author @duboisp
18894 */
18895/*global jsonpointer, jsonpatch */
18896( function( $, window, wb ) {
18897"use strict";
18898
18899/*
18900 * Variable and function definitions.
18901 * These are global to the plugin - meaning that they will be initialized once per page,
18902 * not once per instance of plugin on the page. So, this is a good place to define
18903 * variables that are common to all instances of the plugin on a page.
18904 */
18905var componentName = "wb-jsonmanager",
18906	selector = "[data-" + componentName + "]",
18907	initEvent = "wb-init." + componentName,
18908	postponeEvent = "postpone." + componentName,
18909	patchesEvent = "patches." + componentName,
18910	jsonFailedClass = "jsonfail",
18911	reloadFlag = "data-" + componentName + "-reload",
18912	dsNameRegistered = [],
18913	datasetCache = {},
18914	datasetCacheSettings = {},
18915	dsDelayed = {},
18916	dsPostponePatches = {},
18917	dsFetching = {},
18918	dsFetchIsArray = {},
18919	dsFetchMerged = {},
18920	$document = wb.doc,
18921	defaults = {
18922		ops: [
18923			{
18924				name: "patches",
18925				fn: function( obj, key, tree ) {
18926					var path = this.path,
18927						patches = this.patches,
18928						newTree = jsonpointer.get( tree, path );
18929
18930					patches.forEach( ( patchConf ) => {
18931						patchConf.mainTree = tree;
18932						patchConf.pathParent = path;
18933						jsonpatch.apply( newTree, [ patchConf ] );
18934					} );
18935				}
18936			},
18937			{
18938				name: "wb-count",
18939				fn: function( obj, key, tree ) {
18940					var countme = obj[ key ],
18941						len = 0, i_len, i,
18942						filter = this.filter || [ ],
18943						filternot = this.filternot || [ ];
18944
18945					if ( !Array.isArray( filter ) ) {
18946						filter = [ filter ];
18947					}
18948					if ( !Array.isArray( filternot ) ) {
18949						filternot = [ filternot ];
18950					}
18951
18952					if ( ( filter.length || filternot.length ) && Array.isArray( countme ) ) {
18953
18954						// Iterate in obj[key] / item and check if is true for the given path is any.
18955						i_len = countme.length;
18956
18957						for ( i = 0; i !== i_len; i = i + 1 ) {
18958							if ( filterPassJSON( countme[ i ], filter, filternot ) ) {
18959								len = len + 1;
18960							}
18961						}
18962					} else if ( Array.isArray( countme ) ) {
18963						len = countme.length;
18964					}
18965					applyPatch( tree, "add", this.set, len );
18966				}
18967			},
18968			{
18969				name: "wb-first",
18970				fn: function( obj, key, tree ) {
18971					var currObj = obj[ key ];
18972					if ( !Array.isArray( currObj ) || currObj.length === 0 ) {
18973						return;
18974					}
18975
18976					applyPatch( tree, "add", this.set, currObj[ 0 ] );
18977				}
18978			},
18979			{
18980				name: "wb-last",
18981				fn: function( obj, key, tree ) {
18982					var currObj = obj[ key ];
18983					if ( !Array.isArray( currObj ) || currObj.length === 0 ) {
18984						return;
18985					}
18986
18987					applyPatch( tree, "add", this.set, currObj[ currObj.length - 1 ] );
18988				}
18989			},
18990			{
18991				name: "wb-nbtolocal",
18992				fn: function( obj, key, tree ) {
18993					var val = obj[ key ],
18994						loc = this.locale || window.wb.lang,
18995						suffix = this.suffix || "",
18996						prefix = this.prefix || "";
18997
18998					if ( typeof val === "string" ) {
18999						val = parseFloat( val );
19000						if ( isNaN( val ) ) {
19001							return;
19002						}
19003					}
19004
19005					applyPatch( tree, "replace", this.path, prefix + val.toLocaleString( loc ) + suffix );
19006				}
19007			},
19008			{
19009				name: "wb-decodeUTF8Base64",
19010				fn: function( obj, key, tree ) {
19011					var val = obj[ key ];
19012
19013					if ( !this.set ) {
19014						applyPatch( tree, "replace", this.path, wb.decodeUTF8Base64( val ) );
19015					} else {
19016						applyPatch( tree, "add", this.set, wb.decodeUTF8Base64( val ) );
19017					}
19018				}
19019			},
19020			{
19021				name: "wb-escapeHTML",
19022				fn: function( obj, key, tree ) {
19023					var val = obj[ key ];
19024
19025					if ( !this.set ) {
19026						applyPatch( tree, "replace", this.path, wb.escapeHTML( val ) );
19027					} else {
19028						applyPatch( tree, "add", this.set, wb.escapeHTML( val ) );
19029					}
19030				}
19031			},
19032			{
19033				name: "wb-toDateISO",
19034				fn: function( obj, key, tree ) {
19035					if ( !this.set ) {
19036						applyPatch( tree, "replace", this.path, wb.date.toDateISO( obj[ key ] ) );
19037					} else {
19038						applyPatch( tree, "add", this.set, wb.date.toDateISO( obj[ key ] ) );
19039					}
19040				}
19041			},
19042			{
19043				name: "wb-toDateTimeISO",
19044				fn: function( obj, key, tree ) {
19045					if ( !this.set ) {
19046						applyPatch( tree, "replace", this.path, wb.date.toDateISO( obj[ key ], true ) );
19047					} else {
19048						applyPatch( tree, "add", this.set, wb.date.toDateISO( obj[ key ], true ) );
19049					}
19050				}
19051			},
19052			{
19053				name: "wb-swap",
19054				fn: function( obj, key, tree ) {
19055					var val = obj[ key ],
19056						ref = this.ref,
19057						mainTree = this.mainTree || obj,
19058						path = this.path,
19059						newVal,
19060						refObject, refIsArray, valWasArray,
19061						i, i_len, i_item,
19062						j, j_len, j_item;
19063
19064					if ( val ) {
19065
19066						refObject = jsonpointer.get( mainTree, ref );
19067						refIsArray = Array.isArray( refObject );
19068						valWasArray = Array.isArray( val );
19069
19070						if ( !valWasArray ) {
19071							val =  [ val ];
19072						}
19073
19074						i_len = val.length;
19075
19076						for ( i = 0; i !== i_len; i++ ) {
19077							i_item = val[ i ];
19078							newVal = undefined; // Reinit
19079							if ( !refIsArray ) {
19080								i_item = i_item.replaceAll( "~", "~0" ).replaceAll( "/", "~1" ); // Escape slashed and tilde in val when the key is an IRI for JSON pointer compatibility
19081								newVal = mainTree ? jsonpointer.get( mainTree, ref + "/" 
19081+ i_item ) : jsonpointer.get( tree, ref + "/" + i_item );
19082							} else {
19083
19084								// Iterate until we found a corresponding value in the property "@id"
19085								j_len = refObject.length;
19086								for ( j = 0; j !== j_len; j++ ) {
19087									j_item = refObject[ j ];
19088									if ( j_item[ "@id" ] && j_item[ "@id" ] === i_item ) {
19089										newVal = j_item;
19090										break;
19091									}
19092								}
19093								if ( !newVal ) {
19094									console.error( "wb-swap: Unable to find a corresponding value for: " + val + " in reference " + ref );
19095									break;
19096								}
19097							}
19098							if ( newVal && !valWasArray ) {
19099								applyPatch( tree, "replace", path, newVal );
19100							} else if ( newVal ) {
19101								applyPatch( tree, "replace", path + "/" + i, newVal );
19102							}
19103						}
19104					}
19105				}
19106			}
19107		],
19108		opsArray: [
19109			{
19110				name: "wb-toDateISO",
19111				fn: function( arr )  {
19112					var setval = this.set,
19113						pathval = this.path,
19114						i, i_len = arr.length;
19115					for ( i = 0; i !== i_len; i += 1 ) {
19116						if ( setval ) {
19117							jsonpatch.apply( arr, [
19118								{ op: "wb-toDateISO", set: "/" + i + setval, path: "/" + i + pathval }
19119							] );
19120						} else {
19121							jsonpatch.apply( arr, [
19122								{ op: "wb-toDateISO", path: "/" + i + pathval }
19123							] );
19124						}
19125					}
19126				}
19127			},
19128			{
19129				name: "wb-toDateTimeISO",
19130				fn: function( arr ) {
19131					var setval = this.set,
19132						pathval = this.path,
19133						i, i_len = arr.length;
19134					for ( i = 0; i !== i_len; i += 1 ) {
19135						if ( setval ) {
19136							jsonpatch.apply( arr, [
19137								{ op: "wb-toDateTimeISO", set: "/" + i + setval, path: "/" + i + pathval }
19138							] );
19139						} else {
19140							jsonpatch.apply( arr, [
19141								{ op: "wb-toDateTimeISO", path: "/" + i + pathval }
19142							] );
19143						}
19144					}
19145				}
19146			},
19147			{
19148				name: "wb-swap",
19149				fn: function( arr ) {
19150					arr.forEach( ( item, i ) => {
19151						jsonpatch.apply( arr, [
19152							{ op: "wb-swap", path: "/" + i + this.path, ref: this.ref, mainTree: this.mainTree }
19153						] );
19154					} );
19155				}
19156			},
19157			{
19158				name: "patches",
19159				fn: function( arr ) {
19160					arr.forEach( ( item, i ) => {
19161						jsonpatch.apply( this.mainTree || arr, [
19162							{ op: "patches", path: ( this.pathParent || "" ) + "/" + i + this.path, patches: this.patches }
19163						] );
19164					} );
19165				}
19166			}
19167		],
19168		opsRoot: [],
19169		settings: { },
19170		docMapKeys: { "referer": document.referrer, "locationHref": location.href }
19171	},
19172
19173	// Add debug information after the JSON manager element
19174	debugPrintOut = function( $elm, name, json, patches ) {
19175		$elm.after( "<p lang=\"en\"><strong>JSON Manager Debug</strong> (" +  name + ")</p><ul lang=\"en\"><li>JSON: <pre><code>" + JSON.stringify( json ) + "</code></pre></li><li>Patches: <pre><code>" + JSON.stringify( patches ) + "</code></pre>" );
19176		console.log( json );
19177	},
19178
19179	/**
19180	 * @method init
19181	 * @param {jQuery Event} event Event that triggered the function call
19182	 */
19183	init = function( event ) {
19184
19185		// Start initialization
19186		// returns DOM object = proceed with init
19187		// returns undefined = do not proceed with init (e.g., already initialized)
19188		var elm = wb.init( event, componentName, selector ),
19189			$elm,
19190			jsSettings = window[ componentName ] || { },
19191			ops, opsArray, opsRoot,
19192			i, i_len, i_cache,
19193			url, urlActual, dsName,
19194			fetchOpts = { };
19195
19196		if ( elm ) {
19197			$elm = $( elm );
19198
19199			// Load handlebars
19200			Modernizr.load( {
19201
19202				// For loading multiple dependencies
19203				load: [
19204					"site!deps/json-patch" + wb.getMode() + ".js",
19205					"site!deps/jsonpointer" + wb.getMode() + ".js"
19206				],
19207				testReady: function() {
19208					return window.jsonpatch && window.jsonpointer;
19209				},
19210				complete: function() {
19211					var elmData = wb.getData( $elm, componentName );
19212
19213					if ( !defaults.registered ) {
19214						ops = defaults.ops.concat( jsSettings.ops || [ ] );
19215						opsArray = defaults.opsArray.concat( jsSettings.opsArray || [ ] );
19216						opsRoot = defaults.opsRoot.concat( jsSettings.opsRoot || [ ] );
19217
19218						if ( ops.length ) {
19219							for ( i = 0, i_len = ops.length; i !== i_len; i++ ) {
19220								i_cache = ops[ i ];
19221								jsonpatch.registerOps( i_cache.name, i_cache.fn );
19222							}
19223						}
19224						if ( opsArray.length ) {
19225							for ( i = 0, i_len = opsArray.length; i !== i_len; i++ ) {
19226								i_cache = opsArray[ i ];
19227								jsonpatch.registerOpsArray( i_cache.name, i_cache.fn );
19228							}
19229						}
19230						if ( opsRoot.length ) {
19231							for ( i = 0, i_len = opsRoot.length; i !== i_len; i++ ) {
19232								i_cache = opsRoot[ i ];
19233								jsonpatch.registerOpsRoot( i_cache.name, i_cache.fn );
19234							}
19235						}
19236						defaults.settings = $.extend( {}, defaults.settings, jsSettings.settings || {} );
19237						defaults.registered = true;
19238					}
19239
19240					dsName = elmData.name;
19241					if ( !dsName || dsName in dsNameRegistered ) {
19242						throw "Dataset name must be unique";
19243					}
19244					dsNameRegistered.push( dsName );
19245
19246					url = elmData.url;
19247
19248					if ( url ) {
19249
19250						url = typeof url === "string" ? [ url ] : url;
19251						i_len = url.length;
19252
19253						dsFetching[ dsName ] = i_len;
19254
19255						for ( i = 0; i !== i_len; i++ ) {
19256
19257							urlActual = url[ i ];
19258
19259							// Fetch default configuration
19260							fetchOpts = {
19261								nocache: elmData.nocache,
19262								nocachekey: elmData.nocachekey,
19263								data: elmData.data,
19264								contentType: elmData.contenttype,
19265								method: elmData.method
19266							};
19267
19268							// When the "url" is an extended configuration
19269							if ( urlActual.url ) {
19270								fetchOpts.savingPath = urlActual.path || "";
19271								fetchOpts.url = urlActual.url;
19272							} else {
19273								fetchOpts.url = urlActual;
19274							}
19275
19276							// Fetch the JSON
19277							$elm.trigger( {
19278								type: "json-fetch.wb",
19279								fetch: fetchOpts
19280							} );
19281
19282							// If the URL is a dataset, make it ready
19283							if ( fetchOpts.url.charCodeAt( 0 ) === 35 && fetchOpts.url.charCodeAt( 1 ) === 91 ) {
19284								wb.ready( $elm, componentName );
19285							}
19286						}
19287					} else if ( !url && elmData.extractor ) {
19288						$elm.trigger( {
19289							type: "json-fetched.wb",
19290							fetch: {
19291								response: {}
19292							}
19293						} );
19294						wb.ready( $elm, componentName );
19295
19296					} else {
19297
19298						$elm.trigger( {
19299							type: "json-fetch.wb"
19300						} );
19301						wb.ready( $elm, componentName );
19302					}
19303
19304				}
19305			} );
19306		}
19307	},
19308	extractData = function( elmObj ) {
19309
19310		var isGroup = false,
19311			selectedTag,
19312			targetTag,
19313			lastIndex = [],
19314			j_tag = "",
19315			group = {},
19316			arrMap = [],
19317			node_children = [],
19318			j_node = 0,
19319			arrRepeatPath = [],
19320			combineToObj = function( cur_obj ) {
19321				if ( cur_obj.selector === j_tag ) {
19322					if ( !lastIndex.includes( j_tag ) ) {
19323						group[ cur_obj.path ] = cur_obj.attr && node_children[ j_node ].getAttributeNode( cur_obj.attr ) ?
19324							node_children[ j_node ].getAttributeNode( cur_obj.attr ).textContent :
19325							node_children[ j_node ].textContent;
19326						lastIndex.push( j_tag );
19327					}
19328				}
19329			},
19330			manageObjDir = function( selector, selectedValue, json_return ) {
19331				var arrPath = selector.path.split( "/" ).filter( Boolean );
19332
19333				// Check if selectedValue is an empty value returned by querySelectorAll
19334				if ( selectedValue && selectedValue instanceof NodeList && selectedValue.length === 0 ) {
19335					selectedValue = null;
19336				}
19337
19338				if ( arrPath.length > 1 ) {
19339					var pointer = "";
19340					pointer = arrPath.pop();
19341
19342					if ( arrPath[ 0 ] && arrPath[ 0 ] !== "" ) {
19343
19344						if ( !json_return[ arrPath[ 0 ] ] && !arrRepeatPath.includes( arrPath[ 0 ] ) ) {
19345							arrRepeatPath.push( arrPath[ 0 ] );
19346							json_return[ arrPath[ 0 ] ] = {};
19347						}
19348						if ( selector.selectAll && !json_return[ arrPath[ 0 ] ] [ pointer ]  ) {
19349							json_return[ arrPath[ 0 ] ] [ pointer ] = [];
19350						}
19351						if ( selector.selectAll ) {
19352							json_return[ arrPath[ 0 ] ] [ pointer ].push( selectedValue );
19353						} else {
19354							json_return[ arrPath[ 0 ] ] [ pointer ] = selectedValue;
19355						}
19356
19357					} else {
19358
19359						if ( selector.selectAll ) {
19360							json_return[ arrPath[ 0 ] ].push( selectedValue );
19361						} else {
19362							json_return[ pointer ] = selectedValue;
19363						}
19364					}
19365				} else {
19366
19367					if ( selector.selectAll ) {
19368						if ( !json_return[ selectedTag.path ] ) {
19369							json_return[ selectedTag.path ] = [];
19370						}
19371						json_return[ selectedTag.path ].push( selectedValue );
19372					} else {
19373						json_return[ selectedTag.path ] = selectedValue;
19374					}
19375				}
19376			},
19377			jsonSource = {};
19378
19379
19380		for ( var tag = 0; tag <= elmObj.length - 1; tag++ ) {
19381
19382			selectedTag = elmObj[ tag ];
19383
19384			if ( !selectedTag.interface ) {
19385
19386				targetTag = document.querySelectorAll( selectedTag.selector || "" );
19387				isGroup = selectedTag.extractor && selectedTag.extractor.length >= 1 ? true : false;
19388
19389				if ( selectedTag.selectAll ) {
19390
19391					for ( var i_node = 0; i_node <= targetTag.length - 1; i_node++ ) {
19392
19393						var selectedTagValue = selectedTag.attr && targetTag [ i_node ].getAttributeNode( selectedTag.attr ) ?
19394							targetTag [ i_node ].getAttributeNode( selectedTag.attr ).textContent :
19395							targetTag [ i_node ].textContent;
19396
19397						manageObjDir( selectedTag, selectedTagValue, jsonSource );
19398					}
19399				}
19400
19401				// extract from combined selectors and group the values e.g dt with dd
19402				if ( isGroup ) {
19403
19404					jsonSource[ selectedTag.path ] = [];
19405
19406					node_children = targetTag[ 0 ].children;
19407
19408					var extractorLength = Object.keys( selectedTag.extractor ).length;
19409
19410					for ( j_node = 0; j_node <= node_children.length - 1; j_node++ ) {
19411
19412						j_tag = node_children[ j_node ].tagName.toLowerCase();
19413
19414						selectedTag.extractor.find( combineToObj );
19415						if ( Object.keys( group ).length === extractorLength ) {
19416							arrMap.push( group );
19417							group = {};
19418							lastIndex = [];
19419						}
19420					}
19421					$.extend( jsonSource[ selectedTag.path ], arrMap );
19422				}
19423
19424				if ( targetTag.length ) {
19425					targetTag = selectedTag.attr && targetTag [ 0 ].getAttributeNode( selectedTag.attr ) ?
19426						targetTag [ 0 ].getAttributeNode( selectedTag.attr ).textContent :
19427						targetTag [ 0 ].textContent;
19428				}
19429
19430			} else {
19431
19432				targetTag = defaults.docMapKeys[ selectedTag.interface ];
19433
19434				manageObjDir( selectedTag, targetTag, jsonSource );
19435			}
19436
19437			if ( !selectedTag.selectAll  ) {
19438				if ( isGroup === false ) {
19439					manageObjDir( selectedTag, targetTag, jsonSource );
19440				}
19441			}
19442		}
19443
19444		return jsonSource;
19445	},
19446
19447	// Filtering a JSON
19448	// Return true if trueness && falseness
19449	// Return false if !( trueness && falseness )
19450	// trueness and falseness is an array of { "path": "", "value": "" } object
19451	filterPassJSON = function( obj, trueness, falseness ) {
19452		var i, i_cache,
19453			trueness_len = trueness.length,
19454			falseness_len = falseness.length,
19455			compareResult = false,
19456			isEqual;
19457
19458		if ( trueness_len || falseness_len ) {
19459
19460			for ( i = 0; i < trueness_len; i += 1 ) {
19461				i_cache = trueness[ i ];
19462				isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value );
19463
19464				if ( i_cache.optional ) {
19465					compareResult = compareResult || isEqual;
19466				} else if ( !isEqual ) {
19467					return false;
19468				} else {
19469					compareResult = true;
19470				}
19471			}
19472			if ( trueness_len && !compareResult ) {
19473				return false;
19474			}
19475
19476			for ( i = 0; i < falseness_len; i += 1 ) {
19477				i_cache = falseness[ i ];
19478				isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value );
19479
19480				if ( isEqual && !i_cache.optional || isEqual && i_cache.optional ) {
19481					return false;
19482				}
19483			}
19484
19485		}
19486		return true;
19487	},
19488
19489	// Utility function to compare two JSON value
19490	_equalsJSON = function( a, b ) {
19491		switch ( typeof a ) {
19492			case "undefined":
19493				return false;
19494			case "boolean":
19495			case "string":
19496			case "number":
19497				return a === b;
19498			case "object":
19499				if ( a === null ) {
19500					return b === null;
19501				}
19502				var i, l;
19503				if ( Array.isArray( a ) ) {
19504					if (  Array.isArray( b ) || a.length !== b.length ) {
19505						return false;
19506					}
19507					for ( i = 0, l = a.length; i < l; i++ ) {
19508						if ( !_equalsJSON( a[ i ], b[ i ] ) ) {
19509							return false;
19510						}
19511					}
19512					return true;
19513				}
19514				var bKeys = _objectKeys( b ),
19515					bLength = bKeys.length;
19516				if ( _objectKeys( a ).length !== bLength ) {
19517					return false;
19518				}
19519				for ( i = 0; i < bLength; i++ ) {
19520					if ( !_equalsJSON( a[ i ], b[ i ] ) ) {
19521						return false;
19522					}
19523				}
19524				return true;
19525			default:
19526				return false;
19527		}
19528	},
19529	_objectKeys = function( obj ) {
19530		var keys;
19531		if ( Array.isArray( obj ) ) {
19532			keys = new Array( obj.length );
19533			for ( var k = 0; k < keys.length; k++ ) {
19534				keys[ k ] = "" + k;
19535			}
19536			return keys;
19537		}
19538		if ( Object.keys ) {
19539			return Object.keys( obj );
19540		}
19541		keys = [];
19542		for ( var i in obj ) {
19543			if ( Object.prototype.hasOwnProperty.call( obj, i ) ) {
19544				keys.push( i );
19545			}
19546		}
19547		return keys;
19548	},
19549
19550	// Utility function to apply a JSON patch
19551	applyPatch = function( tree, op, path, value ) {
19552		jsonpatch.apply( tree, [
19553			{ op: op, path: path, value: value }
19554		] );
19555	},
19556
19557	// Create series of patches for filtering
19558	getPatchesToFilter = function( JSONsource, filterPath, filterTrueness, filterFalseness ) {
19559		var filterObj,
19560			i, i_len;
19561
19562		if ( !Array.isArray( filterTrueness ) ) {
19563			filterTrueness = [ filterTrueness ];
19564		}
19565		if ( !Array.isArray( filterFalseness ) ) {
19566			filterFalseness = [ filterFalseness ];
19567		}
19568
19569		filterObj = jsonpointer.get( JSONsource, filterPath );
19570		if ( Array.isArray( filterObj ) ) {
19571			i_len = filterObj.length - 1;
19572			for ( i = i_len; i !== -1; i -= 1 ) {
19573				if ( !filterPassJSON( filterObj[ i ], filterTrueness, filterFalseness ) ) {
19574					jsonpatch.apply( JSONsource, [ { op: "remove", path: filterPath + "/" + i } ] );
19575				}
19576			}
19577		}
19578		return JSONsource;
19579	};
19580
19581$document.on( "json-failed.wb", selector, function( event ) {
19582	var elm = event.target,
19583		$elm;
19584
19585	if ( elm === event.currentTarget ) {
19586		$elm = $( elm );
19587		$elm.addClass( jsonFailedClass );
19588
19589		// Identify that initialization has completed
19590		wb.ready( $elm, componentName );
19591	}
19592} );
19593
19594$document.on( "json-fetched.wb", selector, function( event ) {
19595	var elm = event.target,
19596		$elm = $( elm ),
19597		settings,
19598		fetchedOpts = event.fetch.fetchedOpts,
19599		isReloading = elm.hasAttribute( reloadFlag ),
19600		dsName,
19601		JSONresponse = event.fetch.response,
19602		isArrayResponse,
19603		resultSet,
19604		i, i_len, i_cache, backlog, selector,
19605		objIterator, savingPathSplit,
19606		patches, filterTrueness, filterFalseness, filterPath, extractor;
19607
19608	if ( elm === event.currentTarget ) {
19609		settings = wb.getData( $elm, componentName );
19610
19611		// Is the fetched JSON need to be wrap in another plain object
19612		if ( fetchedOpts && fetchedOpts.savingPath ) {
19613			savingPathSplit = fetchedOpts.savingPath.split( "/" );
19614
19615			for ( i = savingPathSplit.length - 1; i > 0; i-- ) {
19616				if ( !savingPathSplit[ i ] ) {
19617					continue;
19618				}
19619				objIterator = {};
19620				objIterator[ savingPathSplit[ i ] ] = JSONresponse;
19621				JSONresponse = objIterator;
19622			}
19623		}
19624
19625		// Determine if the response is an array
19626		isArrayResponse = Array.isArray( JSONresponse );
19627
19628		// Ensure the response is an independent clone
19629		if ( isArrayResponse ) {
19630			JSONresponse = $.extend( true, [], JSONresponse );
19631		} else {
19632			JSONresponse = $.extend( true, {}, JSONresponse );
19633		}
19634
19635		dsName = settings.name;
19636		dsFetching[ dsName ]--;
19637
19638		// Ensure that we do have fetched and merged all urls everything before to move ahead
19639		dsFetchIsArray[ dsName ] = dsFetchIsArray[ dsName ] ? dsFetchIsArray[ dsName ] : isArrayResponse;
19640
19641		if ( dsFetchIsArray[ dsName ] !== isArrayResponse ) {
19642			throw "Can't merge, incompatible JSON type (array vs object)";
19643		}
19644
19645		if ( !dsFetchMerged[ dsName ] ) {
19646			dsFetchMerged[ dsName ] = JSONresponse;
19647		} else if ( dsFetchMerged[ dsName ] && isArrayResponse ) {
19648			dsFetchMerged[ dsName ] = dsFetchMerged[ dsName ].concat( JSONresponse );
19649		} else {
19650			dsFetchMerged[ dsName ] = $.extend( dsFetchMerged[ dsName ], JSONresponse );
19651		}
19652
19653		// Quit and wait for the next fetch
19654		if ( !isReloading && dsFetching[ dsName ] ) {
19655			return;
19656		}
19657
19658		JSONresponse = dsFetchMerged[ dsName ];
19659
19660		extractor = settings.extractor;
19661		if ( extractor ) {
19662			if ( !Array.isArray( extractor ) ) {
19663				extractor = [ extractor ];
19664			}
19665			JSONresponse = $.extend( JSONresponse, extractData( extractor ) );
19666
19667		}
19668
19669		dsName = "[" + dsName + "]";
19670		patches = settings.patches || [];
19671		filterPath = settings.fpath;
19672		filterTrueness = settings.filter || [];
19673		filterFalseness = settings.filternot || [];
19674
19675		if ( !Array.isArray( patches ) ) {
19676			patches = [ patches ];
19677		}
19678
19679		// Apply a filtering
19680		if ( filterPath ) {
19681			JSONresponse = getPatchesToFilter( JSONresponse, filterPath, filterTrueness, filterFalseness );
19682		}
19683
19684		// Apply the wraproot
19685		if ( settings.wraproot  ) {
19686			i_cache = { };
19687			i_cache[ settings.wraproot ] = JSONresponse;
19688			JSONresponse = i_cache;
19689		}
19690
19691		// Apply the patches
19692		if ( patches.length ) {
19693			jsonpatch.apply( JSONresponse, patches );
19694		}
19695
19696		if ( settings.debug ) {
19697			debugPrintOut( $elm, "initEvent", JSONresponse, patches );
19698		}
19699
19700		try {
19701			datasetCache[ dsName ] = JSONresponse;
19702		} catch ( error ) {
19703			return;
19704		}
19705		datasetCacheSettings[ dsName ] = settings;
19706
19707		if ( isReloading ) {
19708			elm.removeAttribute( reloadFlag );
19709			i_cache = dsPostponePatches[ dsName ];
19710			if ( i_cache ) {
19711				$elm.trigger( i_cache );
19712			}
19713		}
19714
19715		if ( !settings.wait && dsDelayed[ dsName ] ) {
19716			backlog = dsDelayed[ dsName ];
19717			i_len = backlog.length;
19718			for ( i = 0; i !== i_len; i += 1 ) {
19719				i_cache = backlog[ i ];
19720				selector = i_cache.selector;
19721				if ( selector.length ) {
19722					try {
19723						resultSet = jsonpointer.get( JSONresponse, selector );
19724					} catch  ( e ) {
19725						throw dsName + " - JSON selector not found: " + selector;
19726					}
19727				} else {
19728					resultSet = JSONresponse;
19729				}
19730				$( "#" + i_cache.callerId ).trigger( {
19731					type: "json-fetched.wb",
19732					fetch: {
19733						response: resultSet,
19734						status: "200",
19735						refId: i_cache.refId,
19736						xhr: null
19737					}
19738				}, this );
19739			}
19740		}
19741
19742		// Identify that initialization has completed
19743		wb.ready( $elm, componentName );
19744	}
19745} );
19746
19747// Apply patches to a preloaded JSON data
19748$document.on( patchesEvent, selector, function( event ) {
19749	var elm = event.target,
19750		$elm = $( elm ),
19751		patches = event.patches,
19752		filterPath = event.fpath,
19753		filterTrueness = event.filter || [],
19754		filterFalseness = event.filternot || [],
19755		isCumulative = !!event.cumulative,
19756		settings,
19757		dsName,
19758		dsJSON, resultSet,
19759		delayedLst,
19760		i, i_len, i_cache, pntrSelector;
19761
19762	if ( elm === event.currentTarget && Array.isArray( patches ) ) {
19763		settings = wb.getData( $elm, componentName );
19764
19765		if ( !settings ) {
19766			return true;
19767		}
19768		dsName = "[" + settings.name + "]";
19769
19770		// Check if the patches need to be hold until the next json-fetch event
19771		if ( elm.hasAttribute( reloadFlag ) ) {
19772			dsPostponePatches[ dsName ] = event;
19773			return true;
19774		}
19775
19776		if ( !dsDelayed[ dsName ] ) {
19777			throw "Applying patched on undefined dataset name: " + dsName;
19778		}
19779
19780		dsJSON = datasetCache[ dsName ];
19781		if ( !isCumulative ) {
19782			dsJSON = $.extend( true, ( Array.isArray( dsJSON ) ? [] : {} ), dsJSON );
19783		}
19784
19785		// Apply a filtering
19786		if ( filterPath ) {
19787			dsJSON = getPatchesToFilter( dsJSON, filterPath, filterTrueness, filterFalseness );
19788		}
19789
19790		jsonpatch.apply( dsJSON, patches );
19791
19792		if ( settings.debug ) {
19793			debugPrintOut( $elm, "patchesEvent", dsJSON, patches );
19794		}
19795
19796		delayedLst = dsDelayed[ dsName ];
19797		i_len = delayedLst.length;
19798		for ( i = 0; i !== i_len; i += 1 ) {
19799			i_cache = delayedLst[ i ];
19800			pntrSelector = i_cache.selector;
19801			if ( pntrSelector.length ) {
19802				try {
19803					resultSet = jsonpointer.get( dsJSON, pntrSelector );
19804				} catch  ( e ) {
19805					throw dsName + " - JSON selector not found: " + pntrSelector;
19806				}
19807			} else {
19808				resultSet = dsJSON;
19809			}
19810			$( "#" + i_cache.callerId ).trigger( {
19811				type: "json-fetched.wb",
19812				fetch: {
19813					response: resultSet,
19814					status: "200",
19815					refId: i_cache.refId,
19816					xhr: null
19817				}
19818			}, this );
19819		}
19820	}
19821} );
19822
19823
19824// Used by the JSON-fetch plugin for when trying fetching a resource that is mapped a dataset name
19825$document.on( postponeEvent, function( event ) {
19826	var jsonPostpone = event.postpone,
19827		dsName = jsonPostpone.dsname,
19828		callerId = jsonPostpone.callerId,
19829		refId = jsonPostpone.refId,
19830		selector = jsonPostpone.selector,
19831		resultSet;
19832
19833	if ( !dsDelayed[ dsName ] ) {
19834		dsDelayed[ dsName ] = [ ];
19835	}
19836
19837	// Add to the delayed updates list
19838	dsDelayed[ dsName ].push( {
19839		"callerId": callerId,
19840		"refId": refId,
19841		"selector": selector
19842	} );
19843
19844	// Send the data if the dataset is ready?
19845	if ( datasetCache[ dsName ] && !datasetCacheSettings[ dsName ].wait ) {
19846		resultSet = datasetCache[ dsName ];
19847		if ( selector.length ) {
19848			try {
19849				resultSet = jsonpointer.get( resultSet, selector );
19850			} catch  ( e ) {
19851				throw dsName + " - JSON selector not found: " + selector;
19852			}
19853		}
19854		$( "#" + callerId ).trigger( {
19855			type: "json-fetched.wb",
19856			fetch: {
19857				response: resultSet,
19858				status: "200",
19859				refId: refId,
19860				xhr: null
19861			}
19862		}, this );
19863	}
19864
19865} );
19866
19867/*
19868 * Integration with wb-fieldflow
19869 *
19870 */
19871function pushData( $elm, prop, data, reset ) {
19872	var dtCache = $elm.data( prop );
19873	if ( !dtCache || reset ) {
19874		dtCache = [];
19875	}
19876	dtCache.push( data );
19877	return $elm.data( prop, dtCache );
19878}
19879
19880// Fieldflow "op" action
19881$document.on( "op.action.wb-fieldflow", ".wb-fieldflow", function( event, data ) {
19882
19883	if ( !data.op ) {
19884		return;
19885	}
19886
19887	// Postpone the event for form submission
19888	data.preventSubmit = true;
19889	pushData( $( data.provEvt ), "wb-fieldflow-submit", data );
19890} );
19891
19892// Fieldflow "op" submit
19893$document.on( "op.submit.wb-fieldflow", ".wb-fieldflow", function( event, data ) {
19894
19895	// Get the hbs Plugin
19896	var op = data.op,
19897		source = data.source,
19898		ops;
19899
19900	if ( !op ) {
19901		return true;
19902	}
19903
19904	if ( !Array.isArray( op ) ) {
19905		ops = [];
19906		ops.push( op );
19907	} else {
19908		ops = op;
19909	}
19910
19911	$( source ).trigger( {
19912		type: "patches.wb-jsonmanager",
19913		patches: ops
19914	} );
19915} );
19916
19917// Bind the init event of the plugin
19918$document.on( "timerpoke.wb " + initEvent, selector, init );
19919
19920
19921// Add the timer poke to initialize the plugin
19922wb.add( selector );
19923
19924} )( jQuery, window, wb );
19925
19926/**
19927 * @title WET-BOEW wb-postback
19928 * @overview This plugin implements AJAX request for form data to submit on same page without refresh
19929 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
19930 * @author @masterbee, @namjohn920, @GormFrank
19931 **/
19932( function( $, wb ) {
19933"use strict";
19934
19935var $document = wb.doc,
19936	componentName = "wb-postback",
19937	selector = "." + componentName,
19938	initEvent = "wb-init" + selector,
19939	failEvent = "fail" + selector,
19940	successEvent = "success" + selector,
19941	defaults = {},
19942
19943	init = function( event ) {
19944		var elm = wb.init( event, componentName, selector );
19945
19946		if ( elm ) {
19947			var $elm = $( elm ),
19948				settings = $.extend(
19949					true,
19950					{},
19951					defaults,
19952					wb.getData( $elm, componentName )
19953				),
19954				multiple = typeof $elm.data( componentName + "-multiple" ) !== "undefined",
19955				classToggle = settings.toggle || "hide",
19956				selectorSuccess = settings.success,
19957				selectorFailure = settings.failure || selectorSuccess;
19958			const attrBlocked = "data-wb-blocked",
19959				attrPIIBlocked = "data-wb-pii-blocked",
19960				attrSending = "data-wb-sending";
19961
19962			elm.addEventListener( "submit", function( e ) {
19963
19964				// Prevent regular form submit
19965				e.preventDefault();
19966
19967				//Check if the form use the validation plugin
19968				if ( elm.parentElement.classList.contains( "wb-frmvld" ) ) {
19969
19970					// Block invalid forms and allow valid ones
19971					if ( !$elm.valid() ) {
19972						$( this ).attr( attrBlocked, "true" );
19973					} else {
19974						$( this ).removeAttr( attrBlocked );
19975					}
19976				}
19977
19978				// Submit the form unless it's blocked or currently being sent
19979				if ( !$( this ).attr( attrBlocked ) && !$( this ).attr( attrSending ) && !$( this ).attr( attrPIIBlocked ) ) {
19980					$elm.trigger( componentName + ".submit", e.submitter );
19981				}
19982			} );
19983
19984			$elm.on( componentName + ".submit", function( event, submitter ) {
19985				var data = $elm.serializeArray(),
19986					$selectorSuccess = $( selectorSuccess ),
19987					$selectorFailure = $( selectorFailure );
19988
19989				// Indicate that the form is currently being sent (to prevent multiple submissions in parallel)
19990				$( this ).attr( attrSending, true );
19991
19992				// If the submit button contains a variable, add it to the form's parameters
19993				// Note: Submitting a form via Enter will act as if the FIRST submit button was pressed. Therefore, that button's variable will be added (as opposed to nothing). This is in line with default form submission behaviour.
19994				if ( submitter && submitter.name ) {
19995					data.push( { name: submitter.name, value: submitter.value } );
19996				}
19997
19998				// Hide feedback messages
19999				$selectorFailure.addClass( classToggle );
20000				$selectorSuccess.addClass( classToggle );
20001
20002				// Send the form through ajax and ignore the response body.
20003				$.ajax( {
20004					type: this.method,
20005					url: this.action,
20006					data: $.param( data )
20007				} )
20008					.done( function() {
20009						$elm.trigger( successEvent );
20010						$selectorSuccess.removeClass( classToggle );
20011					} )
20012					.fail( function( response ) {
20013						$elm.trigger( failEvent, response );
20014						$selectorFailure.removeClass( classToggle );
20015					} )
20016					.always( function() {
20017
20018						// Hide the form unless multiple submits are allowed
20019						if ( !multiple ) {
20020							$elm.addClass( classToggle );
20021						}
20022
20023						// Remove the sending indicator now that submission is fully complete (i.e. HTTP response code has been received)
20024						$elm.removeAttr( attrSending );
20025					} );
20026			} );
20027
20028			wb.ready( $( elm ), componentName );
20029		}
20030	};
20031
20032// Bind the init event of the plugin
20033$document.on( "timerpoke.wb " + initEvent, selector, init );
20034
20035// Add the timer poke to initialize the plugin
20036wb.add( selector );
20037
20038} )( jQuery, wb );
20039
20040/**
20041 * @title WET-BOEW Randomize
20042 * @overview This plugin randomly picks one of the child component to be shown on the browser.
20043 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
20044 * @author @masterbee @namjohn920
20045 */
20046( function( $, window, wb ) {
20047"use strict";
20048
20049var $document = wb.doc,
20050	componentName = "wb-randomize",
20051	selector = "[data-wb-randomize]",
20052	initEvent = "wb-init" + selector,
20053	defaults = {},
20054
20055	init = function( event ) {
20056		var elm = wb.init( event, componentName, selector ),
20057			$elm, settings, $selectedElm, valuesList;
20058
20059		if ( elm ) {
20060			$elm = $( elm );
20061			settings = $.extend(
20062				true,
20063				{},
20064				defaults,
20065				window[ componentName ],
20066				wb.getData( $elm, componentName )
20067			);
20068
20069			if ( settings.attribute ) {
20070				if ( settings.values && Array.isArray( settings.values ) ) {
20071					valuesList = settings.values;
20072					shuffleArray( valuesList );
20073					elm.setAttribute( settings.attribute, valuesList[ 0 ] );
20074				} else {
20075					throw componentName + ": You must define the property \"values\" to an array of str
20075ings when \"attribute\" property is defined.";
20076				}
20077			} else {
20078				$selectedElm = settings.selector ? $( settings.selector, $elm ) : $elm.children();
20079
20080				if ( !$selectedElm.length ) {
20081					throw componentName + " selector setting is invalid or no children";
20082				}
20083
20084				if ( settings.shuffle ) {
20085					$selectedElm = wb.shuffleDOM( $selectedElm );
20086				}
20087
20088				if ( settings.toggle ) {
20089					$selectedElm = wb.pickElements( $selectedElm, settings.number );
20090					$selectedElm.toggleClass( settings.toggle );
20091				}
20092			}
20093
20094			wb.ready( $elm, componentName );
20095		}
20096	},
20097
20098	shuffleArray = function( array ) {
20099		for ( let i = array.length - 1; i > 0; i-- ) {
20100			const j = Math.floor( Math.random() * ( i + 1 ) );
20101			[ array[ i ], array[ j ] ] = [ array[ j ], array[ i ] ];
20102		}
20103	};
20104
20105// Bind the init event of the plugin
20106$document.on( "timerpoke.wb " + initEvent, selector, init );
20107
20108// Add the timer poke to initialize the plugin
20109wb.add( selector );
20110
20111} )( jQuery, window, wb );
20112
20113/**
20114 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
20115 * @title Zebra
20116 * @overview Apply Zebra stripping on a complex data table and simulate column hovering effect
20117 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
20118 * @author @duboisp
20119 *
20120 */
20121( function( $, window, document, wb ) {
20122"use strict";
20123
20124/**
20125 * Variable and function definitions.
20126 * These are global to the plugin - meaning that they will be initialized once per page,
20127 * not once per instance of plugin on the page. So, this is a good place to define
20128 * variables that are common to all instances of the plugin on a page.
20129 */
20130var componentName = "wb-zebra",
20131	selector = "." + componentName,
20132	hoverColClass = componentName + "-col-hover",
20133	selectorHoverCol = "." + hoverColClass + " td, " + hoverColClass + " th",
20134	initEvent = "wb-init" + selector,
20135	tableParsingEvent = "passiveparse.wb-tableparser",
20136	tableParsingCompleteEvent = "parsecomplete.wb-tableparser",
20137	$document = wb.doc,
20138	idCount = 0,
20139	i18n, i18nText,
20140
20141	/**
20142	 * Main Entry function to apply the complex zebra stripping
20143	 * @method zebraTable
20144	 * @param {jQuery DOM element} $elm table element use to apply complex zebra stripping
20145	 */
20146	zebraTable = function( $elm ) {
20147		var i, iLength, tblGroup,
20148
20149			// Cache the table parsed results
20150			tblparser = $elm.data().tblparser;
20151
20152		function addCellClass( arr, className ) {
20153			var i, iLength;
20154
20155			for ( i = 0, iLength = arr.length; i !== iLength; i += 1 ) {
20156				$( arr[ i ].elem ).addClass( className );
20157			}
20158		}
20159
20160		// Key Cell
20161		if ( tblparser.keycell ) {
20162			addCellClass( tblparser.keycell, "wb-cell-key" );
20163		}
20164
20165		// Description Cell
20166		if ( tblparser.desccell ) {
20167			addCellClass( tblparser.desccell, "wb-cell-desc" );
20168		}
20169
20170		// Layout Cell
20171		if ( tblparser.layoutCell ) {
20172			addCellClass( tblparser.layoutCell, "wb-cell-layout" );
20173		}
20174
20175		// Summary Row Group
20176		if ( tblparser.lstrowgroup ) {
20177			for ( i = 0, iLength = tblparser.lstrowgroup.length; i !== iLength; i += 1 ) {
20178				tblGroup = tblparser.lstrowgroup[ i ];
20179
20180				// Add a class to the row
20181				if ( tblGroup.type === 3 || tblGroup.row[ 0 ].type === 3 ) {
20182					$( tblGroup.elem ).addClass( "wb-group-summary" );
20183				}
20184			}
20185		}
20186
20187		// Summary Column Group
20188		if ( tblparser.colgroup ) {
20189			for ( i = 0, iLength = tblparser.colgroup.length; i !== iLength; i += 1 ) {
20190				tblGroup = tblparser.colgroup[ i ];
20191
20192				// Add a class to the row
20193				if ( tblGroup.type === 3 ) {
20194					$( tblGroup.elem ).addClass( "wb-group-summary" );
20195				}
20196			}
20197		}
20198
20199		// Identify that initialization has completed
20200		wb.ready( $elm, componentName );
20201	},
20202
20203	/**
20204	 * @method init
20205	 * @param {jQuery Event} event Event that triggered the function call
20206	 */
20207	init = function( event ) {
20208
20209		// Start initialization
20210		// returns DOM object = proceed with init
20211		// returns undefined = do not proceed with init (e.g., already initialized)
20212		var elm = wb.init( event, componentName, selector ),
20213			deps = [
20214				"site!deps/tableparser" + wb.getMode() + ".js"
20215			],
20216			elmId;
20217
20218		if ( elm ) {
20219			elmId = elm.id;
20220
20221			// Ensure there is a unique id on the element
20222			if ( !elmId ) {
20223				elmId = componentName + "-id-" + idCount;
20224				idCount += 1;
20225				elm.id = elmId;
20226			}
20227
20228			// Only initialize the i18nText once
20229			if ( !i18nText ) {
20230				i18n = wb.i18n;
20231				i18nText = {
20232					tableMention: i18n( "hyphen" ) + i18n( "tbl-txt" ),
20233					tableFollowing: i18n( "hyphen" ) + i18n( "tbl-dtls" )
20234				};
20235			}
20236
20237			// Load the required dependencies
20238			Modernizr.load( {
20239
20240				// For loading multiple dependencies
20241				load: deps,
20242				complete: function() {
20243
20244					// Let's parse the table
20245					$( "#" + elmId ).trigger( tableParsingEvent );
20246				}
20247			} );
20248		}
20249	};
20250
20251// Bind the init event of the plugin
20252$document.on( "timerpoke.wb " + initEvent + " " + tableParsingCompleteEvent, selector, function( event ) {
20253	var eventTarget = event.target;
20254
20255	switch ( event.type ) {
20256
20257		/*
20258		 * Init
20259		 */
20260		case "timerpoke":
20261		case "wb-init":
20262			init( event );
20263			break;
20264
20265		/*
20266		 * Data table parsed
20267		 */
20268		case "parsecomplete":
20269			if ( event.currentTarget === eventTarget ) {
20270				zebraTable( $( eventTarget ) );
20271			}
20272			break;
20273	}
20274
20275	/*
20276	 * Since we are working with events we want to ensure that we are being passive about our control,
20277	 * so returning true allows for events to always continue
20278	 */
20279	return true;
20280} );
20281
20282// Applying the hover, Simulate Column Hovering Effect
20283$document.on( "mouseenter focusin", selectorHoverCol, function( event ) {
20284	var tblparserCell = $( event.currentTarget ).data().tblparser;
20285
20286	if ( tblparserCell.col && tblparserCell.col.elem ) {
20287		$( tblparserCell.col.elem ).addClass( "table-hover" );
20288	}
20289} );
20290
20291// Removing the hover, Simulate Column Hovering Effect
20292$document.on( "mouseleave focusout", selectorHoverCol, function( event ) {
20293	var tblparserCell = $( event.currentTarget ).data().tblparser;
20294
20295	if ( tblparserCell.col && tblparserCell.col.elem ) {
20296		$( tblparserCell.col.elem ).removeClass( "table-hover" );
20297	}
20298} );
20299
20300// Add the timer poke to initialize the plugin
20301wb.add( selector );
20302
20303} )( jQuery, window, document, wb );

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.