| /** | 
|  * SVG Icon Loader 2.0 | 
|  * | 
|  * jQuery Plugin for loading SVG icons from a single file | 
|  * | 
|  * Adds {@link external:jQuery.svgIcons}, {@link external:jQuery.getSvgIcon}, {@link external:jQuery.resizeSvgIcons} | 
|  * | 
|  * How to use: | 
|   | 
| 1. Create the SVG master file that includes all icons: | 
|   | 
| The master SVG icon-containing file is an SVG file that contains | 
| `<g>` elements. Each `<g>` element should contain the markup of an SVG | 
| icon. The `<g>` element has an ID that should | 
| correspond with the ID of the HTML element used on the page that should contain | 
| or optionally be replaced by the icon. Additionally, one empty element should be | 
| added at the end with id "svg_eof". | 
|   | 
| 2. Optionally create fallback raster images for each SVG icon. | 
|   | 
| 3. Include the jQuery and the SVG Icon Loader scripts on your page. | 
|   | 
| 4. Run `$.svgIcons()` when the document is ready. See its signature | 
|   | 
| 5. To access an icon at a later point without using the callback, use this: | 
|   `$.getSvgIcon(id (string), uniqueClone (boolean))`; | 
|   | 
| This will return the icon (as jQuery object) with a given ID. | 
|   | 
| 6. To resize icons at a later point without using the callback, use this: | 
|   `$.resizeSvgIcons(resizeOptions)` (use the same way as the "resize" parameter) | 
|  * | 
|  * @module jQuerySVGIcons | 
|  * @license MIT | 
|  * @copyright (c) 2009 Alexis Deveria | 
|  * {@link http://a.deveria.com} | 
|  * @example usage #1: | 
|   | 
| $(function() { | 
|   $.svgIcons('my_icon_set.svg'); // The SVG file that contains all icons | 
|   // No options have been set, so all icons will automatically be inserted | 
|   // into HTML elements that match the same IDs. | 
| }); | 
|   | 
| * @example usage #2: | 
|   | 
| $(function() { | 
|   $.svgIcons('my_icon_set.svg', { // The SVG file that contains all icons | 
|     callback (icons) { // Custom callback function that sets click | 
|                   // events for each icon | 
|       $.each(icons, function(id, icon) { | 
|         icon.click(function() { | 
|           alert('You clicked on the icon with id ' + id); | 
|         }); | 
|       }); | 
|     } | 
|   }); //The SVG file that contains all icons | 
| }); | 
|   | 
| * @example usage #3: | 
|   | 
| $(function() { | 
|   $.svgIcons('my_icon_set.svgz', { // The SVGZ file that contains all icons | 
|     w: 32,  // All icons will be 32px wide | 
|     h: 32,  // All icons will be 32px high | 
|     fallback_path: 'icons/',  // All fallback files can be found here | 
|     fallback: { | 
|       '#open_icon': 'open.png',  // The "open.png" will be appended to the | 
|                     // HTML element with ID "open_icon" | 
|       '#close_icon': 'close.png', | 
|       '#save_icon': 'save.png' | 
|     }, | 
|     placement: {'.open_icon','open'}, // The "open" icon will be added | 
|                     // to all elements with class "open_icon" | 
|     resize () { | 
|       '#save_icon .svg_icon': 64  // The "save" icon will be resized to 64 x 64px | 
|     }, | 
|   | 
|     callback (icons) { // Sets background color for "close" icon | 
|       icons['close'].css('background','red'); | 
|     }, | 
|   | 
|     svgz: true // Indicates that an SVGZ file is being used | 
|   | 
|   }) | 
| }); | 
| */ | 
|   | 
| /** | 
| * @callback module:jQuerySVGIcons.SVGIconsLoadedCallback | 
| * @param {PlainObject.<string, external:jQuery>} svgIcons IDs keyed to jQuery objects of images | 
| */ | 
|   | 
| /** | 
|  * @function module:jQuerySVGIcons.jQuerySVGIcons | 
|  * @param {external:jQuery} $ Its keys include all icon IDs and the values, the icon as a jQuery object | 
|  * @returns {external:jQuery} The enhanced jQuery object | 
| */ | 
| export default function ($) { | 
|   const svgIcons = {}; | 
|   | 
|   let fixIDs; | 
|   /** | 
|   * @function external:jQuery.svgIcons | 
|   * @param {string} file The location of a local SVG or SVGz file | 
|   * @param {PlainObject} [opts] | 
|   * @param {Float} [opts.w] The icon widths | 
|   * @param {Float} [opts.h] The icon heights | 
|   * @param {PlainObject.<string, string>} [opts.fallback] List of raster images with each | 
|     key being the SVG icon ID to replace, and the value the image file name | 
|   * @param {string} [opts.fallback_path] The path to use for all images | 
|     listed under "fallback" | 
|   * @param {boolean} [opts.replace] If set to `true`, HTML elements will be replaced by, | 
|     rather than include the SVG icon. | 
|   * @param {PlainObject.<string, string>} [opts.placement] List with selectors for keys and SVG icon ids | 
|     as values. This provides a custom method of adding icons. | 
|   * @param {PlainObject.<string, module:jQuerySVGIcons.Size>} [opts.resize] List with selectors for keys and numbers | 
|     as values. This allows an easy way to resize specific icons. | 
|   * @param {module:jQuerySVGIcons.SVGIconsLoadedCallback} [opts.callback] A function to call when all icons have been loaded. | 
|   * @param {boolean} [opts.id_match=true] Automatically attempt to match SVG icon ids with | 
|     corresponding HTML id | 
|   * @param {boolean} [opts.no_img] Prevent attempting to convert the icon into an `<img>` | 
|     element (may be faster, help for browser consistency) | 
|   * @param {boolean} [opts.svgz] Indicate that the file is an SVGZ file, and thus not to | 
|     parse as XML. SVGZ files add compression benefits, but getting data from | 
|     them fails in Firefox 2 and older. | 
|   * @returns {undefined} | 
|   */ | 
|   $.svgIcons = function (file, opts = {}) { | 
|     const svgns = 'http://www.w3.org/2000/svg', | 
|       xlinkns = 'http://www.w3.org/1999/xlink', | 
|       iconW = opts.w || 24, | 
|       iconH = opts.h || 24; | 
|     let elems, svgdoc, testImg, | 
|       iconsMade = false, dataLoaded = false, loadAttempts = 0; | 
|     const isOpera = !!window.opera, | 
|       // ua = navigator.userAgent, | 
|       // isSafari = (ua.includes('Safari/') && !ua.includes('Chrome/')), | 
|       dataPre = 'data:image/svg+xml;charset=utf-8;base64,'; | 
|   | 
|     let dataEl; | 
|     if (opts.svgz) { | 
|       dataEl = $('<object data="' + file + '" type=image/svg+xml>').appendTo('body').hide(); | 
|       try { | 
|         svgdoc = dataEl[0].contentDocument; | 
|         dataEl.load(getIcons); | 
|         getIcons(0, true); // Opera will not run "load" event if file is already cached | 
|       } catch (err1) { | 
|         useFallback(); | 
|       } | 
|     } else { | 
|       const parser = new DOMParser(); | 
|       $.ajax({ | 
|         url: file, | 
|         dataType: 'string', | 
|         success (data) { | 
|           if (!data) { | 
|             $(useFallback); | 
|             return; | 
|           } | 
|           svgdoc = parser.parseFromString(data, 'text/xml'); | 
|           $(function () { | 
|             getIcons('ajax'); | 
|           }); | 
|         }, | 
|         error (err) { | 
|           // TODO: Fix Opera widget icon bug | 
|           if (window.opera) { | 
|             $(function () { | 
|               useFallback(); | 
|             }); | 
|           } else { | 
|             if (err.responseText) { | 
|               svgdoc = parser.parseFromString(err.responseText, 'text/xml'); | 
|   | 
|               if (!svgdoc.childNodes.length) { | 
|                 $(useFallback); | 
|               } | 
|               $(function () { | 
|                 getIcons('ajax'); | 
|               }); | 
|             } else { | 
|               $(useFallback); | 
|             } | 
|           } | 
|         } | 
|       }); | 
|     } | 
|   | 
|     function getIcons (evt, noWait) { | 
|       if (evt !== 'ajax') { | 
|         if (dataLoaded) return; | 
|         // Webkit sometimes says svgdoc is undefined, other times | 
|         // it fails to load all nodes. Thus we must make sure the "eof" | 
|         // element is loaded. | 
|         svgdoc = dataEl[0].contentDocument; // Needed again for Webkit | 
|         const isReady = (svgdoc && svgdoc.getElementById('svg_eof')); | 
|         if (!isReady && !(noWait && isReady)) { | 
|           loadAttempts++; | 
|           if (loadAttempts < 50) { | 
|             setTimeout(getIcons, 20); | 
|           } else { | 
|             useFallback(); | 
|             dataLoaded = true; | 
|           } | 
|           return; | 
|         } | 
|         dataLoaded = true; | 
|       } | 
|   | 
|       elems = $(svgdoc.firstChild).children(); // .getElementsByTagName('foreignContent'); | 
|   | 
|       if (!opts.no_img) { | 
|         const testSrc = dataPre + 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNzUiIGhlaWdodD0iMjc1Ij48L3N2Zz4%3D'; | 
|   | 
|         testImg = $(new Image()).attr({ | 
|           src: testSrc, | 
|           width: 0, | 
|           height: 0 | 
|         }).appendTo('body') | 
|           .load(function () { | 
|             // Safari 4 crashes, Opera and Chrome don't | 
|             makeIcons(true); | 
|           }).error(function () { | 
|             makeIcons(); | 
|           }); | 
|       } else { | 
|         setTimeout(function () { | 
|           if (!iconsMade) makeIcons(); | 
|         }, 500); | 
|       } | 
|     } | 
|   | 
|     function setIcon (target, icon, id, setID) { | 
|       if (isOpera) icon.css('visibility', 'hidden'); | 
|       if (opts.replace) { | 
|         if (setID) icon.attr('id', id); | 
|         const cl = target.attr('class'); | 
|         if (cl) icon.attr('class', 'svg_icon ' + cl); | 
|         target.replaceWith(icon); | 
|       } else { | 
|         target.append(icon); | 
|       } | 
|       if (isOpera) { | 
|         setTimeout(function () { | 
|           icon.removeAttr('style'); | 
|         }, 1); | 
|       } | 
|     } | 
|   | 
|     let holder; | 
|     function addIcon (icon, id) { | 
|       if (opts.id_match === undefined || opts.id_match !== false) { | 
|         setIcon(holder, icon, id, true); | 
|       } | 
|       svgIcons[id] = icon; | 
|     } | 
|   | 
|     function makeIcons (toImage, fallback) { | 
|       if (iconsMade) return; | 
|       if (opts.no_img) toImage = false; | 
|   | 
|       let tempHolder; | 
|       if (toImage) { | 
|         tempHolder = $(document.createElement('div')); | 
|         tempHolder.hide().appendTo('body'); | 
|       } | 
|       if (fallback) { | 
|         const path = opts.fallback_path || ''; | 
|         $.each(fallback, function (id, imgsrc) { | 
|           holder = $('#' + id); | 
|           const icon = $(new Image()) | 
|             .attr({ | 
|               class: 'svg_icon', | 
|               src: path + imgsrc, | 
|               width: iconW, | 
|               height: iconH, | 
|               alt: 'icon' | 
|             }); | 
|   | 
|           addIcon(icon, id); | 
|         }); | 
|       } else { | 
|         const len = elems.length; | 
|         for (let i = 0; i < len; i++) { | 
|           const elem = elems[i]; | 
|           const {id} = elem; | 
|           if (id === 'svg_eof') break; | 
|           holder = $('#' + id); | 
|           const svgroot = document.createElementNS(svgns, 'svg'); | 
|           // Per https://www.w3.org/TR/xml-names11/#defaulting, the namespace for | 
|           // attributes should have no value. | 
|           svgroot.setAttribute('viewBox', [0, 0, iconW, iconH].join(' ')); | 
|   | 
|           let svg = elem.getElementsByTagNameNS(svgns, 'svg')[0]; | 
|   | 
|           // Make flexible by converting width/height to viewBox | 
|           const w = svg.getAttribute('width'); | 
|           const h = svg.getAttribute('height'); | 
|           svg.removeAttribute('width'); | 
|           svg.removeAttribute('height'); | 
|   | 
|           const vb = svg.getAttribute('viewBox'); | 
|           if (!vb) { | 
|             svg.setAttribute('viewBox', [0, 0, w, h].join(' ')); | 
|           } | 
|   | 
|           // Not using jQuery to be a bit faster | 
|           svgroot.setAttribute('xmlns', svgns); | 
|           svgroot.setAttribute('width', iconW); | 
|           svgroot.setAttribute('height', iconH); | 
|           svgroot.setAttribute('xmlns:xlink', xlinkns); | 
|           svgroot.setAttribute('class', 'svg_icon'); | 
|   | 
|           // Without cloning, Firefox will make another GET request. | 
|           // With cloning, causes issue in Opera/Win/Non-EN | 
|           if (!isOpera) svg = svg.cloneNode(true); | 
|   | 
|           svgroot.append(svg); | 
|   | 
|           let icon; | 
|           if (toImage) { | 
|             tempHolder.empty().append(svgroot); | 
|             const str = dataPre + encode64(unescape(encodeURIComponent(new XMLSerializer().serializeToString(svgroot)))); | 
|             icon = $(new Image()) | 
|               .attr({class: 'svg_icon', src: str}); | 
|           } else { | 
|             icon = fixIDs($(svgroot), i); | 
|           } | 
|           addIcon(icon, id); | 
|         } | 
|       } | 
|   | 
|       if (opts.placement) { | 
|         $.each(opts.placement, function (sel, id) { | 
|           if (!svgIcons[id]) return; | 
|           $(sel).each(function (i) { | 
|             let copy = svgIcons[id].clone(); | 
|             if (i > 0 && !toImage) copy = fixIDs(copy, i, true); | 
|             setIcon($(this), copy, id); | 
|           }); | 
|         }); | 
|       } | 
|       if (!fallback) { | 
|         if (toImage) tempHolder.remove(); | 
|         if (dataEl) dataEl.remove(); | 
|         if (testImg) testImg.remove(); | 
|       } | 
|       if (opts.resize) $.resizeSvgIcons(opts.resize); | 
|       iconsMade = true; | 
|   | 
|       if (opts.callback) opts.callback(svgIcons); | 
|     } | 
|   | 
|     fixIDs = function (svgEl, svgNum, force) { | 
|       const defs = svgEl.find('defs'); | 
|       if (!defs.length) return svgEl; | 
|   | 
|       let idElems; | 
|       if (isOpera) { | 
|         idElems = defs.find('*').filter(function () { | 
|           return !!this.id; | 
|         }); | 
|       } else { | 
|         idElems = defs.find('[id]'); | 
|       } | 
|   | 
|       const allElems = svgEl[0].getElementsByTagName('*'), len = allElems.length; | 
|   | 
|       idElems.each(function (i) { | 
|         const {id} = this; | 
|         /* | 
|         const noDupes = ($(svgdoc).find('#' + id).length <= 1); | 
|         if (isOpera) noDupes = false; // Opera didn't clone svgEl, so not reliable | 
|         if(!force && noDupes) return; | 
|         */ | 
|         const newId = 'x' + id + svgNum + i; | 
|         this.id = newId; | 
|   | 
|         const oldVal = 'url(#' + id + ')'; | 
|         const newVal = 'url(#' + newId + ')'; | 
|   | 
|         // Selector method, possibly faster but fails in Opera / jQuery 1.4.3 | 
|         //  svgEl.find('[fill="url(#' + id + ')"]').each(function() { | 
|         //    this.setAttribute('fill', 'url(#' + newId + ')'); | 
|         //  }).end().find('[stroke="url(#' + id + ')"]').each(function() { | 
|         //    this.setAttribute('stroke', 'url(#' + newId + ')'); | 
|         //  }).end().find('use').each(function() { | 
|         //    if(this.getAttribute('xlink:href') == '#' + id) { | 
|         //      this.setAttributeNS(xlinkns,'href','#' + newId); | 
|         //    } | 
|         //  }).end().find('[filter="url(#' + id + ')"]').each(function() { | 
|         //    this.setAttribute('filter', 'url(#' + newId + ')'); | 
|         //  }); | 
|   | 
|         for (i = 0; i < len; i++) { | 
|           const elem = allElems[i]; | 
|           if (elem.getAttribute('fill') === oldVal) { | 
|             elem.setAttribute('fill', newVal); | 
|           } | 
|           if (elem.getAttribute('stroke') === oldVal) { | 
|             elem.setAttribute('stroke', newVal); | 
|           } | 
|           if (elem.getAttribute('filter') === oldVal) { | 
|             elem.setAttribute('filter', newVal); | 
|           } | 
|         } | 
|       }); | 
|       return svgEl; | 
|     }; | 
|   | 
|     function useFallback () { | 
|       if (file.includes('.svgz')) { | 
|         const regFile = file.replace('.svgz', '.svg'); | 
|         if (window.console) { | 
|           console.log('.svgz failed, trying with .svg'); | 
|         } | 
|         $.svgIcons(regFile, opts); | 
|       } else if (opts.fallback) { | 
|         makeIcons(false, opts.fallback); | 
|       } | 
|     } | 
|   | 
|     function encode64 (input) { | 
|       // base64 strings are 4/3 larger than the original string | 
|       if (window.btoa) return window.btoa(input); | 
|       const _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; | 
|       const output = new Array(Math.floor((input.length + 2) / 3) * 4); | 
|   | 
|       let i = 0, p = 0; | 
|       do { | 
|         const chr1 = input.charCodeAt(i++); | 
|         const chr2 = input.charCodeAt(i++); | 
|         const chr3 = input.charCodeAt(i++); | 
|   | 
|         const enc1 = chr1 >> 2; | 
|         const enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); | 
|   | 
|         let enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); | 
|         let enc4 = chr3 & 63; | 
|         if (isNaN(chr2)) { | 
|           enc3 = enc4 = 64; | 
|         } else if (isNaN(chr3)) { | 
|           enc4 = 64; | 
|         } | 
|   | 
|         output[p++] = _keyStr.charAt(enc1); | 
|         output[p++] = _keyStr.charAt(enc2); | 
|         output[p++] = _keyStr.charAt(enc3); | 
|         output[p++] = _keyStr.charAt(enc4); | 
|       } while (i < input.length); | 
|   | 
|       return output.join(''); | 
|     } | 
|   }; | 
|   | 
|   /** | 
|   * @function external:jQuery.getSvgIcon | 
|   * @param {string} id | 
|   * @param {boolean} uniqueClone Whether to clone | 
|   * @returns {external:jQuery} The icon (optionally cloned) | 
|   */ | 
|   $.getSvgIcon = function (id, uniqueClone) { | 
|     let icon = svgIcons[id]; | 
|     if (uniqueClone && icon) { | 
|       icon = fixIDs(icon, 0, true).clone(true); | 
|     } | 
|     return icon; | 
|   }; | 
|   | 
|   /** | 
|   * @typedef {GenericArray} module:jQuerySVGIcons.Dimensions | 
|   * @property {Integer} length 2 | 
|   * @property {Float} 0 Width | 
|   * @property {Float} 1 Height | 
|   */ | 
|   | 
|   /** | 
|   * If a Float is used, it will represent width and height. Arrays contain the width and height. | 
|   * @typedef {module:jQuerySVGIcons.Dimensions|Float} module:jQuerySVGIcons.Size | 
|   */ | 
|   | 
|   /** | 
|   * @function external:jQuery.resizeSvgIcons | 
|   * @param {PlainObject.<string, module:jQuerySVGIcons.Size>} obj Object with selectors as keys. The values are sizes. | 
|   * @returns {undefined} | 
|   */ | 
|   $.resizeSvgIcons = function (obj) { | 
|     // FF2 and older don't detect .svg_icon, so we change it detect svg elems instead | 
|     const changeSel = !$('.svg_icon:first').length; | 
|     $.each(obj, function (sel, size) { | 
|       const arr = Array.isArray(size); | 
|       const w = arr ? size[0] : size, | 
|         h = arr ? size[1] : size; | 
|       if (changeSel) { | 
|         sel = sel.replace(/\.svg_icon/g, 'svg'); | 
|       } | 
|       $(sel).each(function () { | 
|         this.setAttribute('width', w); | 
|         this.setAttribute('height', h); | 
|         if (window.opera && window.widget) { | 
|           this.parentNode.style.width = w + 'px'; | 
|           this.parentNode.style.height = h + 'px'; | 
|         } | 
|       }); | 
|     }); | 
|   }; | 
|   return $; | 
| } |