<commit_before>'use strict';
goog.provide('branch_view');
goog.require('utils');
goog.require('banner_css');
goog.require('safejson');
goog.require('journeys_utils');

function checkPreviousBanner() {
	// if banner already exists, don't add another
	if (document.getElementById('branch-banner') ||
		document.getElementById('branch-banner-iframe') ||
		document.getElementById('branch-banner-container')) {
		return true;
	}
	return false;
}

/**
 * @param {Object} parent
 * @param {string} html
 * @param {boolean} hasApp
 */
function renderHtmlBlob(parent, html, hasApp) {
	journeys_utils.branch._publishEvent('willShowJourney', { 'banner_id': journeys_utils.branchViewId });

	var ctaText = hasApp ? 'OPEN' : 'GET';

	journeys_utils.setPositionAndHeight(html);

	// Get metadata, css and js from html blob then remove them
	var metadata = journeys_utils.getMetadata(html);
	if (metadata) {
		ctaText = journeys_utils.getCtaText(metadata, hasApp);
		journeys_utils.findInsertionDiv(parent, metadata);
	}
	var cssInsideIframe = journeys_utils.getCss(html);
	journeys_utils.getJsAndAddToParent(html);
	html = journeys_utils.removeScriptAndCss(html);

	// create iframe element, add html, add css, add ctaText
	var iframe = journeys_utils.createAndAppendIframe();
	var iframeHTML = journeys_utils.createIframeInnerHTML(html, utils.mobileUserAgent());
	journeys_utils.addHtmlToIframe(iframe, iframeHTML);
	journeys_utils.addIframeOuterCSS();
	journeys_utils.addIframeInnerCSS(iframe, cssInsideIframe);
	journeys_utils.addDynamicCtaText(iframe, ctaText);

	journeys_utils.animateBannerEntrance(iframe);

	return iframe;
};

/**
 * @param {Object} server
 * @param {Object} branchViewData
 * @param {Object} requestData
 * @param {Object} storage
 * @param {boolean} hasApp
 * @param {boolean} testFlag
 * @param {Object} branch
 */
branch_view.handleBranchViewData = function(server, branchViewData, requestData, storage, hasApp, testFlag, branch) {
	if (checkPreviousBanner()) {
		return;
	}
	journeys_utils.branch = branch;

	var banner = null;
	var cta = null;

	requestData = requestData || {};
	requestData['feature'] = 'journeys';

	var cleanedData = utils.cleanLinkData(requestData);

	// this code removes any leftover css from previous banner
	var branchCSS = document.getElementById('branch-iframe-css')
	if (branchCSS) {
		branchCSS.parentElement.removeChild(branchCSS)
	}

	var placeholder = document.createElement('div');
	placeholder.id = 'branch-banner';
	document.body.insertBefore(placeholder, null);
	banner_utils.addClass(placeholder, 'branch-banner-is-active');

	if (branchViewData['html']) {
		return renderHtmlBlob(document.body, branchViewData['html'], hasApp);
	} else if (branchViewData['url']) {
		var callbackString = 'branch_view_callback__' + (jsonp_callback_index++);
		var postData = encodeURIComponent(utils.base64encode(goog.json.serialize(cleanedData)));
		var url = branchViewData['url'] + '&callback=' + callbackString;
		url += '&_lan=' + (journeys_utils.branch.user_language || utils.getBrowserLanguageCode());
		url += '&data=' + postData;
		server.XHRRequest(url, {}, 'GET', {}, function(error, html){
			var failed = false;
			if (!error && html) {

				var hideBanner = !testFlag
					? journeys_utils.findDismissPeriod(html)
					: 0;

				var timeoutTrigger = window.setTimeout(
					function() {
						window[callbackString] = function() { };
					},
					TIMEOUT
				);

				window[callbackString] = function(data) {
					window.clearTimeout(timeoutTrigger);
					if (failed) {
						return;
					}
					cta = data;

					journeys_utils.finalHookups(branchViewData, storage, cta, banner, hideBanner);
				};

				banner = renderHtmlBlob(document.body, html, hasApp);
				if (banner === null) {
					failed = true;
					return;
				}

				journeys_utils.finalHookups(branchViewData, storage, cta, banner, hideBanner);
			}
			document.body.removeChild(placeholder);
		}, true);
	}
};

// builds data for a Journey in test mode
function buildJourneyTestData(branchViewId, branch_key, data){
	return {
		id: branchViewId,
		number_of_use: -1,
		url: (config.api_endpoint + '/v1/branchview/' + branch_key + '/' + branchViewId + '?_a=audience_rule_id&_t=' + data.browser_fingerprint_id)
	}
}

// checks to see if user dismissed Journey previously and whether it should remain dismissed
function isJourneyDismissed(branchViewData, branch) {
	// check storage to see dismiss timestamp
	var dismissTimeStamp = branch._storage.get('hideBanner' + branchViewData["id"], true);

	if (dismissTimeStamp === true || dismissTimeStamp > Date.now()) {
		return true;
	}

	branch._storage.remove('hideBanner' + branchViewData["id"], true);
	return false;
}

// builds an object that contains data from setBranchViewData() call, hosted deep link data and language data
function compileRequestData(branch, linkClickId) {
		var requestData = branch._branchViewData || {};

		if (!requestData['data']) {
			requestData['data'] = {};
		}

		requestData['data'] = utils.merge(utils.scrapeHostedDeepLinkData(), requestData['data']);
		requestData['data'] = utils.merge(utils.whiteListJourneysLanguageData(session.get(branch._storage) || {}), requestData['data']);
		if (linkClickId) { // linkClickId can be null so ignore passing linkClickId to v1/branchview if it is
			requestData['data'] = utils.merge({'link_click_id': linkClickId}, requestData['data']);
		}
		return requestData;
}

function fetchLinkClickId(referringLink) {
	if (referringLink) {
		return referringLink.substring(
			referringLink.lastIndexOf('/') + 1, referringLink.length
		);
	}
	return null;
}

branch_view.initJourney = function(branch_key, data, eventData, options, branch) {

	branch._branchViewEnabled = !!eventData['branch_view_enabled'];
	branch._storage.set('branch_view_enabled', branch._branchViewEnabled);

	var branchViewId = null;
	var no_journeys = null;
	var hideJourney = null;
	var branchViewData = null;
	var requestData = null;
	var testFlag = false;

	if (options) {
		branchViewId = options['branch_view_id'] || null;
		no_journeys = options['no_journeys'] || null;
		branch.user_language = options['user_language'] || utils.getBrowserLanguageCode();
		journeys_utils.entryAnimationDisabled = options['disable_entry_animation'] || false;
		journeys_utils.exitAnimationDisabled = options['disable_exit_animation'] || false;
	}

	branchViewId = branchViewId || utils.getParameterByName('_branch_view_id') || null;

	if (branchViewId && utils.mobileUserAgent()) {
		testFlag = true;
		branchViewData = buildJourneyTestData(branchViewId, branch_key, data);
	}

	if (!branchViewData) {
		if (eventData.hasOwnProperty('branch_view_data')) {
			branchViewData = eventData['branch_view_data'];
			hideJourney = isJourneyDismissed(branchViewData, branch);
		}
	}

	if (branchViewData && !hideJourney && !no_journeys) {
		journeys_utils.branchViewId = branchViewData.id;
		branch['renderQueue'](function() {
			var makeNewLink = options && options['make_new_link'] || false;
			var linkClickId = null;
			if (!makeNewLink) {
				linkClickId = fetchLinkClickId(branch._referringLink());
			}
			requestData = compileRequestData(branch, linkClickId);
			branch_view.handleBranchViewData(branch._server, branchViewData, requestData, branch._storage, data['has_app'], testFlag, branch);
		});
	}
	else {
		branch._publishEvent('willNotShowJourney');
	}
}
<commit_msg>refactoring code<commit_after>'use strict';
goog.provide('branch_view');
goog.require('utils');
goog.require('banner_css');
goog.require('safejson');
goog.require('journeys_utils');

function checkPreviousBanner() {
	// if banner already exists, don't add another
	if (document.getElementById('branch-banner') ||
		document.getElementById('branch-banner-iframe') ||
		document.getElementById('branch-banner-container')) {
		return true;
	}
	return false;
}

/**
 * @param {Object} parent
 * @param {string} html
 * @param {boolean} hasApp
 */
function renderHtmlBlob(parent, html, hasApp) {
	journeys_utils.branch._publishEvent('willShowJourney', { 'banner_id': journeys_utils.branchViewId });

	var ctaText = hasApp ? 'OPEN' : 'GET';

	journeys_utils.setPositionAndHeight(html);

	// Get metadata, css and js from html blob then remove them
	var metadata = journeys_utils.getMetadata(html);
	if (metadata) {
		ctaText = journeys_utils.getCtaText(metadata, hasApp);
		journeys_utils.findInsertionDiv(parent, metadata);
	}
	var cssInsideIframe = journeys_utils.getCss(html);
	journeys_utils.getJsAndAddToParent(html);
	html = journeys_utils.removeScriptAndCss(html);

	// create iframe element, add html, add css, add ctaText
	var iframe = journeys_utils.createAndAppendIframe();
	var iframeHTML = journeys_utils.createIframeInnerHTML(html, utils.mobileUserAgent());
	journeys_utils.addHtmlToIframe(iframe, iframeHTML);
	journeys_utils.addIframeOuterCSS();
	journeys_utils.addIframeInnerCSS(iframe, cssInsideIframe);
	journeys_utils.addDynamicCtaText(iframe, ctaText);

	journeys_utils.animateBannerEntrance(iframe);

	return iframe;
};

/**
 * @param {Object} server
 * @param {Object} branchViewData
 * @param {Object} requestData
 * @param {Object} storage
 * @param {boolean} hasApp
 * @param {boolean} testFlag
 * @param {Object} branch
 */
branch_view.handleBranchViewData = function(server, branchViewData, requestData, storage, hasApp, testFlag, branch) {
	if (checkPreviousBanner()) {
		return;
	}
	journeys_utils.branch = branch;

	var banner = null;
	var cta = null;

	requestData = requestData || {};
	requestData['feature'] = 'journeys';

	var cleanedData = utils.cleanLinkData(requestData);

	// this code removes any leftover css from previous banner
	var branchCSS = document.getElementById('branch-iframe-css')
	if (branchCSS) {
		branchCSS.parentElement.removeChild(branchCSS)
	}

	var placeholder = document.createElement('div');
	placeholder.id = 'branch-banner';
	document.body.insertBefore(placeholder, null);
	banner_utils.addClass(placeholder, 'branch-banner-is-active');

	if (branchViewData['html']) {
		return renderHtmlBlob(document.body, branchViewData['html'], hasApp);
	} else if (branchViewData['url']) {
		var callbackString = 'branch_view_callback__' + (jsonp_callback_index++);
		var postData = encodeURIComponent(utils.base64encode(goog.json.serialize(cleanedData)));
		var url = branchViewData['url'] + '&callback=' + callbackString;
		url += '&_lan=' + (journeys_utils.branch.user_language || utils.getBrowserLanguageCode());
		url += '&data=' + postData;
		server.XHRRequest(url, {}, 'GET', {}, function(error, html){
			var failed = false;
			if (!error && html) {

				var hideBanner = !testFlag
					? journeys_utils.findDismissPeriod(html)
					: 0;

				var timeoutTrigger = window.setTimeout(
					function() {
						window[callbackString] = function() { };
					},
					TIMEOUT
				);

				window[callbackString] = function(data) {
					window.clearTimeout(timeoutTrigger);
					if (failed) {
						return;
					}
					cta = data;

					journeys_utils.finalHookups(branchViewData, storage, cta, banner, hideBanner);
				};

				banner = renderHtmlBlob(document.body, html, hasApp);
				if (banner === null) {
					failed = true;
					return;
				}

				journeys_utils.finalHookups(branchViewData, storage, cta, banner, hideBanner);
			}
			document.body.removeChild(placeholder);
		}, true);
	}
};

// builds data for a Journey in test mode
function buildJourneyTestData(branchViewId, branch_key, data){
	return {
		id: branchViewId,
		number_of_use: -1,
		url: (config.api_endpoint + '/v1/branchview/' + branch_key + '/' + branchViewId + '?_a=audience_rule_id&_t=' + data.browser_fingerprint_id)
	}
}

// checks to see if user dismissed Journey previously and whether it should remain dismissed
function isJourneyDismissed(branchViewData, branch) {
	// check storage to see dismiss timestamp
	var dismissTimeStamp = branch._storage.get('hideBanner' + branchViewData["id"], true);

	if (dismissTimeStamp === true || dismissTimeStamp > Date.now()) {
		return true;
	}

	branch._storage.remove('hideBanner' + branchViewData["id"], true);
	return false;
}

// builds an object that contains data from setBranchViewData() call, hosted deep link data and language data
function compileRequestData(branch, makeNewLink) {
	var requestData = branch._branchViewData || {};

	if (!requestData['data']) {
		requestData['data'] = {};
	}

	var linkClickId = !makeNewLink ? utils.clickIdFromLink(branch._referringLink()) : null;

	requestData['data'] = utils.merge(utils.scrapeHostedDeepLinkData(), requestData['data']);
	requestData['data'] = utils.merge(utils.whiteListJourneysLanguageData(session.get(branch._storage) || {}), requestData['data']);
	requestData['data'] = linkClickId ? utils.merge({'link_click_id': linkClickId}, requestData['data']) : requestData['data'];
	return requestData;
}

branch_view.initJourney = function(branch_key, data, eventData, options, branch) {

	branch._branchViewEnabled = !!eventData['branch_view_enabled'];
	branch._storage.set('branch_view_enabled', branch._branchViewEnabled);

	var branchViewId = null;
	var no_journeys = null;
	var hideJourney = null;
	var branchViewData = null;
	var requestData = null;
	var testFlag = false;
	var makeNewLink = false;

	if (options) {
		branchViewId = options['branch_view_id'] || null;
		no_journeys = options['no_journeys'] || null;
		branch.user_language = options['user_language'] || utils.getBrowserLanguageCode();
		journeys_utils.entryAnimationDisabled = options['disable_entry_animation'] || false;
		journeys_utils.exitAnimationDisabled = options['disable_exit_animation'] || false;
		makeNewLink = options['make_new_link'] || false;
	}

	branchViewId = branchViewId || utils.getParameterByName('_branch_view_id') || null;

	if (branchViewId && utils.mobileUserAgent()) {
		testFlag = true;
		branchViewData = buildJourneyTestData(branchViewId, branch_key, data);
	}

	if (!branchViewData) {
		if (eventData.hasOwnProperty('branch_view_data')) {
			branchViewData = eventData['branch_view_data'];
			hideJourney = isJourneyDismissed(branchViewData, branch);
		}
	}

	if (branchViewData && !hideJourney && !no_journeys) {
		journeys_utils.branchViewId = branchViewData.id;
		branch['renderQueue'](function() {
			requestData = compileRequestData(branch, makeNewLink);
			branch_view.handleBranchViewData(branch._server, branchViewData, requestData, branch._storage, data['has_app'], testFlag, branch);
		});
	}
	else {
		branch._publishEvent('willNotShowJourney');
	}
}
<|endoftext|>


<commit_before>"use strict";

/*global nodeca, _*/

// stdlib
var crypto = require('crypto');

// nodeca
var HashTree = require('nlib').Support.HashTree;

// internal
var realtime = require('../../realtime');

////////////////////////////////////////////////////////////////////////////////

var helpers = module.exports = {};

////////////////////////////////////////////////////////////////////////////////

helpers.asset_path = function asset_path(path) {
  var asset = nodeca.runtime.assets.environment.findAsset(path);
  return !asset ? "#" : ("/assets/" + asset.digestPath);
};

helpers.asset_include = function asset_include(path) {
  var asset = nodeca.runtime.assets.environment.findAsset(path);
  return !asset ? "" : asset.toString();
};

helpers.config = function (part) {
  return !part ? nodeca.config : HashTree.get(nodeca.config, part);
};

helpers.count_online_users = function () {
  return realtime.activeClients + 1;
};

// crypto-strong random 128 bit string
helpers.random = function () {
  var rnd = crypto.randomBytes(16);
  return crypto.createHash('md5').update(rnd).digest('hex');
};

helpers.link_to = function (name, params) {
  return nodeca.runtime.router.linkTo(name, params) || '#';
};
<commit_msg>Add fixme info for active_users() helper<commit_after>"use strict";

/*global nodeca, _*/

// stdlib
var crypto = require('crypto');

// nodeca
var HashTree = require('nlib').Support.HashTree;

// internal
var realtime = require('../../realtime');

////////////////////////////////////////////////////////////////////////////////

var helpers = module.exports = {};

////////////////////////////////////////////////////////////////////////////////

helpers.asset_path = function asset_path(path) {
  var asset = nodeca.runtime.assets.environment.findAsset(path);
  return !asset ? "#" : ("/assets/" + asset.digestPath);
};

helpers.asset_include = function asset_include(path) {
  var asset = nodeca.runtime.assets.environment.findAsset(path);
  return !asset ? "" : asset.toString();
};

helpers.config = function (part) {
  return !part ? nodeca.config : HashTree.get(nodeca.config, part);
};

// FIXME: this is a temporary shit used just to test that Faye works
helpers.count_online_users = function () {
  return realtime.activeClients + 1;
};

// crypto-strong random 128 bit string
helpers.random = function () {
  var rnd = crypto.randomBytes(16);
  return crypto.createHash('md5').update(rnd).digest('hex');
};

helpers.link_to = function (name, params) {
  return nodeca.runtime.router.linkTo(name, params) || '#';
};
<|endoftext|>


<commit_before>"use strict";

(function()
{
    if(typeof XMLHttpRequest === "undefined")
    {
        v86util.load_file = load_file_nodejs;
    }
    else
    {
        v86util.load_file = load_file;
    }

    v86util.AsyncXHRBuffer = AsyncXHRBuffer;
    v86util.AsyncFileBuffer = AsyncFileBuffer;
    v86util.SyncFileBuffer = SyncFileBuffer;

    /**
     * @param {string} filename
     * @param {Object} options
     */
    function load_file(filename, options)
    {
        var http = new XMLHttpRequest();

        http.open(options.method || "get", filename, true);

        if(!options.as_text)
        {
            http.responseType = "arraybuffer";
        }

        if(options.headers)
        {
            var header_names = Object.keys(options.headers);

            for(var i = 0; i < header_names.length; i++)
            {
                var name = header_names[i];

                http.setRequestHeader(name, options.headers[name]);
            }
        }

        http.onload = function(e)
        {
            if(http.readyState === 4)
            {
                if(http.status !== 200 && http.status !== 206)
                {
                    console.log("Loading the image `" + filename + "` failed");
                }
                else if(http.response)
                {
                    options.done && options.done(http.response, http);
                }
            }
        };

        if(options.progress)
        {
            http.onprogress = function(e)
            {
                options.progress(e);
            };
        }

        http.send(null);
    }

    function load_file_nodejs(filename, options)
    {
        var o = {
            encoding: options.as_text ? "utf-8" : null,
        };

        require("fs")["readFile"](filename, o, function(err, data)
        {
            if(err)
            {
                console.log("Could not read file:", filename);
            }
            else
            {
                var result = data;

                if(!options.as_text)
                {
                    result = new Uint8Array(result).buffer;
                }

                options.done(result);
            }
        });
    }

    /**
     * Asynchronous access to ArrayBuffer, loading blocks lazily as needed,
     * using the `Range: bytes=...` header
     *
     * @constructor
     * @param {string} filename Name of the file to download
     * @param {number|undefined} size
     */
    function AsyncXHRBuffer(filename, size)
    {
        this.filename = filename;

        /** @const */
        this.block_size = 256;
        this.byteLength = size;

        this.loaded_blocks = {};

        this.onload = undefined;
        this.onprogress = undefined;
    }

    AsyncXHRBuffer.prototype.load = function()
    {
        if(this.byteLength !== undefined)
        {
            this.onload && this.onload({});
            return;
        }

        // Determine the size using a request

        load_file(this.filename, {
            done: function done(buffer, http)
            {
                var header = http.getResponseHeader("Content-Range") || "";
                var match = header.match(/\/(\d+)\s*$/);

                if(match)
                {
                    this.byteLength = +match[1]
                    this.onload && this.onload({});
                }
                else
                {
                    console.assert(false,
                        "Cannot use: " + this.filename + ". " +
                        "`Range: bytes=...` header not supported (Got `" + header + "`)");
                }
            }.bind(this),
            headers: {
                Range: "bytes=0-0",

                // Added by Chromium, but can cause the whole file to be sent
                // Settings this to empty also causes problems and Chromium
                // doesn't seem to create this header any more
                //"If-Range": "",
            }
        });
    }

    /**
     * @param {number} offset
     * @param {number} len
     * @param {function(!Uint8Array)} fn
     */
    AsyncXHRBuffer.prototype.get = function(offset, len, fn)
    {
        console.assert(offset % this.block_size === 0);
        console.assert(len % this.block_size === 0);
        console.assert(len);

        var range_start = offset;
        var range_end = offset + len - 1;

        load_file(this.filename, {
            done: function done(buffer)
            {
                var block = new Uint8Array(buffer);
                this.handle_read(offset, len, block);
                fn(block);
            }.bind(this),
            headers: {
                Range: "bytes=" + range_start + "-" + range_end,
            }
        });
    }

    /**
     * Relies on this.byteLength, this.loaded_blocks and this.block_size
     *
     * @this {AsyncFileBuffer|AsyncXHRBuffer}
     *
     * @param {number} start
     * @param {!Uint8Array} data
     * @param {function()} fn
     */
    AsyncXHRBuffer.prototype.set = function(start, data, fn)
    {
        console.assert(start + data.byteLength <= this.byteLength);

        var len = data.length;

        console.assert(start % this.block_size === 0);
        console.assert(len % this.block_size === 0);
        console.assert(len);

        var start_block = start / this.block_size;
        var block_count = len / this.block_size;

        for(var i = 0; i < block_count; i++)
        {
            var block = this.loaded_blocks[start_block + i];

            if(block === undefined)
            {
                block = this.loaded_blocks[start_block + i] = new Uint8Array(this.block_size);
            }

            var data_slice = data.subarray(i * this.block_size, (i + 1) * this.block_size);
            block.set(data_slice);

            console.assert(block.byteLength === data_slice.length);
        }

        fn();
    }

    /**
     * @this {AsyncFileBuffer|AsyncXHRBuffer}
     * @param {number} offset
     * @param {number} len
     * @param {!Uint8Array} block
     */
    AsyncXHRBuffer.prototype.handle_read = function(offset, len, block)
    {
        // Used by AsyncXHRBuffer and AsyncFileBuffer
        // Overwrites blocks from the original source that have been written since

        var start_block = offset / this.block_size;
        var block_count = len / this.block_size;

        for(var i = 0; i < block_count; i++)
        {
            var written_block = this.loaded_blocks[start_block + i];

            if(written_block)
            {
                block.set(written_block, i * this.block_size);
            }
            //else
            //{
            //    var cached = this.loaded_blocks[start_block + i] = new Uint8Array(this.block_size);
            //    cached.set(block.subarray(i * this.block_size, (i + 1) * this.block_size));
            //}
        }
    };

    AsyncXHRBuffer.prototype.get_buffer = function(fn)
    {
        // We must download all parts, unlikely a good idea for big files
        fn();
    };

    /**
     * Synchronous access to File, loading blocks from the input type=file
     * The whole file is loaded into memory during initialisation
     *
     * @constructor
     */
    function SyncFileBuffer(file)
    {
        this.file = file;
        this.byteLength = file.size;

        if(file.size > (1 << 30))
        {
            console.warn("SyncFileBuffer: Allocating buffer of " + (file.size >> 20) + " MB ...");
        }

        this.buffer = new ArrayBuffer(file.size);
        this.onload = undefined;
        this.onprogress = undefined;
    }

    SyncFileBuffer.prototype.load = function()
    {
        this.load_next(0);
    }

    /**
     * @param {number} start
     */
    SyncFileBuffer.prototype.load_next = function(start)
    {
        /** @const */
        var PART_SIZE = 4 << 20;

        var filereader = new FileReader();

        filereader.onload = function(e)
        {
            var buffer = new Uint8Array(e.target.result);
            new Uint8Array(this.buffer, start).set(buffer);
            this.load_next(start + PART_SIZE);
        }.bind(this);

        if(this.onprogress)
        {
            this.onprogress({
                loaded: start,
                total: this.byteLength,
                lengthComputable: true,
            });
        }

        if(start < this.byteLength)
        {
            var end = Math.min(start + PART_SIZE, this.byteLength);
            var slice = this.file.slice(start, end);
            filereader.readAsArrayBuffer(slice);
        }
        else
        {
            this.file = undefined;
            this.onload && this.onload({ buffer: this.buffer });
        }
    }

    /**
     * @param {number} start
     * @param {number} len
     * @param {function(!Uint8Array)} fn
     */
    SyncFileBuffer.prototype.get = function(start, len, fn)
    {
        console.assert(start + len <= this.byteLength);
        fn(new Uint8Array(this.buffer, start, len));
    };

    /**
     * @param {number} offset
     * @param {!Uint8Array} slice
     * @param {function()} fn
     */
    SyncFileBuffer.prototype.set = function(offset, slice, fn)
    {
        console.assert(offset + slice.byteLength <= this.byteLength);

        new Uint8Array(this.buffer, offset, slice.byteLength).set(slice);
        fn();
    };

    SyncFileBuffer.prototype.get_buffer = function(fn)
    {
        fn(this.buffer);
    };

    /**
     * Asynchronous access to File, loading blocks from the input type=file
     *
     * @constructor
     */
    function AsyncFileBuffer(file)
    {
        this.file = file;
        this.byteLength = file.size;

        /** @const */
        this.block_size = 256
        this.loaded_blocks = {};

        this.onload = undefined;
        this.onprogress = undefined;
    }

    AsyncFileBuffer.prototype.load = function()
    {
        this.onload && this.onload({});
    };

    /**
     * @param {number} offset
     * @param {number} len
     * @param {function(!Uint8Array)} fn
     */
    AsyncFileBuffer.prototype.get = function(offset, len, fn)
    {
        console.assert(offset % this.block_size === 0);
        console.assert(len % this.block_size === 0);
        console.assert(len);

        var fr = new FileReader();

        fr.onload = function(e)
        {
            var buffer = e.target.result;
            var block = new Uint8Array(buffer);

            this.handle_read(offset, len, block);
            fn(block);
        }.bind(this);

        fr.readAsArrayBuffer(this.file.slice(offset, offset + len));
    }
    AsyncFileBuffer.prototype.set = AsyncXHRBuffer.prototype.set;
    AsyncFileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read;

    AsyncFileBuffer.prototype.get_buffer = function(fn)
    {
        // We must load all parts, unlikely a good idea for big files
        fn();
    };

    AsyncFileBuffer.prototype.get_as_file = function(name)
    {
        var parts = [];
        var existing_blocks = Object.keys(this.loaded_blocks)
                                .map(Number)
                                .sort(function(x, y) { return x - y; });

        var current_offset = 0;

        for(var i = 0; i < existing_blocks.length; i++)
        {
            var block_index = existing_blocks[i];
            var block = this.loaded_blocks[block_index];
            var start = block_index * this.block_size;
            console.assert(start >= current_offset);

            if(start !== current_offset)
            {
                parts.push(this.file.slice(current_offset, start));
                current_offset = start;
            }

            parts.push(block);
            current_offset += block.length;
        }

        if(current_offset !== this.file.size)
        {
            parts.push(this.file.slice(current_offset));
        }

        var file = new File(parts, name);
        console.assert(file.size === this.file.size);

        return file;
    };

})();
<commit_msg>More efficient asynchronous loading<commit_after>"use strict";

(function()
{
    if(typeof XMLHttpRequest === "undefined")
    {
        v86util.load_file = load_file_nodejs;
    }
    else
    {
        v86util.load_file = load_file;
    }

    v86util.AsyncXHRBuffer = AsyncXHRBuffer;
    v86util.AsyncFileBuffer = AsyncFileBuffer;
    v86util.SyncFileBuffer = SyncFileBuffer;

    /**
     * @param {string} filename
     * @param {Object} options
     */
    function load_file(filename, options)
    {
        var http = new XMLHttpRequest();

        http.open(options.method || "get", filename, true);

        if(!options.as_text)
        {
            http.responseType = "arraybuffer";
        }

        if(options.headers)
        {
            var header_names = Object.keys(options.headers);

            for(var i = 0; i < header_names.length; i++)
            {
                var name = header_names[i];

                http.setRequestHeader(name, options.headers[name]);
            }
        }

        http.onload = function(e)
        {
            if(http.readyState === 4)
            {
                if(http.status !== 200 && http.status !== 206)
                {
                    console.log("Loading the image `" + filename + "` failed");
                }
                else if(http.response)
                {
                    options.done && options.done(http.response, http);
                }
            }
        };

        if(options.progress)
        {
            http.onprogress = function(e)
            {
                options.progress(e);
            };
        }

        http.send(null);
    }

    function load_file_nodejs(filename, options)
    {
        var o = {
            encoding: options.as_text ? "utf-8" : null,
        };

        require("fs")["readFile"](filename, o, function(err, data)
        {
            if(err)
            {
                console.log("Could not read file:", filename);
            }
            else
            {
                var result = data;

                if(!options.as_text)
                {
                    result = new Uint8Array(result).buffer;
                }

                options.done(result);
            }
        });
    }

    /**
     * Asynchronous access to ArrayBuffer, loading blocks lazily as needed,
     * using the `Range: bytes=...` header
     *
     * @constructor
     * @param {string} filename Name of the file to download
     * @param {number|undefined} size
     */
    function AsyncXHRBuffer(filename, size)
    {
        this.filename = filename;

        /** @const */
        this.block_size = 256;
        this.byteLength = size;

        this.loaded_blocks = {};

        this.onload = undefined;
        this.onprogress = undefined;
    }

    AsyncXHRBuffer.prototype.load = function()
    {
        if(this.byteLength !== undefined)
        {
            this.onload && this.onload({});
            return;
        }

        // Determine the size using a request

        load_file(this.filename, {
            done: function done(buffer, http)
            {
                var header = http.getResponseHeader("Content-Range") || "";
                var match = header.match(/\/(\d+)\s*$/);

                if(match)
                {
                    this.byteLength = +match[1]
                    this.onload && this.onload({});
                }
                else
                {
                    console.assert(false,
                        "Cannot use: " + this.filename + ". " +
                        "`Range: bytes=...` header not supported (Got `" + header + "`)");
                }
            }.bind(this),
            headers: {
                Range: "bytes=0-0",

                // Added by Chromium, but can cause the whole file to be sent
                // Settings this to empty also causes problems and Chromium
                // doesn't seem to create this header any more
                //"If-Range": "",
            }
        });
    }

    /**
     * @param {number} offset
     * @param {number} len
     * @param {function(!Uint8Array)} fn
     */
    AsyncXHRBuffer.prototype.get_from_cache = function(offset, len, fn)
    {
        var number_of_blocks = len / this.block_size;
        var block_index = offset / this.block_size;

        for(var i = 0; i < number_of_blocks; i++)
        {
            var block = this.loaded_blocks[block_index + i];

            if(!block)
            {
                return;
            }
        }

        console.log("From cache", number_of_blocks, len);

        if(number_of_blocks === 1)
        {
            return this.loaded_blocks[block_index];
        }
        else
        {
            var result = new Uint8Array(len);
            for(var i = 0; i < number_of_blocks; i++)
            {
                result.set(this.loaded_blocks[block_index + i], i * this.block_size);
            }
            return result;
        }
    };

    /**
     * @param {number} offset
     * @param {number} len
     * @param {function(!Uint8Array)} fn
     */
    AsyncXHRBuffer.prototype.get = function(offset, len, fn)
    {
        console.assert(offset % this.block_size === 0);
        console.assert(len % this.block_size === 0);
        console.assert(len);

        var block = this.get_from_cache(offset, len, fn);
        if(block)
        {
            fn(block);
            return;
        }

        var range_start = offset;
        var range_end = offset + len - 1;

        load_file(this.filename, {
            done: function done(buffer)
            {
                var block = new Uint8Array(buffer);
                this.handle_read(offset, len, block);
                fn(block);
            }.bind(this),
            headers: {
                Range: "bytes=" + range_start + "-" + range_end,
            }
        });
    }

    /**
     * Relies on this.byteLength, this.loaded_blocks and this.block_size
     *
     * @this {AsyncFileBuffer|AsyncXHRBuffer}
     *
     * @param {number} start
     * @param {!Uint8Array} data
     * @param {function()} fn
     */
    AsyncXHRBuffer.prototype.set = function(start, data, fn)
    {
        console.assert(start + data.byteLength <= this.byteLength);

        var len = data.length;

        console.assert(start % this.block_size === 0);
        console.assert(len % this.block_size === 0);
        console.assert(len);

        var start_block = start / this.block_size;
        var block_count = len / this.block_size;

        for(var i = 0; i < block_count; i++)
        {
            var block = this.loaded_blocks[start_block + i];

            if(block === undefined)
            {
                block = this.loaded_blocks[start_block + i] = new Uint8Array(this.block_size);
            }

            var data_slice = data.subarray(i * this.block_size, (i + 1) * this.block_size);
            block.set(data_slice);

            console.assert(block.byteLength === data_slice.length);
        }

        fn();
    }

    /**
     * @this {AsyncFileBuffer|AsyncXHRBuffer}
     * @param {number} offset
     * @param {number} len
     * @param {!Uint8Array} block
     */
    AsyncXHRBuffer.prototype.handle_read = function(offset, len, block)
    {
        // Used by AsyncXHRBuffer and AsyncFileBuffer
        // Overwrites blocks from the original source that have been written since

        var start_block = offset / this.block_size;
        var block_count = len / this.block_size;

        for(var i = 0; i < block_count; i++)
        {
            var written_block = this.loaded_blocks[start_block + i];

            if(written_block)
            {
                block.set(written_block, i * this.block_size);
            }
            //else
            //{
            //    var cached = this.loaded_blocks[start_block + i] = new Uint8Array(this.block_size);
            //    cached.set(block.subarray(i * this.block_size, (i + 1) * this.block_size));
            //}
        }
    };

    AsyncXHRBuffer.prototype.get_buffer = function(fn)
    {
        // We must download all parts, unlikely a good idea for big files
        fn();
    };

    /**
     * Synchronous access to File, loading blocks from the input type=file
     * The whole file is loaded into memory during initialisation
     *
     * @constructor
     */
    function SyncFileBuffer(file)
    {
        this.file = file;
        this.byteLength = file.size;

        if(file.size > (1 << 30))
        {
            console.warn("SyncFileBuffer: Allocating buffer of " + (file.size >> 20) + " MB ...");
        }

        this.buffer = new ArrayBuffer(file.size);
        this.onload = undefined;
        this.onprogress = undefined;
    }

    SyncFileBuffer.prototype.load = function()
    {
        this.load_next(0);
    }

    /**
     * @param {number} start
     */
    SyncFileBuffer.prototype.load_next = function(start)
    {
        /** @const */
        var PART_SIZE = 4 << 20;

        var filereader = new FileReader();

        filereader.onload = function(e)
        {
            var buffer = new Uint8Array(e.target.result);
            new Uint8Array(this.buffer, start).set(buffer);
            this.load_next(start + PART_SIZE);
        }.bind(this);

        if(this.onprogress)
        {
            this.onprogress({
                loaded: start,
                total: this.byteLength,
                lengthComputable: true,
            });
        }

        if(start < this.byteLength)
        {
            var end = Math.min(start + PART_SIZE, this.byteLength);
            var slice = this.file.slice(start, end);
            filereader.readAsArrayBuffer(slice);
        }
        else
        {
            this.file = undefined;
            this.onload && this.onload({ buffer: this.buffer });
        }
    }

    /**
     * @param {number} start
     * @param {number} len
     * @param {function(!Uint8Array)} fn
     */
    SyncFileBuffer.prototype.get = function(start, len, fn)
    {
        console.assert(start + len <= this.byteLength);
        fn(new Uint8Array(this.buffer, start, len));
    };

    /**
     * @param {number} offset
     * @param {!Uint8Array} slice
     * @param {function()} fn
     */
    SyncFileBuffer.prototype.set = function(offset, slice, fn)
    {
        console.assert(offset + slice.byteLength <= this.byteLength);

        new Uint8Array(this.buffer, offset, slice.byteLength).set(slice);
        fn();
    };

    SyncFileBuffer.prototype.get_buffer = function(fn)
    {
        fn(this.buffer);
    };

    /**
     * Asynchronous access to File, loading blocks from the input type=file
     *
     * @constructor
     */
    function AsyncFileBuffer(file)
    {
        this.file = file;
        this.byteLength = file.size;

        /** @const */
        this.block_size = 256
        this.loaded_blocks = {};

        this.onload = undefined;
        this.onprogress = undefined;
    }

    AsyncFileBuffer.prototype.load = function()
    {
        this.onload && this.onload({});
    };

    /**
     * @param {number} offset
     * @param {number} len
     * @param {function(!Uint8Array)} fn
     */
    AsyncFileBuffer.prototype.get = function(offset, len, fn)
    {
        console.assert(offset % this.block_size === 0);
        console.assert(len % this.block_size === 0);
        console.assert(len);

        var block = this.get_from_cache(offset, len, fn);
        if(block)
        {
            fn(block);
            return;
        }

        var fr = new FileReader();

        fr.onload = function(e)
        {
            var buffer = e.target.result;
            var block = new Uint8Array(buffer);

            this.handle_read(offset, len, block);
            fn(block);
        }.bind(this);

        fr.readAsArrayBuffer(this.file.slice(offset, offset + len));
    }
    AsyncFileBuffer.prototype.get_from_cache = AsyncXHRBuffer.prototype.get_from_cache;
    AsyncFileBuffer.prototype.set = AsyncXHRBuffer.prototype.set;
    AsyncFileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read;

    AsyncFileBuffer.prototype.get_buffer = function(fn)
    {
        // We must load all parts, unlikely a good idea for big files
        fn();
    };

    AsyncFileBuffer.prototype.get_as_file = function(name)
    {
        var parts = [];
        var existing_blocks = Object.keys(this.loaded_blocks)
                                .map(Number)
                                .sort(function(x, y) { return x - y; });

        var current_offset = 0;

        for(var i = 0; i < existing_blocks.length; i++)
        {
            var block_index = existing_blocks[i];
            var block = this.loaded_blocks[block_index];
            var start = block_index * this.block_size;
            console.assert(start >= current_offset);

            if(start !== current_offset)
            {
                parts.push(this.file.slice(current_offset, start));
                current_offset = start;
            }

            parts.push(block);
            current_offset += block.length;
        }

        if(current_offset !== this.file.size)
        {
            parts.push(this.file.slice(current_offset));
        }

        var file = new File(parts, name);
        console.assert(file.size === this.file.size);

        return file;
    };

})();
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var citizen = require('citizen');
var config = require('config');
var request = require('request');
var SideComments = require('side-comments');
var StatefulView = require('stateful-view');
var t = require('t');
var o = require('dom');
var template = require('./template');
var log = require('debug')('democracyos:proposal-clause');

/**
 * Expose `ProposalClauses`
 */

module.exports = ProposalClauses;

function ProposalClauses(law) {
  StatefulView.call(this, template, { clauses: law.clauses });

  this.law = law;
  this.loadComments();
}

StatefulView(ProposalClauses);

ProposalClauses.prototype.loadComments = function() {
  this
    .load()
    .ready(this.sideComments.bind(this));
};

ProposalClauses.prototype.load = function(clauseId) {
  var self = this;

  request
    .get('/api/law/:id/clause-comments'.replace(':id', this.law.id))
    .end(function (err, res) {
      if (res.status !== 200) ; //log error

      self.comments = res.body;

      request
        .get('/api/law/:id/summary-comments'.replace(':id', self.law.id))
        .end(function (err, res) {
          if (res.status !== 200) ; //log error

          self.comments = self.comments.concat(res.body);
          self.state('loaded');
        });
    });

  return this;
};

ProposalClauses.prototype.sideComments = function () {
  var self = this;
  var userComment = citizen.id ? { id: citizen.id, avatarUrl: citizen.profilePictureUrl ? citizen.profilePictureUrl : citizen.gravatar, name: citizen.firstName + ' ' + citizen.lastName, isAdmin: citizen.staff } : null;
  var sections = [];
  var law = this.law;
  var summary = law.summary;

  if (isHTML(summary)) {
    log('This topic has been generated with Quill');
    summary = o(summary);
    var paragraphs = summary[0].children;
    for (var i = 0; i < paragraphs.length; i++) {
      var paragraph = paragraphs[i];
      var section = {};
      section.sectionId = law.id + '-' + i;
      section.comments = self.comments
        .filter(function (comment) { return comment.reference == law.id + '-' + i })
        .map(self.formatComment);

      sections.push(section);
    }
  } else {
    log('This law has been generated in the traditional way (with clauses)');
    var paragraphs = summary.split("\n");
    var i = 0;
    paragraphs.forEach(function (paragraph) {
      if ('' !== paragraph) {
        var section = {};
        section.sectionId = law.id + '-' + i;

        function filterBySection(comment) {
          return comment.reference == law.id + '-' + i;
        }

        section.comments = self.comments.filter(filterBySection).map(self.formatComment);
        sections.push(section);
      }
      i++;
    })
    law.clauses.forEach(function (clause) {
      var section = {};
      section.sectionId = clause.id;

      function filterBySection(comment) {
        return comment.reference == clause.id;
      }

      section.comments = self.comments.filter(filterBySection).map(self.formatComment);
      sections.push(section);
    });
  }

  var sideComments = self.sideComments = new SideComments('.commentable-container', userComment, sections, { trans: t, locale: config.locale });
  sideComments.on('commentPosted', self.onClauseCommentPosted.bind(self));
  sideComments.on('commentUpdated', self.onClauseCommentUpdated.bind(self));
  sideComments.on('commentDeleted', self.onClauseCommentDeleted.bind(self));
}

ProposalClauses.prototype.formatComment = function (comment) {
  comment.authorAvatarUrl = comment.author.profilePictureUrl ? comment.author.profilePictureUrl : comment.author.gravatar;
  comment.authorName = comment.author.fullName;
  comment.authorId = comment.author.id;
  comment.comment = comment.text;
  comment.sectionId = comment.reference;
  return comment;
}

ProposalClauses.prototype.onClauseCommentPosted = function(comment) {
  var self = this;
  var clauseComment = {};
  clauseComment.reference = comment.sectionId;
  if (~comment.sectionId.indexOf('-')) {
    clauseComment.context = 'summary';
  } else {
    clauseComment.context = 'clause';
  }
  clauseComment.text = comment.comment;
  request
  .post(
    '/api/:context/:reference/comment'
    .replace(':context', 'law')
    .replace(':reference', comment.sectionId))
  .send({ comment: clauseComment })
  .end(function(err, res) {
    if (err) return log('Error when posting clause comment %s', err);
    var comment = self.formatComment(res.body);
    self.sideComments.insertComment(comment);
  });
};

ProposalClauses.prototype.onClauseCommentUpdated = function(comment) {
  var self = this;

  request
  .put(
    '/api/comment/' + comment.id)
  .send({ text: comment.comment, reference: comment.sectionId })
  .end(function(err, res) {
    if (err) return log('Error when putting clause comment %s', err);
    var comment = self.formatComment(res.body);
    self.sideComments.replaceComment(comment);
  });
};

ProposalClauses.prototype.onClauseCommentDeleted = function(comment) {
  var self = this;
  request
    .del('/api/comment/:id'
      .replace(':id', comment.id))
    .end(function(err, res) {
      if (err) return log('Fetch error: %s', err);
      if (!res.ok) err = res.error, log('Fetch error: %s', err);
      if (res.body && res.body.error) err = res.body.error, log('Fetch response error: %s', err);

      self.sideComments.removeComment(comment.sectionId, comment.id);
    });
};

/**
 * Check if the string is HTML
 *
 * @param {String} str
 * @return {Boolean}
 * @api private
 */

function isHTML(str) {
  // Faster than running regex, if str starts with `<` and ends with `>`, assume it's HTML
  if (str.charAt(0) === '<' && str.charAt(str.length - 1) === '>' && str.length >= 3) return true;

  // Run the regex
  var match = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/.exec(str);
  return !!(match && match[1]);
}
<commit_msg>Fix side comments load #807<commit_after>/**
 * Module dependencies.
 */

var citizen = require('citizen');
var config = require('config');
var request = require('request');
var SideComments = require('side-comments');
var StatefulView = require('stateful-view');
var t = require('t');
var o = require('dom');
var template = require('./template');
var log = require('debug')('democracyos:proposal-clause');

/**
 * Expose `ProposalClauses`
 */

module.exports = ProposalClauses;

function ProposalClauses(law) {
  StatefulView.call(this, template, { clauses: law.clauses });

  this.law = law;
  this.loadComments();
}

StatefulView(ProposalClauses);

ProposalClauses.prototype.loadComments = function() {
  this
    .load()
    .ready(this.sideComments.bind(this));
};

ProposalClauses.prototype.load = function(clauseId) {
  var self = this;

  request
    .get('/api/law/:id/clause-comments'.replace(':id', this.law.id))
    .end(function (err, res) {
      if (res.status !== 200) ; //log error

      self.comments = res.body;

      request
        .get('/api/law/:id/summary-comments'.replace(':id', self.law.id))
        .end(function (err, res) {
          if (res.status !== 200) ; //log error

          self.comments = self.comments.concat(res.body);
          self.state('loaded');
        });
    });

  return this;
};

ProposalClauses.prototype.sideComments = function () {
  var self = this;
  var law = this.law;
  var className = '.commentable-container';

  var userComment = citizen.id ? {
    id: citizen.id,
    avatarUrl: citizen.profilePictureUrl || citizen.gravatar,
    name: citizen.firstName + ' ' + citizen.lastName,
    isAdmin: citizen.staff
  } : null;

  var sections;
  if (isHTML(law.summary)) {
    sections = this.getSections(law);
  } else {
    sections = this.getClausesSections(law);
  }

  var locals = { trans: t, locale: config.locale };

  var sideComments = new SideComments(className, userComment, sections, locals);
  sideComments.on('commentPosted', self.onClauseCommentPosted.bind(self));
  sideComments.on('commentUpdated', self.onClauseCommentUpdated.bind(self));
  sideComments.on('commentDeleted', self.onClauseCommentDeleted.bind(self));

  self.sideComments = sideComments;
}

ProposalClauses.prototype.getSections = function (law) {
  log('This topic has been generated with Quill');
  var self = this;
  var sections = [];
  var summary = o(law.summary);
  var paragraphs = summary[0].childNodes;

  function byReference(index) {
    return function (comment) {
      return comment.reference == law.id + '-' + index;
    }
  }

  for (var i = 0; i < paragraphs.length; i++) {
    var section = {};
    section.sectionId = law.id + '-' + i;
    section.comments = self.comments
      .filter(byReference(i))
      .map(self.formatComment);

    sections.push(section);
  }

  return sections;
}

ProposalClauses.prototype.getClausesSections = function (law) {
  log('This law has been generated in the traditional way (with clauses)');
  var self = this;
  var paragraphs = law.summary.split('\n');
  var sections = [];

  function filterBySection(comment) {
    return comment.reference == law.id + '-' + i;
  }

  var i = 0;
  paragraphs.forEach(function (paragraph) {
    if (!paragraph) return;
    var section = {};
    section.sectionId = law.id + '-' + i;
    section.comments = self.comments
      .filter(filterBySection)
      .map(self.formatComment);
    sections.push(section);
    i++;
  })

  law.clauses.forEach(function (clause) {
    var section = {};
    section.sectionId = clause.id;

    function filterBySection(comment) {
      return comment.reference == clause.id;
    }

    section.comments = self.comments.filter(filterBySection).map(self.formatComment);
    sections.push(section);
  });

  return sections;
}

ProposalClauses.prototype.formatComment = function (comment) {
  comment.authorAvatarUrl = comment.author.profilePictureUrl ? comment.author.profilePictureUrl : comment.author.gravatar;
  comment.authorName = comment.author.fullName;
  comment.authorId = comment.author.id;
  comment.comment = comment.text;
  comment.sectionId = comment.reference;
  return comment;
}

ProposalClauses.prototype.onClauseCommentPosted = function(comment) {
  var self = this;
  var clauseComment = {};
  clauseComment.reference = comment.sectionId;
  if (~comment.sectionId.indexOf('-')) {
    clauseComment.context = 'summary';
  } else {
    clauseComment.context = 'clause';
  }
  clauseComment.text = comment.comment;
  request
  .post(
    '/api/:context/:reference/comment'
    .replace(':context', 'law')
    .replace(':reference', comment.sectionId))
  .send({ comment: clauseComment })
  .end(function(err, res) {
    if (err) return log('Error when posting clause comment %s', err);
    var comment = self.formatComment(res.body);
    self.sideComments.insertComment(comment);
  });
};

ProposalClauses.prototype.onClauseCommentUpdated = function(comment) {
  var self = this;

  request
  .put(
    '/api/comment/' + comment.id)
  .send({ text: comment.comment, reference: comment.sectionId })
  .end(function(err, res) {
    if (err) return log('Error when putting clause comment %s', err);
    var comment = self.formatComment(res.body);
    self.sideComments.replaceComment(comment);
  });
};

ProposalClauses.prototype.onClauseCommentDeleted = function(comment) {
  var self = this;
  request
    .del('/api/comment/:id'
      .replace(':id', comment.id))
    .end(function(err, res) {
      if (err) return log('Fetch error: %s', err);
      if (!res.ok) err = res.error, log('Fetch error: %s', err);
      if (res.body && res.body.error) err = res.body.error, log('Fetch response error: %s', err);

      self.sideComments.removeComment(comment.sectionId, comment.id);
    });
};

/**
 * Check if the string is HTML
 *
 * @param {String} str
 * @return {Boolean}
 * @api private
 */

function isHTML(str) {
  // Faster than running regex, if str starts with `<` and ends with `>`, assume it's HTML
  if (str.charAt(0) === '<' && str.charAt(str.length - 1) === '>' && str.length >= 3) return true;

  // Run the regex
  var match = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/.exec(str);
  return !!(match && match[1]);
}
<|endoftext|>


<commit_before>/**
 * This module exports functions for checking types
 * and throwing exceptions.
 */

/*globals define, module */

(function (globals) {
    'use strict';

    var functions = {
        verifyQuack: verifyQuack,
        quacksLike: quacksLike,
        verifyInstance: verifyInstance,
        isInstance: isInstance,
        verifyEmptyObject: verifyEmptyObject,
        isEmptyObject: isEmptyObject,
        verifyObject: verifyObject,
        isObject: isObject,
        verifyLength: verifyLength,
        isLength: isLength,
        verifyArray: verifyArray,
        isArray: isArray,
        verifyDate: verifyDate,
        isDate: isDate,
        verifyFunction: verifyFunction,
        isFunction: isFunction,
        verifyUnemptyString: verifyUnemptyString,
        isUnemptyString:isUnemptyString,
        verifyString: verifyString,
        isString: isString,
        verifyEvenNumber: verifyEvenNumber,
        isEvenNumber: isEvenNumber,
        verifyOddNumber: verifyOddNumber,
        isOddNumber: isOddNumber,
        verifyPositiveNumber: verifyPositiveNumber,
        isPositiveNumber: isPositiveNumber,
        verifyNegativeNumber: verifyNegativeNumber,
        isNegativeNumber: isNegativeNumber,
        verifyNumber: verifyNumber,
        isNumber: isNumber,
        map: map,
        every: every,
        any: any
    };

    exportFunctions();

    /**
     * Public function `verifyQuack`.
     *
     * Throws an exception if an object does not share
     * the properties of a second, archetypal object
     * (i.e. doesn't 'quack like a duck').
     *
     * @param thing {object}     The object to test.
     * @param duck {object}      The archetypal object,
     *                           or 'duck', that the test
     *                           is against.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyQuack (thing, duck, message) {
        verify(quacksLike, [ thing, duck ], message, 'Invalid type');
    }

    function verify (fn, args, message, defaultMessage) {
        if (fn.apply(null, args) === false) {
            throw new Error(message || defaultMessage);
        }
    }

    /**
     * Public function `quacksLike`.
     *
     * Tests whether an object 'quacks like a duck'.
     * Returns `true` if the first argument has all of
     * the properties of the second, archetypal argument
     * (the 'duck'). Returns `false` otherwise. If either
     * argument is not an object, an exception is thrown.
     *
     * @param thing {object} The object to test.
     * @param duck {object}  The archetypal object, or
     *                       'duck', that the test is
     *                       against.
     */
    function quacksLike (thing, duck) {
        var property;

        verifyObject(thing);
        verifyObject(duck);

        for (property in duck) {
            if (duck.hasOwnProperty(property)) {
                if (thing.hasOwnProperty(property) === false) {
                    return false;
                }

                if (typeof thing[property] !== typeof duck[property]) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Public function `verifyInstance`.
     *
     * Throws an exception if an object is not an instance
     * of a prototype.
     *
     * @param thing {object}       The object to test.
     * @param prototype {function} The prototype that the
     *                             test is against.
     * @param [message] {string}   An optional error message
     *                             to set on the thrown Error.
     */
    function verifyInstance (thing, prototype, message) {
        verify(isInstance, [ thing, prototype ], message, 'Invalid type');
    }

    /**
     * Public function `isInstance`.
     *
     * Returns `true` if an object is an instance of a prototype,
     * `false` otherwise.
     *
     * @param thing {object}       The object to test.
     * @param prototype {function} The prototype that the
     *                             test is against.
     */
    function isInstance (thing, prototype) {
        if (typeof thing === 'undefined' || thing === null) {
            return false;
        }

        if (isFunction(prototype) && thing instanceof prototype) {
            return true;
        }

        return false;
    }

    /**
     * Public function `verifyEmptyObject`.
     *
     * Throws an exception unless something is an empty, non-null,
     * non-array object.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyEmptyObject (thing, message) {
        verify(isEmptyObject, [ thing ], message, 'Invalid object');
    }

    /**
     * Public function `isEmptyObject`.
     *
     * Returns `true` if something is an empty, non-null, non-array object, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isEmptyObject (thing) {
        var property;

        if (isObject(thing)) {
            for (property in thing) {
                if (thing.hasOwnProperty(property)) {
                    return false;
                }
            }

            return true;
        }

        return false;
    }

    /**
     * Public function `verifyObject`.
     *
     * Throws an exception unless something is a non-null,
     * non-array, non-date object.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyObject (thing, message) {
        verify(isObject, [ thing ], message, 'Invalid object');
    }

    /**
     * Public function `isObject`.
     *
     * Returns `true` if something is a non-null, non-array,
     * non-date object, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isObject (thing) {
        return typeof thing === 'object' && thing !== null && isArray(thing) === false && isDate(thing) === false;
    }

    /**
     * Public function `verifyLength`.
     *
     * Throws an exception unless something is a non-null,
     * non-array object.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyLength (thing, length, message) {
        verify(isLength, [ thing, length ], message, 'Invalid length');
    }

    /**
     * Public function `isLength`.
     *
     * Returns `true` if something is has a length property
     * matching the specified value, `false` otherwise.
     *
     * @param thing  The thing to test.
     * @param length The required length to test against.
     */
    function isLength (thing, length) {
        return thing && thing.length === length;
    }

    /**
     * Public function `verifyArray`.
     *
     * Throws an exception unless something is an array.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyArray (thing, message) {
        verify(isArray, [ thing ], message, 'Invalid array');
    }

    /**
     * Public function `isArray`.
     *
     * Returns `true` something is an array, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isArray (thing) {
        if (Array.isArray) {
            return Array.isArray(thing);
        }

        return Object.prototype.toString.call(thing) === '[object Array]';
    }

    /**
     * Public function `verifyDate`.
     *
     * Throws an exception unless something is a date.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyDate (thing, message) {
        verify(isDate, [ thing ], message, 'Invalid date');
    }

    /**
     * Public function `isDate`.
     *
     * Returns `true` something is a date, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isDate (thing) {
        return Object.prototype.toString.call(thing) === '[object Date]';
    }

    /**
     * Public function `verifyFunction`.
     *
     * Throws an exception unless something is function.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyFunction (thing, message) {
        verify(isFunction, [ thing ], message, 'Invalid function');
    }

    /**
     * Public function `isFunction`.
     *
     * Returns `true` if something is function, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isFunction (thing) {
        return typeof thing === 'function';
    }

    /**
     * Public function `verifyUnemptyString`.
     *
     * Throws an exception unless something is a non-empty string.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyUnemptyString (thing, message) {
        verify(isUnemptyString, [ thing ], message, 'Invalid string');
    }

    /**
     * Public function `isUnemptyString`.
     *
     * Returns `true` if something is a non-empty string, `false`
     * otherwise.
     *
     * @param thing          The thing to test.
     */
    function isUnemptyString (thing) {
        return isString(thing) && thing !== '';
    }

    /**
     * Public function `verifyString`.
     *
     * Throws an exception unless something is a string.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyString (thing, message) {
        verify(isString, [ thing ], message, 'Invalid string');
    }

    /**
     * Public function `isString`.
     *
     * Returns `true` if something is a string, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isString (thing) {
        return typeof thing === 'string';
    }

    /**
     * Public function `verifyOddNumber`.
     *
     * Throws an exception unless something is an odd number.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyOddNumber (thing, message) {
        verify(isOddNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isOddNumber`.
     *
     * Returns `true` if something is an odd number,
     * `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isOddNumber (thing) {
        return isNumber(thing) && (thing % 2 === 1 || thing % 2 === -1);
    }

    /**
     * Public function `verifyEvenNumber`.
     *
     * Throws an exception unless something is an even number.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyEvenNumber (thing, message) {
        verify(isEvenNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isEvenNumber`.
     *
     * Returns `true` if something is an even number,
     * `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isEvenNumber (thing) {
        return isNumber(thing) && thing % 2 === 0;
    }

    /**
     * Public function `verifyPositiveNumber`.
     *
     * Throws an exception unless something is a positive number.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyPositiveNumber (thing, message) {
        verify(isPositiveNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isPositiveNumber`.
     *
     * Returns `true` if something is a positive number,
     * `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isPositiveNumber (thing) {
        return isNumber(thing) && thing > 0;
    }

    /**
     * Public function `verifyNegativeNumber`.
     *
     * Throws an exception unless something is a positive number.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyNegativeNumber (thing, message) {
        verify(isNegativeNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isNegativeNumber`.
     *
     * Returns `true` if something is a positive number,
     * `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isNegativeNumber (thing) {
        return isNumber(thing) && thing < 0;
    }

    /**
     * Public function `verifyNumber`.
     *
     * Throws an exception unless something is a number, excluding NaN.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyNumber (thing, message) {
        verify(isNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isNumber`.
     *
     * Returns `true` if something is a number other than NaN,
     * `false` otherwise.
     *
     * @param thing The thing to test.
     */
    function isNumber (thing) {
        return typeof thing === 'number' && isNaN(thing) === false;
    }

    function exportFunctions () {
        if (typeof define === 'function' && define.amd) {
            define(function () {
                return functions;
            });
        } else if (typeof module !== 'undefined' && module !== null) {
            module.exports = functions;
        } else {
            globals.check = functions;
        }
    }

    /**
     * Public function `map`.
     *
     * Returns the result of mapping each predicate to the corresponding
     * object's property. Similar to `quacksLike` but takes a schema of
     * predicate functions instead of a *duck* and returns the result of
     * each predicate instead of a boolean.
     *
     * @param thing The thing to test.
     * @param predicates The associative array of predicates to test
     *                   against each of thing's corresponding property.
     */
    function map (thing, predicates) {
        var property,
            result = {};

        verifyObject(thing);
        verifyObject(predicates);

        for (property in predicates) {
            if (predicates.hasOwnProperty(property)) {
                var predicate = predicates[property];

                if (isFunction(predicate)) {
                    result[property] = thing.hasOwnProperty(property) ?
                        predicate(thing[property]) :
                        undefined;
                }

                else if (isObject(predicate)) {
                    result[property] = thing.hasOwnProperty(property) ?
                        map(thing[property], predicate) :
                        undefined;
                }
            }
        }
        return result;
    }

    /**
     * Public function `every`
     *
     * Returns the conjunction of all booleans in a hash.
     *
     * @param mappedPredicates The hash of evaluated predicates
     */
    function every (mappedPredicates) {
        var property;

        verifyObject(mappedPredicates);

        for (property in mappedPredicates) {
            if (mappedPredicates.hasOwnProperty(property)) {
                var value = mappedPredicates[property];

                if (isObject(value) && !every(value)) {
                    return false;
                }

                else if (!value) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Public function `any`
     *
     * Returns the disjunction of all booleans in a hash.
     *
     * @param mappedPredicates The has of evaluated predicates
     */
    function any (mappedPredicates) {
        var property;

        verifyObject(mappedPredicates);

        for (property in mappedPredicates) {
            if (mappedPredicates.hasOwnProperty(property)) {
                var value = mappedPredicates[property];

                if (isObject(value) && any(value)) {
                    return true;
                }

                else if (value === true) {
                    return true;
                }
            }
        }

        return false;
    }
}(this));
<commit_msg>uniform code style formatting<commit_after>/**
 * This module exports functions for checking types
 * and throwing exceptions.
 */

/*globals define, module */

(function (globals) {
    'use strict';

    var functions = {
        verifyQuack: verifyQuack,
        quacksLike: quacksLike,
        verifyInstance: verifyInstance,
        isInstance: isInstance,
        verifyEmptyObject: verifyEmptyObject,
        isEmptyObject: isEmptyObject,
        verifyObject: verifyObject,
        isObject: isObject,
        verifyLength: verifyLength,
        isLength: isLength,
        verifyArray: verifyArray,
        isArray: isArray,
        verifyDate: verifyDate,
        isDate: isDate,
        verifyFunction: verifyFunction,
        isFunction: isFunction,
        verifyUnemptyString: verifyUnemptyString,
        isUnemptyString:isUnemptyString,
        verifyString: verifyString,
        isString: isString,
        verifyEvenNumber: verifyEvenNumber,
        isEvenNumber: isEvenNumber,
        verifyOddNumber: verifyOddNumber,
        isOddNumber: isOddNumber,
        verifyPositiveNumber: verifyPositiveNumber,
        isPositiveNumber: isPositiveNumber,
        verifyNegativeNumber: verifyNegativeNumber,
        isNegativeNumber: isNegativeNumber,
        verifyNumber: verifyNumber,
        isNumber: isNumber,
        map: map,
        every: every,
        any: any
    };

    exportFunctions();

    /**
     * Public function `verifyQuack`.
     *
     * Throws an exception if an object does not share
     * the properties of a second, archetypal object
     * (i.e. doesn't 'quack like a duck').
     *
     * @param thing {object}     The object to test.
     * @param duck {object}      The archetypal object,
     *                           or 'duck', that the test
     *                           is against.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyQuack (thing, duck, message) {
        verify(quacksLike, [ thing, duck ], message, 'Invalid type');
    }

    function verify (fn, args, message, defaultMessage) {
        if (fn.apply(null, args) === false) {
            throw new Error(message || defaultMessage);
        }
    }

    /**
     * Public function `quacksLike`.
     *
     * Tests whether an object 'quacks like a duck'.
     * Returns `true` if the first argument has all of
     * the properties of the second, archetypal argument
     * (the 'duck'). Returns `false` otherwise. If either
     * argument is not an object, an exception is thrown.
     *
     * @param thing {object} The object to test.
     * @param duck {object}  The archetypal object, or
     *                       'duck', that the test is
     *                       against.
     */
    function quacksLike (thing, duck) {
        var property;

        verifyObject(thing);
        verifyObject(duck);

        for (property in duck) {
            if (duck.hasOwnProperty(property)) {
                if (thing.hasOwnProperty(property) === false) {
                    return false;
                }

                if (typeof thing[property] !== typeof duck[property]) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Public function `verifyInstance`.
     *
     * Throws an exception if an object is not an instance
     * of a prototype.
     *
     * @param thing {object}       The object to test.
     * @param prototype {function} The prototype that the
     *                             test is against.
     * @param [message] {string}   An optional error message
     *                             to set on the thrown Error.
     */
    function verifyInstance (thing, prototype, message) {
        verify(isInstance, [ thing, prototype ], message, 'Invalid type');
    }

    /**
     * Public function `isInstance`.
     *
     * Returns `true` if an object is an instance of a prototype,
     * `false` otherwise.
     *
     * @param thing {object}       The object to test.
     * @param prototype {function} The prototype that the
     *                             test is against.
     */
    function isInstance (thing, prototype) {
        if (typeof thing === 'undefined' || thing === null) {
            return false;
        }

        if (isFunction(prototype) && thing instanceof prototype) {
            return true;
        }

        return false;
    }

    /**
     * Public function `verifyEmptyObject`.
     *
     * Throws an exception unless something is an empty, non-null,
     * non-array object.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyEmptyObject (thing, message) {
        verify(isEmptyObject, [ thing ], message, 'Invalid object');
    }

    /**
     * Public function `isEmptyObject`.
     *
     * Returns `true` if something is an empty, non-null, non-array object, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isEmptyObject (thing) {
        var property;

        if (isObject(thing)) {
            for (property in thing) {
                if (thing.hasOwnProperty(property)) {
                    return false;
                }
            }

            return true;
        }

        return false;
    }

    /**
     * Public function `verifyObject`.
     *
     * Throws an exception unless something is a non-null,
     * non-array, non-date object.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyObject (thing, message) {
        verify(isObject, [ thing ], message, 'Invalid object');
    }

    /**
     * Public function `isObject`.
     *
     * Returns `true` if something is a non-null, non-array,
     * non-date object, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isObject (thing) {
        return typeof thing === 'object' && thing !== null && isArray(thing) === false && isDate(thing) === false;
    }

    /**
     * Public function `verifyLength`.
     *
     * Throws an exception unless something is a non-null,
     * non-array object.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyLength (thing, length, message) {
        verify(isLength, [ thing, length ], message, 'Invalid length');
    }

    /**
     * Public function `isLength`.
     *
     * Returns `true` if something is has a length property
     * matching the specified value, `false` otherwise.
     *
     * @param thing  The thing to test.
     * @param length The required length to test against.
     */
    function isLength (thing, length) {
        return thing && thing.length === length;
    }

    /**
     * Public function `verifyArray`.
     *
     * Throws an exception unless something is an array.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyArray (thing, message) {
        verify(isArray, [ thing ], message, 'Invalid array');
    }

    /**
     * Public function `isArray`.
     *
     * Returns `true` something is an array, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isArray (thing) {
        if (Array.isArray) {
            return Array.isArray(thing);
        }

        return Object.prototype.toString.call(thing) === '[object Array]';
    }

    /**
     * Public function `verifyDate`.
     *
     * Throws an exception unless something is a date.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyDate (thing, message) {
        verify(isDate, [ thing ], message, 'Invalid date');
    }

    /**
     * Public function `isDate`.
     *
     * Returns `true` something is a date, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isDate (thing) {
        return Object.prototype.toString.call(thing) === '[object Date]';
    }

    /**
     * Public function `verifyFunction`.
     *
     * Throws an exception unless something is function.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyFunction (thing, message) {
        verify(isFunction, [ thing ], message, 'Invalid function');
    }

    /**
     * Public function `isFunction`.
     *
     * Returns `true` if something is function, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isFunction (thing) {
        return typeof thing === 'function';
    }

    /**
     * Public function `verifyUnemptyString`.
     *
     * Throws an exception unless something is a non-empty string.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyUnemptyString (thing, message) {
        verify(isUnemptyString, [ thing ], message, 'Invalid string');
    }

    /**
     * Public function `isUnemptyString`.
     *
     * Returns `true` if something is a non-empty string, `false`
     * otherwise.
     *
     * @param thing          The thing to test.
     */
    function isUnemptyString (thing) {
        return isString(thing) && thing !== '';
    }

    /**
     * Public function `verifyString`.
     *
     * Throws an exception unless something is a string.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyString (thing, message) {
        verify(isString, [ thing ], message, 'Invalid string');
    }

    /**
     * Public function `isString`.
     *
     * Returns `true` if something is a string, `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isString (thing) {
        return typeof thing === 'string';
    }

    /**
     * Public function `verifyOddNumber`.
     *
     * Throws an exception unless something is an odd number.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyOddNumber (thing, message) {
        verify(isOddNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isOddNumber`.
     *
     * Returns `true` if something is an odd number,
     * `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isOddNumber (thing) {
        return isNumber(thing) && (thing % 2 === 1 || thing % 2 === -1);
    }

    /**
     * Public function `verifyEvenNumber`.
     *
     * Throws an exception unless something is an even number.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyEvenNumber (thing, message) {
        verify(isEvenNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isEvenNumber`.
     *
     * Returns `true` if something is an even number,
     * `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isEvenNumber (thing) {
        return isNumber(thing) && thing % 2 === 0;
    }

    /**
     * Public function `verifyPositiveNumber`.
     *
     * Throws an exception unless something is a positive number.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyPositiveNumber (thing, message) {
        verify(isPositiveNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isPositiveNumber`.
     *
     * Returns `true` if something is a positive number,
     * `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isPositiveNumber (thing) {
        return isNumber(thing) && thing > 0;
    }

    /**
     * Public function `verifyNegativeNumber`.
     *
     * Throws an exception unless something is a positive number.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyNegativeNumber (thing, message) {
        verify(isNegativeNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isNegativeNumber`.
     *
     * Returns `true` if something is a positive number,
     * `false` otherwise.
     *
     * @param thing          The thing to test.
     */
    function isNegativeNumber (thing) {
        return isNumber(thing) && thing < 0;
    }

    /**
     * Public function `verifyNumber`.
     *
     * Throws an exception unless something is a number, excluding NaN.
     *
     * @param thing              The thing to test.
     * @param [message] {string} An optional error message
     *                           to set on the thrown Error.
     */
    function verifyNumber (thing, message) {
        verify(isNumber, [ thing ], message, 'Invalid number');
    }

    /**
     * Public function `isNumber`.
     *
     * Returns `true` if something is a number other than NaN,
     * `false` otherwise.
     *
     * @param thing The thing to test.
     */
    function isNumber (thing) {
        return typeof thing === 'number' && isNaN(thing) === false;
    }

    function exportFunctions () {
        if (typeof define === 'function' && define.amd) {
            define(function () {
                return functions;
            });
        } else if (typeof module !== 'undefined' && module !== null) {
            module.exports = functions;
        } else {
            globals.check = functions;
        }
    }

    /**
     * Public function `map`.
     *
     * Returns the result of mapping each predicate to the corresponding
     * object's property. Similar to `quacksLike` but takes a schema of
     * predicate functions instead of a *duck* and returns the result of
     * each predicate instead of a boolean.
     *
     * @param thing The thing to test.
     * @param predicates The associative array of predicates to test
     *                   against each of thing's corresponding property.
     */
    function map (thing, predicates) {
        var property,
            result = {};
        verifyObject(thing);
        verifyObject(predicates);

        for (property in predicates) {
            if (predicates.hasOwnProperty(property)) {
                var predicate = predicates[property];
                if (isFunction(predicate)) {
                    result[property] = thing.hasOwnProperty(property) ?
                        predicate(thing[property]) :
                        undefined;
                } else if (isObject(predicate)) {
                    result[property] = thing.hasOwnProperty(property) ?
                        map(thing[property], predicate) :
                        undefined;
                }
            }
        }
        return result;
    }

    /**
     * Public function `every`
     *
     * Returns the conjunction of all booleans in a hash.
     *
     * @param mappedPredicates The hash of evaluated predicates
     */
    function every (mappedPredicates) {
        var property;
        verifyObject(mappedPredicates);

        for (property in mappedPredicates) {
            if (mappedPredicates.hasOwnProperty(property)) {
                var value = mappedPredicates[property];
                if (isObject(value) && !every(value)) {
                    return false;
                } else if (!value) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Public function `any`
     *
     * Returns the disjunction of all booleans in a hash.
     *
     * @param mappedPredicates The has of evaluated predicates
     */
    function any (mappedPredicates) {
        var property;
        verifyObject(mappedPredicates);

        for (property in mappedPredicates) {
            if (mappedPredicates.hasOwnProperty(property)) {
                var value = mappedPredicates[property];
                if (isObject(value) && any(value)) {
                    return true;
                } else if (value === true) {
                    return true;
                }
            }
        }
        return false;
    }
}(this));
<|endoftext|>