<commit_before>/* jshint boss: true */
/* global Text */
;(function(root, factory) {
    'use strict';
    if (typeof define === "function" && define.amd) {
        define(factory);
    } else {
        root.DOMPurify = factory();
    }
}(this, function() {
    'use strict';

    var DOMPurify = {};
    DOMPurify.sanitize = function(dirty, cfg) {

        /**
         * We consider the elements and attributes below to be safe. Ideally
         * don't add any new ones but feel free to remove unwanted ones.
         */

        /* allowed element names */
        var ALLOWED_TAGS = [

            // HTML
            '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','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','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',

            // SVG
            'svg','altglyph','altglyphdef','altglyphitem','animatecolor',
            'animatemotion','animatetransform','circle','clippath','defs','desc',
            'ellipse','font','g','glyph','glyphref','hkern','image','line',
            'lineargradient','marker','mask','metadata','mpath','path','pattern',
            'polygon','polyline','radialgradient','rect','stop','switch','symbol',
            'text','textpath','title','tref','tspan','view','vkern',

            //MathML
            'math','menclose','merror','mfenced','mfrac','mglyph','mi','mlabeledtr',
            'mmuliscripts','mn','mo','mover','mpadded','mphantom','mroot','mrow',
            'ms','mpspace','msqrt','mystyle','msub','msup','msubsup','mtable','mtd',
            'mtext','mtr','munder','munderover',

            //Text
            '#text'
        ];

        /* Decide if custom data attributes are okay */
        var ALLOW_DATA_ATTR = true;

        /* Allowed attribute names */
        var ALLOWED_ATTR = [

            // HTML
            'accept','action','align','alt','autocomplete','bgcolor','border',
            'checked','cite','class','color','cols','colspan','coords','datetime',
            'default','dir','disabled','download','enctype','for','headers','height',
            'hidden','high','href','hreflang','id','ismap','label','lang','list',
            'loop', 'low','max','maxlength','media','method','min','multiple',
            'name','novalidate','open','optimum','pattern','placeholder','poster',
            'preload','pubdate','radiogroup','readonly','rel','required','rev',
            'reversed','rows','rowspan','spellcheck','scope','selected','shape',
            'size','span','srclang','start','src','step','style','summary','tabindex',
            'title','type','usemap','valign','value','width','xmlns',

            // SVG
            'accent-height','accumulate','additivive','alignment-baseline',
            'ascent','azimuth','baseline-shift','bias','clip','clip-path',
            'clip-rule','color','color-interpolation','color-interpolation-filters',
            'color-profile','color-rendering','cx','cy','d','dy','dy','direction',
            'display','divisor','dur','elevation','end','fill','fill-opacity',
            'fill-rule','filter','flood-color','flood-opacity','font-family',
            'font-size','font-size-adjust','font-stretch','font-style','font-variant',
            'front-weight','image-rendering','in','in2','k1','k2','k3','k4','kerning',
            'letter-spacing','lighting-color','local','marker-end','marker-mid',
            'marker-start','max','mask','mode','min','operator','opacity','order',
            'overflow','paint-order','path','points','r','rx','ry','radius','restart',
            'scale','seed','shape-rendering','stop-color','stop-opacity',
            'stroke-dasharray','stroke-dashoffset','stroke-linecap','stroke-linejoin',
            'stroke-miterlimit','stroke-opacity','stroke','stroke-width','transform',
            'text-anchor','text-decoration','text-rendering','u1','u2','viewbox',
            'visibility','word-spacing','wrap','writing-mode','x','x1','x2','y',
            'y1','y2','z',

            // MathML
            'accent','accentunder','bevelled','close','columnsalign','columnlines',
            'columnspan','denomalign','depth','display','displaystyle','fence',
            'frame','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',

            // XML
            'xlink:href','xml:id','xlink:title','xml:space'
        ];

        /* Decide if document with <html>... should be returned */
        var WHOLE_DOCUMENT = false;

        /* Decide if a DOM node or a string should be returned */
        var RETURN_DOM = false;

        /* Output should be safe for jQuery's $() factory? */
        var SAFE_FOR_JQUERY = false;

        /* Output should be free from DOM clobbering attacks? */
        var SANITIZE_DOM = true;

        /* Keep element content when removing element? */
        var KEEP_CONTENT = true;

        /* Tags to keep content from (when KEEP_CONTENT is true) */
        var CONTENT_TAGS = [
            'a','abbr','acronym','address','article','aside','b','bdi','bdo',
            'big','blink','blockquote','caption','center','cite','code','col',
            'dd','del','details','dfn','dir','div','dl','dt','em','figcaption',
            'figure','footer','h1','h2','h3','h4','h5','h6','header','i','ins',
            'kbd','label','legend','li','main','mark','marquee','nav','ol',
            'output','p','pre','q','rp','rt','ruby','s','samp','section','small',
            'span','strike','strong','sub','summary','sup','table','tbody','td',
            'tfoot','th','thead','time','tr','tt','u','ul','var'
        ];

        /* Ideally, do not touch anything below this line */
        /* ______________________________________________ */

        /**
         * _parseConfig
         *
         * @param  optional config literal
         */
        var _parseConfig = function(cfg) {

            /* Set configuration parameters */
            cfg.ALLOWED_ATTR    ? ALLOWED_ATTR    = cfg.ALLOWED_ATTR    : null;
            cfg.ALLOWED_TAGS    ? ALLOWED_TAGS    = cfg.ALLOWED_TAGS    : null;
            cfg.ALLOW_DATA_ATTR ? ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR : null;
            cfg.SAFE_FOR_JQUERY ? SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY : null;
            cfg.WHOLE_DOCUMENT  ? WHOLE_DOCUMENT  = cfg.WHOLE_DOCUMENT  : null;
            cfg.RETURN_DOM      ? RETURN_DOM      = cfg.RETURN_DOM      : null;
            cfg.SANITIZE_DOM    ? SANITIZE_DOM    = cfg.SANITIZE_DOM    : null;
            cfg.KEEP_CONTENT    ? KEEP_CONTENT    = cfg.KEEP_CONTENT    : null;

            /* Merge configuration parameters */
            cfg.ADD_ATTR ? ALLOWED_ATTR = ALLOWED_ATTR.concat(cfg.ADD_ATTR) : null;
            cfg.ADD_TAGS ? ALLOWED_TAGS = ALLOWED_TAGS.concat(cfg.ADD_TAGS) : null;

            /* Add #text in case KEEP_CONTENT is set to true */
            KEEP_CONTENT ? ALLOWED_TAGS.push('#text') : null;
        };

       /**
         * _initDocument
         *
         * @param  a string of dirty markup
         * @return a DOM, filled with the dirty markup
         */
        var _initDocument = function(dirty){

            /* Create documents to map markup to */
            var dom = document.implementation.createHTMLDocument('');
                dom.body.parentNode.removeChild(dom.body.parentNode.firstElementChild);
                dom.body.outerHTML = dirty;

            /* Cover IE9's buggy outerHTML behavior */
            if(dom.body === null) {
                dom.body.innerHTML = dirty;
            }

            /* Work on whole document or just its body */
            var body = WHOLE_DOCUMENT ? dom.body.parentNode : dom.body;
            if (
                !(dom.body instanceof HTMLBodyElement) ||
                !(dom.body instanceof HTMLHtmlElement)
            ) {
                var freshdom = document.implementation.createHTMLDocument('');
                body = WHOLE_DOCUMENT
                    ? freshdom.getElementsByTagName.call(dom,'html')[0]
                    : freshdom.getElementsByTagName.call(dom,'body')[0];
            }
            return body;
        };

        /**
         * _createIterator
         *
         * @param  document/fragment to create iterator for
         * @return iterator instance
         */
        var _createIterator = function(doc) {
            return document.createNodeIterator(
                doc,
                NodeFilter.SHOW_ELEMENT
                | NodeFilter.SHOW_COMMENT
                | NodeFilter.SHOW_TEXT,
                function() { return NodeFilter.FILTER_ACCEPT; },
                false
            );
        };

        /**
         * _isClobbered
         *
         * @param  element to check for clobbering attacks
         * @return true if clobbered, false if safe
         */
        var _isClobbered = function(elm) {
            if(elm instanceof Text) {
                return false;
            }
            if (
                (elm.children && !(elm.children instanceof HTMLCollection))
                || (elm.attributes instanceof NodeList)
                || typeof elm.nodeName !== 'string'
                || typeof elm.textContent !== 'string'
                || typeof elm.nodeType !== 'number'
                || typeof elm.COMMENT_NODE !== 'number'
                || typeof elm.setAttribute !== 'function'
                || typeof elm.cloneNode !== 'function'
                || typeof elm.removeAttributeNode !== 'function'
                || typeof elm.insertAdjacentHTML !== 'function'
                || typeof elm.attributes.item !== 'function'
            ) {
                return true;
            }
            return false;
        };

        /**
         * _sanitizeElements
         *
         * @protect removeChild
         * @protect nodeType
         * @protect nodeName
         * @protect textContent
         * @protect currentNode
         * @protect insertAdjacentHTML
         *
         * @param   node to check for permission to exist
         * @return  true if node was killed, false if left alive
         */
        var _sanitizeElements = function(currentNode) {

            /* Check if element is clobbered or can clobber */
            if (_isClobbered(currentNode)) {

                /* Be harsh with clobbered content, element has to go! */
                try{
                    currentNode.parentNode.removeChild(currentNode);
                } catch(e){
                    currentNode.outerHTML='';
                }
                return true;
            }

            /* Now let's check the element's type and name */
            if(currentNode.nodeType === currentNode.COMMENT_NODE
                || ALLOWED_TAGS.indexOf(currentNode.nodeName.toLowerCase()) === -1
            ) {
                /* Keep content for white-listed elements */
                if(KEEP_CONTENT && currentNode.insertAdjacentHTML
                    && currentNode.nodeName.toLowerCase
                    && CONTENT_TAGS.indexOf(currentNode.nodeName.toLowerCase()) !== -1){
                    try {
                        currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
                    } catch(e) {}
                }

                /* Remove element if anything permits its presence */
                currentNode.parentNode.removeChild(currentNode);
                return true;
            }

            /* Finally, convert markup to cover jQuery behavior */
            if (SAFE_FOR_JQUERY && !currentNode.firstElementChild) {
                currentNode.textContent = currentNode.textContent.replace(/</g, '<');
            }
            return false;
        };

        /**
         * _sanitizeAttributes
         *
         * @protect attributes
         * @protect removeAttribiuteNode
         * @protect setAttribute
         * @protect cloneNode
         *
         * @param   node to sanitize
         * @return  void
         */
        var _sanitizeAttributes = function(currentNode) {
            var regex = /^(\w+script|data):/gi,
                clonedNode = currentNode.cloneNode(true),
                tmp, clobbering;

            /* Check if we have attributes; if not we might have a text node */
            if(currentNode.attributes) {

                /* Go backwards over all attributes; safely remove bad ones */
                for (var attr = currentNode.attributes.length-1; attr >= 0; attr--) {

                    tmp = clonedNode.attributes[attr];
                    clobbering = false;
                    currentNode.removeAttribute(currentNode.attributes[attr].name);

                    if (tmp instanceof Attr) {
                        if(SANITIZE_DOM) {
                            if(tmp.name === 'id' && window[tmp.value]) {
                                clobbering = true;
                            }
                            if(tmp.name === 'name' && document[tmp.value]){
                                clobbering = true;
                            }
                        }

                        /* Safely handle attributes */
                        if (
                            (ALLOWED_ATTR.indexOf(tmp.name.toLowerCase()) > -1 ||
                            (ALLOW_DATA_ATTR && tmp.name.match(/^data-[\w-]+/i)))

                            /* Get rid of script and data URIs */
                            && (!tmp.value.replace(/[\x00-\x20]/g,'').match(regex)

                                /* Keep image data URIs alive if src is allowed */
                                || (tmp.name === 'src'
                                    && tmp.value.indexOf('data:') === 0
                                    && tmp.ownerElement.nodeName === 'IMG'))

                            /* Make sure attribute cannot clobber */
                            && !clobbering
                        ) {
                            currentNode.setAttribute(tmp.name, tmp.value);
                        }
                    }
            }
            }
        };

        /**
         * _sanitizeShadowDOM
         *
         * @param  fragment to iterate over recursively
         * @return void
         */
        var _sanitizeShadowDOM = function(fragment) {
            var shadowNode;
            var shadowIterator = _createIterator(fragment);

            while (shadowNode = shadowIterator.nextNode()) {

                /* Sanitize tags and elements */
                if (_sanitizeElements(shadowNode)) {
                    continue;
                }

                /* Deep shadow DOM detected */
                if (shadowNode.content instanceof DocumentFragment) {
                    _sanitizeShadowDOM(shadowNode.content);
                }

                /* Check attributes, sanitize if necessary */
                _sanitizeAttributes(shadowNode);
            }
        };

        /* Assign config vars */
        cfg ? _parseConfig(cfg) : null;

        /* Initialize the document to work on */
        var body = _initDocument(dirty);

         /* Early exit in case document is empty */
        if(typeof body === 'undefined') {
            return '';
        }

        /* Get node iterator */
        var currentNode;
        var nodeIterator = _createIterator(body);

        /* Now start iterating over the created document */
        while (currentNode = nodeIterator.nextNode()) {
            /* Sanitize tags and elements */
            if (_sanitizeElements(currentNode)) {
                continue;
            }

            /* Shadow DOM detected, sanitize it */
            if (currentNode.content instanceof DocumentFragment) {
                _sanitizeShadowDOM(currentNode.content);
            }

            /* Check attributes, sanitize if necessary */
            _sanitizeAttributes(currentNode);
        }

        /* Return sanitized string or DOM */
        if (RETURN_DOM) {
            return body;
        }
        return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
    };
    return DOMPurify;
}));
<commit_msg>fixed #20<commit_after>/* jshint boss: true */
/* global Text */
;(function(root, factory) {
    'use strict';
    if (typeof define === "function" && define.amd) {
        define(factory);
    } else {
        root.DOMPurify = factory();
    }
}(this, function() {
    'use strict';

    var DOMPurify = {};
    DOMPurify.sanitize = function(dirty, cfg) {

        /**
         * We consider the elements and attributes below to be safe. Ideally
         * don't add any new ones but feel free to remove unwanted ones.
         */

        /* allowed element names */
        var ALLOWED_TAGS = [

            // HTML
            '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','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','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',

            // SVG
            'svg','altglyph','altglyphdef','altglyphitem','animatecolor',
            'animatemotion','animatetransform','circle','clippath','defs','desc',
            'ellipse','font','g','glyph','glyphref','hkern','image','line',
            'lineargradient','marker','mask','metadata','mpath','path','pattern',
            'polygon','polyline','radialgradient','rect','stop','switch','symbol',
            'text','textpath','title','tref','tspan','view','vkern',

            //MathML
            'math','menclose','merror','mfenced','mfrac','mglyph','mi','mlabeledtr',
            'mmuliscripts','mn','mo','mover','mpadded','mphantom','mroot','mrow',
            'ms','mpspace','msqrt','mystyle','msub','msup','msubsup','mtable','mtd',
            'mtext','mtr','munder','munderover',

            //Text
            '#text'
        ];

        /* Decide if custom data attributes are okay */
        var ALLOW_DATA_ATTR = true;

        /* Allowed attribute names */
        var ALLOWED_ATTR = [

            // HTML
            'accept','action','align','alt','autocomplete','bgcolor','border',
            'checked','cite','class','color','cols','colspan','coords','datetime',
            'default','dir','disabled','download','enctype','for','headers','height',
            'hidden','high','href','hreflang','id','ismap','label','lang','list',
            'loop', 'low','max','maxlength','media','method','min','multiple',
            'name','novalidate','open','optimum','pattern','placeholder','poster',
            'preload','pubdate','radiogroup','readonly','rel','required','rev',
            'reversed','rows','rowspan','spellcheck','scope','selected','shape',
            'size','span','srclang','start','src','step','style','summary','tabindex',
            'title','type','usemap','valign','value','width','xmlns',

            // SVG
            'accent-height','accumulate','additivive','alignment-baseline',
            'ascent','azimuth','baseline-shift','bias','clip','clip-path',
            'clip-rule','color','color-interpolation','color-interpolation-filters',
            'color-profile','color-rendering','cx','cy','d','dy','dy','direction',
            'display','divisor','dur','elevation','end','fill','fill-opacity',
            'fill-rule','filter','flood-color','flood-opacity','font-family',
            'font-size','font-size-adjust','font-stretch','font-style','font-variant',
            'front-weight','image-rendering','in','in2','k1','k2','k3','k4','kerning',
            'letter-spacing','lighting-color','local','marker-end','marker-mid',
            'marker-start','max','mask','mode','min','operator','opacity','order',
            'overflow','paint-order','path','points','r','rx','ry','radius','restart',
            'scale','seed','shape-rendering','stop-color','stop-opacity',
            'stroke-dasharray','stroke-dashoffset','stroke-linecap','stroke-linejoin',
            'stroke-miterlimit','stroke-opacity','stroke','stroke-width','transform',
            'text-anchor','text-decoration','text-rendering','u1','u2','viewbox',
            'visibility','word-spacing','wrap','writing-mode','x','x1','x2','y',
            'y1','y2','z',

            // MathML
            'accent','accentunder','bevelled','close','columnsalign','columnlines',
            'columnspan','denomalign','depth','display','displaystyle','fence',
            'frame','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',

            // XML
            'xlink:href','xml:id','xlink:title','xml:space'
        ];

        /* Decide if document with <html>... should be returned */
        var WHOLE_DOCUMENT = false;

        /* Decide if a DOM node or a string should be returned */
        var RETURN_DOM = false;

        /* Output should be safe for jQuery's $() factory? */
        var SAFE_FOR_JQUERY = false;

        /* Output should be free from DOM clobbering attacks? */
        var SANITIZE_DOM = true;

        /* Keep element content when removing element? */
        var KEEP_CONTENT = true;

        /* Tags to keep content from (when KEEP_CONTENT is true) */
        var CONTENT_TAGS = [
            'a','abbr','acronym','address','article','aside','b','bdi','bdo',
            'big','blink','blockquote','caption','center','cite','code','col',
            'dd','del','details','dfn','dir','div','dl','dt','em','figcaption',
            'figure','footer','h1','h2','h3','h4','h5','h6','header','i','ins',
            'kbd','label','legend','li','main','mark','marquee','nav','ol',
            'output','p','pre','q','rp','rt','ruby','s','samp','section','small',
            'span','strike','strong','sub','summary','sup','table','tbody','td',
            'tfoot','th','thead','time','tr','tt','u','ul','var'
        ];

        /* Ideally, do not touch anything below this line */
        /* ______________________________________________ */

        /**
         * _parseConfig
         *
         * @param  optional config literal
         */
        var _parseConfig = function(cfg) {

            /* Set configuration parameters */
            cfg.ALLOWED_ATTR    ? ALLOWED_ATTR    = cfg.ALLOWED_ATTR    : null;
            cfg.ALLOWED_TAGS    ? ALLOWED_TAGS    = cfg.ALLOWED_TAGS    : null;
            cfg.ALLOW_DATA_ATTR ? ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR : null;
            cfg.SAFE_FOR_JQUERY ? SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY : null;
            cfg.WHOLE_DOCUMENT  ? WHOLE_DOCUMENT  = cfg.WHOLE_DOCUMENT  : null;
            cfg.RETURN_DOM      ? RETURN_DOM      = cfg.RETURN_DOM      : null;
            cfg.SANITIZE_DOM    ? SANITIZE_DOM    = cfg.SANITIZE_DOM    : null;
            cfg.KEEP_CONTENT    ? KEEP_CONTENT    = cfg.KEEP_CONTENT    : null;

            /* Merge configuration parameters */
            cfg.ADD_ATTR ? ALLOWED_ATTR = ALLOWED_ATTR.concat(cfg.ADD_ATTR) : null;
            cfg.ADD_TAGS ? ALLOWED_TAGS = ALLOWED_TAGS.concat(cfg.ADD_TAGS) : null;

            /* Add #text in case KEEP_CONTENT is set to true */
            KEEP_CONTENT ? ALLOWED_TAGS.push('#text') : null;
        };

       /**
         * _initDocument
         *
         * @param  a string of dirty markup
         * @return a DOM, filled with the dirty markup
         */
        var _initDocument = function(dirty){

            /* Create documents to map markup to */
            var dom = document.implementation.createHTMLDocument('');
                dom.body.parentNode.removeChild(dom.body.parentNode.firstElementChild);
                dom.body.outerHTML = dirty;

            /* Cover IE9's buggy outerHTML behavior */
            if(dom.body === null) {
                dom.body.innerHTML = dirty;
            }

            /* Work on whole document or just its body */
            var body = WHOLE_DOCUMENT ? dom.body.parentNode : dom.body;
            if (
                !(dom.body instanceof HTMLBodyElement) ||
                !(dom.body instanceof HTMLHtmlElement)
            ) {
                var freshdom = document.implementation.createHTMLDocument('');
                body = WHOLE_DOCUMENT
                    ? freshdom.getElementsByTagName.call(dom,'html')[0]
                    : freshdom.getElementsByTagName.call(dom,'body')[0];
            }
            return body;
        };

        /**
         * _createIterator
         *
         * @param  document/fragment to create iterator for
         * @return iterator instance
         */
        var _createIterator = function(doc) {
            return document.createNodeIterator(
                doc,
                NodeFilter.SHOW_ELEMENT
                | NodeFilter.SHOW_COMMENT
                | NodeFilter.SHOW_TEXT,
                function() { return NodeFilter.FILTER_ACCEPT; },
                false
            );
        };

        /**
         * _isClobbered
         *
         * @param  element to check for clobbering attacks
         * @return true if clobbered, false if safe
         */
        var _isClobbered = function(elm) {
            if(elm instanceof Text) {
                return false;
            }
            if (
                (elm.children && !(elm.children instanceof HTMLCollection))
                || (elm.attributes instanceof NodeList)
                || typeof elm.nodeName !== 'string'
                || typeof elm.textContent !== 'string'
                || typeof elm.nodeType !== 'number'
                || typeof elm.COMMENT_NODE !== 'number'
                || typeof elm.setAttribute !== 'function'
                || typeof elm.cloneNode !== 'function'
                || typeof elm.removeAttributeNode !== 'function'
                || typeof elm.insertAdjacentHTML !== 'function'
                || typeof elm.attributes.item !== 'function'
            ) {
                return true;
            }
            return false;
        };

        /**
         * _sanitizeElements
         *
         * @protect removeChild
         * @protect nodeType
         * @protect nodeName
         * @protect textContent
         * @protect currentNode
         * @protect insertAdjacentHTML
         *
         * @param   node to check for permission to exist
         * @return  true if node was killed, false if left alive
         */
        var _sanitizeElements = function(currentNode) {

            /* Check if element is clobbered or can clobber */
            if (_isClobbered(currentNode)) {

                /* Be harsh with clobbered content, element has to go! */
                try{
                    currentNode.parentNode.removeChild(currentNode);
                } catch(e){
                    currentNode.outerHTML='';
                }
                return true;
            }

            /* Now let's check the element's type and name */
            if(currentNode.nodeType === currentNode.COMMENT_NODE
                || ALLOWED_TAGS.indexOf(currentNode.nodeName.toLowerCase()) === -1
            ) {
                /* Keep content for white-listed elements */
                if(KEEP_CONTENT && currentNode.insertAdjacentHTML
                    && currentNode.nodeName.toLowerCase
                    && CONTENT_TAGS.indexOf(currentNode.nodeName.toLowerCase()) !== -1){
                    try {
                        currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
                    } catch(e) {}
                }

                /* Remove element if anything permits its presence */
                currentNode.parentNode.removeChild(currentNode);
                return true;
            }

            /* Finally, convert markup to cover jQuery behavior */
            if (SAFE_FOR_JQUERY && !currentNode.firstElementChild) {
                currentNode.textContent = currentNode.textContent.replace(/</g, '<');
            }
            return false;
        };

        /**
         * _sanitizeAttributes
         *
         * @protect attributes
         * @protect removeAttribiuteNode
         * @protect setAttribute
         * @protect cloneNode
         *
         * @param   node to sanitize
         * @return  void
         */
        var _sanitizeAttributes = function(currentNode) {
            var regex = /^(\w+script|data):/gi,
                clonedNode = currentNode.cloneNode(true),
                tmp, clobbering;

            /* Check if we have attributes; if not we might have a text node */
            if(currentNode.attributes) {

                /* Go backwards over all attributes; safely remove bad ones */
                for (var attr = currentNode.attributes.length-1; attr >= 0; attr--) {

                    tmp = clonedNode.attributes[attr];
                    clobbering = false;
                    currentNode.removeAttribute(currentNode.attributes[attr].name);

                    if (tmp instanceof Attr) {
                        if(SANITIZE_DOM) {
                            if(tmp.name === 'id' && window[tmp.value]) {
                                clobbering = true;
                            }
                            if(tmp.name === 'name' && document[tmp.value]){
                                clobbering = true;
                            }
                        }

                        /* Safely handle attributes */
                        if (
                            (ALLOWED_ATTR.indexOf(tmp.name.toLowerCase()) > -1 ||
                            (ALLOW_DATA_ATTR && tmp.name.match(/^data-[\w-]+/i)))

                            /* Get rid of script and data URIs */
                            && (!tmp.value.replace(/[\x00-\x20]/g,'').match(regex)

                                /* Keep image data URIs alive if src is allowed */
                                || (tmp.name === 'src'
                                    && tmp.value.indexOf('data:') === 0
                                    && currentNode.nodeName === 'IMG'))

                            /* Make sure attribute cannot clobber */
                            && !clobbering
                        ) {
                            currentNode.setAttribute(tmp.name, tmp.value);
                        }
                    }
            }
            }
        };

        /**
         * _sanitizeShadowDOM
         *
         * @param  fragment to iterate over recursively
         * @return void
         */
        var _sanitizeShadowDOM = function(fragment) {
            var shadowNode;
            var shadowIterator = _createIterator(fragment);

            while (shadowNode = shadowIterator.nextNode()) {

                /* Sanitize tags and elements */
                if (_sanitizeElements(shadowNode)) {
                    continue;
                }

                /* Deep shadow DOM detected */
                if (shadowNode.content instanceof DocumentFragment) {
                    _sanitizeShadowDOM(shadowNode.content);
                }

                /* Check attributes, sanitize if necessary */
                _sanitizeAttributes(shadowNode);
            }
        };

        /* Assign config vars */
        cfg ? _parseConfig(cfg) : null;

        /* Initialize the document to work on */
        var body = _initDocument(dirty);

         /* Early exit in case document is empty */
        if(typeof body === 'undefined') {
            return '';
        }

        /* Get node iterator */
        var currentNode;
        var nodeIterator = _createIterator(body);

        /* Now start iterating over the created document */
        while (currentNode = nodeIterator.nextNode()) {
            /* Sanitize tags and elements */
            if (_sanitizeElements(currentNode)) {
                continue;
            }

            /* Shadow DOM detected, sanitize it */
            if (currentNode.content instanceof DocumentFragment) {
                _sanitizeShadowDOM(currentNode.content);
            }

            /* Check attributes, sanitize if necessary */
            _sanitizeAttributes(currentNode);
        }

        /* Return sanitized string or DOM */
        if (RETURN_DOM) {
            return body;
        }
        return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
    };
    return DOMPurify;
}));
<|endoftext|>


<commit_before>Template.postItem.onRendered(function () {
  // Get reference to template instance
  const instance = this;

  // Get Post ID from template instance
  const postId = instance.data.post._id;

  // Get reference to post DOM element
  const postElement = instance.$(`#${postId}`);

  // Render OEmbed content
  postElement.oembed();
});

Template.postItem.helpers({
  userIsPostOwner: function () {
    const instance = Template.instance();

    // Get Post ID from template instance
    const postOwnerId = instance.data.post.userId;
    const loggedUser = Meteor.userId();
    console.log("owner=" , loggedUser);
    return postOwnerId === loggedUser;
  }
})
<commit_msg>Added event handler for Delete button<commit_after>import { Posts } from '/posts/collection';

Template.postItem.onRendered(function () {
  // Get reference to template instance
  const instance = this;

  // Get Post ID from template instance
  const postId = instance.data.post._id;

  // Get reference to post DOM element
  const postElement = instance.$(`#${postId}`);

  // Render OEmbed content
  postElement.oembed();
});

Template.postItem.helpers({
  userIsPostOwner: function () {
    const instance = Template.instance();

    // Get Post ID from template instance
    const postOwnerId = instance.data.post.userId;
    const loggedUser = Meteor.userId();
    // console.log("owner=" , loggedUser);
    return postOwnerId === loggedUser;
  },
})

Template.postItem.events({
  'click .delete'() {
    // console.log("poisto");
    if (confirm("Really want delete this post!")) {
      const instance = Template.instance();
      const postId = instance.data.post._id;
      // console.log("Trying to remove ", postId);
      Posts.remove(postId);
    }

  },
});
<|endoftext|>


<commit_before>import React from 'react';
import './LinkList.css';

function findDuplicates (links) {
  const uniq = {};
  return links.reduce((memo, link) => {
    if (!uniq[link.href]) {
      memo.push(false);
      uniq[link.href] = true;
    } else {
      memo.push(true)
    }
    return memo;
  }, []);
}

function groupByDomain(links) {
  let mapped = links.map((link, i) => {
    return {
      index: i,
      reverseHostname: link.hostname.split('.').reverse().join('.')
    };
  });
  mapped.sort((a, b) => {
    if (a.reverseHostname  b.reverseHostname) {
      return 1;
    }
    if (a.index  b.index) {
      return 1;
    }
    return 0;
  });
  return mapped.map(v => links[v.index]);
}

const LinkList = React.createClass({
  getInitialState: function () {
    return {
      dedup: this.props.dedup,
      groupByDomain: false
    };
  },
  toggleDedup: function () {
    this.setState({
      dedup: !this.state.dedup
    });
  },
  toggleGroupByDomain: function () {
    this.setState({
      groupByDomain: !this.state.groupByDomain
    });
  },
  render: function () {
    if (this.props.expired) {
      return (

Expired

            Link information has expired and is no longer available.
            Please close this tab and try again.

      );
    }
    const links = this.state.groupByDomain ? groupByDomain(links) : this.props.links;
    let noLinksFound = null;
    if (links.length === 0) {
      noLinksFound = (

          No links were found.

      );
    }
    const duplicates = findDuplicates(links);
    const items = links.reduce((memo, link, index) => {
      const style = {};
      if (duplicates[index]) {
        if (this.state.dedup) {
          return memo;
        } else {
          style.color = '#aaa';
        }
      }
      memo.push(

          {link.href}

      );
      return memo;
    }, []);
    return (

{this.props.source}

          {items.length} links out of {links.length} shown

            <input type="checkbox" checked={this.state.dedup} onChange={this.toggleDedup} /> Hide duplicate links

            <input type="checkbox" checked={this.state.groupByDomain} onChange={this.toggleGroupByDomain} /> Group by domain

        {noLinksFound}

          {items}

    );
  }
});

export default LinkList;
<commit_msg>slightly better grammar<commit_after>import React from 'react';
import './LinkList.css';

function findDuplicates (links) {
  const uniq = {};
  return links.reduce((memo, link) => {
    if (!uniq[link.href]) {
      memo.push(false);
      uniq[link.href] = true;
    } else {
      memo.push(true)
    }
    return memo;
  }, []);
}

function groupByDomain(links) {
  let mapped = links.map((link, i) => {
    return {
      index: i,
      reverseHostname: link.hostname.split('.').reverse().join('.')
    };
  });
  mapped.sort((a, b) => {
    if (a.reverseHostname  b.reverseHostname) {
      return 1;
    }
    if (a.index  b.index) {
      return 1;
    }
    return 0;
  });
  return mapped.map(v => links[v.index]);
}

const LinkList = React.createClass({
  getInitialState: function () {
    return {
      dedup: this.props.dedup,
      groupByDomain: false
    };
  },
  toggleDedup: function () {
    this.setState({
      dedup: !this.state.dedup
    });
  },
  toggleGroupByDomain: function () {
    this.setState({
      groupByDomain: !this.state.groupByDomain
    });
  },
  render: function () {
    if (this.props.expired) {
      return (

Expired

            Link information has expired and is no longer available.
            Please close this tab and try again.

      );
    }
    const links = this.state.groupByDomain ? groupByDomain(links) : this.props.links;
    let noLinksFound = null;
    if (links.length === 0) {
      noLinksFound = (

          No links were found.

      );
    }
    const duplicates = findDuplicates(links);
    const items = links.reduce((memo, link, index) => {
      const style = {};
      if (duplicates[index]) {
        if (this.state.dedup) {
          return memo;
        } else {
          style.color = '#aaa';
        }
      }
      memo.push(

          {link.href}

      );
      return memo;
    }, []);
    let status = null;
    if (items.length === 0) {
      // do nothing
    } else if (items.length === 1) {
      status = (

          1 link out of {links.length} shown

      )
    } else {
      status = (

          {items.length} links out of {links.length} shown

      );
    }
    return (

{this.props.source}

        {status}

            <input type="checkbox" checked={this.state.dedup} onChange={this.toggleDedup} /> Hide duplicate links

            <input type="checkbox" checked={this.state.groupByDomain} onChange={this.toggleGroupByDomain} /> Group by domain

        {noLinksFound}

          {items}

    );
  }
});

export default LinkList;
<|endoftext|>


<commit_before>//var xmlhttp = Ajax.getTransport();

var _feed_cur_page = 0;
var _infscroll_disable = 0;

function viewCategory(cat) {
	active_feed_is_cat = true;
	viewfeed(cat, '', true);
	return false;
}

function feedlist_callback() {
	if (xmlhttp.readyState == 4) {
		feedlist_callback2(xmlhttp);
	}
}

function feedlist_callback2(transport) {
	try {
		debug("feedlist_callback2");
		var f = document.getElementById("feeds-frame");
		f.innerHTML = transport.responseText;
		feedlist_init();
	} catch (e) {
		exception_error("feedlist_callback2", e);
	}
}

function viewNextFeedPage() {
	try {
		if (!getActiveFeedId()) return;

		notify_progress("Loading, please wait...");

		viewfeed(getActiveFeedId(), undefined, undefined, undefined,
			undefined, _feed_cur_page+1);

	} catch (e) {
		exception_error(e, "viewFeedGoPage");
	}
}

function viewfeed(feed, subop, is_cat, subop_param, skip_history, offset) {
	try {

//		if (!offset) page_offset = 0;

		var page_offset = 0;

		if (offset > 0) {
			page_offset = offset;
		} else {
			page_offset = 0;
			_feed_cur_page = 0;
			_infscroll_disable = 0;
		}

		if (getActiveFeedId() != feed) {
			_feed_cur_page = 0;
			active_post_id = 0;
			_infscroll_disable = 0;
		}

		enableHotkeys();

		closeInfoBox();

		Form.enable("main_toolbar_form");

		var toolbar_form = document.forms["main_toolbar_form"];
		var toolbar_query = Form.serialize("main_toolbar_form");

		if (document.forms["main_toolbar_form"].query) {
			toolbar_form.query.value = "";
		}

		var query = "backend.php?op=viewfeed&feed=" + feed + "&" +
			toolbar_query + "&subop=" + param_escape(subop);

		if (document.getElementById("search_form")) {
			var search_query = Form.serialize("search_form");
			query = query + "&" + search_query;
			closeInfoBox(true);
		}

//		debug("IS_CAT_STORED: " + activeFeedIsCat() + ", IS_CAT: " + is_cat);

		if (subop == "MarkAllRead") {

			var feedlist = document.getElementById('feedList');

			var next_unread_feed = getRelativeFeedId(feedlist,
					feed, "next", true);

			if (!next_unread_feed) {
				next_unread_feed = getRelativeFeedId(feedlist,
					-3, "next", true);
			}

			var show_next_feed = getInitParam("on_catchup_show_next_feed") == "1";

			if (next_unread_feed && show_next_feed && !activeFeedIsCat()) {
				query = query + "&nuf=" + param_escape(next_unread_feed);
				setActiveFeedId(next_unread_feed);
			}
		}

		if (is_cat) {
			query = query + "&cat=1";
		}

		if (page_offset != 0) {
			query = query + "&skip=" + page_offset;
		}

		var date = new Date();
		var timestamp = Math.round(date.getTime() / 1000);
		query = query + "&ts=" + timestamp

		disableContainerChildren("headlinesToolbar", false);
		Form.enable("main_toolbar_form");

		debug(query);

		// for piggybacked counters

		if (tagsAreDisplayed()) {
			query = query + "&omode=lt";
		} else {
			query = query + "&omode=flc";
		}

		if (!page_offset) {
			notify_progress("Loading, please wait...", true);
		}

		var container = document.getElementById("headlinesInnerContainer");

		if (container && page_offset == 0) {
			new Effect.Fade(container, {duration: 1, to: 0.01,
				queue: { position:'end', scope: 'FeED-' + feed, limit: 1 } } );
		}

		new Ajax.Request(query, {
			asynchronous: page_offset == 0,
			onComplete: function(transport) {
				headlines_callback2(transport, feed, is_cat, page_offset);
			} });

	} catch (e) {
		exception_error("viewfeed", e);
	}
}

function toggleCollapseCat_af(effect) {
	//var caption = elem.id.replace("FCATLIST-", "");

	try {

		var elem = effect.element;
		var cat = elem.id.replace("FCATLIST-", "");
		var cap = document.getElementById("FCAP-" + cat);

		if (Element.visible(elem)) {
			cap.innerHTML = cap.innerHTML.replace("...", "");
		} else {
			if (cap.innerHTML.lastIndexOf("...") != cap.innerHTML.length-3) {
				cap.innerHTML = cap.innerHTML + "...";
			}
		}

	} catch (e) {
		exception_error("toggleCollapseCat_af", e);
	}
}

function toggleCollapseCat(cat) {
	try {
		if (!xmlhttp_ready(xmlhttp)) {
			printLockingError();
			return;
		}

		var cat_elem = document.getElementById("FCAT-" + cat);
		var cat_list = document.getElementById("FCATLIST-" + cat).parentNode;
		var caption = document.getElementById("FCAP-" + cat);

/*		if (cat_list.className.match("invisible")) {
			cat_list.className = "";
			caption.innerHTML = caption.innerHTML.replace("...", "");
			if (cat == 0) {
				setCookie("ttrss_vf_uclps", "0");
			}
		} else {
			cat_list.className = "invisible";
			caption.innerHTML = caption.innerHTML + "...";
			if (cat == 0) {
				setCookie("ttrss_vf_uclps", "1");
			}

		} */

		if (cat == 0) {
			if (Element.visible("FCATLIST-" + cat)) {
				setCookie("ttrss_vf_uclps", "1");
			} else {
				setCookie("ttrss_vf_uclps", "0");
			}
		}

		if (cat == -2) {
			if (Element.visible("FCATLIST-" + cat)) {
				setCookie("ttrss_vf_lclps", "1");
			} else {
				setCookie("ttrss_vf_lclps", "0");
			}
		}

		if (cat == -1) {
			if (Element.visible("FCATLIST-" + cat)) {
				setCookie("ttrss_vf_vclps", "1");
			} else {
				setCookie("ttrss_vf_vclps", "0");
			}
		}

		Effect.toggle('FCATLIST-' + cat, 'blind', { duration: 0.5,
			afterFinish: toggleCollapseCat_af });

		new Ajax.Request("backend.php?op=feeds&subop=collapse&cid=" +
			param_escape(cat));

	} catch (e) {
		exception_error("toggleCollapseCat", e);
	}
}

function feedlist_init() {
	try {
//		if (arguments.callee.done) return;
//		arguments.callee.done = true;

		debug("in feedlist init");

		hideOrShowFeeds(document, getInitParam("hide_read_feeds") == 1);
		document.onkeydown = hotkey_handler;
		setTimeout("timeout()", 0);

		debug("about to remove splash, OMG!");

		var o = document.getElementById("overlay");

		if (o) {
			o.style.display = "none";
			debug("removed splash!");
		}

		if (typeof correctPNG != 'undefined') {
			correctPNG();
		}

		if (getActiveFeedId()) {
			debug("some feed is open on feedlist refresh, reloading");
			setTimeout("viewCurrentFeed()", 100);
		} else {
			setTimeout("viewfeed(-3)", 100);
		}

	} catch (e) {
		exception_error("feedlist/init", e);
	}
}
<commit_msg>fix category collapsing<commit_after>var _feed_cur_page = 0;
var _infscroll_disable = 0;

function viewCategory(cat) {
	active_feed_is_cat = true;
	viewfeed(cat, '', true);
	return false;
}

function feedlist_callback2(transport) {
	try {
		debug("feedlist_callback2");
		var f = document.getElementById("feeds-frame");
		f.innerHTML = transport.responseText;
		feedlist_init();
	} catch (e) {
		exception_error("feedlist_callback2", e);
	}
}

function viewNextFeedPage() {
	try {
		if (!getActiveFeedId()) return;

		notify_progress("Loading, please wait...");

		viewfeed(getActiveFeedId(), undefined, undefined, undefined,
			undefined, _feed_cur_page+1);

	} catch (e) {
		exception_error(e, "viewFeedGoPage");
	}
}

function viewfeed(feed, subop, is_cat, subop_param, skip_history, offset) {
	try {

//		if (!offset) page_offset = 0;

		var page_offset = 0;

		if (offset > 0) {
			page_offset = offset;
		} else {
			page_offset = 0;
			_feed_cur_page = 0;
			_infscroll_disable = 0;
		}

		if (getActiveFeedId() != feed) {
			_feed_cur_page = 0;
			active_post_id = 0;
			_infscroll_disable = 0;
		}

		enableHotkeys();

		closeInfoBox();

		Form.enable("main_toolbar_form");

		var toolbar_form = document.forms["main_toolbar_form"];
		var toolbar_query = Form.serialize("main_toolbar_form");

		if (document.forms["main_toolbar_form"].query) {
			toolbar_form.query.value = "";
		}

		var query = "backend.php?op=viewfeed&feed=" + feed + "&" +
			toolbar_query + "&subop=" + param_escape(subop);

		if (document.getElementById("search_form")) {
			var search_query = Form.serialize("search_form");
			query = query + "&" + search_query;
			closeInfoBox(true);
		}

//		debug("IS_CAT_STORED: " + activeFeedIsCat() + ", IS_CAT: " + is_cat);

		if (subop == "MarkAllRead") {

			var feedlist = document.getElementById('feedList');

			var next_unread_feed = getRelativeFeedId(feedlist,
					feed, "next", true);

			if (!next_unread_feed) {
				next_unread_feed = getRelativeFeedId(feedlist,
					-3, "next", true);
			}

			var show_next_feed = getInitParam("on_catchup_show_next_feed") == "1";

			if (next_unread_feed && show_next_feed && !activeFeedIsCat()) {
				query = query + "&nuf=" + param_escape(next_unread_feed);
				setActiveFeedId(next_unread_feed);
			}
		}

		if (is_cat) {
			query = query + "&cat=1";
		}

		if (page_offset != 0) {
			query = query + "&skip=" + page_offset;
		}

		var date = new Date();
		var timestamp = Math.round(date.getTime() / 1000);
		query = query + "&ts=" + timestamp

		disableContainerChildren("headlinesToolbar", false);
		Form.enable("main_toolbar_form");

		debug(query);

		// for piggybacked counters

		if (tagsAreDisplayed()) {
			query = query + "&omode=lt";
		} else {
			query = query + "&omode=flc";
		}

		if (!page_offset) {
			notify_progress("Loading, please wait...", true);
		}

		var container = document.getElementById("headlinesInnerContainer");

		if (container && page_offset == 0) {
			new Effect.Fade(container, {duration: 1, to: 0.01,
				queue: { position:'end', scope: 'FeED-' + feed, limit: 1 } } );
		}

		new Ajax.Request(query, {
			asynchronous: page_offset == 0,
			onComplete: function(transport) {
				headlines_callback2(transport, feed, is_cat, page_offset);
			} });

	} catch (e) {
		exception_error("viewfeed", e);
	}
}

function toggleCollapseCat_af(effect) {
	//var caption = elem.id.replace("FCATLIST-", "");

	try {

		var elem = effect.element;
		var cat = elem.id.replace("FCATLIST-", "");
		var cap = document.getElementById("FCAP-" + cat);

		if (Element.visible(elem)) {
			cap.innerHTML = cap.innerHTML.replace("...", "");
		} else {
			if (cap.innerHTML.lastIndexOf("...") != cap.innerHTML.length-3) {
				cap.innerHTML = cap.innerHTML + "...";
			}
		}

	} catch (e) {
		exception_error("toggleCollapseCat_af", e);
	}
}

function toggleCollapseCat(cat) {
	try {

		var cat_elem = document.getElementById("FCAT-" + cat);
		var cat_list = document.getElementById("FCATLIST-" + cat).parentNode;
		var caption = document.getElementById("FCAP-" + cat);

/*		if (cat_list.className.match("invisible")) {
			cat_list.className = "";
			caption.innerHTML = caption.innerHTML.replace("...", "");
			if (cat == 0) {
				setCookie("ttrss_vf_uclps", "0");
			}
		} else {
			cat_list.className = "invisible";
			caption.innerHTML = caption.innerHTML + "...";
			if (cat == 0) {
				setCookie("ttrss_vf_uclps", "1");
			}

		} */

		if (cat == 0) {
			if (Element.visible("FCATLIST-" + cat)) {
				setCookie("ttrss_vf_uclps", "1");
			} else {
				setCookie("ttrss_vf_uclps", "0");
			}
		}

		if (cat == -2) {
			if (Element.visible("FCATLIST-" + cat)) {
				setCookie("ttrss_vf_lclps", "1");
			} else {
				setCookie("ttrss_vf_lclps", "0");
			}
		}

		if (cat == -1) {
			if (Element.visible("FCATLIST-" + cat)) {
				setCookie("ttrss_vf_vclps", "1");
			} else {
				setCookie("ttrss_vf_vclps", "0");
			}
		}

		Effect.toggle('FCATLIST-' + cat, 'blind', { duration: 0.5,
			afterFinish: toggleCollapseCat_af });

		new Ajax.Request("backend.php?op=feeds&subop=collapse&cid=" +
			param_escape(cat));

	} catch (e) {
		exception_error("toggleCollapseCat", e);
	}
}

function feedlist_init() {
	try {
//		if (arguments.callee.done) return;
//		arguments.callee.done = true;

		debug("in feedlist init");

		hideOrShowFeeds(document, getInitParam("hide_read_feeds") == 1);
		document.onkeydown = hotkey_handler;
		setTimeout("timeout()", 0);

		debug("about to remove splash, OMG!");

		var o = document.getElementById("overlay");

		if (o) {
			o.style.display = "none";
			debug("removed splash!");
		}

		if (typeof correctPNG != 'undefined') {
			correctPNG();
		}

		if (getActiveFeedId()) {
			debug("some feed is open on feedlist refresh, reloading");
			setTimeout("viewCurrentFeed()", 100);
		} else {
			setTimeout("viewfeed(-3)", 100);
		}

	} catch (e) {
		exception_error("feedlist/init", e);
	}
}
<|endoftext|>


<commit_before>const { Writable } = require("stream");

const async = require("async");
const env = require("require-env");
const { Pool } = require("pg");
const QueryStream = require("pg-query-stream");

const { NOOP } = require("..");
const getBadges = require("./sum_check");
const getDateBasedBadges = require("./date_check_total");
const getSequentialBadges = require("./date_check_sequential");

const query = async (pool, query, params) => {
  const client = await pool.connect();

  try {
    return await client.query(query, params);
  } finally {
    client.release();
  }
};

class AbstractBadgeProcessor extends Writable {
  constructor(pool) {
    super({
      objectMode: true
    });

    this.pool = pool;
  }

  updateBadges(userId, badges, callback) {
    return Promise.all(
      Object.keys(badges)
        .map(x => badges[x])
        .map(badge =>
          query(
            this.pool,
            `
INSERT INTO badges_users (user_id, badge_id) VALUES (
  $1, (SELECT id FROM badges WHERE category=$2 AND level=$3)
)
ON CONFLICT DO NOTHING
            `,
            [userId, badge.category, badge.level]
          )
        )
    )
      .then(() => callback())
      .catch(callback);
  }
}

class BadgeProcessor extends AbstractBadgeProcessor {
  _write(row, _, callback) {
    // TODO TM badges
    const badges = getBadges({
      buildings: Number(row.buildings_added),
      pois: Number(row.pois_added),
      roadKms: row.road_km_added,
      roadKmMods: row.road_km_modified,
      waterways: row.waterway_km_added,
      josm: Number(row.josm_edits),
      countries: Number(row.countries),
      hashtags: Number(row.hashtags)
    });

    return this.updateBadges(row.user_id, badges, callback);
  }
}

class DateBasedBadgeProcessor extends AbstractBadgeProcessor {
  _write(row, _, callback) {
    const { user_id: userId, timestamps } = row;

    if (userId == null) {
      return callback();
    }

    const badges = Object.assign(
      {},
      getDateBasedBadges(timestamps),
      getSequentialBadges(timestamps)
    );

    return this.updateBadges(userId, badges, callback);
  }
}

module.exports.updateBadges = callback => {
  callback = callback || NOOP;

  const pool = new Pool({
    connectionString: env.require("DATABASE_URL")
  });

  pool.on("error", function(err, client) {
    console.warn("Unexpected error on idle client", err);
    throw err;
  });

  const now = new Date();

  return async.series(
    [
      async.apply(async.parallel, [
        next =>
          pool.connect((err, client, release) => {
            if (err) {
              throw err;
            }

            const done = () => {
              release();

              return next();
            };

            const query = new QueryStream(
              `
SELECT *
FROM user_stats
WHERE updated_at > (
  SELECT
    last_run
  FROM badge_updater_status
)
              `
            );

            client
              .query(query)
              .pipe(new BadgeProcessor(pool).on("finish", done));
          }),
        next =>
          pool.connect((err, client, release) => {
            if (err) {
              throw err;
            }

            const done = () => {
              release();

              return next();
            };

            // fetch data for users w/ changesets updated since the last time badges were updated
            const query = new QueryStream(`
  SELECT
    c.user_id,
    array_agg(DISTINCT date_trunc('day', c.created_at)) timestamps
  FROM raw_changesets c
  JOIN user_stats u USING (user_id)
  WHERE c.created_at IS NOT NULL
    AND u.updated_at > (SELECT last_run FROM badge_updater_status)
  GROUP BY c.user_id
    `);

            client
              .query(query)
              .pipe(new DateBasedBadgeProcessor(pool).on("finish", done));
          })
      ]),
      done =>
        query(pool, "UPDATE badge_updater_status SET last_run = $1", [now])
          .then(() => done())
          .catch(done)
    ],
    async err => {
      try {
        await pool.end();
      } finally {
        return callback(err);
      }
    }
  );
};
<commit_msg>Pass errors to the provided callback<commit_after>const { Writable } = require("stream");

const async = require("async");
const env = require("require-env");
const { Pool } = require("pg");
const QueryStream = require("pg-query-stream");

const { NOOP } = require("..");
const getBadges = require("./sum_check");
const getDateBasedBadges = require("./date_check_total");
const getSequentialBadges = require("./date_check_sequential");

const query = async (pool, query, params) => {
  const client = await pool.connect();

  try {
    return await client.query(query, params);
  } finally {
    client.release();
  }
};

class AbstractBadgeProcessor extends Writable {
  constructor(pool) {
    super({
      objectMode: true
    });

    this.pool = pool;
  }

  updateBadges(userId, badges, callback) {
    return Promise.all(
      Object.keys(badges)
        .map(x => badges[x])
        .map(badge =>
          query(
            this.pool,
            `
INSERT INTO badges_users (user_id, badge_id) VALUES (
  $1, (SELECT id FROM badges WHERE category=$2 AND level=$3)
)
ON CONFLICT DO NOTHING
            `,
            [userId, badge.category, badge.level]
          )
        )
    )
      .then(() => callback())
      .catch(callback);
  }
}

class BadgeProcessor extends AbstractBadgeProcessor {
  _write(row, _, callback) {
    // TODO TM badges
    const badges = getBadges({
      buildings: Number(row.buildings_added),
      pois: Number(row.pois_added),
      roadKms: row.road_km_added,
      roadKmMods: row.road_km_modified,
      waterways: row.waterway_km_added,
      josm: Number(row.josm_edits),
      countries: Number(row.countries),
      hashtags: Number(row.hashtags)
    });

    return this.updateBadges(row.user_id, badges, callback);
  }
}

class DateBasedBadgeProcessor extends AbstractBadgeProcessor {
  _write(row, _, callback) {
    const { user_id: userId, timestamps } = row;

    if (userId == null) {
      return callback();
    }

    const badges = Object.assign(
      {},
      getDateBasedBadges(timestamps),
      getSequentialBadges(timestamps)
    );

    return this.updateBadges(userId, badges, callback);
  }
}

module.exports.updateBadges = callback => {
  callback = callback || NOOP;

  const pool = new Pool({
    connectionString: env.require("DATABASE_URL")
  });

  pool.on("error", function(err, client) {
    console.warn("Unexpected error on idle client", err);
    throw err;
  });

  const now = new Date();

  return async.series(
    [
      async.apply(async.parallel, [
        next =>
          pool.connect((err, client, release) => {
            if (err) {
              return next(err);
            }

            const done = () => {
              release();

              return next();
            };

            const query = new QueryStream(
              `
SELECT *
FROM user_stats
WHERE updated_at > (
  SELECT
    last_run
  FROM badge_updater_status
)
              `
            );

            client
              .query(query)
              .pipe(new BadgeProcessor(pool).on("finish", done));
          }),
        next =>
          pool.connect((err, client, release) => {
            if (err) {
              return next(err);
            }

            const done = () => {
              release();

              return next();
            };

            // fetch data for users w/ changesets updated since the last time badges were updated
            const query = new QueryStream(`
  SELECT
    c.user_id,
    array_agg(DISTINCT date_trunc('day', c.created_at)) timestamps
  FROM raw_changesets c
  JOIN user_stats u USING (user_id)
  WHERE c.created_at IS NOT NULL
    AND u.updated_at > (SELECT last_run FROM badge_updater_status)
  GROUP BY c.user_id
    `);

            client
              .query(query)
              .pipe(new DateBasedBadgeProcessor(pool).on("finish", done));
          })
      ]),
      done =>
        query(pool, "UPDATE badge_updater_status SET last_run = $1", [now])
          .then(() => done())
          .catch(done)
    ],
    async err => {
      try {
        await pool.end();
      } finally {
        return callback(err);
      }
    }
  );
};
<|endoftext|>


<commit_before>define([
	"dojo/on",
	"dojo/_base/array", // array.forEach
	"dojo/keys", // keys.ENTER keys.SPACE
	"dojo/_base/declare", // declare
	"dojo/has", // has("dom-addeventlistener")
	"dojo/_base/unload", // unload.addOnWindowUnload
	"dojo/_base/window" // win.doc.addEventListener win.doc.attachEvent win.doc.detachEvent
], function(on, array, keys, declare, has, unload, win){

	// module:
	//		dijit/_OnDijitClickMixin
	// summary:
	//		Mixin so you can pass "ondijitclick" to this.connect() method,
	//		as a way to handle clicks by mouse, or by keyboard (SPACE/ENTER key).
	// description:
	//		Setting an ondijitclick handler on a node has two effects:
	//			1. converts keyboard "click" events into actual click events, so
	//			   that a "click" event bubbles up from the widget to any listeners on ancestor nodes.
	//			2. sets up a click listener on the node, which catches native click events plus
	//			   the events generated from the previous step.

	// Keep track of where the last keydown event was, to help avoid generating
	// spurious ondijitclick events when:
	// 1. focus is on a <button> or
	// 2. user presses then releases the ENTER key
	// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
	// 4. onkeyup event fires, causing the ondijitclick handler to fire
	var lastKeyDownNode = null;
	if(has("dom-addeventlistener")){
		win.doc.addEventListener('keydown', function(evt){
			lastKeyDownNode = evt.target;
		}, true);
	}else{
		// Fallback path for IE6-8
		(function(){
			var keydownCallback = function(evt){
				lastKeyDownNode = evt.srcElement;
			};
			win.doc.attachEvent('onkeydown', keydownCallback);
			unload.addOnWindowUnload(function(){
				win.doc.detachEvent('onkeydown', keydownCallback);
			});
		})();
	}

	function clickKey(/*Event*/ e){
		return (e.keyCode === keys.ENTER || e.keyCode === keys.SPACE) &&
				!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
	}

	// Custom a11yclick (a.k.a. ondijitclick) event
	var a11yclick = function(node, listener){
		if(/input|button/i.test(node.nodeName)){
			// pass through, the browser already generates click event on SPACE/ENTER key
			return on(node, "click", listener);
		}else{
			// Don't fire the click event unless both the keydown and keyup occur on this node.
			// Avoids problems where focus shifted to this node or away from the node on keydown,
			// either causing this node to process a stray keyup event, or causing another node
			// to get a stray keyup event.

			var handles = [
				on(node, "keypress", function(e){
					//console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
					if(clickKey(e)){
						// needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
						lastKeyDownNode = e.target;

						// Prevent viewport scrolling on space key in IE<9.
						// (Reproducible on test_Button.html on any of the first dijit.form.Button examples)
						// Do this onkeypress rather than onkeydown because onkeydown.preventDefault() will
						// suppress the onkeypress event, breaking _HasDropDown
						e.preventDefault();
					}
				}),

				on(node, "keyup", function(e){
					//console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
					if(clickKey(e) && e.target == lastKeyDownNode){	// === breaks greasemonkey
						//need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
						lastKeyDownNode = null;
						on.emit(e.target, "click", {
							cancelable: true,
							bubbles: true
						});
					}
				}),

				on(node, "click", function(e){
					// catch actual mouse clicks too (or touch-clicks on mobile), plus the on.emit() from above
					listener.call(this, e);
				})
			];

			return {
				remove: function(){
					array.forEach(handles, function(h){ h.remove(); });
				}
			};
		}
	};

	var ret = declare("dijit._OnDijitClickMixin", null, {
		connect: function(
				/*Object|null*/ obj,
				/*String|Function*/ event,
				/*String|Function*/ method){
			// summary:
			//		Connects specified obj/event to specified method of this object
			//		and registers for disconnect() on widget destroy.
			// description:
			//		Provide widget-specific analog to connect.connect, except with the
			//		implicit use of this widget as the target object.
			//		This version of connect also provides a special "ondijitclick"
			//		event which triggers on a click or space or enter keyup.
			//		Events connected with `this.connect` are disconnected upon
			//		destruction.
			// returns:
			//		A handle that can be passed to `disconnect` in order to disconnect before
			//		the widget is destroyed.
			// example:
			//	|	var btn = new dijit.form.Button();
			//	|	// when foo.bar() is called, call the listener we're going to
			//	|	// provide in the scope of btn
			//	|	btn.connect(foo, "bar", function(){
			//	|		console.debug(this.toString());
			//	|	});
			// tags:
			//		protected

			return this.inherited(arguments, [obj, event == "ondijitclick" ? a11yclick : event, method]);
		}
	});

	ret.a11yclick = a11yclick;

	return ret;
});
<commit_msg>Manually generate click event from touchend event to avoid iOS/safari bugs when a click event doesn't occur after focus has been programatically shifted.<commit_after>define([
	"dojo/on",
	"dojo/_base/array", // array.forEach
	"dojo/keys", // keys.ENTER keys.SPACE
	"dojo/_base/declare", // declare
	"dojo/has", // has("dom-addeventlistener")
	"dojo/_base/unload", // unload.addOnWindowUnload
	"dojo/_base/window" // win.doc.addEventListener win.doc.attachEvent win.doc.detachEvent
], function(on, array, keys, declare, has, unload, win){

	// module:
	//		dijit/_OnDijitClickMixin
	// summary:
	//		Mixin so you can pass "ondijitclick" to this.connect() method,
	//		as a way to handle clicks by mouse, or by keyboard (SPACE/ENTER key).
	// description:
	//		Setting an ondijitclick handler on a node has two effects:
	//			1. converts keyboard "click" events into actual click events, so
	//			   that a "click" event bubbles up from the widget to any listeners on ancestor nodes.
	//			2. sets up a click listener on the node, which catches native click events plus
	//			   the events generated from the previous step.

	// Keep track of where the last keydown event was, to help avoid generating
	// spurious ondijitclick events when:
	// 1. focus is on a <button> or
	// 2. user presses then releases the ENTER key
	// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
	// 4. onkeyup event fires, causing the ondijitclick handler to fire
	var lastKeyDownNode = null;
	if(has("dom-addeventlistener")){
		win.doc.addEventListener('keydown', function(evt){
			lastKeyDownNode = evt.target;
		}, true);
	}else{
		// Fallback path for IE6-8
		(function(){
			var keydownCallback = function(evt){
				lastKeyDownNode = evt.srcElement;
			};
			win.doc.attachEvent('onkeydown', keydownCallback);
			unload.addOnWindowUnload(function(){
				win.doc.detachEvent('onkeydown', keydownCallback);
			});
		})();
	}

	function clickKey(/*Event*/ e){
		return (e.keyCode === keys.ENTER || e.keyCode === keys.SPACE) &&
				!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
	}

	// Custom a11yclick (a.k.a. ondijitclick) event
	var a11yclick = function(node, listener){
		if(/input|button/i.test(node.nodeName)){
			// pass through, the browser already generates click event on SPACE/ENTER key
			return on(node, "click", listener);
		}else{
			// Don't fire the click event unless both the keydown and keyup occur on this node.
			// Avoids problems where focus shifted to this node or away from the node on keydown,
			// either causing this node to process a stray keyup event, or causing another node
			// to get a stray keyup event.

			var handles = [
				on(node, "keypress", function(e){
					//console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
					if(clickKey(e)){
						// needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
						lastKeyDownNode = e.target;

						// Prevent viewport scrolling on space key in IE<9.
						// (Reproducible on test_Button.html on any of the first dijit.form.Button examples)
						// Do this onkeypress rather than onkeydown because onkeydown.preventDefault() will
						// suppress the onkeypress event, breaking _HasDropDown
						e.preventDefault();
					}
				}),

				on(node, "keyup", function(e){
					//console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
					if(clickKey(e) && e.target == lastKeyDownNode){	// === breaks greasemonkey
						//need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
						lastKeyDownNode = null;
						on.emit(e.target, "click", {
							cancelable: true,
							bubbles: true
						});
					}
				}),

				on(node, "click", function(e){
					// catch mouse clicks, plus the on.emit() calls from above and below
					listener.call(this, e);
				})
			];

			if(has("touch")){
				handles.push(
					on(node, "touchend", function(e){
						// touchstart-->touchend will automatically generate a click event, but there are problems
						// on iOS after focus has been programatically shifted (#14604, #14918), so do it manually.
						e.preventDefault();
						on.emit(e.target, "click", {
							cancelable: true,
							bubbles: true
						});
					})
				);
			}

			return {
				remove: function(){
					array.forEach(handles, function(h){ h.remove(); });
				}
			};
		}
	};

	var ret = declare("dijit._OnDijitClickMixin", null, {
		connect: function(
				/*Object|null*/ obj,
				/*String|Function*/ event,
				/*String|Function*/ method){
			// summary:
			//		Connects specified obj/event to specified method of this object
			//		and registers for disconnect() on widget destroy.
			// description:
			//		Provide widget-specific analog to connect.connect, except with the
			//		implicit use of this widget as the target object.
			//		This version of connect also provides a special "ondijitclick"
			//		event which triggers on a click or space or enter keyup.
			//		Events connected with `this.connect` are disconnected upon
			//		destruction.
			// returns:
			//		A handle that can be passed to `disconnect` in order to disconnect before
			//		the widget is destroyed.
			// example:
			//	|	var btn = new dijit.form.Button();
			//	|	// when foo.bar() is called, call the listener we're going to
			//	|	// provide in the scope of btn
			//	|	btn.connect(foo, "bar", function(){
			//	|		console.debug(this.toString());
			//	|	});
			// tags:
			//		protected

			return this.inherited(arguments, [obj, event == "ondijitclick" ? a11yclick : event, method]);
		}
	});

	ret.a11yclick = a11yclick;

	return ret;
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the MIT license
 * If a copy of the MIT license was not distributed with this file, you can
 * obtain one at http://www.mozillapopcorn.org/butter-license.txt */

define( [ "util/lang", "text!layouts/controls.html" ],
  function( LangUtils, CONTROLS_LAYOUT ) {

  return function( container, p ) {

    var _controls = LangUtils.domFragment( CONTROLS_LAYOUT ).querySelector( "#butter-controls" ),
        _container = typeof container === "string" ? document.getElementById( container ) : container,
        // variables
        muteButton, playButton, currentTimeDialog, fullscreenButton,
        durationDialog, timebar, progressBar, bigPlayButton,
        scrubber, seeking, playStateCache, active,
        volume, volumeProgressBar, volumeScrubber, position,
        // functions
        bigPlayClicked, activate, deactivate, volumechange,
        togglePlay, timeMouseMove, timeMouseUp,
        timeMouseDown, volumeMouseMove, volumeMouseUp,
        volumeMouseDown, durationchange, mutechange;

    var ready = function() {

      _container.appendChild( _controls );

      p.media.removeEventListener( "loadedmetadata", ready, false );

      muteButton = document.getElementById( "controls-mute" );
      playButton = document.getElementById( "controls-play" );
      currentTimeDialog = document.getElementById( "controls-currenttime" );
      durationDialog = document.getElementById( "controls-duration" );
      timebar = document.getElementById( "controls-timebar" );
      progressBar = document.getElementById( "controls-progressbar" );
      bigPlayButton = document.getElementById( "controls-big-play-button" );
      scrubber = document.getElementById( "controls-scrubber" );
      volume = document.getElementById( "controls-volume" );
      fullscreenButton = document.getElementById( "controls-fullscreen" );
      volumeProgressBar = document.getElementById( "controls-volume-progressbar" );
      volumeScrubber = document.getElementById( "controls-volume-scrubber" );
      seeking = false;
      playStateCache = false;
      active = false;

      p.controls( false );

      function requestFullscreen( elem ) {
        // Prefix + case differences.
        if ( elem.requestFullscreen ) {
          elem.requestFullscreen();
        } else if ( elem.mozRequestFullscreen ) {
          elem.mozRequestFullscreen();
        } else if ( elem.mozRequestFullScreen ) {
          elem.mozRequestFullScreen();
        } else if ( elem.webkitRequestFullscreen ) {
          elem.webkitRequestFullscreen();
        }
      }

      function checkFullscreen() {
        if ( document.isFullScreen || document.mozIsFullScreen || document.webkitIsFullScreen ) {
          return true;
        }
        return false;
      }

      function cancelFullscreen() {
        // Prefix + case differences.
        if ( document.exitFullScreen ) {
          document.exitFullScreen();
        } else if ( document.mozCancelFullScreen ) {
          document.mozCancelFullScreen();
        } else if ( document.webkitCancelFullScreen ) {
          document.webkitCancelFullScreen();
        }
      }

      if ( bigPlayButton ) {

        bigPlayButton.classList.add( "controls-ready" );

        bigPlayClicked = function() {

          p.media.removeEventListener( "play", bigPlayClicked, false );
          bigPlayButton.removeEventListener( "mouseup", bigPlayClicked, false );
          bigPlayButton.classList.remove( "controls-ready" );
          p.media.addEventListener( "mouseover", activate, false );
          if ( p.paused ) {
            p.play();
          }
        };

        bigPlayButton.addEventListener( "mouseup", bigPlayClicked, false );
        p.media.addEventListener( "play", bigPlayClicked, false );
      }

      _controls.classList.add( "controls-ready" );

      activate = function() {

        active = true;
        _controls.classList.add( "controls-active" );
      };

      deactivate = function() {

        active = false;
        if ( !seeking ) {
          _controls.classList.remove( "controls-active" );
        }
      };

      p.media.addEventListener( "mouseout", deactivate, false );
      _controls.addEventListener( "mouseover", activate, false );
      _controls.addEventListener( "mouseout", deactivate, false );

      togglePlay = function( e ) {

        if ( e.button !== 0 ) {

          return;
        }

        if ( p.paused() ) {

          p.play();
        } else {

          p.pause();
        }
      };

      p.media.addEventListener( "mouseup", togglePlay, false );

      if ( playButton ) {

        playButton.addEventListener( "mouseup", togglePlay, false );

        p.on( "play", function() {

          playButton.classList.remove( "controls-paused" );
          playButton.classList.add( "controls-playing" );
        });
        p.on( "pause", function() {

          playButton.classList.remove( "controls-playing" );
          playButton.classList.add( "controls-paused" );
        });
      }

      if ( muteButton ) {

        muteButton.addEventListener( "mouseup", function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          p[ p.muted() ? "unmute" : "mute" ]();
        }, false );

        mutechange = function() {

          if ( p.muted() ) {

            muteButton.classList.remove( "controls-unmuted" );
            muteButton.classList.add( "controls-muted" );
          } else {

            muteButton.classList.remove( "controls-muted" );
            muteButton.classList.add( "controls-unmuted" );
          }
        };

        p.on( "volumechange", mutechange );
        mutechange();
      }

      if ( volume ) {

        volumeMouseMove = function( e ) {

          e.preventDefault();

          position = e.clientX - volume.getBoundingClientRect().left;

          if ( position <= 0 ) {

            p.mute();
            position = 0;
          } else if ( position > volume.offsetWidth ) {

            position = volume.offsetWidth;
          } else if ( p.muted() ) {

            p.unmute();
          }

          if ( volumeProgressBar ) {

            volumeProgressBar.style.width = ( position / volume.offsetWidth * 100 ) + "%";
          }

          if ( volumeScrubber ) {

            volumeScrubber.style.left = ( ( position - ( volumeScrubber.offsetWidth / 2 ) ) / volume.offsetWidth * 100 ) + "%";
          }

          p.volume( position / volume.offsetWidth );
        };

        if ( fullscreenButton ) {
          p.on( "butter-fullscreen-allowed", function( e ) {
            fullscreenButton.addEventListener( "click", function() {
              if ( checkFullscreen() ) {
                cancelFullscreen();
              } else {
                requestFullscreen( e );
              }
            }, false);
          });
        }

        volumeMouseUp = function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          e.preventDefault();
          window.removeEventListener( "mouseup", volumeMouseUp, false );
          window.removeEventListener( "mousemove", volumeMouseMove, false );
        };

        volumeMouseDown = function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          position = e.clientX - volume.getBoundingClientRect().left;

          e.preventDefault();
          window.addEventListener( "mouseup", volumeMouseUp, false );
          window.addEventListener( "mousemove", volumeMouseMove, false );

          if ( position === 0 ) {

            p.mute();
          } else if ( p.muted() ) {

            p.unmute();
          }

          if ( volumeProgressBar ) {

            volumeProgressBar.style.width = ( position / volume.offsetWidth * 100 ) + "%";
          }

          if ( volumeScrubber ) {

            volumeScrubber.style.left = ( ( position - ( volumeScrubber.offsetWidth / 2 ) ) / volume.offsetWidth * 100 ) + "%";
          }

          p.volume( position / volume.offsetWidth );
        };

        volume.addEventListener( "mousedown", volumeMouseDown, false );

        volumechange = function() {

          var width;

          if ( p.muted() ) {

            width = 0;
          } else {

            width = p.volume();
          }

          if ( width === 0 ) {

            if ( muteButton ) {

              muteButton.classList.remove( "controls-unmuted" );
              muteButton.classList.add( "controls-muted" );
            }
          }

          if ( volumeProgressBar ) {

            volumeProgressBar.style.width = ( width * 100 ) + "%";
          }

          if ( volumeScrubber ) {

            volumeScrubber.style.left = ( ( width - ( volumeScrubber.offsetWidth / 2 ) ) * 100 ) + "%";
          }
        };

        p.on( "volumechange", volumechange );

        // fire to get and set initially volume slider position
        volumechange();
      }

      if ( durationDialog ) {

        durationchange = function() {

          var timeStamp = new Date( 1970, 0, 1 ),
              time = p.duration(),
              seconds;

          timeStamp.setSeconds( Math.round( time ) );
          seconds = timeStamp.toTimeString().substr( 0, 8 );

          durationDialog.innerHTML = seconds;
        };

        durationchange();
      }

      if ( timebar ) {

        timeMouseMove = function( e ) {

          e.preventDefault();

          position = e.clientX - timebar.getBoundingClientRect().left;

          if ( position < 0 ) {

            position = 0;
          } else if ( position > timebar.offsetWidth ) {

            position = timebar.offsetWidth;
          }

          if ( progressBar ) {

            progressBar.style.width = ( position / timebar.offsetWidth * 100 ) + "%";
          }

          if ( scrubber ) {

            scrubber.style.left = ( ( position - ( scrubber.offsetWidth / 2 ) ) / timebar.offsetWidth * 100 ) + "%";
          }

          p.currentTime( position / timebar.offsetWidth * 100 * p.duration() / 100 );
        };

        timeMouseUp = function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          e.preventDefault();
          seeking = false;
          if ( !active ) {
            deactivate();
          }
          if ( playStateCache ) {
            p.play();
          }
          window.removeEventListener( "mouseup", timeMouseUp, false );
          window.removeEventListener( "mousemove", timeMouseMove, false );
        };

        timeMouseDown = function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          position = e.clientX - timebar.getBoundingClientRect().left;

          e.preventDefault();
          seeking = true;
          playStateCache = !p.paused();
          p.pause();
          window.addEventListener( "mouseup", timeMouseUp, false );
          window.addEventListener( "mousemove", timeMouseMove, false );

          if ( progressBar ) {

            progressBar.style.width = ( position / timebar.offsetWidth * 100 ) + "%";
          }

          if ( scrubber ) {

            scrubber.style.left = ( ( position - ( scrubber.offsetWidth / 2 ) ) / timebar.offsetWidth * 100 ) + "%";
          }

          p.currentTime( position / timebar.offsetWidth * 100 * p.duration() / 100 );
        };

        timebar.addEventListener( "mousedown", timeMouseDown, false );

        p.on( "timeupdate", function() {

          var timeStamp = new Date( 1970, 0, 1 ),
              time = p.currentTime(),
              seconds,
              width = ( time / p.duration() * 100 * timebar.offsetWidth / 100 );

          if ( progressBar ) {

            progressBar.style.width = ( width / timebar.offsetWidth * 100 ) + "%";
          }

          if ( scrubber ) {

            scrubber.style.left = ( ( width - ( scrubber.offsetWidth / 2 ) ) / timebar.offsetWidth * 100 ) + "%";
          }

          timeStamp.setSeconds( Math.round( time ) );
          seconds = timeStamp.toTimeString().substr( 0, 8 );

          if ( currentTimeDialog ) {

            currentTimeDialog.innerHTML = seconds;
          }
        });
      }
    };

    if ( !_container ) {

      return;
    }

    if ( p.readyState() >= 1 ) {

      ready();
    } else {

      p.media.addEventListener( "loadedmetadata", ready, false );
    }

    return _container;
  }; //controls
});
<commit_msg>[#2241] Exported SoundCloud projects play twice<commit_after>/* This Source Code Form is subject to the terms of the MIT license
 * If a copy of the MIT license was not distributed with this file, you can
 * obtain one at http://www.mozillapopcorn.org/butter-license.txt */

define( [ "util/lang", "text!layouts/controls.html" ],
  function( LangUtils, CONTROLS_LAYOUT ) {

  return function( container, p ) {

    var _controls = LangUtils.domFragment( CONTROLS_LAYOUT ).querySelector( "#butter-controls" ),
        _container = typeof container === "string" ? document.getElementById( container ) : container,
        // variables
        muteButton, playButton, currentTimeDialog, fullscreenButton,
        durationDialog, timebar, progressBar, bigPlayButton,
        scrubber, seeking, playStateCache, active,
        volume, volumeProgressBar, volumeScrubber, position,
        // functions
        bigPlayClicked, activate, deactivate, volumechange,
        togglePlay, timeMouseMove, timeMouseUp,
        timeMouseDown, volumeMouseMove, volumeMouseUp,
        volumeMouseDown, durationchange, mutechange;

    var ready = function() {

      _container.appendChild( _controls );

      p.media.removeEventListener( "loadedmetadata", ready, false );

      muteButton = document.getElementById( "controls-mute" );
      playButton = document.getElementById( "controls-play" );
      currentTimeDialog = document.getElementById( "controls-currenttime" );
      durationDialog = document.getElementById( "controls-duration" );
      timebar = document.getElementById( "controls-timebar" );
      progressBar = document.getElementById( "controls-progressbar" );
      bigPlayButton = document.getElementById( "controls-big-play-button" );
      scrubber = document.getElementById( "controls-scrubber" );
      volume = document.getElementById( "controls-volume" );
      fullscreenButton = document.getElementById( "controls-fullscreen" );
      volumeProgressBar = document.getElementById( "controls-volume-progressbar" );
      volumeScrubber = document.getElementById( "controls-volume-scrubber" );
      seeking = false;
      playStateCache = false;
      active = false;

      p.controls( false );

      function requestFullscreen( elem ) {
        // Prefix + case differences.
        if ( elem.requestFullscreen ) {
          elem.requestFullscreen();
        } else if ( elem.mozRequestFullscreen ) {
          elem.mozRequestFullscreen();
        } else if ( elem.mozRequestFullScreen ) {
          elem.mozRequestFullScreen();
        } else if ( elem.webkitRequestFullscreen ) {
          elem.webkitRequestFullscreen();
        }
      }

      function checkFullscreen() {
        if ( document.isFullScreen || document.mozIsFullScreen || document.webkitIsFullScreen ) {
          return true;
        }
        return false;
      }

      function cancelFullscreen() {
        // Prefix + case differences.
        if ( document.exitFullScreen ) {
          document.exitFullScreen();
        } else if ( document.mozCancelFullScreen ) {
          document.mozCancelFullScreen();
        } else if ( document.webkitCancelFullScreen ) {
          document.webkitCancelFullScreen();
        }
      }

      if ( bigPlayButton ) {

        bigPlayButton.classList.add( "controls-ready" );

        bigPlayClicked = function() {

          p.media.removeEventListener( "play", bigPlayClicked, false );
          bigPlayButton.removeEventListener( "mouseup", bigPlayClicked, false );
          bigPlayButton.classList.remove( "controls-ready" );
          p.media.addEventListener( "mouseover", activate, false );
          if ( p.paused() ) {
            p.play();
          }
        };

        bigPlayButton.addEventListener( "mouseup", bigPlayClicked, false );
        p.media.addEventListener( "play", bigPlayClicked, false );
      }

      _controls.classList.add( "controls-ready" );

      activate = function() {

        active = true;
        _controls.classList.add( "controls-active" );
      };

      deactivate = function() {

        active = false;
        if ( !seeking ) {
          _controls.classList.remove( "controls-active" );
        }
      };

      p.media.addEventListener( "mouseout", deactivate, false );
      _controls.addEventListener( "mouseover", activate, false );
      _controls.addEventListener( "mouseout", deactivate, false );

      togglePlay = function( e ) {

        if ( e.button !== 0 ) {

          return;
        }

        if ( p.paused() ) {

          p.play();
        } else {

          p.pause();
        }
      };

      p.media.addEventListener( "mouseup", togglePlay, false );

      if ( playButton ) {

        playButton.addEventListener( "mouseup", togglePlay, false );

        p.on( "play", function() {

          playButton.classList.remove( "controls-paused" );
          playButton.classList.add( "controls-playing" );
        });
        p.on( "pause", function() {

          playButton.classList.remove( "controls-playing" );
          playButton.classList.add( "controls-paused" );
        });
      }

      if ( muteButton ) {

        muteButton.addEventListener( "mouseup", function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          p[ p.muted() ? "unmute" : "mute" ]();
        }, false );

        mutechange = function() {

          if ( p.muted() ) {

            muteButton.classList.remove( "controls-unmuted" );
            muteButton.classList.add( "controls-muted" );
          } else {

            muteButton.classList.remove( "controls-muted" );
            muteButton.classList.add( "controls-unmuted" );
          }
        };

        p.on( "volumechange", mutechange );
        mutechange();
      }

      if ( volume ) {

        volumeMouseMove = function( e ) {

          e.preventDefault();

          position = e.clientX - volume.getBoundingClientRect().left;

          if ( position <= 0 ) {

            p.mute();
            position = 0;
          } else if ( position > volume.offsetWidth ) {

            position = volume.offsetWidth;
          } else if ( p.muted() ) {

            p.unmute();
          }

          if ( volumeProgressBar ) {

            volumeProgressBar.style.width = ( position / volume.offsetWidth * 100 ) + "%";
          }

          if ( volumeScrubber ) {

            volumeScrubber.style.left = ( ( position - ( volumeScrubber.offsetWidth / 2 ) ) / volume.offsetWidth * 100 ) + "%";
          }

          p.volume( position / volume.offsetWidth );
        };

        if ( fullscreenButton ) {
          p.on( "butter-fullscreen-allowed", function( e ) {
            fullscreenButton.addEventListener( "click", function() {
              if ( checkFullscreen() ) {
                cancelFullscreen();
              } else {
                requestFullscreen( e );
              }
            }, false);
          });
        }

        volumeMouseUp = function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          e.preventDefault();
          window.removeEventListener( "mouseup", volumeMouseUp, false );
          window.removeEventListener( "mousemove", volumeMouseMove, false );
        };

        volumeMouseDown = function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          position = e.clientX - volume.getBoundingClientRect().left;

          e.preventDefault();
          window.addEventListener( "mouseup", volumeMouseUp, false );
          window.addEventListener( "mousemove", volumeMouseMove, false );

          if ( position === 0 ) {

            p.mute();
          } else if ( p.muted() ) {

            p.unmute();
          }

          if ( volumeProgressBar ) {

            volumeProgressBar.style.width = ( position / volume.offsetWidth * 100 ) + "%";
          }

          if ( volumeScrubber ) {

            volumeScrubber.style.left = ( ( position - ( volumeScrubber.offsetWidth / 2 ) ) / volume.offsetWidth * 100 ) + "%";
          }

          p.volume( position / volume.offsetWidth );
        };

        volume.addEventListener( "mousedown", volumeMouseDown, false );

        volumechange = function() {

          var width;

          if ( p.muted() ) {

            width = 0;
          } else {

            width = p.volume();
          }

          if ( width === 0 ) {

            if ( muteButton ) {

              muteButton.classList.remove( "controls-unmuted" );
              muteButton.classList.add( "controls-muted" );
            }
          }

          if ( volumeProgressBar ) {

            volumeProgressBar.style.width = ( width * 100 ) + "%";
          }

          if ( volumeScrubber ) {

            volumeScrubber.style.left = ( ( width - ( volumeScrubber.offsetWidth / 2 ) ) * 100 ) + "%";
          }
        };

        p.on( "volumechange", volumechange );

        // fire to get and set initially volume slider position
        volumechange();
      }

      if ( durationDialog ) {

        durationchange = function() {

          var timeStamp = new Date( 1970, 0, 1 ),
              time = p.duration(),
              seconds;

          timeStamp.setSeconds( Math.round( time ) );
          seconds = timeStamp.toTimeString().substr( 0, 8 );

          durationDialog.innerHTML = seconds;
        };

        durationchange();
      }

      if ( timebar ) {

        timeMouseMove = function( e ) {

          e.preventDefault();

          position = e.clientX - timebar.getBoundingClientRect().left;

          if ( position < 0 ) {

            position = 0;
          } else if ( position > timebar.offsetWidth ) {

            position = timebar.offsetWidth;
          }

          if ( progressBar ) {

            progressBar.style.width = ( position / timebar.offsetWidth * 100 ) + "%";
          }

          if ( scrubber ) {

            scrubber.style.left = ( ( position - ( scrubber.offsetWidth / 2 ) ) / timebar.offsetWidth * 100 ) + "%";
          }

          p.currentTime( position / timebar.offsetWidth * 100 * p.duration() / 100 );
        };

        timeMouseUp = function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          e.preventDefault();
          seeking = false;
          if ( !active ) {
            deactivate();
          }
          if ( playStateCache ) {
            p.play();
          }
          window.removeEventListener( "mouseup", timeMouseUp, false );
          window.removeEventListener( "mousemove", timeMouseMove, false );
        };

        timeMouseDown = function( e ) {

          if ( e.button !== 0 ) {

            return;
          }

          position = e.clientX - timebar.getBoundingClientRect().left;

          e.preventDefault();
          seeking = true;
          playStateCache = !p.paused();
          p.pause();
          window.addEventListener( "mouseup", timeMouseUp, false );
          window.addEventListener( "mousemove", timeMouseMove, false );

          if ( progressBar ) {

            progressBar.style.width = ( position / timebar.offsetWidth * 100 ) + "%";
          }

          if ( scrubber ) {

            scrubber.style.left = ( ( position - ( scrubber.offsetWidth / 2 ) ) / timebar.offsetWidth * 100 ) + "%";
          }

          p.currentTime( position / timebar.offsetWidth * 100 * p.duration() / 100 );
        };

        timebar.addEventListener( "mousedown", timeMouseDown, false );

        p.on( "timeupdate", function() {

          var timeStamp = new Date( 1970, 0, 1 ),
              time = p.currentTime(),
              seconds,
              width = ( time / p.duration() * 100 * timebar.offsetWidth / 100 );

          if ( progressBar ) {

            progressBar.style.width = ( width / timebar.offsetWidth * 100 ) + "%";
          }

          if ( scrubber ) {

            scrubber.style.left = ( ( width - ( scrubber.offsetWidth / 2 ) ) / timebar.offsetWidth * 100 ) + "%";
          }

          timeStamp.setSeconds( Math.round( time ) );
          seconds = timeStamp.toTimeString().substr( 0, 8 );

          if ( currentTimeDialog ) {

            currentTimeDialog.innerHTML = seconds;
          }
        });
      }
    };

    if ( !_container ) {

      return;
    }

    if ( p.readyState() >= 1 ) {

      ready();
    } else {

      p.media.addEventListener( "loadedmetadata", ready, false );
    }

    return _container;
  }; //controls
});
<|endoftext|>