<commit_before>/**
 * Parses text to return to the log items
 * Created by cos-caner on 12/4/15.
 * Remember to embed nodes, user, linked_node and template_node in api call i.e. var url = $osf.apiV2Url('nodes/' + nodeId + '/logs/', { query : { 'embed' : ['nodes', 'user']}});
 */
var m = require('mithril'); // exposes mithril methods, useful for redraw etc.
var logActions = require('json!js/_allLogTexts.json');
var $ = require('jquery');  // jQuery
var $osf = require('js/osfHelpers');

var ravenMessagesCache = []; // Cache messages to avoid sending multiple times in one page view
/**
 * Utility function to not repeat logging errors to Sentry
 * @param message {String} Custom message for error
 * @param logObject {Object} the entire log object returned from the api
 */
function ravenMessage (message, logObject) {
    if(ravenMessagesCache.indexOf(message) === -1){
        Raven.captureMessage(message, {logObject: logObject});
        ravenMessagesCache.push(message);
    }
}

/**
 * Checks if the required parameter to complete the log is returned
 * This may intentionally not be returned to make log anonymous
 * @param param {string|number} The parameter to be used
 * @param logObject {Object} the entire log object returned from the api
 * @returns {boolean}
 */
var paramIsReturned = function _paramIsReturned (param, logObject){
    if(!param){
        var message = 'Expected parameter for Log action ' + logObject.attributes.action + ' was not returned from log api.';
        ravenMessage(message, logObject);
        return false;
    }
    if (param.errors){
        return false;
    }
    return true;
};

/**
 * Returns the text parameters since their formatting is mainly the same
 * @param param {string} The parameter to be used, has to be available under logObject.attributes.param
 * @param text {string} The text to be used if param is not available
 * @param logObject {Object} the entire log object returned from the api
 * @returns {*}
 */
var returnTextParams = function (param, text, logObject, view_url) {
    var source = logObject.attributes.params[param];

    if(paramIsReturned(source, logObject)){
        if($.isArray(source)){
            return m('span', [
                source.map(function(item, index, arr){
                    if(arr.length === 1 && index === 0){
                        return m('span', item);
                    }
                    if(index === arr.length-1) {
                        return m('span', ', and ' + item);
                    }
                    return m('span', item + ', ');
                })
            ]);
        }
        if (param === 'path' && source.charAt(0) === '/'){
            source = source.substr(1, source.length);
        }
        return view_url ? m('a', {href: view_url}, source) : m('span', source);
    }
    return m('span', text);
};

var LogText = {
    controller: function(logObject){
        var self = this;

        self.userInfoReturned = function(userObject){
            if (userObject){
                if (userObject.data){
                    return true;
                }
                else if (userObject.errors[0].meta){
                    return true;
                }
            }
            return false;
        };

        self.logText = function() {
            var text = logActions[logObject.attributes.action];
            if (text) {
                if (text.indexOf('${user}') !== -1) {
                    var userObject = logObject.embeds.user;
                    if (self.userInfoReturned(userObject)) {
                        return text;
                    }
                    else {
                        var newAction = logObject.attributes.action + '_no_user';
                        return logActions[newAction] ? logActions[newAction]: text;
                    }
                }
                return text;
            }
        return null;
        };
    },
    view : function(ctrl, logObject) {
        var message = '';
        var text = ctrl.logText();
        if(text){
            var list = text.split(/(\${.*?})/);
            return m('span.osf-log-item',[
                list.map(function(piece){
                    if (piece === '') {
                        return m('span');
                    }
                    var startsWith = piece.substring(0,2);
                    if(startsWith === '${'){
                        var last = piece.length-1;
                        var logComponentName = piece.substring(2,last);
                        if(LogPieces[logComponentName]){
                            return m.component(LogPieces[logComponentName], logObject);
                        } else {
                            message = 'There is no template in logTextParser.js for  ' + logComponentName + '.';
                            ravenMessage(message, logObject);
                            return m('');
                        }
                    }
                    return piece;
                })
            ]);
        } else {
            message = 'There is no text entry in dictionary for the action :' + logObject.attributes.action;
            ravenMessage(message, logObject);
            return m('');
        }
    }
};

var LogPieces = {
    // User that took the action
    user: {
        view: function (ctrl, logObject) {
            var userObject = logObject.embeds.user;
            if(paramIsReturned(userObject, logObject) && userObject.data) {
                return m('a', {href: userObject.data.links.html, onclick: function() {
                    $osf.trackClick(logObject.trackingCategory, logObject.trackingAction, 'navigate-to-user-from-logs');
                }}, userObject.data.attributes.full_name);
            }
            else if (userObject && userObject.errors[0].meta) {
                return m('span', userObject.errors[0].meta.full_name);
            }
            else {
                return m('span', 'A user');
            }
        }
    },
    // Node involved
    node: {
        view: function (ctrl, logObject) {
            var nodeObject = logObject.embeds.nodes;

            if (logObject.attributes.action === 'node_removed') {
                if (logObject.attributes.params.params_node) {
                return m('span', logObject.attributes.params.params_node.title);
            }}
            else if(paramIsReturned(nodeObject, logObject) && nodeObject.data[0]){
                if (nodeObject.data[0].links && nodeObject.data[0].attributes) {
                    return m('a', {href: nodeObject.data[0].links.html, onclick: function() {
                        $osf.trackClick(logObject.trackingCategory, logObject.trackingAction, 'navigate-to-project-from-logs');
                    }}, nodeObject.data[0].attributes.title);
                }
                else if (nodeObject.data[0].attributes) {
                    return m('span', nodeObject.data[0].attributes.title);
                }
            } else {
                return m('span', 'a project');
            }
        }
    },

    // Contributor list of added, updated etc.
    contributors: {
        view: function (ctrl, logObject) {
            var contributors = logObject.embeds.contributors;
            if(paramIsReturned(contributors, logObject)) {
                return m('span', contributors.data.map(function(item, index, arr){
                    var comma = ' ';
                    if(index !== arr.length - 1){
                        comma = ', ';
                    }
                    if(index === arr.length-2){
                        comma = ' and ';
                    }
                    if (item.attributes) {
                        if (item.attributes.active) {
                            return [ m('a', {href: item.links.html}, item.attributes.full_name), comma];
                        }
                        else {
                            return [item.attributes.full_name, comma];
                        }
                    }
                }));
            }
            return m('span', 'some users');
        }
    },
    // The tag added to item involved
    tag: {
        view: function (ctrl, logObject) {
            var tag = logObject.attributes.params.tag;
            if(paramIsReturned(tag, logObject)) {
               return m('a', {href: '/search/?q=%22' + tag + '%22'}, tag);
            }
            return m('span', 'a tag');
       }
    },
    // Node that is linked to the node involved
    pointer: {
        view: function (ctrl, logObject) {
            var linked_node = logObject.embeds.linked_node;
            if(paramIsReturned(linked_node, logObject)){
                return m('a', {href: linked_node.data.links.html}, linked_node.data.attributes.title);
            }
            return m('span','a project');
        }
    },
    // Pointer category
    category: {
      view: function (ctrl, logObject) {
            var linked_node = logObject.embeds.linked_node;
            if(paramIsReturned(linked_node, logObject)){
                return m('span', linked_node.data.attributes.category);
            }
            return m('span','');
      }
    },
    // Node that acted as template to create a new node involved
    template: {
        view: function (ctrl, logObject) {
            var template_node = logObject.embeds.template_node;
            if(paramIsReturned(template_node, logObject)){
                return m('a', {href: template_node.data.links.html}, template_node.data.attributes.title);
            }
            return m('span','a project' );
        }
    },
    // The original title of node involved
    title_original: {
        view: function (ctrl, logObject) {
            return returnTextParams('title_original', 'a title', logObject);
        }
    },
    // The new title of node involved
    title_new: {
        view: function (ctrl, logObject) {
            return returnTextParams('title_new', 'a title', logObject);
        }
    },
    // Update fields for the node
    updated_fields: {
        view: function (ctrl, logObject) {
            return returnTextParams('updated_fields', 'field(s)', logObject);
        }
    },
    // external identifiers on node
    identifiers: {
        view: function (ctrl, logObject) {
            return returnTextParams('identifiers', 'identifier(s)', logObject);
        }
    },
    // Wiki page name
    page: {
        view: function (ctrl, logObject) {
            return returnTextParams('page', 'a title', logObject);
        }
    },
    // Old wiki title that's renamed
    old_page: {
        view: function (ctrl, logObject) {
            return returnTextParams('old_page', 'a title', logObject);
        }
    },
    // Wiki page version
    version: {
        view: function (ctrl, logObject) {
            var version = logObject.attributes.params.version;
            if(paramIsReturned(version, logObject)){
                return m('span', version);
            }
            return m('span', '#');
        }
    },
    //
    source: {
        view: function (ctrl, logObject) {
            var source = logObject.attributes.params.source;
            if(paramIsReturned(source, logObject)){
                return m('span', [source.materialized, ' in ', source.addon]);
            }
            return m('span','a name/location' );
        }
    },
    //
    destination: {
        view: function (ctrl, logObject) {
            var destination = logObject.attributes.params.destination;
            if(paramIsReturned(destination, logObject)){
                if (destination.materialized.endsWith('/')){
                    return m('span', [destination.materialized, ' in ', destination.addon]);
                }
                return m('span', [m('a', {href: destination.url}, destination.materialized), ' in ', destination.addon]);
            }
            return m('span','a new name/location' );
        }
    },
        //
    path: {
        controller: function(logObject){
            var self = this;
            self.returnLinkForPath = function() {
                if (logObject) {
                    var action = logObject.attributes.action;
                    var acceptableLinkedItems = ['osf_storage_file_added', 'osf_storage_file_updated'];
                    if (acceptableLinkedItems.indexOf(action) !== -1) {
                        return logObject.attributes.params.view_url;
                    }
                }
                return null;
            };
        },
        view: function (ctrl, logObject) {
            var url = ctrl.returnLinkForPath();
            return returnTextParams('path', 'a file', logObject, url);
        }
    },
        //
    filename: {
        view: function (ctrl, logObject) {
            return returnTextParams('filename', 'a title', logObject);
        }
    },

    folder: {
        view: function(ctrl, logObject) {
            return returnTextParams('folder', 'a folder', logObject);
        }
    },

    repo: {
        view: function(ctrl, logObject) {
            var github_user = logObject.attributes.params.github_user;
            var github_repo = logObject.attributes.params.github_repo;
            if (paramIsReturned(github_repo, logObject) && paramIsReturned(github_user, logObject)){
                return m('span', github_user + '/' + github_repo);
            }
            return m('span', '');
        }
    },

    folder_name: {
        view: function(ctrl, logObject) {
            return returnTextParams('folder_name', 'a folder', logObject);
        }
    },

    bucket: {
        view: function(ctrl, logObject) {
            return returnTextParams('bucket', 'a bucket', logObject);
        }
    },

    forward_url: {
        view: function(ctrl, logObject) {
            var url = logObject.attributes.params.forward_url;
            return returnTextParams('forward_url', 'a new URL', logObject, url);
        }
    },

    box_folder: {
        view: function(ctrl, logObject) {
            var folder = logObject.attributes.params.folder;
            if(paramIsReturned(folder, logObject)){
                return m('span', folder === 'All Files' ? '/ (Full Box)' : (folder || '').replace('All Files',''));
            }
            return m('span', '');
        }
    },

    citation: {
        view: function(ctrl, logObject) {
            return returnTextParams('citation_name', '', logObject);
        }
    },

    dataset: {
        view: function(ctrl, logObject){
            return returnTextParams('data_set', '', logObject);
        }
    },

    study: {
        view: function(ctrl, logObject){
            return returnTextParams('study', '', logObject);
        }
    },

    googledrive_path: {
        view: function(ctrl, logObject){
            var path = logObject.attributes.params.path;
            if(paramIsReturned(path, logObject)){
                return m('span', decodeURIComponent(path));
            }
            return m('span', '');
        }
    },

    googledrive_folder: {
        view: function(ctrl, logObject){
            var folder = logObject.attributes.params.folder;
            if(paramIsReturned(folder, logObject)){
                return m('span', folder === '/' ? '(Full Google Drive)' : decodeURIComponent(folder));
            }
            return m('span', '');
        }
    },

    addon: {
        view: function(ctrl, logObject){
            return returnTextParams('addon', '', logObject);
        }
    },

    previous_institution: {
        view: function(ctrl, logObject){
            var previous_institution = logObject.attributes.params.previous_institution;
            if (paramIsReturned(previous_institution, logObject)){
                return m('span', previous_institution.name);
            }
            return m('span', 'an institution');
        }
    },

    institution: {
        view: function(ctrl, logObject){
            var institution = logObject.attributes.params.institution;
            if (paramIsReturned(institution, logObject)){
                return m('span', institution.name);
            }
            return m('span', 'an institution');
        }
    },

    comment_file: {
        view: function(ctrl,logObject){
            var file = logObject.attributes.params.file;
            if (file){  // skipe paramIsReturned, as not having a file is expected at times
                return m('span', ['in ', m('a', {href: file.url}, file.name)]);
            }
            return m('span', '');
        }
    }
};

module.exports = LogText;
<commit_msg>Add links to log actions wiki_updated and wiki_renamed.  Old wiki title does not get a link.<commit_after>/**
 * Parses text to return to the log items
 * Created by cos-caner on 12/4/15.
 * Remember to embed nodes, user, linked_node and template_node in api call i.e. var url = $osf.apiV2Url('nodes/' + nodeId + '/logs/', { query : { 'embed' : ['nodes', 'user']}});
 */
var m = require('mithril'); // exposes mithril methods, useful for redraw etc.
var logActions = require('json!js/_allLogTexts.json');
var $ = require('jquery');  // jQuery
var $osf = require('js/osfHelpers');

var ravenMessagesCache = []; // Cache messages to avoid sending multiple times in one page view
/**
 * Utility function to not repeat logging errors to Sentry
 * @param message {String} Custom message for error
 * @param logObject {Object} the entire log object returned from the api
 */
function ravenMessage (message, logObject) {
    if(ravenMessagesCache.indexOf(message) === -1){
        Raven.captureMessage(message, {logObject: logObject});
        ravenMessagesCache.push(message);
    }
}

/**
 * Checks if the required parameter to complete the log is returned
 * This may intentionally not be returned to make log anonymous
 * @param param {string|number} The parameter to be used
 * @param logObject {Object} the entire log object returned from the api
 * @returns {boolean}
 */
var paramIsReturned = function _paramIsReturned (param, logObject){
    if(!param){
        var message = 'Expected parameter for Log action ' + logObject.attributes.action + ' was not returned from log api.';
        ravenMessage(message, logObject);
        return false;
    }
    if (param.errors){
        return false;
    }
    return true;
};

/**
 * Returns the text parameters since their formatting is mainly the same
 * @param param {string} The parameter to be used, has to be available under logObject.attributes.param
 * @param text {string} The text to be used if param is not available
 * @param logObject {Object} the entire log object returned from the api
 * @returns {*}
 */
var returnTextParams = function (param, text, logObject, view_url) {
    var source = logObject.attributes.params[param];

    if(paramIsReturned(source, logObject)){
        if($.isArray(source)){
            return m('span', [
                source.map(function(item, index, arr){
                    if(arr.length === 1 && index === 0){
                        return m('span', item);
                    }
                    if(index === arr.length-1) {
                        return m('span', ', and ' + item);
                    }
                    return m('span', item + ', ');
                })
            ]);
        }
        if (param === 'path' && source.charAt(0) === '/'){
            source = source.substr(1, source.length);
        }
        return view_url ? m('a', {href: view_url}, source) : m('span', source);
    }
    return m('span', text);
};

var LogText = {
    controller: function(logObject){
        var self = this;

        self.userInfoReturned = function(userObject){
            if (userObject){
                if (userObject.data){
                    return true;
                }
                else if (userObject.errors[0].meta){
                    return true;
                }
            }
            return false;
        };

        self.logText = function() {
            var text = logActions[logObject.attributes.action];
            if (text) {
                if (text.indexOf('${user}') !== -1) {
                    var userObject = logObject.embeds.user;
                    if (self.userInfoReturned(userObject)) {
                        return text;
                    }
                    else {
                        var newAction = logObject.attributes.action + '_no_user';
                        return logActions[newAction] ? logActions[newAction]: text;
                    }
                }
                return text;
            }
        return null;
        };
    },
    view : function(ctrl, logObject) {
        var message = '';
        var text = ctrl.logText();
        if(text){
            var list = text.split(/(\${.*?})/);
            return m('span.osf-log-item',[
                list.map(function(piece){
                    if (piece === '') {
                        return m('span');
                    }
                    var startsWith = piece.substring(0,2);
                    if(startsWith === '${'){
                        var last = piece.length-1;
                        var logComponentName = piece.substring(2,last);
                        if(LogPieces[logComponentName]){
                            return m.component(LogPieces[logComponentName], logObject);
                        } else {
                            message = 'There is no template in logTextParser.js for  ' + logComponentName + '.';
                            ravenMessage(message, logObject);
                            return m('');
                        }
                    }
                    return piece;
                })
            ]);
        } else {
            message = 'There is no text entry in dictionary for the action :' + logObject.attributes.action;
            ravenMessage(message, logObject);
            return m('');
        }
    }
};

var LogPieces = {
    // User that took the action
    user: {
        view: function (ctrl, logObject) {
            var userObject = logObject.embeds.user;
            if(paramIsReturned(userObject, logObject) && userObject.data) {
                return m('a', {href: userObject.data.links.html, onclick: function() {
                    $osf.trackClick(logObject.trackingCategory, logObject.trackingAction, 'navigate-to-user-from-logs');
                }}, userObject.data.attributes.full_name);
            }
            else if (userObject && userObject.errors[0].meta) {
                return m('span', userObject.errors[0].meta.full_name);
            }
            else {
                return m('span', 'A user');
            }
        }
    },
    // Node involved
    node: {
        view: function (ctrl, logObject) {
            var nodeObject = logObject.embeds.nodes;

            if (logObject.attributes.action === 'node_removed') {
                if (logObject.attributes.params.params_node) {
                return m('span', logObject.attributes.params.params_node.title);
            }}
            else if(paramIsReturned(nodeObject, logObject) && nodeObject.data[0]){
                if (nodeObject.data[0].links && nodeObject.data[0].attributes) {
                    return m('a', {href: nodeObject.data[0].links.html, onclick: function() {
                        $osf.trackClick(logObject.trackingCategory, logObject.trackingAction, 'navigate-to-project-from-logs');
                    }}, nodeObject.data[0].attributes.title);
                }
                else if (nodeObject.data[0].attributes) {
                    return m('span', nodeObject.data[0].attributes.title);
                }
            } else {
                return m('span', 'a project');
            }
        }
    },

    // Contributor list of added, updated etc.
    contributors: {
        view: function (ctrl, logObject) {
            var contributors = logObject.embeds.contributors;
            if(paramIsReturned(contributors, logObject)) {
                return m('span', contributors.data.map(function(item, index, arr){
                    var comma = ' ';
                    if(index !== arr.length - 1){
                        comma = ', ';
                    }
                    if(index === arr.length-2){
                        comma = ' and ';
                    }
                    if (item.attributes) {
                        if (item.attributes.active) {
                            return [ m('a', {href: item.links.html}, item.attributes.full_name), comma];
                        }
                        else {
                            return [item.attributes.full_name, comma];
                        }
                    }
                }));
            }
            return m('span', 'some users');
        }
    },
    // The tag added to item involved
    tag: {
        view: function (ctrl, logObject) {
            var tag = logObject.attributes.params.tag;
            if(paramIsReturned(tag, logObject)) {
               return m('a', {href: '/search/?q=%22' + tag + '%22'}, tag);
            }
            return m('span', 'a tag');
       }
    },
    // Node that is linked to the node involved
    pointer: {
        view: function (ctrl, logObject) {
            var linked_node = logObject.embeds.linked_node;
            if(paramIsReturned(linked_node, logObject)){
                return m('a', {href: linked_node.data.links.html}, linked_node.data.attributes.title);
            }
            return m('span','a project');
        }
    },
    // Pointer category
    category: {
      view: function (ctrl, logObject) {
            var linked_node = logObject.embeds.linked_node;
            if(paramIsReturned(linked_node, logObject)){
                return m('span', linked_node.data.attributes.category);
            }
            return m('span','');
      }
    },
    // Node that acted as template to create a new node involved
    template: {
        view: function (ctrl, logObject) {
            var template_node = logObject.embeds.template_node;
            if(paramIsReturned(template_node, logObject)){
                return m('a', {href: template_node.data.links.html}, template_node.data.attributes.title);
            }
            return m('span','a project' );
        }
    },
    // The original title of node involved
    title_original: {
        view: function (ctrl, logObject) {
            return returnTextParams('title_original', 'a title', logObject);
        }
    },
    // The new title of node involved
    title_new: {
        view: function (ctrl, logObject) {
            return returnTextParams('title_new', 'a title', logObject);
        }
    },
    // Update fields for the node
    updated_fields: {
        view: function (ctrl, logObject) {
            return returnTextParams('updated_fields', 'field(s)', logObject);
        }
    },
    // external identifiers on node
    identifiers: {
        view: function (ctrl, logObject) {
            return returnTextParams('identifiers', 'identifier(s)', logObject);
        }
    },
    // Wiki page name
    page: {
        controller: function(logObject){
            var self = this;
            self.nodeObject = logObject.embeds.nodes;
            self.action = logObject.attributes.action;
            self.page = logObject.attributes.params.page;
            self.acceptableLinkedItems = ['wiki_updated', 'wiki_renamed'];

            self.returnLinkForPath = function() {
                if (self.acceptableLinkedItems.indexOf(self.action) !== -1 && (paramIsReturned(self.nodeObject, logObject) &&
                    self.nodeObject.data[0])) {
                        var nodeUrl = self.nodeObject.data[0].links.html;
                        if (paramIsReturned(self.page, logObject)){
                            return nodeUrl + 'wiki/' + encodeURIComponent(self.page);
                        }
                }
                return null;
            };
        },
        view: function (ctrl, logObject) {
            var url = ctrl.returnLinkForPath();
            return returnTextParams('page', 'a title', logObject, url);
        }
    },
    // Old wiki title that's renamed
    old_page: {
        view: function (ctrl, logObject) {
            return returnTextParams('old_page', 'a title', logObject);
        }
    },
    // Wiki page version
    version: {
        view: function (ctrl, logObject) {
            var version = logObject.attributes.params.version;
            if(paramIsReturned(version, logObject)){
                return m('span', version);
            }
            return m('span', '#');
        }
    },
    //
    source: {
        view: function (ctrl, logObject) {
            var source = logObject.attributes.params.source;
            if(paramIsReturned(source, logObject)){
                return m('span', [source.materialized, ' in ', source.addon]);
            }
            return m('span','a name/location' );
        }
    },
    //
    destination: {
        view: function (ctrl, logObject) {
            var destination = logObject.attributes.params.destination;
            if(paramIsReturned(destination, logObject)){
                if (destination.materialized.endsWith('/')){
                    return m('span', [destination.materialized, ' in ', destination.addon]);
                }
                return m('span', [m('a', {href: destination.url}, destination.materialized), ' in ', destination.addon]);
            }
            return m('span','a new name/location' );
        }
    },
        //
    path: {
        controller: function(logObject){
            var self = this;
            self.returnLinkForPath = function() {
                if (logObject) {
                    var action = logObject.attributes.action;
                    var acceptableLinkedItems = ['osf_storage_file_added', 'osf_storage_file_updated'];
                    if (acceptableLinkedItems.indexOf(action) !== -1) {
                        return logObject.attributes.params.view_url;
                    }
                }
                return null;
            };
        },
        view: function (ctrl, logObject) {
            var url = ctrl.returnLinkForPath();
            return returnTextParams('path', 'a file', logObject, url);
        }
    },
        //
    filename: {
        view: function (ctrl, logObject) {
            return returnTextParams('filename', 'a title', logObject);
        }
    },

    folder: {
        view: function(ctrl, logObject) {
            return returnTextParams('folder', 'a folder', logObject);
        }
    },

    repo: {
        view: function(ctrl, logObject) {
            var github_user = logObject.attributes.params.github_user;
            var github_repo = logObject.attributes.params.github_repo;
            if (paramIsReturned(github_repo, logObject) && paramIsReturned(github_user, logObject)){
                return m('span', github_user + '/' + github_repo);
            }
            return m('span', '');
        }
    },

    folder_name: {
        view: function(ctrl, logObject) {
            return returnTextParams('folder_name', 'a folder', logObject);
        }
    },

    bucket: {
        view: function(ctrl, logObject) {
            return returnTextParams('bucket', 'a bucket', logObject);
        }
    },

    forward_url: {
        view: function(ctrl, logObject) {
            var url = logObject.attributes.params.forward_url;
            return returnTextParams('forward_url', 'a new URL', logObject, url);
        }
    },

    box_folder: {
        view: function(ctrl, logObject) {
            var folder = logObject.attributes.params.folder;
            if(paramIsReturned(folder, logObject)){
                return m('span', folder === 'All Files' ? '/ (Full Box)' : (folder || '').replace('All Files',''));
            }
            return m('span', '');
        }
    },

    citation: {
        view: function(ctrl, logObject) {
            return returnTextParams('citation_name', '', logObject);
        }
    },

    dataset: {
        view: function(ctrl, logObject){
            return returnTextParams('data_set', '', logObject);
        }
    },

    study: {
        view: function(ctrl, logObject){
            return returnTextParams('study', '', logObject);
        }
    },

    googledrive_path: {
        view: function(ctrl, logObject){
            var path = logObject.attributes.params.path;
            if(paramIsReturned(path, logObject)){
                return m('span', decodeURIComponent(path));
            }
            return m('span', '');
        }
    },

    googledrive_folder: {
        view: function(ctrl, logObject){
            var folder = logObject.attributes.params.folder;
            if(paramIsReturned(folder, logObject)){
                return m('span', folder === '/' ? '(Full Google Drive)' : decodeURIComponent(folder));
            }
            return m('span', '');
        }
    },

    addon: {
        view: function(ctrl, logObject){
            return returnTextParams('addon', '', logObject);
        }
    },

    previous_institution: {
        view: function(ctrl, logObject){
            var previous_institution = logObject.attributes.params.previous_institution;
            if (paramIsReturned(previous_institution, logObject)){
                return m('span', previous_institution.name);
            }
            return m('span', 'an institution');
        }
    },

    institution: {
        view: function(ctrl, logObject){
            var institution = logObject.attributes.params.institution;
            if (paramIsReturned(institution, logObject)){
                return m('span', institution.name);
            }
            return m('span', 'an institution');
        }
    },

    comment_file: {
        view: function(ctrl,logObject){
            var file = logObject.attributes.params.file;
            if (file){  // skipe paramIsReturned, as not having a file is expected at times
                return m('span', ['in ', m('a', {href: file.url}, file.name)]);
            }
            return m('span', '');
        }
    }
};

module.exports = LogText;
<|endoftext|>


<commit_before>(function(utils, undefined){

  /**
   * Setup the widget when the page loads.
   */
  $(document).ready(function(){
    try {
      setup();
    } catch(e) {
      utils.reportError(e, window.location.href);
    }
  });

  function setup() {

    /**
     * Get the name
     */
    var nameParts = utils.splitName( $('.pInfo h2').html() );

    var personData = {
      'givenName': nameParts[0],
      'familyName': nameParts[1],
      'birthDate': $('.pInfo .birth .date').text(),
      'birthPlace': $('.pInfo .birth .place .placeLink').text(),
    };

    // Parse death info if the person isn't living
    var deathDetails = $('.pInfo .death:visible');
    if( $('a.livingTag', deathDetails).length == 0 ) {
      personData['deathDate'] = $('.date', deathDetails).text();
      personData['deathPlace'] = $('.place .placeLink', deathDetails).text();
    }

    //
    // Process relationships
    //

    var parentsBlock = $('.famMem .section').eq(0);

    // Father's name
    if( $('.male.add').length == 0 ) {
      var fatherNameParts = utils.splitName( $('.male .details .name', parentsBlock).text() );
      personData['fatherGivenName'] = fatherNameParts[0];
      personData['fatherFamilyName'] = fatherNameParts[1];
    }

    // Mother's name
    if( $('.female.add').length == 0 ) {
      var motherNameParts = utils.splitName( $('.female .details .name', parentsBlock).text() );
      personData['motherGivenName'] = motherNameParts[0];
      personData['motherFamilyName'] = motherNameParts[1];
    }

    // Spouse's name
    var spouseBlock = $('.famMem .section').eq(1);
    var spouseNameParts = utils.splitName( $('.main li .details .name', spouseBlock).text() );
    personData['spouseGivenName'] = spouseNameParts[0];
    personData['spouseFamilyName'] = spouseNameParts[1];

    /**
     * Now we send the data to the background script that
     * will activate the popup icon in the address bar
     */

    chrome.extension.sendRequest({
      'type': 'person_info',
      'data': personData
    });

    /**
     * We recommend examining other widgets to get a better idea of how things work.
     */
  }

}(utils));<commit_msg>Refix ancestry changes reverted by pull request<commit_after>(function(utils, undefined){

  /**
   * Setup the widget when the page loads.
   */
  $(document).ready(function(){
    try {
      setup();
    } catch(e) {
      utils.reportError(e, window.location.href);
    }
  });

  function setup() {

    /**
     * Get the name
     */
    var nameParts = utils.splitName( $('.pInfo h1').html() );

    var personData = {
      'givenName': nameParts[0],
      'familyName': nameParts[1]
    };

    var events = {};

    // Gather events
    $('.eventList .eventDefinition').each(function(){
      var event = $(this),
          type = event.find('dt').text().trim().toLowerCase(),
          day = event.find('.eventDay').text().trim(),
          year = event.find('.eventYear').text().trim(),
          place = event.find('.eventPlace').text().trim();
      events[type] = {
        date: day + ' ' + year,
        place: place
      };
    });

    // Birth
    var birth = events.birth || events.christening || null;
    if(birth) {
      personData['birthDate'] = events.birth.date;
      personData['birthPlace'] = events.birth.place;
    }

    // Death
    var death = events.death || events.burial || null;
    if(death) {
      personData['deathDate'] = events.death.date;
      personData['deathPlace'] = events.death.place;
    }

    // TODO get the marriage info

    //
    // Process relationships
    //

    var parentsBlock = $('.famMem .section').eq(0);

    // Father's name
    if($('.iconMale.add', parentsBlock).length == 0) {
      var fatherNameParts = utils.splitName( $('.iconMale + .nameandyears a', parentsBlock).text() );
      personData['fatherGivenName'] = fatherNameParts[0];
      personData['fatherFamilyName'] = fatherNameParts[1];
    }

    // Mother's name
    if($('.iconFemale.add', parentsBlock).length == 0) {
      var motherNameParts = utils.splitName( $('.iconFemale + .nameandyears a', parentsBlock).text() );
      personData['motherGivenName'] = motherNameParts[0];
      personData['motherFamilyName'] = motherNameParts[1];
    }

    // Spouse's name
    var spouseBlock = $('.famMem .section').eq(1);
    if($('.add', spouseBlock).length == 0){
      var spouseNameParts = utils.splitName( $('.main .nameandyears a', spouseBlock).text() );
      personData['spouseGivenName'] = spouseNameParts[0];
      personData['spouseFamilyName'] = spouseNameParts[1];
    }

    /**
     * Now we send the data to the background script that
     * will activate the popup icon in the address bar
     */

    chrome.extension.sendRequest({
      'type': 'person_info',
      'data': personData
    });

    /**
     * We recommend examining other widgets to get a better idea of how things work.
     */
  }

}(utils));<|endoftext|>


<commit_before>$(function(){
	var $target = $('#drop-zone');

	function dropZone($target, onDrop){
		$target.
			bind('dragover', function(){
				$target.addClass( 'drag-over' );
				return false;
			}).
			bind("dragend", function () {
				$target.removeClass( 'drag-over' );
				return false;
			}).
			bind("mouseout", function () {
				$target.removeClass( 'drag-over' );
				return false;
			}).
			bind("drop", function(event) {
				var file = event.originalEvent.dataTransfer.files[0];

				event.stopPropagation();
				event.preventDefault();

				$target.removeClass( 'drag-over' );

				var droppedImage = new Image();
				var fileReader = new FileReader();

				fileReader.onload = function (event) {
					droppedImage.src = event.target.result;
					$target.html(droppedImage);
				};

				fileReader.readAsDataURL(file);

				onDrop(file);
			});
	}

	dropZone($target, function(file){

		resemble(file).onComplete(function(data){
			$('#image-data').show();
			$('#red').css('width',data.red+'%');
			$('#green').css('width',data.green+'%');
			$('#blue').css('width',data.blue+'%');
			$('#brightness').css('width',data.brightness+'%');
		});

	});

	function onComplete(data){
		var time = Date.now();
		var diffImage = new Image();
		diffImage.src = data.getImageDataUrl();

		$('#image-diff').html(diffImage);

		$(diffImage).click(function(){
			window.open(diffImage.src, '_blank');
		});

		$('#buttons').show();

		if(data.misMatchPercentage == 0){
			$('#thesame').show();
			$('#diff-results').hide();
		} else {
			$('#mismatch').text(data.misMatchPercentage);
			if(!data.isSameDimensions){
				$('#differentdimensions').show();
			} else {
				$('#differentdimensions').hide();
			}
			$('#diff-results').show();
			$('#thesame').hide();
		}
	}

	var file1;
	var file2;
	var resembleControl;
	dropZone($('#dropzone1'), function(file){
		console.log(file);
		file1 = file;
		if(file2){
			resembleControl = resemble(file).compareTo(file2).onComplete(onComplete);
		}
	});
	dropZone($('#dropzone2'), function(file){
		file2 = file;
		if(file1){
			resembleControl = resemble(file).compareTo(file1).onComplete(onComplete);
		}
	});

	var buttons = $('#raw, #colors, #antialising');

	buttons.click(function(){
		var $this = $(this);

		buttons.removeClass('active');
		$this.addClass('active');

		if($this.is('#raw')){
			resembleControl.ignoreNothing();
		}
		else
		if($this.is('#colors')){
			resembleControl.ignoreColors();
		}
		else
		if($this.is('#antialising')){
			resembleControl.ignoreAntialiasing();
		}
	});

	(function(){
		var xhr = new XMLHttpRequest();
		var xhr2 = new XMLHttpRequest();
		var done = $.Deferred();
		var dtwo = $.Deferred();

		xhr.open('GET', 'demoassets/People.jpg', true);
		xhr.responseType = 'blob';
		xhr.onload = function(e) {
			done.resolve(this.response);
		};
		xhr.send();

		xhr2.open('GET', 'demoassets/People2.jpg', true);
		xhr2.responseType = 'blob';
		xhr2.onload = function(e) {
			dtwo.resolve(this.response);
		};
		xhr2.send();

		$('#example-images').click(function(){

			$('#dropzone1').html('');
			$('#dropzone2').html('');

			$.when(done, dtwo).done(function(file, file1){
				if (typeof FileReader === 'undefined') {
					resembleControl = resemble('demoassets/People.jpg').compareTo('demoassets/People2.jpg').onComplete(onComplete);
				} else {
					resembleControl = resemble(file).compareTo(file1).onComplete(onComplete);
				}
			});

			return false;
		});

	}());

});<commit_msg>Update main.js<commit_after>$(function(){
	var $target = $('#drop-zone');

	function dropZone($target, onDrop){
		$target.
			bind('dragover', function(){
				$target.addClass( 'drag-over' );
				return false;
			}).
			bind("dragend", function () {
				$target.removeClass( 'drag-over' );
				return false;
			}).
			bind("dragleave", function () {
				$target.removeClass( 'drag-over' );
				return false;
			}).
			bind("drop", function(event) {
				var file = event.originalEvent.dataTransfer.files[0];

				event.stopPropagation();
				event.preventDefault();

				$target.removeClass( 'drag-over' );

				var droppedImage = new Image();
				var fileReader = new FileReader();

				fileReader.onload = function (event) {
					droppedImage.src = event.target.result;
					$target.html(droppedImage);
				};

				fileReader.readAsDataURL(file);

				onDrop(file);
			});
	}

	dropZone($target, function(file){

		resemble(file).onComplete(function(data){
			$('#image-data').show();
			$('#red').css('width',data.red+'%');
			$('#green').css('width',data.green+'%');
			$('#blue').css('width',data.blue+'%');
			$('#brightness').css('width',data.brightness+'%');
		});

	});

	function onComplete(data){
		var time = Date.now();
		var diffImage = new Image();
		diffImage.src = data.getImageDataUrl();

		$('#image-diff').html(diffImage);

		$(diffImage).click(function(){
			window.open(diffImage.src, '_blank');
		});

		$('#buttons').show();

		if(data.misMatchPercentage == 0){
			$('#thesame').show();
			$('#diff-results').hide();
		} else {
			$('#mismatch').text(data.misMatchPercentage);
			if(!data.isSameDimensions){
				$('#differentdimensions').show();
			} else {
				$('#differentdimensions').hide();
			}
			$('#diff-results').show();
			$('#thesame').hide();
		}
	}

	var file1;
	var file2;
	var resembleControl;
	dropZone($('#dropzone1'), function(file){
		console.log(file);
		file1 = file;
		if(file2){
			resembleControl = resemble(file).compareTo(file2).onComplete(onComplete);
		}
	});
	dropZone($('#dropzone2'), function(file){
		file2 = file;
		if(file1){
			resembleControl = resemble(file).compareTo(file1).onComplete(onComplete);
		}
	});

	var buttons = $('#raw, #colors, #antialising');

	buttons.click(function(){
		var $this = $(this);

		buttons.removeClass('active');
		$this.addClass('active');

		if($this.is('#raw')){
			resembleControl.ignoreNothing();
		}
		else
		if($this.is('#colors')){
			resembleControl.ignoreColors();
		}
		else
		if($this.is('#antialising')){
			resembleControl.ignoreAntialiasing();
		}
	});

	(function(){
		var xhr = new XMLHttpRequest();
		var xhr2 = new XMLHttpRequest();
		var done = $.Deferred();
		var dtwo = $.Deferred();

		xhr.open('GET', 'demoassets/People.jpg', true);
		xhr.responseType = 'blob';
		xhr.onload = function(e) {
			done.resolve(this.response);
		};
		xhr.send();

		xhr2.open('GET', 'demoassets/People2.jpg', true);
		xhr2.responseType = 'blob';
		xhr2.onload = function(e) {
			dtwo.resolve(this.response);
		};
		xhr2.send();

		$('#example-images').click(function(){

			$('#dropzone1').html('');
			$('#dropzone2').html('');

			$.when(done, dtwo).done(function(file, file1){
				if (typeof FileReader === 'undefined') {
					resembleControl = resemble('demoassets/People.jpg').compareTo('demoassets/People2.jpg').onComplete(onComplete);
				} else {
					resembleControl = resemble(file).compareTo(file1).onComplete(onComplete);
				}
			});

			return false;
		});

	}());

});
<|endoftext|>


<commit_before>/**
 * An API returned game mode stats
 * @class GameModeStats
 */
class GameModeStats {
    constructor(content) {
        /**
         * Assists
         * @type {Number}
         */
        this.assists = content.assists;

        /**
         * Boosts
         * @type {Number}
         */
        this.boosts = content.boosts;

        /**
         * Downs but not out
         * @type {Number}
         */
        this.dBNOs = content.dBNOs;

        /**
         * Daily kills
         * @type {Number}
         */
        this.dailyKills = content.dailyKills;

        /**
         * Damage dealt
         * @type {Number}
         */
        this.damageDealt = content.damageDealt;

        /**
         * Days
         * @type {string}
         */
        this.days = content.days;

        /**
         * Headshot kills
         * @type {string}
         */
        this.headshotKills = content.headshotKills;

        /**
         * Total HP healed
         * @type {string}
         */
        this.heals = content.heals;

        /**
         * Kills
         * @type {string}
         */
        this.kills = content.kills;

        /**
         * Longest kill
         * @type {string}
         */
        this.longestKill = content.longestKill;

        /**
         * Longest time survived
         * @type {string}
         */
        this.longestTimeSurvived = content.longestTimeSurvived;

        /**
         * Losses
         * @type {string}
         */
        this.losses = content.losses;

        /**
         * Maximum kill streaks
         * @type {string}
         */
        this.maxKillStreaks = content.maxKillStreaks;

        /**
         * Most survival time
         * @type {string}
         */
        this.mostSurvivalTime = content.mostSurvivalTime;

        /**
         * Revives
         * @type {string}
         */
        this.revives = content.revives;

        /**
         * Ride distance
         * @type {string}
         */
        this.rideDistance = content.rideDistance;

        /**
         * Road kills
         * @type {string}
         */
        this.roadKills = content.roadKills;

        /**
         * Round most kills
         * @type {string}
         */
        this.roundMostKills = content.roundMostKills;

        /**
         * Rounds played
         * @type {string}
         */
        this.roundsPlayed = content.roundsPlayed;

        /**
         * Suicides
         * @type {string}
         */
        this.suicides = content.suicides;

        /**
         * Team kills
         * @type {string}
         */
        this.teamKills = content.teamKills;

        /**
         * Time survived
         * @type {string}
         */
        this.timeSurvived = content.timeSurvived;

        /**
         * Top 10s
         * @type {string}
         */
        this.top10s = content.top10s;

        /**
         * Vehicles Destroyed
         * @type {string}
         */
        this.vehicleDestroys = content.vehicleDestroys;

        /**
         * Walk distance
         * @type {string}
         */
        this.walkDistance = content.walkDistance;

        /**
         * Weapon Acquired
         * @type {string}
         */
        this.weaponsAcquired = content.weaponsAcquired;

        /**
         * Weekly kills
         * @type {string}
         */
        this.weeklyKills = content.weeklyKills;

        /**
         * Win ratio
         * @type {string}
         */
        this.winRatio = content.winRatio;

        /**
         * Wins
         * @type {string}
         */
        this.wins = content.wins;

        /**
         * Unknown
         * @type {string}
         */
        this.bestRankPoint = content.bestRankPoint;

        /**
         * Wins gained within the last 24 hours
         * @type {string}
         */
        this.dailyWins = content.dailyWins;

        /**
         * Unknown
         * @type {string}
         */
        this.rankPoints = content.rankPoints;

        /**
         * Distance player has swam
         * @type {string}
         */
        this.swimDistance = content.swimDistance;

        /**
         * Wins gained within the last 7 days
         * @type {string}
         */
        this.weeklyWins = content.weeklyWins;

        // DEPRECATED

        /**
         * Wins gained within the last 7 days
         * @type {string}
         */
        this.weeklyWins = content.weeklyWins;

        /**
         * Points from winning
         * @type {string}
         */
        this.winPoints = content.winPoints;

        /**
         * Points from kills
         * @type {string}
         */
        this.killPoints = content.killPoints;
    }
}

module.exports = GameModeStats;
<commit_msg>Add rankPointsTitle<commit_after>/**
 * An API returned game mode stats
 * @class GameModeStats
 */
class GameModeStats {
    constructor(content) {
        /**
         * Assists
         * @type {Number}
         */
        this.assists = content.assists;

        /**
         * Boosts
         * @type {Number}
         */
        this.boosts = content.boosts;

        /**
         * Downs but not out
         * @type {Number}
         */
        this.dBNOs = content.dBNOs;

        /**
         * Daily kills
         * @type {Number}
         */
        this.dailyKills = content.dailyKills;

        /**
         * Damage dealt
         * @type {Number}
         */
        this.damageDealt = content.damageDealt;

        /**
         * Days
         * @type {string}
         */
        this.days = content.days;

        /**
         * Headshot kills
         * @type {string}
         */
        this.headshotKills = content.headshotKills;

        /**
         * Total HP healed
         * @type {string}
         */
        this.heals = content.heals;

        /**
         * Kills
         * @type {string}
         */
        this.kills = content.kills;

        /**
         * Longest kill
         * @type {string}
         */
        this.longestKill = content.longestKill;

        /**
         * Longest time survived
         * @type {string}
         */
        this.longestTimeSurvived = content.longestTimeSurvived;

        /**
         * Losses
         * @type {string}
         */
        this.losses = content.losses;

        /**
         * Maximum kill streaks
         * @type {string}
         */
        this.maxKillStreaks = content.maxKillStreaks;

        /**
         * Most survival time
         * @type {string}
         */
        this.mostSurvivalTime = content.mostSurvivalTime;

        /**
         * Revives
         * @type {string}
         */
        this.revives = content.revives;

        /**
         * Ride distance
         * @type {string}
         */
        this.rideDistance = content.rideDistance;

        /**
         * Road kills
         * @type {string}
         */
        this.roadKills = content.roadKills;

        /**
         * Round most kills
         * @type {string}
         */
        this.roundMostKills = content.roundMostKills;

        /**
         * Rounds played
         * @type {string}
         */
        this.roundsPlayed = content.roundsPlayed;

        /**
         * Suicides
         * @type {string}
         */
        this.suicides = content.suicides;

        /**
         * Team kills
         * @type {string}
         */
        this.teamKills = content.teamKills;

        /**
         * Time survived
         * @type {string}
         */
        this.timeSurvived = content.timeSurvived;

        /**
         * Top 10s
         * @type {string}
         */
        this.top10s = content.top10s;

        /**
         * Vehicles Destroyed
         * @type {string}
         */
        this.vehicleDestroys = content.vehicleDestroys;

        /**
         * Walk distance
         * @type {string}
         */
        this.walkDistance = content.walkDistance;

        /**
         * Weapon Acquired
         * @type {string}
         */
        this.weaponsAcquired = content.weaponsAcquired;

        /**
         * Weekly kills
         * @type {string}
         */
        this.weeklyKills = content.weeklyKills;

        /**
         * Win ratio
         * @type {string}
         */
        this.winRatio = content.winRatio;

        /**
         * Wins
         * @type {string}
         */
        this.wins = content.wins;

        /**
         * Unknown
         * @type {string}
         */
        this.bestRankPoint = content.bestRankPoint;

        /**
         * Wins gained within the last 24 hours
         * @type {string}
         */
        this.dailyWins = content.dailyWins;

        /**
         * Unknown
         * @type {string}
         */
        this.rankPoints = content.rankPoints;

        /**
         * Rank title in the form title-level
         * @type {string}
         */
        this.rankPointsTitle = content.rankPointsTitle;

        /**
         * Distance player has swam
         * @type {string}
         */
        this.swimDistance = content.swimDistance;

        /**
         * Wins gained within the last 7 days
         * @type {string}
         */
        this.weeklyWins = content.weeklyWins;

        // DEPRECATED

        /**
         * Wins gained within the last 7 days
         * @type {string}
         */
        this.weeklyWins = content.weeklyWins;

        /**
         * Points from winning
         * @type {string}
         */
        this.winPoints = content.winPoints;

        /**
         * Points from kills
         * @type {string}
         */
        this.killPoints = content.killPoints;
    }
}

module.exports = GameModeStats;
<|endoftext|>


<commit_before>// @flow
const log = require('../../../util/logger').default;
const avro = require('avsc');

/*
 * There are currently 2 distinct analytics logging methods
 * 1. `stdout`, which will be consumed automatically from apps running in
 *    Google Container Engine
 * 2. Google Pub/Sub, which is used from inside Google App Engine
 *
 * The Pub/Sub setup _only_ works when the environment is configured to
 * automatically authorize Pub/Sub messages, so we use an `isGAE` env variable
 * to determine whether the app is running in GAE.
 *
 * @see https://meetup.atlassian.net/wiki/display/MUP/Analytics+Logging#AnalyticsLogging-Theinputmechanism
 */
const getPlatformAnalyticsLog = (
	isGAE: ?string = process.env.GAE_INSTANCE
): (string => void) => {
	if (isGAE) {
		const pubsub = require('@google-cloud/pubsub')();
		const analyticsLog = pubsub.topic('analytics-log-json');
		return (serializedRecord: string) => {
			analyticsLog.publish(serializedRecord).then(
				(messageIds, apiResponse) => {
					if (messageIds) {
						log.info({ messageIds }, 'GAE PubSub');
					}
				},
				err => log.error(err)
			);
		};
	}
	return (serializedRecord: string) => {
		process.stdout.write(`analytics=${serializedRecord}\n`);
	};
};

const analyticsLog = getPlatformAnalyticsLog();
const debugLog = deserializedRecord =>
	console.log(JSON.stringify(deserializedRecord));

// currently the schema is manually copied from
// https://github.dev.meetup.com/meetup/meetup/blob/master/modules/base/src/main/versioned_avro/Click_v2.avsc
const click = {
	namespace: 'com.meetup.base.avro',
	type: 'record',
	name: 'Click',
	doc: 'v2',
	fields: [
		{ name: 'timestamp', type: 'string' },
		{ name: 'requestId', type: 'string' },
		{ name: 'memberId', type: 'int' },
		{ name: 'lineage', type: 'string' },
		{ name: 'linkText', type: 'string' },
		{ name: 'coordX', type: 'int' },
		{ name: 'coordY', type: 'int' },
		{ name: 'tag', type: 'string', default: '' },
	],
};

// currently the schema is manually copied from
// https://github.com/meetup/meetup/blob/master/modules/base/src/main/versioned_avro/Activity_v6.avsc
const activity = {
	namespace: 'com.meetup.base.avro',
	type: 'record',
	name: 'Activity',
	doc: 'v6',
	fields: [
		{ name: 'requestId', type: 'string' },
		{ name: 'timestamp', type: 'string' },
		{ name: 'url', type: 'string' },
		{ name: 'aggregratedUrl', type: 'string', default: '' }, // it's misspelled in the original spec
		{ name: 'ip', type: 'string', default: '' },
		{ name: 'agent', type: 'string', default: '' },
		{ name: 'memberId', type: 'int' },
		{ name: 'trackId', type: 'string' },
		{ name: 'mobileWeb', type: 'boolean' },
		{ name: 'platform', type: 'string' },
		{ name: 'referer', type: 'string' }, // it's misspelled in the original spec
		{ name: 'trax', type: { type: 'map', values: 'string' } },
		{
			name: 'platformAgent',
			type: {
				type: 'enum',
				name: 'PlatformAgent',
				symbols: [
					'WEB',
					'MUP_WEB',
					'PRO_WEB',
					'NATIVE',
					'NATIVE_APP_WEB_VIEW',
					'THIRD_PARTY_UNKNOWN',
					'UNKNOWN',
				],
			},
			default: 'UNKNOWN',
		},
		{ name: 'isUserActivity', type: 'boolean', default: true },
		{ name: 'browserId', type: 'string', default: '' },
	],
};

type Serializer = Object => string;
type Deserializer = string => Object;

const avroSerializer: Object => Serializer = schema => data => {
	const record = avro.parse(schema).toBuffer(data);
	// data.timestamp _must_ be ISOString if it exists
	const timestamp = data.timestamp || new Date().toISOString();
	const analytics = {
		record: record.toString('base64'),
		schema: `gs://meetup-logs/avro_schemas/${schema.name}_${schema.doc}.avsc`,
		date: timestamp.substr(0, 10), // YYYY-MM-DD
	};
	return JSON.stringify(analytics);
};

const avroDeserializer: Object => Deserializer = schema => serialized => {
	const { record } = JSON.parse(serialized);
	const avroBuffer = new Buffer(record, 'base64');
	return avro.parse(schema).fromBuffer(avroBuffer);
};

const logger = (serializer: Serializer, deserializer: Deserializer) => (
	record: Object
) => {
	const serializedRecord = serializer(record);
	const deserializedRecord = deserializer(serializedRecord);
	analyticsLog(serializedRecord);
	debugLog(deserializedRecord);
};

const schemas = {
	activity,
	click,
};
const serializers = {
	avro: avroSerializer,
	activity: avroSerializer(schemas.activity),
	click: avroSerializer(schemas.click),
};
const deserializers = {
	avro: avroDeserializer,
	activity: avroDeserializer(schemas.activity),
	click: avroDeserializer(schemas.click),
};
const loggers = {
	activity: logger(serializers.activity, deserializers.activity),
	click: logger(serializers.click, deserializers.click),
};

module.exports = {
	avroSerializer,
	getPlatformAnalyticsLog,
	schemas,
	serializers,
	deserializers,
	loggers,
};
<commit_msg>specify pubsub project<commit_after>// @flow
const log = require('../../../util/logger').default;
const avro = require('avsc');

/*
 * There are currently 2 distinct analytics logging methods
 * 1. `stdout`, which will be consumed automatically from apps running in
 *    Google Container Engine
 * 2. Google Pub/Sub, which is used from inside Google App Engine
 *
 * The Pub/Sub setup _only_ works when the environment is configured to
 * automatically authorize Pub/Sub messages, so we use an `isGAE` env variable
 * to determine whether the app is running in GAE.
 *
 * @see https://meetup.atlassian.net/wiki/display/MUP/Analytics+Logging#AnalyticsLogging-Theinputmechanism
 */
const getPlatformAnalyticsLog = (
	isGAE: ?string = process.env.GAE_INSTANCE
): (string => void) => {
	if (isGAE) {
		const pubsub = require('@google-cloud/pubsub')({
			projectId: 'meetup-prod',
		});
		const analyticsLog = pubsub.topic('analytics-log-json');
		return (serializedRecord: string) => {
			analyticsLog.publish(serializedRecord).then(
				(messageIds, apiResponse) => {
					if (messageIds) {
						log.info({ messageIds }, 'GAE PubSub');
					}
				},
				err => log.error(err)
			);
		};
	}
	return (serializedRecord: string) => {
		process.stdout.write(`analytics=${serializedRecord}\n`);
	};
};

const analyticsLog = getPlatformAnalyticsLog();
const debugLog = deserializedRecord =>
	console.log(JSON.stringify(deserializedRecord));

// currently the schema is manually copied from
// https://github.dev.meetup.com/meetup/meetup/blob/master/modules/base/src/main/versioned_avro/Click_v2.avsc
const click = {
	namespace: 'com.meetup.base.avro',
	type: 'record',
	name: 'Click',
	doc: 'v2',
	fields: [
		{ name: 'timestamp', type: 'string' },
		{ name: 'requestId', type: 'string' },
		{ name: 'memberId', type: 'int' },
		{ name: 'lineage', type: 'string' },
		{ name: 'linkText', type: 'string' },
		{ name: 'coordX', type: 'int' },
		{ name: 'coordY', type: 'int' },
		{ name: 'tag', type: 'string', default: '' },
	],
};

// currently the schema is manually copied from
// https://github.com/meetup/meetup/blob/master/modules/base/src/main/versioned_avro/Activity_v6.avsc
const activity = {
	namespace: 'com.meetup.base.avro',
	type: 'record',
	name: 'Activity',
	doc: 'v6',
	fields: [
		{ name: 'requestId', type: 'string' },
		{ name: 'timestamp', type: 'string' },
		{ name: 'url', type: 'string' },
		{ name: 'aggregratedUrl', type: 'string', default: '' }, // it's misspelled in the original spec
		{ name: 'ip', type: 'string', default: '' },
		{ name: 'agent', type: 'string', default: '' },
		{ name: 'memberId', type: 'int' },
		{ name: 'trackId', type: 'string' },
		{ name: 'mobileWeb', type: 'boolean' },
		{ name: 'platform', type: 'string' },
		{ name: 'referer', type: 'string' }, // it's misspelled in the original spec
		{ name: 'trax', type: { type: 'map', values: 'string' } },
		{
			name: 'platformAgent',
			type: {
				type: 'enum',
				name: 'PlatformAgent',
				symbols: [
					'WEB',
					'MUP_WEB',
					'PRO_WEB',
					'NATIVE',
					'NATIVE_APP_WEB_VIEW',
					'THIRD_PARTY_UNKNOWN',
					'UNKNOWN',
				],
			},
			default: 'UNKNOWN',
		},
		{ name: 'isUserActivity', type: 'boolean', default: true },
		{ name: 'browserId', type: 'string', default: '' },
	],
};

type Serializer = Object => string;
type Deserializer = string => Object;

const avroSerializer: Object => Serializer = schema => data => {
	const record = avro.parse(schema).toBuffer(data);
	// data.timestamp _must_ be ISOString if it exists
	const timestamp = data.timestamp || new Date().toISOString();
	const analytics = {
		record: record.toString('base64'),
		schema: `gs://meetup-logs/avro_schemas/${schema.name}_${schema.doc}.avsc`,
		date: timestamp.substr(0, 10), // YYYY-MM-DD
	};
	return JSON.stringify(analytics);
};

const avroDeserializer: Object => Deserializer = schema => serialized => {
	const { record } = JSON.parse(serialized);
	const avroBuffer = new Buffer(record, 'base64');
	return avro.parse(schema).fromBuffer(avroBuffer);
};

const logger = (serializer: Serializer, deserializer: Deserializer) => (
	record: Object
) => {
	const serializedRecord = serializer(record);
	const deserializedRecord = deserializer(serializedRecord);
	analyticsLog(serializedRecord);
	debugLog(deserializedRecord);
};

const schemas = {
	activity,
	click,
};
const serializers = {
	avro: avroSerializer,
	activity: avroSerializer(schemas.activity),
	click: avroSerializer(schemas.click),
};
const deserializers = {
	avro: avroDeserializer,
	activity: avroDeserializer(schemas.activity),
	click: avroDeserializer(schemas.click),
};
const loggers = {
	activity: logger(serializers.activity, deserializers.activity),
	click: logger(serializers.click, deserializers.click),
};

module.exports = {
	avroSerializer,
	getPlatformAnalyticsLog,
	schemas,
	serializers,
	deserializers,
	loggers,
};
<|endoftext|>


<commit_before>
if(typeof define !== 'function') {
  var define = require('amdefine')(module);
}

define(function(require) {
  var View = inServer ? require('../../lib/View') : require('View')
    , ConditionView = require('./ConditionView')
    , InputView = require('./InputView')
    , ElseView = require('./ElseView')
    , template = inServer ? content_appTmpls : require('contentTmpls')
    , _ = require('underscore');

  return View.extend({

    /**
     * Initializer
     *
     * @return {void}
     * @api public
     */

    initialize : function(model) {
      this.model = model;
      this._conditionViews = [];
      this._inputViews = [];
      if(inClient) {
        this._bindMethods();
      }
    },

    /**
     * Bind model
     *
     * @return {void}
     * @api public
     */

    bindModel : function() {
      this._bindConditionsAddition();
      this._bindConditionRemoval();
      this._bindInputAddition();
      this._bindElseAddition();
    },

    /**
     * Bind else addition
     *
     * @return {void}
     * @api private
     */

    _bindElseAddition : function() {
      var _this = this;
      this.model.on('change:else', function(_else) {
        if(_this.model.get('else')) {
          _this._elseView = new ElseView(_else.get('else'));
          _this.$('[data-row="1"]').after(_this._elseView.render());
          _this._elseView.setElement('[data-row="2"]');
          _this._elseView.bindDOM();
        }
      });
    },

    /**
     * Bind condition addition
     *
     * @return {void}
     * @api private
     */

    _bindConditionsAddition : function() {
      var _this = this;
      this.model.on('add:conditions', function(condition) {
        var view = new ConditionView(condition)
          , insertingRow = condition.get('row')
          , $condition;

        // We need a defer block because firstOperand and lastOperans relation
        // are not set yet.
        _.defer(function() {
          // If we insert a condition by choosing `and` or `or` in the `then` dropdown
          // We need to increase the row property on other conditions
          // and inputs. Otherwise some DOM bindings/event handling
          // will be wrong. We check that this is condition insertion from the `then`
          // dropdown by checking if inserting row is equals the `else` row minus 2
          if(_this.model.get('else') && insertingRow !== _this.model.get('else').get('row')) {
            _this.model.get('conditions').forEach(function(_condition) {
              var currentRow = _condition.get('row');
              if(currentRow >= insertingRow && condition.cid !== _condition.cid) {
                _condition.set('row', currentRow + 1);
              }
            });
            _this.model.get('inputs').forEach(function(input) {
              var currentRow = input.get('row');
              if(currentRow >= insertingRow) {
                input.set('row', currentRow + 1);
              }
            });
            var _else = _this.model.get('else');
            _else.set('row', _else.get('row') + 1);
            $condition = $(view.render());
          }
          else {
            if(insertingRow === 0) {
              _this.model.get('inputs').forEach(function(input) {
                var currentRow = input.get('row');
                if(currentRow >= insertingRow) {
                  input.set('row', currentRow + 1);
                }
              });
            }
            $condition = $(view.render()).addClass('invisible');
          }

          // bind conditions' DOM
          if(insertingRow > 0) {
            this.$('[data-row="' + (insertingRow - 1) + '"]').after($condition);
          }
          else {
            this.$('[data-row="1"]').before($condition);
          }

          var conditionSelector = '.condition[data-row="' + insertingRow + '"]';
          view.setElement(conditionSelector);
          view.bindDOM();
          view.firstOperandView.setElement(conditionSelector + ' .condition-first-operand');
          view.firstOperandView.bindDOM();
          view.lastOperandView.setElement(conditionSelector + ' .condition-last-operand');
          view.lastOperandView.bindDOM();

          // We must insert the view in the right index in `_conditionViews`
          var appended = false;
          _this._conditionViews.some(function(conditionView, index) {
            if(conditionView.model.get('row') === insertingRow - 1) {
              _this._conditionViews.splice(index + 1, 0, view);
              appended = true;
              return true;
            }
            return false;
          });
          if(!appended) {
            _this._conditionViews.push(view);
          }
        });
      });
    },

    /**
     * Bind condition removal
     *
     * @return {void}
     * @api private
     */

    _bindConditionRemoval : function() {
      var _this = this;
      this.model.on('remove:conditions', function(condition) {
        var row = condition.get('row');
        if(row === 0) {
          _this._removeLastCondition(condition);
          return;
        }
        _this.model.get('conditions').some(function(_condition) {
          if(_condition.get('row') === row - 1) {
            _this._removeSingleCondition(condition);
            return true;
          }
          return false;
        });
        _this.model.get('inputs').some(function(input) {
          if(input.get('row') === row - 1) {
            // _this._removeConditionAndInputs(condition);
          }
          return false;
        });
      });
    },

    /**
     * Remove condition only. This method only removes the `condition` from
     * the DOM by calling `Backbone.View.prototype.remove`.
     *
     * @return {void}
     * @api private
     */

    _removeSingleCondition : function(condition) {
      var _this = this, removalIndex, removalRow;
      this._conditionViews.forEach(function(conditionView, index) {
        if(conditionView.model === condition) {
          conditionView.remove();
          // We remove the view pointer from TranslatioView`
          removalRow = conditionView.model.get('row');
          removalIndex = index;
        }

        // We must decrease the row index on all subsequent conditions
        if(removalIndex && index > removalIndex) {
          conditionView.model.set('row', conditionView.model.get('row') - 1);
        }
      });

      if(removalRow) {
        this._inputViews.forEach(function(inputView) {
          var row = inputView.model.get('row');
          if(row > removalRow) {
            inputView.model.set('row', row - 1);
          }
        });

        if(this._elseView) {
          this._elseView.model.set('row', this._elseView.model.get('row') - 1);
        }

        _this._conditionViews.splice(removalIndex, 1);
      }
    },

    /**
     * Remove condition only. This method only removes the `condition` from
     * the DOM by calling `Backbone.View.prototype.remove`.
     *
     * @return {void}
     * @api private
     */

    _removeLastCondition : function() {
      this._conditionViews[0].remove();
      this.model.get('else').destroy();
      this._elseView.remove();
      this._inputViews[0].model.destroy();
      this._inputViews[0].remove();
      this._inputViews.splice(0, 1);
      this._inputViews[0].model.set('row', 0);
    },

    /**
     * Bind condition removal
     *
     * @return {void}
     * @api private
     */

    _bindInputAddition : function() {
      var _this = this;
      this.model.on('add:inputs', function(input) {
        var row = input.get('row')
          , view = new InputView(input);

        _this.$('[data-row="' + (row - 1) + '"]').after(view.render());
        view.setElement('[data-row="' + row + '"]');
        view.bindDOM();

        _this._inputViews.push(view);
      });
    },

    /**
     * Bind condition removal
     *
     * @return {void}
     * @api private
     */

    _bindInputRemoval : function() {
      var _this = this;
      this.model.on('remove:inputs', function(input) {

      });
    },

    /**
     * Bind view
     *
     * @return {void}
     * @api private
     */

    bindDOM : function() {
      if(!has.touch) {
        this._addMouseInteractions();
      }
      // We loop through each relation view and try to bind
      // them with our object
      this._conditionViews.forEach(function(conditionView) {
        var conditionSelector = '.condition[data-row="' + conditionView.model.get('row') + '"]';
        conditionView.setElement(conditionSelector);
        conditionView.bindDOM();
        conditionView.firstOperandView.setElement(conditionSelector + ' .condition-first-operand');
        conditionView.firstOperandView.bindDOM();
        conditionView.lastOperandView.setElement(conditionSelector + ' .condition-last-operand');
        conditionView.lastOperandView.bindDOM();
      });
      this._inputViews.forEach(function(inputView) {
        var conditionSelector = '.input[data-row="' + inputView.model.get('row') + '"]';
        inputView.setElement(conditionSelector);
        inputView.bindDOM();
      });
      if(typeof this._elseView !== 'undefined') {
        this._elseView.setElement('.condition-else');
        this._elseView.bindModel();
      }

      this._setElements();

      this.boundDOM = true;
    },

    /**
     * Bind methods
     *
     * @return {void}
     * @api private
     */

    _bindMethods : function() {
      _.bindAll(
        this,
        '_addCondition',
        '_save'
      );
    },

    /**
     * Add desktop listeners
     *
     * @return {void}
     * @api private
     */

    _addMouseInteractions : function() {
      this.$('[disabled]').removeAttr('disabled');
      this.$el.on('click', '.add-condition', this._addCondition);
      this.$el.on('click', '.save', this._save);
    },

    /**
     * Set elements
     *
     * @return {void}
     * @api private
     */

    _setElements : function() {
      this.$region = $('[data-region=translation]');
    },

    /**
     * Add condition
     *
     * @delegate
     */

    _addCondition : function(event) {
      var _this = this;

      event.preventDefault();

      var _else = this.model.get('else'), row = 0, elseRow, inputRow;

      if(_else) {
        row = _else.get('row');
      }

      // We increase the row by two. So it becomes behind the input,
      // which has a row that is one bigger than inserting row.
      elseRow = row + 2;
      inputRow = elseRow + 1;

      var $inputs =
      $('[data-row="' + row + '"]')
        .add('[data-row="' + (row + 1) + '"]')
        .add('[data-row="' + (row + 2) + '"]')
        .addClass('invisible');

      var data = {
        statement : row === 0 ? 'if' : 'else if',
        firstOperand : 'value1',
        operator : '==',
        lastOperand : 'value2',
        vars : this.model.get('vars'),
        operators : cf.OPERATORS,
        additionalCompairOperators : cf.ADDITIONAL_COMPAIR_OPERATORS,
        row : row,
        translation : this.model
      };

      new this.model.Condition(data);

      if(_else) {
        _.defer(function() {
          _else.set('row', elseRow);
          new _this.model.Input({ value : '', row : inputRow, translation : _this.model});
        });
      }
      else {
        _.defer(function() {
          new _this.model.Else({ row : 2, parent : _this.model });
          var input = new _this.model.Input({ value : '', row : 3, translation : _this.model});
        });
      }

      _.defer(function() {
        $('[data-row="' + row + '"]')
          .add('[data-row="' + (row + 1) + '"]')
          .add('[data-row="' + (row + 2) + '"]')
          .removeClass('invisible');
      });
    },

    /**
     * Save
     *
     * @delegate
     */

    _save : function(event) {
      event.preventDefault();
      this.model.save();
    },

    /**
     * Render view
     *
     * @return {void}
     * @api public
     */

    render : function() {
      var _this = this
        , html = '', values = []
        , json = this.model.toJSON()
        , conditions = this.model.get('conditions')
        , inputs = this.model.get('inputs');

      // We loop through each relation object to get the HTML. We use `row`
      // to determine the order of the HTML
      conditions.forEach(function(condition) {
        var view = new ConditionView(condition);
        _this._conditionViews.push(view);
        values[condition.get('row')] = view.render();
      });
      inputs.forEach(function(input, index) {
        var view = new InputView(input);
        _this._inputViews.push(view);
        values[input.get('row')] = view.render();
      });

      // `else` might be null if there is no conditions on translations
      var _else = this.model.get('else');
      if(_else) {
        this._elseView = new ElseView(_else);
        values[_else.get('row')] = this._elseView.render();
      }

      json.values = values.join('');

      html += template['Translation'](json);

      return html;
    },

    /**
     * Remove
     *
     * @delegate
     */

    remove : function() {
      View.prototype.remove.call(this);
      this.$region[0].classList.add('hidden');
    }
  });
});
<commit_msg>Fixed bug related to elseView not being bound to model<commit_after>
if(typeof define !== 'function') {
  var define = require('amdefine')(module);
}

define(function(require) {
  var View = inServer ? require('../../lib/View') : require('View')
    , ConditionView = require('./ConditionView')
    , InputView = require('./InputView')
    , ElseView = require('./ElseView')
    , template = inServer ? content_appTmpls : require('contentTmpls')
    , _ = require('underscore');

  return View.extend({

    /**
     * Initializer
     *
     * @return {void}
     * @api public
     */

    initialize : function(model) {
      this.model = model;
      this._conditionViews = [];
      this._inputViews = [];
      if(inClient) {
        this._bindMethods();
      }
    },

    /**
     * Bind model
     *
     * @return {void}
     * @api public
     */

    bindModel : function() {
      this._bindConditionsAddition();
      this._bindConditionRemoval();
      this._bindInputAddition();
      this._bindElseAddition();
    },

    /**
     * Bind else addition
     *
     * @return {void}
     * @api private
     */

    _bindElseAddition : function() {
      var _this = this;
      this.model.on('change:else', function(_else) {
        if(_this.model.get('else')) {
          _this._elseView = new ElseView(_else.get('else'));
          _this.$('[data-row="1"]').after(_this._elseView.render());
          _this._elseView.bindModel();
          _this._elseView.setElement('[data-row="2"]');
          _this._elseView.bindDOM();
        }
        else {
          _this._elseView.remove();
          _this._elseView = null;
        }
      });
    },

    /**
     * Bind condition addition
     *
     * @return {void}
     * @api private
     */

    _bindConditionsAddition : function() {
      var _this = this;
      this.model.on('add:conditions', function(condition) {
        var view = new ConditionView(condition)
          , insertingRow = condition.get('row')
          , $condition;

        // We need a defer block because firstOperand and lastOperans relation
        // are not set yet.
        _.defer(function() {
          // If we insert a condition by choosing `and` or `or` in the `then` dropdown
          // We need to increase the row property on other conditions
          // and inputs. Otherwise some DOM bindings/event handling
          // will be wrong. We check that this is condition insertion from the `then`
          // dropdown by checking if inserting row is equals the `else` row minus 2
          if(_this.model.get('else') && insertingRow !== _this.model.get('else').get('row')) {
            _this.model.get('conditions').forEach(function(_condition) {
              var currentRow = _condition.get('row');
              if(currentRow >= insertingRow && condition.cid !== _condition.cid) {
                _condition.set('row', currentRow + 1);
              }
            });
            _this.model.get('inputs').forEach(function(input) {
              var currentRow = input.get('row');
              if(currentRow >= insertingRow) {
                input.set('row', currentRow + 1);
              }
            });
            var _else = _this.model.get('else');
            _else.set('row', _else.get('row') + 1);
            $condition = $(view.render());
          }
          else {
            if(insertingRow === 0) {
              _this.model.get('inputs').forEach(function(input) {
                var currentRow = input.get('row');
                if(currentRow >= insertingRow) {
                  input.set('row', currentRow + 1);
                }
              });
            }
            $condition = $(view.render()).addClass('invisible');
          }

          // bind conditions' DOM
          if(insertingRow > 0) {
            this.$('[data-row="' + (insertingRow - 1) + '"]').after($condition);
          }
          else {
            this.$('[data-row="1"]').before($condition);
          }

          var conditionSelector = '.condition[data-row="' + insertingRow + '"]';
          view.setElement(conditionSelector);
          view.bindDOM();
          view.firstOperandView.setElement(conditionSelector + ' .condition-first-operand');
          view.firstOperandView.bindDOM();
          view.lastOperandView.setElement(conditionSelector + ' .condition-last-operand');
          view.lastOperandView.bindDOM();

          // We must insert the view in the right index in `_conditionViews`
          var appended = false;
          _this._conditionViews.some(function(conditionView, index) {
            if(conditionView.model.get('row') === insertingRow - 1) {
              _this._conditionViews.splice(index + 1, 0, view);
              appended = true;
              return true;
            }
            return false;
          });
          if(!appended) {
            _this._conditionViews.push(view);
          }
        });
      });
    },

    /**
     * Bind condition removal
     *
     * @return {void}
     * @api private
     */

    _bindConditionRemoval : function() {
      var _this = this;
      this.model.on('remove:conditions', function(condition) {
        var row = condition.get('row');
        if(row === 0) {
          _this._removeLastCondition(condition);
          return;
        }
        _this.model.get('conditions').some(function(_condition) {
          if(_condition.get('row') === row - 1) {
            _this._removeSingleCondition(condition);
            return true;
          }
          return false;
        });
        _this.model.get('inputs').some(function(input) {
          if(input.get('row') === row - 1) {
            // _this._removeConditionAndInputs(condition);
          }
          return false;
        });
      });
    },

    /**
     * Remove condition only. This method only removes the `condition` from
     * the DOM by calling `Backbone.View.prototype.remove`.
     *
     * @return {void}
     * @api private
     */

    _removeSingleCondition : function(condition) {
      var _this = this, removalIndex, removalRow;
      this._conditionViews.forEach(function(conditionView, index) {
        if(conditionView.model === condition) {
          conditionView.remove();
          // We remove the view pointer from TranslatioView`
          removalRow = conditionView.model.get('row');
          removalIndex = index;
        }

        // We must decrease the row index on all subsequent conditions
        if(removalIndex && index > removalIndex) {
          conditionView.model.set('row', conditionView.model.get('row') - 1);
        }
      });

      if(removalRow) {
        this._inputViews.forEach(function(inputView) {
          var row = inputView.model.get('row');
          if(row > removalRow) {
            inputView.model.set('row', row - 1);
          }
        });

        if(this._elseView) {
          this._elseView.model.set('row', this._elseView.model.get('row') - 1);
        }

        _this._conditionViews.splice(removalIndex, 1);
      }
    },

    /**
     * Remove condition only. This method only removes the `condition` from
     * the DOM by calling `Backbone.View.prototype.remove`.
     *
     * @return {void}
     * @api private
     */

    _removeLastCondition : function() {
      this._conditionViews[0].remove();
      this._conditionViews.splice(0, 1);
      this.model.get('else').destroy();
      this._inputViews[0].model.destroy();
      this._inputViews[0].remove();
      this._inputViews.splice(0, 1);
      this._inputViews[0].model.set('row', 0);
    },

    /**
     * Bind condition removal
     *
     * @return {void}
     * @api private
     */

    _bindInputAddition : function() {
      var _this = this;
      this.model.on('add:inputs', function(input) {
        var row = input.get('row')
          , view = new InputView(input);

        _this.$('[data-row="' + (row - 1) + '"]').after(view.render());
        view.setElement('[data-row="' + row + '"]');
        view.bindDOM();

        _this._inputViews.push(view);
      });
    },

    /**
     * Bind condition removal
     *
     * @return {void}
     * @api private
     */

    _bindInputRemoval : function() {
      var _this = this;
      this.model.on('remove:inputs', function(input) {

      });
    },

    /**
     * Bind view
     *
     * @return {void}
     * @api private
     */

    bindDOM : function() {
      if(!has.touch) {
        this._addMouseInteractions();
      }
      // We loop through each relation view and try to bind
      // them with our object
      this._conditionViews.forEach(function(conditionView) {
        var conditionSelector = '.condition[data-row="' + conditionView.model.get('row') + '"]';
        conditionView.setElement(conditionSelector);
        conditionView.bindDOM();
        conditionView.firstOperandView.setElement(conditionSelector + ' .condition-first-operand');
        conditionView.firstOperandView.bindDOM();
        conditionView.lastOperandView.setElement(conditionSelector + ' .condition-last-operand');
        conditionView.lastOperandView.bindDOM();
      });
      this._inputViews.forEach(function(inputView) {
        var conditionSelector = '.input[data-row="' + inputView.model.get('row') + '"]';
        inputView.setElement(conditionSelector);
        inputView.bindDOM();
      });
      if(typeof this._elseView !== 'undefined') {
        this._elseView.setElement('.condition-else');
        this._elseView.bindModel();
      }

      this._setElements();

      this.boundDOM = true;
    },

    /**
     * Bind methods
     *
     * @return {void}
     * @api private
     */

    _bindMethods : function() {
      _.bindAll(
        this,
        '_addCondition',
        '_save'
      );
    },

    /**
     * Add desktop listeners
     *
     * @return {void}
     * @api private
     */

    _addMouseInteractions : function() {
      this.$('[disabled]').removeAttr('disabled');
      this.$el.on('click', '.add-condition', this._addCondition);
      this.$el.on('click', '.save', this._save);
    },

    /**
     * Set elements
     *
     * @return {void}
     * @api private
     */

    _setElements : function() {
      this.$region = $('[data-region=translation]');
    },

    /**
     * Add condition
     *
     * @delegate
     */

    _addCondition : function(event) {
      var _this = this;

      event.preventDefault();

      var _else = this.model.get('else'), row = 0, elseRow, inputRow;

      if(_else) {
        row = _else.get('row');
      }

      // We increase the row by two. So it becomes behind the input,
      // which has a row that is one bigger than inserting row.
      elseRow = row + 2;
      inputRow = elseRow + 1;

      var $inputs =
      $('[data-row="' + row + '"]')
        .add('[data-row="' + (row + 1) + '"]')
        .add('[data-row="' + (row + 2) + '"]')
        .addClass('invisible');

      var data = {
        statement : row === 0 ? 'if' : 'else if',
        firstOperand : 'value1',
        operator : '==',
        lastOperand : 'value2',
        vars : this.model.get('vars'),
        operators : cf.OPERATORS,
        additionalCompairOperators : cf.ADDITIONAL_COMPAIR_OPERATORS,
        row : row,
        translation : this.model
      };

      new this.model.Condition(data);

      if(_else) {
        _.defer(function() {
          _else.set('row', elseRow);
          new _this.model.Input({ value : '', row : inputRow, translation : _this.model});
        });
      }
      else {
        _.defer(function() {
          new _this.model.Else({ row : 2, parent : _this.model });
          var input = new _this.model.Input({ value : '', row : 3, translation : _this.model});
        });
      }

      _.defer(function() {
        $('[data-row="' + row + '"]')
          .add('[data-row="' + (row + 1) + '"]')
          .add('[data-row="' + (row + 2) + '"]')
          .removeClass('invisible');
      });
    },

    /**
     * Save
     *
     * @delegate
     */

    _save : function(event) {
      event.preventDefault();
      this.model.save();
    },

    /**
     * Render view
     *
     * @return {void}
     * @api public
     */

    render : function() {
      var _this = this
        , html = '', values = []
        , json = this.model.toJSON()
        , conditions = this.model.get('conditions')
        , inputs = this.model.get('inputs');

      // We loop through each relation object to get the HTML. We use `row`
      // to determine the order of the HTML
      conditions.forEach(function(condition) {
        var view = new ConditionView(condition);
        _this._conditionViews.push(view);
        values[condition.get('row')] = view.render();
      });
      inputs.forEach(function(input, index) {
        var view = new InputView(input);
        _this._inputViews.push(view);
        values[input.get('row')] = view.render();
      });

      // `else` might be null if there is no conditions on translations
      var _else = this.model.get('else');
      if(_else) {
        this._elseView = new ElseView(_else);
        values[_else.get('row')] = this._elseView.render();
      }

      json.values = values.join('');

      html += template['Translation'](json);

      return html;
    },

    /**
     * Remove
     *
     * @delegate
     */

    remove : function() {
      View.prototype.remove.call(this);
      this.$region[0].classList.add('hidden');
    }
  });
});
<|endoftext|>