<commit_before>/*
 * Jakefile for toast.js (https://github.com/srackham/toast.js).
 */
'use strict';

var pkg = require('./package.json');
var shelljs = require('shelljs');
var child_process = require('child_process');

/* Inputs and outputs */

var TOAST_JS = './dist/toast.js';
var TOAST_TS = './src/toast.ts';
var TOAST_MIN_JS = './dist/toast.min.js';

/* Utility functions. */

/*
 Execute shell commands in parallel then run the callback when they have all finished.
 `callback` defaults to the Jake async `complete` function.
 Abort if an error occurs.
 Write command output to the inherited stdout (unless the Jake --quiet option is set).
 Print a status message when each command starts and finishes (unless the Jake --quiet option is set).

 NOTE: This function is similar to the built-in jake.exec function but is twice as fast.
 */
function exec(commands, callback) {
  if (typeof commands === 'string') {
    commands = [commands];
  }
  callback = callback || complete;
  var remaining = commands.length;
  if (remaining === 0) {
    callback();
  } else {
    commands.forEach(function(command) {
      jake.logger.log('Starting: ' + command);
      child_process.exec(command, function(error, stdout, stderr) {
        jake.logger.log('Finished: ' + command);
        if (!jake.program.opts.quiet) {
          process.stdout.write(stdout);
        }
        if (error !== null) {
          fail(error, error.code);
        }
        remaining--;
        if (remaining === 0) {
          callback();
        }
      });
    });
  }
}

/*
 Tasks

 All tasks are synchronous (another task will not run until the current task has completed).
 Consequently all task dependencies are executed asynchronously in declaration order.
 The `exec` function ensures shell commands within each task run in parallel.
 */

desc('Run test task.');
task('default', ['test']);

desc('compile, lint, test');
task('build', ['test']);

desc('Lint source files.');
task('lint', {async: true}, function() {
  var commands = [];
  commands.push('jsonlint --quiet package.json');
  commands.push('tslint -f ' + TOAST_TS);
  exec(commands);
});

desc('Run tests (recompile if necessary).');
task('test', ['compile', 'lint'], {async: true}, function() {
  var commands = [];
  // TODO: Unit tests.
  exec(commands);
});

desc('Compile Typescript to JavaScript then uglify.');
task('compile', [TOAST_JS, TOAST_MIN_JS]);

file(TOAST_JS, [TOAST_TS], {async: true}, function() {
  exec('tsc --noImplicitAny --out ' + TOAST_JS + ' ' + TOAST_TS);
});

file(TOAST_MIN_JS, [TOAST_JS], {async: true}, function() {
  var preamble = '/* ' + pkg.name + ' ' + pkg.version + ' (' + pkg.repository.url + ') */';
  var command = 'uglifyjs  --preamble "' + preamble + '" --output ' + TOAST_MIN_JS + ' ' + TOAST_JS;
  exec(command);
});

desc('Validate HTML documents with W3C Validator.');
task('validate-html', {async: true}, function() {
  var commands = [];
  commands.push('w3cjs validate ./doc/toast-examples.html');
  exec(commands);
});

desc('Display or update the project version number. Use vers=x.y.z syntax to set a new version number.');
task('version', function() {
  var version = process.env.vers;
  if (!version) {
    console.log('\nversion: ' + pkg.version);
  }
  else {
    if (!version.match(/^\d+\.\d+\.\d+$/)) {
      fail('Invalid version number: ' + version + '\n');
    }
    shelljs.sed('-i', /(\n\s*"version"\s*:\s*)"\d+\.\d+\.\d+"/, '$1' + '"' + version + '"', 'package.json');
  }
});

desc('Create tag ' + pkg.version);
task('tag', ['test'], {async: true}, function() {
  exec('git tag -a -m "Tag ' + pkg.version + '" ' + pkg.version);
});

desc('Commit changes to local Git repo.');
task('commit', ['test'], {async: true}, function() {
  jake.exec('git commit -a', {interactive: true}, complete);
});

/*
desc('push, publish-npm.');
task('publish', ['push', 'publish-npm']);
*/

desc('Push local commits to Github.');
task('push', ['test'], {async: true}, function() {
  exec('git push -u --tags origin master');
});

/*
desc('Publish to npm.');
task('publish-npm', {async: true}, ['test'], function() {
  exec('npm publish');
});
*/

<commit_msg>Jakefile: Include HTML validation in build task.<commit_after>/*
 * Jakefile for toast.js (https://github.com/srackham/toast.js).
 */
'use strict';

var pkg = require('./package.json');
var shelljs = require('shelljs');
var child_process = require('child_process');

/* Inputs and outputs */

var TOAST_JS = './dist/toast.js';
var TOAST_TS = './src/toast.ts';
var TOAST_MIN_JS = './dist/toast.min.js';

/* Utility functions. */

/*
 Execute shell commands in parallel then run the callback when they have all finished.
 `callback` defaults to the Jake async `complete` function.
 Abort if an error occurs.
 Write command output to the inherited stdout (unless the Jake --quiet option is set).
 Print a status message when each command starts and finishes (unless the Jake --quiet option is set).

 NOTE: This function is similar to the built-in jake.exec function but is twice as fast.
 */
function exec(commands, callback) {
  if (typeof commands === 'string') {
    commands = [commands];
  }
  callback = callback || complete;
  var remaining = commands.length;
  if (remaining === 0) {
    callback();
  } else {
    commands.forEach(function(command) {
      jake.logger.log('Starting: ' + command);
      child_process.exec(command, function(error, stdout, stderr) {
        jake.logger.log('Finished: ' + command);
        if (!jake.program.opts.quiet) {
          process.stdout.write(stdout);
        }
        if (error !== null) {
          fail(error, error.code);
        }
        remaining--;
        if (remaining === 0) {
          callback();
        }
      });
    });
  }
}

/*
 Tasks

 All tasks are synchronous (another task will not run until the current task has completed).
 Consequently all task dependencies are executed asynchronously in declaration order.
 The `exec` function ensures shell commands within each task run in parallel.
 */

desc('Run test task.');
task('default', ['test']);

desc('compile, lint, test, validate HTML.');
task('build', ['test', 'validate-html']);

desc('Lint source files.');
task('lint', {async: true}, function() {
  var commands = [];
  commands.push('jsonlint --quiet package.json');
  commands.push('tslint -f ' + TOAST_TS);
  exec(commands);
});

desc('Run tests (recompile if necessary).');
task('test', ['compile', 'lint'], {async: true}, function() {
  var commands = [];
  // TODO: Unit tests.
  exec(commands);
});

desc('Compile Typescript to JavaScript then uglify.');
task('compile', [TOAST_JS, TOAST_MIN_JS]);

file(TOAST_JS, [TOAST_TS], {async: true}, function() {
  exec('tsc --noImplicitAny --out ' + TOAST_JS + ' ' + TOAST_TS);
});

file(TOAST_MIN_JS, [TOAST_JS], {async: true}, function() {
  var preamble = '/* ' + pkg.name + ' ' + pkg.version + ' (' + pkg.repository.url + ') */';
  var command = 'uglifyjs  --preamble "' + preamble + '" --output ' + TOAST_MIN_JS + ' ' + TOAST_JS;
  exec(command);
});

desc('Validate HTML documents with W3C Validator.');
task('validate-html', {async: true}, function() {
  var commands = [];
  commands.push('w3cjs validate ./doc/toast-examples.html');
  exec(commands);
});

desc('Display or update the project version number. Use vers=x.y.z syntax to set a new version number.');
task('version', function() {
  var version = process.env.vers;
  if (!version) {
    console.log('\nversion: ' + pkg.version);
  }
  else {
    if (!version.match(/^\d+\.\d+\.\d+$/)) {
      fail('Invalid version number: ' + version + '\n');
    }
    shelljs.sed('-i', /(\n\s*"version"\s*:\s*)"\d+\.\d+\.\d+"/, '$1' + '"' + version + '"', 'package.json');
  }
});

desc('Create tag ' + pkg.version);
task('tag', ['test'], {async: true}, function() {
  exec('git tag -a -m "Tag ' + pkg.version + '" ' + pkg.version);
});

desc('Commit changes to local Git repo.');
task('commit', ['test'], {async: true}, function() {
  jake.exec('git commit -a', {interactive: true}, complete);
});

/*
desc('push, publish-npm.');
task('publish', ['push', 'publish-npm']);
*/

desc('Push local commits to Github.');
task('push', ['test'], {async: true}, function() {
  exec('git push -u --tags origin master');
});

/*
desc('Publish to npm.');
task('publish-npm', {async: true}, ['test'], function() {
  exec('npm publish');
});
*/

<|endoftext|>


<commit_before>import language from '../config/language.js'
import stateManege from '../util/stateManage.js'
import config from '../config/config.js'
import socket from './socket.js'
import { browserHistory } from 'react-router'
import { mergeUserInfo, updateUserInfo, createRoom } from './user.js'
import { pushSnackbar, setRightManager, setLeftManager, msgContainerScroll } from './pageUI.js'
import { roomsSchema , historySchema } from '../middlewares/schemas.js'
import { socketEmit, dispatchAction, dispatchThunk } from './common.js'
import { updateRoomInfo, mergeRoomInfo } from './activeList.js'
import { normalize } from 'normalizr'
import {
    getSendFunc,
    mergeMessage,
    addMessage,
    clearHistory,
    addHistories,
    initItem,
    removeItemMessage,
} from './messages.js'

export const errPrint = (err) => {
    console.error(err);
    language[err]? pushSnackbar(language[err]): pushSnackbar(language['ERROR1000']);
}

function mergeCbMessage(preMsg,ret){
    let message = {};
    message[preMsg._id] = {isLoading: false};
    message.Tid = ret._id;
    message.Ttimestamp = ret.timestamp;
    message.Tcontent = ret.content;
    mergeMessage(message);
}
export const sendMessage = (isPrivate = false) => (msg,preMsg) => {
    addMessage(preMsg);
    getSendFunc(isPrivate)(msg)
    .then((ret) => mergeCbMessage(preMsg, ret))
    .catch(err => errPrint(err))
}

export const sendFile = (isPrivate = false) => (msg,fileHandle) => {
    fileHandle.getUrlData()
    .then(ret=>{
        if(msg.preMessage.type === 'file'){
            msg.preMessage.content = JSON.stringify(fileHandle.getFileInfo());
        } else {
            msg.preMessage.content =  ret;
        }
        addMessage(msg.preMessage);
        return fileHandle.upload();
    })
    .then(ret => {
        if(msg.message.type === 'file'){
            msg.message.content = JSON.stringify({...fileHandle.getFileInfo(),src: ret.src});
        } else {
            msg.message.content = ret.src;
        }
        return getSendFunc(isPrivate)(msg.message);
    })
    .then((ret) => mergeCbMessage(msg.preMessage,ret))
    .catch((err) => errPrint(err))
}

export const loadRoomHistory = dispatchThunk( () => {
    return (dispatch,getState) => {
        const state = getState(),
              limit = config.ScreenMessageLenght;
        const curRoomInfo = stateManege.getCurRoomInfo(state),
              messages = state.get('messages'),
              userId =  state.getIn(['user','_id']);
        const first = curRoomInfo.get('histories').first(),
              _id = curRoomInfo.get('_id');
        const timestamp = messages.getIn([first,'Ttimestamp']) || messages.getIn([first,'timestamp']);
        if(curRoomInfo.get('isPrivate')){
            return socketEmit('loadPrivateHistories')({
                limit, timestamp,
                fromUserId: _id,
                toUserId: userId,
            })
            .then((ret) => {
                const normalizeHis = normalize(ret,historySchema);
                addHistories({
                    histories: normalizeHis.entities.histories,
                    room: {_id , histories: normalizeHis.result}
                });
            })
        } else{
            return socketEmit('loadRoomHistories')({limit, timestamp , _id})
            .then((ret)=>{
                let entity = normalize([ret],roomsSchema).entities;
                let { _id, histories } = entity.rooms[ret._id];
                addHistories({ histories: entity.histories, room: {_id, histories} });
            })
        }

    }
})

export const changeRoom = isPrivate => curRoom => {
    msgContainerScroll(true);
    setRightManager({isShow: false});
    clearHistory();
    mergeUserInfo({curRoom});
    dispatchThunk(() => (dispatch,getState) =>{
        const state = getState(),
              maxLength = config.ScreenMessageLenght;
        const curRoom = state.getIn(['user','curRoom']);
        const curRoomInfo = state.getIn(['activeList',curRoom]);
        if(!curRoom) return;
        if(!curRoomInfo) return initItem(isPrivate)(curRoom).then(()=>loadRoomHistory());
        const length = curRoomInfo.get('histories').size;
        if(length < maxLength) return loadRoomHistory();
    })()
}
export const changeUserInfo = info => {
    updateUserInfo(info)
    .then(ret => mergeUserInfo(info))
    .catch(err => errPrint(err))
}

export const changeRoomInfo = info => {
    updateRoomInfo(info)
    .then(ret => {
        mergeRoomInfo(info);
    })
    .catch(err => errPrint(err))
}

export const createGroup = (info) => {
    pushSnackbar(language.newGroup);
    createRoom(info)
    .then(ret => {
        pushSnackbar(language.success);
        setLeftManager({isShow: false});
        changeRoom(false)(ret._id);
    })
    .catch(err => errPrint(err));
}

export const exitRoom = (info) => {
    socketEmit('exitRoom')(info)
    .then(ret => {
        changeRoom(false)('');
        removeItemMessage(info);
    })
    .catch(err => errPrint(err))
}

export const joinRoom = (info) => {
    socketEmit('joinRoom')(info)
    .then(ret => {
        if(ret.isOk) {
            browserHistory.push('/');
            pushSnackbar(language.joinRoomSuccess);
            changeRoom(false)(ret._id);

        }
    })
    .catch(err => pushSnackbar(language.inviteLinkDisabled))
}

export const logout = () => {
    browserHistory.push('/login');
    delete localStorage.token;
    socket.disconnect();
    socket.connect();
}<commit_msg>fix: merge message bug<commit_after>import language from '../config/language.js'
import stateManege from '../util/stateManage.js'
import config from '../config/config.js'
import socket from './socket.js'
import { browserHistory } from 'react-router'
import { mergeUserInfo, updateUserInfo, createRoom } from './user.js'
import { pushSnackbar, setRightManager, setLeftManager, msgContainerScroll } from './pageUI.js'
import { roomsSchema , historySchema } from '../middlewares/schemas.js'
import { socketEmit, dispatchAction, dispatchThunk } from './common.js'
import { updateRoomInfo, mergeRoomInfo } from './activeList.js'
import { normalize } from 'normalizr'
import {
    getSendFunc,
    mergeMessage,
    addMessage,
    clearHistory,
    addHistories,
    initItem,
    removeItemMessage,
} from './messages.js'

export const errPrint = (err) => {
    console.error(err);
    language[err]? pushSnackbar(language[err]): pushSnackbar(language['ERROR1000']);
}

function mergeCbMessage(preMsg,ret){
    let message = {};
    message[preMsg._id] = {isLoading: false};
    message[preMsg._id].Tid = ret._id;
    message[preMsg._id].Ttimestamp = ret.timestamp;
    message[preMsg._id].Tcontent = ret.content;
    mergeMessage(message);
}
export const sendMessage = (isPrivate = false) => (msg,preMsg) => {
    addMessage(preMsg);
    getSendFunc(isPrivate)(msg)
    .then((ret) => mergeCbMessage(preMsg, ret))
    .catch(err => errPrint(err))
}

export const sendFile = (isPrivate = false) => (msg,fileHandle) => {
    fileHandle.getUrlData()
    .then(ret=>{
        if(msg.preMessage.type === 'file'){
            msg.preMessage.content = JSON.stringify(fileHandle.getFileInfo());
        } else {
            msg.preMessage.content =  ret;
        }
        addMessage(msg.preMessage);
        return fileHandle.upload();
    })
    .then(ret => {
        if(msg.message.type === 'file'){
            msg.message.content = JSON.stringify({...fileHandle.getFileInfo(),src: ret.src});
        } else {
            msg.message.content = ret.src;
        }
        return getSendFunc(isPrivate)(msg.message);
    })
    .then((ret) => mergeCbMessage(msg.preMessage,ret))
    .catch((err) => errPrint(err))
}

export const loadRoomHistory = dispatchThunk( () => {
    return (dispatch,getState) => {
        const state = getState(),
              limit = config.ScreenMessageLenght;
        const curRoomInfo = stateManege.getCurRoomInfo(state),
              messages = state.get('messages'),
              userId =  state.getIn(['user','_id']);
        const first = curRoomInfo.get('histories').first(),
              _id = curRoomInfo.get('_id');
        const timestamp = messages.getIn([first,'Ttimestamp']) || messages.getIn([first,'timestamp']);
        if(curRoomInfo.get('isPrivate')){
            return socketEmit('loadPrivateHistories')({
                limit, timestamp,
                fromUserId: _id,
                toUserId: userId,
            })
            .then((ret) => {
                const normalizeHis = normalize(ret,historySchema);
                addHistories({
                    histories: normalizeHis.entities.histories,
                    room: {_id , histories: normalizeHis.result}
                });
            })
        } else{
            return socketEmit('loadRoomHistories')({limit, timestamp , _id})
            .then((ret)=>{
                let entity = normalize([ret],roomsSchema).entities;
                let { _id, histories } = entity.rooms[ret._id];
                addHistories({ histories: entity.histories, room: {_id, histories} });
            })
        }

    }
})

export const changeRoom = isPrivate => curRoom => {
    msgContainerScroll(true);
    setRightManager({isShow: false});
    clearHistory();
    mergeUserInfo({curRoom});
    dispatchThunk(() => (dispatch,getState) =>{
        const state = getState(),
              maxLength = config.ScreenMessageLenght;
        const curRoom = state.getIn(['user','curRoom']);
        const curRoomInfo = state.getIn(['activeList',curRoom]);
        if(!curRoom) return;
        if(!curRoomInfo) return initItem(isPrivate)(curRoom).then(()=>loadRoomHistory());
        const length = curRoomInfo.get('histories').size;
        if(length < maxLength) return loadRoomHistory();
    })()
}
export const changeUserInfo = info => {
    updateUserInfo(info)
    .then(ret => mergeUserInfo(info))
    .catch(err => errPrint(err))
}

export const changeRoomInfo = info => {
    updateRoomInfo(info)
    .then(ret => {
        mergeRoomInfo(info);
    })
    .catch(err => errPrint(err))
}

export const createGroup = (info) => {
    pushSnackbar(language.newGroup);
    createRoom(info)
    .then(ret => {
        pushSnackbar(language.success);
        setLeftManager({isShow: false});
        changeRoom(false)(ret._id);
    })
    .catch(err => errPrint(err));
}

export const exitRoom = (info) => {
    socketEmit('exitRoom')(info)
    .then(ret => {
        changeRoom(false)('');
        removeItemMessage(info);
    })
    .catch(err => errPrint(err))
}

export const joinRoom = (info) => {
    socketEmit('joinRoom')(info)
    .then(ret => {
        if(ret.isOk) {
            browserHistory.push('/');
            pushSnackbar(language.joinRoomSuccess);
            changeRoom(false)(ret._id);

        }
    })
    .catch(err => pushSnackbar(language.inviteLinkDisabled))
}

export const logout = () => {
    browserHistory.push('/login');
    delete localStorage.token;
    socket.disconnect();
    socket.connect();
}<|endoftext|>


<commit_before>(function (global, factory) {
    if (typeof define === "function" && define.amd) {
        define(["exports", "jQuery", "esprima"], factory);
    } else if (typeof exports !== "undefined") {
        factory(exports, require("jQuery"), require("esprima"));
    } else {
        var mod = {
            exports: {}
        };
        factory(mod.exports, global.jQuery, global.esprima);
        global.jqueryPlugincreator = mod.exports;
    }
})(this, function (exports, _jQuery, _esprima) {
    "use strict";

    Object.defineProperty(exports, "__esModule", {
        value: true
    });

    var _jQuery2 = _interopRequireDefault(_jQuery);

    var _esprima2 = _interopRequireDefault(_esprima);

    function _interopRequireDefault(obj) {
        return obj && obj.__esModule ? obj : {
            default: obj
        };
    }

    /*!
     * jQuery Plugin Creator 0.3.0
     * https://github.com/OOPMan/jquery-plugin-creator
     *
     * Copyright 2014 Adam Jorgensen
     * Released under the MIT license.
     * http://github.com/OOPMan/jquery-plugin-creator/LICENSE
     *
     */
    /**
     * TODO: Add checks to prevent over-writing plugins
     * TODO: Add ability to not specify plugin name and have a randomly generated name assigned
     */

    var $ = _jQuery2.default,
        noOp = _jQuery2.default.noop,
        scopeName = "jquery-plugincreator-",

    /**
     * Given a function parameter name and a function, determines whether the function in question accepts the
     * named parameter.
     *
     * As this function is used internally to detect whether a function defines the _super parameter and given
     * that the _super parameter will always be the last parameter IF it is defined, the search is restricted
     * to the very last parameter defined for a given function.
     *
     * @param {function} inputFunction
     * @returns {boolean}
     */
    isSuperParameterDefinedForFunction = function isSuperParameterDefinedForFunction(inputFunction) {
        var inputFunctionAST = _esprima2.default.parse("f = " + inputFunction.toString()),
            inputFunctionParams = inputFunctionAST.body[0].expression.right.params,
            firstInputFunctionParam = inputFunctionParams.shift();
        if (typeof firstInputFunctionParam == "undefined") return false;
        return firstInputFunctionParam.name == "_super";
    },

    /**
     *
     * @param {function} childMember
     * @param {function} parentMember
     * @param {boolean} [alwaysInjectSuper=false]
     * @returns {function}
     */
    parentWrapper = function parentWrapper(childMember, parentMember) {
        var alwaysInjectSuper = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];

        var parentMember = parentMember == childMember ? noOp : parentMember,
            childMemberExpectsSuper = alwaysInjectSuper ? true : isSuperParameterDefinedForFunction(childMember);

        function parentGenerator(self) {
            var _super = parentMember;
            if (_super._isParentGenerator) _super = _super(self);else _super = function _super() {
                return parentMember.apply(self, arguments);
            };
            return function () {
                var args = $.makeArray(arguments);
                if (childMemberExpectsSuper) args.unshift(_super);
                return childMember.apply(self, args);
            };
        }

        parentGenerator._isParentGenerator = true;
        return parentGenerator;
    },

    /**
     *
     * @param {Object} childMembers
     * @param {Object} parentMembers
     * @param {Object} [self=null]
     * @param {boolean} [alwaysInjectSuper=false]
     * @returns {Object}
     */
    wrapParents = function wrapParents(childMembers, parentMembers) {
        var self = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
        var alwaysInjectSuper = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];

        var childMembers = $.extend(true, {}, childMembers),
            parentMembers = $.extend(true, {}, parentMembers);
        for (var memberName in parentMembers) {
            if (childMembers[memberName]) {
                if (typeof childMembers[memberName] == "function" && !childMembers[memberName]._isParentGenerator) {
                    childMembers[memberName] = parentWrapper(childMembers[memberName], parentMembers[memberName], alwaysInjectSuper);
                    if (self) childMembers[memberName] = childMembers[memberName](self);
                }
            } else {
                childMembers[memberName] = parentMembers[memberName];
            }
        }
        return childMembers;
    },

    /**
     *
     * @type {{addPlugin: pluginCreator.addPlugin}}
     */
    pluginCreator = {
        /**
         * Adds a new stateful plugin to jQuery.
         *
         * A stateful plugin consists of the following optional components:
         * - An object defining default properties to be associated with a plugin instance via its options member
         * - An object defining member functions and values to be associated with a plugin instances prototype.
         *
         * @param {string} name
         * @param {Object} [defaults={}]
         * @param {Object} [members={}]
         * @param {boolean} [alwaysInjectSuper=false]
         * @return {string}
         */
        addPlugin: function addPlugin(name) {
            var defaults = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
            var members = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
            var alwaysInjectSuper = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];

            var defaults = $.extend(true, {}, defaults),
                baseMembers = {
                /**
                 * Default init function. Does nothing.
                 */
                init: noOp,
                /**
                 *
                 * @returns {Object}
                 */
                getInstance: function getInstance() {
                    return this;
                },
                /**
                 *
                 * @param {Object} [options]
                 * @returns {Object}
                 */
                update: function update(options) {
                    $.extend(true, this.options, options || {});
                },
                /**
                 *
                 * @param {Object} members
                 * @param {boolean} [alwaysInjectSuper=false]
                 * @returns {boolean}
                 */
                extend: function extend(members) {
                    var alwaysInjectSuper = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];

                    $.extend(true, this, wrapParents(members, this, this, alwaysInjectSuper));
                },
                /**
                 * Destructor function, performs the following:
                 *
                 * 1: Triggers a scopeName + name + ".destroy" (E.g. "jquery-plugincreator-myPlugin.destroy") event on the jQuery context (I.e. $(this) ) for the plugin instance.
                 * 2: Removes the scopeName + name (E.g. "jquery-plugincreator-myPlugin") class from the element linked to the plugin instance.
                 * 3: Removes the scopeName + name (E.g. "jquery-plugincreator-myPlugin") data value from the element linked to the plugin instance.
                 * 4: Removes the "data-" + scopeName + name (E.g. "data-jquery-plugincreator-myPlugin") attribute from the element linked to the plugin instance.
                 */
                destroy: function destroy() {
                    this.context.trigger(scopeName + name + ".destroy").removeClass(scopeName + name).removeData(scopeName + name).removeAttr("data-" + scopeName + name);
                }
            },
                members = $.isArray(members) ? members : [wrapParents(members, wrapParents(baseMembers, baseMembers, null, alwaysInjectSuper), null, alwaysInjectSuper)];

            /**
             * This function is used to instantiate an instance of the plugin on a given element.
             *
             * @param {Object} element
             * @param {Object} [options]
             * @param {Array} [initArguments]
             */
            function instantiatePlugin(element, options, initArguments) {
                var options = options || {},
                    prototype = $.extend(true, {}, members[0]),
                    instance = null;

                function pluginConstructor() {
                    this.instanceOf = name;
                    this.element = element;
                    this.context = $(element).addClass(scopeName + name);
                    this.options = $.extend(true, {}, defaults, options);
                    for (var memberName in prototype) {
                        if (typeof prototype[memberName] == "function" && prototype[memberName]._isParentGenerator) {
                            prototype[memberName] = prototype[memberName](this);
                        }
                    }
                    $.extend(true, this, prototype);
                    this.init.apply(this, initArguments);
                }

                instance = new pluginConstructor();
                $.data(element, scopeName + name, instance);
                return instance;
            }

            /**
             * A function to handle the actual process of instantiating a plugin instance or calling a method on
             * a given plugin instance.
             *
             * @param {Object} self
             * @param {Object|String} options
             * @param {Array} args
             * @returns {*}
             */
            function processPluginCall(self, options, args) {
                var instance = $.data(self, scopeName + name);
                if (instance) {
                    if (typeof options == "string" && typeof instance[options] == "function") {
                        // call a method on the instance
                        return instance[options].apply(instance, args.slice(1));
                    } else if (typeof instance.update == "function" && $.isPlainObject(options)) {
                        // call update on the instance
                        return instance.update.apply(instance, args);
                    } else {
                        throw options + " is not a member of " + self;
                    }
                } else {
                    return instantiatePlugin(self, options, args.slice(1));
                }
            }

            /**
             *
             * @param {Object|string} options
             * @returns {jQuery}
             */
            $.fn[name] = function (options) {
                var args = $.makeArray(arguments),
                    result = this;
                if (this.length === 1) {
                    result = processPluginCall(this[0], options, args);
                } else {
                    if (options === "map") {
                        result = this.map(function () {
                            return processPluginCall(this, args[1], args.slice(2));
                        });
                    } else {
                        result = this.each(function () {
                            processPluginCall(this, options, args);
                        });
                    }
                }
                if (typeof result == "undefined") result = this;
                return result;
            };

            /**
             *
             * @type {Object}
             */
            $.fn[name].defaults = defaults;

            /**
             *
             * @param {Object} options
             * @returns {Object}
             */
            $.fn[name].updateDefaultsWith = function (options) {
                $.extend(true, defaults, options);
                return defaults;
            };

            /**
             *
             * @param {Object} childMembers
             * @param {boolean} [alwaysInjectSuper=false]
             * @returns {function}
             */
            $.fn[name].extendMembersWith = function (childMembers) {
                var alwaysInjectSuper = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];

                members.unshift(wrapParents(childMembers, members[0], null, alwaysInjectSuper));
            };

            /**
             * Clones a plugin added using jQuery PluginCreator.
             *
             * While the defaults and members are cloned completely and are independent, the constructor is shared.
             *
             * @param {string} name
             * @param {string} newName
             * @returns {string}
             */
            $.fn[name].cloneTo = function (newName) {
                return pluginCreator.addPlugin(newName, defaults, $.extend(true, [], members));
            };

            /**
             * Extends a plugin added using jQuery PluginCreator.
             *
             * Defaults and members are cloned and extended from the original plugin while the extended constructor
             * functions such that the constructor for the original plugin is called first, then new new
             * constructor, if present.
             *
             * @param {string} name
             * @param {string} newName
             * @param {Object} [childMembers]
             * @return {string}
             */
            $.fn[name].extendTo = function (newName, childMembers) {
                $.fn[name].cloneTo(newName);
                $.fn[newName].extendMembersWith(childMembers);
            };

            return name;
        }
    };

    _jQuery2.default.extend(_jQuery2.default, pluginCreator);

    exports.default = pluginCreator.addPlugin;
});<commit_msg>#4: Regenerated jquery.plugincreator.js<commit_after>(function (global, factory) {
    if (typeof define === "function" && define.amd) {
        define(["exports", "jQuery", "esprima"], factory);
    } else if (typeof exports !== "undefined") {
        factory(exports, require("jQuery"), require("esprima"));
    } else {
        var mod = {
            exports: {}
        };
        factory(mod.exports, global.jQuery, global.esprima);
        global.jqueryPlugincreator = mod.exports;
    }
})(this, function (exports, _jQuery, _esprima) {
    "use strict";

    Object.defineProperty(exports, "__esModule", {
        value: true
    });

    var _jQuery2 = _interopRequireDefault(_jQuery);

    var _esprima2 = _interopRequireDefault(_esprima);

    function _interopRequireDefault(obj) {
        return obj && obj.__esModule ? obj : {
            default: obj
        };
    }

    /*!
     * jQuery Plugin Creator 0.3.0
     * https://github.com/OOPMan/jquery-plugin-creator
     *
     * Copyright 2014 Adam Jorgensen
     * Released under the MIT license.
     * http://github.com/OOPMan/jquery-plugin-creator/LICENSE
     *
     */
    /**
     * TODO: Add checks to prevent over-writing plugins
     * TODO: Add ability to not specify plugin name and have a randomly generated name assigned
     */

    var $ = _jQuery2.default,
        noOp = _jQuery2.default.noop,
        scopeName = "jquery-plugincreator-",

    /**
     * Given a function parameter name and a function, determines whether the function in question accepts the
     * named parameter.
     *
     * As this function is used internally to detect whether a function defines the _super parameter and given
     * that the _super parameter will always be the last parameter IF it is defined, the search is restricted
     * to the very last parameter defined for a given function.
     *
     * @param {function} inputFunction
     * @returns {boolean}
     */
    isSuperParameterDefinedForFunction = function isSuperParameterDefinedForFunction(inputFunction) {
        var inputFunctionAST = _esprima2.default.parse("f = " + inputFunction.toString()),
            inputFunctionParams = inputFunctionAST.body[0].expression.right.params,
            firstInputFunctionParam = inputFunctionParams.shift();
        if (typeof firstInputFunctionParam == "undefined") return false;
        return firstInputFunctionParam.name == "_super";
    },

    /**
     *
     * @param {function} childMember
     * @param {function} parentMember
     * @param {boolean} [alwaysInjectSuper=false]
     * @returns {function}
     */
    parentWrapper = function parentWrapper(childMember, parentMember) {
        var alwaysInjectSuper = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];

        var parentMember = parentMember == childMember ? noOp : parentMember,
            childMemberExpectsSuper = alwaysInjectSuper ? true : isSuperParameterDefinedForFunction(childMember);

        function parentGenerator(self) {
            var _super = parentMember;
            if (_super._isParentGenerator) _super = _super(self);else _super = function _super() {
                return parentMember.apply(self, arguments);
            };
            return function () {
                var args = $.makeArray(arguments);
                if (childMemberExpectsSuper) args.unshift(_super);
                return childMember.apply(self, args);
            };
        }

        parentGenerator._isParentGenerator = true;
        return parentGenerator;
    },

    /**
     *
     * @param {Object} childMembers
     * @param {Object} parentMembers
     * @param {Object} [self=null]
     * @param {boolean} [alwaysInjectSuper=false]
     * @returns {Object}
     */
    wrapParents = function wrapParents(childMembers, parentMembers) {
        var self = arguments.length <= 2 || arguments[2] === undefined ? null : arguments[2];
        var alwaysInjectSuper = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];

        var childMembers = $.extend(true, {}, childMembers),
            parentMembers = $.extend(true, {}, parentMembers);
        for (var memberName in parentMembers) {
            if (childMembers[memberName]) {
                if (typeof childMembers[memberName] == "function" && !childMembers[memberName]._isParentGenerator) {
                    childMembers[memberName] = parentWrapper(childMembers[memberName], parentMembers[memberName], alwaysInjectSuper);
                    if (self) childMembers[memberName] = childMembers[memberName](self);
                }
            } else {
                childMembers[memberName] = parentMembers[memberName];
            }
        }
        return childMembers;
    },

    /**
     *
     * @type {{addPlugin: pluginCreator.addPlugin}}
     */
    pluginCreator = {
        /**
         * Adds a new stateful plugin to jQuery.
         *
         * A stateful plugin consists of the following optional components:
         * - An object defining default properties to be associated with a plugin instance via its options member
         * - An object defining member functions and values to be associated with a plugin instances prototype.
         *
         * @param {string} name
         * @param {Object} [defaults={}]
         * @param {Object} [members={}]
         * @param {boolean} [alwaysInjectSuper=false]
         * @return {string}
         */
        addPlugin: function addPlugin(name) {
            var defaults = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
            var members = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
            var alwaysInjectSuper = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];

            var defaults = $.extend(true, {}, defaults),
                baseMembers = {
                /**
                 * Default init function. Does nothing.
                 */
                init: noOp,
                /**
                 *
                 * @returns {Object}
                 */
                getInstance: function getInstance() {
                    return this;
                },
                /**
                 *
                 * @param {Object} [options]
                 * @returns {Object}
                 */
                update: function update(options) {
                    $.extend(true, this.options, options || {});
                },
                /**
                 *
                 * @param {Object} members
                 * @param {boolean} [alwaysInjectSuper=false]
                 * @returns {boolean}
                 */
                extend: function extend(members) {
                    var alwaysInjectSuper = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];

                    $.extend(true, this, wrapParents(members, this, this, alwaysInjectSuper));
                },
                /**
                 * Destructor function, performs the following:
                 *
                 * 1: Triggers a scopeName + name + ".destroy" (E.g. "jquery-plugincreator-myPlugin.destroy") event on the jQuery context (I.e. $(this) ) for the plugin instance.
                 * 2: Removes the scopeName + name (E.g. "jquery-plugincreator-myPlugin") class from the element linked to the plugin instance.
                 * 3: Removes the scopeName + name (E.g. "jquery-plugincreator-myPlugin") data value from the element linked to the plugin instance.
                 * 4: Removes the "data-" + scopeName + name (E.g. "data-jquery-plugincreator-myPlugin") attribute from the element linked to the plugin instance.
                 */
                destroy: function destroy() {
                    this.context.trigger(scopeName + name + ".destroy").removeClass(scopeName + name).removeData(scopeName + name).removeAttr("data-" + scopeName + name);
                }
            },
                members = $.isArray(members) ? members : [wrapParents(members, wrapParents(baseMembers, baseMembers, null, alwaysInjectSuper), null, alwaysInjectSuper)];

            /**
             * This function is used to instantiate an instance of the plugin on a given element.
             *
             * @param {Object} element
             * @param {Object} [options]
             * @param {Array} [initArguments]
             */
            function instantiatePlugin(element, options, initArguments) {
                var options = options || {},
                    prototype = $.extend(true, {}, members[0]),
                    instance = null;

                function pluginConstructor() {
                    this.instanceOf = name;
                    this.element = element;
                    this.context = $(element).addClass(scopeName + name);
                    this.options = $.extend(true, {}, defaults, options);
                    for (var memberName in prototype) {
                        if (typeof prototype[memberName] == "function" && prototype[memberName]._isParentGenerator) {
                            prototype[memberName] = prototype[memberName](this);
                        }
                    }
                    $.extend(true, this, prototype);
                    this.init.apply(this, initArguments);
                }

                instance = new pluginConstructor();
                $.data(element, scopeName + name, instance);
                return instance;
            }

            /**
             * A function to handle the actual process of instantiating a plugin instance or calling a method on
             * a given plugin instance.
             *
             * @param {Object} self
             * @param {Object|String} options
             * @param {Array} args
             * @returns {*}
             */
            function processPluginCall(self, options, args) {
                var instance = $.data(self, scopeName + name);
                if (instance) {
                    if (typeof options == "string" && typeof instance[options] == "function") {
                        // call a method on the instance
                        return instance[options].apply(instance, args.slice(1));
                    } else if (typeof instance.update == "function" && $.isPlainObject(options)) {
                        // call update on the instance
                        return instance.update.apply(instance, args);
                    } else {
                        throw options + " is not a member of " + self;
                    }
                } else {
                    return instantiatePlugin(self, options, args.slice(1));
                }
            }

            /**
             *
             * @param {Object|string} options
             * @returns {jQuery}
             */
            $.fn[name] = function (options) {
                var args = $.makeArray(arguments),
                    result = this;
                if (this.length === 1) {
                    result = processPluginCall(this[0], options, args);
                } else {
                    if (options === "map") {
                        result = this.map(function () {
                            return processPluginCall(this, args[1], args.slice(2));
                        });
                    } else {
                        result = this.each(function () {
                            processPluginCall(this, options, args);
                        });
                    }
                }
                if (typeof result == "undefined") result = this;
                return result;
            };

            /**
             *
             * @type {Object}
             */
            $.fn[name].defaults = defaults;

            /**
             *
             * @param {Object} options
             * @returns {Object}
             */
            $.fn[name].updateDefaultsWith = function (options) {
                $.extend(true, defaults, options);
                return defaults;
            };

            /**
             *
             * @param {Object} childMembers
             * @param {boolean} [alwaysInjectSuper=false]
             * @returns {function}
             */
            $.fn[name].extendMembersWith = function (childMembers) {
                var alwaysInjectSuper = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];

                members.unshift(wrapParents(childMembers, members[0], null, alwaysInjectSuper));
            };

            /**
             * Clones a plugin added using jQuery PluginCreator.
             *
             * While the defaults and members are cloned completely and are independent, the constructor is shared.
             *
             * @param {string} name
             * @param {string} newName
             * @param {boolean} [alwaysInjectSuper=false]
             * @returns {string}
             */
            $.fn[name].cloneTo = function (newName) {
                var alwaysInjectSuper = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];

                return pluginCreator.addPlugin(newName, defaults, $.extend(true, [], members), alwaysInjectSuper);
            };

            /**
             * Extends a plugin added using jQuery PluginCreator.
             *
             * Defaults and members are cloned and extended from the original plugin while the extended constructor
             * functions such that the constructor for the original plugin is called first, then new new
             * constructor, if present.
             *
             * @param {string} name
             * @param {string} newName
             * @param {Object} [childMembers]
             * @param {boolean} [alwaysInjectSuper=false]
             * @return {string}
             */
            $.fn[name].extendTo = function (newName, childMembers) {
                var alwaysInjectSuper = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];

                $.fn[name].cloneTo(newName, alwaysInjectSuper);
                $.fn[newName].extendMembersWith(childMembers, alwaysInjectSuper);
            };

            return name;
        }
    };

    _jQuery2.default.extend(_jQuery2.default, pluginCreator);

    exports.default = pluginCreator.addPlugin;
});<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import _ from 'lodash';
import * as InsightsPropTypes from '../helpers/InsightsPropTypes';
import {toMomentFromTimestamp} from '../helpers/toMoment';
import PerDistrictContainer from '../components/PerDistrictContainer';
import LightProfileHeader from './LightProfileHeader';
import LightProfileTab, {LightShoutNumber} from './LightProfileTab';
import AttendanceDetails from './AttendanceDetails';
import ElaDetails from './ElaDetails';
import MathDetails from './MathDetails';
import LightNotesDetails from './LightNotesDetails';
import LightServiceDetails from './LightServiceDetails';
import LightNotesHelpContext from './LightNotesHelpContext';
import StudentSectionsRoster from './StudentSectionsRoster';
import {tags} from './lightTagger';

// Prototype of profile v3
export default class LightProfilePage extends React.Component {
  countEventsBetween(events, daysBack) {
    const {nowMomentFn} = this.props;
    const startMoment = nowMomentFn().startOf('day');
    const endMoment = startMoment.clone().subtract(daysBack, 'days');
    return countEventsBetween(events, startMoment, endMoment);
  }

  onColumnClicked(columnKey) {
    this.props.actions.onColumnClicked(columnKey);
  }

  render() {
    const {student, districtKey} = this.props;
    const isHighSchool = (student.school_type === 'HS');
    return (
      <PerDistrictContainer districtKey={districtKey}>

          {this.renderHeader()}

{this.renderNotesColumn()}

            {isHighSchool &&
{this.renderGradesColumn()}
}
            {isHighSchool &&
{this.renderMcasColumn()}
}
            {!isHighSchool &&
{this.renderReadingColumn()}
}
            {!isHighSchool &&
{this.renderMathColumn()}
}

{this.renderAttendanceColumn()}

{this.renderBehaviorColumn()}

            {this.renderSectionDetails()}

      </PerDistrictContainer>
    );
  }

  renderHeader() {
    const {
      student,
      districtKey,
      access,
      transitionNotes,
      educatorsIndex,
      iepDocument
    } = this.props;

    return (
      <LightProfileHeader
        student={student}
        access={access}
        iepDocument={iepDocument}
        transitionNotes={transitionNotes}
        educatorsIndex={educatorsIndex}
        districtKey={districtKey}
      />
    );
  }

  renderSectionDetails() {
    const {selectedColumnKey} = this.props;
    if (selectedColumnKey === 'notes') return this.renderNotes();
    if (selectedColumnKey === 'grades') return this.renderGrades();
    if (selectedColumnKey === 'reading') return this.renderReading();
    if (selectedColumnKey === 'math') return this.renderMath();
    if (selectedColumnKey === 'attendance') return this.renderAttendance();
    if (selectedColumnKey === 'behavior') return this.renderBehavior();
    return null;
  }

  renderNotesColumn() {
    const {feed, nowMomentFn, selectedColumnKey} = this.props;
    const columnKey = 'notes';
    const topRecentTags = findTopRecentTags(feed.event_notes, nowMomentFn());

    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#4A90E2"
        fadedColor="#ededed"
        text="Notes">
          {(topRecentTags.length === 0)
            ?
No recent keywords

            : topRecentTags.map(tag =>
“{tag}”
)}
        </LightProfileTab>
    );
  }

  renderGradesColumn() {
    const {sections, selectedColumnKey} = this.props;
    const columnKey = 'grades';
    const strugglingSectionsCount = sections.filter(section => {
      if (section.grade_numeric === null) return false;
      return (section.grade_numeric < 69);
    }).length;

    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#ff7f00"
        fadedColor="hsl(24, 100%, 92%)"
        text="Grades">
          <LightShoutNumber number={strugglingSectionsCount}>

courses with a D or F

right now

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderMcasColumn() {
    const {selectedColumnKey} = this.props;
    const columnKey = 'mcas';
    // const {nDaysText, percentileText} = latestStar(chartData.star_series_reading_percentile, nowMomentFn());
    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#6A2987"
        fadedColor="hsl(237,80%,95%)"
        text="Testing">
          <LightShoutNumber number={"NI"}>

ELA MCAS

{"?"}

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderReadingColumn() {
    const {chartData, nowMomentFn, selectedColumnKey} = this.props;
    const columnKey = 'reading';
    const {nDaysText, percentileText} = latestStar(chartData.star_series_reading_percentile, nowMomentFn());
    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#ff7f00"
        fadedColor="hsl(24, 100%, 92%)"
        text="Reading">
          <LightShoutNumber number={percentileText}>

STAR percentile

{nDaysText}

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderMathColumn() {
    const {selectedColumnKey, chartData, nowMomentFn} = this.props;
    const columnKey = 'math';
    const {nDaysText, percentileText} = latestStar(chartData.star_series_math_percentile, nowMomentFn());
    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#6A2987"
        fadedColor="hsl(237,80%,95%)"
        text="Math">
          <LightShoutNumber number={percentileText}>

STAR percentile

{nDaysText}

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderAttendanceColumn() {
    const {selectedColumnKey} = this.props;
    const columnKey = 'attendance';
    const count = this.countEventsBetween(this.props.attendanceData.absences, DAYS_AGO);
    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#c8326b"
        fadedColor="#f5e1e8"
        text="Attendance">
          <LightShoutNumber number={count}>

{count === 1 ? 'absence' : 'absences'}

last {DAYS_AGO} days

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderBehaviorColumn() {
    const {selectedColumnKey} = this.props;
    const columnKey = 'behavior';
    const count = this.countEventsBetween(this.props.attendanceData.discipline_incidents, DAYS_AGO);

    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#31AB39"
        fadedColor="hsl(120,80%,95%)"
        text="Behavior">
          <LightShoutNumber number={count}>

{count === 1 ? 'discipline incident' : 'discipline incidents'}

last {DAYS_AGO} days

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderNotes() {
    return (

        <LightNotesDetails
          student={this.props.student}
          educatorsIndex={this.props.educatorsIndex}
          currentEducator={this.props.currentEducator}
          feed={this.props.feed}
          actions={this.props.actions}
          requests={this.props.requests}
          showingRestrictedNotes={false}
          helpContent={<LightNotesHelpContext />}
          helpTitle="What is a Note?"
          title="Notes"
          noteInProgressText={this.props.noteInProgressText}
          noteInProgressType={this.props.noteInProgressType}
          noteInProgressAttachmentUrls={this.props.noteInProgressAttachmentUrls }/>
        <LightServiceDetails
          student={this.props.student}
          serviceTypesIndex={this.props.serviceTypesIndex}
          educatorsIndex={this.props.educatorsIndex}
          currentEducator={this.props.currentEducator}
          feed={this.props.feed}
          actions={this.props.actions}
          requests={this.props.requests} />

    );
  }

  renderGrades() {
    const sections = this.props.sections;

    // If there are no sections, don't generate the student sections roster
    if (!sections || sections.length == 0) return null;

    // If this student is not a high school student, don't generate the student sections roster
    if (this.props.student.school_type != 'HS') return null;

    return (

          Sections

        <StudentSectionsRoster
          sections={this.props.sections}
          linkableSections={this.props.currentEducatorAllowedSections}
          />

    );
  }

  renderReading() {
    return (
      <ElaDetails
        className="LightProfilePage-ela"
        hideNavbar={true}
        chartData={this.props.chartData}
        student={this.props.student} />
    );
  }

  renderMath() {
    return (
      <MathDetails
        className="LightProfilePage-math"
        hideNavbar={true}
        chartData={this.props.chartData}
        student={this.props.student} />
    );
  }

  renderAttendance() {
    return (
      <AttendanceDetails
        className="LightProfilePage-attendance"
        hideNavbar={true}
        disciplineIncidents={this.props.attendanceData.discipline_incidents}
        absences={this.props.attendanceData.absences}
        tardies={this.props.attendanceData.tardies}
        student={this.props.student}
        feed={this.props.feed}
        serviceTypesIndex={this.props.serviceTypesIndex} />
    );
  }

  renderBehavior() {
    return (
      <AttendanceDetails
        className="LightProfilePage-attendance"
        hideNavbar={true}
        disciplineIncidents={this.props.attendanceData.discipline_incidents}
        absences={this.props.attendanceData.absences}
        tardies={this.props.attendanceData.tardies}
        student={this.props.student}
        feed={this.props.feed}
        serviceTypesIndex={this.props.serviceTypesIndex} />
    );
  }
}
LightProfilePage.propTypes = {
  // UI
  selectedColumnKey: PropTypes.string.isRequired,

  // context
  nowMomentFn: PropTypes.func.isRequired,
  currentEducator: PropTypes.shape({
    labels: PropTypes.arrayOf(PropTypes.string).isRequired
  }).isRequired,
  districtKey: PropTypes.string.isRequired,

  // constants
  educatorsIndex: PropTypes.object.isRequired,
  serviceTypesIndex: PropTypes.object.isRequired,

  // data
  student: PropTypes.object.isRequired,
  feed: PropTypes.object.isRequired,
  transitionNotes: PropTypes.array.isRequired,
  dibels: PropTypes.array.isRequired,
  chartData: PropTypes.shape({
    // ela
    most_recent_star_reading_percentile: PropTypes.number,
    most_recent_mcas_ela_scaled: PropTypes.number,
    most_recent_mcas_ela_growth: PropTypes.number,
    star_series_reading_percentile: PropTypes.array,
    mcas_series_ela_scaled: PropTypes.array,
    mcas_series_ela_growth: PropTypes.array,
    // math
    most_recent_star_math_percentile: PropTypes.number,
    most_recent_mcas_math_scaled: PropTypes.number,
    most_recent_mcas_math_growth: PropTypes.number,
    star_series_math_percentile: PropTypes.array,
    mcas_series_math_scaled: PropTypes.array,
    mcas_series_math_growth: PropTypes.array
  }),
  attendanceData: PropTypes.shape({
    discipline_incidents: PropTypes.array,
    tardies: PropTypes.array,
    absences: PropTypes.array
  }),
  noteInProgressText: PropTypes.string.isRequired,
  noteInProgressType: PropTypes.number,
  noteInProgressAttachmentUrls: PropTypes.arrayOf(
    PropTypes.string
  ).isRequired,
  access: PropTypes.object,
  iepDocument: PropTypes.object,
  sections: PropTypes.array,
  currentEducatorAllowedSections: PropTypes.array,

  // flux-y bits
  requests: InsightsPropTypes.requests,
  actions: InsightsPropTypes.actions
};

const styles = {
  root: {
    fontSize: 14
  },
  tabsContainer: {
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'stretch',
    margin: 20,
    flex: 1,
    height: 150
  },
  tabLayout: {
    flex: 1,
    display: 'flex',
    flexDirection: 'column'
  },
  tab: {
    flex: 1,
    textAlign: 'center'
  },
  detailsContainer: {
    margin: 20
  }
};

function findTopRecentTags(eventNotes, nowMoment) {
  const thresholdInDays = 100000; // should be 45, this is for testing locally, where dates are busted
  const recentNotes = eventNotes.filter(e => nowMoment.clone().diff(toMomentFromTimestamp(e.recorded_at), 'days') < thresholdInDays);
  const recentNotesText = recentNotes.map(e => e.text).join(' ');
  const recentTags = tags(recentNotesText);
  return recentTags.slice(0, 4);
}

function countEventsBetween(events, startMoment, endMoment) {
  return events.filter(event => {
    return moment.utc(event.occurred_at).isBetween(startMoment, endMoment);
  }).length;
}

const DAYS_AGO = 45;

export function latestStar(sortedStarDataPoints, nowMoment) {
  const starDataPoint = _.last(sortedStarDataPoints);
  if (!starDataPoint) return {
    nDaysText: 'not yet taken',
    percentileText: '-'
  };

  const testMoment = toMomentFromTimestamp(starDataPoint.date_taken);
  const nDaysText = testMoment.from(nowMoment);
  const percentile = starDataPoint.percentile_rank;
  const percentileText = (percentile)
    ? percentileWithSuffix(percentile)
    : '-';
  return {nDaysText, percentileText};
}

function percentileWithSuffix(percentile) {
  const lastDigit = _.last(percentile.toString());
  const suffix = {
    1: 'st',
    2: 'nd',
    3: 'rd'
  }[lastDigit] || 'th';
  return `${percentile}${suffix}`;
}<commit_msg>Remove horizontal scrollbars from profile v3<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import _ from 'lodash';
import {updateGlobalStylesToRemoveHorizontalScrollbars} from '../helpers/globalStylingWorkarounds';
import * as InsightsPropTypes from '../helpers/InsightsPropTypes';
import {toMomentFromTimestamp} from '../helpers/toMoment';
import PerDistrictContainer from '../components/PerDistrictContainer';
import LightProfileHeader from './LightProfileHeader';
import LightProfileTab, {LightShoutNumber} from './LightProfileTab';
import AttendanceDetails from './AttendanceDetails';
import ElaDetails from './ElaDetails';
import MathDetails from './MathDetails';
import LightNotesDetails from './LightNotesDetails';
import LightServiceDetails from './LightServiceDetails';
import LightNotesHelpContext from './LightNotesHelpContext';
import StudentSectionsRoster from './StudentSectionsRoster';
import {tags} from './lightTagger';

// Prototype of profile v3
export default class LightProfilePage extends React.Component {
  componentDidMount() {
    updateGlobalStylesToRemoveHorizontalScrollbars();
  }

  countEventsBetween(events, daysBack) {
    const {nowMomentFn} = this.props;
    const startMoment = nowMomentFn().startOf('day');
    const endMoment = startMoment.clone().subtract(daysBack, 'days');
    return countEventsBetween(events, startMoment, endMoment);
  }

  onColumnClicked(columnKey) {
    this.props.actions.onColumnClicked(columnKey);
  }

  render() {
    const {student, districtKey} = this.props;
    const isHighSchool = (student.school_type === 'HS');
    return (
      <PerDistrictContainer districtKey={districtKey}>

          {this.renderHeader()}

{this.renderNotesColumn()}

            {isHighSchool &&
{this.renderGradesColumn()}
}
            {isHighSchool &&
{this.renderMcasColumn()}
}
            {!isHighSchool &&
{this.renderReadingColumn()}
}
            {!isHighSchool &&
{this.renderMathColumn()}
}

{this.renderAttendanceColumn()}

{this.renderBehaviorColumn()}

            {this.renderSectionDetails()}

      </PerDistrictContainer>
    );
  }

  renderHeader() {
    const {
      student,
      districtKey,
      access,
      transitionNotes,
      educatorsIndex,
      iepDocument
    } = this.props;

    return (
      <LightProfileHeader
        student={student}
        access={access}
        iepDocument={iepDocument}
        transitionNotes={transitionNotes}
        educatorsIndex={educatorsIndex}
        districtKey={districtKey}
      />
    );
  }

  renderSectionDetails() {
    const {selectedColumnKey} = this.props;
    if (selectedColumnKey === 'notes') return this.renderNotes();
    if (selectedColumnKey === 'grades') return this.renderGrades();
    if (selectedColumnKey === 'reading') return this.renderReading();
    if (selectedColumnKey === 'math') return this.renderMath();
    if (selectedColumnKey === 'attendance') return this.renderAttendance();
    if (selectedColumnKey === 'behavior') return this.renderBehavior();
    return null;
  }

  renderNotesColumn() {
    const {feed, nowMomentFn, selectedColumnKey} = this.props;
    const columnKey = 'notes';
    const topRecentTags = findTopRecentTags(feed.event_notes, nowMomentFn());

    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#4A90E2"
        fadedColor="#ededed"
        text="Notes">
          {(topRecentTags.length === 0)
            ?
No recent keywords

            : topRecentTags.map(tag =>
“{tag}”
)}
        </LightProfileTab>
    );
  }

  renderGradesColumn() {
    const {sections, selectedColumnKey} = this.props;
    const columnKey = 'grades';
    const strugglingSectionsCount = sections.filter(section => {
      if (section.grade_numeric === null) return false;
      return (section.grade_numeric < 69);
    }).length;

    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#ff7f00"
        fadedColor="hsl(24, 100%, 92%)"
        text="Grades">
          <LightShoutNumber number={strugglingSectionsCount}>

courses with a D or F

right now

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderMcasColumn() {
    const {selectedColumnKey} = this.props;
    const columnKey = 'mcas';
    // const {nDaysText, percentileText} = latestStar(chartData.star_series_reading_percentile, nowMomentFn());
    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#6A2987"
        fadedColor="hsl(237,80%,95%)"
        text="Testing">
          <LightShoutNumber number={"NI"}>

ELA MCAS

{"?"}

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderReadingColumn() {
    const {chartData, nowMomentFn, selectedColumnKey} = this.props;
    const columnKey = 'reading';
    const {nDaysText, percentileText} = latestStar(chartData.star_series_reading_percentile, nowMomentFn());
    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#ff7f00"
        fadedColor="hsl(24, 100%, 92%)"
        text="Reading">
          <LightShoutNumber number={percentileText}>

STAR percentile

{nDaysText}

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderMathColumn() {
    const {selectedColumnKey, chartData, nowMomentFn} = this.props;
    const columnKey = 'math';
    const {nDaysText, percentileText} = latestStar(chartData.star_series_math_percentile, nowMomentFn());
    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#6A2987"
        fadedColor="hsl(237,80%,95%)"
        text="Math">
          <LightShoutNumber number={percentileText}>

STAR percentile

{nDaysText}

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderAttendanceColumn() {
    const {selectedColumnKey} = this.props;
    const columnKey = 'attendance';
    const count = this.countEventsBetween(this.props.attendanceData.absences, DAYS_AGO);
    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#c8326b"
        fadedColor="#f5e1e8"
        text="Attendance">
          <LightShoutNumber number={count}>

{count === 1 ? 'absence' : 'absences'}

last {DAYS_AGO} days

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderBehaviorColumn() {
    const {selectedColumnKey} = this.props;
    const columnKey = 'behavior';
    const count = this.countEventsBetween(this.props.attendanceData.discipline_incidents, DAYS_AGO);

    return (
      <LightProfileTab
        style={styles.tab}
        isSelected={selectedColumnKey === columnKey}
        onClick={this.onColumnClicked.bind(this, columnKey)}
        intenseColor="#31AB39"
        fadedColor="hsl(120,80%,95%)"
        text="Behavior">
          <LightShoutNumber number={count}>

{count === 1 ? 'discipline incident' : 'discipline incidents'}

last {DAYS_AGO} days

          </LightShoutNumber>
        </LightProfileTab>
    );
  }

  renderNotes() {
    return (

        <LightNotesDetails
          student={this.props.student}
          educatorsIndex={this.props.educatorsIndex}
          currentEducator={this.props.currentEducator}
          feed={this.props.feed}
          actions={this.props.actions}
          requests={this.props.requests}
          showingRestrictedNotes={false}
          helpContent={<LightNotesHelpContext />}
          helpTitle="What is a Note?"
          title="Notes"
          noteInProgressText={this.props.noteInProgressText}
          noteInProgressType={this.props.noteInProgressType}
          noteInProgressAttachmentUrls={this.props.noteInProgressAttachmentUrls }/>
        <LightServiceDetails
          student={this.props.student}
          serviceTypesIndex={this.props.serviceTypesIndex}
          educatorsIndex={this.props.educatorsIndex}
          currentEducator={this.props.currentEducator}
          feed={this.props.feed}
          actions={this.props.actions}
          requests={this.props.requests} />

    );
  }

  renderGrades() {
    const sections = this.props.sections;

    // If there are no sections, don't generate the student sections roster
    if (!sections || sections.length == 0) return null;

    // If this student is not a high school student, don't generate the student sections roster
    if (this.props.student.school_type != 'HS') return null;

    return (

          Sections

        <StudentSectionsRoster
          sections={this.props.sections}
          linkableSections={this.props.currentEducatorAllowedSections}
          />

    );
  }

  renderReading() {
    return (
      <ElaDetails
        className="LightProfilePage-ela"
        hideNavbar={true}
        chartData={this.props.chartData}
        student={this.props.student} />
    );
  }

  renderMath() {
    return (
      <MathDetails
        className="LightProfilePage-math"
        hideNavbar={true}
        chartData={this.props.chartData}
        student={this.props.student} />
    );
  }

  renderAttendance() {
    return (
      <AttendanceDetails
        className="LightProfilePage-attendance"
        hideNavbar={true}
        disciplineIncidents={this.props.attendanceData.discipline_incidents}
        absences={this.props.attendanceData.absences}
        tardies={this.props.attendanceData.tardies}
        student={this.props.student}
        feed={this.props.feed}
        serviceTypesIndex={this.props.serviceTypesIndex} />
    );
  }

  renderBehavior() {
    return (
      <AttendanceDetails
        className="LightProfilePage-attendance"
        hideNavbar={true}
        disciplineIncidents={this.props.attendanceData.discipline_incidents}
        absences={this.props.attendanceData.absences}
        tardies={this.props.attendanceData.tardies}
        student={this.props.student}
        feed={this.props.feed}
        serviceTypesIndex={this.props.serviceTypesIndex} />
    );
  }
}
LightProfilePage.propTypes = {
  // UI
  selectedColumnKey: PropTypes.string.isRequired,

  // context
  nowMomentFn: PropTypes.func.isRequired,
  currentEducator: PropTypes.shape({
    labels: PropTypes.arrayOf(PropTypes.string).isRequired
  }).isRequired,
  districtKey: PropTypes.string.isRequired,

  // constants
  educatorsIndex: PropTypes.object.isRequired,
  serviceTypesIndex: PropTypes.object.isRequired,

  // data
  student: PropTypes.object.isRequired,
  feed: PropTypes.object.isRequired,
  transitionNotes: PropTypes.array.isRequired,
  dibels: PropTypes.array.isRequired,
  chartData: PropTypes.shape({
    // ela
    most_recent_star_reading_percentile: PropTypes.number,
    most_recent_mcas_ela_scaled: PropTypes.number,
    most_recent_mcas_ela_growth: PropTypes.number,
    star_series_reading_percentile: PropTypes.array,
    mcas_series_ela_scaled: PropTypes.array,
    mcas_series_ela_growth: PropTypes.array,
    // math
    most_recent_star_math_percentile: PropTypes.number,
    most_recent_mcas_math_scaled: PropTypes.number,
    most_recent_mcas_math_growth: PropTypes.number,
    star_series_math_percentile: PropTypes.array,
    mcas_series_math_scaled: PropTypes.array,
    mcas_series_math_growth: PropTypes.array
  }),
  attendanceData: PropTypes.shape({
    discipline_incidents: PropTypes.array,
    tardies: PropTypes.array,
    absences: PropTypes.array
  }),
  noteInProgressText: PropTypes.string.isRequired,
  noteInProgressType: PropTypes.number,
  noteInProgressAttachmentUrls: PropTypes.arrayOf(
    PropTypes.string
  ).isRequired,
  access: PropTypes.object,
  iepDocument: PropTypes.object,
  sections: PropTypes.array,
  currentEducatorAllowedSections: PropTypes.array,

  // flux-y bits
  requests: InsightsPropTypes.requests,
  actions: InsightsPropTypes.actions
};

const styles = {
  root: {
    fontSize: 14
  },
  tabsContainer: {
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'stretch',
    margin: 20,
    flex: 1,
    height: 150
  },
  tabLayout: {
    flex: 1,
    display: 'flex',
    flexDirection: 'column'
  },
  tab: {
    flex: 1,
    textAlign: 'center'
  },
  detailsContainer: {
    margin: 20
  }
};

function findTopRecentTags(eventNotes, nowMoment) {
  const thresholdInDays = 100000; // should be 45, this is for testing locally, where dates are busted
  const recentNotes = eventNotes.filter(e => nowMoment.clone().diff(toMomentFromTimestamp(e.recorded_at), 'days') < thresholdInDays);
  const recentNotesText = recentNotes.map(e => e.text).join(' ');
  const recentTags = tags(recentNotesText);
  return recentTags.slice(0, 4);
}

function countEventsBetween(events, startMoment, endMoment) {
  return events.filter(event => {
    return moment.utc(event.occurred_at).isBetween(startMoment, endMoment);
  }).length;
}

const DAYS_AGO = 45;

export function latestStar(sortedStarDataPoints, nowMoment) {
  const starDataPoint = _.last(sortedStarDataPoints);
  if (!starDataPoint) return {
    nDaysText: 'not yet taken',
    percentileText: '-'
  };

  const testMoment = toMomentFromTimestamp(starDataPoint.date_taken);
  const nDaysText = testMoment.from(nowMoment);
  const percentile = starDataPoint.percentile_rank;
  const percentileText = (percentile)
    ? percentileWithSuffix(percentile)
    : '-';
  return {nDaysText, percentileText};
}

function percentileWithSuffix(percentile) {
  const lastDigit = _.last(percentile.toString());
  const suffix = {
    1: 'st',
    2: 'nd',
    3: 'rd'
  }[lastDigit] || 'th';
  return `${percentile}${suffix}`;
}<|endoftext|>


<commit_before>define([
  'backbone',
  'handlebars',
  'moment',
  'chosen',
  'mps',
  'helpers/languagesHelper',
  'connect/views/ListItemDeleteConfirmView',
  'connect/views/ListItemDatasetsConfirmView',
  'text!connect/templates/subscriptionListItem.handlebars'
], function(
  Backbone,
  Handlebars,
  moment,
  chosen,
  mps,
  languagesHelper,
  ListItemDeleteConfirmView,
  ListItemDatasetsConfirmView,
  tpl
) {

  'use strict';

  var SubscriptionListItemView = Backbone.View.extend({

    events: {
      'click .btn-edit-name-subscription': 'onClickEditName',
      'blur  .btn-edit-name-subscription': 'onBlurEditName',
      'change #select-language-subscription': 'onChangeLanguage',
      'click .btn-delete-subscription': 'onClickDestroy',
      'click .btn-view-on-map-subscription': 'onClickViewOnMap',
      'click .btn-dataset-subscription': 'onClickDataset',
      'click .btn-dataset-remove': 'onClickDatasetRemove',
    },

    tagName: 'li',

    template: Handlebars.compile(tpl),

    initialize: function(options) {
      this.subscription = options.subscription;
      this.user = options.user;
      this.render();
    },

    render: function() {
      var languagesList = languagesHelper.getList();
      var subscription = _.extend({}, this.subscription.toJSON(), {
        confirmationUrl: this.getConfirmationURL(),
        topics: this.subscription.formattedTopics(),
        topicsDelete: (this.subscription.get('datasets').length > 1) ? true : false,
        createdAt: (!!this.subscription.get('createdAt')) ? moment(this.subscription.get('createdAt')).format('dddd, YYYY-MM-DD, h:mm a') : this.subscription.get('createdAt'),
        languages: languagesList
      });

      this.$el.html(this.template(subscription));
      this.cache();
      this.renderChosen();
      return this;
    },

    cache: function() {
      this.$selectLanguage = this.$el.find('#select-language-subscription');
    },

    renderChosen: function() {
      this.$selectLanguage
        .val(this.subscription.get('language') || 'en')
        // .chosen({
        //   width: '150px',
        //   disable_search: true,
        //   allow_single_deselect: true,
        //   inherit_select_classes: true,
        //   no_results_text: 'Oops, nothing found!'
        // });
    },

    /**
     * GETTERS
     * - getConfirmationURL
     * - getViewOnMapURL
     */
    getConfirmationURL: function() {
      var subscription = this.subscription.toJSON();
      return window.gfw.config.GFW_API_HOST_NEW_API + '/subscriptions/' + subscription.id + '/send_confirmation';
    },

    /**
     * UI EVENTS
     * - onClickEditName
     * - onBlurEditName
     * - onChangeLanguage
     * - onClickDestroy
     * - onClickViewOnMap
     * - onClickDataset
     * - onKeyUp
     */
    // Name
    onClickEditName: function(e) {
      this.user.checkLogged()
        .then(function(response) {
          var $el = $(e.currentTarget);
          if (!$el.hasClass('-editing')) {
            var value = this.subscription.get('name');

            $el.addClass('-editing').
            html('<input />').
            find('input').val(value).
            focus();

            $el.on('keyup.'+this.subscription.get('id'), this.onKeyUpEditName.bind(this));
          }
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    onBlurEditName: function(e) {
      this.user.checkLogged()
        .then(function(response) {
          var $el = $(e.currentTarget);
          if ($el.hasClass('-editing')) {
            var old_value = this.subscription.get('name'),
              new_value = $el.find('input').val();

            $el.off('keyup.'+this.subscription.get('id'));

            // Check if the value has changed before save it
            if (old_value != new_value) {
              this.subscription.save('name', new_value, {
                patch: true,
                wait: true,
                silent: true,
                success: this.resetName.bind(this),
                error: function() {
                  $el.find('input').
                  addClass('error').
                  val(old_value).
                  focus();
                }
              });
            }
          }
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    onKeyUpEditName: function(e) {
      if (e.keyCode === 13) {
        return $(e.currentTarget).blur();
      }

      if (e.keyCode === 27) {
        return this.resetName();
      }
    },

    // Language
    onChangeLanguage: function(e) {
      this.user.checkLogged()
        .then(function(response) {
          var $el = $(e.currentTarget),
            old_value = this.subscription.get('language'),
            new_value = $el.val();

          this.subscription.save('language', new_value, {
            wait: true,
            silent: true,
            patch: true,
            success: function() {
              $el.val(new_value);
              mps.publish('Notification/open', ['notification-my-gfw-subscription-correct']);
            },
            error: function() {
              $el.val(old_value);
              mps.publish('Notification/open', ['notification-my-gfw-subscription-incorrect']);
            }
          });
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    // Destroy
    onClickDestroy: function(e) {
      e.preventDefault();

      this.user.checkLogged()
        .then(function(response) {
          this.subscription.set('from', 'your profile');

          // Create and append confirm view
          var confirmView = new ListItemDeleteConfirmView({
            model: this.subscription
          });
          this.$el.append(confirmView.render().el);

          // Listen to confirmed param of confirmView
          this.listenTo(confirmView, 'confirmed', function() {
            this.subscription.destroy({
              success: this.remove.bind(this)
            });
            mps.publish('Notification/open', ['notification-my-gfw-subscription-deleted']);
            window.ga('send', 'event', 'User Profile', 'Delete Subscription');
          }.bind(this));
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    // View on map
    onClickViewOnMap: function() {
      window.ga('send', 'event', 'User Profile', 'Go to the Map');
    },

    // Datasets
    onClickDataset: function() {
      this.user.checkLogged()
        .then(function(response) {
          var confirmView = new ListItemDatasetsConfirmView({
            model: this.subscription
          });

          this.$el.append(confirmView.render().el);

          // Listen to confirmed param of confirmView
          this.listenTo(confirmView, 'confirmed', function(datasets) {
            this.saveDatasets(datasets);
          }.bind(this));
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    onClickDatasetRemove: function(e) {
      this.user.checkLogged()
        .then(function(response) {
          e && e.preventDefault();
          var datasets = _.without(this.subscription.get('datasets'),$(e.currentTarget).data('dataset'));
          this.saveDatasets(datasets);
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    /**
     * HELPERS
     * - resetName
     */
    saveDatasets: function(datasets) {
      this.user.checkLogged()
        .then(function(response) {
          this.subscription.save('datasets', datasets, {
            wait: true,
            silent: true,
            patch: true,
            success: function() {
              mps.publish('Notification/open', ['notification-my-gfw-subscription-correct']);
            },
            error: function() {
              mps.publish('Notification/open', ['notification-my-gfw-subscription-incorrect']);
            }
          });
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    resetName: function() {
      var $el = this.$('.btn-edit-name-subscription'),
          value = this.subscription.get('name');
      $el.removeClass('-editing').html(value);
    }
  });

  return SubscriptionListItemView;

});
<commit_msg>Keeping the preventDefault outside the promise<commit_after>define([
  'backbone',
  'handlebars',
  'moment',
  'chosen',
  'mps',
  'helpers/languagesHelper',
  'connect/views/ListItemDeleteConfirmView',
  'connect/views/ListItemDatasetsConfirmView',
  'text!connect/templates/subscriptionListItem.handlebars'
], function(
  Backbone,
  Handlebars,
  moment,
  chosen,
  mps,
  languagesHelper,
  ListItemDeleteConfirmView,
  ListItemDatasetsConfirmView,
  tpl
) {

  'use strict';

  var SubscriptionListItemView = Backbone.View.extend({

    events: {
      'click .btn-edit-name-subscription': 'onClickEditName',
      'blur  .btn-edit-name-subscription': 'onBlurEditName',
      'change #select-language-subscription': 'onChangeLanguage',
      'click .btn-delete-subscription': 'onClickDestroy',
      'click .btn-view-on-map-subscription': 'onClickViewOnMap',
      'click .btn-dataset-subscription': 'onClickDataset',
      'click .btn-dataset-remove': 'onClickDatasetRemove',
    },

    tagName: 'li',

    template: Handlebars.compile(tpl),

    initialize: function(options) {
      this.subscription = options.subscription;
      this.user = options.user;
      this.render();
    },

    render: function() {
      var languagesList = languagesHelper.getList();
      var subscription = _.extend({}, this.subscription.toJSON(), {
        confirmationUrl: this.getConfirmationURL(),
        topics: this.subscription.formattedTopics(),
        topicsDelete: (this.subscription.get('datasets').length > 1) ? true : false,
        createdAt: (!!this.subscription.get('createdAt')) ? moment(this.subscription.get('createdAt')).format('dddd, YYYY-MM-DD, h:mm a') : this.subscription.get('createdAt'),
        languages: languagesList
      });

      this.$el.html(this.template(subscription));
      this.cache();
      this.renderChosen();
      return this;
    },

    cache: function() {
      this.$selectLanguage = this.$el.find('#select-language-subscription');
    },

    renderChosen: function() {
      this.$selectLanguage
        .val(this.subscription.get('language') || 'en')
        // .chosen({
        //   width: '150px',
        //   disable_search: true,
        //   allow_single_deselect: true,
        //   inherit_select_classes: true,
        //   no_results_text: 'Oops, nothing found!'
        // });
    },

    /**
     * GETTERS
     * - getConfirmationURL
     * - getViewOnMapURL
     */
    getConfirmationURL: function() {
      var subscription = this.subscription.toJSON();
      return window.gfw.config.GFW_API_HOST_NEW_API + '/subscriptions/' + subscription.id + '/send_confirmation';
    },

    /**
     * UI EVENTS
     * - onClickEditName
     * - onBlurEditName
     * - onChangeLanguage
     * - onClickDestroy
     * - onClickViewOnMap
     * - onClickDataset
     * - onKeyUp
     */
    // Name
    onClickEditName: function(e) {
      this.user.checkLogged()
        .then(function(response) {
          var $el = $(e.currentTarget);
          if (!$el.hasClass('-editing')) {
            var value = this.subscription.get('name');

            $el.addClass('-editing').
            html('<input />').
            find('input').val(value).
            focus();

            $el.on('keyup.'+this.subscription.get('id'), this.onKeyUpEditName.bind(this));
          }
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    onBlurEditName: function(e) {
      this.user.checkLogged()
        .then(function(response) {
          var $el = $(e.currentTarget);
          if ($el.hasClass('-editing')) {
            var old_value = this.subscription.get('name'),
              new_value = $el.find('input').val();

            $el.off('keyup.'+this.subscription.get('id'));

            // Check if the value has changed before save it
            if (old_value != new_value) {
              this.subscription.save('name', new_value, {
                patch: true,
                wait: true,
                silent: true,
                success: this.resetName.bind(this),
                error: function() {
                  $el.find('input').
                  addClass('error').
                  val(old_value).
                  focus();
                }
              });
            }
          }
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    onKeyUpEditName: function(e) {
      if (e.keyCode === 13) {
        return $(e.currentTarget).blur();
      }

      if (e.keyCode === 27) {
        return this.resetName();
      }
    },

    // Language
    onChangeLanguage: function(e) {
      this.user.checkLogged()
        .then(function(response) {
          var $el = $(e.currentTarget),
            old_value = this.subscription.get('language'),
            new_value = $el.val();

          this.subscription.save('language', new_value, {
            wait: true,
            silent: true,
            patch: true,
            success: function() {
              $el.val(new_value);
              mps.publish('Notification/open', ['notification-my-gfw-subscription-correct']);
            },
            error: function() {
              $el.val(old_value);
              mps.publish('Notification/open', ['notification-my-gfw-subscription-incorrect']);
            }
          });
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    // Destroy
    onClickDestroy: function(e) {
      e.preventDefault();

      this.user.checkLogged()
        .then(function(response) {
          this.subscription.set('from', 'your profile');

          // Create and append confirm view
          var confirmView = new ListItemDeleteConfirmView({
            model: this.subscription
          });
          this.$el.append(confirmView.render().el);

          // Listen to confirmed param of confirmView
          this.listenTo(confirmView, 'confirmed', function() {
            this.subscription.destroy({
              success: this.remove.bind(this)
            });
            mps.publish('Notification/open', ['notification-my-gfw-subscription-deleted']);
            window.ga('send', 'event', 'User Profile', 'Delete Subscription');
          }.bind(this));
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    // View on map
    onClickViewOnMap: function() {
      window.ga('send', 'event', 'User Profile', 'Go to the Map');
    },

    // Datasets
    onClickDataset: function() {
      this.user.checkLogged()
        .then(function(response) {
          var confirmView = new ListItemDatasetsConfirmView({
            model: this.subscription
          });

          this.$el.append(confirmView.render().el);

          // Listen to confirmed param of confirmView
          this.listenTo(confirmView, 'confirmed', function(datasets) {
            this.saveDatasets(datasets);
          }.bind(this));
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    onClickDatasetRemove: function(e) {
      e && e.preventDefault();

      this.user.checkLogged()
        .then(function(response) {
          var datasets = _.without(this.subscription.get('datasets'),$(e.currentTarget).data('dataset'));
          this.saveDatasets(datasets);
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    /**
     * HELPERS
     * - resetName
     */
    saveDatasets: function(datasets) {
      this.user.checkLogged()
        .then(function(response) {
          this.subscription.save('datasets', datasets, {
            wait: true,
            silent: true,
            patch: true,
            success: function() {
              mps.publish('Notification/open', ['notification-my-gfw-subscription-correct']);
            },
            error: function() {
              mps.publish('Notification/open', ['notification-my-gfw-subscription-incorrect']);
            }
          });
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    resetName: function() {
      var $el = this.$('.btn-edit-name-subscription'),
          value = this.subscription.get('name');
      $el.removeClass('-editing').html(value);
    }
  });

  return SubscriptionListItemView;

});
<|endoftext|>


<commit_before>var strava = require('strava-v3');

var uploadSplitStuff = {
	/*
	 * Activity objects require parameters:
	 * name: 				string
	 * type: 				string, "ride" "run" "swim"
	 * start_date_local: 	ISO 8601 formatted date time
	 * elapsed_time: 		integer, seconds
	 * optional stuff...
	 */
	uploadSplitActivities : function (swim, bike, run) {
		//check objects for required parameters?
		strava.activities.create(swim, this.doneFunc);
		strava.activities.create(bike, this.doneFunc);
		strava.activities.create(run, this.doneFunc);
	}

	doneFunc : function(data) {
		console.log("successfully uploaded activity " + data.stuff);
	}
}

module.exports = uploadSplitStuff;<commit_msg>documented fully, who know's what the callback gets<commit_after>var strava = require('strava-v3');

var uploadSplitStuff = {
	/*
	 * Activity objects require parameters:
	 * name: 				string
	 * type: 				string, "ride" "run" "swim"
	 * start_date_local: 	ISO 8601 formatted date time
	 * elapsed_time: 		integer, seconds
	 * description:			string, OPTIONAL
	 * distance:  			float, meters, OPTIONAL
	 */
	uploadSplitActivities : function (swim, bike, run) {
		//check objects for required parameters?
		strava.activities.create(swim, this.doneFunc);
		strava.activities.create(bike, this.doneFunc);
		strava.activities.create(run, this.doneFunc);
	}

	doneFunc : function(data) {
		console.log("successfully uploaded activity " + data);
	}
}

module.exports = uploadSplitStuff;<|endoftext|>