<commit_before>var content = (function() {
    'use strict';

    function isScriptNode(node) {
        if (node.nodeType !== Node.ELEMENT_NODE) {
            return false;
        }
        if (node.nodeName === "SCRIPT") {
            return true;
        }
        return false;
    }

    function isWhiteSpaceOnly(node) {
        if (node.nodeValue.replace(/[\n\t ]+/, "") === "") {
            return true;
        }
        return false;
    }

    function getAllTextNodes(elem) {
        var filter = NodeFilter.SHOW_TEXT,
            walker = document.createTreeWalker(elem, filter, null, false),
            arr = [],
            node;
        while (walker.nextNode()) {
            node = walker.currentNode;
            if (node.parentNode && isScriptNode(node.parentNode)) {
                continue;
            }
            if (node.isElementContentWhitespace || isWhiteSpaceOnly(node)) {
                continue;
            }
            arr.push(node);
        }
        return arr;
    }

    function makeTextOrSpanNode(data, highlight) {
        if (!data.altered || !highlight) {
            return document.createTextNode(data.text);
        }
        var span = document.createElement('span');
        span.setAttribute("style", "background-color: yellow;");
        span.appendChild(document.createTextNode(data.text));
        return span;
    }

    function replaceTextNodes(origNodes, newData, highlight) {
        newData.forEach(function(result) {
            var origNode = origNodes[result.origNode],
                parentNode = origNode.parentNode;
            result.replacement.forEach(function(repl) {
                var newNode = makeTextOrSpanNode(repl, highlight);
                parentNode.insertBefore(newNode, origNode);
            });
            parentNode.removeChild(origNode);
        });
    }

    function prepareJson(nodes) {
        var ret = [],
            i = 0;
        for (i = 0; i < nodes.length; i += 1) {
            ret.push({
                index: i,
                text: nodes[i].nodeValue
            });
        }
        return ret;
    }

    chrome.runtime.onMessage.addListener(function(rq, sender, sendResponse) {
        if (rq.method && (rq.method === "convert-to-si")) {
            var textNodes = getAllTextNodes(document.body);
            chrome.runtime.sendMessage({
                method: "text-for-processing",
                data: prepareJson(textNodes)
            }, function(response) {
                console.log("response: " + JSON.stringify(response, null, 4));
                replaceTextNodes(textNodes, response, rq.highlight);
                sendResponse({"text": "ok"});
            });
        }
    });

    return {
        isWhiteSpaceOnly: isWhiteSpaceOnly,
        getAllTextNodes: getAllTextNodes
    };
}());
<commit_msg>Rename and export a function from content.js<commit_after>var content = (function() {
    'use strict';

    function isScriptNode(node) {
        if (node.nodeType !== Node.ELEMENT_NODE) {
            return false;
        }
        if (node.nodeName === "SCRIPT") {
            return true;
        }
        return false;
    }

    function isWhiteSpaceOnly(node) {
        if (node.nodeValue.replace(/[\n\t ]+/, "") === "") {
            return true;
        }
        return false;
    }

    function getAllTextNodes(elem) {
        var filter = NodeFilter.SHOW_TEXT,
            walker = document.createTreeWalker(elem, filter, null, false),
            arr = [],
            node;
        while (walker.nextNode()) {
            node = walker.currentNode;
            if (node.parentNode && isScriptNode(node.parentNode)) {
                continue;
            }
            if (node.isElementContentWhitespace || isWhiteSpaceOnly(node)) {
                continue;
            }
            arr.push(node);
        }
        return arr;
    }

    function makeTextOrSpanNode(data, highlight) {
        if (!data.altered || !highlight) {
            return document.createTextNode(data.text);
        }
        var span = document.createElement('span');
        span.setAttribute("style", "background-color: yellow;");
        span.appendChild(document.createTextNode(data.text));
        return span;
    }

    function replaceTextNodes(origNodes, newData, highlight) {
        newData.forEach(function(result) {
            var origNode = origNodes[result.origNode],
                parentNode = origNode.parentNode;
            result.replacement.forEach(function(repl) {
                var newNode = makeTextOrSpanNode(repl, highlight);
                parentNode.insertBefore(newNode, origNode);
            });
            parentNode.removeChild(origNode);
        });
    }

    function nodesToIndexedArray(nodes) {
        var ret = [],
            i = 0;
        for (i = 0; i < nodes.length; i += 1) {
            ret.push({
                index: i,
                text: nodes[i].nodeValue
            });
        }
        return ret;
    }

    chrome.runtime.onMessage.addListener(function(rq, sender, sendResponse) {
        if (rq.method && (rq.method === "convert-to-si")) {
            var textNodes = getAllTextNodes(document.body);
            chrome.runtime.sendMessage({
                method: "text-for-processing",
                data: nodesToIndexedArray(textNodes)
            }, function(response) {
                console.log("response: " + JSON.stringify(response, null, 4));
                replaceTextNodes(textNodes, response, rq.highlight);
                sendResponse({"text": "ok"});
            });
        }
    });

    return {
        isWhiteSpaceOnly: isWhiteSpaceOnly,
        getAllTextNodes: getAllTextNodes,
        nodesToIndexedArray: nodesToIndexedArray
    };
}());
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive;
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var isNegativeZero = require( '@stdlib/math/base/assert/is-negative-zero' );
var PINF = require( '@stdlib/constants/math/float64-pinf' );

// MAIN //

/**
* Returns an accumulator function which incrementally computes a moving minimum value.
*
* @param {PositiveInteger} W - window size
* @throws {TypeError} must provide a positive integer
* @returns {Function} accumulator function
*
* @example
* var accumulator = incrmmin( 3 );
*/
function incrmmin( W ) {
	var buf;
	var min;
	var N;
	var i;
	if ( !isPositiveInteger( W ) ) {
		throw new TypeError( 'invalid input argument. Must provide a positive integer. Value: `' + W + '`.' );
	}
	buf = new Array( W );
	min = PINF;
	i = -1;
	N = 0;

	return accumulator;

	/**
	* If provided a value, the accumulator function returns an updated minimum. If not provided a value, the accumulator function returns the current minimum.
	*
	* @private
	* @param {number} [x] - input value
	* @returns {(number|null)} minimum value or null
	*
	* @example
	* var m = accumulator();
	* // returns null
	*
	* m = accumulator( 2.0 );
	* // returns 2.0
	*
	* m = accumulator( -5.0 );
	* // returns -5.0
	*
	* m = accumulator( 3.0 );
	* // returns -5.0
	*
	* m = accumulator( 5.0 );
	* // returns -5.0
	*
	* m = accumulator();
	* // returns -5.0
	*/
	function accumulator( x ) {
		var v;
		var k;
		if ( arguments.length === 0 ) {
			if ( N === 0 ) {
				return null;
			}
			return min;
		}
		// Update the index for managing the circular buffer:
		i = (i+1) % W;

		// Case: update initial window...
		if ( N < W ) {
			N += 1;
			if (
				isnan( x ) ||
				x < min ||
				( x === min && isNegativeZero( x ) )
			) {
				min = x;
			}
		}
		// Case: incoming value is NaN or less than current minimum value...
		else if ( isnan( x ) || x < min ) {
			min = x;
		}
		// Case: outgoing value is the current minimum and the new value is greater than the minimum, and, thus, we need to find a new minimum among the current values...
		else if ( ( buf[ i ] === min && x > min ) || isnan( buf[ i ] ) ) {
			min = x;
			for ( k = 0; k < W; k++ ) {
				if ( k !== i ) {
					v = buf[ k ];
					if ( isnan( v ) ) {
						min = v;
						break; // no need to continue searching
					}
					if ( v < min || ( v === min && isNegativeZero( v ) ) ) {
						min = v;
					}
				}
			}
		}
		// Case: outgoing value is the current minimum, which is zero, and the new value is also zero, and, thus, we need to correctly handle signed zeros...
		else if ( buf[ i ] === min && x === min && x === 0.0 ) {
			if ( isNegativeZero( x ) ) {
				min = x;
			} else if ( isNegativeZero( buf[ i ] ) ) {
				// Because the outgoing and incoming are different signs (-,+), we need to search the buffer to see if it contains a negative zero. If so, it becomes the new minimum value; otherwise, the minimum value is incoming value...
				min = x;
				for ( k = 0; k < W; k++ ) {
					if ( k !== i && isNegativeZero( buf[ k ] ) ) {
						min = buf[ k ];
						break;
					}
				}
			}
			// Case: both the outgoing and incoming are both negative zero, so nothing changes
		}
		// Case: updating existing window; however, the minimum value does not change so nothing to do but update our buffer...

		buf[ i ] = x;
		return min;
	}
}

// EXPORTS //

module.exports = incrmmin;
<commit_msg>Fix comment<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive;
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var isNegativeZero = require( '@stdlib/math/base/assert/is-negative-zero' );
var PINF = require( '@stdlib/constants/math/float64-pinf' );

// MAIN //

/**
* Returns an accumulator function which incrementally computes a moving minimum value.
*
* @param {PositiveInteger} W - window size
* @throws {TypeError} must provide a positive integer
* @returns {Function} accumulator function
*
* @example
* var accumulator = incrmmin( 3 );
*/
function incrmmin( W ) {
	var buf;
	var min;
	var N;
	var i;
	if ( !isPositiveInteger( W ) ) {
		throw new TypeError( 'invalid input argument. Must provide a positive integer. Value: `' + W + '`.' );
	}
	buf = new Array( W );
	min = PINF;
	i = -1;
	N = 0;

	return accumulator;

	/**
	* If provided a value, the accumulator function returns an updated minimum. If not provided a value, the accumulator function returns the current minimum.
	*
	* @private
	* @param {number} [x] - input value
	* @returns {(number|null)} minimum value or null
	*
	* @example
	* var m = accumulator();
	* // returns null
	*
	* m = accumulator( 2.0 );
	* // returns 2.0
	*
	* m = accumulator( -5.0 );
	* // returns -5.0
	*
	* m = accumulator( 3.0 );
	* // returns -5.0
	*
	* m = accumulator( 5.0 );
	* // returns -5.0
	*
	* m = accumulator();
	* // returns -5.0
	*/
	function accumulator( x ) {
		var v;
		var k;
		if ( arguments.length === 0 ) {
			if ( N === 0 ) {
				return null;
			}
			return min;
		}
		// Update the index for managing the circular buffer:
		i = (i+1) % W;

		// Case: update initial window...
		if ( N < W ) {
			N += 1;
			if (
				isnan( x ) ||
				x < min ||
				( x === min && isNegativeZero( x ) )
			) {
				min = x;
			}
		}
		// Case: incoming value is NaN or less than current minimum value...
		else if ( isnan( x ) || x < min ) {
			min = x;
		}
		// Case: outgoing value is the current minimum and the new value is greater than the minimum, and, thus, we need to find a new minimum among the current values...
		else if ( ( buf[ i ] === min && x > min ) || isnan( buf[ i ] ) ) {
			min = x;
			for ( k = 0; k < W; k++ ) {
				if ( k !== i ) {
					v = buf[ k ];
					if ( isnan( v ) ) {
						min = v;
						break; // no need to continue searching
					}
					if ( v < min || ( v === min && isNegativeZero( v ) ) ) {
						min = v;
					}
				}
			}
		}
		// Case: outgoing value is the current minimum, which is zero, and the new value is also zero, and, thus, we need to correctly handle signed zeros...
		else if ( buf[ i ] === min && x === min && x === 0.0 ) {
			if ( isNegativeZero( x ) ) {
				min = x;
			} else if ( isNegativeZero( buf[ i ] ) ) {
				// Because the outgoing and incoming are different signs (-,+), we need to search the buffer to see if it contains a negative zero. If so, it becomes the new minimum value; otherwise, the minimum value is incoming value...
				min = x;
				for ( k = 0; k < W; k++ ) {
					if ( k !== i && isNegativeZero( buf[ k ] ) ) {
						min = buf[ k ];
						break;
					}
				}
			}
			// Case: both the outgoing and incoming are both positive zero, so nothing changes
		}
		// Case: updating existing window; however, the minimum value does not change so nothing to do but update our buffer...

		buf[ i ] = x;
		return min;
	}
}

// EXPORTS //

module.exports = incrmmin;
<|endoftext|>


<commit_before>$('a').click(function (b) {
	if (b.which == 2) { 	// If middle click detected
		var tab = new Object();
		tab.url = $(this).attr("href");

		chrome.extension.sendRequest(parseUrl(tab));
		return false;		// Stop current page from also loading link
	}
});

function parseUrl(tab) {
	var isAbsoluteUrl = /^\w+:\/\//

	if (!(isAbsoluteUrl.test(tab.url))) {
		if (/^\//.test(tab.url)) {
			// If starts with / then append tohostname
			tab.url = window.location.protocol + "//" + window.location.hostname + tab.url;
		} else if (/.*\/$/.test(window.location.href)) {
			// else if starts with any other character then if ends in / append to current level
			tab.url = window.location.href + "/" + tab.url;
		} else {
			// else lose last bit until / and append to current level
			var str = window.location.href.split("/");
			str.pop();
			tab.url = str.join("/") + "/" + tab.url;
		}
	}

	return tab;
}
<commit_msg>should have finally fixed relative url issues<commit_after>$('a').click(function (b) {
	if (b.which == 2) { 	// If middle click detected
		var tab = new Object();
		tab.url = $(this).attr("href");

		chrome.extension.sendRequest(parseUrl(tab));
		return false;		// Stop current page from also loading link
	}
});

function parseUrl(tab) {
	var isAbsoluteUrl = /^\w+:\/\//

	if (!(isAbsoluteUrl.test(tab.url))) {
		if (/^\//.test(tab.url)) {
			// If starts with / then append tohostname
			tab.url = window.location.protocol + "//" + window.location.hostname + tab.url;
		} else if (/.*\/$/.test(window.location.href)) {
			// else if starts with any other character then if ends in / append to current level
			tab.url = window.location.href + tab.url;
		} else if (/^.*[^/]+\.[^./]+$/.test(window.location.href)) {
			// else if ends in a file, lose last bit until / and append to current level
			var str = window.location.href.split("/");
			str.pop();
			tab.url = str.join("/") + "/" + tab.url;
		} else {
			// else doesn't end in / so append / and relative url
			tab.url = window.location.href + "/" + tab.url;
		}
	}

	return tab;
}
<|endoftext|>


<commit_before>/*===========================================
* * *     I'm Mark Zuckerberg           * * *
=============================================

"I'm Mark Zuckerberg" is a Chrome extension that customises your Facebook
experience by replacing all your friends profile images and names with
Mark Zuckerberg.

Aim:
    Try to replace the existance of all usernames and profile images with
    Mark Zuckerberg within the Facebook DOM, as to give the illusion that
    its just you and Mark there.

TODO:
    *   Update selector lists to be more page specific, as to reduce the
        number of active MutationObservers. This will require editing the
        Mark.zuckify method to filter out un-needed selections also.

    *   Refine selector locations to be more element specific and hunt out
        un-applied profile images and usernames.

    *   Correctly label with comments all the DOM selectors.

    *   Re-write without jQuery dependency.

    *   Maybe Add with multiple images of Mark to spice it up a bit.
*/
"use strict";

$(document).ready(function() {
    var mark = new Mark();
    mark.zuckify();
});

const MarkZuckerberg = "Mark Zuckerberg",
      MarkImage128   = chrome.extension.getURL("mark_xoxo/mark128.png"),
      MarkImage512   = chrome.extension.getURL("mark_xoxo/mark512.png");

function Mark() {
    /* Mark xoxoxoxo */
    this.pageurl = window.location.href;

    this.zuckify = function() {
        replaceUsernames(fbNameLocations);
        replaceImages(fbProfileThumbs, MarkImage128);
        // Are we at the homepage or somewhere else?
        if (!this.pageurl.endsWith(".com/")) {
            replaceImages(fbProfilePicture, MarkImage512);
        }
    }

    // replaceImages : call with a list of selectors and
    // a specific size image to relplace with.
    function replaceImages(locations, image) {
        for (var i = 0; i < locations.length; i++) {
            ready(locations[i], function(element) {
                $(element).attr('src', image);
            });
        }
    }

    // replaceUsernames : Usernames are allways replaced
    // with "Mark Zuckerberg". Call with different selectors
    // depending on page the location.
    function replaceUsernames(locations) {
        for (var i = 0; i < locations.length; i++) {
            ready(locations[i], function(element) {
                $(element).text(MarkZuckerberg);
            });
        }
    }
}

/* Selector locations. *
========================
These are probally going to change pretty often.
*/

// fbNameLocations :
// Locations of text affected by replaceUsernames().
var fbNameLocations = [
    ".fwb.fcg a",                   // Feed post header.
    ".fwb a",                       // Feed post header.
    ".tickerFeedMessage span.fwb",  // top right side feed notifications.
    "a.profileLink",                //
    "a.UFICommentActorName",        // Feed post comments.
    "._55lr",                       // Right sidebar contact.
    "#fb-timeline-cover-name",      //
    ".titlebarText.fixemoji span",  //
    ".author.fixemoji span",        // Blue navigation messages drop-down.
    ".fbRemindersTitle strong",     // reminders like birthdays.
    "div._4l_v span.fwb",           // notifications dropdown.
    "a._zci",                       // name on videos pictures ?
    ".tooltipText span",            // pop ups ?

    // Following items are forming the basis of a seperate list
    // that are not present in the hompage.
    "._33vv a",                      // Page Name.
    "._50f3",                        // friends tiles (profile page).
    "a.nameButton span.uiButtonText",// secondary header.

    // just on homepage
    "a.fwn"                         // when you click on birthday's and a list pops up.
];

// fbProfileThumbs :
// Locations of images affected by replaceUserThumbnails().
var fbProfileThumbs = [
    "img._s0._4ooo._5xib._5sq7._44ma._rw.img",  // Post thumbnail.
    "img._s0._4ooo._5xib._44ma._54ru.img",      // Sponsored link that others liked.
    "._38vo img._s0._4ooo._5xib._44ma.img",     // Shared post.
    "._3b-9 img.UFIActorImage._54ru.img",       // Comments.
    "._55lt img.img",                           //
    "._31o4._3njy img.img",                     //
    "img._s0._4ooo.tickerStoryImage._54ru.img", // Top right notifications.
    ".img._s0._rw.img",                         //

    "img._s0._4ooo._1ve7._3s6v._rv.img"         // Friends list.
];

// fbProfilePicture :
// Locations of images affected by replaceUserProfileImages().
var fbProfilePicture = [
    "img.profilePic.img",               // main profile image normal user.
    "img._4jhq.img",                    // Page profile pic on far left style.
];

/* 'ready' Utility. *
=====================
Written by Ryan Morr.
http://ryanmorr.com/using-mutation-observers-to-watch-for-element-availability/
*/
(function(win) {
    var listeners = [],
        doc = win.document,
        MutationObserver = win.MutationObserver || win.WebKitMutationObserver,
        observer;

    function ready(selector, fn) {
        // Store the selector and callback to be monitored
        listeners.push({
            selector: selector,
            fn: fn
        });
        if (!observer) {
            // Watch for changes in the document
            observer = new MutationObserver(check);
            observer.observe(doc.documentElement, {
                childList: true,
                subtree: true
            });
        }
        // Check if the element is currently in the DOM
        check();
    }
    function check() {
        // Check the DOM for elements matching a stored selector
        for (var i = 0, len = listeners.length, listener, elements; i < len; i++) {
            listener = listeners[i];
            // Query for elements matching the specified selector
            elements = doc.querySelectorAll(listener.selector);
            for (var j = 0, jLen = elements.length, element; j < jLen; j++) {
                element = elements[j];
                // Make sure the callback isn't invoked with the
                // same element more than once
                if (!element.ready) {
                    element.ready = true;
                    // Invoke the callback with the element
                    listener.fn.call(element, element);
                }
            }
        }
    }
    // Expose `ready`
    win.ready = ready;
})(this);

<commit_msg>further DOM Additions<commit_after>/*===========================================
* * *     I'm Mark Zuckerberg           * * *
=============================================

"I'm Mark Zuckerberg" is a Chrome extension that customises your Facebook
experience by replacing all your friends profile images and names with
Mark Zuckerberg.

Aim:
    Try to replace the existance of all usernames and profile images with
    Mark Zuckerberg within the Facebook DOM, as to give the illusion that
    its just you and Mark there.

TODO:
    *   Update selector lists to be more page specific, as to reduce the
        number of active MutationObservers. This will require editing the
        Mark.zuckify method to filter out un-needed selections also.

    *   Refine selector locations to be more element specific and hunt out
        un-applied profile images and usernames.

    *   Correctly label with comments all the DOM selectors.

    *   Re-write without jQuery dependency.

    *   Maybe Add with multiple images of Mark to spice it up a bit.

    * Publish: https://developer.chrome.com/webstore/publish
*/
"use strict";

function main() {
    var mark = new Mark();
    mark.zuckerfy();
}

const MarkZuckerberg = "Mark Zuckerberg",
      MarkImage128   = chrome.extension.getURL("mark_xoxo/mark128.png"),
      MarkImage512   = chrome.extension.getURL("mark_xoxo/mark512.png");

function Mark() {
    /* Mark xoxoxoxo */

    this.zuckerfy = function() {
        // Intialise site-wide observers.
        addUsernameObservers(fbUserNames.allSite);
        addImageObservers(fbUserImg.globalThumbs, MarkImage128);
        // Are we at the homepage or somewhere else?
        if (window.location.pathname == '/') {
            addUsernameObservers(fbUserNames.home);
        } else {
            addUsernameObservers(fbUserNames.page);
            addImageObservers(fbUserImg.pageThumbs, MarkImage128);
            addImageObservers(fbUserImg.mainProfile, MarkImage512);
        }
    }
    // addImageObservers : call with a list of selectors and
    // a specific size image to relplace with.
    function addImageObservers(locations, image) {
        for (var i = 0; i < locations.length; i++) {
            ready(locations[i], function(element) {
                $(element).attr('src', image);
            });
        }
    }
    // addUsernameObservers : Usernames are allways replaced
    // with "Mark Zuckerberg". Call with different selectors
    // depending on page the location.
    function addUsernameObservers(locations) {
        for (var i = 0; i < locations.length; i++) {
            ready(locations[i], function(element) {
                $(element).text(MarkZuckerberg);
                // replaceText(element, MarkZuckerberg);
            });
        }
    }
}

/* Selector locations. *
========================
These are probally going to change pretty often.
*/
var fbUserNames = {
    allSite: [
        ".fwb.fcg a",                   // Feed post header.
        ".fwb a",                       // Feed post header.
        ".tickerFeedMessage span.fwb",  // top right side feed notifications.
        "a.profileLink",                //
        "a.UFICommentActorName",        // Feed post comments.
        "._55lr",                       // Right sidebar contact.
        ".titlebarText.fixemoji span",  //
        ".author.fixemoji span",        // Blue navigation messages drop-down.
        "div._4l_v span.fwb",           // notifications dropdown.
        "a._zci",                       // name on videos pictures ?
        ".tooltipText span"             // pop ups ?
    ],
    home: [
        ".fbRemindersTitle strong",     // reminders like birthdays.
        "a.fwn"                         // when you click on birthday's and a list pops up.
    ],
    page: [
        "#fb-timeline-cover-name",       //
        "._33vv a",                      // Page Name.
        "._50f3",                        // friends tiles (profile page).
        "a.nameButton span.uiButtonText" // secondary header.
    ]
};

var fbUserImg = {
    globalThumbs: [
        "img._s0._4ooo._5xib._5sq7._44ma._rw.img",  // Post thumbnail.
        "img._s0._4ooo._5xib._44ma._54ru.img",      // Sponsored link that others liked.
        "._38vo img._s0._4ooo._5xib._44ma.img",     // Shared post.
        "._3b-9 img.UFIActorImage._54ru.img",       // Comments.
        "._55lt img.img",                           //
        "._31o4._3njy img.img",                     //
        "img._s0._4ooo.tickerStoryImage._54ru.img", // Top right notifications.
        ".img._s0._rw.img"                          //
    ],
    pageThumbs: [
        "img._s0._4ooo._1ve7._3s6v._rv.img",        // Friends list.
        "img._s0._4ooo._1ve7._rv.img"               // friends list in friends list page.
    ],
    mainProfile: [
        "img.profilePic.img",                       // main profile image normal user.
        "img._4jhq.img"                             // Page profile pic on far left style.
    ]
};

/* 'ready' Utility. *
=====================
Written by Ryan Morr.
http://ryanmorr.com/using-mutation-observers-to-watch-for-element-availability/
*/
(function(win) {
    var listeners = [],
        doc = win.document,
        MutationObserver = win.MutationObserver || win.WebKitMutationObserver,
        observer;

    function ready(selector, fn) {
        // Store the selector and callback to be monitored
        listeners.push({
            selector: selector,
            fn: fn
        });
        if (!observer) {
            // Watch for changes in the document
            observer = new MutationObserver(check);
            observer.observe(doc.documentElement, {
                childList: true,
                subtree: true
            });
        }
        // Check if the element is currently in the DOM
        check();
    }

    function check() {
        // Check the DOM for elements matching a stored selector
        for (var i = 0, len = listeners.length, listener, elements; i < len; i++) {
            listener = listeners[i];
            // Query for elements matching the specified selector
            elements = doc.querySelectorAll(listener.selector);
            for (var j = 0, jLen = elements.length, element; j < jLen; j++) {
                element = elements[j];
                // Make sure the callback isn't invoked with the
                // same element more than once
                if (!element.ready) {
                    element.ready = true;
                    // Invoke the callback with the element
                    listener.fn.call(element, element);
                }
            }
        }
    }
    // Expose `ready`
    win.ready = ready;
})(this);

/* Run Main... */
main();<|endoftext|>


<commit_before>'use strict'

import React, { PropTypes } from 'react'

const draw = (width, height, canvasCtx, volume) => {
  canvasCtx.clearRect(0, 0, width, height)
  for (let i = 0; i < 5; i++) {
    canvasCtx.fillStyle = (i * 255 / 5 < volume) ? 'green' : 'grey'
    const x = width * i / 5
    const y = height * 0.6 - height * i * 0.15
    canvasCtx.fillRect(x, y, width / 6, height - y)
  }
}

const VolumeMeter = React.createClass({
  getVolume () {
    this.analyser.getByteFrequencyData(this.array)
    const length = this.array.length
    let total = 0

    for (let i = 0; i < length; i++) {
      total += this.array[i]
    }
    return total / length
  },

  start () {
    const { width, height } = this.props
    const canvasCtx = this.refs.canvas.getContext('2d')

    const drawLoop = () => {
      if (this.analyser.ended) return
      draw(width, height, canvasCtx, this.getVolume())
      this.rafId = window.requestAnimationFrame(drawLoop)
    }

    drawLoop()
  },

  stop () {
    window.cancelAnimationFrame(this.rafId)
  },

  componentDidMount () {
    const { width, height } = this.props
    const canvasCtx = this.refs.canvas.getContext('2d')

    draw(width, height, canvasCtx, 0)
  },

  componentWillUpdate (nextProps) {
    if (!this.props.src && nextProps.src) {
      const { audioContext, src } = this.props
      this.analyser = audioContext.createAnalyser()
      src.connect(this.analyser)
      this.array = new Uint8Array(this.analyser.frequencyBinCount)
    }
  },

  componentDidUpdate (prevProps) {
    if (this.props.command && this.props.command !== 'none' && this.prevProps.command !== this.props.command) {
      this[this.props.command]()
    }
  },

  render () {
    const { width, height } = this.props
    const st = {
      width: width,
      height: height
    }

    return (
      <canvas
        ref='canvas'
        style={st}
      />
    )
  },

  propTypes: {
    command: PropTypes.oneOf(['start', 'stop', 'none']),
    audioContext: PropTypes.object.isRequired,
    src: PropTypes.object,
    width: PropTypes.number.isRequired,
    height: PropTypes.number.isRequired
  }
})

export default VolumeMeter
<commit_msg>fix componentWillUpdate bug<commit_after>'use strict'

import React, { PropTypes } from 'react'

const draw = (width, height, canvasCtx, volume) => {
  canvasCtx.clearRect(0, 0, width, height)
  for (let i = 0; i < 5; i++) {
    canvasCtx.fillStyle = (i * 255 / 5 < volume) ? 'green' : 'grey'
    const x = width * i / 5
    const y = height * 0.6 - height * i * 0.15
    canvasCtx.fillRect(x, y, width / 6, height - y)
  }
}

const VolumeMeter = React.createClass({
  getVolume () {
    this.analyser.getByteFrequencyData(this.array)
    const length = this.array.length
    let total = 0

    for (let i = 0; i < length; i++) {
      total += this.array[i]
    }
    return total / length
  },

  start () {
    const { width, height } = this.props
    const canvasCtx = this.refs.canvas.getContext('2d')

    const drawLoop = () => {
      if (this.analyser.ended) return
      draw(width, height, canvasCtx, this.getVolume())
      this.rafId = window.requestAnimationFrame(drawLoop)
    }

    drawLoop()
  },

  stop () {
    window.cancelAnimationFrame(this.rafId)
  },

  componentDidMount () {
    const { width, height } = this.props
    const canvasCtx = this.refs.canvas.getContext('2d')

    draw(width, height, canvasCtx, 0)
  },

  componentWillUpdate (nextProps) {
    if (!this.props.src && nextProps.src) {
      const { audioContext, src } = nextProps
      this.analyser = audioContext.createAnalyser()
      src.connect(this.analyser)
      this.array = new Uint8Array(this.analyser.frequencyBinCount)
    }
  },

  componentDidUpdate (prevProps) {
    if (this.props.command && this.props.command !== 'none' && this.prevProps.command !== this.props.command) {
      this[this.props.command]()
    }
  },

  render () {
    const { width, height } = this.props
    const st = {
      width: width,
      height: height
    }

    return (
      <canvas
        ref='canvas'
        style={st}
      />
    )
  },

  propTypes: {
    command: PropTypes.oneOf(['start', 'stop', 'none']),
    audioContext: PropTypes.object.isRequired,
    src: PropTypes.object,
    width: PropTypes.number.isRequired,
    height: PropTypes.number.isRequired
  }
})

export default VolumeMeter
<|endoftext|>


<commit_before>angular
  .module('lnPatterns')
  .directive('lnOGravityForm', lnOGravityForm);

/**
 * TODO:
 *    - Expand the list of supported input types (see comments in form parser)
 *    - Add in support for confirmation types other than just 'message'
 *    - Add in support for input masks on the client side
 *    - Finish implementing paged support
 */

lnOGravityForm.$inject = ['$compile'];

function lnOGravityForm($compile) {
  return {
    restrict: 'A',
    transclude: true,
    templateUrl: 'lnPatterns/organisms/gravity-form/template.html',
    scope: {},
    controller: 'lnOGravityFormController',
    controllerAs: 'vm',
    bindToController: {
      id: '@lnOGravityForm',
      onSubmit: '&lnSubmit',
      onError: '&lnError',
      loadFromAPI: '=lnLoadFromApi'
    },
    link: function link(scope, element) {
      var unbind = scope.$watch('formHTML', function (newHTML) {
        if (angular.isDefined(newHTML)) {
          var template = angular.element(newHTML);

          // If we are using jQuery as a global, we need to unrwap the HTML.
          if (window.$) {
            template = angular.element(newHTML.$$unwrapTrustedValue());
          }
          element.html(template[0]);
          $compile(element.contents())(scope);
        }
      });

      scope.$on('$destroy', function () {
        unbind();
      });
    }
  };
}

<commit_msg>Fix typo<commit_after>angular
  .module('lnPatterns')
  .directive('lnOGravityForm', lnOGravityForm);

/**
 * TODO:
 *    - Expand the list of supported input types (see comments in form parser)
 *    - Add in support for confirmation types other than just 'message'
 *    - Add in support for input masks on the client side
 *    - Finish implementing paged support
 */

lnOGravityForm.$inject = ['$compile'];

function lnOGravityForm($compile) {
  return {
    restrict: 'A',
    transclude: true,
    templateUrl: 'lnPatterns/organisms/gravity-form/template.html',
    scope: {},
    controller: 'lnOGravityFormController',
    controllerAs: 'vm',
    bindToController: {
      id: '@lnOGravityForm',
      onSubmit: '&lnSubmit',
      onError: '&lnError',
      loadFromAPI: '=lnLoadFromApi'
    },
    link: function link(scope, element) {
      var unbind = scope.$watch('formHTML', function (newHTML) {
        if (angular.isDefined(newHTML)) {
          var template = angular.element(newHTML);

          // If we are using jQuery as a global, we need to unwrap the HTML.
          if (window.$) {
            template = angular.element(newHTML.$$unwrapTrustedValue());
          }
          element.html(template[0]);
          $compile(element.contents())(scope);
        }
      });

      scope.$on('$destroy', function () {
        unbind();
      });
    }
  };
}

<|endoftext|>


<commit_before>'use strict'

{
  const EVENT = new Event('endOfList');

  function inject() {
    console.log('Injecting script...');

    const injScript = document.createElement('script');

    injScript.textContent = '(' + function () {
      const URL = 2; // Some constants
      const TITLE = 3; // Offset of values in array
      const AUTHOR = 4;

      const audio = document.querySelector('div.audio_row');
      audio.click(); // Forcing vk to get url
      window.ap.setVolume(0);

      const l = window.ap._currentPlaylist._ref._list.length; // getting length of audios

      for (let i = 0; i < l; i++) {
        const title = window.ap._currentAudio[TITLE];
        const author = window.ap._currentAudio[AUTHOR];
        const url = window.ap._currentAudio[URL];

        const D = new CustomEvent('download_music', { detail:
          { author: author, url: url, title: title }, });
        document.dispatchEvent(D);
      }
    } + ')();';

    document.head.appendChild(injScript); // Injecting script
    injScript.remove();
    console.log('Script injected successfuly...');
  }

  chrome.runtime.onMessage.addListener(function (mes, sen, resp) {
    console.log('mda');
    document.addEventListener('download_music', (e) => chrome.runtime.sendMessage(e.detail));

    const bottom = document.getElementById('ui_audio_load_more');

    bottom.addEventListener('endOfList', () => inject());
    bottom.dispatchEvent(EVENT);

    // const label = setInterval(() => {
    //   bottom.scrollIntoView();
    //   if (bottom.style.display === 'none') {
    //     clearInterval(label);
    //     bottom.dispatchEvent(EVENT);
    //   }
    // }, 500);

  });
}
<commit_msg>Ending with content.js, starting to work on downloads in bg.js<commit_after>'use strict'

{
  const EVENT = new Event('endOfList');

  function inject() {
    console.log('Injecting script...');

    const injScript = document.createElement('script');

    injScript.textContent = '(' + function () {
      const URL = 2; // Some constants
      const TITLE = 3; // Offset of values in array
      const AUTHOR = 4;

      const audio = document.querySelector('div.audio_row');
      audio.click(); // Forcing vk to get url
      window.ap.setVolume(0);

      const l = window.ap._currentPlaylist._ref._list.length; // getting length of audios

      // When you fiercely skip songs vk will not load them.
      // So u need to stay on song some time, to get it's url

      setTimeout(() => (function getSong(index) {
        if (index >= l) return;

        let title = window.ap._currentAudio[TITLE];
        let author = window.ap._currentAudio[AUTHOR];
        let url = window.ap._currentAudio[URL];

        const D = new CustomEvent('download_music', { detail:
          { author: author, url: url, title: title }, });
        document.dispatchEvent(D); // Passing data to bg.js for downloading

        window.ap.playNext(); // Getting next song

        setTimeout(() => getSong(index + 1), 1000); // Cauz vk need some time to load song
      })(0), 1000);
    } + ')();';

    document.head.appendChild(injScript); // Injecting script
    injScript.remove();
    console.log('Script injected successfuly...');
  }

  chrome.runtime.onMessage.addListener(function (mes, sen, resp) {
    document.addEventListener('download_music', (e) => chrome.runtime.sendMessage(e.detail));

    const bottom = document.getElementById('ui_audio_load_more');

    bottom.addEventListener('endOfList', () => inject());

    const label = setInterval(() => {
      bottom.scrollIntoView();
      if (bottom.style.display === 'none') {
        clearInterval(label);
        bottom.dispatchEvent(EVENT);
      }
    }, 500);

  });
}
<|endoftext|>


<commit_before>/*
 * LocalStore key => "gh_search_text"
 *
 * FORMAT
 *      "gh_search_text" : {
 *          <USER+REPONAME_1> : {
 *               "text_search" : "SEARCH_STRING",
 *               "matches" : [ "FILENAME", "FILENAME" ]
 *          },
            <USER+REPONNAME_X>  : {
 *               "text_search" : "SEARCH_STRING",
 *               "matches" : [ "FILENAME", "FILENAME" ]
 *          },

 *      }
 *
 * */

window.addEventListener ("load", main, false);

// add event keyUp
document.addEventListener('keyup', focus_keyUp, false);

// global variable used to loop on focus elements
var current_focus_index = 0;
var total_matches       = 0;
var match_color         = null;

// localstore
const EXT_MAIN_KEY    = "gh_text_search";

const ID_MATCH_BASE     = 'gh_ext_match_';

function first_focus()
{
    if (total_matches == 0)
        return;

    var id_name = ID_MATCH_BASE + "0";

    document.getElementById(id_name).focus();
}

function clean_page()
{
    // clean table_elem
    var table_elem = document.getElementById( ID_EXT_MATCHES );
    while (table_elem.hasChildNodes()){
        table_elem.removeChild(table_elem.firstChild);
    }
}

function save_data(data)
{

    console.log(data);
    localStorage.setItem(EXT_MAIN_KEY, JSON.stringify(data));
}

function get_data()
{
    var data = null;
    data =  JSON.parse(localStorage.getItem(EXT_MAIN_KEY));

    return data;
}

/* remove data from storage */
function clean_data()
{
    data = {};
    data["text_search"] = {};
    save_data(data);
}

function store_last_search(text_search, matches)
{
    data = get_data();

    data["text_search"] = {"value": text_search, "matches" : matches };
    // TODO add history

    save_data(data);
}

function replace_match(match, p1)
{

    var id_name = ID_MATCH_BASE + total_matches;

    new_value = create_highlight_wrapper(id_name, match_color, p1);

    // update total matches
    total_matches++;

    return new_value;
}

function highlight(container, what) {

    if (container == undefined)
        return;

    var content = container.innerHTML;

    if ((content == "") || (content == undefined))
        return;

    // BLACK (RegExp) MAGIC
    // (?!([^<])*?>) avoid to have match inside HTML tags
    var  pattern = new RegExp('(' + what + ')(?!([^<])*?>)','ig');

    var new_content = content.replace(pattern, replace_match);

    if (new_content != content )
        container.innerHTML = new_content;

}

function search_in_table_file(container, search_for)
{

    // these files have a table
    // class="highlight tab-size js-file-line-container"
    // container is div with itemprop=text

    chrome.runtime.sendMessage({type: "getMatchColor"}, function(response) {

        match_color = response.data;

        table = container.querySelectorAll('[class="highlight tab-size js-file-line-container"]');
        if (table)
        {
            // iterate rows and for each row the cells
            for (var i = 0, row; row = table[0].rows[i]; i++) {

                for (var j = 0, col; col = row.cells[j]; j++) {

                    highlight(col, search_for);

                }// end cells
            } // end ROWS
        }

        first_focus();
        create_match_div();
    });
}

function search_and_highlight_text(container, search_for)
{

    // ignore every node whis isn't an element node
    if (container.nodeType != Node.ELEMENT_NODE)
    {
        console.log("I'm not a node element");
        return;
    }
    var children = container.childNodes;

    if (children.length == 0)
    {
        // search the text
        console.log("HIGHLIGHT");
        highlight(container, search_for);
        return;
    } else {
        for (i = 0; i < 2; i++)
        {
            if (children[i] == undefined)
                continue;
            search_and_highlight_text(children[i], search_for);
        }
    }

}

function search_in_article_file(container, search_for)
{
    // these files have a table
    // class="highlight tab-size js-file-line-container"
    // container is div with itemprop=text

    chrome.runtime.sendMessage({type: "getMatchColor"}, function(response) {

        match_color = response.data;

        article = document.querySelector('[class="markdown-body entry-content"]');
        if (article)
        {
            highlight(article, search_for);
        }

        first_focus();
        create_match_div();
    });

}

function highlight_matches_on_current_file()
{
    // get current location/path
    var window_path = window.location.pathname;

    // TODO must be changed, this is a work around
    // split using 'master', the second item is  the real path + filename
    var tmp = window_path.split('master');

    // remove '/'
    var current_file = tmp[1].substring(1);

    var ext_data = get_data();

    if (ext_data === null)
        return;

    var match_infos = ext_data['text_search'];

    var search_for = match_infos["value"];
    var matches    = match_infos["matches"];

    if (matches.indexOf(current_file) <= -1)
    {
        console.log("This file is not a correct one");
        return;
    }

    // check which kinfd of file we're talking about (code, markup ecc)
    //itemprop="text"
    var container =  document.querySelectorAll('[itemprop="text"]');
    if (container) {

        if (document.querySelector('[class="markdown-body entry-content"]'))
        {
            search_in_article_file(container[0], search_for);
        }
        else
        {
           search_in_table_file(container[0], search_for);
        }

    }
}

function show_matches(matches)
{

    var table_elem = document.getElementById("ext_matches");
    if (table_elem != null)
    {
         for (i = 0; i < matches.length; i++)
         {
            var row_elem = table_elem.insertRow(i);
            row_elem.className += "js-navigation-item";

            var text_elem = document.createTextNode(matches[i]);
            var a_elem = document.createElement("a");
            a_elem.appendChild(text_elem);
            a_elem.style.marginLeft =  '10px';

            var path_name = window.location.pathname;
            var items = path_name.split("/");
            var href_value = items[2]+'/blob/master/'+matches[i];
            a_elem.setAttribute('href', href_value);

            row_elem.appendChild(a_elem);
         }

    }
}

function do_search()
{
    var content = document.getElementById(ID_EXT_SEARCH_BAR).value;
    var span_info = document.getElementById(ID_GH_MATCH_INFOS);

    // clean match result table
    clean_page();

    if (content == "")
    {
        clean_data();
        span_info.innerHTML = "";
        return;
    }

    // SHOW Searching....
    span_info.innerHTML = "Searching....";

    var gh_request = new XMLHttpRequest();
    gh_request.onreadystatechange = function() {

        if (this.readyState == 4 && this.status == 422) {
			return;
        }

        if (this.readyState == 4 && this.status == 200) {

            var data_json =  eval("(" + this.responseText + ")");
            var elements = document.getElementsByClassName("js-navigation-open");

            var matches = [];

            for (i = 0; i < data_json["total_count"]; i++)
            {
                var tmp = data_json["items"][i]["path"];
                matches.push(data_json["items"][i]["path"]);
            }

            // SHOW Matches Found: X
            //var span_info = document.getElementById("gh_match_infos");
            span_info.innerHTML = "Matches found: " + matches.length;
            store_last_search(content, matches);
            show_matches(matches);
        }
    };

    var path_name = window.location.pathname;
    var items = path_name.split("/");

    var url_query ='https://api.github.com/search/code?q=' + content;
    url_query += "+in:file+repo:"+items[1]+"/"+items[2];

    gh_request.open("GET", url_query, true);
    gh_request.send();

}

function search_bar_keypress(event)
{
    // enter has keyCode = 13, change it if you want to use another button
    if (event.keyCode == 13) {

       do_search();
       return false;
    }
}

function get_last_search()
{
    var result = "";

    data = get_data();

    if (data === null)
    {
        return result;
    }

    if (data["text_search"])
    {
        var text_search = data["text_search"];
        if (text_search["value"])
            result = text_search["value"];
    }

    return result;

}

function init_content_script()
{

    if (localStorage.getItem(EXT_MAIN_KEY) == undefined)
        clean_data();
}

function main(evt) {

    // check if localStorage
    init_content_script();

    var file_nav = document.getElementsByClassName("commit-tease");
    if (file_nav)
    {

        var file_div = document.getElementsByClassName("file");

		// if exists element with class file probably we're in a page
		// with content file, so do no show the search bar
        if (file_div.length > 0)
        {
            // do not show search bar
            // but try to highlight the matches
            highlight_matches_on_current_file();
            return;
        }
        create_ext_search_div(file_nav[0]);
    }

}
<commit_msg>code moved into storage.js<commit_after>/*
 * LocalStore key => "gh_search_text"
 *
 * FORMAT
 *      "gh_search_text" : {
 *          <USER+REPONAME_1> : {
 *               "text_search" : "SEARCH_STRING",
 *               "matches" : [ "FILENAME", "FILENAME" ]
 *          },
            <USER+REPONNAME_X>  : {
 *               "text_search" : "SEARCH_STRING",
 *               "matches" : [ "FILENAME", "FILENAME" ]
 *          },

 *      }
 *
 * */

window.addEventListener ("load", main, false);

// add event keyUp
document.addEventListener('keyup', focus_keyUp, false);

// global variable used to loop on focus elements
var current_focus_index = 0;
var total_matches       = 0;
var match_color         = null;

const ID_MATCH_BASE     = 'gh_ext_match_';

function first_focus()
{
    if (total_matches == 0)
        return;

    var id_name = ID_MATCH_BASE + "0";

    document.getElementById(id_name).focus();
}

function clean_page()
{
    // clean table_elem
    var table_elem = document.getElementById( ID_EXT_MATCHES );
    while (table_elem.hasChildNodes()){
        table_elem.removeChild(table_elem.firstChild);
    }
}

function store_last_search(text_search, matches)
{

    var data = create_entry(text_search, matches);
    save_data(data);

    // key is username + reponame
    var path_name = window.location.pathname;
    var items = path_name.split("/");

    var key = items[0] + "/" + items[1];
    save_data(key, data);

}

function store_last_search(text_search, matches)
{
    data = get_data();

    console.log(data);
    data["text_search"] = {"value": text_search, "matches" : matches };
    // TODO add history

    save_data(data);
}

function replace_match(match, p1)
{

    var id_name = ID_MATCH_BASE + total_matches;

    new_value = create_highlight_wrapper(id_name, match_color, p1);

    // update total matches
    total_matches++;

    return new_value;
}

function highlight(container, what) {

    if (container == undefined)
        return;

    var content = container.innerHTML;

    if ((content == "") || (content == undefined))
        return;

    // BLACK (RegExp) MAGIC
    // (?!([^<])*?>) avoid to have match inside HTML tags
    var  pattern = new RegExp('(' + what + ')(?!([^<])*?>)','ig');

    var new_content = content.replace(pattern, replace_match);

    if (new_content != content )
        container.innerHTML = new_content;

}

function search_in_table_file(container, search_for)
{

    // these files have a table
    // class="highlight tab-size js-file-line-container"
    // container is div with itemprop=text

    chrome.runtime.sendMessage({type: "getMatchColor"}, function(response) {

        match_color = response.data;

        table = container.querySelectorAll('[class="highlight tab-size js-file-line-container"]');
        if (table)
        {
            // iterate rows and for each row the cells
            for (var i = 0, row; row = table[0].rows[i]; i++) {

                for (var j = 0, col; col = row.cells[j]; j++) {

                    highlight(col, search_for);

                }// end cells
            } // end ROWS
        }

        first_focus();
        create_match_div();
    });
}

function search_and_highlight_text(container, search_for)
{

    // ignore every node whis isn't an element node
    if (container.nodeType != Node.ELEMENT_NODE)
    {
        console.log("I'm not a node element");
        return;
    }
    var children = container.childNodes;

    if (children.length == 0)
    {
        // search the text
        console.log("HIGHLIGHT");
        highlight(container, search_for);
        return;
    } else {
        for (i = 0; i < 2; i++)
        {
            if (children[i] == undefined)
                continue;
            search_and_highlight_text(children[i], search_for);
        }
    }

}

function search_in_article_file(container, search_for)
{
    // these files have a table
    // class="highlight tab-size js-file-line-container"
    // container is div with itemprop=text

    chrome.runtime.sendMessage({type: "getMatchColor"}, function(response) {

        match_color = response.data;

        article = document.querySelector('[class="markdown-body entry-content"]');
        if (article)
        {
            highlight(article, search_for);
        }

        first_focus();
        create_match_div();
    });

}

function highlight_matches_on_current_file()
{
    // get current location/path
    var window_path = window.location.pathname;

    // TODO must be changed, this is a work around
    // split using 'master', the second item is  the real path + filename
    var tmp = window_path.split('master');

    // remove '/'
    var current_file = tmp[1].substring(1);

    var ext_data = get_data();

    if (ext_data === null)
        return;

    var match_infos = ext_data['text_search'];

    var search_for = match_infos["value"];
    var matches    = match_infos["matches"];

    if (matches.indexOf(current_file) <= -1)
    {
        console.log("This file is not a correct one");
        return;
    }

    // check which kinfd of file we're talking about (code, markup ecc)
    //itemprop="text"
    var container =  document.querySelectorAll('[itemprop="text"]');
    if (container) {

        if (document.querySelector('[class="markdown-body entry-content"]'))
        {
            search_in_article_file(container[0], search_for);
        }
        else
        {
           search_in_table_file(container[0], search_for);
        }

    }
}

function show_matches(matches)
{

    var table_elem = document.getElementById("ext_matches");
    if (table_elem != null)
    {
         for (i = 0; i < matches.length; i++)
         {
            var row_elem = table_elem.insertRow(i);
            row_elem.className += "js-navigation-item";

            var text_elem = document.createTextNode(matches[i]);
            var a_elem = document.createElement("a");
            a_elem.appendChild(text_elem);
            a_elem.style.marginLeft =  '10px';

            var path_name = window.location.pathname;
            var items = path_name.split("/");
            var href_value = items[2]+'/blob/master/'+matches[i];
            a_elem.setAttribute('href', href_value);

            row_elem.appendChild(a_elem);
         }

    }
}

function do_search()
{
    var content = document.getElementById(ID_EXT_SEARCH_BAR).value;
    var span_info = document.getElementById(ID_GH_MATCH_INFOS);

    // clean match result table
    clean_page();

    if (content == "")
    {
        clean_data();
        span_info.innerHTML = "";
        return;
    }

    // SHOW Searching....
    span_info.innerHTML = "Searching....";

    var gh_request = new XMLHttpRequest();
    gh_request.onreadystatechange = function() {

        if (this.readyState == 4 && this.status == 422) {
			return;
        }

        if (this.readyState == 4 && this.status == 200) {

            var data_json =  eval("(" + this.responseText + ")");

            var elements = document.getElementsByClassName("js-navigation-open");

            var matches = [];

            for (i = 0; i < data_json["total_count"]; i++)
            {
                var tmp = data_json["items"][i]["path"];
                matches.push(data_json["items"][i]["path"]);
            }

            // SHOW Matches Found: X
            //var span_info = document.getElementById("gh_match_infos");
            span_info.innerHTML = "Matches found: " + matches.length;
            store_last_search(content, matches);
            show_matches(matches);
        }
    };

    var path_name = window.location.pathname;
    var items = path_name.split("/");

    var url_query ='https://api.github.com/search/code?q=' + content;
    url_query += "+in:file+repo:"+items[1]+"/"+items[2];

    gh_request.open("GET", url_query, true);
    gh_request.send();

}

function search_bar_keypress(event)
{
    // enter has keyCode = 13, change it if you want to use another button
    if (event.keyCode == 13) {

       do_search();
       return false;
    }
}

function get_last_search()
{
    var result = "";

    data = get_data();

    if (data === null)
    {
        return result;
    }

    if (data["text_search"])
    {
        var text_search = data["text_search"];
        if (text_search["value"])
            result = text_search["value"];
    }

    return result;

}

function main(evt) {

    // check if localStorage
    init_storage();

    var file_nav = document.getElementsByClassName("commit-tease");
    if (file_nav)
    {

        var file_div = document.getElementsByClassName("file");

		// if exists element with class file probably we're in a page
		// with content file, so do no show the search bar
        if (file_div.length > 0)
        {
            // do not show search bar
            // but try to highlight the matches
            highlight_matches_on_current_file();
            return;
        }
        create_ext_search_div(file_nav[0]);
    }

}
<|endoftext|>


<commit_before>/* global Log */

const createClient = require("hafas-client");
const shortenStationName = require("vbb-short-station-name");
const bvgProfile = require("hafas-client/p/bvg");
const pjson = require("./package.json");

const bvgClient = createClient(
  bvgProfile,
  `MMM-PublicTransportBerlin v${pjson.version}`
);

class BvgFetcher {
  constructor(config) {
    this.config = config;
    this.id = config.name;
  }

  getId() {
    return this.id;
  }

  getStationId() {
    return this.config.stationId;
  }

  async getStationName() {
    const station = await bvgClient.stop(this.config.stationId);
    return station.name;
  }

  async getDirectionDescriptor() {
    if (typeof this.config.directionStationId === "undefined") {
      return "all directions";
    }
    const station = await bvgClient.stop(this.config.directionStationId);
    return station.name;
  }

  async fetchDepartures() {
    // when value for a request is calculated to be 5 minutes before travelTimeToStation time
    // so we can also show the non-reachable departures in the module
    let when;

    if (this.config.travelTimeToStation > 0) {
      when = new Date();
      when.setTime(
        Date.now() + this.config.travelTimeToStation * 60000 - 5 * 60000
      );
    } else {
      when = Date.now();
    }

    let opt;

    // Handle single direction case
    if (
      !this.config.directionStationId ||
      this.config.directionStationId === ""
    ) {
      opt = {
        when,
        duration: this.config.departureMinutes
      };
    } else {
      opt = {
        direction: this.config.directionStationId,
        when,
        duration: this.config.departureMinutes
      };
    }

    const departures = await bvgClient.departures(this.config.stationId, opt);
    const processedDepartures = this.processData(departures);

    return processedDepartures;
  }

  processData(data) {
    const departuresData = {
      fetcherId: this.id,
      departuresArray: []
    };

    data.forEach((row) => {
      // check for:
      // excluded transportation types
      // ignored lines

      // TODO: Make real stop/station handling here
      // Quick fix to work around missing station objects
      if (!row.station) {
        row.station = row.stop; // eslint-disable-line no-param-reassign
      }

      if (
        !this.config.excludedTransportationTypes.includes(row.line.product) &&
        !this.config.ignoredLines.includes(row.line.name)
      ) {
        const current = {
          when: row.when || row.plannedWhen,
          delay: row.delay || 0,
          cancelled: row.cancelled || false,
          name: row.line.name,
          nr: row.line.nr,
          type: row.line.product,
          direction: this.config.shortenStationNames
            ? shortenStationName(row.direction)
            : row.direction
        };

        departuresData.departuresArray.push(current);
      }
    });

    departuresData.departuresArray.sort(this.compareTimes);
    return departuresData;
  }

  static compareTimes(a, b) {
    if (a.when < b.when) {
      return -1;
    }
    if (a.when > b.when) {
      return 1;
    }
    return 0;
  }

  // helper function to print departure for debugging
  printDeparture(row) {
    const delayMinutes = Math.floor(
      (((row.delay % 31536000) % 86400) % 3600) / 60
    );

    const time = row.when.toLocaleTimeString([], {
      hour: "2-digit",
      minute: "2-digit"
    });

    Log.log(
      `${time} ${delayMinutes} ${row.line.product} ${row.direction} | stationId: ${row.station.id}`
    );
  }
}

module.exports = BvgFetcher;
<commit_msg>Add hidden debug option for printing departures<commit_after>const createClient = require("hafas-client");
const shortenStationName = require("vbb-short-station-name");
const bvgProfile = require("hafas-client/p/bvg");
const Log = require("../../js/logger");
const pjson = require("./package.json");

const bvgClient = createClient(
  bvgProfile,
  `MMM-PublicTransportBerlin v${pjson.version}`
);

class BvgFetcher {
  constructor(config) {
    this.config = config;
    this.id = config.name;
  }

  getId() {
    return this.id;
  }

  getStationId() {
    return this.config.stationId;
  }

  async getStationName() {
    const station = await bvgClient.stop(this.config.stationId);
    return station.name;
  }

  async getDirectionDescriptor() {
    if (typeof this.config.directionStationId === "undefined") {
      return "all directions";
    }
    const station = await bvgClient.stop(this.config.directionStationId);
    return station.name;
  }

  async fetchDepartures() {
    // when value for a request is calculated to be 5 minutes before travelTimeToStation time
    // so we can also show the non-reachable departures in the module
    let when;

    if (this.config.travelTimeToStation > 0) {
      when = new Date();
      when.setTime(
        Date.now() + this.config.travelTimeToStation * 60000 - 5 * 60000
      );
    } else {
      when = Date.now();
    }

    let opt;

    // Handle single direction case
    if (
      !this.config.directionStationId ||
      this.config.directionStationId === ""
    ) {
      opt = {
        when,
        duration: this.config.departureMinutes
      };
    } else {
      opt = {
        direction: this.config.directionStationId,
        when,
        duration: this.config.departureMinutes
      };
    }

    const departures = await bvgClient.departures(this.config.stationId, opt);
    const processedDepartures = this.processData(departures);

    return processedDepartures;
  }

  processData(data) {
    const departuresData = {
      fetcherId: this.id,
      departuresArray: []
    };

    data.forEach((row) => {
      // check for:
      // excluded transportation types
      // ignored lines

      // TODO: Make real stop/station handling here
      // Quick fix to work around missing station objects
      if (!row.station) {
        row.station = row.stop; // eslint-disable-line no-param-reassign
      }

      // Set hidden debug option to print debug info about departures
      if (this.config.debug) this.printDeparture(row);

      if (
        !this.config.excludedTransportationTypes.includes(row.line.product) &&
        !this.config.ignoredLines.includes(row.line.name)
      ) {
        const current = {
          when: row.when || row.plannedWhen,
          delay: row.delay || 0,
          cancelled: row.cancelled || false,
          name: row.line.name,
          nr: row.line.nr,
          type: row.line.product,
          direction: this.config.shortenStationNames
            ? shortenStationName(row.direction)
            : row.direction
        };

        departuresData.departuresArray.push(current);
      }
    });

    departuresData.departuresArray.sort(this.compareTimes);
    return departuresData;
  }

  static compareTimes(a, b) {
    if (a.when < b.when) {
      return -1;
    }
    if (a.when > b.when) {
      return 1;
    }
    return 0;
  }

  // helper function to print departure for debugging
  static printDeparture(row) {
    const delayMinutes = Math.floor(
      (((row.delay % 31536000) % 86400) % 3600) / 60
    );

    const time = new Date(row.when).toLocaleTimeString([], {
      hour: "2-digit",
      minute: "2-digit"
    });

    Log.log(
      `${time} ${delayMinutes} ${row.line.product} ${row.direction} | stationId: ${row.station.id}`
    );
  }
}

module.exports = BvgFetcher;
<|endoftext|>


<commit_before>/* @flow */
import type { NavigationNavigateAction } from 'react-navigation';

import {
  REHYDRATE,
  APP_ONLINE,
  APP_STATE,
  DEAD_QUEUE,
  INIT_SAFE_AREA_INSETS,
  APP_ORIENTATION,
  START_EDIT_MESSAGE,
  CANCEL_EDIT_MESSAGE,
  DEBUG_FLAG_TOGGLE,
  ACCOUNT_SWITCH,
  REALM_ADD,
  ACCOUNT_REMOVE,
  LOGIN_SUCCESS,
  LOGOUT,
  REALM_INIT,
  GOT_PUSH_TOKEN,
  UNACK_PUSH_TOKEN,
  ACK_PUSH_TOKEN,
  MESSAGE_FETCH_START,
  MESSAGE_FETCH_COMPLETE,
  INITIAL_FETCH_START,
  INITIAL_FETCH_COMPLETE,
  INIT_REALM_EMOJI,
  INIT_REALM_FILTER,
  SETTINGS_CHANGE,
  DRAFT_UPDATE,
  DO_NARROW,
  PRESENCE_RESPONSE,
  MESSAGE_SEND_START,
  MESSAGE_SEND_COMPLETE,
  DELETE_OUTBOX_MESSAGE,
  TOGGLE_OUTBOX_SENDING,
  EVENT_MESSAGE_DELETE,
  EVENT_USER_GROUP_ADD,
  EVENT_USER_GROUP_REMOVE,
  EVENT_USER_GROUP_UPDATE,
  EVENT_USER_GROUP_ADD_MEMBERS,
  EVENT_USER_GROUP_REMOVE_MEMBERS,
  EVENT_USER_STATUS_UPDATE,
  EVENT_TYPING_START,
  EVENT_TYPING_STOP,
  EVENT_NEW_MESSAGE,
  EVENT_REACTION_ADD,
  EVENT_REACTION_REMOVE,
  EVENT_PRESENCE,
  EVENT_UPDATE_GLOBAL_NOTIFICATIONS_SETTINGS,
  EVENT_UPDATE_MESSAGE,
  EVENT_UPDATE_MESSAGE_FLAGS,
  EVENT_USER_ADD,
  CLEAR_TYPING,
  INIT_ALERT_WORDS,
  INIT_STREAMS,
  INIT_TOPICS,
  INIT_SUBSCRIPTIONS,
  EVENT_MUTED_TOPICS,
  EVENT_REALM_FILTERS,
  EVENT_USER_REMOVE,
  EVENT_USER_UPDATE,
  EVENT_REALM_EMOJI_UPDATE,
  EVENT_UPDATE_DISPLAY_SETTINGS,
  EVENT_SUBSCRIPTION,
  EVENT,
} from './actionConstants';

import type { MessageEvent, PresenceEvent, StreamEvent } from './api/eventTypes';

import type {
  Dimensions,
  Orientation,
  GlobalState,
  Message,
  MessagesState,
  Outbox,
  Narrow,
  Reaction,
  Identity,
  User,
  UserGroup,
  InitialData,
  RealmFilter,
  Stream,
  Subscription,
  Topic,
  PresenceState,
  RealmEmojiState,
  SettingsState,
  CaughtUpState,
  MuteState,
  AlertWordsState,
  UserStatusEvent,
} from './types';

/**
 * Dispatched by redux-persist when the stored state is loaded.
 *
 * It can be very convenient to pass `payload` to selectors, but beware it's
 * incomplete.  At a minimum, reducers should always separately handle the
 * case where the state is empty or has `null` properties before passing the
 * object to any selector.
 *
 * @prop payload A version of the global Redux state, as persisted by the
 *     app's previous runs.  This will be empty on first startup or if the
 *     persisted store is just missing keys, and will have `null` at each
 *     key where an error was encountered in reading the persisted store.
 *     In any case it will only contain the keys we configure to be persisted.
 * @prop error
 */
type RehydrateAction = {|
  type: typeof REHYDRATE,
  payload: GlobalState | { accounts: null } | {||} | void,
  error: ?Object,
|};

type AppOnlineAction = {|
  type: typeof APP_ONLINE,
  isOnline: boolean,
|};

type AppStateAction = {|
  type: typeof APP_STATE,
  isActive: boolean,
|};

type DeadQueueAction = {|
  type: typeof DEAD_QUEUE,
|};

type InitSafeAreaInsetsAction = {|
  type: typeof INIT_SAFE_AREA_INSETS,
  safeAreaInsets: Dimensions,
|};

type AppOrientationAction = {|
  type: typeof APP_ORIENTATION,
  orientation: Orientation,
|};

type StartEditMessageAction = {|
  type: typeof START_EDIT_MESSAGE,
  messageId: number,
  message: string,
  topic: string,
|};

type CancelEditMessageAction = {|
  type: typeof CANCEL_EDIT_MESSAGE,
|};

type DebugFlagToggleAction = {|
  type: typeof DEBUG_FLAG_TOGGLE,
  key: string,
  value: string,
|};

export type NavigateAction = NavigationNavigateAction;

type AccountSwitchAction = {|
  type: typeof ACCOUNT_SWITCH,
  index: number,
|};

type RealmAddAction = {|
  type: typeof REALM_ADD,
  realm: string,
|};

type AccountRemoveAction = {|
  type: typeof ACCOUNT_REMOVE,
  index: number,
|};

type LoginSuccessAction = {|
  type: typeof LOGIN_SUCCESS,
  realm: string,
  email: string,
  apiKey: string,
|};

type LogoutAction = {|
  type: typeof LOGOUT,
|};

type RealmInitAction = {|
  type: typeof REALM_INIT,
  data: InitialData,
|};

/** We learned the device token from the system.  See `SessionState`. */
type GotPushTokenAction = {|
  type: typeof GOT_PUSH_TOKEN,
  pushToken: string,
|};

/** We're about to tell the server to forget our device token. */
type UnackPushTokenAction = {|
  type: typeof UNACK_PUSH_TOKEN,
  identity: Identity,
|};

/** The server acknowledged our device token. */
type AckPushTokenAction = {|
  type: typeof ACK_PUSH_TOKEN,
  identity: Identity,
  pushToken: string,
|};

type MessageFetchStartAction = {|
  type: typeof MESSAGE_FETCH_START,
  narrow: Narrow,
  numBefore: number,
  numAfter: number,
|};

type MessageFetchCompleteAction = {|
  type: typeof MESSAGE_FETCH_COMPLETE,
  messages: Message[],
  narrow: Narrow,
  anchor: number,
  numBefore: number,
  numAfter: number,
  foundNewest: boolean | void,
  foundOldest: boolean | void,
|};

type InitialFetchStartAction = {|
  type: typeof INITIAL_FETCH_START,
|};

type InitialFetchCompleteAction = {|
  type: typeof INITIAL_FETCH_COMPLETE,
|};

type ServerEvent = {|
  id: number,
|};

type EventAlertWordsAction = {|
  type: typeof INIT_ALERT_WORDS,
  alertWords: AlertWordsState,
|};

type EventRealmFiltersAction = {|
  type: typeof EVENT_REALM_FILTERS,
  [string]: any,
|};

type EventUpdateGlobalNotificationsSettingsAction = {|
  ...ServerEvent,
  type: typeof EVENT_UPDATE_GLOBAL_NOTIFICATIONS_SETTINGS,
  notification_name: | 'enable_offline_push_notiications'
    | 'enable_online_push_notiications'
    | 'enable_stream_push_notifiations',
  setting: boolean,
|};

type EventSubscriptionAddAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'add',
  subscriptions: Subscription[],
|};

type EventSubscriptionRemoveAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'remove',
  subscriptions: Array<{
    name: string,
    stream_id: number,
  }>,
|};

type EventSubscriptionUpdateAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'update',
  email: string,
  name: string,
  property: string,
  stream_id: number,
  value: boolean | number | string,
|};

type EventSubscriptionPeerAddAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'peer_add',
  subscriptions: string[],
  user_id: number,
|};

type EventSubscriptionPeerRemoveAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'peer_remove',
  subscriptions: string[],
  user_id: number,
|};

type GenericEventAction = {|
  type: typeof EVENT,
  event: StreamEvent,
|};

type EventNewMessageAction = {|
  ...$Diff<MessageEvent, { flags: mixed }>,
  type: typeof EVENT_NEW_MESSAGE,
  caughtUp: CaughtUpState,
  ownEmail: string,
|};

type EventMessageDeleteAction = {|
  type: typeof EVENT_MESSAGE_DELETE,
  messageId: number,
|};
type EventUpdateMessageAction = {|
  ...ServerEvent,
  type: typeof EVENT_UPDATE_MESSAGE,
  edit_timestamp: number,
  message_id: number,
  orig_content: string,
  orig_subject?: string,
  orig_rendered_content: string,
  prev_rendered_content_version: number,
  rendered_content: string,
  subject_links: string[],
  subject: string,
  user_id: number,
|};

type EventReactionCommon = {|
  ...ServerEvent,
  ...$Exact<Reaction>,
  message_id: number,
|};

type EventReactionAddAction = {|
  ...ServerEvent,
  ...EventReactionCommon,
  type: typeof EVENT_REACTION_ADD,
|};

type EventReactionRemoveAction = {|
  ...ServerEvent,
  ...EventReactionCommon,
  type: typeof EVENT_REACTION_REMOVE,
|};

type EventPresenceAction = {|
  ...PresenceEvent,
  type: typeof EVENT_PRESENCE,
|};

type EventTypingCommon = {|
  ...ServerEvent,
  ownEmail: string,
  recipients: Array<{
    user_id: number,
    email: string,
  }>,
  sender: {
    user_id: number,
    email: string,
  },
  time: number,
|};

type EventTypingStartAction = {|
  ...EventTypingCommon,
  type: typeof EVENT_TYPING_START,
  op: 'start',
|};

type EventTypingStopAction = {|
  ...EventTypingCommon,
  type: typeof EVENT_TYPING_STOP,
  op: 'stop',
|};

type EventUpdateMessageFlagsAction = {|
  ...ServerEvent,
  type: typeof EVENT_UPDATE_MESSAGE_FLAGS,
  all: boolean,
  allMessages: MessagesState,
  flag: string,
  messages: number[],
  operation: 'add' | 'remove',
|};

type EventUserAddAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_ADD,
  person: User,
|};

type EventUserRemoveAction = {|
  type: typeof EVENT_USER_REMOVE,
  // In reality there's more -- but this will prevent accidentally using
  // the type before going and adding those other properties here properly.
|};

type EventUserUpdateAction = {|
  type: typeof EVENT_USER_UPDATE,
  // In reality there's more -- but this will prevent accidentally using
  // the type before going and adding those other properties here properly.
|};

type EventMutedTopicsAction = {|
  ...ServerEvent,
  type: typeof EVENT_MUTED_TOPICS,
  muted_topics: MuteState,
|};

type EventUserGroupAddAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_ADD,
  op: 'add',
  group: UserGroup,
|};

type EventUserGroupRemoveAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_REMOVE,
  op: 'remove',
  group_id: number,
|};

type EventUserGroupUpdateAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_UPDATE,
  op: 'update',
  group_id: number,
  data: { description?: string, name?: string },
|};

type EventUserGroupAddMembersAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_ADD_MEMBERS,
  op: 'add_members',
  group_id: number,
  user_ids: number[],
|};

type EventUserGroupRemoveMembersAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_REMOVE_MEMBERS,
  op: 'remove_members',
  group_id: number,
  user_ids: number[],
|};

type EventRealmEmojiUpdateAction = {|
  type: typeof EVENT_REALM_EMOJI_UPDATE,
  [string]: any,
|};

type EventUpdateDisplaySettingsAction = {|
  type: typeof EVENT_UPDATE_DISPLAY_SETTINGS,
  [string]: any,
|};

type EventReactionAction = EventReactionAddAction | EventReactionRemoveAction;

type EventUserStatusUpdateAction = {|
  ...UserStatusEvent,
  type: typeof EVENT_USER_STATUS_UPDATE,
|};

type EventSubscriptionAction =
  | EventSubscriptionAddAction
  | EventSubscriptionRemoveAction
  | EventSubscriptionUpdateAction
  | EventSubscriptionPeerAddAction
  | EventSubscriptionPeerRemoveAction;

type EventTypingAction = EventTypingStartAction | EventTypingStopAction;

type EventUserAction = EventUserAddAction | EventUserRemoveAction | EventUserUpdateAction;

type EventUserGroupAction =
  | EventUserGroupAddAction
  | EventUserGroupRemoveAction
  | EventUserGroupUpdateAction
  | EventUserGroupAddMembersAction
  | EventUserGroupRemoveMembersAction;

/** Covers all actions we make from server events. */
export type EventAction =
  | GenericEventAction
  // Specific actions.
  | EventAlertWordsAction
  | EventMessageDeleteAction
  | EventMutedTopicsAction
  | EventNewMessageAction
  | EventPresenceAction
  | EventRealmEmojiUpdateAction
  | EventRealmFiltersAction
  | EventUpdateGlobalNotificationsSettingsAction
  | EventUpdateDisplaySettingsAction
  | EventUpdateMessageAction
  | EventUpdateMessageFlagsAction
  // Unions, found just above.
  | EventReactionAction
  | EventSubscriptionAction
  | EventTypingAction
  | EventUserAction
  | EventUserGroupAction
  | EventUserStatusUpdateAction
  // Dummy actions.
  | {| type: 'ignore' |}
  | {| type: 'unknown', event: {} |};

type InitRealmEmojiAction = {|
  type: typeof INIT_REALM_EMOJI,
  emojis: RealmEmojiState,
|};

type InitRealmFilterAction = {|
  type: typeof INIT_REALM_FILTER,
  filters: RealmFilter[],
|};

type SettingsChangeAction = {|
  type: typeof SETTINGS_CHANGE,
  update: $Shape<SettingsState>,
|};

type DraftUpdateAction = {|
  type: typeof DRAFT_UPDATE,
  narrow: Narrow,
  content: string,
|};

type DoNarrowAction = {|
  type: typeof DO_NARROW,
  narrow: Narrow,
|};

type PresenceResponseAction = {|
  type: typeof PRESENCE_RESPONSE,
  presence: PresenceState,
  serverTimestamp: number,
|};

type MessageSendStartAction = {|
  type: typeof MESSAGE_SEND_START,
  outbox: Outbox,
|};

type MessageSendCompleteAction = {|
  type: typeof MESSAGE_SEND_COMPLETE,
  local_message_id: number,
|};

type DeleteOutboxMessageAction = {|
  type: typeof DELETE_OUTBOX_MESSAGE,
  local_message_id: number,
|};

type ToggleOutboxSendingAction = {|
  type: typeof TOGGLE_OUTBOX_SENDING,
  sending: boolean,
|};

type ClearTypingAction = {|
  type: typeof CLEAR_TYPING,
  outdatedNotifications: string[],
|};

type InitStreamsAction = {|
  type: typeof INIT_STREAMS,
  streams: Stream[],
|};

type InitTopicsAction = {|
  type: typeof INIT_TOPICS,
  topics: Topic[],
  streamId: number,
|};

type InitSubscriptionsAction = {|
  type: typeof INIT_SUBSCRIPTIONS,
  subscriptions: Subscription[],
|};

//
// The `Action` union type.
//

type AccountAction =
  | AccountSwitchAction
  | RealmAddAction
  | AccountRemoveAction
  | LoginSuccessAction
  | LogoutAction;

type LoadingAction = DeadQueueAction | InitialFetchStartAction | InitialFetchCompleteAction;

type MessageAction = MessageFetchStartAction | MessageFetchCompleteAction;

type OutboxAction = MessageSendStartAction | MessageSendCompleteAction | DeleteOutboxMessageAction;

type RealmAction =
  | RealmInitAction
  | UnackPushTokenAction
  | AckPushTokenAction
  | InitRealmEmojiAction
  | InitRealmFilterAction;

type SessionAction =
  | RehydrateAction
  | AppStateAction
  | AppOnlineAction
  | InitSafeAreaInsetsAction
  | AppOrientationAction
  | DoNarrowAction
  | GotPushTokenAction
  | StartEditMessageAction
  | CancelEditMessageAction
  | DebugFlagToggleAction
  | ToggleOutboxSendingAction;

/** Covers all actions we ever `dispatch`. */
// The grouping here is completely arbitrary; don't worry about it.
export type Action =
  | EventAction
  | AccountAction
  | LoadingAction
  | MessageAction
  | OutboxAction
  | RealmAction
  | SessionAction
  | DraftUpdateAction
  | PresenceResponseAction
  | SettingsChangeAction
  | InitStreamsAction
  | InitSubscriptionsAction
  | InitTopicsAction
  | ClearTypingAction;
<commit_msg>actionTypes: Make some vague types more specific.<commit_after>/* @flow */
import type { NavigationNavigateAction } from 'react-navigation';

import {
  REHYDRATE,
  APP_ONLINE,
  APP_STATE,
  DEAD_QUEUE,
  INIT_SAFE_AREA_INSETS,
  APP_ORIENTATION,
  START_EDIT_MESSAGE,
  CANCEL_EDIT_MESSAGE,
  DEBUG_FLAG_TOGGLE,
  ACCOUNT_SWITCH,
  REALM_ADD,
  ACCOUNT_REMOVE,
  LOGIN_SUCCESS,
  LOGOUT,
  REALM_INIT,
  GOT_PUSH_TOKEN,
  UNACK_PUSH_TOKEN,
  ACK_PUSH_TOKEN,
  MESSAGE_FETCH_START,
  MESSAGE_FETCH_COMPLETE,
  INITIAL_FETCH_START,
  INITIAL_FETCH_COMPLETE,
  INIT_REALM_EMOJI,
  INIT_REALM_FILTER,
  SETTINGS_CHANGE,
  DRAFT_UPDATE,
  DO_NARROW,
  PRESENCE_RESPONSE,
  MESSAGE_SEND_START,
  MESSAGE_SEND_COMPLETE,
  DELETE_OUTBOX_MESSAGE,
  TOGGLE_OUTBOX_SENDING,
  EVENT_MESSAGE_DELETE,
  EVENT_USER_GROUP_ADD,
  EVENT_USER_GROUP_REMOVE,
  EVENT_USER_GROUP_UPDATE,
  EVENT_USER_GROUP_ADD_MEMBERS,
  EVENT_USER_GROUP_REMOVE_MEMBERS,
  EVENT_USER_STATUS_UPDATE,
  EVENT_TYPING_START,
  EVENT_TYPING_STOP,
  EVENT_NEW_MESSAGE,
  EVENT_REACTION_ADD,
  EVENT_REACTION_REMOVE,
  EVENT_PRESENCE,
  EVENT_UPDATE_GLOBAL_NOTIFICATIONS_SETTINGS,
  EVENT_UPDATE_MESSAGE,
  EVENT_UPDATE_MESSAGE_FLAGS,
  EVENT_USER_ADD,
  CLEAR_TYPING,
  INIT_ALERT_WORDS,
  INIT_STREAMS,
  INIT_TOPICS,
  INIT_SUBSCRIPTIONS,
  EVENT_MUTED_TOPICS,
  EVENT_REALM_FILTERS,
  EVENT_USER_REMOVE,
  EVENT_USER_UPDATE,
  EVENT_REALM_EMOJI_UPDATE,
  EVENT_UPDATE_DISPLAY_SETTINGS,
  EVENT_SUBSCRIPTION,
  EVENT,
} from './actionConstants';

import type { MessageEvent, PresenceEvent, StreamEvent } from './api/eventTypes';

import type {
  Dimensions,
  Orientation,
  GlobalState,
  Message,
  MessagesState,
  Outbox,
  Narrow,
  Reaction,
  Identity,
  User,
  UserGroup,
  InitialData,
  RealmFilter,
  Stream,
  Subscription,
  Topic,
  PresenceState,
  RealmEmojiState,
  SettingsState,
  CaughtUpState,
  MuteState,
  AlertWordsState,
  UserStatusEvent,
} from './types';

/**
 * Dispatched by redux-persist when the stored state is loaded.
 *
 * It can be very convenient to pass `payload` to selectors, but beware it's
 * incomplete.  At a minimum, reducers should always separately handle the
 * case where the state is empty or has `null` properties before passing the
 * object to any selector.
 *
 * @prop payload A version of the global Redux state, as persisted by the
 *     app's previous runs.  This will be empty on first startup or if the
 *     persisted store is just missing keys, and will have `null` at each
 *     key where an error was encountered in reading the persisted store.
 *     In any case it will only contain the keys we configure to be persisted.
 * @prop error
 */
type RehydrateAction = {|
  type: typeof REHYDRATE,
  payload: GlobalState | { accounts: null } | {||} | void,
  error: mixed,
|};

type AppOnlineAction = {|
  type: typeof APP_ONLINE,
  isOnline: boolean,
|};

type AppStateAction = {|
  type: typeof APP_STATE,
  isActive: boolean,
|};

type DeadQueueAction = {|
  type: typeof DEAD_QUEUE,
|};

type InitSafeAreaInsetsAction = {|
  type: typeof INIT_SAFE_AREA_INSETS,
  safeAreaInsets: Dimensions,
|};

type AppOrientationAction = {|
  type: typeof APP_ORIENTATION,
  orientation: Orientation,
|};

type StartEditMessageAction = {|
  type: typeof START_EDIT_MESSAGE,
  messageId: number,
  message: string,
  topic: string,
|};

type CancelEditMessageAction = {|
  type: typeof CANCEL_EDIT_MESSAGE,
|};

type DebugFlagToggleAction = {|
  type: typeof DEBUG_FLAG_TOGGLE,
  key: string,
  value: string,
|};

export type NavigateAction = NavigationNavigateAction;

type AccountSwitchAction = {|
  type: typeof ACCOUNT_SWITCH,
  index: number,
|};

type RealmAddAction = {|
  type: typeof REALM_ADD,
  realm: string,
|};

type AccountRemoveAction = {|
  type: typeof ACCOUNT_REMOVE,
  index: number,
|};

type LoginSuccessAction = {|
  type: typeof LOGIN_SUCCESS,
  realm: string,
  email: string,
  apiKey: string,
|};

type LogoutAction = {|
  type: typeof LOGOUT,
|};

type RealmInitAction = {|
  type: typeof REALM_INIT,
  data: InitialData,
|};

/** We learned the device token from the system.  See `SessionState`. */
type GotPushTokenAction = {|
  type: typeof GOT_PUSH_TOKEN,
  pushToken: string,
|};

/** We're about to tell the server to forget our device token. */
type UnackPushTokenAction = {|
  type: typeof UNACK_PUSH_TOKEN,
  identity: Identity,
|};

/** The server acknowledged our device token. */
type AckPushTokenAction = {|
  type: typeof ACK_PUSH_TOKEN,
  identity: Identity,
  pushToken: string,
|};

type MessageFetchStartAction = {|
  type: typeof MESSAGE_FETCH_START,
  narrow: Narrow,
  numBefore: number,
  numAfter: number,
|};

type MessageFetchCompleteAction = {|
  type: typeof MESSAGE_FETCH_COMPLETE,
  messages: Message[],
  narrow: Narrow,
  anchor: number,
  numBefore: number,
  numAfter: number,
  foundNewest: boolean | void,
  foundOldest: boolean | void,
|};

type InitialFetchStartAction = {|
  type: typeof INITIAL_FETCH_START,
|};

type InitialFetchCompleteAction = {|
  type: typeof INITIAL_FETCH_COMPLETE,
|};

type ServerEvent = {|
  id: number,
|};

type EventAlertWordsAction = {|
  type: typeof INIT_ALERT_WORDS,
  alertWords: AlertWordsState,
|};

type EventRealmFiltersAction = {|
  type: typeof EVENT_REALM_FILTERS,
  realm_filters: RealmFilter[],
|};

type EventUpdateGlobalNotificationsSettingsAction = {|
  ...ServerEvent,
  type: typeof EVENT_UPDATE_GLOBAL_NOTIFICATIONS_SETTINGS,
  notification_name: | 'enable_offline_push_notiications'
    | 'enable_online_push_notiications'
    | 'enable_stream_push_notifiations',
  setting: boolean,
|};

type EventSubscriptionAddAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'add',
  subscriptions: Subscription[],
|};

type EventSubscriptionRemoveAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'remove',
  subscriptions: Array<{
    name: string,
    stream_id: number,
  }>,
|};

type EventSubscriptionUpdateAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'update',
  email: string,
  name: string,
  property: string,
  stream_id: number,
  value: boolean | number | string,
|};

type EventSubscriptionPeerAddAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'peer_add',
  subscriptions: string[],
  user_id: number,
|};

type EventSubscriptionPeerRemoveAction = {|
  ...ServerEvent,
  type: typeof EVENT_SUBSCRIPTION,
  op: 'peer_remove',
  subscriptions: string[],
  user_id: number,
|};

type GenericEventAction = {|
  type: typeof EVENT,
  event: StreamEvent,
|};

type EventNewMessageAction = {|
  ...$Diff<MessageEvent, { flags: mixed }>,
  type: typeof EVENT_NEW_MESSAGE,
  caughtUp: CaughtUpState,
  ownEmail: string,
|};

type EventMessageDeleteAction = {|
  type: typeof EVENT_MESSAGE_DELETE,
  messageId: number,
|};
type EventUpdateMessageAction = {|
  ...ServerEvent,
  type: typeof EVENT_UPDATE_MESSAGE,
  edit_timestamp: number,
  message_id: number,
  orig_content: string,
  orig_subject?: string,
  orig_rendered_content: string,
  prev_rendered_content_version: number,
  rendered_content: string,
  subject_links: string[],
  subject: string,
  user_id: number,
|};

type EventReactionCommon = {|
  ...ServerEvent,
  ...$Exact<Reaction>,
  message_id: number,
|};

type EventReactionAddAction = {|
  ...ServerEvent,
  ...EventReactionCommon,
  type: typeof EVENT_REACTION_ADD,
|};

type EventReactionRemoveAction = {|
  ...ServerEvent,
  ...EventReactionCommon,
  type: typeof EVENT_REACTION_REMOVE,
|};

type EventPresenceAction = {|
  ...PresenceEvent,
  type: typeof EVENT_PRESENCE,
|};

type EventTypingCommon = {|
  ...ServerEvent,
  ownEmail: string,
  recipients: Array<{
    user_id: number,
    email: string,
  }>,
  sender: {
    user_id: number,
    email: string,
  },
  time: number,
|};

type EventTypingStartAction = {|
  ...EventTypingCommon,
  type: typeof EVENT_TYPING_START,
  op: 'start',
|};

type EventTypingStopAction = {|
  ...EventTypingCommon,
  type: typeof EVENT_TYPING_STOP,
  op: 'stop',
|};

type EventUpdateMessageFlagsAction = {|
  ...ServerEvent,
  type: typeof EVENT_UPDATE_MESSAGE_FLAGS,
  all: boolean,
  allMessages: MessagesState,
  flag: string,
  messages: number[],
  operation: 'add' | 'remove',
|};

type EventUserAddAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_ADD,
  person: User,
|};

type EventUserRemoveAction = {|
  type: typeof EVENT_USER_REMOVE,
  // In reality there's more -- but this will prevent accidentally using
  // the type before going and adding those other properties here properly.
|};

type EventUserUpdateAction = {|
  type: typeof EVENT_USER_UPDATE,
  // In reality there's more -- but this will prevent accidentally using
  // the type before going and adding those other properties here properly.
|};

type EventMutedTopicsAction = {|
  ...ServerEvent,
  type: typeof EVENT_MUTED_TOPICS,
  muted_topics: MuteState,
|};

type EventUserGroupAddAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_ADD,
  op: 'add',
  group: UserGroup,
|};

type EventUserGroupRemoveAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_REMOVE,
  op: 'remove',
  group_id: number,
|};

type EventUserGroupUpdateAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_UPDATE,
  op: 'update',
  group_id: number,
  data: { description?: string, name?: string },
|};

type EventUserGroupAddMembersAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_ADD_MEMBERS,
  op: 'add_members',
  group_id: number,
  user_ids: number[],
|};

type EventUserGroupRemoveMembersAction = {|
  ...ServerEvent,
  type: typeof EVENT_USER_GROUP_REMOVE_MEMBERS,
  op: 'remove_members',
  group_id: number,
  user_ids: number[],
|};

type EventRealmEmojiUpdateAction = {|
  type: typeof EVENT_REALM_EMOJI_UPDATE,
  realm_emoji: RealmEmojiState,
|};

type EventUpdateDisplaySettingsAction = {|
  type: typeof EVENT_UPDATE_DISPLAY_SETTINGS,
  setting_name: string,
  /** In reality, this can be a variety of types. It's boolean for a
   * `setting_name` of `twenty_four_hour_time`, which is the only case we
   * currently look at. */
  setting: boolean,
|};

type EventReactionAction = EventReactionAddAction | EventReactionRemoveAction;

type EventUserStatusUpdateAction = {|
  ...UserStatusEvent,
  type: typeof EVENT_USER_STATUS_UPDATE,
|};

type EventSubscriptionAction =
  | EventSubscriptionAddAction
  | EventSubscriptionRemoveAction
  | EventSubscriptionUpdateAction
  | EventSubscriptionPeerAddAction
  | EventSubscriptionPeerRemoveAction;

type EventTypingAction = EventTypingStartAction | EventTypingStopAction;

type EventUserAction = EventUserAddAction | EventUserRemoveAction | EventUserUpdateAction;

type EventUserGroupAction =
  | EventUserGroupAddAction
  | EventUserGroupRemoveAction
  | EventUserGroupUpdateAction
  | EventUserGroupAddMembersAction
  | EventUserGroupRemoveMembersAction;

/** Covers all actions we make from server events. */
export type EventAction =
  | GenericEventAction
  // Specific actions.
  | EventAlertWordsAction
  | EventMessageDeleteAction
  | EventMutedTopicsAction
  | EventNewMessageAction
  | EventPresenceAction
  | EventRealmEmojiUpdateAction
  | EventRealmFiltersAction
  | EventUpdateGlobalNotificationsSettingsAction
  | EventUpdateDisplaySettingsAction
  | EventUpdateMessageAction
  | EventUpdateMessageFlagsAction
  // Unions, found just above.
  | EventReactionAction
  | EventSubscriptionAction
  | EventTypingAction
  | EventUserAction
  | EventUserGroupAction
  | EventUserStatusUpdateAction
  // Dummy actions.
  | {| type: 'ignore' |}
  | {| type: 'unknown', event: {} |};

type InitRealmEmojiAction = {|
  type: typeof INIT_REALM_EMOJI,
  emojis: RealmEmojiState,
|};

type InitRealmFilterAction = {|
  type: typeof INIT_REALM_FILTER,
  filters: RealmFilter[],
|};

type SettingsChangeAction = {|
  type: typeof SETTINGS_CHANGE,
  update: $Shape<SettingsState>,
|};

type DraftUpdateAction = {|
  type: typeof DRAFT_UPDATE,
  narrow: Narrow,
  content: string,
|};

type DoNarrowAction = {|
  type: typeof DO_NARROW,
  narrow: Narrow,
|};

type PresenceResponseAction = {|
  type: typeof PRESENCE_RESPONSE,
  presence: PresenceState,
  serverTimestamp: number,
|};

type MessageSendStartAction = {|
  type: typeof MESSAGE_SEND_START,
  outbox: Outbox,
|};

type MessageSendCompleteAction = {|
  type: typeof MESSAGE_SEND_COMPLETE,
  local_message_id: number,
|};

type DeleteOutboxMessageAction = {|
  type: typeof DELETE_OUTBOX_MESSAGE,
  local_message_id: number,
|};

type ToggleOutboxSendingAction = {|
  type: typeof TOGGLE_OUTBOX_SENDING,
  sending: boolean,
|};

type ClearTypingAction = {|
  type: typeof CLEAR_TYPING,
  outdatedNotifications: string[],
|};

type InitStreamsAction = {|
  type: typeof INIT_STREAMS,
  streams: Stream[],
|};

type InitTopicsAction = {|
  type: typeof INIT_TOPICS,
  topics: Topic[],
  streamId: number,
|};

type InitSubscriptionsAction = {|
  type: typeof INIT_SUBSCRIPTIONS,
  subscriptions: Subscription[],
|};

//
// The `Action` union type.
//

type AccountAction =
  | AccountSwitchAction
  | RealmAddAction
  | AccountRemoveAction
  | LoginSuccessAction
  | LogoutAction;

type LoadingAction = DeadQueueAction | InitialFetchStartAction | InitialFetchCompleteAction;

type MessageAction = MessageFetchStartAction | MessageFetchCompleteAction;

type OutboxAction = MessageSendStartAction | MessageSendCompleteAction | DeleteOutboxMessageAction;

type RealmAction =
  | RealmInitAction
  | UnackPushTokenAction
  | AckPushTokenAction
  | InitRealmEmojiAction
  | InitRealmFilterAction;

type SessionAction =
  | RehydrateAction
  | AppStateAction
  | AppOnlineAction
  | InitSafeAreaInsetsAction
  | AppOrientationAction
  | DoNarrowAction
  | GotPushTokenAction
  | StartEditMessageAction
  | CancelEditMessageAction
  | DebugFlagToggleAction
  | ToggleOutboxSendingAction;

/** Covers all actions we ever `dispatch`. */
// The grouping here is completely arbitrary; don't worry about it.
export type Action =
  | EventAction
  | AccountAction
  | LoadingAction
  | MessageAction
  | OutboxAction
  | RealmAction
  | SessionAction
  | DraftUpdateAction
  | PresenceResponseAction
  | SettingsChangeAction
  | InitStreamsAction
  | InitSubscriptionsAction
  | InitTopicsAction
  | ClearTypingAction;
<|endoftext|>


<commit_before>window.requestAnimationFrame =
    window.requestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.msRequestAnimationFrame;

// target = id of Html element where counting occurs
// endVal = the value you want to arrive at
// decimals = number of decimal places in number, default 0
// duration = duration in seconds, default 2

function countUp(target, endVal, decimals, duration) {

    var self = this;
    this.d = document.getElementById(target);
    this.dec = decimals * 10 || 0;
    this.duration = duration * 1000 || 2000;

    this.startTime = null;
    this.frameVal = 0;

    // Robert Penner's easeOutExpo
    this.easeOutExpo = function(t, b, c, d) {
        return c * (-Math.pow(2, -10 * t / d) + 1) + b;
    }
    this.stepUp = function(timestamp) {

        if (self.startTime === null) self.startTime = timestamp;

        var progress = timestamp - self.startTime;

        // easing
        self.frameVal = self.easeOutExpo(progress, 0, endVal, self.duration);

        // decimal
        if (self.dec > 0) {
            self.frameVal = Math.round(self.frameVal*self.dec)/self.dec;
            self.frameVal = (self.frameVal > endVal) ? endVal : self.frameVal;
        }

        self.d.innerHTML = self.addCommas(self.frameVal.toFixed(decimals));

        if (progress < self.duration) {
            requestAnimationFrame(self.stepUp);
        } else {
            // bc easing prevents endVal being reached
            self.d.innerHTML = self.addCommas(endVal.toFixed(decimals));
        }
    }
    this.start = function() {
        // make sure endVal is a number
        if (!isNaN(endVal) && endVal !== null) {
            requestAnimationFrame(self.stepUp);
        } else {
            console.log('countUp error: endVal is not a number');
            self.d.innerHTML = '--';
        }
        return false;
    }
    this.reset = function() {
        this.d.innerHTML = 0;
    }
    this.addCommas = function(nStr) {
        nStr += '';
        x = nStr.split('.');
        x1 = x[0];
        x2 = x.length > 1 ? '.' + x[1] : '';
        var rgx = /(\d+)(\d{3})/;
        while (rgx.test(x1)) {
            x1 = x1.replace(rgx, '$1' + ',' + '$2');
        }
        return x1 + x2;
    }
}
// Example:
// var numAnim = new countUp("SomeElementYouWantToAnimate", 99.99, 2, 1.5);
// numAnim.start();<commit_msg>fix the scaling issue for decimals > 2.<commit_after>window.requestAnimationFrame =
    window.requestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.msRequestAnimationFrame;

// target = id of Html element where counting occurs
// endVal = the value you want to arrive at
// decimals = number of decimal places in number, default 0
// duration = duration in seconds, default 2

function countUp(target, endVal, decimals, duration) {

    var self = this;
    this.d = document.getElementById(target);

    decimals = Math.max(0, decimals || 0);
    this.dec = Math.pow(10, decimals);
    this.duration = duration * 1000 || 2000;

    this.startTime = null;
    this.frameVal = 0;

    // Robert Penner's easeOutExpo
    this.easeOutExpo = function(t, b, c, d) {
        return c * (-Math.pow(2, -10 * t / d) + 1) + b;
    }
    this.stepUp = function(timestamp) {

        if (self.startTime === null) self.startTime = timestamp;

        var progress = timestamp - self.startTime;

        // easing
        self.frameVal = self.easeOutExpo(progress, 0, endVal, self.duration);

        // decimal
        if (self.dec > 0) {
            self.frameVal = Math.round(self.frameVal*self.dec)/self.dec;
            self.frameVal = (self.frameVal > endVal) ? endVal : self.frameVal;
        }

        self.d.innerHTML = self.addCommas(self.frameVal.toFixed(decimals));

        if (progress < self.duration) {
            requestAnimationFrame(self.stepUp);
        } else {
            // bc easing prevents endVal being reached
            self.d.innerHTML = self.addCommas(endVal.toFixed(decimals));
        }
    }
    this.start = function() {
        // make sure endVal is a number
        if (!isNaN(endVal) && endVal !== null) {
            requestAnimationFrame(self.stepUp);
        } else {
            console.log('countUp error: endVal is not a number');
            self.d.innerHTML = '--';
        }
        return false;
    }
    this.reset = function() {
        this.d.innerHTML = 0;
    }
    this.addCommas = function(nStr) {
        nStr += '';
        x = nStr.split('.');
        x1 = x[0];
        x2 = x.length > 1 ? '.' + x[1] : '';
        var rgx = /(\d+)(\d{3})/;
        while (rgx.test(x1)) {
            x1 = x1.replace(rgx, '$1' + ',' + '$2');
        }
        return x1 + x2;
    }
}
// Example:
// var numAnim = new countUp("SomeElementYouWantToAnimate", 99.99, 2, 1.5);
// numAnim.start();<|endoftext|>