<commit_before>var Api = require('./api');
var ApiError = require('./api-error');

/**
 * @param {Express.Request} req
 * @param {Object} declaredParams
 * @returns {Object}
 */
function getParamsFromRequest(req, declaredParams) {
    return Object.keys(declaredParams).reduce(function (result, key) {
        // Param would be search in the next sources:
        //   * URL path.
        //   * Query.
        //   * Request body.
        result[key] = req.param(key);
        return result;
    }, {});
}

/**
 * API middleware.
 *
 * @param {String|Api} methodPathPattern Path to api files or an instance of Api class.
 *                                       Path includes a file mask (supports minimatch).
 * @param {Object} options Extra options.
 * @param {Boolean} options.disableDocPage Turn off generating page with documentation.
 * @param {Function} options.buildMethodName Function to build a method name.
 *  Should return a string for a provided express.Request.
 */
module.exports = function (methodPathPattern, options) {
    options = options || {};
    var api = methodPathPattern instanceof Api ? methodPathPattern : new Api(methodPathPattern);

    return function (req, res) {
        var methodName = options.buildMethodName ? options.buildMethodName(req) : req.param('method');

        if (!methodName && !options.disableDocPage) {
            return api.generateHelp('html')
                .then(function (html) {
                    res.set('Content-Type', 'text/html; charset=utf-8');
                    res.end(html);
                })
                .done();
        }

        api.getMethod(methodName)
            .then(function (method) {
                var declaredParams = method.getParamsDeclarations();
                var params = getParamsFromRequest(req, declaredParams);

                return api.getMethod(methodName)
                    .then(function (method) {
                        if (method.getOption('executeOnServerOnly')) {
                            throw new ApiError('BAD_REQUEST', 'Method can be executed only on server side');
                        }

                        return api.exec(methodName, params, req);
                    });
            })
            .then(function (data) {
                res.set('Content-Type', 'application/json;charset=utf-8');
                res.end(JSON.stringify({
                    data: data
                }));
            })
            .fail(function (error) {
                res.set('Content-Type', 'application/json;charset=utf-8');
                res.end(JSON.stringify({
                    error: {
                        type: error.type || ApiError.INTERNAL_ERROR,
                        message: error.message
                    }
                }));
            });
    };
};
<commit_msg>Remove unnecessary code<commit_after>var Api = require('./api');
var ApiError = require('./api-error');

/**
 * @param {Express.Request} req
 * @param {Object} declaredParams
 * @returns {Object}
 */
function getParamsFromRequest(req, declaredParams) {
    return Object.keys(declaredParams).reduce(function (result, key) {
        // Param would be search in the next sources:
        //   * URL path.
        //   * Query.
        //   * Request body.
        result[key] = req.param(key);
        return result;
    }, {});
}

/**
 * API middleware.
 *
 * @param {String|Api} methodPathPattern Path to api files or an instance of Api class.
 *                                       Path includes a file mask (supports minimatch).
 * @param {Object} options Extra options.
 * @param {Boolean} options.disableDocPage Turn off generating page with documentation.
 * @param {Function} options.buildMethodName Function to build a method name.
 *  Should return a string for a provided express.Request.
 */
module.exports = function (methodPathPattern, options) {
    options = options || {};
    var api = methodPathPattern instanceof Api ? methodPathPattern : new Api(methodPathPattern);

    return function (req, res) {
        var methodName = options.buildMethodName ? options.buildMethodName(req) : req.param('method');

        if (!methodName && !options.disableDocPage) {
            return api.generateHelp('html')
                .then(function (html) {
                    res.set('Content-Type', 'text/html; charset=utf-8');
                    res.end(html);
                })
                .done();
        }

        api.getMethod(methodName)
            .then(function (method) {
                var declaredParams = method.getParamsDeclarations();
                var params = getParamsFromRequest(req, declaredParams);

                if (method.getOption('executeOnServerOnly')) {
                    throw new ApiError('BAD_REQUEST', 'Method can be executed only on server side');
                }

                return api.exec(methodName, params, req);
            })
            .then(function (data) {
                res.set('Content-Type', 'application/json;charset=utf-8');
                res.end(JSON.stringify({
                    data: data
                }));
            })
            .fail(function (error) {
                res.set('Content-Type', 'application/json;charset=utf-8');
                res.end(JSON.stringify({
                    error: {
                        type: error.type || ApiError.INTERNAL_ERROR,
                        message: error.message
                    }
                }));
            });
    };
};
<|endoftext|>


<commit_before>/*jshint node:true, unused:true */
"use strict";

var rateLimiter = require('./rate-limiter');

var DEFAULT_EXPIRY = 60; // 60s default expiry
var DEFAULT_LIMIT = 100; // 100 calls within the period

/**
 * Utility for creating functions from options
 */
function createConfigFunction(options, name, defaultValue) {
  var optionValue = options[name];
  if(typeof optionValue === 'function') {
    return optionValue;
  }

  if(!defaultValue) {
    throw new Error(name + ' required');
  }

  var value = name in options ? optionValue : defaultValue;

  return function() {
    return value;
  };

}

module.exports = function(options) {
  var keyFn = createConfigFunction(options, 'keyFunction', DEFAULT_EXPIRY);
  var expiryFn = createConfigFunction(options, 'expiry', DEFAULT_EXPIRY);
  var limitFn = createConfigFunction(options, 'limit', DEFAULT_LIMIT);
  var applyLimitFn = createConfigFunction(options, 'applyLimit', true);

  var limiter = rateLimiter(options);

  return function(req, res, next) {
    var applyLimit = applyLimitFn(req);
    if(!applyLimit) return next();

    var key = keyFn(req);
    var expiry = expiryFn(req);

    limiter(key, expiry, function(err, count, ttl) {
      if(err) return next(err);

      var limit = limitFn(req);

      res.setHeader('X-RateLimit-Limit', limit);
      res.setHeader('X-RateLimit-Remaining', limit - count);
      res.setHeader('X-RateLimit-Reset', Date.now() + ttl * 1000);

      if(count > limit) {
        var e = new Error("API rate limit exceeded.");
        e.statusCode = 403;
        e.rateLimit = true;

        return next(e);
      }

      next();

    });

  };
};
<commit_msg>Added additional error details<commit_after>/*jshint node:true, unused:true */
"use strict";

var rateLimiter = require('./rate-limiter');

var DEFAULT_EXPIRY = 60; // 60s default expiry
var DEFAULT_LIMIT = 100; // 100 calls within the period

/**
 * Utility for creating functions from options
 */
function createConfigFunction(options, name, defaultValue) {
  var optionValue = options[name];
  if(typeof optionValue === 'function') {
    return optionValue;
  }

  if(!defaultValue) {
    throw new Error(name + ' required');
  }

  var value = name in options ? optionValue : defaultValue;

  return function() {
    return value;
  };

}

module.exports = function(options) {
  var keyFn = createConfigFunction(options, 'keyFunction', DEFAULT_EXPIRY);
  var expiryFn = createConfigFunction(options, 'expiry', DEFAULT_EXPIRY);
  var limitFn = createConfigFunction(options, 'limit', DEFAULT_LIMIT);
  var applyLimitFn = createConfigFunction(options, 'applyLimit', true);

  var limiter = rateLimiter(options);

  return function(req, res, next) {
    var applyLimit = applyLimitFn(req);
    if(!applyLimit) return next();

    var key = keyFn(req);
    var expiry = expiryFn(req);

    limiter(key, expiry, function(err, count, ttl) {
      if(err) return next(err);

      var limit = limitFn(req);

      res.setHeader('X-RateLimit-Limit', limit);
      res.setHeader('X-RateLimit-Remaining', limit - count);
      res.setHeader('X-RateLimit-Reset', Date.now() + ttl * 1000);

      if(count > limit) {
        var e = new Error("API rate limit exceeded for key " + key + ". Will reset in " + ttl + "s");
        e.statusCode = 403;
        e.rateLimit = true;

        return next(e);
      }

      next();

    });

  };
};
<|endoftext|>


<commit_before>import { isError } from 'flux-standard-action';

function isFunction(val) {
  return typeof val === 'function';
}

export default function handleAction(type, reducers) {
  return (state, action) => {
    // If action type does not match, return previous state
    if (action.type !== type) return state;

    const handlerKey = isError(action) ? 'throw' : 'next';

    // If function is passed instead of map, use as reducer
    if (isFunction(reducers)) return reducers(state, action);

    // Otherwise, assume an action map was passed
    const reducer = reducers[handlerKey];

    return isFunction(reducer)
      ? reducer(state, action)
      : state;
  };
}
<commit_msg>set a default `throw` function<commit_after>import { isError } from 'flux-standard-action';

function isFunction(val) {
  return typeof val === 'function';
}

export default function handleAction(type, reducers) {
  return (state, action) => {
    // If action type does not match, return previous state
    if (action.type !== type) return state;

    const handlerKey = isError(action) ? 'throw' : 'next';

    // If function is passed instead of map, use as reducer
    if (isFunction(reducers)) {
      reducers.next = reducers;
    }

    // Otherwise, assume an action map was passed
    const reducer = reducers[handlerKey];

    return isFunction(reducer)
      ? reducer(state, action)
      : state;
  };
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule TransformPropTypes
 * @flow
 */
'use strict';

var ReactPropTypes = require('prop-types');

var deprecatedPropType = require('deprecatedPropType');

var TransformMatrixPropType = function(
  props : Object,
  propName : string,
  componentName : string
) : ?Error {
  if (props[propName]) {
    return new Error(
      'The transformMatrix style property is deprecated. ' +
      'Use `transform: [{ matrix: ... }]` instead.'
    );
  }
};

var DecomposedMatrixPropType = function(
  props : Object,
  propName : string,
  componentName : string
) : ?Error {
  if (props[propName]) {
    return new Error(
      'The decomposedMatrix style property is deprecated. ' +
      'Use `transform: [...]` instead.'
    );
  }
};

var TransformPropTypes = {
  transform: ReactPropTypes.arrayOf(
    ReactPropTypes.oneOfType([
      ReactPropTypes.shape({perspective: ReactPropTypes.number}),
      ReactPropTypes.shape({rotate: ReactPropTypes.string}),
      ReactPropTypes.shape({rotateX: ReactPropTypes.string}),
      ReactPropTypes.shape({rotateY: ReactPropTypes.string}),
      ReactPropTypes.shape({rotateZ: ReactPropTypes.string}),
      ReactPropTypes.shape({scale: ReactPropTypes.number}),
      ReactPropTypes.shape({scaleX: ReactPropTypes.number}),
      ReactPropTypes.shape({scaleY: ReactPropTypes.number}),
      ReactPropTypes.shape({translateX: ReactPropTypes.number}),
      ReactPropTypes.shape({translateY: ReactPropTypes.number}),
      ReactPropTypes.shape({skewX: ReactPropTypes.string}),
      ReactPropTypes.shape({skewY: ReactPropTypes.string})
    ])
  ),

  /* Deprecated */
  transformMatrix: TransformMatrixPropType,
  decomposedMatrix: DecomposedMatrixPropType,

  /* Deprecated transform props used on Android only */
  scaleX: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
  scaleY: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
  rotation: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
  translateX: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
  translateY: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
};

module.exports = TransformPropTypes;
<commit_msg>update docs for Transforms<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule TransformPropTypes
 * @flow
 */
'use strict';

var ReactPropTypes = require('prop-types');

var deprecatedPropType = require('deprecatedPropType');

var TransformMatrixPropType = function(
  props : Object,
  propName : string,
  componentName : string
) : ?Error {
  if (props[propName]) {
    return new Error(
      'The transformMatrix style property is deprecated. ' +
      'Use `transform: [{ matrix: ... }]` instead.'
    );
  }
};

var DecomposedMatrixPropType = function(
  props : Object,
  propName : string,
  componentName : string
) : ?Error {
  if (props[propName]) {
    return new Error(
      'The decomposedMatrix style property is deprecated. ' +
      'Use `transform: [...]` instead.'
    );
  }
};

var TransformPropTypes = {
  /**
   * `transform` accepts an array of transformation objects. Each object specifies
   * the property that will be transformed as the key, and the value to use in the
   * transformation. Objects should not be combined. Use a single key/value pair
   * per object.
   *
   * The rotate transformations require a string so that the transform may be
   * expressed in degrees (deg) or radians (rad). For example:
   *
   * `transform([{ rotateX: '45deg' }, { rotateZ: '0.785398rad' }])`
   *
   * The skew transformations require a string so that the transform may be
   * expressed in degrees (deg). For example:
   *
   * `transform([{ skewX: '45deg' }])`
   */
  transform: ReactPropTypes.arrayOf(
    ReactPropTypes.oneOfType([
      ReactPropTypes.shape({perspective: ReactPropTypes.number}),
      ReactPropTypes.shape({rotate: ReactPropTypes.string}),
      ReactPropTypes.shape({rotateX: ReactPropTypes.string}),
      ReactPropTypes.shape({rotateY: ReactPropTypes.string}),
      ReactPropTypes.shape({rotateZ: ReactPropTypes.string}),
      ReactPropTypes.shape({scale: ReactPropTypes.number}),
      ReactPropTypes.shape({scaleX: ReactPropTypes.number}),
      ReactPropTypes.shape({scaleY: ReactPropTypes.number}),
      ReactPropTypes.shape({translateX: ReactPropTypes.number}),
      ReactPropTypes.shape({translateY: ReactPropTypes.number}),
      ReactPropTypes.shape({skewX: ReactPropTypes.string}),
      ReactPropTypes.shape({skewY: ReactPropTypes.string})
    ])
  ),

  /**
   * Deprecated. Use the transform prop instead.
   */
  transformMatrix: TransformMatrixPropType,
  /**
   * Deprecated. Use the transform prop instead.
   */
  decomposedMatrix: DecomposedMatrixPropType,

  /* Deprecated transform props used on Android only */
  scaleX: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
  scaleY: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
  rotation: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
  translateX: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
  translateY: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
};

module.exports = TransformPropTypes;
<|endoftext|>


<commit_before>const mapItem = item => (
  item.values ? { date: item.date, weight: item.values.weight } : { date: item.date });

const fetchWeights = (context) => {
  fetch(`${process.env.API_URL}/api/weights`, { credentials: 'include' })
    .then((response) => {
      if (response.ok) {
        return response.json();
      }
      throw new Error('could not fetch weights');
    })
    .then((items) => {
      console.log(items);
      const weightList = items
        .map(item => mapItem(item));
      context.commit('setWeightList', weightList);
    })
    .catch((e) => {
      console.error(e);
    });
};

const deleteWeight = (context, date) => {
  fetch(`${process.env.API_URL}/api/weights/${date}`, { method: 'DELETE', credentials: 'include' })
    .then((response) => {
      if (response.ok) {
        fetchWeights(context);
      } else {
        throw new Error('could not delete weight');
      }
    })
    .catch((e) => {
      console.error(e);
    });
};

const addWeight = (context, { date, weight }) => {
  const body = JSON.stringify({ date, values: { weight } });

  fetch(`${process.env.API_URL}/api/weights`,
    {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body,
    })
    .then((response) => {
      if (response.ok) {
        fetchWeights(context);
      } else {
        throw new Error('could not add new record');
      }
    })
    .catch((e) => {
      console.error(e);
    });
};

export default { fetchWeights, deleteWeight, addWeight };
<commit_msg>removed unnecessary console log<commit_after>const mapItem = item => (
  item.values ? { date: item.date, weight: item.values.weight } : { date: item.date });

const fetchWeights = (context) => {
  fetch(`${process.env.API_URL}/api/weights`, { credentials: 'include' })
    .then((response) => {
      if (response.ok) {
        return response.json();
      }

      throw new Error('could not fetch weights');
    })
    .then((items) => {
      const weightList = items
        .map(item => mapItem(item));

      context.commit('setWeightList', weightList);
    })
    .catch((e) => {
      console.error(e);
    });
};

const deleteWeight = (context, date) => {
  fetch(`${process.env.API_URL}/api/weights/${date}`, { method: 'DELETE', credentials: 'include' })
    .then((response) => {
      if (response.ok) {
        fetchWeights(context);
      } else {
        throw new Error('could not delete weight');
      }
    })
    .catch((e) => {
      console.error(e);
    });
};

const addWeight = (context, { date, weight }) => {
  const body = JSON.stringify({ date, values: { weight } });

  fetch(`${process.env.API_URL}/api/weights`,
    {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body,
    })
    .then((response) => {
      if (response.ok) {
        fetchWeights(context);
      } else {
        throw new Error('could not add new record');
      }
    })
    .catch((e) => {
      console.error(e);
    });
};

export default { fetchWeights, deleteWeight, addWeight };
<|endoftext|>


<commit_before>'use strict';

const mongoose = require('mongoose');
const Promise = mongoose.Promise = require('bluebird');
const eachAsync = require('each-async');

const db = require('./common');

const exp = {};

module.exports = exp;

const User = mongoose.models.User;
const Team = mongoose.models.Team;
const TeamUser = mongoose.models.TeamUser;
const TeamEvent = mongoose.models.TeamEvent;
const Program = mongoose.models.Program;
const Event = mongoose.models.Event;

exp.getTeamDetails = function(myId, teamId) {
    return new Promise(async function (fulfill, reject) {
        console.log("GET-TEAM-DETAILS");
        try {
            const p = await exp.teamPermissions(myId, teamId);
            const t = await Team.findOneActive({_id: teamId});
            if (!t) {
                console.log('Team not found id=',teamId);
                return reject(null);
            }
            const pr = await Program.findOneActive({_id:t.programId});
            if (pr)
                t.programName = pr.name;
            else
                t.programName = "-- undefined --";
            // apply permissions
            const te = await TeamEvent.findOne({teamId:t.id, programId:pr.id});
            if (te) {
                t.registeredOn = te.registeredOn;
                const ev = await Event.findOneActive({_id:te.eventId});
                if (ev)
                    t.eventName = ev.name;
                else
                    t.eventName = "-- undefined ---";
            }
            switch (p){
                case 'P1':
                    t.billingContact = {};
                    t.shippingContact = {};
                    break;
                case 'X':
                    t.billingContact = {};
                    t.shippingContact = {};
                    t.billingAdr = {};
                    t.shippingAdr = {};
                    break;
            }

            return fulfill(t);

        } catch (err) {
            console.log('Error getting teamDetails');
            console.log(err);
            return reject(err);
        }

    });
};

exp.getTeamCoaches = function(myId, teamId) {
    return new Promise(async function (fulfill, reject) {
        console.log("GET-TEAM-COACHES");
        const coaches = [];
        try {
            const p = await exp.teamPermissions(myId, teamId);
            const t = await Team.findOneActive({_id: teamId});
            if (!t) {
                console.log('Team not found id=',teamId);
                return reject(null);
            }
            console.log("Found team: ", t.name, t.id);
            let tc = [];
            if (p != 'R1')
                tc = await TeamUser.findActive({teamId: teamId, role:'coach'});
            console.log('TEAM coaches',tc);
            //let us = await* [233,2342,234,432,423].map(id=>User.findById(id));

            eachAsync(
                tc,
                async function(item, index, done) {
                    console.log("Searching coach ",item.userId);
                    let u = await User.findOneActive({_id: item.userId});
                    let ud = {};
                    switch (p) {
                        case 'A':
                        case 'W':
                        case 'R':
                            if (u.dateOfBirth) ud.dateOfBirth = u.dateOfBirth;
                        case 'R2':
                            ud.fullName = u.fullName;
                            ud.id = u.id;
                            break;
                    }
                    coaches.push(ud);
                    done();
                },
                function(err) {
                    if (err) return reject(err);
                    console.log("Coaches loaded");
                    return fulfill(coaches);
                }
            )

        } catch (err) {
            console.log('Error getting team coaches');
            console.log(err);
            return reject(err);
        }

    });
};

exp.getTeamMembers = function(myId, teamId) {
    return new Promise(async function (fulfill, reject) {
        console.log("GET-TEAM-MEMBERS");
        const members = [];
        try {
            const p = await exp.teamPermissions(myId, teamId);
            const t = await Team.findOneActive({_id: teamId});
            if (!t) {
                console.log('Team not found id=',teamId);
                return reject(null);
            }
            console.log("Found team: ", t.name, t.id);
            let tm = [];
            if (p != 'R1')
                tm = await TeamUser.findActive({teamId: teamId, role:'member'});
            console.log('Found members',tm.length);
            eachAsync(
                tm,
                async function(item, index, done) {
                    console.log("Processing member ",item.userId);
                    let u = await User.findOneActive({_id: item.userId});
                    let ud = {};
                    switch (p) {
                        case 'A':
                        case 'W':
                        case 'R':
                            if (u.dateOfBirth) ud.dateOfBirth = u.dateOfBirth;
                            if (u.email) ud.email = u.email;
                        case 'R2':
                            ud.fullName = u.fullName;
                            ud.id = u.id;
                            break;
                    }
                    members.push(ud);
                    console.log("Adding member",ud.fullName, ud.dateOfBirth);
                    return done();
                },
                function(err) {
                    if (err) return reject(err);
                    console.log("Members loaded");
                    return fulfill(members);
                }
            )

        } catch (err) {
            console.log('Error getting team members');
            console.log(err);
            return reject(err);
        }

    });
};

exp.saveTeamDetails = function(myId, teamId, doc) {
    return new Promise(async function (fulfill, reject) {
        console.log("SAVE-TEAM-DETAILS");

        try {
            const p = await exp.teamPermissions(myId, teamId);
            const t = await Team.findOneActive({_id: teamId});
            if (!t) {
                console.log('Team not found id=',teamId);
                return reject(null);
            }
            console.log("Found team: ", t.name, t.id);

            if (p === 'A' || p === 'W') {
                console.log('Ssving team',doc);
                const res = await Team.findOneAndUpdate({_id: teamId}, doc);
                return fulfill(res);
            } else
                return reject({message:"Permission denied"});

        } catch (err) {
            console.log('Error saving team details');
            console.log(err);
            return reject(err);
        }

    });
};

exp.teamPermissions = async function(myId, teamId) {
    return (new Promise(async function (fulfill, reject) {
        //TODO: return team permissions for specified user
        /*
         A - admin
         W - write (can modify members)
         R - read (can read all data about team)
         R1 - can read only team name & team contact
         R2 - R1 + team member names (no personal details)
         X - nothing
         */
        fulfill('W');
    }));
};
<commit_msg>event info included in team details<commit_after>'use strict';

const mongoose = require('mongoose');
const Promise = mongoose.Promise = require('bluebird');
const eachAsync = require('each-async');

const db = require('./common');

const exp = {};

module.exports = exp;

const User = mongoose.models.User;
const Team = mongoose.models.Team;
const TeamUser = mongoose.models.TeamUser;
const TeamEvent = mongoose.models.TeamEvent;
const Program = mongoose.models.Program;
const Event = mongoose.models.Event;

exp.getTeamDetails = function(myId, teamId) {
    return new Promise(async function (fulfill, reject) {
        console.log("GET-TEAM-DETAILS");
        try {
            const p = await exp.teamPermissions(myId, teamId);
            const t = await Team.findOneActive({_id: teamId});
            if (!t) {
                console.log('Team not found id=',teamId);
                return reject(null);
            }
            const pr = await Program.findOneActive({_id:t.programId});
            if (pr)
                t.programName = pr.name;
            else
                t.programName = "-- undefined --";

            t.events = [];
            const evList = await TeamEvent.find({teamId:t.id});
            const evDetails = await Event.populate(evList,'eventId');
            //console.log("*** EVENT DETAILS",evDetails);
            evDetails.forEach(e => t.events.push({id:e.eventId.id, name:e.eventId.name, programId:e.programId, registeredOn:e.registeredOn}));

            //console.log("***** POPULATED TEAM",t);

            // apply permissions
            switch (p){
                case 'P1':
                    t.billingContact = {};
                    t.shippingContact = {};
                    break;
                case 'X':
                    t.billingContact = {};
                    t.shippingContact = {};
                    t.billingAdr = {};
                    t.shippingAdr = {};
                    break;
            }

            return fulfill(t);

        } catch (err) {
            console.log('Error getting teamDetails');
            console.log(err);
            return reject(err);
        }

    });
};

exp.getTeamCoaches = function(myId, teamId) {
    return new Promise(async function (fulfill, reject) {
        console.log("GET-TEAM-COACHES");
        const coaches = [];
        try {
            const p = await exp.teamPermissions(myId, teamId);
            const t = await Team.findOneActive({_id: teamId});
            if (!t) {
                console.log('Team not found id=',teamId);
                return reject(null);
            }
            console.log("Found team: ", t.name, t.id);
            let tc = [];
            if (p != 'R1')
                tc = await TeamUser.findActive({teamId: teamId, role:'coach'});
            console.log('TEAM coaches',tc);
            //let us = await* [233,2342,234,432,423].map(id=>User.findById(id));

            eachAsync(
                tc,
                async function(item, index, done) {
                    console.log("Searching coach ",item.userId);
                    let u = await User.findOneActive({_id: item.userId});
                    let ud = {};
                    switch (p) {
                        case 'A':
                        case 'W':
                        case 'R':
                            if (u.dateOfBirth) ud.dateOfBirth = u.dateOfBirth;
                        case 'R2':
                            ud.fullName = u.fullName;
                            ud.id = u.id;
                            break;
                    }
                    coaches.push(ud);
                    done();
                },
                function(err) {
                    if (err) return reject(err);
                    console.log("Coaches loaded");
                    return fulfill(coaches);
                }
            )

        } catch (err) {
            console.log('Error getting team coaches');
            console.log(err);
            return reject(err);
        }

    });
};

exp.getTeamMembers = function(myId, teamId) {
    return new Promise(async function (fulfill, reject) {
        console.log("GET-TEAM-MEMBERS");
        const members = [];
        try {
            const p = await exp.teamPermissions(myId, teamId);
            const t = await Team.findOneActive({_id: teamId});
            if (!t) {
                console.log('Team not found id=',teamId);
                return reject(null);
            }
            console.log("Found team: ", t.name, t.id);
            let tm = [];
            if (p != 'R1')
                tm = await TeamUser.findActive({teamId: teamId, role:'member'});
            console.log('Found members',tm.length);
            eachAsync(
                tm,
                async function(item, index, done) {
                    console.log("Processing member ",item.userId);
                    let u = await User.findOneActive({_id: item.userId});
                    let ud = {};
                    switch (p) {
                        case 'A':
                        case 'W':
                        case 'R':
                            if (u.dateOfBirth) ud.dateOfBirth = u.dateOfBirth;
                            if (u.email) ud.email = u.email;
                        case 'R2':
                            ud.fullName = u.fullName;
                            ud.id = u.id;
                            break;
                    }
                    members.push(ud);
                    console.log("Adding member",ud.fullName, ud.dateOfBirth);
                    return done();
                },
                function(err) {
                    if (err) return reject(err);
                    console.log("Members loaded");
                    return fulfill(members);
                }
            )

        } catch (err) {
            console.log('Error getting team members');
            console.log(err);
            return reject(err);
        }

    });
};

exp.saveTeamDetails = function(myId, teamId, doc) {
    return new Promise(async function (fulfill, reject) {
        console.log("SAVE-TEAM-DETAILS");

        try {
            const p = await exp.teamPermissions(myId, teamId);
            const t = await Team.findOneActive({_id: teamId});
            if (!t) {
                console.log('Team not found id=',teamId);
                return reject(null);
            }
            console.log("Found team: ", t.name, t.id);

            if (p === 'A' || p === 'W') {
                console.log('Ssving team',doc);
                const res = await Team.findOneAndUpdate({_id: teamId}, doc);
                return fulfill(res);
            } else
                return reject({message:"Permission denied"});

        } catch (err) {
            console.log('Error saving team details');
            console.log(err);
            return reject(err);
        }

    });
};

exp.teamPermissions = async function(myId, teamId) {
    return (new Promise(async function (fulfill, reject) {
        //TODO: return team permissions for specified user
        /*
         A - admin
         W - write (can modify members)
         R - read (can read all data about team)
         R1 - can read only team name & team contact
         R2 - R1 + team member names (no personal details)
         X - nothing
         */
        fulfill('W');
    }));
};
<|endoftext|>


<commit_before>com.sppad.BeQuiet.MediaState = new function() {

	let self = this;

	/** The handler that was playing, but was paused */
	self.pausedHandler = null;

	/** The handler that is currently playing media */
	self.playingHandler = null;

	this.pause = function() {
		let currentHandler = self.playingHandler;

   		self.pausedHandler = null;
		self.playingHandler = null;

		currentHandler.pause();

	  	self.firePauseEvent();
	};

	this.play = function() {
		let lastPlayingHandler = new com.sppad.collect.Iterable(com.sppad.BeQuiet.Main.handlers.values())
			.filter(function(a) { return a.isActive() })
			.max(function(a, b) { return a.getLastPlayTime() - b.getLastPlayTime() });

		if(lastPlayingHandler != null)
			lastPlayingHandler.play();
	};

	this.next = function() {
		if(self.playingHandler)
			self.playingHandler.next();
	};

	this.previous = function() {
		if(self.playingHandler)
			self.playingHandler.previous();
	};

	this.resume = function() {
		if(self.pausedHandler != null)
    		self.pausedHandler.play();
	};

	this.firePlayEvent = function() {
    	let evt = document.createEvent('Event');
		evt.initEvent('com_sppad_media_play', false, false);
		document.dispatchEvent(evt);
	};

	this.firePauseEvent = function() {
    	let evt = document.createEvent('Event');
		evt.initEvent('com_sppad_media_pause', false, false);
		document.dispatchEvent(evt);
	};

    this.onPause = function(aEvent) {
    	let handler = aEvent.handler;

    	// A handler has been paused due to onPlay, ignore it
    	if(handler != self.playingHandler)
			return;

       	self.playingHandler = null;
	  	self.firePauseEvent();

       	self.resume();
    };

    this.onPlay = function(aEvent) {
    	let handler = aEvent.handler;

    	if(self.playingHandler != null)
    		self.playingHandler.pause();

		self.pausedHandler = self.playingHandler;
    	self.playingHandler = handler;

      	self.firePlayEvent();
    };

	window.addEventListener("load", function() {
		document.addEventListener("com_sppad_handler_play", self.onPlay, false);
		document.addEventListener("com_sppad_handler_pause", self.onPause, false);
	});
};

<commit_msg>Fixed issue when playing a video causing a different handler to play instead.<commit_after>com.sppad.BeQuiet.MediaState = new function() {

	let self = this;

	/** The handler that was playing, but was paused */
	self.pausedHandler = null;

	/** The handler that is currently playing media */
	self.playingHandler = null;

	this.pause = function() {
		let currentHandler = self.playingHandler;
		self.playingHandler = null;

		if(currentHandler != null)
			currentHandler.pause();

   		self.pausedHandler = null;

	  	self.firePauseEvent();
	};

	this.play = function() {
		let lastPlayingHandler = new com.sppad.collect.Iterable(com.sppad.BeQuiet.Main.handlers.values())
			.filter(function(a) { return a.isActive() })
			.max(function(a, b) { return a.getLastPlayTime() - b.getLastPlayTime() });

		if(lastPlayingHandler != null)
			lastPlayingHandler.play();
	};

	this.next = function() {
		if(self.playingHandler)
			self.playingHandler.next();
	};

	this.previous = function() {
		if(self.playingHandler)
			self.playingHandler.previous();
	};

	this.resume = function() {
		if(self.pausedHandler != null)
    		self.pausedHandler.play();
	};

	this.firePlayEvent = function() {
    	let evt = document.createEvent('Event');
		evt.initEvent('com_sppad_media_play', false, false);
		document.dispatchEvent(evt);
	};

	this.firePauseEvent = function() {
    	let evt = document.createEvent('Event');
		evt.initEvent('com_sppad_media_pause', false, false);
		document.dispatchEvent(evt);
	};

    this.onPause = function(aEvent) {
    	let handler = aEvent.handler;

    	// A handler has been paused due to onPlay, ignore it
    	if(handler != self.playingHandler)
			return;

       	self.playingHandler = null;
	  	self.firePauseEvent();

       	self.resume();
    };

    this.onPlay = function(aEvent) {
    	let handler = aEvent.handler;

		let currentHandler = self.playingHandler;
		self.playingHandler = null;

    	if(currentHandler != null)
    		currentHandler.pause();

		self.pausedHandler = self.playingHandler;
    	self.playingHandler = handler;

      	self.firePlayEvent();
    };

	window.addEventListener("load", function() {
		document.addEventListener("com_sppad_handler_play", self.onPlay, false);
		document.addEventListener("com_sppad_handler_pause", self.onPause, false);
	});
};

<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 Glyptodon Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @file Overrides the default document.execCommand() implementation, providing
 * versions of the "cut", "copy", and "paste" commands which directly access
 * clipboard data if permission has been explicitly granted for the current
 * page. If permission has not been granted, or a different command is given,
 * the default document.execCommand() implementation is used.
 *
 * @author Michael Jumper
 */

// Override document.execCommand() such that clipboard data can be exposed
// without corresponding user events if permission is explicitly granted.
(function overrideExecCommand() {

    /**
     * The current contents of the local clipboard. This will be continuously
     * updated by the clipboard monitor script. If the clipboard cannot be read
     * this will be null.
     *
     * @type {String}
     */
    var clipboardContents = null;

    /**
     * The previous definition of document.execCommand(). This function will be
     * invoked when the original behavior of document.execCommand() is needed.
     *
     * @type {Function}
     */
    var _execCommand = document.execCommand;

    /**
     * Sets the value of the local clipboard to the given string.
     *
     * @param {String} value
     *     The value to assign to the local clipboard.
     */
    var setLocalClipboard = function setLocalClipboard(value) {
        document.dispatchEvent(new CustomEvent('_clip-perm-man-set-data', {
            'detail' : value
        }));
    };

    /**
     * Requests that data from the local clipboard be pulled. This pull is
     * asynchronous; data will be returned via a "_clip-perm-man-data" event
     * if reading from the clipboard is successful.
     */
    var requestLocalClipboard = function requestLocalClipboard() {
        document.dispatchEvent(new CustomEvent('_clip-perm-man-get-data'));
    };

    /**
     * Requests that the extension prompt the user to confirm whether they wish
     * to allow or deny direct clipboard access.
     */
    var confirmClipboardAccess = function confirmClipboardAccess() {
        document.dispatchEvent(new CustomEvent('_clip-perm-man-confirm'));
    };

    /**
     * Map of handlers for document.execCommand() functions, where the key of
     * each entry is the name of the command being overridden.
     *
     * @type {Object.<String, Function>}
     */
    var commandHandlers = {

        /**
         * Handler for document.execCommand('cut').
         */
        'cut' : function handleCut() {
            setLocalClipboard(window.getSelection().toString());
            _execCommand.call(this, 'insertText', false, '');
        },

        /**
         * Handler for document.execCommand('copy').
         */
        'copy' : function handleCopy() {
            setLocalClipboard(window.getSelection().toString());
        },

        /**
         * Handler for document.execCommand('paste').
         */
        'paste' : function handlePaste() {
            _execCommand.call(this, 'insertText', false, clipboardContents);
        }

    };

    /**
     * An implementation of document.execCommand() which allows access to all
     * clipboard commands as long as the user has approved this access ahead of
     * time. All other commands are passed through to the browser's own
     * document.execCommand() implementation.
     *
     * @param {String} name
     *     The name of the command to execute within the current document.
     *
     * @return {Boolean}
     *     true if the command was executed successfully, false otherwise.
     */
    document.execCommand = function clipExecCommand(name) {

        // Override behavior only if the command has a defined handler
        var commandHandler = commandHandlers[name];
        if (commandHandler) {

            // Override default document.execCommand() behavior if enabled and
            // we have successfully read from the clipboard at least once
            if (clipboardContents !== null) {

                // Prompt the user regarding whether access should continue
                // being granted/denied going forward
                confirmClipboardAccess();

                // Pass request for data through to clipboard broker
                commandHandler.apply(this);
                return true;

            }

        }

        // Otherwise pass through to original execCommand() implementation,
        // requesting confirmation if the operation failed
        var successful = _execCommand.apply(this, arguments);
        if (!successful)
            confirmClipboardAccess();

    };

    // Request an update of the local clipboard when it may have changed
    window.addEventListener('load',  requestLocalClipboard, true);
    window.addEventListener('focus', requestLocalClipboard, true);
    window.addEventListener('copy',  requestLocalClipboard, true);
    window.addEventListener('cut',   requestLocalClipboard, true);

    // When clipboard contents are received, update our internal clipboard
    // content tracking property
    document.addEventListener('_clip-perm-man-data', function handleData(e) {
        clipboardContents = e.detail;
    });

    // If clipboard access is denied, revert to default implementation
    document.addEventListener('_clip-perm-man-denied', function handleData(e) {
        clipboardContents = null;
    });

})();

<commit_msg>GUAC-1480: Only request permission if clipboard operations are used. Actually return the success/failure value.<commit_after>/*
 * Copyright (C) 2016 Glyptodon Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @file Overrides the default document.execCommand() implementation, providing
 * versions of the "cut", "copy", and "paste" commands which directly access
 * clipboard data if permission has been explicitly granted for the current
 * page. If permission has not been granted, or a different command is given,
 * the default document.execCommand() implementation is used.
 *
 * @author Michael Jumper
 */

// Override document.execCommand() such that clipboard data can be exposed
// without corresponding user events if permission is explicitly granted.
(function overrideExecCommand() {

    /**
     * The current contents of the local clipboard. This will be continuously
     * updated by the clipboard monitor script. If the clipboard cannot be read
     * this will be null.
     *
     * @type {String}
     */
    var clipboardContents = null;

    /**
     * The previous definition of document.execCommand(). This function will be
     * invoked when the original behavior of document.execCommand() is needed.
     *
     * @type {Function}
     */
    var _execCommand = document.execCommand;

    /**
     * Sets the value of the local clipboard to the given string.
     *
     * @param {String} value
     *     The value to assign to the local clipboard.
     */
    var setLocalClipboard = function setLocalClipboard(value) {
        document.dispatchEvent(new CustomEvent('_clip-perm-man-set-data', {
            'detail' : value
        }));
    };

    /**
     * Requests that data from the local clipboard be pulled. This pull is
     * asynchronous; data will be returned via a "_clip-perm-man-data" event
     * if reading from the clipboard is successful.
     */
    var requestLocalClipboard = function requestLocalClipboard() {
        document.dispatchEvent(new CustomEvent('_clip-perm-man-get-data'));
    };

    /**
     * Requests that the extension prompt the user to confirm whether they wish
     * to allow or deny direct clipboard access.
     */
    var confirmClipboardAccess = function confirmClipboardAccess() {
        document.dispatchEvent(new CustomEvent('_clip-perm-man-confirm'));
    };

    /**
     * Map of handlers for document.execCommand() functions, where the key of
     * each entry is the name of the command being overridden.
     *
     * @type {Object.<String, Function>}
     */
    var commandHandlers = {

        /**
         * Handler for document.execCommand('cut').
         */
        'cut' : function handleCut() {
            setLocalClipboard(window.getSelection().toString());
            _execCommand.call(this, 'insertText', false, '');
        },

        /**
         * Handler for document.execCommand('copy').
         */
        'copy' : function handleCopy() {
            setLocalClipboard(window.getSelection().toString());
        },

        /**
         * Handler for document.execCommand('paste').
         */
        'paste' : function handlePaste() {
            _execCommand.call(this, 'insertText', false, clipboardContents);
        }

    };

    /**
     * An implementation of document.execCommand() which allows access to all
     * clipboard commands as long as the user has approved this access ahead of
     * time. All other commands are passed through to the browser's own
     * document.execCommand() implementation.
     *
     * @param {String} name
     *     The name of the command to execute within the current document.
     *
     * @return {Boolean}
     *     true if the command was executed successfully, false otherwise.
     */
    document.execCommand = function clipExecCommand(name) {

        // Override behavior only if the command has a defined handler
        var commandHandler = commandHandlers[name];
        if (commandHandler) {

            // Override default document.execCommand() behavior if enabled and
            // we have successfully read from the clipboard at least once
            if (clipboardContents !== null) {

                // Prompt the user regarding whether access should continue
                // being granted/denied going forward
                confirmClipboardAccess();

                // Pass request for data through to clipboard broker
                commandHandler.apply(this);
                return true;

            }

        }

        // Otherwise pass through to original execCommand() implementation,
        // requesting confirmation if the operation failed
        var successful = _execCommand.apply(this, arguments);
        if (!successful && commandHandler)
            confirmClipboardAccess();

        // Return whether execCommand() was successful
        return successful;

    };

    // Request an update of the local clipboard when it may have changed
    window.addEventListener('load',  requestLocalClipboard, true);
    window.addEventListener('focus', requestLocalClipboard, true);
    window.addEventListener('copy',  requestLocalClipboard, true);
    window.addEventListener('cut',   requestLocalClipboard, true);

    // When clipboard contents are received, update our internal clipboard
    // content tracking property
    document.addEventListener('_clip-perm-man-data', function handleData(e) {
        clipboardContents = e.detail;
    });

    // If clipboard access is denied, revert to default implementation
    document.addEventListener('_clip-perm-man-denied', function handleData(e) {
        clipboardContents = null;
    });

})();

<|endoftext|>


<commit_before>$(function()
{
  var THROTTLE_TIME = 1000 / 60;
  var SOCKET_COLORS = [
    '#8C0', '#F00', '#F90', '#FFEF00',
    'violet', '#444', '#177245', '#08C', '#800020'
  ];
  var SPARE_COLOR = 'grey';
  var USER_COLOR = '#00BFFF';
  var GRID = [10, 10];
  var SELECTED_ELEMENT_CLASS = 'element-selected';

  _.noop = function() {};

  var socket = io.connect();
  var sockets = {};
  var socketCount = 0;

  var $editor = $('.editor');
  var $toolbar = $editor.find('.editor-toolbar');
  var $canvas = $editor.find('.editor-canvas');
  var $editors = $editor.find('.editor-editors');
  var $editorsCount = $editor.find('.editor-editors-count');
  var $editorsBadges = $editors.find('.editor-editors-badges');
  var $chatUsers = $editor.find('.editor-chat-users');
  var $chatMessages = $editor.find('.editor-chat-messages');
  var $chatText = $editor.find('.editor-chat-text');

  var elementTypes = {};
  var elements = {};
  var selectedElements = [];
  var connections = {};

  function renderTemplate(file, model)
  {
    return $(new EJS({url: '/templates/' + file}).render(model));
  }

  // Editors
  socket.on('screen.join', function(joinedSockets)
  {
    joinedSockets.forEach(function(joinedSocket)
    {
      if (joinedSocket.id === socket.socket.sessionid)
      {
        return;
      }

      joinedSocket.color = SOCKET_COLORS.shift() || SPARE_COLOR;

      sockets[joinedSocket.id] = joinedSocket;

      ++socketCount;

      var $badge = $(' ');

      $badge.attr({
        'data-id': joinedSocket.id,
        title: joinedSocket.name
      });

      $badge.css({
        backgroundColor: joinedSocket.color
      });

      $editorsBadges.append($badge);

      renderTemplate('editor-chat-user', joinedSocket).appendTo($chatUsers);
    });

    if (socketCount === 0)
    {
      return;
    }

    $editorsCount.text(socketCount);
    $editor.attr('data-socketCount', socketCount > 5 ? 5 : socketCount);
  });

  socket.on('screen.leave', function(sid)
  {
    if (!sockets.hasOwnProperty(sid))
    {
      return;
    }

    var leaver = sockets[sid];

    delete sockets[sid];

    if (leaver.color !== SPARE_COLOR)
    {
      SOCKET_COLORS.unshift(leaver.color);
    }

    $editorsBadges.find('.editor-editors-badge[data-id="' + sid + '"]').remove();
    $chatUsers.find('.editor-chat-user[data-id="' + sid + '"]').remove();

    --socketCount;

    $editorsCount.text(socketCount === 0 ? 'No' : socketCount);
    $editor.attr('data-socketCount', socketCount > 5 ? 5 : socketCount);
  });

  // Drag and drop
  function getPositionDifference($el, newPosition)
  {
    var position = $el.position();

    return {
      left: newPosition.left - position.left,
      top: newPosition.top - position.top
    };
  }

  function getNewPosition($el, positionDifference)
  {
    var position = $el.position();

    position.left += positionDifference.left;
    position.top += positionDifference.top;

    return position;
  }

  var emitElementDrag = _.throttle(
    socket.emit.bind(socket, 'element.drag'),
    THROTTLE_TIME
  );

  function emitDragEvent(eventName, e, ui, done)
  {
    var el = e.target;
    var $el = $(el);
    var movedElements = [];

    movedElements.push({
      id: $el.attr('data-id'),
      left: ui.position.left,
      top: ui.position.top
    });

    if (selectedElements.length < 2
      || selectedElements.indexOf(el) === -1)
    {
      if (eventName === 'element.drag')
      {
        emitElementDrag(movedElements, done);
      }
      else
      {
        socket.emit(eventName, movedElements, done);
      }

      return;
    }

    var positionDifference = getPositionDifference($el, ui.position);

    selectedElements.forEach(function(selectedElement)
    {
      if (selectedElement === el)
      {
        return;
      }

      var $selectedElement = $(selectedElement);
      var newPosition = getNewPosition($selectedElement, positionDifference);

      movedElements.push({
        id: $selectedElement.attr('data-id'),
        left: newPosition.left,
        top: newPosition.top
      });

      $selectedElement.css({
        left: newPosition.left + 'px',
        top: newPosition.top + 'px'
      });
    });

    if (eventName === 'element.drag')
    {
      emitElementDrag(movedElements, done);
    }
    else
    {
      socket.emit(eventName, movedElements, done);
    }
  }

  function setElementPositionData(movedElement)
  {
    var element = elements[movedElement.id];

    if (_.isUndefined(element))
    {
      return;
    }

    element.$.attr({
      'data-left': movedElement.left,
      'data-top': movedElement.top
    });
  }

  // TODO: z-index
  var dragOptions = {
    cursor: 'move',
    stack: '.element',
    scrollSensitivity: 100,
    grid: [1, 1],
    start: function(e, ui)
    {
      emitDragEvent('element.dragStart', e, ui);
    },
    drag: function(e, ui)
    {
      emitDragEvent('element.drag', e, ui);
    },
    stop: function(e, ui)
    {
      emitDragEvent('element.dragStop', e, ui, function(err, movedElements)
      {
        if (err)
        {
          console.error(err);
        }
        else
        {
          movedElements.forEach(setElementPositionData);
        }
      });
    }
  };

  socket.on('element.dragStart', function(sid, movedElements)
  {
    var color = _.isObject(sockets[sid]) ? sockets[sid].color : SPARE_COLOR;

    movedElements.forEach(function(movedElement)
    {
      var element = elements[movedElement.id];

      if (_.isUndefined(element))
      {
        return;
      }

      element.$
        .draggable('option', 'disabled', true)
        .addClass('element-dragged')
        .css('outline-color', color);
    });
  });

  socket.on('element.drag', function(sid, movedElements)
  {
    var color = _.isObject(sockets[sid]) ? sockets[sid].color : SPARE_COLOR;

    movedElements.forEach(function(movedElement)
    {
      var element = elements[movedElement.id];

      if (_.isUndefined(element))
      {
        return;
      }

      element.$.css({
        left: movedElement.left + 'px',
        top: movedElement.top + 'px',
        outlineColor: color
      });
    });
  });

  socket.on('element.dragStop', function(sid, movedElements)
  {
    movedElements.forEach(function(movedElement)
    {
      var element = elements[movedElement.id];

      if (_.isUndefined(element))
      {
        return;
      }

      element.$
        .removeClass('element-dragged')
        .draggable('option', 'disabled', false)
        .css({
          left: movedElement.left + 'px',
          top: movedElement.top + 'px',
          outlineColor: ''
        });
    });
  });

  // Canvas
  function addElementType(elementType)
  {
    elementTypes[elementType.id] = elementType;

    elementType.render = function(element)
    {
      element.$ = $(new EJS({url: '/templates/elementTypes/' + elementType.id}).render(element));

      element.$.attr({
        'data-id': element.id,
        'data-top': element.y,
        'data-left': element.x
      });

      element.$.css({
        top: element.y + 'px',
        left: element.x + 'px'
      });

      return element.$;
    };
  }

  function renderElements(elements)
  {
    elements.forEach(renderElement);
  }

  function renderElement(element)
  {
    if (!elementTypes.hasOwnProperty(element.type))
    {
      throw new Error("Unknown element type: " + element.type);
    }

    element.type = elementTypes[element.type];
    element.type.render(element).appendTo($canvas);

    jsPlumb.draggable(element.$, dragOptions);

    elements[element.id] = element;
  }

  //
  jsPlumb.bind('jsPlumbConnection', function(e)
  {
    console.log('jsPlumb#jsPlumbConnection', e);
  });

  jsPlumb.bind('jsPlumbConnectionDetached', function(e)
  {
    console.log('jsPlumb#jsPlumbConnectionDetached', e);
  });

  // Editor toolbar
  $toolbar.find('.editor-toolbar-snap').on('click', function()
  {
    _.defer(toggleSnapToGrid);
  });

  $toolbar.find('.editor-toolbar-grid').on('click', function()
  {
    _.defer(toggleGrid);
  });

  $toolbar.find('.editor-toolbar-grid-sizes').on('click', 'a', function(e)
  {
    e.preventDefault();

    var grid = $(this).attr('data-size').split('x').map(Number);

    if (grid[0] === 0 && grid[1] === 0)
    {
      console.log('TODO: Show custom grid size dialog');
    }
    else
    {
      GRID = grid;
    }

    toggleSnapToGrid();
    toggleGrid();
  });

  $editors.click(function()
  {
    toggleChat();
  });

  function toggleSnapToGrid()
  {
    var grid = [GRID[0], GRID[1]];

    if (!$toolbar.find('.editor-toolbar-snap').hasClass('active'))
    {
      grid[0] = grid[1] = 1;
    }

    dragOptions.grid = grid;

    $('.element').draggable('option', 'grid', grid);
  }

  function toggleGrid()
  {
    var img = 'none';

    if ($toolbar.find('.editor-toolbar-grid').hasClass('active'))
    {
      img = 'url(/grid.svg?w=' + GRID[0] + '&h=' + GRID[1] + ')';
    }

    $canvas.css('background-image', img);
  }

  // Selection management
  $(document.body).on('keydown', function(e)
  {
    if (!e.ctrlKey || e.keyCode !== 65 || e.target !== document.body)
    {
      return true;
    }

    selectedElements = $canvas
      .find('.element')
      .addClass(SELECTED_ELEMENT_CLASS)
      .get();

    return false;
  });

  $canvas.on('click', '.element', function(e)
  {
    var el = this;
    var $el = $(el);

    if ($el.hasClass(SELECTED_ELEMENT_CLASS))
    {
      if (e.ctrlKey)
      {
        $el.removeClass(SELECTED_ELEMENT_CLASS);

        selectedElements.splice(selectedElements.indexOf(el), 1);
      }
      else
      {
        $(selectedElements).not(el).removeClass(SELECTED_ELEMENT_CLASS);

        selectedElements = [el];
      }
    }
    else
    {
      $el.addClass(SELECTED_ELEMENT_CLASS);

      if (e.ctrlKey)
      {
        selectedElements.push(el);
      }
      else
      {
        $(selectedElements).removeClass(SELECTED_ELEMENT_CLASS);
        selectedElements = [el];
      }
    }

    return false;
  });

  $canvas.on('click', function()
  {
    $(selectedElements).removeClass(SELECTED_ELEMENT_CLASS);
    selectedElements = [];
  });

  $canvas.xselectable({
    filter: '.element',
    cancel: '.element',
    selectedCssClass: SELECTED_ELEMENT_CLASS,
    boxCssClass: 'editor-selection-box'
  });

  $canvas.on('xselectableselected', function(e, ui)
  {
    selectedElements.push.apply(selectedElements, ui.selected);
  });

  $canvas.on('xselectableunselected', function(e, ui)
  {
    selectedElements = _.difference(selectedElements,  ui.unselected);
  });

  // Chat
  var lastChatMessageUser;

  /**
   * @param {boolean=} state
   */
  function toggleChat(state)
  {
    if (_.isUndefined(state))
    {
      $editor.toggleClass('editor-with-chat');
    }
    else if (state)
    {
      $editor.addClass('editor-with-chat');
    }
    else
    {
      $editor.removeClass('editor-with-chat');
    }

    if ($editor.hasClass('editor-with-chat'))
    {
      $chatText.focus();
    }
  }

  function addChatMessage(data)
  {
    data.followup = lastChatMessageUser === data.user;

    lastChatMessageUser = data.user;

    var userSocket = sockets[data.user];

    if (typeof userSocket === 'undefined')
    {
      if (data.user === socket.socket.sessionid)
      {
        data.color = USER_COLOR;
        data.user = 'Me';
      }
      else
      {
        data.color = SPARE_COLOR;
        data.user = 'Guest';
      }
    }
    else
    {
      data.user = userSocket.name;
      data.color = userSocket.color;
    }

    var scrollToBottom = $chatMessages.height() + $chatMessages[0].scrollTop >= $chatMessages[0].scrollHeight;

    renderTemplate('editor-chat-message', data).appendTo($chatMessages);

    if (scrollToBottom)
    {
      $chatMessages.scrollTop($chatMessages[0].scrollHeight);
    }
  }

  $(document.body).on('keypress', function(e)
  {
    if (e.target !== document.body || e.keyCode !== 32)
    {
      return true;
    }

    toggleChat(true);

    return false;
  });

  $chatText.on('keypress', function(e)
  {
    if (e.keyCode !== 13)
    {
      return;
    }

    socket.emit('chat.message', {
      text: this.value
    });

    addChatMessage({
      user: socket.socket.sessionid,
      text: this.value
    });

    this.value = '';

    return false;
  });

  socket.on('chat.message', function(data)
  {
    if (data.user !== socket.socket.sessionid)
    {
      addChatMessage(data);
    }
  });

  // Start
  toggleSnapToGrid();
  toggleGrid();

  var loadElementTypesReq = $.ajax({
    url: '/elementTypes',
    success: function(data)
    {
      data.forEach(addElementType);
    }
  });

  var loadElementsReq = $.ajax({
    url: '/elements',
    success: function(data)
    {
      loadElementTypesReq.then(function()
      {
        renderElements(data);
      });
    }
  });
});
<commit_msg>Empty chat messages (or all whitespace) are no longer sent.<commit_after>$(function()
{
  var THROTTLE_TIME = 1000 / 60;
  var SOCKET_COLORS = [
    '#8C0', '#F00', '#F90', '#FFEF00',
    'violet', '#444', '#177245', '#08C', '#800020'
  ];
  var SPARE_COLOR = 'grey';
  var USER_COLOR = '#00BFFF';
  var GRID = [10, 10];
  var SELECTED_ELEMENT_CLASS = 'element-selected';

  _.noop = function() {};

  var socket = io.connect();
  var sockets = {};
  var socketCount = 0;

  var $editor = $('.editor');
  var $toolbar = $editor.find('.editor-toolbar');
  var $canvas = $editor.find('.editor-canvas');
  var $editors = $editor.find('.editor-editors');
  var $editorsCount = $editor.find('.editor-editors-count');
  var $editorsBadges = $editors.find('.editor-editors-badges');
  var $chatUsers = $editor.find('.editor-chat-users');
  var $chatMessages = $editor.find('.editor-chat-messages');
  var $chatText = $editor.find('.editor-chat-text');

  var elementTypes = {};
  var elements = {};
  var selectedElements = [];
  var connections = {};

  function renderTemplate(file, model)
  {
    return $(new EJS({url: '/templates/' + file}).render(model));
  }

  // Editors
  socket.on('screen.join', function(joinedSockets)
  {
    joinedSockets.forEach(function(joinedSocket)
    {
      if (joinedSocket.id === socket.socket.sessionid)
      {
        return;
      }

      joinedSocket.color = SOCKET_COLORS.shift() || SPARE_COLOR;

      sockets[joinedSocket.id] = joinedSocket;

      ++socketCount;

      var $badge = $(' ');

      $badge.attr({
        'data-id': joinedSocket.id,
        title: joinedSocket.name
      });

      $badge.css({
        backgroundColor: joinedSocket.color
      });

      $editorsBadges.append($badge);

      renderTemplate('editor-chat-user', joinedSocket).appendTo($chatUsers);
    });

    if (socketCount === 0)
    {
      return;
    }

    $editorsCount.text(socketCount);
    $editor.attr('data-socketCount', socketCount > 5 ? 5 : socketCount);
  });

  socket.on('screen.leave', function(sid)
  {
    if (!sockets.hasOwnProperty(sid))
    {
      return;
    }

    var leaver = sockets[sid];

    delete sockets[sid];

    if (leaver.color !== SPARE_COLOR)
    {
      SOCKET_COLORS.unshift(leaver.color);
    }

    $editorsBadges.find('.editor-editors-badge[data-id="' + sid + '"]').remove();
    $chatUsers.find('.editor-chat-user[data-id="' + sid + '"]').remove();

    --socketCount;

    $editorsCount.text(socketCount === 0 ? 'No' : socketCount);
    $editor.attr('data-socketCount', socketCount > 5 ? 5 : socketCount);
  });

  // Drag and drop
  function getPositionDifference($el, newPosition)
  {
    var position = $el.position();

    return {
      left: newPosition.left - position.left,
      top: newPosition.top - position.top
    };
  }

  function getNewPosition($el, positionDifference)
  {
    var position = $el.position();

    position.left += positionDifference.left;
    position.top += positionDifference.top;

    return position;
  }

  var emitElementDrag = _.throttle(
    socket.emit.bind(socket, 'element.drag'),
    THROTTLE_TIME
  );

  function emitDragEvent(eventName, e, ui, done)
  {
    var el = e.target;
    var $el = $(el);
    var movedElements = [];

    movedElements.push({
      id: $el.attr('data-id'),
      left: ui.position.left,
      top: ui.position.top
    });

    if (selectedElements.length < 2
      || selectedElements.indexOf(el) === -1)
    {
      if (eventName === 'element.drag')
      {
        emitElementDrag(movedElements, done);
      }
      else
      {
        socket.emit(eventName, movedElements, done);
      }

      return;
    }

    var positionDifference = getPositionDifference($el, ui.position);

    selectedElements.forEach(function(selectedElement)
    {
      if (selectedElement === el)
      {
        return;
      }

      var $selectedElement = $(selectedElement);
      var newPosition = getNewPosition($selectedElement, positionDifference);

      movedElements.push({
        id: $selectedElement.attr('data-id'),
        left: newPosition.left,
        top: newPosition.top
      });

      $selectedElement.css({
        left: newPosition.left + 'px',
        top: newPosition.top + 'px'
      });
    });

    if (eventName === 'element.drag')
    {
      emitElementDrag(movedElements, done);
    }
    else
    {
      socket.emit(eventName, movedElements, done);
    }
  }

  function setElementPositionData(movedElement)
  {
    var element = elements[movedElement.id];

    if (_.isUndefined(element))
    {
      return;
    }

    element.$.attr({
      'data-left': movedElement.left,
      'data-top': movedElement.top
    });
  }

  // TODO: z-index
  var dragOptions = {
    cursor: 'move',
    stack: '.element',
    scrollSensitivity: 100,
    grid: [1, 1],
    start: function(e, ui)
    {
      emitDragEvent('element.dragStart', e, ui);
    },
    drag: function(e, ui)
    {
      emitDragEvent('element.drag', e, ui);
    },
    stop: function(e, ui)
    {
      emitDragEvent('element.dragStop', e, ui, function(err, movedElements)
      {
        if (err)
        {
          console.error(err);
        }
        else
        {
          movedElements.forEach(setElementPositionData);
        }
      });
    }
  };

  socket.on('element.dragStart', function(sid, movedElements)
  {
    var color = _.isObject(sockets[sid]) ? sockets[sid].color : SPARE_COLOR;

    movedElements.forEach(function(movedElement)
    {
      var element = elements[movedElement.id];

      if (_.isUndefined(element))
      {
        return;
      }

      element.$
        .draggable('option', 'disabled', true)
        .addClass('element-dragged')
        .css('outline-color', color);
    });
  });

  socket.on('element.drag', function(sid, movedElements)
  {
    var color = _.isObject(sockets[sid]) ? sockets[sid].color : SPARE_COLOR;

    movedElements.forEach(function(movedElement)
    {
      var element = elements[movedElement.id];

      if (_.isUndefined(element))
      {
        return;
      }

      element.$.css({
        left: movedElement.left + 'px',
        top: movedElement.top + 'px',
        outlineColor: color
      });
    });
  });

  socket.on('element.dragStop', function(sid, movedElements)
  {
    movedElements.forEach(function(movedElement)
    {
      var element = elements[movedElement.id];

      if (_.isUndefined(element))
      {
        return;
      }

      element.$
        .removeClass('element-dragged')
        .draggable('option', 'disabled', false)
        .css({
          left: movedElement.left + 'px',
          top: movedElement.top + 'px',
          outlineColor: ''
        });
    });
  });

  // Canvas
  function addElementType(elementType)
  {
    elementTypes[elementType.id] = elementType;

    elementType.render = function(element)
    {
      element.$ = $(new EJS({url: '/templates/elementTypes/' + elementType.id}).render(element));

      element.$.attr({
        'data-id': element.id,
        'data-top': element.y,
        'data-left': element.x
      });

      element.$.css({
        top: element.y + 'px',
        left: element.x + 'px'
      });

      return element.$;
    };
  }

  function renderElements(elements)
  {
    elements.forEach(renderElement);
  }

  function renderElement(element)
  {
    if (!elementTypes.hasOwnProperty(element.type))
    {
      throw new Error("Unknown element type: " + element.type);
    }

    element.type = elementTypes[element.type];
    element.type.render(element).appendTo($canvas);

    jsPlumb.draggable(element.$, dragOptions);

    elements[element.id] = element;
  }

  //
  jsPlumb.bind('jsPlumbConnection', function(e)
  {
    console.log('jsPlumb#jsPlumbConnection', e);
  });

  jsPlumb.bind('jsPlumbConnectionDetached', function(e)
  {
    console.log('jsPlumb#jsPlumbConnectionDetached', e);
  });

  // Editor toolbar
  $toolbar.find('.editor-toolbar-snap').on('click', function()
  {
    _.defer(toggleSnapToGrid);
  });

  $toolbar.find('.editor-toolbar-grid').on('click', function()
  {
    _.defer(toggleGrid);
  });

  $toolbar.find('.editor-toolbar-grid-sizes').on('click', 'a', function(e)
  {
    e.preventDefault();

    var grid = $(this).attr('data-size').split('x').map(Number);

    if (grid[0] === 0 && grid[1] === 0)
    {
      console.log('TODO: Show custom grid size dialog');
    }
    else
    {
      GRID = grid;
    }

    toggleSnapToGrid();
    toggleGrid();
  });

  $editors.click(function()
  {
    toggleChat();
  });

  function toggleSnapToGrid()
  {
    var grid = [GRID[0], GRID[1]];

    if (!$toolbar.find('.editor-toolbar-snap').hasClass('active'))
    {
      grid[0] = grid[1] = 1;
    }

    dragOptions.grid = grid;

    $('.element').draggable('option', 'grid', grid);
  }

  function toggleGrid()
  {
    var img = 'none';

    if ($toolbar.find('.editor-toolbar-grid').hasClass('active'))
    {
      img = 'url(/grid.svg?w=' + GRID[0] + '&h=' + GRID[1] + ')';
    }

    $canvas.css('background-image', img);
  }

  // Selection management
  $(document.body).on('keydown', function(e)
  {
    if (!e.ctrlKey || e.keyCode !== 65 || e.target !== document.body)
    {
      return true;
    }

    selectedElements = $canvas
      .find('.element')
      .addClass(SELECTED_ELEMENT_CLASS)
      .get();

    return false;
  });

  $canvas.on('click', '.element', function(e)
  {
    var el = this;
    var $el = $(el);

    if ($el.hasClass(SELECTED_ELEMENT_CLASS))
    {
      if (e.ctrlKey)
      {
        $el.removeClass(SELECTED_ELEMENT_CLASS);

        selectedElements.splice(selectedElements.indexOf(el), 1);
      }
      else
      {
        $(selectedElements).not(el).removeClass(SELECTED_ELEMENT_CLASS);

        selectedElements = [el];
      }
    }
    else
    {
      $el.addClass(SELECTED_ELEMENT_CLASS);

      if (e.ctrlKey)
      {
        selectedElements.push(el);
      }
      else
      {
        $(selectedElements).removeClass(SELECTED_ELEMENT_CLASS);
        selectedElements = [el];
      }
    }

    return false;
  });

  $canvas.on('click', function()
  {
    $(selectedElements).removeClass(SELECTED_ELEMENT_CLASS);
    selectedElements = [];
  });

  $canvas.xselectable({
    filter: '.element',
    cancel: '.element',
    selectedCssClass: SELECTED_ELEMENT_CLASS,
    boxCssClass: 'editor-selection-box'
  });

  $canvas.on('xselectableselected', function(e, ui)
  {
    selectedElements.push.apply(selectedElements, ui.selected);
  });

  $canvas.on('xselectableunselected', function(e, ui)
  {
    selectedElements = _.difference(selectedElements,  ui.unselected);
  });

  // Chat
  var lastChatMessageUser;

  /**
   * @param {boolean=} state
   */
  function toggleChat(state)
  {
    if (_.isUndefined(state))
    {
      $editor.toggleClass('editor-with-chat');
    }
    else if (state)
    {
      $editor.addClass('editor-with-chat');
    }
    else
    {
      $editor.removeClass('editor-with-chat');
    }

    if ($editor.hasClass('editor-with-chat'))
    {
      $chatText.focus();
    }
  }

  function addChatMessage(data)
  {
    data.followup = lastChatMessageUser === data.user;

    lastChatMessageUser = data.user;

    var userSocket = sockets[data.user];

    if (typeof userSocket === 'undefined')
    {
      if (data.user === socket.socket.sessionid)
      {
        data.color = USER_COLOR;
        data.user = 'Me';
      }
      else
      {
        data.color = SPARE_COLOR;
        data.user = 'Guest';
      }
    }
    else
    {
      data.user = userSocket.name;
      data.color = userSocket.color;
    }

    var scrollToBottom = $chatMessages.height() + $chatMessages[0].scrollTop >= $chatMessages[0].scrollHeight;

    renderTemplate('editor-chat-message', data).appendTo($chatMessages);

    if (scrollToBottom)
    {
      $chatMessages.scrollTop($chatMessages[0].scrollHeight);
    }
  }

  $(document.body).on('keypress', function(e)
  {
    if (e.target !== document.body || e.keyCode !== 32)
    {
      return true;
    }

    toggleChat(true);

    return false;
  });

  $chatText.on('keypress', function(e)
  {
    if (e.keyCode !== 13)
    {
      return;
    }

    var text = this.value.trim();

    if (text.length > 0)
    {
      socket.emit('chat.message', {
        text: text
      });

      addChatMessage({
        user: socket.socket.sessionid,
        text: text
      });
    }

    this.value = '';

    return false;
  });

  socket.on('chat.message', function(data)
  {
    if (data.user !== socket.socket.sessionid)
    {
      addChatMessage(data);
    }
  });

  // Start
  toggleSnapToGrid();
  toggleGrid();

  var loadElementTypesReq = $.ajax({
    url: '/elementTypes',
    success: function(data)
    {
      data.forEach(addElementType);
    }
  });

  var loadElementsReq = $.ajax({
    url: '/elements',
    success: function(data)
    {
      loadElementTypesReq.then(function()
      {
        renderElements(data);
      });
    }
  });
});
<|endoftext|>


<commit_before>/* eslint-env node, jest */

jest.mock('../achievements');

const sushi = require('./index.js');
const Slack = require('../lib/slackMock.js');

let slack = null;

beforeEach(() => {
	slack = new Slack();
	sushi(slack);
});

it('reacts to "おすし"', () => new Promise((resolve) => {
	slack.on('reactions.add', ({name, channel, timestamp}) => {
		expect(name).toBe('sushi');
		expect(channel).toBe(slack.fakeChannel);
		expect(timestamp).toBe(slack.fakeTimestamp);
		resolve();
	});

	slack.rtmClient.emit('message', {
		channel: slack.fakeChannel,
		text: 'おすし',
		user: slack.fakeUser,
		ts: slack.fakeTimestamp,
	});
}));

it('reacts to ":korosuzo:"', () => new Promise((resolve) => {
	const table = {no_good: false, cookies146: false};
	slack.on('reactions.add', ({name, channel, timestamp}) => {
		expect(Object.keys(table)).toContain(name); // FIXME
		expect(channel).toBe(slack.fakeChannel);
		expect(timestamp).toBe(slack.fakeTimestamp);
		table[name] = true;
		if(Object.values(table).every(x=>x)) {
			resolve();
		}
	});

	slack.rtmClient.emit('message', {
		channel: slack.fakeChannel,
		text: ':korosuzo:',
		user: slack.fakeUser,
		ts: slack.fakeTimestamp,
	});
}));

it('marks :x::four: to "sushi"x4', () => new Promise((resolve) => {
	const table = {sushi: false, x: false, four: false};
	slack.on('reactions.add', ({name, channel, timestamp}) => {
		expect(Object.keys(table)).toContain(name); // FIXME
		expect(channel).toBe(slack.fakeChannel);
		expect(timestamp).toBe(slack.fakeTimestamp);
		table[name] = true;
		if(Object.values(table).every(x=>x)) {
			resolve();
		}
	});

	slack.rtmClient.emit('message', {
		channel: slack.fakeChannel,
		text: 'sushisushisushisushi',
		user: slack.fakeUser,
		ts: slack.fakeTimestamp,
	});
}));

it('reacts to "\u3059\u0328"', () => new Promise((resolve) => {
	slack.on('reactions.add', ({name, channel, timestamp}) => {
		expect(name).toBe('sushi');
		expect(channel).toBe(slack.fakeChannel);
		expect(timestamp).toBe(slack.fakeTimestamp);
		resolve();
	});

	slack.rtmClient.emit('message', {
		channel: slack.fakeChannel,
		text: '\u3059\u0328',
		user: slack.fakeUser,
		ts: slack.fakeTimestamp,
	});
}));

it('reacts to "寿司ランキング 確認"', () => new Promise((resolve) => {
	slack.on('chat.postMessage', ({username, channel, text}) => {
		expect(username).toBe('sushi-bot');
		expect(channel).toBe("D00000000");
		expect(text).toContain('あなたのすし数は1個');
		expect(text).toContain('現在の順位は');
		resolve();
	});

	(async () => {
		const promise = new Promise(resolve => {
			slack.on('reactions.add', ({name, channel}) => {
				if (name === 'sushi' && channel === 'D00000000') {
					resolve();
				}
			});
		});

		slack.rtmClient.emit('message', {
			channel: "D00000000",
			text: 'sushi',
			user: slack.fakeUser,
			ts: slack.fakeTimestamp,
		});

		await promise;

		slack.rtmClient.emit('message', {
			channel: "D00000000",
			text: '寿司ランキング 確認',
			user: slack.fakeUser,
			ts: slack.fakeTimestamp,
		});
	})();
}));

<commit_msg>Add a test for '凍結ランキング 確認'<commit_after>/* eslint-env node, jest */

jest.mock('../achievements');

const sushi = require('./index.js');
const Slack = require('../lib/slackMock.js');

let slack = null;

beforeEach(() => {
	slack = new Slack();
	sushi(slack);
});

it('reacts to "おすし"', () => new Promise((resolve) => {
	slack.on('reactions.add', ({name, channel, timestamp}) => {
		expect(name).toBe('sushi');
		expect(channel).toBe(slack.fakeChannel);
		expect(timestamp).toBe(slack.fakeTimestamp);
		resolve();
	});

	slack.rtmClient.emit('message', {
		channel: slack.fakeChannel,
		text: 'おすし',
		user: slack.fakeUser,
		ts: slack.fakeTimestamp,
	});
}));

it('reacts to ":korosuzo:"', () => new Promise((resolve) => {
	const table = {no_good: false, cookies146: false};
	slack.on('reactions.add', ({name, channel, timestamp}) => {
		expect(Object.keys(table)).toContain(name); // FIXME
		expect(channel).toBe(slack.fakeChannel);
		expect(timestamp).toBe(slack.fakeTimestamp);
		table[name] = true;
		if(Object.values(table).every(x=>x)) {
			resolve();
		}
	});

	slack.rtmClient.emit('message', {
		channel: slack.fakeChannel,
		text: ':korosuzo:',
		user: slack.fakeUser,
		ts: slack.fakeTimestamp,
	});
}));

it('marks :x::four: to "sushi"x4', () => new Promise((resolve) => {
	const table = {sushi: false, x: false, four: false};
	slack.on('reactions.add', ({name, channel, timestamp}) => {
		expect(Object.keys(table)).toContain(name); // FIXME
		expect(channel).toBe(slack.fakeChannel);
		expect(timestamp).toBe(slack.fakeTimestamp);
		table[name] = true;
		if(Object.values(table).every(x=>x)) {
			resolve();
		}
	});

	slack.rtmClient.emit('message', {
		channel: slack.fakeChannel,
		text: 'sushisushisushisushi',
		user: slack.fakeUser,
		ts: slack.fakeTimestamp,
	});
}));

it('reacts to "\u3059\u0328"', () => new Promise((resolve) => {
	slack.on('reactions.add', ({name, channel, timestamp}) => {
		expect(name).toBe('sushi');
		expect(channel).toBe(slack.fakeChannel);
		expect(timestamp).toBe(slack.fakeTimestamp);
		resolve();
	});

	slack.rtmClient.emit('message', {
		channel: slack.fakeChannel,
		text: '\u3059\u0328',
		user: slack.fakeUser,
		ts: slack.fakeTimestamp,
	});
}));

it('reacts to "寿司ランキング 確認"', () => new Promise((resolve) => {
	slack.on('chat.postMessage', ({username, channel, text}) => {
		expect(username).toBe('sushi-bot');
		expect(channel).toBe("D00000000");
		expect(text).toContain('あなたのすし数は1個');
		expect(text).toContain('現在の順位は');
		resolve();
	});

	(async () => {
		const promise = new Promise(resolve => {
			slack.on('reactions.add', ({name, channel}) => {
				if (name === 'sushi' && channel === 'D00000000') {
					resolve();
				}
			});
		});

		slack.rtmClient.emit('message', {
			channel: "D00000000",
			text: 'sushi',
			user: slack.fakeUser,
			ts: slack.fakeTimestamp,
		});

		await promise;

		slack.rtmClient.emit('message', {
			channel: "D00000000",
			text: '寿司ランキング 確認',
			user: slack.fakeUser,
			ts: slack.fakeTimestamp,
		});
	})();
}));

it('reacts to "凍結ランキング 確認"', () => new Promise((resolve) => {
	slack.on('chat.postMessage', ({username, channel, text}) => {
		expect(username).toBe('sushi-bot');
		expect(channel).toBe("D00000000");
		expect(text).toContain('あなたの凍結回数は1回');
		expect(text).toContain('現在の順位は');
		resolve();
	});

	(async () => {
		const promise = new Promise(resolve => {
			slack.on('reactions.add', ({name, channel}) => {
				if (name === 'no_good' && channel === 'D00000000') {
					resolve();
				}
			});
		});

		slack.rtmClient.emit('message', {
			channel: "D00000000",
			text: '死',
			user: slack.fakeUser,
			ts: slack.fakeTimestamp,
		});

		await promise;

		slack.rtmClient.emit('message', {
			channel: "D00000000",
			text: '凍結ランキング 確認',
			user: slack.fakeUser,
			ts: slack.fakeTimestamp,
		});
	})();
}));

<|endoftext|>


<commit_before>import Ember from 'ember';
import AjaxPromise from '../utils/ajax-promise';
import config from '../config/environment';
import preloadDataMixin from '../mixins/preload_data';
const { getOwner } = Ember;

export default Ember.Route.extend(preloadDataMixin, {
  cordova: Ember.inject.service(),
  i18n: Ember.inject.service(),
  isErrPopUpAlreadyShown: false,
  isOfflineErrAlreadyShown: false,

  _loadDataStore: function(){
    return this.preloadData(true).catch(error => {
      if (error.status === 0 || (error.errors && error.errors[0].status === "0")) {
        this.transitionTo("offline");
      } else {
        this.handleError(error);
      }
    }).finally(() => {
      // don't know why but placing this before preloadData on iPhone 6 causes register_device request to fail with status 0
      if (this.session.get('isLoggedIn')) {
        this.get("cordova").appLoad();
      }
    });
  },

  beforeModel(transition = []) {

    window.addEventListener("storage", function() {
      storageHandler(_this);
    }, false);

    var _this = this;
    var storageHandler = function (object) {
      if(!window.localStorage.getItem('authToken')) {
        object.get('messageBox').alert(object.get("i18n").t('must_login'), () => {
          var loginController = this.controllerFor('login');
          loginController.set('attemptedTransition', transition);
          this.transitionTo('login');
          return false;
        });
      }
    };

    try {
      window.localStorage.test = "isSafariPrivateBrowser";
    } catch (e) {
      this.get("messageBox").alert(this.get("i18n").t("QuotaExceededError"));
    }
    window.localStorage.removeItem('test');
    if (transition.queryParams.ln) {
      var language = transition.queryParams.ln === "zh-tw" ? "zh-tw" : "en";
      this.set('session.language', language);
    }

    var language = this.session.get("language") || "en";
    moment.locale(language);
    this.set("i18n.locale", language);

    Ember.onerror = window.onerror = error => this.handleError(error);
    return this._loadDataStore();
  },

  afterModel() {
    if(this.get("session.isAdminApp")) {
      this.loadStaticData(true).catch(error => {
        if (error.status === 0 || (error.errors && error.errors[0].status === "0")) {
          this.transitionTo("offline");
        } else {
          this.handleError(error);
        }
      });
    }
  },

  renderTemplate() {
    this.render(); // default template

    this.render('notifications', {   // the template to render
      into: 'application',      // the template to render into
      outlet: 'notifications', // the name of the outlet in that template
      controller: 'notifications'   // the controller to use for the template
    });

    if(this.get("session.isAdminApp")){
      this.render('notification_link', {
        into: 'application',
        outlet: 'notification_link',
        controller: 'notification_link'
      });

      this.render('internet_call_status', {
        into: 'application',
        outlet: 'internet_call_status',
        controller: 'internet_call_status'
      });
    }
  },

  logger: Ember.inject.service(),
  messageBox: Ember.inject.service(),

  offlineError(reason){
    if(!this.get('isOfflineErrAlreadyShown')) {
      this.set('isOfflineErrAlreadyShown', true);
      this.get("messageBox").alert(this.get("i18n").t("offline_error"), () => {
        this.set('isOfflineErrAlreadyShown', false);
      });
      if(!reason.isAdapterError){
        this.get("logger").error(reason);
      }
    }
  },

  quotaExceededError(reason){
    this.get("logger").error(reason);
    this.get("messageBox").alert(this.get("i18n").t("QuotaExceededError"));
  },

  handleError: function(reason) {
    try
    {
      var status;
      try { status = parseInt(reason.errors[0].status, 10); }
      catch (err) { status = reason.status; }

      if(!window.navigator.onLine){
        this.offlineError(reason);
      } else if(reason.name === "QuotaExceededError") {
        this.quotaExceededError(reason);
      } else if (status === 401) {
        if (this.session.get('isLoggedIn')) {
          this.controllerFor("application").send('logMeOut');
        }
      } else if ([403, 404].indexOf(status) >= 0) {
        this.get("logger").error(reason);
        this.get("messageBox").alert(this.get("i18n").t(status+"_error"));
      } else if (status === 0) {
        // status 0 means request was aborted, this could be due to connection failure
        // but can also mean request was manually cancelled
        this.get("messageBox").alert(this.get("i18n").t("offline_error"));
      } else {
        this.get("logger").error(reason);
        if(!this.get('isErrPopUpAlreadyShown')) {
          this.set('isErrPopUpAlreadyShown', true);
          this.get("messageBox").alert(this.get("i18n").t("unexpected_error"), () => {
            this.set('isErrPopUpAlreadyShown', false);
          });
        }
      }
    } catch (err) {}
  },

  actions: {
    setLang(language) {
      this.session.set("language", language);
      window.location.reload();
    },
    loading() {
      Ember.$(".loading-indicator").remove();
      var view = getOwner(this).lookup('component:loading').append();
      this.router.one('didTransition', view, 'destroy');
    },
    // this is hopefully only triggered from promises from routes
    // so in this scenario redirect to home for 404
    error(reason) {
      try {
        var errorStatus = parseInt(reason.status || reason.errors && reason.errors[0].status, 10)
        if ([403, 404].indexOf(errorStatus) >= 0) {
          this.get("messageBox").alert(this.get("i18n").t(errorStatus+"_error"), () => this.transitionTo("/"));
        } else {
          this.handleError(reason);
        }
      } catch (err) {}
    },

    willTransition(transition) {
      Ember.run.next(function() {
        // before transitioning close all foundation-dialog box
        Ember.$(".reveal-modal").foundation("reveal", "close");

        // remove joyride-popup if not assigned for page
        if($(".joyride-list").length === 0) {
          Ember.$('.joyride-tip-guide').remove();
        }
      });
    }
  }
});
<commit_msg>GCW-1679 Added reload if no authToken present<commit_after>import Ember from 'ember';
import AjaxPromise from '../utils/ajax-promise';
import config from '../config/environment';
import preloadDataMixin from '../mixins/preload_data';
const { getOwner } = Ember;

export default Ember.Route.extend(preloadDataMixin, {
  cordova: Ember.inject.service(),
  i18n: Ember.inject.service(),
  isErrPopUpAlreadyShown: false,
  isOfflineErrAlreadyShown: false,
  logger: Ember.inject.service(),
  messageBox: Ember.inject.service(),

  _loadDataStore: function(){
    return this.preloadData(true).catch(error => {
      if (error.status === 0 || (error.errors && error.errors[0].status === "0")) {
        this.transitionTo("offline");
      } else {
        this.handleError(error);
      }
    }).finally(() => {
      // don't know why but placing this before preloadData on iPhone 6 causes register_device request to fail with status 0
      if (this.session.get('isLoggedIn')) {
        this.get("cordova").appLoad();
      }
    });
  },

  init() {
    var _this = this;
    var storageHandler = function (object) {
      if(!window.localStorage.getItem('authToken')) {
        object.get('messageBox').alert(object.get("i18n").t('must_login'), () => {
          window.location.reload();
        });
      }
    };
    window.addEventListener("storage", function() {
      storageHandler(_this);
    }, false);
  },

  beforeModel(transition = []) {
    try {
      window.localStorage.test = "isSafariPrivateBrowser";
    } catch (e) {
      this.get("messageBox").alert(this.get("i18n").t("QuotaExceededError"));
    }
    window.localStorage.removeItem('test');
    if (transition.queryParams.ln) {
      var language = transition.queryParams.ln === "zh-tw" ? "zh-tw" : "en";
      this.set('session.language', language);
    }

    var language = this.session.get("language") || "en";
    moment.locale(language);
    this.set("i18n.locale", language);

    Ember.onerror = window.onerror = error => this.handleError(error);
    return this._loadDataStore();
  },

  afterModel() {
    if(this.get("session.isAdminApp")) {
      this.loadStaticData(true).catch(error => {
        if (error.status === 0 || (error.errors && error.errors[0].status === "0")) {
          this.transitionTo("offline");
        } else {
          this.handleError(error);
        }
      });
    }
  },

  renderTemplate() {
    this.render(); // default template

    this.render('notifications', {   // the template to render
      into: 'application',      // the template to render into
      outlet: 'notifications', // the name of the outlet in that template
      controller: 'notifications'   // the controller to use for the template
    });

    if(this.get("session.isAdminApp")){
      this.render('notification_link', {
        into: 'application',
        outlet: 'notification_link',
        controller: 'notification_link'
      });

      this.render('internet_call_status', {
        into: 'application',
        outlet: 'internet_call_status',
        controller: 'internet_call_status'
      });
    }
  },

  offlineError(reason){
    if(!this.get('isOfflineErrAlreadyShown')) {
      this.set('isOfflineErrAlreadyShown', true);
      this.get("messageBox").alert(this.get("i18n").t("offline_error"), () => {
        this.set('isOfflineErrAlreadyShown', false);
      });
      if(!reason.isAdapterError){
        this.get("logger").error(reason);
      }
    }
  },

  quotaExceededError(reason){
    this.get("logger").error(reason);
    this.get("messageBox").alert(this.get("i18n").t("QuotaExceededError"));
  },

  handleError: function(reason) {
    try
    {
      var status;
      try { status = parseInt(reason.errors[0].status, 10); }
      catch (err) { status = reason.status; }

      if(!window.navigator.onLine){
        this.offlineError(reason);
      } else if(reason.name === "QuotaExceededError") {
        this.quotaExceededError(reason);
      } else if (status === 401) {
        if (this.session.get('isLoggedIn')) {
          this.controllerFor("application").send('logMeOut');
        }
      } else if ([403, 404].indexOf(status) >= 0) {
        this.get("logger").error(reason);
        this.get("messageBox").alert(this.get("i18n").t(status+"_error"));
      } else if (status === 0) {
        // status 0 means request was aborted, this could be due to connection failure
        // but can also mean request was manually cancelled
        this.get("messageBox").alert(this.get("i18n").t("offline_error"));
      } else {
        this.get("logger").error(reason);
        if(!this.get('isErrPopUpAlreadyShown')) {
          this.set('isErrPopUpAlreadyShown', true);
          this.get("messageBox").alert(this.get("i18n").t("unexpected_error"), () => {
            this.set('isErrPopUpAlreadyShown', false);
          });
        }
      }
    } catch (err) {}
  },

  actions: {
    setLang(language) {
      this.session.set("language", language);
      window.location.reload();
    },
    loading() {
      Ember.$(".loading-indicator").remove();
      var view = getOwner(this).lookup('component:loading').append();
      this.router.one('didTransition', view, 'destroy');
    },
    // this is hopefully only triggered from promises from routes
    // so in this scenario redirect to home for 404
    error(reason) {
      try {
        var errorStatus = parseInt(reason.status || reason.errors && reason.errors[0].status, 10)
        if ([403, 404].indexOf(errorStatus) >= 0) {
          this.get("messageBox").alert(this.get("i18n").t(errorStatus+"_error"), () => this.transitionTo("/"));
        } else {
          this.handleError(reason);
        }
      } catch (err) {}
    },

    willTransition(transition) {
      Ember.run.next(function() {
        // before transitioning close all foundation-dialog box
        Ember.$(".reveal-modal").foundation("reveal", "close");

        // remove joyride-popup if not assigned for page
        if($(".joyride-list").length === 0) {
          Ember.$('.joyride-tip-guide').remove();
        }
      });
    }
  }
});
<|endoftext|>


<commit_before>"use strict";

import { SEARCH_KEY_NAME } from "./constants";

// Enable chromereload by uncommenting this line:
import "./lib/livereload";

import createMenu from "./lib/contextMenus";

chrome.runtime.onInstalled.addListener(function (details) {
    createMenu();
});

// TODO: executeScript only if shortcut is "search-beer"
chrome.commands.onCommand.addListener(shortcut => {
    console.log("shortcut: ", shortcut);
    chrome.tabs.executeScript(null, {
        file: "./scripts/getSelection.js",
        runAt: "document_end",
    });
});

// TODO: executeScript only if changes include SEARCH_KEY_NAME
chrome.storage.onChanged.addListener(function(changes, namespace) {
    console.log("changes: ", changes);
    chrome.storage.local.get(SEARCH_KEY_NAME, (items) => {
        console.log(`Value: ${items[SEARCH_KEY_NAME]}`);
        chrome.tabs.executeScript(null, {
            file: "./scripts/search.js",
            runAt: "document_end",
        });
    });
});
<commit_msg>Add shortcut and changes[SEARCH_KEY_NAME] checks<commit_after>"use strict";

import { SEARCH_KEY_NAME, OLUTTAMO_SEARCH_SHORTCUT } from "./constants";

// Enable chromereload by uncommenting this line:
import "./lib/livereload";

import createMenu from "./lib/contextMenus";

chrome.runtime.onInstalled.addListener(function (details) {
    createMenu();
});

chrome.commands.onCommand.addListener(shortcut => {
    if (shortcut === OLUTTAMO_SEARCH_SHORTCUT){
        chrome.browserAction.setBadgeText({text: "<3"});
        chrome.tabs.executeScript(null, {
            file: "./scripts/getSelection.js",
            runAt: "document_end",
        });
    }
});

chrome.storage.onChanged.addListener(function(changes, namespace) {
    if (changes[SEARCH_KEY_NAME]){
        chrome.browserAction.setBadgeText({text: "<3"});
        chrome.tabs.executeScript(null, {
            file: "./scripts/search.js",
            runAt: "document_end",
        });
    }
});
<|endoftext|>