<commit_before>'use strict';

var Promise = require('bluebird');
var $ = require('jquery'); $.noConflict();
var jQuery = $;
var MESSAGES = require('../messages');
var JSONF = require('../../../../modules/jsonf');
var UniqueSelector = require('../../../../modules/get-unique-selector');
var SS_MARKER_IMG = require('../screenshot-marker').base64;
var debounce = require('lodash.debounce');
var Ws4ever = require('../../../../modules/ws4ever');
var defaults = require('lodash.defaults');
var objectAssign = require('object-assign');
var BrowserPuppetCommands = require('./browser-puppet-commands.partial');
var promiseWhile = require('../../../../modules/promise-while')(Promise);
var Loggr = require('../../../../modules/loggr');
var SelectorObserver = require('../../../../modules/selector-observer');

var INSERT_ASSERTION_DEBOUNCE = 500;

var DEFAULT_SERVER_URL = 'ws://localhost:47225';

exports = module.exports = BrowserPuppet;

/**
 * @class
 * @extends {BrowserPuppetCommands}
 * @param {Object} [opts]
 * @param {String} [opts.serverUrl=DEFAULT_SERVER_URL] - BrowserPuppeteer websocket server URL
 */
function BrowserPuppet(opts) {
    this._opts = defaults({}, opts, {
        serverUrl: DEFAULT_SERVER_URL,
    });

    assert(/^ws:\/\/.+/.test(this._opts.serverUrl), 'BrowserPuppet: missing or invalid serverUrl, expected "ws://..."');

    this._transmitEvents = false;
    this._isExecuting = false;
    this._isTerminating = false;

    this._wsConn = null;

    this.$ = $;

    this._uniqueSelector = new UniqueSelector();

    this._selectorObserver = null;

    this._mouseoverSelector = null;

    this._activeElementBeforeWindowBlur = null;

    this._log = new Loggr({
        namespace: 'BrowserPuppet',
        // TODO logLevel
        logLevel: Loggr.LEVELS.ALL,
    });

    this._ssMarkerTopLeft = document.createElement('div');
    this._ssMarkerTopLeft.className = 'browser-puppet--screenshot-marker--top-left';
    this._ssMarkerTopLeft.setAttribute('style', 'position:absolute;top:0;left:0;width:4px;height:4px;z-index:16777000;');
    this._ssMarkerTopLeft.style.background = 'url(' + SS_MARKER_IMG + ')';

    this._ssMarkerBottomRight = document.createElement('div');
    this._ssMarkerBottomRight.className = 'browser-puppet--screenshot-marker--bottom-right';
    this._ssMarkerBottomRight.setAttribute('style', 'position:absolute;bottom:0;right:0;width:4px;height:4px;z-index:16777000;');
    this._ssMarkerBottomRight.style.background = 'url(' + SS_MARKER_IMG + ')';
}

objectAssign(BrowserPuppet.prototype, BrowserPuppetCommands.prototype);

BrowserPuppet.prototype.start = function () {
    this._startWs();
    this._attachCaptureEventListeners();
};

BrowserPuppet.prototype._startWs = function () {
    var self = this;

    self._wsConn = new Ws4ever(self._opts.serverUrl);
    self._wsConn.onmessage = function (e) {
        self._onMessage(e.data);
    };
    self._wsConn.onerror = function (err) {
        console.error(err);
    };
};

BrowserPuppet.prototype._sendMessage = function (rawData) {
    var data = rawData;

    if (typeof data === 'object') {
        data = JSONF.stringify(data);
    }

    this._wsConn.send(data);
};

BrowserPuppet.prototype._isJQueryElementsVisible = function ($els) {
    if ($els.length === 0) {
        return false;
    }
    if (!$els.is(':visible')) {
        return false;
    }

    for (var i = 0; i < $els.length; i++) {
        var el = $els[i];
        var rect = el.getBoundingClientRect();
        var elCenterX = rect.left + rect.width / 2;
        var elCenterY = rect.top + rect.height / 2;
        var elFromPoint = document.elementFromPoint(elCenterX, elCenterY);

        if (elFromPoint === el || el.contains(elFromPoint)) {
            return true;
        }
    }

    return false;
};

BrowserPuppet.prototype.isSelectorVisible = function (selector) {
    return this._isJQueryElementsVisible(this.$(selector));
};

BrowserPuppet.prototype._onMessage = function (rawData) {
    var self = this;

    if (self._isTerminating) {
        throw new Error('BrowserPuppet::_onMessage: cannot process message, puppet is terminating');
    }

    // no return
    Promise.try(function () {
        var data = JSONF.parse(rawData);

        switch (data.type) {
            case MESSAGES.DOWNSTREAM.EXEC_COMMAND:
            case MESSAGES.DOWNSTREAM.EXEC_FUNCTION:
                self._isExecuting = true;
                return self._onExecMessage(data);

            case MESSAGES.DOWNSTREAM.SET_SELECTOR_BECAME_VISIBLE_DATA:
                return self.setOnSelectorBecameVisibleSelectors(data.selectors);

            case MESSAGES.DOWNSTREAM.SHOW_SCREENSHOT_MARKER:
                return self.setScreenshotMarkerState(true);
            case MESSAGES.DOWNSTREAM.HIDE_SCREENSHOT_MARKER:
                return self.setScreenshotMarkerState(false);

            case MESSAGES.DOWNSTREAM.SET_TRANSMIT_EVENTS:
                return self.setTransmitEvents(data.value);

            case MESSAGES.DOWNSTREAM.CLEAR_PERSISTENT_DATA:
                return self.clearPersistentData();

            case MESSAGES.DOWNSTREAM.SET_MOUSEOVER_SELECTORS:
                self._mouseoverSelector = data.selectors.join(', ');
                self._attachMouseoverCaptureEventListener();
                return;

            case MESSAGES.DOWNSTREAM.SET_IGNORED_CLASSES:
                debugger;
                // TODO ugly
                self._uniqueSelector._opts.ignoredClasses = data.classes;
                return;

            case MESSAGES.DOWNSTREAM.TERMINATE_PUPPET:
                self._isTerminating = true;
                return;

            default:
                throw new Error('BrowserPuppet: unknown message type: ' + data.type);
        }
    })
    .then(function (result) {
        self._log.info('Sending ACK message');
        self._sendMessage({ type: MESSAGES.UPSTREAM.ACK, result: result });
    })
    .catch(function (err) {
        var errorDTO = {};

        Object.keys(err).forEach(function (key) {
            if (!err.hasOwnProperty(key)) {
                return;
            }
            errorDTO[key] = err[key];
        });

        errorDTO.message = err.message;
        errorDTO.stack = err.stack;

        self._sendMessage({ type: MESSAGES.UPSTREAM.NAK, error: errorDTO });
    })
    .finally(function () {
        self._isExecuting = false;

        if (self._isTerminating) {
            self._wsConn.close();
            self._wsConn = null;
        }
    });
};

BrowserPuppet.prototype._canCapture = function () {
    return this._transmitEvents && !this._isExecuting;
};

BrowserPuppet.prototype._attachCaptureEventListeners = function () {
    document.addEventListener('click', this._onClickCapture.bind(this), true);
    document.addEventListener('focus', this._onFocusCapture.bind(this), true);
    document.addEventListener('input', this._onInputCapture.bind(this), true);
    document.addEventListener('scroll', this._onScrollCapture.bind(this), true);
    document.addEventListener('keydown', this._onKeydownCapture.bind(this), true);

    window.addEventListener('blur', this._onWindowBlur.bind(this));
};

BrowserPuppet.prototype._attachMouseoverCaptureEventListener = function () {
    // TODO check if listener is already attached
    document.body.addEventListener('mouseover', this._onMouseoverCapture.bind(this), true);
};

var SHIFT_KEY = 16;
var CTRL_KEY = 17;

BrowserPuppet.prototype._onClickCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    try {
        var selector = this._uniqueSelector.get(target);
        var fullSelectorPath = this._uniqueSelector.getFullSelectorPath(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'click',
            $timestamp: Date.now(),
            selector: selector,
            $fullSelectorPath: fullSelectorPath,
            target: cleanTarget(target),
        },
    });
};

BrowserPuppet.prototype._onFocusCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    if (this._activeElementBeforeWindowBlur === target) {
        this._log.debug('focus capture prevented during window re-focus');
        this._activeElementBeforeWindowBlur = null;
        return;
    }

    try {
        var selector = this._uniqueSelector.get(target);
        var fullSelectorPath = this._uniqueSelector.getFullSelectorPath(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'focus',
            $timestamp: Date.now(),
            selector: selector,
            $fullSelectorPath: fullSelectorPath,
            target: cleanTarget(target),
        },
    });
};

BrowserPuppet.prototype._onInputCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    try {
        var selector = this._uniqueSelector.get(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'input',
            $timestamp: Date.now(),
            selector: selector,
            value: target.value,
            target: cleanTarget(target),
        },
    });
};

var SCROLL_DEBOUNCE = 500;

BrowserPuppet.prototype._onScrollCapture = debounce(function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    try {
        var selector = this._uniqueSelector.get(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    var targetDTO = cleanTarget(target);
    targetDTO.scrollTop = target.scrollTop;

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'scroll',
            $timestamp: Date.now(),
            selector: selector,
            target: targetDTO,
        },
    });
}, SCROLL_DEBOUNCE);

BrowserPuppet.prototype._onKeydownCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    if (event.keyCode === SHIFT_KEY && event.ctrlKey === true ||
        event.keyCode === CTRL_KEY && event.shiftKey === true) {

        this._sendInsertAssertionDebounced();
        return;
    }

    var target = event.target;

    try {
        var selector = this._uniqueSelector.get(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'keydown',
            $timestamp: Date.now(),
            selector: selector,
            keyCode: event.keyCode || event.charCode,
            ctrlKey: event.ctrlKey,
            shiftKey: event.shiftKey,
            altKey: event.altKey,
            target: cleanTarget(target),
        },
    });
};

BrowserPuppet.prototype._onMouseoverCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    if (this.$(target).is(this._mouseoverSelector)) {
        try {
            var selector = this._uniqueSelector.get(target);
        }
        catch (err) {
            console.error(err);
            return;
        }

        this._sendMessage({
            type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
            event: {
                type: 'mouseover',
                $timestamp: Date.now(),
                selector: selector,
                target: cleanTarget(target),
            },
        });
    }

};

BrowserPuppet.prototype._onWindowBlur = function () {
    this._activeElementBeforeWindowBlur = document.activeElement;
};

BrowserPuppet.prototype._sendInsertAssertionDebounced = debounce(function () {
    this._sendMessage({ type: MESSAGES.UPSTREAM.INSERT_ASSERTION });
}, INSERT_ASSERTION_DEBOUNCE);

BrowserPuppet.prototype.setOnSelectorBecameVisibleSelectors = function (selectors) {
    var self = this;

    if (self._selectorObserver !== null) {
        self._selectorObserver.disconnect();
        self._selectorObserver = null;
    }

    // TODO check _canCapture

    var observeList = selectors.map(function (selector) {
        return {
            selector: selector,
            listener: self._sendMessage.bind(self, { type: MESSAGES.UPSTREAM.SELECTOR_BECAME_VISIBLE, selector: selector })
        };
    });

    this._selectorObserver = new SelectorObserver({ observeList: observeList });
};

BrowserPuppet.prototype.setTransmitEvents = function (value) {
    if (typeof value !== 'boolean') {
        throw new Error('BrowserPuppet::setTransmitEvents: invalid type for value');
    }
    this._transmitEvents = value;
};

BrowserPuppet.prototype._onExecMessage = Promise.method(function (data) {
    if (data.type === MESSAGES.DOWNSTREAM.EXEC_COMMAND) {
        return this.execCommand(data.command);
    }
    else if (data.type === MESSAGES.DOWNSTREAM.EXEC_FUNCTION) {
        return this.execFunction(data.fn, data.args);
    }

    throw new Error('Unknown exec type: ' + data.type);

});

BrowserPuppet.prototype.execFunction = Promise.method(function (fn, args) {
    return fn.apply(null, args);
});

BrowserPuppet.prototype.execCommand = Promise.method(function (command) {
    this._log.trace('execCommand: ' + JSON.stringify(command));

    switch (command.type) {
        case 'click':
        case 'setValue':
        case 'getValue':
        case 'pressKey':
        case 'waitForVisible':
        case 'waitWhileVisible':
        case 'focus':
        case 'isVisible':
        case 'scroll':
        case 'mouseover':
        case 'uploadFileAndAssign':
            return this[command.type](command);

        case 'composite':
            return this.execCompositeCommand(command.commands);
        default:
            throw new Error('Unknown command type: ' + command.type);
    }
});

BrowserPuppet.prototype.execCompositeCommand = Promise.method(function (commands) {
    var self = this;

    return Promise.each(commands, function (command) {
        return self.execCommand(command);
    });
});

// TODO separate file
// from https://stackoverflow.com/a/179514/4782902
function deleteAllCookies() {
    var cookies = document.cookie.split(";");

    for (var i = 0; i < cookies.length; i++) {
        var cookie = cookies[i];
        var eqPos = cookie.indexOf("=");
        var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
        document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
    }
}

BrowserPuppet.prototype.clearPersistentData = function () {
    deleteAllCookies();
    window.localStorage.clear();
};

BrowserPuppet.prototype.setScreenshotMarkerState = function (state) {
    if (state) {
        document.body.appendChild(this._ssMarkerTopLeft);
        document.body.appendChild(this._ssMarkerBottomRight);
    }
    else {
        document.body.removeChild(this._ssMarkerTopLeft);
        document.body.removeChild(this._ssMarkerBottomRight);
    }
};

// TODO rename to getTargetDTO
function cleanTarget(target) {
    return {
        className: target.className,
        id: target.id,
        innerText: target.innerText,
        tagName: target.tagName,
        type: target.type,
    };
}

function deepCopy(o) {
    return JSON.parse(JSON.stringify(o));
}

function assert(v, m) {
    if (!v) {
        throw new Error(m);
    }
}

<commit_msg>Remove debugger lines<commit_after>'use strict';

var Promise = require('bluebird');
var $ = require('jquery'); $.noConflict();
var jQuery = $;
var MESSAGES = require('../messages');
var JSONF = require('../../../../modules/jsonf');
var UniqueSelector = require('../../../../modules/get-unique-selector');
var SS_MARKER_IMG = require('../screenshot-marker').base64;
var debounce = require('lodash.debounce');
var Ws4ever = require('../../../../modules/ws4ever');
var defaults = require('lodash.defaults');
var objectAssign = require('object-assign');
var BrowserPuppetCommands = require('./browser-puppet-commands.partial');
var promiseWhile = require('../../../../modules/promise-while')(Promise);
var Loggr = require('../../../../modules/loggr');
var SelectorObserver = require('../../../../modules/selector-observer');

var INSERT_ASSERTION_DEBOUNCE = 500;

var DEFAULT_SERVER_URL = 'ws://localhost:47225';

exports = module.exports = BrowserPuppet;

/**
 * @class
 * @extends {BrowserPuppetCommands}
 * @param {Object} [opts]
 * @param {String} [opts.serverUrl=DEFAULT_SERVER_URL] - BrowserPuppeteer websocket server URL
 */
function BrowserPuppet(opts) {
    this._opts = defaults({}, opts, {
        serverUrl: DEFAULT_SERVER_URL,
    });

    assert(/^ws:\/\/.+/.test(this._opts.serverUrl), 'BrowserPuppet: missing or invalid serverUrl, expected "ws://..."');

    this._transmitEvents = false;
    this._isExecuting = false;
    this._isTerminating = false;

    this._wsConn = null;

    this.$ = $;

    this._uniqueSelector = new UniqueSelector();

    this._selectorObserver = null;

    this._mouseoverSelector = null;

    this._activeElementBeforeWindowBlur = null;

    this._log = new Loggr({
        namespace: 'BrowserPuppet',
        // TODO logLevel
        logLevel: Loggr.LEVELS.ALL,
    });

    this._ssMarkerTopLeft = document.createElement('div');
    this._ssMarkerTopLeft.className = 'browser-puppet--screenshot-marker--top-left';
    this._ssMarkerTopLeft.setAttribute('style', 'position:absolute;top:0;left:0;width:4px;height:4px;z-index:16777000;');
    this._ssMarkerTopLeft.style.background = 'url(' + SS_MARKER_IMG + ')';

    this._ssMarkerBottomRight = document.createElement('div');
    this._ssMarkerBottomRight.className = 'browser-puppet--screenshot-marker--bottom-right';
    this._ssMarkerBottomRight.setAttribute('style', 'position:absolute;bottom:0;right:0;width:4px;height:4px;z-index:16777000;');
    this._ssMarkerBottomRight.style.background = 'url(' + SS_MARKER_IMG + ')';
}

objectAssign(BrowserPuppet.prototype, BrowserPuppetCommands.prototype);

BrowserPuppet.prototype.start = function () {
    this._startWs();
    this._attachCaptureEventListeners();
};

BrowserPuppet.prototype._startWs = function () {
    var self = this;

    self._wsConn = new Ws4ever(self._opts.serverUrl);
    self._wsConn.onmessage = function (e) {
        self._onMessage(e.data);
    };
    self._wsConn.onerror = function (err) {
        console.error(err);
    };
};

BrowserPuppet.prototype._sendMessage = function (rawData) {
    var data = rawData;

    if (typeof data === 'object') {
        data = JSONF.stringify(data);
    }

    this._wsConn.send(data);
};

BrowserPuppet.prototype._isJQueryElementsVisible = function ($els) {
    if ($els.length === 0) {
        return false;
    }
    if (!$els.is(':visible')) {
        return false;
    }

    for (var i = 0; i < $els.length; i++) {
        var el = $els[i];
        var rect = el.getBoundingClientRect();
        var elCenterX = rect.left + rect.width / 2;
        var elCenterY = rect.top + rect.height / 2;
        var elFromPoint = document.elementFromPoint(elCenterX, elCenterY);

        if (elFromPoint === el || el.contains(elFromPoint)) {
            return true;
        }
    }

    return false;
};

BrowserPuppet.prototype.isSelectorVisible = function (selector) {
    return this._isJQueryElementsVisible(this.$(selector));
};

BrowserPuppet.prototype._onMessage = function (rawData) {
    var self = this;

    if (self._isTerminating) {
        throw new Error('BrowserPuppet::_onMessage: cannot process message, puppet is terminating');
    }

    // no return
    Promise.try(function () {
        var data = JSONF.parse(rawData);

        switch (data.type) {
            case MESSAGES.DOWNSTREAM.EXEC_COMMAND:
            case MESSAGES.DOWNSTREAM.EXEC_FUNCTION:
                self._isExecuting = true;
                return self._onExecMessage(data);

            case MESSAGES.DOWNSTREAM.SET_SELECTOR_BECAME_VISIBLE_DATA:
                return self.setOnSelectorBecameVisibleSelectors(data.selectors);

            case MESSAGES.DOWNSTREAM.SHOW_SCREENSHOT_MARKER:
                return self.setScreenshotMarkerState(true);
            case MESSAGES.DOWNSTREAM.HIDE_SCREENSHOT_MARKER:
                return self.setScreenshotMarkerState(false);

            case MESSAGES.DOWNSTREAM.SET_TRANSMIT_EVENTS:
                return self.setTransmitEvents(data.value);

            case MESSAGES.DOWNSTREAM.CLEAR_PERSISTENT_DATA:
                return self.clearPersistentData();

            case MESSAGES.DOWNSTREAM.SET_MOUSEOVER_SELECTORS:
                self._mouseoverSelector = data.selectors.join(', ');
                self._attachMouseoverCaptureEventListener();
                return;

            case MESSAGES.DOWNSTREAM.SET_IGNORED_CLASSES:
                // TODO ugly
                self._uniqueSelector._opts.ignoredClasses = data.classes;
                return;

            case MESSAGES.DOWNSTREAM.TERMINATE_PUPPET:
                self._isTerminating = true;
                return;

            default:
                throw new Error('BrowserPuppet: unknown message type: ' + data.type);
        }
    })
    .then(function (result) {
        self._log.info('Sending ACK message');
        self._sendMessage({ type: MESSAGES.UPSTREAM.ACK, result: result });
    })
    .catch(function (err) {
        var errorDTO = {};

        Object.keys(err).forEach(function (key) {
            if (!err.hasOwnProperty(key)) {
                return;
            }
            errorDTO[key] = err[key];
        });

        errorDTO.message = err.message;
        errorDTO.stack = err.stack;

        self._sendMessage({ type: MESSAGES.UPSTREAM.NAK, error: errorDTO });
    })
    .finally(function () {
        self._isExecuting = false;

        if (self._isTerminating) {
            self._wsConn.close();
            self._wsConn = null;
        }
    });
};

BrowserPuppet.prototype._canCapture = function () {
    return this._transmitEvents && !this._isExecuting;
};

BrowserPuppet.prototype._attachCaptureEventListeners = function () {
    document.addEventListener('click', this._onClickCapture.bind(this), true);
    document.addEventListener('focus', this._onFocusCapture.bind(this), true);
    document.addEventListener('input', this._onInputCapture.bind(this), true);
    document.addEventListener('scroll', this._onScrollCapture.bind(this), true);
    document.addEventListener('keydown', this._onKeydownCapture.bind(this), true);

    window.addEventListener('blur', this._onWindowBlur.bind(this));
};

BrowserPuppet.prototype._attachMouseoverCaptureEventListener = function () {
    // TODO check if listener is already attached
    document.body.addEventListener('mouseover', this._onMouseoverCapture.bind(this), true);
};

var SHIFT_KEY = 16;
var CTRL_KEY = 17;

BrowserPuppet.prototype._onClickCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    try {
        var selector = this._uniqueSelector.get(target);
        var fullSelectorPath = this._uniqueSelector.getFullSelectorPath(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'click',
            $timestamp: Date.now(),
            selector: selector,
            $fullSelectorPath: fullSelectorPath,
            target: cleanTarget(target),
        },
    });
};

BrowserPuppet.prototype._onFocusCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    if (this._activeElementBeforeWindowBlur === target) {
        this._log.debug('focus capture prevented during window re-focus');
        this._activeElementBeforeWindowBlur = null;
        return;
    }

    try {
        var selector = this._uniqueSelector.get(target);
        var fullSelectorPath = this._uniqueSelector.getFullSelectorPath(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'focus',
            $timestamp: Date.now(),
            selector: selector,
            $fullSelectorPath: fullSelectorPath,
            target: cleanTarget(target),
        },
    });
};

BrowserPuppet.prototype._onInputCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    try {
        var selector = this._uniqueSelector.get(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'input',
            $timestamp: Date.now(),
            selector: selector,
            value: target.value,
            target: cleanTarget(target),
        },
    });
};

var SCROLL_DEBOUNCE = 500;

BrowserPuppet.prototype._onScrollCapture = debounce(function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    try {
        var selector = this._uniqueSelector.get(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    var targetDTO = cleanTarget(target);
    targetDTO.scrollTop = target.scrollTop;

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'scroll',
            $timestamp: Date.now(),
            selector: selector,
            target: targetDTO,
        },
    });
}, SCROLL_DEBOUNCE);

BrowserPuppet.prototype._onKeydownCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    if (event.keyCode === SHIFT_KEY && event.ctrlKey === true ||
        event.keyCode === CTRL_KEY && event.shiftKey === true) {

        this._sendInsertAssertionDebounced();
        return;
    }

    var target = event.target;

    try {
        var selector = this._uniqueSelector.get(target);
    }
    catch (err) {
        console.error(err);
        return;
    }

    this._sendMessage({
        type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
        event: {
            type: 'keydown',
            $timestamp: Date.now(),
            selector: selector,
            keyCode: event.keyCode || event.charCode,
            ctrlKey: event.ctrlKey,
            shiftKey: event.shiftKey,
            altKey: event.altKey,
            target: cleanTarget(target),
        },
    });
};

BrowserPuppet.prototype._onMouseoverCapture = function (event) {
    if (!this._canCapture()) {
        return;
    }

    var target = event.target;

    if (this.$(target).is(this._mouseoverSelector)) {
        try {
            var selector = this._uniqueSelector.get(target);
        }
        catch (err) {
            console.error(err);
            return;
        }

        this._sendMessage({
            type: MESSAGES.UPSTREAM.CAPTURED_EVENT,
            event: {
                type: 'mouseover',
                $timestamp: Date.now(),
                selector: selector,
                target: cleanTarget(target),
            },
        });
    }

};

BrowserPuppet.prototype._onWindowBlur = function () {
    this._activeElementBeforeWindowBlur = document.activeElement;
};

BrowserPuppet.prototype._sendInsertAssertionDebounced = debounce(function () {
    this._sendMessage({ type: MESSAGES.UPSTREAM.INSERT_ASSERTION });
}, INSERT_ASSERTION_DEBOUNCE);

BrowserPuppet.prototype.setOnSelectorBecameVisibleSelectors = function (selectors) {
    var self = this;

    if (self._selectorObserver !== null) {
        self._selectorObserver.disconnect();
        self._selectorObserver = null;
    }

    // TODO check _canCapture

    var observeList = selectors.map(function (selector) {
        return {
            selector: selector,
            listener: self._sendMessage.bind(self, { type: MESSAGES.UPSTREAM.SELECTOR_BECAME_VISIBLE, selector: selector })
        };
    });

    this._selectorObserver = new SelectorObserver({ observeList: observeList });
};

BrowserPuppet.prototype.setTransmitEvents = function (value) {
    if (typeof value !== 'boolean') {
        throw new Error('BrowserPuppet::setTransmitEvents: invalid type for value');
    }
    this._transmitEvents = value;
};

BrowserPuppet.prototype._onExecMessage = Promise.method(function (data) {
    if (data.type === MESSAGES.DOWNSTREAM.EXEC_COMMAND) {
        return this.execCommand(data.command);
    }
    else if (data.type === MESSAGES.DOWNSTREAM.EXEC_FUNCTION) {
        return this.execFunction(data.fn, data.args);
    }

    throw new Error('Unknown exec type: ' + data.type);

});

BrowserPuppet.prototype.execFunction = Promise.method(function (fn, args) {
    return fn.apply(null, args);
});

BrowserPuppet.prototype.execCommand = Promise.method(function (command) {
    this._log.trace('execCommand: ' + JSON.stringify(command));

    switch (command.type) {
        case 'click':
        case 'setValue':
        case 'getValue':
        case 'pressKey':
        case 'waitForVisible':
        case 'waitWhileVisible':
        case 'focus':
        case 'isVisible':
        case 'scroll':
        case 'mouseover':
        case 'uploadFileAndAssign':
            return this[command.type](command);

        case 'composite':
            return this.execCompositeCommand(command.commands);
        default:
            throw new Error('Unknown command type: ' + command.type);
    }
});

BrowserPuppet.prototype.execCompositeCommand = Promise.method(function (commands) {
    var self = this;

    return Promise.each(commands, function (command) {
        return self.execCommand(command);
    });
});

// TODO separate file
// from https://stackoverflow.com/a/179514/4782902
function deleteAllCookies() {
    var cookies = document.cookie.split(";");

    for (var i = 0; i < cookies.length; i++) {
        var cookie = cookies[i];
        var eqPos = cookie.indexOf("=");
        var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
        document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
    }
}

BrowserPuppet.prototype.clearPersistentData = function () {
    deleteAllCookies();
    window.localStorage.clear();
};

BrowserPuppet.prototype.setScreenshotMarkerState = function (state) {
    if (state) {
        document.body.appendChild(this._ssMarkerTopLeft);
        document.body.appendChild(this._ssMarkerBottomRight);
    }
    else {
        document.body.removeChild(this._ssMarkerTopLeft);
        document.body.removeChild(this._ssMarkerBottomRight);
    }
};

// TODO rename to getTargetDTO
function cleanTarget(target) {
    return {
        className: target.className,
        id: target.id,
        innerText: target.innerText,
        tagName: target.tagName,
        type: target.type,
    };
}

function deepCopy(o) {
    return JSON.parse(JSON.stringify(o));
}

function assert(v, m) {
    if (!v) {
        throw new Error(m);
    }
}

<|endoftext|>


<commit_before>// Global state
var $upNext = null;
var $w;
var $h;
var $slides;
var $arrows;
var $nextArrow;
var $startCardButton;
var $controlBtn;
var $thisPlayerProgress;
var $playedBar;
var $subtitleWrapper;
var $subtitles;
var $slideTitle;
var $ambientPlayer;
var $narrativePlayer;
var $share;
var $shareModal;
var $progressIndicator
var $currentProgress;
var $support;
var $supportBtn;
var $question;
var $careStory;
var $careStoryBtns;
var $email;
var $emailBtn;
var isTouch = Modernizr.touch;
var mobileSuffix;
var aspectWidth = 16;
var aspectHeight = 9;
var optimalWidth;
var optimalHeight;
var w;
var h;
var completion = 0;
var arrowTest;
var progressTest;
var conclusionTest;
var firstRightArrowClicked = false;
// var hammer;
var NO_AUDIO = (window.location.search.indexOf('noaudio') >= 0);
var visibilityProperty = null;

var resize = function() {
    $w = $(window).width();
    $h = $(window).height();

    $slides.width($w);

    optimalWidth = ($h * aspectWidth) / aspectHeight;
    optimalHeight = ($w * aspectHeight) / aspectWidth;

    w = $w;
    h = optimalHeight;

    if (optimalWidth > $w) {
        w = optimalWidth;
        h = $h;
    }

};

var setUpFullPage = function() {
    var anchors = ['_'];
    for (var i = 0; i < COPY.content.length; i++) {
        anchors.push(COPY.content[i][0]);
    }
    $.fn.fullpage({
        anchors: (!APP_CONFIG.DEPLOYMENT_TARGET) ? anchors : false,
        autoScrolling: false,
        keyboardScrolling: false,
        verticalCentered: false,
        fixedElements: '.primary-navigation, #share-modal, .share, .progress-indicator',
        resize: false,
        css3: true,
        loopHorizontal: false,
        afterRender: onPageLoad,
        afterSlideLoad: lazyLoad,
        onSlideLeave: onSlideLeave
    });
};

var onPageLoad = function() {
    setSlidesForLazyLoading(0);
    $('.section').css({
      'opacity': 1,
      'visibility': 'visible',
    });
    showNavigation();
};

// after a new slide loads
var lazyLoad = function(anchorLink, index, slideAnchor, slideIndex) {
    setSlidesForLazyLoading(slideIndex);
    showNavigation();
    AUDIO.checkForAudio(slideAnchor);
    animateProgress(slideIndex);

    if (slideIndex === 0) {
        $share.hide();
    } else {
        $share.show();
    }

    if (slideIndex === $slides.length - 1) {
        buildConclusionSlide();
    }

    // Completion tracking
    how_far = (slideIndex + 1) / ($slides.length - 1);

    if (how_far >= completion + 0.25) {
        completion = how_far - (how_far % 0.25);

        if (completion === 0.25) {
            ANALYTICS.completeTwentyFivePercent(progressTest);
        }
        else if (completion === 0.5) {
            ANALYTICS.completeFiftyPercent(progressTest);
        }
        else if (completion === 0.75) {
            ANALYTICS.completeSeventyFivePercent(progressTest);
        }
        else if (completion === 1) {
            ANALYTICS.completeOneHundredPercent(progressTest);
        }
    }
};

var setSlidesForLazyLoading = function(slideIndex) {
    /*
    * Sets up a list of slides based on your position in the deck.
    * Lazy-loads images in future slides because of reasons.
    */
    var slides = [
        $slides.eq(slideIndex),
        $slides.eq(slideIndex + 1),
        $slides.eq(slideIndex + 2),
        $slides.eq(slideIndex + 3),
        $slides.eq(slideIndex + 4)
    ];

    // Mobile suffix should be blank by default.
    mobileSuffix = '';
    /*
    if ($w < 769) {
        mobileSuffix = '-sq';
    }*/

    for (var i = 0; i < slides.length; i++) {
        loadImages(slides[i]);
    };

}

var loadImages = function($slide) {
    /*
    * Sets the background image on a div for our fancy slides.
    */
    if ($slide.data('bgimage')) {
        var image_filename = $slide.data('bgimage').split('.')[0];
        var image_extension = '.' + $slide.data('bgimage').split('.')[1];
        var image_path = 'assets/' + image_filename + mobileSuffix + image_extension;

        if ($slide.css('background-image') === 'none') {
            $slide.css('background-image', 'url(' + image_path + ')');
        }
    }

    var $images = $slide.find('img.lazy-load');
    if ($images.length > 0) {
        for (var i = 0; i < $images.length; i++) {
            var image = $images.eq(i).data('src');
            $images.eq(i).attr('src', 'assets/' + image);
        }
    }
};

var showNavigation = function() {
    /*
    * Nav doesn't exist by default.
    * This function loads it up.
    */

    if ($slides.first().hasClass('active')) {
        /*
        * Don't show arrows on titlecard
        */
        $arrows.hide();
    }

    else if ($slides.last().hasClass('active')) {
        /*
        * Last card gets no next arrow but does have the nav.
        */
        if (!$arrows.hasClass('active')) {
            showArrows();
        }

        $nextArrow.removeClass('active');
        $nextArrow.hide();
    } else if ($slides.eq(1).hasClass('active')) {
        showArrows();

        switch (arrowTest) {
            case 'bright-arrow':
                $nextArrow.addClass('titlecard-nav');
                break;
            case 'bouncy-arrow':
                $nextArrow.addClass('shake animated titlecard-nav');
                break;
            default:
                break;
        }

        $nextArrow.on('click', onFirstRightArrowClick);
    } else {
        /*
        * All of the other cards? Arrows and navs.
        */
        if ($arrows.filter('active').length != $arrows.length) {
            showArrows();
        }
        $nextArrow.removeClass('shake animated titlecard-nav');

        $nextArrow.off('click', onFirstRightArrowClick);
    }

    if (progressTest === 'progress-bar') {
        $progressIndicator.show();
    }
}

var showArrows = function() {
    /*
    * Show the arrows.
    */
    $arrows.addClass('active');
    $arrows.show();
};

var determineTest = function(possibleTests) {
    var test = possibleTests[getRandomInt(0, possibleTests.length)]
    return test
}

var getRandomInt = function(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

var setCustomVars = function() {
    ANALYTICS.setCustomVar(40, 'progress-test', progressTest);
}

var buildConclusionSlide = function() {
    ANALYTICS.trackEvent('tests-run', conclusionTest);

    if (conclusionTest === 'no-question') {
        $support.show();
    } else {
        $question.text(COPY['post_metadata'][conclusionTest]);
        $careStory.show();
    }
}

var onCareStoryBtnClick = function(e) {
    e.preventDefault();

    var $this = $(this);

    $careStory.hide();

    if ($this.hasClass('yes')) {
        ANALYTICS.trackEvent('like-story', 'yes');
        $support.show();
    } else {
        ANALYTICS.trackEvent('like-story', 'no');
        $email.show();
    }
}

var onSupportBtnClick = function(e) {
    e.preventDefault();

    var $this = $(this);
    var link = $this.attr('href');

    ANALYTICS.trackEvent('support-btn-click', conclusionTest);

    window.top.location = link
    return true;
}

var onEmailBtnClick = function() {
    ANALYTICS.trackEvent('email-btn-click');
}

var animateProgress = function(index) {
    var totalSlides = $slides.length;
    var percentage = (index + 1) / totalSlides;
    $currentProgress.css('width', percentage * 100 + '%');

    if (index === 0) {
        $progressIndicator.width(0);
    } else {
        $progressIndicator.width('100%');
    }
}

var onSlideLeave = function(anchorLink, index, slideIndex, direction) {
    /*
    * Called when leaving a slide.
    */
    ANALYTICS.exitSlide(slideIndex.toString());
}

var onFirstRightArrowClick = function() {
    if (firstRightArrowClicked === false) {
        ANALYTICS.firstRightArrowClick(arrowTest);
        firstRightArrowClicked = true;
    }
}

var onStartCardButtonClick = function() {
    $.fn.fullpage.moveSlideRight();
    if (isTouch) {
        AUDIO.fakeAmbientPlayer();
        AUDIO.fakeNarrativePlayer();
    }
}

var onDocumentKeyDown = function(e) {
    if (e.which === 37 || e.which === 39) {
        ANALYTICS.useKeyboardNavigation();
        if (e.which === 37) {
            $.fn.fullpage.moveSlideLeft();
        } else if (e.which === 39) {
            $.fn.fullpage.moveSlideRight();
        }
    }
    // jquery.fullpage handles actual scrolling
    return true;
}

var onSlideClick = function(e) {
    if (isTouch) {
        if ($slides.first().hasClass('active')) {
            AUDIO.fakeAmbientPlayer();
            AUDIO.fakeNarrativePlayer();
        }
        $.fn.fullpage.moveSlideRight();
    }
    return true;
}

var onNextPostClick = function(e) {
    e.preventDefault();

    ANALYTICS.trackEvent('next-post');
    window.top.location = NEXT_POST_URL;
    return true;
}

var fakeMobileHover = function() {
    $(this).css({
        'background-color': '#fff',
        'color': '#000',
        'opacity': .9
    });
}

var rmFakeMobileHover = function() {
    $(this).css({
        'background-color': 'rgba(0, 0, 0, 0.2)',
        'color': '#fff',
        'opacity': .3
    });
}

/*
 * Text copied to clipboard.
 */
var onClippyCopy = function(e) {
    alert('Copied to your clipboard!');

    ANALYTICS.copySummary();
}

var onControlBtnClick = function(e) {
    e.preventDefault();
    AUDIO.toggleNarrativeAudio();
    ANALYTICS.trackEvent('pause-button');

    e.stopPropagation();
}

var onVisibilityChange = function() {
    AUDIO.toggleAllAudio();
}

var getHiddenProperty = function() {
    var prefixes = ['webkit','moz','ms','o'];

    // if 'hidden' is natively supported just return it
    if ('hidden' in document) return 'hidden';

    // otherwise loop over all the known prefixes until we find one
    for (var i = 0; i < prefixes.length; i++){
        if ((prefixes[i] + 'Hidden') in document)
            return prefixes[i] + 'Hidden';
    }

    // otherwise it's not supported
    return null;
}

var isHidden = function() {
    var prop = getHiddenProperty();
    if (!prop) return false;

    return document[prop];
}

/*
 * Share modal opened.
 */
var onShareModalShown = function(e) {
    ANALYTICS.openShareDiscuss();
}

/*
 * Share modal closed.
 */
var onShareModalHidden = function(e) {
    ANALYTICS.closeShareDiscuss();
}

$(document).ready(function() {
    $w = $(window).width();
    $h = $(window).height();

    $slides = $('.slide');
    $navButton = $('.primary-navigation-btn');
    $startCardButton = $('.btn-go');
    $arrows = $('.controlArrow');
    $nextArrow = $arrows.filter('.next');
    $upNext = $('.up-next');
    $controlBtn = $('.control-btn');
    $narrativePlayer = $('#narrative-player');
    $ambientPlayer = $('#ambient-player');
    $share = $('.share');
    $shareModal = $('#share-modal')
    $progressIndicator = $('.progress-indicator');
    $currentProgress = $('.current-progress');
    $support = $('.support')
    $supportBtn = $('.support-btn');
    $careStory = $('.care-story');
    $question = $('.question');
    $careStoryBtns = $('.care-story-btn');
    $email = $('.email');
    $emailBtn = $('.email-btn');

    arrowTest = determineTest(['faded-arrow', 'bright-arrow', 'bouncy-arrow']);
    progressTest = determineTest(['progress-bar', 'no-progress-bar']);
    conclusionTest = determineTest(['no-question', 'question_a', 'question_b', 'question_c']);

    $shareModal.on('shown.bs.modal', onShareModalShown);
    $shareModal.on('hidden.bs.modal', onShareModalHidden);
    $startCardButton.on('click', onStartCardButtonClick);
    $slides.on('click', onSlideClick);
    $upNext.on('click', onNextPostClick);
    $controlBtn.on('click', onControlBtnClick);
    $arrows.on('touchstart', fakeMobileHover);
    $arrows.on('touchend', rmFakeMobileHover);
    $careStoryBtns.on('click', onCareStoryBtnClick);
    $supportBtn.on('click', onSupportBtnClick);
    $emailBtn.on('click', onEmailBtnClick);
    $(document).keydown(onDocumentKeyDown);

    AUDIO.setUpNarrativePlayer();
    AUDIO.setUpAmbientPlayer();
    setUpFullPage();
    resize();
    setCustomVars();

    // Redraw slides if the window resizes
    window.addEventListener("deviceorientation", resize, true);
    $(window).resize(resize);

    // listen for page visibility changes
    visibilityProperty = getHiddenProperty();
    if (visibilityProperty) {
        var evtname = visibilityProperty.replace(/[H|h]idden/,'') + 'visibilitychange';
        document.addEventListener(evtname, onVisibilityChange);
    }
});<commit_msg>four questions<commit_after>// Global state
var $upNext = null;
var $w;
var $h;
var $slides;
var $arrows;
var $nextArrow;
var $startCardButton;
var $controlBtn;
var $thisPlayerProgress;
var $playedBar;
var $subtitleWrapper;
var $subtitles;
var $slideTitle;
var $ambientPlayer;
var $narrativePlayer;
var $share;
var $shareModal;
var $progressIndicator
var $currentProgress;
var $support;
var $supportBtn;
var $question;
var $careStory;
var $careStoryBtns;
var $email;
var $emailBtn;
var isTouch = Modernizr.touch;
var mobileSuffix;
var aspectWidth = 16;
var aspectHeight = 9;
var optimalWidth;
var optimalHeight;
var w;
var h;
var completion = 0;
var arrowTest;
var progressTest;
var conclusionTest;
var firstRightArrowClicked = false;
// var hammer;
var NO_AUDIO = (window.location.search.indexOf('noaudio') >= 0);
var visibilityProperty = null;

var resize = function() {
    $w = $(window).width();
    $h = $(window).height();

    $slides.width($w);

    optimalWidth = ($h * aspectWidth) / aspectHeight;
    optimalHeight = ($w * aspectHeight) / aspectWidth;

    w = $w;
    h = optimalHeight;

    if (optimalWidth > $w) {
        w = optimalWidth;
        h = $h;
    }

};

var setUpFullPage = function() {
    var anchors = ['_'];
    for (var i = 0; i < COPY.content.length; i++) {
        anchors.push(COPY.content[i][0]);
    }
    $.fn.fullpage({
        anchors: (!APP_CONFIG.DEPLOYMENT_TARGET) ? anchors : false,
        autoScrolling: false,
        keyboardScrolling: false,
        verticalCentered: false,
        fixedElements: '.primary-navigation, #share-modal, .share, .progress-indicator',
        resize: false,
        css3: true,
        loopHorizontal: false,
        afterRender: onPageLoad,
        afterSlideLoad: lazyLoad,
        onSlideLeave: onSlideLeave
    });
};

var onPageLoad = function() {
    setSlidesForLazyLoading(0);
    $('.section').css({
      'opacity': 1,
      'visibility': 'visible',
    });
    showNavigation();
};

// after a new slide loads
var lazyLoad = function(anchorLink, index, slideAnchor, slideIndex) {
    setSlidesForLazyLoading(slideIndex);
    showNavigation();
    AUDIO.checkForAudio(slideAnchor);
    animateProgress(slideIndex);

    if (slideIndex === 0) {
        $share.hide();
    } else {
        $share.show();
    }

    if (slideIndex === $slides.length - 1) {
        buildConclusionSlide();
    }

    // Completion tracking
    how_far = (slideIndex + 1) / ($slides.length - 1);

    if (how_far >= completion + 0.25) {
        completion = how_far - (how_far % 0.25);

        if (completion === 0.25) {
            ANALYTICS.completeTwentyFivePercent(progressTest);
        }
        else if (completion === 0.5) {
            ANALYTICS.completeFiftyPercent(progressTest);
        }
        else if (completion === 0.75) {
            ANALYTICS.completeSeventyFivePercent(progressTest);
        }
        else if (completion === 1) {
            ANALYTICS.completeOneHundredPercent(progressTest);
        }
    }
};

var setSlidesForLazyLoading = function(slideIndex) {
    /*
    * Sets up a list of slides based on your position in the deck.
    * Lazy-loads images in future slides because of reasons.
    */
    var slides = [
        $slides.eq(slideIndex),
        $slides.eq(slideIndex + 1),
        $slides.eq(slideIndex + 2),
        $slides.eq(slideIndex + 3),
        $slides.eq(slideIndex + 4)
    ];

    // Mobile suffix should be blank by default.
    mobileSuffix = '';
    /*
    if ($w < 769) {
        mobileSuffix = '-sq';
    }*/

    for (var i = 0; i < slides.length; i++) {
        loadImages(slides[i]);
    };

}

var loadImages = function($slide) {
    /*
    * Sets the background image on a div for our fancy slides.
    */
    if ($slide.data('bgimage')) {
        var image_filename = $slide.data('bgimage').split('.')[0];
        var image_extension = '.' + $slide.data('bgimage').split('.')[1];
        var image_path = 'assets/' + image_filename + mobileSuffix + image_extension;

        if ($slide.css('background-image') === 'none') {
            $slide.css('background-image', 'url(' + image_path + ')');
        }
    }

    var $images = $slide.find('img.lazy-load');
    if ($images.length > 0) {
        for (var i = 0; i < $images.length; i++) {
            var image = $images.eq(i).data('src');
            $images.eq(i).attr('src', 'assets/' + image);
        }
    }
};

var showNavigation = function() {
    /*
    * Nav doesn't exist by default.
    * This function loads it up.
    */

    if ($slides.first().hasClass('active')) {
        /*
        * Don't show arrows on titlecard
        */
        $arrows.hide();
    }

    else if ($slides.last().hasClass('active')) {
        /*
        * Last card gets no next arrow but does have the nav.
        */
        if (!$arrows.hasClass('active')) {
            showArrows();
        }

        $nextArrow.removeClass('active');
        $nextArrow.hide();
    } else if ($slides.eq(1).hasClass('active')) {
        showArrows();

        switch (arrowTest) {
            case 'bright-arrow':
                $nextArrow.addClass('titlecard-nav');
                break;
            case 'bouncy-arrow':
                $nextArrow.addClass('shake animated titlecard-nav');
                break;
            default:
                break;
        }

        $nextArrow.on('click', onFirstRightArrowClick);
    } else {
        /*
        * All of the other cards? Arrows and navs.
        */
        if ($arrows.filter('active').length != $arrows.length) {
            showArrows();
        }
        $nextArrow.removeClass('shake animated titlecard-nav');

        $nextArrow.off('click', onFirstRightArrowClick);
    }

    if (progressTest === 'progress-bar') {
        $progressIndicator.show();
    }
}

var showArrows = function() {
    /*
    * Show the arrows.
    */
    $arrows.addClass('active');
    $arrows.show();
};

var determineTest = function(possibleTests) {
    var test = possibleTests[getRandomInt(0, possibleTests.length)]
    return test
}

var getRandomInt = function(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

var setCustomVars = function() {
    ANALYTICS.setCustomVar(40, 'progress-test', progressTest);
}

var buildConclusionSlide = function() {
    ANALYTICS.trackEvent('tests-run', conclusionTest);

    if (conclusionTest === 'no-question') {
        $support.show();
    } else {
        $question.text(COPY['post_metadata'][conclusionTest]);
        $careStory.show();
    }
}

var onCareStoryBtnClick = function(e) {
    e.preventDefault();

    var $this = $(this);

    $careStory.hide();

    if ($this.hasClass('yes')) {
        ANALYTICS.trackEvent('like-story', 'yes');
        $support.show();
    } else {
        ANALYTICS.trackEvent('like-story', 'no');
        $email.show();
    }
}

var onSupportBtnClick = function(e) {
    e.preventDefault();

    var $this = $(this);
    var link = $this.attr('href');

    ANALYTICS.trackEvent('support-btn-click', conclusionTest);

    window.top.location = link
    return true;
}

var onEmailBtnClick = function() {
    ANALYTICS.trackEvent('email-btn-click');
}

var animateProgress = function(index) {
    var totalSlides = $slides.length;
    var percentage = (index + 1) / totalSlides;
    $currentProgress.css('width', percentage * 100 + '%');

    if (index === 0) {
        $progressIndicator.width(0);
    } else {
        $progressIndicator.width('100%');
    }
}

var onSlideLeave = function(anchorLink, index, slideIndex, direction) {
    /*
    * Called when leaving a slide.
    */
    ANALYTICS.exitSlide(slideIndex.toString());
}

var onFirstRightArrowClick = function() {
    if (firstRightArrowClicked === false) {
        ANALYTICS.firstRightArrowClick(arrowTest);
        firstRightArrowClicked = true;
    }
}

var onStartCardButtonClick = function() {
    $.fn.fullpage.moveSlideRight();
    if (isTouch) {
        AUDIO.fakeAmbientPlayer();
        AUDIO.fakeNarrativePlayer();
    }
}

var onDocumentKeyDown = function(e) {
    if (e.which === 37 || e.which === 39) {
        ANALYTICS.useKeyboardNavigation();
        if (e.which === 37) {
            $.fn.fullpage.moveSlideLeft();
        } else if (e.which === 39) {
            $.fn.fullpage.moveSlideRight();
        }
    }
    // jquery.fullpage handles actual scrolling
    return true;
}

var onSlideClick = function(e) {
    if (isTouch) {
        if ($slides.first().hasClass('active')) {
            AUDIO.fakeAmbientPlayer();
            AUDIO.fakeNarrativePlayer();
        }
        $.fn.fullpage.moveSlideRight();
    }
    return true;
}

var onNextPostClick = function(e) {
    e.preventDefault();

    ANALYTICS.trackEvent('next-post');
    window.top.location = NEXT_POST_URL;
    return true;
}

var fakeMobileHover = function() {
    $(this).css({
        'background-color': '#fff',
        'color': '#000',
        'opacity': .9
    });
}

var rmFakeMobileHover = function() {
    $(this).css({
        'background-color': 'rgba(0, 0, 0, 0.2)',
        'color': '#fff',
        'opacity': .3
    });
}

/*
 * Text copied to clipboard.
 */
var onClippyCopy = function(e) {
    alert('Copied to your clipboard!');

    ANALYTICS.copySummary();
}

var onControlBtnClick = function(e) {
    e.preventDefault();
    AUDIO.toggleNarrativeAudio();
    ANALYTICS.trackEvent('pause-button');

    e.stopPropagation();
}

var onVisibilityChange = function() {
    AUDIO.toggleAllAudio();
}

var getHiddenProperty = function() {
    var prefixes = ['webkit','moz','ms','o'];

    // if 'hidden' is natively supported just return it
    if ('hidden' in document) return 'hidden';

    // otherwise loop over all the known prefixes until we find one
    for (var i = 0; i < prefixes.length; i++){
        if ((prefixes[i] + 'Hidden') in document)
            return prefixes[i] + 'Hidden';
    }

    // otherwise it's not supported
    return null;
}

var isHidden = function() {
    var prop = getHiddenProperty();
    if (!prop) return false;

    return document[prop];
}

/*
 * Share modal opened.
 */
var onShareModalShown = function(e) {
    ANALYTICS.openShareDiscuss();
}

/*
 * Share modal closed.
 */
var onShareModalHidden = function(e) {
    ANALYTICS.closeShareDiscuss();
}

$(document).ready(function() {
    $w = $(window).width();
    $h = $(window).height();

    $slides = $('.slide');
    $navButton = $('.primary-navigation-btn');
    $startCardButton = $('.btn-go');
    $arrows = $('.controlArrow');
    $nextArrow = $arrows.filter('.next');
    $upNext = $('.up-next');
    $controlBtn = $('.control-btn');
    $narrativePlayer = $('#narrative-player');
    $ambientPlayer = $('#ambient-player');
    $share = $('.share');
    $shareModal = $('#share-modal')
    $progressIndicator = $('.progress-indicator');
    $currentProgress = $('.current-progress');
    $support = $('.support')
    $supportBtn = $('.support-btn');
    $careStory = $('.care-story');
    $question = $('.question');
    $careStoryBtns = $('.care-story-btn');
    $email = $('.email');
    $emailBtn = $('.email-btn');

    arrowTest = determineTest(['faded-arrow', 'bright-arrow', 'bouncy-arrow']);
    progressTest = determineTest(['progress-bar', 'no-progress-bar']);
    conclusionTest = determineTest(['no-question', 'question_a', 'question_b', 'question_c', 'question_d']);

    $shareModal.on('shown.bs.modal', onShareModalShown);
    $shareModal.on('hidden.bs.modal', onShareModalHidden);
    $startCardButton.on('click', onStartCardButtonClick);
    $slides.on('click', onSlideClick);
    $upNext.on('click', onNextPostClick);
    $controlBtn.on('click', onControlBtnClick);
    $arrows.on('touchstart', fakeMobileHover);
    $arrows.on('touchend', rmFakeMobileHover);
    $careStoryBtns.on('click', onCareStoryBtnClick);
    $supportBtn.on('click', onSupportBtnClick);
    $emailBtn.on('click', onEmailBtnClick);
    $(document).keydown(onDocumentKeyDown);

    AUDIO.setUpNarrativePlayer();
    AUDIO.setUpAmbientPlayer();
    setUpFullPage();
    resize();
    setCustomVars();

    // Redraw slides if the window resizes
    window.addEventListener("deviceorientation", resize, true);
    $(window).resize(resize);

    // listen for page visibility changes
    visibilityProperty = getHiddenProperty();
    if (visibilityProperty) {
        var evtname = visibilityProperty.replace(/[H|h]idden/,'') + 'visibilitychange';
        document.addEventListener(evtname, onVisibilityChange);
    }
});<|endoftext|>


<commit_before>/*
 * Copyright 2013, All Rights Reserved.
 *
 * Code licensed under the BSD License:
 * https://github.com/node-gh/gh/blob/master/LICENSE.md
 *
 * @author Eduardo Lundgren <eduardolundgren@gmail.com>
 */

var fs = require('fs'),
    moment = require('moment'),
    github = require('github'),
    logger = require('./logger'),
    path = require('path'),
    updateNotifier = require('update-notifier'),
    userhome = require('userhome');

exports.github = new github({
    version: '3.0.0',
    debug: false
});

exports.checkVersion = function(opt_callback) {
    var notifier,
        packagesPath = ['../package'],
        plugins = exports.getPlugins();

    plugins.forEach(function(plugin) {
        packagesPath.push(path.join(exports.getPluginsPath(), plugin, 'package'));
    });

    packagesPath.forEach(function(path) {
        notifier = updateNotifier({
            packagePath: path,
            updateCheckInterval: 1000 * 60 * 60 * 24 // 1 day
        });

        if (notifier.update) {
            notifier.notify();
        }
    });

    opt_callback && opt_callback();
};

exports.clone = function(o) {
    var i, c = {};

    for (i in o) {
        if (o.hasOwnProperty(i)) {
            c[i] = o[i];
        }
    }

    return c;
};

exports.find = function(filepath, opt_pattern) {
    return fs.readdirSync(filepath).filter(function(file) {
        return (opt_pattern || /.*/).test(file);
    });
};

exports.getConfigPath = function() {
    return path.join(__dirname, '../', '.gh.json');
};

exports.getDuration = function(start, opt_end) {
    if (opt_end === undefined) {
        opt_end = Date.now();
    }

    return moment.duration(moment(opt_end).diff(start)).humanize(true);
};

exports.getGlobalConfig = function() {
    var configPath,
        config,
        userConfig;

    configPath = exports.getGlobalConfigPath();

    if (!fs.existsSync(configPath)) {
        fs.writeFileSync(configPath, '{}');
    }

    try {
        config = require(exports.getConfigPath());
        userConfig = require(configPath);

        Object.keys(userConfig).forEach(function(value) {
            config[value] = userConfig[value];
        });

        return config;
    }
    catch (err) {
        return {};
    }
};

exports.getGlobalConfigPath = function() {
    return userhome('.gh.json');
};

exports.getPlugins = function() {
    var nodeModulesPath = exports.getPluginsPath(),
        plugins;

    plugins = fs.readdirSync(nodeModulesPath).filter(function(plugin) {
        return plugin.substring(0, 3) === 'gh-';
    });

    return plugins;
};

exports.getPluginsPath = function() {
    return path.join(process.execPath, '/../../lib/node_modules');
};

exports.getUser = function() {
    var config = exports.getGlobalConfig();

    return config.github_user;
};

exports.getUserHomePath = function() {
    return userhome('.gh.json');
};

exports.removeGlobalConfig = function(key) {
    var config = exports.getGlobalConfig();

    delete config[key];

    fs.writeFileSync(
        exports.getUserHomePath(),
        JSON.stringify(config, null, 4)
    );
};

exports.writeGlobalConfig = function(jsonPath, value) {
    var config = exports.getGlobalConfig(),
        i,
        output,
        path,
        pathLen;

    path = jsonPath.split('.');
    output = config;

    for (i = 0, pathLen = path.length; i < pathLen; i++) {
        output[path[i]] = config[path[i]] || (i + 1 === pathLen ? value : {});
        output = output[path[i]];
    }

    fs.writeFileSync(
        exports.getUserHomePath(),
        JSON.stringify(config, null, 4)
    );
};

exports.writeGlobalConfigCredentials = function(user, token) {
    var configPath = exports.getUserHomePath();

    exports.writeGlobalConfig('github_user', user);
    exports.writeGlobalConfig('github_token', token);
    logger.success('Writing GH config data: ' + configPath);
};<commit_msg>Rename base.getPluginsPath() to base.getNodeModulesGlobalPath()<commit_after>/*
 * Copyright 2013, All Rights Reserved.
 *
 * Code licensed under the BSD License:
 * https://github.com/node-gh/gh/blob/master/LICENSE.md
 *
 * @author Eduardo Lundgren <eduardolundgren@gmail.com>
 */

var fs = require('fs'),
    moment = require('moment'),
    github = require('github'),
    logger = require('./logger'),
    path = require('path'),
    updateNotifier = require('update-notifier'),
    userhome = require('userhome');

exports.github = new github({
    version: '3.0.0',
    debug: false
});

exports.checkVersion = function(opt_callback) {
    var notifier,
        packagesPath = ['../package'],
        plugins = exports.getPlugins();

    plugins.forEach(function(plugin) {
        packagesPath.push(path.join(exports.getPluginsPath(), plugin, 'package'));
    });

    packagesPath.forEach(function(path) {
        notifier = updateNotifier({
            packagePath: path,
            updateCheckInterval: 1000 * 60 * 60 * 24 // 1 day
        });

        if (notifier.update) {
            notifier.notify();
        }
    });

    opt_callback && opt_callback();
};

exports.clone = function(o) {
    var i, c = {};

    for (i in o) {
        if (o.hasOwnProperty(i)) {
            c[i] = o[i];
        }
    }

    return c;
};

exports.find = function(filepath, opt_pattern) {
    return fs.readdirSync(filepath).filter(function(file) {
        return (opt_pattern || /.*/).test(file);
    });
};

exports.getConfigPath = function() {
    return path.join(__dirname, '../', '.gh.json');
};

exports.getDuration = function(start, opt_end) {
    if (opt_end === undefined) {
        opt_end = Date.now();
    }

    return moment.duration(moment(opt_end).diff(start)).humanize(true);
};

exports.getNodeModulesGlobalPath = function() {
    return path.join(process.execPath, '/../../lib/node_modules');
};

exports.getGlobalConfig = function() {
    var configPath,
        config,
        userConfig;

    configPath = exports.getGlobalConfigPath();

    if (!fs.existsSync(configPath)) {
        fs.writeFileSync(configPath, '{}');
    }

    try {
        config = require(exports.getConfigPath());
        userConfig = require(configPath);

        Object.keys(userConfig).forEach(function(value) {
            config[value] = userConfig[value];
        });

        return config;
    }
    catch (err) {
        return {};
    }
};

exports.getGlobalConfigPath = function() {
    return userhome('.gh.json');
};

exports.getPlugins = function() {
    var nodeModulesPath = exports.getNodeModulesGlobalPath(),
        plugins;

    plugins = fs.readdirSync(nodeModulesPath).filter(function(plugin) {
        return plugin.substring(0, 3) === 'gh-';
    });

    return plugins;
};

exports.getUser = function() {
    var config = exports.getGlobalConfig();

    return config.github_user;
};

exports.getUserHomePath = function() {
    return userhome('.gh.json');
};

exports.removeGlobalConfig = function(key) {
    var config = exports.getGlobalConfig();

    delete config[key];

    fs.writeFileSync(
        exports.getUserHomePath(),
        JSON.stringify(config, null, 4)
    );
};

exports.writeGlobalConfig = function(jsonPath, value) {
    var config = exports.getGlobalConfig(),
        i,
        output,
        path,
        pathLen;

    path = jsonPath.split('.');
    output = config;

    for (i = 0, pathLen = path.length; i < pathLen; i++) {
        output[path[i]] = config[path[i]] || (i + 1 === pathLen ? value : {});
        output = output[path[i]];
    }

    fs.writeFileSync(
        exports.getUserHomePath(),
        JSON.stringify(config, null, 4)
    );
};

exports.writeGlobalConfigCredentials = function(user, token) {
    var configPath = exports.getUserHomePath();

    exports.writeGlobalConfig('github_user', user);
    exports.writeGlobalConfig('github_token', token);
    logger.success('Writing GH config data: ' + configPath);
};<|endoftext|>


<commit_before>/*
	Typographie, v1.1
	https://github.com/asleepwalker/typographie

	by Artyom "Sleepwalker" Fedosov, 2014
	http://me.asleepwalker.ru/
	mail@asleepwalker.ru
*/

window.onload = function() {

	var OptionsCollection = Backbone.Collection.extend({
		url: 'defaults.json',
		initialize: function() { this.fetch(); }
	});

	var TypographieModel = Backbone.Model.extend({
		defaults: {
			in: 'plain',
			out: 'plain',
			highlight: true,
			in_state: 'free',
		},
		state: function(state) {
			this.set({in_state: state});
		},
		process: function(raw, callback) {
			var core = this;
			$.ajax({
				beforeSend: function() {
					core.state('loading');
				},
				data: {
					in: core.get('in'),
					out: core.get('out'),
					highlight: core.get('highlight') ? 'enabled' : 'disabled',
					actions: (function() {
						var actions = new Backbone.Collection(core.get('options').where({ send: true,  active: true }));
						return actions.pluck('name').join(',');
					}()),
					raw: raw
				},
				type: 'POST',
				url: 'engine/main.php',
				dataType: 'json',
				success: callback,
				error: this.error,
				timeout: 5000
			});
		},
		error: function() {
			this.state('free');
			alert('Ошибка соединения. Попробуйте ещё раз или обратитесь к администратору.');
		}
	});

	var EditorView = Backbone.View.extend({
		el: $('#editor'),
		initialize: function() {
			this.model.bind('change:in', _.bind(this.process, this));
			this.model.bind('change:out', _.bind(this.process, this));
			this.model.bind('change:highlight', _.bind(this.process, this));
		},
		events: {
			'click #input .controls #submit': 'process',
			'change #input #raw_input': 'liveProcess',
			'keyup #input #raw_input': 'liveProcess'
		},
		liveProcess: function() {
			if (this.model.get('options').where({ name: 'live', active: true }).length) {
				var editor = this;
				this.model.state('typing');
				clearTimeout(this.model.get('live'));
				this.model.set({'live': window.setTimeout(function() { editor.process(); }, 1000)});
			}
		},
		process: function() {
			this.model.process($('#raw_input')[0].value, this.render);
			this.model.state('free');
		},
		render: function(data) {
			$('#display').html(data.response);
		}
	});

	var InputSwitcherView = Backbone.View.extend({
		el: $('#input_mode'),
		events: {
			'click LI': 'switchInput',
		},
		initialize: function() {
			this.model.bind('change:in', _.bind(this.render, this));
		},
		switchInput: function(e) {
			var mode = e.target.getAttribute('data-mode');
			this.model.set({in: mode});
		},
		render: function() {
			$('.active', this.$el[0])[0].removeAttribute('class');
			$('[data-mode='+this.model.get('in')+']', this.$el[0])[0].className = 'active';
		}
	});

	var OutputSwitcherView = Backbone.View.extend({
		el: $('#output_mode'),
		events: {
			'click LI': 'switchOutput',
		},
		initialize: function() {
			this.model.bind('change:out', _.bind(this.render, this));
		},
		switchOutput: function(e) {
			var mode = e.target.getAttribute('data-mode');
			this.model.set({out: mode});
		},
		render: function() {
			$('.active', this.$el[0])[0].removeAttribute('class');
			$('[data-mode='+this.model.get('out')+']', this.$el[0])[0].className = 'active';
		}
	});

	var HighlightCheckboxView = Backbone.View.extend({
		el: $('#highlight'),
		events: {
			'click': 'toggleHighlight',
		},
		initialize: function() {
			this.model.bind('change:highlight', _.bind(this.render, this));
		},
		toggleHighlight: function() {
			this.model.set({'highlight': !this.model.get('highlight')});
		},
		render: function() {
			if (this.model.get('highlight')) $('#hcheckbox')[0].setAttribute('checked', '')
			else $('#hcheckbox')[0].removeAttribute('checked');
		}
	});

	var OptionView = Backbone.View.extend({
		tagName: 'li',
		itemTemplate: _.template('
<div class="checkbox"<% if(active) { %> checked<% } %>>
<%= caption %>
'),
		events: {
			'click DIV': 'toggleOption',
		},
		initialize: function() {
			this.model.bind('change:active', _.bind(this.render, this));
		},
		toggleOption: function() {
			this.model.set({'active': !this.model.get('active')});
		},
		render: function() {
			this.$el.html(this.itemTemplate(this.model.toJSON()));
			return this;
		},
		destroy: function() {
			this.model.off('change:active');
			this.el.remove();
		}
	});

	var OptionsDialogView = Backbone.View.extend({
		el: $('#options'),
		initialize: function() {
			$('#show_options').on('click', _.bind(this.showDialog, this));
			$('.hide_options').on('click', _.bind(this.hideDialog, this));
		},
		optionViewList: [],
		showDialog: function() {
			this.render();
			$('#dialog_wrapper').fadeInIfNotShown();
		},
		hideDialog: function(e) {
			var dialogView = this;
			$('#dialog_wrapper').fadeOutIfShown(e, function() {
				dialogView.optionViewList.forEach(function(view) {
					view.destroy();
				});
			});
		},
		render: function() {
			var prevOptionGroup;

			_.each(this.model.get('options').models, function(option) {
				var optionView = new OptionView({model: option}),
				    optionElement = optionView.render().el;
				this.optionViewList.push(optionView);

				if (prevOptionGroup && (prevOptionGroup != option.get('group')))
					optionElement.className = 'first_in_group';
				prevOptionGroup = option.get('group');

				this.el.appendChild(optionElement);
			}, this);
		}
	});

	var LoadingView = Backbone.View.extend({
		el: $('#loader'),
		initialize: function() {
			this.model.bind('change:in_state', _.bind(this.render, this));
		},
		render: function() {
			switch (this.model.get('in_state')) {
				case 'free': this.el.removeAttribute('data-mode'); break;
				case 'typing': this.el.setAttribute('data-mode', 'typing'); break;
				case 'loading': this.el.setAttribute('data-mode', 'loading'); break;
			}
		}
	});

	var optionsCollection = new OptionsCollection();
	var typographieModel = new TypographieModel({options: optionsCollection});
	var editorView = new EditorView({model: typographieModel});
	var inputSwitcherView = new InputSwitcherView({model: typographieModel});
	var outputSwitcherView = new OutputSwitcherView({model: typographieModel});
	var highlightCheckboxView = new HighlightCheckboxView({model: typographieModel});
	var optionsDialogView = new OptionsDialogView({model: typographieModel});
	var loadingView = new LoadingView({model: typographieModel});

	function adaptationResize() {
		var w = window,
		    d = document,
		    e = d.documentElement,
		    g = d.getElementsByTagName('body')[0],
		    y = w.innerHeight|| e.clientHeight || g.clientHeight;
		h = y/2;
		if (h > (y-300)) h = y-300;
		if (h < 200) h = 200;
		document.getElementById('input').style.height = h+'px';
		document.getElementById('output').style.height = h+'px';
		document.getElementById('raw_input').style.height = (h-40)+'px';
		document.getElementById('display').style.height = (h-40)+'px';
	}
	window.onresize = adaptationResize;
	adaptationResize();

};

//-------------------------------------------------------------------
// DIRTY WORK

$.prototype.fadeInIfNotShown = function(callback) {
	var el = this[0];
	if (!el.hasAttribute('shown')) {
		el.style.display = 'block';
		el.style.opacity = 0;
		var last = +new Date();
		var tick = function() {
			el.style.opacity = +el.style.opacity + (new Date() - last) / 400;
			last = +new Date();
			if (+el.style.opacity < 1) {
				(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 10)
			} else {
				el.setAttribute('shown', '');
				if (typeof callback == 'function') callback();
			}
		};
		tick();
	}
};

$.prototype.fadeOutIfShown = function(e, callback) {
	var el = this[0];
	if ((e.target.className.indexOf('hide_options') != -1) && el.hasAttribute('shown')) {
		var last = +new Date();

		var tick = function() {
			el.style.opacity = +el.style.opacity - (new Date() - last) / 400;
			last = +new Date();

			if (+el.style.opacity > 0) {
				(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16)
			} else {
				el.style.display = 'none';
				el.removeAttribute('shown');
				if (typeof callback == 'function') callback();
			}
		};
		tick();
	}
};<commit_msg>Fixed not showing loading indicator<commit_after>/*
	Typographie, v1.1
	https://github.com/asleepwalker/typographie

	by Artyom "Sleepwalker" Fedosov, 2014
	http://me.asleepwalker.ru/
	mail@asleepwalker.ru
*/

window.onload = function() {

	var OptionsCollection = Backbone.Collection.extend({
		url: 'defaults.json',
		initialize: function() { this.fetch(); }
	});

	var TypographieModel = Backbone.Model.extend({
		defaults: {
			in: 'plain',
			out: 'plain',
			highlight: true,
			in_state: 'free',
		},
		state: function(state) {
			this.set({in_state: state});
		},
		process: function(raw, callback) {
			var core = this;
			$.ajax({
				beforeSend: function() {
					core.state('loading');
				},
				data: {
					in: core.get('in'),
					out: core.get('out'),
					highlight: core.get('highlight') ? 'enabled' : 'disabled',
					actions: (function() {
						var actions = new Backbone.Collection(core.get('options').where({ send: true,  active: true }));
						return actions.pluck('name').join(',');
					}()),
					raw: raw
				},
				type: 'POST',
				url: 'engine/main.php',
				dataType: 'json',
				success: function(data) {
					core.state('free');
					callback(data);
				},
				error: this.error,
				timeout: 5000
			});
		},
		error: function() {
			this.state('free');
			alert('Ошибка соединения. Попробуйте ещё раз или обратитесь к администратору.');
		}
	});

	var EditorView = Backbone.View.extend({
		el: $('#editor'),
		initialize: function() {
			this.model.bind('change:in', _.bind(this.process, this));
			this.model.bind('change:out', _.bind(this.process, this));
			this.model.bind('change:highlight', _.bind(this.process, this));
		},
		events: {
			'click #input .controls #submit': 'process',
			'change #input #raw_input': 'liveProcess',
			'keyup #input #raw_input': 'liveProcess'
		},
		liveProcess: function() {
			if (this.model.get('options').where({ name: 'live', active: true }).length) {
				var editor = this;
				this.model.state('typing');
				clearTimeout(this.model.get('live'));
				this.model.set({'live': window.setTimeout(function() { editor.process(); }, 1000)});
			}
		},
		process: function() {
			this.model.process($('#raw_input')[0].value, this.render);
		},
		render: function(data) {
			$('#display').html(data.response);
		}
	});

	var InputSwitcherView = Backbone.View.extend({
		el: $('#input_mode'),
		events: {
			'click LI': 'switchInput',
		},
		initialize: function() {
			this.model.bind('change:in', _.bind(this.render, this));
		},
		switchInput: function(e) {
			var mode = e.target.getAttribute('data-mode');
			this.model.set({in: mode});
		},
		render: function() {
			$('.active', this.$el[0])[0].removeAttribute('class');
			$('[data-mode='+this.model.get('in')+']', this.$el[0])[0].className = 'active';
		}
	});

	var OutputSwitcherView = Backbone.View.extend({
		el: $('#output_mode'),
		events: {
			'click LI': 'switchOutput',
		},
		initialize: function() {
			this.model.bind('change:out', _.bind(this.render, this));
		},
		switchOutput: function(e) {
			var mode = e.target.getAttribute('data-mode');
			this.model.set({out: mode});
		},
		render: function() {
			$('.active', this.$el[0])[0].removeAttribute('class');
			$('[data-mode='+this.model.get('out')+']', this.$el[0])[0].className = 'active';
		}
	});

	var HighlightCheckboxView = Backbone.View.extend({
		el: $('#highlight'),
		events: {
			'click': 'toggleHighlight',
		},
		initialize: function() {
			this.model.bind('change:highlight', _.bind(this.render, this));
		},
		toggleHighlight: function() {
			this.model.set({'highlight': !this.model.get('highlight')});
		},
		render: function() {
			if (this.model.get('highlight')) $('#hcheckbox')[0].setAttribute('checked', '')
			else $('#hcheckbox')[0].removeAttribute('checked');
		}
	});

	var OptionView = Backbone.View.extend({
		tagName: 'li',
		itemTemplate: _.template('
<div class="checkbox"<% if(active) { %> checked<% } %>>
<%= caption %>
'),
		events: {
			'click DIV': 'toggleOption',
		},
		initialize: function() {
			this.model.bind('change:active', _.bind(this.render, this));
		},
		toggleOption: function() {
			this.model.set({'active': !this.model.get('active')});
		},
		render: function() {
			this.$el.html(this.itemTemplate(this.model.toJSON()));
			return this;
		},
		destroy: function() {
			this.model.off('change:active');
			this.el.remove();
		}
	});

	var OptionsDialogView = Backbone.View.extend({
		el: $('#options'),
		initialize: function() {
			$('#show_options').on('click', _.bind(this.showDialog, this));
			$('.hide_options').on('click', _.bind(this.hideDialog, this));
		},
		optionViewList: [],
		showDialog: function() {
			this.render();
			$('#dialog_wrapper').fadeInIfNotShown();
		},
		hideDialog: function(e) {
			var dialogView = this;
			$('#dialog_wrapper').fadeOutIfShown(e, function() {
				dialogView.optionViewList.forEach(function(view) {
					view.destroy();
				});
			});
		},
		render: function() {
			var prevOptionGroup;

			_.each(this.model.get('options').models, function(option) {
				var optionView = new OptionView({model: option}),
				    optionElement = optionView.render().el;
				this.optionViewList.push(optionView);

				if (prevOptionGroup && (prevOptionGroup != option.get('group')))
					optionElement.className = 'first_in_group';
				prevOptionGroup = option.get('group');

				this.el.appendChild(optionElement);
			}, this);
		}
	});

	var LoadingView = Backbone.View.extend({
		el: $('#loader'),
		initialize: function() {
			this.model.bind('change:in_state', _.bind(this.render, this));
		},
		render: function() {
			switch (this.model.get('in_state')) {
				case 'free': this.el.removeAttribute('data-mode'); break;
				case 'typing': this.el.setAttribute('data-mode', 'typing'); break;
				case 'loading': this.el.setAttribute('data-mode', 'loading'); break;
			}
		}
	});

	var optionsCollection = new OptionsCollection();
	var typographieModel = new TypographieModel({options: optionsCollection});
	var editorView = new EditorView({model: typographieModel});
	var inputSwitcherView = new InputSwitcherView({model: typographieModel});
	var outputSwitcherView = new OutputSwitcherView({model: typographieModel});
	var highlightCheckboxView = new HighlightCheckboxView({model: typographieModel});
	var optionsDialogView = new OptionsDialogView({model: typographieModel});
	var loadingView = new LoadingView({model: typographieModel});

};

//-------------------------------------------------------------------
// DIRTY WORK

function adaptationResize() {
	var w = window,
	    d = document,
	    e = d.documentElement,
	    g = d.getElementsByTagName('body')[0],
	    y = w.innerHeight|| e.clientHeight || g.clientHeight;
	h = y/2;
	if (h > (y-300)) h = y-300;
	if (h < 200) h = 200;
	document.getElementById('input').style.height = h+'px';
	document.getElementById('output').style.height = h+'px';
	document.getElementById('raw_input').style.height = (h-40)+'px';
	document.getElementById('display').style.height = (h-40)+'px';
}
window.onresize = adaptationResize;
adaptationResize();

$.prototype.fadeInIfNotShown = function(callback) {
	var el = this[0];
	if (!el.hasAttribute('shown')) {
		el.style.display = 'block';
		el.style.opacity = 0;
		var last = +new Date();
		var tick = function() {
			el.style.opacity = +el.style.opacity + (new Date() - last) / 400;
			last = +new Date();
			if (+el.style.opacity < 1) {
				(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 10)
			} else {
				el.setAttribute('shown', '');
				if (typeof callback == 'function') callback();
			}
		};
		tick();
	}
};

$.prototype.fadeOutIfShown = function(e, callback) {
	var el = this[0];
	if ((e.target.className.indexOf('hide_options') != -1) && el.hasAttribute('shown')) {
		var last = +new Date();

		var tick = function() {
			el.style.opacity = +el.style.opacity - (new Date() - last) / 400;
			last = +new Date();

			if (+el.style.opacity > 0) {
				(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16)
			} else {
				el.style.display = 'none';
				el.removeAttribute('shown');
				if (typeof callback == 'function') callback();
			}
		};
		tick();
	}
};<|endoftext|>


<commit_before>"use strict";

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('wuw', ['ionic', 'wuw.controllers', 'wuw.services', 'wuw.filters', 'angular.filter'])

.run(function($ionicPlatform, Settings) {
  $ionicPlatform.ready(function() {
    // predefined settings
    if (typeof Settings.getSetting('apiUrl') === 'undefined') {
        Settings.setSetting('apiUrl', 'http://wuw.benleb.de:8088/api/v0');
    }
    if (typeof Settings.getSetting('course') === 'undefined') {
        Settings.setSetting('course', 'IF');
    }

    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
  });
})

.config(function($stateProvider, $urlRouterProvider) {

    // Ionic uses AngularUI Router which uses the concept of states
    // Learn more here: https://github.com/angular-ui/ui-router
    // Set up the various states which the app can be in.
    // Each state's controller can be found in controllers.js

    $stateProvider
    // setup an abstract state for the tabs directive
    .state('tab', {
        url: "/tab",
        abstract: true,
        templateUrl: "templates/tabs.html"
    })

    // Each tab has its own nav history stack:
    .state('tab.home', {
        url: '/home',
        views: {
            'tab-home': {
            templateUrl: 'templates/tab-home.html',
            controller: 'HomeCtrl'
        }
      }
    })

    .state('tab.deadlines', {
        url: '/deadlines',
        views: {
            'tab-deadlines': {
            templateUrl: 'templates/tab-deadlines.html',
            controller: 'DeadlinesCtrl'
        }
      }
    })
    .state('tab.deadline-detail', {
        url: '/deadlines/:deadlineId',
        views: {
            'tab-deadlines': {
            templateUrl: 'templates/tab-deadline-detail.html',
            controller: 'DeadlinesDetailCtrl'
        }
      }
    })
    .state('tab.deadline-create', {
        url: '/createDeadline',
        views: {
            'tab-deadlines': {
            templateUrl: 'templates/tab-create-deadline.html',
            controller: 'DeadlinesCreateCtrl'
        }
      }
    })

    .state('tab.lectures', {
        url: '/lectures',
        views: {
            'tab-lectures': {
            templateUrl: 'templates/tab-lectures.html',
            controller: 'LecturesCtrl'
        }
      }
    })
    .state('tab.lectures-detail', {
        url: '/lectures/:lectureId',
        views: {
            'tab-lectures': {
            templateUrl: 'templates/tab-lectures-detail.html',
            controller: 'LecturesDetailCtrl'
        }
      }
    })

    .state('tab.settings', {
        url: '/settings',
        views: {
            'tab-settings': {
            templateUrl: 'templates/tab-settings.html',
            controller: 'SettingsCtrl'
        }
      }
    });

    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/tab/home');
});
<commit_msg>we can set default values before the platform is ready<commit_after>"use strict";

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('wuw', ['ionic', 'wuw.controllers', 'wuw.services', 'wuw.filters', 'angular.filter'])

.run(function($ionicPlatform, Settings) {

  // predefined settings
  if (typeof Settings.getSetting('apiUrl') === 'undefined') {
      Settings.setSetting('apiUrl', 'http://wuw.benleb.de:8088/api/v0');
  }
  if (typeof Settings.getSetting('course') === 'undefined') {
      Settings.setSetting('course', 'IF');
  }

  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
  });
})

.config(function($stateProvider, $urlRouterProvider) {

    // Ionic uses AngularUI Router which uses the concept of states
    // Learn more here: https://github.com/angular-ui/ui-router
    // Set up the various states which the app can be in.
    // Each state's controller can be found in controllers.js

    $stateProvider
    // setup an abstract state for the tabs directive
    .state('tab', {
        url: "/tab",
        abstract: true,
        templateUrl: "templates/tabs.html"
    })

    // Each tab has its own nav history stack:
    .state('tab.home', {
        url: '/home',
        views: {
            'tab-home': {
            templateUrl: 'templates/tab-home.html',
            controller: 'HomeCtrl'
        }
      }
    })

    .state('tab.deadlines', {
        url: '/deadlines',
        views: {
            'tab-deadlines': {
            templateUrl: 'templates/tab-deadlines.html',
            controller: 'DeadlinesCtrl'
        }
      }
    })
    .state('tab.deadline-detail', {
        url: '/deadlines/:deadlineId',
        views: {
            'tab-deadlines': {
            templateUrl: 'templates/tab-deadline-detail.html',
            controller: 'DeadlinesDetailCtrl'
        }
      }
    })
    .state('tab.deadline-create', {
        url: '/createDeadline',
        views: {
            'tab-deadlines': {
            templateUrl: 'templates/tab-create-deadline.html',
            controller: 'DeadlinesCreateCtrl'
        }
      }
    })

    .state('tab.lectures', {
        url: '/lectures',
        views: {
            'tab-lectures': {
            templateUrl: 'templates/tab-lectures.html',
            controller: 'LecturesCtrl'
        }
      }
    })
    .state('tab.lectures-detail', {
        url: '/lectures/:lectureId',
        views: {
            'tab-lectures': {
            templateUrl: 'templates/tab-lectures-detail.html',
            controller: 'LecturesDetailCtrl'
        }
      }
    })

    .state('tab.settings', {
        url: '/settings',
        views: {
            'tab-settings': {
            templateUrl: 'templates/tab-settings.html',
            controller: 'SettingsCtrl'
        }
      }
    });

    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/tab/home');
});
<|endoftext|>


<commit_before>'use strict';

var d        = require('es5-ext/lib/Object/descriptor')
  , callable = require('es5-ext/lib/Object/valid-callable')
  , id       = require('./_id')

  , apply = Function.prototype.apply, call = Function.prototype.call
  , create = Object.create, defineProperty = Object.defineProperty
  , defineProperties = Object.defineProperties

  , on, once, off, emit
  , colId, methods, descriptors, base;

colId = id + 'l_';

on = function (type, listener) {
	var data;

	callable(listener);

	if (!this.hasOwnProperty(id)) {
		defineProperty(this, id, d(data = {}));
	} else {
		data = this[id];
	}
	if (!data.hasOwnProperty(type)) {
		data[type] = listener;
	} else if (data[type].hasOwnProperty(colId)) {
		data[type].push(listener);
	} else {
		(data[type] = [data[type], listener])[colId] = true;
	}

	return this;
};

once = function (type, listener) {
	var once, self;

	callable(listener);
	self = this;
	on.call(this, type, once = function () {
		off.call(self, type, once);
		apply.call(listener, this, arguments);
	});

	once._listener = listener;
	return this;
};

off = function (type, listener) {
	var data, listeners, candidate, i;

	callable(listener);

	if (!this.hasOwnProperty(id)) {
		return this;
	}
	data = this[id];
	if (!data.hasOwnProperty(type)) {
		return this;
	}
	listeners = data[type];

	if (listeners.hasOwnProperty(colId)) {
		for (i = 0; (candidate = listeners[i]); ++i) {
			if ((candidate === listener) || (candidate._listener === listener)) {
				if (listeners.length === 2) {
					data[type] = listeners[i ? 0 : 1];
				} else {
					listeners.splice(i, 1);
				}
			}
		}
	} else {
		if ((listeners === listener) || (listeners._listener === listener)) {
			delete data[type];
		}
	}

	return this;
};

emit = function (type) {
	var data, i, l, listener, listeners, args;

	if (!this.hasOwnProperty(id)) {
		return;
	}
	data = this[id];
	if (!data.hasOwnProperty(type)) {
		return;
	}
	listeners = data[type];

	if (listeners.hasOwnProperty(colId)) {
		l = arguments.length;
		args = new Array(l - 1);
		for (i = 1; i < l; ++i) {
			args[i - 1] = arguments[i];
		}

		listeners = listeners.slice();
		for (i = 0; (listener = listeners[i]); ++i) {
			apply.call(listener, this, args);
		}
	} else {
		switch (arguments.length) {
		case 1:
			call.call(listeners, this);
			break;
		case 2:
			call.call(listeners, this, arguments[1]);
			break;
		case 3:
			call.call(listeners, this, arguments[1], arguments[2]);
			break;
		default:
			l = arguments.length;
			args = new Array(l - 1);
			for (i = 1; i < l; ++i) {
				args[i - 1] = arguments[i];
			}
			apply.call(listeners, this, args);
		}
	}
};

methods = {
	on: on,
	once: once,
	off: off,
	emit: emit
};

descriptors = {
	on: d(on),
	once: d(once),
	off: d(off),
	emit: d(emit)
};

base = defineProperties({}, descriptors);

module.exports = exports = function (o) {
	return (o == null) ? create(base) : defineProperties(Object(o), descriptors);
};
exports.methods = methods;
<commit_msg>Be nice to garbage collector and reuse descriptor<commit_after>'use strict';

var d        = require('es5-ext/lib/Object/descriptor')
  , callable = require('es5-ext/lib/Object/valid-callable')
  , id       = require('./_id')

  , apply = Function.prototype.apply, call = Function.prototype.call
  , create = Object.create, defineProperty = Object.defineProperty
  , defineProperties = Object.defineProperties
  , descriptor = { configurable: true, enumerable: false, writable: true }

  , on, once, off, emit
  , colId, methods, descriptors, base;

colId = id + 'l_';

on = function (type, listener) {
	var data;

	callable(listener);

	if (!this.hasOwnProperty(id)) {
		data = descriptor.value = {};
		defineProperty(this, id, descriptor);
		descriptor.value = null;
	} else {
		data = this[id];
	}
	if (!data.hasOwnProperty(type)) {
		data[type] = listener;
	} else if (data[type].hasOwnProperty(colId)) {
		data[type].push(listener);
	} else {
		(data[type] = [data[type], listener])[colId] = true;
	}

	return this;
};

once = function (type, listener) {
	var once, self;

	callable(listener);
	self = this;
	on.call(this, type, once = function () {
		off.call(self, type, once);
		apply.call(listener, this, arguments);
	});

	once._listener = listener;
	return this;
};

off = function (type, listener) {
	var data, listeners, candidate, i;

	callable(listener);

	if (!this.hasOwnProperty(id)) {
		return this;
	}
	data = this[id];
	if (!data.hasOwnProperty(type)) {
		return this;
	}
	listeners = data[type];

	if (listeners.hasOwnProperty(colId)) {
		for (i = 0; (candidate = listeners[i]); ++i) {
			if ((candidate === listener) || (candidate._listener === listener)) {
				if (listeners.length === 2) {
					data[type] = listeners[i ? 0 : 1];
				} else {
					listeners.splice(i, 1);
				}
			}
		}
	} else {
		if ((listeners === listener) || (listeners._listener === listener)) {
			delete data[type];
		}
	}

	return this;
};

emit = function (type) {
	var data, i, l, listener, listeners, args;

	if (!this.hasOwnProperty(id)) {
		return;
	}
	data = this[id];
	if (!data.hasOwnProperty(type)) {
		return;
	}
	listeners = data[type];

	if (listeners.hasOwnProperty(colId)) {
		l = arguments.length;
		args = new Array(l - 1);
		for (i = 1; i < l; ++i) {
			args[i - 1] = arguments[i];
		}

		listeners = listeners.slice();
		for (i = 0; (listener = listeners[i]); ++i) {
			apply.call(listener, this, args);
		}
	} else {
		switch (arguments.length) {
		case 1:
			call.call(listeners, this);
			break;
		case 2:
			call.call(listeners, this, arguments[1]);
			break;
		case 3:
			call.call(listeners, this, arguments[1], arguments[2]);
			break;
		default:
			l = arguments.length;
			args = new Array(l - 1);
			for (i = 1; i < l; ++i) {
				args[i - 1] = arguments[i];
			}
			apply.call(listeners, this, args);
		}
	}
};

methods = {
	on: on,
	once: once,
	off: off,
	emit: emit
};

descriptors = {
	on: d(on),
	once: d(once),
	off: d(off),
	emit: d(emit)
};

base = defineProperties({}, descriptors);

module.exports = exports = function (o) {
	return (o == null) ? create(base) : defineProperties(Object(o), descriptors);
};
exports.methods = methods;
<|endoftext|>